vue3.0要来了,你有考虑过使用TypeScript吗?作为一个强类型检测语言,我们都知道它的限制很多,比如:类型检测,参数个数检测...等,你以为只有这些吗,我们都知道js有"use strict",那么TypeScript的严格模式又是怎样的呢?了解这些严格模式,有助于我们更深更细致的了解js/ts,成为更好的程序员。
ts环境准备
为方便下面的严格测试,先部署下运行环境:
typeScript开发环境部署:
1、安装 Node.js
2、安装 TypeScript 包:
windows: npm install typescript -g
mac: sudo npm install typescript -g
3、初始化项目
3.1、生成package.json文件:npm init -y
3.2 、创建tsconfig.json文件:tsc --init
// tsconfig.json TypeScript项目的配置文件,可以通过读取它来设置TypeScript编译器的编译参数。
{
"compilerOptions": {
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
...
"strict": true, /* 是否启用严格模式 */
"noImplicitThis": true, /* 是否允许this上下文隐式定义 */
...
/* Advanced Options */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}
4、创建一个hello.ts 测试一下:
// hello.ts
var message:string = " Hello World "
console.log(message)
5、到项目文件下,终端运行:tsc (ts 文件名)
运行后会生成对应的hello.js文件,也可以只执行tsc,后面不加文件名,ts内部会去查找该文件夹下面的ts文件并编译运行。
// hello.js
"use strict";
const message = 'hello world';
console.log(message);
下面的一系列测试都会在hello.ts文件中进行编辑
ts严格模式规则
若是要关闭严格模式,修改tsconfig.json中对应规则名称为false,默认为true
规则 | 解释 |
noImplicitAny | 不允许变量或者函数参数具有隐式any类型 |
noImplicitThis | 不允许this上下文隐式定义 |
strictNullChecks | 不允许出现null或者undefined的可能性 |
strictPropertyInitialization | 验证构造函数内部初始化前后已定义的属性 |
strictFunctionTypes | 对函数参数进行严格逆变比较 |
1、 noImplicitAny规则不允许变量或者函数参数具有隐式any类型。
// 非严格模式下
function extractIds (list) {
return list.map(member => member.id)
}
// Typescript 严格模式
function extractIds (list) {
// ❌ ~~~~
// 参数“list”隐式具有“any”类型
return list.map(member => member.id)
// ❌ ~~~~~~
// 参数“member”隐式具有“any”类型
}
由于typeScript强类型检查,在编译之前就会提示错误,强行运行后报错如下:
正确的写法:将list进行明确的类型指明
interface Member {
id: number,
name: string
}
function extractIds (list: Member[]) {
return list.map(member => member.id)
}
1.1、e.preventDefault()
这个是阻止浏览器默认行为常用的代码,像这样的浏览器自带事件,在js中是很常见的,但在ts的严格模式下存有隐式any类型
// 非严格模式下
function onChangeCheckbox (e) {
e.preventDefault()
console.log('target', e.target.checked)
}
// 严格模式下
function onChangeCheckbox (e) {
// ❌ ~~
// 参数“e”隐式具有“any”类型
e.preventDefault()
console.log('target', e.target.checked)
}
正确的写法,定义一个全局的扩展类型接口:
interface ChangeCheckboxEvent extends MouseEvent {
target: HTMLInputElement
}
function onChangeCheckbox (e: ChangeCheckboxEvent) {
e.preventDefault()
console.log('target', e.target.checked)
}
2、noImplicitThis规则不允许this上下文隐式定义
// 非严格模式下 或者 noImplicitThis:true
function twoFixed () {
return this.money.toFixed(2)
}
// 严格模式下
function twoFixed () {
return this.money.toFixed(2)
// ❌~~~~
// "this" 隐式具有类型 "any",因为它没有类型注释
}
const billData = {
money: 11.11,
twoFixed
}
config.twoFixed()
这里的this指向调用twoFixed函数的billData对象,在js或者非严格模式下this.money只需要检索billData.money就可以,但是this的指向并不明确,严格模式下会造成上面的报错。
避免该类问题的一种方法就是,避免this在没有上下文的情况下使用函数,改造后的写法:
const billData = {
money: 11.11,
twoFixed () {
return this.money.toFixed(2)
}
}
更推荐的方法是:将billData的类型也定义一个接口,而不是靠typescript来判断
interface MyBill {
money: number
twoFixed: (params: void) => string
}
const config: MyBill = {
money: 11,
twoFixed () {
return this.money.toFixed(2)
}
}
3、strictNullChecks规则不允许出现null或者undefined的可能性
interface Article {
id: string,
title: string,
content: string,
author: string
}
// Typescript 严格模式下
function getArticleById (articles: Article[], id: string) {
const article = articles.find(article => article.id === id)
return article.title
// ❌~~~~~~~
// 对象可能为“未定义”。
}
在非严格模式下这样写是没问题的,但是在严格模式下,会提示article对象可能未定义(当传入的id在articles列表中不存在时)
所以我们这里可能需要做个判断:
function getArticleById (articles: Article[], id: string) {
const article = articles.find(article => article.id === id)
if (article) {
return article.title
} else {
throw new Error(`can't find id`)
}
}
4、strictPropertyInitialization规则验证构造函数内部初始化前后已定义的属性
// Typescript非严格模式
class User {
username: string;
}
const user = new User();
const username = user.username.toLowerCase();
严格模式下,将进行进一步的类型检查:必须要确保每个实例的属性都有初始值,才可以对该属性进行操作,此时可以在构造函数里或者属性定义时赋以初始值。
// Typescript严格模式
class User {
username: string;
//❌~~~~~~
// 属性“username”没有初始化表达式,且未在构造函数中明确赋值。
}
const user = new User();
const username = user.username.toLowerCase();
解决该问题的方法一:
为username添加undefined类型,但是这样又会引发另外一个问题,即user.username对象可能为“未定义”。
所以需要确保username值为string类型:
class User {
username: string | undefined;
}
const user = new User();
const username = typeof user.username === "string"
? user.username.toLowerCase()
: "";
// 或者
const username = user.username ? user.username.toLowerCase() : "";
方法二:为属性设置初始值
class User {
username="jdek";
}
const user = new User();
// ok
const username = user.username.toLowerCase();
方法三:在构造函数中进行赋值
class User {
username: string;
constructor (username: string) {
this.username = username
}
}
const user = new User('jdek');
// ok
const username = user.username.toLowerCase();
// 简化后
class User {
constructor (username: string) {
}
}
const user = new User('jdek');
// ok
const username = user.username.toLowerCase();
5、strictFunctionTypes规则
该规则将检查并限制函数类型参数是抗变(contravariantly
)而非双变(bivariantly
)的。
简单来说就是:非严格模式下,父类可以转化为子类,子类可以转化为父类,即为双变;但是在严格模式下,只有子类可以转化为父类,父类不可以转化为子类,即为抗变。
举个简单的例子:
class Animal {
name: string | undefined
}
class Dog extends Animal{
age: string | undefined
}
class Cat extends Animal{
sex: string | undefined
}
declare let f1: (x: Animal) => void;
declare let f2: (x: Dog) => void;
declare let f3: (x: Cat) => void;
f1 = f2; // 严格时错误
// ~~❌ 不能将类型“(x: Dog) => void”分配给类型“(x: Animal) => void”
f2 = f1; // 正确
f2 = f3; // 错误
// ~~❌ 不能将类型“(x: Cat) => void”分配给类型“(x: Dog) => void”。
(1)f1 = f2在默认的类型检查模式中是允许的,但是在严格函数类型模式下会被标记错误,因为Animal中没有age属性,但在Dog是需要age这个属性的,两个参数类型互不兼容。
(2)任何一种模式中,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; // 正确
js严格模式 vs ts严格模式
了解了ts的严格模式,我们再举例几个js的严格模式,来进行对比:
1、作用范围
ts设置了严格模式后,就是全局的,而js是要根据"use strict"在代码中的位置来决定是否执行严格模式以及执行严格模式的范围。
(1)将“use strict”放在第一行,则整个脚本文件都执行严格模式
<script>
"use strict";
console.log("这是严格模式。");
</script>
<script>
console.log("这是正常模式。");
</script>
(2)将“use strict”放在函数体第一行,则整个函数会执行严格模式
function strict(){
"use strict";
return "这是严格模式。";
}
function notStrict() {
return "这是正常模式。";
}
2、变量声明
js在正常模式下,直接给一个没有声明的变量赋值的话,会默认是全局变量,但严格模式下禁止这样操作;
ts在正常模式下就严禁这样做
// js正常模式下
a = 1 // 不报错
// js严格模式下
a = 1 // ❌报错
// ts正常模式下
a = 1 // ❌报错
3、禁止使用with语句
js正常模式下可以使用with语句,但是在严格模式下,因为with语句无法在编译时,就确定属性到底归属哪个对象,所以禁止使用;
ts在正常模式就禁止使用:因为with程序块中的所有符号都具有类型“any”
// js正常模式下
var v = 1;
with (o){ // 👌
v = 2;
}
// js严格模式下
"use strict";
var v = 1;
with (o){ // 语法错误 Strict mode code may not include a with statement
v = 2;
}
// ts正常模式下
var v = 1;
with (o){ // 不支持 "with" 语句。"with" 程序块中的所有符号都将具有类型 "any"。
v = 2;
}
4、禁止this关键字指向全局对象
js正常模式下,this指向全局对象,返回false,js严格模式下,this值为undefined,所以返回true
ts在正常模式和严格模式下编译的时候没有报错,但返回结果同js一致。
// js/ts正常模式下
function f(){
return !this;
}
// 返回false,因为"this"指向全局对象,"!this"就是false
// js/ts严格模式下
function f(){
"use strict";
return !this;
}
// 返回true,因为严格模式下,this的值为undefined,所以"!this"为true。
总结:通过上面的对比验证,更进一步验证了作为js超集的ts,不仅能兼容js代码,且在代码检测的严格性>=js严格性,拥有比js更强的监控力,同时也更进一步提高了代码质量,及早检测出了有问题代码,减少代码出错的几率。