​JavaScript进阶必会的手写功能(一)​


6. 手写浅拷贝

6.1 JavaScript数据类型分类
  1. 单数据类型: Number、 String、Boolean、null、undefined、Symbol
  1. 用数据类型: Array、Object
6.2 不同数据类型的存储方式


JavaScript进阶必会的手写功能(二)_浅拷贝

由上图可见,简单数据类型,将值存储在栈中与堆无关,引用数据类型将值存储在堆中,

而在栈中存放的是指向堆的指针

浅拷贝​ 创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。

6.3 手写浅拷贝参考代码
/**
* 手写浅拷贝
*/
let objData={
name:'gy',
sex:'男',
hobbies:['睡觉','吃饭','唱歌']
}
let obj= Object.assign({},objData)
console.log('Object.assign 浅拷贝 obj= ',obj )
// 修改拷贝过来的obj数据
obj.name='ckx'
obj.hobbies.push('跳舞')
console.log('原始数据 objData ', objData )
console.log('拷贝过来的 obj ', obj )

let myNewObj={
name:'gry',
sex:'男',
hobbies:['睡觉','吃饭']
}

const shallowCopy = (obj) => {
let newObj={}
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key]=obj[key]
}
}
return newObj
}

let myObj=shallowCopy (myNewObj)
console.log('手写 浅拷贝 myObj= ',myObj )

// 修改拷贝过来的obj数据
myObj.name='xmx'
myObj.hobbies.push('跳舞')
console.log('手写 原始数据 myNewObj ', myNewObj )
console.log('手写 拷贝过来的 myObj ', myObj )
6.4 参考代码执行结果截图

JavaScript进阶必会的手写功能(二)_浅拷贝_02

JavaScript进阶必会的手写功能(二)_初始化_03

由上面的代码执行的效果可以看出:修改基础类型的数据不会影响到原始数据但是修改引用数据类型的数据

会同时改变原始数据

7. 手写深拷贝

深拷贝​ 与浅拷贝的区别就是,拷贝过来的数据修改不会影响到原始数据

7.1手写深拷贝参考代码
/**
* 手写深拷贝
*/
let objData={
name:'gy',
sex:'男',
hobbies:['睡觉','吃饭','唱歌']
}
let obj= JSON.parse(JSON.stringify(objData))
console.log('JSON.stringify 深拷贝 obj= ',obj )
// 修改拷贝过来的obj数据
obj.hobbies.push('跳舞')
console.log('原始数据 objData ', objData )
console.log('拷贝过来的 obj ', obj )

let myNewObj={
name:'gry',
sex:'男',
hobbies:['睡觉','吃饭']
}
const deepCopy = (obj,hash=new WeakMap()) => {
if(obj==null) return
if(obj instanceof Date ) return new Date(obj)
if(obj instanceof RegExp ) return new RegExp(obj)
// 如果不是对象 是基本数据类型 直接返回
if(typeof obj !='object') return obj
if(hash.get(obj)) return hash.get(obj)
// 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
let cloneObj= new obj.constructor()
hash.set(obj,cloneObj)
for(let key in obj){
if(obj.hasOwnProperty(key)){
cloneObj[key]=deepCopy(obj[key],hash)
}
}
return cloneObj
}

let myObj= deepCopy(myNewObj)
// 修改拷贝过来的obj数据
myObj.hobbies.push('跳舞')
console.log('手写 原始数据 myNewObj ', myNewObj )
console.log('手写 拷贝过来的 myObj ', myObj )
7.2 参考代码执行结果截图

JavaScript进阶必会的手写功能(二)_浅拷贝_04

JavaScript进阶必会的手写功能(二)_浅拷贝_05

​JSON.stringify()​​​ 拷贝的对象中包括时间对象(Date)、Error对象、正则表达式(RegExp),函数,或者undefined等值会出现错误在手写的时候可以特殊处理 这里只是处理了​​Date​​​、​​RegExp​


