最近在做electron的內(nèi)容,但是踩了很多坑,其中一個坑:
問題背景:對于進(jìn)程間的通信,實現(xiàn)帶參數(shù)的輸入輸出,就是直接寫一些函數(shù),可以實現(xiàn)其他文件對于函數(shù)的調(diào)用
問題內(nèi)容:其中一個js是實現(xiàn)功能函數(shù)(renderer.js),也就是帶參數(shù)的輸入輸出,另一個js是調(diào)用這些功能(hello.js),那么如何實現(xiàn)這個調(diào)用過程呢?
我的hello.js和renderer.js在同一個文件夾下面。
來嘗試一下網(wǎng)上的方法:
方法一:在html文件中body標(biāo)簽后加入js文件,在index.html中的body標(biāo)簽(</body>與</html>之間插入所引用的hello.js),代碼如下:
<html>
<head>
<!---head內(nèi)容--->
</head>
<body>
<!---body內(nèi)容--->
</body>
<!---在body后面添加所引用的js文件-->
<script language="JAVASCRIPT" src='hello.js'></script>
</html>
然后,對于hello.js文件,為了調(diào)用renderer.js中的函數(shù),需要在hello.js中添加以下代碼:
new_element = document.createElement("script");
new_element.setAttribute("type","text/javascript");
new_element.setAttribute("src", "renderer.js");
document.body.appendChild(new_element);
function testBtn(struct, buttonId, msg){
//renderer.js中函數(shù)為test
test(struct, buttonId, msg);
}
testBtn('click', 'asynchronous message', 'ping');
對于代碼,網(wǎng)上解釋如下:
讓我們來分析一下關(guān)鍵的幾句代碼:首先,我們利用document.createElement("script")生成了一個script的標(biāo)簽,設(shè)置其type屬性為text/javascript,src為renderer.js(這里的renderer.js同hello.js放在同一個目錄,也可放在不同的目錄)。最后將這個標(biāo)簽動態(tài)地加入body中。如此一來,我們就可以調(diào)用到不同js文件中的方法了。
注意:<script language="JAVASCRIPT" src='b.js'></script>一定要放在body下面。
因為hello.js中用到了body(document.body.appendChild(new_element);)
如果將引如hello.js的代碼放在body上面,也就是說,
進(jìn)入頁面后,還沒有生成body就已經(jīng)執(zhí)行hello.js里的document.body.appendChild(new_element);了。
這時body不存在就會拋javascript錯誤。
而實際效果呢,并不行(至少我沒有測試成功,目前還不知道原因出現(xiàn)在哪里,求告知?。。?/p>
方法二:在調(diào)用者程序的開始直接加入要被調(diào)用的js文件,代碼如下:
//加入下面的代碼
document.write("<script language=javascript src='./renderer.js'></script>");
//調(diào)用函數(shù)
test('click', 'asynchronous message', 'ping');
結(jié)果呵呵了。。。。。還是不行。。。。。又是一頭霧水
方法三:在html文件中加入兩個腳本程序,注意,加入的位置在</head>和<body>兩個標(biāo)簽之間,(也有的在<body></body>兩個標(biāo)簽之間加入的),代碼如下:
</head>
<script src="hello.js"></script>
<script src="renderer.js"></script>
<body>
之后在hello.js中直接調(diào)用函數(shù)就行。
test('click', 'asynchronous message', 'ping');
然后呢?如果說前兩種方法不行我還信了,畢竟沒有看到執(zhí)行結(jié)果,但是第三種人家明明成功了,而且兩種加入的方法都成功了,到我這兒走不通了,幾近崩潰。。。
冷靜冷靜,如果你試了試前面的方法也不行,一定要淡定,我也不知道怎么想的,然后試了一下下面這個方法。。竟然成功了??!
方法四:首先,在方法三的基礎(chǔ)上,在html中直接利用require,將兩個js文件直接加載進(jìn)去,然后就可以實現(xiàn)調(diào)用了。
html中的代碼:
//首先,方法三中的加入內(nèi)容不變
</head>
<script src="hello.js"></script>
<script src="renderer.js"></script>
<body>
<!--- body內(nèi)容--->
//方法四加入的內(nèi)容
<script>
// You can also require other files to run in this process
require('./renderer.js')
require('./hello.js')
</script>
</body>
</head>
至于調(diào)用函數(shù)的代碼,與方法三一樣,直接在hello.js中調(diào)用即可。
解釋解釋,知其然必須知其所以然(個人理解):
對于方法三,為什么不行呢?我打開electron調(diào)試工具的時候,第一次加載頁面時,輸出了這個信息(極為重要):
test is not defined----我的test函數(shù)沒有被定義,為什么沒有被定義,我明明已經(jīng)寫好了的,看一下方法三中的加載順序,先加載的hello.js,之后加載的renderer.js,也就是說,先加載了的hello.js中的test方法沒有被定義,然后自然函數(shù)執(zhí)行不成功。
看了看程序,發(fā)現(xiàn)方法四的加載順序與方法三完全相反,在查看了文檔之后,發(fā)現(xiàn)方法三與方法四的加載的最終結(jié)果并沒有什么不同,只不過:require引入的的文件,內(nèi)部聲明的最外層變量不屬于全局變量,而script引入的屬于全局變量。
最重要的信息:
如果用script引入需要考慮引入順序,避免變量沖突和前置依賴??紤]順序,考慮順序,考慮順序,重要的事情說三遍(之后自己也試了試,把方法三的順序顛倒,發(fā)現(xiàn)可以,之所以沒刪除這些內(nèi)容,是想記得更清,也避免讓更多的人入坑)
哈哈哈哈,腦子真的被門給夾了,這個坑跳的真的值(說的我自己都信了)
不過,以方法三加載腳本程序的方法并不好:
(來自阮一峰先生的日志:http://www.ruanyifeng.com/blog/2012/11/require_js.html) 首先,加載的時候,瀏覽器會停止網(wǎng)頁渲染,加載文件越多,網(wǎng)頁失去響應(yīng)的時間就會越長;其次,由于js文件之間存在依賴關(guān)系,因此必須嚴(yán)格保證加載順序(比如上例的1.js要在2.js的前面),依賴性最大的模塊一定要放到最后加載,當(dāng)依賴關(guān)系很復(fù)雜的時候,代碼的編寫和維護(hù)都會變得困難。
最后,介紹一種我最喜歡的方式,對于功能的封裝這種方式應(yīng)該再好不過了,exports和require大法好。
在nodejs中,提供了exports 和 require 兩個對象,其中 exports 是模塊公開的接口,require 用于從外部獲取一個模塊的接口,即所獲取模塊的 exports 對象。而在exports拋出的接口中,我們可以拋出變量或者函數(shù)。
如果你希望你的模塊就想為一個特別的對象類型,請使用module.exports;如果希望模塊成為一個傳統(tǒng)的模塊實例,請使用exports.xx方法;module.exports才是真正的接口,exports只不過是它的一個輔助工具。最終返回給調(diào)用的是module.exports而不是exports。
總而言之,二者的關(guān)系是:
exports 是指向的 module.exports 的引用,二者指向同一塊內(nèi)存
module.exports 初始值為一個空對象 {},所以 exports 初始值也是 {}
require() 返回的是 module.exports 而不是 exports
下面給出代碼:
renderer.js中使用exports導(dǎo)出函數(shù):
//在這里面寫好函數(shù)的封裝,然后在hello.js中調(diào)用
var test = function(struct, buttonId, msg){
const asyncMsgBtn = document.getElementById(buttonId);
asyncMsgBtn.addEventListener(struct, function(){
switch(struct){
case 'click':
ipc.send('asynchronous-message', msg);
console.log("調(diào)用成功");
break;
default:
console.log('Error!!!')
}
})
}
//這種方式是成功的
exports.test = test;
//這種方式也是可以得
//module.exports.test = test;
而hello.js中對于代碼的使用如下:
//利用require加載模塊
const renderer = require('./renderer')
renderer.test('click', 'asynchronous message', 'ping');
renderer.test('click', 'changeView', 'change');
可以說,這種方式完全符合我們程序封裝的概念,思路統(tǒng)一,結(jié)構(gòu)規(guī)整,個人最愛。而且,上面的程序中兩種方法都可以輸出成功,其原因就在于:
exports變量是在模塊的文件級別作用域內(nèi)有效的,它在模塊被執(zhí)行前被賦于 module.exports 的值。它有一個快捷方式,以便 module.exports.f = ... 可以被更簡潔地寫成exports.f = ...。 注意,就像任何變量,如果一個新的值被賦值給exports,它就不再綁定到module.exports(其實是exports.xx會自動掛載到?jīng)]有命名沖突的module.exports.xx)
而且,對于require函數(shù),exports只是函數(shù)內(nèi)部一個局部變量,最后返回的仍是module.exports,這應(yīng)該就是exports稱為module.exports的引用所在。代碼內(nèi)容如下(應(yīng)該很清晰了):
function require(...) {
var module = { exports: {} };
((module, exports) => {
// 你的被引入代碼 Start
// var exports = module.exports = {}; (默認(rèn)都有的)
function some_func() {};
exports = some_func;
// 此時,exports不再掛載到module.exports,
// export將導(dǎo)出{}默認(rèn)對象
module.exports = some_func;
// 此時,這個模塊將導(dǎo)出some_func對象,覆蓋exports上的some_func
// 你的被引入代碼 End
})(module, module.exports);
// 不管是exports還是module.exports,最后返回的還是module.exports
return module.exports;
}
最后,這個坑跳的真的值?。。?/p>