Zepto源代码分析之二~三个API

时间:2022-06-13 13:26:29
因为时间关系:本次仅仅对这三个API($.camelCase、$.contains、$.each)方法进行分析

第一个方法变量转驼峰:$.camelCase('hello-world-welcome');
源代码:
var camelize;
/**
 * 字符串替换
 * 使用replace第二个參数带回调
 */
camelize = function(str) {
     return str.replace(/-+(.)?/g,
          function(match, chr) {
               return chr ? chr.toUpperCase() : '';
          }
     );

};
/**
 * 将一组字符串变成驼峰命名法
 */
$.camelCase = camelize;

第二个方法检查父节点是否包括给定的dom节点,假设两者是同样节点,返回false:$.contains(parent, node)
源代码:
/**

 * $.contains(parent, node);

 * 检查父节点是否包括给定的dom节点,假设两者是同样节点,返回false

 * return {boolean} true/false

 */
$.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;  
     };

第三个方法遍历数组或以key-value值对方式遍历对象。回调函数返回false时停止遍历。
$.each(collection, function(index, item) { ... }) => collection
遍历数组元素或以key-value值对方式遍历对象。

回调函数返回false时停止遍历。

$.each(['a', 'b', 'c'], function(index, item) {
     console.log('item %d is: %s', index, item);
});
var hash = { name: 'zepto.js', size: 'micro' };
$.each(hash, function(key, value) {
     console.log('%s: %s', key, value);
});

源代码:
/**
 * Zepto对象迭代器
 * @param {object|array} elements 数据对象
 * @param {function} callback 回调函数
 * return {object|array} elements 数据对象
 */
