在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ HTML/ This? Yes,this!
代碼復(fù)用模式(避免篇)
S.O.L.I.D 五大原則之接口隔離原則 ISP
設(shè)計(jì)模式之狀態(tài)模式
JavaScript 核心(晉級(jí)高手必讀篇)
設(shè)計(jì)模式之建造者模式
JavaScript 與 DOM(上)——也適用于新手
設(shè)計(jì)模式之中介者模式
設(shè)計(jì)模式之裝飾者模式
設(shè)計(jì)模式之模板方法
設(shè)計(jì)模式之外觀模式
強(qiáng)大的原型和原型鏈
設(shè)計(jì)模式之構(gòu)造函數(shù)模式
揭秘命名函數(shù)表達(dá)式
深入理解J avaScript 系列(結(jié)局篇)
執(zhí)行上下文(Execution Contexts)
函數(shù)(Functions)
《你真懂 JavaScript 嗎?》答案詳解
設(shè)計(jì)模式之適配器模式
設(shè)計(jì)模式之組合模式
設(shè)計(jì)模式之命令模式
S.O.L.I.D 五大原則之單一職責(zé) SRP
編寫高質(zhì)量 JavaScript 代碼的基本要點(diǎn)
求值策略
閉包(Closures)
對(duì)象創(chuàng)建模式(上篇)
This? Yes,this!
設(shè)計(jì)模式之代理模式
變量對(duì)象(Variable Object)
S.O.L.I.D 五大原則之里氏替換原則 LSP
面向?qū)ο缶幊讨话憷碚?/span>
設(shè)計(jì)模式之單例模式
Function 模式(上篇)
S.O.L.I.D 五大原則之依賴倒置原則 DIP
設(shè)計(jì)模式之迭代器模式
立即調(diào)用的函數(shù)表達(dá)式
設(shè)計(jì)模式之享元模式
設(shè)計(jì)模式之原型模式
根本沒有“JSON 對(duì)象”這回事!
JavaScript 與 DOM(下)
面向?qū)ο缶幊讨?ECMAScript 實(shí)現(xiàn)
全面解析 Module 模式
對(duì)象創(chuàng)建模式(下篇)
設(shè)計(jì)模式之職責(zé)鏈模式
S.O.L.I.D 五大原則之開閉原則 OCP
設(shè)計(jì)模式之橋接模式
設(shè)計(jì)模式之策略模式
設(shè)計(jì)模式之觀察者模式
代碼復(fù)用模式(推薦篇)
作用域鏈(Scope Chain)
Function 模式(下篇)
設(shè)計(jì)模式之工廠模式

This? Yes,this!

介紹

在這篇文章里,我們將討論跟執(zhí)行上下文直接相關(guān)的更多細(xì)節(jié)。討論的主題就是 this 關(guān)鍵字。實(shí)踐證明,這個(gè)主題很難,在不同執(zhí)行上下文中 this 的確定經(jīng)常會(huì)發(fā)生問題。

許多程序員習(xí)慣的認(rèn)為,在程序語言中,this 關(guān)鍵字與面向?qū)ο蟪绦蜷_發(fā)緊密相關(guān),其完全指向由構(gòu)造器新創(chuàng)建的對(duì)象。在 ECMAScript 規(guī)范中也是這樣實(shí)現(xiàn)的,但正如我們將看到那樣,在 ECMAScript 中,this 并不限于只用來指向新創(chuàng)建的對(duì)象。

讓我們更詳細(xì)的了解一下,在 ECMAScript 中 this 到底是什么?

定義

this 是執(zhí)行上下文中的一個(gè)屬性:

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

這里 VO 是我們前一章討論的變量對(duì)象。

this 與上下文中可執(zhí)行代碼的類型有直接關(guān)系,this 值在進(jìn)入上下文時(shí)確定,并且在上下文運(yùn)行期間永久不變。

下面讓我們更詳細(xì)研究這些案例:

全局代碼中的 this

在這里一切都簡(jiǎn)單。在全局代碼中,this 始終是全局對(duì)象本身,這樣就有可能間接的引用到它了。

// 顯示定義全局對(duì)象的屬性
this.a = 10; // global.a = 10
alert(a); // 10
// 通過賦值給一個(gè)無標(biāo)示符隱式
b = 20;
alert(this.b); // 20
// 也是通過變量聲明隱式聲明的
// 因?yàn)槿稚舷挛牡淖兞繉?duì)象是全局對(duì)象自身
var c = 30;
alert(this.c); // 30

函數(shù)代碼中的 this

在函數(shù)代碼中使用 this 時(shí)很有趣,這種情況很難且會(huì)導(dǎo)致很多問題。

這種類型的代碼中,this 值的首要特點(diǎn)(或許是最主要的)是它不是靜態(tài)的綁定到一個(gè)函數(shù)。

正如我們上面曾提到的那樣,this 是進(jìn)入上下文時(shí)確定,在一個(gè)函數(shù)代碼中,這個(gè)值在每一次完全不同。

不管怎樣,在代碼運(yùn)行時(shí)的 this 值是不變的,也就是說,因?yàn)樗皇且粋€(gè)變量,就不可能為其分配一個(gè)新值(相反,在 Python 編程語言中,它明確的定義為對(duì)象本身,在運(yùn)行期間可以不斷改變)。

var foo = {x: 10};
var bar = {
  x: 20,
  test: function () {
    alert(this === bar); // true
    alert(this.x); // 20
    this = foo; // 錯(cuò)誤,任何時(shí)候不能改變this的值
    alert(this.x); // 如果不出錯(cuò)的話,應(yīng)該是10,而不是20
  }
};
// 在進(jìn)入上下文的時(shí)候
// this被當(dāng)成bar對(duì)象
// determined as "bar" object; why so - will
// be discussed below in detail
bar.test(); // true, 20
foo.test = bar.test;
// 不過,這里this依然不會(huì)是foo
// 盡管調(diào)用的是相同的function
foo.test(); // false, 10

那么,影響了函數(shù)代碼中 this 值的變化有幾個(gè)因素:

首先,在通常的函數(shù)調(diào)用中,this 是由激活上下文代碼的調(diào)用者來提供的,即調(diào)用函數(shù)的父上下文(parent context )。this 取決于調(diào)用函數(shù)的方式。

為了在任何情況下準(zhǔn)確無誤的確定 this 值,有必要理解和記住這重要的一點(diǎn)。正是調(diào)用函數(shù)的方式影響了調(diào)用的上下文中的 this 值,沒有別的什么(我們可以在一些文章,甚至是在關(guān)于 javascript 的書籍中看到,它們聲稱:“this 值取決于函數(shù)如何定義,如果它是全局函數(shù),this 設(shè)置為全局對(duì)象,如果函數(shù)是一個(gè)對(duì)象的方法,this 將總是指向這個(gè)對(duì)象。–這絕對(duì)不正確”)。繼續(xù)我們的話題,可以看到,即使是正常的全局函數(shù)也會(huì)被調(diào)用方式的不同形式激活,這些不同的調(diào)用方式導(dǎo)致了不同的 this 值。

function foo() {
  alert(this);
}
foo(); // global
alert(foo === foo.prototype.constructor); // true
// 但是同一個(gè)function的不同的調(diào)用表達(dá)式,this是不同的
foo.prototype.constructor(); // foo.prototype

有可能作為一些對(duì)象定義的方法來調(diào)用函數(shù),但是 this 將不會(huì)設(shè)置為這個(gè)對(duì)象。

var foo = {
  bar: function () {
    alert(this);
    alert(this === foo);
  }
};
foo.bar(); // foo, true
var exampleFunc = foo.bar;
alert(exampleFunc === foo.bar); // true
// 再一次,同一個(gè) function 的不同的調(diào)用表達(dá)式,this 是不同的
exampleFunc(); // global, false

那么,調(diào)用函數(shù)的方式如何影響 this 值?為了充分理解 this 值的確定,需要詳細(xì)分析其內(nèi)部類型之一——引用類型(Reference type)。

引用類型(Reference type)

使用偽代碼我們可以將引用類型的值可以表示為擁有兩個(gè)屬性的對(duì)象——base(即擁有屬性的那個(gè)對(duì)象),和 base 中的 propertyName 。

var valueOfReferenceType = {
  base: <base object>,
  propertyName: <property name>
};

引用類型的值只有兩種情況:

  1. 當(dāng)我們處理一個(gè)標(biāo)示符時(shí)
  2. 或一個(gè)屬性訪問器

標(biāo)示符的處理過程在下一篇文章里詳細(xì)討論,在這里我們只需要知道,在該算法的返回值中,總是一個(gè)引用類型的值(這對(duì) this 來說很重要)。

