TS -- (1)环境,快速入门,基础类型,类型断言、变量声明、解构、展开
2019-10-28:
学习内容:快速入门,环境搭建,基础类型,类型断言、变量声明、解构、展开
补充:
(1)尽可能地使用let
来代替var 创建对象
一、npm环境:
如何安装NPM和Node:https://blog.teamtreehouse.com/install-node-js-npm-mac
二、安装TS并编译第一个代码:
// TS: class Student { fullName: string; constructor(public firstName, public middleInitial, public lastName) { this.fullName = firstName + " " + middleInitial + " " + lastName; } } interface Person { firstName: string; lastName: string; } function greeter(person : Person) { return "Hello, " + person.firstName + " " + person.lastName; } let user = new Student("Jane", "M.", "User"); document.body.innerHTML = greeter(user);
转换成JS:
// JS: var Student = /** @class */ (function () { function Student(firstName, middleInitial, lastName) { this.firstName = firstName; this.middleInitial = middleInitial; this.lastName = lastName; this.fullName = firstName + " " + middleInitial + " " + lastName; } return Student; }()); function greeter(person) { return "Hello, " + person.firstName + " " + person.lastName; } var user = new Student("Jane", "M.", "User"); document.body.innerHTML = greeter(user);
三、基础类型:
(1)最简单的数据单元:数字,字符串,结构体,布尔值等。 TypeScript支持与JavaScript几乎相同的数据类型,此外还提供了实用的枚举类型方便我们使用。
(2)TS number浮点数的字面量除了支持JS的十进制和十六进制(0x),还引入了二进制(0b)和八进制(0o)字面量
(3)类型标注:string、number、Array<数据类型>(或数据类型[],例如:number[])
(4)限定对应位置类型的元组:
// 例子:
// Declare a tuple type let x: [string, number]; // Initialize it x = ['hello', 10]; // OK // Initialize it incorrectly x = [10, 'hello']; // Error
(5)当访问一个越界的元素,会使用联合类型替代:正如上例,只有前两个元素类型被限制,当想访问越界元素x[5]时,它的类型可以是(string | number)
(6)TS新增的枚举类型:使用枚举类型可以为一组数值赋予友好的名字。
(7)Any类型:动态类型的变量,不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查,使用 any
类型来标记这些变量
注意:在对现有代码进行改写的时候,any
类型是十分有用的,它允许你在编译时可选择地包含或移除类型检查。 你可能认为 Object
有相似的作用,就像它在其它语言中那样。 但是 Object
类型的变量只是允许你给它赋任意值 - 但是却不能够在它上面调用任意的方法,即便它真的有这些方法
// 例子: let notSure: any = 4; notSure.ifItExists(); // okay, ifItExists might exist at runtime notSure.toFixed(); // okay, toFixed exists (but the compiler doesn't check) let prettySure: Object = 4; prettySure.toFixed(); // Error: Property 'toFixed' doesn't exist on type 'Object'.
当数组内容不想限定类型时: list: any[] = [1, true, 'free']
(8)void类型:与any相反,viod是没有类型,例如函数无返回值时,它的类型标注就是void。
声明一个void
类型的变量没有什么大用,因为你只能为它赋予undefined
和null,例如:let unusable: void = undefined
(9)null 和 undefined类型:
TypeScript里,undefined
和null
两者各自有自己的类型分别叫做undefined
和null
。 和 void
相似,它们的本身的类型用处不是很大
默认情况下null
和undefined
是所有类型的子类型。 就是说你可以把 null
和undefined
赋值给number
类型的变量。然而,当你指定了--strictNullChecks
标记,null
和undefined
只能赋值给void
和它们各自。 这能避免很多常见的问题。 也许在某处你想传入一个 string
或null
或undefined
,你可以使用联合类型string | null | undefined
。
我们鼓励尽可能地使用--strictNullChecks,使用联合类型
(10)Never类型:
(11):Object类型
object
表示非原始类型,也就是除number
,string
,boolean
,symbol
,null
或undefined
之外的类型。使用object
类型,就可以更好的表示像Object.create
这样的API。
四、类型断言:
通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。
通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。 类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。
两种语法:
(1)尖括号:
let someValue: any = "this is a string"; let strLength: number = (<string>someValue).length;
(2)as:
let someValue: any = "this is a string"; let strLength: number = (someValue as string).length;
区别:
然而,当你在TypeScript里使用JSX时,只有 as
语法断言是被允许的。
(JSX: JavaScript XML是React的核心组成部分,它使用XML标记的方式去直接声明界面,界面组件之间可以互相嵌套。 可以理解为在JS中编写与XML类似的语言,一种定义带属性树结构(DOM结构)的语法,它的目的不是要在浏览器或者引擎中实现,它的目的是通过各种编译器将这些标记编译成标准的JS语言)
注意:类型断言不是类型转换,断言成一个联合类型中不存在的类型是不允许的:
function toBoolean(something: string | number): boolean { return <boolean>something; } // index.ts(2,10): error TS2352: Type 'string | number' cannot be converted to type 'boolean'. // Type 'number' is not comparable to type 'boolean'.
五、变量声明:
let
和const
是JavaScript里相对较新的变量声明方式。 像我们之前提到过的, let
在很多方面与var
是相似的,但是可以帮助大家避免在JavaScript里常见一些问题。 const
是对let
的一个增强,它能阻止对一个变量再次赋值。
(1)var的怪异之处:为什么我们不用var用let
例子1:其它函数内部访问相同的变量
function f() { var a = 10; return function g() { var b = a + 1; return b; } } var g = f(); g(); // returns 11;
上面的例子里,g
可以获取到f
函数里定义的a
变量。 每当 g
被调用时,它都可以访问到f
里的a
变量。 即使当 g
在f
已经执行完后才被调用,它仍然可以访问及修改a
。
例子2: 奇异的作用域规则
function f(shouldInitialize: boolean) { if (shouldInitialize) { var x = 10; } return x; } f(true); // returns '10' f(false); // returns 'undefined'
变量 x
是定义在*if
语句里面*,但是我们却可以在语句的外面访问它。 这是因为 var
声明可以在包含它的函数,模块,命名空间或全局作用域内部任何位置被访问,包含它的代码块对此没有什么影响。 有些人称此为* var
作用域或函数作用域*。 函数参数也使用函数作用域。
这些作用域规则可能会引发一些错误。 其中之一就是,多次声明同一个变量并不会报错,例如:多层嵌套的for循环,都以i作为计数,然后两层for循环i++互相影响,这就是var作用域规则导致的代码问题,很不好检查。
例子3: 捕获变量的怪异之处:
for (var i = 0; i < 10; i++) { setTimeout(function() { console.log(i); }, 100 * i); } // 结果是:10 10 10 10 10 10 10 10 10 10
我们传给setTimeout
的每一个函数表达式实际上都引用了相同作用域里的同一个i
。
setTimeout
在若干毫秒后执行一个函数,并且是在for
循环结束后。 for
循环结束后,i
的值为10
。 所以当函数被调用的时候,它会打印出 10
!
JS的解决方法是用即时函数捕获即时量:
for (var i = 0; i < 10; i++) { // capture the current state of 'i' // by invoking a function with its current value (function(i) { setTimeout(function() { console.log(i); }, 100 * i); })(i); }
参数 i
会覆盖for
循环里的i
,但是因为我们起了同样的名字,所以我们不用怎么改for
循环体里的代码。
(2)let:如何避免var的问题
第一、块作用域:
当用let
声明一个变量,它使用的是词法作用域或块作用域。 不同于使用 var
声明的变量那样可以在包含它们的函数外访问,块作用域变量在包含它们的块或for
循环之外是不能访问的。
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; }
拥有块级作用域的变量的另一个特点是,它们不能在被声明之前读或写。 虽然这些变量始终“存在”于它们的作用域里,但在直到声明它的代码之前的区域都属于 暂时性死区。 它只是用来说明我们不能在 let
语句之前访问它们。-- 说明let不会让声明变量提升
第二、重定义及屏蔽:
块级作用域变量需要在明显不同的块里声明。互相屏蔽,例如:两个for循环嵌套,里外层的 i 是互相屏蔽的
第三、块级作用域变量的获取:
终于是每次循环都是一个新的作用域了!不再需要这里用到即时函数
当let
声明出现在循环体里时拥有完全不同的行为。 不仅是在循环里引入了一个新的变量环境,而是针对 每次迭代都会创建这样一个新作用域。 这就是我们在使用立即执行的函数表达式时做的事,所以在 setTimeout
例子里我们仅使用let
声明就可以了。
for (let i = 0; i < 10 ; i++) { setTimeout(function() {console.log(i); }, 100 * i); }
(3)const: 比let更加严格的声明
它们拥有与 let
相同的作用域规则,但是不能对它们重新赋值。实际上,const变量的内部状态是可以修改的
const numLivesForCat = 9; const kitty = { name: "Aurora", numLives: numLivesForCat, } // Error kitty = { name: "Danielle", numLives: numLivesForCat }; // all "okay" kitty.name = "Rory"; kitty.name = "Kitty"; kitty.name = "Cat"; kitty.numLives--;
《接口》一章中会说,TypeScript允许你将对象的成员设置成只读的。
什么时候用const,什么时候用let?
使用最小特权原则,所有变量除了你计划去修改的都应该使用const
。 基本原则就是如果一个变量不需要对它写入,那么其它使用这些代码的人也不能够写入它们,并且要思考为什么会需要对这些变量重新赋值。 使用 const
也可以让我们更容易的推测数据的流动。
(最小特权原则,则是指"应限定网络中每个主体所必须的最小特权,确保可能的事故、错误、网络部件的篡改等原因造成的损失最小"。)
六、解构:
(1)数组解构:
swap 变量也算是一种解构: [a, b] = [b, a]
相当于使用了索引,但更为方便
let input = [1, 2]; let [first, second] = input; console.log(first); // outputs 1 console.log(second); // outputs 2
(2)对象解构:
let o = { a: "foo", b: 12, c: "bar" }; let { a, b } = o;
注意,我们需要用括号将它括起来,因为Javascript通常会将以 {
起始的语句解析为一个块。
* 属性重命名:
// 属性重命名: let { a: newName1, b: newName2 } = o; // 等价的写法: let newName1 = o.a; let newName2 = o.b; // 令人困惑的是,这里的冒号不是指示类型的。 如果你想指定它的类型, 仍然需要在其后写上完整的模式: let {a, b}: {a: string, b: number} = o;
* 默认值:
function keepWholeObject(wholeObject: { a: string, b?: number }) { let { a, b = 1001 } = wholeObject; } // b后面的?是可能有可能无 // 默认值可以让你在属性为 undefined 时使用缺省值
(3)函数声明:
函数声明中也能使用解构:
type C = { a: string, b?: number } function f({ a, b }: C): void { // ... }
要小心使用解构。 就算是最简单的解构表达式也是难以理解的。 尤其当存在深层嵌套解构的时候,就算这时没有堆叠在一起的重命名,默认值和类型注解,也是令人难以理解的。 解构表达式要尽量保持小而简单。 你自己也可以直接使用解构将会生成的赋值表达式。
七、展开:
展开与解构相反:它允许你将一个数组展开为另一个数组,或将一个对象展开为另一个对象。
(1)展开数组:
// 展开数组: let first = [1, 2]; let second = [3, 4]; let bothPlus = [0, ...first, ...second, 5]; // [0, 1, 2, 3, 4, 5]
(2)展开对象:
let defaults = { food: "spicy", price: "$$", ambiance: "noisy" }; let search = { ...defaults, food: "rich" }; // 结果:search的值为{ food: "rich", price: "$$", ambiance: "noisy" } // 对象的展开比数组的展开要复杂的多。 像数组展开一样,它是从左至右进行处理,但结果仍为对象。 这就意味着出现在展开对象后面的属性会覆盖前面的属性。
对象展开还有其它一些意想不到的限制。 首先,它仅包含对象 自身的可枚举属性。 大体上是说当你展开一个对象实例时,你会丢失其方法:
class C { p = 12; m() { } } let c = new C(); let clone = { ...c }; clone.p; // ok clone.m(); // error!
其次,TypeScript编译器不允许展开泛型函数上的类型参数。