0%

JS的call,bind,apply笔记

很奇怪的是项目里面很少用到 call,bind,apply 这些,感觉有必要 mark 一下,以后就不用重复查资料了

区别

三者都是用于改变函数体内 this 的指向,但是 bind 与 apply 和 call 的最大的区别是:bind 不会立即调用,而是返回一个新函数,称为绑定函数,其内的 this 指向为创建它时传入 bind 的第一个参数,而传入 bind 的第二个及以后的参数作为原函数的参数来调用原函数。

1
2
3
4
5
6
7
8
9
10
var obj = {};

function test() {
console.log(this === obj);
}

test(); //false

var testObj = test.bind(obj);
testObj(); //true

apply 和 call 都是为了改变某个函数运行时的上下文而存在的(就是为了改变函数内部 this 的指向);apply 和 call 的调用返回函数执行结果;

如果使用 apply 或 call 方法,那么 this 指向他们的第一个参数,apply 的第二个参数是一个参数数组,call 的第二个及其以后的参数都是数组里面的元素,就是说要全部列举出来;

bind 语法

func.bind(thisArg[, arg1[, arg2[, ...]]])
thisArg 当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用 new 操作符调用绑定函数时,该参数无效。
arg1, arg2, … 当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法。

call 语法

fun.call(thisArg, arg1, arg2, ...)
thisArg 在 fun 函数运行时指定的 this 值。需要注意的是,指定的 this 值并不一定是该函数执行时真正的 this 值,如果这个函数处于非严格模式下,则指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中就是 window 对象),同时值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象。
arg1, arg2, … 指定的参数列表。

apply 语法

fun.apply(thisArg, [argsArray])
thisArg 在 fun 函数运行时指定的 this 值。需要注意的是,指定的 this 值并不一定是该函数执行时真正的 this 值,如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动指向全局对象(浏览器中就是 window 对象),同时值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象。
argsArray: 一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 fun 函数。如果该参数的值为 null 或 undefined,则表示不需要传入任何参数。从 ECMAScript 5 开始可以使用类数组对象。

区别总结

当我们使用一个函数需要改变 this 指向的时候才会用到 call,apply,bind
如果你要传递的参数不多,则可以使用 fn.call(thisObj, arg1, arg2 …)
如果你要传递的参数很多,则可以用数组将参数整理好调用 fn.apply(thisObj, [arg1, arg2 …])
如果你想生成一个新的函数长期绑定某个函数给某个对象使用,则可以使用

1
2
const newFn = fn.bind(thisObj);
newFn(arg1, arg2...)

call

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

注意:如果 call 方法没有参数,或者参数为 null 或 undefined,则等同于指向全局对象。

1
2
3
4
5
6
7
8
9
10
11
window.color = "red";
var o = { color: "blue" };
function sayColor() {
alert(this.color);
}
sayColor.call(this); //red
sayColor.call(window); //red
sayColor.call();
sayColor.call(null);
sayColor.call(undefined);
sayColor.call(o); //blue

判断对象类型

1
2
3
var arr = [];
Object.prototype.toString.call(arr); // [object Array]
//把函数体Object.prototype.toString()方法内部的this,绑到arr的执行环境(作用域)

