admin管理员组

文章数量:1621479


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

6. 手写浅拷贝
6.1 JavaScript数据类型分类
    1. 简单数据类型: Number、 String、Boolean、null、undefined、Symbol
    2. 引用数据类型: Array、Object
6.2 不同数据类型的存储方式

   由上图可见,简单数据类型,将值存储在栈中与堆无关,引用数据类型将值存储在堆中,
   而在栈中存放的是指向堆的指针

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

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 参考代码执行结果截图

   由上面的代码执行的效果可以看出:修改基础类型的数据不会影响到原始数据但是修改引用数据类型的数据
   会同时改变原始数据
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 参考代码执行结果截图

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

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. pedding: 默认状态,只要没有告诉promise成功或失败,就会一直是pedding
  2. fulfilled:成功态,只要执行resolve(), 状态就会变为fulfilled,表示操作成功
  3. rejected:失败态,只要执行rejected(), 状态就会变为rejected,表示操作失败
8.6 监听promise状态变化
  状态变为 fulfilled 时 会执行 then() 方法
  状态变为 rejected 时 会执行 catch() 方法
8.7 then()方法
  then()接受两个参数
  1. 状态切换为成功时的回调函数
  2. 状态切换为失败时的回调函数
 promise.then(functon(){
     console.log('成功')
 },function(){
     console.log('失败')
 })
 3. then方法会返回一个新的promise对象
 4. 可以通过上一个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

代码执行接口截图

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

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

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

 5. 同一个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');
})
运行结果截图

8.8 catch 方法
   catch 其实是 then(undefined, () => {}) 的语法糖
       -   和then方法一样,可以传递参数给catch方法中的回调函数。
       -   和then方法一样,同一个promise方法可以多次调用catch方法。
       -   和then一样, catch方法每次执行完毕后会返回一个新的promise对象
   需要注意的是:
       和then方法第二个参数的区别在于, catch方法可以捕获上一个promise对象then方法中的异常
8.9 all方法
    1. all方法接收一个数组
    2. 如果数组中有多个promise对象,只有的都成功了,才会执行then()方法,并且会按照添加的顺序,
       将所有成功的结果都打包到一个数组中返回给我们
    3. 如果数组中不是promise对象,那么会直接执行.then()方法
/**
 * promise.all() 
 */

let imgArr=[
    {
        url:'https://img2.baidu/it/u=759632097,781040432&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=320',
        time:3
    },
    {
        url:'https://img1.baidu/it/u=3382370226,1921570240&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=320',
        time:5
    },
    {
        url:'https://img1.baidu/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);
})

代码执行结果截图

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

8.10 race 方法
   race 含义是比赛,和all不同的的是 多个promise 谁的状态先改变 无论是成功还是失败都会采用,
   但是返回的不再是数组
/**
 * promise.race() 
 */

let imgArr=[
    {
        url:'https://img2.baidu/it/u=759632097,781040432&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=320',
        time:3
    },
    {
        url:'https://img1.baidu/it/u=3382370226,1921570240&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=320',
        time:5
    },
    {
        url:'https://img1.baidu/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('都会执行');
})
代码执行结果截图

开胃菜结束 ! 接下来就要开始我们的重头菜 重写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. myPromise 有三个状态在初始化的是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. myPromise 的链式为了解决回调地狱
2. 为了实现链式,then 返回一个新的newPromise,将新newPromise返回的值 传递给下一个then中
3. then中 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. onFulfilled返回一个普通的值,成功时直接等于 value => value
2. onRejected返回一个普通的值, 直接扔出错误
3. onFulfilled或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已经完成 不信你向下看

代码执行结果

言归正传 接下来实现一下 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/it/u=759632097,781040432&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=320',
        time:3
    },
    {
        url:'https://img1.baidu/it/u=3382370226,1921570240&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=320',
        time:2
    },
    {
        url:'https://img1.baidu/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 代码执行结果

代码执行结果

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

最后

本文标签: 进阶必会功能JavaScript