zepto源码阅读

  翻完红宝书,第6,7章看完还是似懂非懂,也不想跟着视频写什么高大上的项目,最后想想还是找个简单的JS库看看源码吧。

zepto版本 v1.2.0

代码折叠后的基本结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//最外层为自执行函数
(function(global, factory) {
if (typeof define === 'function' && define.amd)
//这边是AMD的模块? 不懂,没用过AMD
define(function() { return factory(global) })
else
factory(global)
//为最外层的自执行函数传入this和factory参数
}(this, function(window) {
var Zepto = (function() {
//.....

zepto.Z.prototype = Z.prototype = $.fn

return $
})()
window.Zepto = Zepto //将Zepto赋值给全局Zepto属性
window.$ === undefined && (window.$ = Zepto) //若window.$未被定义,将zepto复制给$

//这边的自执行函数传入Zepto将这个变量作为局部变量,提高效率,缩短使用的作用域链
;(function($){
})(Zepto)

;(function($){
})(Zepto)

;(function($){
})(Zepto)

;(function($){
})(Zepto)

return Zepto
})
)

运行原理

通过自运行函数,将Zepto($)暴露给全局变量Zepto($),此时Zeptp($)为函数,同时该变量下挂载了许多属性方法,通过 $.funName 来调用,当向Zepto($)传入参数后,执行该函数生成对象,同时通过zepto.Z.prototype =Z.prototype = $.fn为生成的对象挂载上DOM的操作方法,部分方法返回值为this(即当前对象),可用于链式操作。

三元运算符“&&”和“||”

源码中使用了很多三元运算符以及赋值问题

操作符||

  1. 只要“||”前面为false,不管“||”后面是true还是false,都返回“||”后面的值。
  2. 只要“||”前面为true,不管“||”后面是true还是false,都返回“||”前面的值。

操作符&&

  1. 只要“&&”前面是false,无论“&&”后面是true还是false,结果都将返“&&”前面的值
  2. 只要“&&”前面是true,无论“&&”后面是true还是false,结果都将返“&&”后面的值

应用

  1. var Yahoo = Yahoo || {}; //赋初值
  2. callback&&callback() //若存在callback则运行callback

执行过程

1
2
3
$ = function(selector, context){
return zepto.init(selector, context)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
  zepto.init = function(selector, context) {
var dom
//如果传入值空,则返回空对象
if (!selector) return zepto.Z()
//当selector为字符串时
else if (typeof selector == 'string') {
selector = selector.trim()
//判断是否为'<'开头的html标签
if (selector[0] == '<' && fragmentRE.test(selector))
dom = zepto.fragment(selector, RegExp.$1, context), selector = null
// 否则context不为空时,调用 $(context).find(selector)
else if (context !== undefined) return $(context).find(selector)
// 否则则为CSS选择器
else dom = zepto.qsa(document, selector)
}
//如果传入为function
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传进来不应该在string那边判断么
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)
}
// 调用zepto.Z创建对象
return zepto.Z(dom, selector)
}
1
2
3
zepto.Z = function(dom, selector) {
return new Z(dom, selector)
}
1
2
3
4
5
6
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 || ''
}

//将$.fn的对象集合赋值给zepto.Z与Z的原型

1
zepto.Z.prototype = Z.prototype = $.fn

zepto.qsa css选择器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  zepto.qsa = function(element, selector){
var found,
maybeID = selector[0] == '#', //判断是否有ID选择器
maybeClass = !maybeID && selector[0] == '.', //判断是否为Class选择器
nameOnly = maybeID || maybeClass ? selector.slice(1) : selector, // Ensure that a 1 char tag name still gets checked
isSimple = simpleSelectorRE.test(nameOnly) //是否为单选择器
return (element.getElementById && isSimple && maybeID) ? //判断document.getElementByID能否使用
( (found = element.getElementById(nameOnly)) ? [found] : [] ) :
(element.nodeType !== 1 && element.nodeType !== 9 && element.nodeType !== 11) ? [] :
slice.call(
isSimple && !maybeID && element.getElementsByClassName ? // 为单选择器,非id选择器,存在getElementsByClassName
maybeClass ? element.getElementsByClassName(nameOnly) : // 为class选择器
element.getElementsByTagName(selector) : // 否则调用getElementsByTagName
element.querySelectorAll(selector) // 否则调用原生querySelectorAll
)
}

内置方法

isArray

先判断是否有 Array.isArray 方法 否则调用 object instanceof Array

1
2
isArray = Array.isArray ||
function(object){ return object instanceof Array }

