Function 知识储备

436 阅读5分钟

Function 的使用

// Function
const fun1 = new Function('a', 'b', 'c', 'console.log(a + b + c)');
const fun2 = new Function('a, b, c', 'console.log(a + b + c)');
fun1(1, 2, 3);
fun2(4, 5, 6);

特点

我们先从一个案例题目来看看,Function 的一个特点

var a = 1,
  b = 2;
  
function test() {
  var b = 3;

  return new Function('c', 'console.log(a + b + c)');
}

var t = test();
t(4); // 7

从这个案例中,可以知道,最后 console.log 输出的值为:7。

一般来说,对于这个题目,有时候会傻傻分不清,输出的是7、还是8。

这里就涉及到一个 Function 的特点,Function 声明的函数,是全局作用域下的。针对于上题中的 new Function,其实并不是声明在局部的 test 函数作用域下的,而是在全局作用域下的,所以对于 b 的取值,取的是全局作用域下的 2。

关于这点,可以查看 MDN Web 文档中 Function 介绍,它里面有这一段描述:

Difference between Function constructor and function declaration

Functions created with the Function constructor do not create closures to their creation contexts; they always are created in the global scope. When running them, they will only be able to access their own local variables and global ones, not the ones from the scope in which the Function constructor was created. This is different from using eval with code for a function expression.

Function 构造器与函数声明之间的不同

由 Function 构造器创建的函数不会创建当前环境的闭包,它们总是被创建于全局环境,因此在运行时它们只能访问全局变量和自己的局部变量,不能访问它们被 Function 构造器创建时所在的作用域的变量。这一点与使用 eval 执行创建函数的代码不同。

官方案例:

var x = 10;

function createFunction1() {
    var x = 20;
    return new Function('return x;'); // 这里的 x 指向最上面全局作用域内的 x
}

function createFunction2() {
    var x = 20;
    function f() {
        return x; // 这里的 x 指向上方本地作用域内的 x
    }
    return f;
}

var f1 = createFunction1();
console.log(f1());          // 10
var f2 = createFunction2();
console.log(f2());          // 20

注意点:

  1. new Function()Function 其实是一样的,是一个声明 + 定义一个函数,更重要的是不要看 new Function() 有一个 new,其实它并没有返回一个对象,却返回了一个函数;
  2. 例题中的 t(4)f1() 如果是在 Node.js 环境下执行的话,那会报错(找不到变量 a 或者 x 的 ReferenceError 错误),这是因为在 Node 环境下定义的 var xvar b 是在当前 Module 作用域下的,而不是全局作用于 global 这个对象上的。

延伸

eval

阅读 MDN Web 文档后,文档中有说到由 Function 构造器创建的函数和使用 eval 执行创建函数是不同的,这里同样采用上面案例题目进行测试:

var a = 1,
  b = 2;
  
function test() {
  var b = 3;

  // return new Function('c', 'console.log(a + b + c)');
  eval('!function _ (c) { console.log(a + b + c) }(4)') // 输出 8
}

var t = test();
// t(4); // 7

在函数作用域中使用 eval 在函数声明的话,是存在于函数作用域中的,并且会形成闭包,由此这里可以验证 MDN 所描述的:由 Function 构造器创建的函数和使用 eval 执行创建函数是不同的。

匿名函数自执行

从上面延伸知识点的 eval 节可以看到,匿名函数自执行的时候,在前面加了一个 !,这是什么意思呢?

其实这是告诉解析器,这是一段函数表达式,并且也只是匿名函数自执行的其中一个方法。

+function () { console.log("self 1"); }();

!function () { console.log("self 2"); }();

(function() { console.log("self 3"); })();

(function() { console.log("self 4"); }());

[function() { console.log("self 5"); }()];

以上几种方法都是匿名函数自执行的方法。

考题学知识

以下是一道面试题,能够全面认识到 function 的相关知识

// func1
function Foo() {
  getName = function () {
    console.log(1);
  }
  console.log(this);
  return this;
}

// func2
Foo.getName = function () {
  console.log(2);
}

// func3
Foo.prototype.getName = function () {
  console.log(3)
}

// func4
var getName = function () {
  console.log(4);
}

// func5
function getName() {
  console.log(5)
}

// print 1
Foo.getName() // 2

// print 2
getName() // 4

// print 3
Foo().getName() // 1

// print 4
getName() // 1

// print 5
new Foo.getName() // 2

// print 6
new Foo().getName() // 3

print 7
new new Foo().getName() // 3

// print 8
new new Foo() // error,Uncaught TypeError: (intermediate value) is not a constructor

看到上面执行的输出是不是有一些感到困惑,现在来分析一下,整个流程是怎么跑的。

我们先来看看所有函数的声明和定义:

  1. 首先代码在执行的时候会进行一个预编译处理,而这个预编译的过程,按照从上到下声明一个 Foo 函数,也就是 func1,接下来会声明 getName,也就是 func5

  2. 预编译完成后,游览器执行这段代码,func2 会给 Foo 定义一个静态属性,func3 会给 Foo 定义一个原型属性,func4 执行的时候,会对全局 window 属性进行重新赋值,也就是上面第一点中的 getName 在这个时候会进行重新赋值

到此函数的声明和定义就都完成了,在这里可以发现,func5 已经被覆盖掉了,将不会有进行调用它的机会了,下面我们来看看输出结果:

// 这里执行的就是 Foo 的静态方法 fun2
Foo.getName() // 2

// 这里执行的是 func5,这里的 getName 的演变从 func5 -> func4
getName() // 4

// 这里首先执行的是 Foo,Foo 执行的时候,内部的 this 为全局 winsow 对象
// Foo 中会进行 getName 重新赋值,所以 getName 的演变从 func5 -> func4 -> func1 中的 getName
// 所以 Foo() 执行后返回 this -> window 然后执行 func1 中的 getName
Foo().getName() // 1

// 这里就是执行全局的 getName,他的演变过程为 func5 -> func4 -> func1 中的 getName
getName() // 1

// 这里就是执行 Foo 的静态属性 func2,只不过这个 func2 作为 new 的构造函数了一下
new Foo.getName() // 2

// 首先 Foo 作为构造函数 new 了一个实例,这时候 Foo 返回的 this 指的就是这个 new 出来的实例,然后使用该实例 this 调用 getName 就会执行 func3 的原型方法
new Foo().getName() // 3

//  Foo 作为构造函数 new 了一个实例, 然后返回的实例 this 执行了原型方法 getName 并且作为第一个 new 的构造函数
new new Foo().getName() // 3

// Foo 作为构造函数 new 了一个实例,然后这个返回的的实例对象作为第一个 new 的构造函数,这时候就会报错了,说了 new 的时候这个中间值不是一个构造函数
new new Foo() // error,Uncaught TypeError: (intermediate value) is not a constructor