博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
zepto源码学习-02 工具方法-详细解读
阅读量:4936 次
发布时间:2019-06-11

本文共 8911 字,大约阅读时间需要 29 分钟。

上一篇:

先解决上次留下的疑问,开始看到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.z[0]就是$.fn

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

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

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

 

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

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

 

我决定来模拟一把,结构和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]明显是不同的,一个是数组,一个就是普通对象。

进入本期主题,工具方法

先看官网上的API

 $()这个不是工具方法,$.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。

 

$.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)。请看下图

 

 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的值,如下

 

 如果我们自己去判断一个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设计成这样了!!

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

本文地址:

 

转载于:https://www.cnblogs.com/Bond/p/4195800.html

你可能感兴趣的文章
数组转集合踩坑
查看>>
node.js的异步I/O、事件驱动、单线程
查看>>
vue cli3 子目录问题
查看>>
github.com访问慢解决
查看>>
微服务架构最强详解
查看>>
转:哈夫曼树详解
查看>>
.Net Core Identity外面使用Cookie中间件
查看>>
【坐在马桶上看算法】算法1:最快最简单的排序——桶排序
查看>>
C#中泛型之Dictionary
查看>>
强连通分量
查看>>
使用Code First模式开发如何更新数据库(转载)
查看>>
sqoop导出工具
查看>>
Codeforces Round #376 (Div. 2)
查看>>
Codeforces 607D Power Tree 线段树 (看题解)
查看>>
写在人生的路上——2016年上半年总结
查看>>
员工选票系统-java
查看>>
C语言、C语言的起源以及类似C语言的编程语言的历史简直不要太漫长,我简单总结列表如下:...
查看>>
sp1.3-1.4 Neural Networks and Deep Learning
查看>>
JavaScript易错知识点整理
查看>>
Biological Clocks
查看>>