搜尋
首頁web前端js教程帶你進一步理解js閉包(詳細)

帶你進一步理解js閉包(詳細)

Oct 18, 2018 pm 01:39 PM
javascript

這篇文章帶給大家的內容是關於帶你進一步理解js閉包(詳細),有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

譯者:閉包都被討論爛了,不理解閉包都不好意思說自己會js,但我看到這篇文章還是感覺眼前一亮,也讓我對閉包有了一些新的理解,並且涉及了一些類和原型鏈的知識,這是一篇2012年的文章,稍微有點早,內容也略微基礎,但是很明晰,希望能給讀者帶來新的理解。

閉包(Closure) 是javascript這門語言中有些複雜且充滿誤解的特性。簡言之,閉包是一個對象,這個物件包含一個方法(function)和該方法創建時環境的引用(reference to the enviroment)。為了完全理解閉包,我們還需要理解兩個js中的特性,一個是一級方法(first-class function),另一個是內部方法(inner function)。

一級方法/First-Class Functions

在js中,方法是頭等公民,因為它可以輕易轉換成其他資料型別。例如,一級方法可以即時建構並且賦值給一個變數。也可以傳遞給其他方法,或透過其他方法返回。除了滿足這些標準以外,方法也擁有自己的屬性和方法。
透過下述例子,我們來看一級方法的能力。

var foo = function() {
  alert("Hello World!");
};

var bar = function(arg) {
  return arg;
};

bar(foo)();
譯者註:省略原文對程式碼的文字解釋,這裡體現的是一級方法可以回傳參數,參數可以是另一個一級函數,回傳的結果還可以呼叫。

內部方法/Inner Functions

內部方法或說嵌套方法,是指定義在其他方法內部的方法,每當外部方法被喚起,內部方法的實例就被創建。下面的範例反應內部方法的使用,add方法是外部方法,doAdd是內部方法。

function add(value1, value2) {
  function doAdd(operand1, operand2) {
    return operand1 + operand2;
  }

  return doAdd(value1, value2);
}

var foo = add(1, 2);
// foo equals 3

這個例子中,一個重要的特性是,內部方法獲取到了外部方法的作用域,這意味著內部方法能夠使用外部方法的變量,參數等。範例中add()的參數value1,value2傳遞給doAdd()的operand1,operand2參數。然而這並沒有必要,因為doAdd可以直接取得value1,value2。所以上面的例子我們還可以這麼寫:

function add(value1, value2) {
  function doAdd() {
    return value1 + value2;
  }

  return doAdd();
}

var foo = add(1, 2);
// foo equals 3

建立閉包/Creating Closures

內部方法取得外部方法的作用域,便形成了一個閉包。典型的場景是外部函數將其內部方法傳回,內部方法保持了外部環境的引用,並保存了作用域下的所有變數。
一下範例展示閉包如何建立並使用。

function add(value1) {
  return function doAdd(value2) {
    return value1 + value2;
  };
}

var increment = add(1);
var foo = increment(2);
// foo equals 3

說明:

  • add傳回了內部方法doAdd,doAdd呼叫了add的參數,閉包建立。

  • value1是add方法的本機變量,對doAdd來說是非本機變數(非本機變數指變數既不在函數體本身,也不在全域),value2是doAdd的本機變數。

  • 當add(1)被調用,一個閉包被創建並儲存在increment中,在該閉包的引用環境中,value1綁定了1,被綁定的1相當於「封鎖」在這個函數中,這也是「閉包」這個名字的由來。

  • 當increment(2)被調用,進入閉包函數,這意味著攜帶value1為1的doAdd被調用,因此該閉包本質上可以當做如下函數:

function increment(value2) {
  return 1 + value2;
}

何時使用閉包?

閉包可以實現很多功能。例如將回調函數綁定指定參數。我們說兩個讓你的生活和開發變得更簡單的場景。

  1. 配合定時器

閉包結合setTimeout和setInterval非常有用,閉包可讓你向回呼函數傳入指定參數,例如下面的例子,每秒鐘在給指定dom插入字串。

nbsp;html>


  <title>Closures</title>
  <meta>
  <script>
    window.addEventListener("load", function() {
      window.setInterval(showMessage, 1000, "some message<br />");
    });

    function showMessage(message) {
      document.getElementById("message").innerHTML += message;
    }
  </script>


  <span></span>

