Javascript的域和提升
在这篇文章中已经简单的提到了函数和变量提升的概念。下面将更加仔细的阐述Javascript中的变量和函数的提升。
一、变量和函数提升的现象
首先思考下面的代码执行后将打印出什么?
var foo = 1;
function bar()
{
if (!foo)
{
var foo = 10;
}
alert(foo);
}
bar();
如果这个结果("10")会让你感到惊讶,那么下面的例子会不会也会让你同样的惊讶:
var a = 1;
function b()
{
a = 10;
return;
function a() {}
}
b();
alert(a);
没错,浏览器将打印"1"。这里面到底发生了什么?这看起来似乎很奇怪,危险而且很迷惑,
这事实上就是Javascript这门语言强大的、具有表现力的功能。以上的现象没有一个标准的名字,姑且就叫其为“提升”,这篇文章主要就是讲解一下Javascript的这一机制,但是在学习提升之前我们首先要了解一下Javascript的“域”,这样才能更好的理解提升。
二、Javascript的域
对于Javascript的初学者来讲,域是学习Javascript最大的疑惑,事实上很多熟练的Javascript开发人员也不是能完全的理解域,域在Javascript中如此具有迷惑性的原因是它看起来像C族语言,考虑以下的C代码:
#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
}
以上代码将依次输出1,2,1,这是因为C以及其他C族语言拥有“块级域”(block-level scope)。当控制语句进入一个块,比如if语句块,我们能在这个块中声明新的变量,而不会影响外部的“域”。但在Javascript中并不是这样的,尝试在FireFox中运行以下代码:
var x = 1;
console.log(x); // 1
if (true) {
var x = 2;
console.log(x); // 2
}
console.log(x); // 2
以上FireFox将打印1,2,2,这是因为Javascript拥有“函数级域”(function-level scope),这是和C族语言根本上的不同,像if语句这样的块不能形成一个新的域,只有函数能创建新的域。对于很多曾经喜欢使用C,C++,C#或者Java的程序员来讲,这种现象是不希望也是不受欢迎的。幸运的是由于Javascript函数的灵活性,这一现象是可以得到弥补的,如果非要在一个函数里面再创建一个临时域,看以下例子:
function foo() {
var x = 1;
if (x) {
(function () {
var x = 2;
// some other code
}());
}
// x is still 1.
}
这个方法实际上非常的灵活,而且能够在任何你需要使用临时域的地方使用这种方法,不仅仅是在语句块中。强烈建立大家真正的花时间去理解和领会Javascript的域。它非常的强大,如果你理解了Javascript的域和提升,对你非常有意义的。
三、声明,命名和提升
在Javascript中,一个名字在一个域中创建可以通过以下四个方式之一:
1.语言自身的定义:默认所有的域都被给予this和arguments两个命名。
2.正式的参数:函数提供的域中会为该函数参数提供命名。
3.函数声明:函数提供的域会为函数体内声明的其他函数提供命名。
4.变量声明:函数提供的域会为函数体内声明的变量提供命名。
函数声明和变量声明常常会被Javascript解释器隐形的提升到包含它们的域的顶部,这就意味着像一下的代码:
function foo() {
bar();
var x = 1;
}
实际上更像是这样被解读的:
function foo() {
var x;
bar();
x = 1;
}
这就说明,我们声明变量或者函数的那一行语句会不会被执行是没有关系的。什么意思呢?就是以下两个函数是等效的:
function foo() {
if (false) {
var x = 1;
}
return;
var y = 1;
}
function foo() {
var x, y;
if (false) {
x = 1;
}
return;
y = 1;
}
需要注意的是:变量初始化的部分并没有被提升,只是变量声明的部分被提升。但是对于函数来说有两种形式:函数声明和函数表达式。对于函数表达式是和变量一样的,但对于函数声明,是将整个函数体一起提升的:
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();
这就是提升的基本原则,没有想象中的那么奇怪和复杂。其实提升的本质还是《Javascript的函数和执行环境》
一文中讲到的Javascript的执行环境的创建问题,提升只是我们看到的直观的现象,如果要深入了解提升的本质,请参考该文。
良好的编码习惯,由于Javascript的变量提升的语言特点,如果我们在函数中到处声明变量,那完全是自讨苦吃,既然我们在哪儿声明变量都会被Javascript解释器隐式的提升到最顶部,我们为什么不直接在函数的最顶部声明我们的变量或者函数,这样既直观,看起来也整洁,也不至于产生混淆,会避免很多不必要的麻烦。
/*jslint onevar: true [...] */
function foo(a, b, c) {
var x = 1,
bar,
baz = "something";
}