努力让学习成为一种习惯,自信来源于充分的准备
如果你觉得该文章对你有帮助,欢迎大家点赞、关注和分享
前言
在vue中,我们可以在methods里面访问vue实例:
export default {
methods: {
test() {
this.XX
},
}
}
这里有个疑问就是:test方法里面的this理论上应该是methods引用的对象。为什么会是vue实例呢?
显然vue内部做了处理,显示绑定了this为 vue实例。我们可以看下 vue源码
因此我们可以在method方法内部使用vue实例上的属性、方法
无论是日常使用的第三方库还是日常业务开发。bind的出场率都非常高,接下来让我们实现bind
解读
function bar (a, b, c) {
console.log(this.value);
console.log(a);
console.log(b);
console.log(c);
}
const foo = bar.bind({value: 'value'}, 1)
foo(2,3) // 'value'、1、2、3
在实现之前,我们先从规范结合现象来分析下bind具体有哪些行为表现
bind规范如下(具体规范详情可以查看Function.prototype.bind)
其中4-10步主要设置函数相关信息(设置长度、函数名称等),不是本文的重点,不展开讨论
底部的注意点和 call、apply一样,如果是箭头函数或者之前已经绑定过则会失效
整体流程大同小异,具体相似的细节不再描述,可以参考实现自己的call、apply
我们重点关注第3步,可以发现bind新创建了一个函数,并在最后将其作为返回值返回,我们用代码验证下
function bar() {}
const bar2 = bar.bind()
console.log(bar === bar2); // false
我们继续深入看看第三步到底做了什么
简单翻译下步骤:
- 创建一个基本对象,并设置其
prototype属性为绑定函数(targetFunction)的prototype - 设置内部属性[[call]]、[[Construct]]
- 设置内部属性[[BoundTargetFunction]]为绑定的函数
- 设置内部属性[[BoundThis]]为传入的this
- 设置内部属性[[BoundArguments]]为传入的arguments
- 返回该对象
这里可能有些晦涩难懂,我们通过一段代码打印直观的看下👀
function foo() {}
const _foo = foo.bind({a:1}, 1,2)
console.dir(foo)
console.dir(_foo)
console.log(Object.getPrototypeOf(foo) === Object.getPrototypeOf(_foo)) // true
通过打印_foo。再回头看规范,我们可以很清晰的了解了bind做了哪些事情
_foo函数的名称在原基础上加了bound前缀,并且同步设置了length- 设置_foo函数的
[[prototype]],指向绑定函数foo的[[prototype]] - 设置[[BoundThis]]为传入的对象
- 设置[[BoundArgs]]为传入的参数
到这里 bind的核心步骤相信大家已经有个清晰的概念了。可能还会有个疑惑。第二步设置[[prototype]]指向的目的是什么。别着急,下来会讲到。我们一步一步来实现bind
实现
第一步
首先bind相比于apply的最大直观区别是:bind不会立刻执行,它只是绑定this,并返回一个新的函数,将新函数的执行权交给用户,我们可以很自然的想到返回apply函数就可以了
Function.prototype.bind2 = function (context) {
var _this = this
return function () {
return _this.apply(context)
}
}
function foo() {
console.log(this.a);
}
const _foo = foo.bind({a:1})
_foo() // 1
第二步
bind函数的另一个特点是可以传入参数,作为最后调用后的参数。我们可以利用闭包的特性将最初传入的参数缓存下来
Function.prototype.bind2 = function (context) {
var args = Array.prototype.slice.call(arguments, 1)
var _this = this
return function () {
var bindArgs = args.concat(Array.prototype.slice.call(arguments))
return _this.apply(context, bindArgs)
}
}
function foo(...args) {
console.log(this.a);
console.log(args);
}
const _foo = foo.bind({a:1}, 1,2,3)
_foo(4,5) // 1,1,2,3,4,5
第三步
接下来是bind最难也是容易忽视的一个点(与call、apply最大的不同),bind绑定的函数可以作为构造函数(这也是之前提到的[[prototype]]赋值的核心原因)
我们来看个例子
const obj = {a:1}
function foo(num1, num2) {
this.num1 = num1
this.num2 = num2
}
foo.prototype.test = function() {
console.log(this.num1);
console.log(this.num2);
}
const _foo = foo.bind(obj, 1)
const i = new _foo(2)
i.test() // 1,2
console.log(obj); // {a:1}
在this绑定的优先级中:new绑定 > 显式绑定,具体可以参考深入理解this机制
一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数
我们可以拆分成两个步骤实现
_foo函数被作为构造函数调用,并且实例可以调用绑定函数原型链上的方法
Function.prototype.bind2 = function (context) {
var args = Array.prototype.slice.call(arguments, 1)
var _this = this
function fBind () {
var bindArgs = args.concat(Array.prototype.slice.call(arguments))
return _this.apply(context, bindArgs)
}
// 让fBind的原型指向绑定函数的原型
fBind.prototype = this.prototype
return fBind
}
- 如果是作为构造函数调用,原本传入bind中的上下文对象
obj会被忽略,参数仍被使用,这里的关键在于判断内部函数func是作为构造函数还是普通函数调用。从而决定传给apply的this指向
Function.prototype.bind2 = function (context) {
var args = Array.prototype.slice.call(arguments, 1)
var _this = this
function fBind () {
var bindArgs = args.concat(Array.prototype.slice.call(arguments))
return _this.apply(this instanceof fBind ? this : context, bindArgs)
}
fBind.prototype = this.prototype
return fBind
}
到这里就是bind的最终核心实现了,最后有两个细节需要调整下:
bind传入的不是函数时,应该给出报错fBind.prototype = this.prototype两者是同一个引用,如果修改了fBind的prototype那么原绑定函数的prototype也会被修改
Function.prototype.bind2 = function (context) {
if (typeof this !== "function") {
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}
var args = Array.prototype.slice.call(arguments, 1)
var _this = this
if (this.prototype) {
fNOP.prototype = this.prototype
}
fBInd.prototype = new fNOP()
function fNOP() {}
function fBind () {
var bindArgs = args.concat(Array.prototype.slice.call(arguments))
return _this.apply(this instanceof fBind ? this : context, bindArgs)
}
return fBind
}
最终版终于完成!!🎉🎉🎉
最后
到这里,就是本篇文章的全部内容了
如果你觉得该文章对你有帮助,欢迎大家点赞、关注和分享
如果你有疑问或者出入,评论区告诉我,我们一起讨论