Call, Apply, Bind

语法

func.call(obj, param1, param2, ...)

func.apply(obj, [param1,param2,...])

func.bind(obj, param1, param2, ...)

返回值

call / apply:返回func 执行的结果 ;
bind:返回func的拷贝,并拥有指定的this值和初始参数。

作用

借助已实现的方法,改变函数执行时的this指向,减少重复代码,节省内存。

call与apply的唯一区别:给func传参的方式不同

apply是第2个参数,这个参数是一个类数组对象。
call从第 2~n 的参数都是传给func的。

call/apply 与 bind 的区别

call/apply改变了函数的this的指向并马上执行该函数;
bind则是返回改变了this指向后的函数,不执行该函数。

原生JavaScript实现call()

  • 实现思路
  1. 需要设置一个参数obj,也就是this的指向;
  2. 将obj封装为一个Object;
  3. 为obj创建一个临时方法,这样obj就是调用该临时方法的对象了,临时方法的this隐式指向到obj上
  4. 执行obj的临时方法,并传参
  5. 删除临时方法,返回方法的执行结果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

Function.prototype.myCall = function(obj, ...args) {
// 1.判断参数合法性
if (obj === null || obj === undefined) {
//指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
obj = window;
} else {
obj = Object(obj);//创建一个可包含数字/字符串/布尔值的对象
}

// 2.搞定this的指向

//利用Symbol()创建一个不重复的常量,以免覆盖obj原有的属性
const specialMethod = Symbol();
// 如果调用myCall的函数名是func,即以func.myCall()形式调用;
// 那么myCall函数体内的this指向func

//给obj对象建一个临时属性来储存this(也即func函数)
obj[specialMethod] = this;
// func作为obj对象的一个方法被调用,那么func中的this指向obj对象
let result = obj[specialMethod](...args);
//3.收尾
delete obj[specialMethod]; //删除临时方法
return result; //返回临时方法的执行结果
};

测试:

1
2
3
4
5
6
7
8
9
10
11

let obj = {
name: "hello"
};

function func() {
console.log(this.name);
}

func.myCall(obj);//>> hello

原生JavaScript实现apply()

  • 类数组
    它的特征有:
  1. 可以通过索引(index)调用,如 array[0];
  2. 具有长度属性length;
  3. 可以通过 for 循环或forEach方法,进行遍历。
    eg.
1
2
3
4
5
6
7
8

let arrayLike = {
0: 1,
1: 2,
2: 3,
length: 3
};

  • 实现思路
    只有传递给函数的参数处理,不太一样,第二个参数为类数组对象。其他部分跟call一样;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

Function.prototype.myApply = function(obj) {
if (obj === null || obj === undefined) {
obj = window;
} else {
obj = Object(obj);
}

//判断是否为【类数组对象】
function isArrayLike(o) {
if (
o && // o不是null、undefined等
typeof o === "object" && // o是对象
isFinite(o.length) && // o.length是有限数值
o.length >= 0 && // o.length为非负值
o.length === Math.floor(o.length) && // o.length是整数
o.length < 2**32 // o.length < 2^32
) return true;
else return false;
}

const specialMethod = Symbol();
obj[specialMethod] = this;
// 获取第二个参数,即数组(或类数组)
let args = arguments[1];
let result;

// 处理传进来的第二个参数
if (args) {
// 是否传递第二个参数
if (!Array.isArray(args) && !isArrayLike(args)) {
throw new TypeError(
"第二个参数既不是数组,也不是类数组对象。抛出错误"
);
} else {
// 转为数组
args = Array.from(args);

// 执行函数并展开数组,传递函数参数
result = obj[specialMethod](...args);
}
} else {
result = obj[specialMethod]();
}

delete obj[specialMethod];
return result; // 返回函数执行结果
};

原生Javascript实现bind()

  • bind() 特点
  1. 被函数调用
  2. 返回一个新函数
  3. 可以给新函数传递参数
  4. 修改函数运行时this的指向
  5. 新函数作为构造函数时,this 指向实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Function.prototype.myBind = function (obj, ...params) {
// 调用myBind()的对象不是函数,则抛出异常
if (typeof this !== "function") {
throw new TypeError(
"Function.prototype.bind was called on which is not a function"
);
}
const tempFn = this; //用一个变量临时存储原函数,以及函数参数(上方的params)
//对返回的新函数 secondParams 二次传参
let copiedFunc = function (...secondParams) {
// 若是new调用的,原函数this指向新函数中创建的实例对象
// 若不是new调用,依然是调用myBind时,传递的第一个参数
const isNew = this instanceof copiedFunc;

//new调用就绑定到this上,否则就绑定到传入的obj上
const thisArg = isNew ? this : Object(obj);

// 用call调用临时存储的原函数,绑定this的指向,并传递参数,返回执行结果。
return tempFn.call(thisArg, ...params, ...secondParams);
};

//通过Object.create,以原函数prototype为原型,创建新对象。
//使新函数继承原函数原型上的属性
copiedFunc.prototype = Object.create(tempFn.prototype);
return copiedFunc;//返回拷贝的函数
};

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function func(name){
console.log(this); // {a: 1}
this.name = name;
}

func.prototype.hello = function(){
console.log(this.name); // undefined
}


let newFunc = func.myBind({a:1});
let o = new newFunc('lily');

o.hello();
// 打印:{name:'lily'}
// 打印:'lily'
查看评论