ts(js)中那些似懂非懂的知识,我猜你肯定中招过
先填上篇文章提到的函数双向协变的坑
函数到底是不是双向协变的?
如果你回答说开启 strictFunctionTypes
不是,不开启就是。请看下题
// 假设开启了 strictFunctionTypes
interface Person {
name: string;
age: number;
}
// Student 是 Person 的子类型
interface Student extends Person {
getStatus(): string;
}
interface Worker extends Person {
coding: boolean;
}
// 注意 method 这个方法的定义方式
interface Test<T> {
method(a: T): number;
}
declare let studentTest: Test<Student>;
declare let personTest: Test<Person>;
// 不会报错,strictFunctionTypes 没有生效
personTest = studentTest; // Ok
studentTest = personTest; // Ok
// ⚠️ 如果我们改下 Test 中 method 的定义方式
// 注意 method 这个方法的定义方式
interface Test<T> {
method: (a: T) => number;
}
// 这次达到了预期的效果
personTest = studentTest; // error
studentTest = personTest; // ok
强调总结下:函数作为对象上的属性的时候,他定义方式不同。效果完全不一样
函数方法
不受 strictFunctionTypes 设置的影响,始终保持双向协变。
interface Test<T> {
// 这种叫函数方法
method(a: T): number;
}
函数属性
interface Test<T> {
// 这种叫函数方法
method(a: T): number;
}
受 strictFunctionTypes 设置的影响,为true则是逆变,否则双向协变。
为什么
其实只要把ts编译成js,我们就明白了。
class Test {
method() {
// 这是函数方法
}
property = () => {
// 这是函数属性
}
}
// 编译后
var Test = /** @class */ (function () {
function Test() {
var _this = this;
this.property = function () {
// 这是函数属性
};
}
Test.prototype.method = function () {
// 这是函数方法
};
return Test;
}());
method
被写入了原型对象上,构造函数的通用方法也写在了原型上的。
还记得之前文章中,我们提到为了兼容 Array
这样的数据结构,才导致函数最开始被设计成双向协变吗?
这就是为什么函数方法只能是 双向协变
。所以强烈推荐使用 函数属性
!
双向协变无法保证类型安全,所以在使用 双向协变 的数据结构一定要小心!
// 不安全的示例
const students: Student[] = [];
const arr: Person[] = students;
arr1.push({} as Worker);
// 这里是遍历 students 变量,别看错了
// 就是因为 students 赋值给 arr, 而 arr 调用的 students 上的push 导致的不安全行为
students.forEach((item) => item.getStatus);
class (类)的类型该如何表示?
在ts中,class是少有的几个既可以当值使用,又可以当成类型使用的数据结构。
const ImNull: null = null
const ImUndefined: undefined = undefined
class Test {
show(){}
}
// ???
const test: Test = new Test()
细心的朋友发现了,Test 作为类名, 作为类型来使用的时候,表示的是该类的实例。而不是类本身
意思就是 const test: Test = Test
这种写法是错误的。
正确的写法是 const test: typeof Test = Test
如果我稍微改一下 Test 的定义呢?
class Test {}
// 请问下面是否会报错
const test: Test = Test
// 答案在下文
构造函数
下面这些写法的含义你能准确理解吗?
declare let fn: Function
declare let str: String
declare let obj: Object
或者下面成立吗?
fn = () => {}
fn = (...args: string[]) => {}
fn = false
fn = Boolean
fn = alert
查看答案
✅ ✅ ❌ ✅ ✅
str = 'i am string'
str = String
str = Function
str = alert
查看答案
✅ ❌ ❌ ❌
obj = 'i am string'
obj = Object
obj = function() {}
obj = alert
obj = false
obj = null
查看答案
✅ ✅ ✅ ✅ ✅ ❌
如果你能答对上面的全部问题,相信你的js基础一定不错!
首先,构造函数和类的概念要搞明白。其实上面已经剧透过了,类只是一个构造函数的一个语法糖。所以我们可以把构造函数看作类来处理
那么,declare let fn: Function
的含义就是 fn 的类型是 Function 的实例. 如果你熟悉 js 的原型链,上面的问题就简单了
fn = () => {} // 这是箭头函数,所以 ok
fn = (...args: string[]) => {} // 这是也是箭头函数,所以 ok
fn = false //
fn = Boolean // Boolean 作为构造函数,他也是 Function 的实例。我们可以通过 isntanceOf 来判断
fn = alert // 内置函数,ok
如果对原型链不太熟悉或者忘了,可以参考我之前的 文章
String 比较简单,就不解释了。下面看下Object
obj = 'i am string' // 这是正确的,因为 String 继承 自 Object。 'i am string' 是 String 的实例
obj = Object // Object 本身是函数,所有的函数都是 Function 的实例。 Function 又继承 Object。 所以这是正确的
obj = function() {} // 同上
obj = alert // 同上
obj = false // false 是 Boolean 的实例, Boolean 继承 Object。 所以正确
obj = null // 很明显错误
JS的圈子果然太复杂了,Function 和 Object 的私生活太乱了
友情提示,构造函数就是函数,几乎没有什么区别(除了箭头函数)。可以被 new 关键词调用的函数就叫构造函数。
Object、object、{} 和 Record 的恩怨纠缠
说实话,最开始我写TS的时候,也老是感到困扰。就想定一个对象,怎么还有这么多的方式。不过现在基本都是用 Record
的
Object
上面已经讲过,不再赘述
object
很特殊,他表示非原始类型。即除开 boolean、string、number、null、undefined、symbol 这些原始类型。还记得 WeakMap
这个数据结构吗?
// 它的键就要求:非原始类型
interface WeakMap<K extends object, V> { }
{}
这个则表示一个空对象。 类似 const obj = new Object()
这个意思。他本身没有任何属性,但是可以访问 Object 上的默认方法和属性
不过现在有了 Record
过后,上面都是可以平替的。
object
可以写成 Record<string, unknown>
{}
可以写成 Record<string, never>
最后再提一下另一个非常不好的写法,如果发现你同事这样写,就赶紧艾特他过来,看我锤死他 :)
interface InputProps {
name: string
value?: string
// 这个写法是非常非常不好的,就比 any 强一点
[x: string]: any
}
完结撒花~
转载自:https://juejin.cn/post/7235906887850639415