call和apply区别,哪个性能更好?
call和apply都能够让函数执行,并指定函数执行时的this指向。区别如下:
- call的参数个数不确定,第一个参数为this指向,后面的参数需要一个一个分开来写;
- apply参数个数固定,接收两个参数,第一个参数为this指向,第二个参数为数组或者类数组对象。
- bind是给函数绑定this指向,返回一个绑定了this的函数,并不会执行函数。
性能:3个参数以内,两者性能差不多,3个参数以上,call性能更好。
需要注意:当参数为数组时,优先使用apply,也可以使用call加ES6的展开运算符来实现。
console.time可以用来测试程序的执行时间。
call
call方法使用一个指定的this值和单独给出的一个或多个参数来调用一个函数。
call模拟实现
Function.prototype.myCall = function(context) {
if(typeof this !== 'function') {
throw Error('not a function');
}
context.fn = this;
const args = [...arguments].slice(1);
const res = context.fn(...args);
delete context.fn;
return res;
}
var name = 'windowName';
const obj = {
name: 'lisi',
sayName: function() {
console.log(this);
console.log([...arguments]); // [ 1, 2, 4, 6 ]
console.log(this.name);
}
};
const fn = obj.sayName;
fn(); // windowName
fn.myCall(obj, 1, 2, 4, 6); // lisi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apply
apply方法调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数。
apply(null, [1, 2])
如果函数处于非严格模式下,则指定为null或undefined
时会自动替换为指向全局对象,原始值会被包装。
非严格模式下:
var name = 'wangwu';
const obj = {
name: 'lisi',
sayName: function() {
console.log(this); // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
console.log([...arguments]); // [ 1, 2, 3 ]
console.log(this.name); // wangwu
}
};
obj.sayName.apply(null, [1, 2]);
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
严格模式下:
"use strict";
var name = 'wangwu';
const obj = {
name: 'lisi',
sayName: function() {
console.log(this); // null
console.log([...arguments]); // [ 1, 2, 3 ]
console.log(this.name); // Uncaught TypeError: Cannot read property 'name' of null
}
};
obj.sayName.apply(null, [1, 2]);
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
"use strict";
var name = 'wangwu';
const obj = {
name: 'lisi',
sayName: function() {
console.log(this); // 12
console.log([...arguments]); // [ 1, 2, 3 ]
console.log(this.name); // undefined
}
};
obj.sayName.apply(12, [1, 2]);
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
apply模拟实现
Function.prototype.myApply = function(context) {
if(typeof this !== 'function') {
throw Error('not a function');
}
context.fn = this;
// 如果myApply有参数
console.log(arguments);
// arguments[1]本身就是数组
if (arguments[1]) {
// console.log(...arguments[1]);
// 利用展开运算符(...)将数组参数展开
return context.fn(...arguments[1]);
}
return context.fn();
}
var name = 'windowName';
const obj = {
name: 'lisi',
sayName: function() {
console.log(this);
console.log([...arguments]); // [ 3, 2, 4 ]
console.log(this.name);
}
};
const fn = obj.sayName;
// fn(); // windowName
fn.myApply(obj, [3, 2, 4]); // lisi
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
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
bind
bind方法创建一个新的函数,在bind()被调用时,这个新函数的this被bind的第一个参数指定,其余的参数将作为新函数的参数供调用时使用。
构造函数效果模拟
function test() {
this.hobbies = '篮球';
console.log(this); // test { hobbies: '篮球' }
console.log(this instanceof bindTest); // true
console.log(this.name + '--' + this.age);
}
test.prototype.work = '工人';
const obj = {
name: 'lisi',
age: 12
};
var name = 'wangwu';
var age = 12;
const bindTest = test.bind(obj);
// bindTest当作构造函数调用
// 这个时候的this已经指向了p
const p = new bindTest();
console.log(p.hobbies); // 篮球
console.log(p.work); // 工人
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
bind模拟实现
Function.prototype.mybind = function(thisArg) {
if (typeofthis !== 'function') {
throw TypeError('Bind must be called on a function');
}
// 拿到参数,为了传给调用者
const args = Array.prototype.slice.call(arguments, 1),
// 保存 this
self = this,
// 构建一个干净的函数,用于保存原函数的原型
nop = function() {},
// 绑定的函数
bound = function() {
// this instanceof nop, 判断是否使用 new 来调用 bound
// 如果是 new 来调用的话,this的指向就是其实例,
// 如果不是 new 调用的话,就改变 this 指向到指定的对象 o
return self.apply(
thisinstanceof nop ? this : thisArg,
args.concat(Array.prototype.slice.call(arguments))
);
};
// 箭头函数没有prototype,箭头函数this永远指向它所在的作用域
if (this.prototype) {
nop.prototype = this.prototype;
}
// 修改绑定函数的原型指向
bound.prototype = new nop();
return bound;
}
}
const obj = {
name: 'lisi',
sayName: function() {
console.log(this);
console.log([...arguments]); // [ 1, 2, 3 ]
return this.name;
}
};
const unboundFn = obj.sayName;
const boundFn = unboundFn.myBind(obj, 1, 2, 3);
console.log(boundFn()); // lisi
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
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