$.each = function(elements, callback) {
     var i;
     var key;
     // 数组检測
     if (likeArray(elements)) {
          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;
};

三个方法最后页面:demo.html
<!DOCTYPE html>

<html>

    <head>

        <meta http-equiv="X-UA-Compatible" content="IE=Edge" />

        <meta charset="utf-8" />

        <title>Zepto源代码分析</title>

        <link rel="stylesheet" href="demo.css" type="text/css" />

    </head>

    <body>

        <div id="test">

            測试zepto源代码

            <span class="aa">22</span>

            <span class="bb">2332</span>

        </div>

        <div class="wrap">content</div>

        <script src="zepto-dev.js"></script>

        <script>

            console.log($('div'));

            console.log($('.aa'));

            console.log($('<div>这是測试内容</div>'));

            console.log($("<span />", { text: "測试測试111", id: "ceshi_111", css: { color: 'red' } }));

            Zepto(function($) {

                console.log('Ready to Zepto!');

            });

        </script>

        <script>

            console.log($.camelCase('hello-there-body'));

            console.log($.contains($('#test')[0], $('.aa')[0]));

            $.each(['a', 'b', 'c'], function(index, item) {

                console.log('item %d is: %s', index, item);

            });

            var hash = { name: 'zepto-dev.js', size: 'micro' };

            $.each(hash, function(key, value) {

                console.log('%s: %s', key, value);

            });

        </script>

    </body>

</html>

zepto-dev.js源代码:
var Zepto = (function() {

    /**

     * 变量初始化

     */

    var $;

    var zepto = {};

    var fragmentRE = /^\s*<(\w+|!)[^>]*>/;

    var singleTagRE = /^<(\w+)\s*\/?>(?:<\/\1>|)$/;

    var tagExpanderRE = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig;

    var undefined;

    var emptyArray = [];

    var slice = emptyArray.slice;

    var cssNumber = {

        'column-count': 1,

        'columns': 1,

        'font-weight': 1,

        'line-height': 1,

        'opacity': 1,

        'z-index': 1,

        'zoom': 1

    };

    // 特殊属性集合

    var methodAttributes = [ 'val', 'css', 'html', 'text', 'data', 'width', 'height', 'offset' ];

    var table = document.createElement('table');

    var tableRow = document.createElement('tr');

    var containers = {

        'tr': document.createElement('tbody'),

        'tbody': table,

        'thead': table,

        'tfoot': table,

        'td': tableRow,

        'th': tableRow,

        '*': document.createElement('div')

    };

    var readyRE = /complete|loaded|interactive/;

    var simpleSelectorRE = /^[\w-]*$/;

    var class2type = {};

    var toString = class2type.toString;

    var camelize;

    var isArray = Array.isArray || function(object) {

        return object instanceof Array;

    };





    /**

     * 检測函数

     */

    function type(obj) {

        return obj == null ? String(obj) :

          class2type[toString.call(obj)] || "object";

    }

    function isFunction(value) {

        return type(value) == "function";

    }

    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;

    }

    function likeArray(obj) {

        return typeof obj.length == 'number';

    }

    /**

     * 字符串替换

     * 使用replace第二个參数带回调

     */

    function camelize(str) {

        return str.replace(/-+(.)?/g,

            function(match, chr) {

                console.log(chr);

                return chr ? chr.toUpperCase() : '';

            }

        );

    }

    function dasherize(str) {

        return str.replace(/::/g, '/')

            .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')

            .replace(/([a-z\d])([A-Z])/g, '$1_$2')

            .replace(/_/g, '-')

            .toLowerCase()

    }

    function maybeAddPx(name, value) {

        return (typeof value == "number" && !cssNumber[dasherize(name)]) ?

value + "px" : value;

    }

    /**

     * `$.zepto.fragment`须要一个html字符串和一个可选标记名来生成dom

     * 产生的dom返回一个数组形式

     * 该功能能够被插件覆盖

     * 没有覆盖全部浏览器

     */

    zepto.fragment = function(html, name, properties) {

        var dom;

        var nodes;

        var container;

        // 标签特殊化处理

        if (singleTagRE.test(html)) {

            dom = $(document.createElement(RegExp.$1));

        }

        if (!dom) {

            if (html.replace) {

                html = html.replace(tagExpanderRE, "<$1></$2>");

            }

            if (name === undefined) {

                name = fragmentRE.test(html) && RegExp.$1;

            }

            if (!(name in containers)) {

                name = '*';

            }

            container = containers[name];

            container.innerHTML = '' + html;

            dom = $.each(slice.call(container.childNodes), function() {

                container.removeChild(this);

            });

        }

        if (isPlainObject(properties)) {

            nodes = $(dom);

            $.each(properties, function(key, value) {

                if (methodAttributes.indexOf(key) > -1) {

                    nodes[key](value);

                }

                else {

                    nodes.attr(key, value);

                }

            });

        }

        return dom;

    };

    /**

     * `$.zepto.Z`将给定`dom`节点数组的原型赋上`$.fn`提供的全部Zepto函数

     * 请注意。`__proto__`不支持IE浏览器

     */

    zepto.Z = function(dom, selector) {

        dom = dom || [];

        dom.__proto__ = $.fn;

        dom.selector = selector || '';

        return dom;

    };

    /**

     * `$.zepto.isZ`检查给定的对象是一个Zepto的集合,可被插件覆盖

     */

    zepto.isZ = function(object) {

        return object instanceof zepto.Z;

    };

    /**

     * `$.zepto.init`是Zepto借鉴jQuery的`$.fn.init`方法

     * 採用css选择器和一个可选的上下文(处理各种特殊情况)

     * 该方法可被插件覆盖

     */

    zepto.init = function(selector, context) {

        // 假设没有给出,返回一个空的Zepto集合

        if (!selector) {

            return zepto.Z();

        }

        // 检測字符串类型

        else if (typeof selector == 'string') {

            selector = selector.trim();

            /**

             * 假设是一个HTML片段,创建节点注意,在chrome21和FF15版本号。

             * DOM错误12不是以<被抛出

             */

            if (selector[0] == '<' && fragmentRE.test(selector)) {

                dom = zepto.fragment(selector, RegExp.$1, context);

                selector = null;

            // 假设存在一个上下文环境,建立收集。并从中选择节点

            } else if (context !== undefined) {

                return $(context).find(selector);

            // 假设是一个css选择器。用它来选择节点

            } else {

                dom = zepto.qsa(document, selector);

            }

        // 假设一个函数存在,在domready就绪后触发

        } else if (isFunction(selector)) {

            return $(document).ready(selector);

        }

        // 假设zepto已经收集给出,直接返回

        else if (zepto.isZ(selector)) {

            return selector;

        } else {

            // 假设节点已经为数组,进行聚合

            if (isArray(selector)) {

                dom = compact(selector);

            }

            // 包装DOM节点

            else if (isObject(selector)) {

                dom = [selector];

                selector = null;

            }

            // 假设是一个HTML片段,对该片段创建节点

            else if (fragmentRE.test(selector)) {

                dom = zepto.fragment(selector.trim(), RegExp.$1, context);

                selector = null;

            }

            // 假设存在上下文环境,先建立收集。并从中选择节点

            else if (context !== undefined) {

                return $(context).find(selector);

            }

            // 假设是一个css选择器。用它来选择节点

            else {

                dom = zepto.qsa(document, selector);

            }

        }

        // 对发现的节点创建一个新的Zepto集合

        return zepto.Z(dom, selector);

    };

    // `$`作为Zepto的元对象。当调用`$`该函数将转由`$.zepto.init`处理

    $ = function(selector, context) {

        return zepto.init(selector, context);

    };

    /**

     * `$.zepto.qsa`是Zepto的css选择器,使用document.querySelectorAll及特殊情况

     * 可被插件覆盖

     */

    zepto.qsa = function(element, selector) {

        var found;

        var maybeID = (selector[0] == '#');

        var maybeClass = !maybeID && selector[0] == '.';

        // 确认下标从1開始后的字符串

        var nameOnly = maybeID || maybeClass ?

selector.slice(1) : selector;

        var isSimple = simpleSelectorRE.test(nameOnly);

        return (isDocument(element) && isSimple && maybeID) ?

            ((found = element.getElementById(nameOnly)) ? [found] : []) :

            slice.call((isSimple && !maybeID) ?

                maybeClass ?

element.getElementsByClassName(nameOnly) : // class名称

                element.getElementsByTagName(selector) : // tag名称

                element.querySelectorAll(selector) // 查询全部匹配到的

            );

    };

    /**

     * $.contains(parent, node); 演示样例$.contains($('#test')[0], $('.aa')[0])

     * 检查父节点是否包括给定的dom节点,假设两者是同样节点,返回false

     * return {boolean} true/false

     */

    $.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;

        };

    function setAttribute(node, name, value) {

        value == null ? node.removeAttribute(name) : node.setAttribute(name, value);

    }

    // 函数參数

    function funcArg(context, arg, idx, payload) {

        return isFunction(arg) ? arg.call(context, idx, payload) : arg;

    }

    $.type = type;

    $.isFunction = isFunction;

    $.isWindow = isWindow;

    $.isArray = isArray;

    $.isPlainObject = isPlainObject;

    $.camelCase = camelize;

    /**

     * Zepto对象迭代器

     * @param {object|array} elements 数据对象

     * @param {function} callback 回调函数

     * return {object|array} elements 数据对象

     */

    $.each = function(elements, callback) {

        var i;

        var key;

        // 数组检測

        if (likeArray(elements)) {

            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;

    };

    // 配置类型映射

    $.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {

        class2type["[object " + name + "]"] = name.toLowerCase();

    });

    /**

     * 定义的方法,适用于全部的Zepto对象

     */

    $.fn = {

        ready: function(callback) {

            // 检查document.body存在且文档渲染完毕

            if (readyRE.test(document.readyState) && document.body) {

                callback($);

            } else {

                document.addEventListener('DOMContentLoaded', function() {

                    callback($);

                }, false);

            }

        },

        each: function(callback) {

            emptyArray.every.call(this, function(el, idx) {

                return callback.call(el, idx, el) !== false;

            });

            return this;

        },

        text: function(text) {

            return 0 in arguments ?

this.each(function(idx) {

                    var newText = funcArg(this, text, idx, this.textContent);

                    this.textContent = (newText == null) ? '' : '' + newText;

                }) :

                (0 in this ? this[0].textContent : null);

        },

        attr: function(name, value) {

            var result;

            return (typeof name == 'string' && !(1 in arguments)) ?

                (!this.length || this[0].nodeType !== 1 ? undefined :

                    (!(result = this[0].getAttribute(name)) && name in this[0]) ? this[0][name] : result

                ) :

                this.each(function(idx){

                    if (this.nodeType !== 1) {

                        return;

                    }

                    if (isObject(name)) {

                        for (key in name) {

                            setAttribute(this, key, name[key]);

                        }

                    } else {

                       setAttribute(this, name, funcArg(this, value, idx, this.getAttribute(name)));

                    }

                });

        },

        // css属性设置

        css: function(property, value) {

            if (arguments.length < 2) {

                var element = this[0];

                var computedStyle = getComputedStyle(element, '');

                if (!element) {

                    return;

                }

                if (typeof property == 'string') {

                    return element.style[camelize(property)] || computedStyle.getPropertyValue(property);

                } else if (isArray(property)) {

                    var props = {};

                    $.each(isArray(property) ? property : [property], function(_, prop) {

                        props[prop] = (element.style[camelize(prop)] || computedStyle.getPropertyValue(prop));

                    });

                    return props;

                }

            }

            var css = '';

            if (type(property) == 'string') {

                if (!value && value !== 0) {

                    this.each(function() {

                        this.style.removeProperty(dasherize(property));

                    });

                } else {

                    css = dasherize(property) + ":" + maybeAddPx(property, value);

                }

            } else {

                for (var key in property) {

                    if (!property[key] && property[key] !== 0) {

                        this.each(function() {

                            this.style.removeProperty(dasherize(key));

                        });

                    } else {

                        css += dasherize(key) + ':' + maybeAddPx(key, property[key]) + ";";

                    }

                }

            }

            return this.each(function() {

                this.style.cssText += ';' + css;

            });

        }

    };

    // 继承

    zepto.Z.prototype = $.fn;

    $.zepto = zepto;

    return $;

})();





// 全局变量接口

window.Zepto = Zepto;

window.$ === undefined && (window.$ = Zepto);


浏览器输出结果:
Zepto源代码分析之二~三个API
Zepto源代码分析之二~三个API