一直以来,对于JavaScript中的call,apply以及bink都不是很清楚,只是知道有这么几个东西,但都没有去仔细研究。最近在用JavaScript实现常见数据结构,看书时里面有提到call,正好借此好好研究一些这三个方法。随便一搜,网上相关的文章很多,但是越是这样大家都关注的问题,肯定说明其重要性和一定的难度。因此,对于网上参差不齐的回答,我不敢轻信,因为一开始所摄入的信息对于陌生问题在脑中的定势是非常重要的。
三者是干嘛的
要回答清楚这个问题,那就说明已经对此清楚了,显然我现在还回答不了。目前的理解是call、replay是同一个东西,而bind是另一个东西。MDN中的说明:
- The
call()allows for a function/method belonging to one object to be assigned and called for a different object. call()provides a new value ofthisto the function/method. Withcall(), you can write a method once and then inherit it in another object, without having to rewrite the method for the new object.
//调用父构造函数
function Product(name, price) {
this.name = name;
this.price = price;
}
function Food(name, price) {
Product.call(this, name, price);
this.category = 'food';
}
//同class中的继承
class Food extend Product {
constructor(name,price) {
super(name, price);
this.category = 'food';
}
}
//调用函数并指定上下文的‘this’
function greet() {
var reply = [this.animal, 'typically sleep between', this.sleepDuration].join(' ');
console.log(reply);
}
var obj = {
animal: 'cats', sleepDuration: '12 and 16 hours'
};
greet.call(obj); // cats typically sleep between 12 and 16 hours
而call和replay则是同一个东西:区别是传入参数书写的实行不一样,仅此而已。
While the syntax of this function is almost identical to that of apply(), the fundamental difference is that call() accepts an argument list, while apply() accepts a single array of arguments.
call和apply
从一个简单的例子进行说明。首先,定义一个对象obj,对象非常简单,只有一个属性num。其次,定义一个函数sum,里面可以传入一个参数a。问题在于函数计算的是this.num + a,很显然,在函数里面出现this会有问题,因为对于sum函数来说,this.num是未知的。
let obj = {num: 1};
function sum(a) {
return this.num + a;
}
let result = sum(2);
console.log(result) //NaN
我们是想算obj的num属性值(即1)加上2的和,但是但我们调用sum函数时,函数中的this.num是未知的,要解决这个问题,首先用call。
call
let result = sum.call(obj, 2);
console.log(result); //3
这里成功输出了我们想要的结果,就是obj.num的值加上我们传入的参数2,输出3。
//方式一:
let obj = {num: 1};
function sum(a) {
return this.num + a;
}
let result = sum.call(obj, 1)
//方式二:
function sum(a) {
return 1 + a;
}
let result = sum(1);
上面的两种方式的代码,方式二是我们最常见的,非常容易理解,即计算一个数加上1等于多少;实际上,方式一也是相同的效果。MND中对call的解释,The call() method calls a function with a given this value and arguments provided individually。”given this value”讲得有点不容易理解。这里暂且不去管。
let result = sum.call(obj, 1);
sum函数中没有this.num这个值,因此需要call obj对象,即“借用”obj对象中的name属性(obj.name)。但是在代码中并不是obj.num ,而是this.num,关于this,也是一个复杂的话题。
let obj = {num: 1}
function sum(a) {
return ojb.num + a
}
let result = sum(2)
这段代码显然是没有任何问题的,因为这里的obj.num对于sum函数而言是一个已知量,因为sum能够获取出于全局作用域的obj对象。
let obj = {num: 1}
function sum(a) {
return this.num + a
}
let result = sum.call(obj, 2)
这里call的作用就相当于将sum函数中的this.name变成了obj.num。call即可理解成“借用”this的num属性。因为call的是obj对象,所以this指的就是obj。一句话形象的描述上面发生的过程,我(sum函数)要计算this.num+a的值,a是我的参数,我能知道,但是里面的this.num我不知道是什么,具体来说我不知道是哪个对象的num属性。我(sum函数)通过call对象obj,找obj对象(因为他有num属性),即我借用obj对象的属性值,我就可以算出obj.num+a的值了。
apply
前面讲了,call和apple是同一个东西,因为两者只是在语法(写法)上的区别,仅此而已。
let obj = {num: 1};
function sum(a, b, c) {
return this.num + a + b + c;
}
这里我要计算多个数的和,很容易知道,sum函数知道a,b,c是什么(因为调用sum的使用要传给他),但是其中的this.num是个未知量,sum需要向其他对象“借用”,下面用call和replay来实现。
let result1 = sum.call(obj, 2, 3, 4); //1+2+3+4=10
let result2 = sum.apply(obj, [2, 3, 4]); //1+2+3+4=10
两者都是计算this.num+a+b+c的和,也都是“借用”obj对象的obj.num,两者实现的效果完全相同,只是写法上的不同而已。apply方法传入的参数是obj(借用的对象)以及一个数组
//上面的apply方法代码也可以这么写
let arr = [2, 3, 4];
let result2 = sum.apply(obj, arr];
至此,应该大概是知道call和apply是个什么东西了,即“借用”别的对象。(这种“借用”的表述不严谨,仅个人目前理解的观点)
bind
在React中采用class component时,bind (this)是必不可少的,但是此前也就照着这个样式去写,没有去深究其究竟意味着什么。先来看MND中的关于bind的说明:The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.
最重要的一点是,bind方法返回的是一个函数,这是其与call和apply的不同。
let obj = {num: 1};
function sum(a, b, c) {
return this.num + a + b + c;
}
const newFn = sum.bind(obj);
在控制台中,我们看到newFn是一个函数,当传入参数2,3,4之后,得出10(1+2+3+4),很明显,newFn这个函数中的this.num值为1,即调用newFn函数时,成功“借用”了ojb对象的num属性(obj.num=1)。
创建绑定函数
let cat = {
sound: '喵~',
talk: function () {
console.log(this.sound);
}
};
cat.talk()
//“喵~”
JavaScript中的函数是一个对象,同时也是一个值,可以被赋值给其他变量,这在JavaScript中是很正常的事情,就像下面这样操作,把cat.talk方法(函数)赋值给新的变量catTalk,此时catTalk也是一个函数。
let catTalk = cat.talk;
但执行catTalk函数时,输出的是undefined,因为catTalk函数中并没有this.sound(存在与cat对象中)。自然,这里会输入undefined。bind能够将cat对象中的sound属性绑定给函数。
let cat = {
sound: '喵~',
talk: function () {
console.log(this.sound);
}
};
catTalk = cat.talk.bind(cat);
catTalk(); //喵~