Call, Apply 和 Bind 都能用来改变this的指向。它们之间的区别是:
Call 和 Apply
举例 获取数组中的最大值和最小值
复制 var numbers = [ 5 , 458 , 120 , - 215 ];
var maxInNumbers = Math . max .apply (Math , numbers) , //458
maxInNumbers = Math . max .call (Math , 5 , 458 , 120 , - 215 ); //458
举例 面试题 定义一个 log 方法,让它可以代理 console.log 方法
复制 function log (){
console . log .apply (console , arguments);
};
log ( 1 ); //1
log ( 1 , 2 ); //1 2
接下来的要求是给每一个 log 消息添加一个"(app)"的前辍
复制 function log (){
var args = Array . prototype . slice .call (arguments);
args .unshift ( '(app)' );
console . log .apply (console , args);
};
Bind
bind的定义是,bind()方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。
复制 var bar = function (){
console .log ( this .x);
}
var foo = {
x : 3
}
bar (); // undefined
var func = bar .bind (foo);
func (); // 3
对比
使用举例
复制 var obj = {
x : 81 ,
};
var foo = {
getX : function () {
return this .x;
}
}
console .log ( foo . getX .bind (obj)()); //81
console .log ( foo . getX .call (obj)); //81
console .log ( foo . getX .apply (obj)); //81
三个输出的都是81,但是注意看使用 bind() 方法的,他后面多了对括号。
也就是说,区别是,当你希望改变上下文环境之后并非立即执行,而是回调执行的时候,使用 bind() 方法。而 apply/call 则会立即执行函数。
实现
Bind
参考链接
ES5 实现
复制 Function . prototype . bind2 = function (context) {
if ( typeof this !== "function" ) {
throw new Error ( "Function.prototype.bind - what is trying to be bound is not callable" );
}
var self = this ;
var args = Array . prototype . slice .call (arguments , 1 );
var fNOP = function () {};
var fBound = function () {
var bindArgs = Array . prototype . slice .call (arguments);
return self .apply ( this instanceof fNOP ? this : context , args .concat (bindArgs));
}
fNOP . prototype = this . prototype ;
fBound . prototype = new fNOP ();
return fBound;
}
ES6 实现
复制 // formerArgs 为传递给 bind 函数的第二个到之后的参数
Function . prototype . bind = function (ctx , ... formerArgs) {
let _this = this
// laterArgs 为传递给原函数的参数
return ( ... laterArgs) => {
// bind 函数的不定参数在原函数参数之前
// formerArgs 本身就是数组,可以直接调用数组的 concat 方法
// 无需借助 call 或 apply
return _this .apply (ctx , formerArgs .concat (laterArgs))
}
}
Call
复制 // 第三版
Function . prototype . call2 = function (context) {
var context = context || window; // 处理null就指向window
context .fn = this ;
// 处理参数
var args = [];
for ( var i = 1 , len = arguments. length ; i < len; i ++ ) {
args .push ( 'arguments[' + i + ']' );
}
var result = eval ( 'context.fn(' + args + ')' );
delete context .fn
return result;
}
其中
复制 var args = [];
for ( var i = 1 , len = arguments. length ; i < len; i ++ ) {
args .push ( 'arguments[' + i + ']' );
}
最终的数组为:
复制 var args = [arguments[ 1 ] , arguments[ 2 ] , ... ]
然后
复制 var result = eval ( 'context.fn(' + args + ')' );
在eval中,args 自动调用 args.toString()方法,eval的效果如 jawil所说,最终的效果相当于:
复制 var result = context .fn (arguments[ 1 ] , arguments[ 2 ] , ... );
call 第二种实现方式
复制 Function . prototype . call = function (context , ... args) {
// 检查调用```call```的对象是否为函数
if ( typeof this !== 'function' ) {
throw new TypeError ( 'not a function' )
}
// 将函数作为传入的```context```对象的一个属性,调用该函数
const fn = Symbol ()
context[fn] = this
context[fn]( ... args)
// 不要忘了调用之后删除该属性
delete context[fn]
}
Apply
复制 Function . prototype . apply = function (context , arr) {
var context = Object (context) || window;
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 + ')' )
}
delete context .fn
return result;
}
apply 第二种实现方式
复制 Function . prototype . apply = function (context , args) {
// 检查调用```apply```的对象是否为函数
if ( typeof this !== 'function' ) {
throw new TypeError ( 'not a function' )
}
// 将函数作为传入的```context```对象的一个属性,调用该函数
const fn = Symbol ()
context[fn] = this
context[fn]( ... args)
// 不要忘了调用之后删除该属性
delete context[fn]
}