手写汇总

本文最后更新于:7 个月前

一些手写的内容汇总

1、通过XMLHttpRequest实现网络请求

const xhr = new XMLHttpRequest();
xhr.open('GET',url,true)//异步,false同步
xhr.onreadystatechange = function(){
    if(xhr.readyState==4&&xhr.state==200)
    console.log('成功')
  else
    console.log('失败')
}
xhr.onerror = function(e){
   console.log('连接失败',e)
}
xhr.send();

2、手写call(),apply(),bind()

之前探讨了这三种函数的区别,正好理解的还算可以,所以试一试写出三种函数的手写代码。

call()

Function.prototype.call = function(thisArg){
  if (typeof this !== 'function') {
      throw new TypeError('不能被call');
    }
  //如果传入的是null或者undefined 或者空则绑定为全局
    let thisArg = thisArg || window;
  //在传入的对象上创建方法并赋给它
  thisArg.fn = this;
  
  let args = [];
  //把参数拿出来,从第二个拿,第一个是指定的this
  for(let i = 1;i < arguments.length; i++){
    args.push(`arguments[${i}]`)
  }
  //把参数传入对象的方法执行,得到结果
  let result = eval(`context.fn(${args})`);
  //删除方法
  delete thisArg.fn;
  //返回结果
  return result;
}

apply()

Function.prototype.apply = function(thisArg,arr){
  if (typeof this !== 'function') {
      throw new TypeError('不能被apply');
    }
  //如果传入的是null或者undefined 或者空则绑定为全局
    let thisArg = Object(thisArg) || window;
  //在传入的对象上创建方法并赋给它
  thisArg.fn = this;
  let result;
  //如果arr没有,就直接执行
  if(!arr){
    result = thisArg.fn();
  }else{
    //如果有参数就一一取出来给方法执行
    let args = [];
    //把参数拿出来,从第二个拿,第一个是指定的this
    for(let i = 1;i < arr.length; i++){
      args.push(`arguments[${i}]`)
    }
    //把参数传入对象的方法执行,得到结果
    let result = eval(`context.fn(${args})`); 
  }
  //删除方法
  delete thisArg.fn;
  //返回结果
  return result;
}

bind()

以上两种都好写,但是bind()因为要返回一个新的函数,然后还可以被new出来,new之后this指向新创建的new实例。

Function.prototype.bind = function(otherThis) {
   if (typeof this !== 'function') {
     throw new TypeError('不能被bind');
   }
  //把第一个参数后面的参数拿出来
   var baseArgs= Array.prototype.slice.call(arguments, 1),
       baseArgsLength = baseArgs.length,
       fToBind = this,
       fNOP    = function() {},
       fBound  = function() {
         baseArgs.length = baseArgsLength; // reset to default base arguments
         baseArgs.push.apply(baseArgs, arguments);
         return fToBind.apply(
                fNOP.prototype.isPrototypeOf(this) ? this : otherThis, baseArgs
         );
       };
   if (this.prototype) {
     // Function.prototype doesn't have a prototype property
     fNOP.prototype = this.prototype; 
   }
   fBound.prototype = new fNOP();
   return fBound;
 };

以上代码是mdn上的代码,但是和原本的bind函数还是有很大差别。以下是一个较为完整的方案。

Function.prototype.bind = function bind(that) {
    var target = this;
    if (typeof target !== 'function' || toStr.call(target) !== funcType) {
        throw new TypeError(ERROR_MESSAGE + target);
    }
    var args = slice.call(arguments, 1);
    var bound;
    var binder = function () {
        if (this instanceof bound) {
            var result = target.apply(
                this,
                args.concat(slice.call(arguments))
            );
            if (Object(result) === result) {
                return result;
            }
            return this;
        } else {
            return target.apply(
                that,
                args.concat(slice.call(arguments))
            );
        }
    };
    var boundLength = Math.max(0, target.length - args.length);
    var boundArgs = [];
    for (var i = 0; i < boundLength; i++) {
        boundArgs.push('$' + i);
    }
    bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this,arguments); }')(binder);
    if (target.prototype) {
        var Empty = function Empty() {};
        Empty.prototype = target.prototype;
        bound.prototype = new Empty();
        Empty.prototype = null;
    }
    return bound;
};

3、手写数组深拷贝

深拷贝,主要是要对深层次的内容进行拷贝,拷贝到新的地址里。

