首頁 >web前端 >js教程 >javascript函數的五個運用技巧介紹

javascript函數的五個運用技巧介紹

不言
不言轉載
2018-11-17 15:35:042136瀏覽

這篇文章帶給大家的內容是關於javascript函數的五個運用技巧介紹,有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

函數對任何一門語言來說都是一個核心的概念,在javascript中更是如此。本文將介紹函數的5個進階技巧

作用域安全的建構子

#建構子其實就是一個使用new運算子呼叫的函式

function Person(name,age,job){
    this.name=name;    
    this.age=age;    
    this.job=job;
}
var person=new Person('match',28,'Software Engineer');
console.log(person.name);//match

如果沒有使用new運算符,原本針對Person物件的三個屬性被加入到window物件

function Person(name,age,job){
    this.name=name;    
    this.age=age;    
    this.job=job;
}          
var person=Person('match',28,'Software Engineer');
console.log(person);//undefinedconsole.log(window.name);//match

window的name屬性是用來標識連結目標和框架的,這裡對該屬性的偶然覆蓋可能會導致頁面上的其它錯誤,這個問題的解決方法就是創建一個作用域安全的建構子

function Person(name,age,job){
    if(this instanceof Person){
            this.name=name;        
            this.age=age;        
            this.job=job;
    }else{
            return new Person(name,age,job);
    }
}var person=Person('match',28,'Software Engineer');
console.log(window.name); // ""console.log(person.name); //'match'var person= new Person('match',28,'Software Engineer');
console.log(window.name); // ""console.log(person.name); //'match'

但是,對建構函式竊取模式的繼承,會帶來副作用。這是因為,在下列程式碼中,this物件並非Polygon物件實例,所以建構子Polygon()會建立並傳回一個新的實例

function Polygon(sides){
    if(this instanceof Polygon){
            this.sides=sides;        
            this.getArea=function(){
                        return 0;
        }
    }else{
            return new Polygon(sides);
    }
}
function  Rectangle(wifth,height){
    Polygon.call(this,2);    
    this.width=this.width;    
    this.height=height;    
    this.getArea=function(){        
    return this.width * this.height;
    };
}
var rect= new Rectangle(5,10);
console.log(rect.sides); //undefined

如果要使用作用域安全的建構函式竊取模式的話,需要結合原型鏈繼承,重寫Rectangle的prototype屬性,使它的實例也變成Polygon的實例

function Polygon(sides){
    if(this instanceof Polygon){
            this.sides=sides;        
            this.getArea=function(){
                        return 0;
        }
    }else{
            return new Polygon(sides);
    }
}
function  Rectangle(wifth,height){
    Polygon.call(this,2);    
    this.width=this.width;    
    this.height=height;    
    this.getArea=function(){
            return this.width * this.height;
    };
}
Rectangle.prototype= new Polygon();var rect= new Rectangle(5,10);
console.log(rect.sides); //2

惰性載入函數

因為各瀏覽器之間的行為的差異,我們經常在函數中包含了大量的if語句,以檢查瀏覽器特性,解決不同瀏覽器的兼容問題。例如,我們最常見的為dom節點新增事件的函數

function addEvent(type, element, fun) {
    if (element.addEventListener) {
        element.addEventListener(type, fun, false);
    }    
    else if(element.attachEvent){
        element.attachEvent('on' + type, fun);
    }    else{
        element['on' + type] = fun;
    }
}

每次呼叫addEvent函數的時候,它都要對瀏覽器所支援的能力進行檢查,首先檢查是否支援addEventListener方法,如果不支持,再檢查是否支持attachEvent方法,如果還不支持,就用dom0級的方法添加事件。這個過程,在addEvent函數每次呼叫的時候都要走一遍,其實,如果瀏覽器支援其中的一種方法,那麼他就會一直支援了,就沒有必要再進行其他分支的偵測了。也就是說,if語句不必每次都執行,程式碼可以運行的更快一些。

  解決方案就是惰性載入。所謂惰性載入,指函數執行的分支只會發生一次,有兩種實現惰性載入的方式

  1、第一種是在函數被呼叫時,再處理函數。函數在第一次呼叫時,函數會被覆寫為另一個以適當方式執行的函數,這樣任何對原函數的呼叫都不用再經過執行的分支了

  我們可以用下面的方式使用惰性載入重寫addEvent()

