手撕JS中的this显式绑定 call-apply-bind
能吃手撕牛肉,能吃手撕面包,当然能够手撕call-apply-bind
手撕call
- 快速手撕 (可搭配使用官方的call方法来验证)
Function.prototype.mycall = function(thisArg, ...args) {
var curFn = this
thisArg = thisArg === null || thisArg === undefined ? window : Object(thisArg)
thisArg.fn = curFn
var result = thisArg.fn(...args)
delete thisArg.fn
return result
}
function foo() {
console.log(this, arguments)
if(arguments.length) {
return Array.from(arguments).reduce((prev, item) => prev + item)
}
}
foo() // window
foo.mycall('abc') // String {'abc', fn: ƒ}
foo.mycall(null) // window
foo.mycall(undefined) // window
console.log(foo.mycall(Symbol('哈哈哈'), 20, 30, 40)) // Symbol {Symbol(哈哈哈), description: '哈哈哈', fn: ƒ} 90
- 剖析
Function.prototype.mycall = function() {
var curFn = this
// console.log(curFn)
curFn()
}
function aaa() {
console.log('aaa函数被执行了', this)
}
// 我们自己的call方法
aaa.mycall() // window
// 系统的call方法
aaa.call() // window
- 不是一个函数可以调用mycall方法,还有其他的任何函数都可以调用mycall方法,该怎么办?
- 在函数的原型上添加一个自己的call方法,所有的函数就都可以使用自己的call方法了
- 函数拥有了call方法并执行,但是调用call方法的那个函数的函数体没有执行(指aaa函数体),该怎么办?
- 观察aaa.mycall()是不是是一个隐式调用,可以在mycall中获取this,打印结果为aaa函数
- curFn()执行aaa函数
Function.prototype.mycall = function(thisArg) {
var curFn = this
thisArg.fn = curFn
thisArg.fn()
delete thisArg.fn
}
function aaa() {
console.log('aaa函数被执行了', this)
}
// 我们自己的call方法
aaa.mycall({name: 'ywq'}) // {name: 'ywq', fn: ƒ}
// 系统的call方法
aaa.call({name: 'ywq'}) // {name: 'ywq'}
- 系统的call方法,第一个参数是指定this指向的,思考一下,该怎么修改this指向呢?
- 猜对了,进行隐式绑定,this为传递过来的thisArg。执行一下代码,试试吧
- 代码执行完成后,是不是无故在内存中开辟了一个新的空间,我们来进行删除
Function.prototype.mycall = function(thisArg) {
var curFn = this
thisArg.fn = curFn
thisArg.fn()
delete thisArg.fn
}
function aaa() {
console.log('aaa函数被执行了', this)
}
// 我们自己的call方法
aaa.mycall({name: 'ywq'}) // {name: 'ywq', fn: ƒ}
aaa.mycall([1, 2, 3]) // [1, 2, 3, fn: ƒ]
aaa.mycall(function bbb(){}) // ƒ bbb(){}
// aaa.mycall(123) // Uncaught TypeError: thisArg.fn is not a function
// aaa.mycall('哈哈哈') // Uncaught TypeError: thisArg.fn is not a function
// aaa.mycall(false) // Uncaught TypeError: thisArg.fn is not a function
// aaa.mycall(Symbol('符号')) // Uncaught TypeError: thisArg.fn is not a function
// aaa.mycall(null) // {fn: ƒ}
// aaa.mycall(undefined) // {fn: ƒ}
// 系统的call方法
aaa.call({name: 'ywq'}) // {name: 'ywq'}
aaa.call([1, 2, 3]) // [1, 2, 3]
aaa.call(function bbb(){}) // ƒ bbb(){}
aaa.call(123) // Number {123}
aaa.call('哈哈哈') // String {'哈哈哈'}
aaa.call(false) // Boolean {false}
aaa.call(Symbol('符号')) // Symbol {Symbol(符号), description: '符号'}
aaa.call(null) // window
aaa.call(undefined) // window
- 我们来验证一下传入其他类型的绑定值,与系统的call是否一致。发现有的报错了,该怎么办?
- 看报错信息,不是一个函数。哦,我们传入的大多都是一些基本数据类型报了这个错误,那使用Object() 或者 new Object()使thisArg成为对象,就可以添加方法并调用了
Function.prototype.mycall = function(thisArg, ...args) {
var curFn = this
thisArg = thisArg === null || thisArg === undefined ? window : Object(thisArg)
thisArg.fn = curFn
var result = thisArg.fn(...args)
delete thisArg.fn
return result
}
function aaa() {
console.log('aaa函数被执行了', this)
}
// 我们自己的call方法
aaa.mycall(null) // 没有将thisArg转换为对象的结果是{fn: ƒ}
aaa.mycall(undefined) // 没有将thisArg转换为对象的结果是{fn: ƒ}
aaa.mycall(0, 10, 20, 30) //
// 系统的call方法
aaa.call(null) // window
aaa.call(undefined) // window
aaa.call(0, 10, 20, 30)
- 观察当this绑定的是null/undefined时,系统中的this指向window,我们自己的实现的call和系统的不一样,该怎么办呢?
- 将null/undefined进行特殊处理
- 系统中call还有其他的参数,我们该怎么做?
- 使用剩余参数(...),获取全部的参数,传入函数并得到结果进行返回
手撕apply
- 快速手撕 (可搭配使用官方的apply方法来验证)
Function.prototype.myapply = function(thisArg, arrArgs = []) {
var curFn = this
thisArg = thisArg === null || thisArg === undefined ? window : Object(thisArg)
thisArg.fn = curFn
var result = thisArg.fn(...arrArgs)
delete thisArg.fn
return result
}
function foo() {
console.log(this)
if(arguments.length) {
return Array.from(arguments).reduce((prev, item) => prev + item)
}
}
foo() // window
foo.myapply() // window
foo.myapply(null) // window
foo.myapply(undefined) // window
foo.myapply(123) // Number {123, fn: ƒ}
foo.myapply({ No: 1 }) // {No: 1, fn: ƒ}
console.log(foo.myapply(true, [1, 3, 9, 12, 7])) // Boolean {true, fn: ƒ} 32
- 剖析 apply和call方法的实现相同,唯一不同的是传入参数的区别。所以下面只解析不同的部分,相同的部分参考上面实现的call方法
Function.prototype.myapply = function(thisArg, arrArg) {
var curFn = this
thisArg = thisArg === null || thisArg === undefined ? window : Object(thisArg)
thisArg.fn = curFn
var result = thisArg.fn(...arrArg)
delete thisArg.fn
return result
}
区别就是传入的参数。除了this绑定,剩余的参数,apply方法传入的是一个数组,因此,在内部执行调用apply方法的函数时,将arrArg进行解构
手撕bind
- 快速手撕 (可搭配使用官方的apply方法来验证)
Function.prototype.mybind = function(thisArg, ...args) {
var curFn = this
thisArg = thisArg === null || thisArg === undefined ? window : Object(thisArg)
return function(...restArgs) {
thisArg.fn = curFn
var allArgs = [...args, ...restArgs]
var result = thisArg.fn(...allArgs)
delete thisArg.fn
return result
}
}
function foo() {
console.log(this)
if(arguments.length) {
return Array.from(arguments).reduce((prev, item) => prev + item)
}
}
foo.mybind()() // Window
foo.mybind(null)() // Window
foo.mybind(undefined)() // Window
foo.mybind(function bar() {}, 345)() // ƒ bar() {}
console.log(foo.mybind({}, 10, 20, 30)(100, 2)) //{fn: ƒ} 162
console.log(foo.mybind({})(10, 20, 30, 100, 2)) //{fn: ƒ} 162
- 剖析 bind方法和call/apply方法的实现类似,相同部分不再解析。详细看上面实现的call方法。
Function.prototype.mybind = function() {
return function() {
}
}
- 系统的bind方法,我们在获取结果时,需要再次执行一下。那我们该怎么办?
- 在函数内部再返回一个函数不就可以啦
Function.prototype.mybind = function(thisArg, ...args) {
var curFn = this
thisArg = thisArg === null || thisArg === undefined ? window : Object(thisArg)
return function() {
thisArg.fn = curFn
var result = thisArg.fn()
delete thisArg.fn
return result
}
}
- 我们可以将这段代码,自己写一个函数套用一下,看是否和系统一样。最终发现,系统的bind方法,二次调用的时候,也可以传入参数。那我们该怎么办呢
Function.prototype.mybind = function(thisArg, ...args) {
var curFn = this
thisArg = thisArg === null || thisArg === undefined ? window : Object(thisArg)
return function(...restArgs) {
thisArg.fn = curFn
var result = thisArg.fn(...[...args, ...restArgs])
delete thisArg.fn
return result
}
}
- 在返回的那个函数中,获取二次调用的参数(...restArgs)。在内部执行调用bind方法的函数时,将我们传递的参数进行合并并进行结果,获取结果并返回