js/ts严格模式

3 篇文章 0 订阅

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强类型检查,在编译之前就会提示错误,强行运行后报错如下:

image.png

正确的写法:将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更强的监控力,同时也更进一步提高了代码质量,及早检测出了有问题代码,减少代码出错的几率。

 
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值