8. promise使用(手写之前你总得知道它是怎么用的把)

8.1 什么是promise

promise是es6新增的异步编程解决方案

8.2 es6 为什么会新增 promise

为了解决回调地狱的问题

8.3 如何创建promise

let promise = new Promise(function(resolve, reject){});

值得注意的是:

promise 不是异步的,一旦被创建就会立即执行存放里面的代码

8.4 promise 如何用同步代码来表示异步的操作?

promise是通过监听不同状态的变化,去执行与之对应的函数

8.5 promise 的三种状态
  1. edding: 默认状态,只要没有告诉promise成功或失败,就会一直是pedding
  1. ulfilled:成功态,只要执行resolve(), 状态就会变为fulfilled,表示操作成功
  2. ejected:失败态,只要执行rejected(), 状态就会变为rejected,表示操作失败
8.6 监听promise状态变化

状态变为 fulfilled 时 会执行 then() 方法

状态变为 rejected 时 会执行 catch() 方法

8.7 then()方法

then()接受两个参数

  1. 态切换为成功时的回调函数
  2. 态切换为失败时的回调函数
promise.then(functon(){
console.log('成功')
},function(){
console.log('失败')
})
  1. hen方法会返回一个新的promise对象
  2. 以通过上一个promise对象的then给下一个promise对象的then传递参数

值得注意是:

不管上一个promise是成功或者失败回调都会将参数传递给下一个promise的成功回调中

let promise = new Promise(function (resolve, reject) {
console.log('我promise内部同步代码');
resolve("111"); // 将状态修改为成功
// reject("aaa"); // 将状态修改为失败
});
console.log('我是外部同步代码');
let p2 = promise.then(function (data) {
console.log("成功1", data);
return "222";
}, function (data) {
console.log("失败1", data);
return "bbb";
});

p2.then(function (data) {
// 这里会获取上一个promise返回的参数,如论上一个promise 是成功还是失败
console.log("成功2", data);
}, function (data) {
// 这一步是不会被执行
console.log("失败2", data);
});

console.log(p2); // 返回一个新的 promise
代码执行接口截图

JavaScript进阶必会的手写功能(二)_初始化_06

由代码运行结果截图可知:

  • promise不是异步的
  • 不管上一个promise是成功或者失败回调都会将参数传递给下一个promise的成功回调中
  • then 方法会返回一个新的promise
  • 返回的promise 不是同步代码 一个介于 同步代码和异步代码之间的状态存在

​如果想彻底搞懂事件循环可以查看这篇文章​​​​一文打通事件循环的任督二脉​


  1. 一个promise对象可以多次调用then方法,当promise的状态变为成功态时,所有的then方法都会被调用
let promise =new Promise((resolve,reject)=>{
resolve('成功') // 改为成功态
// reject('失败') // 改为失败态
})

promise.then(()=>{
console.log('成功-->then1');
},()=>{
console.log('失败-->catch1');
})

promise.then(()=>{
console.log('成功-->then2');
},()=>{
console.log('失败-->catch2');
})
运行结果截图

JavaScript进阶必会的手写功能(二)_初始化_07

JavaScript进阶必会的手写功能(二)_浅拷贝_08


8.8 catch 方法

catch 其实是 then(undefined, () => {}) 的语法糖

  • 和then方法一样,可以传递参数给catch方法中的回调函数。
  • 和then方法一样,同一个promise方法可以多次调用catch方法。
  • 和then一样, catch方法每次执行完毕后会返回一个新的promise对象

需要注意的是:

和then方法第二个参数的区别在于, catch方法可以捕获上一个promise对象then方法中的异常

8.9 all方法
  1. ll方法接收一个数组
  1. 果数组中有多个promise对象,只有的都成功了,才会执行then()方法,并且会按照添加的顺序,

