一、Promise
1.概述
-
是一个构造函数(对象),ES6新增的。
- 提供了一种解决异步的方法,而且解决了回调地狱的问题。
- 三种状态:Pending(进行中)、Resolved(已完成)、Rejected(已拒绝)
- 原型方法:Promise.then / catch /finally
- 私有方法:resolve / reject
- 静态方法: all / allsettled / race / any / resolve / reject
- 当把一个任务交给 promise 的时候,它的状态就是Pending,任务完成之后状态变为Resolved,没有完成失败就变成了Rejected。
- 一旦状态发生改变,那么就不能更改状态了。
2. then(解决回调地狱)
- then 的默认返回值是Promise。
- 成功的结果通过 resolve 传递给 then ,下一次的异步可以在下一个 then 中进行可以一直链式调用。
- then 的回调函数中如果写了 return,该值当做 Promise 的成功结果传递给下一个 then
- 如果在 then 中单独返回一个 promise ,那么下一次 then 的成功结果来自于上一个then 中成功回调里面返回的 promise 的 resolve 的结果。
- then 的中断:可以通过回调中的 reject / throw new Error 进行中断。
3. await async
- await 用来修饰 promise。
- async 用来修饰 await 就近的函数。
- async 修饰的函数返回值为 promise。
- 被 async 修饰的函数一定可以被 await 修饰。
- 捕获 async 和 await 中的错误。
- trycatch 捕获
- 原型方法 catch 捕获:then().catch()
4. 静态方法
- Promise.all()
- 可以获取多个 promise 处理异步的结果,返回值是 promise
- 可以调用 .then(),当all 数组中所有promise 都resolve()之后才执行 then ,只要有一个reject()那么就中断。
- all 的then 返回的结果就是对应的 promise 返回的数据
- 内部通过reduce默认遍历数组,通过当前项调用 then来判断当前操作成功与否,失败就走 reject ,成功就走 resolve 传递给下一次 then,一旦有一个项走了reject那就操作就中断了。
- Promise.allsettled()
- 也可以获取多个 promise 异步处理的结果,不管 resolve 还是 reject 都会执行 then ,结果返回一个数组,每个对象通过 status 记录对应 promise 的状态( fulfilled / rejected )
- 通过 then 中成功还是失败来控制 结果对象中的 status的值,成功就为
fulfilled失败就为rejected并且会返回一个 reason 的字段。
- Promise.race()
- 和 all 一样,只返回第一个结果(成功或者失败)。
- Promise.any()
- 返回多个 promise 处理结果中第一个成功的结果。
5. Generator 函数
概念:可以将函数的控制权交出(将函数的执行暂停),也可以利用其更方便的控制异步
格式:用 * 来标记 generator 函数,用 yield 来进行函数暂停(交出控制权)。
function *fn(){
//yield 交出控制权
yield console.log(1)
yield console.log(2)
return 3
}
逐步调用:
const gen = fn()
gen.next()
向外传递数据 :调用next()会返回一个对象,对象里有两个属性,一个是value(每次的返回值),一个是done(函数是否执行结束)
function *fn(){
yield 1
yield 2
return 3
}
const gen = fn()
console.log(gen.next())//{value:1,done:false}
console.log(gen.next())//{value:2,done:false}
console.log(gen.next())//{value:3,done:true}
处理异步:配合 promise 处理
function *fn() {
yield new Promise(resolve => {
setTimeout(() => {
console.log(1)
resolve()
}, 2000)
})
yield new Promise(resolve => {
setTimeout(() => {
console.log(2)
resolve()
}, 1000)
})
yield new Promise(resolve => {
setTimeout(() => {
console.log(3)
resolve()
}, 3000)
})
}
const gen = fn()
//调用(回调地狱)
gen.next().value.then(() => {
gen.next().value.then(() => {
gen.next()
})
})
//利用co 递归
function co(gen) {
//res 的value属性里是一个 promise 对象
const res = gen.next()
//如果函数执行完毕就结束递归
if (res.done) return
//拿到 promise 的 成功回调的返回数据
res.value.then(() => {
co(gen)
})
}
${\textcolor{red}{async、await和generator的区别:}}$

