小满 zs-TypeScript

简介

链接:小满 TypeScript 基础教程全集(完结)
个人学习笔记(基于视频与小满博客整理)

学习 TypeScript19(Mixins 混入)

TypeScript 混入 Mixins 其实 vue 也有mixins这个东西 你可以把他看作为合并

对象混入

可以使用 es6 的Object.assign合并多个对象

此时people 会被推断成一个交差类型 Name & Age & sex;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface Name {
name: string
}
interface Age {
age: number
}
interface Sex {
sex: number
}

let people1: Name = { name: "小满" }
let people2: Age = { age: 20 }
let people3: Sex = { sex: 1 }

//扩展运算符 浅拷贝 返回新的类型
let c = {...people1,...people2,...people3};

//2. Object.assign 浅拷贝 交叉类型
const people = Object.assign(people1,people2,people3)
//3. 深拷贝
structuredClone(people1)

类的混入

首先声明两个mixins类 (严格模式要关闭不然编译不过)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class A {
type: boolean = false;
changeType() {
this.type = !this.type
}
}


class B {
name: string = '张三';
getName(): string {
return this.name;
}
}

下面创建一个类,结合了这两个mixins

首先应该注意到的是,没使用extends而是使用implements。 把类当成了接口

我们可以这么做来达到目的,为将要mixin进来的属性方法创建出占位属性。 这告诉编译器这些成员在运行时是可用的。 这样就能使用mixin带来的便利,虽说需要提前定义一些占位属性

1
2
3
4
5
6
class C implements A,B{
type:boolean
changeType:()=>void;
name: string;
getName:()=> string
}

最后,创建这个帮助函数,帮我们做混入操作。 它会遍历mixins上的所有属性,并复制到目标上去,把之前的占位属性替换成真正的实现代码

Object.getOwnPropertyNames()可以获取对象自身的属性,除去他继承来的属性,
对它所有的属性遍历,它是一个数组,遍历一下它所有的属性名

