前言
近期,笔者阅读了公司新制定的前端代码规范,发现其中有些内容适用范围在 ES6 出现之前,对于当下的 JS 开发环境来说,有些明显不适应了,比如关于在函数体定义时,到底应该优先使用函数声明还是函数表达式呢?
![](https://i-blog.csdnimg.cn/blog_migrate/5916b8de91f415c5cb19a55c2a72ecda.png)
首先我们先明确在 JS 中,什么是函数声明,什么是函数表达式。
JS 中,函数声明和函数表达式都是用来定义函数的。函数声明是独立的语法结构,是单独存在的。但是函数表达式是作为表达式的一部分存在的。
- 函数声明定义了一个具名的且不需要赋值的“函数变量”,它是独立的语法,只能用在全局和函数体内。类似于变量的声明,就像变量声明必须用
var/let/const
开头一样,函数声明也必须用function
开头。- 函数表达式将函数定义为一个表达式的一部分(通常是赋值表达式)。函数表达式可以不命名,分为匿名函数表达式和具名函数表达式。
PS:表达式是由运算符和操作符构成,并产生运算结果的语法结构。也就是说表达式是一个单纯的运算过程,并且总是有返回值。
函数声明
function func() {
const a = 1
const b = 2
return a + b
}
函数表达式
// 不使用箭头函数
const funcA = function () {
const a = 1
const b = 2
return a + b
}
// 使用箭头函数
const funcB = () => {
const a = 1
const b = 2
return a + b
}
关于上图代码规范中对于函数体定义方法的描述,笔者认为存在讨论空间
代码规范建议使用函数声明代替函数表达式。
理由1:“因为函数声明是可命名的,所以他们在调用栈中更容易被识别”,这一点笔者暂时无法理解,因为函数表达式同样可以命名,至于在调用栈中是否更容易识别,以现代JS解析执行引擎谷歌V8来说,应该是影响很小的。
理由2:“此外,函数声明会把整个函数提升(hoisted),而函数表达式只会把函数的引用变量名提升。”在现代的JS开发中,由于ESM模块化的日渐普及规范,函数声明将整个函数提升和函数表达式只把函数引用提升对一般的日常开发来说影响极小,首先在同一个JS模块中,我们在对函数或者一般的变量命名时,是绝不会重现变量命名重名的情况的,其次,在常规代码理解上,我们会认为一个函数,只有函数体完全声明完毕,才能够被调用,若函数体还未声明,为何要提前调用函数呢?
此外,“函数表达式只会把函数的引用变量提升”的问题出现在ES6之前,当时,变量命名关键字let 和 const 等还未出现,函数表达式因为使用了 var 关键字进行函数声明,而var本身存在变量提升的问题才导致所谓“函数表达式只会把函数的引用变量提升”的问题,实际上,在ES6之后,JS开发人员在编写JS代码时,已普遍弃用了var关键字来对变量进行定义,如今基本使用 let/const 关键字替换。如下图:
这样现象在js中也叫“暂时性死区”。
总结
笔者认为在JS函数声明方面,可不刻意推荐函数声明方式,一定要推荐的话,更加推荐使用了ES6+ 语法的函数表达式代替函数声明。在函数体不涉及一些 this 关键字调用时,更推荐使用箭头函数的函数表达式。如下:
更加详细深入的解释
在 JavaScript 中,函数是第一等对象,之所以这样说,是因为它不仅可以像对象一样拥有属性和方法,而且可普通对象不一样的是它还可以被调用执行。函数表达式能更直观地看出是创建了一个对象。
函数表达式的使用范围更广。函数声明语句并非真正的语句,JS 规范只是允许它们作为顶级语句。它们可以出现在全局代码中,或者内嵌在其他函数中,但它们不能出现在循环、条件判断,或者 try/cache/finally 以及 with 语句中。相比之下,函数表达式是更大意义上表达式的一部分。你可以创建匿名函数用作回调函数,作为其它对象的属性(这里只指定义函数,函数声明的函数名也可以这样用),用立即执行函数创建私有作用域等等,它们可以出现在代码的任何地方。
函数表达式不会出现函数提升,放在哪里就在哪里执行,不会像函数声明那样随意放置具有误导性。
函数声明不能放在 if 条件句中,在一些浏览器中无论条件为 false 还是 true,函数都会被定义。
但是匿名函数表达式存在一些问题:
可读性,可理解性较差,有时候一个描述性的名称可以让你的代码不言自明。
匿名函数在栈追踪中不会显示出有意义的名称,使得调试很困难,虽然可以用具名函数表达式,但是赋值具名函数表达式在 IE9 以下又无法正确执行(浏览器对命名函数赋值表达式进行错误的解析, 解析成两个函数,一个变量名,一个函数名)。
如果没有函数名,函数在调用自身的时候就只能用 过期 的 arguments.callee 。函数调用自身的例子有递归以及事件触发后事件监听器需要解绑自身。
因此,除了赋值命名函数表达式外,对函数表达式命名是很有必要的。
总结:大部分情况下,用函数表达式代替函数声明都是很好的实践,但是有时用函数声明定义函数也是很有必要的,比如要用到多次的函数。所以还是要根据具体情况决定到底用哪种方式去定义函数。
![](https://i-blog.csdnimg.cn/blog_migrate/d25d5b9b8c379b7a6441f8224f7b126e.png)
参考: