JS手写系列

1、Promise系列

Promise手写

//极简的实现+链式调用+延迟机制+状态
class Promise {
    callbacks = [];
    state = 'pending';//增加状态
    value = null;//保存结果
    constructor(fn) {
        fn(this._resolve.bind(this));
    }
    then(onFulfilled) {
        if (this.state === 'pending') {//在resolve之前,跟之前逻辑一样,添加到callbacks中
            this.callbacks.push(onFulfilled);
        } else {//在resolve之后,直接执行回调,返回结果了
            onFulfilled(this.value);
        }
        return this;
    }
    _resolve(value) {
        this.state = 'fulfilled';//改变状态
        this.value = value;//保存结果
        this.callbacks.forEach(fn => fn(value));
    }
}

Promise.resolve:

  1. Promise.resolve最终结果还是一个Promise,并且与Promise.resolve(该值)传入的值息息相关

  2. 传入的参数可以是一个Promise实例,那么该函数执行的结果是直接将实例返回

    Promise.myResolve  = function(val){
      if(val && (typeof(val) === 'object') && (val instanceof Promise)){
        return val
      }
      return new Promise(rs=>{
        rs(val)
      })
    }

Promise.reject:

Promise.myReject = function(val){
  return new Promise((rs,rj)=>{
    rj(val)
  })
}

Promise.all:

Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

只要是一个失败了,结果即进入失败状态

Promise.myAll = (promises) => {
 return new Promise((rs,rj) => {
   const result = []
   let count = 0
  if(!promises.length) return rs([])
  promises.forEach((p,i) => {
    Promise.resolve(p).then(res=>{
       result[i] = p
      count += 1
      if(count === promises.length){
        rs(result)
      }
    }
    ).catch(rj)
  });
 })
}

Promise.allSettled

  • 不管是全部成功还是有部分失败,最终都会进入Promise.allSettled.then回调中

  • 最后的返回值中,成功和失败的项都有status属性,成功时值是fulfilled,失败时是rejected

  • 最后的返回值中,成功含有value属性,而失败则是reason属性

Promise.myAllSettled = (promises) => {
  return new Promise((rs, rj) => {
    let count = 0
    let result = []
    const len = promises.length
    // 数组是空的话,直接返回空数据
    if (len === 0) {
      return rs([])
    }

    promises.forEach((p, i) => {
      Promise.resolve(p).then((res) => {
        count += 1
        // 成功属性设置 
        result[ i ] = {
          status: 'fulfilled',
          value: res
        }
        
        if (count === len) {
          rs(result)
        }
      }).catch((err) => {
        count += 1
        // 失败属性设置 
        result[i] = { 
          status: 'rejected', 
          reason: err 
        }

        if (count === len) {
          rs(result)
        }
      })
    })
  })
}

Promise.race

Promise.myRace = (promises) => {
  return new Promise((rs, rj) => {
    promises.forEach((p) => {
      // 对p进行一次包装,防止非Promise对象
      // 并且对齐进行监听,将我们自己返回的Promise的resolve,reject传递给p,哪个先改变状态,我们返回的Promise也将会是什么状态
      Promise.resolve(p).then(rs).catch(rj)
    })
  })
}

 2、发布订阅模式-----EventEmitter

class EventEmitter {
    constructor(){
        this.cache = {}
    }
    on(name,fn){
        if(this.cache[name]){
            this.cache[name].push(fn)
        }else{
            this.cache[name] = [fn]
        }
    }
    emit(name,isOnce){
        if (this.cache[name]){
            const tasks = this.cache[name].slice()
                for (let fn of tasks){
                    fn()
                }
                if(isOnce){
                    delete this.cache[name]
                }
        }
    }
    off(name,fn){
        const tasks = this.cache[name]
        if (tasks){
            const index = tasks.findIndex(f=>f === fn || f.callback === fn)
            if(index >= 0){
                tasks.splice(index,1)
            }
        }
    }
}
// 测试
const eventBus = new EventEmitter()
const task1 = () => { console.log('task1'); }
const task2 = () => { console.log('task2'); }

eventBus.on('task', task1)
eventBus.on('task', task2)
eventBus.off('task', task1)
setTimeout(() => {
  eventBus.emit('task') // task2
}, 1000)

