箭頭函數(shù)的This指向
更新 2017年11月26號(hào)
前言
樓主在昨天在看Vue文檔的時(shí)候腿准,主要到methods
和computed
里面不要使用箭頭函數(shù),去看了下源碼解析拾碌,發(fā)現(xiàn)里面調(diào)用的是通過自定義的bind
函數(shù)吐葱,通過call()
來執(zhí)行函數(shù)以及綁定作用域,想鞏固一下箭頭函數(shù)校翔,于是這篇有內(nèi)涵的blog就上線了弟跑。
之前樓主有一篇箭頭函數(shù)的This, 對(duì)于它的理解感覺有偏差,這里全部再重復(fù)總結(jié)一遍防症。
看完本篇文章孟辑,你可以徹底了解this和bind
涉及知識(shí)點(diǎn)
- bind函數(shù)的深入了解解析
- 作用域
- thisArg
實(shí)現(xiàn)一個(gè)bind函數(shù)
var fn = function(a,b,c,d) {
return a+b+c+d ;
}
fn.bind(scope,a,b)(c,d)
// 調(diào)用方式 var fn1 = fn.bind(scope,a,b) fn1(c, d)
// scope是傳遞進(jìn)來的this
Function.prototype.bind = function(scope) {
let newFn = this;
// 獲取通過bind傳遞的參數(shù)
let args = Array.prototype.slice.call(arguments,1)
let fbind = function() {
// 這里執(zhí)行函數(shù)奈嘿,通過閉包綁定了this===scope
// 然后通過concat合并2個(gè)參數(shù)
return newFn.apply(scope,args.concat(Array.prototype.slice.call(arguments)))
}
return fbind
}
調(diào)用方式:
- 直接調(diào)用 (內(nèi)部this一般是window)
- 構(gòu)造函數(shù)的調(diào)用袄膏,通過bind綁定的this無效 (this是實(shí)例對(duì)象)
當(dāng)使用構(gòu)造函數(shù)的時(shí)候,我們需要構(gòu)建原型鏈心赶,所以需要加工一下荔燎。
Function.prototype.bind = function(scope) {
if( typeof this !== 'function') {
throw('this is illegal')
}
let newFn = this;
// 獲取通過bind傳遞的參數(shù)
let args = Array.prototype.slice.call(arguments,1)
let fbind = function() {
// 里面的this可能是window和構(gòu)造函數(shù)實(shí)例
// 然后通過concat合并2個(gè)參數(shù)
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})
// 直接調(diào)用
hcc() // 返回一個(gè)新函數(shù)fbind, fbind里面的this則是window
// 所以 this instanceof fbind 為 false
// 構(gòu)造函數(shù)的調(diào)用
let obj = new hcc() // fbind里面的this則是fbind的實(shí)例
// 所以 this instanceof fbind 為 true ,所以改變this沒有生效
解析 : 當(dāng)我們調(diào)用bind()的時(shí)候婉商,即執(zhí)行了var fn1 = fn.bind({name: 1},1,3)
, 會(huì)返回一個(gè)新的函數(shù),下面是作用域鏈解析筷狼。
實(shí)例 : 當(dāng)我們調(diào)用bind()的時(shí)候俏险,即執(zhí)行了var fn1 = fn.bind({name: 1},1,3)
, 會(huì)返回一個(gè)新的函數(shù),下面是作用域鏈解析航瞭。
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)))
}
再調(diào)用fn1(3,4),相當(dāng)于執(zhí)行函數(shù)
fn.apply({name: 1},[1,3].concat(Array.prototype.slice.call(arguments)))
// 即相當(dāng)于這樣執(zhí)行
fn.apply({name: 1},[1,3,3,4]) => 11
思考,下面函數(shù)執(zhí)行時(shí)多少锉走,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) 調(diào)用 xxx(綁定了xxx的this= {name:2})
// xxx里面通過apply調(diào)用已經(jīng)制定了this的fn函數(shù)
fn.apply({name: 1},[1,2,3,4]) // this => {name: 1} a=1, b=2
所以當(dāng)我們執(zhí)行fn.bind({name:1},1,2).call({name:2},3,4)
梁厉,本質(zhì)上call
并不能改變bind的返回函數(shù)的this,只是改變了內(nèi)部封裝了一個(gè)函數(shù)(xxx)的this,這也是bind的this參數(shù)不能被重寫的原因。
總結(jié)bind函數(shù)到底做了什么
fun.bind(thisArg[, arg1[, arg2[, ...]]])
// 簡(jiǎn)化版
Function.prototype.bind = function bind(self) {
return function() { return fn.apply(self) }
}
當(dāng)一個(gè)函數(shù)(fn)使用函數(shù)原型鏈上面的bind函數(shù)的時(shí)候,傳遞
this
(thisArg)和參數(shù)進(jìn)去,返回的是一個(gè)新函數(shù)(xxx)混移,新函數(shù)內(nèi)部調(diào)用的是通過apply
調(diào)用原來的函數(shù)(fn)并制定原函數(shù)(fn)的this亲茅。用簡(jiǎn)單的代碼表示就是:
function fn(a,b) {
return a+b;
}
fn.bind({name:1},1,3) 相當(dāng)于變成這樣=> function xxx() {
return fn.apply({name: 1},Array.prototype.slice.call(arguments));
}
箭頭函數(shù)的this(定義時(shí)候的this)
一句話總結(jié): 箭頭函數(shù)的函數(shù)體內(nèi)的this
就是定義時(shí)候的this
腔长,和使用所在的this沒有關(guān)系胚膊。
即:在定義箭頭函數(shù)的時(shí)候就已經(jīng)綁定了this辑舷,可以理解為就是在定義的時(shí)候碌廓,通過bind函數(shù)進(jìn)行強(qiáng)行綁定this。
案例一
var calculate = {
array: [1, 2, 3],
sum:() => {
console.log(this === window) // => true
}
};
// 當(dāng)我們?cè)诙xsum是一個(gè)箭頭函數(shù)的時(shí)候异袄,還沒有執(zhí)行,內(nèi)部已經(jīng)綁定了this,而此時(shí)的this就是全局的window
//可以轉(zhuǎn)換成
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)
}
}
};
// 此時(shí)我們?cè)趯慶alculate.sum的時(shí)候呐籽,由于還沒有執(zhí)行,所有并不存在里面的箭頭函數(shù),當(dāng)我們執(zhí)行calculate.sum()才算生成了箭頭函數(shù),箭頭函數(shù)就是在這個(gè)時(shí)候綁定this的并淋,所有這里就會(huì)和怎么調(diào)用sum函數(shù)有關(guān)系了。
案例三
const App = new Vue({
el: '#app',
methods: {
foo: () => {
console.log(this) // undefined
}
}
})
// 如果我們?cè)赩ue的實(shí)例中的methods使用箭頭函數(shù)兔毙,那么在定義的時(shí)候神郊,箭頭函數(shù)會(huì)自動(dòng)綁定當(dāng)前作用域的this,并不會(huì)是綁定實(shí)例中的this
// 初始化的時(shí)候,執(zhí)行的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)//通過返回函數(shù)修飾了事件的回調(diào)函數(shù)躬贡。綁定了事件回調(diào)函數(shù)的this酸些。并且讓參數(shù)自定義缀拭。更加的靈活
: fn.call(ctx, a)
: fn.call(ctx)
}
// record original fn length
boundFn._length = fn.length;
return boundFn
}
總結(jié):我們知道Vue內(nèi)部調(diào)用methods的時(shí)候蛛淋,通過的call
方法來執(zhí)行methods中的相應(yīng)的key函數(shù)敷扫,當(dāng)我們使用箭頭函數(shù)的時(shí)候缀台,定義的時(shí)候就綁定了this,它源碼中寫的call()
并不會(huì)被使用脯丝,所以必須不能使用箭頭函數(shù)
參考文章
Vue methods 用箭頭函數(shù)取不到 this