為什么JavaScript沒(méi)有塊級(jí)作用域
最近在看ES2015 實(shí)戰(zhàn),里面有句話是這么說(shuō)的
JavaScript 中沒(méi)有塊級(jí)作用域
可能會(huì)對(duì)這個(gè)問(wèn)題大家可能有點(diǎn)不理解,先看個(gè)例子
var a = [] for(var i = 0; i < 10; i++){ a[i] = function(){ console.log(i); } } a[6]();
我想很多人會(huì)覺得這個(gè)問(wèn)題的結(jié)果是6,然而很不幸,答案是10.在試試別的呢.a[7]()、a[8]()、a[8]()結(jié)果都是10!!
由于JS在處理primitive的變量的時(shí)候,很多時(shí)候會(huì)把primitive變量包裝成對(duì)應(yīng)的對(duì)象來(lái)處理,比如對(duì)于var str = "hello world";str.slice(1).
JS真正的處理過(guò)程大概是var str = "hello world";new String(str).slice(1).這樣的過(guò)程可能會(huì)對(duì)我們理解問(wèn)題造成困擾.
這里為了解釋這個(gè)問(wèn)題,同時(shí)i屬于primitive類型中的number類型,我就顯式的聲明為Number類型.由于基本類型的賦值過(guò)程就是重新申請(qǐng)內(nèi)存,修改變量的指向的過(guò)程,對(duì)于這一過(guò)程我們也用重新new Number對(duì)象的過(guò)程來(lái)模擬.修改過(guò)后的代碼如下:
var a = [] var i = new Number(0); for(; i < 10; i = new Number(i+1)){ a[i] = function(){ console.log(i.toString()); } } a[6](); // 10 a[7](); // 10 a[8](); // 10 a[9](); // 10
下面結(jié)合一段程序,我們來(lái)看看這些這變量的相對(duì)內(nèi)存地址
(function() { var id = 0; function generateId() { return id++;}; Object.prototype.id = function() { var newId = generateId(); this.id = function() { return newId; }; return newId; }; })(); var a = [] var i = new Number(0); console.log(i.id());// 0 for(; i < 10; i = new Number(i+1),i.id()){ a[i] = function(){ console.log(i.id()); console.log(i.toString()); } } a[6](); // 10 10 a[7](); // 10 10 a[8](); // 10 10 a[9](); // 10 10 console.log(i.id())// 10
這邊我們的i的整個(gè)的”賦值”的效果我們確實(shí)是模擬出來(lái)了,i的相對(duì)地址從0變到10(最后還需要加一次才可以跳出for循環(huán)).
在看i的相對(duì)地址的同時(shí),我們發(fā)現(xiàn)一個(gè)問(wèn)題:a[x](x:0~9)對(duì)應(yīng)的函數(shù)在執(zhí)行的時(shí)候,所引用的i的相對(duì)地址都為10.為什么呢?
這里就要牽扯出塊級(jí)作用域問(wèn)題來(lái),這里我們引用阮一峰在ES6入門中的一段話:
ES5只有全局作用域和函數(shù)作用域,沒(méi)有塊級(jí)作用域.
ES5就是大家使用最廣泛的JS的版本.這句話說(shuō)在javascript中,是不存在塊作用域的.只存在全局作用域和塊級(jí)作用域.
怎么理解呢?舉個(gè)例子
for(var i = 0;i < 10; i++){ console.log(i); } console.log(i);//10 console.log(window.i);//10
直觀的看,我們覺得for循環(huán)是一個(gè)代碼塊,應(yīng)該屬于一個(gè)塊級(jí)作用域.但是這里不僅能正常的輸出0~9,居然還可以在for循環(huán)的外部輸出10.同時(shí)我們發(fā)現(xiàn),雖然我們是在for循環(huán)上定義的i,但是似乎i是掛在了全局的window對(duì)象上(如果是nodejs的執(zhí)行環(huán)境,就會(huì)掛到global對(duì)象上)
所以說(shuō)在JavaScript中for循環(huán)之類的block并不會(huì)起到一個(gè)塊級(jí)作用域的效果,在for循環(huán)之類的代碼塊中定義變量,跟在當(dāng)前所在的作用域中直接定義變量沒(méi)什么區(qū)別.
但是我們可以通過(guò)函數(shù)隔離出作用域出來(lái):
(function(){ for(var i = 0;i < 10; i++){ console.log(i); } console.log(i); })() console.log(i);////i is not defined
同時(shí)如果執(zhí)行console.log(window.i);會(huì)得到undefined的結(jié)果.這里我們用一個(gè)立即執(zhí)行函數(shù)來(lái)形成一個(gè)作用域.起到類似于代碼塊的作用,出了這個(gè)函數(shù)作用域,就不再可以訪問(wèn)i這個(gè)變量.但是在函數(shù)作用域內(nèi)可以任意訪問(wèn)i.
回到之前的問(wèn)題,同時(shí)結(jié)合JavaScript中只有全局作用域和塊級(jí)作用域再來(lái)理解一下.我們?cè)趂or循環(huán)中,定義的i肯定是定義在當(dāng)前作用域的,也就是window作用域.在循環(huán)體中,我們給a[i]賦值了一個(gè)函數(shù),當(dāng)我們執(zhí)行這個(gè)函數(shù)時(shí),情況如下:
function中不存在i,于是順著作用域鏈去window作用域找得到了i.我們這個(gè)時(shí)候輸出的i就是這個(gè)i.由于i在跳出循環(huán)最后一次的+1,使得i變成了10,所以輸出結(jié)果一直都是10.但是我們真正需要的i不是最后的i,而是中間過(guò)程中的i.如果要解決這個(gè)問(wèn)題,我們需要拋開i這個(gè)變量(因?yàn)樽詈蟮膇不可避免的變成10).我們要讓a[0]對(duì)應(yīng)的function引用0這個(gè)值,讓a[1]對(duì)應(yīng)的function引用1這個(gè)值.如下圖所示:
在回到我們之前的代碼.
我們?cè)趫D中的箭頭出是可以正確的訪問(wèn)i(0~9).這里由于for循環(huán)并沒(méi)有自己形成一個(gè)塊級(jí)作用域.導(dǎo)致了我們順著作用域鏈去訪問(wèn)i的時(shí)候就訪問(wèn)到了for循環(huán)定義的i.
這里我們用一個(gè)立即執(zhí)行函數(shù)包裹我們的代碼,就可以形成一個(gè)作用域,同時(shí)我們?yōu)槠鋫髦礽.如下:
var a = [] var i = new Number(0); console.log(i.id());// 0 for(; i < 10; i = new Number(i+1),i.id()){ (function(i){ a[i] = function(){ console.log(i.id()); console.log(i.toString()); } })(i); a[6](); // 6 6 a[7](); // 7 7 a[8](); // 8 8 a[9](); // 9 9 console.log(i.id());// 10 }
由于這個(gè)立即執(zhí)行函數(shù)引用著數(shù)值0~9,當(dāng)我們執(zhí)行函數(shù)a[i]的時(shí)候,會(huì)順著作用域鏈先找到這個(gè)立即執(zhí)行函數(shù)的作用域.立即執(zhí)行函數(shù)維護(hù)著0~9的數(shù)值引用,我們就可以在函數(shù)a[i]中正確的輸出i的值.通過(guò)執(zhí)行結(jié)果,我們可以看到,不光執(zhí)行結(jié)果是對(duì)的,同時(shí)我們引用的值的相對(duì)內(nèi)存地址也都是對(duì)的.接著我們把原來(lái)為了測(cè)試顯式聲明的Number對(duì)象改回去.如下:
var a = []; for(var i = 0; i < 10; i++){ (function(i){ a[i] = function(){ console.log(i); } })(i); }
最后我們?cè)賮?lái)看看ES6的語(yǔ)法中推薦用let代替var以及經(jīng)過(guò)bable編譯生成ES5的代碼是如何的:
//ES6代碼 var a = [] for(let i = 0; i < 10; i++){ a[i] = function(){ console.log(i); } } a[6](); //babel編譯生成的ES5代碼 "use strict"; var a = []; var _loop = function _loop(i) { a[i] = function () { console.log(i); }; }; for (var i = 0; i < 10; i++) { _loop(i); } a[6]();
看~我們的解決方法和ES6的解決方法是不是很像.這里我們的立即執(zhí)行函數(shù)相當(dāng)于生成的ES5代碼中的_loop函數(shù)以及_loop(i)的執(zhí)行.
- 深入理解JavaScript中的塊級(jí)作用域、私有變量與模塊模式
- JavaScript的作用域和塊級(jí)作用域概念理解
- Javascript中的作用域及塊級(jí)作用域
- JavaScript匿名函數(shù)之模仿塊級(jí)作用域
- 解析JavaScript模仿塊級(jí)作用域
- 通過(guò)函數(shù)作用域和塊級(jí)作用域看javascript的作用域鏈
- JavaScript使用閉包模仿塊級(jí)作用域操作示例
- js類中的公有變量和私有變量
- javascript 動(dòng)態(tài)生成私有變量訪問(wèn)器
- JavaScript私有變量實(shí)例詳解
- JS塊級(jí)作用域和私有變量實(shí)例分析
相關(guān)文章
JavaScript實(shí)現(xiàn)旋轉(zhuǎn)輪播圖
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)旋轉(zhuǎn)輪播圖,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08分享15個(gè)JavaScript的重要數(shù)組方法
這篇文章主要介紹了分享15個(gè)JavaScript的重要數(shù)組方法,數(shù)組方法的重要一點(diǎn)是有些是可變的,有些是不可變的。在決定針對(duì)特定問(wèn)題使用哪種方法時(shí),務(wù)必牢記,下文就來(lái)分享重要數(shù)組方法,需要的小伙伴可以參考一下2022-05-05javascript計(jì)時(shí)器編寫過(guò)程與實(shí)現(xiàn)方法
這篇文章主要為大家詳細(xì)介紹了javascript計(jì)時(shí)器編寫過(guò)程與實(shí)現(xiàn)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-02-02JS異步宏隊(duì)列與微隊(duì)列原理區(qū)別詳解
這篇文章主要介紹了JS異步宏隊(duì)列與微隊(duì)列原理區(qū)別詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07使用Bootstrap Tabs選項(xiàng)卡Ajax加載數(shù)據(jù)實(shí)現(xiàn)
這篇文章主要介紹了使用Bootstrap Tabs選項(xiàng)卡Ajax加載數(shù)據(jù)實(shí)現(xiàn),以及遇到的問(wèn)題,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12js生成的驗(yàn)證碼的實(shí)現(xiàn)與技術(shù)分析
本文主要是分享了一段由JS生成驗(yàn)證碼并驗(yàn)證的代碼,非常簡(jiǎn)單,并分析了此方法的實(shí)用性,提供給大家參考下2014-09-09遍歷json 對(duì)象的屬性并且動(dòng)態(tài)添加屬性的實(shí)現(xiàn)
下面小編就為大家?guī)?lái)一篇遍歷json 對(duì)象的屬性并且動(dòng)態(tài)添加屬性的實(shí)現(xiàn)。小編覺的挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-12-12layer iframe 設(shè)置關(guān)閉按鈕的方法
今天小編就為大家分享一篇layer iframe 設(shè)置關(guān)閉按鈕的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-09-09