首页 > 网站 > WEB开发 > 正文

JavaScript的this用法

2024-04-27 14:17:58
字体:
来源:转载
供稿:网友

javaScript的this用法

1. 全局代码中的this

this在全局上下文中,它的值是全局对象本身(Global Object),在浏览器中就是Window Object,如下图示。

看下面几个例子:

//Globalscope//TheimplicitPRopertyoftheglobalobjectvarfoo1="abc";console.log(this.foo1==window.foo1);console.log(foo1);console.log(this.foo1);console.log(window.foo1);//trueabcabcabc//Theimplicitpropertyoftheglobalobjectfoo2="def";console.log(this.foo2==window.foo2);console.log(foo2);console.log(this.foo2);console.log(window.foo2);//truedefdefdef//Theexplicitpropertyoftheglobalobjectthis.foo3="gh";console.log(this.foo3==window.foo3);console.log(foo3);console.log(this.foo3);console.log(window.foo3);//trueghghgh

2. 函数代码中的this

共有四种函数调用方式:

1)方法调用模式:作为对象属性调用,obj.func();(详见8.对象中的this)

2)函数调用模式:指向全局,直接调用func(); (详见1.全局代码中this)

3)构造器调用模式:使用new调用; (详见6.作为构造器调用的函数中的this)

4)call/apply调用模式:动态改变this指向。 (详见7.手动设置函数调用时this的值)

函数代码中this值的第一个特性,同时也是最主要的特性,即:它并非静态地绑定在函数上。

this的值是在进入执行上下文(Context Excution)的阶段确定的,在函数代码中,其值每次都不尽相同。然而进入执行代码阶段,其值就不能改变。

如果想给this赋一个新值是不可能的,因为在那时this根本就不是变量了。

结合例子来分析:

varfoo={name:"Foo"};varbar={name:"ting",sayName:function(){console.log(this=== bar);console.log("Mynameis:"+this.name);}};bar.sayName();//trueMynameis: tingfoo.sayName=bar.sayName;foo.sayName();//falseMynameis: Foo

通过上述结果分析可知:调用bar对象的sayName方法时,this指向bar对象。通过赋值方式使得foo的sayName方法指向bar的sayName方法,再调用foo对象的sayName方法时,this指向foo对象。Why?

this的值在函数中是非静态的,它的值确定是在执行代码阶段(函数调用时,具体代码执行前)。this的值是由激活上下文代码的调用者提供的,比如调用函数的外层上下文,更重要的是,this的值是由调用表达式的形式决定的。因此说,this并非静态绑定在函数上。

由于this非静态地绑定在函数上,那么我们是否可以在函数中动态地修改this的值呢?

varfoo={name:"Foo"};varperson={name:"ting",sayName:function(){console.log(this==person);this=foo;console.log("Mynameis:"+this.name);}};person.sayName();//MynameisFoofoo.sayName=person.sayName;foo.sayName();//Mynameisting

在sayName方法中,动态地修改this的值,重新执行以上的代码,发现this的值引用错误。这是由于一旦进入代码执行阶段(函数调用时,执行代码前),this的值确定,不能被改变。

影响函数代码中this值变化的因素在通常的函数调用中,this是由激活上下文代码的调用者来提供的,即调用函数的父上下文(parent context)。this值取决于调用函数的方式。

误区:一些文章或Javascript书籍中提到:this值取决于函数定义的方式,如果它是全局函数,this设置为全局对象,如果它是一个对象的方法,那么this总是指向这个对象。这是绝对不正确的。

切记一点:正是调用函数的方式影响了上下文中this的值。

eg: 全局函数被调用方式的不同形式激活,不同的调用方式导致了不同的this值。

functionfoo(){console.log(this);}foo();//Windowconsole.log(foo==foo.prototype.constructor);//truefoo.prototype.constructor();//foo{}

eg: 对象定义的方法,执行该方法,this值未必指向该对象。

varfoo={bar:function(){console.log(this);console.log(this===foo);}};foo.bar();//footruevarfooTest=foo.bar;console.log(fooTest===foo.bar);//同一个function,不同的调用表达式,this值不同fooTest();//Windowfalse

调用方式如何影响this的值? 为充分理解this值的确定,需要详细分析其内部类型之一,即引用类型(Reference type)。

3. 引用类型(Reference Type)

使用伪代码将引用类型的值表示为拥有两个属性的对象,包括base(拥有属性的那个对象),对象中的propertyName(属性名)。

varvalueofRefereceType={base:<baSEObject>,propertyName:<propertyname>};

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

1)当我们处理一个标识符时

2)或属性访问器

标识符的处理过程另作讨论,目前只需知道,该算法的返回值中,总是一个引用类型的值,这对于this来说非常重要。

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

varfoo;functionbar(){}

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

varfooReference={base:global,propertyName:'foo'};varbarReference={base:global,propertyName:'bar'};

伪代码GetValue描述从一个引用类型中得到对象真正的值。

functionGetValue(value){if(Type(value)!=Reference){returnvalue;}varbase=GetBase(value);if(base===null){throwreturnnewReferenceTypeError;}returnbase.[[Get]](GetPropertyName(value));}

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

GetValue(fooReference);//10GetValue(barReference);//functionobject"bar"

属性访问器:点(.)语法或括号([])语法

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

在中间计算的返回值中,引用类型的值为:

varfooReference={base:foo,propertyName:'bar'};GetValue(fooReference);//functionobject"bar"

引用类型的值域函数上下文中的this如何相关? --从最重要的意义上来说。 这个关联的过程是这篇文章的核心。

一个函数上下文中,确定this值的通用规则如下:

1)在一个函数上下文中,this由调用者提供,由调用函数的方式来决定。

