>웹 프론트엔드 >JS 튜토리얼 >네이티브 JS를 사용하여 필요한 플러그인을 캡슐화하는 방법

네이티브 JS를 사용하여 필요한 플러그인을 캡슐화하는 방법

php中世界最好的语言
php中世界最好的语言원래의
2018-03-06 11:51:374616검색

오늘 저는 직장에서 플러그인에 대한 요구 사항에 대해 논의하고 싶습니다. 코드를 작성할 때 모든 비즈니스 또는 논리 코드를 추출하여 재사용해야 하는 것은 아닙니다. 먼저, 나중에 사용할 수 있도록 자주 반복되는 코드 중 일부를 별도의 파일로 추상화해야 하는지 확인해야 합니다. 비즈니스 로직이 팀에 도움이 될 수 있는지 다시 살펴보겠습니다. 플러그인은 무작위로 작성되지 않고 자체 비즈니스 로직에 따라 추상화됩니다. 모든 것에 적용되는 일률적인 플러그인은 없으며 플러그인만 플러그인이라고 불리는 이유는 바로 사용할 수 있거나 일부 구성 매개변수만 추가하면 되기 때문입니다. 우리에게 필요한 결과. 이러한 조건이 충족되면 플러그인 제작을 고려할 것입니다.

플러그인 캡슐화 조건

재사용 가능한 플러그인은 다음 조건을 충족해야 합니다.

플러그인 자체의 범위는 사용자의 현재 범위, 즉 플러그인 내부의 개인 변수와 독립적입니다. in은 사용자의 환경 변수에 영향을 미칠 수 없습니다.

플러그인에는 기본 설정 매개변수가 있어야 합니다.

플러그인은 구현된 기본 기능 외에도 일부 API를 제공해야 합니다. -사용자 정의 플러그인 효과를 달성하기 위해 API를 통한 기능

플러그인 체인 호출 지원

플러그인은 모니터링 입구를 제공하고 요소와 플러그인이 지정된 요소를 모니터링해야 합니다. 이에 대응하여 플러그인 효과를 달성합니다.

플러그인 캡슐화 조건은 NativeJavaScript플러그인 작성 가이드
기사를 참조하세요. 그리고 제가 설명하고 싶은 것은 플러그인 캡슐화를 단계별로 구현하는 방법입니다. 그럼 간단한 메소드 함수부터 시작하겠습니다.

플러그인의 외부 포장

은 기능으로 포장되어 있습니다.

일명 플러그인은 실제로 클로저에 캡슐화된 기능 세트입니다. 제가 처음 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;
}

좋아, 이제 필요한 모든 기능이 구현되었습니다. 그리고 우리는 이러한 함수를 js에도 작성했습니다. 단독으로 사용한다면 무엇을 정의했는지, 어떤 내용을 작성했는지, 어떤 페이지에 필요한지 명확하게 알 수 있습니다. 그러면 이 js 파일을 직접 import하면 됩니다.
그러나 2명 이상의 팀이거나 다른 사람들과 협력하여 코드를 작성하는 경우, 이때 상대방은 당신이 add 메소드를 작성했는지 여부를 알 수 없으며, 그도 동일한 add 메소드를 정의했습니다. 방법. 그러면 여러분 사이에 이름 충돌이 생길 것입니다. 이를 일반적으로 전역 변수 오염이라고 합니다.

Wrap with 전역 개체

이 전역 변수 오염 문제를 해결하려면. 이때 도구 기능을 수신하기 위해 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)

위 방법에서는 이 플러그인의 이름이 플러그인이라는 점에 동의하여 팀원들이 명명 규칙을 준수해야 하므로 지구 오염 문제가 어느 정도 해결되었습니다. 팀 협업에서는 명명 규칙에 동의하고 다른 학생들에게 알리기만 하면 됩니다. 물론, 누군가가 귀하의 프로젝트를 인수하고 이 전역 변수가 정의되었음을 알지 못하는 경우도 배제되지 않습니다. 그런 다음 그는 이를 다시 정의하고 이때 귀하의 개체를 덮어쓰게 됩니다. 물론 이름 충돌 문제를 해결하기 위해 다음과 같이 할 수도 있습니다.

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

<script type="text/javascript" src="part1.js"></script>
<script type="text/javascript" src="part2.js"></script>
<script type="text/javascript" src="part3.js"></script>...<script type="text/javascript" src="main.js"></script>

