改变数组对象的原型
首先我们看一下如下代码,如何给一个数组对象添加其他方法
var arr = [1,2,3];
arr.__proto__ = {
addClass: function () {
console.log('this is addClass');
},
concat: Array.prototype.concat,
push: Array.prototype.push
};
arr.push(4); // [1, 2, 3, 4]
arr.addClass(); // 'addClass'
我们修改了数组arr的隐式原型__proto__
,使它拥有了更多方法,更多原型继承可以参考另一篇博文原型继承与应用。zepto的原理也是这样:通过$()选择器返回的类数组对象,将对象的隐式原型修改,使zepto对象具有多种方法可以被我们使用
入口
zepto主要结构如下所示,先使用iife(立即执行函数)返回一个值,然后把这个返回值放在window的Zepto属性中,也就是说Zepto成为了一个全局变量。window.$ === false && expression
,这是一个简洁写法,意为第一个表达式为false时返回第二个表达式,也就是执行expression。当$
没被定义时将$
函数赋值给Zepto。所以我们引用$()
的时候,实际上是引用Zepto。
var Zepto = (function(){
}()
window.Zepto = Zepto;
window.$ === undefined && (window.$ = Zepto)
显而易见,IIFE的返回值是一个对象,因为我们经过$()
选择元素之后,可以使用一系列方法,如addClass ,find等等。那么这个对象是什么,请看以下代码
var Zepto = (function(){
var $;
$ = function(selector, context){
return zepto.init(selector, context)
}
return $;
}()
当我们使用$('#aa')
的时候,其实执行的是Zepto('#aa')
,也就是IIFE的返回值$(selector, context)
,也就是zepto.init(selector, context)
init函数
zepto.init = function(selector, context){
var dom;
// 分情况对dom赋值:
// 1. selector 为空
// 2. selector 是字符串,其中又分好几种情况
// 3. selector 是函数
// 4. 其他情况,例如 selector 是数组、对象等
return zepto.Z(dom, selector)
}
主逻辑转移到了zepto.Z函数中
zepto.Z函数
上个版本的Z函数是这样实现的:
zepto.Z = function(dom, selector){
//如果dom数组没有取到就初始化为数组
dom = dom || [];
//源码核心,dom数组的隐式原型被改变了,fn就是这个原型
dom.__proto__ = $.fn
//js中 a||b 是一种赋值方式,当a不为真的时候返回b
dom.selector = selector || ''
return dom;
}
最新版本的Z函数如下:
function Z(dom, selector){
var i, len = dom ? dom.length : 0
//进行一次浅拷贝
for(i = 0; i< len; i++) this[i] = dom[i]
this.length = len
this.selector = selector || ''
}
zepto.Z = function(dom, selector){
return new Z();
}
zepto.Z.prototype = Z.prototype = $.fn
这种方法直接改变了构造函数Z的原型,导致new出来的对象的__proto__
指向$.fn,和上面的方法效果相同,唯一不同的是,最新的Z函数返回的是像
{
0: dom[0],
1: dom[1],
2: dom[2],
length: 3,
selector: ''
}
前者的对象称为类数组对象,而原来是数组
[dom[0], dom[1], dom[2]]