likes
comments
collection
share

小白也能搞懂的五个常见手写函数

作者站长头像
站长
· 阅读数 28

Hello,这里是mouche,当然你也可以叫我某车,反正大家都爱这么叫😁

以下函数的实现都有详细讲解,带你一步一步搞懂它

一、new的实现

👉过程

  • 创建一个新的对象
  • 对象构造函数的原型通过原型链连接起来 小白也能搞懂的五个常见手写函数
  • 将构建函数中的this指向新建的对象obj
    • 这里可以通过apply, call改变this的指向,如果使用bind还需要对他进行调用
  • 根据构造函数的返回类型作判断,如果是原始值则忽略,如果是返回对象,则需要处理
    • 原因:传入的这个函数是可以返回值的
小白也能搞懂的五个常见手写函数

👉代码实现

function myNew(func, ...args) {
  let res = {}; //创建一个新对象
  if(func.prototype !== null) {
    res.__proto__ = func.prototype; //如图一
  }
  let result = func.apply(res, args); //绑定this
  return result instanceof Object? result: res; //如图二
}

👉思考

  • 如果使用call :
    let result = func.call(res, ...args)
    
  • 如果使用bind :
    let result = func.bind(res, ...args)()
    
  • 如果最后一步使用typeof:
    return (typeof result === "object" && result !== null) || 
    (typeof result === "function") ? result : res
    

我们上面使用到了call,bind,apply,接下去我们就挨个实现

二、实现call

👉了解

  • 语法:func.call(thisArg, param1, param2, ...)
  • 返回值: func的执行结果,若没有返回值则返回undefined
  • 指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装

👉过程

  • 对传入的thisArg进行判断封装
    • 如果为null 或 undefined 则绑定到window
    • 否则可以使用Object()
      • 传入为原始类型会将其转为对应的包装对象的实例
      • 传入对象则返回对象
  • 通过 this隐式绑定的思想,将函数本身绑定到对象,然后调用,此时this就指向对象
  • 返回函数调用的返回值

👉代码实现

Function.prototype.myCall = function(thisArg, ...args) {
  //如果传入的为null或者undefined则指向全局对象
  if(thisArg === null || thisArg === undefined) {
     thisArg = window
  } else {
    //用Object对其进行包装
    thisArg = Object(thisArg)
  }
  //通过Symbol()生成一个唯一的属性
  const symBolPrototype = Symbol();
  //将函数绑定到对象该唯一的属性上
  thisArg[symBolPrototype] = this;
  //在对象上调用函数,则this指向对象,并将返回值赋值给result
  let result = thisArg[symBolPrototype](...args);
  //删除我们刚刚绑定的那个属性,防止污染对象
  delete thisArg[symBolPrototype];
  //返回我们调用函数的返回值
  return result
}

👉测试

  • 这里传入thisArg分别为对象和空
  • 对象obj里面的a为20,window上的a通过var a = 10设置为10
const obj = {
  a: 20  
}
var a = 10;
function test(num1, num2) {
  console.log(this.a + num1 + num2);
}
test.myCall(obj,3,5) //28
test.myCall(null,3,5)  //18

小白也能搞懂的五个常见手写函数

👉思考一

  • 看到有一些实现博客直接使用了
    thisArg = thisArg ? Object(thisArg) : window;
    
  • 这样是不可以的,像如果thisArg为0,为false的情况下会把它指向window,但是实际上call是应该把它指向这些原始值的实例对象上

👉思考二

  • 也看到像这样使用的
    thisArg = Object(thisArg) || window
    
  • 也是不太行的,这个只需要用上面测试的代码测试一下就知道了,Object()遇到nullundefined会返回一个空对象{}而不是空引用,所以此时并不会指向window

三、实现apply

👉了解

  • 实现了call再来实现apply就很简单了
  • 只需要记住他们的差异,call传入的为参数列表,apply传入的为参数数组或者类数组对象(快速记忆:apply以数组Array的a开头)

