这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
一.typescript 高阶类型 Exclude 和 Extract
Exclude<T, U>
TypeScript 2.8
中增加了 Exclude
类型,该如何理解这个高级类型的定义呢?
type Exclude<T, U> = T extends U ? never : T;
从 Exclude
的定义来看,就是判断 T
是否继承于 U
,如果是,则返回 never
,否则返回 T
。
1. T, U 之间的关系,是否是基于结构相似呢?
interface IPerson {
name: string,
age: number,
sex: 0 | 1,
}
interface IMan {
name: string,
age: number,
}
type Man = Exclude<IPerson, IMan> // 等效于 type Man = never
结论:只需要两者类型能够保持一致,同时 T
的类型能够兼容 U
的类型即可。
2. 对于联合类型,是如何进行类比的?
type Fruits = "apple" | "banana" | 'peach' | 'orange';
type DislikeFruits = "apple" | "banana";
type FloveFruits = Exclude<Fruits, DislikeFruits> // 等效于 type FloveFruits = "peach" | "orange"
// 实际上 Exclude 进行的比较
type FloveFruits =
| ("apple" extends "apple" | "banana" ? never : "apple")
| ("banana" extends "apple" | "banana" ? never : "banana")
| ("peach" extends "apple" | "banana" ? never : "peach")
| ("orange" extends "apple" | "banana" ? never : "orange")
// 所以最后的结果
type FloveFruits = "peach" | "orange"
当入参是联合类型时,它会以分布式的形式去进行比较。
Extract<T, U>
Extract
的功能,与 Exclude
相反,它是 提取 T
中可以赋值给 U
的类型。
type Extract<T, U> = T extends U ? T : never
1. T, U 之间的关系,是否是基于结构相似呢?
interface IPerson {
name: string,
age: number,
sex: 0 | 1,
}
interface IMan {
name: string,
age: number,
}
type Man = Extract<IPerson, IMan> // 等效于 type Man = IPerson
与 Exclude
相同,均是保持相同的结构即可,只不过他们的取值逻辑相反。
2. 对于联合类型,是如何进行类比的?
type Fruits = "apple" | "banana" | 'peach' | 'orange';
type DislikeFruits = "apple" | "banana";
type FloveFruits = Extract<Fruits, DislikeFruits> // 等效于 type FloveFruits = "apple" | "banana"
原理与 Exclude
类似,仅仅是取值的逻辑不同而已。
二.vue事件方法之$off方法的实现原理
vue中事件方法一共就四个,挂载在vue实例上的$off移除事件中心里面某个事件的回调函数,通常会用到,那么$off的内部实现原理是什么呢?下面我们来详细说下$off:
vm.$off( [event, callback] )
参数:
{string | Array<string>} event (只在 2.2.2+ 支持数组)
{Function} [callback]
作用:
移除自定义事件监听器。
如果没有提供参数,则移除所有的事件监听器;
如果只提供了事件,则移除该事件所有的监听器;
如果同时提供了事件与回调,则只移除这个回调的监听器。
原理:
该方法用来移除事件中心里面某个事件的回调函数,根据所传入参数的不同,作出不同的处理。
Vue.prototype.$off = function (event, fn) {
const vm: Component = this
// all
if (!arguments.length) {
vm._events = Object.create(null)
return vm
}
// array of events
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
this.$off(event[i], fn)
}
return vm
}
// specific event
const cbs = vm._events[event]
if (!cbs) {
return vm
}
if (!fn) {
vm._events[event] = null
return vm
}
if (fn) {
// specific handler
let cb
let i = cbs.length
while (i--) {
cb = cbs[i]
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1)
break
}
}
}
return vm
}
该方法内部就是通过不断判断所传参数的情况进而进行不同的逻辑处理。
首先,判断如果没有传入任何参数(即arguments.length
为0),这就是第一种情况:如果没有提供参数,则移除所有的事件监听器。我们知道,当前实例上的所有事件都存储在事件中心_events
属性中,要想移除所有的事件,那么只需把_events
属性重新置为空对象即可。
接着,判断如果传入的需要移除的事件名是一个数组,就表示需要一次性移除多个事件,那么我们只需同订阅多个事件一样,遍历该数组,然后将数组中的每一个事件都递归调用$off方法进行移除即可。
接着,获取到需要移除的事件名在事件中心中对应的回调函数cbs。
接着,判断如果cbs不存在,那表明在事件中心从来没有订阅过该事件,那就谈不上移除该事件,直接返回,退出程序即可。
接着,如果cbs存在,但是没有传入回调函数fn,这就是第二种情况:如果只提供了事件,则移除该事件所有的监听器。这个也不难,我们知道,在事件中心里面,一个事件名对应的回调函数是一个数组,要想移除所有的回调函数我们只需把它对应的数组设置为null即可。
接着,如果既传入了事件名,又传入了回调函数,cbs也存在,那这就是第三种情况:如果同时提供了事件与回调,则只移除这个回调的监听器。那么我们只需遍历所有回调函数数组cbs,如果cbs中某一项与fn相同,或者某一项的fn属性与fn相同,那么就将其从数组中删除即可。
就这么简单。