摘要:變量作用域一個(gè)變量的作用域表示這個(gè)變量存在的上下文。在這種情況下,僅僅函數(shù)聲明的函數(shù)體被提升到頂部。雖然我們無需用來修飾形式參數(shù),但是形式參數(shù)的確也是變量,并且被自動(dòng)提升到次高的優(yōu)先級函數(shù)聲明。
關(guān)于作用域,變量提升,函數(shù)提升的個(gè)人理解
參考:
阮一峰的JavaScript參考教程2.7函數(shù)部分
思否上一篇關(guān)于作用域,提升的博客
一篇關(guān)于作用域和提升的個(gè)人博客
MockingBird博客作用域和變量提升
顏海鏡的博客
大部分例子代碼都引用的原文章內(nèi)容,如侵權(quán),聯(lián)系刪除
其中會穿插[函數(shù)聲明的方法],[函數(shù)覆蓋],[函數(shù)是一等公民]的知識點(diǎn)
寫博客的原因是看到兩個(gè)題目
var foo = 1; function bar() { if (!foo) { var foo = 10; } alert(foo); } bar();
答案是10!
你是否會疑惑條件語句if(!foo)并不會執(zhí)行,為什么foo會被賦值為10
再來看第二個(gè)例子
var a = 1; function b() { a = 10; return; function a() {} } b(); alert(a);
答案還是10嗎?顯然不是,alert輸出了1
作用域(scope)C語言的一個(gè)例子:
#includeint main() { int x = 1; printf("%d, ", x); // 1 if (1) { int x = 2; printf("%d, ", x); // 2 } printf("%d ", x); // 1 }
程序依次輸出了1,2,1.
C語言中,我們有塊級作用域(block-level scope)。在一個(gè)代碼塊(一對{}花括號括起來的部分)的中變量并不會覆蓋掉代碼塊外面的變量。
JavaScript中的表現(xiàn):
var x = 1; console.log(x); // 1 if (true) { var x = 2; console.log(x); // 2 } console.log(x); // 2
JavaScript有函數(shù)級作用域(function-level scope)。這一點(diǎn)和C家族完全不同。語句塊,如if語言,不創(chuàng)建新的作用域。僅僅函數(shù)能創(chuàng)建新作用域。
在JavaScript中,如果我們需要實(shí)現(xiàn)block-level scope,我們也有一種變通的方式,那就是通過自執(zhí)行函數(shù)創(chuàng)建臨時(shí)作用域:(閉包)
function foo() { var x = 1; if (x) { (function () { var x = 2; // some other code }()); } // x is still 1. }
上面代碼在if條件塊中創(chuàng)建了一個(gè)閉包,它是一個(gè)立即執(zhí)行函數(shù),所以相當(dāng)于我們又創(chuàng)建了一個(gè)函數(shù)作用域,所以內(nèi)部的x并不會對外部產(chǎn)生影響。
變量作用域一個(gè)變量的作用域表示這個(gè)變量存在的上下文。它指定了你可以訪問哪些變量以及你是否有權(quán)限訪問某個(gè)變量。
變量作用域分為局部作用域和全局作用域。
局部變量(處于函數(shù)級別的作用域)javascript沒有塊級作用域(被花括號包圍的);當(dāng)是,javascript有擁有函數(shù)級別的作用域,也就是說,在一個(gè)函數(shù)內(nèi)定義的變量只能在函數(shù)內(nèi)部訪問或者這個(gè)函數(shù)內(nèi)部的函數(shù)訪問(閉包除外)
不要忘記使用var關(guān)鍵字
如果聲明一個(gè)變量的時(shí)候沒有使用var關(guān)鍵字,那么這個(gè)變量將是一個(gè)全局變量!
// If you don"t declare your local variables with the var keyword, they are part of the global scope var name = "Michael Jackson"; function showCelebrityName () { console.log (name); } function showOrdinaryPersonName () { name = "Johnny Evers"; console.log (name); } showCelebrityName (); // Michael Jackson // name is not a local variable, it simply changes the global name variable showOrdinaryPersonName (); // Johnny Evers // The global variable is now Johnny Evers, not the celebrity name anymore showCelebrityName (); // Johnny Evers // The solution is to declare your local variable with the var keyword function showOrdinaryPersonName () { var name = "Johnny Evers"; // Now name is always a local variable and it will not overwrite the global variable console.log (name); }
局部變量優(yōu)先級大于全局變量
如果在全局作用域中什么的變量在局部作用域中再次聲明,那么在局部作用域中調(diào)用這個(gè)變量時(shí),優(yōu)先調(diào)用局部作用域中聲明的變量:
var name = "Paul"; function users () { // Here, the name variable is local and it takes precedence over the same name variable in the global scope var name = "Jack"; // The search for name starts right here inside the function before it attempts to look outside the function in the global scope console.log (name); } users (); // Jack全局變量
所有在函數(shù)外面聲明的變量都處于全局作用域中。在瀏覽器環(huán)境中,這個(gè)全局作用域就是我們的Window對象(或者整個(gè)HTML文檔)。
每一個(gè)在函數(shù)外部聲明或者定義的變量都是一個(gè)全局對象,所以這個(gè)變量可以在任何地方被使用,例如:
// name and sex is not in any function var myName = "zhou"; var sex = "male"; //他們都處在window對象中 console.log(window.myName); //paul console.log("sex" in window); //true
如果一個(gè)變量第一次初始化/聲明的時(shí)候沒有使用var關(guān)鍵字,那么他自動(dòng)加入到全局作用域中。
setTimeout中的函數(shù)是在全局作用域中執(zhí)行的setTimeout中的函數(shù)所處在于全局作用域中,所以函數(shù)中使用this關(guān)鍵字時(shí),這個(gè)this關(guān)鍵字指向的是全局對象(Window):
var Value1 = 200; var Value2 = 20; var myObj = { Value1 : 10, Value2 : 1, caleculatedIt: function(){ setTimeout(function(){ console.log(this.Value1 * this.Value2); }, 1000); } } myObj.caleculatedIt(); //4000提升(Hoisting) 幾個(gè)過程與詞語意義
在說明提升之前,要搞清楚幾個(gè)詞的意義.
作用域中的名字(屬性名)
例如 var a; 中a就是名字或者叫屬性名
聲明
var a;就是變量聲明
function f (){}就是函數(shù)聲明
賦值
a=1;就是賦值
需要注意的是如果這樣寫var b = 2;,那么這句話就是兩個(gè)過程,分別是聲明和賦值
等于下面的代碼
var b;//聲明 b = 2;//賦值
function f (){}函數(shù)在聲明時(shí),聲明與賦值都同時(shí)進(jìn)行.
理解函數(shù)是一等公民參考:
阮一峰:函數(shù)是一等公民
JavaScript 語言將函數(shù)看作一種值,與其它值(數(shù)值、字符串、布爾值等等)地位相同。凡是可以使用值的地方,就能使用函數(shù)。
可以把函數(shù)賦值給變量和對象的屬性
可以當(dāng)作參數(shù)傳入其他函數(shù)
可以作為函數(shù)的結(jié)果返回
函數(shù)只是一個(gè)可以執(zhí)行的值,此外并無特殊之處。
由于函數(shù)與其他數(shù)據(jù)類型地位平等,所以在 JavaScript 語言中又稱函數(shù)為第一等公民。
function add(x, y) { return x + y; } // 將函數(shù)賦值給一個(gè)變量 var operator = add; // 將函數(shù)作為參數(shù)和返回值 function a(op){ return op; } a(add)(1, 1) // 2函數(shù)聲明會覆蓋變量聲明
因?yàn)槠涫且坏裙?與其他值地位相同,所以函數(shù)聲明會覆蓋變量聲明
如果存在函數(shù)聲明和變量聲明(注意:僅僅是聲明,還沒有被賦值),而且變量名跟函數(shù)名是相同的,那么,它們都會被提示到外部作用域的開頭,但是,函數(shù)的優(yōu)先級更高,所以變量的值會被函數(shù)覆蓋掉。
// Both the variable and the function are named myName var myName;? function myName () { console.log ("Rich"); } // The function declaration overrides the variable name console.log(typeof myName); // function
但是,如果這個(gè)變量或者函數(shù)其中是賦值了的,那么另外一個(gè)將無法覆蓋它:
// But in this example, the variable assignment overrides the function declaration var myName = "Richard"; // This is the variable assignment (initialization) that overrides the function declaration. function myName () { console.log ("Rich"); } console.log(typeof myName); // string
因?yàn)樯厦娴拇a等價(jià)于
var myName; function myName () { console.log ("Rich"); } //上面是提升的區(qū)域 myName= "Richard";//然后再賦值 console.log(typeof myName); // string變量的提升與函數(shù)的提升
在Javascript中,變量進(jìn)入一個(gè)作用域可以通過下面四種方式:
語言自定義變量:所有的作用域中都存在this和arguments這兩個(gè)默認(rèn)變量
函數(shù)形參:函數(shù)的形參存在函數(shù)作用域中
函數(shù)聲明:function foo() {}
變量定義:var foo
(下面會詳細(xì)的講這四種方式)
在代碼運(yùn)行前,函數(shù)聲明和變量定義通常會被解釋器移動(dòng)到其所在作用域的最頂部
如何理解這句話呢?
function foo() { bar(); var x = 1; }
上面這段在嗎,被代碼解釋器編譯完后,將變成下面的形式:
function foo() { var x; bar(); x = 1; }
我們注意到,x變量的聲明被移動(dòng)到函數(shù)(自己所在的作用域)的最頂部。然后在bar()后,再對其進(jìn)行賦值,即賦值的位置不變,還是原來的位置.
再來看一個(gè)例子,下面兩段代碼其實(shí)是等價(jià)的:
function foo() { if (false) { var x = 1; } return; var y = 1; }
function foo() { var x, y; if (false) { x = 1; } return; y = 1; }
所以變量的上升(Hoisting)只是其聲明(定義)上升,而變量的賦值并不會上升。
我們都知道,創(chuàng)建一個(gè)函數(shù)的方法有兩種,一種是通過函數(shù)聲明function foo(){}
另一種是通過定義一個(gè)變量var foo = function(){} 那這兩種在代碼執(zhí)行上有什么區(qū)別呢?
來看下面的例子:
function test() { foo(); // TypeError "foo is not a function" bar(); // "this will run!" var foo = function () { // function expression assigned to local variable "foo" alert("this won"t run!"); } function bar() { // function declaration, given the name "bar" alert("this will run!"); } } test();
在這個(gè)例子中,foo()調(diào)用的時(shí)候報(bào)錯(cuò)了,而bar能夠正常調(diào)用
我們前面說過變量會上升,所以var foo首先會上升到函數(shù)體頂部,然而此時(shí)的foo為undefined,所以執(zhí)行報(bào)錯(cuò)TypeError "foo is not a function"。而對于函數(shù)bar, 函數(shù)本身也是一種變量,所以也存在變量上升的現(xiàn)象,但是它這種聲明方法會上升了整個(gè)函數(shù)(包括聲明和賦值),所以bar()才能夠順利執(zhí)行。
在這種情況下,僅僅函數(shù)聲明的函數(shù)體被提升到頂部。名字“foo”被提升(即聲明變量),但后面的函數(shù)體(即復(fù)制的部分),在執(zhí)行的時(shí)候才被指派。
所以以上代碼相當(dāng)于下面
function test() { var foo; function bar() { // function declaration, given the name "bar" alert("this will run!"); } foo(); // TypeError "foo is not a function" bar(); // "this will run!" foo = function () { // function expression assigned to local variable "foo" alert("this won"t run!"); } } test();
再回到一開始我們提出的兩個(gè)例子,能理解其輸出原理了嗎?
var foo = 1; function bar() { if (!foo) { var foo = 10; } alert(foo); } bar();
其實(shí)就是:
var foo = 1; function bar() { var foo;//foo此時(shí)被初始化為undefined if (!foo) {//!undefined是true foo = 10; } alert(foo);//輸出的是局部變量foo } bar();
那么下面
var a = 1; function b() { a = 10; return; function a() {} } b(); alert(a);
其實(shí)就是:
var a = 1; function b() { function a() {} a = 10;//相當(dāng)于只是覆蓋了在b函數(shù)這個(gè)作用域內(nèi)聲明的a函數(shù)所以這個(gè)a=10只是局部變量,影響不到外面的a return; } b(); alert(a);
這就是為什么,我們寫代碼的時(shí)候,變量定義總要寫在最前面。
四中變量進(jìn)入作用域被提升的順序以下文字引用自阮一峰的教程函數(shù)一章(2.7)下方評論(需要梯子才能看到評論)
關(guān)于 Hoisting 那部分,有兩點(diǎn)值得說明:
Hoisting 的作用范圍是隨著函數(shù)作用域的。我理解在這里尚未講到函數(shù)作用域,不過可以提一句提醒讀者注意,然后鏈接至作用域的部分進(jìn)一步探討;
“Hoisting 只對 var 聲明的變量有效”,不盡然如此(意思是不完全對)。變量聲明的提升并非 hoisting 的全部,JavaScript 有四種讓聲明在作用域內(nèi)獲得提升的途徑(按優(yōu)先級):
-語言定義的聲明,如 this 和 arguments。你不能在作用域內(nèi)重新定義叫做 this 的變量,是因?yàn)?this是語言自動(dòng)定義的聲明,并且它的優(yōu)先級最高,也就是被 Hoisting 到最頂上了,沒人能覆蓋它
-形式參數(shù)。雖然我們無需用 var 來修飾形式參數(shù),但是形式參數(shù)的確也是變量,并且被自動(dòng)提升到次高的優(yōu)先級-函數(shù)聲明。除了 var 以外,function declaration 也可以定義新的命名,并且同樣會被 hoisting
至作用域頂部,僅次于前兩者-最后,是本文提到的常規(guī)變量,也就是 var 聲明的變量
對于上面話的理解:
也就是說優(yōu)先級越高的越?jīng)]法被覆蓋.那么表現(xiàn)在代碼層,提升時(shí)就會轉(zhuǎn)換成下面這樣:
提升時(shí)代碼表現(xiàn)上的排序?yàn)?
1 var a;
2 function f (){};
3 形參
4 this和arguments
這就解釋了在提升時(shí),函數(shù)聲明時(shí)賦值聲明的函數(shù)會覆蓋函數(shù)表達(dá)式方式的聲明(function f (){};)的函數(shù),因?yàn)?strong>函數(shù)表達(dá)式提升了(創(chuàng)建,初始化,賦值都被提升),而賦值時(shí)的函數(shù)只在賦值的時(shí)候才被解析.
var f = function () { console.log("1"); } function f() { console.log("2"); } f() // 1
如果同時(shí)采用function命令和賦值語句聲明同一個(gè)函數(shù),最后總是采用賦值語句的定義。提升的本質(zhì)
參考
方應(yīng)杭的文章:let
要搞清楚提升的本質(zhì),需要理解 JS 變量的「創(chuàng)建create、初始化initialize 和賦值assign」
有的地方把創(chuàng)建說成是聲明(declare),為了將這個(gè)概念與變量聲明區(qū)別開,我故意不使用聲明這個(gè)字眼。
有的地方把初始化叫做綁定(binding),但我感覺這個(gè)詞不如初始化形象。
我們來看看 var 聲明的「創(chuàng)建、初始化和賦值」過程
假設(shè)有如下代碼:
function fn(){ var x = 1 var y = 2 } fn()
在執(zhí)行 fn 時(shí),會有以下過程(不完全):
進(jìn)入 fn,為 fn 創(chuàng)建一個(gè)環(huán)境。
找到 fn 中所有用 var 聲明的變量,在這個(gè)環(huán)境中「創(chuàng)建」這些變量(即 x 和 y)。
將這些變量「初始化」為 undefined。
開始執(zhí)行代碼
x = 1 將 x 變量「賦值」為 1
y = 2 將 y 變量「賦值」為 2
也就是說 var 聲明會在代碼執(zhí)行之前就將「創(chuàng)建變量,并將其初始化為 undefined」。
這就解釋了為什么在 var x = 1 之前 console.log(x) 會得到 undefined。
接下來來看 function 聲明的「創(chuàng)建、初始化和賦值」過程
假設(shè)代碼如下:
fn2() function fn2(){ console.log(2) }
JS 引擎會有一下過程:
找到所有用 function
聲明的變量,在環(huán)境中「創(chuàng)建」這些變量。
將這些變量「初始化」并「賦值」為 function(){console.log(2) }。(三步全部都被提升)
開始執(zhí)行代碼 fn2()
也就是說 function 聲明會在代碼執(zhí)行之前就「創(chuàng)建、初始化并賦值」。 (三步全部都被提升)
接下來看 let 聲明的「創(chuàng)建、初始化和賦值」過程
假設(shè)代碼如下:
{ let x = 1 x = 2 }
我們只看 {} 里面的過程:
找到所有用 let 聲明的變量,在環(huán)境中「創(chuàng)建」這些變量
開始執(zhí)行代碼(注意現(xiàn)在還沒有初始化)
執(zhí)行 x = 1,將 x 「初始化」為 1(這并不是一次賦值,如果代碼是 let x,就將 x 初始化為 undefined)
執(zhí)行 x = 2,對 x 進(jìn)行「賦值」
這就解釋了為什么在 let x 之前使用 x 會報(bào)錯(cuò):
let x = "global" { console.log(x) // Uncaught ReferenceError: x is not defined let x = 1 }
原因有兩個(gè)
console.log(x) 中的 x 指的是下面的 x,而不是全局的 x
執(zhí)行 log 時(shí) x 還沒「初始化」,所以不能使用(也就是所謂的暫時(shí)死區(qū))
看到這里,你應(yīng)該明白了 let 到底有沒有提升:
**let 的「創(chuàng)建」過程被提升了,但是初始化沒有提升。
var 的「創(chuàng)建」和「初始化」都被提升了。
function 的「創(chuàng)建」「初始化」和「賦值」都被提升了。**
最后看 const,其實(shí) const 和 let 只有一個(gè)區(qū)別,那就是 const 只有「創(chuàng)建」和「初始化」,沒有「賦值」過程。
這四種聲明,用下圖就可以快速理解:
所謂暫時(shí)死區(qū),就是不能在初始化之前,使用變量。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/94751.html
摘要:回調(diào)傳遞函數(shù)是將函數(shù)當(dāng)做值并作為參數(shù)傳遞給函數(shù)。這個(gè)例子中就是因?yàn)槭录壎C(jī)制中的傳入了回調(diào)函數(shù),產(chǎn)生了閉包,引用著所在的作用域,所以此處的數(shù)據(jù)無法從內(nèi)存中釋放。 javascript作用域 一門語言需要一套設(shè)計(jì)良好的規(guī)則來存儲變量,并且之后可以方便的找到這些變量,這逃規(guī)則被稱為作用域。 這也意味著當(dāng)我們訪問一個(gè)變量的時(shí)候,決定這個(gè)變量能否訪問到的依據(jù)就是這個(gè)作用域。 一、詞法作用域 ...
摘要:建筑的頂層代表全局作用域。實(shí)際的塊級作用域遠(yuǎn)不止如此塊級作用域函數(shù)作用域早期盛行的立即執(zhí)行函數(shù)就是為了形成塊級作用域,不污染全局。這便是閉包的特點(diǎn)吧經(jīng)典面試題下面的代碼輸出內(nèi)容答案個(gè)如何處理能夠輸出閉包方式方式下一篇你不知道的筆記 下一篇:《你不知道的javascript》筆記_this 寫在前面 這一系列的筆記是在《javascript高級程序設(shè)計(jì)》讀書筆記系列的升華版本,旨在將零碎...
摘要:除此以外,讓元素脫離文檔流也是一個(gè)很好的方法。因?yàn)樵匾坏┟撾x文檔流,它對其他元素的影響幾乎為零,性能的損耗就能夠有效局限于一個(gè)較小的范圍。講完重排與重繪,往元素上綁定事件也是引起性能問題的元兇。高性能這本書非常精致,內(nèi)容也非常豐富。 showImg(https://segmentfault.com/img/bVJgbt?w=600&h=784); 入手《高性能JavaScript》一...
摘要:對比常量聲明與聲明常量聲明與聲明,都是塊級聲明。最后一點(diǎn)全局塊級綁定與不同于的另一個(gè)方面是在全局作用域上的表現(xiàn)。塊級綁定新的最佳實(shí)踐在的發(fā)展階段,被廣泛認(rèn)可的變量聲明方式是默認(rèn)情況下應(yīng)當(dāng)使用而不是。總結(jié)與塊級綁定將詞法作用域引入。 var變量與變量提升 使用var關(guān)鍵字聲明的變量,無論其實(shí)際聲明位置在何處,都會被視為聲明于所在函數(shù)的頂部(如果聲明不在任意函數(shù)內(nèi),則被視為在全局作用域的頂...
摘要:基于函數(shù)進(jìn)行調(diào)用的,用來確保函數(shù)是在指定的值所在的上下文中調(diào)用的。添加私有函數(shù)目前上面為類庫添加的屬性都是公開的,可以被隨時(shí)修改。以基于的富應(yīng)用開發(fā)為主要學(xué)習(xí)資料。 控制類庫的作用域 在類和實(shí)例中都添加proxy函數(shù),可以在事件處理程序之外處理函數(shù)的時(shí)候保持類的作用域。下面是不用proxy的辦法: var Class = function(parent){ var klas...
閱讀 2486·2021-11-15 18:14
閱讀 1711·2021-10-14 09:42
閱讀 3746·2021-10-11 10:58
閱讀 3939·2021-10-09 09:44
閱讀 2410·2021-09-26 09:55
閱讀 2430·2021-09-24 10:38
閱讀 2025·2021-09-04 16:48
閱讀 3268·2021-09-02 15:21