【进阶】TS高级类型,泛型

# 类型别名 type

  • 类型别名就是给已有的类型取一个新名字,并不会新建类型
  • 类型别名:可以用于原始值联合类型交叉类型元组, 其他任何需要手写的类型
  • 错误信息、鼠标悬停时,不会使用别名,而是直接显示为所引用的类型
  • 别名不能被extends和implements
  • 给原始类型取别名通常没什么用
  • 如果你无法通过接口来描述一个类型并且需要使用联合类型或元组类型,交叉类型,这时通常会使用类型别名。
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;  // type可以用于联合类型
function getName(n: NameOrResolver): Name {
    if (typeof n === 'string') {
        return n;
    } else {
        return n();
    }
}

# 泛型-类型别名

type Tree<T> = {
    value: T;          // value是T类型
    left: Tree<T>;     // 在类型别名的属性中引用自己
    right: Tree<T>;
}


类型别名也可以是泛型,类型参数在别名右侧传入

# 类型别名和交叉类型一起使用

  • 与交叉类型一起使用,我们可以创建出一些十分稀奇古怪的类型。
type LinkedList<T> = T & { next: LinkedList<T> };

interface Person {
    name: string;
}

var people: LinkedList<Person>;  相当于 { name: string } & { next: { name: string } & {next...}}
var s = people.name;
var s = people.next.name;
var s = people.next.next.name;
var s = people.next.next.next.name;



    ----
    type TP<P> = P & { next?: TP<P> }
    interface IP {
      name: string;
    }
    const a = (age: TP<IP>) => {
      return age;
    }
    window.console.log(a({ name: 'wang', next: { name: ''}}))

# 交叉类型 ( & )

交叉类型 intersection types是将多个类型合并成一个类型

# 联合类型 ( | )

联合类型表示一个值可以是几种类型之一

  • 如果一个值是联合类型,我们只能访问此联合类型的所有类型里共有的成员
interface Bird {
    fly()
    layEggs()
}
interface Fish {
    swim()
    layEggs()
}
let pet = getPet() // getPet()的返回值类型是`Bird | Fish` 
pet.layEggs() // 允许
pet.swim() // 报错



函数的返回值类型是 Bird | Fish
我们唯一可以确定的是,不管是Bird还是Fish,都有layEggs()方法
所以访问pet.layEggs()不会报错,不是共有的成员则可能类型不符合,导致会报错

# 类型保护与区分类型

联合类型可以让一个值可以为不同的类型,但随之带来的问题就是访问非共同方法时会报错。那么该如何区分值的具体类型,以及如何访问共有成员?
(1) 使用类型断言
  • 类型断言有两种语法 <类型>值值 as 类型
let someValue: any = "this is a string";  ---------------------- 是一个any类型
let strLength: number = (<string>someValue).length; ------------ 断言成string类型


let someValue: any = "this is a string";
let strLength: number = (someValue as string).length; ---------- jsx中使用as语法
let pet = getSmallPet();
// 每一个成员访问都会报错
if (pet.swim) {   -------------------- 报错,因为pet可能没有swim属性
    pet.swim();
}
else if (pet.fly) { ------------------- 同样报错
    pet.fly();
}



let pet = getSmallPet();
if ((<Fish>pet).swim) { ---------------- 断言成Fish类型,就肯定有 swim 属性
    (<Fish>pet).swim();
}
else {
    (<Bird>pet).fly();
}
(2) 使用类型保护

使用类型断言,需要多次判断十分麻烦。所以使用类型保护

  • 什么是类型保护: 这种param is SomeType的形式,就是类型保护,它用来明确一个联合类型变量的具体类型
  • 类型谓词 谓词为 parameterName is Type这种形式,parameterName必须是来自于当前函数签名里的一个参数名。
function isFish(pet: Fish | Bird): pet is Fish {
    return (<Fish>pet).swim !== undefined;
}


// 'swim' 和 'fly' 调用都没有问题了
if (isFish(pet)) {
    pet.swim();
}
else {
    pet.fly();
}

# typeof类型保护

typeof 只能用于 number, string, boolean, symbol(只有这几种类型会被认为是类型保护)

# instanceof类型保护 ---- 用于类


