理想情况下,开发者只需要实现核心的业务逻辑,其他都可以加载别人已经写好的模块。然而,理想很丰满,现实很骨感,特别是在WPS编辑器中,加载别人写好的模块需要用eval方法。很不安全,WPS官方又没有开发出独特的加载方式,下面是一个模块的加载示例:
var CryptoJS; //.MD5(inputString).toString()
var _;
async function cryptoJs(){
let str = await fetch("https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js").then(x => x.text());
eval(str);
}
async function lodash(){
const str = await fetch("https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js").then(x => x.text());
eval(str);
}
async function MD5Test(){
await cryptoJs(); //引入前端加密模块crypto-js.min.js
print(CryptoJS.MD5("123").toString());
await lodash(); //引入前端模块lodash
const array = [1, 2, 3, 4, 5];
const chunked = _.chunk(array, 2);
console.log(JSON.stringify(chunked));
}
效果图:
这是执行三次后的结果,可以发现,有时某些模块并没有成功的加载进来。经过UP多次测试,发现,当同时加载多个模块时,部分模块无法成功加载。很不好用,而且要还是使用eval,是一种不安全的行为。因此,在wps编辑器中,程序员开发自己的模块成了一个迫切的需求。
在以往的文章中,已经介绍过面向对象的编程思路。模块化,也是面向对象编程的一种,也就是说之前我们定义的构造函数、class类本身就是一种简单的模块化编程方式。在本文中,将对这种模块化的编程方式进行详细介绍。
一、原始写法
模块就是实现特定功能的一组方法。只要把不同的函数(以及记录状态的变量)简单地放在一起,就算是一个模块。
例如:
function myModule1(){
//定义你的方法
}
function myModule2(){
//定义你的方法
}
上面,myModule1 与 myModule2 两个函数,放在一起,就构成了一个模块
二、对象写法
就是之前文章中提到的使用new Object( )或{ }来定义一个对象,在其中定义自己的属性和方法。例如:
var module1 = new Object({
_count : 0,
mod1 : function (){
//...
},
mod2 : function (){
//...
}
});
在上述示例中,将mod1和mod2两个方法都封装在module1对象里。使用的时候,就是调用这个对象的属性。并且定义了一个私有变量_count。然而,这样的写法会暴露所有模块成员,内部状态可以被外部改写。比如,外部代码可以直接改变内部计数器的值。不是很安全。
三、使用构造函数封装私有变量
function mod() {
var buffer = [];
this.add = function (str) {
buffer.push(str);
};
this.toString = function () {
return buffer.join('');
};
}
这种方法虽然将私有变量封装在构造函数中,但是违反了构造函数与实例对象相分离的原则。并且,非常耗费内存。不是很推荐。有人可能会说,我可以这样:
function mod() {
this._buffer = [];
}
mod.prototype = {
constructor: mod,
add: function (str) {
this._buffer.push(str);
},
toString: function () {
return this._buffer.join('');
}
};
然而,如果这样编写,_buffer又不是真正意义上的私有变量了。在外部依旧可以从外部进行读写,不太安全。
四、IIFE封装模块的方法与属性(本文重点)
使用"立即执行函数"(Immediately-Invoked Function Expression,IIFE),可以达到不暴露私有成员的目的。简单示例如下:
var module1 = (function(){
var _count = 0;
var setCount = function(count){
_count = count;
}
var getCount = () => _count;
return {
setCount : setCount,
getCount : getCount
}
})();
效果图如下:
由于立即函数表达式的返回值中,没有直接返回_count,所以,外部无法直接访问它,除非调用对应的方法进行访问和修改。
1、放大模式
var module1 = (function (mod = {}){
mod.f = function () {
//...
};
return mod;
})(module1);
如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用"放大模式",上述示例中,为module1模块添加了一个新方法f,并返回新的module1。
2、输入全局变量
var module1 = (function (math, array) {
// 私有变量和函数
var pi = math.PI;
function getRandomInt(min, max) {
// 使用math.floor和math.random生成一个随机整数
return math.floor(math.random() * (max - min + 1)) + min;
}
// 公有API
return {
calculateCircumference: function (radius) {
// 计算圆的周长
return 2 * pi * radius;
},
createArrayWithRange: function (start, end) {
// 创建一个包含从start到end的数组
var result = [];
for (var i = start; i <= end; i++) {
result.push(i);
}
return result;
},
shuffleArray: function (arr) {
// 使用Fisher-Yates洗牌算法打乱数组
for (var i = arr.length - 1; i > 0; i--) {
var j = getRandomInt(0, i);
// 交换元素
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
return arr;
},
printArrayInfo: function (arr) {
// 打印数组信息
console.log("Array length: " + arr.length);
console.log("Array elements: " + arr.join(", "));
}
};
})(Math, Array);
function main(){
// 使用module1的公有API
var circumference = module1.calculateCircumference(5);
console.log("Circumference of a circle with radius 5: " + circumference); // 输出: Circumference of a circle with radius 5: 31.41592653589793
var rangeArray = module1.createArrayWithRange(1, 10);
console.log("Array with range 1 to 10: ", rangeArray); // 输出: Array with range 1 to 10: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
var shuffledArray = module1.shuffleArray([1, 2, 3, 4, 5]);
console.log("Shuffled array: ", shuffledArray); // 输出: Shuffled array: [ ... ] (随机顺序)
module1.printArrayInfo(shuffledArray); // 输出数组的长度和元素
}
独立性是模块的重要特点,模块内部最好不与程序的其他部分直接交互。为了在模块内部调用全局变量,必须显式地将其他变量输入模块。上述示例中,就是将全局的Math,Array对象当作了module1的输入参数,这样做除了保证模块的独立性,还使得模块之间的依赖关系变得明显。执行效果图如下所示:
3、常规封装结构
var myModule = function() { // open IIFE
// public (公有)
var self = {
publicMethod: function (...) {
privateData = ...;
privateFunction(...);
},
publicData: ...
};
// private (私有)
var privateData = ...;
function privateFunction(...) {
privateData = ...;
self.publicData = ...;
self.publicMethod(...);
}
return self;
}(); // close IIFE
五、总结
总的来说,JavaScript 模块化编程是一种将代码分割成独立、可复用的模块的方法。这种方法有助于提高代码的可维护性、可读性和可重用性。模块化编程的核心思想是将功能相关的代码组织在一起,而将无关的代码分离,从而降低代码的复杂性。在WPS编辑器中,模块化可以通过多种方式实现,其中最为推荐的是IIFE的写法,因为这种写法作用域完全封闭,比较安全。当然,调用Symbol方法,在ES6类中,也可以实现私有属性:
const privateKey = Symbol('privateKey');
class MyClass {
constructor(value) {
this[privateKey] = value; // 私有变量
}
// 公有方法
getValue() {
return this[privateKey];
}
}
const instance = new MyClass(42);
console.log(instance.getValue()); // 输出: 42
console.log(instance[privateKey]); // undefined,因为 Symbol 是唯一的,外部无法访问
但是,该方法比较复杂,需要为每个私有变量创建一个Symbol,而且,在JSON序列化时,会被忽略,虽然可以实现,但也不如IIFE来的方便快捷。
标签:function,...,return,进阶,模块化,module1,JS,模块,var From: https://blog.csdn.net/jackispy/article/details/145297897