怎样用原生JS封装自己需要的插件

今天想和大家讨论一下我们在工作中对于插件的需求,我们写代码,并不是所有的业务或者逻辑代码都要抽出来复用。首先,我们得看一下是否需要将一部分经常重复的代码抽象出来,写到一个单独的文件中为以后再次使用。再看一下我们的业务逻辑是否可以为团队服务。插件不是随手就写成的,而是根据自己业务逻辑进行抽象。没有放之四海而皆准的插件,只有对插件,之所以叫做插件,那么就是开箱即用,或者我们只要添加一些配置参数就可以达到我们需要的结果。如果都符合了这些情况,我们才去考虑做一个插件。

插件封装的条件

一个可复用的插件需要满足以下条件:

插件自身的作用域与用户当前的作用域相互独立,也就是插件内部的私有变量不能影响使用者的环境变量;

插件需具备默认设置参数;

插件除了具备已实现的基本功能外,需提供部分API,使用者可以通过该API修改插件功能的默认参数,从而实现用户自定义插件效果;

插件支持链式调用;

插件需提供监听入口,及针对指定元素进行监听,使得该元素与插件响应达到插件效果。

关于插件封装的条件,可以查看一篇文章:原生JavaScript插件编写指南
而我想要说明的是,如何一步一步地实现我的插件封装。所以,我会先从简单的方法函数来做起。

插件的外包装

用函数包装

所谓插件,其实就是封装在一个闭包中的一种函数集。我记得刚开始写js的时候,我是这样干的,将我想要的逻辑,写成一个函数,然后再根据不同需要传入不同的参数就可以了。
比如,我想实现两个数字相加的方法:

function add(n1,n2) {    return n1 + n2;}// 调用add(1,2)// 输出:3

登录后复制

这就是我们要的功能的简单实现。如果仅仅只不过实现这么简单的逻辑,那已经可以了,没必要弄一些花里胡哨的东西。js函数本身就可以解决绝大多数的问题。不过我们在实际工作与应用中,一般情况的需求都是比较复杂得多。
如果这时,产品来跟你说,我不仅需要两个数相加的,我还要相减,相乘,相除,求余等等功能。这时候,我们怎么办呢?
当然,你会想,这有什么难的。直接将这堆函数都写出来不就完了。然后都放在一个js文件里面。需要的时候,就调用它就好了。

// 加function add(n1,n2) {    return n1 + n2;}// 减function sub(n1,n2) {    return n1 - n2;}// 乘function mul(n1,n2) {    return n1 * n2;}// 除function div(n1,n2) {    return n1 / n2;}// 求余function sur(n1,n2) {    return n1 % n2;}

登录后复制

OK,现在已经实现我们所需要的所有功能。并且我们也把这些函数都写到一个js里面了。如果是一个人在用,那么可以很清楚知道自己是否已经定义了什么,并且知道自己写了什么内容,我在哪个页面需要,那么就直接引入这个js文件就可以搞定了。
不过,如果是两个人以上的团队,或者你与别人一起协作写代码,这时候,另一个人并不知道你是否写了add方法,这时他也定义了同样的add方法。那么你们之间就会产生命名冲突,一般称之为变量的 全局污染

用全局对象包装

为了解决这种全局变量污染的问题。这时,我们可以定义一个js对象来接收我们这些工具函数。

