call

概念

MDNcall()方法的解释是:

call()方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数

也就是说:一个对象可以使用call()来调用另一个对象的方法,相当于设置被调用方法内this的值。可以扩充函数的作用域

1
2
3
4
5
6
7
8
9
10
11
12
13
var color = 'red';

var obj = {
color: 'green'
};

function showColor(color) {
this.color = color || this.color;
console.log(this.color);
}

showColor(); // => red
showColor.call(obj, 'blue'); // => blue

call的语法

1
func.call(thisArg, arg1, arg2, ...)
  • thisArg
    func运行时指定的this值。如果这个函数在非严格模式下运行,则指定为 nullundefinedthis值会自动指向全局对象),同时值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象。

  • arg1, arg2, ...

    指定的参数列表

实现方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Function.prototype.myCall = function (context) {
context = context || window;
context.func = this;

let args = [];
for (let i = 1; i < arguments.length; i++) {
args.push(arguments[i]);
}
context.func(...args)
delete context.func;
}

// ES5可以用eval()实现
Function.prototype.myCall = function (context) {
context = context || window;
context.func = this;

var args = [];
for (var i = 1; i < arguments.length; i++) {
args.push('arguments[' + i + ']');
}
eval('context.func(' + args + ')')
delete context.func;
}

用法

使用call()方法调用父构造函数实现继承

可以通过调用父构造函数的call()方法来实现继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Product(name, price) {
this.name = name;
this.price = price;
this.showPrice = function () {
console.log(this.price);
}
}

function Food(name, price) {
Product.call(this, name, price);
this.category = 'food';
}

var apple = new Food('apple', 5);
apple.showPrice(); // => 5

使用call()方法调用匿名函数

1
2
3
4
5
6
7
8
9
10
var objArr = [
{ index: 0, name: 'a' },
{ index: 1, name: 's' }
];

for (let i = 0; i < objArr.length; i++) {
(function(i) {
console.log(i + ': ' + this.name);
}).call(objArr[i], i);
}

apply

call()方法的作用和apply()方法类似,区别就是call()方法接受的是参数列表,而apply()方法接受的是一个参数数组

语法

1
func.apply(thisArg, [argsArray])
  • thisArg

    func函数运行时使用的this值。如果这个函数处于非严格模式下,则指定为nullundefined时会自动替换为指向全局对象,原始值会被包装。

  • argsArray

    可选的。一个数组或者类数组对象,其中的数组元素将作为单独的参数传给func函数。如果该参数的值为nullundefined,则表示不需要传入任何参数,可以使用类数组对象。

1
2
3
4
5
function add(x, y, z) {
console.log(x + y + z);
}

add.apply(this, [1, 2, 3])

实现方法:

1
2
3
4
5
6
7
8
9
10
11
Function.prototype.myApply = function (context, arr) {
context = context || window;
context.func = this;

if (!arr) {
context.func();
} else {
context.func(...arr);
}
delete context.func;
}

用途

将数组转换成列表

类似于ES6的...

1
2
3
4
5
var arr1 = [1, 2],
arr2 = [3, 4];

arr1.push(...arr2) // => [1, 2, 3, 4]
arr1.push.apply(arr1, arr2) // => [1, 2, 3, 4]

类似的,也可以用在Math.max()Math.min()等方法上

bind

bind()方法创建一个新的函数实例,其this值会被绑定到传给bind()函数的值

1
2
3
4
5
6
global.x = 1;
var obj = { x: 10 };

(function () {
console.log(this.x);
}.bind(obj))(); // => 10

语法

1
function.bind(thisArg[, arg1[, arg2[, ...]]])
  • thisArg

    调用绑定函数时作为this参数传递给目标函数的值。 如果使用new运算符构造绑定函数,则忽略该值

  • arg1, arg2, …

    当目标函数被调用时,预先添加到绑定函数的参数列表中的参数

实现方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Function.prototype.myBind = function(obj) {
var self = this,
boundArgs = arguments;

return function() {
var args = [], i;
for (i = 1; i < boundArgs.length; i++) {
args.push(boundArgs[i]);
}
for (i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}
return self.apply(obj, args);
}
}

用途

创建绑定函数

bind()可以创建一个函数,不论怎么调用,这个函数都有同样的this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
global.x = 1;
var obj = {
x: 10,
getX: function() {
console.log(this.x);
}
};

obj.getX();

var oneGetX = obj.getX;
oneGetX();

var anotherGetX = obj.getX.bind(obj);
anotherGetX();

让函数拥有预设的参数

bind()可以使一个函数拥有预设的初始参数,只要将这些参数作为bind()的参数写在this后面

1
2
3
4
5
6
7
8
9
10
11
function list() {
return Array.prototype.slice.call(arguments);
}

console.log(list(1, 2, 3)); // => [1, 2, 3]

// 创建一个函数,它拥有预设参数列表。
var anotherList = list.bind(null, 10);

console.log(anotherList()); // => [10]
console.log(anotherList(1, 2, 3)); // => [10, 1, 2, 3]

与setTimeout一起使用

正常情况下,调用setTimeout的时候this会指向全局对象,但是使用类的方法时我们需要指向类的实例,所以要把this绑定到回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function f() {
this.x = 1;
}

f.prototype.say1 = function () {
window.setTimeout(this.show.bind(this), 2000);
}
f.prototype.say2 = function () {
window.setTimeout(this.show, 2000);
}

f.prototype.show = function () {
console.log(this.x);
}

var obj = new f();
obj.say1(); // => 2s后,1
obj.say2(); // => 2s后,undefined