jQuery为开发者提供了快速便捷的DOM操作, 那么如果我们想添加自己的方法到jQuery原型或者构造函数上, 我们该怎么做呢? jQuery暴露出了extendAPI来解决这个问题, 这篇文章就来观摩一下其拓展机制.

一、更新


[2019-4-21]

Changed

  • 改进文章格式🎉

二、前置


2.1 深拷贝vs浅拷贝

PS: 关于拷贝的知识, 网上的各种博客已经层出不穷了, 对其实现方式以及基本原理都讲解的很清楚. 这里还是苦口婆心地提一下, 它们的主要区别, 也是为了接下来地阅读作个铺垫吧.

那么, 问题来了:

Q: 两者的主要区别是什么?

  • 对于数组, 两者是一样的功能
  • 对于键值对对象
    • shallowCopy只拷贝Object.keys(), 也就是第一层键值
    • deepCopy则反之

一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const obj = {
b: 1,
c: 2,
d: {
e: 3,
f: [],
g: {
h: {
...
},
},
},
};

// shallowCopy: {b, c, d,}
// deepCopy: {b: 1, c: 2, d: { e: 3, f: [], g: { h: {} } }}

关于它们的区别, 还可以参考我之前的文章:

PS: 前端基础重拾系列之——深浅拷贝

三、用法


3.1 jQuery.extend&jQuery.fn.extend

jq提供了两种方式, 供我们自定义拓展:

  • jQuery.extend(…)
    • 拓展Static, 也就是静态方法
  • jQuery.fn.extend(…)
    • 拓展Non-static(实例方法)

3.2 如何使用?

分析拓展机制前, 先做一件事情, 那就是如何去使用它, 俗话说知其意,悟其理,守其则,践其行, 了解了它是如何使用, 再去深究其原理, 这也是我写文章的一贯原则.

PS: 自己用jQuery很少, 所以对extendAPI也基本没用过, 但是这并不影响对其理解, 毕竟知识是相通的, 所谓的框架只不过是换了身皮.

一般来说, 开发者可以根据可以传递的参数个数不同, 通过下述三种方式使用(以jQuery.fn.extend)为例:

3.2.1 使用方式一

PS: 给jq原型添加方法. jQuery的各种plugin就是通过如此来挂载

1
2
3
4
5
$.fn.extend({
sayHello: function() {
// Do something
},
});

还有另一种方式, 那就是通过直接赋值的方式来拓展:

1
2
3
$.fn['sayHello'] = function() {
// Do something
};

3.2.2 使用方式二

PS: 合并后续对象到首个对象, 纯粹返回一个拓展后的对象, 此时与jQuery没有任何关系.

1
2
3
const origin = {};

const newOrigin = $.fn.extend(origin, { name: '' });

3.2.3 使用方式三

PS: 是否执行深层的copy操作.

1
2
3
4
5
6
7
8
9
10
11
12
const origin = {};

const newOrigin = $.fn.extend(true, origin, {
name: 'duan',
skill: {
hobby: {
favorite: {},
},
},
});

// { xxx, skill: { hobby: { favorite: {} } } }

四、细说


PS: 对于内部的全部代码逻辑, 我觉得并没有必要细看, 所以这里只选取了一种情况————单个参数, 也就是对于拓展jQuery原型or静态方法的处理.

4.1 贴源码

先来贴一下部分源码:

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
jQuery.extend = jQuery.fn.extend = function() {
var target = arguments[0] || {};
var i;
var option;
var length = arguments.length;
var name;
var src;
var copy;

// *原文注释*: Extend jQuery itself if only one arguments is passed
// *人话*: 只传递一个参数时, 拓展jQuery静态方法或实例方法
if(i === length) {
target = this;
i--;
}

// 拷贝
for(; i < length; i++) {
options = arguments[i];

for(name in options) {
copy = options[name];

if(copy !== undefined) {
target[name] = copy;
}
}
}
}

4.2 析源码

对于单个参数的处理逻辑很简单:

  • target赋值为this, this有两种情况
    • jQuery构造函数
    • jQuery实例
  • for-in遍历options
  • target赋值
    • 过滤掉target的值为undefined的情况

五、总结


jQuery提供的extend极大地方便了开发者, 同样的, var框架同样有对应的拓展方法, 这里就不说了(其实是太菜了).

当然, 最近也在挤时间看react源码, 感觉其复杂度高了几个层级, 所以还是一步一步地走下去吧!

五、示例代码


使用ts重构了我之前写的jQuery类库, 项目地址戳这里