一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

TS 變量聲明

 悅光陰 2021-08-31

本文知識體系:
  • 變量聲明

  • var聲明

    • 作用域規(guī)則

    • 捕獲變量怪異之處

  • let聲明

    • 塊作用域

    • 重定義及屏蔽

    • 塊級作用域變量的獲取

  • const聲明

  • let vs const

  • 解構(gòu)

    • 解構(gòu)函數(shù)

    • 對象函數(shù)

    • 屬性重命名

    • 默認值

    • 函數(shù)聲明

  • 展開

 

  • 變量聲明

letconst是JavaScript里相對較新的變量聲明方式。 像我們之前提到過的, let在很多方面與var是相似的,但是可以幫助大家避免在JavaScript里的常見一些問題。 const是對let的一個增強,它能阻止對一個變量再次賦值。

因為TypeScript是JavaScript的超集,所以它本身就支持letconst。 下面我們會詳細說明這些新的聲明方式以及為什么推薦使用它們來代替 var

  • var 聲明

通過var關(guān)鍵字定義JavaScript變量。
 
var a=10;
這里定義了一個名為a,值為10的變量;也可以在函數(shù)內(nèi)部定義變量:
function f(){    var mes = "Hello world!";    return mes;
}
也可以在其他函數(shù)內(nèi)部訪問相同的變量。
function f(){    var a = 10;    return function g(){        var b = a + 1;        return b;
    }
} 
var g = f();
g();  //return 11;
上面的例子中,g可以獲取到f函數(shù)里定義的a變量。每當(dāng)g被調(diào)用時,它都可以訪問到f里的a變量。即當(dāng)g在f已經(jīng)執(zhí)行完后才被調(diào)用,它仍然可以訪問以及修改a。
function f(){    var a = 1;
    a = 2;    var b = g();
    a = 3;    return b;    function g(){        return a;
    }
}
 
f(); //return 2;

作用域規(guī)則

對于熟悉其它語言的人來說,var聲明有些奇怪的作用域規(guī)則。 看下面的例子:
function f(shouldInittialize:boolean){    if (shouldInitialize){        var x=10;
    }    return x;
}
 
f(true); //returns '10'f(false); //returns 'undefined'

變量 x是定義在*if語句里面*,但是我們卻可以在語句的外面訪問它。 這是因為 var聲明可以在包含它的函數(shù),模塊,命名空間或全局作用域內(nèi)部任何位置被訪問(我們后面會詳細介紹),包含它的代碼塊對此沒有什么影響。 有些人稱此為* var作用域或 函數(shù)作用域*。 函數(shù)參數(shù)也使用函數(shù)作用域。

這些作用域規(guī)則可能會引發(fā)一些錯誤。 其中之一就是,多次聲明同一個變量并不會報錯:

function sumMatrix(matrix: number[][]){    var sum = 0;    for(var i = 0;i < matrix.length;i++){        var cur = matrix[i];        for (var i = 0;i < cur.length;i++){
            sum += cur[i];
        }
    }    return sum;
}
這里很容易看出一些問題,里層的for循環(huán)會覆蓋變量i,因為所有i都引用相同的函數(shù)作用域內(nèi)的變量。 有經(jīng)驗的開發(fā)者們很清楚,這些問題可能在代碼審查時漏掉,引發(fā)無窮的麻煩。

捕獲變量怪異之處

快速的猜一下下面的代碼會返回什么:

for (var i = 0; i < 10; i++) {
    setTimeout(function() { console.log(i); }, 100 * i);
}

介紹一下,setTimeout會在若干毫秒的延時后執(zhí)行一個函數(shù)(等待其它代碼執(zhí)行完畢)。

好吧,看一下結(jié)果:

10
10
10
10
10
10
10
10
10
10

還記得我們上面提到的捕獲變量嗎?我們傳給setTimeout的每一個函數(shù)表達式實際上都引用了相同作用域里的同一個i

讓我們花點時間思考一下這是為什么。 setTimeout在若干毫秒后執(zhí)行一個函數(shù),并且是在for循環(huán)結(jié)束后。 for循環(huán)結(jié)束后,i的值為10。 所以當(dāng)函數(shù)被調(diào)用的時候,它會打印出 10

一個通常的解決方法是使用立即執(zhí)行的函數(shù)表達式(IIFE)來捕獲每次迭代時i的值:

for (var i = 0; i < 10; i++) {    // 捕捉'i'的當(dāng)前狀態(tài)
    // 通過調(diào)用函數(shù)的當(dāng)前值
    (function(i) {
        setTimeout(function() { console.log(i); }, 100 * i);
    })(i);
}