function deepClone(obj){
    if(typeof obj!='object' || obj ==null){
    return obj
  }
  let result;
  if(obj instanceof Array){
    result = []
  }else{
    result = {}
  }
  for(let key in obj){
    if(obj.hasOwnProperty(key)){
        result[key]=deepClone(obj[key])
    }
  }
  return result;
}
let test = {
  aa:12,
  bb:'string',
  cc:[1,2,3,4],
  dd:{
    ab:123,
    ac:'array',
    bc:['2','3']
  }
}
test.__proto__.gg=123;
deepClone(test)==test       //false
let tt = test;
tt==test;                                   //true

3、柯里化函数的实现

柯里化就是将多参数转换成单一参数的函数,要实现参数的分解,我们主要使用bind()来分解参数。

function currying(fn,length){
	length=length||fn.length;
  return function(...args){
    return args.length >= length ? 
      fn.apply(this,args):
    currying(fn.bind(this,...args),length-args.length);
  }
}

代码实例

4、手写EventEmitter

遇到这么个题目,嗯,题目,某人让我写一下,就写一下吧,照着文档算是能够实现一些功能了,但是有挺多要求没有实现的,但起码还是能达到一定要求的。
events(事件触发器)是nodejs中的提供事件触发的模块,events提供了类EventEmitter,EventEmitter 的核心就是事件触发与事件监听器功能的封装。提供了很多方法来进行监听器的绑定移除,这里我就只写其中几个简单的(主要是好实现,而且懒得做多了)。

class EventEmitterOfme {
    constructor() {
        this.MaxListeners = 10;
        this.currentListeners = 0;
        this.events = [];
    }
    //为指定事件添加一个监听器到监听器数组的尾部。
    addListener(event, listener) {
        if (!this.events[event]) {
            console.log('没有此监听器')
        } else {
            this.events[event].push(listener);
        }
    }
    //为指定事件注册一个监听器,接受一个字符串 event 和一个回调函数。
    on(event, listener) {
        if (this.currentListeners + 1 > this.MaxListeners) {
            console.log('超过监听器上限')
        } else {
            if (!this.events[event]) {
                this.currentListeners++;
                this.events[event] = [listener];
            } else {
                this.events[event].push(listener);
            }
        }
        return this;
    }
    //为指定事件注册一个单次监听器,即 监听器最多只会触发一次,触发后立刻解除该监听器。
    once(event, listener) {
        let fn = (arg) => {
            listener(arg);
            this.removeListener(event, fn);
        }
        this.on(event, fn);
        return this;
    }
    //移除指定事件的某个监听器,监听器必须是该事件已经注册过的监听器。
    //它接受两个参数,第一个是事件名称,第二个是回调函数名称。
    removeListener(event, listener) {
        if (!this.events[event]) {
            console.log(`不存在${event}监听器`)
        } else {
            this.events[event].splice(this.events[event].lastIndexOf(listener),1);
        }
        return this;
    }
    //移除所有事件的所有监听器, 如果指定事件,则移除指定事件的所有监听器。
    removeAllListeners(events) {
        if (events === undefined) {
            this.events = [];
            this.currentListeners = 0;
        } else {
            for(let key of events) {
                delete this.events[key];
                this.currentListeners--;
            }
        }
        return this;
    }
    //默认情况下, EventEmitters 如果你添加的监听器超过 10 个就会输出警告信息。 setMaxListeners 函数用于提高监听器的默认限制的数量。
    setMaxListeners(n) {
        this.MaxListeners = n;
        return this;
    }
    //返回指定事件的监听器数组。
    listeners(event) {
        return this.events[event]
    }
    allListeners() {
        return this.events;
    }
    //按监听器的顺序执行执行每个监听器,如果事件有注册监听返回 true,否则返回 false。
    emit(event, ...args) {
        if (!this.events[event])
            return false;
        else {
            this.events[event].forEach((fun, index) => fun.call(this, args[index]))
            return true;
        }
    }
}

5、实现防抖函数

防抖的非立即执行版本就是在触发一次后的固定时间内,一直不会执行操作,如果在固定时间内触发了,则重新计时,如果一直没触发,则等到固定时间到了执行.

function thro(fn,wait){
    let timer;
  let _that = this;
  let agrs = arguments;
  return function(){
        clearTimeout(timer);
        timer = setTimeout(()=>{
        fn.apply(_that,agrs);
      },wait)
  }
}

