var x = 1;
function foo(x, y = function() { x = 2; }) {
var x = 3;
y();
console.log(x);
}
foo() // 3
x // 1
上面是從阮一峰老師寫的es6入門一書中函數(shù)作用域倒數(shù)第二個例子,阮一峰老師的解釋是這樣的
上面代碼中,函數(shù)foo的參數(shù)形成一個單獨作用域。這個作用域里面,首先聲明了變量x,然后聲明了變量y,y的默認值是一個匿名函數(shù)。這個匿名函數(shù)內(nèi)部的變量x,指向同一個作用域的第一個參數(shù)x。函數(shù)foo內(nèi)部又聲明了一個內(nèi)部變量x,該變量與第一個參數(shù)x由于不是同一個作用域,所以不是同一個變量,因此執(zhí)行y后,內(nèi)部變量x和外部全局變量x的值都沒變。
瀏覽器中的運行結(jié)果也和預(yù)期一樣
而通過Babel將這個例子轉(zhuǎn)es5后為
"use strict";
var x = 1;
function foo(x) {
var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {
x = 2;
};
var x = 3;
y();
console.log(x);
}
foo(); // 3
x; // 1
也就是下圖所展示的內(nèi)容
再放到瀏覽器運行時,產(chǎn)生了下圖
函數(shù)執(zhí)行結(jié)果居然不再是es6環(huán)境當(dāng)中的3
根據(jù)阮一峰老師所說
一旦設(shè)置了參數(shù)的默認值,函數(shù)進行聲明初始化時,參數(shù)會形成一個單獨的作用域(context)。等到初始化結(jié)束,這個作用域就會消失。這種語法行為,在不設(shè)置參數(shù)默認值時,是不會出現(xiàn)的。
那么這個參數(shù)作用域應(yīng)該如何理解,而es6的代碼通過Babel轉(zhuǎn)換后放到瀏覽器運行結(jié)果發(fā)生改變?這是不是說是Babel轉(zhuǎn)換的問題?那么es6應(yīng)當(dāng)以何為標(biāo)準?
Babel的實現(xiàn)是錯的。
這是ECMA-262中 9.2.12函數(shù)聲明初始化 小節(jié)中的部分說明:
...If the function’s formal parameters do not include any defaultvalue initializers then the body declarations are instantiated in the same Environment Record as the parameters.If default value parameter initializers exist, a second Environment Record is created for the body declarations...
"...如果函數(shù)形參不含有默認參數(shù),那么函數(shù)體聲明和參數(shù)在同一個Enviroment Record
中初始化。否則將為函數(shù)體聲明創(chuàng)建第二個Enviroment Record
..."
這里可以簡單地將Enviroment Record
看做獨立的作用域。詳細說明在這里
所以當(dāng)函數(shù)存在默認參數(shù)的時候,應(yīng)為參數(shù)創(chuàng)建一個獨立的作用域,然后在這個作用域之中再創(chuàng)建函數(shù)體的作用域,因此一個定義在全局環(huán)境的、帶有默認參數(shù)的函數(shù)聲明,在運行時共產(chǎn)生至少3個作用域,長這個樣子(這里"作用域"更準確的說法是Lexical Enviroment
,詞法環(huán)境):
(為什么說至少呢,因為function body內(nèi)部可能還會有其他作用域,這個不是重點)
ES Spec之所以這么規(guī)定,是因為如果默認參數(shù)引用了函數(shù)作用域外部的變量,同時函數(shù)內(nèi)部有同名的變量存在的話,那么實際所使用的變量應(yīng)該是外部的變量,而不是函數(shù)內(nèi)部的。這是符合人類的思考習(xí)慣的,你不會在一個變量定義之前就使用它。
所以如果要把含有默認參數(shù)的函數(shù)轉(zhuǎn)為ES5寫法的話,必須用另一個函數(shù)隔絕參數(shù)和函數(shù)體,以實現(xiàn)作用域的隔離。以題目的做法為例,轉(zhuǎn)化后的函數(shù)應(yīng)該長這樣:
function foo(x, y) {
if (typeof y === 'undefined') y = function () { x = 2; };
return (function() {
var x = 3;
y();
console.log(x);
}).call(this, x, y)
}
如果在Babel基礎(chǔ)上修改的話,應(yīng)該是這樣:
function foo(x) {
var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {
x = 2;
};
return (function () {
var x = 3;
y();
console.log(x);
}).call(this, x, y)
}
Babel沒有這么做可能是有別的考慮,也可能是沒想到或者不愿意改,無論如何當(dāng)前的實現(xiàn)都是錯誤的。
最后放一個Chrome運行這段代碼的scope狀態(tài),可以看出是按spec實現(xiàn)的:
Local
即為上面圖示中的parameters,Block
就是function body
另外阮一峰說的也不全對,初始化之后作用域是不會消失的,否則運行函數(shù)y()
的時候就沒辦法獲取Local
里面的x
了
你可能要補一下js的基礎(chǔ)變量提升
。在運行時(細講的話有兩個階段),變量都會提升到當(dāng)前作用域的頂端的,所以你的代碼等價于
var x;
function foo(x) {
// 變量提升
var y;
// 變量提升
var x;
y = function () { x = 2};
x = 3;
// y函數(shù)改變了x的值
y();
console.log(x);
}
x = 1;
foo(); // 2
console.log(x); // 1
所以你的foo()函數(shù)里一旦有var x
的存在,那么這個函數(shù)內(nèi)部的x都是局部的變量
然后要譴責(zé)你的一點是:下次能用markdown貼代碼嗎?
北大青鳥APTECH成立于1999年。依托北京大學(xué)優(yōu)質(zhì)雄厚的教育資源和背景,秉承“教育改變生活”的發(fā)展理念,致力于培養(yǎng)中國IT技能型緊缺人才,是大數(shù)據(jù)專業(yè)的國家
北大青鳥中博軟件學(xué)院創(chuàng)立于2003年,作為華東區(qū)著名互聯(lián)網(wǎng)學(xué)院和江蘇省首批服務(wù)外包人才培訓(xùn)基地,中博成功培育了近30000名軟件工程師走向高薪崗位,合作企業(yè)超4
中公教育集團創(chuàng)建于1999年,經(jīng)過二十年潛心發(fā)展,已由一家北大畢業(yè)生自主創(chuàng)業(yè)的信息技術(shù)與教育服務(wù)機構(gòu),發(fā)展為教育服務(wù)業(yè)的綜合性企業(yè)集團,成為集合面授教學(xué)培訓(xùn)、網(wǎng)
達內(nèi)教育集團成立于2002年,是一家由留學(xué)海歸創(chuàng)辦的高端職業(yè)教育培訓(xùn)機構(gòu),是中國一站式人才培養(yǎng)平臺、一站式人才輸送平臺。2014年4月3日在美國成功上市,融資1
曾工作于聯(lián)想擔(dān)任系統(tǒng)開發(fā)工程師,曾在博彥科技股份有限公司擔(dān)任項目經(jīng)理從事移動互聯(lián)網(wǎng)管理及研發(fā)工作,曾創(chuàng)辦藍懿科技有限責(zé)任公司從事總經(jīng)理職務(wù)負責(zé)iOS教學(xué)及管理工作。
浪潮集團項目經(jīng)理。精通Java與.NET 技術(shù), 熟練的跨平臺面向?qū)ο箝_發(fā)經(jīng)驗,技術(shù)功底深厚。 授課風(fēng)格 授課風(fēng)格清新自然、條理清晰、主次分明、重點難點突出、引人入勝。
精通HTML5和CSS3;Javascript及主流js庫,具有快速界面開發(fā)的能力,對瀏覽器兼容性、前端性能優(yōu)化等有深入理解。精通網(wǎng)頁制作和網(wǎng)頁游戲開發(fā)。
具有10 年的Java 企業(yè)應(yīng)用開發(fā)經(jīng)驗。曾經(jīng)歷任德國Software AG 技術(shù)顧問,美國Dachieve 系統(tǒng)架構(gòu)師,美國AngelEngineers Inc. 系統(tǒng)架構(gòu)師。