jQuery源码分析
1. 所有的插件必须包含在一个闭包中
- 为了避免全局变量气污染
- 防止内部变量被外部调用
//插件主要运用了闭包的保护和保存机制
(function( global, factory ) {
// global -> 在浏览器环境下运行(或者webview等)它的值是window,在NODE环境下运行值可能是global
"use strict";//开启严格模式,只管控当前函数使用严格模式,函数之外不是严格模式。
// ...
//利用js暂时性死区机制“基于typeof检测一个未被声明的变量,结果是undefined”:如果是在浏览器或者webview环境下运行JS,则A=window,如果是在Node环境下执行,则A=global或者当前模块。
})(typeof window !== "undefined" ? window : this,function factory( window, noGlobal){
//...
})
JS运行环境
- 浏览器 this => window
- Hybrid -> webview
- NODE : 不存在window
typeof window = "undefined"
- …
globa:利用typeof检测的暂时性死区机制判断当前环境是否支持window,支持global等于window,不支持则global等于this;
factory : 回调函数,把函数作为实参传递给另一个函数。
2. commonJS的模块导入导出规范
- WEBPACK 环境
- 支持
CommonJS
/ES6Module
规范 - 打包后的内容是放在浏览器中运行的,也是有
window
的
- 支持
- 纯NODE 环境下运行
- 不支持
ES6Module
规范,只支持commonJS
规范 - 里面没有
window
- 不支持
- 直接在浏览器环境下运行:
<script src='xxx.js'></script>
- 不支持
CommonJS
/ES6Module
规范 - 但是有
window
script
的type
属性值为module
时,新版本浏览器可以支持ES6Module规范
- 不支持
commonJS
导出导入规范:- A :
module.exports = { fn(){}, ... }
//模块导出 - B :
let B = require("./A") ; B.fn();
// 模块导入
- A :
ES6Module
导入导出法规范 ,- A:
export default {fn(){}};
- B:
import A from ". / A"; A.fn() ;
- A:
(function( global, factory ) {
"use strict";
if ( typeof module === "object" && typeof module.exports === "object" ) {
// 支持CommonJS模块的导入导出规范「NODE OR WEBPACK」
// global.document 存在说明是WEBPACK环境「global->window」
module.exports = global.document ?
factory( global, true ): //global:window, noGlobal:true
// 在其余的没有window的环境下,导出一个函数,后期执行函数,如果可以传递一个window进来,也能正常使用,否则报错。
function( w ) {
if ( !w.document ) throw new Error( "jQuery requires a window with a document" );
return factory( w ); //global:window, noGlobal:undefined
};
} else {
//浏览器或者webview中基于<script>直接导入的
// + global:window noGlobal:undefined
factory(global);
};
})(typeof window !== "undefined" ? window : this,function factory( window, noGlobal){
// 直接SCRIPT导入的
// window:window noGlobal:undefined
// 基于WEBPACK中的CommonJS/ES6Module规范导入的JQ
// window:window noGlobal:true
//...
})
学习到的思想
关于自己写的插件/组件,我们想让自己的代码,既要支持浏览器script直接导入,也要支持commonJS / ES6Module 规范导入。
(function(){
function jsonp(){};
if(typeof window !== "undefined"){
window.jsonp = jsonp;
};
if(typeof module ==="object" && typeof module.exports === "object"){
module.exports = jsonp;
//导入方式:import jsonp from './jsonp.js';
}
})()
3. 让插件支持浏览器只导入 、基于webpack处理和AMD模块化处理
//factory 是jQuery闭包中传入函数的实参函数
function factory( window, noGlobal){
// 直接SCRIPT导入的
// window:window noGlobal:undefined
// 基于WEBPACK中的CommonJS/ES6Module规范导入的JQ
// window:window noGlobal:true
"use strict";
var version = "3.6.0",
jQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context );
};
//支持AMD模块化思想,不常用
if ( typeof define === "function" && define.amd ) {
define( "jquery", [], function() {
return jQuery;
} );
};
//浏览器直接导入处理
// $() / jQuery() 都是把内部的jQuery方法执行
if ( typeof noGlobal === "undefined" ) {
window.jQuery = window.$ = jQuery;
};
// 基于WEBPACK处理
// module.export = jQuery;
// 导入方式:import jQuery from 'jQuery';
return jQuery;
};
4. 解决多库共存的权限冲突
//factory 是jQuery闭包中传入函数的实参函数
function factory( window, noGlobal){
// 直接SCRIPT导入的 : window:window noGlobal:undefined
// 基于WEBPACK中的CommonJS/ES6Module规范导入的JQ : window:window noGlobal:true
"use strict";
var version = "3.6.0",jQuery = function( selector, context ) {return new jQuery.fn.init( selector, context ); };
//多库共存解决权限冲突,导入JQ(代码执行前)解决权限冲突
var _jQuery = window.jQuery, /*jQuery冲突*/ _$ = window.$; /*$冲突*/
jQuery.noConflict = function( deep ) {
if ( window.$ === jQuery) { window.$ = _$; };
if ( deep && window.jQuery === jQuery) { window.jQuery = _jQuery;}; return jQuery;
};
//支持AMD模块化思想,不常用
if ( typeof define === "function" && define.amd ) { define( "jquery", [], function() { return jQuery; } );};
//浏览器直接导入处理 => $() / jQuery() 都是把内部的jQuery方法执行
if ( typeof noGlobal === "undefined" ) {window.jQuery = window.$ = jQuery;};
// 基于WEBPACK处理 module.export = jQuery; 导入方式:import jQuery from 'jQuery';
return jQuery;
};
自定义一个jsonp插件
(function(){
//核心方法
function jsonp(config){
config == null ?config = {} :null;
typeof config !== "object" ? config = {} : null;
let {
url,
params = {},
configName = "callback",
success = Function.prototype
} = config;
// 自己创建一个全局函数
let f_name = `jsonp${+new Date()}`;
window[f_name] = function (result){
typeof succes === "function"? success(result) :null;
delete window[f_name];
document.body.removeChild(script);
};
// 处理url
params = QS.stringify(params); // QS需要安装 npm install qs 引入
if(params) url += `${url.includes("?")?"&":"?"}${params}`;
url += `${url.includes("?")?"&":"?"}${jsonpNam} = ${f_name}`;
// 发送请求
let script = document.createElement("script");
script.src = url;
document.body.appendChild(script);
};
/*多库共存*/
var _jsonp = window.jsonp, /*jsonp冲突*/ _$ = window.$; /*$冲突*/
jQuery.noConflict = function( deep ) {
if ( window.$ === jsonp) { window.$ = _$; };
if ( deep && window.jsonp=== jsonp) { window.jsonp= _jsonp;}; return jsonp;
};
/*暴露API*/
//浏览器直接导入处理 => $() / jsonp() 都是把内部的jsonp方法执行
if ( typeof noGlobal === "undefined" ) {window.jsonp= window.$ = jsonp;};
// 基于WEBPACK处理 module.export = jQuery; 导入方式:import jsonp from 'jsonp';
return jsonp;
})();
// 调用方法jsonp 或者$ 都可以
jsonp({
url:'https://www.baidu.com/sugrec',
params:{
prod:"pc",
wd:"珠峰"
},
jsonpName:'cb',
success:result =>{
console.log(result);
},
});