将所有成功的结果都打包到一个数组中返回给我们

  1. 果数组中不是promise对象,那么会直接执行.then()方法
/**
* promise.all()
*/

let imgArr=[
{
url:'https://img2.baidu.com/it/u=759632097,781040432&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=320',
time:3
},
{
url:'https://img1.baidu.com/it/u=3382370226,1921570240&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=320',
time:5
},
{
url:'https://img1.baidu.com/it/u=342297274,173990673&fm=253&fmt=auto&app=138&f=JPEG?w=700&h=444',
time:7
}
]

const loadImg = (data) =>{
const { url,time } =data
return new Promise((resolve,reject)=>{
let oImg=new Image()
let delayTime=time*1000
setTimeout(()=>{
oImg.src=url
},delayTime)
oImg.onload = ()=>{
console.log( Date(),time);
resolve(oImg)
}
oImg.onerror = ()=>{
reject('图片加载失败')
}
})
}

Promise.all([loadImg(imgArr[0]),loadImg(imgArr[1]),loadImg(imgArr[2])]).then((result)=>{
result.forEach(item=> document.body.appendChild(item) )
}).catch((error)=>{
console.log('error=',error);
})
代码执行结果截图

JavaScript进阶必会的手写功能(二)_初始化_09

执行以上代码即可知道 在打印完7之后 才会挂载dom

8.10 race 方法

race 含义是比赛,和all不同的的是 多个promise 谁的状态先改变 无论是成功还是失败都会采用,

但是返回的不再是数组

/**
* promise.race()
*/

let imgArr=[
{
url:'https://img2.baidu.com/it/u=759632097,781040432&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=320',
time:3
},
{
url:'https://img1.baidu.com/it/u=3382370226,1921570240&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=320',
time:5
},
{
url:'https://img1.baidu.com/it/u=342297274,173990673&fm=253&fmt=auto&app=138&f=JPEG?w=700&h=444',
time:7
}
]

const loadImg = (data) =>{
const { url,time } =data
return new Promise((resolve,reject)=>{
let oImg=new Image()
let delayTime=time*1000
setTimeout(()=>{
oImg.src=url
},delayTime)
oImg.onload = ()=>{
console.log( Date(),time);
resolve(oImg)
}
oImg.onerror = ()=>{
reject('图片加载失败')
}
})
}

Promise.race([loadImg(imgArr[0]),loadImg(imgArr[1]),loadImg(imgArr[2])]).then((result)=>{
document.body.appendChild(result)
}).catch((error)=>{
console.log('error=',error);
})

代码运行预测: 就是那个图片的时间短页面就显示那个图片

8.11 finally 方法

不管promise返回什么状态都会执行

/**
* promise.finally()
*/

let promise =new Promise((resolve,reject)=>{
resolve('111') // 将状态改为 成功态
// reject('222') // 将状态改为 失败态
})

promise.then((data)=>{
console.log('成功', data);
}).catch((data)=>{
console.log('失败', data);
}).finally(()=>{
console.log('都会执行');
})
代码执行结果截图

JavaScript进阶必会的手写功能(二)_数组_10

JavaScript进阶必会的手写功能(二)_浅拷贝_11

开胃菜结束 ! 接下来就要开始我们的重头菜 重写promise

9. 手写Promise

9.1 myPromise的申明
/**
* myPromise的申明
*/
class myPromise {
constructor (executor){
// 成功回调
let resolve = () => { console.log(11); }
// 失败回调
let reject = () =>{ }
// 立即执行
executor(resolve,reject)
}
}

promise 在被创建的时候就会立即执行存放里面的代码

所以需要在定义myPromise类的时候需要定义一个立即执行函数

并且立即执行函数executor里面有两个参数,一个叫resolve(成功),一个叫reject(失败)


