zepto源码学习-02 工具方法-详细解读

时间:2023-03-08 19:07:30

上一篇:地址

先解决上次留下的疑问,开始看到zepto.z[0]这个东西的时候,我很是不爽,看着它都不顺眼,怎么一个zepto的实例对象var test1=$('#items');  test__proto__ 指向的是zepto.z[0];之前看到过如下代码

    zepto.Z.prototype = Z.prototype = $.fn
// Export internal API functions in the `$.zepto` namespace
zepto.uniq = uniq
zepto.deserializeValue = deserializeValue
$.zepto = zepto
//返回内部的$对象
return $

根据以上代码可以得出出test1__proto__===$.fn 应该是true

zepto源码学习-02 工具方法-详细解读

既是zepto.z[0]就是$.fn

最终找到如下代码。代码上有英文注释, 让对象有看起来是数组,内部还给其加上了很多数组的行为。

zepto源码学习-02 工具方法-详细解读

$.fn ={} 但是constructor指向了zepto.Z ,还加了一个length: 0,

我尝试去掉length=0这个属性,得到如下结果

zepto源码学习-02 工具方法-详细解读

此时不再是zepto.z[0],而是zepto.z

如果再把constructor去掉,此时test__proto__指向Object; 其实最开始的时候$.fn={},本来就是赋值一个Object对象,只是手动改变了内部的constructor加上了length,

zepto源码学习-02 工具方法-详细解读

我决定来模拟一把,结构和zepto大体相同,声明了相应的c、z、fn。最后创建一个实例对象d,d的__proto__也指向了z[0];

这里折腾了一下,我发现如果fn这个Object对象,缺少forEach: emptyArray.forEach,reduce: emptyArray.reduce,push: emptyArray.push,sort: emptyArray.sort,splice: emptyArray.splice,indexOf: emptyArray.indexOf,其中的一些方法就不会显示z[0],直接会是z,感觉怪怪的。具体没去研究是哪几个方法。z和z[0]明显是不同的,一个是数组,一个就是普通对象。

zepto源码学习-02 工具方法-详细解读

进入本期主题,工具方法

先看官网上的API

zepto源码学习-02 工具方法-详细解读

$()这个不是工具方法,$.camelCase

$.camelCase('hello-there') //=> "helloThere"
$.camelCase('helloThere') //=> "helloThere"

内部有很多地方均要调用这个方法,实现很简单基本,正则匹配替换 /-+(.)?/g, 前面可以有多个”-“后面(.)匹配一个字符,g全局匹配。

camelize = function(str) {
return str.replace(/-+(.)?/g, function(match, chr) {
return chr ? chr.toUpperCase() : ''
})
}

$.contains

检查父节点是否包含给定的dom节点。

    //父元素是否有某个子元素,原生有的支持contains就调用element.contains,不支持就循环判断子节点的父节点
$.contains = document.documentElement.contains ?
function(parent, node) {
return parent !== node && parent.contains(node)
} :
function(parent, node) {
while (node && (node = node.parentNode))
if (node === parent) return true
return false
}

$.each

$.each方法用于遍历数组和对象,然后返回原始对象。它接受两个参数,分别是数据集合和回调函数,。回调函数返回 false 时停止遍历。 一般我们在不断的使用for的时候我们就可以考虑是否需要使用each来干掉for。

zepto源码学习-02 工具方法-详细解读

    $.each = function(elements, callback) {
var i, key
//判断是否是数组
if (likeArray(elements)) {
//为什么不 缓存length,这样每次都得去取 elements.length
for (i = 0; i < elements.length; i++)
if (callback.call(elements[i], i, elements[i]) === false) return elements
} else {
for (key in elements)
if (callback.call(elements[key], key, elements[key]) === false) return elements
}
return elements
}
  function likeArray(obj) {
return typeof obj.length == 'number'
}
 

$.extend

$.extend(target, [source, [source2, ...]]) ⇒ target
$.extend(true, target, [source, ...]) ⇒ target v1.0+
通过源对象扩展目标对象的属性,源对象属性将覆盖目标对象属性。

默认情况下为,复制为浅拷贝(浅复制)。如果第一个参数为true表示深度拷贝(深度复制)。有的人没搞清楚深拷贝和浅拷贝的意思,结果掉坑里了。

    function extend(target, source, deep) {
for (key in source)
//深拷贝 并且source[key]是Object或者数组;====source[key] source[key] 可以提前缓存,每次写着不累啊。zepto的代码始终看着不够爽,个人习惯吧
if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
//如果source[key]是纯对象 但是target[key]不是纯对象
if (isPlainObject(source[key]) && !isPlainObject(target[key]))
target[key] = {}
//source[key]是数组 但是target[key]不是数组
if (isArray(source[key]) && !isArray(target[key]))
target[key] = []
//递归
extend(target[key], source[key], deep)
//不是深拷贝直接赋值给target
} else if (source[key] !== undefined) target[key] = source[key]
} // Copy all but undefined properties from one or more
// objects to the `target` object.
$.extend = function(target) {
var deep,
//转为数组
args = slice.call(arguments, 1)
//第一个参数为boolean类型
if (typeof target == 'boolean') {
deep = target
//取到args第一个对象
target = args.shift()
}
args.forEach(function(arg) {
extend(target, arg, deep)
})
return target
}

里面用到了isPlainObject其实$.isPlainObject 就是isPlainObject;改方法测试对象是否是“纯粹”的对象,这个对象是通过 对象常量("{}") 或者 new Object 创建的,如果是,则返回true。

$.isPlainObject({})         // => true
$.isPlainObject(new Object) // => true
$.isPlainObject(new Date) // => false
$.isPlainObject(window) // => false