# 索引类型

# 索引类型查询操作符 ( keyof )
# 索引访问操作符 ( T[K] )

对于任何类型 T, keyof T的结果为 T上已知的公共属性名的联合
1)首先,使用keyof关键字,它是索引类型查询操作符,它能够获得任何类型T上已知的公共属性名的联合。如例子中,keyof T相当于'name' | 'age'
2)然后,K extends keyof T表明K的取值限制于'name' | 'age'
3)而T[K]则代表对象里相应key的元素的类型

  public componentDidMount() {
    interface P {
      name: string;
      age: number;
    }
    const people: P = {
      age: 20,
      name: 'wang',
    };
    const fn: <P, T extends keyof P>(p: P, t: T[]) => Array<P[T]> = (p, t) => {
      return t.map(item => p[item])
    };
    const res = fn(people, ['age']);
    window.console.log(res)
  }



(1) fn是一个泛型函数
(2) 传入两个类型参数,fn函数的第一个参数是P类型,第二个参数是T类型的数组
(3) 索引类型查询  ---  keyof P 是P上已知的公共属性名的联合类型,即 age | name 
(4) 索引访问      ---  函数的返回值Array<P[T]>是类型为 P接口对应的T属性的类型 组成的数组
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key]
}
let obj = {
    name: 'RuphiLau',
    age: 21,
    male: true
}
let x1 = getProperty(obj, 'name') // 允许,x1的类型为string
let x2 = getProperty(obj, 'age') // 允许,x2的类型为number
let x3 = getProperty(obj, 'male') // 允许,x3的类型为boolean
let x4 = getProperty(obj, 'hobby') // 报错:Argument of type '"hobby"' is not assignable to parameter of type '"name" | "age" | "male"'.


# 索引类型和字符串索引签名

keyof和 T[K]与字符串索引签名进行交互。 如果你有一个带有字符串索引签名的类型,那么 keyof T会是 string。 并且 T[string]为索引签名的类型:

interface Map<T> {
    [key: string]: T;
}
let keys: keyof Map<number>;   // string
let value: Map<number>['foo']; // number

# 映射类型 - 从旧类型中创建新类型

它的语法与索引签名的语法类型,内部使用了 for .. in。 具有三个部分:

  1. 类型变量 K,它会依次绑定到每个属性。
  2. 字符串字面量联合的 Keys,它包含了要迭代的属性名的集合。
  3. 属性的结果类型。
interface Person {
    name: string
    age: number
}
type Readonly<T> = {
    readonly [P in keyof T]: T[P];   
}
type Partial<T> = {
    [P in keyof T]?: T[P];
}
type PersonPartial = Partial<Person>;
type ReadonlyPerson = Readonly<Person>;




-------------
解析:
type Readonly<T> = {
    readonly [P in keyof T]: T[P];   // 类似 for...in循环属性名,然后用索引访问操作符得到属性的类型
}
相当于
type Readonly<T> = {
  readonly name: string
  readonly age: number 
}
  public componentDidMount() {
    interface P {
      name: string;
      age: number;
    }
    type NewP<P> = {
      [K in keyof P]?: P[K]  // 映射类型,这里把P类型的属性,变为了可选属性
    };
    const b: NewP<P> = {
      age: 20,
    }
    window.console.log(b);
  }

我们还可以写出更多的通用映射类型,如:

// 可为空类型
type Nullable<T> {
    [P in keyof T]: T[P] | null
}

// 包装一个类型的属性
type Proxy<T> = {
    get(): T
    set(value: T): void
}
type Proxify<T> = {
    [P in keyof T]: Proxy<T[P]>
}
function proxify(o: T): Proxify<T> {
    // ...
}
let proxyProps = proxify(props)

https://www.ruphi.cn/archives/266/

# 泛型函数

function identity<T>(arg: T): T {
    return arg;
}



调用时,可以传入类型参数,也可以使用类型推论
let output = identity<string>("myString");  // type of output will be 'string'
let output = identity("myString");  // type of output will be 'string'



可以把泛型变量作为类型的一部分
function loggingIdentity<T>(arg: T[]): T[] {
    console.log(arg.length);  // Array has a .length, so no more error
    return arg;
}

