JavaScript跑马灯插件架构设计与性能优化实战

作者:寒星

时间:2026-10-29

浏览:122

本文深入解析JavaScript跑马灯抽奖插件的开发流程与性能优化策略。首先阐述成熟插件应具备的五大核心特征,包括配置简化、作用域隔离、代码复用、参数自定义及销毁机制。随后针对实际项目中的性能瓶颈,通过减少循环次数、利用Array.reverse方法以及引入闭包替代多重setTimeout,显著降低时间复杂度与执行队列负载,提升代码执行效率。

文章目录

跑马灯插件基础架构设计

在前文介绍了JavaScript跑马灯抽奖活动的实现代码解析与优化(一)。既然目标是开发一个插件,那么该组件必须具备某些特性,以满足日常开发需求或提升开发效率。一个合格的插件应当具备哪些基本特征?接下来我们将进行总结。

1. JavaScript插件的基本特征:

配置过程必须尽可能简洁; 插件内部定义的变量不得污染全局作用域; 同一套代码逻辑需能在不同场景下复用; 支持用户自定义功能参数; 提供销毁变量和清理参数的功能。 若遵循上述特征编写插件,可总结出一个基础代码结构,我们将逐一解析。

1. 插件配置应尽量简洁

在HTML中配置容器节点


//这里的node-type="reward-area" 是标识我们插件的容器节点
<div class="re-area" node-type="reward-area" >

在DOM加载完成后初始化插件


$(function() {
//这里的 test 是代表容器的 class
window.LightRotate.init($('[node-type=reward-area]'));
});

2. 插件内部变量不污染全局变量

JavaScript中具备块级作用域标识符的是function。那么如何声明变量以避免污染全局环境呢? 此处需利用JavaScript函数的自执行特性。代码如下:


(function(){
// do something
})();

3. 实现不同场景下的功能代码复用

这需要运用面向对象的思想,将功能代码抽象为对象,在使用时实例化该对象即可。接着基于第二步的代码继续编写,


//
(function($){
// 创建功能对象
var LightRotate = function (select) {
// do something
};
LightRotate.init = function (select) {
var _this = this;
//根据不同的容器实例化不同的对象
select.each(function () {
new _this($(this));
});
};
window.LightRotate = LightRotate;
})(jQuery);

4. 支持用户自定义功能参数

首先应设定默认参数,例如以下形式


//
(function($){
// 创建功能对象
var LightRotate = function (select) {
// 自定义的参数
this.setting = {
liAutoPlay: false, //周围的灯是否自动旋转
roLiSpeed: 100, //灯旋转的速度ms
roPrSpeed: 200, //奖品旋转速度ms
liDirection: true, //旋转方向 true 正方向 false 反方向
randomPrize: false //空格是否随机选取
};
};
LightRotate.init = function (select) {
var _this = this;
//根据不同的容器实例化不同的对象
select.each(function () {
new _this($(this));
});
};
window.LightRotate = LightRotate;
})(jQuery);

虽然这样写允许使用者通过修改JavaScript文件来自定义配置,但为了提升插件的易用性,例如面对完全不懂JS的用户该怎么办? 我们可以将这些参数以自定义属性的形式配置在HTML中,如下所示:


<div class="re-area" node-type="reward-area" data-setting='{
"liAutoPlay":false,
"roLiSpeed":100,
"roPrSpeed":200,
"liDirection":true,
"randomPrize":false}'>

这样用户只需在HTML节点中即可配置当前容器运行的参数。其优势在于同一页面上的不同容器可单独配置参数,从而降低耦合度。

那么在JS中如何获取这些参数?在上述代码中已存在功能对象函数。若要扩展对象方法以获取用户自定义参数,通常使用prototype来扩展已有对象的方法,代码如下:


//
(function($){
// 创建功能对象
var LightRotate = function (select) {
// 自定义的参数
this.setting = {
liAutoPlay: false, //周围的灯是否自动旋转
roLiSpeed: 100, //灯旋转的速度ms
roPrSpeed: 200, //奖品旋转速度ms
liDirection: true, //旋转方向 true 正方向 false 反方向
randomPrize: false //空格是否随机选取
};
//这里调用对象的获取用户自定义参数的方法,并且将默认参数合并
$.extend(_this.setting, _this.getSettingUser());
};
LightRotate.prototype = {
//扩展获取用户自定义参数的方法
getSettingUser: function () {
var userSetting = this.LightArea.attr('data-setting');
if (userSetting && userSetting !== '') {
return $.parseJSON(userSetting);
} else {
return {};
}
}
};
LightRotate.init = function (select) {
var _this = this;
//根据不同的容器实例化不同的对象
select.each(function () {
new _this($(this));
});
};
window.LightRotate = LightRotate;
})(jQuery);

5. 提供销毁变量和参数的功能;

最后一点是插件应具备销毁自身变量和参数的能力。如何在上述代码基础上继续扩展功能对象的可调用方法?代码如下:


