> 웹 프론트엔드 > JS 튜토리얼 > 자바스크립트 시리즈 심층 분석(13): 이게? 그래, 이거!_javascript 스킬

자바스크립트 시리즈 심층 분석(13): 이게? 그래, 이거!_javascript 스킬

WBOY
풀어 주다: 2016-05-16 15:21:43
원래의
1015명이 탐색했습니다.

머리말

이 기사에서는 실행 컨텍스트와 직접적으로 관련된 자세한 내용을 논의하겠습니다. 토론 주제는 바로 이 키워드입니다. 실습을 통해 이 주제가 어렵다는 것이 입증되었으며 다양한 실행 상황에서 이를 결정하는 데 문제가 자주 발생합니다.

많은 프로그래머는 프로그래밍 언어에서 this 키워드가 객체 지향 프로그램 개발과 밀접하게 관련되어 있으며 생성자가 새로 생성한 객체를 완전히 가리킨다고 믿는 데 익숙합니다. 이는 ECMAScript 사양에서도 구현되지만 앞으로 살펴보겠지만 ECMAScript에서는 새로 생성된 객체를 가리키는 것에만 국한되지 않습니다.

영어 번역: Dmitry A. Soshnikov Stoyan Stefanov의 도움으로 출판: 2010-03-07http://dmitrysoshnikov.com/ecmascript/chapter-3-this/러시아어 원본: Dmitry A. Soshnikov 수정: Zerogif 출판: 2009-06-28; 업데이트 날짜: 2010-03-07 http://dmitrysoshnikov.com/ecmascript/ru-chapter-3-this/ 이 기사의 대부분 내용은 http://www.denisdeng.com을 참조합니다. /? p=900 문장의 일부는 다음을 의미합니다: 저스틴의 중국어 번역

ECMAScript에서 이것이 정확히 무엇인지 자세히 살펴볼까요?

정의

실행 컨텍스트의 속성입니다.

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

여기서 VO는 이전 장에서 논의한 변수 객체입니다.

이 값은 컨텍스트에 들어갈 때 결정되며 컨텍스트가 실행되는 동안 변경되지 않습니다.

이러한 사례를 더 자세히 살펴보겠습니다.

전역 코드의


여기에서는 모든 것이 간단합니다. 전역 코드에서는 항상 전역 객체 자체이므로 간접적으로 참조하는 것이 가능합니다.

// 전역 개체의 속성을 명시적으로 정의합니다. this.a = 10; // global.a = 10alert(a); // 10 // 식별자에 할당하여 암시적 b = 20;alert(this.b ); // 20 // 변수 선언을 통해서도 암시적으로 선언됩니다. // 전역 컨텍스트의 변수 객체가 전역 객체이기 때문입니다. var c = 30 // 30

이것
함수 코드

이것을 함수 코드에서 사용할 때 흥미롭습니다. 이 상황은 어렵고 많은 문제를 일으킬 수 있습니다.

이 유형의 코드에서 this 값의 첫 번째(아마도 가장 중요한) 특징은 함수에 정적으로 바인딩되지 않는다는 것입니다.

위에서 언급했듯이 Context에 들어갈 때 이 값이 결정되는데, 함수 코드에서는 매번 완전히 다릅니다.

