首頁 web前端 js教程 分享5個JS函數的高階技巧

分享5個JS函數的高階技巧

Oct 26, 2020 pm 05:57 PM
javascript 函數

分享5個JS函數的高階技巧

函數是由事件驅動的或是當它被呼叫時執行的可重複使用的程式碼區塊。函數對任何一門語言來說都是一個核心的概念,在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 Polygo
登入後複製

惰性載入函數

因為各瀏覽器之間的行為的差異,我們經常在函數中包含了大量的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)); //5
console.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(&#39;a&#39;);
    a = function(){
        console.log(&#39;b&#39;);
    }
}
登入後複製

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

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

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

var a = (function() {
    function someSetup() {
        var setup = &#39;done&#39;;
    }
    function actualWork() {
        console.log(&#39;work&#39;);
    }
    someSetup();
    return actualWork;
})()
登入後複製

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

相关免费学习推荐:js视频教程

更多编程相关知识,请访问:编程入门!!

以上是分享5個JS函數的高階技巧的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1662
14
CakePHP 教程
1419
52
Laravel 教程
1311
25
PHP教程
1261
29
C# 教程
1234
24
golang函數動態建立新函數的技巧 golang函數動態建立新函數的技巧 Apr 25, 2024 pm 02:39 PM

Go語言提供了兩種動態函數創建技術:closures和反射。 closures允許存取閉包作用域內的變量,而反射可使用FuncOf函數建立新函數。這些技術在自訂HTTP路由器、實現高度可自訂的系統和建置可插拔的元件方面非常有用。

C++ 函數命名中參數順序的考慮 C++ 函數命名中參數順序的考慮 Apr 24, 2024 pm 04:21 PM

在C++函數命名中,考慮參數順序至關重要,可提高可讀性、減少錯誤並促進重構。常見的參數順序約定包括:動作-物件、物件-動作、語意意義和遵循標準函式庫。最佳順序取決於函數目的、參數類型、潛在混淆和語言慣例。

excel函數公式大全 excel函數公式大全 May 07, 2024 pm 12:04 PM

1. SUM函數,用於對一列或一組單元格中的數字進行求和,例如:=SUM(A1:J10)。 2、AVERAGE函數,用於計算一列或一組儲存格中的數字的平均值,例如:=AVERAGE(A1:A10)。 3.COUNT函數,用於計算一列或一組單元格中的數字或文字的數量,例如:=COUNT(A1:A10)4、IF函數,用於根據指定的條件進行邏輯判斷,並返回相應的結果。

C++ 函式預設參數與可變參數的優缺點比較 C++ 函式預設參數與可變參數的優缺點比較 Apr 21, 2024 am 10:21 AM

C++函數中預設參數的優點包括簡化呼叫、增強可讀性、避免錯誤。缺點是限制靈活性、命名限制。可變參數的優點包括無限彈性、動態綁定。缺點包括複雜性更高、隱式型別轉換、除錯困難。

C++ 函式回傳參考型別有什麼好處? C++ 函式回傳參考型別有什麼好處? Apr 20, 2024 pm 09:12 PM

C++中的函數傳回參考類型的好處包括:效能提升:引用傳遞避免了物件複製,從而節省了記憶體和時間。直接修改:呼叫方可以直接修改傳回的參考對象,而無需重新賦值。程式碼簡潔:引用傳遞簡化了程式碼,無需額外的賦值操作。

如何在Java中寫出高效和可維護的函數? 如何在Java中寫出高效和可維護的函數? Apr 24, 2024 am 11:33 AM

編寫高效且可維護的Java函數的關鍵在於:保持簡潔。使用有意義的命名。處理特殊情況。使用適當的可見性。

自訂 PHP 函數和預定義函數之間有什麼區別? 自訂 PHP 函數和預定義函數之間有什麼區別? Apr 22, 2024 pm 02:21 PM

自訂PHP函數與預定義函數的差異在於:作用域:自訂函數僅限於其定義範圍,而預定義函數可在整個腳本中存取。定義方式:自訂函數使用function關鍵字定義,而預先定義函數則由PHP核心定義。參數傳遞:自訂函數接收參數,而預先定義函數可能不需要參數。擴充性:自訂函數可以根據需要創建,而預定義函數是內建的且無法修改。

C++ 函式異常進階:客製化錯誤處理 C++ 函式異常進階:客製化錯誤處理 May 01, 2024 pm 06:39 PM

C++中的異常處理可透過自訂異常類別增強,提供特定錯誤訊息、上下文資訊以及根據錯誤類型執行自訂操作。定義繼承自std::exception的異常類,提供特定的錯誤訊息。使用throw關鍵字拋出自訂異常。在try-catch區塊中使用dynamic_cast將捕獲到的異常轉換為自訂異常類型。在實戰案例中,open_file函數會拋出FileNotFoundException異常,捕捉並處理該異常可提供更具體的錯誤訊息。

See all articles