如果看懂了jQuery库对于不同环境在安装使用上处理,就也可以明白其他库是如何运行在不同的环境上的
在webpack中安装jquery,打开node_module,可以看到文件的结构。src里是各个模块的源文件,打包编译完成后,最终代码放到dist里面
不同环境导入处理
jquery源码最终是要提供给各个不同的环境去使用的,前端代码使用主要有以下环境
- webpack环境
- 支持Commonjs/Es6Module规范
- 因为打包后放到浏览器运行,所以此环境也是有
window
全局变量的
- 纯node环境下运行
- 只支持Commonjs规范,不支持ES6module规范
- 全局变量无
window
(全局作用域下,this
的输出值是全局变量global
)
- 直接在浏览器的环境下(或者webview等)运行(基于
<script src='xxx'></script>
的形式)- 不支持Commonjs,在新版浏览器下使用
<script type='module'></script>
,可以支持ES6Module,否则不支持 - 全局变量有
window
- 不支持Commonjs,在新版浏览器下使用
下面从闭包和面相对象思想看jquery源码,主要为了自己以后可以自己封装插件组件
环境判断与导入
前置知识:
关于 typeof
的小tip
小tip1
typeof
一个声明但未赋值的变量,返回结果为undefined
,typeof
一个未声明的变量,返回结果也为undefined
小tip2
- 基于CommonJs导出的模块,即可以用CommonJs导入,又可以用ES6Module导入
- 但是基于ES6Module模块规范导出的模块,只能使用ES6Module模块规范导入
最开始的立即执行函数做了什么?
会通过闭包机制,直接
- **
return
** 或者导出factory
函数执行的结果 - 或者直接执行
factory()
,执行函数
到底是 return
结果还是直接执行,都是通过两个方面即,传入的 gobal
+是否支持ES6Module模块规范或Commonjs模块规范来判断的
gobal
表示全局环境下是否有window
变量,有的话,gobal
就是window
,没有的话gobal
表示全局环境下的this
- 是否支持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函数获取不同环境来执行
通过传入的window
, noGlobal
参数,就能用来在不同的环境执行
noGlobal
为undefined
,说明是浏览器环境,是直接SCRIPT导入的,所以要把jQuery挂载到window
上noGlobal
为true
,说明是基于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的包
使用:
<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>
复制代码
以后该系列会从面向对象的角度解读其余的部分源码