어쨌든 this의 값은 코드가 실행되는 동안 변경되지 않습니다. 즉, 변수가 아니기 때문에 새 값을 할당하는 것이 불가능합니다(반면 Python 프로그래밍 언어에서는 분명히 정의된 객체 자체는 런타임 중에 지속적으로 변경될 수 있습니다.

var foo = {x: 10}; var bar = { x: 20, test: function () { alert(this === bar); // true alert(this.x); // 20 this = foo; // 错误,任何时候不能改变this的值 alert(this.x); // 如果不出错的话,应该是10,而不是20 } }; // 在进入上下文的时候// this被当成bar对象// determined as "bar" object; why so - will// be discussed below in detail bar.test(); // true, 20 foo.test = bar.test; // 不过,这里this依然不会是foo// 尽管调用的是相同的function foo.test(); // false, 10 
로그인 후 복사

따라서 함수 코드에서 이 값의 변경에 영향을 미치는 몇 가지 요소가 있습니다.

우선 일반 함수 호출에서는 컨텍스트 코드, 즉 호출 함수의 상위 컨텍스트를 활성화하는 호출자가 이를 제공합니다. 이는 함수가 어떻게 호출되는지에 따라 다릅니다.

어떤 상황에서도 이 값을 정확하게 결정하기 위해서는 이 중요한 점을 이해하고 기억해야 합니다. 호출 컨텍스트에서 this 값에 영향을 미치는 것은 함수가 호출되는 방식이며 다른 것은 없습니다. (일부 기사에서 볼 수 있으며 심지어 자바스크립트에 관한 책에서도 다음과 같이 주장합니다. "this의 값은 함수가 어떻게 동작하는지에 따라 달라집니다.) 정의됩니다. 전역 함수인 경우 전역 개체로 설정되고, 함수가 개체의 메서드인 경우 항상 개체를 가리킵니다. 이는 절대 사실이 아닙니다.") 주제를 계속 진행하면 일반적인 전역 함수도 다양한 호출 방법에 의해 활성화된다는 점을 알 수 있습니다. 이러한 다양한 호출 방법으로 인해 this 값이 달라집니다.

function foo() { alert(this);} foo(); // global alert(foo === foo.prototype.constructor); // true // 但是同一个function的不同的调用表达式,this是不同的 foo.prototype.constructor(); // foo.prototype 
로그인 후 복사

일부 개체에 정의된 메서드로 함수를 호출하는 것이 가능하지만 이 개체에는 설정되지 않습니다.

var foo = { bar: function () { alert(this); alert(this === foo); }}; foo.bar(); // foo, true var exampleFunc = foo.bar; alert(exampleFunc === foo.bar); // true // 再一次,同一个function的不同的调用表达式,this是不同的 exampleFunc(); // global, false 
로그인 후 복사

그렇다면 함수를 호출하는 방식이 이 값에 어떤 영향을 미칠까요? 이 값의 결정을 완전히 이해하려면 내부 유형 중 하나인 참조 유형(참조 유형)을 자세히 분석해야 합니다.

참조 유형

의사 코드를 사용하면 참조 유형의 값을 두 가지 속성, 즉 기본(즉, 속성을 소유한 개체)과 기본의 propertyName이 있는 개체로 나타낼 수 있습니다.

var valueOfReferenceType = { base: <base object>, propertyName: <property name>}; 
로그인 후 복사

引用类型的值只有两种情况:

1. 当我们处理一个标示符时

2. 或一个属性访问器

标示符的处理过程在下一篇文章里详细讨论,在这里我们只需要知道,在该算法的返回值中,总是一个引用类型的值(这对this来说很重要)。

标识符是变量名,函数名,函数参数名和全局对象中未识别的属性名。例如,下面标识符的值:

var foo = 10;function bar() {} 
로그인 후 복사

在操作的中间结果中,引用类型对应的值如下:

var fooReference = { base: global, propertyName: 'foo'}; var barReference = { base: global, propertyName: 'bar'}; 
로그인 후 복사

为了从引用类型中得到一个对象真正的值,伪代码中的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)); } 
로그인 후 복사

内部的[[Get]]方法返回对象属性真正的值,包括对原型链中继承的属性分析。

GetValue(fooReference); // 10GetValue(barReference); // function object "bar" 
로그인 후 복사

属性访问器都应该熟悉。它有两种变体:点(.)语法(此时属性名是正确的标示符,且事先知道),或括号语法([])。

foo.bar();foo['bar'](); 
로그인 후 복사

在中间计算的返回值中,我们有了引用类型的值。

var fooBarReference = { base: foo, propertyName: 'bar'}; GetValue(fooBarReference); // function object "bar" 
로그인 후 복사

引用类型的值与函数上下文中的this值如何相关?——从最重要的意义上来说。 这个关联的过程是这篇文章的核心。 一个函数上下文中确定this值的通用规则如下:

