前端進擊的巨人(六):知否知否,須知this

前端進擊的巨人(六):知否知否,須知this

常見this的誤解

  1. 指向函數自身(源於this英文意思的誤解)
  2. 指向函數的詞法做用域(部分狀況)

this的應用環境

1. 全局環境

不管是否在嚴格模式下,全局執行環境中(任何函數體外部)this都指向全局對象html

var name = '以樂之名';
this.name;  // 以樂之名
2. 函數(運行內)環境

函數內部,this的值取決於函數被調用的方式(被誰調用)前端

var name = '無名氏';
function getName() {
 console.log(this.name);
}
getName();         // 無名氏 調用者是全局對象

var myInfo = {
  name: '以樂之名',
  getName: getName
};
myInfo.getName();  // 以樂之名 調用者是myInfo對象

this的正解

"this的指向是在運行時進行綁定的,而不是代碼書寫(函數聲明)時肯定!!!"git

"看誰用",this的指向取決於調用者,這也是不少文章提到過的觀點。"誰調用,this指向誰",只是這句話稍有偏頗,某些狀況不見得都適用。github

生活栗子:你的錢並不必定是你的錢,只有當你使用消費了纔是你的錢 。
"看誰用"),借出去的錢就不是你的了。。。segmentfault

回到正文,咱們先經過棧,來理解什麼是調用位置?數組

JavaScript中函數的調用是以棧的方式來存儲,棧頂是正在運行的函數,函數調用時入棧,執行完成後出棧。瀏覽器

function foo() {
  // 此時的棧:全局 -> foo,調用位置在foo
  bar();
}

function bar() {
  // 此時的棧:全局 -> foo -> bar,調用位置在bar
  baz();
}

function baz() {
  // 此時的棧:全局 -> foo -> bar -> baz,調用位置在baz
  // ...
}

foo();

代碼中雖然函數存在多層嵌套使用,但處於棧頂的只有正在執行的函數,也即調用者只有頂層的那一個(或最後一個),理清調用位置(調用者)有助於咱們理解this前端工程師

