我最近开始维护别人的 JavaScript 代码。我正在修复错误、添加功能并尝试整理代码并使其更加一致。
之前的开发者使用了两种声明函数的方式,我不知道这背后是否有原因。
两种方式是:
var functionOne = function() {
// Some code
};
function functionTwo() {
// Some code
}
使用这两种不同方法的原因是什么,每种方法的优缺点是什么?有什么可以用一种方法完成而另一种方法不能完成的事情吗?
不同之处在于 functionOne
是一个函数表达式,因此仅在到达该行时才定义,而 functionTwo
是一个函数声明,并在执行其周围的函数或脚本时立即定义(由于 hoisting)。
例如,一个函数表达式:
// TypeError: functionOne 不是函数 functionOne(); var functionOne = function() { console.log("Hello!"); };
并且,一个函数声明:
// 输出:“你好!”函数二(); function functionTwo() { console.log("Hello!"); }
从历史上看,块内定义的函数声明在浏览器之间的处理方式不一致。严格模式(在 ES5 中引入)通过将函数声明范围限定为它们的封闭块来解决这个问题。
'使用严格'; { // 注意这个块!函数 functionThree() { 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()
的情况下——答案是:它取决于浏览器。
abc
的值不是函数本身,而是函数的返回值。 abc.name 为空是有意义的,因为 abc 返回一个未命名的函数。 @ikirachen 提到删除 ()
因为那是调用该函数的原因。没有它,它只是用多余的括号括起来。
var
的括号内声明的变量将像往常一样在函数范围内,但该匿名函数在它所包裹的括号之外不再可访问。谢天谢地,这些天我们有 let
,它使用了普通(正常)人所期望的块范围。在我看来,最好假装 var
不存在。
以下是创建函数的标准表单的概要:(最初是为另一个问题编写的,但在移入规范问题后进行了调整。)
条款:
ES5:ECMAScript 第 5 版,2009
ES2015:ECMAScript 2015(也称为“ES6”)
快速列表:
函数声明
“匿名”函数表达式(尽管有这个术语,但有时会创建带有名称的函数)
命名函数表达式
访问函数初始化器 (ES5+)
箭头函数表达式 (ES2015+)(与匿名函数表达式一样,不涉及显式名称,但可以创建具有名称的函数)
对象初始化器中的方法声明 (ES2015+)
类中的构造函数和方法声明(ES2015+)
函数声明
第一种形式是函数声明,如下所示:
function x() {
console.log('x');
}
函数声明是一个声明;它不是一个陈述或表达。因此,您不要使用 ;
跟随它(尽管这样做是无害的)。
函数声明在执行进入它出现的上下文时被处理,在任何一步一步的代码被执行。它创建的函数有一个正确的名称(上例中的 x
),并且该名称被放置在声明出现的范围内。
因为它是在同一上下文中的任何分步代码之前处理的,所以您可以执行以下操作:
x(); // Works even though it's above the declaration
function x() {
console.log('x');
}
在 ES2015 之前,该规范并未涵盖如果您将函数声明放在 try
、if
、switch
、while
等控制结构中,JavaScript 引擎应该做什么,如下所示:
if (someCondition) {
function foo() { // <===== HERE THERE
} // <===== BE DRAGONS
}
而且由于它们是在运行分步代码之前处理的,因此很难知道当它们处于控制结构中时要做什么。
尽管直到 ES2015 才指定这样做,但它是支持块中函数声明的允许扩展。不幸的是(并且不可避免地),不同的引擎做了不同的事情。
从 ES2015 开始,规范说明了要做什么。事实上,它提供了三个不同的事情要做:
如果不是在网络浏览器上处于松散模式,JavaScript 引擎应该做一件事 如果在网络浏览器上处于松散模式,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)
“匿名”函数表达式
第二种常见形式称为匿名函数表达式:
var y = function () {
console.log('y');
};
像所有表达式一样,它在代码的逐步执行过程中被评估。
在 ES5 中,这个创建的函数没有名字(它是匿名的)。在 ES2015 中,如果可能,函数会通过从上下文中推断来分配一个名称。在上面的示例中,名称为 y
。当函数是属性初始值设定项的值时,会执行类似的操作。 (有关何时发生这种情况和规则的详细信息,请在 the specification 中搜索 SetFunctionName
——它到处出现。)
命名函数表达式
第三种形式是命名函数表达式(“NFE”):
var z = function w() {
console.log('zw')
};
此创建的函数具有正确的名称(在本例中为 w
)。与所有表达式一样,在逐步执行代码时会评估 this。函数名不加到表达式出现的作用域;名称 is 在函数本身的范围内:
var z = function w() {
console.log(typeof w); // "function"
};
console.log(typeof w); // "undefined"
请注意,NFE 经常成为 JavaScript 实现的错误来源。例如,IE8 和更早版本处理 NFE completely incorrectly,在两个不同的时间创建两个不同的函数。 Safari 的早期版本也存在问题。好消息是当前版本的浏览器(IE9 及更高版本,当前 Safari)不再存在这些问题。 (但遗憾的是,在撰写本文时,IE8 仍在广泛使用,因此将 NFE 与 Web 代码一起使用通常仍然存在问题。)
访问函数初始化器 (ES5+)
有时功能可能会在很大程度上被忽视;访问器函数就是这种情况。这是一个例子:
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+)
ES2015 为我们带来了箭头函数。这是一个例子:
var a = [1, 2, 3];
var b = a.map(n => n * 2);
console.log(b.join(", ")); // 2, 4, 6
看到隐藏在 map()
调用中的 n => n * 2
东西了吗?那是一个功能。
关于箭头函数的一些事情:
他们没有自己的这个。相反,它们关闭了定义它们的上下文的 this。 (它们还关闭了参数,并且在相关的情况下,超级。)这意味着它们中的 this 与创建它们的 this 相同,并且无法更改。正如您在上面所注意到的,您不使用关键字函数;相反,您使用 =>。
上面的 n => n * 2
示例是其中的一种形式。如果您有多个参数来传递函数,则使用括号:
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));
没有 { ... }
的版本称为带有 表达式主体 或 简洁主体 的箭头函数。 (另外:简洁箭头函数。)带有{ ... }
定义主体的箭头函数是带有函数主体的箭头函数。 (另外:一个 verbose 箭头函数。)
对象初始化器中的方法声明 (ES2015+)
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
的情况下处理它)。
类中的构造函数和方法声明(ES2015+)
ES2015 为我们带来了 class
语法,包括声明的构造函数和方法:
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
getFullName() {
return this.firstName + " " + this.lastName;
}
}
上面有两个函数声明:一个用于构造函数,其名称为 Person
,另一个用于 getFullName
,它是分配给 Person.prototype
的函数。
说到全局上下文,var
语句和末尾的 FunctionDeclaration
都会在全局对象上创建一个 non-deleteable 属性,但两者的值 都可以覆盖。
两种方式之间的细微差别在于,当 Variable Instantiation 进程运行时(在实际代码执行之前),所有用 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
的分配一直持续到运行时。
FunctionDeclaration
创建的全局属性可以像变量值一样被覆盖而不会出现任何问题,例如:
function test () {}
test = null;
您的两个示例之间的另一个明显区别是第一个函数没有名称,但第二个有名称,这在调试时非常有用(即检查调用堆栈)。
关于您编辑的第一个示例 (foo = function() { alert('hello!'); };
),这是一个未声明的分配,我强烈建议您始终使用 var
关键字。
使用赋值,没有 var
语句,如果在作用域链中找不到引用的标识符,它将成为全局对象的 deleteable 属性。
此外,未声明的赋值会在 ECMAScript 5 的 Strict Mode 下抛出 ReferenceError
。
必读:
命名函数表达式揭秘
注意:此答案已从 another question 合并,其中 OP 的主要疑问和误解是使用 FunctionDeclaration
声明的标识符不能被覆盖,但事实并非如此。
对于几乎所有目的,您在此处发布的两个代码片段的行为方式都相同。
但是,行为上的区别在于,对于第一个变体 (var functionOne = function() {}
),该函数只能在代码中的该点之后调用。
对于第二个变体 (function functionTwo()
),该函数可用于在声明该函数的位置之上运行的代码。
这是因为对于第一个变体,函数在运行时被分配给变量 foo
。在第二个中,函数在解析时分配给该标识符 foo
。
更多技术信息
JavaScript 有三种定义函数的方式。
您的第一个片段显示了一个函数表达式。这涉及使用“函数”运算符创建一个函数 - 该运算符的结果可以存储在任何变量或对象属性中。这样,函数表达式就很强大了。函数表达式通常称为“匿名函数”,因为它不必有名称,您的第二个示例是函数声明。这使用“function”语句来创建一个函数。该函数在解析时可用,并且可以在该范围内的任何地方调用。您仍然可以稍后将其存储在变量或对象属性中。定义函数的第三种方法是“Function()”构造函数,您的原始帖子中没有显示。不建议使用它,因为它的工作方式与 eval() 相同,但存在问题。
对 Greg's answer 的更好解释
functionTwo();
function functionTwo() {
}
为什么没有错误?我们总是被告知表达式是从上到下执行的(??)
因为:
函数声明和变量声明总是被 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
}
}
只有在满足 condition
时才能正确定义 myfunction
。
一个重要的原因是添加一个且只有一个变量作为命名空间的“根”...
var MyNamespace = {}
MyNamespace.foo= function() {
}
或者
var MyNamespace = {
foo: function() {
},
...
}
命名空间有很多技术。随着大量可用的 JavaScript 模块的出现,它变得更加重要。
另请参阅 How do I declare a namespace in JavaScript?
Hoisting 是 JavaScript 解释器将所有变量和函数声明移动到当前作用域顶部的操作。
然而,只有实际的声明被提升。通过将作业留在原处。
在页面内声明的变量/函数是全局的,可以在该页面的任何地方访问。
在函数内声明的变量/函数具有本地范围。意味着它们在函数体(范围)内可用/访问,它们在函数体之外不可用。
Javascript 被称为松散类型语言。这意味着 Javascript 变量可以保存任何 Data-Type 的值。 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';
}
在页面内声明的函数被提升到具有全局访问权限的页面顶部。
在功能块内声明的功能被提升到块的顶部。
函数的默认返回值是“未定义”,变量声明默认值也是“未定义”范围相对于功能块全局。未定义页面的范围 |无法使用。
函数声明
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 Constructor Function Classes:使用 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 ? '奇偶'; };控制台.log(fn(2)); // 甚至 console.log( fn(3) ); // 奇怪的
我添加我自己的答案只是因为其他人都已经彻底覆盖了吊装部分。
很长一段时间以来,我一直想知道哪种方式更好,感谢http://jsperf.com,我现在知道了 :)
https://i.stack.imgur.com/bCrSm.png
函数声明更快,这在 Web 开发中真正重要吗? ;)
𝗧𝗵𝗲𝗿𝗲𝗮𝗿𝗲𝗰𝗼𝗺𝗽𝗮𝗿𝗶𝘀𝗼𝗻𝘀𝘁𝗵𝗲𝘁𝘄𝗼𝗱𝗶𝗳𝗳𝗲𝗿𝗲𝗻𝘁𝗼𝗳𝗮𝘀𝗹𝗶𝘀𝘁𝗲𝗱𝗯𝗲𝗹𝗼𝘄𝗯𝗲𝗹𝗼𝘄。
功能的可用性(范围)
以下内容有效,因为 function add()
的范围为最近的块:
尝试 { console.log("成功:", add(1, 1)); } catch(e) { console.log("错误:" + e); } function add(a, b){ return a + b; }
以下内容不起作用,因为在将函数值分配给变量 add
之前调用了该变量。
尝试 { console.log("成功:", add(1, 1)); } catch(e) { console.log("错误:" + e); } var add=function(a, b){ return a + b; }
上面的代码在功能上与下面的代码相同。请注意,显式分配 add = undefined
是多余的,因为仅执行 var add;
与 var add=undefined
完全相同。
变量添加 = 未定义;尝试 { console.log("成功:", add(1, 1)); } catch(e) { console.log("错误:" + e); } add = function(a, b){ return a + b; }
以下行不通,因为 var add=
开始一个表达式并导致后面的 function add()
是一个表达式而不是一个块。命名函数只对它们自己和它们周围的块可见。由于这里的 function add()
是一个表达式,它没有周围的块,所以它只对自己可见。
尝试 { console.log("成功:", add(1, 1)); } catch(e) { console.log("错误:" + e); } var add=function add(a, b){ return a + b; }
(函数).name
以这种方式声明的函数 function thefuncname(){}
的名称是 thefuncname。
函数 foobar(a, b){} console.log(foobar.name);
var a = 函数 foobar(){};控制台.log(a.name);
否则,如果函数声明为 function(){}
,则 function.name 是用于存储函数的第一个变量。
var a = 函数(){}; var b = (function(){ return function(){} });控制台.log(a.name);控制台.log(b.name);
如果没有为函数设置变量,则函数名称为空字符串 (""
)。
console.log((function(){}).name === "");
最后,虽然分配给函数的变量最初设置名称,但设置给函数的后续变量不会更改名称。
var a = 函数(){};变量 b = a;变量 c = b;控制台.log(a.name);控制台.log(b.name);控制台.log(c.name);
表现
在 Google 的 V8 和 Firefox 的 Spidermonkey 中,JIT 编译可能存在几微秒的差异,但最终结果是完全相同的。为了证明这一点,让我们通过比较两个空白代码片段的速度来检验 JSPerf 在微基准上的效率。 JSPerf tests are found here。而且,jsben.ch tests are found here。如您所见,当不存在时,存在明显的差异。如果您真的像我一样是性能狂,那么尝试减少范围内的变量和函数的数量,尤其是消除多态性(例如使用相同的变量来存储两种不同的类型)可能更值得。
可变可变性
当您使用 var
关键字声明变量时,您可以像这样为变量重新分配不同的值。
(function(){ "use strict"; var foobar = function(){}; // 初始值 try { foobar = "Hello World!"; // 新值 console.log("[no error]"); } catch(error) { console.log("ERROR: " + error.message); } console.log(foobar, window.foobar); })();
但是,当我们使用 const 语句时,变量引用变得不可变。这意味着我们不能为变量分配新值。但是请注意,这不会使变量的内容不可变:如果您执行 const arr = []
,那么您仍然可以执行 arr[10] = "example"
。只有执行 arr = "new value"
或 arr = []
之类的操作才会引发错误,如下所示。
(function(){ "use strict"; const foobar = function(){}; // 初始值 try { foobar = "Hello World!"; // 新值 console.log("[no error]"); } catch(error) { console.log("ERROR: " + error.message); } console.log(foobar, window.foobar); })();
有趣的是,如果我们将变量声明为 function funcName(){}
,那么变量的不变性与使用 var
声明它是一样的。
(function(){ "use strict"; function foobar(){}; // 初始值 try { foobar = "Hello World!"; // 新值 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 将简单地返回 "undefined" if (typeof add !== "undefined") { add(1, 1); // 只是为了证明它 console.log("Not a block"); }else if(add===undefined){ // 如果 add 不存在则抛出异常 console.log('Behaves like var add=function(a,b){return a+b}'); } } catch(e) { console.log("是一个块"); } var add=function(a, b){return a + b}
普通函数 add(){}
try { // 如果变量不存在,typeof 将简单地返回 "undefined" if (typeof add !== "undefined") { add(1, 1); // 只是为了证明它 console.log("Not a block"); }else if(add===undefined){ // 如果 add 不存在则抛出异常 console.log('Behaves like var add=function(a,b){return a+b}') } } catch (e) { console.log("是一个块"); } function add(a, b){ return a + b; }
功能
try { // 如果变量不存在,typeof 将简单地返回 "undefined" if (typeof add !== "undefined") { add(1, 1); // 只是为了证明它 console.log("Not a block"); }else if(add===undefined){ // 如果 add 不存在则抛出异常 console.log('Behaves like var add=function(a,b){return a+b}') } } catch (e) { console.log("是一个块"); } (function () { function add(a, b){ return a + b; } })();
语句(如 if、else、for、while、try/catch/finally、switch、do/while、with)
try { // 如果变量不存在,typeof 将简单地返回 "undefined" if (typeof add !== "undefined") { add(1, 1); // 只是为了证明它 console.log("Not a block"); }else if(add===undefined){ // 如果 add 不存在则抛出异常 console.log('Behaves like var add=function(a,b){return a+b}') } } catch (e) { console.log("是一个块"); } { function add(a, b){ return a + b; } }
带有 var add=function() 的箭头函数
try { // 如果变量不存在,typeof 将简单地返回 "undefined" if (typeof add !== "undefined") { add(1, 1); // 只是为了证明它 console.log("Not a block"); }else if(add===undefined){ // 如果 add 不存在则抛出异常 console.log('Behaves like var add=function(a,b){return a+b}') } } catch (e) { console.log("是一个块"); } (() => { var add=function(a, b){ return a + b; } })();
带有函数 add() 的箭头函数
try { // 如果变量不存在,typeof 将简单地返回 "undefined" if (typeof add !== "undefined") { add(1, 1); // 只是为了证明它 console.log("Not a block"); }else if(add===undefined){ // 如果 add 不存在则抛出异常 console.log('Behaves like var add=function(a,b){return a+b}') } } catch (e) { console.log("是一个块"); } (() => { function add(a, b){ return a + b; } })();
建立绑定后,分配给变量的函数声明和函数表达式的行为相同。
然而,函数对象如何以及何时与其变量实际关联是有区别的。这种差异是由于 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
。接下来,为这个变量分配 10
。换句话说:赋值不分配给外部变量 a
。
第一个例子是一个函数声明:
function abc(){}
第二个例子是一个函数表达式:
var abc = function() {};
主要区别在于它们的吊装方式(吊装和申报)。在第一个示例中,提升了整个函数声明。在第二个例子中,只有 var 'abc' 被提升,它的值(函数)将是未定义的,并且函数本身保持在它被声明的位置。
简而言之:
//this will work
abc(param);
function abc(){}
//this would fail
abc(param);
var abc = function() {}
要了解有关此主题的更多信息,我强烈推荐您使用此link
在代码维护成本方面,命名函数更可取:
独立于它们被声明的地方(但仍受范围限制)。
更能抵抗条件初始化等错误(如果需要,您仍然可以覆盖)。
通过将本地函数与作用域功能分开分配,代码变得更具可读性。通常在范围内,功能首先出现,然后是局部函数的声明。
在调试器中,您将清楚地看到调用堆栈上的函数名称,而不是“匿名/评估”函数。
我怀疑命名函数的更多优点如下。命名函数的优点是匿名函数的缺点。
从历史上看,匿名函数的出现是由于 JavaScript 作为一种语言无法列出具有命名函数的成员:
{
member:function() { /* How do I make "this.member" a named function? */
}
}
在计算机科学术语中,我们谈论匿名函数和命名函数。我认为最重要的区别是匿名函数不绑定名称,因此名称为匿名函数。在 JavaScript 中,它是在运行时动态声明的第一类对象。
有关匿名函数和 lambda 演算的更多信息,维基百科是一个好的开始:Anonymous Functions。
我在我的代码中使用变量方法是出于一个非常具体的原因,其理论已经在上面以抽象的方式进行了介绍,但是一个示例可能会对像我这样的 JavaScript 专业知识有限的人有所帮助。
我有需要运行 160 个独立设计的品牌的代码。大多数代码都在共享文件中,但特定于品牌的东西在一个单独的文件中,每个品牌都有一个文件。
有些品牌需要特定的功能,有些则不需要。有时我必须添加新功能来做新的品牌特定的事情。我很高兴更改共享编码,但我不想更改所有 160 组品牌文件。
通过使用变量语法,我可以在共享代码中声明变量(本质上是一个函数指针)并分配一个普通的存根函数,或者设置为 null。
然后,需要特定功能实现的一两个品牌可以定义他们的功能版本,并在需要时将其分配给变量,其余的则什么都不做。在共享代码中执行空函数之前,我可以对其进行测试。
从上面人们的评论中,我认为也可以重新定义静态函数,但我认为变量解决方案很好而且清晰。
Greg's Answer 已经足够好了,但我仍然想在其中添加一些我刚刚在观看 Douglas Crockford's 视频时学到的东西。
函数表达式:
var foo = function foo() {};
功能说明:
function foo() {};
function 语句只是具有 function
值的 var
语句的简写。
所以
function foo() {};
扩展到
var foo = function foo() {};
进一步扩展为:
var foo = undefined;
foo = function foo() {};
它们都被提升到代码的顶部。
https://i.stack.imgur.com/6TVZL.jpg
@EugeneLazutkin 给出了一个示例,其中他 names an assigned function to be able to use shortcut()
作为对自身的内部引用。 John Resig 在他的 Learning Advanced Javascript 教程中给出了另一个示例 - 复制分配给另一个对象的递归函数。虽然在这里为属性分配函数并不是严格意义上的问题,但我建议积极尝试本教程 - 通过单击右上角的按钮运行代码,然后双击代码以根据自己的喜好进行编辑。
教程中的示例:yell()
中的递归调用:
Tests fail when the original ninja object is removed.(第 13 页)
函数断言(谓词,消息){如果(!谓词){抛出新错误(消息); } } var ninja = { 喊:函数(n){ 返回 n > 0 ? ninja.yell(n-1) + "a" : "hiy"; } }; assert( ninja.yell(4) == "hiyaaaa", "单个对象也不错。" ); var 武士 = { 叫喊:ninja.yell }; var忍者=空;试试 { samurai.yell(4); } catch(e){ assert( false, "呃,这不好!ninja.yell 去哪儿了?" ); }
If you name the function that will be called recursively, the tests will pass.(第 14 页)
函数断言(谓词,消息){如果(!谓词){抛出新错误(消息); } } var ninja = { 喊:函数喊(n){ 返回 n > 0 ?叫喊(n-1)+“a”:“嗨”; } }; assert( ninja.yell(4) == "hiyaaaa", "按照我们的预期工作!" ); var 武士 = { 叫喊:ninja.yell };变种忍者 = {}; assert( samurai.yell(4) == "hiyaaaa", "该方法正确地调用了自己。" );控制台.log(武士.yell(4));
其他答案中未提及的另一个区别是,如果您使用匿名函数
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) 将语法定义为函数标识符 (FormalParameterListopt) { 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) 将语法定义为 function Identifieropt ( FormalParameterListopt ) { FunctionBody }
我列出了以下差异:
函数声明可以放在代码中的任何位置。即使在定义出现在代码中之前调用它,它也会在函数声明被提交到内存或以某种方式被提升时执行,然后页面中的任何其他代码开始执行。看看下面的函数: function outerFunction() { function foo() { return 1; } 返回 foo();函数 foo() { 返回 2; } } 警报(外部函数()); // 显示 2 这是因为在执行期间,它看起来像:- function foo() { // 第一个函数声明被移到顶部 return 1; } function foo() { // 第二个函数声明移到顶部 return 2; } 函数外部函数() { 返回 foo(); } 警报(外部函数()); //所以从上到下执行,//最后一个 foo() 返回 2 被显示 一个函数表达式,如果在调用它之前没有定义,会导致错误。此外,这里的函数定义本身并没有像函数声明中那样移动到顶部或提交到内存中。但是我们分配函数的变量被提升并且未定义被分配给它。使用函数表达式的相同函数: function outerFunction() { var foo = function() { return 1; } 返回 foo(); var foo = function() { return 2; } } 警报(外部函数()); // 显示 1 这是因为在执行过程中,它看起来像: function outerFunction() { var foo = undefined; var foo = 未定义; foo = function() { 返回 1; };返回 foo (); foo = function() { // 这个函数表达式不可达 return 2; }; } 警报(外部函数()); // 显示 1 在像 if 这样的非函数块中编写函数声明是不安全的,因为它们不可访问。 if (test) { function x() { doSomething(); } } 命名函数表达式如下所示,可能不适用于 9 版之前的 Internet Explorer 浏览器。 var today = function today() {return new Date()}
如果你使用这些函数来创建对象,你会得到:
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
关于性能:
V8
的新版本引入了一些底层优化,SpiderMonkey
也是如此。
现在表达式和声明之间几乎没有区别。
函数表达式 appears to be faster 现在。
https://i.stack.imgur.com/lW91X.png
https://i.stack.imgur.com/po3gG.png
https://i.stack.imgur.com/lcPvN.png
匿名函数表达式似乎比命名函数表达式具有更好的性能。
https://i.stack.imgur.com/npaAl.png
在 JavaScript 中有两种创建函数的方法:
函数声明:function fn(){ console.log("Hello"); } fn();这是非常基本的,不言自明的,用于许多语言和跨 C 语言家族的标准。我们声明了一个函数来定义它并通过调用它来执行它。你应该知道的是,函数实际上是 JavaScript 中的对象。在内部,我们为上述函数创建了一个对象,并给它一个名为 fn 的名称,或者对该对象的引用存储在 fn 中。函数是 JavaScript 中的对象;函数的实例实际上是一个对象实例。函数表达式: var fn=function(){ console.log("Hello"); } fn(); JavaScript 具有一流的函数,即创建一个函数并将其分配给一个变量,就像您创建一个字符串或数字并将其分配给一个变量一样。这里, fn 变量被分配给一个函数。这个概念的原因是函数是 JavaScript 中的对象; fn 指向上述函数的对象实例。我们已经初始化了一个函数并将其分配给一个变量。它没有执行函数并分配结果。
参考:JavaScript function declaration syntax: var fn = function() {} vs function fn() {}
鉴于“命名函数出现在堆栈跟踪中”的说法,现代 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'));
V8
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
两者都是定义函数的不同方式。不同之处在于浏览器如何解释它们并将它们加载到执行上下文中。
第一种情况是函数表达式,仅在解释器到达该行代码时才加载。所以如果你像下面这样做,你会得到一个错误,即 functionOne 不是一个函数。
functionOne();
var functionOne = function() {
// Some code
};
原因是第一行没有给 functionOne 赋值,因此它是未定义的。我们试图将它作为一个函数来调用,因此我们得到了一个错误。
在第二行,我们将匿名函数的引用分配给 functionOne。
第二种情况是在执行任何代码之前加载的函数声明。因此,如果您执行以下操作,则在代码执行之前加载声明时不会出现任何错误。
functionOne();
function functionOne() {
// Some code
}
命名函数与。匿名函数
第一个函数语法是匿名函数表达式:
var functionOne = function() {
// do something...
};
而第二个是函数声明:
function functionTwo () {
// do something...
}
两者之间的主要区别在于函数名称,因为匿名函数没有可调用的名称。匿名函数声明起来既快速又容易,许多库和工具都倾向于鼓励这种惯用的代码风格。但是,匿名函数有一些缺点:
可读性:匿名函数省略可能导致代码可读性降低的名称。
调试:匿名函数在堆栈跟踪中没有名称,这会使调试更加困难。
自引用:如果函数需要引用自身,例如递归,该怎么办。
命名函数表达式
为您的函数表达式提供一个名称非常有效地解决了所有这些缺点,并且没有明显的缺点。最佳做法是始终命名您的函数表达式:
setTimeout(function timeHandler() { // <-- look, a name here!
console.log("I've waited 1 second");
}, 1000);
命名 IIFE(立即调用函数表达式)
(function IIFE(str) { // <-- look, always name IIFEs!
console.log(str); // "Hello!"
})('Hello!');
对于分配给变量的函数,在这种情况下命名函数不是很常见并且可能会引起混淆,在这种情况下,箭头函数可能是更好的选择。
它们非常相似,但有一些小的区别,第一个是分配给匿名函数的变量(函数声明),第二个是在 JavaScript 中创建函数的常规方法(匿名函数声明),两者都有用途,优缺点:
1. 函数表达式
var functionOne = function() {
// Some code
};
函数表达式将函数定义为更大的表达式语法(通常是变量赋值)的一部分。通过函数表达式定义的函数可以命名或匿名。函数表达式不能以“function”开头(因此下面的自调用示例周围有括号)。
将变量分配给函数,意味着没有Hoisting,因为我们知道JavaScript中的函数可以Hoist,意味着它们可以在声明之前被调用,而变量需要在访问它们之前声明,所以在这种情况下我们不能在声明函数之前访问函数,也可以是编写函数的一种方式,对于返回另一个函数的函数,这种声明可能有意义,同样在 ECMA6 及更高版本中,您可以将其分配给箭头函数可用于调用匿名函数,这种声明方式也是在 JavaScript 中创建构造函数的更好方法。
2.函数声明
function functionTwo() {
// Some code
}
函数声明定义了一个命名函数变量,而不需要变量赋值。函数声明作为独立结构出现,不能嵌套在非功能块中。将它们视为变量声明的兄弟姐妹会很有帮助。正如变量声明必须以“var”开头一样,函数声明必须以“function”开头。
这是在 JavaScript 中调用函数的正常方式,甚至可以在声明它之前调用这个函数,因为在 JavaScript 中所有函数都会被提升,但是如果你有 'use strict' 这不会像预期的那样提升,这是一个好方法调用所有行数不大的普通函数,也不是构造函数。
此外,如果您需要有关提升在 JavaScript 中如何工作的更多信息,请访问以下链接:
https://developer.mozilla.org/en-US/docs/Glossary/Hoisting
这只是声明函数的两种可能方式,第二种方式可以在声明之前使用函数。
let
或const
来定义一个被其中的函数封闭的变量,那将是不可避免的,并且始终如一地应用该规则可能会更好而不是仅在不可避免的情况下才应用它。var functionOne
和function functionTwo
都在某种程度上被提升了 - 只是 functionOne 设置为未定义(你可以称之为半提升,变量总是只提升到那个程度)而函数 functionTwo 完全提升它的定义和声明。调用未定义的东西当然会抛出 typeError。let functionFour = function () {...}
时,var
的情况也有细微的变化。在这种情况下,let functionFour
的声明被提升。但它没有被初始化,即使是undefined
值。所以它会产生一个稍微不同的错误:Uncaught ReferenceError: Cannot access 'functionFour' before initialization 这同样适用于const
。