JavaScript中的this

原文地址javascript

JavaScript中的this

原理

錯誤的this指向

一般所說的:若是是全局環境中,this指向全局對象,若是是對象的方法,這this指向這個對象。html

例子1:java

var foo = {
  bar: function() {
    console.log(this)
  }
}

foo.bar();
(foo.bar)();

(foo.bar = foo.bar)();
(false || foo.bar)();
(foo.bar, foo.bar)();

例子1前二者爲foo,後面都是全局對象。後三者並無指向foo。因此咱們上面的一般說法不精確。git

精確的this指向

在全局環境中,this指向全局對象。而在普通函數調用中,this是由激活上下文的調用者提供,即調用這個函數的父做用域,以及函數調用的語法形式,決定了this的值,這是一個動態可變的值。github

例子2:編程

var foo = {
  bar: function() {
    console.log(this)
    console.log(this === foo)
  }
}

foo.bar() // foo, true

var fn = foo.bar

console.log(fn === foo.bar) // true

fn() // global, false

例子2中,第一次調用指向foo,把foo.bar賦值給fn以後,this沒有指向foo。是什麼致使this指向的變化呢?數組

this指向的內部原理

this是執行上下文的一個屬性:緩存

activeExecutionContext = {
  VO: {...},
  this: thisValue
}

在普通函數調用中,this是由激活上下文的調用者提供,即調用這個函數的父做用域,函數調用的語法形式,決定了this的值,這是一個動態可變的值。閉包

爲何會引發這個差別呢?
由於引用類型的不一樣處理,是否會獲取真實的值,所致使的。app

引用類型存在形式:

1 標識符(變量名,函數名,函數參數名,全局對象屬性名)