防抖的立即执行版本

function thro(fn,wait,rightNow){
    let timer;
  let _that = this;
  let agrs = arguments;
  return function(){
    //进去先把计时器清除
    if(timer) clearTimeout(timer);
    //在这里判断是不是第一次运行,是就立刻执行,不是就只需要执行计时器就行
    const im = !timer;
    if(rightNow){
        timer = setTimeout(()=>{
        timer = null;
      },wait);
      if(im) fn.apply(_that,agrs);
    }else{
        timer = setTimeout(()=>{
        fn.apply(_that,agrs);
      },wait)
    }
  }
}

6、节流函数

节流函数就是在规定时间内只执行一次操作,触发多少次都只会执行一次。

function thro(fn,wait){
	let timer;
  let _that = this;
  let agrs = arguments;
  return function(){
  	if(!timer){
    	timer = setTimeout(()=>{
      	fn.apply(_that,agrs);
        timer = null;
      },wait)
    }
  }
}

7、手写flat()扁平化

一般是对数组实现扁平化,这里我们可以用到apply和concat的组合,因为apply会把数组参数拆解,所以正好符合我们拍扁数组的需求

function flat(arrs){
  const isDeep = arrs.some(arr=>arr instanceof Array)
  if(!isDeep){
    return arrs
  }else{
    return flat(Array.prototype.concat.apply([],arrs))
  }
}
flat([1,[2,2,[3,3]],[4,5]]) //[1, 2, 2, 3, 3, 4, 5]

8、instanceof的实现

instanceof是通过判断其原型链上的属性是否在目标对象的原型链上也有,所以只需要对比prototype就行了。

function myInstanceof(obj,resouceObj){
  let objProto = obj.__proto__;
  let resouceProto = resouceObj.prototype
  while(true){
    if(objProto === null){
        return false
    }
    if(objProto === resouceProto){
        return true
    }
    objProto = objProto.__proto__
  }
}

9、手写继承

JavaScript的继承虽然有class可以用extends来继承,但是如果需要我们使用原型链进行继承,我们就需要自己来实现了。
需要实现继承,需要我们把父类的原型链和静态属性继承到子类中,所以代码如下:

//父类
function Parents(name){
    this.name = name;
}
//父类原型链上的方法
Parents.prototype.getName = function(){
    console.log(this.name)
}
//父类的静态方法
Parents.family = 'Bob';
function child(age){
  //继承父类的属性
    Parents.call(this);
}
//把父类的原型链拷贝一个进来,同时把构造函数修正为自己的构造函数,并且不能被修改
child.prototype = Object.create(Parents.prototype,{
    constructor:{
    value:new child(),
    writeable:false
  }
});
//获取父类的静态属性
let keys = Object.entries(Parents);
//把静态属性放进子类
for(let i = 0;i<keys.length;i++){
    let key = keys[i][0];
  let value = keys[i][1];
  child[key] = value;
}
new child();

10、手写new方法

new操作符做了哪些事情?

  • 创建一个新的对象

  • 新对象会被执行[[Prototype]]连接,关联到构造函数的.prototype对象上

  • 新对象会绑定到函数调用的this

  • 如果函数没有返回其他对象,那么new表达式中的函数会调用自动返回这个新对象

    function makeNew(fun){
        if(typeof fun !== "Object"){
        throw TypeError();
      }
      let obj = Object.create(null);
      //设置对象的原型(即内部 [[Prototype]] 属性)。
      Object.setPrototypeof(obj,fun.prototype);
      let result = fun.apply(obj,Array.prototype.slice.call(arguments,1));
      if(typeof result == "Object" || (typeof result =="Function" && result !==null)){
        return result
      }else{
        return obj;
      }
    }

    11、手写Object.create方法

    要实现create方法,我们需要知道create有哪些属性

    Object.create(proto,ptopertiesObject)

    • 使用指定的proto对象和属性创建一个新的对象
    • ptopertiesObject表示给新创建的对象的属性进行设置
    • 如果ptopertiesObject是null或者不是对象,就会抛出异常
Object.create = function(proto,propertiesObject){
	if(typeof propertiesObject !== "object"){
  	throw TypeError();
  }
  function temp(){};
  //挂上原型链
  temp.prototype = proto;
  let o = new temp();

  if(propertiesObject !== undefined){
  	if(propertiesObject!==Object(propertiesObject)){
    	throw TypeError();
    }
    Object.defineProperties(o,propertiesObject);
  }
  return o;
}

