js中的call(), apply()和bind()是Function.prototype下的方法,都是用于改变函数运行时上下文,最终的返回值是你调用的方法的返回值,若该方法没有返回值,则返回undefined。
call和apply方法的作用都是改变函数的执行环境,第一个参数传入上下文执行环境,然后传入函数执行所需的参数。
let a = {
value: 1
}
function getValue(name, age) {
console.log(name, age)
console.log(this.value)
}
getValue.call(a, 'xql', '18')
getValue.apply(a, ['xql', '18'])
getValue.bind(a, 'xql', '18')
// getValue.bind(a, 'xql', '18')() 执行该函数
使用 apply, 你可以继承其他对象的方法:注意这里apply()的第一个参数是null,在非严格模式下,第一个参数为null或者undefined时会自动替换为指向全局对象,apply()的第二个参数为数组或类数组。
使用方法
function Product(name, price) {
this.name = name
this.price = price
}
function Food(name, price) {
// 调用Product的call方法并将当前作用域对象this传入替换掉Product内部的this作用域对象
Product.call(this, name, price)
this.category = 'food'
}
let tempFood = new Food('cheese', 5)
console.log('输出:' + tempFood.name, tempFood.price, tempFood.category)
// 输出:cheese 5 food
这是一个简单使用 call 来实现的继承操作,在上面我们可以看到 Food 这个构造函数它自身并没有定义 name 和 price 这两个属性,重点就在 Product.call(this, name, price) 这一行代码上面,调用 Product 的 call (调用 call 方法会调用一遍自身)方法并将当前作用域对象 this 传入替换掉 Product 内部的 this 作用域对象,都知道当对象作为参数的时候都是地址传递,对任何一个引用修改都会修改到源对象,所以这里最终 new 出来的对象就有了 name 和 price 属性
接下来看一下具体的实现:
Function.prototype.imitateCall = function (context) {
// 赋值作用域参数,如果没有则默认为 window,即访问全局作用域对象
context = context || window
// 绑定调用函数(.call之前的方法即this,前面提到过调用call方法会调用一遍自身,所以这里要存下来)
context.invokFn = this
// 截取作用域对象参数后面的参数
let args = [...arguments].slice(1)
// 执行调用函数,记录拿取返回值
let result = context.invokFn(...args)
// 销毁调用函数,以免作用域污染
Reflect.deleteProperty(context, 'invokFn')
return result
}
可用imitateCall代替call
call()是apply()的一颗语法糖,作用和apply()一样,同样可实现继承,唯一的区别就在于call()接收的是参数列表,而apply()则接收参数数组。
唯参数形式不同而已,依此只需要稍微改动 imitateCall 方法即可模拟出我们的 imitateApply 方法
Function.prototype.imitateApply = function (context) {
// 赋值作用域参数,如果没有则默认为 window,即访问全局作用域对象
context = context || window
// 绑定调用函数(.call之前的方法即this,前面提到过调用call方法会调用一遍自身,所以这里要存下来)
context.invokFn = this
// 执行调用函数,需要对是否有参数做判断,记录拿取返回值
let result
if (arguments[1]) {
result = context.invokFn(...arguments[1])
} else {
result = context.invokFn()
}
// 销毁调用函数,以免作用域污染
Reflect.deleteProperty(context, 'invokFn')
return result
}
bind()的作用与call()和apply()一样,都是可以改变函数运行时上下文,区别是call()和apply()在调用函数之后会立即执行,而bind()方法调用并改变函数运行时上下文后,返回一个新的函数,供我们需要时再调用。
var Preson = {
name:'person';
getName:function(){
return this.name;
}
}
var boy ={
name:'twy'
}
//bind返回一个新函数,供以后调,this指向bind里的对象
var getName = Person.getName.bind(boy);
//现在调用
console.log(getName());//输出twy
来看一个例子深入理解:
const people = {
names: '渣渣逆天',
getName: function () {
return this.names
}
}
const temp = people.getName
console.log('输出:' + temp())
// 输出:undefined
虽然这里的 temp 方法和 getName 方法指向同一个对地址(即同一段代码块),但是这里的 temp 的调用者是 window,然而在 window 对象上找 names 属性就会发现是 undefined
绑定bind后
const people = {
names: '渣渣逆天',
getName: function () {
return this.names
}
}
const temp = people.getName,
context = temp.bind(people)
// temp的this换成people的this
console.log('输出:' + context())
// 输出:渣渣逆天
需要注意的是这里新建并被返回的方法当被执行时,绑定 bind 的原方法(temp)将被调用,并将原方法内部作用域对象替换为绑定 bind 时传入的第一个参数(people),即然如此,应该能联想到 bind 的实现离不开 call 或 apply
看看如何实现
Function.prototype.imitateBind = function (context) {
// 获取绑定时的传参
let args = [...arguments].slice(1),
// 定义中转构造函数,用于通过原型连接绑定后的函数和调用bind的函数
F = function () {},
// 记录调用函数,生成闭包,用于返回函数被调用时执行
self = this,
// 定义返回(绑定)函数
bound = function () {
// 合并参数,绑定时和调用时分别传入的
let finalArgs = [...args, ...arguments]
// 改变作用域,注:aplly/call是立即执行函数,即绑定会直接调用
// 这里之所以要使用instanceof做判断,是要区分是不是new xxx()调用的bind方法
return self.call((this instanceof F ? this : context), ...finalArgs)
}
// 将调用函数的原型赋值到中转函数的原型上
F.prototype = self.prototype
// 通过原型的方式继承调用函数的原型
bound.prototype = new F()
return bound
}
这是《JavaScript Web Application》一书中对 bind() 的实现:通过设置一个中转构造函数 F,使绑定后的函数与调用 bind() 的函数处于同一原型链上,用 new 操作符调用绑定后的函数,返回的对象也能正常使用 instanceof,因此这是最严谨的 bind() 实现
注:在 Javascript 中,多次 bind() 是无效的,只有第一次有用
不确定具体有多少参数被传入函数,选用apply();如果确定函数可接收多少个参数,并且想一目了然表达形参和实参的对应关系,用call();如果我们想要将来再调用方法,不需立即得到函数返回结果,则使用bind();
call()、apply()和bind()都是用来改变函数执行时的上下文,可借助它们实现继承;call()和apply()唯一区别是参数不一样,call()是apply()的语法糖;bind()是返回一个新函数,供以后调用,而apply()和call()是立即调用。