call
call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
注意:该方法的语法和作用与
apply()方法类似,只有一个区别,就是call()方法接受的是一个参数列表,而apply()方法接受的是一个包含多个参数的数组。
语法
function.call(thisArg, arg1, arg2, ...)
- thisArg:可选,在 function 函数运行时指定的 this 值,如果该函数处于 非严格模式 下,指定
null或者undefined时会替换为指向全局对象。 - 若该函数没有返回值,则返回
undefined
示例
1. 调用父构造函数的 call 方法来实现继承
下例中,使用 Food 和 Toy 构造函数创建的对象实例都会拥有在 Product 构造函数中添加的 name 属性和 price 属性,但 category 属性是在各自的构造函数中定义的。
function Product(name, price) {
this.name = name
this.price = price
}
function Food(name, price) {
Product.call(this, name, price)
this.category = 'food'
}
function Toy(name, price) {
Product.call(this, name, price)
this.category = 'toy'
}
const cheese = new Food('feta', 5)
const fun = new Toy('rebot', 40)
console.log(cheese.name, fun.price)
// feta 40
2. 使用 call 方法调用匿名函数
const animals = [
{species: 'Lion', name: 'King'},
{species: 'Whale', name: 'Fail'}
]
for(let i = 0; i < animals.length; i++) {
(function(i) {
this.print = function() {
console.log('#' + i + ' ' + this.species + ': ' + this.name)
}
this.print()
}).call(animals[i], i)
}
// #0 Lion: King
// #1 Whale: Fail
3. 使用 call 方法调用函数并指定上下文 this
function greet() {
const reply = [this.animal, ':', this.sleepDuration].join(' ')
console.log(reply)
}
let obj = {
animal: 'cats', sleepDuration: '12 and 16 hours'
}
greet.call(obj)
// cats : 12 and 16 hours
4. 使用 call 方法调用函数并且不指定第一个参数
非严格模式下,如果没有传递第一个参数,
this的值将会绑定为全局对象;在严格模式下,this的值将会是undefined
// 非严格模式
var sData = 'Wisen';
function display() {
console.log('sData value is %s ', this.sData);
}
display.call();
// sData value is Wisen
// 严格模式
'use strict';
var sData = 'Wisen';
function display() {
console.log('sData value is %s ', this.sData);
}
display.call();
// Cannot read the property of 'sData' of undefined
apply
apply() 方法调用一个具有给定 this 值的函数,以及作为一个数组(或类似数组对象)提供的参数。
注意:call()方法的作用和 apply() 方法类似,区别就是call()方法接受的是参数列表,而apply()方法接受的是一个参数数组。
语法
func.apply(thisArg, [argsArray])
- thisArg:必选。在
func函数运行时的this值。如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象。 - argsArray:可选。一个数组或者类数组对象,其中的数组元素将作为单独的参数传给
func函数。如果该参数的值为 null 或 undefined,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象
在调用一个存在的函数时,你可以为其指定一个 this 对象。 this 指当前对象,也就是正在调用这个函数的对象。
示例
1. 用 apply 将数组元素添加到两一个数组
let array = ['a', 'b']
let elements = [0, 1, 2]
array.push.apply(array, elements)
console.log(array)
// [ 'a', 'b', 0, 1, 2 ]
2. 使用 apply 和内置函数(找出数组的中的最值)
const numbers = [5, 6, 2, 3, 7]
const max = Math.max.apply(null, numbers)
const min = Math.min.apply(null, numbers)
console.log(max, min)
// 7 2
bind
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
语法
function.bind(thisArg[, arg1[, arg2[, ...]]])
- 返回值:返回有个原函数的拷贝,并拥有指定的 this 值和初始参数
示例
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 对象
// 新手可能会将全局变量 x 与 module 的属性 x 混淆
var boundGetX = retrieveX.bind(module);
boundGetX(); // 81
总结
bind和apply、call的异同
- 相同点:都立足于改变函数的
this指向 - 不同点:
call和apply会立即执行函数,bind只是绑定了函数,并不会立即执行函数call、apply因为要立即执行函数,所以第二个参数或之后的参数都是当前的真实参数,bind是“预设参数”
手写 call、apply 及 bind 函数
首先从以下几点来考虑如何实现这几个函数:
- 不传入第一个参数,那么上下文默认为
window - 改变了
this指向,让新的对象可以执行该函数,并能接受参数
1. call的实现
Function.prototype.myCall = function(context) {
// 这里的this指的是要调用的函数:fn.call(null, arg1,arg2, ...),因此对于call来讲它的this就是fn,因为fn调用了call
if (typeof this !== 'function') {
throw new TypeError('Error')
}
// 新的this
context = context || window
context.fn = this
// 去掉第一个参数:新的this
const args = [...arguments].slice(1)
const result = context.fn(...args)
delete context.fn
return result
}
分析
- 首先
context为可选参数,如果不传的话默认上下文为window - 接下来给
context创建一个fn属性,并将值设置为需要调用的函数 - 因为
call可以传入多个参数作为调用函数的参数,所以需要将参数剥离出来 - 然后调用函数并将对象上的函数删除
2. apply 的实现
Function.prototype.myApply = function(context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
context = context || window
context.fn = this
let result
// 处理参数和 call 有区别
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
分析
- 由于
apply方法接受的是一个参数数组,因此需要对参数特殊处理,其余逻辑与call类似
3. bind 的实现
bind 的实现对比其他两个函数略微地复杂了一点,因为 bind 需要返回一个函数,需要判断一些边界问题,以下是 bind 的实现
Function.prototype.myBind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
const _this = this
const 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])
}
}
分析
bind返回了一个函数,对于函数来说有两种方式调用,一种是直接调用,一种是通过new的方式,我们先来说直接调用的方式- 对于直接调用来说,这里选择了
apply的方式实现,但是对于参数需要注意以下情况:因为bind可以实现类似这样的代码f.bind(obj, 1)(2),所以我们需要将两边的参数拼接起来,于是就有了这样的实现args.concat([...arguments]) - 对于
new的情况来说,this被固化在新生成的对象上,所以对于这种情况我们需要忽略传入的this,同时可以使用this instanceof F判断新生成的对象是否是F的实例。