iT邦幫忙

2021 iThome 鐵人賽

DAY 10
0
Modern Web

JavaScript學習日記系列 第 10

JavaScript學習日記 : Day10 - This

1. 為什麼要有this?

JavaScript允許在函數內部,引用當前執行中環境的其他變數。

function func() {
    console.log(a)
}

如上代碼,func函數引用了當前執行環境的a,問題是這個函數可以在任何執行環境中被調用,因此這時候的a可能就指向不同了,所以JS引擎需要有一個機制,可以依靠其優雅的、準確地指向當前代碼運行的執行環境。

何謂優雅的?

//假設這個object名稱很長,且有可能改名
let objectWithALongName={
    name:"David",
    func1(){
        return objectWithALongName.name;
    },
    func2(){
        return this.name;
    }
}

objectWithALongName的方法func2使用了this關鍵字,就優雅多了,然後即使往後對象名稱改變了,func2內部代碼也不需改變。

另外如果有其他執行環境也需要使用到這個函數,使用this也可以優雅的指向該執行環境:

let otherObject = {
    name:"John",
    cloneMethod:objectWithALongName.func2
}

otherObject.cloneMethod(); // John func2中的this這時候指到otherObject

何謂精準的

this可以精準的指向某個對象。

//全域變數
let objectWithALongName = {
  name: "David"
};

(function() {
  //局部變數
  let objectWithALongName = {
    name: "John",
    func1() {
      return objectWithALongName.name;
    },
    func2() {
      //相對於func1,這邊用了this可以清楚知道指向內部IIFE的執行環境
      return this.name;
    }
  };

  console.log(objectWithALongName.func1());// John
})();

2. 呼叫的位置

呼叫位子就是函數在代碼中在哪裡被調用,而不是聲明的位子。所以搞清楚「由誰在哪調用」才能精確的找到this的指向。

let module = {
  id: 10,
  getId: function() {
    return this.id;
  }
}

console.log(module.getId());//>> 10
let globalGetId = module.getId;//getX和module.getX都是指向記憶體中函数的地址而已,這邊並沒有被()執行
console.log(globalGetId()); //>> undefined

作為module對象的getId方法被調用,this指向module,module對象有一個屬性id值為10,所以console.log(module.getX())出10,而globalGetId中的this會指向全域,全域中並沒有定義id,所以出undefined。

3. 對this的誤解

-->This既不指向函數自身,也不指向函數的作用域

  1. This的指向,是在函數被調用的時候確定的,也就是執行環境被創建時確定的。
  2. this的指向與函數聲明的位置沒有任何關係,只取決於函數調用的位置(由誰、在哪調用這個函數)
  3. 正因為執行環境創建階段this就確定了,在執行階段this的指向不可在被變更。
let obj = {
    a:"123"
}
function func() {
    this = obj; //報錯,因為在執行階段試圖改變this
    console.log(this.a);
}
func();

4. this指向規則

  1. 默認指向
    獨立函數的調用(無法應用後面指向規則時),this指向全域對象。
function func() {
    console.log( this.a ); // this指向全域對象
}
let a = 2;
func(); // 2

對於默認指向來說,決定this指向的並不是調用位置是否是嚴格模式(use strict),而是函數本體是否處於嚴格模式。如果函數處於嚴格模式,this指向undefined,否則指向全域。

function func() {
  "use strict";//函数中處於嚴格模式下,this指向undefined
  console.log(this.a);
}

let a = 123;

func(); // 報錯

function func() {
  console.log(this.a);
}

var a = 123;
(function() {
  "use strict";
  func(); // 123
})();
  1. 隱式指向

隱式指向是日常開發中最常見的,也就是決定this指向的是由誰呼叫,與函數存在的位置無關:

function func() {
  console.log(this.a);
}
let obj = {
  a: 2,
  func: func
};
obj.func(); // 2
// 找到呼叫位置,由obj對象来呼叫函數func,
// 此時可以說函數func被呼叫時,obj對象擁有或者包含func函數
// 所以此時的 this 指向呼叫 func 函数的 obj 對象。

對象屬性引用鏈中只有最頂層或者說最後一層才會影響調用位置,簡單說,this指向最靠近被調用函數的對象。

function func() {
  console.log(this.a);
}

let obj2 = {
  a: 10,
  func: func
};

let obj1 = {
  a: 20,
  obj2: obj2
};

// this指向obj2對象,因為obj2離的最近
obj1.obj2.func(); // 10
  1. 顯示指向

Javascript內建對象Function的三種原型方法call()、apply()、bind(),他們的第一個參數是對象,他們會把這個對象綁定到this,接著在調用函數時讓this指向這個對象。

let a = 10;

function func() {
    console.log( this.a );
}
let obj = {
    a:20
};

func.call(obj); // 20
// 在呼叫func時強制把this指向obj

5.new操作符與this關係

使用new來調用函數,或者說發生構造函數調用時,會執行底下操作 :

  1. 創建一個新的對象。
  2. 將構造函數的作用域賦與新對象(因此this就指向了這個新對象)
  3. 執行構造函數中的代碼(為這個新對象添加屬性、方法等)
  4. 如果函數沒有返回其他對象,那就返回這個新對象。
function func(a) {
    this.a = a;
}
let bar = new func("test");
console.log(bar.a); // test
// 使用new 來呼叫func(..)时,創建一個新對象並把它绑定到func(..)调用中的this上

另外一個觀點:

function User(name) {
  // this = {};(隱藏創建)

  // 添加属性到 this
  this.name = name;
  this.isAdmin = false;

  // return this;(隱藏返回)
}

6. 整理判斷順序

用以上介紹規則可以簡單做一個判斷流程:

  1. 函數是否在new中被調用(new操作符指向)?

如果是的話,this綁定的是新創建的對象。

function func(name) {
  this.name = name;
  this.getName = function() {
    return this.name;
  };
}

let obj = new func("apple"); //this會指向obj
console.log(obj.getName()); // apple
  1. 函數是否通過call、apply、bind顯式指向?
    如果是的話,this指向的是call、apply、bind三個方法的第一個參數指定的對象。
let obj1 = {
  name: "David"
};
function func() {
  return this.name; //這裡的this本来指向window
}
let str = func.call(obj1); //改變了func函数裡面this的指向,指向obj1
console.log(str); // David
  1. 函數是否被當作某個對象的方法而調用(隱式指向)?
    如果是的話,this指向是這個對象。
let obj1 = {
  name: "Jack",
  func() {
    return this.name; //指向obj1
  }
};

//這裡的obj1.func(),表明func函数被obj1调用,因此func中的this指向obj1
console.log(obj1.func()); // Jack
  1. 若以上都不是的話,使用默認綁定

如果在非嚴格模式下,就綁定到全域對象。

let a = 123; //為全域對象增加一個變數a
function func() {
  return this.a;
}

console.log(func()); // 123

上一篇
JavaScript學習日記 : Day9 - 執行環境(Execution Context)
下一篇
JavaScript學習日記 : Day11 - 函數綁定
系列文
JavaScript學習日記30

尚未有邦友留言

立即登入留言