但是不推荐这么做,这样做与我们所追求的插件的封装性相背。
不过现在前端界有一堆流行的模块加载器,比如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)(&#39;this&#39;); }());
    if (typeof module !== "undefined" && module.exports) {
        module.exports = plugin;
    } else if (typeof define === "function" && define.amd) {
        define(function(){return plugin;});
    } else {
        !(&#39;plugin&#39; 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)(&#39;this&#39;); }());
    if (typeof module !== "undefined" && module.exports) {
        module.exports = plugin;
    } else if (typeof define === "function" && define.amd) {
        define(function(){return plugin;});
    } else {
        !(&#39;plugin&#39; 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: &#39;The %s field is required.&#39;,
            matches: &#39;The %s field does not match the %s field.&#39;,
            "default": &#39;The %s field is still set to default, please change.&#39;,
            valid_email: &#39;The %s field must contain a valid email address.&#39;,
            valid_emails: &#39;The %s field must contain all valid email addresses.&#39;,
            min_length: &#39;The %s field must be at least %s characters in length.&#39;,
            max_length: &#39;The %s field must not exceed %s characters in length.&#39;,
            exact_length: &#39;The %s field must be exactly %s characters in length.&#39;,
            greater_than: &#39;The %s field must contain a number greater than %s.&#39;,
            less_than: &#39;The %s field must contain a number less than %s.&#39;,
            alpha: &#39;The %s field must only contain alphabetical characters.&#39;,
            alpha_numeric: &#39;The %s field must only contain alpha-numeric characters.&#39;,
            alpha_dash: &#39;The %s field must only contain alpha-numeric characters, underscores, and dashes.&#39;,
            numeric: &#39;The %s field must contain only numbers.&#39;,
            integer: &#39;The %s field must contain an integer.&#39;,
            decimal: &#39;The %s field must contain a decimal number.&#39;,
            is_natural: &#39;The %s field must contain only positive numbers.&#39;,
            is_natural_no_zero: &#39;The %s field must contain a number greater than zero.&#39;,
            valid_ip: &#39;The %s field must contain a valid IP.&#39;,
            valid_base64: &#39;The %s field must contain a base64 string.&#39;,
            valid_credit_card: &#39;The %s field must contain a valid credit card number.&#39;,
            is_file_type: &#39;The %s field must contain only %s files.&#39;,
            valid_url: &#39;The %s field must contain a valid URL.&#39;,
            greater_than_date: &#39;The %s field must contain a more recent date than %s.&#39;,
            less_than_date: &#39;The %s field must contain an older date than %s.&#39;,
            greater_than_or_equal_date: &#39;The %s field must contain a date that\&#39;s at least as recent as %s.&#39;,
            less_than_or_equal_date: &#39;The %s field must contain a date that\&#39;s %s or older.&#39;
        },
        callback: function(errors) {
        }
    };
    var ruleRegex = /^(.+?)\[(.+)\]$/,
        numericRegex = /^[0-9]+$/,
        integerRegex = /^\-?[0-9]+$/,
        decimalRegex = /^\-?[0-9]*\.?[0-9]+$/,
        emailRegex = /^[a-zA-Z0-9.!#$%&&#39;*+/=?^_`{|}~-]+@[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 !== &#39;undefined&#39; && module.exports) {
    module.exports = FormValidator;
}

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

var validator = new FormValidator(&#39;example_form&#39;, [{
    name: &#39;req&#39;,
    display: &#39;required&#39;,
    rules: &#39;required&#39;
}, {
    name: &#39;alphanumeric&#39;,
    rules: &#39;alpha_numeric&#39;
}, {
    name: &#39;password&#39;,
    rules: &#39;required&#39;
}, {
    name: &#39;password_confirm&#39;,
    display: &#39;password confirmation&#39;,
    rules: &#39;required|matches[password]&#39;
}, {
    name: &#39;email&#39;,
    rules: &#39;valid_email&#39;
}, {
    name: &#39;minlength&#39;,
    display: &#39;min length&#39;,
    rules: &#39;min_length[8]&#39;
}, {
    names: [&#39;fname&#39;, &#39;lname&#39;],
    rules: &#39;required|alpha&#39;
}], 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),方法则直接叫钩子函数。这是一种形象生动的说法,就好像我们在一条绳子上放很多挂钩,我们可以按需要在上面挂东西。
实际上,我们即知道插件可以像一条绳子上挂东西,也可以拿掉挂的东西。那么一个插件,实际上就是个形象上的链。不过我们上面的所有钩子都是挂在对象上的,用于实现链并不是很理想。

相信看了这些案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!

相关阅读:

禁止页面缓存有哪些方法

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

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

meta的标签有哪些作用

위 내용은 네이티브 JS를 사용하여 필요한 플러그인을 캡슐화하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.