3、手写深拷贝

  1.  基础版
    deepClone(obj) {
          let cloneObj = {};
          for (let key in obj) {
            if (typeof obj[key] === 'object') {
              cloneObj[key] = deepClone(obj[key]);
            } else {
              cloneObj[key] = obj[key];
            }
          }
          return cloneObj;
        },

     缺点:

    1. 这个深拷贝函数并不能复制不可枚举的属性以及 Symbol 类型;
    2. 这种方法只是针对普通的引用类型的值做递归复制,而对于 Array、Date、RegExp、Error、Function 这样的引用类型并不能正确地拷贝;
    3. 对象的属性里面成环,即循环引用没有解决
    2、增强版
    const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)
    const deepClone = function (obj, hash = new WeakMap()) {
      if (obj.constructor === Date) {
        return new Date(obj)       // 日期对象直接返回一个新的日期对象
      }
      if (obj.constructor === RegExp){
        return new RegExp(obj)     //正则对象直接返回一个新的正则对象
      }
    // 普通值或函数不需要深拷贝 
    if (typeof obj !== "object") return obj;
      //如果循环引用了就用 weakMap 来解决
      if (hash.has(obj)) {
        return hash.get(obj)
      }
      let allDesc = Object.getOwnPropertyDescriptors(obj)
      //遍历传入参数所有键的特性
      let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)
      // 把cloneObj原型复制到obj上
      hash.set(obj, cloneObj)
      for (let key of Reflect.ownKeys(obj)) { 
        //普通值或函数不需要深拷贝
        cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ? deepClone(obj[key], hash) : obj[key]
      }
      return cloneObj
    }
    
    // 下面是验证代码
    let obj = {
      num: 0,
      str: '',
      boolean: true,
      unf: undefined,
      nul: null,
      obj: { name: '我是一个对象', id: 1 },
      arr: [0, 1, 2],
      func: function () { console.log('我是一个函数') },
      date: new Date(0),
      reg: new RegExp('/我是一个正则/ig'),
      [Symbol('1')]: 1,
    };
    Object.defineProperty(obj, 'innumerable', {
      enumerable: false, value: '不可枚举属性' }
    );
    obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj))
    obj.loop = obj    // 设置loop成循环引用的属性
    let cloneObj = deepClone(obj)
    cloneObj.arr.push(4)
    console.log('obj', obj)
    console.log('cloneObj', cloneObj)
  1. 针对能够遍历对象的不可枚举属性以及 Symbol 类型,可以使用 Reflect.ownKeys 方法;
  2. 当参数为 Date、RegExp 类型,则直接生成一个新的实例返回;
  3. 利用 Object 的 getOwnPropertyDescriptors 方法可以获得对象的所有属性,以及对应的特性,顺便结合 Object.create 方法创建一个新对象,并继承传入原对象的原型链;
  4. 利用 WeakMap 类型作为 Hash 表,因为 WeakMap 是弱引用类型,可以有效防止内存泄漏(你可以关注一下 Map 和 weakMap 的关键区别,这里要用 weakMap),作为检测循环引用很有帮助,如果存在循环,则引用直接返回 WeakMap 存储的值

4、Object.assign

用于将所有可枚举(Object.propertyIsEnumerable() 返回 true)和自有(Object.hasOwnProperty() 返回 true)属性的值从一个或多个源对象复制到目标对象。它将返回修改后的目标对象(请注意这个操作是浅拷贝)。

Object.assign = function(target, ...source) {
    if(target == null) {
        throw new TypeError('Cannot convert undefined or null to object');
    }
    let res = Object(target);
    source.forEach(function(obj) {
        if(obj != null) {
            // for...in 只会遍历对象自身的和继承的可枚举的属性(不含 Symbol 属性)
            // hasOwnProperty 方法只考虑对象自身的属性
            for(let key in obj) {
                if(obj.hasOwnProperty(key)) {
                    res[key] = obj[key];
                }
            }
        }
    });
    return res;
}

 5、New操作符

  • 首先创建一个对象,对象原型指向构造函数原型;
  • 其次调用构造函数,并将this绑定到该对象;
  • 最后构造函数执行返回值,如果是非引用类型,返回创建的对象,否则直接返回构造函数的返回值;