標(biāo)識(shí)符是變量名,函數(shù)名,函數(shù)參數(shù)名和全局對(duì)象中未識(shí)別的屬性名。例如,下面標(biāo)識(shí)符的值:

var foo = 10;
function bar() {}

在操作的中間結(jié)果中,引用類型對(duì)應(yīng)的值如下:

var fooReference = {
  base: global,
  propertyName: 'foo'
};
var barReference = {
  base: global,
  propertyName: 'bar'
};

為了從引用類型中得到一個(gè)對(duì)象真正的值,偽代碼中的 GetValue 方法可以做如下描述:

function GetValue(value) {

  if (Type(value) != Reference) {
    return value;
  }
  var base = GetBase(value);
  if (base === null) {
    throw new ReferenceError;
  }
  return base.[[Get]](GetPropertyName(value));
}

內(nèi)部的[[Get]]方法返回對(duì)象屬性真正的值,包括對(duì)原型鏈中繼承的屬性分析。

GetValue(fooReference); // 10
GetValue(barReference); // function object "bar"

屬性訪問器都應(yīng)該熟悉。它有兩種變體:點(diǎn)(.)語法(此時(shí)屬性名是正確的標(biāo)示符,且事先知道),或括號(hào)語法([])。

foo.bar();
foo['bar']();

在中間計(jì)算的返回值中,我們有了引用類型的值。

var fooBarReference = {
  base: foo,
  propertyName: 'bar'
};
GetValue(fooBarReference); // function object "bar"

引用類型的值與函數(shù)上下文中的 this 值如何相關(guān)?——從最重要的意義上來說。 這個(gè)關(guān)聯(lián)的過程是這篇文章的核心。 一個(gè)函數(shù)上下文中確定 this 值的通用規(guī)則如下:

在一個(gè)函數(shù)上下文中, this 由調(diào)用者提供,由調(diào)用函數(shù)的方式來決定。如果調(diào)用括號(hào)()的左邊是引用類型的值,this 將設(shè)為引用類型值的 base 對(duì)象(base object),在其他情況下(與引用類型不同的任何其它屬性),這個(gè)值為 null。不過,實(shí)際不存在 this 的值為 null 的情況,因?yàn)楫?dāng) this 的值為 null 的時(shí)候,其值會(huì)被隱式轉(zhuǎn)換為全局對(duì)象。*注:第 5 版的 ECMAScript 中,已經(jīng)不強(qiáng)迫轉(zhuǎn)換成全局變量了,而是賦值為 undefined。*

我們看看這個(gè)例子中的表現(xiàn):

function foo() {
  return this;
}
foo(); // global

我們看到在調(diào)用括號(hào)的左邊是一個(gè)引用類型值(因?yàn)?foo 是一個(gè)標(biāo)示符)。

var fooReference = {
  base: global,
  propertyName: 'foo'
};

相應(yīng)地,this 也設(shè)置為引用類型的 base 對(duì)象。即全局對(duì)象。

同樣,使用屬性訪問器:

var foo = {
  bar: function () {
    return this;
  }
};
foo.bar(); // foo

我們?cè)俅螕碛幸粋€(gè)引用類型,其 base 是 foo 對(duì)象,在函數(shù) bar 激活時(shí)用作 this。

var fooBarReference = {
  base: foo,
  propertyName: 'bar'
};

但是,用另外一種形式激活相同的函數(shù),我們得到其它的 this 值。

var test = foo.bar;
test(); // global

因?yàn)?test 作為標(biāo)示符,生成了引用類型的其他值,其 base(全局對(duì)象)用作 this 值。

var testReference = {
  base: global,
  propertyName: 'test'
};

現(xiàn)在,我們可以很明確的告訴你,為什么用表達(dá)式的不同形式激活同一個(gè)函數(shù)會(huì)不同的 this 值,答案在于引用類型(type Reference)不同的中間值。

function foo() {
  alert(this);
}
foo(); // global, because
var fooReference = {
  base: global,
  propertyName: 'foo'
};
alert(foo === foo.prototype.constructor); // true
// 另外一種形式的調(diào)用表達(dá)式
foo.prototype.constructor(); // foo.prototype, because
var fooPrototypeConstructorReference = {
  base: foo.prototype,
  propertyName: 'constructor'
};

另外一個(gè)通過調(diào)用方式動(dòng)態(tài)確定 this 值的經(jīng)典例子:

function foo() {
  alert(this.bar);
}
var x = {bar: 10};
var y = {bar: 20};
x.test = foo;
y.test = foo;
x.test(); // 10
y.test(); // 20

函數(shù)調(diào)用和非引用類型

因此,正如我們已經(jīng)指出,當(dāng)調(diào)用括號(hào)的左邊不是引用類型而是其它類型,這個(gè)值自動(dòng)設(shè)置為 null,結(jié)果為全局對(duì)象。

讓我們?cè)偎伎歼@種表達(dá)式:

(function () {
  alert(this); // null => global
})();

在這個(gè)例子中,我們有一個(gè)函數(shù)對(duì)象但不是引用類型的對(duì)象(它不是標(biāo)示符,也不是屬性訪問器),相應(yīng)地,this 值最終設(shè)為全局對(duì)象。

更多復(fù)雜的例子:

var foo = {
  bar: function () {
    alert(this);
  }
};
foo.bar(); // Reference, OK => foo
(foo.bar)(); // Reference, OK => foo
(foo.bar = foo.bar)(); // global?
(false || foo.bar)(); // global?
(foo.bar, foo.bar)(); // global?

為什么我們有一個(gè)屬性訪問器,它的中間值應(yīng)該為引用類型的值,在某些調(diào)用中我們得到的 this 值不是 base 對(duì)象,而是 global 對(duì)象?

問題在于后面的三個(gè)調(diào)用,在應(yīng)用一定的運(yùn)算操作之后,在調(diào)用括號(hào)的左邊的值不在是引用類型。

  1. 第一個(gè)例子很明顯———明顯的引用類型,結(jié)果是,this 為 base 對(duì)象,即 foo。
  2. 在第二個(gè)例子中,組運(yùn)算符并不適用,想想上面提到的,從引用類型中獲得一個(gè)對(duì)象真正的值的方法,如 GetValue。相應(yīng)的,在組運(yùn)算的返回中———我們得到仍是一個(gè)引用類型。這就是 this 值為什么再次設(shè)為 base對(duì)象,即 foo。
  3. 第三個(gè)例子中,與組運(yùn)算符不同,賦值運(yùn)算符調(diào)用了 GetValue方法。返回的結(jié)果是函數(shù)對(duì)象(但不是引用類型),這意味著 this 設(shè)為 null,結(jié)果是 global 對(duì)象。
  4. 第四個(gè)和第五個(gè)也是一樣——逗號(hào)運(yùn)算符和邏輯運(yùn)算符(OR)調(diào)用了 GetValue 方法,相應(yīng)地,我們失去了引用而得到了函數(shù)。并再次設(shè)為 global。

引用類型和 this 為 null

有一種情況是這樣的:當(dāng)調(diào)用表達(dá)式限定了 call 括號(hào)左邊的引用類型的值, 盡管 this 被設(shè)定為 null,但結(jié)果被隱式轉(zhuǎn)化成 global。當(dāng)引用類型值的 base 對(duì)象是被活動(dòng)對(duì)象時(shí),這種情況就會(huì)出現(xiàn)。

下面的實(shí)例中,內(nèi)部函數(shù)被父函數(shù)調(diào)用,此時(shí)我們就能夠看到上面說的那種特殊情況。正如我們?cè)诘?12 章知道的一樣,局部變量、內(nèi)部函數(shù)、形式參數(shù)儲(chǔ)存在給定函數(shù)的激活對(duì)象中。

function foo() {
  function bar() {
    alert(this); // global
  }
  bar(); // the same as AO.bar()
}

活動(dòng)對(duì)象總是作為 this 返回,值為 null——(即偽代碼的 AO.bar()相當(dāng)于 null.bar())。這里我們?cè)俅位氐缴厦婷枋龅睦?,this 設(shè)置為全局對(duì)象。

有一種情況除外:如果 with 對(duì)象包含一個(gè)函數(shù)名屬性,在 with 語句的內(nèi)部塊中調(diào)用函數(shù)。With 語句添加到該對(duì)象作用域的最前端,即在活動(dòng)對(duì)象的前面。相應(yīng)地,也就有了引用類型(通過標(biāo)示符或?qū)傩栽L問器), 其 base 對(duì)象不再是活動(dòng)對(duì)象,而是 with 語句的對(duì)象。順便提一句,它不僅與內(nèi)部函數(shù)相關(guān),也與全局函數(shù)相關(guān),因?yàn)?with 對(duì)象比作用域鏈里的最前端的對(duì)象(全局對(duì)象或一個(gè)活動(dòng)對(duì)象)還要靠前。