function addEvent(type, element, fun) {
    if (element.addEventListener) {
        addEvent = function (type, element, fun) {
            element.addEventListener(type, fun, false);
        }
    }    
    else if(element.attachEvent){
        addEvent = function (type, element, fun) {
            element.attachEvent('on' + type, fun);
        }
    }    
    else{
        addEvent = function (type, element, fun) {
            element['on' + type] = fun;
        }
    }    
    return addEvent(type, element, fun);
}

在這個惰性載入的addEvent()中,if語句的每個分支都會為addEvent變數賦值,有效覆寫了原函數。最後一步便是呼叫了新賦函數。下次呼叫addEvent()時,就會直接呼叫新賦值的函數,這樣就不用再執行if語句了

  但是,這種方法有個缺點,如果函數名稱有所改變,修改起來比較麻煩

  2、第二種是宣告函數時就指定適當的函數。這樣在第一次呼叫函數時就不會損失效能了,只在程式碼載入時會損失一點效能

  以下就是依照這個想法重寫的addEvent()。以下程式碼建立了一個匿名的自執行函數,透過不同的分支以決定應該使用哪個函數實作

var addEvent = (function () {
    if (document.addEventListener) {
            return function (type, element, fun) {
            element.addEventListener(type, fun, false);
        }
    }    
    else if (document.attachEvent) {
            return function (type, element, fun) {
            element.attachEvent('on' + type, fun);
        }
    }    
    else { 
           return function (type, element, fun) {
            element['on' + type] = fun;
        }
    }
})();

函數綁定

在javascript與DOM互動中經常需要使用函數綁定,定義一個函數然後將其綁定到特定DOM元素或集合的某個事件觸發程序上,綁定函數經常和回調函數及事件處理程序一起使用,以便把函數作為變量傳遞的同時保留程式碼執行環境

<button id="btn">按钮</button><script>            
    var handler={
        message:"Event handled.",
        handlerFun:function(){
            alert(this.message);
        }
    };
btn.onclick = handler.handlerFun;
</script>

上面的程式碼建立了一個叫做handler的物件。 handler.handlerFun()方法被指派為一個DOM按鈕的事件處理程序。當按下該按鈕時,就呼叫該函數,顯示一個警告框。雖然看起來像警告框應該顯示Event handled,然而實際上顯示的是undefiend。這個問題在於沒有儲存handler.handleClick()的環境,所以this物件最後是指向了DOM按鈕而非handler

  可以用閉包來修正這個問題

<button id="btn">按钮</button>
<script>            
var handler={
    message:"Event handled.",
    handlerFun:function(){
        alert(this.message);
    }
};
btn.onclick = function(){
    handler.handlerFun();    
}
</script>

當然這是特定於此場景的解決方案,建立多個閉包可能會令程式碼難以理解和除錯。更好的方法是使用函數綁定

  一個簡單的綁定函數bind()接受一個函數和一個環境,​​並傳回一個在給定環境中呼叫給定函數的函數,並且將所有參數原封不動傳遞過去

function bind(fn,context){
    return function(){        
    return fn.apply(context,arguments);
    }
}

這個函數似乎簡單,但其功能是非常強大的。在bind()中建立了一個閉包,閉包使用apply()呼叫傳入的函數,並給apply()傳遞context物件和參數。當呼叫傳回的函數時,它會在給定環境中執行被傳入的函數並給出所有參數

<button id="btn">按钮</button><script>  function bind(fn,context){    return function(){        return fn.apply(context,arguments);
    }
}          
var handler={
    message:"Event handled.",
    handlerFun:function(){
        alert(this.message);
    }
};
btn.onclick = bind(handler.handlerFun,handler);
</script>

ECMAScript5為所有函數定義了一個原生的bind()方法,進一步簡化了操作

  只要是将某个函数指针以值的形式进行传递,同时该函数必须在特定环境中执行,被绑定函数的效用就突显出来了。它们主要用于事件处理程序以及setTimeout()和setInterval()。然而,被绑定函数与普通函数相比有更多的开销,它们需要更多内存,同时也因为多重函数调用稍微慢一点,所以最好只在必要时使用

函数柯里化

与函数绑定紧密相关的主题是函数柯里化(function currying),它用于创建已经设置好了一个或多个参数的函数。函数柯里化的基本方法和函数绑定是一样的:使用一个闭包返回一个函数。两者的区别在于,当函数被调用时,返回的函数还需要设置一些传入的参数

function add(num1,num2){
    return num1+num2;
}function curriedAdd(num2){
    return add(5,num2);
}
console.log(add(2,3));//5console.log(curriedAdd(3));//8

