啥是call,apply,bind?
call, apply, bind
都用于改变函数的this指向
call 、apply 、 bind
这三个函数的第一个参数都是 this 的指向对象
第二个参数:
call 表示: 你把参数都按顺序写进来我一个一个读取并执行
xxx.call(obj, 参数一,参数二,参数三,…) // 会立即执行
apply 表示: 你把参数写成数组传给我,我帮你遍历并执行
xxx.apply(obj, [参数一,参数二,参数三…] ) //会立即执行
bind 表示: 我接收参数跟call一样,但是我可不直接执行,需要执行你得自己操作
xxx.bind(obj, 参数一,参数二,参数三…) //会生成一个新的函数
但凡是学JS的,肯定对这三兄弟不陌生了,这里直接上栗子🌰
const p1 = {
name: 'cxk',
duration: '两年半', // 定义方法不能用箭头函数,箭头函数没有this所以在创建时继承自上级作用域,这里是全局--window)
say: function(person,str){ // 而普通函数的this执行时确定
console.log(`${person}们${str},我是练习时长${this.duration}的个人练习生:${this.name}`)
}
}
const p2={
name: 'ctrl',
duration: '5年',
}
p1.say('全民制作人','大家好') // "全民制作人们大家好,我是练习时长两年半的个人练习生:cxk"
p1.say.call(p2,'全民制作人call','大家好') // "全民制作人call们大家好,我是练习时长5年的个人练习生:ctrl"
p1.say.apply(p2,['全面制作人apply','大家好']) // "全面制作人apply们大家好,我是练习时长5年的个人练习生:ctrl"
const bd = p1.say.bind(p2,'全民制作人bind','大家好')
bd() // "全民制作人bind们大家好,我是练习时长5年的个人练习生:ctrl"
先拿 call 来试试,其他都类似
如何着手?
在执行 p1.say.call的时候
请问 call 方法内部的this指向谁?
答: call方法由p1.say调用,所以call方法内部的this就指向p1.say这个目标函数
其实这个点我也没有理解很透彻,在call方法执行中,
//给Function的原型对象添加一个myCall的方法 这样所有普通函数都能用myCall改变this
Function.prototype.myCall = function(target,...args){ //传入执行的目标和需要的参数
target._say=this //把这个函数保存给target的_say方法
// 原型对象里面的方法以及构造函数调用时 this指向实例对象(这里p1.say就是Function.prototype的实例对象) 这个this就是p1.say这个函数
target._say(...args) //用target执行_say 函数内部的this就指向target了
}
p1.say.myCall(p2,'myCall','大家不好') // "myCall们大家不好,我是练习时长5年的个人练习生:ctrl"
思考:
以上代码有哪些缺陷?
1.每次调用myCall都会给target对象绑定一次方法,方法留在了target对象内.
2.如果恰好对象本身就有_say方法,我们却把它覆盖掉了,真很不好!
优化:
- 使用Symbol类型来保证该方法独一无二,不会覆盖target对象原有的某些方法
- 每次调用完成后删除掉挂载的方法
Function.prototype.myCall = function(target,...args){
const say = Symbol() //每次都是独一无二的
target[say]=this // Object[xx] 等同于 Object.xx
const res = target[say](...args) // 等同于 target.say(...args)
delete target[say] // 删除对象上的该方法
return res // 这里一开始没注意到,需要把执行结果返回出去
}
p1.say.myCall(p2,'myCall','大家不好') // "myCall们大家不好,我是练习时长5年的个人练习生:ctrl"
😎大功告…成?😯
现在来写 apply 原理类似
// apply 注意第二个参数需要是数组
Function.prototype.myApply = function(target,argsArray){
const say = Symbol()
target[say]=this
const res = target[say](...argsArray)
delete target[say]
return res
}
p1.say.myApply(p2,['myApply','大家好']) //"myApply们大家好,我是练习时长5年的个人练习生:ctrl"
最后是bind,这个就有点麻烦了,先看看有哪些注意点:
- 返回一个函数,即不会立即执行
- 如果调用者是一个构造函数,那么返回参数也要是构造函数
- 返回的构造函数需要实现原型继承
- 使用新返回的构造函数new一个实例时,需要继承构造函数原型对象的属性和方法
- bind调用的时候可以传参,调用之后返回的函数也可以传参
简单版本
Function.prototype.myBind = function(target,...args){
let that = this
return function(){
let newArgs = [...args,...arguments] // 拼接参数 也可以用concat
return that.apply(target,newArgs)
}
}
const bd = p1.say.myBind(p2)
bd('myBind','大家好') //"myBind们大家好,我是练习时长5年的个人练习生:ctrl"
如果调用者是构造函数呢?
构造函数中
this
指向new
创建的实例。所以可以通过在函数内判断this
是否为当前函数的实例进而判断当前函数是否作为构造函数。
Function.prototype.myBind = function(target,...args){
let that = this
// 构造函数不能是匿名函数
let Fn = function(){
let newArgs = [...args,...arguments] // 拼接参数 也可以用concat
// 判断是否作为构造函数
//通过在函数内判断 this 是否为当前函数的实例进而判断当前函数是否作为构造函数。
target = this instanceof Fn ? this : target
return that.apply(target,newArgs)
}
// 返回的构造函数要继承调用myBind的构造函数的原型对象的属性和方法
// Object.create() 这个方法把 调用此方法的构造函数的原型作为返回的构造函数的原型,实现继承
Fn.prototype = Object.create( this.prototype )
return Fn
}
const bd = p1.say.myBind(p2)
bd('myBind','大家好') //"myBind们大家好,我是练习时长5年的个人练习生:ctrl"
Object.create()
方法用于创建一个新对象,使用现有的对象来作为新创建对象的原型(prototype)
终于结束了😫
其实细节上还有很多不足,比如判断调用者是否为函数…因为只有函数能用Function.prototype上面的call,apply,bind方法
这里面还涉及到很多关于 原型对象
,原型继承
,构造函数
的重要知识,从多方面理解JavaScript较低层的原理,再回头思考这些问题,自然是迎刃而解。
这里推荐一本书
《JavaScript 编程精解(第三版)》
这本书搭配红宝书**《JavaScript高级程序设计》**一起看,并且认真去分析书中的示例,相信你会对JS这门语言理解更加到位。另外《JS编程精解》中还有很多有趣的案例(自动寻路机器人–、游戏制作–canvas相关、乌鸦互联网-Promise和http相关)