搜尋
首頁web前端js教程JavaScript ECMA-262-3 深入解析.第三章.this_javascript技巧

介绍
在这篇文章里,我们将讨论跟执行上下文直接相关的更多细节。讨论的主题就是this关键字。
实践证明,这个主题很难,在不同执行上下文中确定this的值经常会发生问题。
许多程序员习惯的认为,在程序语言中,this关键字与面向对象程序开发紧密相关,其完全指向由构造器新创建的对象。在ECMAScript规范中也是这样实现的,但正如我们将看到那样,在ECMAScript中,this并不限于只用来指向新创建的对象。
下面让我们更详细的了解一下,在ECMAScript中this的值到底是什么?
定义
this是执行上下文中的一个属性:

复制代码 代码如下:

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

这里VO是我们前一章讨论的变量对象。
this与上下文中可执行代码(的类型)直接相关。this的值在进入上下文时确定,并且在上下文运行代码期间不会改变this的值。
下面让我们更详细研究这些场景。
this在全局代码中的值
在这里一切都很简单。在全局代码中,this始终是全局对象本身,这样就有可能间接的引用到它了。
复制代码 代码如下:

// explicit property definition of
// the global object
this.a = 10; // global.a = 10
alert(a); // 10
// implicit definition via assigning
// to unqualified identifier
b = 20;
alert(this.b); // 20
// also implicit via variable declaration
// because variable object of the global context
// is the global object itself
var c = 30;
alert(this.c); // 30

this在函数代码中的值
在函数代码中使用this时很有趣,这种应用场景很难且会导致很多问题。
在这种类型的代码中,this值的首要(也许是最主要的)特点是它没有静态绑定到一个函数。
正如我们上面曾提到的那样,this的值在进入上下文时确定,在函数代码中,this的值每一次(进入上下文时)可能完全不同。
不管怎样,在代码运行期间,this的值是不变的,也就是说,因为this不是一个变量,所以不可能为其分配一个新值。(相反,在Python编程语言中,它明确的定义为对象本身,在运行期间可以不断改变)。
复制代码 代码如下:

var foo = {x: 10};
var bar = {
x: 20,
test: function () {
alert(this === bar); // true
alert(this.x); // 20
this = foo; // error
alert(this.x); // if there wasn't an error then 20, not 10
}
};
// on entering the context this value is
// determined as "bar" object; why so - will
// be discussed below in detail
bar.test(); // true, 20
foo.test = bar.test;
// however here this value will now refer
// to "foo" – even though we're calling the same function
foo.test(); // false, 10

那么,在函数代码中,什么影响了this的值发生变化?有几个因素。
首先,在通常的函数调用中,this是由激活上下文代码的调用者来提供的,即调用函数的父上下文(parent context)。this取决于调用函数的方式。(译者注:参考这里)
为了在任何情况下准确无误的确定this值,有必要理解和记住这重要的一点:正是调用函数的方式影响了调用的上下文中this的值,没有别的什么(我们可以在一些文章,甚至是在关于javascript的书籍中看到,它们声称:“this的值取决于函数如何定义,如果它是全局函数,this设置为全局对象,如果函数是一个对象的方法,this将总是指向这个对象。–这绝对不正确”)。继续我们的话题,可以看到,即使是正常的全局函数也会因为不同调用方式而激活,这些不同调用方式产生了this不同的值。
复制代码 代码如下:

function foo() {
alert(this);
}
foo(); // global
alert(foo === foo.prototype.constructor); // true
// but with another form of the call expression
// of the same function, this value is different
foo.prototype.constructor(); // foo.prototype

有时可能将函数作为某些对象的一个方法来调用,此时this的值不会设置为这个对象。
复制代码 代码如下:

var foo = {
bar: function () {
alert(this);
alert(this === foo);
}
};
foo.bar(); // foo, true
var exampleFunc = foo.bar;
alert(exampleFunc === foo.bar); // true
// again with another form of the call expression
// of the same function, we have different this value
exampleFunc(); // global, false

