變量聲明
let
和const
是JavaScript里相對(duì)較新的變量聲明方式,而let
在很多方面與var
是相似的,但是可以避免在JavaScript里常見的一些問題.const
是對(duì)let
的一個(gè)增強(qiáng),它能阻止對(duì)一個(gè)變量再次賦值.
因?yàn)門ypeScript是JavaScript的超集,所以它本身支持let
和const
.
var
聲明
一直以來我們都是通過var
關(guān)鍵字定義JavaScript變量:
var a = 10;
我們也能在函數(shù)內(nèi)部定義變量
function f(){
var message = "Hello, world!";
return message;
}
我們也可以在其他函數(shù)內(nèi)部訪問相同的變量
function f() {
var a = 10;
return function g(){
var b = a + 1;
return b;
}
}
var g = f();
g();//return 11
function f() {
var a = 1;
a = 2;
var b = g();//這里調(diào)用了函數(shù),傳入了a=2
a = 3;//這里的賦值a=3并沒有傳入函數(shù)中
return b;
//定義函數(shù)
function g() {
return a;
}
}
f(); // returns 2
作用域規(guī)則
var
聲明有些奇怪的作用域規(guī)則
function f(shouldInitialize: boolean) {
if (shouldInitialize) {
var x = 10;
}
return x;
}
f(true);// 10;
f(false);// undefined
變量雖然定義在if語句里面,但是我們卻可以在語句的外面訪問它.這是因?yàn)?code>var聲明可以在包含它的函數(shù),模塊,命名空間或全局作用域內(nèi)部任何位置被訪問,包含它的代碼塊對(duì)此沒有什么影響,有些人稱此為var
作用域或函數(shù)作用域.函數(shù)參數(shù)也使用函數(shù)作用域.
這些作用域規(guī)則可能會(huì)引發(fā)一些錯(cuò)誤,其中之一就是,多次聲明同一個(gè)變量并不會(huì)報(bào)錯(cuò):
function sumMatrix(matrix: number[][]) {
var sum = 0;
for (var i = 0; i < matrix.length; i++) {
var currentRow = matrix[i];
for (var i = 0; i < currentRow.length; i++) {
sum += currentRow[i];
}
}
return sum;
}//錯(cuò)誤代碼
上面的代碼里,里層的for
循環(huán)會(huì)覆蓋變量i
,因?yàn)樗?code>i都引用相同的函數(shù)作用域內(nèi)的變量.
變量獲取怪異之處
for (var i = 0; i < 10; i++) {
setTimeout(function() { console.log(i); }, 100 * i);
}//10個(gè)10
在這個(gè)for
循環(huán)中,setTimeout
在若干秒后執(zhí)行一個(gè)函數(shù),并且是在for
循環(huán)結(jié)束后.for
循環(huán)結(jié)束后,i
的值為10.所以函數(shù)被調(diào)用的時(shí)候,它會(huì)打印出10.
一個(gè)通常的解決方法是使用立即執(zhí)行的函數(shù)表達(dá)式(IIFE)來捕獲每次迭代時(shí)i
的值
for (var i = 0; i <10; i++){
(function(i) {
setTimeout(function(){
console.log(i);
}, 100 * i)
})(i);
}
參數(shù)i
會(huì)覆蓋for
循環(huán)里的i
,但是因?yàn)槲覀兤鹆送瑯拥拿?所以我們不用怎么改for
循環(huán)里的代碼
let
聲明
除了名字不同外,let
與var
的寫法一致
let hello = "Hello";
主要的區(qū)別不在語法上,而是語義.
塊作用域
當(dāng)用let
聲明一個(gè)變量,它使用的是詞法作用域或塊作用域.不同于使用var
聲明的變量那樣可以在包含它們的函數(shù)外訪問,塊作用域變量在包含它們的塊或for循環(huán)之外是不能訪問的.
function f(input: boolean) {
let a = 100;
if (input){
let b = a + 1;
return b;
}
return b;//報(bào)錯(cuò)
}
a 的作用域在f
函數(shù)體內(nèi),而b
的作用域是if
語句塊里.
在catch
語句里聲明的變量也具有同樣的作用域規(guī)則.
try {
throw "oh no!";
}
catch (e) {
console.log("Oh well.")
}
console.log(e);//報(bào)錯(cuò),該變量未定義
擁有塊級(jí)作用域的變量的另一個(gè)特點(diǎn)是,它們不能在聲明之前讀或?qū)?雖然這些變量始終"存在"與它們的作用域里,但在直到聲明它的代碼之前的區(qū)域都屬于暫時(shí)性死區(qū).它只能說明我們不能在let
語句之前訪問它們,而TypeScript可以告訴我們這些信息
a++;//illegal to user 'a' before it's declared;
let a;
注意:我們?nèi)匀豢梢栽谝粋€(gè)擁有塊作用域變量被聲明前獲取它.只是我們不能在變量聲明前去調(diào)用那個(gè)函數(shù),如果生成代碼為ES2015運(yùn)行時(shí)會(huì)拋出一個(gè)錯(cuò)誤,然而TypeScript是不會(huì)報(bào)錯(cuò)的.
function foo(){
return a;//這里可以使用a變量
}
foo();//這里調(diào)用在變量聲明前應(yīng)該報(bào)錯(cuò)(但是TypeScript不報(bào)錯(cuò))
let a;
重定義及屏蔽
使用var
聲明時(shí)它不在乎你聲明多少次:你只會(huì)得到一個(gè).
function (){
var x;
var x;
if (true) {
var x;
}
}
在上面的代碼里所有x
的聲明實(shí)際上都引用一個(gè)相同的'x',并且這是完全有效的代碼,這經(jīng)常會(huì)導(dǎo)致一些bug的出現(xiàn).而現(xiàn)在,let
聲明就不會(huì)那么寬松了.
let x = 10;
let x = 20;//錯(cuò)誤,不能在1個(gè)作用域里多次聲明'x'
并不是要求兩個(gè)均是塊級(jí)作用域的聲明TypeScript才會(huì)給出一個(gè)錯(cuò)誤的警告
fun f(x){
let x = 100;//因?yàn)槠鋵?shí)在函數(shù)的傳參過程中,x已經(jīng)被聲明,所以會(huì)報(bào)錯(cuò);
}
function g(){
let x = 100;
var x = 100;//這里的意思就是說同一個(gè)塊級(jí)作用域下不能聲明兩個(gè)變量(只要有一個(gè)使用了let)
}
并不是說塊級(jí)作用域變量不能用函數(shù)作用域變量來聲明.而是塊級(jí)作用域變量需要在明顯不同的塊里聲明
function f(condition, x){
if (condition){
let x = 100;
return x;//這里面的x是if塊級(jí)作用域里聲明的
}
return x;//這個(gè)x是函數(shù)傳參時(shí)聲明的其實(shí)省略了else
}
f(false, 0);//0
f(true, 0);//100
在一個(gè)嵌套作用域里引入一個(gè)新名字的行為稱作屏蔽.它是一把雙刃劍,它可能不小心引入新問題,同時(shí)也可能解決一些錯(cuò)誤,例如:
function sumMatrix(matrix: number[][]) {
let sum = 0;
for (let i = 0; i < matrix.length; i++) {
var currentRow = matrix[i];
for (let i = 0; i < currentRow.length; i++) {
sum += currentRow[i];
}
}
return sum;
}
這個(gè)版本的循環(huán)能夠得到正確的結(jié)果,因?yàn)閮?nèi)層循環(huán)的i
可以屏蔽外層循環(huán)的i
.通常來說我們應(yīng)該避免使用屏蔽,但是有些場(chǎng)景又需要利用它,看情況而定
塊級(jí)作用域變量的獲取
獲取用var
聲明的變量時(shí),每次進(jìn)入一個(gè)作用域時(shí)創(chuàng)建了一個(gè)變量的環(huán)境,就算作用域內(nèi)代碼已經(jīng)執(zhí)行完畢,這個(gè)環(huán)境與其捕獲的變量依然存在.
function theCityThatAlwaysSleeps (){
let getCity;
if (true) {
let city = "Seattle";
getCity = function(){
return city;
}
}
return getCity();
} // Seattle
因?yàn)槲覀円呀?jīng)在city
的環(huán)境里獲取到了city
,所以就算if
語句執(zhí)行結(jié)束后我們?nèi)匀豢梢栽L問它.
當(dāng)let
聲明出現(xiàn)在循環(huán)體里擁有完全不同于var
的行為,不僅是在循環(huán)里引入了一個(gè)新的變量環(huán)境,而且針對(duì)每次迭代都會(huì)創(chuàng)建一個(gè)新的作用域,這就是我們?cè)谑褂昧⒓磮?zhí)行的函數(shù)表達(dá)式時(shí)做的事
for (let i = 0; i < 10; i++){
setTimeout(function(){
console.log(i);
}, 100 * i);
}
這樣就能得到我們想要的結(jié)果.
const
聲明
const
聲明是聲明變量的另一種方式.
const numberLivesForCat = 9;
與let
聲明類似,但是const
被賦值后不能再改變.也就是說,const
擁有與let
相同的作用域規(guī)則,但是不能對(duì)它們重新賦值.
const num= 1;
const kitty = {
name: 'xiaoji',
numLives: num;
}
kitty = {
name: "jiji",
numLives: num
}//報(bào)錯(cuò),因?yàn)閏onst聲明的變量的值是不可變的
//但是可以像下面一樣進(jìn)行變量?jī)?nèi)部的改變
kitty.name = 'xiaogang';//ok
kitty.numLives--;//ok
就是說,除非使用特使的方法去避免,實(shí)際上const
變量的內(nèi)部狀態(tài)是可修改的.
使用最小特權(quán)原則朴爬,所有變量除了你計(jì)劃去修改的都應(yīng)該使用const。 基本原則就是如果一個(gè)變量不需要對(duì)它寫入,那么其它使用這些代碼的人也不能夠?qū)懭胨鼈冋笪⑶乙伎紴槭裁磿?huì)需要對(duì)這些變量重新賦值娘汞。 使用 const也可以讓我們更容易的推測(cè)數(shù)據(jù)的流動(dòng)绰筛。
解構(gòu)
解構(gòu)數(shù)組
最簡(jiǎn)單的解構(gòu):數(shù)組的解構(gòu)賦值
let input = [1, 2];
let [first, second] = input;
console.log(first);//1
console.log(second);//2
上面的代碼相當(dāng)于:
first = input[0];
second = input[1];
解構(gòu)作用于已聲明的變量:
[first, second] = [second, first];//將兩者的值交換一下
作用于函數(shù)參數(shù):
function f([first, second]: [number, number]) {
console.log(first);
console.log(second);
}
f(input);
可以在數(shù)組里使用...
語法創(chuàng)建剩余變量:
let [first, ...rest] = [1 ,2 , 3, 4];
console.log(first);//1
console.log(rest);//[2, 3, 4]
當(dāng)然也可以忽略尾隨元素:
let [first] = [1, 2, 3, 4];
console.log(first);//1
或其他元素:
let [, second, , fourth] = [1, 2, 3, 4];
console.log(second);//2
consol.log(fourth);//4
對(duì)象解構(gòu)
let o = {
a: 'foo',
b: 12,
c: 'bar'
};
let {a, b} = o;
console.log(a);//'foo'
console.log(b);//12
就像數(shù)組解構(gòu),可以使用沒有聲明的賦值:
({a, b} = {a: 'baz', b: 101});
注意我們需要用括號(hào)將它包起來,因?yàn)镴avaScript通常會(huì)將以{
起始的語句解析為一個(gè)塊,上面這行代碼經(jīng)測(cè)試直接運(yùn)行會(huì)報(bào)錯(cuò),還是需要在前面加上let a, b;
可以在對(duì)象里使用...
語法創(chuàng)建剩余變量
let {a, ..passthrough} = o;
let total = passthrough.b + passthrough.c.length;
屬性重命名
可以給屬性以不同的名字:
let { a: newName1, b: newName2} = o;
這里可以讀作"a
作為newName1
,意思是:
let newName1 = o.a;
let newName2 = o.b;
這里的冒號(hào)不是指定類型的,如果想指定它的類型,仍然需要在其后寫上完整的模式
let {a, b} : {a: string, b: number} = o;
默認(rèn)值
默認(rèn)值可以讓你在屬性為undefined
時(shí)使用缺省值:
function keepWholeObject (wholeObject: {a: string, b?: number}) {
let {a, b = 1001} = wholeObject;
}
現(xiàn)在即使b
為undefined
,keepWholeObject
函數(shù)的變量wholeObject
的屬性a
,b
都會(huì)有值.
函數(shù)聲明
解構(gòu)也能用于函數(shù)聲明:
type C = { a: string, b?: number }
function f({a, b}: C):void {
//...
}
通常更多情況下是指定默認(rèn)值:
function f({a, b} = {a: "", b: 0}): void {
//...
}
f();//ok default to {a: "", b: 0}
function f({ a, b = 0 } = { a: "" }): void {
// ...
}
f({ a: "yes" }); // ok, default b = 0
f(); // ok, default to {a: ""}, which then defaults b = 0
f({}); // error, 'a' is required if you supply an argument
展開
展開操作符與解構(gòu)相反,它允許你將一個(gè)數(shù)組展開為另一個(gè)數(shù)組,或?qū)⒁粋€(gè)對(duì)象展開為另一個(gè)對(duì)象:
let first = [1, 2];
let second = [3, 4];
let bothPlus = [0, ...first, ...second, 5];//[0, 1, 2, 3, 4, 5]
展開對(duì)象
let defaults = {food: "spicy", price: "$$", amibiance: "noisy"};
let search = {...defaults, food: "rich"};//{food: "rich", price: "$$", amibiance: "noisy"}
如果將defaults
放在了search
后面,則展開為:
{ food: 'spicy', price: '$$', ambiance: 'no
isy' }
所以這點(diǎn)要注意,默認(rèn)值的覆蓋問題
對(duì)象展開僅包含對(duì)象自身的可枚舉屬性,意思是說當(dāng)你展開一個(gè)對(duì)象實(shí)例時(shí),你會(huì)丟失其方法:
class C {
p = 12;
m() {
}
}
let c = new C();
let clone = { ...c };
clone.p; // ok
clone.m(); // error!