今天我想简单讨论下关于Javascript的作用域和this变量。“作用域”的概念就是说,我们的代码能够从哪里去访问某些函数或者变量,也就是它们所存在的上下文,或者说就是它们被执行的地方。
你可能已经见过有的人写类似这样的代码:
function someFunc() { var _this = this; something.on("click", function() { console.log(_this); }); };
可是你却搞不懂其中“var _this=this;”这一句到底是要干嘛。希望本篇文章能澄清这种疑惑。
第一种作用域是全局作用域(Global Scope),它的定义很简单。如果一个函数或者变量是全局的,那么在任何地方都能够访问它们。在浏览器里,全局作用域其实就是window对象。所以如果你在代码里这样定义一个变量:
var x = 9;
那么实际上你是在window对象上创建一个叫x的属性,并给它赋值为9。当然如果你愿意也可以这样写:
window.x = 9;
不过既然它是全局变量,一般人都不会这样做。window对象是全局的,它上面的所有属性都可以在代码里的任何地方直接访问到。
另外一种,也是最后一种作用域,就是本地作用域(Local Scope)。JavaScript在函数水平上创建本地作用域,比如:
function myFunc() { var x = 5; }; console.log(x); //undefined
因为x是在myFunc()之内声明的,所以它仅仅在myFunc()内是可以访问到的。
一点小提醒:
如果你没有使用var关键字来声明一个变量,那么它会自动变成全局的。所以这样写也是可行的:
function myFunc() { x = 5; }); console.log(x); //5
但这并不是一个好主意。这样做会把全局作用域搞乱,所以这种做法很不推荐。你应该尽量少地在全局作用域里声明变量。之所以像jQuery一类的函数库会这样做,也是出于这个原因:
(function() { var jQuery = { /* all my methods go here */ }; window.jQuery = jQuery. })();
先把所有的东西都放在一个匿名函数的函数体里,然后再立刻执行这个函数,这样一来,在这个函数里声明的所有变量就都只存在于本地作用域了。而在最后,你再把jQuery对象绑定到window对象上,这样jQuery就是全局的了,而你也就进而把所有的东西变成是全局可访问到的了(译者注:这些变量函数什么的仍然存在于本地作用域,所以不能从外面直接访问,当然,要访问的话,只能通过这个唯一的全局的接口:jQuery,这就是所谓的闭包的最大功德)。尽管在这段示例代码里我把jQuery简化到了不能再简化的地步,可是本质上说,它的源码的工作原理就是这样的。如果你想知道更多细节,强烈推荐你读一下Paul
Irish的文章:10 Things I learned from the jQuery Source。
因为本地作用域以函数为单位,所以在一个函数内定义的函数,是可以访问外面这个包含它的函数的本地变量的:
function outer() { var x = 5; function inner() { console.log(x); //5 } inner(); }
不过反过来却不行,outer()函数并不能访问inner()里面的任何变量:
function outer() { var x = 5; function inner() { console.log(x); //5 var y = 10; } inner(); console.log(y); //undefined }
目前来看,这都是些很简单很基本的东西。不过,如果我们要来审视一番this关键字,情况就变复杂很多了,我想咱们都遇见过这种情况(译者注:关于这里有争议,见后面注解【1】。):
$("myLink").on("click", function() { console.log(this); //points to myLink (as expected) $.ajax({ //ajax set up success: function() { console.log(this); //points to the global object. Huh? } }); });
每次在你的函数被执行的时候,this变量都是被自动赋值的,至于它的具体值到底是什么,这取决于该函数被呼叫的方式。JavaScript里面有几种主要的呼叫函数的方式,我并不打算现在在这里一一叙述,不过常用的就那么三种:作为一个对象的方法被呼叫;或者作为函数独自被呼叫;或者作为一个事件的处理器(event handler)被呼叫。不同的呼叫方式将导致this的值是不同的:
function foo() { console.log(this); //global object 译者注:其实就是window }; myapp = {}; myapp.foo = function() { console.log(this); //points to myapp object } var link = document.getElementById("myId"); link.addEventListener("click", function() { console.log(this); //points to link }, false);
情况一目了然。MDN对于第三种情况有详细的解释:
通常来说,在一个事件处理器被执行过程中,我们都希望能追踪到触发这个事件的对象,尤其是有时候可能会在若干个相似的对象上绑定同一个事件处理器(译者注:比如说你在一系列链接<a>对象上绑定了同一个处理click事件的处理器)。当我们用addEventListener()来绑定一个函数的时候,this的值会被改变,注意:this的值实际上是由呼叫者传递给函数的。
所以,现在,再回过头来看一开始的关于“var _this = this;”这一句的用意的疑问,我们才猛然发现已经离答案不远了。
$("#myLink").on("click", function() {})这句的意图是当这个DOM元素被点击时,这个函数便被执行。可是由于这个函数是作为一个事件处理器被呼叫的,所以this变量会指向ID为myLink的DOM元素。而你在Ajax请求里指定的success方法只是一个常规的函数,所以当它被执行的时候,this被赋值为全局对象。(译者注:关于这里有争议,见后面注解【1】。)
上述原因就是为什么你总会见到有人写:var _this = this或者var that = this,或者类似的东西,这样做的目的是把当前this的值备份留作以后不时之需。关于下面这段代码里第二个console.log()究竟应该输出什么值,很多人提出了异议,这个问题我以后再讨论(译者注:看来,作者本人没回避这个问题,我也是在后面提出异议者之一)。
$("myLink").on("click", function() { console.log(this); //points to myLink (as expected) var _this = this; //store reference $.ajax({ //ajax set up success: function() { console.log(this); //points to the global object. Huh? console.log(_this); //better! } }); });
另外也有一些呼叫函数的方法是可以主动明确地指定this的值的,不过因为这篇文章现在已经够长了,所以不如等改天再详细讨论吧。如有问题请留言,我会一一回复。
注解【1】:
这个问题已经有人在原文下面的评论中提到,就是
success: function() {
console.log(this); //points to the global object. Huh?
}
输出的并不是window,而是另外一个对象,看起来是jQuery用来记录Ajax设定参数的一个对象。
假设说我在一个加载了jQuery的网页里插入了一个id为vince的<a>标签,然后我执行:
$("#vince").on("click", function() { console.log(this); // options : var my_city="Washington,USA"; var my_key="xxxxxxxxxxxxxxxxxxxxxx"; var no_of_days=2; // build URI: var uri="http://free.worldweatheronline.com/feed/weather.ashx?q="+my_city+"&key="+my_key+"&format=json&no_of_days="+no_of_days+"&includeLocation=yes"; // uri-encode it to prevent errors : uri=encodeURI(uri); $.ajax({ type: "POST", url: uri, complete: function() { console.log(this); } }); });
我本想模拟用Ajax访问一个公共的并且仍然活跃的web service,用来做这个演示,而不是用我本地的server,但是找了半天也找不到,最后找到一个天气的,可是需要注册才有API码,所以上面的my_key是“xxxxx”,因此,这个Ajax请求将不会成功返回。所以我没有像原文那样使用success事件处理函数,而是使用了complete事件,这样不论如何保证它都会被调用。那么看到的结果其实是:
(图片上传不成功,可能是由于CSDN服务器的问题,迟些再补)
JavaScript中的作用域以及this变量,布布扣,bubuko.com
原文:http://blog.csdn.net/zlxadhkust/article/details/24520645