跳到主要内容

声明

2023年11月13日
柏拉文
越努力,越幸运

一、声明式


V8 编译执行声明式函数的过程 在编译阶段,如果解析到了某个函数声明,那么将其函数名放到作用域中,将其值设为函数的引用地址。在执行阶段,如果调用了某个函数,就可以直接去作用域中去查找。

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

二、赋值表达式


V8 编译执行赋值表达式函数的过程 如果解析到了某个变量声明,也会将其放到作用域中,但是会将其值设置为 undefined,表示该变量还未被使用**。在执行阶段,如果使用了某个变量,就可以直接去作用域中去查找。

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

三、立即表达式


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

(function (){console.log('哈哈');})()

四、构造函数式


new Function() 创建了一个新的 Function 对象。直接调用构造函数可以动态创建函数,但可能会经受一些安全和类似于 eval()(但远不重要)的性能问题。然而,不像 eval(可能访问到本地作用域),Function 构造函数只创建全局执行的函数。

4.1 语法

new Function(functionBody)
new Function(arg0, functionBody)
new Function(arg0, arg1, functionBody)
new Function(arg0, arg1, /* … ,*/ argN, functionBody)

4.2 用法

4.3 场景

场景一、动态生成 render 函数

const functionArgu0 = `x`;
const functionArgu1 = `y`
const functionBody = `
return function render(z){
const result = x + y + z;
return "_x + _y + z 的值为:" + result;
}
`
const renderFn = new Function(functionArgu0,functionArgu1,functionBody);
const render = renderFn(1,2);
console.log(render(3));

场景二、动态生成 createElement 函数

import Babel from '@babel/core';
import React from '../../react/index.js';

const template = `<div className="home-page"> Home Page </div>`;
const result = Babel.transform(template, {
plugins: [['@babel/plugin-transform-react-jsx', { runtime: 'classic' }]]
});

const createElementStr = result.code;
const createElement = new Function('React', `return ${createElementStr}`);

4.4 作用域

new Function 无论在哪块调用 new Function(), 创建的函数始终被创建到了全局环境。 因此, 通过new Function() 创建的函数在运行时它们只能访问全局作用域中的变量和函数自己的局部变量, 不能访问它们被 new Function() 构造函数创建时所在的作用域的变量

new Function() VS eval(): 它们都可以动态解析和执行字符串,但是它们对解析内容的运行环境判定不同。

  • eval(): eval 中的代码执行时的作用域为当前作用域, 如果找不到, 在从作用链中的上一层寻找。eval 是一个危险的函数, 它使用与调用者相同的权限执行代码。如果你用 eval() 运行的字符串代码被恶意方(不怀好意的人)修改,您最终可能会在您的网页/扩展程序的权限下,在用户计算机上运行恶意代码。更重要的是,第三方代码可以看到某一个 eval() 被调用时的作用域,这也有可能导致一些不同方式的攻击。eval() 通常比其他替代方法更慢,因为它必须调用 JS 解释器,而许多其他结构则可被现代 JS 引擎进行优化。现代 JavaScript 解释器将 JavaScript 转换为机器代码。这意味着任何变量命名的概念都会被删除。因此,任意一个 eval 的使用都会强制浏览器进行冗长的变量名称查找,以确定变量在机器代码中的位置并设置其值。另外,新内容将会通过 eval() 引进给变量,比如更改该变量的类型,因此会强制浏览器重新执行所有已经生成的机器代码以进行补偿。

    var x = 'gloabl scope';

    function foo() {
    var x = 'local scope';
    eval('console.log(x)'); // local scope
    }

    foo();
  • new Function(): new Function() 中的代码执行时的作用域为全局作用域。无论在哪个地方调用, 它访问的是全局变量。 new Function() 要比 eval() 函数的执行速度快得多, new Function() 不会强制浏览器重新执行所有已经生成的机器代码进行补偿, 也不会强制浏览器进行冗长的变量名查找。

    var x = 'gloabl scope';

    function foo() {
    var x = 'local scope';
    new Function('console.log(x)')(); // global scope
    }

    foo();

    注意: 虽然这段代码可以在浏览器中正常运行,但在 Node.jsfoo() 会产生一个“找不到变量 xReferenceError。这是因为在 Node 中顶级作用域不是全局作用域,而 x 其实是在当前模块的作用域之中。