本文知識體系:
因為TypeScript是JavaScript的超集,所以它本身就支持
通過 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' 變量 這些作用域規(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); } 介紹一下, 好吧,看一下結(jié)果: 10 10 10 10 10 10 10 10 10 10 還記得我們上面提到的捕獲變量嗎?我們傳給 讓我們花點時間思考一下這是為什么。 一個通常的解決方法是使用立即執(zhí)行的函數(shù)表達式(IIFE)來捕獲每次迭代時 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ù)
現(xiàn)在你已經(jīng)知道了 let hello = "Hello world!" 主要的區(qū)別不在語法上,而是語義,接下來深入研究。 塊作用域 當(dāng)用 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個變量 在 try { throw "oh no!"; }catch (e) { console.log("Oh well."); }// Error: 'e' doesn't exist hereconsole.log(e); 擁有塊級作用域的變量的另一個特點是,它們不能在被聲明之前讀或?qū)憽?雖然這些變量始終“存在”于它們的作用域里,但直到聲明它的代碼之前的區(qū)域都屬于暫時性死區(qū)。 它只是用來說明我們不能在 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.
我們提過使用 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)的 通常來講應(yīng)該避免使用屏蔽,因為我們需要寫出清晰的代碼。 同時也有些場景適合利用它,你需要好好打算一下。 塊級作用域變量的獲取 在我們最初談及獲取用 function theCityThatAlwaysSleeps() { let getCity; if (true) { let city = "Seattle"; getCity = function() { return city; } } return getCity(); } 因為我們已經(jīng)在 回想一下前面 當(dāng) 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 num = 9; 它們與 這很好理解,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--; 除非你使用特殊的方法去避免,實際上
現(xiàn)在我們有兩種作用域相似的聲明方式,我們自然會問到底應(yīng)該使用哪個。 與大多數(shù)泛泛的問題一樣,答案是:依情況而定。 使用最小特權(quán)原則,所有變量除了你計劃去修改的都應(yīng)該使用
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 = 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ù)組里使用 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)對象: 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; 通過 就像數(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通常會將以 你可以在對象里使用 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)在,即使
解構(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)屬性上給予一個默認或可選的屬性用來替換主初始化列表。 要知道 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]); 這會令 你還可以展開對象: 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" });
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"} 那么, 對象展開還有其它一些意想不到的限制。 首先,它僅包含對象 自身的可枚舉屬性。 大體上是說當(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)。
|
|