努力让学习成为一种习惯,自信来源于充分的准备
如果你觉得该文章对你有帮助,欢迎大家点赞、关注和分享
前言
在深入理解this机制中,我们知道了call、apply用于函数执行的时候显示绑定this。那么它内部的原理是怎么样的呢,接下来带你一步一步实现自己的call,apply
解读
function foo() {
console.log(this.a)
}
const obj = {a:1}
foo.call(obj) // 1
在实现之前,我们先从规范结合现象来分析下call具体有哪些行为表现
call规范如下(具体规范详情可以查看Function.prototype.call)
这里简单翻译下:
- 让当前函数赋值给this(这里其实就是
this的隐式绑定, 也侧面说明,函数也是一个对象)
Function.prototype.call2 = function () {
console.log(this)
}
function bar() {}
bar.call2() // ƒ bar() {}
- 如果函数不可执行,抛出异常(argumens为对象且包含内部方法[[call]]代表可执行)
- 执行
尾调用(这里与主逻辑关系不大,不详细介绍。感兴趣可以参考函数:尾调用) - 执行函数(传入函数体、this引用、函数形参)
额外有两点需要注意:
1.当thisArgs传入的是null或者undefined,在非严格模式下,this会指向全局对象(即采用了默认绑定规则),如果是基本属性类型,则会转化成对象类型(装箱)
function foo() {
console.log(this);
}
foo.call(null) // window 严格模式下为 undefined
foo.call(1) // Number {1}
2.如果函数是箭头函数或者是被bind绑定过的函数(绑定函数是一个 Exotic Function Object(怪异函数对象,ECMAScript 2015 中的术语),它包装了原函数对象。调用绑定函数通常会导致执行 包装函数),则call传入的对象会被thisArgs忽略
const foo = () => {
console.log(this); // window
}
foo.call({a:1})
var a = 7
function foo() {
console.log(this.a);
}
const _foo = foo.bind({a:1})
_foo.call({a:2}) // 1
const _foo2 = foo.bind()
_foo2.call({a:2}) // 7
好了。有关call的使用与具体细节如上,接下来让我们一步一步实现
实现
1.让this绑定call函数传入的对象
在这里,我们可以很自然的想到运用隐式绑定的规则
当函数作为一个对象方法调用的时候。函数内的this指向当前的上下文对象
Function.prototype.call2 = function (context) {
context.fn = this
context.fn()
delete context.fn
}
- 传入
null或undefined,this指向全局。传入基本类型,将其转化成对象
Function.prototype.call2 = function (context) {
if (context === null || context === undefined) {
context = window
} else {
context = Object(context)
}
// xxx
}
- 支持给函数传入参数,并支持返回值
Function.prototype.call2 = function (context) {
var _args = []
for (var index = 1; index < arguments.length; index++) {
_args.push(arguments[index])
}
context.fn = this
var result = eval('context.fn(' + _args + ')')
return result
}
好了最后我们把所有代码统一下,就是整个call函数的实现了
Function.prototype.call2 = function(context) {
if(context === null || context === undefined) {
context = window
} else {
context = Object(context)
}
context.fn = this
var _args = []
for (var index = 1, len = arguments.length; index < len; index++) {
_args.push(arguments[index])
}
var result = eval('context.fn(' + _args + ')')
delete context.fn
return result
}
apply的实现与call大同小异,这里直接给出实现
Function.prototype.apply2 = function(context) {
if(context === null || context === undefined) {
context = window
} else {
context = Object(context)
}
context.fn = this
var _args = arguments[1]
var result
if (_args) {
result = eval('context.fn(' + _args + ')')
} else {
result = context.fn()
}
delete context.fn
return result
}
结语
上面的实现用es6的语法实现会更加简洁
到这里,就是本篇文章的全部内容了
如果你觉得该文章对你有帮助,欢迎大家点赞、关注和分享
如果你有疑问或者出入,评论区告诉我,我们一起讨论