這種奇怪的形式我們已經(jīng)司空見慣了。 參數(shù) i會覆蓋for循環(huán)里的i,但是因為我們起了同樣的名字,所以我們不用怎么改for循環(huán)體里的代碼。

  • let 聲明

現(xiàn)在你已經(jīng)知道了var存在一些問題,這恰好說明了為什么用let語句來聲明變量。 除了名字不同外, letvar的寫法一致。

let hello = "Hello world!"

主要的區(qū)別不在語法上,而是語義,接下來深入研究。

塊作用域

當(dāng)用let聲明一個變量,它使用的是詞法作用域或塊作用域。 不同于使用 var聲明的變量那樣可以在包含它們的函數(shù)外訪問,塊作用域變量在包含它們的塊或for循環(huán)之外是不能訪問的。

function f(input: boolean) {
    let a = 100;    if (input) {        // Still okay to reference 'a'
        let b = a + 1;        return b;
    }    // Error: 'b' doesn't exist here
    return b;
}

這里我們定義了2個變量ab。 a的作用域是f函數(shù)體內(nèi),而b的作用域是if語句塊里。

catch語句里聲明的變量也具有同樣的作用域規(guī)則。

try {    throw "oh no!";
}catch (e) {
    console.log("Oh well.");
}// Error: 'e' doesn't exist hereconsole.log(e);

擁有塊級作用域的變量的另一個特點是,它們不能在被聲明之前讀或?qū)憽?雖然這些變量始終“存在”于它們的作用域里,但直到聲明它的代碼之前的區(qū)域都屬于暫時性死區(qū)。 它只是用來說明我們不能在 let語句之前訪問它們,幸運的是TypeScript可以告訴我們這些信息。

a ++;  //在聲明之前使用'a'是違法的;let a;

注意一點,我們?nèi)匀豢梢栽谝粋€擁有塊作用域變量被聲明前獲取它。 只是我們不能在變量聲明前去調(diào)用那個函數(shù)。 如果生成代碼目標為ES2015,現(xiàn)代的運行時會拋出一個錯誤;然而,現(xiàn)今TypeScript是不會報錯的。

function foo() {    // okay to capture 'a'
    return a;
}// 不能在'a'被聲明前調(diào)用'foo'// 運行時應(yīng)該拋出錯誤foo();
 
let a;

關(guān)于暫時性死區(qū)的更多信息,查看這里Mozilla Developer Network.

  • 重定義及屏蔽

我們提過使用var聲明時,它不在乎你聲明多少次;你只會得到1個。

function f(x) {    var x;    var x;    if (true) {        var x;
    }
}
在上面的例子里,所有x的聲明實際上都引用一個相同x,并且這是完全有效的代碼。 這經(jīng)常會成為bug的來源。 好的是, let聲明就不會這么寬松了。
let x = 10;
let x = 20;// 錯誤,不能在1個作用域里多次聲明`x`

并不是要求兩個均是塊級作用域的聲明TypeScript才會給出一個錯誤的警告。

function f(x) {
    let x = 100; // error: 干擾參數(shù)說明} 
function g() {
    let x = 100;    var x = 100; // error: 不能同時聲明一個“x”}
并不是說塊級作用域變量不能用函數(shù)作用域變量來聲明。 而是塊級作用域變量需要在明顯不同的塊里聲明。
function f(condition, x) {    if (condition) {
        let x = 100;        return x;
    }    return x;
}
f(false, 0); // returns 0f(true, 0);  // returns 100
在一個嵌套作用域里引入一個新名字的行為稱做屏蔽。 它是一把雙刃劍,它可能會不小心地引入新問題,同時也可能會解決一些錯誤。 例如,假設(shè)我們現(xiàn)在用 let重寫之前的sumMatrix函數(shù)。
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;
}

這個版本的循環(huán)能得到正確的結(jié)果,因為內(nèi)層循環(huán)的i可以屏蔽掉外層循環(huán)的i。

通常來講應(yīng)該避免使用屏蔽,因為我們需要寫出清晰的代碼。 同時也有些場景適合利用它,你需要好好打算一下。

塊級作用域變量的獲取

在我們最初談及獲取用var聲明的變量時,我們簡略地探究了一下在獲取到了變量之后它的行為是怎樣的。 直觀地講,每次進入一個作用域時,它創(chuàng)建了一個變量的 環(huán)境。 就算作用域內(nèi)代碼已經(jīng)執(zhí)行完畢,這個環(huán)境與其捕獲的變量依然存在。

function theCityThatAlwaysSleeps() {
    let getCity;    if (true) {
        let city = "Seattle";
        getCity = function() {            return city;
        }
    }    return getCity();
}

因為我們已經(jīng)在city的環(huán)境里獲取到了city,所以就算if語句執(zhí)行結(jié)束后我們?nèi)匀豢梢栽L問它。

