讀阮老師的摘錄筆記^_^
一赤惊,let/const
1.1 let
let所聲明的變量敲长,只在let命令所在的代碼塊內有效醉鳖。
for循環(huán)還有一個特別之處捡硅,就是設置循環(huán)變量的那部分是一個父作用域,而循環(huán)體內部是一個單獨的子作用域盗棵。
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc
var命令會發(fā)生”變量提升“現象壮韭,即變量可以在聲明之前使用,值為undefined纹因。let命令改變了語法行為喷屋,它所聲明的變量一定要在聲明后使用,否則報錯瞭恰,不存在變量提升屯曹。
暫時性死區(qū)(temporal dead zone,簡稱 TDZ)
只要塊級作用域內存在let命令惊畏,它所聲明的變量就“綁定”(binding)這個區(qū)域恶耽,不再受外部的影響⊙掌簦總之驳棱,在代碼塊內,使用let命令聲明變量之前农曲,該變量都是不可用的社搅。
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
“暫時性死區(qū)”也意味著typeof不再是一個百分之百安全的操作。
typeof x; // ReferenceError
let x;
作為比較乳规,如果一個變量根本沒有被聲明形葬,使用typeof反而不會報錯。typeof undeclared_variable // "undefined"
所以暮的,在沒有l(wèi)et之前笙以,typeof運算符是百分之百安全的,永遠不會報錯《潮纾現在這一點不成立了猖腕。
另外下面的代碼也會報錯,與var的行為不同恨闪。
// 不報錯
var x = x;
// 報錯
let x = x;
不允許重復聲明
let不允許在相同作用域內倘感,重復聲明同一個變量。因此咙咽,不能在函數內部重新聲明參數老玛,除非在函數里面的子作用域聲明。
1.2 塊級作用域
ES6 允許塊級作用域的任意嵌套。內層作用域可以定義和外層作用域同名的變量蜡豹。塊級作用域的出現麸粮,實際上使得獲得廣泛應用的立即執(zhí)行函數表達式(IIFE)不再必要了。
ES5 規(guī)定镜廉,函數只能在頂層作用域和函數作用域之中聲明弄诲,不能在塊級作用域聲明。但是娇唯,瀏覽器沒有遵守這個規(guī)定客燕,為了兼容以前的舊代碼沸手,還是支持在塊級作用域之中聲明函數督勺。ES6 引入了塊級作用域蔬墩,明確允許在塊級作用域之中聲明函數敢茁,但是只在使用大括號的情況下成立佑淀,如果沒有使用大括號,就會報錯彰檬。ES6 規(guī)定伸刃,塊級作用域之中,函數聲明語句的行為類似于let逢倍,在塊級作用域之外不可引用捧颅。
為了減輕因此產生的不兼容問題,ES6在附錄B里面規(guī)定较雕,瀏覽器的實現可以有自己的行為方式碉哑。注意這幾條規(guī)定只對 ES6 的瀏覽器實現有效,其他環(huán)境的實現不用遵守亮蒋,還是將塊級作用域的函數聲明當作let處理扣典。
- 允許在塊級作用域內聲明函數。
- 函數聲明類似于var慎玖,即會提升到全局作用域或函數作用域的頭部贮尖。
- 同時,函數聲明還會提升到所在的塊級作用域的頭部趁怔。
考慮到環(huán)境導致的行為差異太大湿硝,應該避免在塊級作用域內聲明函數。如果確實需要润努,也應該寫成函數表達式关斜,而不是函數聲明語句。
注:這一部分例子看教程代碼铺浇。
本質上蚤吹,塊級作用域是一個語句,將多個操作封裝在一起,沒有返回值裁着。用do表達式(提案)可以使塊級作用域可以變?yōu)楸磉_式繁涂。
let x = do {
let t = f();
t * t + 1;
};
1.3 const命令
const聲明的變量不得改變值,這意味著二驰,const一旦聲明變量扔罪,就必須立即初始化,不能留到以后賦值桶雀。
const實際上保證的矿酵,并不是變量的值不得改動,而是變量指向的那個內存地址不得改動矗积。對于簡單類型的數據(數值全肮、字符串、布爾值)棘捣,值就保存在變量指向的那個內存地址辜腺,因此等同于常量。但對于復合類型的數據(主要是對象和數組)乍恐,變量指向的內存地址评疗,不能控制指向值是否可變。因此茵烈,將一個對象聲明為常量必須非常小心百匆。
如果真的想將對象凍結,應該使用Object.freeze方法呜投。除了將對象本身凍結加匈,對象的屬性也應該凍結。
ES5 只有兩種聲明變量的方法:var命令和function命令仑荐。ES6除了添加let和const命令矩动,后面章節(jié)還會提到,另外兩種聲明變量的方法:import命令和class命令释漆。所以悲没,ES6 一共有6種聲明變量的方法。
1.4 頂層對象的屬性
ES5之中男图,頂層對象的屬性與全局變量是等價的示姿。
頂層對象的屬性與全局變量掛鉤,被認為是JavaScript語言最大的設計敗筆之一逊笆。ES6為了改變這一點栈戳,一方面規(guī)定,為了保持兼容性难裆,var命令和function命令聲明的全局變量子檀,依舊是頂層對象的屬性镊掖;另一方面規(guī)定,let命令褂痰、const命令亩进、class命令聲明的全局變量,不屬于頂層對象的屬性缩歪。
1.5 global對象
ES5 的頂層對象归薛,本身也是一個問題,因為它在各種實現里面是不統(tǒng)一的匪蝙。
- 瀏覽器里面主籍,頂層對象是window,但 Node 和 Web Worker 沒有window逛球。
- 瀏覽器和 Web Worker 里面千元,self也指向頂層對象,但是 Node 沒有self颤绕。
- Node 里面幸海,頂層對象是global,但其他環(huán)境都不支持屋厘。
同一段代碼為了能夠在各種環(huán)境,都能取到頂層對象月而,現在一般是使用this變量汗洒,但是有局限性。
- 全局環(huán)境中父款,this會返回頂層對象溢谤。但是,Node 模塊和 ES6 模塊中憨攒,this返回的是當前模塊世杀。
- 函數里面的this,如果函數不是作為對象的方法運行肝集,而是單純作為函數運行瞻坝,this會指向頂層對象。但是杏瞻,嚴格模式下所刀,這時this會返回undefined。
- 不管是嚴格模式捞挥,還是普通模式浮创,new Function('return this')(),總是會返回全局對象砌函。但是斩披,如果瀏覽器用了CSP(Content Security Policy溜族,內容安全政策),那么eval垦沉、new Function這些方法都可能無法使用煌抒。
現在有一個提案,在語言標準的層面乡话,引入global作為頂層對象摧玫。也就是說,在所有環(huán)境下绑青,global都是存在的诬像,都可以從它拿到頂層對象。墊片庫system.global模擬了這個提案闸婴,可以在所有環(huán)境拿到global坏挠。
二,變量的解構(Destructuring)賦值
2.1 數組的解構賦值
ES6 允許寫成下面這樣let [a, b, c] = [1, 2, 3];
邪乍。本質上降狠,這種寫法屬于“模式匹配”。
如果解構不成功庇楞,變量的值就等于undefined榜配。
let [foo] = [];
let [bar, foo] = [1];
另一種情況是不完全解構,即等號左邊的模式吕晌,只匹配一部分的等號右邊的數組。這種情況下睛驳,解構依然可以成功。
let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4
如果等號的右邊不是數組(或者嚴格地說乏沸,不是可遍歷的結構,參見《Iterator》一章)蹬跃,那么將會報錯。對于 Set 結構還有generator函數蝶缀,也可以使用數組的解構賦值。事實上扼劈,只要某種數據結構具有 Iterator 接口,都可以采用數組形式的解構賦值荐吵。
解構賦值允許指定默認值骑冗。但是要注意條件:注意,ES6 內部使用嚴格相等運算符(===)贼涩,判斷一個位置是否有值;所以遥倦,如果一個數組成員不嚴格等于undefined,默認值是不會生效的袒哥。
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x = 1] = [undefined]; x // 1
let [x = 1] = [null]; x // null
如果默認值是一個表達式,那么這個表達式是惰性求值的堡称,即只有在用到的時候(等號那邊是undefined)瞎抛,才會求值。默認值可以引用解構賦值的其他變量却紧,但該變量必須已經聲明桐臊。let [x = 1, y = x] = []; // x=1; y=1
.
2.2 對象的解構賦值
對象的屬性沒有次序,變量必須與屬性同名晓殊,才能取到正確的值断凶。
如果變量名與屬性名不一致,必須寫成下面這樣巫俺。
這實際上說明认烁,對象的解構賦值是下面形式的簡寫(參見《對象的擴展》一章)。let { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };
也就是說识藤,對象的解構賦值的內部機制砚著,是先找到同名屬性次伶,然后再賦給對應的變量痴昧。真正被賦值的是后者,而不是前者冠王。
let { foo: baz } = { foo: "aaa", bar: "bbb" };
baz // "aaa"
foo // error: foo is not defined
上面代碼中赶撰,foo是匹配的模式,baz才是變量柱彻。
與數組一樣豪娜,解構也可以用于嵌套結構的對象。如果p也要作為變量賦值哟楷,可以寫成下面這樣瘤载。
let obj = {
p: [
'Hello',
{ y: 'World' }
]
};
let { p, p: [x, { y }] } = obj;
x // "Hello"
y // "World"
p // ["Hello", {y: "World"}]
對象的解構也可以指定默認值。默認值生效的條件是卖擅,對象的屬性值嚴格等于undefined鸣奔。如果解構失敗墨技,變量的值等于undefined。如果解構模式是嵌套的對象挎狸,而且子對象所在的父屬性不存在扣汪,那么將會報錯。// 報錯 let {foo: {bar}} = {baz: 'baz'};
锨匆。
如果要將一個已經聲明的變量用于解構賦值崭别,必須非常小心。
// 錯誤的寫法
let x;
{x} = {x: 1};
// SyntaxError: syntax error
// 正確的寫法
let x;
({x} = {x: 1});
上面第一個代碼的寫法會報錯恐锣,因為 JavaScript 引擎會將{x}理解成一個代碼塊茅主,從而發(fā)生語法錯誤。然后代碼將整個解構賦值語句侥蒙,放在一個圓括號里面暗膜,就可以正確執(zhí)行。
解構賦值允許等號左邊的模式之中鞭衩,不放置任何變量名。因此论衍,可以寫出非常古怪的賦值表達式坯台。({} = [true, false]);
對象的解構賦值,可以很方便地將現有對象的方法稠炬,賦值到某個變量首启。
let { log, sin, cos } = Math;
由于數組本質是特殊的對象毅桃,因此可以對數組進行對象屬性的解構准夷。
2.3 字符串的解構賦值
字符串也可以解構賦值。這是因為字符串被轉換成了一個類似數組的對象读宙。const [a, b, c, d, e] = 'hello'; a // "h" b // "e" c // "l" d // "l" e // "o"
结闸。
類似數組的對象都有一個length屬性,因此還可以對這個屬性解構賦值幔亥。let {length : len} = 'hello'; len // 5
察纯。
2.4 數值和布爾值的解構賦值
解構賦值時饼记,如果等號右邊是數值和布爾值,則會先轉為對象即纲。
let {toString: s} = 123;
s === Number.prototype.toString // true
解構賦值的規(guī)則是低斋,只要等號右邊的值不是對象或數組匪凡,就先將其轉為對象病游。由于undefined和null無法轉為對象,所以對它們進行解構賦值买猖,都會報錯玉控。
2.5 函數參數的解構賦值
函數參數的解構也可以使用默認值奸远。undefined就會觸發(fā)函數參數的默認值。
function add([x, y]){
return x + y;
}
add([1, 2]); // 3
注意下面兩種寫法丸冕,第一種x和y等于默認值胖烛,第二種是為函數move的參數指定默認值诅迷。
//first
function move({x = 0, y = 0} = {}) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]
//second
function move({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]
這個例子中是函數參數默認值可以與解構賦值的默認值罢杉,結合起來使用滩租。
2.6 圓括號
建議只要有可能律想,就不要在模式中放置圓括號绍弟。
以下三種解構賦值不得使用圓括號:(a)變量聲明語句 (b)函數參數 /其實也屬于變量聲明/(c)賦值語句的模式 /如([a]) = [5];
/
這里不得使用是指整個或者一部分放置圓括號都不行。
可以使用圓括號的情況只有一種:賦值語句的非模式部分樟遣,可以使用圓括號。
[(b)] = [3]; // 正確
({ p: (d) } = {}); // 正確
上面三行語句都可以正確執(zhí)行澈歉,因為首先它們都是賦值語句屿衅,而不是聲明語句;其次它們的圓括號都不屬于模式的一部分涡尘。第一行語句中响迂,模式是取數組的第一個成員,跟圓括號無關川梅;第二行語句中然遏,模式是p,而不是d丢早。
2.7 變量解構賦值的用途
(1)交換變量的值(2)從函數返回多個值(3)函數參數的定義(4)提取JSON數據(5)函數參數的默認值(6)遍歷Map結構(7)輸入模塊的指定方法
在 es5 時代怨酝,如果想給一個函數默認值的話必須要這樣寫
var es5Fun = function (config) {
var foo = config.foo || 'default foo'
console.log(foo)
}
在 es6 有了結構賦值之后,傳入的參數可以自動賦予默認值
const es6Fun = ({foo = 'default foo'} = {}) => {
console.log(foo)
}