JS函数提升以及修饰器不能用于函数的原因

在阮一峰老师的ECMAScript6入门中提到修饰器(Decorator)不能用于函数,里面提到原因是因为存在函数提升,这里只有简单几句话带过,如果熟悉JS的函数提升,就会好理解一些。

首先看看什么是函数提升。

简单的讲,函数提升指的是JavaScript编译器(解释器)将会把代码中所有的函数定义“提升”到代码的顶部,因此,可以“先”调用函数,“再”声明函数,而不是像传统的C++等语言,函数的使用必须在声明之后,通常会需要把函数声明都写在头文件中。而对于普通的变量,也是如此,var完全可以在后面的代码里面再补上

如果我们直接运行下面这一句:

console.log(foo);    // ReferenceError

运行后将会抛出异常,ReferenceError: foo is not defined

但如果在后面补上变量的定义,则会正常运行,并输出undefined

console.log(foo);    // outputs: undefined
var foo;

其实解释器对其进行预处理,找出所有变量定义,并放在了前面,相当于如下效果:

var foo;
console.log(foo);

对于函数,也是可以正常运行

foo();  // Outputs: bar

function foo() {
    console.log('bar');
}

不过需要注意的是,函数表达式不适用

foo();  // Outputs: bar

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

因为这其实是一种“赋值”,将一个匿名函数赋值给变量foo(类似函数指针),而不是变量或函数声明。

下面再来说为什么修饰器不能用于函数。

原文的例子如下:

var counter = 0;

var add = function () {
  counter++;
};

@add
function foo() {
}

这看起来将会是先执行counter=0, 然后执行add函数,counter=1

但是其中包含三个变量定义,分别是counter,add和foo,都提升到前面后变成:

@add
function foo() {
}

var counter;
var add;

counter = 0;

add = function () {
  counter++;
};

可以看到,counter=0变成在最后执行,于是counter最终为0。

因为别忘了,修饰器是在代码编译时发生的,而不是在运行时,因此add函数其实在前面就执行了。

而类不存在这样的问题,因为class定义不会提升

var foo = new Foo();   // Uncaught ReferenceError: Foo is not defined

class Foo {
    constructor () {
        console.log('bar')
    }
}