我最近开始维护其他人的JavaScript代码.我正在修复错误,添加功能,并尝试整理代码并使其更加一致.
以前的开发人员使用两种声明函数的方法,如果背后有原因,我就无法解决.
这两种方式是:
var functionOne = function() { // Some code };
function functionTwo() { // Some code }
使用这两种不同方法的原因是什么?每种方法的优缺点是什么?有一种方法可以通过一种方法完成,而另一种方法无法完成吗?
不同之处在于它functionOne
是一个函数表达式,因此只在到达该行时定义,而是functionTwo
一个函数声明,并且只要执行其周围的函数或脚本(由于提升)就会定义.
例如,一个函数表达式:
// TypeError: functionOne is not a function
functionOne();
var functionOne = function() {
console.log("Hello!");
};
首先,我想纠正Greg:function abc(){}
也是作用域 - 名称abc
是在遇到此定义的范围内定义的.例:
function xyz(){ function abc(){}; // abc is defined here... } // ...but not here
其次,可以结合两种风格:
var xyz = function abc(){};
xyz
将被定义为通常,abc
在所有浏览器中都是未定义的,但Internet Explorer - 不依赖于它的定义.但它将在其内部定义:
var xyz = function abc(){ // xyz is visible here // abc is visible here } // xyz is visible here // abc is undefined here
如果要在所有浏览器上使用别名函数,请使用以下类型的声明:
function abc(){}; var xyz = abc;
在这种情况下,两个xyz
和abc
是同一个对象的别名:
console.log(xyz === abc); // prints "true"
使用组合样式的一个令人信服的理由是函数对象的"名称"属性(Internet Explorer不支持).基本上当你定义一个函数时
function abc(){}; console.log(abc.name); // prints "abc"
它的名称是自动分配的.但是当你定义它时
var abc = function(){}; console.log(abc.name); // prints ""
它的名称是空的 - 我们创建了一个匿名函数并将其分配给某个变量.
使用组合样式的另一个好理由是使用简短的内部名称来引用自身,同时为外部用户提供长的非冲突名称:
// Assume really.long.external.scoped is {} really.long.external.scoped.name = function shortcut(n){ // Let it call itself recursively: shortcut(n - 1); // ... // Let it pass itself as a callback: someFunction(shortcut); // ... }
在上面的例子中,我们可以使用外部名称做同样的事情,但它太笨重(而且速度较慢).
(引用自身的另一种方法是使用arguments.callee
,它仍然相对较长,并且在严格模式下不受支持.)
在内心深处,JavaScript以不同的方式处理两种语句.这是一个函数声明:
function abc(){}
abc
这里定义了当前范围内的所有位置:
// We can call it here abc(); // Works // Yet, it is defined down there. function abc(){} // We can call it again abc(); // Works
此外,它通过return
声明提出:
// We can call it here abc(); // Works return; function abc(){}
这是一个函数表达式:
var xyz = function(){};
xyz
这里是从作业点定义的:
// We can't call it here xyz(); // UNDEFINED!!! // Now it is defined xyz = function(){} // We can call it here xyz(); // works
函数声明与函数表达式是Greg证明存在差异的真正原因.
有趣的事实:
var xyz = function abc(){}; console.log(xyz.name); // Prints "abc"
就个人而言,我更喜欢"函数表达式"声明,因为这样我可以控制可见性.当我定义函数时
var abc = function(){};
我知道我在本地定义了这个函数.当我定义函数时
abc = function(){};
我知道我在全球范围内定义它,只要我没有abc
在范围链中的任何地方定义.这种定义风格即使在内部使用也具有弹性eval()
.而定义
function abc(){};
取决于上下文,可能会让你猜测它实际定义的位置,特别是在 - 的情况下eval()
- 答案是:它取决于浏览器.
这是创建函数的标准表单的纲要:( 最初是为另一个问题编写的,但在被移入规范问题后进行了调整.)
条款:
ES5:ECMAScript第5版,2009年
ES2015:ECMAScript 2015(也称为"ES6")
快速清单:
功能声明
"匿名" function
表达(尽管有术语,有时会创建带有名称的函数)
命名function
表达
存取器功能初始化器(ES5 +)
箭头函数表达式(ES2015 +)(与匿名函数表达式一样,不涉及显式名称,但可以创建带有名称的函数)
对象初始化器中的方法声明(ES2015 +)
class
(ES2015 +)中的构造函数和方法声明
第一种形式是函数声明,如下所示:
function x() { console.log('x'); }
函数声明是一个声明 ; 这不是一个陈述或表达.因此,你不要跟随它;
(尽管这样做是无害的).
在执行任何逐步执行代码之前,执行进入其出现的上下文时,将处理函数声明.它创建的函数具有正确的名称(x
在上面的示例中),并且该名称放在声明出现的范围内.
因为它是在同一个上下文中的任何分步代码之前处理的,所以你可以这样做:
x(); // Works even though it's above the declaration function x() { console.log('x'); }
直到ES2015,该规范并没有涵盖,如果你把一个控制结构像内部函数声明中的JavaScript引擎应该做的事情try
,if
,switch
,while
,等等,是这样的:
if (someCondition) { function foo() { // <===== HERE THERE } // <===== BE DRAGONS }
而且由于它们是在逐步运行代码之前进行处理的,所以当它们处于控制结构中时知道该怎么做是很棘手的.
尽管在ES2015之前没有指定这样做,但它是一个允许的扩展来支持块中的函数声明.不幸的是(并且不可避免地),不同的引擎做了不同的事情.
从ES2015开始,规范说明了该怎么做.事实上,它提供了三个单独的事情:
如果松散模式不在 Web浏览器上,JavaScript引擎应该做一件事
如果在Web浏览器上处于松散模式,则JavaScript引擎应该执行其他操作
如果在严格模式下(浏览器与否),JavaScript引擎应该做另外的事情
松散模式的规则很棘手,但在严格模式下,块中的函数声明很容易:它们是块的本地(它们具有块范围,这在ES2015中也是新的),并且它们被提升到顶部块.所以:
"use strict"; if (someCondition) { foo(); // Works just fine function foo() { } } console.log(typeof foo); // "undefined" (`foo` is not in scope here // because it's not in the same block)
function
表达第二种常见形式称为匿名函数表达式:
var y = function () { console.log('y'); };
与所有表达式一样,它是在逐步执行代码时达到的.
在ES5中,它创建的函数没有名称(它是匿名的).在ES2015中,如果可能,通过从上下文推断该函数来为该函数指定名称.在上面的示例中,名称将是y
.当函数是属性初始值设定项的值时,会执行类似的操作.(有关何时发生这种情况的细节和规则,搜索SetFunctionName
在规范 -它似乎遍布的地方.)
function
表达第三种形式是命名函数表达式("NFE"):
var z = function w() { console.log('zw') };
它创建的函数具有正确的名称(w
在本例中).与所有表达式一样,在逐步执行代码时,会对其进行评估.函数的名称未添加到表达式出现的范围中; 名称是在函数内部范围:
var z = function w() { console.log(typeof w); // "function" }; console.log(typeof w); // "undefined"
请注意,NFE经常成为JavaScript实现的错误来源.例如,IE8及更早版本完全错误地处理NFE ,在两个不同的时间创建两个不同的函数.早期版本的Safari也存在问题.好消息是当前版本的浏览器(IE9及更高版本,当前的Safari)不再存在这些问题.(但在撰写本文时,遗憾的是,IE8仍然广泛使用,因此使用NFE和Web代码一般仍然存在问题.)
有时功能可以潜入大部分未被注意到; 这是访问者功能的情况.这是一个例子:
var obj = { value: 0, get f() { return this.value; }, set f(v) { this.value = v; } }; console.log(obj.f); // 0 console.log(typeof obj.f); // "number"
请注意,当我使用该功能时,我没有使用()
!那是因为它是一个属性的访问函数.我们以正常方式获取并设置属性,但在幕后,调用该函数.
您还可以使用Object.defineProperty
,Object.defineProperties
和不太知名的第二个参数创建访问器函数Object.create
.
ES2015为我们带来了箭头功能.这是一个例子:
var a = [1, 2, 3]; var b = a.map(n => n * 2); console.log(b.join(", ")); // 2, 4, 6
看到n => n * 2
隐藏在map()
通话中的东西?这是一个功能.
关于箭头功能的一些事情:
他们没有自己的this
.相反,他们关闭了在this
他们定义成背景.(它们也会关闭,arguments
并且在相关的地方super
.)这意味着它们this
内部与this
它们创建的位置相同,并且不能更改.
正如您已经注意到的那样,您不使用关键字function
; 相反,你使用=>
.
n => n * 2
上面的例子是它们的一种形式.如果您有多个参数来传递函数,则使用parens:
var a = [1, 2, 3]; var b = a.map((n, i) => n * i); console.log(b.join(", ")); // 0, 2, 6
(请记住,Array#map
将条目作为第一个参数传递,将索引作为第二个参数传递.)
在这两种情况下,函数的主体只是一个表达式; 函数的返回值将自动成为该表达式的结果(您不使用显式return
).
如果你做的不仅仅是单个表达式,请使用{}
和显式return
(如果你需要返回一个值),正常情况下:
var a = [ {first: "Joe", last: "Bloggs"}, {first: "Albert", last: "Bloggs"}, {first: "Mary", last: "Albright"} ]; a = a.sort((a, b) => { var rv = a.last.localeCompare(b.last); if (rv === 0) { rv = a.first.localeCompare(b.first); } return rv; }); console.log(JSON.stringify(a));
没有的版本{ ... }
被称为带有表达体或简洁体的箭头函数.(另外:一个简洁的箭头功能.){ ... }
定义主体的那个是带有功能体的箭头功能.(另外:一个冗长的箭头功能.)
ES2015允许更短的形式声明一个引用称为方法定义的函数的属性; 它看起来像这样:
var o = { foo() { } };
在ES5和之前的几乎相当于:
var o = { foo: function foo() { } };
差异(除了详细程度)是一个方法可以使用super
,但一个功能不能.因此,举例来说,如果你有一个valueOf
使用方法语法定义(比方说)的对象,它可以super.valueOf()
用来获取Object.prototype.valueOf
返回的值(之前可能用它做其他事情),而ES5版本则必须这样做Object.prototype.valueOf.call(this)
.
这也意味着该方法引用了它所定义的对象,因此如果该对象是临时的(例如,您将其Object.assign
作为源对象之一传递),则方法语法可能意味着该对象被保留在内存中,否则它可能被垃圾收集(如果JavaScript引擎没有检测到这种情况,并处理它,如果没有方法使用super
).
class
(ES2015 +)中的构造函数和方法声明ES2015为我们带来了class
语法,包括声明的构造函数和方法:
class Person { constructor(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } getFullName() { return this.firstName + " " + this.lastName; } }
上面有两个函数声明:一个用于构造函数,一个用于获取名称Person
,另一个用于for getFullName
,它是一个分配给的函数Person.prototype
.
说到全局上下文,var
语句和FunctionDeclaration
最后都将在全局对象上创建一个不可删除的属性,但两者的值都可以被覆盖.
两种方式之间的细微差别在于,当变量实例化过程运行时(在实际代码执行之前),所有声明的标识符var
都将被初始化undefined
,并且FunctionDeclaration
自那时起使用的标识符将可用,例如:
alert(typeof foo); // 'function', it's already available alert(typeof bar); // 'undefined' function foo () {} var bar = function () {}; alert(typeof bar); // 'function'
分配bar
FunctionExpression
发生在运行时间.
由a创建的全局属性FunctionDeclaration
可以像变量值一样被覆盖而没有任何问题,例如:
function test () {} test = null;
您的两个示例之间的另一个明显区别是第一个函数没有名称,但第二个函数有它,这在调试(即检查调用堆栈)时非常有用.
关于您编辑的第一个示例(foo = function() { alert('hello!'); };
),这是一个未声明的作业,我强烈建议您始终使用该var
关键字.
使用赋值,如果没有var
语句,如果在作用域链中找不到引用的标识符,它将成为全局对象的可删除属性.
此外,未声明的作业ReferenceError
在严格模式下投入ECMAScript 5 .
必读:
命名函数表达式揭秘
注意:这个答案已经与另一个问题合并,其中OP的主要疑问和误解是用a声明的标识符FunctionDeclaration
不能被覆盖,但事实并非如此.
您在那里发布的两个代码片段几乎在所有目的下都会以相同的方式运行.
但是,行为的差异在于使用第一个variant(var functionOne = function() {}
),该函数只能在代码中的该点之后调用.
使用第二个variant(function functionTwo()
),该函数可用于在声明函数的上方运行的代码.
这是因为对于第一个变体,函数foo
在运行时分配给变量.在第二个中,foo
在解析时将该函数分配给该标识符.
更多技术信息
JavaScript有三种定义函数的方法.
您的第一个代码段显示了一个函数表达式.这涉及使用"function"运算符来创建函数 - 该运算符的结果可以存储在任何变量或对象属性中.函数表达式就是这样强大的.函数表达式通常称为"匿名函数",因为它不必具有名称,
你的第二个例子是函数声明.这使用"function"语句来创建函数.该函数在分析时可用,并且可以在该范围内的任何位置调用.您以后仍可以将其存储在变量或对象属性中.
定义函数的第三种方法是"Function()"构造函数,它在原始帖子中没有显示.不建议使用它,因为它的工作方式与eval()
存在问题的方式相同.
更好地解释格雷格的答案
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(); ---------------
其他评论者已经涵盖了上述两种变体的语义差异.我想要注意一个风格差异:只有"赋值"变体可以设置另一个对象的属性.
我经常用这样的模式构建JavaScript模块:
(function(){ var exports = {}; function privateUtil() { ... } exports.publicUtil = function() { ... }; return exports; })();
使用此模式,您的公共函数将全部使用赋值,而您的私有函数使用声明.
(另请注意,赋值在语句后应该使用分号,而声明禁止它.)
何时优先考虑第一种方法到第二种方法的说明是当你需要避免覆盖函数的先前定义时.
同
if (condition){ function myfunction(){ // Some code } }
,这个定义myfunction
将覆盖任何先前的定义,因为它将在分析时完成.
而
if (condition){ var myfunction = function (){ // Some code } }
myfunction
只有在condition
满足时才能正确定义.
一个重要的原因是添加一个且只有一个变量作为命名空间的"根"...
var MyNamespace = {} MyNamespace.foo= function() { }
要么
var MyNamespace = { foo: function() { }, ... }
命名空间有很多技巧.随着大量JavaScript模块的出现,它变得越来越重要.
另请参阅如何在JavaScript中声明命名空间?
提升 是JavaScript解释器将所有变量和函数声明移动到当前范围顶部的操作.
但是,只有实际的声明才会被提升.将任务留在原处.
在页面内声明的变量/函数是全局的,可以访问该页面中的任何位置.
函数内声明的变量/函数具有局部范围.意味着它们在功能体(范围)内可用/访问,它们在功能体外部不可用.
变量
Javascript被称为松散类型的语言.这意味着Javascript变量可以保存任何数据类型的值.Javascript自动根据运行时提供的值/文字来更改变量类型.
global_Page = 10; var global_Page; « undefined « Integer literal, Number Type. ------------------- global_Page = 10; « Number global_Page = 'Yash'; | Interpreted | global_Page = 'Yash'; « String « String literal, String Type. « AS « global_Page = true; « Boolean var global_Page = true; | | global_Page = function (){ « function « Boolean Type ------------------- var local_functionblock; « undefined global_Page = function (){ local_functionblock = 777;« Number var local_functionblock = 777; }; // Assigning function as a data. };
功能
function Identifier_opt ( FormalParameterList_opt ) { FunctionBody | sequence of statements « return; Default undefined « return 'some data'; }
在页面内声明的函数被提升到具有全局访问权限的页面顶部.
在函数块内声明的函数被提升到块的顶部.
函数的默认返回值是' undefined ',变量声明默认值也是'undefined'
Scope with respect to function-block global. Scope with respect to page undefined | not available.
功能声明
function globalAccess() { function globalAccess() { } ------------------- } globalAccess(); | | function globalAccess() { « Re-Defined / overridden. localAccess(); « Hoisted As « function localAccess() { function globalAccess() { | | } localAccess(); ------------------- localAccess(); « function accessed with in globalAccess() only. function localAccess() { } } globalAccess(); } localAccess(); « ReferenceError as the function is not defined
功能表达
10; « literal (10); « Expression (10).toString() -> '10' var a; a = 10; « Expression var a.toString() -> '10' (function invoke() { « Expression Function console.log('Self Invoking'); (function () { }); }) () -> 'Self Invoking' var f; f = function (){ « Expression var Function console.log('var Function'); f () -> 'var Function' };
分配给变量的函数示例:
(function selfExecuting(){ console.log('IIFE - Immediately-Invoked Function Expression'); }()); var anonymous = function (){ console.log('anonymous function Expression'); }; var namedExpression = function for_InternalUSE(fact){ if(fact === 1){ return 1; } var localExpression = function(){ console.log('Local to the parent Function Scope'); }; globalExpression = function(){ console.log('creates a new global variable, then assigned this function.'); }; //return; //undefined. return fact * for_InternalUSE( fact - 1); }; namedExpression(); globalExpression();
javascript解释为
var anonymous; var namedExpression; var globalExpression; anonymous = function (){ console.log('anonymous function Expression'); }; namedExpression = function for_InternalUSE(fact){ var localExpression; if(fact === 1){ return 1; } localExpression = function(){ console.log('Local to the parent Function Scope'); }; globalExpression = function(){ console.log('creates a new global variable, then assigned this function.'); }; return fact * for_InternalUSE( fact - 1); // DEFAULT UNDEFINED. }; namedExpression(10); globalExpression();
您可以使用查看不同浏览器的函数声明,表达式测试 jsperf Test Runner
ES5构造函数类:使用Function.prototype.bind创建的函数对象
JavaScript将函数视为第一类对象,因此作为对象,您可以为函数指定属性.
function Shape(id) { // Function Declaration this.id = id; }; // Adding a prototyped method to a function. Shape.prototype.getID = function () { return this.id; }; Shape.prototype.setID = function ( id ) { this.id = id; }; var expFn = Shape; // Function Expression var funObj = new Shape( ); // Function Object funObj.hasOwnProperty('prototype'); // false funObj.setID( 10 ); console.log( funObj.getID() ); // 10
ES6引入了箭头函数:箭头函数表达式具有较短的语法,它们最适合非方法函数,并且它们不能用作构造函数.
ArrowFunction : ArrowParameters => ConciseBody
.const fn = (item) => { return item & 1 ? 'Odd' : 'Even'; }; console.log( fn(2) ); // Even console.log( fn(3) ); // Odd
我正在添加我自己的答案,因为其他人都彻底覆盖了吊装部件.
我想知道现在哪种方式更好,现在感谢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
.范围的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
函数声明优先于变量声明,最后一个函数声明"粘".
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
.
第一个例子是函数声明:
function abc(){}
第二个例子是函数表达式:
var abc = function() {};
主要区别在于如何吊起(举起和宣布).在第一个示例中,整个函数声明被提升.在第二个例子中,只有var'abc'被提升,它的值(函数)将是未定义的,并且函数本身保持在声明它的位置.
简而言之:
//this will work abc(param); function abc(){} //this would fail abc(param); var abc = function() {}
要了解有关此主题的更多信息,我强烈建议您使用此 链接
在代码维护成本方面,命名函数更为可取:
独立于宣布它们的地方(但仍受范围限制).
更能抵抗条件初始化等错误(如果需要,您仍然可以覆盖).
通过分配范围功能的本地功能,代码变得更具可读性.通常在范围内,首先是功能,然后是本地功能的声明.
在调试器中,您将清楚地看到调用堆栈上的函数名称,而不是"匿名/已评估"函数.
我怀疑命名函数的PROS会更多.被列为命名函数优势的是匿名函数的缺点.
从历史上看,匿名函数出现在JavaScript无法作为列出具有命名函数的成员的语言中:
{ member:function() { /* How do I make "this.member" a named function? */ } }
我在代码中使用变量方法是出于一个非常具体的原因,其理论已在上面以一种抽象的方式介绍,但是一个例子可能会帮助像我这样的人,JavaScript专业知识有限.
我有代码需要运行160个独立设计的品牌.大多数代码都在共享文件中,但品牌特定的东西在一个单独的文件中,每个品牌一个.
有些品牌需要特定功能,有些则不需要.有时我必须添加新功能来执行新的品牌特定事物.我很乐意更改共享编码,但我不想更改所有160套品牌文件.
通过使用变量语法,我可以在共享代码中声明变量(本质上是一个函数指针),并分配一个普通的存根函数,或设置为null.
然后,需要特定功能实现的一个或两个品牌可以定义它们的函数版本,并在需要时将其分配给变量,其余的则不执行任何操作.我可以在共享代码中执行之前测试null函数.
根据上面人们的评论,我认为也可以重新定义静态函数,但我认为变量解决方案很好而且清晰.
在计算机科学术语中,我们讨论匿名函数和命名函数.我认为最重要的区别是匿名函数没有绑定到名称,因此名称是匿名函数.在JavaScript中,它是在运行时动态声明的第一个类对象.
有关匿名函数和lambda演算的更多信息,维基百科是一个良好的开端(http://en.wikipedia.org/wiki/Anonymous_function).
格雷格的答案已经足够好了,但我还是想补充一点,我刚才看到道格拉斯·克罗克福德的视频.
功能表达:
var foo = function foo() {};
功能说明:
function foo() {};
函数语句只是var
带有function
值的语句的简写.
所以
function foo() {};
扩展到
var foo = function foo() {};
其进一步扩展到:
var foo = undefined; foo = function foo() {};
它们都被提升到代码的顶部.
@EugeneLazutkin给出了一个例子,他将一个指定的函数命名为能够shortcut()
用作自身的内部引用.John Resig给出了另一个例子 - 在他的Learning Advanced Javascript教程中复制分配给另一个对象的递归函数.虽然向属性分配函数并不是严格意义上的问题,但我建议您主动尝试教程 - 通过单击右上角的按钮运行代码,然后双击代码以根据自己的喜好进行编辑.
本教程中的示例:递归调用yell()
:
删除原始ninja对象时测试失败.(第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." ); 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页)
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." );
其他答案中没有提到的另一个区别是,如果您使用匿名函数
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
.
第一个(函数doSomething(x))应该是对象表示法的一部分.
第二个(var doSomething = function(x){ alert(x);}
)只是创建一个匿名函数并将其赋值给变量doSomething
.所以doSomething()将调用该函数.
您可能想知道函数声明和函数表达式是什么.
函数声明定义了一个命名函数变量,而不需要变量赋值.函数声明作为独立构造出现,不能嵌套在非函数块中.
function foo() { return 3; }
ECMA 5(13.0)将语法定义为
函数Identifier(FormalParameterList opt){FunctionBody}
在上述条件中,函数名称在其范围内以及其父级的范围内可见(否则它将无法访问).
并在函数表达式中
函数表达式将函数定义为较大表达式语法(通常是变量赋值)的一部分.通过函数表达式定义的函数可以是命名的或匿名的.函数表达式不应以"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)将语法定义为
函数Identifier 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
我列出了以下差异:
函数声明可以放在代码中的任何位置.即使在定义出现在代码之前调用它,它也会在函数声明被提交到内存或以某种方式被提升之前执行,之后页面中的任何其他代码开始执行.
看看下面的功能:
function outerFunction() { function foo() { return 1; } return foo(); function foo() { return 2; } } alert(outerFunction()); // Displays 2
这是因为,在执行期间,它看起来像: -
function foo() { // The first function declaration is moved to top return 1; } function foo() { // The second function declaration is moved to top return 2; } function outerFunction() { return foo(); } alert(outerFunction()); //So executing from top to bottom, //the last foo() returns 2 which gets displayed
函数表达式(如果在调用之前未定义)将导致错误.此外,这里函数定义本身不会像函数声明一样移动到顶部或提交到内存.但是我们分配函数的变量被提升并且未定义被分配给它.
使用函数表达式的相同函数
function outerFunction() { var foo = function() { return 1; } return foo(); var foo = function() { return 2; } } alert(outerFunction()); // Displays 1
这是因为在执行期间,它看起来像:
function outerFunction() { var foo = undefined; var foo = undefined; foo = function() { return 1; }; return foo (); foo = function() { // This function expression is not reachable return 2; }; } alert(outerFunction()); // Displays 1
它是不是安全写在非功能块函数声明一样,如果因为他们将无法访问.
if (test) { function x() { doSomething(); } }
如下所示的命名函数表达式可能无法在版本9之前的Internet Explorer浏览器中使用.
var today = function today() {return new Date()}
下面列出的两种不同功能声明之间有四个值得注意的比较.
功能的可用性(范围)
以下工作原因因为function add()
范围限定为最近的块:
try {
console.log("Success: ", add(1, 1));
} catch(e) {
console.log("ERROR: " + e);
}
function add(a, b){
return a + b;
}
鉴于"命名函数显示在堆栈跟踪"参数,现代JavaScript引擎实际上非常能够表示匿名函数.
在撰写本文时,V8,SpiderMonkey,Chakra和Nitro总是通过他们的名字来引用命名函数.它们几乎总是通过标识符引用匿名函数(如果有的话).
SpiderMonkey可以找出从另一个函数返回的匿名函数的名称.其余的不能.
如果你真的,真的希望你的迭代器和成功回调显示在跟踪中,你可以命名那些......
[].forEach(function iterator() {});
但在大多数情况下,不值得强调.
'use strict'; var a = function () { throw new Error(); }, b = function b() { throw new Error(); }, c = function d() { throw new Error(); }, e = { f: a, g: b, h: c, i: function () { throw new Error(); }, j: function j() { throw new Error(); }, k: function l() { throw new Error(); } }, m = (function () { return function () { throw new Error(); }; }()), n = (function () { return function n() { throw new Error(); }; }()), o = (function () { return function p() { throw new Error(); }; }()); console.log([a, b, c].concat(Object.keys(e).reduce(function (values, key) { return values.concat(e[key]); }, [])).concat([m, n, o]).reduce(function (logs, func) { try { func(); } catch (error) { return logs.concat('func.name: ' + func.name + '\n' + 'Trace:\n' + error.stack); // Need to manually log the error object in Nitro. } }, []).join('\n\n'));
func.name: Trace: Error at a (http://localhost:8000/test.js:4:11) at http://localhost:8000/test.js:47:9 at Array.reduce (native) at http://localhost:8000/test.js:44:27 func.name: b Trace: Error at b (http://localhost:8000/test.js:7:15) at http://localhost:8000/test.js:47:9 at Array.reduce (native) at http://localhost:8000/test.js:44:27 func.name: d Trace: Error at d (http://localhost:8000/test.js:10:15) at http://localhost:8000/test.js:47:9 at Array.reduce (native) at http://localhost:8000/test.js:44:27 func.name: Trace: Error at a (http://localhost:8000/test.js:4:11) at http://localhost:8000/test.js:47:9 at Array.reduce (native) at http://localhost:8000/test.js:44:27 func.name: b Trace: Error at b (http://localhost:8000/test.js:7:15) at http://localhost:8000/test.js:47:9 at Array.reduce (native) at http://localhost:8000/test.js:44:27 func.name: d Trace: Error at d (http://localhost:8000/test.js:10:15) at http://localhost:8000/test.js:47:9 at Array.reduce (native) at http://localhost:8000/test.js:44:27 func.name: Trace: Error at e.i (http://localhost:8000/test.js:17:19) at http://localhost:8000/test.js:47:9 at Array.reduce (native) at http://localhost:8000/test.js:44:27 func.name: j Trace: Error at j (http://localhost:8000/test.js:20:19) at http://localhost:8000/test.js:47:9 at Array.reduce (native) at http://localhost:8000/test.js:44:27 func.name: l Trace: Error at l (http://localhost:8000/test.js:23:19) at http://localhost:8000/test.js:47:9 at Array.reduce (native) at http://localhost:8000/test.js:44:27 func.name: Trace: Error at http://localhost:8000/test.js:28:19 at http://localhost:8000/test.js:47:9 at Array.reduce (native) at http://localhost:8000/test.js:44:27 func.name: n Trace: Error at n (http://localhost:8000/test.js:33:19) at http://localhost:8000/test.js:47:9 at Array.reduce (native) at http://localhost:8000/test.js:44:27 func.name: p Trace: Error at p (http://localhost:8000/test.js:38:19) at http://localhost:8000/test.js:47:9 at Array.reduce (native) at http://localhost:8000/test.js:44:27 test.js:42
func.name: Trace: a@http://localhost:8000/test.js:4:5 @http://localhost:8000/test.js:47:9 @http://localhost:8000/test.js:54:1 func.name: b Trace: b@http://localhost:8000/test.js:7:9 @http://localhost:8000/test.js:47:9 @http://localhost:8000/test.js:54:1 func.name: d Trace: d@http://localhost:8000/test.js:10:9 @http://localhost:8000/test.js:47:9 @http://localhost:8000/test.js:54:1 func.name: Trace: a@http://localhost:8000/test.js:4:5 @http://localhost:8000/test.js:47:9 @http://localhost:8000/test.js:54:1 func.name: b Trace: b@http://localhost:8000/test.js:7:9 @http://localhost:8000/test.js:47:9 @http://localhost:8000/test.js:54:1 func.name: d Trace: d@http://localhost:8000/test.js:10:9 @http://localhost:8000/test.js:47:9 @http://localhost:8000/test.js:54:1 func.name: Trace: e.i@http://localhost:8000/test.js:17:13 @http://localhost:8000/test.js:47:9 @http://localhost:8000/test.js:54:1 func.name: j Trace: j@http://localhost:8000/test.js:20:13 @http://localhost:8000/test.js:47:9 @http://localhost:8000/test.js:54:1 func.name: l Trace: l@http://localhost:8000/test.js:23:13 @http://localhost:8000/test.js:47:9 @http://localhost:8000/test.js:54:1 func.name: Trace: m<@http://localhost:8000/test.js:28:13 @http://localhost:8000/test.js:47:9 @http://localhost:8000/test.js:54:1 func.name: n Trace: n@http://localhost:8000/test.js:33:13 @http://localhost:8000/test.js:47:9 @http://localhost:8000/test.js:54:1 func.name: p Trace: p@http://localhost:8000/test.js:38:13 @http://localhost:8000/test.js:47:9 @http://localhost:8000/test.js:54:1
func.name: undefined Trace: Error at a (http://localhost:8000/test.js:4:5) at Anonymous function (http://localhost:8000/test.js:47:9) at Global code (http://localhost:8000/test.js:42:1) func.name: undefined Trace: Error at b (http://localhost:8000/test.js:7:9) at Anonymous function (http://localhost:8000/test.js:47:9) at Global code (http://localhost:8000/test.js:42:1) func.name: undefined Trace: Error at d (http://localhost:8000/test.js:10:9) at Anonymous function (http://localhost:8000/test.js:47:9) at Global code (http://localhost:8000/test.js:42:1) func.name: undefined Trace: Error at a (http://localhost:8000/test.js:4:5) at Anonymous function (http://localhost:8000/test.js:47:9) at Global code (http://localhost:8000/test.js:42:1) func.name: undefined Trace: Error at b (http://localhost:8000/test.js:7:9) at Anonymous function (http://localhost:8000/test.js:47:9) at Global code (http://localhost:8000/test.js:42:1) func.name: undefined Trace: Error at d (http://localhost:8000/test.js:10:9) at Anonymous function (http://localhost:8000/test.js:47:9) at Global code (http://localhost:8000/test.js:42:1) func.name: undefined Trace: Error at e.i (http://localhost:8000/test.js:17:13) at Anonymous function (http://localhost:8000/test.js:47:9) at Global code (http://localhost:8000/test.js:42:1) func.name: undefined Trace: Error at j (http://localhost:8000/test.js:20:13) at Anonymous function (http://localhost:8000/test.js:47:9) at Global code (http://localhost:8000/test.js:42:1) func.name: undefined Trace: Error at l (http://localhost:8000/test.js:23:13) at Anonymous function (http://localhost:8000/test.js:47:9) at Global code (http://localhost:8000/test.js:42:1) func.name: undefined Trace: Error at Anonymous function (http://localhost:8000/test.js:28:13) at Anonymous function (http://localhost:8000/test.js:47:9) at Global code (http://localhost:8000/test.js:42:1) func.name: undefined Trace: Error at n (http://localhost:8000/test.js:33:13) at Anonymous function (http://localhost:8000/test.js:47:9) at Global code (http://localhost:8000/test.js:42:1) func.name: undefined Trace: Error at p (http://localhost:8000/test.js:38:13) at Anonymous function (http://localhost:8000/test.js:47:9) at Global code (http://localhost:8000/test.js:42:1)
func.name: Trace: a@http://localhost:8000/test.js:4:22 http://localhost:8000/test.js:47:13 reduce@[native code] global code@http://localhost:8000/test.js:44:33 func.name: b Trace: b@http://localhost:8000/test.js:7:26 http://localhost:8000/test.js:47:13 reduce@[native code] global code@http://localhost:8000/test.js:44:33 func.name: d Trace: d@http://localhost:8000/test.js:10:26 http://localhost:8000/test.js:47:13 reduce@[native code] global code@http://localhost:8000/test.js:44:33 func.name: Trace: a@http://localhost:8000/test.js:4:22 http://localhost:8000/test.js:47:13 reduce@[native code] global code@http://localhost:8000/test.js:44:33 func.name: b Trace: b@http://localhost:8000/test.js:7:26 http://localhost:8000/test.js:47:13 reduce@[native code] global code@http://localhost:8000/test.js:44:33 func.name: d Trace: d@http://localhost:8000/test.js:10:26 http://localhost:8000/test.js:47:13 reduce@[native code] global code@http://localhost:8000/test.js:44:33 func.name: Trace: i@http://localhost:8000/test.js:17:30 http://localhost:8000/test.js:47:13 reduce@[native code] global code@http://localhost:8000/test.js:44:33 func.name: j Trace: j@http://localhost:8000/test.js:20:30 http://localhost:8000/test.js:47:13 reduce@[native code] global code@http://localhost:8000/test.js:44:33 func.name: l Trace: l@http://localhost:8000/test.js:23:30 http://localhost:8000/test.js:47:13 reduce@[native code] global code@http://localhost:8000/test.js:44:33 func.name: Trace: http://localhost:8000/test.js:28:30 http://localhost:8000/test.js:47:13 reduce@[native code] global code@http://localhost:8000/test.js:44:33 func.name: n Trace: n@http://localhost:8000/test.js:33:30 http://localhost:8000/test.js:47:13 reduce@[native code] global code@http://localhost:8000/test.js:44:33 func.name: p Trace: p@http://localhost:8000/test.js:38:30 http://localhost:8000/test.js:47:13 reduce@[native code] global code@http://localhost:8000/test.js:44:33
在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(){}
两者都是定义函数的不同方式.不同之处在于浏览器如何解释并将它们加载到执行上下文中.
第一种情况是函数表达式,仅在解释器到达该行代码时才加载.因此,如果您执行以下操作,您将收到一个错误,即functionOne不是函数.
functionOne(); var functionOne = function() { // Some code };
原因是在第一行没有为functionOne赋值,因此它是未定义的.我们试图将其称为函数,因此我们得到一个错误.
在第二行,我们将一个匿名函数的引用分配给functionOne.
第二种情况是在执行任何代码之前加载的函数声明.因此,如果您喜欢以下内容,则在代码执行之前加载声明时不会出现任何错误.
functionOne(); function functionOne() { // Some code }
关于表现:
新版本V8
引入了几个底层优化,如此SpiderMonkey
.
表达式和声明之间现在几乎没有区别.
函数表达式现在看起来更快.
Chrome 62.0.3202
FireFox 55
Chrome Canary 63.0.3225
Anonymous
函数表达式似乎有更好的表现 对Named
函数表达式.
火狐 Chrome金丝雀 铬
它们非常相似,只有一些小差异,第一个是分配给匿名函数的变量(函数声明),第二个是在JavaScript中创建函数的常规方法(匿名函数声明),两者都有用法,缺点和优点:
1.功能表达
var functionOne = function() { // Some code };
函数表达式将函数定义为更大表达式语法(通常是变量赋值)的一部分.通过函数表达式定义的函数可以命名或匿名.函数表达式不能以"function"开头(因此下面是自调用示例的括号).
将一个变量分配给一个函数,意味着没有提升,因为我们知道JavaScript中的函数可以提升,意味着它们可以在声明之前被调用,而变量需要在获取它们之前声明,所以在这种情况下意味着我们不能在声明它之前访问函数,也可以是你编写函数的一种方式,对于返回另一个函数的函数,这种声明是有意义的,同样在ECMA6及以上你可以将它赋给箭头函数可以用来调用匿名函数,这种声明方式也是在JavaScript中创建构造函数的更好方法.
2.功能声明
function functionTwo() { // Some code }
函数声明定义了一个命名函数变量,无需变量赋值.函数声明作为独立构造出现,不能嵌套在非函数块中.将它们视为变量声明的兄弟是有帮助的.正如变量声明必须以"var"开头一样,函数声明必须以"function"开头.
这是在JavaScript中调用函数的常规方法,这个函数可以在你将它声明之前调用,因为在JavaScript中所有函数都被提升,但如果你有'使用严格',这将不会像预期的那样提升,这是一个好方法调用行中不大的所有普通函数,它们都不是构造函数.
此外,如果您需要有关如何在JavaScript中提升的更多信息,请访问以下链接:
https://developer.mozilla.org/en-US/docs/Glossary/Hoisting
这只是声明函数的两种可能方式,在第二种方式中,您可以在声明之前使用该函数.
new Function()
可用于在字符串中传递函数的主体。因此,可以将其用于创建动态功能。也传递脚本而不执行脚本。
var func = new Function("x", "y", "return x*y;"); function secondFunction(){ var result; result = func(10,20); console.log ( result ); } secondFunction()