6. Promise A+ 规范
规定了Promise开发设计时的规则。
7. 手写 Promise 源码
// 定义三个状态
const PENDING = 'PENDING'
const RESOLVED = 'RESOLVED'
const REJECTED = 'REJECTED'
function resolvePromise(x, promise2, resolve, reject) {
//判断x === promise, 抛出类型错误
if (x === promise2) {
console.log('======')
return reject(new TypeError('类型错误'))
}
// 允许状态改变一次
let called = false
try {
//判断x是否包含then属性,thenable
if (typeof x === 'object' && x !== null) {
const then = x.then
if (typeof then === 'function') {
// console.log(typeof then)
x.then(
x,
v => {
if (called) return
called = true
resolvePromise(v, promise2, resolve, reject)
},
r => {
if (called) return
called = true
reject(r)
}
)
} else {
if (called) return
called = true
resolve(x)
}
} else {
if (called) return
called = true
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
}
class Promise {
constructor(exectuor) {
try {
//捕获执行器错误
exectuor(this.resolve, this.reject)
} catch (e) {
this.reject(e)
}
}
status = PENDING
value = null
reason = null
//存储失败和成功的回调
onFullFilledCallbacks = []
onRejectedCallbacks = []
static all(args) {
return new Promise((resolve, reject) => {
args.reduce((prev, curr, i, arr) => {
if (curr instanceof Promise) {
curr.then(
v => {
prev[i] = v
if (prev.length === arr.length) {
resolve(prev)
}
},
r => {
reject(r)
}
)
} else {
prev[i] = curr
}
return prev
}, [])
})
}
static resolve(v) {
if (v instanceof Promise) return v
return new Promise((resolve, reject) => resolve(v))
}
static reject(r) {
return new Promise((resolve, reject) => {
reject(r)
})
}
static allSettled(args) {
return new Promise((resolve, reject) => {
function addData(prev, index, value) {
prev[index] = value
if (prev.length === args.length) {
resolve(prev)
}
}
args.reduce((prev, curr, index, arr) => {
if (curr instanceof Promise) {
curr.then(
res => {
addData(prev, index, {
value: res,
status: 'fulfilled'
})
},
r => {
addData(prev, index, {
reason: r,
status: 'rejected'
})
}
)
} else {
addData(prev, index, {
reason: curr,
status: 'fulfilled'
})
}
})
})
}
static race(args) {
return new Promise((resolve, reject) => {
args.forEach(item => {
if (item instanceof Promise) {
item.then(
v => {
resolve(v)
},
r => {
reject(r)
}
)
} else {
resolve(item)
}
})
})
}
finally(cb) {
return this.then(
v => {
return Promise.resolve(cb()).then(() => v)
},
r => {
return Promise.resolve(cb()).then(() => {
throw r
})
}
)
}
resolve = v => {
//只有状态为pending才执行
if (this.status === PENDING) {
this.status = RESOLVED
this.value = v
console.log(this.onFullFilledCallbacks)
this.onFullFilledCallbacks.forEach(c => c())
}
}
reject = r => {
if (this.status === PENDING) {
this.status = REJECTED
this.reason = r
this.onRejectedCallbacks.forEach(c => c())
}
}
then(onFullFilled, onRejected) {
debugger
//onFullFilled onRejected类型判断
if (typeof onFullFilled !== 'function') onFullFilled = v => v
if (typeof onFullFilled !== 'function') {
onRejected = r => {
throw r
}
}
const promise2 = new Promise((resolve, reject) => {
if (this.status === RESOLVED) {
// Promise为微任务,所以放到微任务队列执行
queueMicrotask(() => {
try {
const x = onFullFilled(this.value)
resolvePromise(x, promise2, resolve, reject)
} catch (e) {
reject(e)
}
})
}
if (this.status === REJECTED) {
queueMicrotask(() => {
try {
const x = onRejected(this.reason)
resolvePromise(x, promise2, resolve, reject)
} catch (e) {
reject(e)
}
})
}
if (this.status === PENDING) {
//如果状态为pending,则执行方法放入数组中,等待resolve或reject时候执行
this.onFullFilledCallbacks.push(() => {
queueMicrotask(() => {
try {
const x = onFullFilled(this.value)
resolvePromise(x, promise2, resolve, reject)
} catch (e) {
reject(e)
}
})
})
this.onRejectedCallbacks.push(() => {
queueMicrotask(() => {
try {
const x = onRejected(this.reason)
resolvePromise(x, promise2, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
})
return promise2
}
}
Promise.deferred = function() {
var result = {}
result.promise = new Promise(function(resolve, reject) {
result.resolve = resolve
result.reject = reject
})
return result
}
module.exports = Promise
resolvePromise() 源码分析
// 判断 x 是不是对象 并且不是null
// x 是 return new Promise((resolve, reject) => {resolve(2)})
if (typeof x === 'object' && x !== null) {
// 如果是对象 x是否有then方法
const then = x.then
if (typeof then === 'function') {
// console.log(typeof then)
// 手动返回的promise.then
x.then(
v => {
if (called) return
called = true
// 有可能手动返回的promise中的resolve还有可能继续返回Promise
resolvePromise(v, promise2, resolve, reject)
},
r => {
if (called) return
called = true
reject(r)
}
)
} else {
if (called) return
called = true
// 默认返回promise的resolve
// 既然走到了这里 x 不是promise 那x就应该携带到下一个then中
resolve(x)
}
} else {
if (called) return
called = true
// x 不是一个对象,直接调用resolve(x) 传递到下一个then中
resolve(x)
}
8. 源码分析
- 初始化
- 用
status来存储当前 promise 的状态。 - 用
value存成功的结果值,用resson存失败的结果值。 - 用${\textcolor{red}{数组}}$分别存储成功和失败的回调,因为promise实例可以多次调用 then 。
- 初始化 Promise 中的 resolve和reject 回调方法。
- 执行构造函数
- 把 Promise 中传递的参数通过
executor进行接收,通过 trycatch 进行捕获,如果传递的参数不是一个函数,就进行捕获,调用reject()私有方法。 - 如果正常执行,那么就调用
executor()方法,并且把私有方法传递过去。因此 Promise 中的函数是同步的。
- 调用
resolve()
- 当 Promise 中调用
resolve()并且传递了一个参数,那么就会调用类中的私有方法,并且先判断是否处于 Pedding 状态,处于才可以执行,调用resolve()的promise会把pedding状态变为 resolved状态,因此是不可逆的,然后会把传递来的参数,存储到成功的结果值中,接着会执行成功回调数组里的回调函数。
- 调用
then()执行异步
- 传递
then()中的参数,然后在类中拿到参数并且判断该参数是否为函数,不是函数给该参数包装为一个函数,为了保证then()的链式调用性,则需要在内部重新实例化一个 promise2 ,继续在内部判断当前promise2 的状态,最后return promise2。 - 如果当前状态为 Resolve ,则通过
queueMicrotask()中调用onFullFill()传递成功的返回值(value),则在外部的 then 中就能拿到成功的返回值。(为了确保then中的回调是异步的以及是一个微任务,A+规范只规定了then需要是一个异步中并没有规定then中必须为微任务,所以setTimeout()也可以) - 如果状态为 padding 则把方法放入到回调数组中,等待resolve或者是reject的执行,从而继续执行回调数组中的回调。
- then 中手动返回 promise
- 如果在 then 中继续 return promise或者 return 一个数据 ,则会调用
resolvePromise()得到onFullFill()的返回值,在内部判断返回值。- 如果返回值是 promise ,那就继续调用
then继续递归,直到返回值不是promise。 - 如果返回值不是promise那么就调用 默认的内部的 promise 内置的
resolve()传递到下一个 then 中。
- 如果返回值是 promise ,那就继续调用
二、同步异步
1. 概念
- 异步任务:因为 JS 是单线程的,同一时间只能执行一个任务,当我们执行一个比较耗时的任务的时候,就会阻塞下面的任务,此时 JS 就会把耗时任务委托给宿主当宿主环境执行,当宿主环境执行完毕之后,然后把回调交给 JS 执行,这些交给宿主环境执行的耗时任务就是异步任务。
- 同步任务:在主线程上执行的,当前一个任务执行完毕,才能执行后一个任务。
2. 执行流程
- 主线程先判断任务的类型
- 如果为同步任务,那么就先执行。
- 如果为异步任务,那么会交给宿主环境执行
- 当宿主环境每执行一个异步,会把这个异步的回调放入任务队列中。
- 当主线程的全部任务执行完毕之后,会取任务队列中的任务,取任务也会依次取先进来的任务。
- 任务队列取出来的任务执行完毕之后,再继续取一下个,重复的从任务队列中压入执行栈的操作称之为 EventLoop 事件循环。
3. 宏任务
由宿主环境发起的异步任务。
setTimeOut、setInterval、script代码块
4. 微任务
由 javascript 自身发起的任务。
.then()、catch()、MutationObserver
5. 宏任务、微任务执行顺序
- 整个 script 当做一个宏任务
- 先执行宏任务
- 查看任务队列是否有微任务,
- 如果有微任务,那么就先执行完所有队列中的微任务。
- 如果没有微任务,那么就执行下一个宏任务。
async function async1() {
console.log('async1 start')
await async2()
// await后面的代码可以理解为promise.then(function(){ 回调执行的 })
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(function() {
console.log('setTimeout')
}, 0)
async1()
console.log('script end')
// script start、async1 start、async2、script end、async1 end'、setTimeout
const async1 = async () => {
console.log('async1')
setTimeout(() => {
console.log('timer1')
}, 2000)
await new Promise((resolve) => {
console.log('promise1')
// resolve()
})
console.log('async1 end')
return 'async1 success'
}
console.log('script start')
async1().then((res) => console.log(res))
console.log('script end')
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.catch(4)
.then((res) => console.log(res))
setTimeout(() => {
console.log('timer2')
}, 1000)
//script start、async1、promise1、script end、1、timer2、timer1
三 、闭包
1. 概念
- 可以在一个内层函数中可以访问到外层函数的作用域。
2. 原理
- 核心其实是作用域链,当我们在当前作用域访问不到数据的时候,就向父级访问,如果父级也没有,那么就继续 向外访问,一直找到找到全局。
3. 作用
- 数据私有化,防止污染全局
- 当我们在局部定义的数据和全局的变量冲突时,闭包会把我们在内部声明的变量保留在该范围之内,不会影响外部的变量,避免了污染全局变量的可能性。
4. 缺点
- 如果使用不当的话可能造成内存泄漏,比如我们的内部函数的执行需要外部函数里面的变量,当函数调用完毕之后,但是外部函数中的变量还一直存在于内存中,没有被回收,这就导致了内存泄漏的可能。
- 因此我们需要在函数调用结束之后把不使用的变量手动删除或者置空 。
四、垃圾回收机制
1. 概念
js 内存是自动进行分配和回收,内存在不使用的时候会被垃圾回收自动回收,当然还有一些情况下,内存无法回收,比如(使用闭包不当的话),这时候就需要手动的回收。
2. 生命周期
主要为三个阶段
- 内存创建分配:当前阶段进行变量、对象、函数内存的申请
- 内存使用:对内存进行读写的阶段,也就是使用变量、函数或者对象
- 内存销毁:当变量、函数和对象不再使用,就会被垃圾回收自动回收。
3. 算法
-
引用计数
- 计算当前内存被引用的次数,内存被引用一次那么计数+1,不被引用计数-1,当计数为0的时候,就释放内存。
- 缺点:当两个内存循环引用的时候,也就说明两个内存的引用计数都不为0,他们的内存不会被销毁,就造成了内存泄漏。
function fn() { var a = {} var b = {} a.a1 = b b.b1 = a } fn() //调用完之后 a 、b 相互引用,次数都为 1 不会被销毁。 -
标记清除
- 通过全局(根节点)标记所有从根节点开始能够访问到的对象,未被标记的对象最终会被清除。
五、数据类型以及类型检测
1. 数据类型
- 基本数据类型(值类型):字符串(String)、数字(Number)、布尔(Boolean)、空 (Null)、未定义(undefined)、Symbol、bigInt。
- 引用数据类型(对象类型):对象(Object)、数字(Array)、函数(Function)、正则(RegExp)、日期(Date)
2. 数据类型的检测
type of
正常检测出:Number、Boolean、String、Function、Undefined、Symbol、BigInt
- 检测基本数据类型:Null会返回Object,因为它是一个空的对象引用。
- 检测引用数据类型:除Function之外,都返回Object。
Null 和 Undefined 的区别?
- Null 是定义了一个空对象的引用。
- Undefined 是未定义。
instanceof
用于检测构造函数的 prototype 属性是否出现在实例对象的原型链上。
- 只能进行单一的检测,判断某个数据是不是某个数据类型。
//判断实例对象是否可以根据原型链找到对应的构造函数的原型对象
const _instanceof = (target, Fn) => {
// 补全代码
let targetProto = target.__proto__;
let fnProto = Fn.prototype;
console.log(targetProto, fnProto);
while (true) {
if (targetProto == fnProto) {
return true;
}
if (targetProto == null) {
return false;
}
targetProto = targetProto.__proto__;
}
};
let arr = new Array();
str = "";
// console.log(arr);
console.dir(arr);
console.log(arr instanceof Array, _instanceof(arr, Array));
console.log(str instanceof Array, _instanceof(str, Array));
console.log(arr instanceof Object, _instanceof(arr, Object));
toString
它是 Object 的原型方法,调用它会返回当前对象的[[Class]]。会返回[[object xxx]],xxx就是数据类型。
- 如果数组调用这个方法,是不会返回他的数据类型的,因为这个方法相当于用了自己原型上的方法,而不是Object原型上的。
- 此时需要借调这个方法
- 通过
Object.prototype.toString.call(数据),通过 call 改变 this 指向,让 toString 的this 指向要判断的数据。
- 通过
constructor
constructor本质是对函数本身的引用。可以检测由字面量方式创建出来的数据类型。因为我们可以通过实例的原型找到它的 constructor ,而constructor就是构造函数本事 ,字面量方式的本质就是创建了一个这个对象的实例。因此就可以判断这个实例是不是由该构造类型创建的。
isArray()
用来检测是否为数组
const arr = [];
Array.isArray(arr);//true
3. Set和Map数据结构
- Set(类似于数组)
特点:成员的值唯一,没有重复的值,可以用来数组去重,[…new Set(array)],可以接受数组或者伪数组作为参数。
声明:new Set()
${\textcolor{red}{方法}}$:添加成员:add()
判断成员是否存在:has()
删除成员返回布尔:delete() 清除set:clear() 遍历set:foreach()
- WeakSet(和Set类似)
特点:首先成员只能是对象,其次WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。
let ws = new WeakSet();
ws.add({
name: "张三",
age: 18,
});
ws.add({
name: "李四",
age: 20,
});
console.log(ws);

- Map(像对象,但是键(key)能为任意类型的值)
声明:new Map()
${\textcolor{red}{方法}}$:添加成员:set(key,value)
判断成员是否存在:has(key) 获取成员的值:get(key) 删除成员 :delete(key) 返回Map结构成员:size 清除所有成员,没有返回值:clear()let o = {
userm: "wer",
};
let map = new Map();
map.set(o, "age");
console.log(map.get(o));
console.log(map.has(o));
// map.delete(o);
console.log(map);

- WeakMap()
WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。
// WeakMap 可以使用 set 方法添加成员
const wm1 = new WeakMap();
const key = {foo: 1};
wm1.set(key, 2);
wm1.get(key) // 2
// WeakMap 也可以接受一个数组,
// 作为构造函数的参数
const k1 = [1, 2, 3];
const k2 = [4, 5, 6];
const wm2 = new WeakMap([[k1, 'foo'], [k2, 'bar']]);
wm2.get(k2) // "bar"
4. String 和 toString 的区别
- String 可以将所有的数据都转化为字符串,但是 toString() 不能对null和undefined进行转换
<script>
var str1 = false.toString();
var str2 = String(flase);
console.log(str1,typeof str1 );//false string
console.log(str2,typeof str2 );//false string
</script>
- 使用 toString() 对数字进行转换的时候可以通过在括号中添加数字,来将数字转换为相应的进制,但是使用 String() 不能进行进制转换。
<script>
var str1 = String(undefined);
console.log(str1,typeof str1);
var str2 = undefined.toString();
console.log(str2,typeof str2);
</script>
六、继承
1. 原型链继承
function Father(uname) {
this.uanme = uname;
this.some = "yiixe";
this.arr = [1, 2, 3, 4];
}
//把函数的方法定义到 原型 上
Father.prototype.work = function () {
console.log("工作");
};
function Son(uname) {
this.uanme = uname;
}
Son.prototype = new Father();
//这样 Son的原型里就没有了构造函数
Son.prototype.constructor = Son;
console.log(Son.prototype); //Father
Son.prototype.study = function () {
console.log("学习");
};
let father = new Father("父类1");
let father2 = new Father("父类2");
father.work(); //工作
father2.work(); //工作
let son = new Son("子类");
let son1 = new Son("子类1");
son.study(); //学习
console.log(son.arr); //[1,2,3,4]
son.arr.push(5);
console.log(son1.arr); //[1,2,3,4,5]
console.log(father.arr); //[1,2,3,4]
son1.arr = [1, 2];
console.log(son1.arr); //[1,2]
console.log(father.arr); //[1,2,3,4]
缺点:
- 如果Father定义了一个引用类型的属性,Son.prototype = new Father()相当于 Son.prototype 变成了 Father的一个实例对象,从而相当于创建了Son.prototype.引用类型 这个属性 ,最后Son的所有实例都会通过地址访问这个属性的内容,如果对该引用类型属性进行修改,则其他的实例访问的该引用类型的内容都会改变。
- 原型链的第二个问题是,子类型在实例化时不允许给父类型的构造函数传参,这样做不符合面向对象编程的规则:对象(实例)才是属性的拥有者。如果在子类定义时就将属性赋了值,就变成了类拥有属性,而不是对象拥有属性了。
- 给子类型的原型添加属性和方法必须在替换原型之后。
- 子类型的原型的构造函数被破坏覆盖。需要我们重新手动把创建constructor并且把子类型的构造函数指向子类型。
2. 借用构造函数继承
function Father(uname, age) {
this.uname = uname;
this.age = age;
this.work = function () {
console.log("工作");
};
}
function Son(uname, age) {
Father.call(this); //让父类型的this指向子类
this.uname = uname;
this.age = age;
this.study = function () {
console.log("学习");
};
}
let father = new Father("父类", 30);
console.log(father.age);
father.work();// 工作
father.__proto__.work1 = function () {
console.log("再工作");
};
father.work1();// 再工作
console.log("===========");
let son = new Son("儿子", 18);
console.log(son.uname, son.age);// 男,18
son.gender = "男";
console.log(son.gender);// 男
son.study();// 学习
son.work();// 工作
son.work1();//work1 not defined
缺点:
- 所有方法都定义在构造函数中,无法做到函数复用,父类原型中定义的方法,子类访问不到,因为子类的实例只继承了父类的实例属性/方法,没有继承父类原型对象中的属性/方法。
3. 组合式继承
- 原型链和借用构造函数的技术组合到一块。
- 使用原型链实现对原型方法的继承,而通过构造函数来实现对属性的继承。既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。
// 创建人的类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.say = function() {
console.log('说话了')
}
function Dog(name, age) {
Person.call(this, name, age)
}
// 此方式不推荐 子原型和父原型公用同一个原型
// Dog.prototype = Person.prototype
Dog.prototype = new Person()
const d1 = new Dog('大黄', 3)
const d2 = new Dog('小黄', 2)
缺点:
- 调用了两次父类,产生了两份实例。
4. 寄生式组合继承
通过 Object.create(数据),可以创建一个对象,该对象的原型指向参数对象。
- 即用构造函数继承属性,又用原型链继承的方式继承方法。
- 把子类型的对象指向创建的这个空对象,而空对象的原型对象就是原来父类型。
// 创建人的类
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.say = function() {
console.log('说话了')
}
function Dog(name, age) {
Person.call(this, name, age)
}
// 此方式不推荐 子原型和父原型公用同一个原型
// Dog.prototype = Person.prototype
Dog.prototype = Object.create(Person.prototype)
const d1 = new Dog('大黄', 3)
const d2 = new Dog('小黄', 2)
5. ES6类继承
class Person {
say() {
console.log('说话了')
}
}
class Child extends Person {}
const child = new Child()
child.say()
七、深浅拷贝
1. 浅拷贝
- 只能拷贝对象的一层。
- 如果进行浅拷贝深层次的引用类型的话,如果拷贝之后的数据被修改,那么原来数据也会被修改,因为他们都是引用的同一个对象。
- ` Object.assign({},obj)` 浅拷贝
...扩展运算符
2. 深拷贝
- JSON实现深拷贝
- JSON.stringify()把对象转换为字符串
-
JSON.parse()把字符串转回对象。
-
问题:
- 造成数据丢失和数据异常
- function、undefined 直接丢失
- NaN、Infinity、-Infinity 变为 null
- RegExp、Error 对象只得到空对象
- 因此可以在通过递归深拷贝的时候需要把对应丢失的数据类型重新new 之后再返回。
-
问题:
- 循环引用问题:数据可能是嵌套引用的,造成堆栈溢出的问题。
-
解决:
- 将每次拷贝的数据进行存储,每次在拷贝之前,先看该数据是否拷贝过,如果拷贝过,直接用这个数据,不再拷贝。如果没有拷贝,对该数据进行拷贝并记录该数据记录到存储中。
- 每次拷贝的数据可以使用数组进行存储。
- 可以用map进行存储,但是他是强引用类型,无法被垃圾回收。
- 可以使用WeakMap,他是弱引用类型,可以被垃圾回收。
-
区别:

八、new 的过程
- 创建一个新的空对象
- 将新对象的
__proto__指向构造函数的prototype - 将构造函数内部的 this 指向新对象
- 执行构造函数,给实例对象添加属性和方法
- 返回 this
- 如果手动返回简单数据类型,那么忽略,最终还是返回 this
- 如果返回复杂数据类型,那么最终得到该复杂数据类型,this 无效。
九、ES6
1.let、const
- let 用来声明变量
- const 用来声明常量
- 具有块级作用域,在块级作用域之外访问不到。
- 不存在变量提升。
- 具有暂时性死区:只要块级作用域中声明了let或const,就不会受外界影响,如果在声明之前使用这些变量或常量 ,就会报错。
- 不允许重复声明。
十、字符串方法
1. substr 和 substring
-
substr (开始索引,长度):从开始索引截取对应长度(不包含最后元素)。
- 第一个参数是负数:从末尾截取对应长度(不是倒数)。


- 不传递第二个参数:从开始索引截取到最后。


- 传递第二个参数:从开始索引截取对应长度。


- 第二个参数是
str.length(或者大于等于长度) :表示从开始截取到末尾。
- substring(开始索引,结束索引):从开始索引截取到结束索引(不包含最后元素)。
- 不传递第二个参数:从开始截取到结束(包括最后)


- 传递第二个参数:从开始索引截取到结束索引。


-
如果有一个参数是负值,代表从0开始截取到第一个正参数的索引处。


-
如果两个参数都是负数,就返回一个空字符串。
十一、数组方法
十二、this指向
1. 函数模式调用
- (函数名加小括号直接调用),指向 ${\textcolor{red}{Window}}$ 。
2. 对象模式调用
- 谁调用指向谁。
3. 箭头函数
- 和它的定义有关,指向它的上下文。
4. 构造函数
- 默认指向 ${\textcolor{red}{实例对象}}$ 。
5. 修改this指向
call(宿主对象,参数1,参数2...)- 宿主对象:将this指向宿主对象。
apply(宿主对象,伪数组)- 为数组中放想要传递的参数。
bind(宿主对象,参数1,参数2...)- 不会自动调用

十三、storage 和 cookie 的区别
localstorage 和 sessionstoreage 为 ==5~20MB
获取 cookie 获取的是整个字符串,无法精确或缺到某一个值,需要引入第三方库。