# 泛型函数类型

function identity<T>(arg: T): T {
    return arg;
}
let myIdentity: <T>(arg: T) => T = identity;
<T>(arg: T) => T  -------------------------------- 泛型函数的函数类型




我们也可以使用不同的泛型参数名,只要在数量上和使用方式上能对应上就可以。
function identity<T>(arg: T): T {
    return arg;
}
let myIdentity: <U>(arg: U) => U = identity;




我们还可以使用带有调用签名的对象字面量来定义泛型函数:(!!!)
function identity<T>(arg: T): T {
    return arg;
}
let myIdentity: {<T>(arg: T): T} = identity;

# 泛型接口

interface GenericIdentityFn {
    <T>(arg: T): T;
}
function identity<T>(arg: T): T {
    return arg;
}
let myIdentity: GenericIdentityFn = identity;




把泛型参数当作整个接口的一个参数。 这样我们就能清楚的知道使用的具体是哪个泛型类型
这样接口里的其它成员也能知道这个参数的类型了。
interface GenericIdentityFn<T> {
    (arg: T): T;
}
function identity<T>(arg: T): T {
    return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
















# Event对象中的 this , target 和 currentTarget

结论:
(1) this始终等于currentTarget即事件的监听函数所绑定的节点对象
(2) target指的是最初触发事件的监听函数的节点对象

  • target:指向 ( 最初触发事件的节点对象 )。
  • currentTarget: 指向 正在执行的 ( 监听函数所绑定的节点对象 )。
  • this:指向 (绑定监听函数的节点对象)

实例:

<div onClick={this.go}> // ------ 事件的监听函数绑定的节点对象是 father 所在节点
   father
   <div>child</div>
</div>


当我们点击child的时候:
e.target指的就是最初点击触发事件监听函数的节点,即child 所在的节点
e.currentTarget = this 指的是事件监听函数所绑定的节点,即 father 所在的节点

# Event对象实例方法中的 preventDefault

  • e.preventDefault() 取消浏览器对当前事件的默认行为
  • 生效的前提是 cancelable 属性为truecancelable是只读属性,表示事件是否可以取消
    实例:
<input name="Fruit" type="radio" value="" onClick={this.goRadio}/> 

  public goRadio = (e: React.MouseEvent) => {
    window.console.log(e.cancelable);  // 只读属性,查看事件否可以取消
    if ( e.cancelable ) { // 如果事件可以取消
      e.preventDefault(); // 阻止事件的默认行为,注意生效的前提一定是 cancelable为 true
    }
  }


单选框的默认行为是点击选中,但是如果在cancelable为true的情况下,
使用 e.preventDefault()则会阻止默认行为, 使得单选框不能被选中

# Event对象中得 stopPropagation 和 stopImmediatePropagation

  • e.stopPropagation() 阻止事件在DOM中继续传播,防止再触发定义在别得节点上得监听函数,但是不包括当前节点上的其他监听函数
  • e.stopImmediatePropagation()阻止同一事件其他监听函数被调用,不管监听函数是在当前节点还是其他节点

# 属性操作的标准方法

  • getAttribute()
  • setAttribute()
  • hasAttribute()
  • removeAttribute()
    getAttribute()返回当前元素节点指定的属性,如果属性不存在,返回false
    setAttribute()为当前元素节点新增属性,如果同名属性已存在,则相当于编辑已存在的属性

# dataset属性

<div id="mydiv" data-foo="bar">

var n = document.getElementById('mydiv');
n.dataset.foo // bar
n.dataset.foo = 'baz'


也可以通过 setAttribute('data-foo')操作该属性
注意,data-后面的属性名有限制,
只能包含字母、数字、连词线(-)、点(.)、冒号(:)和下划线(_)。
而且,属性名不应该使用A到Z的大写字母,比如不能有data-helloWorld这样的属性名,而要写成data-hello-world。

# prototype

js继承机制的思想:原型对象的所有属性和方法,都能被实例对象共享。

  • 每个函数都有一个prototype属性,指向一个对象
  • 对于构造函数来说,生成实例对象的时候,prototype属性会成为 实例对象的 原型对象,原型对象的属性不是实例对象自身的属性,只要修改原型对象,变动就会立马体现在所有实例对象上
  • 如果实例对象自身就有某个属性和方法,就不会到原型对象上查找
  • 原型对象的作用,就是定义所有实例对象所共享的属性和方法
  • 所有对象都有自己的原型对象,任何对象都继承了Object.prototype,Object.prototype的原型是null,原型链到此终止

# constructor

prototype对象默认有一个constructor属性,默认指向prototype对象所在的构造函数

  • constructor属性定义在prototype对象上,所以constructor属性被所有实列对象所继承
  • constructor属性的作用是:可以得知某个实例对象,到底是由哪个构造函数产生的
  • 有了constructor就可以从一个实例新建另一个实例
  • 修改了原型对象,一定要同时修改constructor属性的指向

# instanceof

instanceof返回一个boolean值,表示对象是否为某个构造函数实例

  • instanceof 运算符 ( 左边是实例对象 ) ,( 右边是构造函数 )
  • instanceof检查整个原型链,所以同一个实例对象可能对多个构造函数返回true
  • instanceof的原理,是检查右边的构造函数的prototype属性,是否在左边实例对象的原型链上,特殊情况:(就是左边实例对象的原型链上只有null对象,这时,instanceof判断就是失真)
  • instanceof运算符的一个作用,就是判断值得类型
instanceof 运算符,只能用于对象,不能用于原始类型得值
typeof 则不能判断出具体得的对象类型


对于null和undefined,instanceof总是返回false

# getPrototypeof()

Object.getPrototypeof() 返回参数对象的原型,这是获得原型对象的标准方法

# setPrototypeof()

Object.setPrototypeof(a, b)为参数对象设置原型,返回参数对象

  • 有两个参数: 第一个是现有对象,第二个是原型对象
var a = {};
var b = {x: 1};
Object.setPrototypeOf(a, b); // 把a对象的原型设置成b

Object.getPrototypeOf(a) === b // true  Object.getPrototypeof()得到参数对象的原型
a.x // 1
var F = function () {
  this.foo = 'bar';
};
var f = new F();
// 等同于
var f = Object.setPrototypeOf({}, F.prototype); //Object.setPrototypeof(a,b)返回a,并把b设置成a的原型
F.call(f);




new命令实质上是以上过程
1)把空对象的原型设置成构造函数的prototype属性
2)将构造函数内部的 this 绑定到 空对象上,并执行构造函数