这段代码定义了两个函数:add()和curriedAdd()。后者本质上是在任何情况下第一个参数为5的add()版本。尽管从技术来说curriedAdd()并非柯里化的函数,但它很好地展示了其概念

  柯里化函数通常由以下步骤动态创建:调用另一个函数并为它传入要柯里化的函数和必要参数。下面是创建柯里化函数的通用方式

function curry(fn){
    var args = Array.prototype.slice.call(arguments, 1);    
    return function(){        
    var innerArgs = Array.prototype.slice.call(arguments),
        finalArgs = args.concat(innerArgs);        
        return fn.apply(null, finalArgs);
    };
}

curry()函数的主要工作就是将被返回函数的参数进行排序。curry()的第一个参数是要进行柯里化的函数,其他参数是要传入的值。为了获取第一个参数之后的所有参数,在arguments对象上调用了slice()方法,并传入参数1表示被返回的数组包含从第二个参数开始的所有参数。然后args数组包含了来自外部函数的参数。在内部函数中,创建了innerArgs数组用来存放所有传入的参数(又一次用到了slice())。有了存放来自外部函数和内部函数的参数数组后,就可以使用concat()方法将它们组合为finalArgs,然后使用apply()将结果传递给函数。注意这个函数并没有考虑到执行环境,所以调用apply()时第一个参数是null。curry()函数可以按以下方式应用

function add(num1, num2){    
return num1 + num2;
}var curriedAdd = curry(add, 5);
alert(curriedAdd(3)); //8

在这个例子中,创建了第一个参数绑定为5的add()的柯里化版本。当调用cuurriedAdd()并传入3时,3会成为add()的第二个参数,同时第一个参数依然是5,最后结果便是和8。也可以像下例这样给出所有的函数参数:

function add(num1, num2){
    return num1 + num2;
}
var curriedAdd2 = curry(add, 5, 12);
alert(curriedAdd2()); //17

在这里,柯里化的add()函数两个参数都提供了,所以以后就无需再传递给它们了

函数柯里化还常常作为函数绑定的一部分包含在其中,构造出更为复杂的bind()函数

function bind(fn, context){
    var args = Array.prototype.slice.call(arguments, 2);    
    return function(){        
    var innerArgs = Array.prototype.slice.call(arguments),
        finalArgs = args.concat(innerArgs);        
        return fn.apply(context, finalArgs);
    };
}

对curry()函数的主要更改在于传入的参数个数,以及它如何影响代码的结果。curry()仅仅接受一个要包裹的函数作为参数,而bind()同时接受函数和一个object对象。这表示给被绑定的函数的参数是从第三个开始而不是第二个,这就要更改slice()的第一处调用。另一处更改是在倒数第3行将object对象传给apply()。当使用bind()时,它会返回绑定到给定环境的函数,并且可能它其中某些函数参数已经被设好。要想除了event对象再额外给事件处理程序传递参数时,这非常有用

var handler = {
    message: "Event handled",
    handleClick: function(name, event){
        alert(this.message + ":" + name + ":" + event.type);
    }
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler, "my-btn"));

handler.handleClick()方法接受了两个参数:要处理的元素的名字和event对象。作为第三个参数传递给bind()函数的名字,又被传递给了handler.handleClick(),而handler.handleClick()也会同时接收到event对象

ECMAScript5的bind()方法也实现函数柯里化,只要在this的值之后再传入另一个参数即可

var handler = {
    message: "Event handled",
    handleClick: function(name, event){
        alert(this.message + ":" + name + ":" + event.type);
    }
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler, "my-btn"));

javaScript中的柯里化函数和绑定函数提供了强大的动态函数创建功能。使用bind()还是curry()要根据是否需要object对象响应来决定。它们都能用于创建复杂的算法和功能,当然两者都不应滥用,因为每个函数都会带来额外的开销

函数重写

由于一个函数可以返回另一个函数,因此可以用新的函数来覆盖旧的函数

function a(){
    console.log('a');
    a = function(){
        console.log('b');
    }
}

这样一来,当我们第一次调用该函数时会console.log('a')会被执行;全局变量a被重定义,并被赋予新的函数

当该函数再次被调用时, console.log('b')会被执行

再复杂一点的情况如下所示

var a = (function(){
    function someSetup(){
            var setup = 'done';
    }
    function actualWork(){
        console.log('work');
    }
    someSetup();    
    return actualWork;
})()

我们使用了私有函数someSetup()和actualWork(),当函数a()第一次被调用时,它会调用someSetup(),并返回函数actualWork()的引用.

以上是javascript函數的五個運用技巧介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:segmentfault.com。如有侵權,請聯絡admin@php.cn刪除