JavaScript中的作用域以及this变量

原文:Scope and this in JavaScript


今天我想简单讨论下关于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,或者类似的东西(见后面注解【2】),这样做的目的是把当前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事件,这样不论如何保证它都会被调用。那么看到的结果其实是:



实际上关于这个被输出的东西到底是怎么来的,我们可以在Chrome里面用单步调试的方法来追踪,不过前提是,要加载非min版本的jQuery源码,不然没有人看的懂,所以这需要时间,我想还是等我比较闲的时候再来搞吧。


注解【2】:

你多数会见到的一种做法应该是用self,"self = this;"这种写法是目前比较流行的。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值