在前篇
小议javascript之全局对象创建
译文中提到过JavaScript Hoisting概念,当时不甚了解。google之后发现
ben cherry
解释的最为明了,于是有了本文这篇干货翻译稿。
翻译能力有限,辞不达意的地方请自动跳过或者直接阅读 原文 。
——以下为翻译——
JavaScript Scoping and Hoisting(JavaScript中的作用域与变量声明提升/声明时机提升)
你知道下面JavaScript代码的执行结果是什么吗?
发现浏览器会弹出alert(1)。怎么回事呢?看起来很诡异,这恰好是JavaScript语言的特性。我不确定这种表现行为是否已经有一个标准的命名了,于是我将此称为变量声明提升。本文会对这种机制进行揭秘,先从作用域谈起好了。
Scoping in JavaScript(JavaScript中的作用域)
对于很多JavaScript语言初学者而言,作用域带来许多困惑。当然也不仅仅是初学者会遇到这些问题,不少资深JavaScript程序员也未能完全掌握作用域的精髓。之所以带来困惑,其中一个原因是由于JavaScript实在太像C风格的语言了。如下是一段C程序:
程序输出结果是 1, 2, 1 。因为C风格的语言有 块级作用域(block-level scope) 。当函数鱼运行到
程序输出结果 1, 2, 2 。原因是JavaScript支持 函数作用域(function-level scope) ,这个特性与C风格的语言格格不入。
对于许多熟谙C\C++\C#\Java语言的程序员来说,JavaScript这个语言特性非常出乎意料。幸运的是, 正由于JavaScript函数的灵活性,产生了一个变通方案。如果你必须在函数中创建一个临时作用域,可以这么做 :
Declarations, Names, and Hoisting(声明、名称以及变量声明提升/声明时机提升 )
当访问函数内的变量时,JavaScript会按照下面顺序查找:
函数声明与变量声明经常被JavaScript引擎隐式地提升到当前作用域的顶部 ,也就是说:
可以发现,声明语句中的赋值部分并没有被提升声明,只有名称被提升了。两种函数声明方式:
Name resolution order(名称解析顺序)
名称解析顺序有4步,一般来说,如果一个名称已经被定义了,它就不会被另一个具有同名称的属性所覆盖。这也就意味着,函数声明会比变量声明优先。并不是名称的赋值操作不会被执行,只是说声明部分被忽略了而已。有些例外:
Named Function Expressions(函数命名表达式)
你可以通过函数表达式给函数命名,语法上看起来像是函数声明,实则不是 。上一段代码:
How to Code With This Knowledge(如何编码)
文章到此你应该已经理解什么是作用域和提升声明时机了,那么如何在实战中运用呢?切记,使用var表达式创建变量,在此强烈建议在代码块顶部使用一个var表达式来创建变量。然而,这么做的同时也导致开发者很难对当前作用域下实际被声明的变量进行跟踪。我建议开发者使用 JSLint 来进行验证:
翻译能力有限,辞不达意的地方请自动跳过或者直接阅读 原文 。
——以下为翻译——
JavaScript Scoping and Hoisting(JavaScript中的作用域与变量声明提升/声明时机提升)
你知道下面JavaScript代码的执行结果是什么吗?
var foo = 1;如果你对foo的值实际上为"10"而感到诧异的话,再看一下下面这个例子:
function bar() {
if (!foo) {
var foo = 10;
}
alert(foo);
}
bar();
var a = 1;
function b() {
a = 10;
return;
function a() {}
}
b();
alert(a);
发现浏览器会弹出alert(1)。怎么回事呢?看起来很诡异,这恰好是JavaScript语言的特性。我不确定这种表现行为是否已经有一个标准的命名了,于是我将此称为变量声明提升。本文会对这种机制进行揭秘,先从作用域谈起好了。
Scoping in 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风格的语言有 块级作用域(block-level scope) 。当函数鱼运行到
if
语句中时,当前作用域中的新变量会被声明,并且不会影响到外部作用域。但在JavaScript中情况并不是这样:
var x = 1;
console.log(x); // 1
if (true) {
var x = 2;
console.log(x); // 2
}
console.log(x); // 2
程序输出结果 1, 2, 2 。原因是JavaScript支持 函数作用域(function-level scope) ,这个特性与C风格的语言格格不入。
if
语句中的代码块并不会创建新的作用域,只有函数才会。
对于许多熟谙C\C++\C#\Java语言的程序员来说,JavaScript这个语言特性非常出乎意料。幸运的是, 正由于JavaScript函数的灵活性,产生了一个变通方案。如果你必须在函数中创建一个临时作用域,可以这么做 :
function foo() {这个函数非常灵活,不仅仅在块级语句内,需要时能在任何地方使用。然而我强烈建议你多花点时间来理解JavaScript的作用域。
var x = 1;
if (x) {
(function () {
var x = 2;
// some other code
}());
}
// x is still 1.
}
Declarations, Names, and Hoisting(声明、名称以及变量声明提升/声明时机提升 )
当访问函数内的变量时,JavaScript会按照下面顺序查找:
- 语言级别:默认在所用作用域下会定义
this
、arguments
- 传入参数:函数命名的参数,作用域是当前函数体
- 函数声明:例如function foo() {}
- 变量声明:例如var foo;
函数声明与变量声明经常被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();
这个例子中,只有包含函数体的函数声明会被提升声明。foo虽然会被提升声明,但是函数体却在执行中被赋值。以上就是提升声明时机的基本概念,看起来一点也不复杂。
Name resolution order(名称解析顺序)
名称解析顺序有4步,一般来说,如果一个名称已经被定义了,它就不会被另一个具有同名称的属性所覆盖。这也就意味着,函数声明会比变量声明优先。并不是名称的赋值操作不会被执行,只是说声明部分被忽略了而已。有些例外:
- 原生变量
arguments
特立独行,包含了传递到函数中的参数。如果自定义以arguments
为命名的参数,将会阻止原生arguments
对象的创建。所以勿使用arguments
为名称的参数。 - 胡乱使用
this
标识符会引起语法错误。 - 如果多个参数具有相同的命名,那么最后一个参数会优先于先前的,即时这个参数未定义。
Named Function Expressions(函数命名表达式)
你可以通过函数表达式给函数命名,语法上看起来像是函数声明,实则不是 。上一段代码:
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(如何编码)
文章到此你应该已经理解什么是作用域和提升声明时机了,那么如何在实战中运用呢?切记,使用var表达式创建变量,在此强烈建议在代码块顶部使用一个var表达式来创建变量。然而,这么做的同时也导致开发者很难对当前作用域下实际被声明的变量进行跟踪。我建议开发者使用 JSLint 来进行验证:
/*jslint onevar: true [...] */
function foo(a, b, c) {
var x = 1,
bar,
baz = "something";
}