new、apply、call、bind方法学习

这是我参与8月更文挑战的第N天,活动详情查看: 8月更文挑战”juejin.cn/post/698796…

new基本原理

主要作用

执行一个构造函数,返回一个新的实例对象。

在执行过程中,根据构造函数的参数情况来确定是否需要参数的传递。

执行步骤

  • 创建一个新对象
  • 将构造函数的作用域指赋给新对象
  • 执行构造函数中代码
  • 返回新对象
var ObjectFactory = function () {
    // 创建一个对象
    var obj = new Object()
    // 返回第一个参数作为构造函数
    var Constructor = [].shift.call(arguments)
    // 将构造函数的原型复制于对象的原型
    obj.__proto__ = Constructor.prototype
    // 调用构造函数,并将obj 作为this, arguments作为参数
    var ret = Constructor.apply(obj, arguments)
    // 如果构造函数里返回一个对象的话,就直接返回,否则我们就返回this即new创建的对象
    return typeof ret === 'object'? ret: obj
}
// 效果等效
var a = ObjectFactory(Person, 'sven');
复制代码

注意点

  • 不使用new且构造函数没有返回值

    • 相当于正常函数执行,此时函数中this指向window
  • 构造函数中有返回值

    • 返回一个对象

      • 因为new要求必须返回一个对象,所以如果返回的是一个与this无关的对象,则最终的值就是该对象而不是new生成的this对象
      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
      复制代码
  • 返回的不是对象

    • 还会按照 new 的实现步骤,返回新生成的this对象

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

apply、call、bind基本原理

基本情况

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

基本语法

func.call(thisArg, param1, param2, ...)
func.apply(thisArg, [param1,param2,...])
func.bind(thisArg, param1, param2, ...)
复制代码

区别

  • 相同点:都可以改变函数 func 的 this 指向。

  • 不同点:

    • call 和 apply 的区别在于,传参的写法不同:

      • apply 的第 2 个参数为数组
      • call 则是从第 2 个至第 N 个都是给 func 的传参;
    • bind 和这两个(call、apply)又不同,

      • bind 虽然改变了 func 的 this 指向,但不是马上执行
      • 这两个(call、apply)是在改变了函数的 this 指向之后立马执行

使用场景

判断数据类型

function getType(obj){
 let type = typeof obj;
 if(type !== "object"){
     return type;
 }
 return Object.prototype.toString.call(obj).replace(/^[Object (\S+)]$/,'$1');
}
复制代码

数据通过 call 借用了 toString 方法来达到数据类型的判断

类数组借用数组方法

类数组因为不是真正的数组,所以无法使用数组的方法,只能通过call借用

var arrayLike = { 
  0: 'java',
  1: 'script',
  length: 2
} 
Array.prototype.push.call(arrayLike, 'jack', 'lily'); 
console.log(typeof arrayLike); // 'object'
console.log(arrayLike);
// {0: "java", 1: "script", 2: "jack", 3: "lily", length: 4}
复制代码

获取最大值、最小值

用apply来实现,可以直接传递数组为参数,又可以避免进一步展开数组。

let arr = [13, 6, 10, 11, 16];
const max = Math.max.apply(Math, arr); 
const min = Math.min.apply(Math, arr);
console.log(max);  // 16
console.log(min);  // 6
复制代码

继承

function Parent1(){
    this.name = 'parent1';
  }
  Parent1.prototype.getName = function () {
    return this.name;
  }

  // 构造函数式继承
  function Child1(){
    Parent1.call(this);  // Parent1.apply(this) Parent1.bind(this)()
    this.type = 'child1'
  }
  // 组合式继承
  Child1.prototype = new Parent1();
  Child1.prototype.constructor = Child1;

  let child = new Child1();
  console.log(child);  // 没问题   {name: "parent1", type: "child1"}
  console.log(child.getName());  // 会报错
复制代码

方法实现

new方法

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;
};
复制代码

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;
}
复制代码

bind方法

bind 的实现思路基本和 apply 一样,但是在最后实现返回结果这里,bind 和 apply 有着比较大的差异,bind 不需要直接执行,因此不再需要用 eval ,而是需要通过返回一个函数的方式将结果返回,之后再通过执行这个结果,得到想要的执行效果。

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) {
      fbound.prototype = Object.create(this.prototype);
    }
    return fbound;
}
复制代码

实现 bind 的核心在于返回的时候需要返回一个函数,故这里的 fbound 需要返回,

但是在返回的过程中原型链对象上的属性不能丢失。因此这里需要用Object.create 方法,将 this.prototype 上面的属性挂到 fbound 的原型上面,最后再返回 fbound。

这样调用 bind 方法接收到函数的对象,再通过执行接收的函数,即可得到想要的结果

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享