JavaScript動態(tài)執(zhí)行JS代碼的四種方法
今天來聊一聊javascript如何動態(tài)執(zhí)行js代碼,我總結了以下四種方法。
第一種方式:eval
eval函數會將傳入的字符串作為js代碼執(zhí)行,并且返回最后一個表達式的值。
eval('console.log(1)') // 1
eval的特點:
- 同步執(zhí)行;
- 執(zhí)行環(huán)境為當前作用域。
let a = 1;
function test() {
let a = 2;
eval('console.log(a)'); // 2
}
test();
使用eval需要注意:
- 安全風險:任何時候都不要把用戶的輸入放入eval中執(zhí)行,可能被注入惡意代碼(如 eval('alert("XSS")'))。
- 性能差:eval 會中斷 JavaScript 引擎的優(yōu)化,導致執(zhí)行速度變慢。
- 可讀性和維護性差:動態(tài)生成的代碼難以調試和維護。
第二種方式:new Function
可以通過構造函數Function動態(tài)創(chuàng)建一個函數,然后主動執(zhí)行它。
Function的函數簽名如下:
new Function([arg1, arg2, ...argN], functionBody)
其中,arg1, arg2, ...argN 是參數名,functionBody 是函數體。
let a = 1;
function test() {
let a = 2;
let fn = new Function('console.log(a)');
fn(); // 1
}
test();
new Function的特點:
- 同步執(zhí)行;
- 執(zhí)行環(huán)境為全局作用域。
第三種方式:setTimeout
平常我們使用setTimeout第一個傳遞的是一個函數,第二個參數是延遲執(zhí)行的時間。
setTimeout(functionRef, delay);
但其實,setTimeout的第一個參數也可以是一個字符串,表示要執(zhí)行的代碼。
setTimeout('console.log(1)', 1000); // 1
來看看setTimeout執(zhí)行的作用域:
let a = 1;
function test() {
let a = 2;
setTimeout('console.log(a)', 1000); // 1
}
test();
所以setTimeout的特點如下:
- 異步執(zhí)行;
- 執(zhí)行環(huán)境為全局作用域。
第四種方式:script標簽
在瀏覽器環(huán)境中,我們可以動態(tài)創(chuàng)建script標簽來執(zhí)行代碼。
var a = 1;
let script = document.createElement('script');
script.textContent = 'console.log(1)';
document.body.appendChild(script); // 1
這個都不用多說了,肯定是全局作用域,所以script動態(tài)執(zhí)行的特點如下:
- 同步執(zhí)行;
- 執(zhí)行環(huán)境為全局作用域。
應用場景
動態(tài)執(zhí)行js這個功能在業(yè)務開發(fā)幾乎不用用到,但在一些打包工具中會用到,比如webpack。
1. devtool使用eval
在webpack的配置文件webpack.config.js,有一項關于devtool的配置。
// webpack.config.js
module.exports = {
devtool: 'eval-source-map',
}
當把devtool設置為eval-source-map時,webpack打包出來的代碼會使用eval進行包裹。
打包前的代碼:
// ./src/index.js console.log(1)
打包后的產物長這樣,這里我使用的是webpack5。
// bundle.js
(() => {
var __webpack_modules__ = ({
"./src/index.js":
(() => {
eval("console.log(1)//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiLi9zcmMvaW5kZXguanMiLCJtYXBwaW5ncyI6IkFBQUEiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly93ZWJwYWNrLWRlbW8vLi9zcmMvaW5kZXguanM/YjYzNSJdLCJzb3VyY2VzQ29udGVudCI6WyJjb25zb2xlLmxvZygxKSJdLCJuYW1lcyI6W10sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///./src/index.js\n");
})
});
var __webpack_exports__ = {};
__webpack_modules__["./src/index.js"]();
})();
這樣的好處是:
source-map映射內置在打包后的js文件中,不會生產單獨的source-map文件;- 由于是用
eval直接包裹,所以構建速度快,適合開發(fā)環(huán)境調試使用。
順便提一下,開發(fā)環(huán)境一般會使用eval-cheap-module-source-map,相比于eval-source-map:
cheap: 不生成列映射,只生成行映射,生成的映射文件體積更??;module: 生成映射文件時,會保留模塊的路徑信息,方便調試。
生產環(huán)境如果需要調試的話,一般會使用全量的最完整的sourcemap,也就是devtool: 'source-map',這樣生成的映射文件雖然體積最大,但調試體驗最好。
2. tapable中使用 new Function
在webpack的編譯過程中,tapable 實現了在編譯過程中的一種發(fā)布訂閱者模式的插件 Plugin 機制。在tapable的源碼中,就用到了new Function。
簡單舉個例子:
// Tapable 動態(tài)生成的調用函數(偽代碼)
const hook = new SyncHook(["arg1", "arg2"]);
hook.tap("plugin1", (arg1, arg2) => console.log(arg1, arg2));
hook.tap("plugin2", (arg1, arg2) => console.log(arg2, arg1));
這里用到了SyncHook做了一個監(jiān)聽,按傳統的發(fā)布訂閱的實現的話,內部會用callbacks.forEach(fn => fn(...args)))進行調用,每次觸發(fā)事件都需要動態(tài)遍歷回調數組,性能較低,所以tapable用new Function做了優(yōu)化。
// 動態(tài)生成的調用邏輯
const compiledFn = new Function("arg1", "arg2", `
plugin1Fn(arg1, arg2);
plugin2Fn(arg2, arg1);
`);
compiledFn("a", "b");
這里直接用new Function把邏輯進行動態(tài)組裝成一個函數,節(jié)省了上下文開銷,實現更加高效的事件觸發(fā)邏輯,尤其是在處理大量鉤子(Hooks)和插件時。
總結
主要介紹了四種動態(tài)執(zhí)行js的方法:
- eval: 同步,當前作用域;
- new Function: 同步,全局作用域;
- script標簽: 同步,全局作用域;
- setTimeout: 異步,全局作用域。
還簡單介紹了動態(tài)執(zhí)行的應用場景,比如webpack的devtool配置使用到了eval,tapable中使用new Function。
以上就是JavaScript動態(tài)執(zhí)行JS代碼的四種方法的詳細內容,更多關于JavaScript動態(tài)執(zhí)行JS代碼的資料請關注腳本之家其它相關文章!
相關文章
uniapp中微信小程序與H5相互跳轉以及傳參詳解(webview)
在單位做項目的時候碰到一個需求,需要從微信小程序跳轉到H5頁面,下面這篇文章主要給大家介紹了關于uniapp中微信小程序與H5相互跳轉以及傳參的相關資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2022-08-08
JavaScript的Object.defineProperty詳解
本篇文章給大家詳細講述了JavaScript的Object.defineProperty的相關知識點內容,有興趣的朋友參考學習下。2018-07-07
前端知識點之Javascript選擇輸入框confirm用法
這篇文章主要介紹了JavaScript中的confirm方法的基本用法、功能特點、注意事項及常見用途,文中通過代碼介紹的非常詳細,對大家學習或者使用js具有一定的參考借鑒價值,需要的朋友可以參考下2025-02-02

