likes
comments
collection
share

六种动态运行js字符串的方案对比先有问题再有答案 动态运行js字符串的方案有哪些? 这些方案在运行环境,代码来源,访问权

作者站长头像
站长
· 阅读数 24

先有问题再有答案

  1. 动态运行js字符串的方案有哪些?
  2. 这些方案在运行环境,代码来源,访问权限,执行时机上有什么差异?
  3. 为什么我们需要动态执行js字符串?
  4. 为什么大家都不建议使用动态运行js字符串的方案?
  5. 如果必须要选择一种方案 我们应该推荐哪种方案?

eval 函数(不建议)

<script>
    var outerName = 'outer'
    function test() {
        let localName = 'local' // 可以访问局部变量
        const jsString = "var innerName = 'inner'; " + "  console.log('这是一段动态执行的js字符串', outerName, innerName, localName) ";
        eval(jsString)
        console.log('外部打印 innerName:', innerName)
    }
    test();
</script>

六种动态运行js字符串的方案对比先有问题再有答案 动态运行js字符串的方案有哪些? 这些方案在运行环境,代码来源,访问权

  • 运行环境:当前作用域,eval()函数会在其所在的函数作用域去执行代码,而不是创建一个新的作用域。所以,如果eval()函数在函数内部调用时可以访问函数的局部变量,而且还会污染全局对象。
  • 代码来源:只接受字符串,由开发者预先定义
  • 访问权限:可以访问或修改当前作用域中的任何对象
  • 同步/异步:同步执行
  • 安全性和性能:安全性差,可能被用于执行恶意代码;性能相较于其它方式较差,因为它需要运行时解析字符串

使用script元素

示例

<script type="module">
    // 定义要执行的 JavaScript 字符串
    const jsString = "var innerName = 'hahha'; " + "  console.log('这是一段动态执行的js字符串', innerName) ";

    // 创建一个新的 <script> 元素
    const script = document.createElement('script');

    // 将 type 属性设置为 text/javascript,告诉浏览器这是 JavaScript 代码
    script.type = 'text/javascript';

    // 将 JavaScript 字符串设置为 script 元素的 textContent
    script.textContent = jsString;

    // 将 script 元素添加到页面的 head 或 body 中
    document.head.appendChild(script);

    console.log('外部打印 innerName', innerName)
</script>

我们创建了一个新的 <script> 元素,将其 type 属性设置为 'text/javascript',并将我们的 JavaScript 字符串赋值给 textContent 属性。最后,我们将这个 <script> 元素添加到文档的 <head> 中,这种方法会立即执行 <script> 元素中的代码,因此它可以用于按需执行或延迟执行 JavaScript 代码。然而,这种方法也意味着一旦 <script> 元素被添加到 DOM 中,代码就会被执行,这可能不适用于所有场景。

webpack动态加载的原理就是这个方案....

  • 运行环境:全局作用域
  • 代码来源:script 元素的内容或加载的外部脚本
  • 访问权限:可以访问和修改全局作用域中的任何对象
  • 同步/异步:异步执行
  • 安全性和性能:安全性取决于代码的来源(内部脚本通常是安全的,但应仔细处理外部脚本以防止XSS攻击等);性能一般

使用 new Function() 构造函数

    var outerName = 'outer'
    function test() {
        let localName = 'local' // 不能访问局部变量

        const jsString = "function sayHello() {" +
            " var innerName = 'inner'; " +
            "  console.log('这是一段动态执行的js字符串', outerName, innerName ) " +
            "}" +
            "sayHello();";

        // 使用new Function()来创建一个新的函数
        var fun = new Function(jsString);

        // 调用这个新创建的函数
        var result = fun();
    }
    test();

需要注意的是 通过new Function() 创建的函数不会继承其创建时的局部作用域。 这种特殊的作用域行为,与普通 JavaScript 函数的作用域闭包机制不同。

优点:

  1. 隔离作用域:使用 new Function() 创建的函数不会访问到局部作用域(闭包作用域),它们只能访问全局变量, 或者显示传入的变量参数。
  2. 避免变量污染 : 字符串内部定义的变量在外部无法获取,不会污染全局 。

缺点

  1. 安全风险:执行从不可信来源动态生成的代码总是风险很高的操作。如果代码中包含恶意内容,则可能导致安全漏洞,如XSS攻击或远程代码执行。
  2. 性能问题:使用 new Function() 生成的函数可能比普通函数执行效率低,因为它们在运行时需要经过编译和解析过程。而且,由于它们通常无法被JavaScript引擎优化,因此可能会影响应用的整体性能。
  3. 代码维护性:由字符串生成的代码难以阅读和维护。错误更难被追踪,代码逻辑也更难被其他开发者理解。
  • 运行环境:全局作用域
  • 代码来源:参数形式为字符串,由开发者预先定义
  • 访问权限:只能访问全局对象
  • 同步/异步:同步执行
  • 安全性和性能:安全性差,可能被用于执行恶意代码;性能同样较差

使用 setTimeout 或 setInterval

<script>
    var outerName = 'outer'
    function test() {
        let localName = 'local' // 不能访问局部变量
        const jsString = "var innerName = 'inner'; " + "  console.log('这是一段动态执行的js字符串', outerName, innerName) ";
        setTimeout(jsString, 2 * 1000)
        setTimeout(() => {
            console.log('外部打印 innerName:', innerName)
        }, 4 * 1000)
    }
    test();
</script>

向 setTimeout 函数提供一个字符串作为参数时,该函数会把这个字符串当作脚本来解析和执行。这类似于使用 new Function(),也就是说 setTimeout 也是全局作用域运行 不会访问创建它的局部作用域。

