jQuery 源码剖析 - 1.核心功能函数
提到 jQuery,相信很多前端工程师都知道,这个已经火了十来年的框架,为前端开发提供便利性的同时也解决了各种各样的浏览器兼容性问题,一个框架为什么这么火?其中的原因不言而喻,但能否以一种第三人称的方式,站在作者的角度来来思考设计,这估计是很多人不愿意去做的事,那么今天开始,我想以第三人称的方式来剖析源码,自问自答的方式,读懂作者的意图,体会大牛的编程思想,学以致用,提升编码的思想和格局。
一.准备工作
- 首先官网下载源码 jQuery 官网
- 2.选择 jQuery 版本并下载到本地,并在本地给自己新建件
myjQuery-1.0.0.js
(这个文件是用来仿写 jQuery) - 3.创建入口文件并引入这官方 jQuery 和自己创建的
myjQuery-1.0.0.js
文件 - 4.开始剖析源码
二.剖析源码采用的方法技巧
- 1.裁剪
- 2.注释翻译
- 3.一目十行找关键类名
- 4.注释掉不相关干扰代码
- 5.仿写
三.开始剖析
本篇通过三个方向来剖析 jQuery 的精髓.
1.jQuery 无 new 构建实例
为什么 jQuery 对象可以通过 $
符号直接可以访问呢?我们先来看下下面这张 jQuery 共享原型设计图:
通过上图分解,可以很清晰的分析出最佳方案,创建一个 jQuery 对象,返回 jQuery 原型对象的 init 方法,然后共享原型,将 jQuery 挂载到 windows 上起别名 $
,实现通过 $
来访问 jQuery 的构造函数,同理通过 $.fn
来替代 jQuery.prototype
。
// 闭包 立即执行函数
;(function(root){
var jQuery = function() {
return new jQuery.prototype.init();
}
jQuery.fn = jQuery.prototype = {
}
// 共享原型对象
jQuery.fn.init.prototype = jQuery.fn;
root.$ = root.jQuery = jQuery;
})(this);
2.共享原型设计
上面的代码已经很明显的体现出共享原型设计的思想,将 jQuery 原型对象共享,然后通过扩展实例方法属性以及添加静态属性及静态方法的形式充分实现 jQuery 的灵活扩展性。
3.extend 源码解析
在使用 jQuery 源码中我们有时候为了给源码扩展一个新的方法,一般会采用以下几种方式:
// 任意对象扩展
var obj = $.extend({},{name:"james"});
// 本身扩展
$.extend({
work:function(){
}
});
// 实例对象扩展
$.fn.extend({
sex:"男"
});
$().sex; // 男
通过以上代码我们来反推,jQuery 源码中 extend 是如何实现的。
jQuery.fn.extend = jQuery.extend = function () {
var target = arguments[0] || {};
var length = arguments.length;
// 从第1个参数开始解析,因为第0个是我们targer,用来接收解析过的数据的
var i = 1;
var option,name;
if(typeof target !== "object") {
target = {};
}
// 浅拷贝
for (;i<length;i++){
if((option = arguments[i]) != null) {
for(name in option) {
target[name] = option[name];
}
}
}
return target;
}
测试代码:
var ret = {name:'james',list:{age:26,sex:'女'}};
var obj = $.extend({},ret);
console.log(obj);
以下是输出结果:
此时我们成功的扩展了一个为任意对象扩展的 extender 方法。
问题来了,既然是任意对象,那么我们是否可以通过 extender 方法来扩展多个属性呢?测试代码:
var ret = {name:'james',list:{age:26,sex:'女'}};
var res = {list:{sex:'男'}}
var obj = $.extend({},ret,res);
以下是输出结果:
咦???不对吧!!明明我是写了两个对象,怎第一个对象的 list 属性被覆盖掉了??
通过仔细阅读源码,终于得出了结论,extender 方法扩展了浅拷贝和深拷贝,于是重新写了扩展方法,通过传入一个 boolean 参数来决定是否需要深拷贝。
jQuery.extend = jQuery.fn.extend = function () {
// 声明变量
var options,name,copy,src,copyIsArray,clone,
target = arguments[0] || {},
length = arguments.length,
// 从第1个参数开始解析,因为第0个是我们targer,用来接收解析过的数据的
i = 1,
// 是否是深拷贝,外界传过来的第一个参数
deep = false;
// 处理深层复制情况
if(typeof target === "boolean") {
// extender(deep,{},obj1,obj2)
deep = target;
target = arguments[i] || {};
i ++;
}
// 判断 targer不是对象也不是方法
if(typeof target !== "object" && !isFunction(target)) {
target = {};
}
// 如果只传递一个参数,则扩展jQuery本身
if (length === i) {
target = this;
// 此时把i变为0
i--;
}
for ( ; i < length ; i++){
// 仅处理非null /未定义的值
if((options = arguments[i]) != null) {
// 仅处理非null /未定义的值
for(name in options) {
copy = options[name];
src = target[name];
// 防止Object.prototype污染
// 防止死循环循环
if (name === "__proto__" || target == copy) {
continue;
}
//如果我们要合并普通对象或数组,请递归
// 此时的copy必须是数组或者是对象
if ( deep && (jQuery.isPlainObject(copy) ||
(copyIsArray = jQuery.isArray(copy)))) {
// 确保源值的正确类型 源值只能是数组或者对象
if ( copyIsArray ) {
copyIsArray = false;
clone = src && jQuery.isArray(src)?src:[];
} else {
clone = src && jQuery.isPlainObject(src)?src:{};
}
//永远不要移动原始对象,克隆它们
target[name] = jQuery.extend(deep,clone,copy);
//不要引入未定义的值
} else if (copy !== undefined){
// 浅拷贝
target[name] = copy;
}
}
}
}
//返回修改后的对象
return target;
};
扩展了三个属性判断:
// 判断是否是方法
var isFunction = function isFunction( obj ) {
return typeof obj === "function" && typeof obj.nodeType !== "number";
};
// 扩展属性和方法
jQuery.extend({
// 类型检测 是否是对象
isPlainObject: function(obj) {
// "[object Object]" 第二个O一定是大写,坑了我好几个小时.......
return toString.call(obj) === "[object Object]";
},
// 是否是数组
isArray: function(obj) {
return toString.call(obj) === "[object Array]";
}
});
测试代码:
var a = {name:"james",list:{age:"26"}};
var b = {list:{sex:"男"}};
var c = $.extend(true,{},a,b);
终于大功告成,输出了我想要的结果,合并了相同键的对象。
总结
本篇主要分享 jQuery 入口函数共享原型链思想以及核心功能扩展函数 extend 的剖析。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论