【repost】JavaScript Scoping and Hoisting
JavaScript Scoping and Hoisting
Do you know what value will be alerted if the following is executed as a JavaScript program?
var foo = 1;
function bar() {
	if (!foo) {
		var foo = 10;
	}
	alert(foo);
}
bar();
If it surprises you that the answer is “10”, then this one will probably really throw you for a loop:
var a = 1;
function b() {
	a = 10;
	return;
	function a() {}
}
b();
alert(a);
Here, of course, the browser will alert “1”. So what’s going on here? While it might seem strange, dangerous, and confusing, this is actually a powerful and expressive feature of the language. I don’t know if there is a standard name for this specific behavior, but I’ve come to like the term “hoisting”. This article will try to shed some light on this mechanism, but first lets take a necessary detour to understand JavaScript’s scoping.
Scoping in JavaScript
One of the sources of most confusion for JavaScript beginners is scoping. Actually, it’s not just beginners. I’ve met a lot of experienced JavaScript programmers who don’t fully understand scoping. The reason scoping is so confusing in JavaScript is because it looks like a C-family language. Consider the following C program:
#include <stdio.h>
int main() {
	int x = 1;
	printf("%d, ", x); // 1
	if (1) {
		int x = 2;
		printf("%d, ", x); // 2
	}
	printf("%d\n", x); // 1
}
The output from this program will be 1, 2, 1. This is because C, and the rest of the C family, has block-level scope. When control enters a block, such as the if statement, new variables can be declared within that scope, without affecting the outer scope. This is not the case in JavaScript. Try the following in Firebug:
var x = 1;
console.log(x); // 1
if (true) {
	var x = 2;
	console.log(x); // 2
}
console.log(x); // 2
In this case, Firebug will show 1, 2, 2. This is because JavaScript has function-level scope. This is radically different from the C family. Blocks, such as if statements, do not create a new scope. Only functions create a new scope.
To a lot of programmers who are used to languages like C, C++, C#, or Java, this is unexpected and unwelcome. Luckily, because of the flexibility of JavaScript functions, there is a workaround. If you must create temporary scopes within a function, do the following:
function foo() {
	var x = 1;
	if (x) {
		(function () {
			var x = 2;
			// some other code
		}());
	}
	// x is still 1.
}
This method is actually quite flexible, and can be used anywhere you need a temporary scope, not just within block statements. However, I strongly recommend that you take the time to really understand and appreciate JavaScript scoping. It’s quite powerful, and one of my favorite features of the language. If you understand scoping, hoisting will make a lot more sense to you.
Declarations, Names, and Hoisting
In JavaScript, a name enters a scope in one of four basic ways:
- Language-defined: All scopes are, by default, given the names 
thisandarguments. - Formal parameters: Functions can have named formal parameters, which are scoped to the body of that function.
 - Function declarations: These are of the form 
function foo() {}. - Variable declarations: These take the form 
var foo;. 
Function declarations and variable declarations are always moved (“hoisted”) invisibly to the top of their containing scope by the JavaScript interpreter. Function parameters and language-defined names are, obviously, already there. This means that code like this:
function foo() {
	bar();
	var x = 1;
}
is actually interpreted like this:
function foo() {
	var x;
	bar();
	x = 1;
}
It turns out that it doesn’t matter whether the line that contains the declaration would ever be executed. The following two functions are equivalent:
function foo() {
	if (false) {
		var x = 1;
	}
	return;
	var y = 1;
}
function foo() {
	var x, y;
	if (false) {
		x = 1;
	}
	return;
	y = 1;
}
Notice that the assignment portion of the declarations were not hoisted. Only the name is hoisted. This is not the case with function declarations, where the entire function body will be hoisted as well. But remember that there are two normal ways to declare functions. Consider the following JavaScript:
function test() {
	foo(); // TypeError "foo is not a function"
	bar(); // "this will run!"
	var foo = function () { // function expression assigned to local variable 'foo'
		alert("this won't run!");
	}
	function bar() { // function declaration, given the name 'bar'
		alert("this will run!");
	}
}
test();
In this case, only the function declaration has its body hoisted to the top. The name ‘foo’ is hoisted, but the body is left behind, to be assigned during execution.
That covers the basics of hoisting, which is not as complex or confusing as it seems. Of course, this being JavaScript, there is a little more complexity in certain special cases.
Name Resolution Order
The most important special case to keep in mind is name resolution order. Remember that there are four ways for names to enter a given scope. The order I listed them above is the order they are resolved in. In general, if a name has already been defined, it is never overridden by another property of the same name. This means that a function declaration takes priority over a variable declaration. This does not mean that an assignment to that name will not work, just that the declaration portion will be ignored. There are a few exceptions:
- The built-in name 
argumentsbehaves oddly. It seems to be declared following the formal parameters, but before function declarations. This means that a formal parameter with the nameargumentswill take precedence over the built-in, even if it is undefined. This is a bad feature. Don’t use the nameargumentsas a formal parameter. - Trying to use the name 
thisas an identifier anywhere will cause a SyntaxError. This is a good feature. - If multiple formal parameters have the same name, the one occurring latest in the list will take precedence, even if it is undefined.
 