各种手写 call

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var foo = {
count: 1,
};
function bar() {
console.log(this.count);
}
bar.myCall(foo); // 1
Function.prototype.myCall = function (context) {
// 取得传入的对象(执行上下文),比如上文的foo对象,这里的context就相当于上文的foo
// 不传第一个参数,默认是window,
var context = context || window;
// 给context添加一个属性,这时的this指向调用myCall的函数,比如上文的bar函数
context.fn = this; //这里的context.fn就相当于上文的bar函数
// 通过展开运算符和解构赋值取出context后面的参数,上文的例子没有传入参数列表
var args = [...arguments].slice(1);
// 执行函数(相当于上文的bar(...args))
var result = context.fn(...args);
// 删除函数
delete context.fn;
return result;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 每个函数都可以调用call方法,来改变当前这个函数执行的this关键字,并且支持传入参数
*/
Function.prototype.myCall = function (context) {
//第一个参数为调用call方法的函数中的this指向
var context = context || global;
//将this赋给context的fn属性
context.fn = this; //此处this是指调用myCall的function

var arr = [];
for (var i = 0, len = arguments.length; i < len; i++) {
arr.push("arguments[" + i + "]");
}
//执行这个函数,并返回结果
var result = eval("context.fn(" + arr.toString() + ")");
//将this指向销毁
delete context.fn;
return result;
};

apply

apply() 方法调用一个具有给定 this 值的函数,以及以一个数组(或类数组对象)的形式提供的参数。

示例

1
2
3
4
5
6
7
var foo = {
count: 1,
};
function bar() {
console.log(this.count);
}
bar.myApply(foo); // 1

应用场景找出数组中最大或最小的元素

1
2
3
var a = [10, 2, 4, 15, 9];
Math.max.apply(Math, a); //15
Math.min.apply(null, a); //2

可以将一个类似(伪)数组的对象(比如 arguments 对象)转为真正的数组。前提:被处理的对象必须有 length 属性,以及相对应的数字键。

1
2
3
4
5
6
//接收的是对象,返回的是数组
Array.prototype.slice.apply({ 0: 1, length: 1 }); // [1]
Array.prototype.slice.apply({ 0: 1 }); // []
Array.prototype.slice.apply({ 0: 1, length: 2 }); // [1, undefined]
Array.prototype.slice.apply({ length: 1 }); // [undefined]
//(切下)[].slice(1, n),返回索引为 1 到索引为 n-1 的数组

数组追加

1
2
3
4
5
var arr1 = [1, 2, 3];
var arr2 = [4, 5, 6];
[].push.apply(arr1, arr2);
console.log(arr1); //[1, 2, 3, 4, 5, 6]
console.log(arr2); //[4, 5, 6]

数组合并

1
2
3
4
5
var arr1 = [1, 2, { id: 1, id: 2 }, [1, 2]];
var arr2 = ["ds", 1, 9, { name: "jack" }];
// var arr = arr1.concat(arr2);//简单做法
Array.prototype.push.apply(arr1, arr2);
console.log(arr1);

各种手写 apply

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Function.prototype.myApply = function (context) {
var context = context || window;
context.fn = this;
var result;
// 判断第二个参数是否存在,也就是context后面有没有一个数组
// 如果存在,则需要展开第二个参数
if (arguments[1]) {
result = context.fn(...arguments[1]);
} else {
result = context.fn();
}
delete context.fn;
return result;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* apply函数传入的是this指向和参数数组
*/
Function.prototype.myApply = function (context, arr) {
var context = context || global;
context.fn = this;
var result;
if (!arr) {
result = context.fn(); //直接执行
} else {
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push("arr[" + i + "]");
}
result = eval("context.fn([" + args.toString() + "])");
}
//将this指向销毁
delete context.fn;
return result;
};

bind

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

示例

1
2
3
4
5
6
7
8
9
var a = {
name: "Cherry",
fn: function (a, b) {
console.log(a + b);
},
};
var b = a.fn;
b.call(a, 1, 2); //立即调用该函数
b.bind(a, 1, 2)(); //手动调用(),它返回一个原函数的拷贝(新的,不是原函数),并拥有指定的this值和初始参数。

各种手写 bind

1
2
3
4
5
6
7
8
9
Function.prototype.myBind = function () {
var _this = this;
var context = [].shift.call(arguments); // 保存需要绑定的this上下文
var args = [].slice.call(arguments); //剩下参数转为数组
console.log(_this, context, args);
return function () {
return _this.apply(context, [].concat.call(args, [].slice.call(arguments)));
};
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Function.prototype.myBind = function (context) {
if (typeof this !== "function") {
throw new TypeError("Error");
}
var _this = this;
var args = [...arguments].slice(1);
// 返回一个函数
return function F() {
// 因为返回了一个函数,我们可以 new F(),所以需要判断
if (this instanceof F) {
return new _this(...args, ...arguments);
}
return _this.apply(context, args.concat(...arguments));
};
};