👉代码实现

//修改了传参
Function.prototype.myApply = function(thisArg, args) { 
  if(thisArg === null || thisArg === undefined) {
    thisArg = window
  } else {
    thisArg = Object(thisArg)
  }
  const symBolPrototype = Symbol();
  thisArg[symBolPrototype] = this;
  let result = thisArg[symBolPrototype](...args); 
  delete thisArg[symBolPrototype];
  return result
}

四、实现bind

👉了解

  • 我抽取了一些实现的点出来:具体可以看:Function.prototype.bind() MDN
  • 对于thisArg:如果使用new运算符构造绑定函数,则忽略该值

涉及知识点:new绑定this的优先级大于显示绑定this

  • 返回值:返回一个原函数的拷贝,并拥有指定的 this 值和初始参数
  • 内部使用的是call方法
  • 可以二次传参

👉思路

  • 判断是否使用new构造绑定函数:可以使用new.target, 如果是普通函数则返回undefined

👉代码实现

Function.prototype.myBind = function (thisArg, ...params) {
  const thisFn = this;  //先存储该函数的this
  //这里是为了可以二次传参
  let cb = function (...secondParams) {
      const isNew = typeof new.target !== 'undefined' //普通函数就返回undefined
      // new构造函数就绑定到this上,否则就绑定到传入的thisArg上
      const context = isNew ? this : thisArg 
      return thisFn.call(context, ...params, ...secondParams); // 内部使用call
  };
  //一些情况下函数没有prototype,比如箭头函数
  if (thisFn.prototype) {
      // 把源函数的prototype给cb 
      cb.prototype = Object.create(thisFn.prototype);
  }
  return cb; // 返回拷贝的函数
}

👉测试

const obj = {
  a: 20,
  name:'某车'
}
var a = 10;
function test(num1, num2) {
  this.name = 'mouche';
  console.log(this.a + num1 + num2);
}
const testBind = test.myBind(obj,3)  
testBind(5)  //20+3+5 = 28
const testBind1 = test.myBind(null,3);
testBind1(5) //10+3+5 = 18
const testObj = new testBind();  
console.log(testObj.name); //是mouche而不是某车

小白也能搞懂的五个常见手写函数

五、instanceof

👉了解

  • 语法:
        object instanceof constructor
    
  • 谈到类型判断就会提到instanceof
  • 提到instanceof就会提到它是:顺着原型链寻找,直到找到相同的原型对象
  • 那我们只需要实现这个即可

👉思路

  • 主要就是检测 constructor.prototype 是否存在于参数 object 的原型链上,那么只需要每次都进行判断,如果存在的话就返回true,如果不存在的话则继续找它的__proto__,直到为null的时候都找不到的话则返回false

👉代码实现

function myInstanceof(leftValue, rightValue) {
//如果左边的为空或者不为对象则返回false
  if(typeof leftValue !== 'object' || leftValue === null) return false;
  //constructor.prototype
  let rightProto = rightValue.prototype;
  //object的原型
  let leftProto = leftValue.__proto__;
  while (true) {
  //如果搜到最上面null了,则说明constructor.prototype没有出现在object的原型链,那么返回false
      if (leftProto === null) {
          return false; 
      }
  //如果找到了就返回true
      if (leftProto === rightProto) {
          return true;
      }
      leftProto = leftProto.__proto__ //顺着原型链判断
  } 
}

👉测试

  • 分别用myInstanceof函数去测试他们是否有相同的原型
function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}
const auto = new Car('Honda', 'Accord', 1998);
console.log(myInstanceof(auto, Car)); //true
console.log(myInstanceof(auto, Object)); //true
console.log(myInstanceof(auto, Function)); //false

小白也能搞懂的五个常见手写函数

  • 原型链图 小白也能搞懂的五个常见手写函数
转载自:https://juejin.cn/post/7151609034699718692
评论
请登录