可惜的是,IE不支援向setInterval的回呼傳參,IE中頁面不會展現「some message」而是「undefined」(無值傳入showMessage()),解決這個問題,可以透過閉包將期望值綁定於回呼函數裡,我們可以改寫如上程式碼:

window.addEventListener("load", function() {
  var showMessage = getClosure("some message<br>");

  window.setInterval(showMessage, 1000);
});

function getClosure(message) {
  function showMessage() {
    document.getElementById("message").innerHTML += message;
  }

  return showMessage;
}

2.模擬私有屬性
絕大多數物件導向的程式語言支援物件的私有屬性,然而js不是純正的物件導向的語言,因此也沒有私有屬性的概念。不過,我們可以透過閉包來模擬私有屬性。回想一下,閉包包含了一份其創建環境的引用,這份引用已經不在當前作用域中了,因此這份引用只能在閉包中訪問,這本質上就是私有屬性。
看如下例子(譯者:省略對程式碼的文字描述):

function Person(name) {
  this._name = name;

  this.getName = function() {
    return this._name;
  };
}

這裡有一個嚴重的問題,因為js不支援私有屬性,所以我們沒辦法阻止別人修改實例的name字段,例如我們建立一個Person實例叫Colin,然後可以將他的名字改成Tom。

var person = new Person("Colin");

person._name = "Tom";
// person.getName() now returns "Tom"

没有人愿意不经同意就被别人改名字,为了阻止这种情况的发生,通过闭包让_name字段变成私有。看如下代码,注意这里的_name是Person构造器的本地变量,而不是对象的属性,闭包形成了,因为外层方法Person对外暴露了一个内部方法getName。

function Person(name) {
  var _name = name;// 注:区别在这里

  this.getName = function() {
    return _name;
  };
}

现在,当getName被调用,能够保证返回的是最初传入类构造器的值。我们依然可以为对象添加新的_name属性,但这并不影响闭包getName最初绑定的值,下面的代码证明,_name字段,事实私有。

var person = new Person("Colin");

person._name = "Tom";
// person._name is "Tom" but person.getName() returns "Colin"

什么时候不要用闭包?

正确理解闭包如何工作何时使用非常重要,而理解什么时候不应该用它也同样重要。过度使用闭包会导致脚本执行变慢并消耗额外内存。由于闭包太容易创建了,所以很容易发生你都不知道怎么回事,就已经创建了闭包的情况。本节我们说几种场景要注意避免闭包的产生。
1.循环中
循环中创建出闭包会导致结果异常。下例中,页面上有三个按钮,分别点击弹出不同的话术。然而实际运行,所有的按钮都弹出button4的话术,这是因为,当按钮被点击时,循环已经执行完毕,而循环中的变量i也已经变成了最终值4.

nbsp;html>


  <title>Closures</title>
  <meta>
  <script>
    window.addEventListener("load", function() {
      for (var i = 1; i < 4; i++) {
        var button = document.getElementById("button" + i);

        button.addEventListener("click", function() {
          alert("Clicked button " + i);
        });
      }
    });
  </script>


  <input>
  <input>
  <input>

去解决这个问题,必须在循环中去掉闭包(译者:这里的闭包指的是click事件回调函数绑定了外层引用i),我们可以通过调用一个引用新环境的函数来解决。下面的代码中,循环中的变量传递给getHandler函数,getHandler返回一个闭包(译者:这个闭包指的是getHandler返回的内部方法绑定传入的i参数),独立于原来的for循环。

function getHandler(i) {
  return function handler() {
    alert("Clicked button " + i);
  };
}

