函数的原型方法:call、apply、bind

283 阅读3分钟

对 call /apply /bind,当第一个参数为空、null、undefined 时,默认传入全局对象!

1. call

(1)参数:call 可以接受多个参数。

fn.call(thisValue, arg1, arg2, ...)
  • 第一个参数是一个对象,也就是 this 要指向的对象,如果call方法的第一个参数是原始值,那么这个原始值会自动转成对应的包装对象
  • 后面的参数是调用函数时所需的参数。
function get() {
    console.log(typeof this);
}
get.call(5);
// 对象 [Number 5]

(2)应用

例如 Object.prototype.toString.call这种用于调用对象的原生方法,避免函数覆盖造成的结果。

(3)源码

func.call(obj, ...args),call 要实现的是将 「修改 func 中的 this指向到 obj 上」,可以看作是 obj.func(...args),这样 func 里的 this 指向就是 obj。

那么我们一开始要怎么取到 func 这个函数呢?this 就是调用 call 的函数!

// fn.call(obj, ...args) => obj.fn(...args)
Function.prototype.myCall = function(context) { // 函数的原型方法
  let obj = context || window;  // 传参为空或 undefined 时,指向 window
  obj.fn = this;  // this 就是调用 myCall 的函数
  const args = [...arguments].slice(1); // 使用展开运算符将伪数组转为数组
  
  let res = obj.fn(...args);  
  delete obj.fn;
  return res;
}

测试用例

let p1 = {
  name: 'Tom',
  say() {
    console.log(this.name);
  }
};
let p2 = {name: 'Jack'};
p1.say.myCall(p2);  // 'Jack'

2. apply

(1)参数:apply 的第2个参数要求是数组。虽然apply第2个参数接收的是数组形式,但给 fn 函数传参时,会将数组展开传入。

fn.apply(thisValue, [arg1, arg2, ...])

(2)应用

将一个类似数组的对象(比如arguments对象)转为真正的数组。

// 1.length等于多少就返回多长的数组    2.返回键值
Array.prototype.slice.apply({0: 1, length: 1}) // [1]
Array.prototype.slice.apply({0: 1, length: 2}) // [1, undefined]

(3)源码

obj.fn(args)

Function.prototype.myApply = function(context) {  // 只有方法名改变
  let obj = context || window; 
  obj.fn = this; 
  const args = [...arguments].slice(1); 
  
  let res = obj.fn(...args);  
  delete obj.fn; 
  return res;
}

测试用例同 call,只需改变传参形式即可。

3. bind:借助 fn.apply(context, args)

(1)bind 返回的是一个新的函数,需要调用该函数才能获取结果。

let newFn = fn.bind(thisValue[, arg1[, arg2[,...]]]);
newFn();

(2)bind 的参数是散列形式,可以是两部分拼接,如下

var add = function (x, y) {
  return x * this.m + y * this.n;
}
var obj = {
  m: 2,
  n: 2
}
var newAdd = add.bind(obj, 5);  // bind的 5 是 add的第1个参数 x
console.log(newAdd(2)) // 新函数的 2 是 add的第2个参数 y
// 14

(3)源码

获取fn.apply(obj, args)需要的变量。

Function.prototype.myBind = function(context) {  
  let obj = context || window;
  const fn = this;  // 函数是单独的
  const args = [...arguments].slice(1);
  
  return function() {  
    return fn.apply(obj, args);  // 借助 apply 实现
  }
}

测试用例

let p1 = {
  name: 'Tom',
  say() {
    console.log(this.name);
  }
};
let p2 = {name: 'Jack'};

let fn = p1.say.myBind(p2);  // 改变
fn();

4、三者区别

  1. 是否立即执行函数call、apply会立即执行函数,bind则是返回了一个新函数、需要调用才能执行。

  2. 参数形式不同:除了第一个参数是要改变的对象,对于其他参数,call散列形式,apply数组形式(call的性能较高,因为不需要解析数组),bind的参数也是散列形式,但它可以是两个函数的参数拼接组成的。

  3. this 指向call apply可以通过多次不同的绑定来改变绑定对象,但bind硬绑定,也就是只能绑定一次 &&多次绑定也以第一次为准

参考文章

模拟call、apply、bind方法