# 手写函数 bind,call 和 apply
# 题目
请手写实现函数 bind
# bind 应用
- 返回一个新的函数(旧函数不会更改)
- 绑定
this和部分参数 - 箭头函数,无法改变
this,只能改变参数
function fn(a, b, c) {
console.log(this, a, b, c)
}
const fn1 = fn.bind({x: 100})
fn1(10, 20, 30) // {x: 100} 10 20 30
const fn2 = fn.bind({x: 100}, 1, 2)
fn2(10, 20, 30) // {x: 100} 1 2 10 (注意第三个参数变成了 10)
fn(10, 20, 30) // window 10 20 30 (旧函数不变)
# 解答
代码参考 bind.ts
// @ts-ignore
Function.prototype.customBind = function (context: any, ...bindArgs: any[]) {
// context 是 bind 传入的 this
// bindArgs 是 bind 传入的各个参数
const self = this // 当前的函数本身
return function (...args: any[]) {
// 拼接参数
const newArgs = bindArgs.concat(args)
return self.apply(context, newArgs)
}
}
// // 功能测试
// function fn(this: any, a: any, b: any, c: any) {
// console.info(this, a, b, c)
// }
// // @ts-ignore
// const fn1 = fn.customBind({x: 100}, 10)
// fn1(20, 30)
要点
- 返回新函数
- 拼接参数(bind 参数 + 执行参数)
- 重新绑定
this
# 单元测试
bind.test.ts
import './bind'
describe('自定义 bind', () => {
it('绑定 this', () => {
function fn(this: any) {
return this
}
// @ts-ignore
const fn1 = fn.customBind({x: 100})
expect(fn1()).toEqual({x: 100})
})
it('绑定参数', () => {
function fn(a: number, b: number, c: number) {
return a + b + c
}
// @ts-ignore
const fn1 = fn.customBind(null, 10, 20)
expect(fn1(30)).toBe(60)
})
})
# 连环问:手写函数 call 和 apply
bind 生成新函数,暂不执行。而 call apply 会直接立即执行函数
- 重新绑定
this(箭头函数不支持) - 传入参数
function fn(a, b, c) {
console.log(this, a, b, c)
}
fn.call({x: 100}, 10, 20, 30)
fn.apply({x: 100}, [10, 20, 30])
代码参考 call-apply.ts
/**
* @description 自定义 call apply
* @author
*/
// @ts-ignore
Function.prototype.customCall = function (context: any, ...args: any[]) {
if (context == null) context = globalThis
if (typeof context !== 'object') context = new Object(context) // 值类型,变为对象
const fnKey = Symbol() // 不会出现属性名称的覆盖
context[fnKey] = this // this 就是当前的函数
const res = context[fnKey](...args) // 绑定了 this
delete context[fnKey] // 清理掉 fn ,防止污染
return res
}
// @ts-ignore
Function.prototype.customApply = function (context: any, args: any[] = []) {
if (context == null) context = globalThis
if (typeof context !== 'object') context = new Object(context) // 值类型,变为对象
const fnKey = Symbol() // 不会出现属性名称的覆盖
context[fnKey] = this // this 就是当前的函数
const res = context[fnKey](...args) // 绑定了 this
delete context[fnKey] // 清理掉 fn ,防止污染
return res
}
function fn(this: any, a: any, b: any, c: any) {
console.info(this, a, b, c)
}
// // @ts-ignore
// fn.customCall({x: 100}, 10, 20, 30)
// @ts-ignore
// fn.customApply({x: 200}, [100, 200, 300])
- 使用
obj.fn执行,即可设置fn执行时的this - 考虑
context各种情况 - 使用
symbol类型扩展属性
# 单元测试
call-apply.test.ts
import './call-apply'
describe('自定义 call', () => {
it('绑定 this - 对象', () => {
function fn(this: any) {
return this
}
// @ts-ignore
const res = fn.customCall({x: 100})
expect(res).toEqual({x: 100})
})
it('绑定 this - 值类型', () => {
function fn(this: any) {
return this
}
// @ts-ignore
const res1 = fn.customCall('abc')
expect(res1.toString()).toBe('abc')
// @ts-ignore
const res1 = fn.customCall(null)
expect(res1).not.toBeNull()
})
it('绑定参数', () => {
function fn(a: number, b: number) {
return a + b
}
// @ts-ignore
const res = fn.customCall(null, 10, 20)
expect(res).toBe(30)
})
})
describe('自定义 apply', () => {
it('绑定 this - 对象', () => {
function fn(this: any) {
return this
}
// @ts-ignore
const res = fn.customApply({x: 100})
expect(res).toEqual({x: 100})
})
it('绑定 this - 值类型', () => {
function fn(this: any) {
return this
}
// @ts-ignore
const res1 = fn.customApply('abc')
expect(res1.toString()).toBe('abc')
// @ts-ignore
const res1 = fn.customApply(null)
expect(res1).not.toBeNull()
})
it('绑定参数', () => {
function fn(a: number, b: number) {
return a + b
}
// @ts-ignore
const res = fn.customApply(null, [10, 20])
expect(res).toBe(30)
})
})
注意:有些同学用 call 来实现 apply (反之亦然),这样是不符合面试官期待的。
← 手写 instanceof 手写深拷贝 →