9.2 Promise的的状态设置
/**
* myPromise 的状态设置
*/
class myPromise {
constructor(executor){
// promise在初始化的时候状态是 pedding
this.state='pending'
// 初始化的状态可能变为成功或者失败
let successValue = ''
let failReason = ''
let resolve = value =>{
if(this.state == 'pedding'){
this.state = 'fulfilled'
this.successValue = value
}
}
let reject = reason =>{
if(this.state == 'pedding') {
this.state = 'rejected'
this.failReason = reason
}
}
// 如果executor执行报错,直接执行reject
try {
executor(resolve,reject)
} catch (error) {
reject(error)
}
}
}
  1. yPromise 有三个状态在初始化的是pedding(等待)、可能转为 fulfilled(成功)、或 rejected(失败)
  2. 功时,不可转为其他状态,且必须有一个不可变的值value 并且存储这个值
  3. 败时,不可转为其他状态,且必须有一个失败原因reason 并且存储这个原因
  4. 是执行executor函数报错 直接执行reject()
9.3 myPromise的​​then​​方法
class myPromise  {
constructor(executor){
...
}
then(onFulfilled,onRejected){
if(this.state == 'fulfilled'){
onFulfilled(this.successValue)
}
if(this.state=='rejected'){
onRejected(this.failReason)
}
}
}

myPromise的then方法,里面有两个参数onFulfilled,onRejected 成功有成功的值,失败有失败的原因

此时需要还要注意两个问题

​1. 如果resolve在setTimeout内(或者其他异步中)执行在使用then的时候state还是pedding状态​

​2. 如何实现同一个promise对象可以多次调用then方法 ​


class myPromise  {
constructor(executor){
// promise在初始化的时候状态是 pedding
this.state='pending'
// 初始化的状态可能变为成功或者失败
let successValue = ''
let failReason = ''
// 成功回调数组
this.onResolvedCallbacks=[]
// 失败回调数组
this.onRejectedCallbacks=[]
let resolve = value =>{
if(this.state == 'pedding'){
this.state = 'fulfilled'
this.successValue = value
// 一旦resolve执行,调用成功数组的函数
this.onResolvedCallbacks.forEach(fn=>fn());
}
}
let reject = reason =>{
if(this.state == 'pedding') {
this.state = 'rejected'
this.failReason = reason
// 一旦reject执行,调用失败数组的函数
this.onRejectedCallbacks.forEach(fn=>fn());
}
}
// 如果executor执行报错,直接执行reject
try {
executor(resolve,reject)
} catch (error) {
reject(error)
}
}
then(onFulfilled,onRejected){
if(this.state == 'fulfilled'){
onFulfilled(this.successValue)
}
if(this.state=='rejected'){
onRejected(this.failReason)
}
if(this.state == 'pedding'){
// 将onFulfilled 存入 成功回调待执行数组
this.onResolvedCallbacks.push(()=>{
onFulfilled(this.successValue)
})
// 将onRejected 存入失败回调待执行数组
this.onRejectedCallbacks.push(()=>{
onRejected(this.failReason)
})
}
}
}

上面两个问题解决思路是 类似发布订阅者模式

将then里面的两个函数储存起来(依赖收集),多个then,存在同一个数组内,一旦reject或者resolve

就遍历执行影响函数数组(onRejectedCallbacks,onResolvedCallbacks)(通知更新)

9.4 myPromise的 链式调用

​例如 new Promise().then().then()​