# create

Object.create( ) 以参数对象为原型,返回实例对象

# isPrototypeOf

实例对象的 isPrototypeof 方法,用来判断该对象是否是参数对象的原型

var o1 = {};
var o2 = Object.create(o1);
var o3 = Object.create(o2);

o2.isPrototypeOf(o3) // true     ---- o2是否是o3的原型
o1.isPrototypeOf(o3) // true     ---- o1是否是o3的原型




var o2 = Object.create(o1)
实际上相当于
var o2 = object.create = function(obj) {
  function X() {};  创建一个空的构造函数
  X.prototype = obj; 将构造函数的prototype指向 参数对象
  return new X(); 返回实例对象赋值给o2
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • 第3章 基本概念 3.1 语法 3.2 关键字和保留字 3.3 变量 3.4 数据类型 5种简单数据类型:Unde...
    RickCole阅读 5,096评论 0 21
  •   面向对象(Object-Oriented,OO)的语言有一个标志,那就是它们都有类的概念,而通过类可以创建任意...
    霜天晓阅读 2,093评论 0 6
  • 概要 64学时 3.5学分 章节安排 电子商务网站概况 HTML5+CSS3 JavaScript Node 电子...
    阿啊阿吖丁阅读 9,095评论 0 3
  • 说话可以分成五大模块,分别是沟通,说服,演讲,谈判,辩论,共同构建起了一个全息话术,沟通的时候权力在流动,因为沟通...
    想到学到做到阅读 339评论 4 3
  • 这星期我非常勤快,帮妈妈做了许多家务,所以爸爸就奖励了一套植物大战僵尸的玩具,我真是喜出望外! 这套玩具有一个“路...
    秋天的丘阅读 898评论 3 51