那么,到底调用函数的方式如何影响this的值?为了充分理解this的值是如何确定的,我们需要详细分析一个内部类型(internal type)——引用类型(Reference type)。
引用类型
用伪代码可以把引用类型表示为拥有两个属性的对象——base(即拥有属性的那个对象),和base中的propertyName 。
复制代码 代码如下:

var valueOfReferenceType = {
base: ,
propertyName:
};

引用类型的值仅存在于两种情况中:
1. 当我们处理一个标示符时;(when we deal with an identifier;)
2. 或一个属性访问器;(or with a property accessor.)
标示符的处理过程在 Chapter 4. Scope chain中讨论;在这里我们只需要知道,使用这种处理方式的返回值总是一个引用类型的值(这对this来说很重要)。
标识符是变量名,函数名,函数参数名和全局对象中未识别的属性名。例如,下面标识符的值:
var foo = 10;
function bar() {}
在操作的中间结果中,引用类型对应的值如下:
复制代码 代码如下:

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

为了从引用类型中得到一个对象真正的值,在伪代码中可以用GetValue方法(译者注:11.1.6)来表示,如下:
复制代码 代码如下:

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

内部的[[Get]]方法返回对象属性真正的值,包括对原型链中继承属性的分析。
GetValue(fooReference); // 10
GetValue(barReference); // function object "bar"
属性访问器都应该熟悉。它有两种变体:点(.)语法(此时属性名是正确的标示符,且事先知道),或括号语法([])。
foo.bar();
foo['bar']();
在计算中间的返回值中,引用类型对应的值如下:
复制代码 代码如下:

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

GetValue(fooBarReference); // function object "bar"
那么,从最重要的意义上来说,引用类型的值与函数上下文中的this的值是如何关联起来的呢?这个关联的过程是这篇文章的核心。(The given moment is the main of this article.) 在一个函数上下文中确定this的值的通用规则如下:
在一个函数上下文中,this的值由调用者提供,且由调用函数的方式决定。如果调用括号()的左边是引用类型的值,this将设为这个引用类型值的base对象,在其他情况下(与引用类型不同的任何其它属性),this的值都为null。不过,实际不存在this的值为null的情况,因为当this的值为null的时候,其值会被隐式转换为全局对象。
下面让我们看个例子:
复制代码 代码如下:

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

我们看到在调用括号的左边是一个引用类型值(因为foo是一个标示符):
复制代码 代码如下:

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

相应地,this也设置为引用类型的base对象。即全局对象。
同样,使用属性访问器:
复制代码 代码如下:

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

同样,我们拥有一个引用类型的值,其base是foo对象,在函数bar激活时将base设置给this。
复制代码 代码如下:

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

但是,如果用另一种方式激活相同的函数,this的值将不同。
var test = foo.bar;
test(); // global
因为test作为标识符,产生了其他引用类型的值,该值的base(全局对象)被设置为this的值。
复制代码 代码如下:

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

现在,我们可以很明确的说明,为什么用不同的形式激活同一个函数会产生不同的this,答案在于不同的引用类型(type Reference)的中间值。
复制代码 代码如下:

function foo() {
alert(this);
}
foo(); // global, because
var fooReference = {
base: global,
propertyName: 'foo'
};
alert(foo === foo.prototype.constructor); // true
// another form of the call expression
foo.prototype.constructor(); // foo.prototype, because
var fooPrototypeConstructorReference = {
base: foo.prototype,
propertyName: 'constructor'
};

另一个通过调用方式动态确定this的值的经典例子:
复制代码 代码如下:

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

函数调用和非引用类型
那么,正如我们已经指出,当调用括号的左边不是引用类型而是其它类型,this的值自动设置为null,实际最终this的值被隐式转换为全局对象。
让我们思考下面这种函数表达式:
复制代码 代码如下:

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

在这个例子中,我们有一个函数对象但不是引用类型的对象(因为它不是标示符,也不是属性访问器),相应地,this的值最终被设为全局对象。
更多复杂的例子:
复制代码 代码如下:

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?

