本文解析JavaScript跑马灯开发中的关键技术细节。针对移动端1px像素线显示异常,提出直接书写像素值的解决方案。对比PC与移动端浏览器对CSS选择器容错率的差异。深入探讨jQuery对象转为原生数组的三种方法:原型扩展、each遍历及Array.from,并分析setTimeout/setInterval中this指向window的问题及执行时机机制。
JavaScript跑马灯开发概述
近期开发的项目中涉及跑马灯抽奖功能的需求,该功能主要依赖JavaScript技术实现。下文将详细解析相关代码与优化思路。
在正式进入核心功能前,先回顾重构过程中涉及的几个关键问题与知识点:
移动端适配与浏览器容错率
一、移动端1px像素线的渲染问题
设计师提供的手机端网页设计稿通常为2倍图。按照常规逻辑,网页中所有对象的实际尺寸应除以2进行缩放。然而,对于1像素宽的线条,这种处理方式会导致显示异常。
首先观察两张对比图片,分别为设计稿的预期效果:
以及在三星S4设备上的实际显示效果:
从上图可见,1像素的线条在三星S4上无法显示。这与S4手机的屏幕像素密度有关。关于屏幕像素密度与1px线条显示关系的原理已有大量文章介绍,此处不再赘述。本文采用的解决方案是对1px线条不做特殊处理,直接按原始尺寸编写,无论基础单位是rem还是其他单位。
{
position: absolute;
width: 13rem;
height: 9.2rem;
border:1px solid #000;
}
二、PC端与移动端浏览器容错率的差异
首先参考以下代码片段:
$('[node-type=row-a').find('div');
可以明显看出,上述代码中的选择器存在语法错误。但在浏览器中运行会产生何种结果?请见下图:

结果显示,对于属性选择器,即便存在语法错误,PC端浏览器仍能正确解析。然而,在移动端浏览器中,这种写法无法被正确解析,导致代码无法运行。
因此,在编写代码时需特别注意此类细节。
jQuery选择器与对象转换技巧
三、jQuery中选择器的使用技巧
在使用jQuery或Zepto时,最常用的选择器写法如下:
$('div.testClass')
即在$()函数中指定所需的DOM节点的class、ID或使用属性选择器。查阅jQuery文档可知,$()函数的描述如下:
jQuery([selector,[context]])
需重点关注context参数(该参数在常规使用中易被忽略,但极具价值)的说明:
默认情况下,若未指定context参数,$()将在当前HTML document中查找DOM元素;若指定了context参数(如一个DOM元素集或jQuery对象),则会在该context范围内查找。自jQuery 1.3.2版本起,返回元素的顺序等同于其在context中出现的先后顺序。
初学JavaScript时便得知,操作DOM会损耗浏览器性能,遍历DOM也会影响程序效率。若在指定范围内查找所需DOM,其速度是否比从整个document中查找更快?此外,在编写Web组件时,同一页面上可能出现多个组件实例,如何精准定位目标组件?context参数在此起到决定性作用。具体应用请继续阅读。
四、jQuery对象转换为数组的方法
初学jQuery时,书中曾提及:
jQuery对象本质上是一个JavaScript数组。
在使用jQuery的过程中,常涉及JS对象与jQuery对象的相互转换,此处不再赘述基础概念。但有时需在jQuery对象上调用原生Array对象的方法或属性。示例如下:

由代码运行结果可知,jQuery对象上并未提供reverse方法,尽管test是一个数组。那么,如何使jQuery对象能够使用原生Array对象的方法呢?
4.1 使用原型链扩展
示例代码如下:
jQuery.prototype.reverse=function(){
//一些操作
}
使用prototype扩展方法时,主要缺点包括可能污染已存在的原型链方法,以及访问方法时需遍历原型链。
4.2 将jQuery对象中的元素添加到数组中
参考以下代码:
var test = $('div.test');
var a=[];
$(test).each(function(){
a.push($(this));
});
a.reverse();
通过上述方式即可实现jQuery对象的翻转。
4.3 使用Array对象的from()方法
此方法为本文作者在编写插件过程中使用的方式。文档描述如下:
Array.from()方法可将一个类数组对象或可迭代对象转换为真实数组。个人感觉此代码较为简洁,暂不确定是否存在性能影响。继续参考以下代码:
var test = $('div.test');
var a= Array.from(test);
a.reverse();
定时器机制与性能分析
五、setInterval()和setTimeout()对程序性能的影响
由于setTimeout()和setInterval()在JavaScript中的实现机制完全相同,此处仅以setTimeout()进行验证。
参考以下两段代码:
var a ={
test:function(){
setTimeout(this.bbb,1000);
},
bbb:function(){
console.log('----');
}
};
a.test()
输出结果如下:

观察以下代码的输出情况:
var a ={
test:function(){
setTimeout(function(){
console.log(this);
this.bbb();
},1000);
},
bbb:function(){
console.log('----');
}
};
a.test();
运行该段代码时,程序报错。

由上述结果可知,在使用setInterval()和setTimeout()时,若在回调中使用this,其作用域已发生改变,并指向window。
setTimeout(fn, 0)的含义是指定某个任务在主线程最早可得的空闲时间执行,即尽可能早地执行。它在“任务队列”的尾部添加一个事件,因此需等待同步任务和“任务队列”中现有事件全部处理完毕后,才会执行。这意味着,设置setTimeout()后,回调并非立即等待指定秒数后执行,而是等待主线程任务处理完毕后再执行,因此存在“等待”时间超过设定时间的现象。同时,若异步队列中已存在其他setTimeout(),也需等待之前的任务执行完毕后再执行当前任务。
参考以下Demo:
setTimeout(function bbb(){},4000);
function aaa(){
setTimeout(function ccc(){},1000);
}
aaa();
若运行上述代码,当执行完aaa()并等待一秒后,并不会立即执行ccc(),而是等待bbb()执行完毕后再执行ccc(),此时距离主线程运行结束已过去4秒。
以上内容针对JavaScript实现跑马灯抽奖活动实例代码解析与优化(一),下篇将继续分享JavaScript实现跑马灯抽奖活动实例代码解析与优化(二),感兴趣的朋友敬请关注。