1
2
3
4
5
6
7
8
Mixins(C, [A, B])
function Mixins(curCls: any, itemCls: any[]) {
itemCls.forEach(item => {
Object.getOwnPropertyNames(item.prototype).forEach(name => {
curCls.prototype[name] = item.prototype[name]
})
})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//插件类型的混入
class Logger{
log(msg:string){
console.log("log");
}
}
class Html{
render(){
console.log('render');
}
}

class App{
run(){
console.log('run');
}
}

//类型约束
type Constructor<T> = new (...args:any[])=>T
//插件类型的混入
function plugMixins<T extends Constructor<App>>(base:T){
return class extends base{
private Logger = new Logger()
private Html = new Html()
constructor(...args:any[]){
super(...args)
this.Logger = new Logger();
this.Html = new Html();

}
run(){
this.Logger.log('run')
}
render(){
this.Logger.log('render')
this.Html.render()
}
}
}


const mixins = plugMixins(App);

const app = new mixins();

app.run();

学习 TypeScript20(装饰器 Decorator)

Decorator 装饰器是一项实验性特性,在未来的版本中可能会发生改变
它们不仅增加了代码的可读性,清晰地表达了意图,而且提供一种方便的手段,增加或修改类的功能

若要启用实验性的装饰器特性,你必须在命令行或 tsconfig.json 里启用编译器选项

装饰器

装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上。

首先定义一个类

1
2
3
4
5
class A {
constructor() {

}
}

定义一个类装饰器函数 他会把 ClassA 的构造函数传入你的 watcher 函数当做第一个参数

1
2
3
4
5
const watcher: ClassDecorator = (target: Function) => {
target.prototype.getParams = <T>(params: T):T => {
return params
}
}

使用的时候 直接通过@函数名使用

1
2
3
4
5
6
@watcher
class A {
constructor() {

}
}

验证

1
2
const a = new A();
console.log((a as any).getParams('123'));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//1.类装饰器 ClassDecorator
const Base:ClassDecorator = (target) =>{
console.log(target);
target.prototype.xiaoman = '小满'
target.prototype.fn = () =>{
console.log('我是韩寒');
}
}

@Base
class Http{

}
const http = new Http() as any;
console.log(http)

装饰器工厂

其实也就是一个高阶函数 外层的函数接受值 里层的函数最终接受类的构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const watcher = (name: string): ClassDecorator => {
return (target: Function) => {
target.prototype.getParams = <T>(params: T): T => {
return params
}
target.prototype.getOptions = (): string => {
return name
}
}
}

@watcher('name')
class A {
constructor() {

}
}

const a = new A();
console.log((a as any).getParams('123'));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//2. 装饰器工厂
const Base = (name: string) => {
const fn: ClassDecorator = (target) => {
console.log(target);
target.prototype.xiaoman = name;
target.prototype.fn = () => {
console.log("我是韩寒");
};
};
return fn
};

@Base('xiao yu')
class Http {}
const http = new Http() as any;
console.log(http.xiaoman);

装饰器组合

就是可以使用多个装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const watcher = (name: string): ClassDecorator => {
return (target: Function) => {
target.prototype.getParams = <T>(params: T): T => {
return params
}
target.prototype.getOptions = (): string => {
return name
}
}
}
const watcher2 = (name: string): ClassDecorator => {
return (target: Function) => {
target.prototype.getNames = ():string => {
return name
}
}
}

@watcher2('name2')
@watcher('name')
class A {
constructor() {

}
}


const a = new A();
console.log((a as any).getOptions());
console.log((a as any).getNames());

方法装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const Get = (url: string) => {
const fn: MethodDecorator = (target, key, descriptor: PropertyDescriptor) => {
console.log("target,key,descriptor :>> ", target, key, descriptor);
axios.get(url).then((res) => {
descriptor.value(res.data);
});
};
return fn;
};

@Base("xiao yu")
class Http {
@Get("https://api.apiopen.top/api/getHaoKanVideo?page=0&size=10")
getList(data: any) {
console.log("data :>> ", data.result.list);
}
create() {}
}

参数装饰器

返回三个参数

对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
成员的名字。
参数在函数参数列表中的索引。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[ {}, 'setParasm', 0 ]

const met:ParameterDecorator = (...args) => {
console.log(args);
}

class A {
constructor() {

}
setParasm (@met name:string = '213') {

}
}
const a = new A();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import axios from "axios";
import "reflect-metadata";
const Base = (name: string) => {
const fn: ClassDecorator = (target) => {
console.log(target);
target.prototype.xiaoman = name;
target.prototype.fn = () => {
console.log("我是韩寒");
};
};
return fn;
};

const Get = (url: string) => {
const fn: MethodDecorator = (
target,
_: any,
descriptor: PropertyDescriptor
) => {
//console.log("target,key,descriptor :>> ", target, key, descriptor);
const key = Reflect.getMetadata("key", target);
axios.get(url).then((res) => {
descriptor.value(key ? res.data[key] : res.data);
});
};
return fn;
};

const Result = () => {
const fn: ParameterDecorator = (target, key, index) => {
console.log("target,key,index :>> ", target, key, index);
Reflect.defineMetadata("key", "result", target);
};
return fn;
};

@Base("xiao yu")
class Http {
@Get("https://api.apiopen.top/api/getHaoKanVideo?page=0&size=10")
getList(@Result() data: any) {
console.log("data :>> ", data);
}
create() {}
}
const http = new Http() as any;
console.log(http.xiaoman);

学习 TypeScript22(实战 TS 编写发布订阅模式)

发布订阅模式是一种常见的设计模式,在许多场景中都有应用。我们可能已经在使用中接触过发布订阅模式,比如使用 addEventListener 方法来监听 DOM 事件、Vue 的事件总线机制等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//举例-自定义事件

const cb = () => {
console.log("触发了");
};

document.addEventListener(
"asd",
() => {
console.log("触发了");
},
{
once: true,
}
);

document.removeEventListener("asd", cb);

const e = new Event("asd");

document.dispatchEvent(e);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//需要实现:once on emit off 订阅中心Map<事件名称,[Function]>
interface I {
events: Map<string, Function[]>;
once: (event: string, callback: Function) => void; //触发一次订阅器
on: (event: string, callback: Function) => void; //订阅
emit: (event: string, ...args: any[]) => void; //派发
off: (event: string, callback: Function) => void; //删除监听器
}
class Emitter implements I {
events: Map<string, Function[]>;
constructor() {
this.events = new Map();
}
once(event: string, callback: Function) {
const cb = (...args: any[]) => {
callback(...args);
this.off(event, cb);
};
this.on(event, cb);
}
off(event: string, callback: Function) {
const callbackList = this.events.get(event);
if (callbackList) {
callbackList.splice(callbackList.indexOf(callback), 1);
}
}
on(event: string, callback: Function) {
if (this.events.has(event)) {
const callbackList = this.events.get(event);
callbackList && callbackList.push(callback);
} else {
this.events.set(event, [callback]);
}
} //订阅
emit(event: string, ...args: any[]) {
const callbackList: Function[] = this.events.get(event);
console.log("callbackList :>> ", callbackList);
if (callbackList) {
callbackList.forEach((fn: Function) => {
fn(...args);
});
}
} //派发
}

const bus = new Emitter();
const fn = (b: Boolean, n: Number) => {
console.log(1);
};
bus.on("message", fn);
bus.off("message", fn);
bus.once("message", fn);
bus.emit("message", false, 1);

学习typeScript(weakMap,weakSet,set,map)

在es5的时候常用的Array object ,在es6又新增了两个类型,Set和Map,类似于数组和对象。

set

集合是由一组无序且唯一(即不能重复)的项组成的,可以想象成集合是一个既没有重复元素,也没有顺序概念的数组

属性

size:返回字典所包含的元素个数

操作方法

add(value):添加某个值,返回 Set 结构本身。

delete(value):删除某个值,返回一个布尔值,表示删除是否成功。

has(value):返回一个布尔值,表示该值是否为 Set 的成员。

clear():清除所有成员,无返回值。

size: 返回set数据结构的数据长度

1
2
3
4
5
6
7
8
9
let set:Set<number> = new Set([1,2,3,4])

set.add(5)

set.has(5)

set.delete(5)

set.size //4

去重

1
2
3
let arr = [...new Set([1,1,1,2,2,3,4,5,5,5,5])]

console.log(arr); //[ 1, 2, 3, 4, 5 ]

Map

它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适

1
2
3
4
5
6
7
8
9
10
11
12
let obj = { name: '小满' }
let map: Map<object, Function> = new Map()

map.set(obj, () => 123)

map.get(obj)

map.has(obj)

map.delete(obj)

map.size

操作方法同set

WeakSet 和 WeakMap

Weak 在英语的意思就是弱的意思,weakSetweakMap 的键都是弱引用,不会被计入垃圾回收,我们来演示一下。

首先obj引用了这个对象 + 1,aahph也引用了 + 1,wmap也引用了,但是不会 + 1,应为他是弱引用,不会计入垃圾回收,因此 obj 和 aahph 释放了该引用 weakMap 也会随着消失的,但是有个问题你会发现控制台能输出,值是取不到的,应为V8的GC回收是需要一定时间的,你可以延长到500ms看一看,并且为了避免这个问题不允许读取键值,也不允许遍历,同理weakSet 也一样

1
2
3
4
5
6
7
8
9
10
11
12
13
let obj:any = {name:'小满zs'} //1
let aahph:any = obj //2
let wmap:WeakMap<object,string> = new WeakMap()

wmap.set(obj,'爱安徽潘慧') //2 他的键是弱引用不会计数的

obj = null // -1
aahph = null;//-1
//v8 GC 不稳定 最少200ms

setTimeout(()=>{
console.log(wmap)
},500)

学习TypeScript23(TS进阶用法proxy & Reflect)

学习proxy对象代理

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)

