JavaScript是一种脚本语言,支持函数式编程、闭包和基于原型的继承等高级功能。JavaScript一开始看起来很容易上手,但是随着你使用的深入,你会发现JavaScript其实很难掌握,一些基本的概念简直匪夷所思。其中,JavaScript中的this关键字是一个比较容易混淆的概念。在不同的场景下,this会转化为不同的对象。有一种观点认为,只有正确把握JavaScript中的this关键字,才能跨入JavaScript语言的门槛。在主流的面向对象语言(如Java、C#等)中,this的含义明确而具体,即指向当前对象。通常在编译时绑定。在JavaScript中,this是在运行时绑定的,这也是JavaScript中this关键字具有多重含义的本质原因。由于JavaScript的运行时绑定特性,JavaScript中的this可以是全局对象、当前对象或任何对象,这完全取决于函数的调用方式。在JavaScript中有几种调用函数的方法:作为对象方法调用、作为函数调用、作为构造函数调用以及使用apply或call调用。俗话说,言不如表,表不如图。为了让人更好的理解JavaScriptthis指向什么?我们用一张图来解释一下:我把上图叫做“JavaScriptthisdecisiontree”(在非严格模式下)。让我们用一个例子来说明这个图如何帮助我们判断这个:varpoint={x:0,y:0,moveTo:function(x,y){this.x=this.x+x;this.y=this.y+y;}};//决策树解释:point.moveTo(1,1)函数没有用new调用,进入无决策,//用dot(.)调用,然后指向之前的点.moveTo调用对象,即pointpoint.moveTo(1,1);//this绑定到当前对象,即点对象point.moveTo()判断过程在“JavaScriptthisdecisiontree”如下:1)point中的.moveTo函数是用new调用的吗?这显然不是,进入“否”分支,即调用的函数是否带点(.)?;2)point.moveTo函数用dot(.)调用,即进入“是”分支,即这里的this指向point.moveTo之前的对象point;point.moveTo函数中this指向什么的分析如图所示:再举个例子,看下面的代码:functionfunc(x){this.x=x;}func(5);//这是全局对象窗口,x是全局变量//决策树分析:func()函数是用new调用的吗?如果不是,func()函数是用点调用的吗?如果不是,则this指向全局对象windowx;//x=>5在“JavaScriptthis决策树”中判断func()函数的过程如下:1)func(5)函数调用是否使用新的??这显然不是,进入“否”分支,即调用的函数是否带点(.)?;2)func(5)函数没有用dot(.)调用,即进入“否”分支,即这里的this指向全局变量window,所以this.x其实就是window.x;func函数的this如图所示指向什么的分析图如下图:对于直接作为函数调用的方法,我们看一个复杂的例子:varpoint={x:0,y:0,moveTo:function(x,y){//内部函数varmoveX=function(x){this.x=x;//这个指向什么?window};//内部函数varmoveY=function(y){this.y=y;//this指向什么?window};moveX(x);moveY(y);}};point.moveTo(1,1);point.x;//=>0point.y;//=>0x;//=>1y;//=>1point.moveTo(1,1)内部其实调用了moveX()和moveY()函数,而moveX()函数里面的this在“JavaScriptthis决策树”中判断如下:1)是用new调用的moveX(1)函数?这显然不是,进入“否”分支,即调用的函数是否带点(.)?;2)moveX(1)函数没有用dot(.)调用,即进入“否”分支,即这里的this指向全局变量window,所以this.x其实就是window.x;让我们来看看它作为函数调用的结构示例:functionPoint(x,y){this.x=x;//this?this.y=y;//this?}varnp=newPoint(1,1);np.x;//1varp=Point(2,2);p.x;//错误,p为空对象undefinedwindow.x;//2Point(1,1)函数invarnp=newPoint(1,1)this在“JavaScriptthisdecision树”中的判断过程如下:1)varnp=newPoint(1,1)calledwithnew?这个很明显,进入“是”分支,即就是,this指向np;2)那么this.x=1,即np.x=1;Point(2,2)函数在varp=Point(2,2)中确定this的过程"JavaScript这个决策树”如下:1)varp=Point(2,2)的调用是用new调用的吗?这个显然不是,进入“否”分支,即调用的函数是用dot(.)?;2)Point(2,2)函数不使用点(.)打电话?如果判断为否,则进入“否”分支,即这里的this指向全局变量window,则this.x实际为window.x;3)this.x=2表示window.x=2。***看一下使用call和apply调用函数的示例:functionPoint(x,y){this.x=x;this.y=y;this.moveTo=function(x,y){this.x=x;this.y=y;}}varp1=newPoint(0,0);varp2={x:0,y:0};p1.moveTo.apply(p2,[10,10]);//应用其实就是p2.moveTo(10,10)p2.x//10p1.moveTo.apply(p2,[10,10])在“JavaScriptthisdecisiontree”中判断的过程如下:我们知道apply和call方法两个功能极其强大。它们允许切换函数执行的上下文,即绑定到this的对象p1.moveTo.apply(p2,[10,10])实际上是p2.moveTo(10,10)。那么p2.moveTo(10,10)可以解释为:1)p2.moveTo(10,10)函数调用是用new调用的吗?这显然不是,进入“否”分支,即调用的函数是否带点(.)?;2)p2.moveTo(10,10)函数用dot(.)调用,即进入“是”分支,即这里的this指向p2.moveTo(10,10),所以p2.x=10;关于JavaScript函数执行环境的过程,IBMdeveloperworks文档库中的一段描述感觉很好,摘录如下:“JavaScript中的一个函数可以作为普通函数执行,也可以作为对象方法执行,这是主要的之所以有这么丰富的含义,当一个函数执行时,会创建一个执行环境(ExecutionContext),函数的所有行为都会发生在这个执行环境中,在构建执行环境时,JavaScript会首先创建参数变量,其中包含调用函数时传入的参数。接下来创建作用域链。然后初始化变量,首先初始化函数的形参表,值为arguments变量中对应的值,如果arguments变量中没有对应的值,则将形参初始化为undefined。如果函数包含内部函数,则初始化这些内部函数。如果不是,则继续初始化函数中定义的局部变量。需要注意的是,此时这些变量被初始化为undefined,它们的赋值操作只有在执行环境(ExecutionContext)创建成功后执行函数时才会执行。这对于我们理解JavaScript中的变量作用域非常重要,限于篇幅,我们这里不讨论这个话题。***给这个变量赋值,上面说了,会根据函数调用方式,给这个全局对象,当前对象等赋值。至此,函数的执行环境(ExecutionContext)创建成功,函数开始逐行执行,需要的变量全部从之前构建的执行环境(ExecutionContext)中读取。“理解这段话对理解Javascript函数会有很大的帮助。