window.addEventListener("load", function() {
  for (var i = 1; i <p>2.构造函数里的非必要使用<br>类的构造函数里,也是经常会产生闭包的错误使用。我们已经知道如何通过闭包设置类的私有属性,而如果当一个方法不需要调用私有属性,则造成的闭包是浪费的。下面的例子中,Person类增加了sayHello方法,但是它没有使用私有属性。</p><pre class="brush:php;toolbar:false">function Person(name) {
  var _name = name;

  this.getName = function() {
    return _name;
  };

  this.sayHello = function() {
    alert("Hello!");
  };
}

每当Person被实例化,创建sayHello都要消耗时间,想象一下有大量的Person被实例化。更好的实践是将sayHello放入Person的原型链里(prototype),原型链里的方法,会被所有的实例化对象共享,因此节省了为每个实例化对象去创建一个闭包(译者:指sayHello),所以我们有必要做如下修改:

function Person(name) {
  var _name = name;

  this.getName = function() {
    return _name;
  };
}

Person.prototype.sayHello = function() {
  alert("Hello!");
};

需要记得一些事情

闭包包含了一个方法,以及创建它的代码环境引用

闭包会在外部函数包含内部函数的情况下形成

闭包可以轻松的帮助回调函数传入参数

类的私有属性可以通过闭包模拟

类的构造器中使用闭包不是一个好主意,将它们放到原型链中

以上是帶你進一步理解js閉包(詳細)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
JavaScript是用C編寫的嗎?檢查證據JavaScript是用C編寫的嗎?檢查證據Apr 25, 2025 am 12:15 AM

是的,JavaScript的引擎核心是用C語言編寫的。 1)C語言提供了高效性能和底層控制,適合JavaScript引擎的開發。 2)以V8引擎為例,其核心用C 編寫,結合了C的效率和麵向對象特性。 3)JavaScript引擎的工作原理包括解析、編譯和執行,C語言在這些過程中發揮關鍵作用。

JavaScript的角色:使網絡交互和動態JavaScript的角色:使網絡交互和動態Apr 24, 2025 am 12:12 AM

JavaScript是現代網站的核心,因為它增強了網頁的交互性和動態性。 1)它允許在不刷新頁面的情況下改變內容,2)通過DOMAPI操作網頁,3)支持複雜的交互效果如動畫和拖放,4)優化性能和最佳實踐提高用戶體驗。

C和JavaScript:連接解釋C和JavaScript:連接解釋Apr 23, 2025 am 12:07 AM

C 和JavaScript通過WebAssembly實現互操作性。 1)C 代碼編譯成WebAssembly模塊,引入到JavaScript環境中,增強計算能力。 2)在遊戲開發中,C 處理物理引擎和圖形渲染,JavaScript負責遊戲邏輯和用戶界面。

從網站到應用程序:JavaScript的不同應用從網站到應用程序:JavaScript的不同應用Apr 22, 2025 am 12:02 AM

JavaScript在網站、移動應用、桌面應用和服務器端編程中均有廣泛應用。 1)在網站開發中,JavaScript與HTML、CSS一起操作DOM,實現動態效果,並支持如jQuery、React等框架。 2)通過ReactNative和Ionic,JavaScript用於開發跨平台移動應用。 3)Electron框架使JavaScript能構建桌面應用。 4)Node.js讓JavaScript在服務器端運行,支持高並發請求。

Python vs. JavaScript:比較用例和應用程序Python vs. JavaScript:比較用例和應用程序Apr 21, 2025 am 12:01 AM

Python更適合數據科學和自動化,JavaScript更適合前端和全棧開發。 1.Python在數據科學和機器學習中表現出色,使用NumPy、Pandas等庫進行數據處理和建模。 2.Python在自動化和腳本編寫方面簡潔高效。 3.JavaScript在前端開發中不可或缺,用於構建動態網頁和單頁面應用。 4.JavaScript通過Node.js在後端開發中發揮作用,支持全棧開發。

C/C在JavaScript口譯員和編譯器中的作用C/C在JavaScript口譯員和編譯器中的作用Apr 20, 2025 am 12:01 AM

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。1)C 用于解析JavaScript源码并生成抽象语法树。2)C 负责生成和执行字节码。3)C 实现JIT编译器,在运行时优化和编译热点代码,显著提高JavaScript的执行效率。

JavaScript在行動中:現實世界中的示例和項目JavaScript在行動中:現實世界中的示例和項目Apr 19, 2025 am 12:13 AM

JavaScript在現實世界中的應用包括前端和後端開發。 1)通過構建TODO列表應用展示前端應用,涉及DOM操作和事件處理。 2)通過Node.js和Express構建RESTfulAPI展示後端應用。

JavaScript和Web:核心功能和用例JavaScript和Web:核心功能和用例Apr 18, 2025 am 12:19 AM

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。

See all articles

熱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

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

熱工具

SublimeText3 Mac版

SublimeText3 Mac版

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

mPDF

mPDF

mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

將Eclipse與SAP NetWeaver應用伺服器整合。

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能