日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

你想知道的关于JavaScript作用域的一切(译)

發布時間:2025/4/5 javascript 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 你想知道的关于JavaScript作用域的一切(译) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

原文鏈接:?Everything you wanted to know about JavaScript scope

原文作者:?Todd Motto

JavaScript中有許多章節是關于scope的,但是對于初學者來說(甚至是一些有經驗的JavaScript開發者),這些有關作用域的章節既不直接也不容易理解. 這篇文章的目的就是為了幫助那些想更深一步學習了解JavaScript作用域的開發者,尤其是當他們聽到一些關于作用域的單詞的時候, 好比:作用域(scope),閉包(closure),this,命名空間(namespace),函數作用域(function scope),全局作用域(global scope),詞法作用域(lexical),公有變量(public scope),私有變量(private scope). 希望通過這篇文章你可以知道下面這些問題的答案:

  • 什么是作用域?
  • 什么是全局(局部)作用域?
  • 什么是命名空間,它和作用域有什么不同?
  • this關鍵字是什么,作用于又是怎么影響它的?
  • 什么是函數/詞法作用域?
  • 什么是閉包?
  • 什么是共有/私有作用域?
  • 我怎么樣才能夠理解/創建/實踐上面所有的情況

什么是作用域?

在JavaScript中,作用域指的是你代碼的當前上下文環境.作用域可以被全局或者局部地定義.理解JavaScript的作用域是讓你寫出穩健的代碼并且成為一個更好的開發者的關鍵. 你將會理解那些變量或者函數是可以訪問的,并且有能力去改變你代碼的作用域進而有能力去寫出運行速度更快,更容易維護,當然調試也非常容易的代碼. 別把作用域想的太復雜,那么我們現在是在A作用域還是B作用域?

什么是全局作用域

當你在開始書寫JavaScript代碼的時候,你所處的作用域就是我們所說的全局作用域.如果我們定義了一個變量,那么它就是被全局定義的:

// global scope var name = 'Todd';

全局作用域是你最好的朋友也是你最壞的噩夢;學會去掌控你的作用域是容易的,如果你那樣做了,你將不會遇到一些關于全局作用域的問題(通常是關于命名空間的沖突). 你也許會經常聽到有人在說全局作用域是不好的,但是你從來沒有考慮過他們那樣說的真正原因.全局作用域當然沒有他們說的那樣,相反全局作用域是很好的, 你需要使用它去創建能夠在別的作用域訪問的模塊還有接口(APIs),你要在使用它的優點的同時確保不產生新的問題.

很多人以前都使用過jQuery,當你寫下下面的代碼的時候...

jQuery('.myClass');

我們這時就是通過全局作用域來使用jQuery的,我們可以把這種使用叫做命名空間.有時命名空間就是一個可以用不同單詞來替代的作用域,但是通常指的是最高一級的作用域. 在這個例子中,jQuery是在全局作用域中,所以也是我們的命名空間.這個jQuery的命名空間是定義在全局作用域上的,它作為這個jQuery庫的命名空間, 所有在jQuery庫內的東西都是這個命名空間的派生物.

什么是局部作用域

局部作用域指的是那些從全局作用域中定義的許多作用域.JavaScript只有一個全局作用域,每一個定義的函數都有自己的局部(嵌套)作用域.那些定義在別的函數中的函數有一個局部的作用域, 并且這個作用域是指向外部的函數.

如果我定義了一個函數,并且在里面創建了一些變量,這些變量的作用域就是局部的.

把下面的當做一個例子:

// Scope A: Global scope out here var myFunction = function () { // Scope B: Local scope in here };

任何局部的東西在全局是不可見的,除非這些東西被導出;這句話的意思是這樣的,如果我在一個新的作用域里定義了一些函數或者變量的話,這些變量或者函數在當前的作用域之外是不可以訪問的. 下面的代碼是關于上面所說的那些的一個小例子:

