在工作中经常看到有使用 call
和 apply
,ibnd
这3个方法,每次看到后都会去网上搜一下用法,然而过一段时间就又不会了,归根到底是自己没有理解他的原理,这次准备彻底弄懂,并把自己的理解总结下来。
作用主要有两点:
- 1、允许为不同的对象分配和调用属于另一个对象的函数/方法,也就是改变(或者说是扩大)函数运行的作用域,优点:对象不需要跟方法有任何耦合关系;
- 2、提供新的
this
值给当前调用的函数/方法。
# 1、fun.call(thisArg, arg1, arg2, ...)
参数 | 描述 |
---|---|
thisArg | 在fun函数运行时指定的this值。 |
arg1,arg2 | 指定的参数列表,必须一个一个的列出来 |
下面来看几个例子:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHi = function(a,b) {
console.log("this的值:" + this)
console.log("sayhi:" + this.name + ":" + this.age + ",参数的个数:" + arguments.length);
}
var p1 = new Person("小白", 2);
p1.sayHi.call();
p1.sayHi.call(null);
p1.sayHi.call(undefined);
p1.sayHi.call("1");
p1.sayHi.call(true);
var obj = {
name: "张三",
age: 21
}
p1.sayHi.call(obj);
function Teacher(name, age) {
this.name = name;
this.age = age;
}
var t1 = new Teacher("张三", 18);
p1.sayHi.call(t1, 1, 2, 3);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
输出结果如下:
结果分析:当给 fun.call()
中的 thisObj
赋值为空、null
、undefined
时,函数 fun
中的 this
值指向 window
。
下面call()函数的源码的大概实现:
Function.prototype.Call = function(context,...args) {
//注意 1 context不传或者为null时,context为window
var context = context || window;
//注意2 要保证context是引用数据类型的值
!/^(object|function)$/.test(typeof context) ? context = Object(context) : null
//注意3 创建唯一值,避免跟原对象的属性值相同
let key=Symbol("key"),result;
//改变this的指向,使得在调用fn时,fn中的this指向context
context[key] = this;
result = context[key](...args);
//用完删除,不改变原始的数据结构
delete context[key];
return result;
}
function foo(){
console.log(this);
}
var obj={c:4};
var arr=[1,2,3];
//Array
foo.Call(arr,1,2,3);
foo.call(arr,1,2,3);
//object
foo.Call(obj,1,2,3);
foo.call(obj,1,2,3);
//Number
foo.Call(1,1,2,3)
foo.call(1,1,2,3);
//Boolean
foo.Call(true,1,2,3)
foo.call(true,1,2,3);
//null
foo.es6Call(null,1,2,3)
foo.call(null,1,2,3);
//undefined
foo.Call(undefined,1,2,3)
foo.call(undefined,1,2,3);
//String
foo.Call("123",1,2,3)
foo.call("123",1,2,3);
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
调用结果如下所示:
上面代码的不足之处在于:只考虑到 Boolean
、String
、Number
、null
、undefined
和 Object
以及 Array
这几种数据类型的功能实现;
下面是一道经典的call
的面试题
var name = '珠峰培训';
function A (x, y) {
var res = x + y;
console.log(res, this.name);
}
function B (x, y) {
var res = x - y;
console.log(res, this.name);
}
B.call(A, 40, 30);//10,A
//B.call.call=>call
B.call.call.call(A, 20, 10);//NAN undefined
Function.prototype.call(A, 60, 50);// 没有输出
//Function.prototype.call.call=>call
Function.prototype.call.call.call(A, 80, 70);// NAN undefined
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 2、fun.apply(thisArg, [arguments])
参数 | 描述 |
---|---|
thisArg | 在fun函数运行时指定的this值。 |
arguments | 指定的参数数组 |
apply()
和 call()
这两个方法的用途是一样的,都是在特定的作用域中调用函数,实际上就是设置函数 fun
内的 this
对象的值。call()
和 apply()
的区别在于 call()
里传递的参数必须一个一个的列出来,apply
里传递的参数可以是数组也可以是 arguments
对象。
区别可见如下代码:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHi = function(a,b) {
console.log("a的值:"+a);
console.log("b的值:"+b);
console.log("this的值:" + this)
console.log("sayhi:" + this.name + ":" + this.age + ",参数的个数:" + arguments.length);
}
var obj={
name:"李四",
age:25
}
var p1=new Person("张三",30);
p1.sayHi.call(obj, 1, 2, 3);
p1.sayHi.call(obj, [1,2,3]);
p1.sayHi.apply(obj, [1,2,3]);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
输出结果如下:
下面 apply()
函数的源码的大概实现(缺点跟 call()
的实现是一样的。):
Function.prototype.es6Apply = function(context, arr) {
/**
* this=>fn 要执行的函数
* context=>obj 最后要改变的this
* args=>最后要传递的参数
*/
var context = context || window;
!/^(object|function)$/.test(typeOf context)?context=Object(context):null
let key=Symbol("key"),result;
context[key] = this;
//判断是否传参
if(!arr) {
result = context[key]();
} else {
if(!(Object.prototype.toString.call(arr) == "[object Array]")) {
throw new Error('CreateListFromArrayLike called on non-object');
}
result = context[key](...arr);
}
delete context[key];
return result
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 3、call()
和 apply
的常见用法
apply
常常被用于数组操作。
1、如合并两个数组,且改变原数组。
var array = ['a', 'b'];
var elements = [0, 1, 2];
array.push.apply(array, elements);
console.log(array); // ["a", "b", 0, 1, 2]
2
3
4
2、如求取数组中的最大最小值。
var numbers = [5, 6, 2, 3, 7];
var max = Math.max.apply(null, numbers);
/* 基本等同于 Math.max(5, 6, 2, 3, 7) */
var min = Math.min.apply(null, numbers);
/* 基本等同于 Math.min(5, 6, 2, 3, 7) */
2
3
4
5
6
3、实现继承
function Person(name, age) {
this.name = name;
this.age = age;
this.showName = function() {
console.log(this.name);
};
this.showAge = function() {
console.log(this.age);
}
}
function Teacher(name, age) {
Person.apply(this, [age,name]);
}
var p1=new Person("李四",25);
var t1 = new Teacher("张三", 30);
t1.showName();//"张三"
t1.showAge();//30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
调用 var p1=new Person("李四",25)
时返回的是如下对象。Person
构造函数中的 this
指的是 Person
。
调用 var 11=new Teacher("张三",30)
时,因为调用了 Person.apply(this, [age,name])
。Person
构造函数中的 this
指的是 Teacher
。所有返回的是如下对象。
Person.apply(this, [age,name])
中的this
在 new Teacher()
时指的是 Teacher
,所以 Person.apply(this, [age,name])
这句代码的返回的是一个Teacher
实例对象。
# 4、bind()
该方法会创建一个函数的实例,其 this
值会绑定到传给 bind()
函数的值。 例子如下:
window.color="red";
var o={color:"blue"};
function sayColor(){
console.log(this.color);
}
var objSayColor=sayClor.bind(o);
objSayColor();//"blue"
sayColor();//"red"
2
3
4
5
6
7
8
上面的例子相当于改变了 sayColor()
函数中 this
的引用。bind()函数的源码的实现如下:
//基础版
Function.prototype.bind=function(oThis,...args){
const fToBind=this;
return function(){
return fToBind.apply(oThis,[...args,...arguments])
}
}
//完整版
Function.prototype.bind = function(oThis,...args) {
if(typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var fToBind = this,
// fNOP = function() {},
fBound = function() {
// this instanceof fBound === true时,说明返回的fBound被当做new的构造函数调用
return fToBind.apply(this instanceof fBound ?
this :oThis,
// 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
[...args,...arguments]);
};
// 维护原型关系
// if(this.prototype) {
// // Function.prototype doesn't have a prototype property
// fNOP.prototype = this.prototype;
// }
//继承原型
this.prototype&&(fBound.prototype=Object.create(this.prototype)
// 下行的代码使fBound.prototype是fNOP的实例,因此
// 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例
// fBound.prototype = new fNOP();
return fBound;
};
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