阅读John Resig的Learning Advanced Javascript,我遇到了两张我不太了解的幻灯片。
幻灯片#13 - 引用作为匿名函数的属性。第二个断言失败。
var ninja = {
yell: function(n){
return n > 0 ? ninja.yell(n-1) + "a" : "hiy";
}
};
assert( ninja.yell(4) == "hiyaaaa", "A single object isn't too bad, either." ); // PASS
var samurai = { yell: ninja.yell };
var ninja = null;
try {
samurai.yell(4);
} catch(e){
assert( false, "Uh, this isn't good! Where'd ninja.yell go?" ); // FAIL
}
Slide#14 - 引用属于已定义函数的属性。第二个断言传递。
var ninja = {
yell: function yell(n){
return n > 0 ? yell(n-1) + "a" : "hiy";
}
};
assert( ninja.yell(4) == "hiyaaaa", "Works as we would expect it to!" ); // PASS
var samurai = { yell: ninja.yell };
var ninja = {};
assert( samurai.yell(4) == "hiyaaaa", "The method correctly calls itself." ); // PASS
这里唯一的区别是yell
是幻灯片14中的命名函数。为什么即使在将原始对象(ninja
)设置为a之后,对定义为命名函数的属性的引用仍然存在新对象(甚至是null)?
答案 0 :(得分:1)
函数表达式计算为对函数的引用。
foo = function () { ... };
foo
的值现在是对函数的引用。您可以将该值复制到任何您喜欢的位置。
命名的函数表达式也在其内部创建一个 的局部变量,其名称与函数相同。
foo = function baz () { ... };
此处foo
是对全局范围中的函数的引用。 baz
是对函数范围内函数的引用。
您可以删除或覆盖任何变量或属性的值,而不会影响包含该引用副本的任何其他变量或属性。
两种情况之间的区别在于,第二种情况是在另一个变量中以第二个引用开始,并且(内部)使用 引用而不是更宽范围内的引用。
答案 1 :(得分:1)
这里唯一的区别是yell是幻灯片14中的命名函数。
不,这不是唯一的区别,也不是最重要的区别(虽然它与重要区别有关)。
在第一个中,请注意ninja.yell
的定义方式:
var ninja = {
yell: function(n){
return n > 0 ? ninja.yell(n-1) + "a" : "hiy";
// ----------------^^^^^^^^^^^^^^^
}
};
它本身明确使用ninja.yell
。因此,调用它会查找变量ninja
,然后尝试在yell
上查找ninja
作为属性。
将其与第二个例子中的定义进行比较:
var ninja = {
yell: function yell(n){
return n > 0 ? yell(n-1) + "a" : "hiy";
// ----------------^^^^^^^^^
}
};
yell
不再使用变量ninja
,它只使用范围内的标识符(通过命名函数表达式为函数赋予名称而创建的标识符)。
这就是为什么在第一个例子中ninja
被清空的原因:
var ninja = null;
......第一个开始失败;您无法在yell
上查找null
。
但是当第二个替换ninaj
时(我不明白为什么John在这两种情况下都没有使用null
,但无论如何):
var ninja = {};
... yell
并不关心,因为它没有使用ninja
。
附注:从ES2015开始,yell
的任何版本都不是匿名函数,即使第一个版本是使用匿名函数表达式创建的。在ES2015中,函数可以根据上下文获取其名称,并且获取名称的方法之一是分配给对象初始值设定项中的对象属性。但是,这对这个问题没有帮助,因为名称不是函数中的范围内标识符,就像使用命名函数表达式时一样。