最近在做移动端web项目,用到了开源框架zepto,语法上和jquery并无明显区别,所以怀着好奇的心情看了一下其源代码,接下来先介绍其核心方法$()
一、$()的源码
// `$` will be the base `Zepto` object. When calling this // function just call `$.zepto.init, which makes the implementation // details of selecting nodes and creating Zepto collections // patchable in plugins. $ = function(selector, context){ return zepto.init(selector, context) }
可以看到$()调用了zepto.init(selctor, context),下面进入zepto的init中
// `$.zepto.init` is Zepto's counterpart to jQuery's `$.fn.init` and // takes a CSS selector and an optional context (and handles various // special cases). // This method can be overriden in plugins. zepto.init = function(selector, context) { var dom // If nothing given, return an empty Zepto collection if (!selector) return zepto.Z() // Optimize for string selectors else if (typeof selector == 'string') { selector = selector.trim() // If it's a html fragment, create nodes from it // Note: In both Chrome 21 and Firefox 15, DOM error 12 // is thrown if the fragment doesn't begin with < if (selector[0] == '<' && fragmentRE.test(selector)) dom = zepto.fragment(selector, RegExp.$1, context), selector = null // If there's a context, create a collection on that context first, and select // nodes from there else if (context !== undefined) return $(context).find(selector) // If it's a CSS selector, use it to select nodes. else dom = zepto.qsa(document, selector) } // If a function is given, call it when the DOM is ready else if (isFunction(selector)) return $(document).ready(selector) // If a Zepto collection is given, just return it else if (zepto.isZ(selector)) return selector else { // normalize array if an array of nodes is given if (isArray(selector)) dom = compact(selector) // Wrap DOM nodes. else if (isObject(selector)) dom = [selector], selector = null // If it's a html fragment, create nodes from it else if (fragmentRE.test(selector)) dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null // If there's a context, create a collection on that context first, and select // nodes from there else if (context !== undefined) return $(context).find(selector) // And last but no least, if it's a CSS selector, use it to select nodes. else dom = zepto.qsa(document, selector) } // create a new Zepto collection from the nodes found return zepto.Z(dom, selector) }
第一步,在init方法中,首先判断如果传入的selector为空,则返回一个空的zepto集合z()
// `$.zepto.Z` swaps out the prototype of the given `dom` array // of nodes with `$.fn` and thus supplying all the Zepto functions // to the array. Note that `__proto__` is not supported on Internet // Explorer. This method can be overriden in plugins. zepto.Z = function(dom, selector) { dom = dom || [] dom.__proto__ = $.fn dom.selector = selector || '' return dom }
在z方法中,待返回的dom的_proto_属性被赋给了$.fn,而$.fn表示了zepto对象的所有可用方法,而传入的dom和selector均为空,则dom的selector为空,下面编写代码验证一下
console.log($());
结果:
可以看到验证了以上所述
第二步,如果selctor是个string,并且是个html串,则调用zepto.fragment
zepto.fragment = function(html, name, properties) { var dom, nodes, container // 如果是类似于<div id="test" />,则先创建一个该node if (singleTagRE.test(html)) dom = $(document.createElement(RegExp.$1)) if (!dom) {//如果node非空,则将类似于<div id="test" />替换为<div id="test"></div> if (html.replace) html = html.replace(tagExpanderRE, "<$1></$2>") if (name === undefined) name = fragmentRE.test(html) && RegExp.$1//取得标签名,例如div // containers = { // 'tr': document.createElement('tbody'), // 'tbody': table, 'thead': table, 'tfoot': table, // 'td': tableRow, 'th': tableRow, // '*': document.createElement('div') //}, if (!(name in containers)) name = '*'//设置容器标签名,如果不是tr,tbody,thead,tfoot,td,th,则容器标签名为div container = containers[name] container.innerHTML = '' + html dom = $.each(slice.call(container.childNodes), function(){//循环遍历每个子节点 container.removeChild(this) }) } //如果properties是原始对象 if (isPlainObject(properties)) { nodes = $(dom)//将dom转成zepto对象,为了方便下面调用zepto上的方法 $.each(properties, function(key, value) { // methodAttributes = ['val', 'css', 'html', 'text', 'data', 'width', 'height', 'offset'], if (methodAttributes.indexOf(key) > -1) nodes[key](value) else nodes.attr(key, value) }) } //返回将字符串转成的DOM节点后的数组,比如'<li></li><li></li><li></li>'转成[li,li,li] return dom }
下面传入一个html字符串来进行验证:
不传入properties
var dom = $("<p>Hello</p>"); console.log(dom.text());
运行结果:
Hello
传入properties
var dom = $("<p />", { text:"Hello", id:"greeting", css:{color:'darkblue'} }); console.log(dom.attr('id')); console.log(dom.text()); console.log(dom.css('color'));
运行结果:
greeting
Hello
rgb(0, 0, 139)
第三步,如果存在context,则在context中查找
第四步,如果是选择器,则使用选择器进行查找
// `$.zepto.qsa` is Zepto's CSS selector implementation which // uses `document.querySelectorAll` and optimizes for some special cases, like `#id`. // This method can be overriden in plugins. zepto.qsa = function(element, selector){ var found, maybeID = selector[0] == '#', maybeClass = !maybeID && selector[0] == '.', nameOnly = maybeID || maybeClass ? selector.slice(1) : selector, // 从第2个字符开始得到剩下的字符串,如#test->test,.test->test isSimple = simpleSelectorRE.test(nameOnly)//不是选择器集合 return (isDocument(element) && isSimple && maybeID) ? ( (found = element.getElementById(nameOnly)) ? [found] : [] ) ://by id (element.nodeType !== 1 && element.nodeType !== 9) ? [] : slice.call( isSimple && !maybeID ? maybeClass ? element.getElementsByClassName(nameOnly) : // If it's simple, it could be a class element.getElementsByTagName(selector) : // Or a tag element.querySelectorAll(selector) // Or it's not simple, and we need to query all ) }
第五步,如果selector是一个函数,则直接执行这个函数
实例:
$(function() { console.log('hello world'); });
结果:
hello world
第六步,如果selector是一个zepto对象,则直接返回此selector
// `$.zepto.isZ` should return `true` if the given object is a Zepto // collection. This method can be overriden in plugins. zepto.isZ = function(object) { return object instanceof zepto.Z }
上述代码就是用来判断是否为zepto对象
第七步,如果selector是一个数组,则dom对象就等于此数组中的非空值组合
function isArray(value) { return value instanceof Array }
上述代码用来判断对象是否为数组
第八步,如果selector是一个对象,则dom对象就等于此对象构成的一个数组
function isObject(obj) { return type(obj) == "object" }
function type(obj) { return obj == null ? String(obj) : class2type[toString.call(obj)] || "object" }
上述代码用来判断是否为一个对象
综上,总的流程是根据selector为空,selector为string类型、selector为函数、selector为数组、selector为zepto对象、selector为对象等进行处理