我阅读了Javascript Hoisting的概念。它让我感到很困惑,但是我看到了一些例子,并且知道了吊装实际上是做什么的。
所以基本上" 提升是JavaScript将所有声明移动到当前范围顶部(到当前脚本或当前函数的顶部)的默认行为。 "
但我无法理解以下实施:
var is_android = true;
if (is_android) {
function foo() {
alert('I am Android');
}
} else {
function foo() {
alert('I am NOT Android');
}
}
foo();
输出显示" 我不是Android "在警报框中。
我想知道为什么即使foo()
值为真,也会从else块调用is_android
。
任何帮助将不胜感激。
答案 0 :(得分:21)
tl; dr:请勿在块内使用看似函数声明的内容,尤其不要使用条件。
事实上,大多数浏览器都以错误的方式解释这段代码。它们将函数定义视为函数声明,即使函数声明不允许在块内,因为函数声明is not a statement,它是source element。
这通常如何运作:
在代码执行之前,解释器会查找所有变量和函数声明,而不是 where ,并在当前/中为它们创建绑定新环境。然后它开始实际执行代码。
因此,假设函数定义被解释为声明,因为有两个声明,最后一个声明获胜。你的代码基本上变成了:
function foo() {
alert('I am Android');
}
function foo() {
alert('I am NOT Android');
}
var is_android;
is_android = true;
if (is_android) {
} else {
}
foo();
其他引擎会interpret it differently,但仍然不正确(IMO,见下文):
在下面的脚本中,零函数永远不会被定义,也不能被调用,因为' if(0)'将其条件评估为false:
if (0) { function zero() { document.writeln("This is zero."); } }
注意:某些JavaScript引擎(不包括SpiderMonkey)错误地将任何函数表达式视为函数定义。即使使用always-false if条件,这也会导致零被定义。有条件地定义函数的更安全的方法是匿名定义函数并将其分配给变量:
if (0) { var zero = function() { document.writeln("This is zero."); } }
但在这种情况下,如果函数定义真的被解释为函数表达式(类似于(function zero() { ... })
),那么函数的名称将无法在包含范围中访问,而且这个功能就会丢失。
答案 1 :(得分:5)
在Javascript中,两个看似相似的
之间存在细微差别 function square(x) { return x*x; }
和
var square = function(x) { return x*x; }
最大的区别在于,在第一种情况下,将函数对象分配给square
名称是在输入范围时完成的,而不是在到达该行时。这允许调用稍后在源代码中定义的函数...例如:
console.log(cube(12));
function cube(x) { return x*x*x; }
是一个有效的脚本,即使调用发生在函数定义“之前”(顺便说一下,允许这种代码是IMO,也是规则在语言中的原因)。
在第二种情况下,赋值只是一个常规语句,并且当控制流通过它时(和如果)执行它。
如果您希望该代码段按预期工作,则只需更改
中的代码即可function <name> (...) { ... }
到
var <name> = function (...) { ... }
PS:您也可以在第二种形式中重复该名称,但通常不会这样做,而是使用匿名函数。
答案 2 :(得分:3)
http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html
这是我读过有关招待的最佳文章。
声明,名称和提升
在JavaScript中,名称以四种基本方式之一进入范围:
语言定义:默认情况下,所有范围都是名称this和arguments。 形式参数:函数可以具有命名形式参数,其范围限定为该函数的主体。 函数声明:它们的形式为函数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;
}
请注意,声明的分配部分未被提升。只有名字被悬挂。函数声明不是这种情况,整个函数体也将被提升。但请记住,声明函数有两种常用方法。请考虑以下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();
在这种情况下,只有函数声明将其主体提升到顶部。名称'foo'被悬挂,但身体被遗忘,在执行期间被分配。
这涵盖了吊装的基础知识,这并不像看起来那么复杂或令人困惑。当然,这是JavaScript,在某些特殊情况下会有一些复杂性。
答案 3 :(得分:3)
您可能已经了解了声明函数以避免提升的方法,但如果不是: 你可以把它写成:
var is_android = true;
if (is_android) {
var foo = function() {
alert(5);
};
} else {
var foo = function() {
alert(7);
};
}
foo();
在javascript解释器评估条件语句之前,不会评估和foo()。
答案 4 :(得分:2)
你正在混合概念。
从这段代码开始:
function foo() {
alert('1');
}
function foo() {
alert('2');
}
foo();
这不会给出错误。而只是警报2.因为foo()的第二个定义会覆盖第一个。
立即尝试以下代码:
if (false) {
function foo() { alert('false');}
}
foo();
这只是警告错误。即使foo是在未执行的块内定义的(如果为false),函数声明总是由JavaScript处理。
考虑到这两个例子很容易理解你的代码中发生了什么:你定义了两次函数foo,第二个定义覆盖了第一个。
您在代码中尝试的内容非常接近“条件编译”,并且可以在JavaScript中将此行为模拟为将变量声明为函数:
if (true) {
var foo = function() {alert ('true');}
}
else {
var foo = function() {alert ('false');}
}
foo();
此代码仅提醒真实。请注意,现在foo被定义为变量(包含函数,但是变量)。
问候