本文深入解析JavaScript中函数声明与函数表达式的区别。重点阐述两者在提升机制、调用时机及作用域上的差异:函数声明在代码执行前即可调用,而函数表达式需定义后调用。同时探讨命名函数表达式在内部调用的局限性,以及条件语句中声明函数的浏览器行为差异,帮助开发者避免潜在Bug。
JavaScript函数声明与函数表达式的核心区别解析
在JavaScript中,定义函数主要有两种语法形式:函数声明式和函数表达式。
这两种方式在语义和行为上存在显著差异,具体表现如下。
第一,函数声明要求必须指定函数名,而函数表达式的函数名则是可选的。
第二,函数声明具有提升特性,允许在声明语句之前调用该函数;相比之下,函数表达式定义的函数必须在声明语句之后才能被调用。
第三,函数声明并非真正的“声明”,它们只能出现在全局作用域或嵌套在其他函数内部。若将函数声明置于循环、条件判断或try/catch/finally块中,其行为在不同浏览器中可能不一致。
函数表达式则没有上述限制,可以在代码的任何合法位置进行声明。
接下来,我们将通过具体代码示例分别展示这两种方法的定义方式。
//函数声明式
function greeting(){
console.log("hello world");
}
//函数表达式
var greeting = function(){
console.log("hello world");
}
下面来看一个具有代表性的JavaScript案例。
function f() { console.log('I am outside!'); }
(function () {
if(false) {
// 重复声明一次函数f
function f() { console.log('I am inside!'); }
}
f();
}());
这段代码会输出什么?直觉上可能认为结果是“I am outside”。然而实际运行结果因浏览器而异:Chrome输出“I am inside”,IE11直接报错,而旧版Firefox则输出“I am outside”。
Chrome的输出结果清晰地反映了函数声明式的核心特性:函数在声明之前即可被调用。
IE浏览器报错“缺少对象”,是因为函数声明被放置在了条件语句内部,这违背了函数声明的标准规范。
关于函数表达式的作用域,如果为函数表达式指定了函数名,该名称将作为函数内部的一个局部变量,仅在函数内部可见并可调用。
举例说明:
var f = function fact(x) {
if (x <= 1)
return 1;
else
return x*fact(x-1);
};
alert(fact()); // Uncaught ReferenceError: fact is not defined
在函数内部,可以通过该名称调用自身;但在函数外部尝试调用时,会抛出“fact未定义”的错误。
函数声明与函数表达式的深入对比
接下来我们深入分析
函数声明
函数声明示例代码
具体代码如下:
function fn () {
console.log('fn 函数执行..');
// code..
}
上述代码声明了一个名为fn的函数。此处引发一个思考:如果在声明该函数的代码行之前尝试调用它,程序会正常执行还是报错?
测试代码如下:fn(); // 在声明前调用我们定义的fn函数 function fn () { console.log('fn 函数执行..'); // code..}
控制台输出结果如下:

可以看到,此时fn函数是可以被成功调用的。以下总结其原因。
总结:
1. 此时fn函数是变量提升的结果,默认存储在全局上下文的变量对象中(可通过 window.函数名 进行验证)。
2. 此方式属于函数声明,在进入全局上下文阶段时创建,在代码执行阶段它们已经可用。注:JavaScript在每次进入方法时都会先初始化上下文环境(由全局 → 局部)。
3. 它可以影响变量对象(仅影响存储在上下文中的变量)。
函数表达式
函数表达式示例代码
具体代码如下:
var fn = function () {
console.log('fn 函数【表达式】声明执行..')
// code..
}
上述代码声明了一个匿名函数,并将其引用赋值给变量fn。
接下来,我们在该表达式声明的函数上方和下方各调用一次,观察控制台的输出结果。
测试代码如下:
// 为了清晰的看到控制台的输出,我们在各自调用前后做个标记,增加可读性。
console.log('之前调用开始..');
fn();
console.log('之前调用结束..');
var fn = function () {
console.log('fn 函数【表达式】声明执行..')
// code..
}
console.log('之后调用开始..');
fn();
console.log('之后调用开始..');
控制台打印结果如下:

可以看到,当代码执行到第一次调用fn()函数时,提示“fn is not a function”(fn不是一个方法),随后程序因错误而终止运行。
这说明在第一次调用fn()的同时,var fn变量尚未作为全局对象的一个属性存在,且fn引用的匿名函数上下文也未被初始化,因此在此处调用会失败。
测试代码如下:
// 现在先把之前的调用逻辑给注释掉,再看下控制台的输出
// console.log('之前调用开始..');
// fn();
// console.log('之前调用结束..');
var fn = function () {
console.log('fn 函数【表达式】声明执行..')
// code..
}
console.log('之后调用开始..');
fn(); // 在表达式之后调用
console.log('之后调用开始..');
控制台打印结果如下:

可以看出,在该表达式函数定义之后进行调用是可以成功的。以下总结其原因。
总结:
1. 首先,变量本身不是一个函数对象,而是一个指向匿名函数的引用(值类型,不属于引用类型)。
2. 在代码执行阶段初始化全局上下文时,它并未作为全局对象的一个属性存在,因此不会造成变量对象的污染。
3. 这种声明方式在插件开发中比较常见,也可作为闭包中回调函数的调用形式。
因此,function fn () {} 并不等于 var fn = function () {},两者存在本质区别。
以上就是本文的全部内容了,思路很清晰,对比也很明确,是篇非常不错的文章,小伙伴们一定要仔细研读下