回想一下前面setTimeout的例子,我們最后需要使用立即執(zhí)行的函數(shù)表達式來獲取每次for循環(huán)迭代里的狀態(tài)。 實際上,我們做的是為獲取到的變量創(chuàng)建了一個新的變量環(huán)境。 這樣做挺痛苦的,但是幸運的是,你不必在TypeScript里這樣做了。

當(dāng)let聲明出現(xiàn)在循環(huán)體里時擁有完全不同的行為。 不僅是在循環(huán)里引入了一個新的變量環(huán)境,而是針對 每次迭代都會創(chuàng)建這樣一個新作用域。 這就是我們在使用立即執(zhí)行的函數(shù)表達式時做的事,所以在 setTimeout例子里我們僅使用let聲明就可以了。

for (let i = 0; i < 10 ; i++) {
    setTimeout(function() {console.log(i); }, 100 * i);
}
會輸出與預(yù)料一致的結(jié)果:
0
1
2
3
4
5
6
7
8
9
  • const 聲明

const 聲明是聲明變量的另一種方式。

const num = 9;

它們與let聲明相似,但是就像它的名字所表達的,它們被賦值后不能再改變。 換句話說,它們擁有與 let相同的作用域規(guī)則,但是不能對它們重新賦值。

這很好理解,const引用的值是不可變的。

const numLivesForCat = 9;
const kitty = {
    name: "Aurora",
    numLives: numLivesForCat,
} 
// Errorkitty = {
    name: "Danielle",
    numLives: numLivesForCat
}; 
// all "okay"kitty.name = "Rory";
kitty.name = "Kitty";
kitty.name = "Cat";
kitty.numLives--;

除非你使用特殊的方法去避免,實際上const變量的內(nèi)部狀態(tài)是可修改的。 幸運的是,TypeScript允許你將對象的成員設(shè)置成只讀的。 接口一章有詳細說明。

  • let vs. const

現(xiàn)在我們有兩種作用域相似的聲明方式,我們自然會問到底應(yīng)該使用哪個。 與大多數(shù)泛泛的問題一樣,答案是:依情況而定。

使用最小特權(quán)原則,所有變量除了你計劃去修改的都應(yīng)該使用const。 基本原則就是如果一個變量不需要對它寫入,那么其它使用這些代碼的人也不能夠?qū)懭胨鼈?,并且要思考為什么會需要對這些變量重新賦值。 使用 const也可以讓我們更容易的推測數(shù)據(jù)的流動。

  • 解構(gòu)

Another TypeScript已經(jīng)可以解析其它 ECMAScript 2015 特性了。 完整列表請參見 the article on the Mozilla Developer Network。 本章,我們將給出一個簡短的概述。

解構(gòu)數(shù)組 []

最簡單的解構(gòu)莫過于數(shù)組的解構(gòu)賦值了:
let input = [1, 2];
let [first, second] = input;
console.log(first); // outputs 1console.log(second); // outputs 2

這創(chuàng)建了2個命名變量 first 和 second。 相當(dāng)于使用了索引,但更為方便:

first = input[0];
second = input[1];

解構(gòu)作用于已聲明的變量會更好:

// swap variables[first, second] = [second, first];

作用于函數(shù)參數(shù):

ts:function f([first, second]: [number, number]) {
    console.log(first);
    console.log(second);
}
f([23,2]);
 
 
js:function f(_a) {    var first = _a[0], second = _a[1];
    console.log(first);
    console.log(second);
}
f([23, 2]);

你可以在數(shù)組里使用...語法創(chuàng)建剩余變量:

ts:
let [first, ...rest] = [1, 2, 3, 4];
console.log(first); // outputs 1console.log(rest); // outputs [ 2, 3, 4 ] 
 js:var _a = [1, 2, 3, 4], first = _a[0], rest = _a.slice(1);
console.log(first);
console.log(rest);

當(dāng)然,由于是JavaScript, 你可以忽略你不關(guān)心的尾隨元素:

ts:
let [first] = [1, 2, 3, 4];
console.log(first); // outputs 1 
 js:var first = [1, 2, 3, 4][0];