2 屬性訪問器(foo.bar(); foo['bar'](), 點標記法;能夠動態設置屬性名的方括號[]

爲了從引用類型中獲取真實的值,存在相似getValue的方法。而函數上下文的規則是,函數上下文中this由調用者提供,並由調用形式決定。若是調用的圓括號左側是一個引用類型,this爲這個引用類型,若是是非引用類型,這爲null,但爲null無心義,被隱式轉化爲全局對象。

爲何有this的特性?

this是一個指針,便於代碼的更爲簡潔地複用。

// 無this
function upper(context) {
  return context.name.toUpperCase()
}
function speak(context) {
  var greeting = "Hello, I'm " + upper(context)
  console.log(greeting)
}

var me = {
  name: 'm'
}

var you = {
  name: 'y'
}

speak(me)

// 利用this

function upper() {
  return this.name.toUpperCase()
}
function speak() {
  var greeting = "Hello, I'm " + upper.call(this)
  console.log(greeting)
}

speak.call(me)

這裏this能夠簡化上下文對象的傳遞。其餘OPP語言中this關鍵字和OPP密切相關,通常是引用剛建立的對象,但在ECMAScript中,this只限於引用建立過的對象,this的指向和函數調用形式有關,不必定引用類型調用就指向引用類型。

this指向的改變

1 構造函數中的this

function C() {
  console.log(this)
  this.x = 10
}

var a = new C()
console.log(a.x);

new操做符會調用函數的內部的Construct方法,建立對象,以後調用函數的Call方法,把新建立對象做爲this值。

2 調用函數時call與apply設置this的值

var b = 10
function a(c) {
  console.log(this.b)
  console.log(c)
}

a(20)
a.call({b: 20}, 30)
a.apply({b: 20}, [40])

call, apply, bind以及箭頭函數

call,apply,bind皆爲動態的改變this指針的方法。其中call和apply是當Object沒有某個方法,可是其它對象有,能夠藉助call和apply改變this的指向,調用其它對象的方法。bind爲綁定this爲某個對象。

典型的應用:

將類數組元素轉化爲數組:
Array.prototype.slice.apply(document.getElementsByTagName('*'))

檢查類型:

function isArray(obj) {
  return Object.prototpye.toString.call(obj) === '[object Array]'
}

箭頭函數則與前三者不一樣。
If kind is Arrow, set the [[ThisModel]] internal slot of F to lexical.If the
value is "lexical", this is an ArrowFunction and does not have a local this

  1. If thisModel is lexical, return NormalCompletion(undefined).

箭頭函數沒有本身的this綁定,同時在函數執行時綁定this會被直接忽略。其中this老是指向定義時所在的對象,而不是運行時所在的對象。即箭頭函數的this值是lexical
scope 的this值。這一特性使得箭頭函數在React中的render函數中使用起來很方便。

function foo() {
  setTimeout(() => {
    console.log('id: ', this.id)
  }, 100)
}

var id = 0

foo.call({id: 42})

// 容易誤解的地方
// {id: 42}
// 是箭頭函數定義所在的對象仍是運行時所在的對象。因爲箭頭函數位於foo函數內部,只有foo函數運行以後他纔會生成,因此foo運行時所在的對象,即箭頭函數定義所在的對象。
var f = () => 5;
// 近似等價於
var f = function() {return 5;}.bind(this);

綜上,call,apply,bind使得JavaScript具備動態改變this的特性,而箭頭函數使得JavaScript具備固定this的指向的特性。一動一靜,相得益彰。

在編程中的運用

ES7中的::

this.x = 0
  let module = {
    x: 1,
    getX: function() {
      console.log(this.x)
    }
  }
  module.getX()
  let get = module.getX
  get() // 0
  let boundGetX = get.bind(module)
  boundGetX() // 1
  let ES7boundGetx = module::get
  ES7boundGetx() // 1

super

class P {
  foo() {
    console.log('P.foo')
  }
}

class C extends P {
  foo() {
    super.foo()
  }
}

var c1 = new C()
c1.foo() // P.foo

var D = {
  foo: function() {
    console.log('D.foo')
  }
}

var E = {
  foo: C.prototype.foo
}

Object.setPrototypeOf(E, D)
E.foo() // P.foo

可見super的綁定是靜態綁定,建立時即完成綁定。因此E委託了D,但並不能調用到D.foo(),相似於箭頭的函數的this綁定。

jQuery中的this

鏈式調用的實現;

function Constructor() {
  this.art = 0
}

Constructor.prototype.fn_0 = function() {
  console.log('0')
  return this;
}

Constructor.prototype.fn_1 = function() {
  console.log('1')
  return this;
}

new Constructor().fn_0().fn_1()

調用的方法返回this便可。

end()的實現

function end() {
  return this.prevObject || this.constructor(null)
}

// 設置preObject的函數
function pushStack( ele ) {
  // Build a new jQuery macthed element set
  var ret = jQuery.merge( this.constructor(), elems);
  ret.prevObject = this // ret.pervObject 設置爲當前jQuery對象引用
  ret.context = this.context
  return ret;
}

pushStack函數在不少涉及DOM操做的函數都有調用,用於緩存了當前的this。因爲只存儲當前,因此這裏只須要一個preObject便可,無需放在一個數組裏。

利與弊

this是JavaScript特性之一,具備腳本語言的動態特性,帶來不少便捷,同時因爲super和箭頭函數的特性,使得this具備了靜態的特性,在這兩種狀況下,this是固定且沒法改變的。其利與弊都是this的靈活,雙刃劍。因此纔有了ES2015中super和箭頭函數的固定this的特性。

拾遺

this可被從新賦值麼?(不能,this是保留字)

問題(答案見原文)

1 call參數爲null時,this的指向

function a() {
  console.log(this)
}
a.call(null)

2 調用形式對this的影響

var foo = {
  bar: function() {
    console.log(this)
  }
}

foo.bar();
(foo.bar)();

(foo.bar = foo.bar)();
(false || foo.bar)();
(foo.bar, foo.bar)();

參考資料:

《你所不知道的JavaScript(上卷)》

關於JavaScript的執行域,標識符解析,閉包的研究

深刻ECMA-262-3 第三章、This

JavaScript內部原理實踐——真的懂JavaScript嗎?

相關文章
相關標籤/搜索