call() 用法
call、apply的根本作用是为了实现继承或复用方法。
继承是面向对象软件技术当中的一个概念,与多态、封装共为面向对象的三个基本特征。继承可以使得子类具有父类的属性和方法或者重新定义、追加属性和方法等。
JavaScript是面向对象的,是基于原型链的继承。
实现继承
function Product(name, price) {
this.name = name;
this.price = price;
}
Product.prototype = {
printPrice: function() {
console.log(`The ${this.name}'s price is ${this.price} yuan`)
}
}
function Food(name, price) {
Product.call(this, name, price);
this.category = 'food';
}
Food.prototype = Product.prototype
const cheese = new Food('cheese', 5)
console.log(cheese.name);
// Expected output: "cheese"
console.log(cheese.price);
// 5
cheese.printPrice()
// The cheese's price is 5 yuan
Product.call(this, name, price) 相当于在Food方法中实现this.name = name 、this.price = price。这里的 call() 调用了 Product 的构造方法,并改变了 this 指向(this 原指向 Product,这里指向 Food)。
使用class语法糖实现上述继承
class Product {
constructor(name, price) {
this.name = name
this.price = price
}
printPrice() {
console.log(`The ${this.name}'s price is ${this.price} yuan`)
}
}
class Food extends Product{
constructor(name, price) {
super(name, price)
this.category = 'food'
}
}
const cheese = new Food('cheese', 5)
console.log(cheese.name);
// Expected output: "cheese"
console.log(cheese.price);
// 5
cheese.printPrice()
// The cheese's price is 5 yuan
使用 class 语法糖可以更方便的实现私有属性和私有方法
如何实现私有属性和私有方法? 自执行函数 + 闭包
实现复用
说是复用,其实不太准确,但可以通过复用的思路来理解它。
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
greet.call(obj) 相当于 obj.greet(),但 obj 对象并没有 greet 方法。call 的作用相当于是在 obj 对象上复用了 greet 方法。如下:
var obj = {
animal: "cats",
sleepDuration: "12 and 16 hours",
greet: function () {
var reply = [this.animal, 'typically sleep between', this.sleepDuration].join(' ');
console.log(reply);
},
};
obj.greet() // cats typically sleep between 12 and 16 hours
如有更好的理解,请评论区告诉我。
匿名函数调用 call
var animals = [
{ species: 'Lion', name: 'King' },
{ species: 'Whale', name: 'Fail' }
];
for (var i = 0; i < animals.length; i++) {
(function(i) {
this.print = function() {
console.log('#' + i + ' ' + this.species
+ ': ' + this.name);
}
this.print();
}).call(animals[i], i);
}
使用匿名函数调用call,更方便我们理解。这里的匿名函数中的 this ,指向 animals[i]。
无需使用 this,调用 call
console.log(Math.max.call(undefined, 1, 100, 200))
相当于是
console.log(Math.max(1, 100, 200))
什么情况下,可以使用call
除了上面列举的用法外,还有比如:类数组对象并不能显式的调用数组方法,但是可以通过 call 调用;上述的Number 集合可以通过 call 调用 Math 方法等。
类数组对象:
const arrayLike = {
length: 5,
0: 2,
3: 4,
4: 1
}
直接使用 Array.prototype.map
arrayLike.map(a => console.log(a))
TypeError: arrayLike.map is not a function
可以用过 call 方法调用
console.log(Array.prototype.map.call(arrayLike, a => a * 2))
// [ 4, <2 empty items>, 8, 2 ]
// <2 empty items> 是因为 arrayLike 没有下标 1, 2
call,apply区别
call() 方法的语法和作用与 apply()方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组。
bind
定义
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
语法
function.bind(thisArg[, arg1[, arg2[, ...]]])
参数
-
thisArg调用绑定函数时作为
this参数传递给目标函数的值。 -
arg1, arg2, ...当目标函数被调用时,被预置入绑定函数的参数列表中的参数。
返回值
返回一个原函数的拷贝,并拥有指定的 this 值和初始参数
描述
因为bind()绑定this之后,并没有立即执行,而是返回一个原函数的拷贝。相当于是在call方法上定义了一个钩子,因为在调用绑定函数(返回值-原函数的拷贝)时,内部就是调用了call(thisArg, arguments)。其中arguments 包括预置的参数列表和调用时的参数列表。
示例
不传参数
因为 module.getX() 没有参数, 就算传了参数也没用。
this.x = 9; // 在浏览器中,this 指向全局的 "window" 对象
var module = {
x: 81,
getX: function() { return this.x; }
};
module.getX(); // 81
var retrieveX = module.getX;
retrieveX();
// 返回 9 - 因为函数是在全局作用域中调用的
// 创建一个新函数,把 'this' 绑定到 module 对象
var boundGetX = retrieveX.bind(module);
boundGetX(); // 81
预置参数
1, 2, 3为预置参数,这里不需要绑定 this 值( =null ),是因为 addArguments 函数实现没有用到 this。
function addArguments() {
return Array.from(arguments).reduce((acc, current) => acc + current)
}
const add1 = addArguments.bind(null, 1, 2, 3)
const result = add1(4, 5, 6)
console.log(result) // 21
add1(4, 5, 6) 的内部执行过程:
addArguments.call(null, 1, 2, 3, 4, 5, 6) // 21
addArguments 为了便于理解这里使用的原函数,其实是原函数的拷贝(bind() 的返回值)
配合 setTimeout
在默认情况下,使用 window.setTimeout()时,this 关键字会指向 window(或 global)对象。当类的方法中需要 this 指向类的实例时,你可能需要显式地把 this 绑定到回调函数,就不会丢失该实例的引用。
function LateBloomer() {
this.petalCount = Math.ceil(Math.random() * 12) + 1;
}
// 在 1 秒钟后声明 bloom
LateBloomer.prototype.bloom = function() {
// 这里需要改变this指向 -> LateBloomer
window.setTimeout(this.declare.bind(this), 1000);
};
LateBloomer.prototype.declare = function() {
console.log('I am a beautiful flower with ' +
this.petalCount + ' petals!');
};
var flower = new LateBloomer();
flower.bloom(); // 一秒钟后,调用 'declare' 方法
bind 与 call 、 apply的区别
- bind 与 call 、 apply 一样也会改变调用函数的this指向。
- bind并不会立即执行调用函数,而是会返回一个调用函数的拷贝,并拥有指定的
this值和初始参数。call、apply会立即执行调用函数。