console.log(first); // outputs 1

或其它元素:

let [, second, , fourth] = [1, 2, 3, 4];
  • 對象解構(gòu) {}

你也可以解構(gòu)對象:

ts:
let o = {
    a: "foo",
    b: 12,
    c: "bar",
}
let { a, b } = o;
 
 
js:var o = {
    a: "foo",
    b: 12,
    c: "bar",
};var a = o.a, b = o.b;

通過 o.a and o.b 創(chuàng)建了 a 和 b 。 注意,如果你不需要 c 你可以忽略它。

就像數(shù)組解構(gòu),你可以用沒有聲明的賦值:

ts:
({ a, b } = { a: 'foo', b: 102 });
 
js:var _a;
(_a = { a: 'foo', b: 102 }, a = _a.a, b = _a.b);

注意:我們需要用括號將它括起來,因為Javascript通常會將以 { 起始的語句解析為一個塊。

你可以在對象里使用...語法創(chuàng)建剩余變量:

ts:
let o = {
    a: "foo",
    b: 12,
    c: "bar"};
 
let { a, ...pass } = o; // output foo,[12,bar]let total = pass.b + pass.c.length; //output 15 
 js:var __rest = (this && this.__rest) || function (s, e) {    var t = {};    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
        t[p] = s[p];    if (s != null && typeof Object.getOwnPropertySymbols === "function")        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0)
            t[p[i]] = s[p[i]];    return t;
};var o = {
    a: "foo",
    b: 12,
    c: "bar"};var a = o.a, pass = __rest(o, ["a"]);var total = pass.b + pass.c.length;

屬性重命名

你也可以給屬性以不同的名字:

ts:
let { a: newName1, b: newName2 } = o;
 
js:var newName1 = o.a, newName2 = o.b;
這里的語法開始變得混亂。 你可以將 a: newName1 讀做 "a 作為 newName1"。 方向是從左到右,好像你寫成了以下樣子:
 
let newName1 = o.a;
let newName2 = o.b;

令人困惑的是,這里的冒號不是指示類型的。 如果你想指定它的類型, 仍然需要在其后寫上完整的模式。

let {a, b}: {a: string, b: number} = o;

默認值

默認值可以讓你在屬性為 undefined 時使用缺省值:

ts:function keepWholeObject(wholeObject: { a: string, b?: number }) {
    let { a, b = 1001 } = wholeObject;
}
 
js:function keepWholeObject(wholeObject) {    var a = wholeObject.a, _a = wholeObject.b, b = _a === void 0 ? 1001 : _a;
}

現(xiàn)在,即使 b 為 undefined , keepWholeObject 函數(shù)的變量 wholeObject 的屬性 a 和 b 都會有值。

  • 函數(shù)聲明

解構(gòu)也能用于函數(shù)聲明。 看以下簡單的情況:

