jQuery学习一

虽然被人嘲笑过在简历里写自己用过jQuery,但我觉得jQuery并不是一个很low的东西,它作为一个库有很多值得学习的地方。在面试的时候,还被问到相关的问题,如选择器实现,链式调用,事件委托,deffered对象实现等,答得不是很好。我决定抽点时间看看这块内容,顺便给久未更新的博客新增一些内容。

但这块内容很多,我得慢慢写。不过也不急。

看的源码来自:https://github.com/jquery/jquery

jQuery的组成架构 (待整理)

jquery.js 采用AMD的写法,core 就对应 jQuery

1
2
3
4
5
6
7
8
9
10
11
12
define( [
"./core",
"./selector",
// ...
], function( jQuery ) {
"use strict";
return jQuery;
} );

  1. 看下core.js的代码组织
    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
    // core.js
    define( [
    "./var/arr",
    "./var/document",
    // ...
    ], function( arr, document) {
    "use strict";
    // 无 new 的构造方式
    var jQuery = function( selector, context ) {
    return new jQuery.fn.init( selector, context );
    },
    // 在原型链上加各种方法, fn更简洁
    jQuery.fn = jQuery.prototype = {
    };
    jQuery.extend = jQuery.fn.extend = function() {
    };
    jQuery.extend( {
    } );
    return jQuery;
    } );

无new的构造方式

无new的实现很好理解,管它调用的时候带不带new,jQuery都new一个实例出来,返回。

问题:

Q: 可以直接写new jQuery()吗?

A: 会无限调用自己,所以需要一个别的构造器init

Q: 构造器返回的实例应该具备所有jQuery的方法,怎么实现?

A: 可以通过原型实现,而且这样做实例的__proto__属性正好就直接为jQuery的原型

1
init.prototype = jQuery.fn

Q: 为什么init要加在jQuery的原型上?

A: 不知道?

1
2
3
jQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context );
},

扩展方法extend

jQuery.extend( [deep ], target, object1 [, objectN ] )

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
jQuery.extend = jQuery.fn.extend = function() {
var options, name, src, copy, copyIsArray, clone,
target = arguments[ 0 ] || {},
i = 1,
length = arguments.length,
deep = false;
// Handle a deep copy situation
if ( typeof target === "boolean" ) {
deep = target;
// Skip the boolean and the target
target = arguments[ i ] || {};
i++;
}
// Handle case when target is a string or something (possible in deep copy)
if ( typeof target !== "object" && !isFunction( target ) ) {
target = {};
}
// Extend jQuery itself if only one argument is passed
if ( i === length ) {
target = this;
i--;
}
for ( ; i < length; i++ ) {
// Only deal with non-null/undefined values
if ( ( options = arguments[ i ] ) != null ) {
// Extend the base object
for ( name in options ) {
src = target[ name ];
copy = options[ name ];
// Prevent never-ending loop
if ( target === copy ) {
continue;
}
// Recurse if we're merging plain objects or arrays
if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
( copyIsArray = Array.isArray( copy ) ) ) ) {
if ( copyIsArray ) {
copyIsArray = false;
clone = src && Array.isArray( src ) ? src : [];
} else {
clone = src && jQuery.isPlainObject( src ) ? src : {};
}
// Never move original objects, clone them
target[ name ] = jQuery.extend( deep, clone, copy );
// Don't bring in undefined values
} else if ( copy !== undefined ) {
target[ name ] = copy;
}
}
}
}
// Return the modified object
return target;
};

默认为浅拷贝,拷贝时舍弃undefined的属性

深拷贝时,对object和数组进行递归处理

extend写好了,给jQuery或是它的原型扩展方法就方便多了:)

Q: 中间调用的isPlainObject是什么?

A: 检查一个元素是不是通过{}或者new Object生成,对于其他复杂的对象(document, dom等),原型不知道如何处理,extend的时候就直接引用复杂对象了

Q: 那如何检测一个对象是这样的普通对象?

A: 利用toString看它的构造函数是否是Object

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
isPlainObject: function( obj ) {
var proto, Ctor;
// Detect obvious negatives
// Use toString instead of jQuery.type to catch host objects
if ( !obj || toString.call( obj ) !== "[object Object]" ) {
return false;
}
proto = getProto( obj );
// Objects with no prototype (e.g., `Object.create( null )`) are plain
if ( !proto ) {
return true;
}
// Objects with prototype are plain iff they were constructed by a global Object function
Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;
return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
},

注意: Object.create(null) 返回的对象,为{},是什么属性都没有的空对象,而且什么都不继承,直接没有__proto__属性。

Q: core.js里还有一个检测是不是类数组对象的方法,是不是只要检测length属性就可以了?

A: function和window也有length属性,
function的length是形参的个数, window的length 属性返回在当前窗口中frames的数量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function isArrayLike( obj ) {
// Support: real iOS 8.2 only (not reproducible in simulator)
// `in` check used to prevent JIT error (gh-2145)
// hasOwn isn't used here due to false negatives
// regarding Nodelist length in IE
var length = !!obj && "length" in obj && obj.length,
type = toType( obj );
if ( isFunction( obj ) || isWindow( obj ) ) {
return false;
}
return type === "array" || length === 0 ||
typeof length === "number" && length > 0 && ( length - 1 ) in obj;
}

这个(length-1) 觉得并没有什么用,都能构造出假的“数组”

core.js 给jquery扩展了不少方法,这里我先不看,等用到再说。