isPlainObject,先判断是不是Object对象,然后在判断不是window对象,在判断原型是Object.prototype。 Object.getPrototypeOf(obj)用来获取对象的原型 。

    function isWindow(obj) {
return obj != null && obj == obj.window
} function isDocument(obj) {
return obj != null && obj.nodeType == obj.DOCUMENT_NODE
} function isObject(obj) {
return type(obj) == "object"
} function isPlainObject(obj) {
return isObject(obj) && !isWindow(obj) && Object.getPrototypeOf(obj) == Object.prototype
}

观察发现 isObject 这个函数内部是return type(obj)=='object',我们还得研究type的实现,type函数代码如下

function type(obj) {
return obj == null ? String(obj) :
class2type[toString.call(obj)] || "object"
}

分析type函数里面的逻辑:如果obj===null返回String(obj)===》'null';如果不等于null返回:class2type[toString.call(obj)] || "object"

先调用toString.call(obj)===>toString是什么东西 ===>是这个 class2type.toString

toString.call(obj)就行相当于({}).toString.call(obj)。请看下图

zepto源码学习-02 工具方法-详细解读

type函数其实就是({}).toString.call(obj) 得到obj的真实类型,然后再到class2type对象中去找对应的值,如果没找到则返回 "object"

那么class2type是个什么东西,有必要看一下,经过寻找,发现以下代码

// Populate the class2type map
$.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
class2type["[object " + name + "]"] = name.toLowerCase()
})

比如({}).toString.call({})==>[object Object]

那么在class2type中应该是这样的 {"[object Object]": "object"},最终我们查看zepto内部class2type的值,如下

zepto源码学习-02 工具方法-详细解读

如果我们自己去判断一个Object的真实类型会这样写:if(({}).toString.call(obj)==='[object Date]')//判断时间。 这样写明显麻烦,所以zepto内部给我们做了封装,我们只需要这样写  if($.type(obj)==='date') //这样是不是很简洁

我们平时直接试用$.type 感觉是很有好的,主要是zepto内部帮我们做了一些封装,JQuery这里的实现是一样滴。

$.trim

这个基本上没说的,司徒正美那本《Javascript框架设计》讲了十多种字符串trim的实现,神一般的实现,有兴趣可以去看看;传送门

$.grep

获取一个新数组,新数组只包含回调函数中返回 ture 的数组项

$.grep = function(elements, callback) {
return filter.call(elements, callback)
}

内部其实就是调用了filter,filter又和not扯上了关系,为什么会这样设计,其实都是都为了代码重用。

filter: function(selector) {
if (isFunction(selector)) return this.not(this.not(selector))
return $(filter.call(this, function(element) {
return zepto.matches(element, selector)
}))
},
not: function(selector) {
var nodes = []
if (isFunction(selector) && selector.call !== undefined)
this.each(function(idx) {
if (!selector.call(this, idx)) nodes.push(this)
})
else {
var excludes = typeof selector == 'string' ? this.filter(selector) :
(likeArray(selector) && isFunction(selector.item)) ? slice.call(selector) : $(selector)
this.forEach(function(el) {
if (excludes.indexOf(el) < 0) nodes.push(el)
})
}
return $(nodes)
},

最核心的也就这句

this.each(function(idx) {
  if (!selector.call(this, idx)) nodes.push(this)
})

nodes是一个新的数组,把当前的值传个外部指定的回到函数,如果函数返回true就加到nodes中去,最后返回nodes这个新数组

$.inArray $.isArray $.isFunction $.isWindow $.parseJSON

以上几个太简单了基本上没啥说的

最后看一个map

$.map

通过遍历集合中的元素,返回通过迭代函数的全部结果,null 和 undefined 将被过滤掉。代码实现基本都是很常规的没有什么技巧,也是先判断传入的对象是Object还是Array

    $.map = function(elements, callback) {
var value, values = [],
i, key
if (likeArray(elements))
for (i = 0; i < elements.length; i++) {
value = callback(elements[i], i)
if (value != null) values.push(value)
} else
for (key in elements) {
value = callback(elements[key], key)
if (value != null) values.push(value)
}
return flatten(values)
}

最后还调用了flatten这个函数。 如果数组长度大于0 则调用$.fn.concat.apply([], array) ,那么在$.fn.concat中的this指向[], 函数参数就是array

concat的实现:遍历arguments,其实就是外面传如的array,挨个判断是不是Zepto对象,如果是value.toArray()就调用它的toArray方法。 最后是concat.apply(zepto.isZ(this) ? this.toArray() : this, args),这里同理,判断当前的this是不是zepto对象,如果是调用toArray方法,不是就传入this。最终把数组链接起来,返回给外部。

function flatten(array) {
return array.length > 0 ? $.fn.concat.apply([], array) : array
} concat: function() {
var i, value, args = []
for (i = 0; i < arguments.length; i++) {
value = arguments[i]
args[i] = zepto.isZ(value) ? value.toArray() : value
}
return concat.apply(zepto.isZ(this) ? this.toArray() : this, args)
}
emptyArray = [],
concat = emptyArray.concat,
this.toArray()的实现如下:toArray调用get方法,之前说过 this其实就是个维数组,这里转换成数组,里面的元素都是原生的dom对象。当然这里可以传入下标,可以取得对应的原生dom对象。
get: function(idx) {
return idx === undefined ? slice.call(this) : this[idx >= 0 ? idx : idx + this.length]
},
toArray: function() {
return this.get()
},

看来一个map方法还是挺复杂的,里面各种处理逻辑,牵扯一堆的东西。为什么会这样 Why?? 等以后分析后面的代码就知道了,很多地方要用到map,为了重用,所以这个map设计成这样了!!

看来这个工具方法还是可以扯很多,其实还省略了不少呢!今天到此为止!

本文地址:http://www.cnblogs.com/Bond/articles/4195800.html