ts:
type C = { a: string, b?: number }function f({ a, b }: C): void {    // ...}
 
js:function f(_a) {    var a = _a.a, b = _a.b;    // ...}

但是,通常情況下更多的是指定默認值,解構(gòu)默認值有些棘手。 首先,你需要在默認值之前設(shè)置其格式。

ts:function f({ a="", b=0 } = {}): void {    // ...}
f();
 
js:function f(_a) {    var _b = _a === void 0 ? {} : _a, _c = _b.a, a = _c === void 0 ? "" : _c, _d = _b.b, b = _d === void 0 ? 0 : _d;    // ...}
f();

上面的代碼是一個類型推斷的例子,將在本手冊后文介紹。

其次,你需要知道在解構(gòu)屬性上給予一個默認或可選的屬性用來替換主初始化列表。 要知道 C 的定義有一個 b 可選屬性:

ts:function f({ a, b = 0 } = { a: "" }): void {    // ...}
f({ a: "yes" }); // ok, default b = 0f(); // ok, default to {a: ""}, which then defaults b = 0f({}); // error, 'a' is required if you supply an argument js:function f(_a) {    var _b = _a === void 0 ? { a: "" } : _a, a = _b.a, _c = _b.b, b = _c === void 0 ? 0 : _c;    // ...}
f({ a: "yes" }); // ok, default b = 0f(); // ok, default to {a: ""}, which then defaults b = 0f({}); // error, 'a' is required if you supply an argument

要小心使用解構(gòu)。 從前面的例子可以看出,就算是最簡單的解構(gòu)表達式也是難以理解的。 尤其當(dāng)存在深層嵌套解構(gòu)的時候,就算這時沒有堆疊在一起的重命名,默認值和類型注解,也是令人難以理解的。 解構(gòu)表達式要盡量保持小而簡單。 你自己也可以直接使用解構(gòu)將會生成的賦值表達式。

  • 展開

展開操作符正與解構(gòu)相反。 它允許你將一個數(shù)組展開為另一個數(shù)組,或?qū)⒁粋€對象展開為另一個對象。 例如:

ts:
let first = [1, 2];
let second = [3, 4];
let bothPlus = [0, ...first, ...second, 5];
 
js:var first = [1, 2];var second = [3, 4];var bothPlus = [0].concat(first, second, [5]);

這會令bothPlus的值為[0, 1, 2, 3, 4, 5]。 展開操作創(chuàng)建了 firstsecond的一份淺拷貝。 它們不會被展開操作所改變。

你還可以展開對象:

ts:
let defaults = { food: "spicy", price: "$$", ambiance: "noisy" };
let search = { ...defaults, food: "rich" };
 
js:var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }        return t;
    };    return __assign.apply(this, arguments);
};var defaults = { food: "spicy", price: "$$", ambiance: "noisy" };var search = __assign({}, defaults, { food: "rich" });

search的值為{ food: "rich", price: "$$", ambiance: "noisy" }。 對象的展開比數(shù)組的展開要復(fù)雜的多。 像數(shù)組展開一樣,它是從左至右進行處理,但結(jié)果仍為對象。 這就意味著出現(xiàn)在展開對象后面的屬性會覆蓋前面的屬性。 因此,如果我們修改上面的例子,在結(jié)尾處進行展開的話:

ts:
let defaults = { food: "spicy", price: "$$", ambiance: "noisy" };
let search = { food: "rich", ...defaults };
 
js:var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }        return t;
    };    return __assign.apply(this, arguments);
};var defaults = { food: "spicy", price: "$$", ambiance: "noisy" };var search = __assign({ food: "rich" }, defaults); 
//{food: "spicy", price: "$$", ambiance: "noisy"}

那么,defaults里的food屬性會重寫food: "rich",在這里這并不是我們想要的結(jié)果。

對象展開還有其它一些意想不到的限制。 首先,它僅包含對象 自身的可枚舉屬性。 大體上是說當(dāng)你展開一個對象實例時,你會丟失其方法:

ts:
class C {
  p = 12;
  m() {
  }
}
let c = new C();
let clone = { ...c };
clone.p; // ok// clone.m(); // error! js:var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }        return t;
    };    return __assign.apply(this, arguments);
};var C = /** @class */ (function () {    function C() {        this.p = 12;
    }
    C.prototype.m = function () {
    };    return C;
}());var c = new C();var clone = __assign({}, c);
clone.p; // ok// clone.m(); // error!
其次,TypeScript編譯器不允許展開泛型函數(shù)上的類型參數(shù)。 這個特性會在TypeScript的未來版本中考慮實現(xiàn)。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    日本人妻丰满熟妇久久| 精品精品国产欧美在线| 中文字幕日韩无套内射| 欧美日韩亚洲国产av| 日韩高清中文字幕亚洲| 五月天婷亚洲天婷综合网| 欧美胖熟妇一区二区三区| 午夜福利在线观看免费| 欧美六区视频在线观看| 开心五月激情综合婷婷色| 国产又色又粗又黄又爽| 一区二区欧美另类稀缺| 欧美日韩一级aa大片| 日本婷婷色大香蕉视频在线观看| 亚洲视频在线观看免费中文字幕 | 麻豆国产精品一区二区三区| 色婷婷在线精品国自产拍| 中文字幕91在线观看| 91欧美一区二区三区| 色婷婷人妻av毛片一区二区三区| 欧美一级特黄特色大色大片| 国产精品视频一级香蕉| 国产福利在线播放麻豆| 欧美日韩国产成人高潮| 欧美精品女同一区二区| 亚洲妇女作爱一区二区三区| 国产欧美韩日一区二区三区| 亚洲午夜精品视频观看| 伊人色综合久久伊人婷婷| 国产欧美日产久久婷婷| 91偷拍裸体一区二区三区| 男女午夜在线免费观看视频| 亚洲一区二区久久观看| 欧美黑人在线一区二区| 熟女少妇一区二区三区蜜桃| 伊人国产精选免费观看在线视频| 日本成人三级在线播放| 日韩一级毛一欧美一级乱| 精品一区二区三区不卡少妇av| 亚洲成人久久精品国产| 四季精品人妻av一区二区三区 |