var plugin = {    add: function(n1,n2){...},//加    sub: function(n1,n2){...},//减    mul: function(n1,n2){...},//乘    div: function(n1,n2){...},//除    sur: function(n1,n2){...} //余}// 调用plugin.add(1,2)

登录后复制

上面的方式,约定好此插件名为plugin,让团队成员都要遵守命名规则,在一定程度上已经解决了全局污染的问题。在团队协作中只要约定好命名规则了,告知其它同学即可以。当然不排除有个别人,接手你的项目,并不知道此全局变量已经定义,则他又定义了一次并赋值,这时,就会把你的对象覆盖掉。当然,可能你会这么干来解决掉命名冲突问题:

if(!plugin){ //这里的if条件也可以用: (typeof plugin == 'undefined')    var plugin = {        // 以此写你的函数逻辑    }}

登录后复制

或者

var plugin;if(!plugin){    plugin = {        // ...    }}

登录后复制

这样子,就不会存在命名上的冲突了。

基本上,这就可以算是一个插件了。解决了全局污染问题,方法函数可以抽出来放到一单独的文件里面去。

利用闭包包装

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

;(function(global,undefined) {    var plugin = {        add: function(n1,n2){...}        ...    }    // 最后将插件对象暴露给全局对象    'plugin' in global && global.plugin = plugin;})(window);

登录后复制

对上面的代码段传参问题进行解释一下:

在定义插件之前添加一个分号,可以解决js合并时可能会产生的错误问题;

undefined在老一辈的浏览器是不被支持的,直接使用会报错,js框架要考虑到兼容性,因此增加一个形参undefined,就算有人把外面的 undefined 定义了,里面的 undefined 依然不受影响;

把window对象作为参数传入,是避免了函数执行的时候到外部去查找。

其实,我们觉得直接传window对象进去,我觉得还是不太妥当。我们并不确定我们的插件就一定用于浏览器上,也有可能使用在一些非浏览端上。所以我们还可以这么干,我们不传参数,直接取当前的全局this对象为作顶级对象用。

;(function(global,undefined) {    "use strict" //使用js严格模式检查,使语法更规范    var _global;    var plugin = {        add: function(n1,n2){...}        ...    }    // 最后将插件对象暴露给全局对象    _global = (function(){ return this || (0, eval)('this'); }());    !('plugin' in _global) && (_global.plugin = plugin);}());

登录后复制

如此,我们不需要传入任何参数,并且解决了插件对环境的依事性。如此我们的插件可以在任何宿主环境上运行了。

关于立即自执行函数,有两种写法:

/ 写法一
(function(){})()//

写法二

(function(){}())

使用模块化的规范包装

虽然上面的包装基本上已经算是ok了的。但是如果是多个人一起开发一个大型的插件,这时我们要该怎么办呢?多人合作,肯定会产生多个文件,每个人负责一个小功能,那么如何才能将所有人开发的代码集合起来呢?这是一个讨厌的问题。要实现协作开发插件,必须具备如下条件:

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

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

关键如何实现,有很多种办法。最笨的办法就是按顺序加载js

...

登录后复制

但是不推荐这么做,这样做与我们所追求的插件的封装性相背。
不过现在前端界有一堆流行的模块加载器,比如require、seajs,或者也可以像类似于Node的方式进行加载,不过在浏览器端,我们还得利用打包器来实现模块加载,比如browserify。不过在此不谈如何进行模块化打包或者加载的问题,如有问题的同学可以去上面的链接上看文档学习。
为了实现插件的模块化并且让我们的插件也是一个模块,我们就得让我们的插件也实现模块化的机制。
我们实际上,只要判断是否存在加载器,如果存在加载器,我们就使用加载器,如果不存在加载器。我们就使用顶级域对象。

if (typeof module !== "undefined" && module.exports) {    module.exports = plugin;} else if (typeof define === "function" && define.amd) {    define(function(){return plugin;});} else {    _globals.plugin = plugin;}

登录后复制

这样子我们的完整的插件的样子应该是这样子的:

// plugin.js;(function(undefined) {    "use strict"    var _global;    var plugin = {        add: function(n1,n2){ return n1 + n2; },//加        sub: function(n1,n2){ return n1 - n2; },//减        mul: function(n1,n2){ return n1 * n2; },//乘        div: function(n1,n2){ return n1 / n2; },//除        sur: function(n1,n2){ return n1 % n2; } //余    }    // 最后将插件对象暴露给全局对象    _global = (function(){ return this || (0, eval)('this'); }());    if (typeof module !== "undefined" && module.exports) {        module.exports = plugin;    } else if (typeof define === "function" && define.amd) {        define(function(){return plugin;});    } else {        !('plugin' in _global) && (_global.plugin = plugin);    }}());

登录后复制

我们引入了插件之后,则可以直接使用plugin对象。

with(plugin){    console.log(add(2,1)) // 3    console.log(sub(2,1)) // 1    console.log(mul(2,1)) // 2    console.log(div(2,1)) // 2    console.log(sur(2,1)) // 0}

登录后复制

插件的API

插件的默认参数

我们知道,函数是可以设置默认参数这种说法,而不管我们是否传有参数,我们都应该返回一个值以告诉用户我做了怎样的处理,比如:

function add(param){            var args = !!param ? Array.prototype.slice.call(arguments) : [];            return args.reduce(function(pre,cur){                    return pre + cur;    }, 0);}console.log(add()) //不传参,结果输出0,则这里已经设置了默认了参数为空数组console.log(add(1,2,3,4,5)) //传参,结果输出15

登录后复制

则作为一个健壮的js插件,我们应该把一些基本的状态参数添加到我们需要的插件上去。
假设还是上面的加减乘除余的需求,我们如何实现插件的默认参数呢?道理其实是一样的。

// plugin.js;(function(undefined) {    "use strict"    var _global;    function result(args,fn){        var argsArr = Array.prototype.slice.call(args);        if(argsArr.length > 0){            return argsArr.reduce(fn);        } else {            return 0;        }    }    var plugin = {        add: function(){            return result(arguments,function(pre,cur){                return pre + cur;            });        },//加        sub: function(){            return result(arguments,function(pre,cur){                return pre - cur;            });        },//减        mul: function(){            return result(arguments,function(pre,cur){                return pre * cur;            });        },//乘        div: function(){            return result(arguments,function(pre,cur){                return pre / cur;            });        },//除        sur: function(){            return result(arguments,function(pre,cur){                return pre % cur;            });        } //余    }    // 最后将插件对象暴露给全局对象    _global = (function(){ return this || (0, eval)('this'); }());    if (typeof module !== "undefined" && module.exports) {        module.exports = plugin;    } else if (typeof define === "function" && define.amd) {        define(function(){return plugin;});    } else {        !('plugin' in _global) && (_global.plugin = plugin);    }}());// 输出结果为:with(plugin){    console.log(add()); // 0    console.log(sub()); // 0    console.log(mul()); // 0    console.log(div()); // 0    console.log(sur()); // 0    console.log(add(2,1)); // 3    console.log(sub(2,1)); // 1    console.log(mul(2,1)); // 2    console.log(div(2,1)); // 2    console.log(sur(2,1)); // 0}

登录后复制

实际上,插件都有自己的默认参数,就以我们最为常见的表单验证插件为例:validate.js

(function(window, document, undefined) {    // 插件的默认参数    var defaults = {        messages: {            required: 'The %s field is required.',            matches: 'The %s field does not match the %s field.',            "default": 'The %s field is still set to default, please change.',            valid_email: 'The %s field must contain a valid email address.',            valid_emails: 'The %s field must contain all valid email addresses.',            min_length: 'The %s field must be at least %s characters in length.',            max_length: 'The %s field must not exceed %s characters in length.',            exact_length: 'The %s field must be exactly %s characters in length.',            greater_than: 'The %s field must contain a number greater than %s.',            less_than: 'The %s field must contain a number less than %s.',            alpha: 'The %s field must only contain alphabetical characters.',            alpha_numeric: 'The %s field must only contain alpha-numeric characters.',            alpha_dash: 'The %s field must only contain alpha-numeric characters, underscores, and dashes.',            numeric: 'The %s field must contain only numbers.',            integer: 'The %s field must contain an integer.',            decimal: 'The %s field must contain a decimal number.',            is_natural: 'The %s field must contain only positive numbers.',            is_natural_no_zero: 'The %s field must contain a number greater than zero.',            valid_ip: 'The %s field must contain a valid IP.',            valid_base64: 'The %s field must contain a base64 string.',            valid_credit_card: 'The %s field must contain a valid credit card number.',            is_file_type: 'The %s field must contain only %s files.',            valid_url: 'The %s field must contain a valid URL.',            greater_than_date: 'The %s field must contain a more recent date than %s.',            less_than_date: 'The %s field must contain an older date than %s.',            greater_than_or_equal_date: 'The %s field must contain a date that's at least as recent as %s.',            less_than_or_equal_date: 'The %s field must contain a date that's %s or older.'        },        callback: function(errors) {        }    };    var ruleRegex = /^(.+?)[(.+)]$/,        numericRegex = /^[0-9]+$/,        integerRegex = /^-?[0-9]+$/,        decimalRegex = /^-?[0-9]*.?[0-9]+$/,        emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/,        alphaRegex = /^[a-z]+$/i,        alphaNumericRegex = /^[a-z0-9]+$/i,        alphaDashRegex = /^[a-z0-9_-]+$/i,        naturalRegex = /^[0-9]+$/i,        naturalNoZeroRegex = /^[1-9][0-9]*$/i,        ipRegex = /^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2}).){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})$/i,        base64Regex = /[^a-zA-Z0-9/+=]/i,        numericDashRegex = /^[d-s]+$/,        urlRegex = /^((http|https)://(w+:{0,1}w*@)?(S+)|)(:[0-9]+)?(/|/([w#!:.?+=&%@!-/]))?$/,        dateRegex = /d{4}-d{1,2}-d{1,2}/;    ... //省略后面的代码})(window,document);/* * Export as a CommonJS module */if (typeof module !== 'undefined' && module.exports) {    module.exports = FormValidator;}

登录后复制

当然,参数既然是默认的,那就意味着我们可以随意修改参数以达到我们的需求。插件本身的意义就在于具有复用性。
如表单验证插件,则就可以new一个对象的时候,修改我们的默认参数:

var validator = new FormValidator('example_form', [{    name: 'req',    display: 'required',    rules: 'required'}, {    name: 'alphanumeric',    rules: 'alpha_numeric'}, {    name: 'password',    rules: 'required'}, {    name: 'password_confirm',    display: 'password confirmation',    rules: 'required|matches[password]'}, {    name: 'email',    rules: 'valid_email'}, {    name: 'minlength',    display: 'min length',    rules: 'min_length[8]'}, {    names: ['fname', 'lname'],    rules: 'required|alpha'}], function(errors) {    if (errors.length > 0) {        // Show the errors    }});

登录后复制

插件的钩子

我们知道,设计一下插件,参数或者其逻辑肯定不是写死的,我们得像函数一样,得让用户提供自己的参数去实现用户的需求。则我们的插件需要提供一个修改默认参数的入口。
如上面我们说的修改默认参数,实际上也是插件给我们提供的一个API。让我们的插件更加的灵活。如果大家对API不了解,可以百度一下API
通常我们用的js插件,实现的方式会有多种多样的。最简单的实现逻辑就是一个方法,或者一个js对象,又或者是一个构造函数等等。
** 然我们插件所谓的API,实际就是我们插件暴露出来的所有方法及属性。 **
我们需求中,加减乘除余插件中,我们的API就是如下几个方法:

...var plugin = {    add: function(n1,n2){ return n1 + n2; },    sub: function(n1,n2){ return n1 - n2; },    mul: function(n1,n2){ return n1 * n2; },    div: function(n1,n2){ return n1 / n2; },    sur: function(n1,n2){ return n1 % n2; } }...

登录后复制

可以看到plubin暴露出来的方法则是如下几个API:

add

sub

mul

div

sur

在插件的API中,我们常常将容易被修改和变动的方法或属性统称为钩子(Hook),方法则直接叫钩子函数。这是一种形象生动的说法,就好像我们在一条绳子上放很多挂钩,我们可以按需要在上面挂东西。
实际上,我们即知道插件可以像一条绳子上挂东西,也可以拿掉挂的东西。那么一个插件,实际上就是个形象上的链。不过我们上面的所有钩子都是挂在对象上的,用于实现链并不是很理想。

相信看了这些案例你已经掌握了方法,更多精彩请关注【创想鸟】其它相关文章!

相关阅读:

禁止页面缓存有哪些方法

在html里怎么添加flash视频格式(flv、swf)文件

怎样通过disabled和readonly将input设置为只读效果

meta的标签有哪些作用

以上就是怎样用原生JS封装自己需要的插件的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2785023.html

(0)
上一篇 2025年3月8日 17:18:42
下一篇 2025年3月2日 22:54:21

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • JS闭包的特性分析

    在body标签中,添加一个u标签,其id为node,ul标签下面有5个li标签。如何在点击不同的li 标签时,弹出其li下标? 1)首先需要获得ul节点  var  node = document.getelementsbyid(‘node…

    编程技术 2025年3月8日
    200
  • js原型详细介绍

    如果大家对js中的原型对象以及prototype属性十分熟悉的话对后面原型链以及继承的理解会十分的容易,这里想和大家分享自己对其的理解,  1  function Person(){ 2 } 3 Person.prototype.name …

    2025年3月8日
    200
  • js中Promise对象实例详解

    假设我现在有个实名验证页面,需要验证身份证号码和真实姓名,实名认证是到公安部系统验证(假设在前端验证),验证通过后我再将当前表单信息保存到提交到我自己后台保存; 方法一 不用promise //外层ajax,校验实名信息$.ajax({  …

    编程技术 2025年3月8日
    200
  • JS面向对象用法实例

    对象是包含了属性和方法的集合体。什么是面向对象呢? 面向对象就是一种编程思想,是一个概念。  在js中,通过一种叫做原型的方式来实现面向对象编程。 nbsp;html>        创建对象点击显示学生信息 //创建一个学生对象 v…

    编程技术 2025年3月8日
    200
  • js深拷贝实例探讨

    深度拷贝就是把父对象拷贝到子对象上,而且两者的内存和以后的操作都互不影响的拷贝,本文主要和大家分享js深拷贝实例探讨,希望能帮助到大家。 (1)方法1 function copy(obj1,obj2){  var obj2=obj2||{}…

    编程技术 2025年3月8日
    200
  • 监听JS变量的变化方法实例

    我现在有这样一个需求,需要监控js的某个变量的改变, 如果该变量发生变化,则触发一些事件, 不能使用timeinterval之类的定时去监控的方法, 不知道有比较好的解决方案么? 流行的MVVM的JS库/框架都有共同的特点就是数据绑定, 在…

    编程技术 2025年3月8日
    200
  • JS原生对象与内置对象区别详解

    一、JS的对象和类型 js中的所有事物都是对象,包括但不限于字符串、数值、数组、函数等等,还包括自定义对象。在红宝书中,将js分为五种基本类型:null、undefined、number、string、boolean和一种复杂类型:obje…

    2025年3月8日
    200
  • 几种js继承的式分享

    本文主要和大家分享几种js继承的式分别有原型继承、借用构造函数继承、组合继承、寄生式继承、寄生组合方式继承,希望能帮助到大家。 原型式继承 可以在不必预先定义构造函数的情况下实现继承,其本质是执行给定对象的浅复制。而复制得到的副本还可以得到…

    编程技术 2025年3月8日
    200
  • Mock.JS拦截HTTP请求实例解析

    mockjs是一种比较通用的前端模拟http请求及回复的工具,能够仿真处各类http的请求及返回结果。实现在无后端的情况下,前端对于后端接口的仿真。mock的基础使用也是比较简单,本文我们主要和大家分享mock.js拦截http请求实例解析…

    编程技术 2025年3月8日
    200
  • js中table数据导出excel文件

    本文主要和大家分享一些代码,主要和大家分享js中table数据导出excel文件的方法实例,希望能帮助到大家。 表格转换成excel并下载 (document).ready(function () {(“#myBtn”).click(fun…

    编程技术 2025年3月8日
    200

发表回复

登录后才能评论