那么,为什么我们有一个属性访问器,它的中间值应该为引用类型的值,但是在某些调用中我们得到this的值不是base对象,而是global对象?
问题出现在后面的三个调用,在执行一定的操作运算之后,在调用括号的左边的值不再是引用类型。
第一个例子很明显———明显的引用类型,结果是,this为base对象,即foo。
在第二个例子中,分组操作符(译者注:这里的分组操作符就是指foo.bar外面的括号"()")没有实际意义,想想上面提到的,从引用类型中获得一个对象真正的值的方法,如GetValue (参考11.1.6)。相应的,在分组操作的返回值中———我们得到的仍是一个引用类型。这就是this的值为什么再次被设为base对象,即 foo。
第三个例子中,与分组操作符不同,赋值操作符调用了GetValue方法(参考11.13.1的第三步)。返回的结果已经是函数对象(不是引用类型),这意味着this的值被设为null,实际最终结果是被设置为global对象。
第四个和第五个也是一样——逗号操作符和逻辑操作符(OR)调用了GetValue 方法,相应地,我们失去了引用类型的值而得到了函数类型的值,所以this的值再次被设为global对象。
引用类型和this为null
有一种情况,如果调用方式确定了引用类型的值(when call expression determinates on the left hand side of call brackets the value of Reference type。译者注,原文有点拖沓!),不管怎样,只要this的值被设置为null,其最终就会被隐式转换成global。当引用类型值的base对象是激活对象时,就会导致这种情况。
下面的实例中,内部函数被父函数调用,此时我们就能够看到上面说的那种特殊情况。正如我们在 第二章 学到的一样,局部变量、内部函数、形式参数都储存在给定函数的激活对象中。
复制代码 代码如下:

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

激活对象总是作为this的值返回——null(即伪代码AO.bar()相当于null.bar())。(译者注:不明白参考这里)这里我们再次回到上面描述的情况,this的值最终还是被设置为全局对象。
有一种情况除外:“在with语句中调用函数,且在with对象(译者注:即下面例子中的__withObject)中包含函数名属性时”。with语句将其对象添加在作用域链最前端,即在激活对象的前面。那么对应的,引用类型有值(通过标识符或属性访问器),其base对象不再是激活对象,而是with语句的对象。顺便提一句,这种情况不仅跟内部函数相关,还跟全局函数相关,因为with对象比作用域链里的最前端的对象(全局对象或一个激活对象)还要靠前。
复制代码 代码如下:

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

在catch语句的实际参数中的函数调用存在类似情况:在这种情况下,catch对象被添加到作用域的最前端,即在激活对象或全局对象的前面。但是,这个特定的行为被确认为是ECMA-262-3的一个bug,这个在新版的ECMA-262-5中修复了。修复后,在特定的激活对象中,this指向全局对象。而不是catch对象。
复制代码 代码如下:

try {
throw function () {
alert(this);
};
} catch (e) {
e(); // __catchObject - in ES3, global - fixed in ES5
}
// on idea
var eReference = {
base: __catchObject,
propertyName: 'e'
};
// but, as this is a bug
// then this value is forced to global
// null => global
var eReference = {
base: global,
propertyName: 'e'
};

同样的情况出现在命名函数(函数的更多细节参考Chapter 5. Functions)的递归调用中。在函数的第一次调用中,base对象是父激活对象(或全局对象),在递归调用中,base对象应该是存储着函数表达式可选名称的特定对象。但是,在这种情况下,this的值也总是被设置为global。
复制代码 代码如下:

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

this在作为构造器调用的函数中的值
还有一个在函数的上下文中与this的值相关的情况是:函数作为构造器调用时。
复制代码 代码如下:

function A() {
alert(this); // newly created object, below - "a" object
this.x = 10;
}
var a = new A();
alert(a.x); // 10

