摘要:允許在塊級作用域內聲明函數。上面代碼中,存在全局變量,但是塊級作用域內又聲明了一個局部變量,導致后者綁定這個塊級作用域,所以在聲明變量前,對賦值會報錯。
ES5的作用域
變量起作用的范圍,js中能創建作用域的只能是函數
{ let a = 1; var b = 2; } console.log(a); // a is not defined console.log(b); // 2
var的作用域就是所在的函數體
let的作用域就是所在的代碼塊
詞法作用域和函數作用域當代碼寫好的時候,能夠根據代碼的結構確定變量的作用域,這種情況下的作用域就是詞法作用域。js就是此法作用域,不是動態作用域。
在某個函數中使用var聲明變量,那個變量就將被視作一個局部變量,只存在于函數中.
作用域鏈函數在調用結束時,該函數
作用域 會被銷毀,里面的所有局部變量也會被銷毀。
console.log(a); // a is not defined function test() { a = 1; } test();
(根據javascript高級程序設計第四章)解析上面的代碼
js中存在全局執行環境和由函數形成的局部執行環境這兩種,統稱為執行環境(這里可以理解成作用域);
執行環境都會對應一個變量對象,包含當前環境的變量和函數(函數中的參數也作為函數執行環境的變量,即函數所在作用域的局部變量,函數的活動對象包括this、arguments以及內部的變量和函數);
只有當函數執行時會形成作用域鏈,作用域鏈的前端始終是當前執行代碼所在的執行環境對應的變量對象,往后是下一個(外部)變量對象,直到最外邊的全局執行環境的變量對象(所謂的作用域鏈就是變量對象組成的一條線);
變量對象中變量的解析查找就是沿著作用域鏈一級一級查找;
如果執行環境中的變量沒有用var聲明,那么在函數執行時(這樣才會形成作用域鏈)會沿著作用域鏈一級一級查找變量對象,如果沒有找到則會在全局變量對象中聲明該變量并初始化。
綜上,上面的代碼可以改寫成下面這樣
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();
上述答案是輸出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
只提升函數對應變量,其他變量直接不提升,同時將變量的聲明var去掉(在一般定義過程中不推薦使用同名變量)
函數和函數同名都提升,但是后面的會覆蓋前面的
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從來不會告訴你是否多次聲明了同一個變量;遇到這種情況,它只會對后續的聲明視而不見(不過,它會執行后續聲明中的變量初始化)。
段是指標簽,代碼執行時不分段的
ES5塊級作用域中的函數聲明
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 ); // undefinedES6的作用域 塊級作用域
塊級作用域就是包含在{}里面的
function f1() { let n = 5; if (true) { let n = 10; } console.log(n); // 5 }ES6塊級作用域中函數聲明
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 functionlet和const都存在暫時性死區
只要塊級作用域內存在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 }let&const相同點
支持塊級作用域;
變量不能提升;
存在暫時性死區
不可重復聲明
let&const區別let聲明變量,const聲明常量
const聲明時必須賦值
const foo; // SyntaxError: Missing initializer in const declaration
聲明基本數據類型必須是寫死的常量
const PI = 3.1415; PI // 3.1415 PI = 3; // TypeError: Assignment to constant variable.
const聲明引用數據類型必須是變量的內存地址不變
ES6中頂層對象ES5 只有兩種聲明變量的方法:var命令和function命令。ES6 除了添加let和const命令,還有import命令和class命令。所以,ES6 一共有6種聲明變量的方法。
現狀: ES5頂層對象很混亂
瀏覽器里面,頂層對象是window,但 Node 和 Web Worker 沒有window。
瀏覽器和 Web Worker 里面,self也指向頂層對象,但是Node沒有self。
Node 里面,頂層對象是global,但其他環境都不支持。(詳見http://es6.ruanyifeng.com/#do...)
ES6的變動
ES6 為了改變這一點,一方面規定,為了保持兼容性,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
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/89594.html
摘要:函數作用域雖然不存在真正意義上的塊級作用域,但是存在函數作用域,為了解決上述偽塊級作用域的問題,使用函數解決法如下結果為結果為注意以上生成函數作用域的寫法存在兩個問題,第一申明了全局的具名函數,污染了全局作用域。 ES5和ES6作用域 ES5的塊級作用域 ES5的塊級作用域是一個偽塊級作用域,代碼塊:{},它的塊里面和塊外面都是共用一個作用域,即: Example: { var...
摘要:和數組遍歷方法詳解在中常用的種數組遍歷方法原始的循環語句數組對象內置方法數組對象內置方法數組對象內置方法數組對象內置方法數組對象內置方法數組對象內置方法數組對象內置方法數組對象內置方法循環語句中新增加了一種循環語句三種數組循環示例如下原始循 ES5和ES6數組遍歷方法詳解 在ES5中常用的10種數組遍歷方法: 1、原始的for循環語句2、Array.prototype.forEach數...
摘要:不同的是函數體并不會再被提升至函數作用域頭部,而僅會被提升到塊級作用域頭部避免全局變量在計算機編程中,全局變量指的是在所有作用域中都能訪問的變量。 ES6 變量作用域與提升:變量的生命周期詳解從屬于筆者的現代 JavaScript 開發:語法基礎與實踐技巧系列文章。本文詳細討論了 JavaScript 中作用域、執行上下文、不同作用域下變量提升與函數提升的表現、頂層對象以及如何避免創建...
摘要:命令用于規定模塊的對外接口,命令用于輸入其他模塊提供的功能所以在一定程度上來說,也具有聲明變量的功能。當沒有聲明,直接給變量賦值時,會隱式地給變量聲明,此時這個變量作為全局變量存在。 前言 如果文章中有出現紕漏、錯誤之處,還請看到的小伙伴多多指教,先行謝過 在ES5階段,JavaScript 使用 var 和 function 來聲明變量, ES6 中又添加了let、const、imp...
摘要:使用或調用由于已經在詞法層面完成了綁定,通過或方法調用一個函數時,只是傳入了參數而已,對并沒有什么影響箭頭函數不會在其內部暴露出參數等等,都不會指向箭頭函數的,而是指向了箭頭函數所在作用域的一個名為的值如果有的話,否則,就是。 ES6之箭頭函數 標簽(空格分隔): 未分類 返回值 單行函數體默認返回改行計算結果, 多行需要指定返回值 let c = (a,b)=>a+b; conso...
閱讀 977·2021-11-22 09:34
閱讀 2161·2021-11-11 16:54
閱讀 2196·2021-09-27 14:00
閱讀 940·2019-08-30 15:55
閱讀 1525·2019-08-29 12:46
閱讀 599·2019-08-26 18:42
閱讀 639·2019-08-26 13:31
閱讀 3183·2019-08-26 11:52