摘要:閉包閉包的概念與詞法域關系緊密。閉包甚至在函數已經返回后也可以獲取其外部函數的變量。一種常見的閉包導致的由立即調用函數表達式解決的例子事實上結果的所有都是,而不是按順序得出的,。
介紹
JavaScript 有一個特征————作用域。理解作用域scope可以使你的代碼脫穎而出,減少錯誤,幫助你用它構造強大的設計模式。
什么是作用域作用域就是在代碼執行期間變量,函數和對象能被獲取到的特定的代碼范圍。換句話說,作用域決定了變量和其他資源在你的代碼區域中的可見性。
為什么會有作用域?———最小存取原則那么,限制變量的可見性不讓其在代碼中處處可見的意義是什么?優勢之一 是作用域使你的代碼具備一定的安全性。一個通用的計算機安全性原則就是讓用戶每次只訪問他們需要的東西。
想想計算機管理員:他們需要控制很多公司系統的東西,給他們完全的用戶權限似乎是可以理解的。假設一個公司有三個系統管理員,他們都有系統的所有權限,一切進展順利。但是突然厄運降臨,其中一人的系統被可惡的病毒感染了,而現在不知道是誰哪里出錯了。現在意識到應該給他們基本權限的用戶賬戶只在他們需要的時候授予他們完全的權限。這會幫助你追蹤變動并一直知曉哪個賬戶做了什么。這就叫做最小存取原則。好像很直觀吧,這個原則也用于程序語言設計,在包括JS在內的編程語言中它叫做作用域。
當你享受編程之旅時,你會意識到你的代碼的作用域部分幫助你提升效率,追蹤bug并減少bug。作用域同時解決了你在編程時不同作用域內的同名變量的問題。不要把環境/上下文與作用域搞混,他們是不同的。
JavaScript的作用域JavaScript有兩種類型的作用域:
全局作用域
局部作用域
定義在函數內部的變量在本地范圍內,而定義在函數外部的變量的作用域是全局。每個函數的觸發調用都會創建一個新的作用域。
全局作用域當你開始寫JS的時候,你就已經處在全局范圍內了,一個變量若不在函數內,便是全局變量。
// the scope is by default global var name = "Hammad";
全局范圍內的變量可以在其他范圍內獲取或修改。
var name = "Hammad"; console.log(name); // logs "Hammad" function logName() { console.log(name); // "name" is accessible here and everywhere else } logName(); // logs "Hammad"局部作用域
定義在函數內的變量就在局部作用域。
每次調用那個函數他們都有不同的作用域,也就是說同名變量可以在不同的函數內使用。因為這些變量與他們各自的函數綁定,各自有不同的作用域,無法在其他函數內獲取。
// Global Scope function someFunction() { // Local Scope #1 function someOtherFunction() { // Local Scope #2 } } // Global Scope function anotherFunction() { // Local Scope #3 } // Global Scope塊語句
像if和switch這種條件語句或for和while這種循環語句————非函數的塊語句,不會創造新的作用域。定義在塊語句內的變量將保持他們當前的作用域。
if (true) { // this "if" conditional block doesn"t create a new scope var name = "Hammad"; // name is still in the global scope } console.log(name); // logs "Hammad"
ECMAScript 6引入了let和const關鍵字,可以用于替換var。相比var,后者支持塊作用域的聲明。
if (true) { // this "if" conditional block doesn"t create a scope // name is in the global scope because of the "var" keyword var name = "Hammad"; // likes is in the local scope because of the "let" keyword let likes = "Coding"; // skills is in the local scope because of the "const" keyword const skills = "JavaScript and PHP"; } console.log(name); // logs "Hammad" console.log(likes); // Uncaught ReferenceError: likes is not defined console.log(skills); // Uncaught ReferenceError: skills is not defined
只要你的應用激活了,全局作用域也就激活了。局部作用域則隨著你的函數的調用和執行而激活。Context————上下文/環境
許多開發者經常把作用域和上下文弄混淆,好像它們是相同的概念。非也。作用域就是我們以上討論的,而上下文是指你得代碼特定區域內this的值。作用域指變量的可見性,上下文指同一范圍下this的值。我們可以用函數方法改變上下文,這個稍后討論。在全局范圍內,上下文總是window對象。
// logs: Window {speechSynthesis: SpeechSynthesis, caches: CacheStorage, localStorage: Storage…} console.log(this); function logFunction() { console.log(this); } // logs: Window {speechSynthesis: SpeechSynthesis, caches: CacheStorage, localStorage: Storage…} // because logFunction() is not a property of an object logFunction();
如果作用域是一個對象的方法,上下文就是方法所屬的對象。
class User { logName() { console.log(this); } } (new User).logName(); // logs User {}
(new User).logName() 是一個在變量中存儲對象并調用logName的快捷的方法。這里你不需要創建一個新變量。
你可能會注意到一件事情:如果你用new關鍵字調用函數,上下文的值會改變為所調用的函數的實例。例如:
function logFunction() { console.log(this); } new logFunction(); // logs logFunction {}
執行上下文嚴格模式下上下文默認為undefined。
將"use strict"放在腳本文件的第一行,則整個腳本都將以"嚴格模式"運行。如果這行語句不在第一行,則無效,整個腳本以"正常模式"運行。
如果不同模式的代碼文件合并成一個文件,這一點需要特別注意。(嚴格地說,只要前面不是產生實際運行結果的語句,"use strict"可以不在第一行,比如直接跟在一個空的分號后面。)將"use strict"放在函數體的第一行,則整個函數以"嚴格模式"運行。
對于腳本,最好將整個腳本文件放在一個立即執行的匿名函數之中。
為了徹底弄清楚以上困惑,在執行上下文中的上下文指的是作用域而不是上下文。這是個奇怪的命名慣例但是因為JavaScript已經明確了它,我們只需記住即可。
JavaScript是一個單線程語言所以他一次只能執行一個任務。剩下的任務在執行上下文中以隊列形式存在。正如我之前所說,當JavaScript編譯器開始執行代碼時,上下文(作用域)就被默認設置為全局的了。這個全局的上下文會添加在執行上下文中,它實際上是啟動執行上下文的第一個上下文。
隨后,
每個函數請求會添加它的上下文到執行上下文。當函數內的另一個函數或其他地方的函數調用時也一樣。
每個函數都會創建自己的執行上下文。
一旦瀏覽器執行完上下文的代碼,上下文會從執行上下文中彈出, 在執行上下文中的當前上下文的狀態會被傳遞給父級上下文。瀏覽器總會執行在任務棧最頂端的執行上下文(也就是你代碼中最內部的作用域)。
只能有一個全局上下文但函數上下文可以有多個。
執行上下文有兩個階段:創建 和 執行。
創建階段第一個階段是創建階段,是指函數被調用還沒有被執行的時期,在創建階段會做三件事情:
創建變量對象
創建作用域鏈
設置上下文的值(this)
代碼執行階段第二個階段是代碼執行階段,這個階段將為變量賦值,最終執行代碼。
詞法域詞法域是指在一組函數中,內部函數可以獲取到他的父級作用域內的變量和其他資源。這意味這子函數在詞法上綁定了父級的執行上下文。詞法域有時也指靜態域。
function grandfather() { var name = "Hammad"; // likes is not accessible here function parent() { // name is accessible here // likes is not accessible here function child() { // Innermost level of the scope chain // name is also accessible here var likes = "Coding"; } } }
您將注意到詞法域提前工作,意思是可以通過它的孩子的執行上下文訪問name。但它在其父級無效,意味著likes不能被父級訪問獲取。也就是說,同名變量內部函數的優先權高于外層函數。
閉包閉包的概念與詞法域關系緊密。當一個內部函數試圖訪問外部函數的作用域鏈即其詞法域外的變量值時,閉包就會被創建了。閉包包含他們自己的的作用域鏈,他們父級作用域鏈以及全局的作用域。閉包就是能夠讀取其他函數內部變量的函數,由于在Javascript語言中,只有函數內部的子函數才能讀取局部變量,因此可以把閉包簡單理解成"定義在一個函數內部的函數"。
閉包不僅可以獲取函數內部的變量,也可以獲取其外部函數的參數資源。
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ return function(){ return this.name; }; } }; alert(object.getNameFunc()()); // =>The Window var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ var that = this; return function(){ return that.name; }; } }; alert(object.getNameFunc()()); // My Object
閉包甚至在函數已經返回后也可以獲取其外部函數的變量。這允許返回函數一直可以獲取其外部函數的所有資源。
當一個函數返回一個內部函數時,即使你調用外部函數時返回函數并不會被請求執行。你必須用一個獨立的變量保存外部函數的調用請求,然后以函數形式調用該變量:
function greet() { name = "Hammad"; return function () { //這個函數就是閉包 console.log("Hi " + name); } } greet(); // nothing happens, no errors // the returned function from greet() gets saved in greetLetter greetLetter = greet(); // calling greetLetter calls the returned function from the greet() function greetLetter(); // logs "Hi Hammad"
同樣可以用()()替換變量分配執行的過程。
function greet() { name = "Hammad"; return function () { console.log("Hi " + name); } } greet()(); // logs "Hi Hammad"
閉包最大用處有兩個,一個是前面提到的可以讀取函數內部的變量,另一個就是讓這些變量的值始終保持在內存中
公共域和私有域在許多其他編程語言中,你可以用 public, private and protected設置屬性和類的方法的可見性。JavaScript中沒有類似的公共域和私有域的機制。但是我們可以用閉包模擬這種機制,為了將所有資源與全局域獨立開來,應該這樣封裝函數:
(function () { // private scope })();
()在函數最后是告訴編譯器直接在讀到該函數時不用等到函數調用就執行它,我們可以在里面添加函數和變量而不用擔心他們被外部獲取到。但是如果我們想讓外部獲取它們即想暴露部分變量或函數供外部修改獲取怎么辦?模塊模式————閉包的一種,支持我們在一個對象內利用公共域和私有域訪問審視我們的函數。
模塊模式模塊模式:
var Module = (function() { function privateMethod() { // do something } return { publicMethod: function() { // can call privateMethod(); } }; })();
模塊的返回語句包含了我們的公共函數。那些沒有返回的便是私有函數。沒有返回函數使得它們在模塊命名空間外無法被存取。但是公共函數可以存取方便我們的輔助函數,ajax請求以及其他需要的函數。
Module.publicMethod(); // works Module.privateMethod(); // Uncaught ReferenceError: privateMethod is not defined
一個慣例是私有函數的命名一般以__開頭并返回一個包含公共函數的匿名對象。
var Module = (function () { function _privateMethod() { // do something } function publicMethod() { // do something } return { publicMethod: publicMethod, } })();Immediately-Invoked Function Expression (IIFE)立即調用函數表達式
另一種形式的閉包叫立即調用的函數表達式,這是一個在window上下文中自我觸發的匿名函數,意思就是this的值是window。它可以暴露一個可交互的全局接口。
(function(window) { // do anything })(this);
一種常見的閉包導致的bug由立即調用函數表達式解決的例子:
// This example is explained in detail below (just after this code box).? ?function celebrityIDCreator (theCelebrities) { var i; var uniqueID = 100; for (i = 0; i < theCelebrities.length; i++) { theCelebrities[i]["id"] = function () { return uniqueID + i; } } return theCelebrities; } ? ?var actionCelebs = [{name:"Stallone", id:0}, {name:"Cruise", id:0}, {name:"Willis", id:0}]; ? ?var createIdForActionCelebs = celebrityIDCreator (actionCelebs); ? ?var stalloneID = createIdForActionCelebs [0];??console.log(stalloneID.id()); // 103
事實上結果的所有id都是103,而不是按順序得出的101,102,103...。
因為for循環中的匿名函數得到是外部函數變量的引用而非變量實際值,而i的值最終結果為3,故所有id103,這樣修改可以得到預想效果:
function celebrityIDCreator (theCelebrities) { var i; var uniqueID = 100; for (i = 0; i < theCelebrities.length; i++) { theCelebrities[i]["id"] = function (j) { // the j parametric variable is the i passed in on invocation of this IIFE? return function () { return uniqueID + j; // each iteration of the for loop passes the current value of i into this IIFE and it saves the correct value to the array? } () // BY adding () at the end of this function, we are executing it immediately and returning just the value of uniqueID + j, instead of returning a function.? } (i); // immediately invoke the function passing the i variable as a parameter? } ? return theCelebrities; } ? ?var actionCelebs = [{name:"Stallone", id:0}, {name:"Cruise", id:0}, {name:"Willis", id:0}]; ? ?var createIdForActionCelebs = celebrityIDCreator (actionCelebs); ? ?var stalloneID = createIdForActionCelebs [0]; ?console.log(stalloneID.id); // 100? ? ?var cruiseID = createIdForActionCelebs [1];?console.log(cruiseID.id); // 101利用.call(), .apply() 和 .bind()改變上下文
Call 和 Apply 函數 在調用函數時可以用來改變上下文。這賦予了你難以置信的編程能力。為了使用兩個函數,你需要在函數上調用它而非用()觸發,并將上下文作為第一個參數傳遞。函數本身的參數可在上下文后傳遞。
function hello() { // do something... } hello(); // the way you usually call it hello.call(context); // here you can pass the context(value of this) as the first argument hello.apply(context); // here you can pass the context(value of this) as the first argument
.call() 和 .apply()的不同之處在于,在傳遞剩余參數時,.call()將剩余參數以,隔開,而.appley()會將這些參數包含在一個數組里傳遞。
function introduce(name, interest) { console.log("Hi! I"m "+ name +" and I like "+ interest +"."); console.log("The value of this is "+ this +".") } introduce("Hammad", "Coding"); // the way you usually call it introduce.call(window, "Batman", "to save Gotham"); // pass the arguments one by one after the contextt introduce.apply("Hi", ["Bruce Wayne", "businesses"]); // pass the arguments in an array after the context // Output: // Hi! I"m Hammad and I like Coding. // The value of this is [object Window]. // Hi! I"m Batman and I like to save Gotham. // The value of this is [object Window]. // Hi! I"m Bruce Wayne and I like businesses. // The value of this is Hi.
在效果上,call的速度要略快于apply
下面展示了文檔內的一組列表并在命令行打印它們:
Things to learn Things to Learn to Rule the World
這里我想起來以前看到過的.caller()和.callee():
.caller()是指調用函數的函數體,返回函數體,類似于toString()
.callee()是Arguments的一個成員,表示對函數對象本身的引用,常用屬性是length,arguments.length是指實參長度,callee.length形參長度。
具體可參考這里
對象可以有方法,同樣函數對象也可以有方法。事實上,一個JavaScript函數生來就有四種內置函數:
Function.prototype.apply()
Function.prototype.bind() (Introduced in ECMAScript 5 (ES5))
Function.prototype.call()
Function.prototype.toString() 將函數字符串化
.prototype => .__proto__
不同于Call和Apply,Bind本身不調用函數,只用來在調用函數前綁定上下文的值和其他參數,例如:
(function introduce(name, interest) { console.log("Hi! I"m "+ name +" and I like "+ interest +"."); console.log("The value of this is "+ this +".") }).bind(window, "Hammad", "Cosmology")(); // logs: // Hi! I"m Hammad and I like Cosmology. // The value of this is [object Window].
Bind就像Call函數,在傳遞剩余的參數時以,隔開而不像Apply傳遞一個數組,它返回的是一個新函數。
var person1 = {firstName: "Jon", lastName: "Kuperman"}; var person2 = {firstName: "Kelly", lastName: "King"}; function say() { console.log("Hello " + this.firstName + " " + this.lastName); } var sayHelloJon = say.bind(person1); var sayHelloKelly = say.bind(person2); sayHelloJon(); // Hello Jon Kuperman sayHelloKelly(); // Hello Kelly KingHappy Coding!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/89604.html
摘要:作用域鏈的用途,是保證對執行環境有權訪問的所有變量和函數的有序訪問。當前的執行環境就是作用域的最前端,標識符解析是沿著作用域鏈最前端向后回溯,直到找到標志符當某個函數被調用時,會創建一個執行環境及相應的作用域鏈。 變量由于JavaScript變量松散類型的本質,決定了它只是在特定時間用于保存特定值的一個名字而已。變量分為基礎類型值和引用類型值;基本類型值指的是簡單的數據段(Undefi...
摘要:但是在調用函數值執行之后并沒有達到我們想要的效果。解析在這里我們為每一個的事件綁定了一個匿名函數,這個匿名函數就形成了一個閉包。這樣我們就為每個的事件的匿名函數,都保存下了自己閉包變量。 博客原址 理解 Javascript中的this 基于不同的調用方式this的指向也會有所不同,調用方式大致有如下幾種: 調用方式 表達式 構造函數調用 new Foo(); 對象方法...
摘要:目錄函數的聲明函數的屬性和方法函數的作用域閉包知識點小結關于函數,可以從以下個方面去理解首先,數據類型上看函數在中是一種數據類型,是對象的一種其次,從功能上看函數本質上是一段反復調用的代碼塊最后,從地位上看函數在中和其他基本數據類型一樣,可 目錄 1.函數的聲明 2.函數的屬性和方法 3.函數的作用域 4.閉包知識點 5.小結 關于函數,可以從以下3個方面去理解:首先,數據類型上看:...
摘要:作用域沒有塊級作用域盡量不要在塊中聲明變量。只有函數級作用域作用域鏈自由變量當前作用域沒有定義的變量即為自由變量。自由變量會去其父級作用域找。 1. 題目 說一下對變量提升的理解 說明this的幾種不同使用場景 創建10個a標簽,點擊的時候彈出來相應的序號 如何理解作用域 實際開發中閉包的應用 手動實現call apply bind 2. 知識點 2.1 執行上下文 范圍:一段scri...
摘要:請解釋事件代理事件代理也稱為事件委托,利用了事件冒泡。同源指的是協議域名端口相同,同源策略是一種安全協議。目的同源策略保證了用戶的信息安全,瀏覽器打開多個站點時,互相之間不能利用獲取對方站點的敏感信息。 請解釋事件代理(event delegation) 事件代理也稱為事件委托,利用了事件冒泡。例如: item1 item2 item3 當頁面li增多時單...
閱讀 5257·2021-09-22 15:50
閱讀 1863·2021-09-02 15:15
閱讀 1164·2019-08-29 12:49
閱讀 2543·2019-08-26 13:31
閱讀 3458·2019-08-26 12:09
閱讀 1210·2019-08-23 18:17
閱讀 2736·2019-08-23 17:56
閱讀 2929·2019-08-23 16:02