class myPromise  {
constructor(executor){
...
}
then(onFulfilled,onRejected){
// 返回一个新的promise
let newPromise = new myPromise ((resolve,reject)=>{
if(this.state == 'fulfilled'){
let result = onFulfilled(this.successValue)
/**
* resolvePromise函数,处理自己return的promise
* 和默认的newPromise的关系
*/
resolvePromise(newPromise,result,resolve,reject)
}
if(this.state=='rejected'){
let result = onRejected(this.failReason)
resolvePromise(newPromise,result,resolve,reject)
}
if(this.state == 'pedding'){
// 将onFulfilled 存入 成功回调待执行数组
this.onResolvedCallbacks.push(()=>{
let result = onFulfilled(this.successValue)
resolvePromise(newPromise,result,resolve,reject)
})
// 将onRejected 存入失败回调待执行数组
this.onRejectedCallbacks.push(()=>{
let result = onRejected(this.failReason)
resolvePromise(newPromise,result,resolve,reject)
})
}
})
// 返回一个新的promise ,完成链式
return newPromise
}
}
  1. yPromise 的链式为了解决回调地狱
  2. 了实现链式,then 返回一个新的newPromise,将新newPromise返回的值 传递给下一个then中
  3. hen中 return 的参数(参数未知,需判断)这个return的新的newpromise就是onFulfilled()或者onRejected()的值
  4. 以现在需要一起完成,对then中返回的参数 result 进行判断的函数 resolvePromise
9.5 resolvePromise() 方法
  1. 先需要判断 then返回的参数 result 是不是 promise
  2. 果是promise,则取它的结果,作为新的newPromise成功的结果
  3. 果是普通值,直接作为newPromise成功的结果
const resolvePromise = (newPromise, result, resolve, reject)=>{
// 循环引用报错
if(result === newPromise){
// reject报错
return reject(new TypeError('Chaining cycle detected for promise'));
}
// 防止多次调用
let called;
// result不是null 且result是对象或者函数
if (result != null && (typeof result === 'object' || typeof result === 'function')) {
try {
// A+规定,声明then = x的then方法
let then = result.then;
// 如果then是函数,就默认是promise了
if (typeof then === 'function') {
// 就让then执行
then.apply(result, value => {
// 成功和失败只能调用一个
if (called) return;
called = true;
// resolve的结果依旧是promise 那就继续解析
resolvePromise(newPromise, value, resolve, reject);
}, error => {
// 成功和失败只能调用一个
if (called) return;
called = true;
reject(error);// 失败了就结束
})
} else {
resolve(result); // 直接成功即可
}
}catch (error) {
// 也属于失败
if (called) return;
called = true;
// 取then出错了那就不要在继续执行了
reject(error);
}
}else {
resolve(result);
}
}
9.6 优化其他问题
  1. nFulfilled返回一个普通的值,成功时直接等于 value => value
  2. nRejected返回一个普通的值, 直接扔出错误
  3. nFulfilled或onRejected不能同步被调用,必须异步调用 使用setTimeout解决异步问题
  4. 果onFulfilled或onRejected报错,则直接返回reject()
class myPromise {
constructor(executor){
// promise在初始化的时候状态是 pedding
this.state='pedding'
// 初始化的状态可能变为成功或者失败
this.successValue = ''
this.failReason = ''
// 成功回调数组
this.onResolvedCallbacks=[]
// 失败回调数组
this.onRejectedCallbacks=[]
let resolve = value =>{
if(this.state == 'pedding'){
this.state = 'fulfilled'
this.successValue = value
// 一旦resolve执行,调用成功数组的函数
this.onResolvedCallbacks.forEach(fn=>fn());
}
}
let reject = reason =>{
if(this.state == 'pedding') {
this.state = 'rejected'
this.failReason = reason
// 一旦reject执行,调用失败数组的函数
this.onRejectedCallbacks.forEach(fn=>fn());
}
}
// 如果executor执行报错,直接执行reject
try {
executor(resolve,reject)
} catch (error) {
reject(error)
}
}
then(onFulfilled,onRejected){
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
// 返回一个新的promise
let newPromise = new myPromise ((resolve,reject)=>{
if(this.state == 'fulfilled'){
setTimeout(()=>{
try {
let result = onFulfilled(this.successValue)
/**
* resolvePromise函数,处理自己return的promise
* 和默认的newPromise的关系
*/
resolvePromise(newPromise,result,resolve,reject)
} catch (error) {
reject(error);
}
},0)
}
if(this.state=='rejected'){
setTimeout(()=>{
try {
let result = onRejected(this.failReason)
resolvePromise(newPromise,result,resolve,reject)
} catch (error) {
reject(error);
}
},0)
}
if(this.state == 'pedding'){
// 将onFulfilled 存入 成功回调待执行数组
this.onResolvedCallbacks.push(()=>{
setTimeout(()=>{
try {
let result = onFulfilled(this.successValue)
resolvePromise(newPromise,result,resolve,reject)
} catch (error) {
reject(error);
}
},0)
})
// 将onRejected 存入失败回调待执行数组
this.onRejectedCallbacks.push(()=>{
setTimeout(()=>{
try {
let result = onRejected(this.failReason)
resolvePromise(newPromise,result,resolve,reject)
} catch (error) {
reject(error);
}
},0)
})
}
})
// 返回一个新的promise ,完成链式
return newPromise
}
}