2)如果调用括号()的左边是引用类型的值,this值将设为引用类型值的base对象(base object)。

3)如果调用括号()的左边不是引用类型的值(与引用类型类型不同的任何其它属性),this值为null,不过实际不存在this值为null的情况,因为当this值为null的时候,其值会被隐示转换为全局对象。(注:第5版的ECMAScript,已经不强迫转换为全局变量,而是赋值为undefined。)

看些例子,理解函数上下文中this值。

functionfoo(){console.log(this);}foo();//window

调用括号()的左边是引用类型的值,因为foo是一个标识符。

varfooReference={base:global,propertyName:'foo'};

相应地,this设置为引用类型的base对象,即全局对象。

使用属性访问器:

varfoo={bar:function(){console.log(this);}};foo.bar();//foo

我们再次拥有一个引用类型,其base是foo对象,在函数bar激活时用作this。

varfooBarReference={base:foo,propertyName:'bar'};

但是,使用另外一种形式激活相同的函数,我们得到其它的this值。

varfoo={bar:function(){console.log(this);}};vartest=foo.bar;test();//window

因为test作为标识符,生成了引用类型的其它值,其base(全局对象)用作this的值。

vartestReference={base:global,propertyName:'test'};

从上述例子中可以分析得知:使用不同的表达式激活函数为何会导致不同的this值,原因在于生成了引用类型(Type Reference)不同的中间值

再看两个例子,加深印象。

varfoo=function(){console.log(this);};foo();//window,becausevarfooReference={base:global,propertyName:'foo'};console.log(foo=foo.prototype.constructor);//另外一种形式的调用表达式foo.prototype.constructor();//foo.prototype,becausevarfooPrototypeConstructor={base:foo.prototype,propertyName:'constructor'};

通过调用方式动态确定this值的经典例子。

functionfoo(){console.log(this.bar);}varx={bar:10};vary={bar:20};x.test=foo;y.test=foo;x.test();//10y.test();//20

4. 函数调用和非引用类型

正如前面所提到的,当调用括号的左边不是引用类型而是其它类型,这个值自动设置为null,结果为全局对象。

思考如下这种表达式:

(function(){console.log(this);})(); //null ---> global

在这个例子中,我们有一个函数对象但不是引用类型的对象(它不是标识符,也不是属性选择器),相应地,this值最终设为全局对象。

更多复杂的例子:

varfoo={bar:function(){console.log(this);}};foo.bar();//foo(foo.bar)();//foo(foo.bar=foo.bar)();//Window(false||foo.bar)();//Window(foo.bar,foo.bar)();//Window

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

原因在于后面3个的调用中,应用一定的操作符之后,调用括号左边的值不再是引用类型的值。

第一个例子 --- 调用括号左边是引用类型的值,this为base对象,即foo;

第二个例子 --- 组运算符并不适用,如上面所提到的,从引用类型获取一个对象真正值的方法,如GetValue。 相应地,在组运算的返回中,得到的仍是一个引用类型。因此this值再次被设为base对象,即foo;

第三个例子 --- 与组操作符不同,赋值运算符触发了GetValue方法,返回的结果是函数对象,而不是引用类型的值,这意味着this值会被设置为null,最终会被变成全局对象。

第四个例子 --- 逻辑或运算符触发了GetValue方法,返回的结果......

第五个例子 --- 逗号运算符触发了GetValue方法,返回的结果是......

5. 引用类型和this为null

例外的情况:调用括号左侧是引用类型的值,但this却为null,最终被隐示转换为全局对象(global object)。情况成立的条件是:当引用类型值的base对象恰好为活跃对象(activiation object)。例如:内部子函数在父函数中被调用。局部变量、内部函数、形式参数存储在给定函数的激活对象中。

functionfoo(){functionbar(){console.log(this);//Window}bar();//thesameasAO.bar()}

foo();

活跃对象总是作为this返回,值为null(用伪代码表示AO.bar()相当于null.bar())。正如上面例子提到的,this的值最终由null隐示转换为全局对象。

上述例子的变形:

functionfoo(){functionbar(){console.log(this);//Window}returnbar();//thesameasAO.bar()}foo();

有一种情况与上述情况又不一样:使用with。

如果with对象包含函数属性,并且在with语句块中调用该函数。with语句会将该对象添加到作用域链的最前面,即在活跃对象之前。相应地,有了引用类型的值(标识符或属性访问器),其base对象不再是活跃对象,而是with语句对象。另外,值得一提的是,它不仅仅只针对内部函数,也针对全局函数,原因在于with对象比作用域链最前面的对象(全局对象或活跃对象)还要靠前。

varx=10;with({foo:function(){console.log(this.x);},x:20}){foo();//20}//becausevarfooReference={base:_withObject,propertyName:'foo'};

同样的情况出现在catch语句的实际参数中函数调用,catch对象添加到作用域的最前端,即活动对象或全局对象的前面。但是,这个特定的行为被确认为ECMAScript-262-3的bug,在ECMAScript-262-5中被修复了。这样,在特定的活动对象中,this不是指向catch对象,而是指向全局对象。

try{throwfunction(){console.log(this);};}catch(e){e();//ES3标准里是_catchObject,ES5标准里是Window}//ES3vareReference={base:_catchObject,propertyName:'e'};//ES5vareReference={base:global,propertyName:'e'};

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

(functionfoo(bar){alert(this);!bar&&foo(1);})();

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

函数作为构造函数时,通过new操作符创建对象实例的过程包括:首先调用Foo()函数内部的[[construct]]方法,其次,在对象创建之后,调用内部的[[call]]方法。所有相同的函数“Foo”将this设置为新创建的对象

functionFoo(){console.log(this);this.x=10;}vara=newFoo();con
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表