美文网首页
js中new,call,apply,bind实现

js中new,call,apply,bind实现

作者: 浅忆_0810 | 来源:发表于2021-09-09 14:09 被阅读0次

1. new

1.1 原理介绍

new 的主要作用就是执行一个构造函数、返回一个实例对象,在 new 的过程中,根据构造函数的情况,来确定是否可以接受参数的传递

function Person(){
   this.name = 'Jack';
}

var p = new Person(); 
console.log(p.name)  // Jack

从输出结果可以看出,p 是一个通过 person 这个构造函数生成的一个实例对象

  1. 创建一个新对象
  2. 将构造函数的作用域赋给新对象(this 指向新对象)
  3. 执行构造函数中的代码(为这个新对象添加属性)
  4. 返回新对象

如果不用 new 这个关键词,结合上面的代码改造一下

function Person() {
  this.name = 'Jack';
}

var p = Person();
console.log(p) // undefined
console.log(name) // Jack
console.log(p.name) // 'name' of undefined

没有使用 new 这个关键词,返回的结果就是 undefined。其中由于 JavaScript 代码在默认情况下 this 的指向是 window,那么 name 的输出结果就为 Jack

当构造函数中有 return 一个对象的操作

function Person(){
   this.name = 'Jack'; 
   return {age: 18}
}

var p = new Person(); 
console.log(p)  // {age: 18}
console.log(p.name) // undefined
console.log(p.age) // 18

当构造函数最后 return 出来的是一个和 this 无关的对象时,new 命令会直接返回这个新对象,而不是通过 new 执行步骤生成的 this 对象

这里要求构造函数必须是返回一个对象,如果返回的不是对象,那么还是会按照 new 的实现步骤,返回新生成的对象

function Person() {
   this.name = 'Jack'; 
   return 'tom';
}

var p = new Person(); 
console.log(p)  // {name: 'Jack'}
console.log(p.name) // Jack

当构造函数中 return 的不是一个对象时,还是会根据 new 关键词的执行逻辑,生成一个新的对象(绑定了最新 this),最后返回出来

总结:new 关键词执行之后总是会返回一个对象,要么是实例对象,要么是 return 语句指定的对象

1.2 实现

new 被调用后大致做了哪几件事情。

  1. 让实例可以访问到私有属性
  2. 让实例可以访问构造函数原型(constructor.prototype)所在原型链上的属性
  3. 构造函数返回的最后结果是引用数据类型
function _new(ctor, ...args) {
    if(typeof ctor !== 'function') {
      throw 'ctor must be a function';
    }

    let obj = new Object();
    obj.__proto__ = Object.create(ctor.prototype);
  
    let res = ctor.apply(obj,  [...args]);
    let isObject = typeof res === 'object' && res !== null;
    let isFunction = typeof res === 'function';

    return isObject || isFunction 
      ? res 
        : obj;
};


2. apply & call & bind

2.1 原理介绍

call、apply 和 bind 是挂在 Function 对象上的三个方法,调用这三个方法的必须是一个函数

func.call(thisArg, param1, param2, ...)
func.apply(thisArg, [param1,param2,...])
func.bind(thisArg, param1, param2, ...)

其中 func 是要调用的函数,thisArg 一般为 this 所指向的对象,后面的 param1、2 为函数 func 的多个参数,如果 func 不需要参数,则后面的 param1、2 可以不写

call 和 apply 的区别在于,传参的写法不同:apply 的第 2 个参数为数组; call 则是从第 2 个至第 N 个都是给 func 的传参;

而 bind 和(call、apply)又不同,bind 虽然改变了 func 的 this 指向,但不是马上执行,而(call、apply)是在改变了函数的 this 指向之后立马执行

2.2 apply 和 call 的实现

Function.prototype.call = function (context, ...args) {
  var context = context || window;
  context.fn = this;

  var result = eval('context.fn(...args)');
  delete context.fn

  return result;
}

Function.prototype.apply = function (context, args) {
  let context = context || window;
  context.fn = this;

  let result = eval('context.fn(...args)');
  delete context.fn

  return result;
}

实现 call 和 apply 的关键就在 eval 这行代码。其中显示了用 context 这个临时变量来指定上下文,然后还是通过执行 eval 来执行 context.fn 这个函数,最后返回 result

这两个方法和 bind 的区别就在于,这两个方法是直接返回执行结果,而 bind 方法是返回一个函数

2.3 bind的实现

Function.prototype.bind = function (context, ...args) {
    if (typeof this !== "function") {
      throw new Error("this must be a function");
    }

    var self = this;
    var fbound = function () {
      self.apply(
        this instanceof self 
            ? this 
            : context, 
        args.concat(Array.prototype.slice.call(arguments))
      );
    }

    if (this.prototype) { // 在特殊情况下.prototype会缺失
      fbound.prototype = Object.create(this.prototype);
    }

    return fbound;
}

实现 bind 的核心在于返回的时候需要返回一个函数,故这里的 fbound 需要返回,但是在返回的过程中原型链对象上的属性不能丢失。因此这里需要用Object.create 方法,将 this.prototype 上面的属性挂到 fbound 的原型上面,最后再返回 fbound

this instanceof self( => this instanceof fBound:判断是否使用new操作符来调用的_new):

当这个绑定函数被当做普通函数调用的时候,可以直接用context; 而返回的这个之后当做构造函数使用的时候,却是指向这个实例,所以this instanceof self为true时,要用this


3. 总结

相关文章

网友评论

      本文标题:js中new,call,apply,bind实现

      本文链接:https://www.haomeiwen.com/subject/gzuiwltx.html