内容简介:》》面试官都在看的题目,点击学习《《
前言
"use strict"
指令在 JavaScript 1.8.5 (ECMAScript5)
中新增。
至今,前端 er 们基本都默认开启严格模式敲代码。
那么,你知道 Typescript
其实也有属于自己的严格模式吗?
1. Typescript
严格模式规则
当 Typescript
严格模式设置为 on
时,它将使用 strict
族下的严格类型规则对项目中的所有文件进行代码验证。规则是:
规则名称 | 解释 |
---|---|
noImplicitAny |
不允许变量或函数参数具有隐式 any 类型。 |
noImplicitThis |
不允许 this 上下文隐式定义。 |
strictNullChecks |
不允许出现 null 或 undefined 的可能性。 |
strictPropertyInitialization |
验证构造函数内部初始化前后已定义的属性。 |
strictBindCallApply |
对 bind, call, apply 更严格的类型检测。 |
strictFunctionTypes |
对函数参数进行严格逆变比较。 |
2. noImplicitAny
此规则不允许变量或函数参数具有隐式 any
类型。请看以下示例:
// Javascript/Typescript 非严格模式 function extractIds (list) { return list.map(member => member.id) }
上述例子没有对 list
进行类型限制, map
循环了 item
的形参 member
。而在 Typescript
严格模式下,会出现以下报错:
// Typescript 严格模式 function extractIds (list) { // :x: ^^^^ // Parameter 'list' implicitly // has an 'any' type. ts(7006) return list.map(member => member.id) // :x: ^^^^^^ // Parameter 'member' implicitly // has an 'any' type. ts(7006) }
正确写法应是:
// Typescript 严格模式 interface Member { id: number name: string } function extractIds (list: Member[]) { return list.map(member => member.id) }
1.1 浏览器自带事件该如何处理?
浏览器自带事件,比如 e.preventDefault()
,是阻止浏览器默认行为的关键代码。
这在 Typescript
严格模式下是会报错的:
// Typescript 严格模式 function onChangeCheckbox (e) { // :x: ^ // Parameter 'e' implicitly // has an 'any' type. ts(7006) e.preventDefault() const value = e.target.checked validateCheckbox(value) }
若需要正常使用这类 Web API
,就需要在全局定义扩展。比如:
// Typescript 严格模式 interface ChangeCheckboxEvent extends MouseEvent { target: HTMLInputElement } function onChangeCheckbox (e: ChangeCheckboxEvent) { e.preventDefault() const value = e.target.checked validateCheckbox(value) }
1.2 第三方库也需定义好类型
请注意,如果导入了非 Typescript
库,这也会引发错误,因为导入的库的类型是 any
。
// Typescript 严格模式 import { Vector } from 'sylvester' // :x: ^^^^^^^^^^^ // Could not find a declaration file // for module 'sylvester'. // 'sylvester' implicitly has an 'any' type. // Try `npm install @types/sylvester` // if it exists or add a new declaration (.d.ts) // file containing `declare module 'sylvester';` // ts(7016)
这可能是项目重构 Typescript
版的一大麻烦,需要专门定义第三方库接口类型
3. noImplicitThis
此规则不允许 this
上下文隐式定义。请看以下示例:
// Javascript/Typescript 非严格模式 function uppercaseLabel () { return this.label.toUpperCase() } const config = { label: 'foo-config', uppercaseLabel } config.uppercaseLabel() // FOO-CONFIG
在非严格模式下, this
指向 config
对象。 this.label
只需检索 config.label
。
但是,this在函数上进行引用可能是不明确的:
// Typescript严格模式 function uppercaseLabel () { return this.label.toUpperCase() // :x: ^^^^ // 'this' implicitly has type 'any' // because it does not have a type annotation. ts(2683) }
如果单独执行 this.label.toUpperCase()
,则会因为 this
上下文 config
不再存在而报错,因为 label
未定义。
解决该问题的一种方法是避免 this
在没有上下文的情况下使用函数:
// Typescript严格模式 const config = { label: 'foo-config', uppercaseLabel () { return this.label.toUpperCase() } }
更好的方法是编写接口,定义所有类型,而不是 Typescript
来推断:
// Typescript严格模式 interface MyConfig { label: string uppercaseLabel: (params: void) => string } const config: MyConfig = { label: 'foo-config', uppercaseLabel () { return this.label.toUpperCase() } }
4. strictNullChecks
此规则不允许出现 null
或 undefined
的可能性。请看以下示例:
// Typescript 非严格模式 function getArticleById (articles: Article[], id: string) { const article = articles.find(article => article.id === id) return article.meta }
Typescript
非严格模式下,这样写不会有任何问题。但严格模式会非给你搞出点幺蛾子:
“你这样不行,万一 find
没有匹配到任何值呢?”:
// Typescript严格模式 function getArticleById (articles: Article[], id: string) { const article = articles.find(article => article.id === id) return article.meta // :x: ^^^^^^^ // Object is possibly 'undefined'. ts(2532) }
“我星星你个星星!”
于是你会将改成以下模样:
// Typescript严格模式 function getArticleById (articles: Article[], id: string) { const article = articles.find(article => article.id === id) if (typeof article === 'undefined') { throw new Error(`Could not find an article with id: ${id}.`) } return article.meta }
“真香!”
5. strictPropertyInitialization
此规则将验证构造函数内部初始化前后已定义的属性。
必须要确保每个实例的属性都有初始值,可以在构造函数里或者属性定义时赋值。
( strictPropertyInitialization
,这臭长的命名像极了 React
源码里的众多任性属性)
请看以下示例:
// Typescript非严格模式 class User { username: string; } const user = new User(); const username = user.username.toLowerCase();
如果启用严格模式,类型检查器将进一步报错:
class User { username: string; // :x: ^^^^^^ // Property 'username' has no initializer // and is not definitely assigned in the constructor } const user = new User(); / const username = user.username.toLowerCase(); // :x: ^^^^^^^^^^^^ // TypeError: Cannot read property 'toLowerCase' of undefined
解决方案有四种。
方案#1:允许 undefined
为 username
属性定义提供一个 undefined
类型:
class User { username: string | undefined; } const user = new User();
username
属性可以为 string | undefined
类型,但这样写, 需要在使用时确保值为 string
类型 :
const username = typeof user.username === "string" ? user.username.toLowerCase() : "n/a";
这也太不 Typescript
了。
方案#2:属性值显式初始化
这个方法有点笨,却挺有效:
class User { username = "n/a"; } const user = new User(); // OK const username = user.username.toLowerCase();
方案#3:在构造函数中赋值
最有用的解决方案是向 username
构造函数添加参数 ,然后将其分配给 username
属性。
这样,无论何时 new User()
,都必须提供默认值作为参数:
class User { username: string; constructor(username: string) { this.username = username; } } const user = new User("mariusschulz"); // OK const username = user.username.toLowerCase();
还可以通过 public
修饰符进一步简化:
class User { constructor(public username: string) {} } const user = new User("mariusschulz"); // OK const username = user.username.toLowerCase();
方案#4:显式赋值断言
在某些场景下,属性会被间接地初始化(使用辅助方法或依赖注入库)。
这种情况下,你可以在属性上使用 显式赋值断言 来帮助类型系统识别类型。
class User { username!: string; constructor(username: string) { this.initialize(username); } private initialize(username: string) { this.username = username; } } const user = new User("mariusschulz"); // OK const username = user.username.toLowerCase();
通过向该 username
属性添加一个明确的赋值断言,我们告诉类型检查器: username
,即使它自己无法检测到该属性,也可以期望该属性被初始化。
6. strictBindCallApply
此规则将对 bind, call, apply
更严格地检测类型。
啥意思?请看以下示例:
// JavaScript function sum (num1: number, num2: number) { return num1 + num2 } sum.apply(null, [1, 2]) // 3
在你不记得参数类型时,非严格模式下不会校验参数类型和数量,运行代码时, Typescript
和环境(可能是浏览器)都不会引发错误:
// Typescript非严格模式 function sum (num1: number, num2: number) { return num1 + num2 } sum.apply(null, [1, 2, 3]) // 还是...3?
而 Typescript
严格模式下,这是不被允许的:
// Typescript严格模式 function sum (num1: number, num2: number) { return num1 + num2 } sum.apply(null, [1, 2, 3]) // :x: ^^^^^^^^^ // Argument of type '[number, number, number]' is not // assignable to parameter of type '[number, number]'. // Types of property 'length' are incompatible. // Type '3' is not assignable to type '2'. ts(2345)
那怎么办? “...”
扩展运算符和 reduce
老友来相救 :
// Typescript严格模式 function sum (...args: number[]) { return args.reduce<number>((total, num) => total + num, 0) } sum.apply(null, [1, 2, 3]) // 6
7. strictFunctionTypes
该规则将检查并限制函数类型参数是抗变( contravariantly
)而非双变( bivariantly
,即协变或抗变)的。
初看,内心 OS:“这什么玩意儿?”,这里有篇介绍:
协变(covariance)和抗变(contravariance)是什么? [1]
协变和逆变维基上写的很复杂,但是总结起来原理其实就一个。
-
子类型可以隐性的转换为父类型
说个最容易理解的例子, int
和 float
两个类型的关系可以写成下面这样。 int
≦ float
:也就是说 int
是 float
的子类型。
这一更严格的检查应用于除方法或构造函数声明以外的所有函数类型。方法被专门排除在外是为了确保带泛型的类和接口(如 Array )总体上仍然保持协变。
请看下面这个 Animal
是 Dog
和 Cat
的父类型的例子:
declare let f1: (x: Animal) => void; declare let f2: (x: Dog) => void; declare let f3: (x: Cat) => void; f1 = f2; // 启用 --strictFunctionTypes 时错误 f2 = f1; // 正确 f2 = f3; // 错误
-
第一个赋值语句在默认的类型检查模式中是允许的,但是在严格函数类型模式下会被标记错误。
-
而严格函数类型模式将它标记为错误,因为它不能 被证明合理。
-
任何一种模式中,第三个赋值都是错误的,因为它 永远不合理。
用另一种方式来描述这个例子则是,默认类型检查模式中 T
在类型 (x: T) => void
是 双变的,但在严格函数类型模式中 T
是 抗变的:
interface Comparer<T> { compare: (a: T, b: T) => number; } declare let animalComparer: Comparer<Animal>; declare let dogComparer: Comparer<Dog>; animalComparer = dogComparer; // 错误 dogComparer = animalComparer; // 正确
写到此处,逼死了一个菜鸡前端。
总结&参考
参考文章:
-
How strict is Typescript’s strict mode? [2]
-
应该怎么理解编程语言中的协变逆变? [3]
-
TypeScript 严格函数类型 [4]
在面试的过程中,常被问到 为什么 Typescript
比 JavaScript
好用?
从这些严格模式规则,你就可以一窥当中的奥秘, 今日开严格,他日 Bug 秒甩锅 ,噢耶。
参考资料
协变(covariance)和抗变(contravariance)是什么?: https://www.stephanboyer.com/post/132/what-are-covariance-and-contravariance
How strict is Typescript’s strict mode?: https://medium.com/swlh/how-strict-is-typescripts-strict-mode-f36a4d1a948a
应该怎么理解编程语言中的协变逆变?: https://www.zhihu.com/question/38861374
TypeScript 严格函数类型: https://www.tslang.cn/docs/release-notes/typescript-2.6.html
》》面试官都在看的题目,点击学习《《
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。