關於this的指向的問題

目錄

前言:面試的一個重要考察點——this 對於一些人來講一直是個老大難。雖然面試題目變化萬千,但只要真正掌握了原理,不少問題就能迎刃而解的。node

下面我整理了一下判斷this指向的3個步驟:es6

  1. 肯定函數調用位置
  2. 應用this的四個綁定規則
  3. 特殊狀況--箭頭函數

第一準則是:this永遠指向函數運行時所在的對象,而不是函數被建立時所在的對象(箭頭函數則相反)。面試

既然this指向取決於函數調用時的位置,那咱們先看看如何確認調用位置。瀏覽器

1.肯定函數調用位置

首先介紹一下什麼是調用棧和調用位置。bash

function a() {
  // 調用棧: c->b->a
  // 當前調用位置在b中
  console.log(a);
}

function b() {
  // 調用棧: c->b
  // 當前調用位置在c中
  a()
}

function c() {
  // 調用棧: c
  // 當前調用位置是全局做用域
  b();
}
c(); // c的調用位置

複製代碼

完成了第一步:肯定了函數的調用位置,下面看看第二步 this的四個綁定規則。app

2.應用this的四個綁定規則

  • 默認綁定
  • 隱式綁定
  • 顯示綁定
  • new 綁定
2.1 默認綁定

能夠這樣理解:沒法應用其餘綁定規則時爲默認規則,獨立函數調用時通常是默認綁定。函數

function test(){
	console.log(this.a);
}
var a = 'aaa'; // 或者window.a='aaa'
test(); // 輸出aaa

複製代碼

上例中,test()不在任何對象內,是獨立調用,屬於默認綁定。ui

(ps:默認綁定this指向的全局對象,在瀏覽器裏面是window,在node中是global, 在嚴格模式下,若是 this 沒有被執行上下文定義,this指向爲 undefined。)this

2.2 隱式綁定

隱式綁定存在於在調用位置有上下文對象或者說調用時被對象包含或擁有。es5

var obj1 = {
	name: 'kelvin',
	say: function(){
		console.log(this.name);
	}
}
obj1.say();// kelvin

// 或者
function say() {
  console.log(this.name);
}
var obj2 = {
  name: 'kelvin',
  say: say
}

obj2.say(); // kelvin
複製代碼

此時能夠用一句簡單的話歸納:誰去調用this就指向誰。不過有時這個規則會在不經意間失效,這種現象就叫隱式丟失。

// 第一種
function fun() {
  console.log(this.a)
}

function doFun(fn) {
  // 參數傳遞其實就是一個隱式的賦值
  fn();
}

var obj = {
  a: 'obj a',
  fun: fun
}

var a = 'global a';
doFun(obj.fun);  // 'global a'
複製代碼
2.3 顯示綁定

顯式綁定裏面有硬綁定和某些API調用的"上下文"控制this

1)硬綁定

硬綁定就是咱們常常看到的call、apply、bind(es5中)三個方法,此處省略實例。

這裏我擴展一下三個方法的實現原理

1. call()

Function.prototype.myCall = function (context) {
  var context = context || window
  // 給 context 添加一個屬性
  // getValue.call(a, 'yck', '24') => a.fn = getValue
  // this 指代調用call方法的當前函數
  context.fn = this
  // 將 context 後面的參數取出來
  var args = [...arguments].slice(1)
  // getValue.call(a, 'yck', '24') => a.fn('yck', '24')
  var result = context.fn(...args)
  // 刪除 fn
  delete context.fn
  return result
}

2. apply()

Function.prototype.myApply = function (context) {
  var context = context || window
  context.fn = this

  var result
  // 須要判斷是否存儲第二個參數
  // 若是存在,就將第二個參數展開
  if (arguments[1]) {
    result = context.fn(...arguments[1])
  } else {
    result = context.fn()
  }

  delete context.fn
  return result
}

3. bind()

Function.prototype.myBind = function (context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  var _this = this
  var args = [...arguments].slice(1)
  // 返回一個函數
  return function F() {
    // 由於返回了一個函數,咱們能夠 new F(),因此須要判斷
    if (this instanceof F) {
      return new _this(...args, ...arguments)
    }
    return _this.apply(context, args.concat(...arguments))
  }
}

結論:能夠看出call、apply是利用「隱式綁定規則」,經過將當前函數this放置到context對象中,爲context所包含或擁有,從而達到this指向context的目的。bind的不一樣之處是它返回一個新函數,且內部使用apply實現。
複製代碼

2)API調用的"上下文"

JS中有幾個內置函數,filter、forEach等都有一個可選參數,在執行 callback 時的用於指定 this 值。以forEach爲例子:

var person = {
  name: '小張'
}

function say(item) {
  console.log(this.name + ' ' + item)
}

[1,2,3,4].forEach(say, person)
//小張 1
//小張 2
//小張 3
//小張 4
複製代碼
2.4 new綁定

首先看new作的4件事:

  • 建立(或者說構造)一個全新的對象
  • 這個新對象會被執行[[Prototype]]鏈接
  • 這個新對象會綁定到函數調用的this(故this指向新對象)
  • 若是函數沒有返回其餘對象,那麼new表達式中的函數調用會自動返回這個新對象。

又new所作的第三件事情可知:this指向新對象

PS: 四種規則的優先級以下: new 綁定>顯式綁定>隱式綁定>默認綁定。

3.特殊狀況:箭頭函數

凡事有例外,es6中引入的箭頭函數卻不適用於上面的四個規則。看代碼以下:

function a(){
	return ()=>{
		console.log(this.name);
	}
}
const obj1={
	name: 「張」,
}
const obj2={
	name: 「劉」,
}
var test = a.call(obj1);
test.call(obj2); // 張

複製代碼

由輸出結果:「張」,能夠看出箭頭函數是不適用於上面的四規則的。箭頭函數的具體規則是:箭頭函數this是在聲明時就肯定了,其this就是聲明時所在做用域的this肯定的。好比上面的例子,箭頭函數是在a函數中聲明的,因此箭頭函數中所用的this就是a的this,而a中的this是根據調用位置和規則肯定是obj1,因此箭頭函數中this也是指向obj1。

到此對this的掌握程度應該足以應對不少面試題了,謝謝你們的閱讀!

Over,thanks!

相關文章
相關標籤/搜索