var myFunction = function () {var name = 'Todd'; console.log(name); // Todd }; // Uncaught ReferenceError: name is not defined console.log(name);

變量name是局部的變量,它并沒有暴露在父作用域上,因此它是沒有被定義的.

函數作用域

JavaScript中所有的作用域在創建的時候都只伴隨著函數作用域,循環語句像for或者while,條件語句像if或者switch都不能夠產生新的作用域.?新的函數 = 新的作用域這就是規則.下面一個簡單的例子用來解釋作用域的創建:

// Scope A var myFunction = function () { // Scope B var myOtherFunction = function () { // Scope C }; };

所以說很容易創建新的作用域和局部的變量/函數/對象.

詞法作用域

每當你看到一個函數里面存在著另一個函數,那么內部的函數能夠訪問外部函數的作用域,這就叫做詞法作用域或者閉包;也被認為是靜態作用域,下面的代碼是最簡單的方法再一次去解釋我們所說的內容:

// Scope A var myFunction = function () { // Scope B var name = 'Todd'; // defined in Scope B var myOtherFunction = function () { // Scope C: `name` is accessible here! }; };

你也許注意到myOtherFunction沒有在這里被調用,它只是簡單地被定義.當然它的調用順序也會影響到作用域里面變量的表現, 在這里我定義了myOtherFunction并且在console語句之后調用了它:

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`

很容易理解和使用詞法作用域,任何被定義在它的父作用域上的變量/對象/函數,在作用域鏈上都是可以訪問到的.例如:

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! }; }; };

需要記住的一個重要地方是,詞法作用域是不可逆的,我們可以從下面的例子中看到結果:

// name = undefined var scope1 = function () { // name = undefined var scope2 = function () { // name = undefined var scope3 = function () { var name = 'Todd'; // locally scoped }; }; };

當然我們可以返回一個指向name的引用,但是永遠不會是name變量本身.

作用域鏈

作用域鏈為一個給定的函數建立了作用域.就像我們知道的那樣,每一個被定義的函數都有它自己嵌套的作用域,并且任何定義在別的函數中的函數都有一個 連接外部函數的局部作用域,這個連接就是我們所說的作用域鏈中的鏈.它常常是在代碼中那些能夠定義作用域的位置,當我們訪問一個變量的時候,?JavaScript從最里面的作用域沿著作用域鏈向外部開始查找,直到找到我們想要的那個變量/對象/函數.

閉包

閉包和詞法作用域是緊密聯系在一起的,關于閉包是如何工作的一個好例子就是當我們返回一個函數的引用的時候,這是一個更實際的用法. 在我們的作用域里,我們可以返回一些東西以便這些東西能夠在父作用域里被訪問和使用:

var sayHello = function (name) {var text = 'Hello, ' + name; return function () { console.log(text); }; };

我們這里使用的閉包概念使我們在sayHello的作用域不能夠被外部(公共的)作用域訪問.單獨運行這個函數不會有什么結果因為它只是返回了一個函數:

sayHello('Todd'); // nothing happens, no errors, just silence...

這個函數返回了一個函數,那就意味著我們需要對它進行賦值,然后對它進行調用:

var helloTodd = sayHello('Todd'); helloTodd(); // will call the closure and log 'Hello, Todd'

好吧,我撒謊了,你也可以直接調用它,你也許之前已經見到過像這樣的函數,這種方式也是可以運行你的閉包:

sayHello('Bob')(); // calls the returned function without assignment

AngularJS的$compile方法使用了上面的技術,你可以將當前作用的引用域傳遞給這個閉包:

$compile(template)(scope);

我們可以猜測他們關于這個方法的(簡化)代碼大概是下面這個樣子:

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 }; };

當然一個函數不必有返回值也能夠被稱為一個閉包.只要能夠訪問外部變量的一個即時的詞法作用域就創建了一個閉包.

作用域和this

每一個作用域都綁定了一個不同值的this,這取決于這個函數是如何調用的.我們都使用過this關鍵詞,但是并不是所有的人都理解它,還有當它被調用的時候是如何的不同. 默認情況下,this指向的是最外層的全局對象window.我們可以很容易的展示關于不同的調用方式我們綁定的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'); // <nav class="nav"> var toggleNav = function () { console.log(this); // this = <nav> element }; nav.addEventListener('click', toggleNav, false);

當我們處理this的值的時候我們又遇到了一些問題,舉個例子如果我添加一些代碼在上面的例子中.就算是在同一個函數內部,作用域和this都是會發生改變的:

var nav = document.querySelector('.nav'); // <nav class="nav"> var toggleNav = function () { console.log(this); // <nav> element setTimeout(function () { console.log(this); // [object Window] }, 1000); }; nav.addEventListener('click', toggleNav, false);

所以這里發生了什么?我們創建了一個新的作用域,這個作用域沒有被我們的事件處理程序調用,所以默認情況下,這里的this指向的是window對象. 當然我們可以做一些事情不讓這個新的作用域影響我們,以便我們能夠訪問到這個正確的this值.你也許已經見到過我們這樣做的方法了,我們可以使用that變量緩存當前的this值, 然后在新的作用域中使用它.

var nav = document.querySelector('.nav'); // <nav class="nav"> var toggleNav = function () { var that = this; console.log(that); // <nav> element setTimeout(function () { console.log(that); // <nav> element }, 1000); }; nav.addEventListener('click', toggleNav, false);

這是一個小技巧,讓我們能夠使用到正確的this值,并且在新的作用域解決一些問題.

使用.call(),.apply()或者.bind()改變作用域

有時,你需要根據你所處理的情況來處理JavaScript的作用域.一個簡單的例子展示如何在循環的時候改變作用域:

var links = document.querySelectorAll('nav li'); for (var i = 0; i < links.length; i++) { console.log(this); // [object Window] }

這里的this沒有指向我們需要的元素,我們不能夠在這里使用this調用我們需要的元素,或者改變循環里面的作用域. 讓我們來思考一下如何能夠改變我們的作用域(好吧,看起來好像是我們改變了作用域,但是實際上我們真正做的事情是去改變我們那個函數的運行上下文).

  • .call()和.apply()?.call()和.apply()函數是非常實用的,它們允許你傳遞一個作用域到一個函數里面,這個作用與綁定了正確的this值. 讓我們來處理上面的那些代碼吧,讓循環里面的this指向正確的元素值:

    var links = document.querySelectorAll('nav li'); for (var i = 0; i < links.length; i++) { (function () { console.log(this); }).call(links[i]); }

    你可以看到我是如何做的,首先我們創建了一個立即執行的函數(新的函數就表明創建了新的作用域), 然后我們調用了.call()方法,將數組里面的循環元素link[i]當做參數傳遞給了.call()方法, 然后我們就改變了哪個立即執行的函數的作用域.我們可以使用.call()或者.apply()方法,但是它們的不同之處是參數的傳遞形式,?.call()方法的參數的傳遞形式是這樣的.call(scope, arg1, arg2, arg3),.apply()的參數的傳遞形式是這樣的.apply(scope, [arg1, arg2]).

    所以當你需要改變你的函數的作用域的時候,不要使用下面的方法:

    myFunction(); // invoke myFunction

    而應該是這樣,使用.call()去調用我們的方法

    myFunction.call(scope); // invoke myFunction using .call()
  • .bind() 不像上面的方法,使用.bind()方法不會調用一個函數,它僅僅在函數調用之前,綁定我們需要的值.就像我們知道的那樣, 我們不能夠給函數的引用傳遞參數.就像下面這樣:

    // works nav.addEventListener('click', toggleNav, false); // will invoke the function immediately nav.addEventListener('click', toggleNav(arg1, arg2), false);

    我們可以解決這個問題,通過在它里面創建一個新的函數:

    nav.addEventListener('click', function () { toggleNav(arg1, arg2); }, false);

    但是這樣就改變了作用域,我們又一次創建了一個不需要的函數,這樣做需要花費很多,當我們在一個循環中綁定事件監聽的時候. 這時候就需要.bind()閃亮登場了,因為我們可以使用他來進行綁定作用域,傳遞參數,并且函數還不會立即執行:

    nav.addEventListener('click', toggleNav.bind(scope, arg1, arg2), false);

    上面的函數沒有被立即調用,并且作用域在需要的情況下也會改變,而且函數的參數也是可以通過這個方法傳入的.

私有/共有的作用域

在許多編程語言中,你應該聽到過私有作用域或者共有作用域,在JavaScript中,是沒有這些概念的.當然我們也可以通過一些手段比如閉包來模擬公共作用域或者是私有作用域.

通過使用JavaScript的設計模式,比如模塊模式,我們可以創造公共作用域和私有作用域.一個簡單的方法創建私有作用域就是使用一個函數去包裹我們自己定義的函數. 就像上面所說的那樣,函數創建了一個與全局作用域隔離的一個作用域:

(function () {// private scope inside here })();

我們可能需要為我們的應用添加一些函數:

(function () {var myFunction = function () {// do some stuff here }; })();

但是當我們去調用位于函數內部的函數的時候,這些函數在外部的作用域是不可得到的:

(function () {var myFunction = function () {// do some stuff here }; })(); myFunction(); // Uncaught ReferenceError: myFunction is not defined

成功了,我們創建了私有的作用域.但是問題又來了,我如何在公共作用域內使用我們之前定義好的函數?不要擔心,我們的模塊設計模式或者說是提示模塊模式, 允許我們將我們的函數在公共作用域內發揮作用,它們使用了公共作用域和私有作用域以及對象.在下面我定義了我的全局命名空間,叫做Module, 這個命名空間里包含了與那個模塊相關的所有代碼:

// define module var Module = (function () {return { myMethod: function () { console.log('myMethod has been called.'); } }; })(); // call module + methods Module.myMethod();

上面的return聲明表明了我們返回了我們的public方法,這些方法是可以在全局作用域里使用的,不過需要通過命名空間來調用. 這就表明了我們的那個模塊只是存在于哪個命名空間中,它可以包含我們想要的任意多的方法或者變量.我們也可以按照我們的意愿來擴展這個模塊:

// define module var Module = (function () {return { myMethod: function () { }, someOtherMethod: function () { } }; })(); // call module + methods Module.myMethod(); Module.someOtherMethod();

那么我們的私有方法該如何使用以及定義呢?總是有許多的開發者隨意的堆砌他們的方法在那個模塊里面,這樣的做法污染了全局的命名空間. 那些幫助我們的代碼運行并且是不必要出現在全局作用域的方法,就不要導出在全局作用域中,我們只導出那些需要在全局作用域內被調用的函數. 我們可以定義私有的方法,只要不返回它們就行:

var Module = (function () {var privateMethod = function () { }; return { publicMethod: function () { } }; })();

上面的代碼意味著,publicMethod是可以在全局的命名空間里調用的,但是privateMethod是不可以的,因為它是在私有的作用域中被定義的. 這些私有的函數方法一般都是一些幫助性的函數,比如addClass,removeClass,Ajax/XHR calls,Arrays,Objects等等.

這里有一些概念需要我們知道,就是同一個作用域中的函數變量可以訪問在同一個作用域中的函數或者變量,甚至是這些函數已經被作為結果返回. 這意味著,我們的公共函數可以訪問我們的私有函數,所以這些私有的函數是仍然可以運行的,只不過他們不可以在公共的作用域里被訪問而已.

var Module = (function () {var privateMethod = function () { }; return { publicMethod: function () { // has access to `privateMethod`, we can call it: // privateMethod(); } }; })();

這允許一個非常強大級別的交互,以及代碼的安全;JavaScript非常重要的一個部分就是確保安全.這就是為什么我們不能夠把所有的函數都放在公共的作用域內, 因為一旦那樣做了就會暴漏我們系統的漏洞,讓一些心懷惡意的人能夠對這些漏洞進行攻擊.

下面的例子就是返回了一個對象,然后在這個對象上面調用一些公有的方法的例子:

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();

一個比較規范的命名私有方法的約定是,在私有方法的名字前面加上一個下劃線,這可以快速的幫助你區分公有方法或者私有方法:

var Module = (function () {var _privateMethod = function () { }; var publicMethod = function () { }; })();

這個約定幫助我們可以簡單地給我們的函數索引賦值,當我們返回一個匿名對象的時候:

var Module = (function () {var _privateMethod = function () { }; var publicMethod = function () { }; return { publicMethod: publicMethod, anotherPublicMethod: anotherPublicMethod } })();

總結

以上是生活随笔為你收集整理的你想知道的关于JavaScript作用域的一切(译)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。