目录
js补漏
箭头函数
一个函数表达式如下:
const fun = function() {
return 200;
}
以上的函数函数表达式用箭头函数可以写为:
const fun = () => {
return 200;
}
小括号里面的是参数,没有参数可以写成上面的形式。如果只有一个参数,可以写为:
const fun = x => {
return x;
}
箭头函数有一个语法:如果函数体中只有一行代码,那么这个return
可以省略(json除外),为以下形式:
const fun = () => 200;
箭头函数和普通函数的区别: this指向
有一个对象:
let obj = {
name: "小明",
age: 2,
sayHello() {
console.log("我是" + this.name);
},
}
obj.sayHello();
上面的代码可以输出我是小明
但是如果我们加一个定时器:
let obj = {
name: "小明",
age: 2,
sayHello() {
setTimeout( function () {
console.log("我是" + this.name);
}, 500)
},
}
obj.sayHello();
以上代码并没有输出我们所期望的“我是小明”,这时候我们在定时器内打印this
指向,会发现this
指向windows:
let obj = {
name: "小明",
age: 2,
sayHello() {
setTimeout( function () {
console.log(this);
console.log("我是" + this.name);
}, 500)
},
}
obj.sayHello();
但是如果我们把对象内部的函数写成箭头函数:
let obj = {
name: "小明",
age: 2,
sayHello() {
setTimeout(() => {
console.log("我是" + this.name);
}, 500)
},
}
obj.sayHello();
会发现控制台打印出了“我是小明”。
这就是箭头函数和普通函数的区别:this指向不同:普通函数,谁调用这个函数this指向谁;箭头函数在哪里定义函数,this指向谁。
this指向
1、直接在script
标签内部输出this,指向全局
<script>
// 直接输出this指向全局对象
console.log(this);
</script>
2、this放在方法中,this指向调用这个方法的对象:
这个sayName()
方法是由cat
这个对象调用,所以this
指向cat
const cat = {
name: "喵喵",
sayName() {
console.log("我是" + this.name)
}
}
cat.sayName();
3、全局函数的this指向windows(全局对象)
这个函数的调用其实是window.fun();
,也就是fun()
这个方法有window
这个对象调用,所以this
指向window
const fun = function() {
console.log(this);
};
fun(); //fun() = window.fun()
4、事件中的this,指向触发这事件的DOM。
<body>
<button>点击</button>
</body>
<script>
let btn = document.querySelector("button");
btn.onclick = function() {
console.log(this);
}
</script>
上面代码的this指向触发点击事件的button
5、构造函数中的this,指向new创建的对象。
构造函数一般大写
构造函数:用来创建对象
new关键字做了什么:new会创建对象,将构造函数中的this
指向创建出来的对象
function F() {
this.name = "小明";
console.log(this);
};
let f = new F();
console.log(f);
6、箭头函数的this。
箭头函数的this可以有三种理解方式:
1、普通函数,谁调用指向谁;箭头函数,在哪里定义指向谁。
2、箭头函数外指向谁就指向谁。
3、箭头函数没有this。
深拷贝和浅拷贝
浅拷贝:
let m = {a: 10, b: 20}
let n = m
n.a = 15
console.log(n);
console.log(m)
输出结果为:
n和m输出结果一样,是因为浅拷贝,两个变量n和m指向同一个堆,对象复制只是复制的对象的引用。如果用图来解释的话就是:
深拷贝:
let m = {a: 10, b: 20}
let n = {a: m.a, b: m.b}
n.a = 14
console.log(m)
console.log(n.a)
console.log(n);
输出结果为:
深拷贝不再是复制对象的引用,而是彻底copy了一个对象,深拷贝出来的变量和被深拷贝的对象指向的不再是同一个堆。
总结:
浅拷贝只是复制某个对象的指针,新旧对象指向的还是同一块内存。
深拷贝会开辟一个新的内存来存放复制过来对象。新对象跟原对象不共享内存,修改新对象不会改到原对象。
JSON.parse(JSON.stringify())一般用来深拷贝一个对象
优化编译
1、TS和JS冲突问题:tsc --init
执行 tsc --init
以后,会生成一个tsconfig.json的文件,其中的 "strict": true,
为严格模式的开关。(推荐开启)
2、自动编译:tsc --watch
3、发出错误:tsc --noEmitOnError fileName
出现类型错误,停止编译成js文件
当我们执行tsc --noEmitOnError --watch
以后,ts文件有类型错误,就会停止自动编译成js文件
4、直接运行ts文件:npm install @types/node -D
、 npm install ts-node -g
安装好以后直接使用命令ts-node 文件名
就可以直接编译ts文件
基本类型
String、Number、Boolean
在JavaScript中有三个最基本的类型:String
、Number
、Booleam
,在typescript中,声明这些变量需要变量名后面加入类型。
let str: string = 'hello typescript';
let num: number = 100;
let bool: boolean = true;
any
如果是一个普通类型,在赋值过程中改变类型是不被允许的:
但是如果给他设置为any
,则允许被赋值为任意类型:
let obj: any = {
x: 0
};
obj.foo();
obj.bar = 100;
obj = "hello";
虽然这杨写ts不会报错,但是在编译成js后,运行,会报错,因为根本就没有bar这个属性:
类型推论
当没有给一个变量声明类型的时候,ts会去自动推断他的类型
let myName = "hahah";
myName = 1;
//不能将类型“number”分配给类型“string”。
实际上,这段代码等价于:
let myName: string = "hahah";
myName = 1;
//不能将类型“number”分配给类型“string”。
由上面可以看出,ts会自动推测变量类型。
如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查:
let age;
age = "123";
age = 123;
函数
可以规定函数形参类型
// 规定形参类型
function grtee(name: string) {
console.log("Hello" + name.toUpperCase() + "!");
};
grtee("JuNe");
也可以规定函数返回类型的规定
// 规定函数返回类型
function Num(x: number, y: number):number {
return x + y;
};
console.log(Num(1,2));
匿名函数,当我们没有给函数的形参规定类型的时候,typescript可以自动根据上下文推断出他的类型。
const user: string[] = ['June', 'JUNe', 'JUNE'];
user.forEach(function(s) {
console.log(s.toUpperCase());
console.log(typeof s); // String
});
可以使用接口来固定函数类参数和返回值类型:
interface User {
name: string,
age: number,
};
// const fn = function(user: User):User {
// return user;
// }
const fn = (user:User):User => user;
let a = fn({
name: "Tom",
age: 21
})
函数重载
重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。
function fn(params:number):void
function fn(params:number,param2:string):void
function fn(params:any, param2?:any){
console.log(params);
console.log(param2);
}
上面定义了两套方案:当fn有一个参数params传入number类型的时候和 fn有两个参数params传入number类型,param2传入string类型。ts会自动识别我们下面的执行函数传入的是什么类型的参数,决定走那套方案。
let a = fn(1)
// 1
//undefined
let a = fn(1,"2")
// 1
// 2
接口
接口声明一个普通的对象,接口一般首字母大写
interface A{
name: string,
age: number,
gender: string,
};
let Tom: A = {
name: "Tom",
age: 1,
gender: "man",
}
上面的例子中,我们定义了一个接口 A,接着定义了一个变量 Tom,它的类型是 A。这样,我们就约束了 Tom 的形状必须和接口 A 一致。
我们在Tom中多声明或者少声明,都会报错
当两个接口重名的话,会把两个接口内的属性合并
定义属性可选可不选:在变量名后面加?
:
// 当两个接口重名的时候,接口内部的属性会合并
interface A {
name: string,
age: number,
};
// 可选属性在变量名后面加入?就可代表可选可不选
interface A {
gender?: string,
};
let Tom: A = {
name: "Tom",
age: 1,
};
后端可能会返给我们一些我们不知道的数据类型,所以就需要我们在接口中定义一个接收任何类型数据的属性,即[propName: string]: any;
上面并没有在接口定义professional
和class
,但是对象中却可以存在。
只读属性
如果希望一些字段只能在被创建的时候被赋值。可以使用readonly
属性。当我们尝试在后面修改该字段属性的时候,会报错
interface A {
name: string,
readonly age: number, // 希望age为只读属性
// 对象里面有任意属性
[propName:string]: string | number,
};
接口里面定义函数
interface A {
fun():number,
};
let Tom: A = {
fun:()=>{
return 123
}
};
接口继承
可以使用es6的关键字extends
// 接口继承
interface A {
name: string,
};
interface B extends A {
age: number,
};
let person: B = {
}
如果这样写,编译器就会提示错误:类型“{}”缺少类型“B”中的以下属性: age, name
,可以看到我们给person
指定了接口B,但是由于B继承了接口A。所以要求把name
也写上。
给函数定义接口
接口不仅仅可以给单独变量定义类型,也可以给函数定义形参类型和函数的返回值定义类型:
interface SearchFunc{
(source: string, subString: string):string
}
上面的接口定义了如果一个函数使用接口SearchFunc
,那么他需要有两个形参(函数的参数不需要与接口定义的名字相同),并且两个形参的类型都需要是string
,如果我们不按照规范使用,ts就会报错:
interface SearchFunc{
(source: string, subString: string):string
}
let nishuo: SearchFunc
nishuo = function(a:string, b:number){ // 不能将类型“(a: string, b: number) => string”分配给类型“SearchFunc”。参数“b”和“subString” 的类型不兼容,不能将类型“string”分配给类型“number”。
let result = a
return result
}
数组
ts的数组,有两种书写方式:类型+方括号 泛型
// 类型 + 方括号
let arr: number[] = [1,2,3];
arr = ['2'];
// 泛型
let arr2: Array<number> = [1,2,3];
arr2 = ['哈哈'];
当规定好数组的类型以后,再向其中写入其他类型数据,会报错。
多维数组:
只有一种类型:
// 正常
let arr2: number[][] = [[1],[2,3]];
// 泛型
let arr3: Array<Array<number>> = [[1,2],[3]];
多个类型:
// 普通
let arr2: (number|string)[][] = [[1,2],[3,4]];
// 泛型
let arr3:Array<Array<string | number>> = [[1008611],['wqe'], ['sadsad','adsad']];
arguments类数组
function Arr(...args:any):void {
console.log(arguments);
// ts内置内置对象IArguments
let arr:IArguments = arguments;
};
Arr(4,5,6); // [Arguments] { '0': 4, '1': 5, '2': 6 }
用接口表示数组
一般是用来描述类数组
interface Arrtype {
[index:number]: number,
};
let zxc:Arrtype = [1,2,3]
联合类型|类型推断|交叉类型
联合类型
// 声明变量的联合类型
let phoneNumber: number|string = 123
// 函数声明的联合类型
let fun = function(type: number|boolean){
return type
}
交叉类型
和extends
功能类似,可以把两个接口的字段整合起来,使用&
连接符
interface People{
name: string,
age: number
}
interface Man{
sex: string
}
const Tom = (Info: People & Man):void=>{
console.log(Info);
}
Tom({
name:"Tom",
age: 23,
sex: "man"
})
类型断言
类型断的语法是:
值 as 类型
<类型> 值
但是类型断言只能去欺骗ts,并不能避免错误
let fn1 = function(num:number|string){
console.log((num as string).length);
}
fn1(1234) // undefined
上面我们把num推断为string
,但是实际调用使用了number
类型,ts并没有报错,但是编译出来时undfined
同样,也可以用在函数上:
interface One{
run: string
}
interface Two{
bulid: string
}
let fn2 = (type:One|Two)=>{
console.log((type as One).run);
}
fn2({
bulid: "123" // undefined
})
内置对象
JavaScript 中有很多内置对象,他们在ts内置,直接声明变量类型即可。
// 正则表达式
const regexp:RegExp = /\w\d\s/
// Date
const date:Date = new Date();
// error
const error:Error = new Error()
等等等
DOM
Document、HTMLElement、Event、NodeList 等。
let body: HTMLElement = document.body
let allDiv: NodeList = document.querySelectorAll('div')
//读取div 这种需要类型断言 或者加个判断应为读不到返回null
let div:HTMLElement = document.querySelector('div') as HTMLDivElement
document.addEventListener('click', function (e: MouseEvent) {
// 操作
}
Class类
ES6新增了Class
语法。
ts也支持Class类。ts需要提前声明变量以及类型,不然会报错:类型“xxxx”上不存在属性“yyyy”
class Person {
// ts需要提前声明变量以及类型,不然会报错:类型“xxxx”上不存在属性“yyyy”
name: string
age: number
sub: boolean
constructor (name:string, age:number, sub:boolean) {
this.name = name;
this.age = age;
this.sub = sub;
}
}
new Person("Tom", 23, true)
元组
元组是一个数组的变种。
元组是固定数量的不同类型的元素的组合。
数组合并了相同类型的对象,而元组合并了不同类型的对象。
定义一个string
和number
类型的元组:
let person:[string, number] = ['string', 22]
我们可以对元组进行赋值或者索引:
let person:[string, number] = ["Tom", 23]
let arrLength = person[0].length
console.log(arrLength);
person[1] = 24
console.log(person);
对于越界元素,ts会在元组中给你推断为联合类型:
let person:[string, number] = ["Tom", 23]
person.push(false)
枚举
数字枚举
默认枚举
在ts中,枚举的关键字是enum
:
enum Color {
red,
green,
blue,
}
而且ts的枚举,默认是从0开始
也可以通过console输出来确认:
增长枚举
当我们不想让枚举从0开始的时候,可以手动给第一个枚举对象设置一个初始化的值:
enum Color {
red = 1,
green,
blue,
}
这样,默认就从1开始了,后面的索引值也会自动递增:
自定义枚举
我们也可以给枚举对象的数字
enum Color {
red = 1,
green = 2,
blue = 3
}
字符串枚举
字符串枚举没有自增长,可以很好的序列化
enum Type {
red = "red",
green = "green",
blue = "blue"
}
异构枚举
字符串和数字混合枚举
enum Type {
yes = 1,
no = "no"
}
const枚举
let
和var
是不允许声明枚举的,只能是const
。因为大多数情况下,枚举是十分有效的方案。在某些情况下要求很严格,为了避免在额外生生成的代码上的开销和额外的非直接对枚举对象的访问,一般会使用const枚举。
泛型
定义一个函数:
function demo(arg:number):number {
return arg
}
这个函数规定了传入的参数和返回的值都是number
类型,但是当我们不知道出入的参数和返回值类型的时候,这么写就会出现问题。为了不出现因为不确定类型而导致的报错,我们会想到用any
类型:
function demo(arg:any):any {
return arg
}
但是当我们使用any
以后会失去ts的检查机制,这个例子最明显的点就是参数和返回值可以类型不一样了,这并不是我们最初想要的内容。这时候就需要泛型:
function demo<T>(arg:T):T{
return arg
}
我们给demo添加类型T
,并且用<>
包裹起来,T
帮我们捕获输入的数据类型,给形参arg
定义相同的数据类型T
,并且返回值也定义为T
这样就可以保证可以传入任何参数,并且实现最初的功能:保持形参和返回值是同一数据类型。
当我们使用泛型创建函数的时候,因为传入的参数可能是任何类型,所以当我们试图打印arg的长度的时候:
function demo<T>(arg:T):T{
console.log(arg.length); // Error:类型"T"上不存在属性“length”
return arg
}
ts会报错:类型"T"上不存在属性“length”。因为传入的数据类型可以是任何类型,假设是number
,而number
是没有长度的,所以ts会报错。
所以,我们要把这些参数当做是任意或所有类型
泛型还可以自定义数据类型:
function fnz<T>(arg:T):T {
return arg
}
fnz<number>(13)
fnz<string>("string")
泛型接口
定义一个通用泛型函数:
function ident<T>(arg:T):T{
return arg
}
如果我们想声明一个基于该通用函数的函数的话,可能需要这么写:
function ident<T>(arg:T):T{
return arg
}
let fun : <T>(arg:T) =>T = ident
这时候就可以把fun
后面的类型,写成一个接口:
interface Gener{
<T>(arg:T):T
}
function ident<T>(arg:T):T{
return arg
}
let fun: Gener = ident
我们也可以把泛型参数作为整个接口的一个参数,这样我们就能清楚的知道使用的具体是哪个泛型类型。接口里的其它成员也能知道这个参数的类型。
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
myIdentity("string") // Error:类型“string”的参数不能赋给类型“number”的参数。
以上需要注意的是,当我们使用GenericIdentityFn
的时候,还需要传入一个类型参数来指定泛型类型,上面是给了number
类型,给了类型参数以后,后面的代码就只能使用传入的参数类型。 如果我们调用myIdentity
给他一个参数为string
类型:myIdentity("name")
,ts就会给出报错类型“string”的参数不能赋给类型“number”的参数。
type
type 可以声明基本类型别名,联合类型,元组等类型:
// 类型别名
type Name = string
// 联合类型
interface Dog {
wong();
}
interface Cat {
miao();
}
type animal = Cat | Dog
// 元组类型
let Yuanzu = ["One", "Two"]