12、手写Promise方法

class MyPromise {
    constructor(exector) {
      this.status = MyPromise.PENDING;
      // 1.3 “value” is any legal JavaScript value (including undefined, a thenable, or a promise).
      this.value = null;
      // 1.5 “reason” is a value that indicates why a promise was rejected.
      this.reason = null;
  
      /**
       * 2.2.6 then may be called multiple times on the same promise
       *  2.2.6.1 If/when promise is fulfilled, all respective onFulfilled callbacks must execute in the order of their originating calls to then
       *  2.2.6.2 If/when promise is rejected, all respective onRejected callbacks must execute in the order of their originating calls to then.
       */
  
      this.onFulfilledCallback = [];
      this.onRejectedCallback = [];
      this.initBind();
      this.init(exector);
    }
    initBind() {
      // 绑定 this
      // 因为 resolve 和 reject 会在 exector 作用域中执行,因此这里需要将 this 绑定到当前的实例
      this.resolve = this.resolve.bind(this);
      this.reject = this.reject.bind(this);
    }
    init(exector) {
      try {
        exector(this.resolve, this.reject);
      } catch (err) {
        this.reject(err);
      }
    }
  
    resolve(value) {
      if (this.status === MyPromise.PENDING) {
        setTimeout(() => {
          this.status = MyPromise.FULFILLED;
          this.value = value;
          this.onFulfilledCallback.forEach(cb => cb(this.value));
        })
      }
    }
  
    reject(reason) {
      if (this.status === MyPromise.PENDING) {
        setTimeout(() => {
          this.status = MyPromise.REJECTED;
          this.reason = reason;
          this.onRejectedCallback.forEach(cb => cb(this.reason));
        })
      }
    }
  