var x = 10;
with ({
  foo: function () {
    alert(this.x);
  },
  x: 20
}) {
  foo(); // 20
}
// because
var  fooReference = {
  base: __withObject,
  propertyName: 'foo'
};

同樣的情況出現(xiàn)在 catch 語句的實(shí)際參數(shù)中函數(shù)調(diào)用:在這種情況下,catch 對(duì)象添加到作用域的最前端,即在活動(dòng)對(duì)象或全局對(duì)象的前面。但是,這個(gè)特定的行為被確認(rèn)為 ECMA-262-3 的一個(gè) bug,這個(gè)在新版的 ECMA-262-5 中修復(fù)了。這樣,在特定的活動(dòng)對(duì)象中,this 指向全局對(duì)象。而不是 catch 對(duì)象。

try {
  throw function () {
    alert(this);
  };
} catch (e) {
  e(); // ES3標(biāo)準(zhǔn)里是__catchObject, ES5標(biāo)準(zhǔn)里是global 
}
// on idea
var eReference = {
  base: __catchObject,
  propertyName: 'e'
};
// ES5新標(biāo)準(zhǔn)里已經(jīng)fix了這個(gè)bug,
// 所以this就是全局對(duì)象了
var eReference = {
  base: global,
  propertyName: 'e'
};

同樣的情況出現(xiàn)在命名函數(shù)(函數(shù)的更對(duì)細(xì)節(jié)參考第 15 章 Functions)的遞歸調(diào)用中。在函數(shù)的第一次調(diào)用中,base 對(duì)象是父活動(dòng)對(duì)象(或全局對(duì)象),在遞歸調(diào)用中,base 對(duì)象應(yīng)該是存儲(chǔ)著函數(shù)表達(dá)式可選名稱的特定對(duì)象。但是,在這種情況下,this 總是指向全局對(duì)象。

(function foo(bar) {
  alert(this);
  !bar && foo(1); // "should" be special object, but always (correct) global
})(); // global

作為構(gòu)造器調(diào)用的函數(shù)中的 this

還有一個(gè)與 this 值相關(guān)的情況是在函數(shù)的上下文中,這是一個(gè)構(gòu)造函數(shù)的調(diào)用。

function A() {
  alert(this); // "a"對(duì)象下創(chuàng)建一個(gè)新屬性
  this.x = 10;
}
var a = new A();
alert(a.x); // 10

在這個(gè)例子中,new 運(yùn)算符調(diào)用“A”函數(shù)的內(nèi)部的[[Construct]] 方法,接著,在對(duì)象創(chuàng)建后,調(diào)用內(nèi)部的[[Call]] 方法。 所有相同的函數(shù)“A”都將 this 的值設(shè)置為新創(chuàng)建的對(duì)象。

函數(shù)調(diào)用中手動(dòng)設(shè)置 this

在函數(shù)原型中定義的兩個(gè)方法(因此所有的函數(shù)都可以訪問它)允許去手動(dòng)設(shè)置函數(shù)調(diào)用的 this 值。它們是 .apply 和 .call 方法。他們用接受的第一個(gè)參數(shù)作為 this 值,this 在調(diào)用的作用域中使用。這兩個(gè)方法的區(qū)別很小,對(duì)于 .apply,第二個(gè)參數(shù)必須是數(shù)組(或者是類似數(shù)組的對(duì)象,如 arguments,反過來,.call 能接受任何參數(shù)。兩個(gè)方法必須的參數(shù)是第一個(gè)——this。

例如:

var b = 10;
function a(c) {
  alert(this.b);
  alert(c);
}
a(20); // this === global, this.b == 10, c == 20
a.call({b: 20}, 30); // this === {b: 20}, this.b == 20, c == 30
a.apply({b: 30}, [40]) // this === {b: 30}, this.b == 30, c == 40

結(jié)論

在這篇文章中,我們討論了 ECMAScript 中 this 關(guān)鍵字的特征(對(duì)比于 C++ 和 Java,它們的確是特色)。我希望這篇文章有助于你準(zhǔn)確的理解 ECMAScript 中 this 關(guān)鍵字如何工作。

其它參考

  1. This
  2. The this keyword
  3. The new operator
  4. Function calls