• 前端分享学习博客,探究前端相关技术,推动天朝前端发展,有任何问题都可以留言一起探究
  • 由于站内自链接问题,部分pre中的代码首字母使用大写以过滤筛选
  • 欢迎友链互换,还有,如果有大神请不要黑我的站点(o´・ェ・`o)
  • 如果你觉得博客还不错,请Ctrl+D收藏( *︾▽︾)

JavaScript笔记之Apply、Call and Bind

jQuery/JavaScript 薛 陈磊 1070次浏览 0个评论 扫描二维码

如题,JavaScript之Apply、Call and Bind,这些和JavaScript中的this/closure闭包关联度高,所以想透彻明白本篇,还是要明白一些关联知识的,关于closure闭包写过一篇了,关于JavaScript的this下一篇写 ,连贯起来好理解;

JavaScript中,Apply和Call经常被用来调用方法和设置this,Bind常用来在当前函数、方法中设置this,科里化函数,语法上有差异,基本功能相同;Apply和Call在ECMAScript3中出现,ECMA5提出了Bind,这三个方法相信前端开发人员经常遇到,所以也有弄清楚三者的必要;

JavaScript中的Bind

Bind方法主要是来调用重新设置this值的函数,也就是说,Bind方法就是让我们可以重新调用一个方法的同时并且改变调用方法引用的this(相对于默认的):

//<button>Get Random Person</button>
//<input type="text">
var user = {
	strings:"this is a string",
    data:[
        {name:"T. Woods", age:37},
        {name:"P. Mickelson", age:43}
    ],
    clickHandler:function (event){
        var randomNum = ((Math.random()*2|0)+1)- 1; //随机0-1的数字
        $("input").val(this.data[randomNum].name + " " + this.data[randomNum].age);
    },
    alerts:function(){
    	alert(this.strings);
    }
}
$("button").click(user.clickHandler);
//$("button").click(user.alerts);

如例子所示,当发生点击事件时,code会报错,因为事件上没有绑定this,执行事件找不到要绑定的对象就undefined了,修改这个问题也很简单,只需绑定正确对象就好了:

$("button").click(user.clickHandler.bind(user));
//$("button").click(user.alerts.bind(user));

还有另一种解决这个问题的方法,可以将一个匿名回调函数传递给click(),jQuery将this放进匿名函数绑定到按钮对象上,注意Bind方法是ECMAScript5中提出的,要在ie9以上浏览器才能使用;

回到上一个例子,Bind也可以为方法绑定其他的“this”:

var data = [{
		name: "Samantha",
		age: 12
	},
	{
		name: "Alexis",
		age: 14
	}
];
var user = {
	// local data variable
	data: [{
			name: "T. Woods",
			age: 37
		},
		{
			name: "P. Mickelson",
			age: 43
		}
	],
	showData: function(event) {
		var randomNum = ((Math.random() * 2 | 0) + 1) - 1; // random number between 0 and 1
		console.log(this.data[randomNum].name + " " + this.data[randomNum].age);
	}
};
// Assign the showData method of the user object to a variable
var showDataVar = user.showData;
showDataVar(); // Samantha 12 (from the global data array, not from the local data array)

返回了Samantha 12,并不是user中的数据,而是来自变量data,这里没有指定this,就默认检索全部变量;同理Bind this能解决这个问题:

// Bind the showData method to the user object
var showDataVar = user.showData.bind(user);

// Now the we get the value from the user object because the this keyword is bound to the user object
showDataVar(); // P. Mickelson 43

使用Bind创建新方法

这里说成创建方法,也可以说成是借鉴方法(Bind  Allows us to Borrow Methods),看个例子:

var user = {
	// local data variable
	data: [{
			name: "T. Woods",
			age: 37
		},
		{
			name: "P. Mickelson",
			age: 43
		}
	],
	showData: function(event) {
		var randomNum = ((Math.random() * 2 | 0) + 1) - 1; // random number between 0 and 1
		
		console.log(this.data[randomNum].name + " " + this.data[randomNum].age);
	}
};

// Here we have a cars object that does not have a method to print its data to the console
var cars = {
	data: [{
			name: "Honda Accord",
			age: 14
		},
		{
			name: "Tesla Model S",
			age: 2
		}
	]
}
// We can borrow the showData () method from the user object we defined in the last example.
// Here we bind the user.showData method to the cars object we just created.
cars.showData = user.showData.bind(cars);
cars.showData(); // Honda Accord 14

对象cars使用user的方法console出了自身的data(注意bind了cars),但是这样做有个问题就是为cars新加了一个方法,也许没有这样的要求,或者说cars已经有了showData的属性,这样就很容易产生混淆,这个时候就可以使用更简洁的Apply或Call;

使用Bind科里化(Curry)函数

科里化,简单的说也就是将一个函数拆分成多个单元,是使用一个函数(接受一个或者多个参数),返回一个新函数,其中一些参数已经设置(Function Currying, also known as partial function application, is the use of a function (that accept one or more arguments) that returns a new function with some of the arguments already set);看下面一个函数:

function greet(gender, age, name) {
	// if a male, use Mr., else use Ms.
	var salutation = gender === "male" ? "Mr. " : "Ms. ";
	if(age > 25) {
		return "Hello, " + salutation + name + ".";
	} else {
		return "Hey, " + name + ".";
	}
}

然后我们用Bind科里化这个greet函数,bind方法的第一个参数设置了this:

// So we are passing null because we are not using the "this" keyword in our greet function.
var greetAnAdultMale = greet.bind (null, "male", 45);
greetAnAdultMale("John Hartlove"); // "Hello, Mr. John Hartlove."

var greetAYoungster = greet.bind (null, "", 16);
greetAYoungster("Alex"); // "Hey, Alex."
greetAYoungster("Emma Waterloo"); // "Hey, Emma Waterloo."


如例当使用Bind方法进行科里化时,greet的除了最后一个参数其他的参数都被预设,我们只改了greet函数科里化出的新函数的最后一个参数,这是个很强大的功能;通过Bind科里化,我们可以显式的对要调用的方法设置this或者拷贝一个被调用对象(greet)中的方法;

JavaScript中的Apply 和 Call

上面Bind说了那么多,该到了Apply和Call了;Apply和Call也是JavaScript中常用的方法,作用也即是调用函数的时候设置新的this;特别的是,Apply是使用数组形式的参数来执行,Call是普通参数形式,其实上面也说了,这两个家伙的功能跟Bind很像,比Bind‘单纯一点’呵呵,看一个例子:

var person1 = {name: 'Marvin', age: 42, size: '2xM'};
var person2 = {name: 'Zaphod', age: 42000000000, size: '1xS'};

var sayHello = function(){
    alert('Hello, ' + this.name);
};

var sayGoodbye = function(){
    alert('Goodbye, ' + this.name);
};

//then
sayHello();
sayGoodbye();

会alert什么那?

没错,这里的sayHello和sayGoobye中的this指向的是window,而window中未定义过name,so,undefined了;

试试下面的:

sayHello.call(person1);
sayGoodbye.call(person2);

sayHello.apply(person1);
sayGoodbye.apply(person2);

上面说过了,apply和call可以设置this,所以会依次alert出:Hello, Marvin,Goodbye, Zaphod,Hello, Marvin,Goodbye, Zaphod;

这里apply和call执行着相同的功能,它们使用参数中传递的this主体上下文及作用域中运行实体代码(alert);

不同的是如果要使用多个参数来传递,比如对say方法做出一点改动:

var say = function(greeting){
    alert(greeting + ', ' + this.name);
};

say.call(person1, 'Hello');
say.call(person2, 'Goodbye');

它在第一个参数的上下文/作用域 中运行,后续的参数被传递给要使用的函数。多个参数又怎么写?

var person1 = {name: 'Marvin', age: 42, size: '2xM'};
var person2 = {name: 'Zaphod', age: 42000000000, size: '1xS'};
var update = function(name, age, size){
    this.name = name;
    this.age = age;
    this.size = size;
};

update.call(person1, 'Slarty', 200, '3xM');

是的,call传递多参数就这样累计传递就可以;而apply在接收后续参数的时候,参数的形式是数组,也就是:

update.apply(person1, ['Slarty', 200, '3xM']);

这些就是call和apply的区别,都可以被函数调用,并运行在第一个传递的参数的作用域中,不同的是多参数时的传递情况;


其他注意点:

使用Apply或Call在回调函数中设置this

// Define an object with some properties and a method
// We will later pass the method as a callback function to another function
var clientData = {
	id: 094545,
	fullName: "Not Set",
	// setUserName is a method on the clientData object
	setUserName: function(firstName, lastName) {
		// this refers to the fullName property in this object
		this.fullName = firstName + " " + lastName;
	}
}
function getUserInput(firstName, lastName, callback, callbackObj) {
	// The use of the Apply method below will set the "this" value to callbackObj
	callback.apply(callbackObj, [firstName, lastName]);
}

Apply方法对callbackObj设置了this,然后用这个this执行回调函数,所以传递给回调函数的参数将被设置在clientData对象上:

// The clientData object will be used by the Apply method to set the "this" value
getUserInput ("Barack", "Obama", clientData.setUserName, clientData);

// the fullName property on the clientData was correctly set
console.log (clientData.fullName); // Barack Obama

Apply,Call和Bind方法都用于在调用方法时设置this,并且方式各不相同,而this的运用在JavaScript语言中非常重要,我们可以根据情况有效的使用这三个基本方法;



薛陈磊的博客 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权 , 转载请注明JavaScript笔记之Apply、Call and Bind
喜欢 (1)
[905044086@qq.com]
分享 (0)
作者薛陈磊
关于作者:
非著名前端Coder,中二非文艺闷骚少年,喜欢动漫、历史、暗荣三国志和游山玩水,关注互联网发展,期待遇到更多小伙伴一起吹水玩耍;
说点什么...
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址