Skip to content

Latest commit

 

History

History
160 lines (119 loc) · 4.83 KB

2.md

File metadata and controls

160 lines (119 loc) · 4.83 KB

手写实现apply、call、bind

apply、call、bind区别

这三个方法都是挂载 Funtion 原型上的方法,所以调用者必须是个函数。

这三个函数的使用语法:

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

他们共有的作用都可以改变函数运行时 this 的指向。

callapply 的区别在于传递参数的方式不同:

  • apply 的第 2 个参数为数组
  • call 则是从第 2 个至第 N 个都是给 func 的传参

bindcallapply 的区别在于函数是否立即执行:

  • callapply是在改变了函数的 this 指向之后立马执行
  • bind 会返回一个函数,虽然改变了 functhis 指向,但不是马上执行, 而是调用返回的函数才执行

apply、call、bind 使用场景

获取数组的最值

可以通过 apply 来获取 Math.max() 最大值和 Math.min() 最小值。当然也可以展开来获取数组的最值。

let arr = [1, 2, 3];
const max = Math.max.apply(null, arr); // es6 Math.max(...arr)
const min = Math.min.apply(null, arr); // es6 Math.min(...arr)
console.log(max); // 3
console.log(min); // 1

判断数据类型

可以通过 Object.toString.call 来判断所有的数据类型。

// 判断原生对象
const isPlainObject = val => Object.toString.call(val) === '[object Object]'
// 判断字符串
const isString = val => Object.toString.call(val) === '[object String]'

将类数组转为数组

类数组因为不是真正的数组,所有没有数组类型上自带的种种方法,所以要转为数组,才能调用数组的方法.

let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};

// ES5的写法
let arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']

// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

继承

组合继承使用call来实现。

function Parent() {
    this.name = '张三';
    this.age = 18;
}

Parent3.prototype.getName = function() {
    return this.name;
}

function Child() {
    Parent3.call(this);
    this.address = 'beijing';
}

Child.prototype = new Parent3();
Child.prototype.constructor = Child3;
var s = new Child();
console.log(s.getName()); // '张三'

手写实现

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;
}

apply的实现

apply 和 call 基本原理是差不多的,只是参数存在区。

Function.prototype.apply = function (context, args) {
  let context = context || window;
  context.fn = this;
  let result = eval('context.fn(...args)');
  delete context.fn
  return result;
}c

bind的实现

bind 的实现思路基本和 apply 一样,但是在最后实现返回结果这里,bindapply 有着比较大的差异,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) {
      fbound.prototype = Object.create(this.prototype);
    }
    return fbound;
}
  • bind需要返回一个函数,但是不能丢失函数原型上的属性,因此fbound.prototype = Object.create(this.prototype);
  • this instanceof self当这个绑定函数被当做普通函数调用的时候,可以直接用context; 而返回的这个之后当做构造函数使用的时候,却是指向这个实例,所以this instanceof selftrue时,要用this。 因此这里加了这个判断。

总结

经过上述的分析,我们来总结下这三个函数的相同点和不同点,来帮助更好的理解。

方法 call apply bind
函数参数 一个参数列表 一个包含多个参数的数组 多个参数
函数作用 改变函数运行时 this 指向 改变函数运行时 this 指向 改变函数运行时 this 指向
返回结果 直接执行 直接执行 等待执行函数
底层实现 通过eval 通过eval 简介调用apply