target

要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。

handler

一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。

handler.get() 本次使用的get

属性读取操作的捕捉器。

handler.set() 本次使用的set

属性设置操作的捕捉器。

Reflect

与大多数全局对象不同Reflect并非一个构造函数,所以不能通过new运算符对其进行调用,或者将Reflect对象作为一个函数来调用。Reflect的所有属性和方法都是静态的(就像Math对象)

Reflect.get(target, name, receiver)
Reflect.get方法查找并返回target对象的name属性,如果没有该属性返回undefined

Reflect.set(target, name,value, receiver)
Reflect.set方法设置target对象的name属性等于value。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
type Person = {
name: string,
age: number,
text: string
}


const proxy = (object: any, key: any) => {
return new Proxy(object, {
get(target, prop, receiver) {
console.log(`get key======>${key}`);
return Reflect.get(target, prop, receiver)
},

set(target, prop, value, receiver) {
console.log(`set key======>${key}`);

return Reflect.set(target, prop, value, receiver)
}
})
}

const logAccess = (object: Person, key: 'name' | 'age' | 'text') => {
return proxy(object, key)
}

let man: Person = logAccess({
name: "小满",
age: 20,
text: "我的很小"
}, 'age')

man.age = 30

console.log(man);

使用泛型+keyof优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
type Person = {
name: string,
age: number,
text: string
}


const proxy = (object: any, key: any) => {
return new Proxy(object, {
get(target, prop, receiver) {
console.log(`get key======>${key}`);
return Reflect.get(target, prop, receiver)
},

set(target, prop, value, receiver) {
console.log(`set key======>${key}`);

return Reflect.set(target, prop, value, receiver)
}
})
}


const logAccess = <T>(object: T, key: keyof T): T => {
return proxy(object, key)
}

let man: Person = logAccess({
name: "小满",
age: 20,
text: "我的很小"
}, 'age')


let man2 = logAccess({
id:1,
name:"小满2"
}, 'name')

man.age = 30

console.log(man);

案例简单实现一个mobx观察者模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const list: Set<Function> = new Set()

const autorun = (cb: Function) => {
if (cb) {
list.add(cb)
}
}

const observable = <T extends object>(params: T) => {
return new Proxy(params, {
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver)
list.forEach(fn => fn())
console.log(list)
return result
}
})
}

const person = observable({ name: "小满", attr: "威猛先生" })

autorun(()=>{
console.log('我变化了')
})

person.attr = '威猛个捶捶'