在一个函数上下文中,this由调用者提供,由调用函数的方式来决定。如果调用括号()的左边是引用类型的值,this将设为引用类型值的base对象(base object),在其他情况下(与引用类型不同的任何其它属性),这个值为null。不过,实际不存在this的值为null的情况,因为当this的值为null的时候,其值会被隐式转换为全局对象。注:第5版的ECMAScript中,已经不强迫转换成全局变量了,而是赋值为undefined。

我们看看这个例子中的表现:

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激活时用作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 // 另外一种形式的调用表达式 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(); // 10y.test(); // 20 
로그인 후 복사

函数调用和非引用类型

因此,正如我们已经指出,当调用括号的左边不是引用类型而是其它类型,这个值自动设置为null,结果为全局对象。
让我们再思考这种表达式:

(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&#63;(false || foo.bar)(); // global&#63;(foo.bar, foo.bar)(); // global&#63; 
로그인 후 복사

为什么我们有一个属性访问器,它的中间值应该为引用类型的值,在某些调用中我们得到的this值不是base对象,而是global对象?

问题在于后面的三个调用,在应用一定的运算操作之后,在调用括号的左边的值不在是引用类型。

1.第一个例子很明显———明显的引用类型,结果是,this为base对象,即foo。

2.在第二个例子中,组运算符并不适用,想想上面提到的,从引用类型中获得一个对象真正的值的方法,如GetValue。相应的,在组运算的返回中———我们得到仍是一个引用类型。这就是this值为什么再次设为base对象,即foo。

3.第三个例子中,与组运算符不同,赋值运算符调用了GetValue方法。返回的结果是函数对象(但不是引用类型),这意味着this设为null,结果是global对象。

4.第四个和第五个也是一样——逗号运算符和逻辑运算符(OR)调用了GetValue 方法,相应地,我们失去了引用而得到了函数。并再次设为global。

引用类型和this为null

有一种情况是这样的:当调用表达式限定了call括号左边的引用类型的值, 尽管this被设定为null,但结果被隐式转化成global。当引用类型值的base对象是被活动对象时,这种情况就会出现。
下面的实例中,内部函数被父函数调用,此时我们就能够看到上面说的那种特殊情况。正如我们在第12章知道的一样,局部变量、内部函数、形式参数储存在给定函数的激活对象中。

function foo() { function bar() { alert(this); // global } bar(); // the same as AO.bar()} 
로그인 후 복사

活动对象总是作为this返回,值为null——(即伪代码的AO.bar()相当于null.bar())。这里我们再次回到上面描述的例子,this设置为全局对象。

有一种情况除外:如果with对象包含一个函数名属性,在with语句的内部块中调用函数。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(); // ES3标准里是__catchObject, ES5标准里是global } // on idea var eReference = { base: __catchObject, propertyName: 'e'}; // ES5新标准里已经fix了这个bug,// 所以this就是全局对象了var eReference = { base: global, propertyName: 'e'}; 
로그인 후 복사

同样的情况出现在命名函数(函数的更对细节参考第15章Functions)的递归调用中。在函数的第一次调用中,base对象是父活动对象(或全局对象),在递归调用中,base对象应该是存储着函数表达式可选名称的特定对象。但是,在这种情况下,this总是指向全局对象。

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

作为构造器调用的函数中的this

还有一个与this值相关的情况是在函数的上下文中,这是一个构造函数的调用。

function A() { alert(this); // "a"对象下创建一个新属性 this.x = 10;} var a = new A();alert(a.x); // 10 
로그인 후 복사

在这个例子中,new运算符调用“A”函数的内部的[[Construct]] 方法,接着,在对象创建后,调用内部的[[Call]] 方法。 所有相同的函数“A”都将this的值设置为新创建的对象。

函数调用中手动设置this

在函数原型中定义的两个方法(因此所有的函数都可以访问它)允许去手动设置函数调用的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 == 30a.apply({b: 30}, [40]) // this === {b: 30}, this.b == 30, c == 40 
로그인 후 복사

结论

在这篇文章中,我们讨论了ECMAScript中this关键字的特征(对比于C++ 和 Java,它们的确是特色)。我希望这篇文章有助于你准确的理解ECMAScript中this关键字如何工作。同样,我很乐意在评论中回到你的问题。

원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