最近在做electron的内容,但是踩了很多坑,其中一个坑:
问题背景:对于进程间的通信,实现带参数的输入输出,就是直接写一些函数,可以实现其他文件对于函数的调用
问题内容:其中一个js是实现功能函数(renderer.js),也就是带参数的输入输出,另一个js是调用这些功能(hello.js),那么如何实现这个调用过程呢?
我的hello.js和renderer.js在同一个文件夹下面。
来尝试一下网上的方法:
方法一:在html文件中body标签后加入js文件,在index.html中的body标签(</body>与</html>之间插入所引用的hello.js),代码如下:
<html>
<head>
<!---head内容--->
</head>
<body>
<!---body内容--->
</body>
<!---在body后面添加所引用的js文件-->
<script language="JAVASCRIPT" src='hello.js'></script>
</html>
然后,对于hello.js文件,为了调用renderer.js中的函数,需要在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中函数为test
test(struct, buttonId, msg);
}
testBtn('click', 'asynchronous message', 'ping');
对于代码,网上解释如下:
让我们来分析一下关键的几句代码:首先,我们利用document.createElement("script")生成了一个script的标签,设置其type属性为text/javascript,src为renderer.js(这里的renderer.js同hello.js放在同一个目录,也可放在不同的目录)。最后将这个标签动态地加入body中。如此一来,我们就可以调用到不同js文件中的方法了。
注意:<script language="JAVASCRIPT" src='b.js'></script>一定要放在body下面。
因为hello.js中用到了body(document.body.appendChild(new_element);)
如果将引如hello.js的代码放在body上面,也就是说,
进入页面后,还没有生成body就已经执行hello.js里的document.body.appendChild(new_element);了。
这时body不存在就会抛javascript错误。
而实际效果呢,并不行(至少我没有测试成功,目前还不知道原因出现在哪里,求告知!!)
方法二:在调用者程序的开始直接加入要被调用的js文件,代码如下:
//加入下面的代码
document.write("<script language=javascript src='./renderer.js'></script>");
//调用函数
test('click', 'asynchronous message', 'ping');
结果呵呵了。。。。。还是不行。。。。。又是一头雾水
方法三:在html文件中加入两个脚本程序,注意,加入的位置在</head>和<body>两个标签之间,(也有的在<body></body>两个标签之间加入的),代码如下:
</head>
<script src="hello.js"></script>
<script src="renderer.js"></script>
<body>
之后在hello.js中直接调用函数就行。
test('click', 'asynchronous message', 'ping');
然后呢?如果说前两种方法不行我还信了,毕竟没有看到执行结果,但是第三种人家明明成功了,而且两种加入的方法都成功了,到我这儿走不通了,几近崩溃。。。
冷静冷静,如果你试了试前面的方法也不行,一定要淡定,我也不知道怎么想的,然后试了一下下面这个方法。。竟然成功了!!
方法四:首先,在方法三的基础上,在html中直接利用require,将两个js文件直接加载进去,然后就可以实现调用了。
html中的代码:
//首先,方法三中的加入内容不变
</head>
<script src="hello.js"></script>
<script src="renderer.js"></script>
<body>
<!--- body内容--->
//方法四加入的内容
<script>
// You can also require other files to run in this process
require('./renderer.js')
require('./hello.js')
</script>
</body>
</head>
至于调用函数的代码,与方法三一样,直接在hello.js中调用即可。
解释解释,知其然必须知其所以然(个人理解):
对于方法三,为什么不行呢?我打开electron调试工具的时候,第一次加载页面时,输出了这个信息(极为重要):
test is not defined----我的test函数没有被定义,为什么没有被定义,我明明已经写好了的,看一下方法三中的加载顺序,先加载的hello.js,之后加载的renderer.js,也就是说,先加载了的hello.js中的test方法没有被定义,然后自然函数执行不成功。
看了看程序,发现方法四的加载顺序与方法三完全相反,在查看了文档之后,发现方法三与方法四的加载的最终结果并没有什么不同,只不过:require引入的的文件,内部声明的最外层变量不属于全局变量,而script引入的属于全局变量。
最重要的信息:
如果用script引入需要考虑引入顺序,避免变量冲突和前置依赖。考虑顺序,考虑顺序,考虑顺序,重要的事情说三遍(之后自己也试了试,把方法三的顺序颠倒,发现可以,之所以没删除这些内容,是想记得更清,也避免让更多的人入坑)
哈哈哈哈,脑子真的被门给夹了,这个坑跳的真的值(说的我自己都信了)
不过,以方法三加载脚本程序的方法并不好:
(来自阮一峰先生的日志:http://www.ruanyifeng.com/blog/2012/11/require_js.html) 首先,加载的时候,浏览器会停止网页渲染,加载文件越多,网页失去响应的时间就会越长;其次,由于js文件之间存在依赖关系,因此必须严格保证加载顺序(比如上例的1.js要在2.js的前面),依赖性最大的模块一定要放到最后加载,当依赖关系很复杂的时候,代码的编写和维护都会变得困难。
最后,介绍一种我最喜欢的方式,对于功能的封装这种方式应该再好不过了,exports和require大法好。
在nodejs中,提供了exports 和 require 两个对象,其中 exports 是模块公开的接口,require 用于从外部获取一个模块的接口,即所获取模块的 exports 对象。而在exports抛出的接口中,我们可以抛出变量或者函数。
如果你希望你的模块就想为一个特别的对象类型,请使用module.exports;如果希望模块成为一个传统的模块实例,请使用exports.xx方法;module.exports才是真正的接口,exports只不过是它的一个辅助工具。最终返回给调用的是module.exports而不是exports。
总而言之,二者的关系是:
exports 是指向的 module.exports 的引用,二者指向同一块内存
module.exports 初始值为一个空对象 {},所以 exports 初始值也是 {}
require() 返回的是 module.exports 而不是 exports
下面给出代码:
renderer.js中使用exports导出函数:
//在这里面写好函数的封装,然后在hello.js中调用
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("调用成功");
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');
可以说,这种方式完全符合我们程序封装的概念,思路统一,结构规整,个人最爱。而且,上面的程序中两种方法都可以输出成功,其原因就在于:
exports变量是在模块的文件级别作用域内有效的,它在模块被执行前被赋于 module.exports 的值。它有一个快捷方式,以便 module.exports.f = ... 可以被更简洁地写成exports.f = ...。 注意,就像任何变量,如果一个新的值被赋值给exports,它就不再绑定到module.exports(其实是exports.xx会自动挂载到没有命名冲突的module.exports.xx)
而且,对于require函数,exports只是函数内部一个局部变量,最后返回的仍是module.exports,这应该就是exports称为module.exports的引用所在。代码内容如下(应该很清晰了):
function require(...) {
var module = { exports: {} };
((module, exports) => {
// 你的被引入代码 Start
// var exports = module.exports = {}; (默认都有的)
function some_func() {};
exports = some_func;
// 此时,exports不再挂载到module.exports,
// export将导出{}默认对象
module.exports = some_func;
// 此时,这个模块将导出some_func对象,覆盖exports上的some_func
// 你的被引入代码 End
})(module, module.exports);
// 不管是exports还是module.exports,最后返回的还是module.exports
return module.exports;
}
最后,这个坑跳的真的值!!!