Skip to main content

04 函数表达式

函数声明与函数表达式的差异

foo();
function foo() {
console.log("foo");
}

// foo 函数被正确执行
foo()
var foo = function (){
console.log('foo')
}

// 变量 foo 并不是一个函数,无法被调用
VM130:1 Uncaught TypeError: foo is not a function
at <anonymous>:1:1

这两种定义函数的方式具有不同语义,不同的语义触发了不同的行为。第一种称之为函数声明,第二种称之为函数表达式。

V8 是怎么处理函数声明的

函数声明定义了一个具有指定参数的函数:

function name([param,[, param,[..., param]]]) {
[statements]
}

V8 在执行 JavaScript 的过程中,会先对其进行编译,然后再执行:

var x = 5;
function foo() {
console.log("Foo");
}

在编译阶段,如果解析到函数声明,那么 V8 会将这个函数声明转换为内存中的函数对象,并将其放到作用域中。同样,如果解析到了某个变量声明,也会将其放到作用域中,但是会将其值设置为 undefined,表示该变量还未被使用。

在 V8 执行阶段,如果使用了某个变量,或者调用了某个函数,V8 便会去作用域查找相关内容。

可以使用 D8 来查看作用域的数据,将这段代码保存到 test.js 中,使用 d8 --print-scopes test.js 命令即可查看作用域的状态:

Global scope:
global { // (0x7fb62281ca48) (0, 50)
// will be compiled
// 1 stack slots
// temporary vars:
TEMPORARY .result; // (0x7fb62281cfe8) local[0]
// local vars:
VAR x; // (0x7fb62281cc98)
VAR foo; // (0x7fb62281cf40)


function foo () { // (0x7fb62281cd50) (22, 50)
// lazily parsed
// 2 heap slots
}
}

作用域中包含了变量 x 和 foo,变量 x 的默认值是 undefined,变量 foo 指向了 foo 函数对象,foo 函数对象被 V8 存放在内存中的堆空间了,这些变量都是在编译阶段被装进作用域中的。

在执行之前,这些变量都被提升到作用域中了,所以在执行阶段,V8 当然就能获取到所有的定义变量了。把这种在编译阶段,将所有的变量提升到作用域的过程称为变量提升。

  • 普通变量,变量提升之后的值都是 undefined,
  • 声明的函数,变量提升之后的值都是函数对象

表达式就是表示值的式子,执行这段代码会返回一个值:

x = 5;

6 === 5;

语句是操作值的式子,是一个语句,执行该语句时 V8 并不会返回一个值。

var x;

function foo() {
return 1;
}
// 执行到这段代码时,V8 并没有返回任何的值,它只是解析 foo 函数,并将函数对象存储到内存中。

在 V8 执行var x = 5这段代码时,会认为它是两段代码,一段是定义变量的语句,一段是赋值的表达式,

var x = undefined; // 语句,在编译阶段完成,即提升变量阶段
x = 5; // 表达式,在执行阶段完成

函数声明不输出任何内容,因此是一个语句,V8 会对函数声明执行变量提升。函数也是一个对象,所以在编译阶段,V8 就会将整个函数对象提升到作用域中,并不是给该函数名称赋一个 undefined。

V8 是怎么处理函数表达式的

在一个表达式中使用 function 来定义一个函数,那么就把该函数称为函数表达式。

foo = function () {
console.log("foo");
};

分析函数表达式:

foo();
var foo = function () {
console.log("foo");
};

var foo = undefined;

foo = function () {
console.log("foo");
};

立即调用的函数表达式(IIFE)

(function () {
//statements
});

小括号之间存放的必须是表达式,所以如果在小阔号里面定义一个函数,那么 V8 就会把这个函数看成是函数表达式,执行时它会返回一个函数对象。

直接在表达式后面加上调用的括号,这就称为立即调用函数表达式(IIFE):

(function () {
//statements
})();

函数立即表达式也是一个表达式,V8 在编译阶段,并不会为该表达式创建函数对象。好处是不会污染环境,函数和函数内部的变量都不会被其他部分的代码访问到。