六种动态运行js字符串的方案对比先有问题再有答案 动态运行js字符串的方案有哪些? 这些方案在运行环境,代码来源,访问权

字符串内部定义的遍历 外部依然可以访问 会存在污染全局变量的问题。

  • 运行环境:全局作用域
  • 代码来源:以字符串形式传入,由开发者预先定义
  • 访问权限:可以访问全局作用域中的所有变量
  • 同步/异步:异步执行
  • 安全性和性能:安全性差,可能被用于执行恶意代码;性能一般

使用 Web Workers

<script>
    // 创建一个可执行的 JavaScript 代码字符串
    var code = `self.onmessage = function(e) {
  var result = e.data[0] + e.data[1];
  self.postMessage(result);
  self.close();
}`;

    // 创建一个 Blob 对象来作为 worker 的源
    var blob = new Blob([code], { type: 'application/javascript' });

    // 根据 Blob 对象创建一个 object URL,并创建 worker
    var worker = new Worker(URL.createObjectURL(blob));

    // 设置 worker 的消息回调
    worker.onmessage = function (e) {
        console.log('Result: ' + e.data);
    };

    // 向 worker 发送数据
    worker.postMessage([1, 2]);

</script>
  • 运行环境:独立的后台线程,隔离于主线程的执行环境
  • 代码来源:外部脚本或由Blob构建的js代码
  • 操作对象的访问:无法访问 DOM 和全局对象,但可以通过 postMessage API 进行主线程和 worker 之间的通信
  • 同步/异步:异步执行
  • 安全性和性能:安全性较好,因为无法访问 DOM;有利于实现复杂或耗时任务的高效处理,不影响主线程

使用 with 语句(不推荐)

JavaScript 的 with 语句本身并不是用来执行 JavaScript 字符串的。它主要被用来修改一个特定的执行环境的作用域。

然而,我们可以将 with 语句与 eval 函数结合使用来在特定的对象环境中执行 JavaScript 字符串。

<script>
    var someObject = {
        someProperty: 'Hello, world!'
    };

    with (someObject) {
        eval('console.log(someProperty)');
    }
</script>

在这个例子中,with 语句更改了 eval 中代码的作用域,使其可以直接访问 someObject 的属性。因此,当 eval 执行字符串 'console.log(someProperty)' 时,它将输出 'Hello, world!'。

  • 运行环境:代码内部的指定对象环境作用域
  • 代码来源:直接在 with 语句块内部编写
  • 访问权限:可以访问和修改指定对象的属性
  • 同步/异步:同步执行
  • 安全性和性能:可能引发作用域混淆和错误,被认为是 JavaScript 的坏实践,并且在严格模式下被禁止使用。对于性能,with 语句需要动态改变作用域链,可能会带来额外的性能开销

总结

优点

  1. 灵活性和动态性:可以在运行时根据条件或数据生成并执行代码,从而提高代码的适应性和灵活性
  2. 按需加载和执行:可以延迟加载和执行代码,减少初始页面加载时间,并按需加载资源
  3. 条件逻辑实现:可以根据运行时的条件动态生成并执行代码,实现复杂的逻辑控制
  4. 低代码平台:在低代码或无代码平台中,动态执行 JavaScript 代码允许用户定义的逻辑以编程方式运行,而无需编写传统的代码

缺点:

动态运行字符串的方案都会有安全风险,这些风险主要有以下两个因素引起:

  1. 代码来源问题:如果你执行的代码是来自不受信任的来源,或者可以被第三方干预,那么存在代码中植入恶意逻辑的风险,这可能会导致一系列严重的安全问题,如注入攻击,跨站脚本攻击等。
  2. 访问和修改外部数据的风险:动态执行代码通常具有访问和操作(例如:修改、删除)外部数据的能力,如果这个过程被安全机制所忽视或者是被恶意利用,那么也可能引发安全漏洞。比如,恶意代码可能会尝试访问敏感信息(如密码、令牌等),或者修改应用的关键数据。

性能问题

  1. 解析和编译:与直接调用事先定义好的函数相比,动态执行字符串代码需要额外的解析(将字符串形式的代码解析成可执行的代码)和编译(编译成机器代码)步骤。这些额外的步骤需要时间,尤其是在必须频繁执行字符串代码的场合,性能损耗将会更加明显。

  2. 优化难度:现代JavaScript引擎通常对常规的JavaScript代码进行优化,以提高执行效率。然而,对于动态生成且运行的代码,在执行前是不可见的,引擎无法提前知道代码的结构,难以进行相同水平的优化。 另外,因为每次执行动态脚本都是一次全新的脚本执行,这使得引擎难以利用以往的代码信息进行优化。

  3. 垃圾回收:动态执行的代码可能会频繁创建新的对象和作用域,依赖于这些对象和作用域的动态代码则可以导致JavaScript引擎更频繁地进行垃圾回收,而垃圾回收是影响应用性能的一个关键因素。

建议

如果有场景必须要使用动态运行js字符串的情况下,应优先考虑动态创建 script 标签new Function() 和 Web Worker。

动态创建 script 标签:这种方式相对安全,可以用于加载外部的 JavaScript 代码,适用于需要动态加载并立即执行脚本的场景。但要尽量避免使用外部不可信的源。

new Function() 提供了较好的安全性和灵活性,适用于需要动态执行但不需要访问外部局部变量的场景。

Web Worker 适合于计算密集型任务,可以避免阻塞 UI 线程,适合于需要长时间运行的后台任务

转载自:https://juejin.cn/post/7407334387933052969
评论
请登录