小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
前言
正文
首先,我们需要知道他们的区别?
首先他们的第一个参数都是要绑定的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。