變量起做用的範圍,js中能建立做用域的只能是函數javascript
{ let a = 1; var b = 2; } console.log(a); // a is not defined console.log(b); // 2
var的做用域就是所在的函數體html
let的做用域就是所在的代碼塊前端
當代碼寫好的時候,可以根據代碼的結構肯定變量的做用域,這種狀況下的做用域就是詞法做用域。js就是此法做用域,不是動態做用域。java
在某個函數中使用var聲明變量,那個變量就將被視做一個局部變量,只存在於函數中.es6
函數在調用結束時,該函數<wiz_tmp_highlight_tag class="cm-searching" style="margin-top: 0px; background: yellow;">做用域</wiz_tmp_highlight_tag>會被銷燬,裏面的全部局部變量也會被銷燬。web
console.log(a); // a is not defined function test() { a = 1; } test();
(根據javascript高級程序設計第四章)解析上面的代碼面試
綜上,上面的代碼能夠改寫成下面這樣數組
function test() { a = 1; } test(); console.log(a); // 1
閉包是指有權訪問另外一個函數做用域中的變量的函數瀏覽器
首先區分一點就是函數內部定義的函數,其做用域鏈會包括外部函數。請看下面兩個例子對比閉包
// 案例一 function foo(){ var num = '123'; function bar(){ console.log(num); // '123' } bar(); } foo(); 案例二 function bar(){ console.log(num);// num is not defined } function foo(){ bar(); } foo();
<script> var arr = []; for(var i = 0; i<10; i++) { arr.push(function(){ console.log(i); }) } arr[0](); arr[1](); </script>
上述答案是輸出10
分析: 數組中每一個函數若是執行時其做用域鏈都會保存全局執行環境對應的變量對象,因此函數執行時函數內部的
i
變量會沿着做用域鏈找到全局執行環境中的變量,此時全局執行環境中的變量i
爲10,因此都會輸出10.
若是想輸出1,2,3...,
第一種方法能夠把for循環中的var變爲let,變量i
是let
聲明的,當前的i
只在本輪循環有效,因此每一次循環的i
其實都是一個新的變量,因此最後輸出的是6
。你可能會問,若是每一輪循環的變量i
都是從新聲明的,那它怎麼知道上一輪循環的值,從而計算出本輪循環的值?這是由於 JavaScript 引擎內部會記住上一輪循環的值,初始化本輪的變量i
時,就在上一輪循環的基礎上進行計算。摘自《ECMAScript 6 入門》
第二種方法就象下面案例一下再寫一個for循環;
第三個方法就如同案例三在函數內部再定義一個函數,並當即執行外部函數,使函數的活動對象中變量i
的值每次都不一樣,從而保證內部函數在執行時其做用域鏈會包括外部函數的變量對象。
(通常來講函數執行完畢該函數的做用域和活動變量會被銷燬,可是由於函數裏面定義的函數它的做用域鏈始終會包括外部函數的活動對象,因此外面的函數即便當即執行了,可是活動對象還在內存中,沒有被銷燬)
下面的案例再次鞏固知識點,第二個案例再執行時,全局執行環境的變量對象i
又從新被動態賦值,for循環中函數當即執行,由於函數的參數是按值傳遞的,因此每一個函數獲得的是不一樣的i
值。
// 案例一 var arr = [ { name: '張三1'}, { name: '張三2' }, { name: '張三3' }, { name: '張三4' } ]; for ( var i = 0; i < arr.length; i++) { arr[ i ].sayHello = function () { console.log(i); }; } arr[0].sayHello(); arr[1].sayHello();
// 案例二 var arr = [ { name: '張三1'}, { name: '張三2' }, { name: '張三3' }, { name: '張三4' } ]; for ( var i = 0; i < arr.length; i++) { arr[ i ].sayHello = function () { console.log(i); }; } for ( var i = 0; i < arr.length; i++ ) { arr[ i ].sayHello(); }
// 案例三 var arr = [ { name: '張三1'}, { name: '張三2' }, { name: '張三3' }, { name: '張三4' } ]; for ( var i = 0; i < arr.length; i++) { arr[ i ].sayHello = (function (i) { return function(){ console.log(i); } })(i); }
分爲預解析階段和執行階段
在預解析階段,會將全部的變量聲明(只提高聲明不提高賦值)以及函數聲明(指整個函數),提高到其所在的做用域的最頂上,通常會先提高函數聲明再提高變量聲明。
注意區分函數聲明和函數表達式聲明的區別
在變量提高條件下函數表達式和通常變量的聲明的規則是同樣的。下面的條件式聲明章節還會用案例做對比
// 函數聲明 fn(); function fn() { console.log('hello world'); }
// 函數表達式 fn(); var fn = function() { console.log('nihao'); }
如下是變量提高中的特別狀況
在變量提高狀況下,變量通常被分紅兩種,即通常變量和函數名變量
console.log(typeof f); // function var f; console.log(typeof f); // undefined function f(){}; console.log(typeof f); // undefined
console.log(typeof a); // function function a() { } console.log(typeof a); // function var a = ''; console.log(typeof a); // string
<font color="red">只提高函數對應變量,其餘變量直接不提高,同時將變量的聲明var去掉(在通常定義過程當中不推薦使用同名變量)</font>
都提高,可是後面的會覆蓋前面的
func(); // second func function func(){ console.log("first func"); } function func(){ console.log("second func"); }
// 通常的變量同名對於變量的提高沒有影響,由於提高的只是變量的聲明,不會提高變量的賦值 console.log(typeof a); // undefined var a = 'abc'; console.log(a); // 'abc' var a = 1; console.log(a); // 1
下面有兩個小栗子
var a = 1; var a = 2; console.log(a); // 2
var a = 1; var a; console.log(a); //1
剛剛去查了資料,參考《JavaScript高級程序設計》第7.3章節,原話以下
JavaScript歷來不會告訴你是否屢次聲明瞭同一個變量;遇到這種狀況,它只會對後續的聲明視而不見(不過,它會執行後續聲明中的變量初始化)。
段是指<script></script>
標籤,代碼執行時不分段的
<script> var num = 10; func(); // 第二個func console.log(str); // 報錯 function func(){ console.log("第一個func"); } function func(){ console.log("第二個func"); } </script> <script> var str = 'abc'; console.log(num); // 10 func(); // 第三個func function func(){ console.log("第三個func"); } </script>
ES5 規定,函數只能在頂層做用域和函數做用域之中聲明,不能在塊級做用域聲明。若是確實須要,也應該寫成函數表達式,而不是函數聲明語句。
test(); //報錯 if(true){ function test(){ console.log("我是在if語句中聲明的函數"); } }
// 各個瀏覽器執行結果不一樣,不建議這麼寫 if(flag){ functiont test(){ console.log("flag爲true時執行"); } }else{ function test(){ console.log("flag爲false時執行"); } }
上面兩種狀況在ES5中都是非法的,能夠將上面的demo改寫成下面這樣
// 下面會根據flag狀態決定執行哪段代碼 if(flag){ test = functiont(){ console.log("flag爲true時執行"); } }else{ test = function(){ console.log("flag爲false時執行"); } }
條件式變量聲明能夠被提高。
console.log( num ); // undefined if ( false ) { var num = 123; } console.log( num ); // undefined
塊級做用域就是包含在{}
裏面的
function f1() { let n = 5; if (true) { let n = 10; } console.log(n); // 5 }
ES6 引入了塊級做用域,明確容許在塊級做用域之中聲明函數。ES6 規定,塊級做用域之中,函數聲明語句的行爲相似於let
,在塊級做用域以外不可引用。
原來,若是改變了塊級做用域內聲明的函數的處理規則,顯然會對老代碼產生很大影響。爲了減輕所以產生的不兼容問題,ES6在附錄B裏面規定,瀏覽器的實現能夠不遵照上面的規定,有本身的行爲方式。
var
,即會提高到全局做用域或函數做用域的頭部。下面的例子可以很好的區分解釋ES5和ES6兩個環境下處理塊級做用域中函數聲明的區別
function f() { console.log('I am outside!'); } (function () { if (false) { // 重複聲明一次函數f function f() { console.log('I am inside!'); } } f(); }());
// ES5 環境 function f() { console.log('I am outside!'); } (function () { function f() { console.log('I am inside!'); } if (false) { } f(); // 輸出I am inside! }());
// 瀏覽器的 ES6 環境 function f() { console.log('I am outside!'); } (function () { var f = undefined; if (false) { function f() { console.log('I am inside!'); } } f(); }()); // Uncaught TypeError: f is not a function
只要塊級做用域內存在let命令,它所聲明的變量就「綁定」(binding)這個區域,再也不受外部的影響。
var tmp = 123; if (true) { tmp = 'abc'; // ReferenceError let tmp; }
上面代碼中,存在全局變量tmp,可是塊級做用域內let又聲明瞭一個局部變量tmp,致使後者綁定這個塊級做用域,因此在let聲明變量前,對tmp賦值會報錯。
或者
{ var a = 1; let a = 1; } // 報錯 Uncaught SyntaxError: Identifier 'a' has already been declared
「暫時性死區」是指在使用let
命令聲明變量以前,該變量都是不可用的。
if (true) { let tmp; tmp = 'abc'; // abc }
const foo; // SyntaxError: Missing initializer in const declaration
const PI = 3.1415; PI // 3.1415 PI = 3; // TypeError: Assignment to constant variable.
ES5 只有兩種聲明變量的方法:
var
命令和function
命令。ES6 除了添加let
和const
命令,還有import
命令和class
命令。因此,ES6 一共有6種聲明變量的方法。
window
,但 Node 和 Web Worker 沒有window
。瀏覽器和 Web Worker 裏面,self
也指向頂層對象,可是Node沒有self
。
Node 裏面,頂層對象是global
,但其餘環境都不支持。(詳見http://es6.ruanyifeng.com/#do...)
var
命令和function
命令聲明的全局變量,依舊是頂層對象的屬性;另外一方面規定,let
命令、const
命令、class
命令聲明的全局變量,不屬於頂層對象的屬性。也就是說,從 ES6 開始,全局變量將逐步與頂層對象的屬性脫鉤。let test = 'out'; function f(){ test = 'in'; console.log(window.test);// undefined } f(); console.log(test);// in
上下兩個demo的區別就是test
有沒有用let聲明,當使用let聲明時,瀏覽器會認爲當前的環境是ES6環境,因此聲明的變量不會複製給window;相反若是沒用let聲明test
,瀏覽器就會默認當前環境是ES5。
test = 'out'; function f(){ test = 'in'; onsole.log(window.test); // in } f(); console.log(test);// in
var num = 123; function f1() { console.log( num ); } function f2() { num = 456; f1(); } f2();
上述執行結果爲456