Named Function Expressions
You can give names to functions defined in function expressions, with syntax like a function declaration. This does not make it a function declaration, and the name is not brought into scope, nor is the body hoisted. Here’s some code to illustrate what I mean:
foo(); // TypeError "foo is not a function"
bar(); // valid
baz(); // TypeError "baz is not a function"
spam(); // ReferenceError "spam is not defined"
var foo = function () {}; // anonymous function expression ('foo' gets hoisted)
function bar() {}; // function declaration ('bar' and the function body get hoisted)
var baz = function spam() {}; // named function expression (only 'baz' gets hoisted)
foo(); // valid
bar(); // valid
baz(); // valid
spam(); // ReferenceError "spam is not defined"
How to Code With This Knowledge
Now that you understand scoping and hoisting, what does that mean for coding in JavaScript? The most important thing is to always declare your variables with a var statement. I strongly recommend that you have exactly one var statement per scope, and that it be at the top. If you force yourself to do this, you will never have hoisting-related confusion. However, doing this can make it hard to keep track of which variables have actually been declared in the current scope. I recommend using JSLint with the onevar option to enforce this. If you’ve done all of this, your code should look something like this:
/*jslint onevar: true [...] */
function foo(a, b, c) {
    var x = 1,
    	bar,
    	baz = "something";
}
What the Standard Says
I find that it’s often useful to just consult the ECMAScript Standard (pdf) directly to understand how these things work. Here’s what it has to say about variable declarations and scope (section 12.2.2 in the older version):
If the variable statement occurs inside a FunctionDeclaration, the variables are defined with function-local scope in that function, as described in section 10.1.3. Otherwise, they are defined with global scope (that is, they are created as members of the global object, as described in section 10.1.3) using property attributes { DontDelete }. Variables are created when the execution scope is entered. A Block does not define a new execution scope. Only Program and FunctionDeclaration produce a new scope. Variables are initialised to undefined when created. A variable with an Initialiser is assigned the value of its AssignmentExpression when the VariableStatement is executed, not when the variable is created.
I hope this article has shed some light on one of the most common sources of confusion to JavaScript programmers. I have tried to be as thorough as possible, to avoid creating more confusion. If I have made any mistakes or have large omissions, please let me know.
filed under javascript
【repost】JavaScript Scoping and Hoisting的更多相关文章
- 【转载】【翻译】JavaScript Scoping and Hoisting--JS作用域和变量提升的探讨
		
原文链接:http://www.adequatelygood.com/2010/2/JavaScript-Scoping-and-Hoisting 你知道下面的JavaScript代码执行后会aler ...
 - 【repost】JavaScript 基本语法
		
JavaScript 基本语法,JavaScript 引用类型, JavaScript 面向对象程序设计.函数表达式和异步编程 三篇笔记是对<JavaScript 高级程序设计>和 < ...
 - 【repost】JavaScript 事件模型 事件处理机制
		
什么是事件? 事件(Event)是JavaScript应用跳动的心脏 ,也是把所有东西粘在一起的胶水.当我们与浏览器中 Web 页面进行某些类型的交互时,事件就发生了.事件可能是用户在某些内容上的点击 ...
 - 【repost】javascript callback
		
