面试官:call、apply和bind的原理?
前言
这里需要些作用域和闭包,this指向的知识。
正文
首先,我们需要知道他们的区别?
首先他们的第一个参数都是要绑定的this
。我记忆方式是: call是打电话,所以我们需要一句一句的说,对应参数就是一个一个的传。apply是应用我想到了app,当我们填写个人信息就是把全部的信息一次性的填写上去,对应就是第二参数是一个数组。bind是绑定,跟一个人绑定起来,形成了一个新个体,第二个参数嘛,之前记住只有一个是数组其他的都是一个个传就可以了。这个是我的记忆方式,一般是不适用大家,你们大家看到这几个单词想到什么就使用这个场景进行记忆
。
call
从区别我们知道call是一个个传参的。
核心代码:
Function.prototype.myCall = function (ctx, ...arg) { let context if(ctx==null || ctx==undefined) { context = window }else { context = Object(ctx) } let symbol = Symbol('特殊的标记') context[symbol] = this let result = context[symbol](...arg) delete context[symbol] return result } 复制代码
例子加核心代码:
Function.prototype.myCall = function (ctx, ...arg) { let context if(ctx==null || ctx==undefined) { context = window }else { context = Object(ctx) } let symbol = Symbol('特殊的标记') context[symbol] = this let result = context[symbol](...arg) delete context[symbol] return result } function sbThis() { console.log(this.name); return this.name } var name = '我是window' const obj = { name: '我是Obj' } sbThis.myCall(obj) // 我是Obj sbThis.myCall(null) // 我是window 复制代码
这里的疑问:
为什么需要对
null
和undefined
做判断?
答: 传入null和undefined是为了不绑定this的指向,当使用bind,可以实现柯里化的效果。
为什么this就是指
sbThis
?
隐式绑定,看段代码:
function sbThis() { console.log(this.name); return this.name } var name = '我是window' const obj = { name: '我是Obj', fn: sbThis } obj.fn() //我是Obj 复制代码
样子是类似的、就是通过obj调用的fn
,所以fn中的this就是obj。前面的代码是sbThis通过调用myCall
,所以在myCall中的this就是指向sbThis。
为什么使用
Symbol
?
答:产生出一个特殊的值,用来传入obj绑定一个特殊的属性
。
为什么要把this的绑定到
context
?
答:就是前面说的隐式绑定
,跟前面的隐式绑定的例子一样产生一个属性
,这个属性的this就是context,这里使用symbol就是以免与obj的属性产生冲突。所以我们在下面一行就是使用这个属性拿到返回值,然后就删除这个属性。
apply
apply和call是相似的就是第二个参数是一个数组:
Function.prototype.myApply = function (ctx) { let context if(ctx==null || ctx==undefined) { context = window }else { context = Object(ctx) } let symbol = Symbol('特殊的标记') context[symbol] = this let result let arg = arguments[1] // 注意点 if(arg) { // 判断有没有传第二个参数 if(!Array.isArray(arg)) { // 判断是不是数组 throw new TypeError('myApply 的第二个参数需要是数组类型') }else { let arr = Array.from(arg) result = context[symbol](...arg) } }else { result = context[symbol]() } delete context[symbol] return result } 复制代码
与call对比。多来一个对第二参数数组的判断
。
注意点:这里的第二参数是根据arguments中获取
的,因为在使用myApply的时候可能是不会传第二个参数的。
bind
bind 和 call的区别就是返回一个函数,核心代码:
Function.prototype.myBind = function (ctx,...arg) { const thisFn = this; // 存储源函数以及上方的params(函数参数) let resultFn = function (...arg2) { return thisFn.call(ctx,...arg,...arg2) } return resultFn } 复制代码
这里的注意点: 为什么需要把this赋值给thisFn?
答: 如果我们使用了this.call()
的话,由于闭包我们把resultFn的函数给返回了出去,这里有一个赋值的操作,然后我们运行的话,this指向是看使用位置(别把this和作用域搞混了
)。详情查看参考,例如:
Function.prototype.myBind = function (ctx,...arg) { const thisFn = this; // 存储源函数以及上方的params(函数参数) let resultFn = function (...arg2) { console.log(this); //window return this.call(ctx,...arg,...arg2) } return resultFn } function sbThis(name) { console.log(name); console.log(this.name); return this.name } var name = '我是window' const obj = { name: '我是Obj', fn: sbThis } let bindResult = sbThis.myBind(obj) bindResult('obj') 复制代码
这里就是我们执行bindResult,在全局的执行,这里的this就指向了window,所以我们需要存储一个thisFn用来指向sbThis函数。
记忆bind:
闭包加call的结合。
结论
先认识call、apply、bind的区别。
以区别的角度出发,填写对应的参数和返回值。
整体的全部原理都是以this的隐式绑定出发。
call:
本身this(被使用函数)绑定到要使用的对象的一个属性。(隐式绑定)
执行这个属性。
删除这个属性。
返回被使用函数的返回值。
apply:
以call的书写出发。
使用arguments来拿第二个参数
针对第二属性做判断。
是数组转成数组传入。
不是数组报错。
是就执行并传传参
没有第二参数直接执行属性。
bind:
返回一个函数用来控制call什么时候被使用(给出一个按钮一样)。
注意点返回出去的函数不能直接使用this,要使用bind绑定的那个this。
参考
作者:晓欲望
链接:https://juejin.cn/post/7015103366049038350