【個人總結】es6箭頭函數以及es5的this指向問題

以下js比較示例,引用“小小小小小亮”同學

點擊我,跳轉至他的分析結果


一、比較並分析下例代碼的this指向問題

var a=11
function test1(){
  this.a=22;
  let b=function(){
    console.log(this.a);
  };
  b();
}
var x=new test1();
輸出11
var a=11;
function test2(){
  this.a=22;
  let b=()=>{console.log(this.a)}
  b();
}
var x=new test2();
//輸出22

二、解析方向

這裏往往有三種反應

  1. 跟我想的不一樣,這兩個結果應該反過來
  2. 前者瞭解,但第二個不懂
  3. 前者不瞭解,後者我懂

1)1與3的反應

1與3的反應一般都是this指向的不熟悉以及相關業務的實踐量缺少

1)2的反應

2的反應,應該是大多數人的反應,前者是解釋的通的,單純定義一個方法,並未被父級對象所引用(姑且這麼說吧),則this指向的是全局,否,則指向父級對象

var a = function () {
    console.log(this.test)
}
var b = {
    test: 666,
    a: a // a掛在了對象上(被引用)
    // 等價於 a: function () {...}
}
b.a()
// 輸出666
var a = function () {
    console.log(this.test)
}
var b = function () {
    this.test = 666
}
b.prototype.a = a // 掛在了原型鏈上(被引用)
var k = new b()
k.a()
// 輸出666
var a = {
    test: 666,
    b: function () {
        var c = function () { // 未被引用
            console.log(this.test)
        }
        c()
    }
}
a.b()
// 輸出undefined

但第二種是百思不得理解的,因缺少混合模式或構造函數模式的開發經驗,又是封裝方法依賴注入,所以大部分的避免了作用域中去定義函數調用。

隨着該問題的暴露,趁着這次,來個實踐出真理

var test = 777
var a = function () {
     // 不實例化的話,這裏定義this是沒有意義的
}
a.prototype.b = function () {
    this.test = 666
    let k = () => {
        console.log(this.test)
    }
    k()
}
a.prototype.b()
// 輸出666

很神奇是不是,這可能關係到=>函數的解析機制,暫且分析爲:

如果=>函數定義在prototype下的作用域中時,this會指向prototype上所引用的父級對象

實踐下

var test = 777
var a = function () {}
a.prototype.b = function () {
    this.test = 666
    let k = () => {
        console.log(this.test)
    }
    k()
}
a.prototype.b.prototype.c = function () {
    this.test = 555
    let k = () => {
        console.log(this.test)
    }
    k()
}
a.prototype.b() // 輸出666
a.prototype.b.prototype.c() // 輸出555

這裏如果執行a.prototype
會發現除了b定義的方法外,還有一個constructor(構造器)函數的生成,但事實上是沒用到這個的(只有實例化的時候),而且delete a.prototype.constructor
刪除該函數後,調用的=>對象也依然沒影響到this的指向或拋錯,所以
有沒constructor並不能成爲判斷=>指向的依據(個人觀點)

經檢驗確實無誤,那實例化試試

var test = 777
var a = function () {
    // 實例化一定會經過構造器,則this可定義該處了
    this.test = 666
}
a.prototype.b = function () {
    let k = () => {
        console.log(this.test)
    }
    k()
}
var b = new a()
b.b()
// 輸出666

那回過頭來看看,爲什麼沒有prototype,直接實例化也能達到this指向局部呢?

var a=11;
function test2(){
  this.a=22;
  let b=()=>{console.log(this.a)}
  b();
}
var x=new test2(); // 關鍵在此,new裏實現了什麼
//輸出22

那關於new裏實現了啥,百度上一搜一大把,這裏簡單來說,就是內部創建了個new Object()
並且將定義的”test2“這個函數用call或apply將this指向了object並return達到了this 的更變

來簡單試試

var a = () => {
    console.log(this.b)
}
var k = {
    b: 666
}
a.call(k)
// 輸出 undefined

按道理來說,應該是變的,因爲ES5的函數定義更變this指向是成功了,那爲啥ES6的=>函數指向沒變?

要知道爲啥,又得來看看=>函數裏發生了什麼……(無語)

這裏就不提及更深層次的了,大致只要知道,=>函數因爲是es6語法,已經被瀏覽器所解析,所以一用=>函數,就已經被預解析了(你還想在瀏覽器執行之前搞定?)因此,call或apply與=>有一定耦合性,沒準在內部的執行過程中被if過濾掉了。

我們再來試試new Object()的創建方式

var a= {
    test: 666,
    b: function () {
        var k= () => {
            var kk = () => {
                console.log(this.test)
            }
            kk()
        }
        k()
    }
}
a.b()
// 輸出666

=>函數在含有父級對象的作用域中定義,this即指向父級對象

好,實踐到此爲止,結果已經顯而易見了。


三、個人結論

1)function:

function定義的子級函數對象必須被父級所引用,是父級對象的一部分,例如:

var a = {
    b: function () { // 一部分
        console.log(this)
    }
}
/* or */
var a = function () {}
a.prototype.b = function () { // 一部分
    console.log(this)
}

this則指向上一級對象,否則this永遠指向全局。

1)=>:

=> 函數,如果定義在含有父級對象的作用域下或被實例化了,則this指向上一級對象,否則永遠指向全局,例如:

var a = {
    b: function () { // 父級
        return (() => { // 父級對象下的作用域
            console.log(this)
        })()
    }
}
/* or */
var a = function () {}
a.prototype.b = function () { // 父級
    return (() => { // 父級對象下的作用域
        console.log(this)
    })()
}

還有二種說法:

1、有constructor則=>指向上一級,無則指向全局(不是很贊同)
2、有prototype則=>指向上一級,無則指向全局(有缺陷)


關於

make:o︻そ╆OVE▅▅▅▆▇◤(清一色天空)

blog:http://blog.csdn.net/mcky_love

掘金:https://juejin.im/user/59fbe6c66fb9a045186a159a/posts


結束語

找一個問題時,又會發現另外的問題,查找另外的問題時,往往又有無窮無盡的問題滾來,前端,廣,還TM深 例如上面的問題,再找下去,估計就跑到瀏覽器的框架源碼去了,所以量力而行,適可而止……

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章