在javascript中回调函数非常重要,它们几乎无处不在.像其他更加传统的编程语言都有回调函数概念,但是非常奇怪的是,完完整整谈论回调函数的在线教程比较少,倒是有一堆关于call()和apply() ...
 - 【repost】JavaScript 运行机制详解:再谈Event Loop
		
一年前,我写了一篇<什么是 Event Loop?>,谈了我对Event Loop的理解. 上个月,我偶然看到了Philip Roberts的演讲<Help, I'm stuck i ...
 - 【repost】javascript:;与javascript:void(0)使用介绍
		
有时候我们在编写js过程中,需要触发事件而不需要返回值,那么就可能需要这样的写法 最近看了好几个关于<a>标签和javascript:void(0)的帖子,谨记于此,以资查阅. 注:以下代 ...
 - 【repost】Javascript操作DOM常用API总结
		
Javascript操作DOM常用API总结 文本整理了javascript操作DOM的一些常用的api,根据其作用整理成为创建,修改,查询等多种类型的api,主要用于复习基础知识,加深对原生js的认 ...
 - 【repost】JavaScript运动框架之速度时间版本
		
一.JavaScript运动框架之速度版 1.1 运动框架的实现思路 运动,其实就是在一段时间内改变 left . right . width . height . opactiy 的值,到达目的地之 ...
 - 【repost】JavaScript完美运动框架的进阶之旅
		
运动框架的实现思路 运动,其实就是在一段时间内改变left.right.width.height.opactiy的值,到达目的地之后停止. 现在按照以下步骤来进行我们的运动框架的封装: 匀速运动. 缓 ...
 
随机推荐
- ui-router中使用ocLazyLoad和resolve
			
1.AngularJS按需加载 AngularJS主要应用开发SPA(Single Page Application)项目,所以在小型项目中,services.filters和controllers都 ...
 - webService学习之路(三):springMVC集成CXF后调用已知的wsdl接口
			
webService学习之路一:讲解了通过传统方式怎么发布及调用webservice webService学习之路二:讲解了SpringMVC和CXF的集成及快速发布webservice 本篇文章将讲 ...
 - CSS  float 定位和缩放问题
			
今天调试一个看起来很简单的前端问题,但却花了太多的时间,示例代码: <!DOCTYPE html> <html> <head> <title></ ...
 - MySQL的数据模型
			
MySQL的数据类型主要分为三大类: 数值型(Numeric Type) 日期与时间型(Date and Time Type) 字符串类型(String Type) 1. 数值 MySQL的数值类型按 ...
 - [占位-未完成]scikit-learn一般实例之十二:用于RBF核的显式特征映射逼近
			
It shows how to use RBFSampler and Nystroem to approximate the feature map of an RBF kernel for clas ...
 - C#基础知识七之const和readonly关键字
			
前言 不知道大家对const和readonly关键字两者的区别了解多少,如果你也不是很清楚的话,那就一起来探讨吧!探讨之前我们先来了解静态常量和动态常量. 静态常量 所谓静态常量就是在编译期间会对变量 ...
 - Linq to SQL 语法查询(链接查询,子查询 & in操作 & join,分组统计等)
			
Linq to SQL 语法查询(链接查询,子查询 & in操作 & join,分组统计等) 子查询 描述:查询订单数超过5的顾客信息 查询句法: var 子查询 = from c i ...
 - VS2012程序打包部署详解
			
VS2012没有自带打包工具,所以要先下载并安装一个打包工具.我采用微软提供的打包工具: InstallShield2015LimitedEdition.下载地址:https://msdn.micr ...
 - C# - 计时器Timer
			
System.Timers.Timer 服务器计时器,允许指定在应用程序中引发事件的重复时间间隔. using System.Timers: // 在应用程序中生成定期事件 public class ...
 - Spring下ActiveMQ实战
			
MessageQueue是分布式的系统里经常要用到的组件,一般来说,当需要把消息跨网段.跨集群的分发出去,就可以用这个.一些典型的示例就是: 1.集群A中的消息需要发送给多个机器共享: 2.集群A中消 ...