函数声明与函数表达式

定义一个函数的方法主要有三种 函数声明、函数表达式、new Function构造函数,函数声明与函数表达式定义的函数较为常用,构造函数的方式可以将字符串定义为函数。

函数声明

函数声明会将声明与赋值都提前,也就是整个函数体都会被提升到作用域顶部。

s(); // 1
function s() {
  console.log(1);
}

也就是说,在某个作用域中定义的函数可以在这个作用域的任意位置调用,整个函数体都将被提前,当然也有不一样的情况:

console.log(s); // undefined // 函数的声明提升但是并未赋值函数体
console.log(ss); // Uncaught ReferenceError: ss is not defined // 打印未定义的ss是为了对比说明函数的声明提升
s(); // Uncaught TypeError: s is not a function
if (1) {
  function s() {
    console.log(1);
  }
}

此处可以看到函数的声明被提升,但是函数体并未被提升,JS只有函数作用域与全局作用域以及ES6引入的letconst块级作用域,此处if不存在作用域的问题,为同一作用域,但是由于解释器在预处理期无法确定if里面的内容,所以只对函数声明的变量进行了提升,而并未将函数体赋值。

函数表达式

函数表达式只会提升变量的声明,本质上是变量提升并将一个匿名函数对象赋值给变量。

console.log(s); // undefined
var s = function s() {
  console.log(1);
};
console.log(s); // f s(){console.log(1);}

由此来看,直接进行函数声明与函数表达式声明的函数之间就存在一个优先级关系。

var s = function () {
  console.log(0);
};
function s() {
  console.log(1);
}
s(); // 0

JS中函数是第一等公民,在《你不知道的JavaScript》(上卷)一书的第40页中写到,函数会首先被提升,然后才是变量。也就是说,同一作用域下提升,函数会在更前面。即在JS引擎的执行的优先级是函数声明、变量声明、变量赋值。

function s() {
  //函数声明
  console.log(1);
}

var s; // 变量声明

// 函数已声明`a` 相同的变量名`var`声明会被直接忽略
console.log(s); // f s(){...}

s = function () {
  // 变量赋值
  console.log(0);
};

s(); // 0

new Function

new Function的方式可以将字符串解析为函数。

var s = new Function("a", "b", "console.log(a,b);");
s(1, 2); // 1,2
console.log(s.__proto__ === Function.prototype); //true
function ss() {} // 声明的函数都是Function的一个实例
console.log(ss.constructor === Function); // true
console.log(ss.__proto__ === Function.prototype); // true

new Functioneval也有所不同,其对解析内容的运行环境的判定有所不同。

// eval中的代码执行时的作用域为当前作用域,它可以访问到函数中的局部变量,并沿着作用域链向上查找。
// new Function中的代码执行时的作用域为全局作用域,无法访问函数内的局部变量。
var a = "Global Scope";
(function b() {
  var a = "Local Scope";
  eval("console.log(a)"); //Local Scope
  new Function("console.log(a)")(); //Global Scope
})();
贡献者: huxiguo