this的綁定規則

  1. 默認綁定(函數單獨調用)
  2. 隱式綁定(做爲對象的屬性方法調用,帶有執行上下文)
  3. 顯示綁定(call/apply/bind
  4. new綁定(new建立實例)
  5. 箭頭函數綁定(ES6新增,基於詞法做用域)

默認綁定下(函數單獨調用)區分嚴格模式

  • 非嚴格模式,this會指向全局對象(瀏覽器全局對象是window,NodeJS全局對象是global);
  • 嚴格模式,this指向undefined
// 非嚴格模式
function getName() {
  console.log(this.name);  // this指向全局對象
}
getName();  // "",並不會報錯,若是外部有全局變量name,則會輸出對應值

// 嚴格模式
function getName() {
  "use strict"
 console.log(this.name);   // this指向undefined
}
getName();  // TypeError: Cannot read property 'name' of undefined

TIPS: 嚴格模式中,對函數中this的影響,只在函數內聲明瞭嚴格模式纔會存在,若是是調用時聲明嚴格模式則不會影響。app

function getName() {
  console.log(this.name);
}

// 調用時聲明嚴格模式
"use strict";
getName();  // ""

隱式綁定

隱式綁定中,函數通常做爲對象的屬性調用,帶有調用者的執行上下文。所以this值取決於調用者的上下文環境。若是存在多層級屬性引用,只有對象屬性引用鏈中最頂層(最後一層)會影響調用位置,而this的值取決於調用位置。文章開頭以棧來理解調用者的例子。函數

function getName() {
  return this.name;
}

var myInfo = {
  name: '以樂之名',
  getName: getName
};

var leader = {
  name: '大神組長'
  man: myInfo
};
leader.man.getName();  // '以樂之名'
// man 指向 myInfo,最頂層(最後一層)對象爲 myInfo

apply/call的區別

apply/call方法二者相似,均可以顯示綁定this,二者的區別是參數傳遞的方式不一樣。apply/call第一個參數都爲要指定this的對象,不一樣的是apply第二個參數接受的是一個參數數組,而call從第二個參數開始接受的是參數列表。

apply語法:func.apply(thisArg, [argsArray])

call語法:func.call(thisArg, arg1, arg2, ...)

var numbers = [5, 6, 2, 3, 7];

// 求numbers的最大值

// apply
var max = Math.max.apply(null, numbers);

// call
var max = Math.max.call(null, ...numbers); // ...展開運算符

TIPS: 若是thisArg爲原始值(數字,字符串,布爾值),this會指向該原始值的自動包裝對象,如Number, String, Boolean

func.apply(1);
// func中的this -> Number對象;

bind的特別(柯里化的應用)

bind是ES5新增的方法,跟apply/call功能同樣,能夠顯示綁定this。

bind語法:function.bind(thisArg[, arg1[, arg2[, ...]]])

bind()方法建立一個新的函數,在調用時設置this關鍵字爲提供的值,並在調用新函數時,將給定參數列表做爲原函數的參數序列的前若干項。

-- 《Function.prototype.bind() | MDN》

"bind與apply/call的區別:apply/call傳入this並當即執行函數,而bind傳入this則返回一個函數,並不會當即執行,只有調用返回的函數纔會執行原始函數"

bind方法是函數柯里化的一種應用,看過上篇《前端進擊的巨人(五):學會函數柯里化(curry) 》的小夥伴,應該還記得"函數柯里化的特色:延遲執行,部分傳參,返回一個可處理剩餘參數的函數"

bind相較apply/call的優勢,能夠經過部分傳參提早對this進行一次"永久綁定",也就是說this只需綁定一次,省卻每次執行都要進行this綁定的操做。

function getName() {
  return this.name;
}

var myInfo = {
  name: '以樂之名',
  job: '前端工程師'
};

var getName = getName.bind(myInfo);
getName();  // '以樂之名';
getName(); //  '以樂之名';

// 一次性綁定,以後調用無需再修改this

TIPS: 函數柯里化能夠用於參數預設,像一次性操做(判斷/綁定)等。

有關函數柯里化的詳解,請回閱:《前端進擊的巨人(五):學會函數柯里化(curry) 》

構造函數中的this

經過new操做符能夠實現對函數的構造調用。JavaScript中自己並無"構造函數",一個函數若是沒有使用new操做符調用,那麼它就是個普通函數,new Func()其實是對函數Func的"構造調用"。

在瞭解構造函數中的this前,有必要先了解下new實例化對象的過程。

new實例過程

  1. 建立(構造)一個全新的空對象
  2. 這個新對象會被執行"原型"連接(新對象的__proto__會指向函數的prototype)
  3. 構造函數的this會指向這個新對象,並對this屬性進行賦值
  4. 若是函數沒有返回其餘對象,則返回這個新對象(注意構造函數的return,通常不會有return)
// 正常不帶return的構造函數
function People(name, sex) {
  this.name = name;
  this.sex = sex;
}

var man = new People('亞當', '男');
var woman = new People('夏娃', '女');
// 實例化對象成功
// 構造函數帶了return
function People(name, sex) {
  return 1;  // 返回的是Number對象
}
function People(name, sex) {
  return 'hello world';  // 返回的是String對象
}
function People(name, sex) {
  return function() {}
}
function People(name, sex) {
  return {};
}
// 以上並未正確實例化對象

構造函數自定義return,會形成new沒法完成正確的實例化操做。若是返回值爲基本類型,則返回其包裝對象Number/String/Bollean

TIPS: 原型鏈中的this指向其實例化的對象

People.prototype.say = function() {
  console.log(`個人名字:${this.name}`);
};

var man = new People('亞當', '男');
man.say();  // 個人名字:亞當

this綁定規則的優先級

顯示綁定 / new綁定 > 隱式綁定 > 默認綁定

TIPS: new沒法跟apply/call同時使用

this斷定步驟

  1. 函數被new操做符使用(new綁定)? YES --> this綁定的是new建立的新對象
  2. 函數經過call/apply/bind(顯示綁定)? YES --> this綁定的是指定的對象
  3. 函數在某個上下文對象中調用(隱式綁定)? YES --> this綁定的是那個上下文對象
  4. 默認綁定,嚴格模式指向undefined,不然指向全局對象

ES6的箭頭函數(詞法做用域的this機制,規則以外)

箭頭函數的this機制不一樣於傳統的this機制,它採起的是另一種機制,詞法做用域的this斷定規則。

// 例子一
var name = '無名氏';
var myInfo = {
  name: '以樂之名',
  getName: () => {
    console.log(this.name);
  }
};
var getName = myInfo.getName;
window.getName();     // 無名氏
myInfo.getName();     // 無名氏
// myInfo是在全局環境定義的,所以根據詞法做用域,this指向全局對象

// 例子二
var name = '無名氏';
var myInfo = {
  name: '以樂之名',
  say: () => {
    setTimeout(() => {
      console.log(this.name);
    })
  }
};
myInfo.say();  // 無名氏
// 箭頭函數經過做用域鏈來逐層查找this,最終找到全局變量myInfo,this指向全局對象

// 例子三
var name = '無名氏';
var myInfo = {
  name: '以樂之名',
  say: function() {
    setTimeout(() => {
      console.log(this.name);
    })
  }
};
myInfo.say(); // 以樂之名
// 箭頭函數找到say: function(){},所以this的做用域來自myInfo

TIPS: setTimeout/setInterval/alert的調用者都是全局對象

"箭頭函數的this始終指向函數定義時的this,而非執行(調用)時的this。箭頭函數中的this必須經過做用域鏈一層一層向外查找,來肯定this指向。"

擴展:箭頭函數的書寫規則

1. 箭頭函數只能用函數表達式,不能用函數聲明式寫法(不包括匿名函數)
// 函數表達式
const getName = (name) => { return 'myName: ' + name };

// 匿名函數
setTimeout((name) => {
  console.log(name);
}, 1000)
2. 若是參數只有一個,可不加括號();若是沒有參數或多個參數需加括號()
// 只有一個參數
const getName = name => {
  return `myName: ${name}`;
}

// 無參數
const getName = () => {
  return 'myName: "以樂之名"';
}

// 多參數
const getName = (firstName, lastName) => {
  return `myName: ${firstName} ${lastName}`;
}
3. 函數體只有一個可不加花括號{}
const getName = name => return `myName: ${name}`;
4. 函數體沒有花括號{},可不寫return,會自動返回
const getName = name => `myName: ${name}`;

參考文檔:

本文首發Github,期待Star!
https://github.com/ZengLingYong/blog

做者:以樂之名 本文原創,有不當的地方歡迎指出。轉載請指明出處。
相關文章
相關標籤/搜索