最近我开始维护别人的JavaScript代码。我正在修复错误,添加功能,并尝试整理代码并使其更加一致。
之前的开发人员使用了两种声明函数的方式,我无法确定是否有什么原因。
这两种方式是:
var functionOne = function() {
// Some code
};
而且,
function functionTwo() {
// Some code
}
为什么要使用这两种不同的方法,各自有哪些优缺点?是否有一种方法可以做到另一种方法无法实现的事情?
最近我开始维护别人的JavaScript代码。我正在修复错误,添加功能,并尝试整理代码并使其更加一致。
之前的开发人员使用了两种声明函数的方式,我无法确定是否有什么原因。
这两种方式是:
var functionOne = function() {
// Some code
};
而且,
function functionTwo() {
// Some code
}
为什么要使用这两种不同的方法,各自有哪些优缺点?是否有一种方法可以做到另一种方法无法实现的事情?
以下代码有效是因为 function add()
的作用域在最近的块级范围内:
try {
console.log("Success: ", add(1, 1));
} catch(e) {
console.log("ERROR: " + e);
}
function add(a, b){
return a + b;
}
add
赋予函数值之前,该变量被调用了。
try {
console.log("Success: ", add(1, 1));
} catch(e) {
console.log("ERROR: " + e);
}
var add=function(a, b){
return a + b;
}
add = undefined
是多余的,因为只需执行 var add;
与 var add=undefined
是完全相同的。
var add = undefined;
try {
console.log("Success: ", add(1, 1));
} catch(e) {
console.log("ERROR: " + e);
}
add = function(a, b){
return a + b;
}
var add=
开始一个表达式,导致后面的function add()
成为表达式而不是块。命名函数只对它们自己和周围的块可见。由于 function add()
在此处是一个表达式,它没有周围的块,因此只对它自己可见。
try {
console.log("Success: ", add(1, 1));
} catch(e) {
console.log("ERROR: " + e);
}
var add=function add(a, b){
return a + b;
}
一个函数的名称function thefuncname(){}
声明时就是thefuncname。
function foobar(a, b){}
console.log(foobar.name);
var a = function foobar(){};
console.log(a.name);
function(){}
,那么function.name就是用来存储该函数的第一个变量。
var a = function(){};
var b = (function(){ return function(){} });
console.log(a.name);
console.log(b.name);
""
)。
console.log((function(){}).name === "");
var a = function(){};
var b = a;
var c = b;
console.log(a.name);
console.log(b.name);
console.log(c.name);
在Google的V8和Firefox的Spidermonkey中,JIT编译可能会有几微秒的差异,但最终结果是完全相同的。为了证明这一点,我们可以通过比较两个空代码片段的速度来检查JSPerf在微基准测试方面的效率。JSPerf测试可以在这里找到。而jsben.ch测试则可以在这里找到。正如你所看到的,当不应该有任何差异时,确实存在明显的差异。如果你像我一样真的很在意性能,那么尝试减少作用域中变量和函数的数量,特别是消除多态性(例如使用同一个变量存储两种不同类型)可能更值得你花费时间。
当你使用var
关键字声明一个变量后,你可以重新分配一个不同的值给这个变量,就像这样:
(function(){
"use strict";
var foobar = function(){}; // initial value
try {
foobar = "Hello World!"; // new value
console.log("[no error]");
} catch(error) {
console.log("ERROR: " + error.message);
}
console.log(foobar, window.foobar);
})();
const arr = []
,那么您仍然可以执行arr[10] = "example"
。只有像arr = "new value"
或arr = []
这样的操作才会抛出错误,如下所示。
(function(){
"use strict";
const foobar = function(){}; // initial value
try {
foobar = "Hello World!"; // new value
console.log("[no error]");
} catch(error) {
console.log("ERROR: " + error.message);
}
console.log(foobar, window.foobar);
})();
function funcName(){}
,那么该变量的不可变性与使用var
声明变量的方式相同。
(function(){
"use strict";
function foobar(){}; // initial value
try {
foobar = "Hello World!"; // new value
console.log("[no error]");
} catch(error) {
console.log("ERROR: " + error.message);
}
console.log(foobar, window.foobar);
})();
“最近的块”是最近的“函数”(包括异步函数、生成器函数和异步生成器函数)。然而,有趣的是,当一个function functionName() {}
在非闭合块中时,对于闭合块外部的项目来说,它的行为就像var functionName = function() {}
。请看以下示例。
var add=function(){}
try {
// typeof will simply return "undefined" if the variable does not exist
if (typeof add !== "undefined") {
add(1, 1); // just to prove it
console.log("Not a block");
}else if(add===undefined){ // this throws an exception if add doesn't exist
console.log('Behaves like var add=function(a,b){return a+b}');
}
} catch(e) {
console.log("Is a block");
}
var add=function(a, b){return a + b}
function add(){}
try {
// typeof will simply return "undefined" if the variable does not exist
if (typeof add !== "undefined") {
add(1, 1); // just to prove it
console.log("Not a block");
}else if(add===undefined){ // this throws an exception if add doesn't exist
console.log('Behaves like var add=function(a,b){return a+b}')
}
} catch(e) {
console.log("Is a block");
}
function add(a, b){
return a + b;
}
try {
// typeof will simply return "undefined" if the variable does not exist
if (typeof add !== "undefined") {
add(1, 1); // just to prove it
console.log("Not a block");
}else if(add===undefined){ // this throws an exception if add doesn't exist
console.log('Behaves like var add=function(a,b){return a+b}')
}
} catch(e) {
console.log("Is a block");
}
(function () {
function add(a, b){
return a + b;
}
})();
if
、else
、for
、while
、try
/catch
/finally
、switch
、do
/while
、with
)try {
// typeof will simply return "undefined" if the variable does not exist
if (typeof add !== "undefined") {
add(1, 1); // just to prove it
console.log("Not a block");
}else if(add===undefined){ // this throws an exception if add doesn't exist
console.log('Behaves like var add=function(a,b){return a+b}')
}
} catch(e) {
console.log("Is a block");
}
{
function add(a, b){
return a + b;
}
}
var add=function()
的箭头函数try {
// typeof will simply return "undefined" if the variable does not exist
if (typeof add !== "undefined") {
add(1, 1); // just to prove it
console.log("Not a block");
}else if(add===undefined){ // this throws an exception if add doesn't exist
console.log('Behaves like var add=function(a,b){return a+b}')
}
} catch(e) {
console.log("Is a block");
}
(() => {
var add=function(a, b){
return a + b;
}
})();
function add()
try {
// typeof will simply return "undefined" if the variable does not exist
if (typeof add !== "undefined") {
add(1, 1); // just to prove it
console.log("Not a block");
}else if(add===undefined){ // this throws an exception if add doesn't exist
console.log('Behaves like var add=function(a,b){return a+b}')
}
} catch(e) {
console.log("Is a block");
}
(() => {
function add(a, b){
return a + b;
}
})();
因为其他人都详细地讨论了变量提升的部分,所以我想加入自己的回答。
我已经长时间思考哪种方式更好了,感谢 http://jsperf.com,现在我知道了 :)
函数声明更快,这才是 Web 开发中真正重要的事情,对吧? ;)
一旦绑定建立,函数声明和赋值给变量的函数表达式的行为相同。
然而,在函数对象实际与其变量关联的方式和时间上却有所不同。这种差异是由JavaScript中的变量提升机制引起的。
基本上,所有函数声明和变量声明都会被提升到声明所在的函数的顶部(这就是我们说JavaScript具有函数作用域的原因)。
当函数声明被提升时,函数体将“跟随”提升,因此当评估函数体时,变量将立即绑定到函数对象。
当变量声明被提升时,初始化不会跟随提升,而是“留下”。变量在函数体的开头被初始化为undefined
,并且将在代码中原始位置分配一个值。(实际上,它将在每个出现相同名称变量的声明位置都分配值。)
变量提升的顺序也很重要:函数声明优先于具有相同名称的变量声明,并且最后一个函数声明优先于先前具有相同名称的函数声明。
以下是一些例子...
var foo = 1;
function bar() {
if (!foo) {
var foo = 10 }
return foo; }
bar() // 10
变量foo
被提升到函数顶部,被初始化为undefined
,因此!foo
为true
,所以foo
被赋值为10
。 bar
作用域之外的foo
无关紧要且不会受到影响。
function f() {
return a;
function a() {return 1};
var a = 4;
function a() {return 2}}
f()() // 2
function f() {
return a;
var a = 4;
function a() {return 1};
function a() {return 2}}
f()() // 2
函数声明优先于变量声明,并且最后一个函数声明“生效”。
function f() {
var a = 4;
function a() {return 1};
function a() {return 2};
return a; }
f() // 4
在这个例子中,a
被初始化为通过评估第二个函数声明而产生的函数对象,并且然后被赋值为4
。var a = 1;
function b() {
a = 10;
return;
function a() {}}
b();
a // 1
这里首先对函数声明进行变量提升,声明并初始化变量a
。接下来,将变量a
赋值为10
。换句话说,赋值操作不会赋值给外部变量a
。
第一个示例是函数声明:
function abc(){}
第二个示例是一个函数表达式:
var abc = function() {};
主要区别在于它们被提升(升起并声明)的方式不同。在第一个示例中,整个函数声明都被提升了。而在第二个示例中,只有变量'abc'被提升,其值(即函数)将为undefined,并且函数本身保持在被声明的位置。
简单来说:
//this will work
abc(param);
function abc(){}
//this would fail
abc(param);
var abc = function() {}
我强烈推荐你访问这个 链接 来更深入地学习这个主题。
从代码维护成本的角度来看,命名函数更可取:
我认为命名函数的优点还有很多。而所列出的命名函数的优点对于匿名函数来说则是一个劣势。
历史上,匿名函数出现是由于JavaScript作为一种语言无法使用命名函数列出成员:
{
member:function() { /* How do I make "this.member" a named function? */
}
}
在计算机科学中,我们谈论匿名函数和命名函数。我认为最重要的区别在于匿名函数没有被绑定到一个名称上,因此称之为匿名函数。在 JavaScript 中,它是一个在运行时动态声明的一等对象。
关于匿名函数和 λ 演算的更多信息,请参阅维基百科:匿名函数。
格雷戈里的回答已经足够好了,但我仍然想补充一些我刚刚在观看道格拉斯·克罗克福德的视频时学到的东西。
函数表达式:
var foo = function foo() {};
函数声明:
function foo() {};
函数声明语句只是使用带有function
值的var
语句的速记。
因此,
function foo() {};
扩展为
var foo = function foo() {};
进一步展开:
var foo = undefined;
foo = function foo() {};
它们都被提升到代码的顶部。
我在我的代码中使用变量方法,有一个非常具体的原因。这个理论已经以抽象的方式涵盖在上面了,但是举个例子可能会帮助像我一样JavaScript专业知识有限的人。
我有一个需要在160个独立设计品牌中运行的代码。大部分代码在共享文件中,但品牌特定的东西在单独的文件中,每个品牌一个文件。
有些品牌需要特定的函数,有些则不需要。有时我必须添加新的函数来做新的品牌特定的事情。我很乐意改变共享代码,但我不想去改变所有160组品牌文件。
通过使用变量语法,我可以在共享代码中声明变量(本质上是一个函数指针),并将其赋值为一个微不足道的存根函数或设置为空。
然后只有一两个品牌需要函数的特定实现,他们可以定义他们版本的函数并将其赋值给变量,而其他品牌则什么都不用做。我可以在执行共享代码前测试空函数。
从上面人们的评论中,我了解到可能也可以重新定义静态函数,但我认为变量解决方案很好很清晰。
@EugeneLazutkin给出了一个例子,他将分配的函数命名为shortcut()
的内部引用以便于自我调用。John Resig在他的Learning Advanced Javascript教程中给出了另一个例子——将递归函数复制到另一个对象中。虽然将函数分配给属性并不是本文的重点,但我建议你积极尝试这个教程——通过点击右上角的按钮运行代码,并双击代码进行编辑。
教程中的例子:在yell()
中进行递归调用:
当删除原始忍者对象时,测试会失败。(第13页)
function assert(predicate, message) { if(!predicate) { throw new Error(message); } }
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." );
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?" );
}
如果您给将被递归调用的函数命名,测试就会通过。(第14页)
function assert(predicate, message) { if(!predicate) { throw new Error(message); } }
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!" );
var samurai = { yell: ninja.yell };
var ninja = {};
assert( samurai.yell(4) == "hiyaaaa", "The method correctly calls itself." );
console.log(samurai.yell(4));
var functionOne = function() {
// Some code
};
var one = new functionOne();
console.log(one.constructor.name);
那么one.constructor.name
将不会被定义。Function.name
是非标准的,但被Firefox、Chrome、其他基于Webkit的浏览器和IE 9+支持。
使用
function functionTwo() {
// Some code
}
two = new functionTwo();
two.constructor.name
来将构造函数的名称作为字符串检索出来。