function myNew(Fn) {
// ES6 中 new.target 指向构造函数
myNew.target = Fn
// const obj = {}
// obj.__proto__=Fn.prototype
// 创建一个对象,对象原型指向构造函数原型
const obj = Object.create(Fn.prototype)
// 调用构造函数,并将this绑定到该对象
const result = Fn.apply(obj, [...arguments])
// 构造函数执行返回值,如果是非引用类型,返回创建的对象,否则直接返回构造函数的返回值
return result instanceof Object ? result : obj

eg:new操作符返回一个对象时:这个返回值会被正常使用

function Test(name) {
  this.name = name
  console.log(this) // Test { name: 'xxx' }
  return { age: 26 }
}
const t = new Test('xxx')
console.log(t) // { age: 26 }
console.log(t.name) // 'undefined'

返回一个原始值时:构造函数中返回一个原始值,然而这个返回值并没有作用

function Test(name) {
  this.name = name
  return 1
}
const t = new Test('xxx')
console.log(t.name) // 'xxx'

 new关键字主要做了以下工作:

  • 创建一个新的对象obj

  • 将对象与构建函数通过原型链连接起来

  • 将构建函数中的this绑定到新建的对象obj

  • 根据构建函数返回类型作判断,如果是原始值则被忽略,如果是返回对象,需要正常处理

6、手写reduce

Array.prototype.myReduce = function (cb, initValue) {
  const array = this; // 获取数组
  let pre = initValue || array[0];
  const startIndex = initValue ? 1 : 0;
  for (let i = startIndex; i < array.length; i++) {
    const cur = array[i];
    pre = cb(pre, cur, i, array);
  }
  return pre;
};

 7、手写call、apply和bind

Function.prototype.myCall = function (context, ...args) {
  if (!context || context === null) {
    context = window;
  }
  // 创造唯一的key值  作为我们构造的context内部方法名
  let fn = Symbol();
  context[fn] = this; //this指向调用call的函数
  // 执行函数并返回结果 相当于把自身作为传入的context的方法进行调用了
  return context[fn](...args);
};
apply原理一致  只是第二个参数是传入的数组
Function.prototype.myApply = function (context, args) {
  if (!context || context === null) {
    context = window;
  }
  // 创造唯一的key值  作为我们构造的context内部方法名
  let fn = Symbol();
  context[fn] = this;
  // 执行函数并返回结果
  return context[fn](...args);
};

bind实现要复杂一点  因为他考虑的情况比较多 还要涉及到参数合并(类似函数柯里化)

Function.prototype.myBind = function (context, ...args) {
  if (!context || context === null) {
    context = window;
  }
  // 创造唯一的key值  作为我们构造的context内部方法名
  let fn = Symbol();
  context[fn] = this;
  let _this = this;
  //  bind情况要复杂一点
  const result = function (...innerArgs) {
    // 第一种情况 :若是将 bind 绑定之后的函数当作构造函数,通过 new 操作符使用,则不绑定传入的 this,而是将 this 指向实例化出来的对象
    // 此时由于new操作符作用  this指向result实例对象  而result又继承自传入的_this 根据原型链知识可得出以下结论
    // this.__proto__ === result.prototype   //this instanceof result =>true
    // this.__proto__.__proto__ === result.prototype.__proto__ === _this.prototype; //this instanceof _this =>true
    if (this instanceof _this === true) {
      // 此时this指向指向result的实例  这时候不需要改变this指向
      this[fn] = _this;
      this[fn](...[...args, ...innerArgs]); //这里使用es6的方法让bind支持参数合并
    } else {
      // 如果只是作为普通函数调用  那就很简单了 直接改变this指向为传入的context
      context[fn](...[...args, ...innerArgs]);
    }
  };
  // 如果绑定的是构造函数 那么需要继承构造函数原型属性和方法
  // 实现继承的方式: 使用Object.create
  result.prototype = Object.create(this.prototype);
  return result;
};

8、instanceof

function myInstanceof(left, right) {
  while (true) {
    if (left === null) {
      return false;
    }
    if (left.__proto__ === right.prototype) {
      return true;
    }
    left = left.__proto__;
  }
}

9、ajax

const getJSON = function (url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open("GET", url, false);
    xhr.setRequestHeader("Content-Type", "application/json");
    xhr.onreadystatechange = function () {
      if (xhr.readyState !== 4) return;
      if (xhr.status === 200 || xhr.status === 304) {
        resolve(xhr.responseText);
      } else {
        reject(new Error(xhr.responseText));
      }
    };
    xhr.send();
  });
};

 10、sleep

function sleep(delay) {
    var start = (new Date()).getTime();
    while ((new Date()).getTime() - start < delay) {
        continue;
    }
}
function test() {
    console.log('111');
    sleep(2000);
    console.log('222');
}
test()

 

posted @ 2022-10-21 15:56  聂丽芳  阅读(44)  评论(0编辑  收藏  举报