摘要:變量作用域一個變量的作用域表示這個變量存在的上下文。在這種情況下,僅僅函數聲明的函數體被提升到頂部。雖然我們無需用來修飾形式參數,但是形式參數的確也是變量,并且被自動提升到次高的優先級函數聲明。
關于作用域,變量提升,函數提升的個人理解
參考:
阮一峰的JavaScript參考教程2.7函數部分
思否上一篇關于作用域,提升的博客
一篇關于作用域和提升的個人博客
MockingBird博客作用域和變量提升
顏海鏡的博客
大部分例子代碼都引用的原文章內容,如侵權,聯系刪除
其中會穿插[函數聲明的方法],[函數覆蓋],[函數是一等公民]的知識點
寫博客的原因是看到兩個題目
var foo = 1; function bar() { if (!foo) { var foo = 10; } alert(foo); } bar();
答案是10!
你是否會疑惑條件語句if(!foo)并不會執行,為什么foo會被賦值為10
再來看第二個例子
var a = 1; function b() { a = 10; return; function a() {} } b(); alert(a);
答案還是10嗎?顯然不是,alert輸出了1
作用域(scope)C語言的一個例子:
#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)。在一個代碼塊(一對{}花括號括起來的部分)的中變量并不會覆蓋掉代碼塊外面的變量。
JavaScript中的表現:
var x = 1; console.log(x); // 1 if (true) { var x = 2; console.log(x); // 2 } console.log(x); // 2
JavaScript有函數級作用域(function-level scope)。這一點和C家族完全不同。語句塊,如if語言,不創建新的作用域。僅僅函數能創建新作用域。
在JavaScript中,如果我們需要實現block-level scope,我們也有一種變通的方式,那就是通過自執行函數創建臨時作用域:(閉包)
function foo() { var x = 1; if (x) { (function () { var x = 2; // some other code }()); } // x is still 1. }
上面代碼在if條件塊中創建了一個閉包,它是一個立即執行函數,所以相當于我們又創建了一個函數作用域,所以內部的x并不會對外部產生影響。
變量作用域一個變量的作用域表示這個變量存在的上下文。它指定了你可以訪問哪些變量以及你是否有權限訪問某個變量。
變量作用域分為局部作用域和全局作用域。
局部變量(處于函數級別的作用域)javascript沒有塊級作用域(被花括號包圍的);當是,javascript有擁有函數級別的作用域,也就是說,在一個函數內定義的變量只能在函數內部訪問或者這個函數內部的函數訪問(閉包除外)
不要忘記使用var關鍵字
如果聲明一個變量的時候沒有使用var關鍵字,那么這個變量將是一個全局變量!
// 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); }
局部變量優先級大于全局變量
如果在全局作用域中什么的變量在局部作用域中再次聲明,那么在局部作用域中調用這個變量時,優先調用局部作用域中聲明的變量:
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全局變量
所有在函數外面聲明的變量都處于全局作用域中。在瀏覽器環境中,這個全局作用域就是我們的Window對象(或者整個HTML文檔)。
每一個在函數外部聲明或者定義的變量都是一個全局對象,所以這個變量可以在任何地方被使用,例如:
// 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
如果一個變量第一次初始化/聲明的時候沒有使用var關鍵字,那么他自動加入到全局作用域中。
setTimeout中的函數是在全局作用域中執行的setTimeout中的函數所處在于全局作用域中,所以函數中使用this關鍵字時,這個this關鍵字指向的是全局對象(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) 幾個過程與詞語意義
在說明提升之前,要搞清楚幾個詞的意義.
作用域中的名字(屬性名)
例如 var a; 中a就是名字或者叫屬性名
聲明
var a;就是變量聲明
function f (){}就是函數聲明
賦值
a=1;就是賦值
需要注意的是如果這樣寫var b = 2;,那么這句話就是兩個過程,分別是聲明和賦值
等于下面的代碼
var b;//聲明 b = 2;//賦值
function f (){}函數在聲明時,聲明與賦值都同時進行.
理解函數是一等公民參考:
阮一峰:函數是一等公民
JavaScript 語言將函數看作一種值,與其它值(數值、字符串、布爾值等等)地位相同。凡是可以使用值的地方,就能使用函數。
可以把函數賦值給變量和對象的屬性
可以當作參數傳入其他函數
可以作為函數的結果返回
函數只是一個可以執行的值,此外并無特殊之處。
由于函數與其他數據類型地位平等,所以在 JavaScript 語言中又稱函數為第一等公民。
function add(x, y) { return x + y; } // 將函數賦值給一個變量 var operator = add; // 將函數作為參數和返回值 function a(op){ return op; } a(add)(1, 1) // 2函數聲明會覆蓋變量聲明
因為其是一等公民,與其他值地位相同,所以函數聲明會覆蓋變量聲明
如果存在函數聲明和變量聲明(注意:僅僅是聲明,還沒有被賦值),而且變量名跟函數名是相同的,那么,它們都會被提示到外部作用域的開頭,但是,函數的優先級更高,所以變量的值會被函數覆蓋掉。
// 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
但是,如果這個變量或者函數其中是賦值了的,那么另外一個將無法覆蓋它:
// 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
因為上面的代碼等價于
var myName; function myName () { console.log ("Rich"); } //上面是提升的區域 myName= "Richard";//然后再賦值 console.log(typeof myName); // string變量的提升與函數的提升
在Javascript中,變量進入一個作用域可以通過下面四種方式:
語言自定義變量:所有的作用域中都存在this和arguments這兩個默認變量
函數形參:函數的形參存在函數作用域中
函數聲明:function foo() {}
變量定義:var foo
(下面會詳細的講這四種方式)
在代碼運行前,函數聲明和變量定義通常會被解釋器移動到其所在作用域的最頂部
如何理解這句話呢?
function foo() { bar(); var x = 1; }
上面這段在嗎,被代碼解釋器編譯完后,將變成下面的形式:
function foo() { var x; bar(); x = 1; }
我們注意到,x變量的聲明被移動到函數(自己所在的作用域)的最頂部。然后在bar()后,再對其進行賦值,即賦值的位置不變,還是原來的位置.
再來看一個例子,下面兩段代碼其實是等價的:
function foo() { if (false) { var x = 1; } return; var y = 1; }
function foo() { var x, y; if (false) { x = 1; } return; y = 1; }
所以變量的上升(Hoisting)只是其聲明(定義)上升,而變量的賦值并不會上升。
我們都知道,創建一個函數的方法有兩種,一種是通過函數聲明function foo(){}
另一種是通過定義一個變量var foo = function(){} 那這兩種在代碼執行上有什么區別呢?
來看下面的例子:
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();
在這個例子中,foo()調用的時候報錯了,而bar能夠正常調用
我們前面說過變量會上升,所以var foo首先會上升到函數體頂部,然而此時的foo為undefined,所以執行報錯TypeError "foo is not a function"。而對于函數bar, 函數本身也是一種變量,所以也存在變量上升的現象,但是它這種聲明方法會上升了整個函數(包括聲明和賦值),所以bar()才能夠順利執行。
在這種情況下,僅僅函數聲明的函數體被提升到頂部。名字“foo”被提升(即聲明變量),但后面的函數體(即復制的部分),在執行的時候才被指派。
所以以上代碼相當于下面
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();
再回到一開始我們提出的兩個例子,能理解其輸出原理了嗎?
var foo = 1; function bar() { if (!foo) { var foo = 10; } alert(foo); } bar();
其實就是:
var foo = 1; function bar() { var foo;//foo此時被初始化為undefined if (!foo) {//!undefined是true foo = 10; } alert(foo);//輸出的是局部變量foo } bar();
那么下面
var a = 1; function b() { a = 10; return; function a() {} } b(); alert(a);
其實就是:
var a = 1; function b() { function a() {} a = 10;//相當于只是覆蓋了在b函數這個作用域內聲明的a函數所以這個a=10只是局部變量,影響不到外面的a return; } b(); alert(a);
這就是為什么,我們寫代碼的時候,變量定義總要寫在最前面。
四中變量進入作用域被提升的順序以下文字引用自阮一峰的教程函數一章(2.7)下方評論(需要梯子才能看到評論)
關于 Hoisting 那部分,有兩點值得說明:
Hoisting 的作用范圍是隨著函數作用域的。我理解在這里尚未講到函數作用域,不過可以提一句提醒讀者注意,然后鏈接至作用域的部分進一步探討;
“Hoisting 只對 var 聲明的變量有效”,不盡然如此(意思是不完全對)。變量聲明的提升并非 hoisting 的全部,JavaScript 有四種讓聲明在作用域內獲得提升的途徑(按優先級):
-語言定義的聲明,如 this 和 arguments。你不能在作用域內重新定義叫做 this 的變量,是因為 this是語言自動定義的聲明,并且它的優先級最高,也就是被 Hoisting 到最頂上了,沒人能覆蓋它
-形式參數。雖然我們無需用 var 來修飾形式參數,但是形式參數的確也是變量,并且被自動提升到次高的優先級-函數聲明。除了 var 以外,function declaration 也可以定義新的命名,并且同樣會被 hoisting
至作用域頂部,僅次于前兩者-最后,是本文提到的常規變量,也就是 var 聲明的變量
對于上面話的理解:
也就是說優先級越高的越沒法被覆蓋.那么表現在代碼層,提升時就會轉換成下面這樣:
提升時代碼表現上的排序為:
1 var a;
2 function f (){};
3 形參
4 this和arguments
這就解釋了在提升時,函數聲明時賦值聲明的函數會覆蓋函數表達式方式的聲明(function f (){};)的函數,因為函數表達式提升了(創建,初始化,賦值都被提升),而賦值時的函數只在賦值的時候才被解析.
var f = function () { console.log("1"); } function f() { console.log("2"); } f() // 1
如果同時采用function命令和賦值語句聲明同一個函數,最后總是采用賦值語句的定義。提升的本質
參考
方應杭的文章:let
要搞清楚提升的本質,需要理解 JS 變量的「創建create、初始化initialize 和賦值assign」
有的地方把創建說成是聲明(declare),為了將這個概念與變量聲明區別開,我故意不使用聲明這個字眼。
有的地方把初始化叫做綁定(binding),但我感覺這個詞不如初始化形象。
我們來看看 var 聲明的「創建、初始化和賦值」過程
假設有如下代碼:
function fn(){ var x = 1 var y = 2 } fn()
在執行 fn 時,會有以下過程(不完全):
進入 fn,為 fn 創建一個環境。
找到 fn 中所有用 var 聲明的變量,在這個環境中「創建」這些變量(即 x 和 y)。
將這些變量「初始化」為 undefined。
開始執行代碼
x = 1 將 x 變量「賦值」為 1
y = 2 將 y 變量「賦值」為 2
也就是說 var 聲明會在代碼執行之前就將「創建變量,并將其初始化為 undefined」。
這就解釋了為什么在 var x = 1 之前 console.log(x) 會得到 undefined。
接下來來看 function 聲明的「創建、初始化和賦值」過程
假設代碼如下:
fn2() function fn2(){ console.log(2) }
JS 引擎會有一下過程:
找到所有用 function
聲明的變量,在環境中「創建」這些變量。
將這些變量「初始化」并「賦值」為 function(){console.log(2) }。(三步全部都被提升)
開始執行代碼 fn2()
也就是說 function 聲明會在代碼執行之前就「創建、初始化并賦值」。 (三步全部都被提升)
接下來看 let 聲明的「創建、初始化和賦值」過程
假設代碼如下:
{ let x = 1 x = 2 }
我們只看 {} 里面的過程:
找到所有用 let 聲明的變量,在環境中「創建」這些變量
開始執行代碼(注意現在還沒有初始化)
執行 x = 1,將 x 「初始化」為 1(這并不是一次賦值,如果代碼是 let x,就將 x 初始化為 undefined)
執行 x = 2,對 x 進行「賦值」
這就解釋了為什么在 let x 之前使用 x 會報錯:
let x = "global" { console.log(x) // Uncaught ReferenceError: x is not defined let x = 1 }
原因有兩個
console.log(x) 中的 x 指的是下面的 x,而不是全局的 x
執行 log 時 x 還沒「初始化」,所以不能使用(也就是所謂的暫時死區)
看到這里,你應該明白了 let 到底有沒有提升:
**let 的「創建」過程被提升了,但是初始化沒有提升。
var 的「創建」和「初始化」都被提升了。
function 的「創建」「初始化」和「賦值」都被提升了。**
最后看 const,其實 const 和 let 只有一個區別,那就是 const 只有「創建」和「初始化」,沒有「賦值」過程。
這四種聲明,用下圖就可以快速理解:
所謂暫時死區,就是不能在初始化之前,使用變量。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/94751.html
摘要:回調傳遞函數是將函數當做值并作為參數傳遞給函數。這個例子中就是因為事件綁定機制中的傳入了回調函數,產生了閉包,引用著所在的作用域,所以此處的數據無法從內存中釋放。 javascript作用域 一門語言需要一套設計良好的規則來存儲變量,并且之后可以方便的找到這些變量,這逃規則被稱為作用域。 這也意味著當我們訪問一個變量的時候,決定這個變量能否訪問到的依據就是這個作用域。 一、詞法作用域 ...
摘要:建筑的頂層代表全局作用域。實際的塊級作用域遠不止如此塊級作用域函數作用域早期盛行的立即執行函數就是為了形成塊級作用域,不污染全局。這便是閉包的特點吧經典面試題下面的代碼輸出內容答案個如何處理能夠輸出閉包方式方式下一篇你不知道的筆記 下一篇:《你不知道的javascript》筆記_this 寫在前面 這一系列的筆記是在《javascript高級程序設計》讀書筆記系列的升華版本,旨在將零碎...
摘要:除此以外,讓元素脫離文檔流也是一個很好的方法。因為元素一旦脫離文檔流,它對其他元素的影響幾乎為零,性能的損耗就能夠有效局限于一個較小的范圍。講完重排與重繪,往元素上綁定事件也是引起性能問題的元兇。高性能這本書非常精致,內容也非常豐富。 showImg(https://segmentfault.com/img/bVJgbt?w=600&h=784); 入手《高性能JavaScript》一...
摘要:對比常量聲明與聲明常量聲明與聲明,都是塊級聲明。最后一點全局塊級綁定與不同于的另一個方面是在全局作用域上的表現。塊級綁定新的最佳實踐在的發展階段,被廣泛認可的變量聲明方式是默認情況下應當使用而不是。總結與塊級綁定將詞法作用域引入。 var變量與變量提升 使用var關鍵字聲明的變量,無論其實際聲明位置在何處,都會被視為聲明于所在函數的頂部(如果聲明不在任意函數內,則被視為在全局作用域的頂...
摘要:基于函數進行調用的,用來確保函數是在指定的值所在的上下文中調用的。添加私有函數目前上面為類庫添加的屬性都是公開的,可以被隨時修改。以基于的富應用開發為主要學習資料。 控制類庫的作用域 在類和實例中都添加proxy函數,可以在事件處理程序之外處理函數的時候保持類的作用域。下面是不用proxy的辦法: var Class = function(parent){ var klas...
閱讀 2495·2021-11-15 18:14
閱讀 1720·2021-10-14 09:42
閱讀 3760·2021-10-11 10:58
閱讀 3961·2021-10-09 09:44
閱讀 2421·2021-09-26 09:55
閱讀 2443·2021-09-24 10:38
閱讀 2035·2021-09-04 16:48
閱讀 3276·2021-09-02 15:21