摘要:原文鏈接原文作者你想知道的關(guān)于作用域的一切譯中有許多章節(jié)是關(guān)于的但是對于初學(xué)者來說甚至是一些有經(jīng)驗的開發(fā)者這些有關(guān)作用域的章節(jié)既不直接也不容易理解這篇文章的目的就是為了幫助那些想更深一步學(xué)習(xí)了解作用域的開發(fā)者尤其是當(dāng)他們聽到一些關(guān)于作用域的
原文鏈接: Everything you wanted to know about JavaScript scope
原文作者: Todd Motto
Github: 你想知道的關(guān)于JavaScript作用域的一切(譯)
JavaScript中有許多章節(jié)是關(guān)于scope的,但是對于初學(xué)者來說(甚至是一些有經(jīng)驗的JavaScript開發(fā)者),這些有關(guān)作用域的章節(jié)既不直接也不容易理解.這篇文章的目的就是為了幫助那些想更深一步學(xué)習(xí)了解JavaScript作用域的開發(fā)者,尤其是當(dāng)他們聽到一些關(guān)于作用域的單詞的時候,好比:作用域(scope),閉包(closure),this,命名空間(namespace),函數(shù)作用域(function scope),全局作用域(global scope),詞法作用域(lexical),公有變量(public scope),私有變量(private scope).希望通過這篇文章你可以知道下面這些問題的答案:
什么是作用域?
什么是全局(局部)作用域?
什么是命名空間,它和作用域有什么不同?
this關(guān)鍵字是什么,作用于又是怎么影響它的?
什么是函數(shù)/詞法作用域?
什么是閉包?
什么是共有/私有作用域?
我怎么樣才能夠理解/創(chuàng)建/實踐上面所有的情況
什么是作用域?在JavaScript中,作用域指的是你代碼的當(dāng)前上下文環(huán)境.作用域可以被全局或者局部地定義.理解JavaScript的作用域是讓你寫出穩(wěn)健的代碼并且成為一個更好的開發(fā)者的關(guān)鍵.你將會理解那些變量或者函數(shù)是可以訪問的,并且有能力去改變你代碼的作用域進(jìn)而有能力去寫出運行速度更快,更容易維護(hù),當(dāng)然調(diào)試也非常容易的代碼.別把作用域想的太復(fù)雜,那么我們現(xiàn)在是在A作用域還是B作用域?
什么是全局作用域當(dāng)你在開始書寫JavaScript代碼的時候,你所處的作用域就是我們所說的全局作用域.如果我們定義了一個變量,那么它就是被全局定義的:
// global scope var name = "Todd";
全局作用域是你最好的朋友也是你最壞的噩夢;學(xué)會去掌控你的作用域是容易的,如果你那樣做了,你將不會遇到一些關(guān)于全局作用域的問題(通常是關(guān)于命名空間的沖突).你也許會經(jīng)常聽到有人在說全局作用域是不好的,但是你從來沒有考慮過他們那樣說的真正原因.全局作用域當(dāng)然沒有他們說的那樣,相反全局作用域是很好的,你需要使用它去創(chuàng)建能夠在別的作用域訪問的模塊還有接口(APIs),你要在使用它的優(yōu)點的同時確保不產(chǎn)生新的問題.
很多人以前都使用過jQuery,當(dāng)你寫下下面的代碼的時候...
jQuery(".myClass");
我們這時就是通過全局作用域來使用jQuery的,我們可以把這種使用叫做命名空間.有時命名空間就是一個可以用不同單詞來替代的作用域,但是通常指的是最高一級的作用域.在這個例子中,jQuery是在全局作用域中,所以也是我們的命名空間.這個jQuery的命名空間是定義在全局作用域上的,它作為這個jQuery庫的命名空間,所有在jQuery庫內(nèi)的東西都是這個命名空間的派生物.
什么是局部作用域局部作用域指的是那些從全局作用域中定義的許多作用域.JavaScript只有一個全局作用域,每一個定義的函數(shù)都有自己的局部(嵌套)作用域.那些定義在別的函數(shù)中的函數(shù)有一個局部的作用域,并且這個作用域是指向外部的函數(shù).如果我定義了一個函數(shù),并且在里面創(chuàng)建了一些變量,這些變量的作用域就是局部的.
把下面的當(dāng)做一個例子:
// Scope A: Global scope out here var myFunction = function () { // Scope B: Local scope in here };
任何局部的東西在全局是不可見的,除非這些東西被導(dǎo)出;這句話的意思是這樣的,如果我在一個新的作用域里定義了一些函數(shù)或者變量的話,這些變量或者函數(shù)在當(dāng)前的作用域之外是不可以訪問的.
下面的代碼是關(guān)于上面所說的那些的一個小例子:
var myFunction = function () { var name = "Todd"; console.log(name); // Todd }; // Uncaught ReferenceError: name is not defined console.log(name);
變量name是局部的變量,它并沒有暴露在父作用域上,因此它是沒有被定義的.
函數(shù)作用域JavaScript中所有的作用域在創(chuàng)建的時候都只伴隨著函數(shù)作用域,循環(huán)語句像for或者while,條件語句像if或者switch都不能夠產(chǎn)生新的作用域.新的函數(shù) = 新的作用域這就是規(guī)則.下面一個簡單的例子用來解釋作用域的創(chuàng)建:
// Scope A var myFunction = function () { // Scope B var myOtherFunction = function () { // Scope C }; };
所以說很容易創(chuàng)建新的作用域和局部的變量/函數(shù)/對象.
詞法作用域每當(dāng)你看到一個函數(shù)里面存在著另一個函數(shù),那么內(nèi)部的函數(shù)能夠訪問外部函數(shù)的作用域,這就叫做詞法作用域或者閉包;也被認(rèn)為是靜態(tài)作用域,下面的代碼是最簡單的方法再一次去解釋我們所說的內(nèi)容:
// Scope A var myFunction = function () { // Scope B var name = "Todd"; // defined in Scope B var myOtherFunction = function () { // Scope C: `name` is accessible here! }; };
你也許注意到myOtherFunction沒有在這里被調(diào)用,它只是簡單地被定義.當(dāng)然它的調(diào)用順序也會影響到作用域里面變量的表現(xiàn),在這里我定義了myOtherFunction并且在console語句之后調(diào)用了它:
var myFunction = function () { var name = "Todd"; var myOtherFunction = function () { console.log("My name is " + name); }; console.log(name); myOtherFunction(); // call function }; // Will then log out: // `Todd` // `My name is Todd`
很容易理解和使用詞法作用域,任何被定義在它的父作用域上的變量/對象/函數(shù),在作用域鏈上都是可以訪問到的.例如:
var name = "Todd"; var scope1 = function () { // name is available here var scope2 = function () { // name is available here too var scope3 = function () { // name is also available here! }; }; };
需要記住的一個重要地方是,詞法作用域是不可逆的,我們可以從下面的例子中看到結(jié)果:
// name = undefined var scope1 = function () { // name = undefined var scope2 = function () { // name = undefined var scope3 = function () { var name = "Todd"; // locally scoped }; }; };
當(dāng)然我們可以返回一個指向name的引用,但是永遠(yuǎn)不會是name變量本身.
作用域鏈作用域鏈為一個給定的函數(shù)建立了作用域.就像我們知道的那樣,每一個被定義的函數(shù)都有它自己嵌套的作用域,并且任何定義在別的函數(shù)中的函數(shù)都有一個連接外部函數(shù)的局部作用域,這個連接就是我們所說的作用域鏈中的鏈.它常常是在代碼中那些能夠定義作用域的位置,當(dāng)我們訪問一個變量的時候,JavaScript從最里面的作用域沿著作用域鏈向外部開始查找,直到找到我們想要的那個變量/對象/函數(shù).
閉包閉包和詞法作用域是緊密聯(lián)系在一起的,關(guān)于閉包是如何工作的一個好例子就是當(dāng)我們返回一個函數(shù)的引用的時候,這是一個更實際的用法.在我們的作用域里,我們可以返回一些東西以便這些東西能夠在父作用域里被訪問和使用:
var sayHello = function (name) { var text = "Hello, " + name; return function () { console.log(text); }; };
我們這里使用的閉包概念使我們在sayHello的作用域不能夠被外部(公共的)作用域訪問.多帶帶運行這個函數(shù)不會有什么結(jié)果因為它只是返回了一個函數(shù):
sayHello("Todd"); // nothing happens, no errors, just silence...
這個函數(shù)返回了一個函數(shù),那就意味著我們需要對它進(jìn)行賦值,然后對它進(jìn)行調(diào)用:
var helloTodd = sayHello("Todd"); helloTodd(); // will call the closure and log "Hello, Todd"
好吧,我撒謊了,你也可以直接調(diào)用它,你也許之前已經(jīng)見到過像這樣的函數(shù),這種方式也是可以運行你的閉包:
sayHello("Bob")(); // calls the returned function without assignment
AngularJS的$compile方法使用了上面的技術(shù),你可以將當(dāng)前作用的引用域傳遞給這個閉包:
$compile(template)(scope);
我們可以猜測他們關(guān)于這個方法的(簡化)代碼大概是下面這個樣子:
var $compile = function (template) { // some magic stuff here // scope is out of scope, though... return function (scope) { // access to `template` and `scope` to do magic with too }; };
當(dāng)然一個函數(shù)不必有返回值也能夠被稱為一個閉包.只要能夠訪問外部變量的一個即時的詞法作用域就創(chuàng)建了一個閉包.
作用域和this每一個作用域都綁定了一個不同值的this,這取決于這個函數(shù)是如何調(diào)用的.我們都使用過this關(guān)鍵詞,但是并不是所有的人都理解它,還有當(dāng)它被調(diào)用的時候是如何的不同.默認(rèn)情況下,this指向的是最外層的全局對象window.我們可以很容易的展示關(guān)于不同的調(diào)用方式我們綁定的this的值也是不同的:
var myFunction = function () { console.log(this); // this = global, [object Window] }; myFunction(); var myObject = {}; myObject.myMethod = function () { console.log(this); // this = Object { myObject } }; var nav = document.querySelector(".nav"); //
當(dāng)我們處理this的值的時候我們又遇到了一些問題,舉個例子如果我添加一些代碼在上面的例子中.就算是在同一個函數(shù)內(nèi)部,作用域和this都是會發(fā)生改變的:
var nav = document.querySelector(".nav"); //var toggleNav = function () { console.log(this); // element setTimeout(function () { console.log(this); // [object Window] }, 1000); }; nav.addEventListener("click", toggleNav, false);
所以這里發(fā)生了什么?我們創(chuàng)建了一個新的作用域,這個作用域沒有被我們的事件處理程序調(diào)用,所以默認(rèn)情況下,這里的this指向的是window對象.當(dāng)然我們可以做一些事情不讓這個新的作用域影響我們,以便我們能夠訪問到這個正確的this值.你也許已經(jīng)見到過我們這樣做的方法了,我們可以使用that變量緩存當(dāng)前的this值,然后在新的作用域中使用它.
var nav = document.querySelector(".nav"); //var toggleNav = function () { var that = this; console.log(that); // element setTimeout(function () { console.log(that); // element }, 1000); }; nav.addEventListener("click", toggleNav, false);
這是一個小技巧,讓我們能夠使用到正確的this值,并且在新的作用域解決一些問題.
使用.call(),.apply()或者.bind()改變作用域有時,你需要根據(jù)你所處理的情況來處理JavaScript的作用域.一個簡單的例子展示如何在循環(huán)的時候改變作用域:
var links = document.querySelectorAll("nav li"); for (var i = 0; i < links.length; i++) { console.log(this); // [object Window] }
這里的this沒有指向我們需要的元素,我們不能夠在這里使用this調(diào)用我們需要的元素,或者改變循環(huán)里面的作用域.讓我們來思考一下如何能夠改變我們的作用域(好吧,看起來好像是我們改變了作用域,但是實際上我們真正做的事情是去改變我們那個函數(shù)的運行上下文).
.call()和.apply()
.call()和.apply()函數(shù)是非常實用的,它們允許你傳遞一個作用域到一個函數(shù)里面,這個作用與綁定了正確的this值.讓我們來處理上面的那些代碼吧,讓循環(huán)里面的this指向正確的元素值:
var links = document.querySelectorAll("nav li"); for (var i = 0; i < links.length; i++) { (function () { console.log(this); }).call(links[i]); }
你可以看到我是如何做的,首先我們創(chuàng)建了一個立即執(zhí)行的函數(shù)(新的函數(shù)就表明創(chuàng)建了新的作用域),然后我們調(diào)用了.call()方法,將數(shù)組里面的循環(huán)元素link[i]當(dāng)做參數(shù)傳遞給了.call()方法,然后我們就改變了哪個立即執(zhí)行的函數(shù)的作用域.我們可以使用.call()或者.apply()方法,但是它們的不同之處是參數(shù)的傳遞形式,.call()方法的參數(shù)的傳遞形式是這樣的.call(scope, arg1, arg2, arg3),.apply()的參數(shù)的傳遞形式是這樣的.apply(scope, [arg1, arg2]).
所以當(dāng)你需要改變你的函數(shù)的作用域的時候,不要使用下面的方法:
myFunction(); // invoke myFunction
而應(yīng)該是這樣,使用.call()去調(diào)用我們的方法
myFunction.call(scope); // invoke myFunction using .call()
.bind()
不像上面的方法,使用.bind()方法不會調(diào)用一個函數(shù),它僅僅在函數(shù)調(diào)用之前,綁定我們需要的值.就像我們知道的那樣,我們不能夠給函數(shù)的引用傳遞參數(shù).就像下面這樣:
// works nav.addEventListener("click", toggleNav, false); // will invoke the function immediately nav.addEventListener("click", toggleNav(arg1, arg2), false);
我們可以解決這個問題,通過在它里面創(chuàng)建一個新的函數(shù):
nav.addEventListener("click", function () { toggleNav(arg1, arg2); }, false);
但是這樣就改變了作用域,我們又一次創(chuàng)建了一個不需要的函數(shù),這樣做需要花費很多,當(dāng)我們在一個循環(huán)中綁定事件監(jiān)聽的時候.這時候就需要.bind()閃亮登場了,因為我們可以使用他來進(jìn)行綁定作用域,傳遞參數(shù),并且函數(shù)還不會立即執(zhí)行:
nav.addEventListener("click", toggleNav.bind(scope, arg1, arg2), false);
上面的函數(shù)沒有被立即調(diào)用,并且作用域在需要的情況下也會改變,而且函數(shù)的參數(shù)也是可以通過這個方法傳入的.
私有/共有的作用域在許多編程語言中,你應(yīng)該聽到過私有作用域或者共有作用域,在JavaScript中,是沒有這些概念的.當(dāng)然我們也可以通過一些手段比如閉包來模擬公共作用域或者是私有作用域.通過使用JavaScript的設(shè)計模式,比如模塊模式,我們可以創(chuàng)造公共作用域和私有作用域.一個簡單的方法創(chuàng)建私有作用域就是使用一個函數(shù)去包裹我們自己定義的函數(shù).就像上面所說的那樣,函數(shù)創(chuàng)建了一個與全局作用域隔離的一個作用域:
(function () { // private scope inside here })();
我們可能需要為我們的應(yīng)用添加一些函數(shù):
(function () { var myFunction = function () { // do some stuff here }; })();
但是當(dāng)我們?nèi)フ{(diào)用位于函數(shù)內(nèi)部的函數(shù)的時候,這些函數(shù)在外部的作用域是不可得到的:
(function () { var myFunction = function () { // do some stuff here }; })(); myFunction(); // Uncaught ReferenceError: myFunction is not defined
成功了,我們創(chuàng)建了私有的作用域.但是問題又來了,我如何在公共作用域內(nèi)使用我們之前定義好的函數(shù)?不要擔(dān)心,我們的模塊設(shè)計模式或者說是提示模塊模式,允許我們將我們的函數(shù)在公共作用域內(nèi)發(fā)揮作用,它們使用了公共作用域和私有作用域以及對象.在下面我定義了我的全局命名空間,叫做Module,這個命名空間里包含了與那個模塊相關(guān)的所有代碼:
// define module var Module = (function () { return { myMethod: function () { console.log("myMethod has been called."); } }; })(); // call module + methods Module.myMethod();
上面的return聲明表明了我們返回了我們的public方法,這些方法是可以在全局作用域里使用的,不過需要通過命名空間來調(diào)用.這就表明了我們的那個模塊只是存在于哪個命名空間中,它可以包含我們想要的任意多的方法或者變量.我們也可以按照我們的意愿來擴展這個模塊:
// define module var Module = (function () { return { myMethod: function () { }, someOtherMethod: function () { } }; })(); // call module + methods Module.myMethod(); Module.someOtherMethod();
那么我們的私有方法該如何使用以及定義呢?總是有許多的開發(fā)者隨意的堆砌他們的方法在那個模塊里面,這樣的做法污染了全局的命名空間.那些幫助我們的代碼運行并且是不必要出現(xiàn)在全局作用域的方法,就不要導(dǎo)出在全局作用域中,我們只導(dǎo)出那些需要在全局作用域內(nèi)被調(diào)用的函數(shù).我們可以定義私有的方法,只要不返回它們就行:
var Module = (function () { var privateMethod = function () { }; return { publicMethod: function () { } }; })();
上面的代碼意味著,publicMethod是可以在全局的命名空間里調(diào)用的,但是privateMethod是不可以的,因為它是在私有的作用域中被定義的.這些私有的函數(shù)方法一般都是一些幫助性的函數(shù),比如addClass,removeClass,Ajax/XHR calls,Arrays,Objects等等.這里有一些概念需要我們知道,就是同一個作用域中的函數(shù)變量可以訪問在同一個作用域中的函數(shù)或者變量,甚至是這些函數(shù)已經(jīng)被作為結(jié)果返回.這意味著,我們的公共函數(shù)可以訪問我們的私有函數(shù),所以這些私有的函數(shù)是仍然可以運行的,只不過他們不可以在公共的作用域里被訪問而已.
var Module = (function () { var privateMethod = function () { }; return { publicMethod: function () { // has access to `privateMethod`, we can call it: // privateMethod(); } }; })();
這允許一個非常強大級別的交互,以及代碼的安全;JavaScript非常重要的一個部分就是確保安全.這就是為什么我們不能夠把所有的函數(shù)都放在公共的作用域內(nèi),因為一旦那樣做了就會暴漏我們系統(tǒng)的漏洞,讓一些心懷惡意的人能夠?qū)@些漏洞進(jìn)行攻擊.
下面的例子就是返回了一個對象,然后在這個對象上面調(diào)用一些公有的方法的例子:
var Module = (function () { var myModule = {}; var privateMethod = function () { }; myModule.publicMethod = function () { }; myModule.anotherPublicMethod = function () { }; return myModule; // returns the Object with public methods })(); // usage Module.publicMethod();
一個比較規(guī)范的命名私有方法的約定是,在私有方法的名字前面加上一個下劃線,這可以快速的幫助你區(qū)分公有方法或者私有方法:
var Module = (function () { var _privateMethod = function () { }; var publicMethod = function () { }; })();
這個約定幫助我們可以簡單地給我們的函數(shù)索引賦值,當(dāng)我們返回一個匿名對象的時候:
var Module = (function () { var _privateMethod = function () { }; var publicMethod = function () { }; return { publicMethod: publicMethod, anotherPublicMethod: anotherPublicMethod } })();
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/79796.html
摘要:理解的函數(shù)基礎(chǔ)要搞好深入淺出原型使用原型模型,雖然這經(jīng)常被當(dāng)作缺點提及,但是只要善于運用,其實基于原型的繼承模型比傳統(tǒng)的類繼承還要強大。中文指南基本操作指南二繼續(xù)熟悉的幾對方法,包括,,。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。 怎樣使用 this 因為本人屬于偽前端,因此文中只看懂了 8 成左右,希望能夠給大家?guī)韼椭?...(據(jù)說是阿里的前端妹子寫的) this 的值到底...
摘要:當(dāng)面試中讓我解釋一下閉包時我懵逼了。這個解釋開始可能有點晦澀,讓我們抽絲剝繭摘下閉包的真面目。此文不詳述作用域有專門的主題闡述,不過作用域是理解閉包原理的基礎(chǔ)。這才是閉包的真正便利之處。閉包使用不當(dāng)就會很坑。 原文鏈接 為什么深度學(xué)習(xí)JavaScript? JavaScript如今是最流行的編程語言之一。它運行在瀏覽器、服務(wù)器、移動設(shè)備、桌面應(yīng)用,也可能包括冰箱。無需我舉其他再多不相干...
摘要:在當(dāng)前階段,僅僅只是字節(jié)碼規(guī)范。如果都沒有將代碼編譯為字節(jié)碼的工具,要起步就很困難了。接下來要做的是使用將格式的代碼轉(zhuǎn)換為二進(jìn)制碼。運行文件,最后就能得到瀏覽器需要的真正的二進(jìn)制碼。 本文轉(zhuǎn)載自:眾成翻譯譯者:文藺鏈接:http://www.zcfy.cc/article/1031原文:http://cultureofdevelopment.com/blog/build-your-fi...
摘要:在控制臺中使用,當(dāng)?shù)竭_(dá)傳入的函數(shù)時,代碼將停止。但除了私有和匿名函數(shù)這可能是找到調(diào)試函數(shù)的最快方法。在控制臺中輸入,當(dāng)調(diào)用時,將以調(diào)試模式停止屏蔽不相關(guān)代碼現(xiàn)在,我們經(jīng)常在應(yīng)用中引入幾個庫或框架。 譯者:SlaneYang原文:https://raygun.com/javascript-debugging-tips 以更快的速度和更高的效率來調(diào)試JavaScript 熟悉工具可以讓工具...
摘要:原文去年,我寫了一篇關(guān)于優(yōu)秀資源之獲取優(yōu)秀資源的博文。在谷歌瀏覽器的團(tuán)隊中,每天的工作是整天修補并了解哪些是可行的,哪些是沒有用的。你需要真正利用在中的特性,不用想就知道你將得到很多來源于各種寫作者,包括谷歌瀏覽器團(tuán)隊在內(nèi)的資源。 原文:http://code.tutsplus.com/articles/resources-for-staying-on-top-of-javascrip...
閱讀 2565·2021-11-23 09:51
閱讀 3360·2021-11-22 15:22
閱讀 1873·2021-11-18 13:22
閱讀 2257·2021-09-24 09:48
閱讀 1312·2019-08-29 13:58
閱讀 1303·2019-08-26 13:39
閱讀 2448·2019-08-26 10:48
閱讀 3035·2019-08-26 10:21