首页 » 编写高质量代码:改善JavaScript程序的188个建议 » 编写高质量代码:改善JavaScript程序的188个建议全文在线阅读

《编写高质量代码:改善JavaScript程序的188个建议》建议88:this是动态指针,不是静态引用

关灯直达底部

this是一个动态指针。在JavaScript中,类似指针特性的标识符还有以下3个。

❑callee:函数的参数集合包含的一个静态指针,它始终指向参数集合所属的函数。

❑prototype:函数包含的一个半静态指针,在默认状态下它始终指向函数附带的原型对象,不过可以改变这个指针,使它指向其他对象。

❑constructor:对象包含的一个指针,它始终指向创建该对象的构造函数。

this所指向的对象是由this所在的执行域决定的,而不是由this所在的定义域决定(如图4.6所示)。this是JavaScript执行作用域的一个属性,它的指针始终指向当前调用对象。

图 4.6 this指针的变化示意图

JavaScript中的函数可以在多个地方被引用,而且这种引用是在执行时才确定的。因此,JavaScript方法的灵活性注定了this指针只能在运行环境中动态地确定。因此,只有当this被最后执行时才能够准确确定它所指代的对象。

在JavaScript中,闭包扮演着非常重要的角色,它能够改变this的指向。例如,在下面这个示例中,在一个对象中(如o1)引用或调用另一个对象(如o)的方法时,该方法中的this指针是变化的。当引用对象o的方法f时,它的this就会根据执行对象而定,但在调用对象o的方法f时,它的this还是指向原定义的对象o。


var name=/"this=window/";

var o={

name:/"this=o/",

f:function{

return this;

}

};

var o1={

name:/"this=o1/",

f:o.f;//引用对象o中的方法f

}

var a=o1.f;

alert(a.name);//this实际指向对象为o1


下面尝试把对象o的方法f封装在闭包中,然后再进行引用:


var o1={

name:/"this=o1/",

f:function{//this使用闭包封装方法的引用

return o.f;

}

}

var a=o1.f;

alert(a.name);//this实际指向对象为Window


方法f中的this既不指向对象o,也不指向对象o1,而是指向对象Window。因为在执行对象o1的方法f时,仅指定了闭包运行的上下文环境,即执行闭包的对象为o1,但是闭包内“包裹”的方法f并没有被执行,当再次执行闭包内的方法时,执行环境已经发生了变化,执行对象从o1变为全局对象Window,所以this就指向了Window。

call和apply方法能够强制改变函数的执行作用域,它们会破坏函数引用和调用的一般规律,因此使用这两个方法可以强制地指定this的指代对象。异步调用也会破坏this指针应用的一般规律,这是因为函数在被传递给定时器或事件处理函数时才被调用,这破坏了函数的上下文运行环境。下面结合代码具体进行说明:

(1)指向当前DOM对象

下面这个按钮定义了一个单击事件属性,其中包含了this关键字。


<input type=/"button/"onclick=/"this.value=/'是我呀,我就是主人/'/"/>


其中,onclick事件属性中包含的this就代表当前DOM对象input。

(2)指向构造函数的实例对象

定义一个构造函数,在其中使用this关键字作为临时代表,然后使用new运算符实例化构造函数。


function F{

this.name=/"我就是主人/";

}

var f=new F;

alert(f.name);


这里的this就代表当前实例对象f。

(3)指向当前对象直接量

下面是一个对象直接量,它包含了两个属性,其中方法me返回关键字this。


var o={

name:/"我是对象o/",

me:function{

return this;

}

}

var who=o.me;

alert(who.name);//读取this所代表对象的属性name,返回字符串/"我是对象o/"


在调用对象直接量o的方法me后,变量who的值就是this的值,它代表当前对象直接量o,然后读取对象o的属性name,返回字符串/"我是对象o/"。

(4)指向全局对象

在函数f中调用this关键字,并且为this定义并初始化一个属性name,在调用函数之后,可以直接读取属性name。


function f{

this.name=/"我是谁?/";

}

f;//调用方法f

alert(name);//直接读取属性name,返回值为/"我是谁?/"


在函数f中,this实际上就是代表window。实际上,上面代码可以写成如下形式:


window.f=function{

this.name=/"我是谁?/"

}

window.f;

alert(window.name);


(5)指向当前作用域对象

this关键字并不总是代表当前对象,下面这个示例能够很好地说明这个问题。


<input type=/"button/"onclick=

/"this.value=/'是我呀,我就是主人/'/"/>

<input type=/"button/"onclick=/"f/"/>

<script language=/"javascript/"type=/"text/javascript/">

function f{

this.;

}

</script>


上面示例中的第一个按钮的事件属性中包含的this就指向当前对象。但由于在第二个按钮的鼠标单击事件属性中以调用的方式调用全局作用域中的函数f,所以其中的this就代表Window对象,而不是当前按钮(第二个按钮)。

要是改变函数的用法,先在脚本中获取第二个按钮对象的引用,然后把函数f作为一个值传递给对象的onclick事件属性,代码如下:


<input type=/"button/"/>

<script language=/"javascript/"type=/"text/javascript/">

var btn2=document.getElementsByTagName(/"input/")[1];

btn2.onclick=f;

function f{

this.;

}

</script>


在上面的示例中,因为this关键字就表示按钮对象本身,所以单击按钮之后就能够改变按钮的值。这也说明在将函数赋值给按钮对象之后,虽然函数的定义作用域没有发生变化,但它的执行作用域已经从全局作用域变为对象作用域,因此,this关键字所代表的对象也会随之发生变化。

如果以同样的方式把该函数作为事件处理函数赋值给不同的按钮对象,那么this会分别代表不同的按钮对象。这也说明如果改变函数的执行作用域,那么函数所包含的this关键字也会指向不同的对象。

下面这个示例可以更好地说明作用域对于this关键字的影响。


function f{

return this;

}

var o={

name:/"对象o/",

me:f,

o1:{

name:/"对象o1/",

me:f,

o2:{

name:/"对象o2/",

me:f

}

}

}

var who=o.o1.o2.me;

alert(who.name);//字符串/"对象o2/",说明当前this代表对象o2

var who=o.o1.me;

alert(who.name);//字符串/"对象o1/",说明当前this代表对象o1

var who=o.me;

alert(who.name);//字符串/"对象o/",说明当前this代表对象o


首先,定义函数f,设置其返回值为this关键字,然后把该函数作为值传递给不同作用域中的属性me,最后分别调用它们,这时会发现this所代表的对象是不同的。

但是,如果不是以值的方式传递函数f,而是直接调用,则会发现其包含的this都代表Window对象。也就是说,this的执行作用域并没有发生变化,仍然根据定义它的作用域来确定,即全局作用域。