call简单实现及原理解析
call强制转换this指向,以便访问其他作用域
let obj = {
name: 'obj'
}
function foo(){
console.log(this.name)
}
foo.call(obj) // obj
call的几种操作
let obj = {
name: 'obj'
}
function foo(){
console.log(this.name)
}
function foo2(){
console.log('foo2')
}
function bar(){
console.log('bar)
}
foo.call(obj) // obj
foo2.call() // foo2
foo2.call(bar) // foo2
foo2.call(1) // foo2
foo2.call(false) // foo2
foo2.call('string') // foo2
foo2.call.call(bar) // bar
foo2.call.call(1) // TypeError: foo2.call.call is not a function
foo2.call.call() // TypeError: foo2.call.call is not a function
foo2.call.call(obj) // TypeError: foo2.call.call is not a function
从上面的测试可以发现
- 直接调用call接收引用类型数据,当传入原始类型时无效但不会报错
- 链式调用call如果传入原始值或者不传或者传的非函数会报错
- 说明链式调用需要接收一个函数
手动实现一个call
思路:
- 首先判断第一个参数(context)是否存在
- 否,使用
globalThis(es11新增,浏览器可用window) - 是,如果是基本类型,转成对象
- 否,使用
- 把this赋值给context.fn
- 执行context.fn把剩余参数传入,此过程是把this指向目标对象
- 删除fn
- 返回执行结果
实现
Function.prototype.call = function (context, ...rest) {
context = context ? Object(context) : globalThis || window;
context.fn = this
if(typeof context.fn !== 'function'){
throw new TypeError('fn is not a function')
}
let ret = context.fn(...rest)
delete context.fn
return ret
}
call执行原理解析
- call第一个参数接收this指向的目标对象(context),剩下的是一堆参数;
- 第一个参数必须是引用类型比如functon,array,object
- 这样方便给context添加属性fn函数,把调用函数foo赋到context.fn上执行,使其能访问context作用的属性,也就是绑定了this指向;
- 如果参数是原始值,那么把其转为对象
Object(context) - 非第一个参数作为fn的参数
- 如果没传参数,分两种情况
- A 函数直接调用call
-此时context默认为window
-call内部实际调用
window.foo - B foo.call.call(bar)
- 由于Function原型上有call方法,所以函数都可以调用call
- 无论调用几次call实际只最后的执行call.call()
- 内部分两种情况
- 如果call存在context 且必须为函数
- 此时context = bar; ocntext.fn = call; 执行bar.call()内部走A
- 若果context不存在或非函数,需要执行两次
- 第一次
- 此时context = window; context.fn = call; 执行了window.call();执行第二次call
- 第二次
- 此时context = window; context.fn = window(fn此时为一个对象而不是函数)
- 执行context.fn()是会报fn不是函数
- 第一次
- 如果call存在context 且必须为函数
- A 函数直接调用call
-此时context默认为window
-call内部实际调用
- 删除fn
- 返回执行结果