jQuery源码阅读系列——不同环境导入与库名冲突的处理

如果看懂了jQuery库对于不同环境在安装使用上处理,就也可以明白其他库是如何运行在不同的环境上的

在webpack中安装jquery,打开node_module,可以看到文件的结构。src里是各个模块的源文件,打包编译完成后,最终代码放到dist里面

不同环境导入处理

jquery源码最终是要提供给各个不同的环境去使用的,前端代码使用主要有以下环境

  1. webpack环境
    • 支持Commonjs/Es6Module规范
    • 因为打包后放到浏览器运行,所以此环境也是有 window 全局变量的
  2. 纯node环境下运行
    • 只支持Commonjs规范,不支持ES6module规范
    • 全局变量无 window (全局作用域下, this 的输出值是全局变量 global
  3. 直接在浏览器的环境下(或者webview等)运行(基于 <script src='xxx'></script> 的形式)
    • 不支持Commonjs,在新版浏览器下使用 <script type='module'></script> ,可以支持ES6Module,否则不支持
    • 全局变量有 window

下面从闭包和面相对象思想看jquery源码,主要为了自己以后可以自己封装插件组件

环境判断与导入

前置知识:

关于 typeof 的小tip

小tip1

typeof 一个声明但未赋值的变量,返回结果为 undefined typeof 一个未声明的变量,返回结果也为 undefined

小tip2

  • 基于CommonJs导出的模块,即可以用CommonJs导入,又可以用ES6Module导入
  • 但是基于ES6Module模块规范导出的模块,只能使用ES6Module模块规范导入

最开始的立即执行函数做了什么?

会通过闭包机制,直接

  1. ** return ** 或者导出 factory 函数执行的结果
  2. 或者直接执行 factory() ,执行函数

到底是 return 结果还是直接执行,都是通过两个方面即,传入的 gobal +是否支持ES6Module模块规范或Commonjs模块规范来判断的

  1. gobal 表示全局环境下是否有 window 变量,有的话, gobal 就是 window ,没有的话 gobal 表示全局环境下的 this
  2. 是否支持ES6Module模块规范或Commonjs模块规范

通过以上两个方面来区分上面几种不同环境(node,浏览器,webpack),最后不管什么环境,都会返回 factory 函数结果或者把 factory 函数执行

下面是不同环境判断的详细方法,规则在注释当中

/*
 * JS代码运行的环境:
 *   + 浏览器 OR Hybrid -> webview
 *   + NODE
 *   + webpack
 *   + ... 
 */
(function (global, factory) {
    /*形参global:typeof window !== "undefined" ? window : this
    1. 浏览器环境:值为window
    2. webpack环境:值为window
    3. NODE环境:值可能是global
    */
    "use strict";
    if (typeof module === "object" && typeof module.exports === "object") {
        // 支持CommonJS模块的导入导出规范「NODE环境 或者 WEBPACK环境」
        // global.document:存在说明是WEBPACK环境「global->window」
        module.exports = global.document ?
        	// webpack环境,直接执行factory函数,并且noGlobal参数为true(为了区别webpack导入和script导入)
            factory(global, true) :
            // 在其余的没有window的环境下,(有可能是node环境等等),我们导出一个函数,后期执行函数,这样如果我们可以传递一个window进来,也能正常使用,否则报错
            function (w) {
                if (!w.document) {
                    throw new Error("jQuery requires a window with a document");
                }
                return factory(w);
            };
    } else {
        // 浏览器或者webview中基于<script>直接导入的,把jquery挂载到浏览器window上,所以不需要返回
        //  + global:window
        factory(global);
    }
})(typeof window !== "undefined" ? window : this, function factory(window, noGlobal) {
    "use strict";
    var version = "3.5.1",
        jQuery = function (selector, context) {
           //...
        };
	//...
    return jQuery;
});
复制代码

可以加上这一层环境区分的逻辑判断,使其支持Commonjs项目

factory函数获取不同环境来执行

通过传入的windownoGlobal参数,就能用来在不同的环境执行

  1. noGlobalundefined,说明是浏览器环境,是直接SCRIPT导入的,所以要把jQuery挂载到window
  2. noGlobaltrue,说明是基于WEBPACK中的CommonJS/ES6Module规范导入的,所以要返回jQuery对象

详细代码解释如下:


(function (global, factory) {
   ...
})(typeof window !== "undefined" ? window : this, function factory(window, noGlobal) {
    // 直接SCRIPT导入的
    //   + window:window  noGlobal:undefined
    // 基于WEBPACK中的CommonJS/ES6Module规范导入的JQ
    //   + window:window  noGlobal:true
    "use strict";
    var version = "3.5.1",
        jQuery = function (selector, context) {
            return new jQuery.fn.init(selector, context);
        };

	...
    
    //为了支持amd模块
    if (typeof define === "function" && define.amd) {
		define("jquery", [], function () {
			return jQuery;
		});
	}
    
    // 导入JQ「执行代码」&& 暴露给全局之前
    var _$ = window.$;
    jQuery.noConflict = function () {
        if (window.$ === jQuery) {
            window.$ = _$;
        }
        return jQuery;
    };

    // 如果是浏览器环境,就直接把jquery挂载在window上,(浏览器直接导入)
    //   这样, $() 或者 jQuery() -> 都是把内部的jQuery方法执行
    if (typeof noGlobal === "undefined") {
        window.jQuery = window.$ = jQuery;
    }

    // 这里factory返回后,上面的环境判断代码就是module.exports=jQuery;(基于WEBPACK处理)
    //使用:import $ from 'jquery'
    return jQuery;
});
复制代码

以上就是使用闭包来保证私有化,来做环境区分,来供外面使用,剩下的核心逻辑和方法封装,就就是跟面相对象有关的

解决多库共存的问题

commonjs方法导入的jQuery不会出现这种问题,因为没有将 $ 挂载到 window

如果 $ 使用权冲突了(例如Zepto也有 $

Zepto库的处理方法是:如果 $ 被占用,就不再使用

jquery在冲突的时候转移 $ 使用权。先把原来别的库已经存在的 $ 暂时存起来,暂时存私有变量 _$ 上面,如果执行了 jQuery.noConflict 方法,会把之前别的库的使用权归还,之前 $ 谁在用就还回去。否则的话,就会直接覆盖别的库。自己可以定一个自定义的新的名字

// 导入JQ「执行代码」&& 暴露给全局之前
    var _$ = window.$;
    jQuery.noConflict = function () {
        if (window.$ === jQuery) {
            window.$ = _$;
        }
        return jQuery;
    };
复制代码

// Map over jQuery in case of overwrite
  var  _jQuery = window.jQuery,

// Map over the $ in case of overwrite
     _$ = window.$;

jQuery.noConflict = function (deep) {
if (window.$ === jQuery) {
        window.$ = _$;
}

if (deep && window.jQuery === jQuery) {
        window.jQuery = _jQuery;
}

return jQuery;
};
复制代码

深度转移,将 jQuery 这个名字也转移

封装一个自己的库

我们使用上面使用到的的方法,包括根据不同环境暴露API,多库共存的原理。来写一个jsonp的库

(function () {
    /* 核心方法 */
    function jsonp(config) {
        config == null ? config = {} : null;
        typeof config !== "object" ? config = {} : null;
        let {
            url,
            params = {},
            jsonpName = 'callback',
            success = Function.prototype
        } = config;

        // 自己创建一个全局的函数
        let f_name =   `jsonp${+new Date()}`  ;
        window[f_name] = function (result) {
            typeof success === "function" ? success(result) : null;
            delete window[f_name];
            document.body.removeChild(script);
        };

        // 处理URL
        params = Qs.stringify(params);
        if (params) url +=   `${url.includes('?')?'&':'?'}${params}`  ;
        url +=   `${url.includes('?')?'&':'?'}${jsonpName}=${f_name}`  ;

        // 发送请求
        let script = document.createElement('script');
        script.src = url;
        document.body.appendChild(script);
    }

    /* 多库共存 */
    let _$ = window.$,
        _jsonp = window.jsonp;
    jsonp.noConflict = function noConflict(deep) {
        if (window.$ === jsonp) {
            window.$ = _$;
        }
        if (deep && window.jsonp === jsonp) {
            window.jsonp = _jsonp;
        }
        return jsonp;
    };

    /* 暴露API */
    if (typeof window !== "undefined") {
        window.jsonp = window.$ = jsonp;
    }
    if (typeof module === "object" && typeof module.exports === "object") {
        module.exports = jsonp;
    }
})();

复制代码

注意以上会依托一个qs的包
图片[1]-jQuery源码阅读系列——不同环境导入与库名冲突的处理-一一网
使用:

<script src="node_modules/qs/dist/qs.js"></script>
<script src="jsonp.js"></script>
<script>
    jsonp({
        url: 'https://www.baidu.com/sugrec',
        params: {
            prod: 'pc',
            wd: '测试jsonp'
        },
        jsonpName: 'cb',
        success: result => {
            console.log(result);
        }
    });
</script>
复制代码

以后该系列会从面向对象的角度解读其余的部分源码

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享