在这个例子中,new操作符调用“A”函数内部的[[Construct]]方法,接着,在对象创建后,调用其内部的[[Call]]方法,所有相同的函数“A”都将this的值设置为新创建的对象。
手动设置一个函数调用的this
在Function.prototype中定义了两个方法允许手动设置函数调用时this的值,它们是.apply和.call方法(所有的函数都可以访问它们)。它们用接受的第一个参数作为this的值,this在调用的作用域中使用。这两个方法的区别不大,对于.apply,第二个参数必须是数组(或者是类似数组的对象,如arguments,相反,.call能接受任何参数。两个方法必须的参数都是第一个——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

结论
在这篇文章中,我们讨论了ECMAScript中this关键字的特征(and they really are features, in contrast, say, with C++ or Java,译者注:这句话没什么大用,还不知道咋翻好,暂不翻了)。我希望这篇文章有助于你准确的理解ECMAScript中this关键字如何工作。同样,我很高兴在评论中回答您的问题。
其他参考
10.1.7 – This;
11.1.1 – The this keyword;
11.2.2 – The new operator;
11.2.3 – Function calls.
英文地址 : ECMA-262-3 in detail. Chapter 3. This.
中文地址 : [JavaScript]ECMA-262-3 深入解析.第三章.this
翻译声明:
1.因为Denis已经翻译过这篇文章,所以该篇译文在部分章节参考了他的译文,参考引用部分大概占整篇文章的30%左右,另外70%左右完全是重新翻译的。
2.在翻译过程中,跟原作者进行了充分的沟通,大家看译文的时候,可以多参考原文的留言列表。
3.再好的翻译也赶不上原汁原味的原文,所以推荐大家看过译文之后还是要再仔细看看原文。
陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
JavaScript和Web:核心功能和用例JavaScript和Web:核心功能和用例Apr 18, 2025 am 12:19 AM

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。

了解JavaScript引擎:實施詳細信息了解JavaScript引擎:實施詳細信息Apr 17, 2025 am 12:05 AM

理解JavaScript引擎內部工作原理對開發者重要,因為它能幫助編寫更高效的代碼並理解性能瓶頸和優化策略。 1)引擎的工作流程包括解析、編譯和執行三個階段;2)執行過程中,引擎會進行動態優化,如內聯緩存和隱藏類;3)最佳實踐包括避免全局變量、優化循環、使用const和let,以及避免過度使用閉包。

Python vs. JavaScript:學習曲線和易用性Python vs. JavaScript:學習曲線和易用性Apr 16, 2025 am 12:12 AM

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

Python vs. JavaScript:社區,圖書館和資源Python vs. JavaScript:社區,圖書館和資源Apr 15, 2025 am 12:16 AM

Python和JavaScript在社區、庫和資源方面的對比各有優劣。 1)Python社區友好,適合初學者,但前端開發資源不如JavaScript豐富。 2)Python在數據科學和機器學習庫方面強大,JavaScript則在前端開發庫和框架上更勝一籌。 3)兩者的學習資源都豐富,但Python適合從官方文檔開始,JavaScript則以MDNWebDocs為佳。選擇應基於項目需求和個人興趣。

從C/C到JavaScript:所有工作方式從C/C到JavaScript:所有工作方式Apr 14, 2025 am 12:05 AM

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

JavaScript引擎:比較實施JavaScript引擎:比較實施Apr 13, 2025 am 12:05 AM

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

超越瀏覽器:現實世界中的JavaScript超越瀏覽器:現實世界中的JavaScriptApr 12, 2025 am 12:06 AM

JavaScript在現實世界中的應用包括服務器端編程、移動應用開發和物聯網控制:1.通過Node.js實現服務器端編程,適用於高並發請求處理。 2.通過ReactNative進行移動應用開發,支持跨平台部署。 3.通過Johnny-Five庫用於物聯網設備控制,適用於硬件交互。

使用Next.js(後端集成)構建多租戶SaaS應用程序使用Next.js(後端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:23 AM

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
1 個月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
1 個月前By尊渡假赌尊渡假赌尊渡假赌
威爾R.E.P.O.有交叉遊戲嗎?
1 個月前By尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

SecLists

SecLists

SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境