恭喜恭喜!至此基本的promise已经完成 不信你向下看

JavaScript进阶必会的手写功能(二)_数组_12


代码执行结果

JavaScript进阶必会的手写功能(二)_浅拷贝_13

JavaScript进阶必会的手写功能(二)_数组_14

言归正传 接下来实现一下 catch和resolve、reject、race、all方法

// catch 就是then(null,fn)的语法糖
catch(fn){
return this.then(null,fn);
}

//resolve方法
myPromise.resolve = (val) =>{
return new myPromise((resolve,reject)=>{
resolve(val)
});
}

//reject方法
myPromise.reject = (val) =>{
return new myPromise((resolve,reject)=>{
reject(val)
});
}

// all 方法
myPromise.all=(promises)=>{
let newPromimse = new myPromise((resolve, reject) => {
// 声明变量
let count = 0; // 没成功一个就加一
let arr = [];
// 遍历
for (let i = 0; i < promises.length; i++) {
promises[i].then((value) => {
// 每一个promise对象 都成功才可以去执行 resolve 函数
count++;
// 将当前promise对象成功的结果 存到数组中
arr[i] = value;
if (count === promises.length) {
// 修改状态
resolve(arr);
}
},
(error) => {
reject(error);
});
}
})
return newPromimse
}

//race方法
myPromise.race = (promises) =>{
return new myPromise((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
promises[i].then(resolve,reject)
};
})
}


let imgArr=[
{
url:'https://img2.baidu.com/it/u=759632097,781040432&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=320',
time:3
},
{
url:'https://img1.baidu.com/it/u=3382370226,1921570240&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=320',
time:2
},
{
url:'https://img1.baidu.com/it/u=342297274,173990673&fm=253&fmt=auto&app=138&f=JPEG?w=700&h=444',
time:5
}
]
const loadImg = (data) =>{
const { url,time } =data
return new myPromise((resolve,reject)=>{
let oImg=new Image()
let delayTime=time*1000
setTimeout(()=>{
oImg.src=url
},delayTime)
oImg.onload = ()=>{
resolve(oImg)
console.log( Date(),time);
}
oImg.onerror = ()=>{
reject('图片加载失败')
}
})
}

myPromise.all([loadImg(imgArr[0]),loadImg(imgArr[1]),loadImg(imgArr[2])]).then((result)=>{
result.forEach(item=> document.body.appendChild(item) )
})

myPromise.race([loadImg(imgArr[0]),loadImg(imgArr[1]),loadImg(imgArr[2])]).then((result)=>{
console.log('result=', result);
document.body.appendChild(result)
})
myPromise.all 代码执行结果

JavaScript进阶必会的手写功能(二)_初始化_15


代码执行结果

JavaScript进阶必会的手写功能(二)_数组_16

JavaScript进阶必会的手写功能(二)_浅拷贝_17

​至此所看即所得 以上代码皆可执行 我保证 哈哈哈 !!! promise 懂了把 来吧自己搞起来​

最后