类型确定方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  $.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
class2type[ "[object " + name + "]" ] = name.toLowerCase()
})

function type(obj) {
return obj == null ? String(obj) :
class2type[toString.call(obj)] || "object"
}
/* 生成 {
'[object boolean]': 'boolean',
'[object number]': 'number',
'[object string]': 'string',
...
} */

类型判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
} //判断是否为纯对象 原型与Object的原型一致

function likeArray(obj) {
var length = !!obj && 'length' in obj && obj.length, //对象不为null,存在length属性 则赋值length
type = $.type(obj)

return 'function' != type && !isWindow(obj) && (
'array' == type || length === 0 || // 如果为 array 类型或者length 的值为 0,返回true
(typeof length == 'number' && length > 0 && (length - 1) in obj) // 或者 length 为数字,并且 length的值大于零,并且 length - 1 为 obj 的 key
)
}

其他方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
  //除去数组中为空的元素
function compact(array) { return filter.call(array, function(item){ return item != null }) }
//连接数组
function flatten(array) { return array.length > 0 ? $.fn.concat.apply([], array) : array }
camelize = function(str){ return str.replace(/-+(.)?/g, function(match, 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()
}
//数组去重 除去当前index与第一次出现该项相同的数组项
uniq = function(array){ return filter.call(array, function(item, idx){ return array.indexOf(item) == idx }) }

function classRE(name) {
return name in classCache ?
classCache[name] : (classCache[name] = new RegExp('(^|\\s)' + name + '(\\s|$)'))
}

function maybeAddPx(name, value) {
return (typeof value == "number" && !cssNumber[dasherize(name)]) ? value + "px" : value
}

//在对象中缓存元素节点并默认显示
function defaultDisplay(nodeName) {
var element, display
if (!elementDisplay[nodeName]) {
element = document.createElement(nodeName)
document.body.appendChild(element)
display = getComputedStyle(element, '').getPropertyValue("display")
element.parentNode.removeChild(element)
display == "none" && (display = "block")
elementDisplay[nodeName] = display
}
return elementDisplay[nodeName]
}

function children(element) {
return 'children' in element ?
slice.call(element.children) :
$.map(element.childNodes, function(node){ if (node.nodeType == 1) return node })
}

//生成DOM
zepto.fragment = function(html, name, properties) {
var dom, nodes, container

// 如果为单个html标签
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
}

//这俩看不懂
function extend(target, source, deep) {
for (key in source)
if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
if (isPlainObject(source[key]) && !isPlainObject(target[key]))
target[key] = {}
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
}

$的工具方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
  $.type = type
$.isFunction = isFunction
$.isWindow = isWindow
$.isArray = isArray
$.isPlainObject = isPlainObject
//判断是否为空对象
$.isEmptyObject = function(obj) {
var name
for (name in obj) return false
return true
}
//判断是否为数字,并且在范围内
$.isNumeric = function(val) {
var num = Number(val), type = typeof val
return val != null && type != 'boolean' &&
(type != 'string' || val.length) &&
!isNaN(num) && isFinite(num) || false
}
//i为起始范围
$.inArray = function(elem, array, i){
return emptyArray.indexOf.call(array, elem, i)
}
//调用内部驼峰转换功能
$.camelCase = camelize
//去除空字符串
$.trim = function(str) {
return str == null ? "" : String.prototype.trim.call(str)
}

$.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)
}

$.each = function(elements, callback){
var i, 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
}
//调用内部filter方法
$.grep = function(elements, callback){
return filter.call(elements, callback)
}
//根据判断JSON对象方法的存在确定
if (window.JSON) $.parseJSON = JSON.parse

// 生成对象映射数组
$.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
class2type[ "[object " + name + "]" ] = name.toLowerCase()
})

$.fn的实例DOM方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
forEach: emptyArray.forEach,
reduce: emptyArray.reduce,
push: emptyArray.push,
sort: emptyArray.sort,
splice: emptyArray.splice,
indexOf: emptyArray.indexOf,
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)
},

// 调用工具方法$.map
map: function(fn){
return $($.map(this, function(el, i){ return fn.call(el, i, el) }))
},
slice: function(){
return $(slice.apply(this, arguments))
},

