Javascript splice的遭坑小记

来源:互联网 发布:艾滋病现状最新数据 编辑:程序博客网 时间:2024/05/21 19:48

在 Javascript 中 Array.splice 方法是个很强大的方法,很多时候我们可以用它来删除数组中的某一个元素(有别于 delete,delete删除会在数组中留下一个空洞而splice不会,用 splice 更像是从链表中移除某个节点)。

今天遇到的坑简单来说是这样的,在 UI 相关的编程中如果有某处的交互很多,交互的组合关系也很复杂的时候,我们往往会采用“数据驱动”的方式来进行代码编写,通过合理的数据结构设计加上“被动视图”来降低复杂度和提高代码质量。

今天的问题也是由此引出的,我们有一块业务也是类似的方式处理的,具体的行驶为trigger-on 的模型,view 去监听数据的变化,数据有变化的时候通知所有的观察者。简化后的业务代码如下:

function Info() {    var events = {}    var _this = this    this.on = function (evt, handler) {        events[evt] || (events[evt] = [])        events[evt].push(handler)    }    this.once = function (evt, handler) {        events[evt] || (events[evt] = [])        function _once() {            handler()            _this.off(evt, _once)        }        events[evt].push(_once)    }    this.off = function (evt, handler) {        var handlers = events[evt]        handlers.forEach(function (_handler, i) {            if (_handler === handler) {                handlers.splice(i, 1)            }        })    }    this.trigger = function (evt) {        var i = 0,            handlers = events[evt],            len = handlers.length        for (; i < len; i++) {            handlers[i] && handlers[i]()        }    }}

代码的逻辑很简单,我就不多做说明了,但是测试的过程中,发现了一件诡异的事情,某些时候监听了 change 事件的函数并没有被触发,但并不是每次都能复现(大家都知道,修复不能 100% 复现的 bug 是多么痛苦),没有办法,问题上报到我这,我只能去翻看了一下内部实现,剥去所有业务代码后,抽出了上面的核心代码,然后写了一个正常和不正常的版本的 case 给小伙伴。

// 正常的代码var info = new Info()info.on('change', function () {    console.log('change')})info.once('change', function () {    console.log('change')})info.trigger('change')info.trigger('change')
// 不正常的代码var info = new Info()info.once('change', function () {    console.log('change')})info.on('change', function () {    console.log('change')})info.trigger('change')info.trigger('change')

以上的代码,按照逻辑来说,会打印三次 change,两次由 on 分别打印,一次由 once 打印,但不正常的代码只会打印两次。

如果单独看每个函数,似乎都没有问题,但留意 once 这个方法,它和 on 唯一的区别在于它自己就会调用一次 off,而 off 方法使用的 splice 会改变原数组的长度。再结合 trigger 的实现,你会发现,只要 once 方法先触发,数组的长度就会减1,被删除的元素就会“掉落”到删除元素的位置,而 for 循环是以 index 为准的,自然就跳过了该位置,那么紧跟着 once 后面的 on 是不会被触发的。

所以,使用 splice 的过程中,一定要反复提醒自己,splice 可能会改变数组长度,而那些依赖数组长度的代码都应该要仔细检查。

修复的方法其实也很简单,就是把 for 循环倒过来写~

for (i = 0; i < len; i++) ===> for (i = len; i >=0; i--)

当然了,虽然 splice 有这样的坑,坑也不一定就不能用来做好事。例如,我们可以利用 splice 的特性来为数组增加一个 half 方法,可以指定是移除奇数位还是偶数位的元素

Array.prototype.half = function (even) {    var i = even ? 0 : 1    var len = this.length    for (;i < len; i++) {        this.splice(i, 1)    }}

当然,可能还有其它的用处,我这里就抛砖引玉,欢迎大家补充。

原创粉丝点击