准备说明

  • 该模块定义了库的原型链结构,生成了Zepto变量,并将其以'Zepto'和'$'的名字注册到了window,然后开始了其它模块的拓展实现。
  • 模块内部除了对选择器和zepto对象的实现,就是一些工具方法和原型方法的定义。
  • 值得一提的是,内部很多实现都利用了原生数组的方法,很多api也是基于内部或公开的方法进一步拓展实现的。
  • 虽然该模块涉及的api非常多,但内部实现上比较统一,因此只会针对性地挑一些方法进行分析。

实现内容

var Zepto = (function () {
// 1.基础变量定义
// 2.内部方法实现
// 3.zepto对象实现——$('选择器')
// 4.$.extend方法实现
// 5.zepto.qsa内部方法和其它5个内部方法
// 6.全局方法实现——$.方法
// 7.原型方法实现——$().方法
// 8.原型链结构设置
}();
  • 上面是主体实现内容,代码定义顺序大致如上,某些地方会穿插定义

重点分析

选择器

选择器的实现逻辑放在了zepto.init方法中,由$()内部调用zepto.init方法,先处理选择器生成dom集合,然后将集合传入能生成zepto对象的内部方法,主体逻辑如下:

zepto.init = function(selector, context) {
// 1.如果selector为空
// 2.如果selector为字符串
a.标签字符串形式
b.context存在的处理
c.css选择器
// 3.如果selector为方法
// 4.如果selector为zepto对象
// 5.如果selector为其它情况
// 6.执行zepto对象生成方法
}

源码如下

zepto.init = function(selector, context) {
var dom
// 如果selector不存在(返回一个空的zepto对象)
if (!selector) return zepto.Z()
// 如果selector为字符串
else if (typeof selector == 'string') {
selector = selector.trim()
// 如果是标签字符串形式(在Chrome 21 and Firefox 15中,如果选择器不以'<'开头,会引发dom错误)
if (selector[0] == '<' && fragmentRE.test(selector))
dom = zepto.fragment(selector, RegExp.$1, context), selector = null
// 如果存在context属性,则应该以它为基础往下查找
else if (context !== undefined) return $(context).find(selector)
// 如果是css选择器
else dom = zepto.qsa(document, selector)
}
// 如果selector是方法
else if (isFunction(selector)) return $(document).ready(selector)
// 如果是zepto对象(直接返回)
else if (zepto.isZ(selector)) return selector
else {
// 如果是本身数组,则去掉不存在的
if (isArray(selector)) dom = compact(selector)
// 如果是对象,则存为数组形式
else if (isObject(selector))
dom = [selector], selector = null
// 这里重复了typeof selector == 'string'时的判断
// 因为typeof无法让new String()包装类型进入条件,因此通过最后的else再进行一次判断
else if (fragmentRE.test(selector))
dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null
else if (context !== undefined) return $(context).find(selector)
else dom = zepto.qsa(document, selector)
}
// 创建zepto集合
return zepto.Z(dom, selector)
}

zepto.qsa方法

// 处理css选择器的情况
zepto.qsa = function(element, selector){
var found,
maybeID = selector[0] == '#',
maybeClass = !maybeID && selector[0] == '.',
nameOnly = maybeID || maybeClass ? selector.slice(1) : selector,
// 是否为单一形式的选择器而非复杂形式
isSimple = simpleSelectorRE.test(nameOnly)
return (element.getElementById && isSimple && maybeID) ? // Safari游览器的DocumentFrament没有getElementById方法
// id选择器处理
( (found = element.getElementById(nameOnly)) ? [found] : [] ) :
// 非id选择器处理
(element.nodeType !== 1 && element.nodeType !== 9 && element.nodeType !== 11) ? [] :
slice.call(
isSimple && !maybeID && element.getElementsByClassName ? // DocumentFragment没有getElementsByClassName/TagName方法
maybeClass ? element.getElementsByClassName(nameOnly) : // 如果是class
element.getElementsByTagName(selector) : // 如果是标签
element.querySelectorAll(selector) // 其它都用querySelectorAll处理
)
}

zepto.fragment方法

// 处理标签字符串的情况
zepto.fragment = function(html, name, properties) {
var dom, nodes, container
// 如果是无标签内容的标签,则直接创建并赋值给dom变量
if (singleTagRE.test(html)) dom = $(document.createElement(RegExp.$1))
// 如果是有内容的标签,则走其他的判断流程
if (!dom) {
// 保证标签字符串为双标签形式
if (html.replace) html = html.replace(tagExpanderRE, "<$1></$2>")
// 如果没有从html字符串中获取到标签名,则再次获取
if (name === undefined) name = fragmentRE.test(html) && RegExp.$1
// 如果标签名为非常规字符串,则name置为'*',容器container设置为div
if (!(name in containers)) name = '*'
// 通过容器标签将html字符串dom化
container = containers[name]
container.innerHTML = '' + html
// 返回dom集合,清空container
dom = $.each(slice.call(container.childNodes), function(){
container.removeChild(this)
})
}
// 如果是纯粹的object对象
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对象

Zepto对象的生成依赖于一连串相关的内部方法,源码实现如下

// zepto对象工厂————第四步
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.fragment = function(html, name, properties) {
// 源码不再赘述...
} // 生成zepto对象————第三步
zepto.Z = function(dom, selector) {
// 通过模块的原型链结构,可获得各种操作方法
return new Z(dom, selector)
} // 判断是否为zepto对象
zepto.isZ = function(object) {
return object instanceof zepto.Z
} // 生成dom集合,然后进行zepto集合创建————第二步
zepto.init = function(selector, context) {
// 生成dom集合
// 执行zepto.Z方法
// 源码不再赘述...
return zepto.Z(dom, selector)
} // 获取zepto对象————第一步
$ = function(selector, context){
return zepto.init(selector, context)
} // 处理css选择器
zepto.qsa = function(element, selector){
// 源码不再赘述...
} // 设置原型方法
$.fn = { } // 设置原型链结构
zepto.Z.prototype = Z.prototype = $.fn // 返回给外部Zepto变量,然后通过window注册
return $

部分api方法

  • 工具方法和原型方法太多,只能挑几个典型了
  • extend和ready的实现都比较简单
  • css方法的实现主要还是对原生element.style和getComputedStyle的熟悉,对属性名转换和样式单位的自动添加的考虑
  • 插入操作的几个方法实现比较精巧,可以认真研究下

$.extend

function extend(target, source, deep) {
for (key in source)
// 如果是深拷贝
// isArray判断是否为数组类型
// isPlainObject判断是否为纯粹的object类型
if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
// 如果拷贝对象的属性值为Object,而目标对象的属性值不为Object,则目标对象的属性值重置为Object
if (isPlainObject(source[key]) && !isPlainObject(target[key]))
target[key] = {}
// 如果拷贝对象的属性值为Array,而目标对象的属性值不为Array,则目标对象的属性值重置为Array
if (isArray(source[key]) && !isArray(target[key]))
target[key] = []
// 进行深拷贝
extend(target[key], source[key], deep)
}
// 如果是浅拷贝
else if (source[key] !== undefined) target[key] = source[key]
} $.extend = function(target){
var deep, args = slice.call(arguments, 1)
// 如果存在深/浅拷贝设置
if (typeof target == 'boolean') {
// 存储深浅拷贝判断值
deep = target
// 获得目标对象
target = args.shift()
}
// 遍历拷贝对象
args.forEach(function(arg){ extend(target, arg, deep) })
return target
}

$.fn.ready

$.fn = {
ready: function(callback){
// 需要检查IE是否存在document.body,因为浏览器在尚未创建body元素时会报告文档就绪
if (readyRE.test(document.readyState) && document.body) callback($)
else document.addEventListener('DOMContentLoaded', function(){ callback($) }, false)
return this
}
}

$.fn.css

$.fn = {
css: function(property, value){
// 如果是获取属性(参数小于2)
if (arguments.length < 2) {
// 取第一个元素
var element = this[0]
// 如果是字符串类型
if (typeof property == 'string') {
if (!element) return
// element.style用于访问直接样式,getComputedStyle用于访问层叠后的样式
// camelize方法用于将a-b,转换aB,驼峰转换
return element.style[camelize(property)] || getComputedStyle(element, '').getPropertyValue(property)
// 如果是数组类型
} else if (isArray(property)) {
if (!element) return
var props = {}
// 存储层叠样式集
var computedStyle = getComputedStyle(element, '')
// 遍历数组样式名,生成样式对象props返回
$.each(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
// dasherize用于将property规范化为'a-b'形式
// maybeAddPx用于自动增加px
css = dasherize(property) + ":" + maybeAddPx(property, value)
} else {
for (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 })
}
}

$.fn.插入

$.fn = {
// 递归执行节点处理函数
function traverseNode(node, fun) {
fun(node)
for (var i = 0, len = node.childNodes.length; i < len; i++)
traverseNode(node.childNodes[i], fun)
} // after|before|append|prepend与insertAfter|insertBefore|appendTo|prepend实现
// adjacencyOperators = [ 'after', 'prepend', 'before', 'append' ]
adjacencyOperators.forEach(function(operator, operatorIndex) {
// inside为0和2, after|before(标签外插入)
// inside为1和3, append|prepend(标签内插入)
var inside = operatorIndex % 2 // after|before|append|prepend实现
$.fn[operator] = function(){
// 获得dom数组
var argType, nodes = $.map(arguments, function(arg) {
var arr = []
// 判断参数项类型
argType = type(arg)
// 如果是array类型,处理成dom数组返回
if (argType == "array") {
arg.forEach(function(el) {
// 如果el是dom对象
if (el.nodeType !== undefined) return arr.push(el)
// 如果el是zepto对象
else if ($.zepto.isZ(el)) return arr = arr.concat(el.get())
// 如果el是标签字符串
arr = arr.concat(zepto.fragment(el))
})
return arr
}
// 如果为object类型或不为null,就返回自身;否则作为标签字符串处理,返回dom
return argType == "object" || arg == null ?
arg : zepto.fragment(arg)
}),
parent, copyByClone = this.length > 1 // 如果不存在,则返回自身
if (nodes.length < 1) return this // 处理dom数组
return this.each(function(_, target){
// inside为0和2, 处理的是after|before, 用父级做容器
// inside为1和3, 处理的是append|prepend, 用自身做容器
parent = inside ? target : target.parentNode
// target在insertBefore方法中用作参照点
// operatorIndex == 0, 操作的是after方法, 取下一个节点
// operatorIndex == 1, 操作的是prepend方法, 取第一个子节点
// operatorIndex == 2, 操作的是before方法, 取自身
// operatorIndex == 3, 操作的是append方法, 取null
target = operatorIndex == 0 ? target.nextSibling :
operatorIndex == 1 ? target.firstChild :
operatorIndex == 2 ? target :
null
// 是否在html内
var parentInDocument = $.contains(document.documentElement, parent)
// 遍历处理插入的节点
nodes.forEach(function(node){
// 因为操作对象可能有多个, 而被插入node只有1个
// 如果要让所有操作对象都被插入内容,需要对插入node进行深克隆
if (copyByClone) node = node.cloneNode(true)
// 如果不存在parent,则删除插入的节点
else if (!parent) return $(node).remove()
// 插入节点
parent.insertBefore(node, target)
// 如果在html内,就递归执行,遇到有js代码的script标签,就执行它
if (parentInDocument) traverseNode(node, function(el){
if (el.nodeName != null && el.nodeName.toUpperCase() === 'SCRIPT' &&
(!el.type || el.type === 'text/javascript') && !el.src){
// 这里用到ownerDocument判断而不直接用window,是考虑到页面有可能是iframe引入的
var target = el.ownerDocument ? el.ownerDocument.defaultView : window
target['eval'].call(target, el.innerHTML)
}
})
})
})
} // insertAfter|insertBefore|appendTo|prepend实现
$.fn[inside ? operator+'To' : 'insert'+(operatorIndex ? 'Before' : 'After')] = function(html){
$(html)[operator](this)
return this
}
})
}

zepto源码分析·core模块的更多相关文章

  1. zepto源码分析·ajax模块

    准备知识 在看ajax实现的时候,如果对ajax技术知识不是很懂的话,可以参看下ajax基础,以便读分析时不会那么迷糊 全局ajax事件 默认$.ajaxSettings设置中的global为true ...

  2. zepto源码分析·event模块

    准备知识 事件的本质就是发布/订阅模式,dom事件也不例外:先简单说明下发布/订阅模式,dom事件api和兼容性 发布/订阅模式 所谓发布/订阅模式,用一个形象的比喻就是买房的人订阅楼房消息,售楼处发 ...

  3. Zepto源码分析-deferred模块

    源码注释 // Zepto.js // (c) 2010-2015 Thomas Fuchs // Zepto.js may be freely distributed under the MIT l ...

  4. Zepto源码分析-event模块

    源码注释 // Zepto.js // (c) 2010-2015 Thomas Fuchs // Zepto.js may be freely distributed under the MIT l ...

  5. Zepto源码分析-ajax模块

    源码注释 // Zepto.js // (c) 2010-2015 Thomas Fuchs // Zepto.js may be freely distributed under the MIT l ...

  6. Zepto源码分析-form模块

    源码注释 // Zepto.js // (c) 2010-2015 Thomas Fuchs // Zepto.js may be freely distributed under the MIT l ...

  7. Zepto源码分析-callbacks模块

    // Zepto.js // (c) 2010-2015 Thomas Fuchs // Zepto.js may be freely distributed under the MIT licens ...

  8. zepto源码分析系列

    如果你也开发移动端web,如果你也用zepto,应该值得你看看.有问题请留言. Zepto源码分析-架构 Zepto源码分析-zepto(DOM)模块 Zepto源码分析-callbacks模块 Ze ...

  9. 读Zepto源码之Event模块

    Event 模块是 Zepto 必备的模块之一,由于对 Event Api 不太熟,Event 对象也比较复杂,所以乍一看 Event 模块的源码,有点懵,细看下去,其实也不太复杂. 读Zepto源码 ...

随机推荐

  1. Spring boot 官网学习笔记 - Spring Boot CLI 入门案例

    安装CLI https://repo.spring.io/release/org/springframework/boot/spring-boot-cli/2.1.1.RELEASE/spring-b ...

  2. 手把手创建gulp

    这几天安装gulp踩了不少坑,现在讲解一个入门的案例解析: ==首先大家要确保node.npm.npx.gulp安装是否成功 == 这些安装都是傻瓜式安装,大家可以找到相应的教材. 创建一个自己的文件 ...

  3. 如何设置eclipse自动提示功能

    1.Window --> preferences 2.java --> Editor --> Content Assist 3.将Auto activation triggers f ...

  4. lcy各种要填的坑

    莫比乌斯反演.FFT/NTT/FWT/FMT/ 数论容斥复习写题 概率期望复习写题 总结一下dp 看斜率优化.四边形不等式 网络流写题 字符串博客写完,写题 lh老师的课件啃完写题 考前打打正睿模拟赛

  5. RedHat安装git报错 expected specifier-qualifier-list before ‘z_stream’

    年初开学的时候认识到了git,因为当时也没装虚拟机甚至是不知道虚拟机这个东西,所以就下载了Windows下的git.当时跟着廖雪峰Git教程 学了几个命令.安装了虚拟机,也学了linux的基本命令后, ...

  6. Maven私服Nexus的搭建

    # Maven私服Nexus的搭建 ## 私服存在的合理性 Maven中的依赖是从服务器仓库中下载的,Maven的仓库只有两大类: - 1) 本地仓库 - 2) 远程仓库,其中在远程仓库中又分成了3种 ...

  7. 《Java语言程序设计》编程练习6.31(财务应用程序:信用卡号的合法性)

    6.31(财务应用程序:信用卡号的合法性)信用卡号遵循下面的模式.一个信用卡号必须是13到16位的整数.它的开头必须是: 4,指Visa卡 5,指Master卡 37,指American Expres ...

  8. ELK日志分析系统(4)-elasticsearch数据存储

    1. 概述 logstash把格式化的数据发送到elasticsearch以后,elasticsearch负责存储搜索日志数据 elasticsearch的搜索接口还是很强大的,这边不详细展开,因为k ...

  9. 常用注解@Controller、@Service、@Autowired

    @Controller.@Service在spring-context-5.1.10.RELEASE.jar包下,所在包如下 @Autowired在spring-beans-5.1.10.RELEAS ...

  10. Python中的可变对象与不可变对象、浅拷贝与深拷贝

    Python中的对象分为可变与不可变,有必要了解一下,这会影响到python对象的赋值与拷贝.而拷贝也有深浅之别. 不可变对象 简单说就是某个对象存放在内存中,这块内存中的值是不能改变的,变量指向这块 ...