ES6
-
数组解构
let arr = [100,200,300] const [name, age, sex] = arr console.log(name, age, sex) const [, ,bar] = arr console.log(bar) const [a,...rest] = arr // rest展开运算符只能用在最后的位置 console.log(a) console.log(rest)
-
对象解构
let person = { name: "John", age: 30, sex: "male" } const { name, age, sex } = person console.log(name, age, sex) const {name: restName , age, sex} = person console.log(restName, age, sex)
-
模板字符串
可以使用 \ 转义
自动识别换行符
插值赋值(${js语句})
// 带标签的模板字符串 模板字符串之前添加一个标签(也就是一个函数) const name = 'aa' const value = 2 function check(strings) { console.log(strings) return '111' } const result = check`hey, ${name} is a ${value}` console.log(result)
-
字符串的扩展方法
includes
:字符串中是否包含某个字符
-
参数默认值(形参中使用等号)
function fn(a = true) { } fn() // 此时默认参数为 true
-
扩展运算符(rest)
// rest 展开数组 const arr = [100, 200, 300] let arr1 = [] console.log.apply(console, arr) // 老语法 console.log(...arr) arr1.push(...arr)
-
箭头函数
优势:1. 代码简洁 2. 易读性高 3. 不改变 this 指向
const i = n => n + 1 console.log(i(100)) const person = { name: 'John', age: 50, say: () => { console.log(this.name) }, go: function() { console.log(this.name) } } person.say() person.go()
-
对象字面量的增强
-
Object.assign(target, source)
将目标对象中的属性放到源对象中const source1 = { a: 123, b: 123 } const source2 = { b: 789, d: 789 } const target = { a: 456, c: 456 } const result = Object.assign(target, source1, source2) console.log(target) console.log(target === result)
-
Object.is(value1, value2)
判断两个对象是否相同(值相同并且内存中的地址也相同)let a = { a: 1, b: 2 } let b = { a: 1, b: 2 } let c = a Object.is(a, b) // false Object.is(a, c) // true
-
Object.keys(obj)
返回对象所有键组成的数组let obj = { a: 1, b: 2, c: 3 } Object.keys(obj) // [a, b, c]
-
Object.values(obj)
返回对象所有值组成的数组let obj = { a: 1, b: 2, c: 3 } Object.values(obj) // [1, 2, 3]
-
Object.entries(obj)
返回对象所有的键值对let obj = { a: 1, b: 2, c: 3 } Object.entries(obj) // [[a, 1], [b, 2], [c, 3]]
-
Object.getOwnPropertyDescriptors(obj)
返回对象的所有信息(包括是否可写、是否可配置、是否可枚举)const p1 = { firstName: 'John', lastName: 'Smith', get fullName() { return this.firstName + ' ' + this.lastName } } const p2 = Object.assign({}, p1) // { firstName: 'aaa', lastName: 'Smith', fullName: 'John Smith' } 单纯的复制 p2.firstName = 'aaa' console.log(p2) const descriptors = Object.getOwnPropertyDescriptors(p1) console.log(descriptors) const p3 = Object.defineProperties({}, descriptors) p3.firstName = 'bbb' console.log(p3.fullName) // bbb Smith 统一进行了修改 // String.prototype.padStart / String.prototype.padEnd 用给定长度的字符串去填充目标字符串的开始/结束位置 const books = { html: 5, css: 16, javascript: 133 } for (const [name, count] of Object.entries(books)) { console.log(name, count) } for (const [name, count] of Object.entries(books)) { console.log(`${name.padEnd(16, '-')}|${count.toString().padStart(3, '0')}`) }
-
-
对象代理(
Proxy
)const person = { name: 'John', age: 30 } const personProxy = new Proxy(person, { get(target, property) { console.log(target) return property in target ? target[property] : 'default' }, set(target, property, value) { if (property === 'age') { if (!Number.isInteger(value)) { throw new typeError('value must be an integer') } } target[property] = value } }) personProxy.age = 100 personProxy.gender = true console.log(personProxy.name) console.log(personProxy.xxx) // Proxy和defineProperty 后者只能监听读写 前者还能监听到诸如delete的操作 const person = { name: 'John', age: 30 } const personProxy = new Proxy(person, { deleteProperty(target, p) { console.log('delete', p) delete target[p] } }) delete personProxy.age console.log(personProxy) const list = [] const listProxy = new Proxy(list, { set(target, p, value) { console.log('set', p, value) target[p] = value return true } }) listProxy.push(100) // set 0 100
-
Reflect
静态类Reflect
成员方法就是Proxy
处理对象的默认实现Reflect
对象提供了一套用于操作对象的APIconst obj = { foo: 123, bar: 456 } const proxy = new Proxy(obj, { get(target,p) { console.log('watch logic') return Reflect.get(target,p) } }) console.log(proxy.foo) Reflect 对象实际使用方法 const obj = { name: 'foo', age: 30 } 老方法 console.log('name' in obj) // true console.log(delete obj['age']) // true console.log(Object.keys(obj)) // ['name'] Reflect对象方法 console.log(Reflect.has(obj,'name')) // true console.log(Reflect.deleteProperty(obj,'age')) //true console.log(Reflect.ownKeys(obj)) // ['name']
-
Promise
全新的异步解决方案 -
class
类关键词// ES5 function Person(name) { this.name = name; } Person.prototype.say = function () { console.log(this.name) } // ES6 class Person2 { constructor(name) { this.name = name } say() { console.log(this.name + 'say') } } const p = new Person('lin') p.say() const p2 = new Person2('lin') p2.say()
-
静态方法
static
关键词class Person2 { constructor(name) { this.name = name } say() { console.log(this.name + 'say') } static fn() { } } Person2.fn() // 类直接调用
-
类的继承
extend
关键词class Person { constructor(name) { this.name = name; } say() { console.log(this.name + 'say'); } } class Student extends Person { constructor(name,number) { super(name); this.number = number; } hello() { super.say(); console.log(this.number + 'number'); } } const person = new Student('lin', 100); person.hello();
-
-
Set
集合 新的类型(数据结构)内部数据是唯一的,不重复的const s = new Set(); s.add(1).add(2).add(3).add(1); console.log(s); console.log(s.size); console.log(s.has(100)); console.log(s.delete(3)); console.log(s) s.clear() console.log(s)
-
Map
键值对,键名可以为任意数据类型const m = new Map(); const o = {p: 'Hello World'}; m.set(o, 'content') m.get(o) // "content" m.has(o) // true m.delete(o) // true m.has(o) // false
-
Symbol
最主要的作用就是为对象添加独一无二的属性名const name = Symbol() const person = { [name]: 'lin', say() { console.log(this[name]) } } person.say() // Symbol 补充 console.log(Symbol() === Symbol()) // false console.log(Symbol('aa') === Symbol('aa')) // false const s1 = Symbol.for('foo') // 内部维护的是字符串的对应关系,不是字符串的会自动转化为字符串 const s2 = Symbol.for('foo') console.log(s1 === s2) // true console.log(Symbol.for('true') === Symbol.for(true)) // true console.log(Symbol.iterator) // 内置对象接口 console.log(Symbol.hasInstance) const obj = {} console.log(obj.toString()) // [object Object] const obj1 = { [Symbol.toStringTag]: 'XObject' } console.log(obj1.toString()) // [object XObject] const obj2 = { [Symbol()]: 'symbol value', foo: 'normal value' } // 正常获取对象属性键名的方法获取不到Symbol类型 console.log(Object.getOwnPropertySymbols(obj2)) // [ Symbol() ]
-
for...of...
循环作为遍历所有数据结构的统一方式
使用
break
终止遍历类数组也可以直接使用
Set Map 对象也可以直接使用
无法遍历非迭代对象
const arr = [100, 200, 300, 400] for (const item of arr) { console.log(item) if (item > 100) { break } } const s = new Set([100, 200]) for (const item of s) { console.log(item) } const m = new Map() m.set('foo', '123') m.set('bar', '456') for (const item of m) { console.log(item) } const obj = { foo: 123, bar: 456 } // obj并不是一个迭代对象 for (const item of obj) { console.log(item) // 报错 TypeError: obj is not iterable }
-
可迭代接口(
Iterable
接口)实现Iterable 接口是
for...of...
循环遍历的前提const s = new Set(['foo', 'bar', 'baz']) const iterable = s[Symbol.iterator]() console.log(iterable.next()) // { value: 'foo', done: false } console.log(iterable.next()) // { value: 'bar', done: false } console.log(iterable.next()) // { value: 'baz', done: false } console.log(iterable.next()) // { value: undefined, done: true } console.log(iterable.next()) // { value: undefined, done: true }
-
实现一个可迭代接口
const obj = { store: ['foo', 'bar', 'baz'], [Symbol.iterator]: function () { let index = 0; const self = this; return { next: function () { const result = { value: self.store[index], done: index >= self.store.length } index += 1 return result } } } } for (const item of obj) { console.log(item) }
-
举个栗子理解迭代器
// 场景: 两人协同开发一个任务清单应用 const todos = { life: ['吃饭', '睡觉', '打豆豆'], learn: ['语文', '数学', '外语'], work: ['敲代码', '出差'], each: function (callback) { const all = [].concat(this.life, this.learn, this.work) for (const item of all) { callback(item) } }, [Symbol.iterator]: function() { const all = [...this.life, ...this.learn, ...this.work] let index = 0 return { next: function () { return { value: all[index], done: index ++ >= all.length } } } } } todos.each(function(item) { console.log(item) }) for (const item of todos) { console.log(item) } // 简化工作流程,降低代码耦合度
-
-
生成器对象函数
function * foo() { console.log('aaaa') return 100 } const result = foo() console.log(result) //Object [Generator] {} console.log(result.next()) // { value: 100, done: true } function * foo1() { console.log('aaaa') yield 100 console.log('bbbb') yield 200 console.log('cccc') yield 300 } const generator = foo1() console.log(generator.next()) console.log(generator.next()) console.log(generator.next())
-
生成器的应用
// 案例1:发号器 function * createIdMaker() { let id = 1 while (true) { yield id ++ } } const idMaker = createIdMaker() console.log(idMaker.next()) console.log(idMaker.next()) // 案例2: 使用Generator 函数实现 iterator 方法 const todos = { life: ['吃饭', '睡觉', '打豆豆'], learn: ['语文', '数学', '外语'], work: ['敲代码', '出差'], [Symbol.iterator]: function * () { const all = [...this.life, ...this.learn, ...this.work] for (const item of all) { yield item } } } for (const item of todos) { console.log(item) }
-
-
数组的
includes
方法(判断数组中是否用某个元素)let arr = [1, 2, 3] arr.includes(1) // true arr.includes(4) // false
-
指数运算符
console.log(Math.pow(2, 10)) console.log(2 ** 10)
-
可以在函数参数中添加尾逗号
function foo( bar, foos, a, ) { }
-
async...await
更加优雅的解决异步方案(内部实现其实也用了生成器的概念)
Flow:JavaScript的类型检查器
-
安装
yarn add flow-bin --dev
-
运行检查
yarn flow # 如遇报错没有配置文件则运行 yarn flow init
-
关闭类型检查监听
yarn flow stop
-
去除flow的注解
-
使用官方的flow-remove-types
安装:
yarn add flow-remove-types --dev
运行:
yarn flow-remove-types . -d dist # . 表示当前目录 # -d 参数表示输出目录 # dist 输出目录的目录名(不存在会自动创建)
-
使用babel
安装:
yarn add @babel/core @babel/cli @babel/preset-flow --dev # babel/core 核心模块 # babel/cli 命令行工具 # babel/preset-flow flow转换工具
添加babel的配置文件 .babelrc:
{ "presets": ["@babel/preset-flow"] }
运行:
yarn babel . -d dist # . 表示当前目录 # -d 参数输出目录 # dist 输出目录名
-
-
安装Flow插件(VSCode):安装Flow Language Support 插件(体验不是很友好)
更多插件请访问Flow官网:https://flow.org/en/docs/editors/
-
Flow的类型推断(Type Inference):根据代码中的使用情况自动推断出参数类型
-
Flow的类型注解(Type Annotations)
// 标记变量的类型 function a(n: number) { return n * n } let num: number = 100 // 标记函数返回值 function b(): number { return 100 } // 没有返回值的函数类型: 函数没有返回值默认返回了undefined, 标记返回值类型为 void function c(): void { }
-
Flow的原始类型(Primitive Types)
const a: string = 'a' const b: number = 100 // Nan // Infinity(无穷大) const c: boolean = false // true const d: null = null const e: void = undefined const f: symbol = Symbol()
-
数组类型(Array Types)
const arr: Array<number> = [1, 2, 3] // 全是数字组成的数组 const arr1: number[] = [1, 2, 3] // 全是数字组成的数组 const foo: [string, number] = ['foo', 100] // 元组:固定长度的数组,同时确定了数据类型
-
对象类型(Object Types)
const obj: {foo: string, bar: number} = {foo: 'foo', bar: 100} // 固定键的数量和值的数据类型 const obj1: {foo?: string, bar: number} = {bar: 100} // 添加 ? 表示当前属性可选(非必填) const obj2: { [string]: string } = {} // 确定后期添加给对象的键和值的类型
-
函数类型(Fuction Types)
// 对函数的参数类型和返回值类型进行限制 // 限制函数参数 function a(n: number) { return n * n } // 限制函数返回值 function b(): number { return 100 } // 没有返回值的函数类型: 函数没有返回值默认返回了undefined, 标记返回值类型为 void function c(): void { } // 限制函数参数中的回调函数的参数类型 function foo(callback: (string, number) => void) { callback('foo', 100) } foo(function(str, num) { // str => string // num => number })
-
特殊类型
// 字面量类型:限制变量必须是某一个值 const a: 'foo' = 'foo' // 赋值时必须为那个值 const b: 'success' | 'warning' | 'danger' = 'success' // | (或运算符)可使用 const c: string | number = 'str' // 类型为string或者number // 通过type关键词声明类型的别名 type StringOrNumber = string | number const d: StringOrNumber = 'string' // 效果同上 // maybe类型 ? const e: ?number = undefined // 不会报错 const f: number | null | void = undefined // 效果同上
-
任意类型(Mixed & Any)
// mixed === string | number | boolean ... function passMixed(value: mixed) { } // any === ..... function paddAny(value: any) { } // 二者之间的差异为mixed类型仍然为强类型(仍然符合类型安全原则),any却为弱类型 // 例 function passMixed(value: mixed) { // 这种用法在语法上会报错 // value.substr(1) // value * value // 正确用法为判断类型之后执行相应的逻辑 if(typeof value === 'string') { value.substr(1) } if(typeof value === 'number') { value * value } } function paddAny(value: any) { // 这种用法在语法上不会报错 value.substr(1) value * value }
-
具体类型问题请查看官方文档
// 官方文档:https://flow.org/en/docs/types/ // 第三方类型手册:https://www.saltycrane.com/cheat-sheets/flow-type/latest/
-
Flow运行环境API(内置对象)
// 对原装API的类型限制 // 例 const element: HTMLElement | null = document.getElementById('app') // 参数中传入其他类型会报错,返回值为一个HTML元素或者null // 具体查看一下链接中的API // https://github.com/facebook/flow/blob/master/lib/core.js // https://github.com/facebook/flow/blob/master/lib/dom.js // https://github.com/facebook/flow/blob/master/lib/bom.js // https://github.com/facebook/flow/blob/master/lib/cssom.js // https://github.com/facebook/flow/blob/master/lib/node.js
TypeScript: 基于JavaScript,是JavaScript类型的超集
在JavaScript的基础之上多了一些拓展特性
- 强大的类型系统
- 对ES6+ 性特性的支持
最终都会被编译为JavaScript代码,任何一种JavaScript运行环境都支持
相对于Flow的优势:
- 功能更为强大
- 生态更加健全、更完善
相对于JavaScript的缺点:
- 语言本身多了很多概念(接口/泛型/枚举)
- 对于周期较短的小型项目来说,TypeScript会增加一些成本(编写很多的类型声明)
配置文件:yarn tsc --init
Typescript 原始类型
!在非严格模式下,string | number | boolean 三种类型是允许为空的,可被赋值为null | undefined
-
string
const a: string = 'foo'
-
number
const b: number = 100 // NaN Infinity
-
boolean
const c: boolean = true // false
-
void
// 常用于标记函数值返回为空,只能为 null | undefined // 在严格模式下只能是 undefined const d: void = null // undefined
-
null
const e: null = null
-
undefined
const f: undefined = undefined
-
symbol
const g: symbol = Symbol() // target: 'es5' 配置时会报错,es5并没有symbol类型 // 对于这种报错解决办法有两种:1. 修改 target 配置 2. 在配置文件中的 lib 标准库声明中添加“ES2015”
中文错误信息
默认跟随系统或者编辑器的语言类型
在执行编译时添加参数:–local zh-CN
并不建议修改为中文
作用域问题
其他类型
-
Object类型
Typescript 中的 Object 类型并不单指对象类型,还包括了函数和数组类型
const foo: object = function () {} // [] | {} // 如果只想让变量只为对象类型就得使用字面量的语法: const foo: { foo: number } = { foo: 100 }
-
数组类型
数组类型的定义和Flow一样
// 定义全为数字的数组 const a: Array<number> = [1, 2, 3] const b: number[] = [4, 5, 6] // 列举强类型的好处(函数中操作必须的元素必须全为数字) function sum(...args: number[]) { return args.reduce((prev, current) => prev + current, 0) } // 数组中有数字有字符串 const arr: (number | string)[] = [1, "string", 2] // 数组中对象类型的定义 const persons: { name: string, age: Number }[] = [ { name: "刘英", age: 18 }, { name: "谢大脚", age: 28 }, ];
-
元组类型
确定了元素数量和元素类型的数组
const tuple: [number, string] = [100, 'foo']
-
枚举类型
在 JavaScript 中,我们常用一个对象来模拟枚举类型
在 Typescript 中,我们使用 enum 关键字来声明一个枚举类型
枚举类型会在编译时被编译成一个键值对对象
// 在声明时可以不用 = 进行赋值,默认从 0 开始累加 如果第一个已经声明了具体数值,后面的会在此基础上进行累加 enum PostStatus { a = 0, b = 1, c = 2 } //枚举类型并不都是数字,也有字符串枚举(字符串枚举不能自动进行累加,需要在声明时就把值定好) enum StringEnum { a = 'a', b = 'b' } // 可以通过索引来访问枚举对象 PostStatus[0] // 在使用时和JavaScript一样 const obj = { aa: 'aa', bb: PostStatus.c }
-
函数类型
对函数的输入输出进行类型约束
// 对形参进行类型约束,可选的形参用 ? 进行标识,剩余参数可用 ...rest操作符进行展开 function fn(a: number, b?: string, ...rest: number[]): string { return 'aa' } // 对函数表达式的类型约束 const fn2 = (a: number, b: string) => string => function(a: number, b: string): string { return 'aa' } // 对于一个无返回值的函数,我们可以使用 void 来标记 function fn1({ one, two }: { one: number, two: number }): void { console.log(one, two) } // 而对于一个永远也执行不完的函数,我们可以使用 never 来标记 function fn2(): never { throw new Error() console.log('hello') } function fn3(): never { while(true) {} console.log('hello') }
-
任意类型
任意类型(弱类型,原本的JavaScript就是使用任意类型的语言)
any 类型:any 类型也是一种动态类型,Typescript 不会对 any 执行类型检查(在 any 的基础上调用任意方法时语法不会报错)
function stringify(value: any) { return JSON.stringify(value) } stringify('bar') stringify(100) let foo: any = 100 foo.bar() // 语法上是不会报错的
-
隐式类型推断
我们没有使用类型注解的方式标注某一变量的类型时,在赋值时自动推断该变量的类型
对于无法推断的变量类型(只声明不赋值的变量),将会把该变量标记为any类型
let age = 18 // 推断为number类型 等于 let age: number = 18 age = 'a' // 此时语法将会报错 let foo // 只声明不赋值的变量默认标记为any类型
-
类型断言
Typescript 在无法推断某个变量的具体类型时,我们需要进行类型断言,让 Typescript 知道这个变量的具体类型,断言其实就是明确告诉 Typescript 某个变量确定为某个类型
// 假设 arr 的来源已确定 const arr = [100, 200, 300] const a = arr.find(i => i > 0) // 我们知道此时 a 的值必定是一个数字,但是 Typescript 并不知道,它认为你的值除了是 number 类型之外还可能是 undefined const b = a * a // 没有进行断言的时候语法将会报错 // 断言的使用 const num1 = a as number const num2 = <number>a // react JSX中不可用
-
接口(interface)
接口就是一种类型规范,我们在使用一个接口的时候就必须遵循该接口的规则(确定接口成员和成员的数据类型)
接口只是用来对我们实际使用中的数据进行类型约束的,并没有实际的使用意义(查看编译过后的 JavaScript 代码就能看出)
interface Post { title: string; // 使用分号分隔(也可以不加,和平时写JavaScript代码一样) content: string id: number } function printPost(post: Post) { console.log(post.title) console.log(post.content) } printPost({ title: 'title', content: 'content', id: 1 })
-
可选成员
// 接上述例子 interface Post { title: string; // 使用分号分隔(也可以不加,和平时写JavaScript代码一样) content: string id: number subtitle?: string // 可选成员 解析类型为 string | undefined }
-
只读成员
// 接上述例子 interface Post { title: string; // 使用分号分隔(也可以不加,和平时写JavaScript代码一样) content: string id: number subtitle?: string // 可选成员 解析类型为 string | undefined readonly summary: string // 只读成员 } const b: Post = { title: 'title', content: 'content', id: 1, summary: 'summary' } b.summary = 'string' // summary为只读成员,再进行更改时将会报错
-
动态成员(例子:缓存的键值对是动态的)
interface Cache { [prop: string]: string // 动态成员,key | value 的类型都为string }
-
-
类(class)
描述一类具体事物的抽象特征
ES6 以前都是用函数 + 原型来模拟实现类
在 typescript 中增强了 class 的相关语法
class Person { name: string // 值可以在此进行初始化,或者在构造函数中进行赋值(二者必须取其一,要不然就会报错) age: number constructor (name: string, age: number) { this.name = name this.age = age } sayHi(msg: string): void { } }
-
类中的访问修饰符
// 接上述代码 class Person { public name: string // 公有属性,类内部属性就是默认为公有属性 private age: number // 私有属性,只能在类中进行访问 protected gender: boolean constructor (name: string, age: number) { this.name = name this.age = age this.gender = true } sayHi(msg: string): void { console.log(msg) } } class Student extends Person { // 对于构造函数也可以使用 private 修饰符,此时该类不能被继承和直接实例化使用,也不能调用实例方法,此时我们可以使用静态方法进行解决 // 使用 protected 是也是不能直接实例化使用,但是可以被继承 //private constructor(name: string, age: number) { // super(name, age) //} //static create(name: string, age: number) { // return new Student(name, age) //} // 在外部调用静态方法进行实例化 // const a = Student.create('a', 18) constructor(name: string, age: number) { super(name, age) // protected 表示只允许在此类对象中访问,子类也归属于父类 console.log(this.gender) // 此时能访问到 } } const ming = new Person('ming', 18) console.log(ming.name) //console.log(ming.age) // 此时将会报错,使用了在实例中使用了类中的私有属性 //console.log(ming.gender) // 此时也报错,对于用 protected 进行修饰的成员对象也不能在类的外部访问
-
类的只读属性
class Person { public name: string private age: number protected readonly gender: boolean // 此时的 gender 只读,只能在此处或者构造函数中进行初始化并且只能初始化一次,再次进行改变时将会报错 constructor (name: string, age: number) { this.name = name this.age = age } sayHi(msg: string): void { } }
-
类与接口
// 使用接口来约束类中的方法属性 interface Run { run(food: string): void } interface Eat { eat(distance: number): void } class Person imolements Eat, Run { // 使用接口来约束类的方法 eat(food: string): void { console.log('人吃东西') } run(distance: number): void { console.log('人在跑') } } class Animal imolements Eat, Run { eat(food: string) { console.log('动物吃东西') } run(distance: number) { console.log('动物在跑') } }
-
抽象类
// 抽象类和接口类似,都是用来约束类的实现 // 不同的是使用抽象类之后能被继承,但是无法使用 new 关键字来创建实例对象 abstract class Animal { eat(food: string): void { console.log('动物在吃东西') } // 定义抽象方法 abstract run(distance: number): void // 不需要有具体的方法函数,但是在子类中需要有具体的方法函数来进行调用 } class Dog extends Animal { run(distance: number): void { console.log('动物在跑') } } const dog = new Dog() dog.eat('狗粮') dog.run(100)
-
泛型
泛型就是我们在定义类或者接口的时候没有指定具体的类型,等到我们在使用的时候在进行指定具体类型的特征
// 创建指定长度的数组,内部元素固定了数据类型(不使用泛型时不够灵活) function createNumberArray(length: number, value: number): number[] { const arr = Array(length).fill(value) return arr } function createStringArray(length: number, value: number): string[] { const arr = Array(length).fill(value) return arr } const res = createNumberArray(3, 100) // res => [100, 100, 100] const res1 = createStringArray(3, 'string') // res1 => ['string', 'string', 'string'] // 使用泛型来进行定义 function createArray<T>(length: number, value: T): T[] { const arr = Array<T>(length).fill(value) return arr } const res2 = createArray<string>(3, 'string') // res2 => ['string', 'string', 'string'] const res3 = createArray<number>(3, 100) // res3 => [100, 100, 100]
-
类型声明
我们在开发过程中会使用各种各样的模块包,这些模块包并不全是使用 Typescript 来进行开发的,在我们使用的时候需要进行单独的类型声明(现在大多数常用的包都有了自己的类型声明模块,如果没有的就需要我们使用 declare 关键词来进行类型声明)
// 以使用 lodash 为例 import { camelCase } from 'loadsh' declare function camelCase(input: string): string const res = camelCase('hello world')
-
JavaScript
的性能优化、了解V8引擎
JavaScript
中的垃圾回收(内存管理)
JavaScript
中的垃圾:JavaScript
中的内存管理是自动的- 对象不再被引用时就是垃圾,需要被回收
- 对象不能从根上访问到时就是垃圾,也需要被回收
JavaScript
中的可达对象:- 可以访问到的对象就是可达对象(对象引用/作用域链)
- 可达的标准就是从根触发是否能够被找到
JavaScript
中的根就是全局变量对象
JavaScript
中的垃圾回收算法(GC算法)
- GC的定义与作用
- GC就是垃圾回收机制的简写
- GC可以找到内存中的垃圾、并释放和回收空间
- GC中的垃圾是什么
- 程序中不再需要使用的对象
- 程序中不能再访问到的对象
- GC算法是什么
- GC是一种机制,垃圾回收期完成具体的工作
- 工作的内容就是查找垃圾释放空间、回收空间
- 算法就是工作时查找和回收所遵循的规则
- 常见的GC算法
- 引用计数法
- 标记清除法
- 标记整理法
- 分代回收法
引用计数法的实现原理
- 核心思想:设置引用数,判断当前引用数是否为0
- 引用计数器
- 引用关系改变时修改引用数字
- 引用数字为0时立即回收
引用计数法的优缺点
- 优点
- 发现垃圾时立即回收
- 减少程序卡顿时间
- 缺点
- 无法回收循环引用的对象
- 资源开销大
标记清除法的实现原理
- 核心思想:分为标记和清除两个阶段完成
- 遍历所有对象对活动对象进行标记
- 遍历所有对象对没有标记的对象进行清除
- 回收相应的空间
标记清除法的优缺点
- 优点
- 对循环引用的对象能进行标记清除
- 缺点
- 空间碎片化,内存地址不连续
- 不会立即回收垃圾对象
标记整理法的实现原理
- 编辑整理可以看做是标记清除的增强
- 标记阶段的操作和标记清除一致
- 清除阶段会先执行空间整理,移动对象位置
- 配合标记清除能有效执行垃圾回收操作
标记整理法的优缺点
- 优点
- 减少碎片化的内存空间
- 缺点
- 也不会立即回收垃圾对象
认识V8引擎
- V8是一款主流的
JavaScript
执行引擎 - V8采用即时编译
- V8内存设置上限(64位操作系统下不超过1.5G,32位操作系统下不超过800M)
V8垃圾回收策略
- 采用分代回收的思想
- 内存分为新生代和老生代
- 针对不同对象执行不同的垃圾回收算法
V8中用到的GC算法
- 分代回收
- 空间复制
- 标记清除
- 标记整理
- 标记增量
V8内存分配
- V8内存空间一分为二(新生代和老生代)
- 小空间用于存放新生代对象(64位操作系统下为32M,32位操作系统下为16M)
- 新生代指的是存活时间较短的对象
- 大空间用于存放老生代对象(64位操作系统下为1.4G,32位操作系统下为700M)
- 老生代对象像指的是存活时间较长的对象
新生代对象的回收
- 回收过程采用复制算法 + 标记整理
- 新生代内存分为两个等大小的空间
- 使用空间为 From 空间空间为 To
- 活动对象存储在 From 空间中
- 标记整理后将活动对象拷贝至 To
- From 与 To 交换空间,释放 From 中的内存
回收细节说明
- 拷贝过程中可能出现晋升
- 晋升就是将新生代对象移动到老生代中
- 一轮GC之后还存活的新生代对象需要晋升
- To 空间的使用率超过了 25%,将空间内的对象移动到老生代空间中
老生代对象的回收
- 主要采用标记清除、标记整理、增量标记算法
- 首先使用标记清除完成垃圾空间的回收
- 采用标记整理进行空间优化
- 采用增量标记进行效率优化
新老生代回收细节对比
- 新生代垃圾回收使用空间换时间
- 老生代垃圾回收不适合复制算法
- 标记增量怎么优化垃圾回收: