Fork me on GitHub

关于JS插件

很早之前就想写一个方法库,用于项目中数据处理,数据验证方面,后来写了一个,使用的是ES6的Class方法,使用import模块化,这就局限于了支持ES6的前提,而且不支持其他模块,无法静态引入,而且很多方法使用繁琐,也不是很满意,就打算研究一下JS插件的一般写法,然后做一下完善;

JS插件实现步骤

关于构造函数

最早写JS插件的时候,用的最多的是构造函数Function,使用方式基本以静态引入为主;

1
2
3
4
5
6
7
function Plugins(){

}

Plugins.prototype.add = function(){
console.log(this)
}

关于闭包

我们的plugin对象,是定义在全局域里面的。我们知道,js变量的调用,从全局作用域上找查的速度会比在私有作用域里面慢得多得多。所以,我们最好将插件逻辑写在一个私有作用域中。
实现私有作用域,最好的办法就是使用闭包。可以把插件当做一个函数,插件内部的变量及函数的私有变量,为了在调用插件后依旧能使用其功能,闭包的作用就是延长函数(插件)内部变量的生命周期,使得插件函数可以重复调用,而不影响用户自身作用域。
故需将插件的所有功能写在一个立即执行函数中:

  • 最开始的if判断是喂了防止命名冲突(最好和开发伙伴定好命名规则,比如每个方法开头标注代表字母等)
  • 在定义插件之前添加一个分号,可以解决js合并时可能会产生的错误问题;
  • undefined在老一辈的浏览器是不被支持的,直接使用会报错,js框架要考虑到兼容性,因此增加一个形参undefined,就算有人把外面的 undefined 定义了,里面的 undefined 依然不受影响;
  • 把window对象作为参数传入,是避免了函数执行的时候到外部去查找。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
;(function (w, undefined) {
if (!Plugins) {
function Plugins() {

}

Plugins.prototype.add = function () {
console.log(this)
}
w.Plugins = Plugins;

}

})(window)

关于Window

以上例子中我把window作为global顶级对象传入插件,但是如果此插件并不是用在浏览器中,比如在node中可以访问到global顶级对象;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(function (global, undefined) {

"use strict" //使用js严格模式检查,使语法更规范
var _global;
function Plugins() {

}

Plugins.prototype.add = function () {
console.log(this)
}

// 最后将插件对象暴露给全局对象
_global = (function () { return this || (0, eval)('this'); }());
!('Plugins' in _global) && (_global.Plugins = Plugins)

}())

关于(0, eval)('this')

1
2
3
var global = (function(){
return this || (0, eval)('this');
}())

在这里的(0,eval) 返回的是eval函数,在这里为什么要用(0,eval),当然,正常情况(非IE)下,可以直接使用eval(),但是在部分低版本的ie中,不可以直接运行eval,所以可以使用 (0,eval);

关于模块化

一般项目除了静态引入JS插件以外,还需要需要支持一定模块化规范common.jsAMDCMD,所以在配置插件的时候也需要注意模块化的设置

为什么需要代码模块化?

如果是多个人一起开发一个大型的插件,多人合作,肯定会产生多个文件,每个人负责一个小功能,那么如何才能将所有人开发的代码集合起来呢?这是一个讨厌的问题。要实现协作开发插件,必须具备如下条件:

  • 每功能互相之间的依赖必须要明确,则必须严格按照依赖的顺序进行合并或者加载

  • 每个子功能分别都要是一个闭包,并且将公共的接口暴露到共享域也即是一个被主函数暴露的公共对象

以上需求也可能通过按需引入JS实现,比较繁琐,而且不能实现按需加载;

综合以上,需要我们插件也实现模块化的机制,只要判断是否存在加载器,如果存在加载器,我们就使用加载器,如果不存在加载器。我们就使用顶级域对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(function (global, undefined) {

"use strict" //使用js严格模式检查,使语法更规范
var _global;
function Plugins() {

}

Plugins.prototype.add = function () {
console.log(this)
}

// 最后将插件对象暴露给全局对象
_global = (function () { return this || (0, eval)('this'); }());
if (typeof module !== "undefined" && module.exports) {
module.exports = Plugins;
} else if (typeof define === "function" && define.amd) {
define(function () { return Plugins; });
} else {
_global.Plugins = Plugins;
}

}())

jQuery插件源码

在实现插件的基本功能之后,把jquery源码抽出来一部分,主要是通过模块化、顶级对象和以上代码对比;

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
(function(global, factory) {

//严格模式
"use strict";

/* 判断是否支持common.js,可以理解为判断是否是node环境*/

if (typeof module === "object" && typeof module.exports === "object") {
// 判断顶级对象中是否有document

module.exports = global.document ?
factory(global, true) :
function(w) {
if (!w.document) {
throw new Error("jQuery requires a window with a document");
}
return factory(w);
};
} else {
factory(global);
}

})(typeof window !== "undefined" ? window : this, function(window, noGlobal) {

var version = "3.3.1",
jQuery = function(selector, context) {

return new jQuery.fn.init(selector, context);
}


jQuery.fn = jQuery.prototype = {

jquery: version,

constructor: jQuery,
}

//设置支持AMD规范

if (typeof define === "function" && define.amd) {
define("jquery", [], function() {
return jQuery;
});
}

if (!noGlobal) {
window.jQuery = window.$ = jQuery;
}
return jQuery;
});