一个优秀的工具库并不是并不是找个对象将一些变量、方法封装到里面就可以的,还要对实际应用过程中可能遇到的问题留下解决预案,比如说:将来需要扩展更多功能怎么办?暴露的$字符
和其他库冲突了怎么办?…
注意:
jQuery不是一朝一夕能学习完的,相关的blog也是会持续发布,如果想系统地查看Query源码阅读相关blog,可以选择前往jQuery源码阅读 - 入口文件
开始
(function(global, factory){
if(typeof module === 'object' && typeof module.exports === 'object'){
// 支持CommonJS模块规范的执行这里(Node......)
// 不管它!!
}else{
// 可以理解为是浏览器或者(Web-View)环境
// global === window
// factory === function(window, noGlobal)
factory(global)
}
// 浏览器下一定有window
})(typeof window !=="undefined"?window : this, function(window, noGlobal){
// jQuery 中的方法、属性
})
分析:
自执行函数,借用函数作用域的隔离,防止变量冲突
(function(global, factory){
})(window, function(window, noGlobal){})
这种写法很有意思,利用自执行函数的参数传递的过程,将整个代码分成了两部分。
- 条件判断。是否在浏览器中调用jQuery
- jQuery中的方法的具体实现
如果是我来写的话大概就是团成一坨放在自执行函数里面,jQuery的这种写法…可以称之为优雅地装逼
回调函数中的window
形参名,作为一个参数名与其它变量名一样,一般情况下并不太妥当,但是形参window
在默认情况下就是指向全局window
变量,那这种写法就很好了。
注意:接下来的代码都是存在于回调函数中的
noGlobal变量作用:
只出现了两次一是回调函数作为参数传递,一是回调函数末尾
回调函数虽然有这个参数,但是在调用的时候,根本没有理它。直接就通过factory(global)
调用了。
当然,这个变量在具备CommonJS
条件的时候是用得到的。
所以,noGlobal的主要作用就是:在浏览器环境中运行的时候向外暴露两个接口:window.jQuery
和window.$
注意:这里的jQuery暂时理解为一个对象
jQuery是如何解决接口冲突的:
浏览器中的暴露接口其实就是在浏览器的window
对象下添加全局属性,这个全局属性承载了库的所有内容。
当两个库同时引入的时候,两个库暴露的接口名可能会冲突。为了防止jQuery库内部的变量和window
下的全局变量产生冲突,jQuery使用了自执行函数;为了解决全局下暴露接口与其它库冲突,jQuery实现了noConflict
方法。
noConflict方法
作用:放弃对$
字符的控制权
var
// Map over jQuery in case of overwrite
_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;
};
分析:
如果同时引入两种库:base.js
和jquery.js
,并且是先引入的base.js
。
<script src="base.js"></script>
<script src="jQuery.js"></script>
那么此时如果jQuery
的接口发生冲突,可以调用noConflict
方法。
在引入jQuery.js
的时候,会按照代码顺序执行。当执行到定义_jQuery变量
和_$
变量的时候,还没有经过对noGlobal变量
的判断暴露jQuery的接口。所以此时$
和_jQuery
指向的是base.js
中暴露的接口。当jQuery初始代码完整执行后,接口$
和jQuery
会指向jQuery库中的jQuery变量
,这样会导致base.js
没有接口可以调用。
在使用base.js之前调用noConflict
方法,jQuery会将接口的控制权移交。如果deep没有设置, window.$
重新指向之前保存的base.js
的接口,移交$
的控制权;如果deep设置了,将jQuery
的控制权也放弃。
问:如何为jQuery设置新的接口?
答:noConflict方法会返回jQuery对象,接收之后可以自行设置。
let test = $.noConflict(true);
window.test = test;
jQuery变量究竟是什么?
代码:(line: 145)
var
version = "3.4.1",
jQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context );
},
// jQuery是一个类(类才有prototype)
// jQuery.fn是给原型设置的一个别名
jQuery.fn = jQuery.prototype = {
// 公共的属性和方法
jquery: version,
constructor: jQuery,
// The default length of a jQuery object is 0
length: 0,
}
jQuery其实就是一个函数,接收selector(选择器)和context(很多人不知道这个)变量。返回值是一个类的实例!!其实就是我们经常使用$('div')
这样子,返回一个jQuery实例对象。
注意:非构造函数jQuery.fn.init
也可以通过new
操作符创建实例
jQuery.fn.init
来源:(line: 2936)
init = jQuery.fn.init = function (selector, context, root){
// 对选择器对象的生成
// ......
// 创建一个类数组
return jQuery.makeArray( selector, this );
}
解读一:init存在的理由
有没有觉得init
是个多余的?jQuery接收的参数全部都传递给init
了,直接在jQuery中实现不好么
问题:为什么要通过init来创建实例?直接使用jQuery创建不好么
答案:一个类的实例的创建需要new
操作符,想要像$("div")
这样,不需要new
操作符就能创建实例,就需要一个中间类来进行转化。
jQuery.fn.init
存在作用:作为一个中间类,省略jQuery创建实例时的new
操作符;
解读二:jQuery两种调用方法的方式
jQuery()
是一个函数,对应我们平时常用的$('div')
jQuery
也是一个对象,对应我们平时用到的$.noConflict()
jQuery给我们提供的方法放到了两个位置上
- jQuery原型上:
jQuery.fn = jQuery.prototype = {}
- $().toArray():只有jQuery实例才能调用
- jQuery对象上:
jQuery.extend({ajax: function(url, options){}})
- 直接通过
$
调用$.ajax()
jQuery实例其实是一个类数组(jQuery对象)基于makeArray创建出来
- 直接通过
jQuery是如何扩展的?
extend: 向jQuery中继续扩展方法
$.extend({}); //=> 扩展到JQ对象上:一般是为了完善类库,提供更多的工具方法
$.extend(true, {});
$.fn.extend(); // => 扩展到JQ原型上: 一般是为了写JQ插件,让JQ的实例来调用
$().extend()
jQuery.extend = jQuery.fn.extend = function () {
}
// 默认情况如果自行扩展的函数名与jQuery中原来有的冲突,那么以jQuery的为主
$.extend({
queryURLParams: function(){
}
})
// 如果想要以自己扩展的为主:添加参数:true
$.extend(true, {
...
})