ready: function(callback){
// need to check if document.body exists for IE as that browser reports
// document ready when it hasn't yet created the body element
if (readyRE.test(document.readyState) && document.body) callback($)
else document.addEventListener('DOMContentLoaded', function(){ callback($) }, false)
return this
},
//若传入参数为空,则转换为数组,若非空则返回对应idx值,若为负数则加上length值(与原生方法一致)
get: function(idx){
return idx === undefined ? slice.call(this) : this[idx >= 0 ? idx : idx + this.length]
},
//调用get方法
toArray: function(){ return this.get() },
//返回length属性
size: function(){
return this.length
},
//先判断是否有父节点,有则删除该节点
remove: function(){
return this.each(function(){
if (this.parentNode != null)
this.parentNode.removeChild(this)
})
},
//判断返回值是否为false
each: function(callback){
emptyArray.every.call(this, function(el, idx){
return callback.call(el, idx, el) !== false
})
return this
},
filter: function(selector){
if (isFunction(selector)) return this.not(this.not(selector))
return $(filter.call(this, function(element){
return zepto.matches(element, selector)
}))
},
//添加元素至集合中
add: function(selector,context){
//去除重复元素
return $(uniq(this.concat($(selector,context))))
},
//判断第一个元素是否满足选择器
is: function(selector){
return this.length > 0 && zepto.matches(this[0], 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)
},
has: function(selector){
return this.filter(function(){
return isObject(selector) ?
//若传入参数为DOM对象
$.contains(this, selector) :
//若传入参数为css选择器
$(this).find(selector).size()
})
},
eq: function(idx){
//若传入为-1 则为最后一个元素,否则为idx,idx+1
return idx === -1 ? this.slice(idx) : this.slice(idx, + idx + 1)
},
first: function(){
//判断是否为对象,并返回第一个元素
var el = this[0]
return el && !isObject(el) ? el : $(el)
},
last: function(){
//判断是否为对象,并返回最后一个元素
var el = this[this.length - 1]
return el && !isObject(el) ? el : $(el)
},
find: function(selector){
var result, $this = this
if (!selector) result = $()
else if (typeof selector == 'object')
result = $(selector).filter(function(){
//若为dom对象通过some过滤出结果
var node = this
return emptyArray.some.call($this, function(parent){
return $.contains(parent, node)
})
})
//如果对象数量为1,则直接从该DOM对象中寻找
else if (this.length == 1) result = $(zepto.qsa(this[0], selector))
else result = this.map(function(){ return zepto.qsa(this, selector) })
return result
},
closest: function(selector, context){
var nodes = [], collection = typeof selector == 'object' && $(selector)
this.each(function(_, node){
while (node && !(collection ? collection.indexOf(node) >= 0 : zepto.matches(node, selector)))
node = node !== context && !isDocument(node) && node.parentNode
if (node && nodes.indexOf(node) < 0) nodes.push(node)
})
return $(nodes)
},
parents: function(selector){
var ancestors = [], nodes = this
while (nodes.length > 0)
//返回元素的所有父元素直到document
nodes = $.map(nodes, function(node){
if ((node = node.parentNode) && !isDocument(node) && ancestors.indexOf(node) < 0) {
ancestors.push(node)
return node
}
})
return filtered(ancestors, selector)
},
parent: function(selector){
return filtered(uniq(this.pluck('parentNode')), selector)
},
children: function(selector){
return filtered(this.map(function(){ return children(this) }), selector)
},
contents: function() {
// 获得每个匹配元素集合元素的子元素,包括文字和注释节点
return this.map(function() { return this.contentDocument || slice.call(this.childNodes) })
},
siblings: function(selector){
//返回除去当前元素的同级元素,若selector存在则过滤
return filtered(this.map(function(i, el){
return filter.call(children(el.parentNode), function(child){ return child!==el })
}), selector)
},
empty: function(){
//清空元素的innerHTML
return this.each(function(){ this.innerHTML = '' })
},
// `pluck` is borrowed from Prototype.js
pluck: function(property){
return $.map(this, function(el){ return el[property] })
},
show: function(){
return this.each(function(){
//若display属性值为none,则去除该属性值
this.style.display == "none" && (this.style.display = '')
//获取该元素最终的CSS属性,使用getPropertyValue方法不必可以驼峰书写形式(不支持驼峰写法
if (getComputedStyle(this, '').getPropertyValue("display") == "none")
this.style.display = defaultDisplay(this.nodeName)
})
},
replaceWith: function(newContent){
//在该元素前插入元素并删除该元素
return this.before(newContent).remove()
},
wrap: function(structure){
var func = isFunction(structure)
if (this[0] && !func)
var dom = $(structure).get(0),
clone = dom.parentNode || this.length > 1

return this.each(function(index){
$(this).wrapAll(
func ? structure.call(this, index) :
clone ? dom.cloneNode(true) : dom
)
})
},
wrapAll: function(structure){
if (this[0]) {
$(this[0]).before(structure = $(structure))
var children
// drill down to the inmost element
while ((children = structure.children()).length) structure = children.first()
$(structure).append(this)
}
return this
},
wrapInner: function(structure){
var func = isFunction(structure)
return this.each(function(index){
var self = $(this), contents = self.contents(),
dom = func ? structure.call(this, index) : structure
contents.length ? contents.wrapAll(dom) : self.append(dom)
})
},
unwrap: function(){
this.parent().each(function(){
$(this).replaceWith($(this).children())
})
return this
},
clone: function(){
return this.map(function(){ return this.cloneNode(true) })
},
hide: function(){
return this.css("display", "none")
},
toggle: function(setting){
return this.each(function(){
var el = $(this)
;(setting === undefined ? el.css("display") == "none" : setting) ? el.show() : el.hide()
})
},
prev: function(selector){ return $(this.pluck('previousElementSibling')).filter(selector || '*') },
next: function(selector){ return $(this.pluck('nextElementSibling')).filter(selector || '*') },
html: function(html){
//若有参数修改innerhtml的值
return 0 in arguments ?
this.each(function(idx){
var originHtml = this.innerHTML
$(this).empty().append( funcArg(this, html, idx, originHtml) )
}) :
//若没参数则直接返回innerHTML值
(0 in this ? this[0].innerHTML : null)
},
//文本内容修改,获取
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.pluck('textContent').join("") : null)
},
attr: function(name, value){
var result
//若无第二个参数
return (typeof name == 'string' && !(1 in arguments)) ?
//返回第一个DOM的属性值
(0 in this && this[0].nodeType == 1 && (result = this[0].getAttribute(name)) != null ? result : undefined) :
若有参数,则set属性的值
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)))
})
},
removeAttr: function(name){
return this.each(function(){ this.nodeType === 1 && name.split(' ').forEach(function(attribute){
setAttribute(this, attribute)
//这个this是怎么传的?
}, this)})
},
//这个是怎么用的 不懂
prop: function(name, value){
name = propMap[name] || name
return (1 in arguments) ?
this.each(function(idx){
this[name] = funcArg(this, value, idx, this[name])
}) :
(this[0] && this[0][name])
},
//删除DOM节点中的一条属性
removeProp: function(name){
name = propMap[name] || name
return this.each(function(){ delete this[name] })
},
data: function(name, value){
//设置自定义属性data- ,若没有传入属性的值则仅设置属性,返回序列化的data的属性值
var attrName = 'data-' + name.replace(capitalRE, '-$1').toLowerCase()

var data = (1 in arguments) ?
this.attr(attrName, value) :
this.attr(attrName)

return data !== null ? deserializeValue(data) : undefined
},
val: function(value){
// 有参数,设置值
if (0 in arguments) {
if (value == null) value = ""
return this.each(function(idx){
this.value = funcArg(this, value, idx, this.value)
})
} else {
// 如果元素是 <select multiple> 多选列表返回数组
return this[0] && (this[0].multiple ?
$(this[0]).find('option').filter(function(){ return this.selected }).pluck('value') :
this[0].value)
}
},
offset: function(coordinates){
//如果参数存在,则设置对应的属性
if (coordinates) return this.each(function(index){
var $this = $(this),
coords = funcArg(this, coordinates, index, $this.offset()),
parentOffset = $this.offsetParent().offset(),
props = {
top: coords.top - parentOffset.top,
left: coords.left - parentOffset.left
}
//如果属性为static则改为relaticv并赋值
if ($this.css('position') == 'static') props['position'] = 'relative'
$this.css(props)
})
if (!this.length) return null
if (document.documentElement !== this[0] && !$.contains(document.documentElement, this[0]))
return {top: 0, left: 0}
var obj = this[0].getBoundingClientRect()
return {
left: obj.left + window.pageXOffset,
top: obj.top + window.pageYOffset,
width: Math.round(obj.width),
height: Math.round(obj.height)
}
},
css: function(property, value){
if (arguments.length < 2) {
//获取第一个元素
var element = this[0]
//如果属性为字符串,则修改css的值
if (typeof property == 'string') {
if (!element) return
return element.style[camelize(property)] || getComputedStyle(element, '').getPropertyValue(property)
//如果属性为数组,则返回对应的属性值
} else if (isArray(property)) {
if (!element) return
var props = {}
var computedStyle = getComputedStyle(element, '')
$.each(property, function(_, prop){
props[prop] = (element.style[camelize(prop)] || computedStyle.getPropertyValue(prop))
})
return props
}
}

var css = ''
// 其他情况:有两个参数、property是对象
if (type(property) == 'string') {
// property 是字符串,设置单个样式
if (!value && value !== 0)
this.each(function(){ this.style.removeProperty(dasherize(property)) })
else
css = dasherize(property) + ":" + maybeAddPx(property, value)
} else {
for (key in property)
//property 是对象,若属性值为空或未定义,则删除属性
if (!property[key] && property[key] !== 0)
this.each(function(){ this.style.removeProperty(dasherize(key)) })
else
css += dasherize(key) + ':' + maybeAddPx(key, property[key]) + ';'
}
//通过cssText修改CSS
return this.each(function(){ this.style.cssText += ';' + css })
},
index: function(element){
//有参数则返回该dom的index,否则返回该dom在兄弟元素中的index
return element ? this.indexOf($(element)[0]) : this.parent().children().indexOf(this[0])
},
hasClass: function(name){
//判断是否有某一css属性
if (!name) return false
return emptyArray.some.call(this, function(el){
return this.test(className(el))
}, classRE(name))
},
addClass: function(name){
if (!name) return this
return this.each(function(idx){
//判断是否存在className属性
if (!('className' in this)) return
classList = []
//保留原来的class
var cls = className(this), newName = funcArg(this, name, idx, cls)
//数组中存入不存在的class
newName.split(/\s+/g).forEach(function(klass){
if (!$(this).hasClass(klass)) classList.push(klass)
}, this)
//传入className
classList.length && className(this, cls + (cls ? " " : "") + classList.join(" "))
})
},
removeClass: function(name){
return this.each(function(idx){
//判断是否存在className属性
if (!('className' in this)) return
if (name === undefined) return className(this, '')
classList = className(this)
funcArg(this, name, idx, classList).split(/\s+/g).forEach(function(klass){
//将匹配的cssName替换为空
classList = classList.replace(classRE(klass), " ")
})
//赋值cssName
className(this, classList.trim())
})
},
toggleClass: function(name, when){
if (!name) return this
return this.each(function(idx){
var $this = $(this), names = funcArg(this, name, idx, className(this))
names.split(/\s+/g).forEach(function(klass){
// 如果有 when 参数,则只通过when参数判断,true则只执行addClass,false则只执行removeClass
// 如果没有 when 参数,则判断元素有没有该class,有则移除,没有则添加
(when === undefined ? !$this.hasClass(klass) : when) ?
$this.addClass(klass) : $this.removeClass(klass)
})
})
},
scrollTop: function(value){
if (!this.length) return
var hasScrollTop = 'scrollTop' in this[0]
//如果无参数,直接返回属性值
if (value === undefined) return hasScrollTop ? this[0].scrollTop : this[0].pageYOffset
return this.each(hasScrollTop ?
function(){ this.scrollTop = value } :
function(){ this.scrollTo(this.scrollX, value) })
},
scrollLeft: function(value){
//同上
if (!this.length) return
var hasScrollLeft = 'scrollLeft' in this[0]
if (value === undefined) return hasScrollLeft ? this[0].scrollLeft : this[0].pageXOffset
return this.each(hasScrollLeft ?
function(){ this.scrollLeft = value } :
function(){ this.scrollTo(value, this.scrollY) })
},
position: function() {
if (!this.length) return

var elem = this[0],
// 找到第一个定位过的祖先元素 “relative”, “absolute” or “fixed”
offsetParent = this.offsetParent(),
// 获取当前偏移
offset = this.offset(),
parentOffset = rootNodeRE.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset()

// 去掉当前元素的 margin 宽度
// note: when an element has margin: auto the offsetLeft and marginLeft
// are the same in Safari causing offset.left to incorrectly be 0
offset.top -= parseFloat( $(elem).css('margin-top') ) || 0
offset.left -= parseFloat( $(elem).css('margin-left') ) || 0

// 增加父元素的 border 宽度
parentOffset.top += parseFloat( $(offsetParent[0]).css('border-top-width') ) || 0
parentOffset.left += parseFloat( $(offsetParent[0]).css('border-left-width') ) || 0

// Subtract the two offsets
return {
top: offset.top - parentOffset.top,
left: offset.left - parentOffset.left
}
},
offsetParent: function() {
return this.map(function(){
var parent = this.offsetParent || document.body
// 如果获取的parent不是null、不是body或html、而且position==static,继续查找,直到为根元素
while (parent && !rootNodeRE.test(parent.nodeName) && $(parent).css("position") == "static")
parent = parent.offsetParent
return parent
})
}