new, call,apply,bind 原理剖析及手写实现

吐槽君 分类:javascript

new, call,apply,bind的手写实现

我们都知道,js中call,apply,bind的作用都是改变作用域,来改变this的指向。这些javascript中概念在我们前端领域有着非常重要的概念,经常被使用到。那么它们的原理到底是什么,怎样用代码模拟实现呢?那么作为经典面试题,我们背答案是肯定行不通的,所以就需要从根本上去理解其实现原理,才能自己手写实现。接下来就让我们彻底掌握它们。

1. new

首先我们看new这个关键词,它在Javascript中的主要作用就是执行一个构造函数、然后返回一个实例对像。那么在new 生成实例的这个过程中,发生了什么呢?

大致分为四个步骤:

  1. 创建一个对象;
  2. 将构造函数的作用域赋给这个新对象(相当于将this 指向新对象);
  3. 执行构造函数中的代码(为这个新对象添加属性和方法);
  4. 返回这个新对象;
  • 那我们就可以用代码来实现了,其实只需要完成这几个功能。
  1. 让实例可以访问到私有属性;
  2. 让实例可以访问构造函数原型所在的原型链上的属性;
  3. 构造函数返回的最后结果是引用数据类型;
function _new(fn, ...args) {
  if(typeof fn !== 'function') {
    throw 'fn must be a function';
  }
  let obj = new Object();
  obj.__proto__ = Object.create(fn.prototype);
  let res = fn.apply(obj,  [...args]);

  let isObject = typeof res === 'object' && res !== null;
  let isFunction = typeof res === 'function';
  return isObject || isFunction ? res : obj;
};
 

2. apply & call & bind 原理解析

我们可以先看看这三个函数的基本用法,call,apply,bind 都是需要挂在Function对象上的三个方法,并且调用这三个方法必须得是函数。

func.call(thisArg, param1, param2, ...)
func.apply(thisArg, [param1,param2, ...])
func.bind(thisArg, param1,param2, ...)
 
  • 其中func 是要调用的函数,thisArg 一般是this所指的对象,后面的为参数。

  • 这三者的公共点就是,都是改变函数func的this指向。其中 call和apply 的区别在于传参的写法不同:
    call 是一个一个参数传,而apply是以数组的形式传入。

    • 然后bind 与这两者的区别在于:bind函数不会马上执行,而(call 与 apply)是在改变了函数的this指向后立马执行。
  • 晓得了这三个的原理,让我们动手来实现一下吧。

    • call 和 apply的基本原理是差不多的,这里放在一块写。
Function.prototype.call = function (context, ...args) {
  var context = context || window;
  context.fn = this;
  var res = eval('context.fn(...args)');
  delete context.fn
  return res;
}

Function.prototype.apply = function (context, args) {
  let context = context || window;
  context.fn = this;
  let res = eval('context.fn(...args)');
  delete context.fn
  return res;
}
 
  • bind的实现
    • 这里需要注意bind的核心是需要返回一个函数。这样调用bind方法接收到函数的对象,再通过执行接收的函数,就可以得到想要的结果。
    • 一个 绑定函数 也能使用 new 操作符创建对象,这种行为就像把原函数当成构造器,thisArg 参数无效。也就是 new 操作符修改 this 指向的优先级更高。
Function.prototype.myBind = function (context) {
  if (typeof this !== 'function') {
    throw TypeError('error');
  }
  // 缓存this
  const self = this;
  const args = [...arguments].slice(1);
  //返回一个函数
  return function fn() {
    // 判断调用方式
    if (this instanceof fn) {
      return new self(...args, ...arguments);
    }
    return self.apply(context, args.concat(...arguments));
  };
};

 

总结

那么到这里我们就用代码模拟完成了日常写代码经常用到的new,apply,call,bind这几种方法,可以看出来,这几个方法是有一些区别和联系的。
下面有一个表格整理了call,apply,bind的异同点。

标题 call apply bind
参数 一个一个列出 一个数组列出 单个单个列出
功能/用处 函数调用改变this 函数调用改变this 函数调用改变this
返回结果 直接执行 直接执行 返回一个待执行函数

从底层逻辑了解这些,可以让我们在一些场景更加熟练的使用它们。
例如:继承--各种各样的继承方式,还有判断数据类型的Object.prototype.toString.call()方法等等都会运用相关的知识点。

本人前端准实习生,在各方面有何不足欢迎指正,让我们共同进步。

回复

我来回复
  • 暂无回复内容