LightRotate.prototype = {
//扩展获取用户自定义参数的方法
getSettingUser: function () {
var userSetting = this.LightArea.attr('data-setting');
if (userSetting && userSetting !== '') {
return $.parseJSON(userSetting);
} else {
return {};
}
},
//销毁对象参数
destory: function () {
$(_this.LightArea).off();
this.closeAnimation();
this.rewardTimer = null;
}
};

通过以上内容,我们可以大致了解一个成熟插件应具备的基本功能。

插件性能优化实战

2. 插件开发与优化示例

该项目恰逢春节放假前的紧急任务,当时为赶进度未详细思考代码结构,这反而为后续优化提供了机会。

由上一节介绍的定时器内容可知,JavaScript是单线程的。因此

若一段代码运行效率低下,将影响后续代码的执行。对于JavaScript而言,代码优化是必需的。 先来看看我们的“跑马灯”插件应具备哪些功能:

能够控制灯是否自动播放; 可控制灯的旋转方向; 可控制灯的旋转速度; 可控制奖品的旋转速度。 此处不详细介绍这些功能点的开发过程,仅介绍优化过程。有兴趣者可查看文章末尾附上的源代码地址,进行下载阅读。

1. 优化“顺序”获取旋转灯代码

由于周围的灯使用绝对定位,因此需要“顺序”获取其列表并进行操作。

首先获取DOM节点。


//获取外围的灯,可以看到我这里使用的选择器多了一个 select,是为了获取当前容器下的某些元素,避免有多个容器存在时冲突
this.topLight = $('[node-type=re-top]', select).find('span');
this.rightLight = $('[node-type=re-right]', select).find('span');
this.bottomLight = $('[node-type=re-bottom]', select).find('span');
this.leftLight = $('[node-type=re-left]', select).find('span');

随后应“顺序”获取“灯”节点的DOM元素列表。

我的第一版实现如下:


Zepto(topLight).each(function() {
lightList.push(this);
});
Zepto(rightLight).each(function() {
lightList.push(this);
});
for (var j = bottomLight.length - 1; j >= 0; j--) {
lightList.push(bottomLight[j]);
}
for (var m = leftLight.length - 1; m >= 0; m--) {
lightList.push(leftLight[m]);
}

由于“下”和“左”方向的灯需要倒序处理,因此使用了两个倒序的for循环。实际上,当出现循环时,我们都应思考代码是否有优化空间。

优化后的代码如下,此处减少了4次循环的使用


function () {
var lightList = [];
var bottomRever;
var leftRever;
bottomRever = Array.from(this.bottomLight).reverse();
leftRever = Array.from(this.leftLight).reverse();
lightList = Array.from(this.topLight).concat(Array.from(this.rightLight));
lightList = lightList.concat(bottomRever);
lightList = lightList.concat(leftRever);
}

列表倒序处理使用了原生Array对象的reverse方法。

2. 使用“闭包”优化顺序循环播放

为了使“灯”能够顺序运行,第一版的思路是:

为每一个“灯”(注意,这里是每一个,罪过…罪过…)定义一个setTimeout(),执行时间按顺序加入JS执行队列。 代码如下:


var zepto_light = Zepto(lightList);
var changeTime = 100;
var lightLength = zepto_light.length;
var totleTime = changeTime * lightLength;
function lightOpen() {
for (var i = 0; i < lightLength; i++) {
(function temp(i) {
lightTimer = setTimeout(function() {
if (stopAnimation === false) {
Zepto(zepto_light).removeClass('light_open');
Zepto(zepto_light[i]).addClass("light_open");
} else {
return;
}
}, changeTime * i);
})(i);
}
}

这种写法的缺点很明显:若有100个“灯”,则会在当前JS执行队列中加入100个setTimeout()。再次强调,此处使用了for循环,时间复杂度随之增加,代码执行效率下降。

经过思考,JavaScript中的“闭包”符合当前使用场景,于是尝试用闭包进行优化,优化后代码如下:


lightRun: function () {
var _this = this;
function tempFunc() {
var lightList = _this.getLightList();
var lightLength = lightList.length;
var i = 0;
return function () {
$(lightList, _this.LightArea).removeClass('light_open');
$(lightList[i], _this.LightArea).addClass("light_open");
i++;
//使一轮循环结束后能够继续下次循环
if (i === lightLength) {
i = 0;
}
};
}
var lightRunFunc = tempFunc();
lightRunFunc();
_this.lightInterVal = setInterval(lightRunFunc, _this.setting.roLiSpeed);
}

从上述代码可明显发现两个优点:第一,减少了for循环的使用,降低了代码的时间复杂度;第二,每次仅在当前代码执行队列中创建一个setInterval(),减小了执行队列的复杂度。

关于JavaScript实现跑马灯抽奖活动实例代码解析与优化(二)的相关知识就介绍到这里,希望本文所述对大家有所帮助。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章