我们小组这周分享了有关于“js 插件封装的方法”。颇有心得,就整理出了一套封装插件的固定模板公式,简称套路,百试百灵。
好插件 首先,我们先来说说怎么样封装插件,才叫一个好插件。它需要满足以下几点:
1.代码能够很好的复用 2.插件自身的作用域要与用户当前的作用域相互独立,避免各个相同功能组件的干扰 3.插件有默认参数且支持使用者修改 4.需提供针对事件的监听接口,便于使用者使用 5.支持链式调用 6.便于维护
说完了一个好插件应该具备的条件,接下来我们通过 js 和 jquery 两种方式通过实现简单加减乘除的运算来总结一下插件封装的套路把。
JS 插件最外层 1 2 3 ;(function (global, undefined) { "use strict"; })(this)
1.在最前面加了” ; “是为了防止跟其他 js 压缩时报错。 2.因为 JS 变量的调用,从全局作用域上查找速度会比私有作用域里慢的多,所以我们需要用闭包的方式。封装的时候把 js 代码放到一个自执行函数里,不仅可以延长插件内部变量的生命周期,让插件可以重复调用,还可以防止变量冲突。 3.使用严格模式,它是一种特殊的执行模式,可以修复部分语言上的不足,提供更强的错误检查,并增强安全性。 4.undefined 是为了兼容较老的浏览器。 最后传 this,而不是 window 或者 global,是因为不确定我们的兼容浏览器端和非浏览器端。直接取全局变量 this 作为局部变量使用,可缩短访问时间。
创建构造函数并初始化变量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ;(function (global, undefined) { "use strict"; //定义一个类,通常首字母大写 var Cal = function (options) { //传参 this.options = $.extend({}, Cal.DEFAULTS, options || {}); //在初始化执行一些操作 this.init(); }; //默认参数列表 Cal.DEFAULTS = { x: 1, y: 2 }; //把这个函数暴露给外部,以便全局调用 global.Cal = Cal; })(this)
1.利用 $ .extend 用来合并默认参数和用户传进来的参数
$ .extend( [deep ], target, object1 [, objectN ] ) deep:可选,默认为 false.如果该值为 true,且多个对象的某个同名属性也都是对象,则该”属性对象”的属性也将进行合并 target:Object 类型 目标对象,其他对象的成员属性将被附加到该对象上。 object1:可选。Object 类型 第一个被合并的对象。 objectN:可选。Object 类型 第 N 个被合并的对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 var item1 = { name: "Lili", age: 14, sex: "girl", address: { pro: "cc" } }; var item2 = { name: "Lucy", age: 18, address: { pro: "zh", city: "x" } }; //默认为false var result = $.extend(false,item2, item1); var resultTrue = $.extend(true, {}, item2, item1); console.log(result); //{name: "Lili", age: 14, sex: "girl",address: {pro: "cc"}} console.log(resultTrue); //{name: "Lili", age: 14, sex: "girl", address: {pro: "zh", city: "x"}}
2.插件的定义和执行都在闭包中,所以他们的作用域也就是这个闭包函数,只有绑定到全局对象上,我们才可以调用。
利用原型链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 //原型链上提供方法 Cal.prototype = { //定义方法 init() { console.log(`你输入的两个参数分别为${this.options.x},${this.options.y}`); }, add() { return `计算结果为${this.options.x + this.options.y}` }, sub() { return `计算结果为${this.options.x - this.options.y}` }, rid() { return `计算结果为${this.options.x * this.options.y}` }, div() { return `计算结果为${this.options.x / this.options.y}` } };
在 js 中,所有对象都是继承自原型的,所以对象都有一个proto 的内置属性用于指向创建它的函数对象的原型对象 prototype。有关与原型链和继承的问题,可以看看之前的文章 。一般我们都将方法写在原型里,将属性写在构造函数里。因为方法写在原型中比写在构造函数中消耗的内存更小,因为在内存中一个类的原型只有一个,写在原型中的行为可以被所有实例共享,实例化的时候并不会在实例的内存中再复制一份。而写在类中的方法,实例化的时候会在每个实例中再复制一份,所以消耗的内存更高。
兼容多种模块规范 1 2 3 4 5 6 7 8 //兼容CommonJs规范 if (typeof module !== 'undefined' && module.exports) { module.exports = Cal; }; //兼容AMD/CMD规范 if (typeof define === 'function') define(function () { return Cal; });
完整套路 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 ;(function (global, undefined) { //使用严格模式 "use strict"; //创建一个构造函数 var X = function (options) { //合并参数 this.options = $.extend({}, X.DEFAULTS, options || {}); }; X.DEFAULTS = { //默认参数 }; //原型链上提供方法 X.prototype = { //定义方法 }; //兼容CommonJs规范 if (typeof module !== 'undefined' && module.exports) { module.exports = X; }; //兼容AMD/CMD规范 if (typeof define === 'function') define(function () { return X; }); //暴露给外部 global.X = X; })(this) //调用 new X({ //参数 });
改写 ES6 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 (function (global, undefined) { //检测传入的参数类型是否为对象,若不是则抛出错误 if (arguments.length > 0 && (typeof options).toLowerCase() !== 'object') { throw new TypeError(options + 'is not a Object'); } //定义了一个名字为Cal的类 class Cal { //constructor是一个构造方法,用来接收参数 constructor(options) { //传入参数覆盖默认值 //this代表的是实例对象 this.options = Object.assign({ x: 1, y: 2 }, options); this.init(); } //这是一个类的方法,注意千万不要加上function init() { console.log(`你输入的两个参数分别为${this.options.x},${this.options.y}`); } //方法之间不要用逗号分隔,否则会报错 add() { return `计算结果为${this.options.x + this.options.y}` } sub() { return `计算结果为${this.options.x - this.options.y}` } rid() { return `计算结果为${this.options.x * this.options.y}` } div() { return `计算结果为${this.options.x / this.options.y}` } } global.Cal = Cal; })(this); var t = new Cal({ x: 5, y: 6 }); console.log(t.add()); //计算结果为11 console.log(t.sub()); //计算结果为-1 console.log(t.rid()); //计算结果为30 console.log(t.div()); //计算结果为0.8333333333333334
明显看出,ES6 中新引入了 class 关键字可以定义类。相比于 ES5,不仅代码量少了很多,写法上也更加清晰,更像是一种面向对象的语言。类自身指向的就是构造函数,所以我们可以当 class 其实是构造函数的另外一种写法。constructor 方法是类的构造函数的默认方法,当我们通过 new 生成对象实例时,会自动调用该方法。如果 construtor 方法没有显示定义,会隐式生成一个 constructor 方法且默认返回实例对象 this。constructor 中定义的属性为实例属性,也就是定义在 this 对象上的。constructor 外声明的属性是定义的原型上的。这里在书写的时候要注意两点:1.写一个类的方法,注意不要加上 function2.方法之间不要用逗号分隔
JQ 接下来我们通过 jQ 的两种使用方法 jQuery.function() 和 jQuery(“selector”)来看看如何用 jq 封装插件。
jQuery.fn 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 //利用闭包创建一个自执行的函数 (function ($) { "use strict"; function Cal(element, options) { this.$element = $(element); this.options = $.extend({}, Cal.DEFAULTS, options || {}); } function Plugin(option) { var args = Array.prototype.slice.call(arguments, 1); //链式调用 return this.each(function () { //单例模式 var $this = $(this), cal = $this.data('cal'); if (!cal) { $this.data('cal', (cal = new Cal(this, option))); } if (typeof option == 'string') { cal[option] && cal[option].apply(cal, args); } }); } $.fn.Cal = Plugin; })(jquery);
以上我们将之前写的 JS 插件改写成了 jquery 的形式。通过运用面向对象的思维方式,在 jquery 的原型上扩展方法。1.链式调用: return.this:返回当前对象,来维护插件的链式调用 .each: 循环实现每个元素的访问2.单例模式 利用 data 来存放插件对象的实例,如果实例存在,则不再重新创建
完整套路 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 //利用闭包创建一个自执行的函数 (function () { //使用严格模式 "use strict"; //定义一个类 function X(element, options) { this.$element = $(element); this.options = $.extend({}, Cal.DEFAULTS, options || {}); } X.DEFAULTS = { //默认参数 }; //原型链上提供方法 X.prototype = { //定义方法 }; function Plugin(option) { var args = Array.prototype.slice.call(arguments, 1); //链式调用 return this.each(function () { //单例模式 var $this = $(this), x = $this.data('x'); if (!x) { $this.data('x', (x = new X(this, option))); } if (typeof option == 'string') { x[option] && x[option].apply(x, args); } }); } $.fn.X = Plugin; })(); //调用插件 $("selector").X();
jQuery.extend() 1 2 3 4 5 6 7 $.extend({ x: function () { } }); //调用方式 $.x();
jQuery.extend()在上面已经详细说了用法。这种一般是为扩展 jQuery 类本身,为类添加新的方法,所以不需要生成实例,可以直接通过$ 符号调用。一般很少用这种方式封装插件。
打赏支持
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!