js 手写系列

991 阅读5分钟

1.new关键字

function createNew(fn){
    let obj = {}    //1.新建一个对象
    obj.__proto__ = fn.prototype //2.原型链接到构造函数的原型对象上
    args = Array.prototype.slice.call(arguments,1) //获取后面参数
    let result = fn.apply(obj,args) //3.改变this指向
    return result instanceof Object ? result : obj //4.如果是对象就返回不是就返回原始值
}
function getName(name,age){
    this.name = namethis.age = age
}
let a = createNew(getName,'pepsi','23')
console.log(a.name) //pepsi

2.Promise

      promise是有三个状态:pending,resolved,rejected,它接受一个自执行函数executor里面包含resolve和reject方法。resolve负责把状态从pending变为resolved并把值存起来,reject方法会把状态变为rejected,把失败的原因存起来,状态一旦发生改变就不会在变化。

class PromiseHand {
    constructor(executor) {
        let that = this
        this.value = undefined
        this.reason = undefined
        this.status = 'pending'
        //this.resolveList = []
        //this.rejectList = []
        let resolve = (val) => {
            if (this.status === 'pending') {
                this.value = val
                this.status = 'resolved'
                // this.resolveList.forEach(fn =>
                //     fn(this.value))
            }
        }
        let reject = (err) => {
            if (this.status === 'pending') {
                this.reason = err
                this.status = 'rejected'
                // this.rejectList.forEach(fn => fn())
            }
        }
        try {
            executor(resolve, reject)
        } catch (e) {
            reject(e)
        }
    }
    then(onFulfilled, onRejected) {
        let that = this,x
        if(that.status ==='resolved'){
            onFulfilled(this.value)
        }if (that.status === 'rejected') {
   }
}    

然后这个promise只能适用于简单的业务,如果我们是在异步方法中调用它的resolve方法,就必须先收集then上面的方法 当resolve方法调用时再让then方法执行

class PromiseHand {
    constructor(executor) {
        let that = this
        this.value = undefined
        this.reason = undefined
        this.status = 'pending'
        this.resolveList = []
        this.rejectList = []
        let resolve = (val) => {
            if (this.status === 'pending') {
                this.value = val
                this.status = 'resolved'
                this.resolveList.forEach(fn =>
                    fn(this.value))
            }
        }
        let reject = (err) => {
            if (this.status === 'pending') {
                this.reason = err
                this.status = 'rejected'
                this.rejectList.forEach(fn => fn())
            }
        }
        try {
            executor(resolve, reject)
        } catch (e) {
            reject(e)
        }
    }
    then(onFulfilled, onRejected) {
        let that = this,x
        if(that.status ==='resolved'){
            onFulfilled(this.value)
        }
        if (that.status === 'rejected') {
            onRejected(this.reason)
        }
        if(that.status === 'pending'){
            this.resolveList.push(onFulfilled)
            this.rejectList.push(onRejected)
        }
    }}

然并卵,问题并没有那么简单,如果resolve返回的还是一个promise 我们在做链式调用也要处理传进来的promise,定义并使用resolvePromise处理这种情况。

class PromiseHand { 
   constructor(executor) {
        let that = this
        this.value = undefined
        this.reason = undefined 
       this.status = 'pending'
        this.resolveList = []
        this.rejectList = []
        let resolve = (val) => {
            if (this.status === 'pending') {
                this.value = val
                this.status = 'resolved'
                this.resolveList.forEach(fn =>
                    fn(this.value))
            }
        }
        let reject = (err) => { 
           if (this.status === 'pending') {
                this.reason = err
                this.status = 'rejected'
                this.rejectList.forEach(fn => fn())
            } 
       }
        try {
            executor(resolve, reject)
        } catch (e) {
            reject(e)
        }
    }
    then(onFulfilled, onRejected) {
        let that = this,x
                let promise2 = new PromiseHand((resolve, reject) => {
            if (that.status === 'resolved') {
                x = onFulfilled(this.value)
                console.log(x)
                resolvePromise(promise2, x, resolve, reject);
            }
            if (that.status === 'rejected') {
                x = onRejected(this.reason)
                resolvePromise(promise2, x, resolve, reject);
            }
            if (that.status === 'pending') {
                that.resolveList.push(() => {
                    x = onFulfilled(this.value)
                    resolvePromise(promise2, x, resolve, reject);
                })
                that.rejectList.push(() => {
                    x = onRejected(this.reason)
                    resolvePromise(promise2, x, resolve, reject);
                })
            } 
       })
                return promise2
    }}
function resolvePromise(promise2, x, resolve, reject) { 
     // 不能返回自己
    if (promise2 === x) {
        return reject(new TypeError('mistake')) 
   } 
   let called; // 防止多次调用
    // x返回的可能是对象和函数也可能是一个普通的值
    if (x != null && (typeof x === 'object' || typeof x === 'function')) {
        try {
            let then = x.then
            if (typeof then === 'function') {
                then.call(x, y => {
                    if (called) return
                    called = true
                    // resolve的结果依旧是promise 那就继续解析
                    resolvePromise(promise2, y, resolve, reject);
                }, err => {
                    // 成功和失败只能调用一个
                    if (called) return;
                    called = true;
                    reject(err);// 失败了就失败了
                }) 
           }else{
                resolve(x)
            }
        } catch (error) {
            // 也属于失败 
           if (called) return;
            called = true; 
           // 取then出错了那就不要在继续执行了
            reject(error);
        }
    } else {
        resolve(x)
    }}

接下来实现以下all和race ,all方法可以说是Promise中很常用的方法了,它的作用就是将一个数组的Promise对象放在其中,当全部resolve的时候就会执行then方法,当有一个reject的时候就会执行catch,并且他们的结果也是按着数组中的顺序来排放的,那么我们来实现一下。

race就是看哪个先执行完就直接resolve结束。

//all方法
PromiseHand.all = function(promises){
    let arr = [];
    let i = 0;
    function processData(index,data){
      arr[index] = data; 
     i++; 
     if(i == promises.length){ 
       resolve(arr); 
     }; 
   };
    return new Promise((resolve,reject)=>{
      for(let i=0;i<promises.length;i++){ 
       promises[i].then(data=>{
          processData(i,data); 
       },reject);
      };
    });
}

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

3.call、apply和bind的实现

call,apply实现原理差不多只是传参不太一样,bind是需要返回一个函数,这个函数也有可能被new再次传参执行 并且new的优先级比较高,所以需要判断new的调用,还有一个特点就是bind调用的时候可以传参,调用之后生成的新的函数也可以传参,效果是一样的,所以这一块也要做处理 因为上面已经实现了apply,这里就借用一下,实际上不借用就是把代码copy过来

Function.prototype._call = function (obj,...args) {
    console.log(args)
    //给context新增一个独一无二的属性以免覆盖原有属性
    const key = Symbol()
    obj[key] = this
    let result = obj[key](...args)
    delete obj[key]
    return result
}
Function.prototype._apply = function (obj,args) {
    console.log(args)
    const key = Symbol()
    obj[key] = this
    let result = obj[key](...args)
    delete obj[key]
    return result
}
Function.prototype._bind = function (obj,...args) {
    const fn1 = this
    let fn2 =function(...innerArgs){
        if(this instanceof fn2){
            return new fn1(...args,...innerArgs)
        }
        return fn1.apply(obj,[...args,...innerArgs])
    }
    return fn2
}

4.柯里化的实现

function add(a,b,c){
    return a+b+c
}
function curry(fn,...args){
    let newargs=[]
    return function next(...innerArgs){
        newargs = [...innerArgs,...newargs]
        console.log(newargs)
        if(newargs.length<fn.length){
            return next
        }else{
            return fn(...newargs)
        }
    }
}
var newAdd = curry(add)
console.log(newAdd(1)(2)(3))    //6

5.深拷贝的实现

深拷贝,浅拷贝常常是前端容易混淆的地方。常见的浅拷贝如

  • 基本数据类型的赋值
  • 引用数据类型赋值,浅拷贝,栈内存指向的内存地址一样
  •  Object.assign(),只能第一层维度深拷贝故也输入浅拷贝
  • ...,展开运算符,和Object.assign()一样 需要再处理才能对深层次维度深拷贝

深拷贝我们常常会用到JSON.parse(JSON.stringify(obj)),这也是我们写业务代码经常用的

然后现在我们再代码写一版对引用类型的遍历赋值实现深拷贝

function deepClone(obj){
    if(typeof obj !== "object"){
        throw new Error('obj 不是一个对象!')
    }
   var newObj = Array.isArray(obj)?[]:{}
    for(let key in obj){
        if(obj.hasOwnProperty(key)){
            if(typeof obj[key] ==='object'){
                newObj[key] = deepClone(obj[key])
            }else{
                newObj[key] = obj[key]
            }
        }
    }
    return newObj
}

同样,也可以用Object.assign或者...去遍历处理深层次实现深拷贝。