    then(onFulfilled, onRejected) {
      onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value
      onRejected = typeof onRejected === "function" ? onRejected : reason => { throw reason }
      let promise2;
      if (this.status === MyPromise.FULFILLED) {
        return promise2 = new MyPromise((resolve,reject) => {
          setTimeout(() => {
            try{
              const x = onFulfilled(this.value);
              MyPromise.resolvePromise(promise2,x,resolve,reject);
            }catch(e){
              reject(e)
            }
          })
        })
      }
  
      if (this.status === MyPromise.REJECTED) {
        return promise2 = new MyPromise((resolve,reject) => {
          setTimeout(() => {
            try{
              const x = onRejected(this.reason)
              MyPromise.resolvePromise(promise2,x,resolve,reject)
            }catch(e){
              reject(e)
            }
          })
        })
      }
  
      if (this.status === MyPromise.PENDING) {
        return promise2 = new MyPromise((resolve,reject) => {
          // 向对了中装入 onFulfilled 和 onRejected 函数
          this.onFulfilledCallback.push((value) => {
            try{
              const x = onFulfilled(value)
              MyPromise.resolvePromise(promise2,x,resolve,reject)
            }catch(e){
              reject(e)
            }
          })
  
          this.onRejectedCallback.push((reason) => {
            try{
              const x = onRejected(reason)
              MyPromise.resolvePromise(promise2,x,resolve,reject)
            }catch(e){
              reject(e)
            }
          })
        })
      }
    }
  }
  
  // 2.1 A promise must be in one of three states: pending, fulfilled, or rejected.
  MyPromise.PENDING = "pending"
  MyPromise.FULFILLED = "fulfilled"
  MyPromise.REJECTED = "rejected"
  
  MyPromise.resolvePromise = (promise2,x,resolve,reject) => {
    let called = false;
    /**
     * 2.3.1 If promise and x refer to the same object, reject promise with a TypeError as the reason.
     */
    if(promise2 === x){
      return reject(new TypeError("cannot return the same promise object from onfulfilled or on rejected callback."))
    }
    
    if(x instanceof MyPromise){
      // 处理返回值是 Promise 对象的情况
      /**
       * new MyPromise(resolve => {
       *  resolve("Success")
       * }).then(data => {
       *  return new MyPromise(resolve => {
       *    resolve("Success2")
       *  })
       * })
       */
      if(x.status === MyPromise.PENDING){
        /**
         * 2.3.2.1 If x is pending, promise must remain pending until x is fulfilled or rejected.
         */
        x.then(y => {
          // 用 x 的 fulfilled 后的 value 值 y,去设置 promise2 的状态
          // 上面的注视,展示了返回 Promise 对象的情况,这里调用 then 方法的原因
          // 就是通过参数 y 或者 reason,获取到 x 中的 value/reason
  
          // 拿到 y 的值后,使用 y 的值来改变 promise2 的状态
          // 依照上例,上面生成的 Promise 对象,其 value 应该是 Success2
  
          // 这个 y 值,也有可能是新的 Promise,因此要递归的进行解析,例如下面这种情况
  
          /**
           * new Promise(resolve => {
           *  resolve("Success")
           * }).then(data => {
           *  return new Promise(resolve => {
           *    resolve(new Promise(resolve => {
           *      resolve("Success3")
           *    }))
           *  })
           * }).then(data => console.log(data))
           */
  
          MyPromise.resolvePromise(promise2, y, resolve, reject)
        },reason => {
          reject(reason)
        })
      }else{
        /**
         * 2.3 If x is a thenable, it attempts to make promise adopt the state of x, 
         * under the assumption that x behaves at least somewhat like a promise. 
         * 
         * 2.3.2 If x is a promise, adopt its state [3.4]:
         * 2.3.2.2 If/when x is fulfilled, fulfill promise with the same value.
         * 2.3.2.4 If/when x is rejected, reject promise with the same reason.
         */
        x.then(resolve,reject)
      }
      /**
       * 2.3.3 Otherwise, if x is an object or function,
       */
    }else if((x !== null && typeof x === "object") || typeof x === "function"){
      /**
       * 2.3.3.1 Let then be x.then. 
       * 2.3.3.2 If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.
       */
      try{
        // then 方法可能设置了访问限制(setter),因此这里进行了错误捕获处理
        const then = x.then;
        if(typeof then === "function"){
  
          /**
           * 2.3.3.2 If retrieving the property x.then results in a thrown exception e, 
           * reject promise with e as the reason.
           */
  
          /**
           * 2.3.3.3.1 If/when resolvePromise is called with a value y, run [[Resolve]](promise, y).
           * 2.3.3.3.2 If/when rejectPromise is called with a reason r, reject promise with r.
           */
          
          then.call(x,y => {
            /**
             * If both resolvePromise and rejectPromise are called, 
             * or multiple calls to the same argument are made, 
             * the first call takes precedence, and any further calls are ignored.
             */
            if(called) return;
            called = true;
            MyPromise.resolvePromise(promise2, y, resolve, reject)          
          },r => {
            if(called) return;
            called = true;
            reject(r);
          })
        }else{
          resolve(x)
        }
      }catch(e){
        /**
         * 2.3.3.3.4 If calling then throws an exception e,
         * 2.3.3.3.4.1 If resolvePromise or rejectPromise have been called, ignore it.
         * 2.3.3.3.4.2 Otherwise, reject promise with e as the reason.
         */
  
        if(called) return;
        called = true;
        reject(e)
      }
    }else{
      // If x is not an object or function, fulfill promise with x.
      resolve(x);
    }
  }
  
  MyPromise.deferred  = function() {
    const defer = {}
    defer.promise = new MyPromise((resolve, reject) => {
      defer.resolve = resolve
      defer.reject = reject
    })
    return defer
  }
  
  try {
    module.exports = MyPromise
  } catch (e) {
  }

13、setInterval

  • setInterval() 方法可按照指定的周期(以毫秒计)来调用函数或计算表达式
  • setInterval() 方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭。
  • 由 setInterval() 返回的 ID 值可用作clearInterval()方法的参数。

  要实现这个函数,可以通过setTimeout()来实现,我们也可以修改实现的效果,比如说按照叠加的周期进行输出。实现过程很简单:

//在外面定义一个timer标记我们的定时器,到时候可以关掉
let timer = null;
function mySetInterVal(fn, a, b) {
    return {
        start: () => {
            timer = setTimeout(() => {
                fn();
                timer ? mySetInterVal(fn, a, b + b).start() : null;
            }, a + b)
        },
        end: () => {
            clearTimeout(timer);
            timer = null;
        }
    }
}
mySetInterVal(()=>{console.log(123),500,500}).start();          //开始
mySetInterVal(()=>{console.log(123),500,500}).end();                //结束

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!