箭头函数的This指向
更新 2017年11月26号
前言
楼主在昨天在看Vue文档的时候,主要到methods
和computed
里面不要使用箭头函数,去看了下源码解析,发现里面调用的是通过自定义的bind
函数,通过call()
来执行函数以及绑定作用域,想巩固一下箭头函数,于是这篇有内涵的blog就上线了。
之前楼主有一篇箭头函数的This, 对于它的理解感觉有偏差,这里全部再重复总结一遍。
看完本篇文章,你可以彻底了解this和bind
涉及知识点
- bind函数的深入了解解析
- 作用域
- thisArg
实现一个bind函数
var fn = function(a,b,c,d) {
return a+b+c+d ;
}
fn.bind(scope,a,b)(c,d)
// 调用方式 var fn1 = fn.bind(scope,a,b) fn1(c, d)
// scope是传递进来的this
Function.prototype.bind = function(scope) {
let newFn = this;
// 获取通过bind传递的参数
let args = Array.prototype.slice.call(arguments,1)
let fbind = function() {
// 这里执行函数,通过闭包绑定了this===scope
// 然后通过concat合并2个参数
return newFn.apply(scope,args.concat(Array.prototype.slice.call(arguments)))
}
return fbind
}
调用方式:
- 直接调用 (内部this一般是window)
- 构造函数的调用,通过bind绑定的this无效 (this是实例对象)
当使用构造函数的时候,我们需要构建原型链,所以需要加工一下。
Function.prototype.bind = function(scope) {
if( typeof this !== 'function') {
throw('this is illegal')
}
let newFn = this;
// 获取通过bind传递的参数
let args = Array.prototype.slice.call(arguments,1)
let fbind = function() {
// 里面的this可能是window和构造函数实例
// 然后通过concat合并2个参数
return newFn.apply(this instanceof fbind ? this : scope ,args.concat(Array.prototype.slice.call(arguments)))
}
// 维持原型链
if(this.prototype) {
fbind.prototype = this.prototype
}
return fbind
}
//
function Fn(a,b) {
this.a = a
this.b = b
}
Fn.prototype = function() { console.log(this)}
let hcc = Fn.bind({a:1})
// 直接调用
hcc() // 返回一个新函数fbind, fbind里面的this则是window
// 所以 this instanceof fbind 为 false
// 构造函数的调用
let obj = new hcc() // fbind里面的this则是fbind的实例
// 所以 this instanceof fbind 为 true ,所以改变this没有生效
解析 : 当我们调用bind()的时候,即执行了var fn1 = fn.bind({name: 1},1,3)
, 会返回一个新的函数,下面是作用域链解析。
实例 : 当我们调用bind()的时候,即执行了var fn1 = fn.bind({name: 1},1,3)
, 会返回一个新的函数,下面是作用域链解析。
bind => function(scope) {
let newFn = fn;
let args = [1,3]
let fbind = function() {
return newFn.apply(scope,[1,3].concat(Array.prototype.slice.call(arguments)))
}
return fbind
}
fn1 => function() {
return fn.apply({name: 1},[1,3].concat(Array.prototype.slice.call(arguments)))
}
再调用fn1(3,4),相当于执行函数
fn.apply({name: 1},[1,3].concat(Array.prototype.slice.call(arguments)))
// 即相当于这样执行
fn.apply({name: 1},[1,3,3,4]) => 11
思考,下面函数执行时多少,this是什么
var fn = function(a,b) {
console.log(this)
return a+b ;
}
fn.bind({name:1},1,2).call({name:2},3,4)
答案 {name:1} 3
//fn.bind({name:1},1,2) 返回xxx
function xxx() {
return fn.apply({name: 1},[1,2].concat(Array.prototype.slice.call(arguments)))
}
// xxx.call({name:2},3,4) 调用 xxx(绑定了xxx的this= {name:2})
// xxx里面通过apply调用已经制定了this的fn函数
fn.apply({name: 1},[1,2,3,4]) // this => {name: 1} a=1, b=2
所以当我们执行fn.bind({name:1},1,2).call({name:2},3,4)
,本质上call
并不能改变bind的返回函数的this,只是改变了内部封装了一个函数(xxx)的this,这也是bind的this参数不能被重写的原因。
总结bind函数到底做了什么
fun.bind(thisArg[, arg1[, arg2[, ...]]])
// 简化版
Function.prototype.bind = function bind(self) {
return function() { return fn.apply(self) }
}
当一个函数(fn)使用函数原型链上面的bind函数的时候,传递
this
(thisArg)和参数进去,返回的是一个新函数(xxx),新函数内部调用的是通过apply
调用原来的函数(fn)并制定原函数(fn)的this。用简单的代码表示就是:
function fn(a,b) {
return a+b;
}
fn.bind({name:1},1,3) 相当于变成这样=> function xxx() {
return fn.apply({name: 1},Array.prototype.slice.call(arguments));
}
箭头函数的this(定义时候的this)
一句话总结: 箭头函数的函数体内的this
就是定义时候的this
,和使用所在的this没有关系。
即:在定义箭头函数的时候就已经绑定了this,可以理解为就是在定义的时候,通过bind函数进行强行绑定this。
案例一
var calculate = {
array: [1, 2, 3],
sum:() => {
console.log(this === window) // => true
}
};
// 当我们在定义sum是一个箭头函数的时候,还没有执行,内部已经绑定了this,而此时的this就是全局的window
//可以转换成
var calculate = {
array: [1, 2, 3],
sum:function() {
console.log(this === window) // => true
}.bind(this)
};
案例二
var calculate = {
array: [1, 2, 3],
sum() => {
return () => {
console.log(this === window)
}
}
};
// 此时我们在写calculate.sum的时候,由于还没有执行,所有并不存在里面的箭头函数,当我们执行calculate.sum()才算生成了箭头函数,箭头函数就是在这个时候绑定this的,所有这里就会和怎么调用sum函数有关系了。
案例三
const App = new Vue({
el: '#app',
methods: {
foo: () => {
console.log(this) // undefined
}
}
})
// 如果我们在Vue的实例中的methods使用箭头函数,那么在定义的时候,箭头函数会自动绑定当前作用域的this,并不会是绑定实例中的this
// 初始化的时候,执行的initMethods中绑定了this(vm)
function initMethods (vm: Component) {
const methods = vm.$options.methods
if (methods) {
for (const key in methods) {
vm[key] = methods[key] == null ? noop : bind(methods[key], vm)
if (process.env.NODE_ENV !== 'production' && methods[key] == null) {
warn(
`method "${key}" has an undefined value in the component definition. ` +
`Did you reference the function correctly?`,
vm
)
}
}
}
}
//bind
function bind (fn, ctx) {
function boundFn (a) {
var l = arguments.length;
return l
? l > 1
? fn.apply(ctx, arguments)//通过返回函数修饰了事件的回调函数。绑定了事件回调函数的this。并且让参数自定义。更加的灵活
: fn.call(ctx, a)
: fn.call(ctx)
}
// record original fn length
boundFn._length = fn.length;
return boundFn
}
总结:我们知道Vue内部调用methods的时候,通过的call
方法来执行methods中的相应的key函数,当我们使用箭头函数的时候,定义的时候就绑定了this,它源码中写的call()
并不会被使用,所以必须不能使用箭头函数