我最近开始维护别人的JavaScript代码。我正在修复错误,添加功能,还试图整理代码并使之更加一致。
以前的开发人员使用了两种方法来声明函数,如果有背后的原因,我将无法解决。
两种方式是:
var functionOne = function() {
// Some code
};
function functionTwo() {
// Some code
}
使用这两种不同方法的原因是什么,每种方法的利弊是什么?有什么方法可以用一种方法不能完成的吗?
我最近开始维护别人的JavaScript代码。我正在修复错误,添加功能,还试图整理代码并使之更加一致。
以前的开发人员使用了两种方法来声明函数,如果有背后的原因,我将无法解决。
两种方式是:
var functionOne = function() {
// Some code
};
function functionTwo() {
// Some code
}
使用这两种不同方法的原因是什么,每种方法的利弊是什么?有什么方法可以用一种方法不能完成的吗?
这只是声明函数的两种可能方法,第二种方法是可以在声明之前使用该函数。
关于效果:
新版本V8
引入了一些后台优化,因此也进行了优化SpiderMonkey
。
现在,表达式和声明之间几乎没有区别。现在
函数表达似乎更快。
Anonymous
function expressions appear to have better performance againstNamed
function expression.
两者都是定义函数的不同方式。不同之处在于浏览器如何解释它们并将其加载到执行上下文中。
第一种情况是函数表达式,仅在解释器到达该行代码时才加载。因此,如果您按照以下方式进行操作,则会收到错误消息,认为functionOne不是function。
functionOne();
var functionOne = function() {
// Some code
};
原因是在第一行没有将任何值分配给functionOne,因此未定义。我们试图将其称为函数,因此会出现错误。
在第二行中,我们将匿名函数的引用分配给functionOne。
第二种情况是在执行任何代码之前加载的函数声明。因此,如果您喜欢以下代码,则在代码执行之前加载声明不会有任何错误。
functionOne();
function functionOne() {
// Some code
}
在JavaScript中,有两种创建函数的方法:
函数声明:
function fn(){
console.log("Hello");
}
fn();
这是非常基本的,不言自明的,用于多种语言,并跨C语言族成为标准。我们声明了一个定义它的函数,并通过调用它来执行它。
您应该知道的是,函数实际上是JavaScript中的对象。在内部,我们为上述函数创建了一个对象,并为其指定了名称fn或对该对象的引用存储在fn中。函数是JavaScript中的对象;函数的实例实际上是一个对象实例。
函数表达式:
var fn=function(){
console.log("Hello");
}
fn();
JavaScript具有一流的函数,即创建函数并将其分配给变量,就像创建字符串或数字并将其分配给变量一样。在此,将fn变量分配给一个函数。这个概念的原因是函数是JavaScript中的对象。fn指向上述函数的对象实例。我们已经初始化了一个函数并将其分配给变量。它没有执行功能并没有分配结果。
参考:JavaScript函数声明语法:var fn = function(){} vs function fn(){}
The first one (function doSomething(x)) should be part of an object notation.
The second one (var doSomething = function(x){ alert(x);}
) is simply creating an anonymous function and assigning it to a variable, doSomething
. So doSomething() will call the function.
You may want to know what a function declaration and function expression is.
A function declaration defines a named function variable without requiring variable assignment. Function declarations occur as standalone constructs and cannot be nested within non-function blocks.
function foo() {
return 3;
}
ECMA 5 (13.0) defines the syntax as
function Identifier ( FormalParameterListopt ) { FunctionBody }
In above condition the function name is visible within its scope and the scope of its parent (otherwise it would be unreachable).
并在函数表达式中
函数表达式将函数定义为较大的表达式语法(通常是变量赋值)的一部分。通过函数表达式定义的函数可以命名或匿名。函数表达式不应以“ function”开头。
// Anonymous function expression
var a = function() {
return 3;
}
// Named function expression
var a = function foo() {
return 3;
}
// Self-invoking function expression
(function foo() {
alert("hello!");
})();
ECMA 5(13.0)将语法定义为
函数标识符opt(FormalParameterList opt){FunctionBody}
如果使用这些函数创建对象,则会得到:
var objectOne = new functionOne();
console.log(objectOne.__proto__); // prints "Object {}" because constructor is an anonymous function
var objectTwo = new functionTwo();
console.log(objectTwo.__proto__); // prints "functionTwo {}" because constructor is a named function
其他答案中未提及的另一个区别是,如果您使用匿名函数
var functionOne = function() {
// Some code
};
并将其用作构造函数,如
var one = new functionOne();
那么one.constructor.name
将不会被定义。Function.name
是非标准的,但受Firefox,Chrome,其他Webkit衍生的浏览器和IE 9+支持。
用
function functionTwo() {
// Some code
}
two = new functionTwo();
可以使用来检索构造函数的名称作为字符串two.constructor.name
。
我在代码中使用可变方法的原因非常特殊,上面已以抽象的方式介绍了该方法的理论,但是一个示例可能会帮助一些像我这样的人,但是他们的JavaScript专业知识有限。
我有需要与160个独立设计的品牌一起运行的代码。大多数代码位于共享文件中,而与品牌有关的内容位于单独的文件中,每个品牌一个。
有些品牌需要特定的功能,而有些则不需要。有时,我必须添加新功能来进行特定于品牌的事情。我很乐意更改共享编码,但是我不想更改所有160套品牌文件。
通过使用变量语法,我可以在共享代码中声明变量(本质上是一个函数指针),然后分配一个琐碎的存根函数,或者设置为null。
然后,需要该功能的特定实现的一个或两个品牌可以定义其功能版本,然后根据需要将其分配给变量,其余的则不执行任何操作。我可以在共享代码中执行null函数之前对其进行测试。
从上面的评论中,我认为也许也可以重新定义静态函数,但是我认为变量解决方案很好而且很明确。
用计算机科学术语,我们谈论匿名函数和命名函数。我认为最重要的区别是匿名函数未绑定到名称,因此名称匿名函数。在JavaScript中,它是在运行时动态声明的一流对象。
有关匿名函数和lambda演算的更多信息,Wikipedia是一个好的开始(http://en.wikipedia.org/wiki/Anonymous_function)。
就代码维护成本而言,更可取的是命名函数:
I suspect more PROS for named functions are follow. And what is listed as an advantage of named functions is a disadvantage for anonymous ones.
Historically, anonymous functions appeared from the inability of JavaScript as a language to list members with named functions:
{
member:function() { /* How do I make "this.member" a named function? */
}
}
建立绑定后,分配给变量的函数声明和函数表达式的行为相同。
但是,在功能对象实际与其变量相关联的方式和时间方面存在差异。这种差异是由于JavaScript中称为变量提升的机制引起的。
基本上,所有函数声明和变量声明都被提升到声明所在函数的顶部(这就是我们说JavaScript具有函数作用域的原因)。
吊起函数声明时,函数主体将“跟随”,因此在评估函数主体时,变量将立即绑定到函数对象。
当一个变量声明悬挂,初始化并没有
跟随,而是“留下”。将该变量初始化为
undefined
函数体的开头,并将
在代码的原始位置为其分配一个值。(实际上,将在每个声明具有相同名称的变量的每个位置分配一个值。)
提升的顺序也很重要:函数声明优先于具有相同名称的变量声明,而最后一个函数声明优先于具有相同名称的先前函数声明。
一些例子...
var foo = 1;
function bar() {
if (!foo) {
var foo = 10 }
return foo; }
bar() // 10
变量foo
被提升到函数的顶部,初始化为undefined
,!foo
即为true
so foo
赋值10
。范围的foo
外部bar
不起任何作用,并且没有受到影响。
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
函数声明优先于变量声明,最后一个函数声明为“ sticks”。
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
。接下来,分配此变量10
。换句话说:分配没有分配给外部变量a
。
一个重要的原因是添加一个且仅一个变量作为名称空间的“根”。
var MyNamespace = {}
MyNamespace.foo= function() {
}
要么
var MyNamespace = {
foo: function() {
},
...
}
有很多命名空间的技术。随着大量可用JavaScript模块的出现,这一点变得越来越重要。
当您需要避免覆盖函数的先前定义时,最好使用第一种方法而不是第二种方法。
用
if (condition){
function myfunction(){
// Some code
}
}
,此定义myfunction
将覆盖任何先前的定义,因为它将在解析时完成。
而
if (condition){
var myfunction = function (){
// Some code
}
}
做正确的工作,myfunction
只定义何时condition
满足。
其他评论者已经涵盖了以上两个变体的语义差异。我想指出一种风格上的差异:只有“赋值”变体可以设置另一个对象的属性。
我经常用以下模式构建JavaScript模块:
(function(){
var exports = {};
function privateUtil() {
...
}
exports.publicUtil = function() {
...
};
return exports;
})();
使用这种模式,您的公共函数将全部使用赋值,而您的私有函数将使用声明。
(还请注意,在声明之后,赋值应使用分号,而在声明中则禁止使用分号。)
格雷格答案的更好解释
functionTwo();
function functionTwo() {
}
为什么没有错误?我们总是被教导表达式从上到下执行(??)
hoisted
JavaScript解释器总是将函数声明和变量声明不可见地移动()到其包含范围的顶部。函数参数和语言定义的名称显然已经存在。本樱桃
这意味着这样的代码:
functionOne(); --------------- var functionOne;
| is actually | functionOne();
var functionOne = function(){ | interpreted |-->
}; | like | functionOne = function(){
--------------- };
请注意,声明的赋值部分未悬挂。仅悬挂名称。
但是对于函数声明,整个函数体也将被提升:
functionTwo(); --------------- function functionTwo() {
| is actually | };
function functionTwo() { | interpreted |-->
} | like | functionTwo();
---------------
区别在于它functionOne
是一个函数表达式,因此仅在到达该行时才定义,而是functionTwo
函数声明,并在其周围的函数或脚本执行后(由于提升)而定义。
例如,一个函数表达式:
// TypeError: functionOne is not a function
functionOne();
var functionOne = function() {
console.log("Hello!");
};
并且,一个函数声明:
// Outputs: "Hello!"
functionTwo();
function functionTwo() {
console.log("Hello!");
}
这也意味着您不能使用函数声明有条件地定义函数:
if (test) {
// Error or misbehavior
function functionThree() { doSomething(); }
}
上面的定义实际上functionThree
与test
的值无关,除非use strict
有效-除非有效,在这种情况下,它只会引发错误。
您在此处张贴的两个代码段几乎可以出于所有目的以相同的方式运行。
但是,行为上的差异在于,对于第一个变体(var functionOne = function() {}
),只能在代码中的该点之后调用该函数。
使用第二个变体(function functionTwo()
),该函数可用于在声明该函数的位置上方运行的代码。
这是因为在第一个变量中,foo
在运行时将函数分配给了变量。在第二步中,foo
在解析时将函数分配给该标识符。
更多技术信息
JavaScript具有三种定义函数的方式。
eval()
,但存在问题。
new Function()
可用于在字符串中传递函数的主体。因此,可以将其用于创建动态功能。还传递脚本而不执行脚本。