如果直接减少数组的`length`属性,如何获取已删除的项目(通过代理进行后处理)?

问题描述:

JavaScript(ECMAScript 6)如果直接减少数组的`length`属性,如何获取已删除的项目(通过代理进行后处理)?

我写了Array项目的代理。我解决问题的方法做到这一点...我表现出这种例子的基础上,这个问题:

function Node(value){ 
    this.parent = null; // parent Node item 
    this.value = value; 
    this.children = []; // child Node items 
} 

我想children阵列的每个项目都会更新自己的parent属性时,该项目将被添加到该阵列或从中删除。

下面是我的决定单元测试。 我为俄罗斯评论道歉(你可以用google translate翻译那些你感兴趣的内容)

此外,我将我的代码放在jsFiddle here(此资源显示代码行号)。

您可以看到我的其中一个测试失败...当我通过直接减少数组的length属性删除项目时,会发生这种情况。我看到deleteProperty处理程序不会捕获此事件。

I.e.我我的问题是存在的项目删除这样的变种:

myarray.length -= 1; 

在这种情况下,最后一个项目从数组中删除,但其parent属性将不会更新到null值。

我该如何通过代理来捕获这种情况?

谢谢。

/* This is the module which I test. My QUnit tests start with code row 118. 
 
The failed test is on the code row 351.*/ 
 
(function(exports){ 
 

 
    /* Массив, автоматически управляющий ссылками на родителя для всех своих 
 
    * дочерних элементов при их добавлении или удалении. 
 
    * 
 
    * propName - имя свойства, которое автоматически должно появляться и 
 
    * обновляться у каждого элемента массива. 
 
    * 
 
    * parent - ссылка на объект, который должен быть указан в качестве значения 
 
    * свойства, имя которого указано в propName у каждого элемента массива. 
 
    * 
 
    * ВНИМАНИЕ! 
 
    * Если удалять элементы из хвоста прокси-массива посредством назначения 
 
    * свойству length меньшего значения, то у удалённых таким образом элементов 
 
    * свойство, подлежащее мониторингу, НЕ обновит своё значение! Поэтому не 
 
    * следует пользоваться таким способом удаления элементов из прокси-массива, 
 
    * полученного при помощи ArrayWithParentLink. Вместо этого используйте 
 
    * методы pop(), shift() и splice(). 
 
    */ 
 
    exports.ArrayWithParentLink = function(propName, parent){ 
 

 
     /** Выяснение того, является ли ключ корректным индексом массива. 
 
     * Вычисление выполняется в соответствии со спецификацией ECMAScript 6. 
 
     * См. книгу "ECMAScript 6 для разработчиков" Николаса Вирта, стр. 307. 
 
     * */ 
 
     function isArrayIndex(key){ 
 
      const toUint32 = function(value){ 
 
       return Math.floor(Math.abs(Number(value))) % Math.pow(2,32); 
 
      }; 
 

 
      let numericKey = toUint32(key); 
 
      return String(numericKey) == key && numericKey < (Math.pow(2,32)-1); 
 
     }; 
 

 
     /* Редактирование состава вложенного массива будем выполнять через 
 
     * прокси, дабы автоматизировать синхронизацию значения свойства, 
 
     * имя которого было ранее указано в propName при создании экземпляра 
 
     * прокси-массива. */ 
 
     var arrayChangeHandler = { 
 

 
      set: function(target, key, value, receiver) { 
 

 
       /* Если имя свойства является допустимым индексом массива, 
 
       * значит выполняется операция над элементом массива... */ 
 
       if(isArrayIndex(key)){ 
 

 
        value[propName] = parent; 
 

 
        /* Очистить значение свойства (имя которого указано в 
 
        * propName) у заменяемого элемента, если в массиве на него 
 
        * имеется только одна ссылка. В некоторые моменты в массиве 
 
        * может присутствовать одновременно несколько ссылок на 
 
        * один и тот же элемент. Пример такого случая приведён в 
 
        * комментарии ниже.*/ 
 
        if((key in target) && target[key] && 
 
         (propName in target[key])){ 
 

 
         /* Если выполняется ВСТАВКА новых элементов методом 
 
         * Array.prototype.splice(), то происходит добавление к 
 
         * массиву необходимого количества дополнительных ячеек, 
 
         * в которые записываются ссылки на уже существующие 
 
         * элементы массива, а их прежние ячейки переписываются 
 
         * ссылками на вставляемые элементы. Т.е. до того, как 
 
         * произойдёт перезапись, в составе массива временно 
 
         * присутствует ДВЕ ссылки на смещаемый элемент. Если не 
 
         * учесть этот момент, то свойству parent перемещаемого 
 
         * дочернего элемента будет назначено значение null, что 
 
         * будет являться неправильным действием. Чтобы избежать 
 
         * этой ошибки, проверяем количество ссылок на удаляемый 
 
         * объект. Если их более одной, то считаем, что 
 
         * происходит не удаление, но перемещение элемента в 
 
         * массиве: */ 
 
         let linksCount = 0; 
 

 
         for (let i = 0; i < target.length; i++){ 
 

 
          if(target[i] == target[key]) 
 
           linksCount++; 
 
         } 
 

 
         if(1 == linksCount) 
 
          target[key][propName] = null; 
 
        } 
 
       } 
 

 
       return Reflect.set(target, key, value, receiver); 
 
      }, 
 
      deleteProperty: function(target, key) { 
 

 
       /* Если имя свойства является допустимым индексом массива, 
 
       * значит выполняется операция над элементом массива... */ 
 
       if(isArrayIndex(Number(key))){ 
 

 
        /* ВНИМАНИЕ! Прочти комментарий перед аналогичной строкой 
 
        * кода для сеттера set, объявленого выше, дабы понимать, 
 
        * для чего выполняется проверка количества ссылок. */ 
 
        let linksCount = 0; 
 

 
        for (let i = 0; i < target.length; i++){ 
 

 
         if(target[i] == target[key]) 
 
          linksCount++; 
 
        } 
 

 
        if(1 == linksCount) 
 
         target[key][propName] = null; 
 
       } 
 
       return Reflect.deleteProperty(target, key); 
 
      } 
 
     }; 
 
     return new Proxy([], arrayChangeHandler); 
 
    }; 
 

 
})(window.Services = Object.create(null)); 
 

 
QUnit.module('Services.ArrayWithParentLink'); 
 

 
/** Проверка всех элементов прокси-массива. 
 
* 
 
* Это дополнительная проверка, которая используется в тестах, дабы убедиться в 
 
* том, что помимо ожидаемых изменений не произошли изменения неожиданные, 
 
* затрагивающие другие элементы, имеющиеся в прокси-массиве. 
 
* 
 
* array - прокси-массив, подлежащий проверке. 
 
* propName - имя свойства, проверяемого у элементов прокси-массива. 
 
* value - ожидаемое значение свойства, имя которого указано в propName. 
 
* assert - ссылка на объект assert фреймворка QUnit. 
 
* */ 
 
function checkAllItems(array, propName, value, assert) { 
 
    // Проверим, что все элементы массива имеют правильное значение подлежащего 
 
    // мониторингу свойства. 
 
    let rightValues = true; 
 

 
    for(let i = 0; i < array.length; i++){ 
 
     if(array[i][propName] != value){ 
 
      rightValues = false; 
 
      break; 
 
     } 
 
    } 
 

 
    assert.ok(rightValues, "Каждый элемент в прокси-массиве содержат правильное" 
 
    + " значение в свойстве, подлежащем мониторингу."); 
 
} 
 

 
QUnit.test("Новый прокси-массив является обёрткой над экземпляром Array.", 
 
    function(assert) { 
 

 
    const root = Object.create(null); 
 
    const name = 'parent'; 
 
    root.items = new window.Services.ArrayWithParentLink(name, root); 
 

 
    assert.ok(root.items instanceof Array); 
 
}); 
 

 
QUnit.test("Длина нового прокси-массива равна нулю.", function(assert) { 
 

 
    const root = Object.create(null); 
 
    const name = 'parent'; 
 
    root.items = new window.Services.ArrayWithParentLink(name, root); 
 

 
    assert.equal(root.items.length, 0); 
 
}); 
 

 
QUnit.test("В новом элементе, добавленном в прокси-массив при помощи метода" + 
 
    "'push()', автоматически появляется подлежащее мониторингу свойство, " + 
 
    "инициализированное правильным значением. ", 
 
    function(assert) { 
 

 
    const root = Object.create(null); 
 
    const name = 'parent'; 
 
    root.items = new window.Services.ArrayWithParentLink(name, root); 
 

 
    root.items.push({}); 
 

 
    assert.equal(root.items[0][name], root); 
 

 
    // Дополнительная проверка 
 
    checkAllItems(root.items, name, root, assert); 
 
}); 
 

 
QUnit.test("В новом элементе, добавленном в прокси-массив при помощи индекса" + 
 
    " [index], автоматически появляется подлежащее мониторингу свойство, " + 
 
    "инициализированное правильным значением. ", 
 
    function(assert) { 
 

 
     const root = Object.create(null); 
 
     const name = 'parent'; 
 
     root.items = new window.Services.ArrayWithParentLink(name, root); 
 

 
     root.items[0] = {}; 
 

 
     assert.equal(root.items[0][name], root); 
 

 
     // Дополнительная проверка 
 
     checkAllItems(root.items, name, root, assert); 
 
    }); 
 

 
QUnit.test("Метод 'pop()' прокси-массива успешно удаляет последний элемент.", 
 
    function(assert){ 
 

 
     const root = Object.create(null); 
 
     const name = 'parent'; 
 
     root.items = new window.Services.ArrayWithParentLink(name, root); 
 
     const count = 5; 
 

 
     for(let i = 0; i < count; i++){ 
 
      root.items[i] = {}; 
 
     } 
 

 
     const item = root.items[count - 1]; 
 
     const item2 = root.items.pop(); 
 

 
     assert.equal(item2, item); 
 
     assert.equal(root.items.length, count - 1); 
 

 
     // Дополнительная проверка 
 
     checkAllItems(root.items, name, root, assert); 
 
}); 
 

 
QUnit.test("У удаляемого методом 'pop()' объекта, значение подлежащего " 
 
    + " мониторингу свойства автоматически становится равным null, если в " + 
 
    "массиве имелась только одна ссылка на удаляемый объект.", 
 
    function(assert){ 
 

 
     const root = Object.create(null); 
 
     const name = 'parent'; 
 
     root.items = new window.Services.ArrayWithParentLink(name, root); 
 
     const count = 5; 
 

 
     for(let i = 0; i < count; i++){ 
 
      root.items[i] = {}; 
 
     } 
 

 
     let item = root.items.pop(); 
 

 
     assert.equal(item[name], null); 
 

 
     // Дополнительная проверка 
 
     checkAllItems(root.items, name, root, assert); 
 
    }); 
 

 
QUnit.test("У удаляемого методом 'pop()' объекта, значение подлежащего " 
 
    + " мониторингу свойства не изменяется на null, если в " + 
 
    "массиве имелось более одной ссылки на удаляемый объект.", 
 
    function(assert){ 
 

 
     const root = Object.create(null); 
 
     const name = 'parent'; 
 
     root.items = new window.Services.ArrayWithParentLink(name, root); 
 
     const count = 2; 
 
     const obj = {}; 
 

 
     for(let i = 0; i < count; i++){ 
 
      root.items[i] = obj; 
 
     } 
 

 
     let item = root.items.pop(); 
 

 
     assert.equal(item[name], root); 
 

 
     // Дополнительная проверка 
 
     checkAllItems(root.items, name, root, assert); 
 
    }); 
 

 
QUnit.test("В новом элементе, добавленном в прокси-массив при помощи метода" + 
 
    " 'unshift()', автоматически появляется подлежащее мониторингу свойство, " + 
 
    "инициализированное правильным значением. ", 
 
    function(assert) { 
 

 
     const root = Object.create(null); 
 
     const name = 'parent'; 
 
     root.items = new window.Services.ArrayWithParentLink(name, root); 
 

 
     root.items.unshift({}); 
 

 
     assert.equal(root.items[0][name], root); 
 

 
     // Дополнительная проверка 
 
     checkAllItems(root.items, name, root, assert); 
 
    }); 
 

 
QUnit.test("Метод 'shift()' успешно удаляет первый элемент прокси-массива.", 
 
    function(assert){ 
 

 
     const root = Object.create(null); 
 
     const name = 'parent'; 
 
     root.items = new window.Services.ArrayWithParentLink(name, root); 
 
     const count = 5; 
 

 
     for(let i = 0; i < count; i++){ 
 
      root.items[i] = {}; 
 
     } 
 

 
     const item = root.items[0]; 
 
     const item2 = root.items.shift(); 
 

 
     assert.equal(item2, item); 
 
     assert.equal(root.items.length, count - 1); 
 

 
     // Дополнительная проверка 
 
     checkAllItems(root.items, name, root, assert); 
 
    }); 
 

 
QUnit.test("У удаляемого методом 'shift()' объекта, значение подлежащего " 
 
    + " мониторингу свойства автоматически становится равным null, если в " + 
 
    "массиве имелась только одна ссылка на удаляемый объект.", 
 
    function(assert){ 
 

 
     const root = Object.create(null); 
 
     const name = 'parent'; 
 
     root.items = new window.Services.ArrayWithParentLink(name, root); 
 
     const count = 5; 
 

 
     for(let i = 0; i < count; i++){ 
 
      root.items[i] = {}; 
 
     } 
 

 
     let item = root.items.shift(); 
 

 
     assert.equal(item[name], null); 
 

 
     // Дополнительная проверка 
 
     checkAllItems(root.items, name, root, assert); 
 
    }); 
 

 
QUnit.test("У удаляемого методом 'shift()' объекта, значение подлежащего " 
 
    + " мониторингу свойства не становится равным null, если в " + 
 
    "массиве имелось более одной ссылки на удаляемый объект.", 
 
    function(assert){ 
 

 
     const root = Object.create(null); 
 
     const name = 'parent'; 
 
     root.items = new window.Services.ArrayWithParentLink(name, root); 
 
     const count = 2; 
 
     const obj = {}; 
 

 
     for(let i = 0; i < count; i++){ 
 
      root.items[i] = obj; 
 
     } 
 

 
     let item = root.items.shift(); 
 

 
     assert.equal(item[name], root); 
 

 
     // Дополнительная проверка 
 
     checkAllItems(root.items, name, root, assert); 
 
    }); 
 

 
QUnit.test("Значение подлежащего мониторингу свойства у элемента, удаляемого " + 
 
    "посредством уменьшения значения свойства length массива, становится " 
 
    + "равным null, если в массиве имелось не более одной ссылки на удаляемый " 
 
    + "элемент", function(assert){ 
 

 
    const root = Object.create(null); 
 
    const name = 'parent'; 
 
    root.items = new window.Services.ArrayWithParentLink(name, root); 
 
    const count = 2; 
 

 
    for(let i = 0; i < count; i++){ 
 
     root.items[i] = {}; 
 
    } 
 

 
    let item = root.items[count - 1]; 
 
    root.items.length = 1; 
 

 
    assert.equal(item[name], null); 
 

 
    // Дополнительная проверка 
 
    checkAllItems(root.items, name, root, assert); 
 
}); 
 

 
QUnit.test("Значение подлежащего мониторингу свойства у элемента, удаляемого " + 
 
    "посредством уменьшения значения свойства length массива, не становится " 
 
    + "равным null, если в массиве имелось более одной ссылки на удаляемый " 
 
    + "элемент", function(assert){ 
 

 
    const root = Object.create(null); 
 
    const name = 'parent'; 
 
    root.items = new window.Services.ArrayWithParentLink(name, root); 
 
    const count = 2; 
 
    const obj = {}; 
 

 
    for(let i = 0; i < count; i++){ 
 
     root.items[i] = obj; 
 
    } 
 

 
    let item = root.items[count - 1]; 
 
    root.items.length = 1; 
 

 
    assert.equal(item[name], root); 
 

 
    // Дополнительная проверка 
 
    checkAllItems(root.items, name, root, assert); 
 
}); 
 

 
QUnit.test("Значение подлежащего мониторингу свойства у элемента, удаляемого " + 
 
    "посредством метода 'splice()', становится равным null, если в массиве " + 
 
    "имелось не более одной ссылки на удаляемый элемент", function(assert){ 
 

 
    const root = Object.create(null); 
 
    const name = 'parent'; 
 
    root.items = new window.Services.ArrayWithParentLink(name, root); 
 
    const count = 3; 
 

 
    for(let i = 0; i < count; i++){ 
 
     root.items[i] = {}; 
 
    } 
 

 
    const index = 1; 
 

 
    let item = root.items[index]; 
 
    root.items.splice(index,1); 
 

 
    assert.equal(item[name], null); 
 

 
    // Дополнительная проверка 
 
    checkAllItems(root.items, name, root, assert); 
 
}); 
 

 
QUnit.test("Значение подлежащего мониторингу свойства у элемента, удаляемого " + 
 
    "посредством метода 'splice()', не становится равным null, если в массиве " 
 
    + "имелось более одной ссылки на удаляемый элемент", function(assert){ 
 

 
    const root = Object.create(null); 
 
    const name = 'parent'; 
 
    root.items = new window.Services.ArrayWithParentLink(name, root); 
 
    const count = 3; 
 
    const obj = {}; 
 

 
    for(let i = 0; i < count; i++){ 
 
     root.items[i] = obj; 
 
    } 
 

 
    const index = 1; 
 

 
    let item = root.items[index]; 
 
    root.items.splice(index,1); 
 

 
    assert.equal(item[name], root); 
 

 
    // Дополнительная проверка 
 
    checkAllItems(root.items, name, root, assert); 
 
}); 
 

 
QUnit.test("Значение подлежащего мониторингу свойства у элемента, добавляемого " 
 
    + "в прокси-массив посредством метода 'splice()', инициализируется " + 
 
    "правильным значением.", function(assert){ 
 

 
    const root = Object.create(null); 
 
    const name = 'parent'; 
 
    root.items = new window.Services.ArrayWithParentLink(name, root); 
 
    const count = 3; 
 

 
    for(let i = 0; i < count; i++){ 
 
     root.items[i] = {}; 
 
    } 
 

 
    const index = 1; 
 
    const obj = {}; 
 
    root.items.splice(index,0,obj); 
 

 
    assert.equal(obj[name], root); 
 

 
    // Дополнительная проверка 
 
    checkAllItems(root.items, name, root, assert); 
 
});
/*! 
 
* QUnit 2.3.3 
 
* https://qunitjs.com/ 
 
* 
 
* Copyright jQuery Foundation and other contributors 
 
* Released under the MIT license 
 
* https://jquery.org/license 
 
* 
 
* Date: 2017-06-02T14:07Z 
 
*/ 
 

 
/** Font Family and Sizes */ 
 

 
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult { 
 
\t font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 
 
} 
 

 
#qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 
 
#qunit-tests { font-size: smaller; } 
 

 

 
/** Resets */ 
 

 
#qunit-tests, #qunit-header, #qunit-banner, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { 
 
\t margin: 0; 
 
\t padding: 0; 
 
} 
 

 

 
/** Header (excluding toolbar) */ 
 

 
#qunit-header { 
 
\t padding: 0.5em 0 0.5em 1em; 
 

 
\t color: #8699A4; 
 
\t background-color: #0D3349; 
 

 
\t font-size: 1.5em; 
 
\t line-height: 1em; 
 
\t font-weight: 400; 
 

 
\t border-radius: 5px 5px 0 0; 
 
} 
 

 
#qunit-header a { 
 
\t text-decoration: none; 
 
\t color: #C2CCD1; 
 
} 
 

 
#qunit-header a:hover, 
 
#qunit-header a:focus { 
 
\t color: #FFF; 
 
} 
 

 
#qunit-banner { 
 
\t height: 5px; 
 
} 
 

 
#qunit-filteredTest { 
 
\t padding: 0.5em 1em 0.5em 1em; 
 
\t color: #366097; 
 
\t background-color: #F4FF77; 
 
} 
 

 
#qunit-userAgent { 
 
\t padding: 0.5em 1em 0.5em 1em; 
 
\t color: #FFF; 
 
\t background-color: #2B81AF; 
 
\t text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 
 
} 
 

 

 
/** Toolbar */ 
 

 
#qunit-testrunner-toolbar { 
 
\t padding: 0.5em 1em 0.5em 1em; 
 
\t color: #5E740B; 
 
\t background-color: #EEE; 
 
} 
 

 
#qunit-testrunner-toolbar .clearfix { 
 
\t height: 0; 
 
\t clear: both; 
 
} 
 

 
#qunit-testrunner-toolbar label { 
 
\t display: inline-block; 
 
} 
 

 
#qunit-testrunner-toolbar input[type=checkbox], 
 
#qunit-testrunner-toolbar input[type=radio] { 
 
\t margin: 3px; 
 
\t vertical-align: -2px; 
 
} 
 

 
#qunit-testrunner-toolbar input[type=text] { 
 
\t box-sizing: border-box; 
 
\t height: 1.6em; 
 
} 
 

 
.qunit-url-config, 
 
.qunit-filter, 
 
#qunit-modulefilter { 
 
\t display: inline-block; 
 
\t line-height: 2.1em; 
 
} 
 

 
.qunit-filter, 
 
#qunit-modulefilter { 
 
\t float: right; 
 
\t position: relative; 
 
\t margin-left: 1em; 
 
} 
 

 
.qunit-url-config label { 
 
\t margin-right: 0.5em; 
 
} 
 

 
#qunit-modulefilter-search { 
 
\t box-sizing: border-box; 
 
\t width: 400px; 
 
} 
 

 
#qunit-modulefilter-search-container:after { 
 
\t position: absolute; 
 
\t right: 0.3em; 
 
\t content: "\25bc"; 
 
\t color: black; 
 
} 
 

 
#qunit-modulefilter-dropdown { 
 
\t /* align with #qunit-modulefilter-search */ 
 
\t box-sizing: border-box; 
 
\t width: 400px; 
 
\t position: absolute; 
 
\t right: 0; 
 
\t top: 50%; 
 
\t margin-top: 0.8em; 
 

 
\t border: 1px solid #D3D3D3; 
 
\t border-top: none; 
 
\t border-radius: 0 0 .25em .25em; 
 
\t color: #000; 
 
\t background-color: #F5F5F5; 
 
\t z-index: 99; 
 
} 
 

 
#qunit-modulefilter-dropdown a { 
 
\t color: inherit; 
 
\t text-decoration: none; 
 
} 
 

 
#qunit-modulefilter-dropdown .clickable.checked { 
 
\t font-weight: bold; 
 
\t color: #000; 
 
\t background-color: #D2E0E6; 
 
} 
 

 
#qunit-modulefilter-dropdown .clickable:hover { 
 
\t color: #FFF; 
 
\t background-color: #0D3349; 
 
} 
 

 
#qunit-modulefilter-actions { 
 
\t display: block; 
 
\t overflow: auto; 
 

 
\t /* align with #qunit-modulefilter-dropdown-list */ 
 
\t font: smaller/1.5em sans-serif; 
 
} 
 

 
#qunit-modulefilter-dropdown #qunit-modulefilter-actions > * { 
 
\t box-sizing: border-box; 
 
\t max-height: 2.8em; 
 
\t display: block; 
 
\t padding: 0.4em; 
 
} 
 

 
#qunit-modulefilter-dropdown #qunit-modulefilter-actions > button { 
 
\t float: right; 
 
\t font: inherit; 
 
} 
 

 
#qunit-modulefilter-dropdown #qunit-modulefilter-actions > :last-child { 
 
\t /* insert padding to align with checkbox margins */ 
 
\t padding-left: 3px; 
 
} 
 

 
#qunit-modulefilter-dropdown-list { 
 
\t max-height: 200px; 
 
\t overflow-y: auto; 
 
\t margin: 0; 
 
\t border-top: 2px groove threedhighlight; 
 
\t padding: 0.4em 0 0; 
 
\t font: smaller/1.5em sans-serif; 
 
} 
 

 
#qunit-modulefilter-dropdown-list li { 
 
\t white-space: nowrap; 
 
\t overflow: hidden; 
 
\t text-overflow: ellipsis; 
 
} 
 

 
#qunit-modulefilter-dropdown-list .clickable { 
 
\t display: block; 
 
\t padding-left: 0.15em; 
 
} 
 

 

 
/** Tests: Pass/Fail */ 
 

 
#qunit-tests { 
 
\t list-style-position: inside; 
 
} 
 

 
#qunit-tests li { 
 
\t padding: 0.4em 1em 0.4em 1em; 
 
\t border-bottom: 1px solid #FFF; 
 
\t list-style-position: inside; 
 
} 
 

 
#qunit-tests > li { 
 
\t display: none; 
 
} 
 

 
#qunit-tests li.running, 
 
#qunit-tests li.pass, 
 
#qunit-tests li.fail, 
 
#qunit-tests li.skipped, 
 
#qunit-tests li.aborted { 
 
\t display: list-item; 
 
} 
 

 
#qunit-tests.hidepass { 
 
\t position: relative; 
 
} 
 

 
#qunit-tests.hidepass li.running, 
 
#qunit-tests.hidepass li.pass:not(.todo) { 
 
\t visibility: hidden; 
 
\t position: absolute; 
 
\t width: 0; 
 
\t height: 0; 
 
\t padding: 0; 
 
\t border: 0; 
 
\t margin: 0; 
 
} 
 

 
#qunit-tests li strong { 
 
\t cursor: pointer; 
 
} 
 

 
#qunit-tests li.skipped strong { 
 
\t cursor: default; 
 
} 
 

 
#qunit-tests li a { 
 
\t padding: 0.5em; 
 
\t color: #C2CCD1; 
 
\t text-decoration: none; 
 
} 
 

 
#qunit-tests li p a { 
 
\t padding: 0.25em; 
 
\t color: #6B6464; 
 
} 
 
#qunit-tests li a:hover, 
 
#qunit-tests li a:focus { 
 
\t color: #000; 
 
} 
 

 
#qunit-tests li .runtime { 
 
\t float: right; 
 
\t font-size: smaller; 
 
} 
 

 
.qunit-assert-list { 
 
\t margin-top: 0.5em; 
 
\t padding: 0.5em; 
 

 
\t background-color: #FFF; 
 

 
\t border-radius: 5px; 
 
} 
 

 
.qunit-source { 
 
\t margin: 0.6em 0 0.3em; 
 
} 
 

 
.qunit-collapsed { 
 
\t display: none; 
 
} 
 

 
#qunit-tests table { 
 
\t border-collapse: collapse; 
 
\t margin-top: 0.2em; 
 
} 
 

 
#qunit-tests th { 
 
\t text-align: right; 
 
\t vertical-align: top; 
 
\t padding: 0 0.5em 0 0; 
 
} 
 

 
#qunit-tests td { 
 
\t vertical-align: top; 
 
} 
 

 
#qunit-tests pre { 
 
\t margin: 0; 
 
\t white-space: pre-wrap; 
 
\t word-wrap: break-word; 
 
} 
 

 
#qunit-tests del { 
 
\t color: #374E0C; 
 
\t background-color: #E0F2BE; 
 
\t text-decoration: none; 
 
} 
 

 
#qunit-tests ins { 
 
\t color: #500; 
 
\t background-color: #FFCACA; 
 
\t text-decoration: none; 
 
} 
 

 
/*** Test Counts */ 
 

 
#qunit-tests b.counts      { color: #000; } 
 
#qunit-tests b.passed      { color: #5E740B; } 
 
#qunit-tests b.failed      { color: #710909; } 
 

 
#qunit-tests li li { 
 
\t padding: 5px; 
 
\t background-color: #FFF; 
 
\t border-bottom: none; 
 
\t list-style-position: inside; 
 
} 
 

 
/*** Passing Styles */ 
 

 
#qunit-tests li li.pass { 
 
\t color: #3C510C; 
 
\t background-color: #FFF; 
 
\t border-left: 10px solid #C6E746; 
 
} 
 

 
#qunit-tests .pass       { color: #528CE0; background-color: #D2E0E6; } 
 
#qunit-tests .pass .test-name    { color: #366097; } 
 

 
#qunit-tests .pass .test-actual, 
 
#qunit-tests .pass .test-expected   { color: #999; } 
 

 
#qunit-banner.qunit-pass     { background-color: #C6E746; } 
 

 
/*** Failing Styles */ 
 

 
#qunit-tests li li.fail { 
 
\t color: #710909; 
 
\t background-color: #FFF; 
 
\t border-left: 10px solid #EE5757; 
 
\t white-space: pre; 
 
} 
 

 
#qunit-tests > li:last-child { 
 
\t border-radius: 0 0 5px 5px; 
 
} 
 

 
#qunit-tests .fail       { color: #000; background-color: #EE5757; } 
 
#qunit-tests .fail .test-name, 
 
#qunit-tests .fail .module-name    { color: #000; } 
 

 
#qunit-tests .fail .test-actual    { color: #EE5757; } 
 
#qunit-tests .fail .test-expected   { color: #008000; } 
 

 
#qunit-banner.qunit-fail     { background-color: #EE5757; } 
 

 

 
/*** Aborted tests */ 
 
#qunit-tests .aborted { color: #000; background-color: orange; } 
 
/*** Skipped tests */ 
 

 
#qunit-tests .skipped { 
 
\t background-color: #EBECE9; 
 
} 
 

 
#qunit-tests .qunit-todo-label, 
 
#qunit-tests .qunit-skipped-label { 
 
\t background-color: #F4FF77; 
 
\t display: inline-block; 
 
\t font-style: normal; 
 
\t color: #366097; 
 
\t line-height: 1.8em; 
 
\t padding: 0 0.5em; 
 
\t margin: -0.4em 0.4em -0.4em 0; 
 
} 
 

 
#qunit-tests .qunit-todo-label { 
 
\t background-color: #EEE; 
 
} 
 

 
/** Result */ 
 

 
#qunit-testresult { 
 
\t color: #2B81AF; 
 
\t background-color: #D2E0E6; 
 

 
\t border-bottom: 1px solid #FFF; 
 
} 
 
#qunit-testresult .clearfix { 
 
\t height: 0; 
 
\t clear: both; 
 
} 
 
#qunit-testresult .module-name { 
 
\t font-weight: 700; 
 
} 
 
#qunit-testresult-display { 
 
\t padding: 0.5em 1em 0.5em 1em; 
 
\t width: 85%; 
 
\t float:left; 
 
} 
 
#qunit-testresult-controls { 
 
\t padding: 0.5em 1em 0.5em 1em; 
 
    width: 10%; 
 
\t float:left; 
 
} 
 

 
/** Fixture */ 
 

 
#qunit-fixture { 
 
\t position: absolute; 
 
\t top: -10000px; 
 
\t left: -10000px; 
 
\t width: 1000px; 
 
\t height: 1000px; 
 
}
<div id="qunit"></div> 
 
<div id="qunit-fixture"></div> 
 
<script src="https://code.jquery.com/qunit/qunit-2.3.3.js"></script>

UPD

Here是我固定的变种。有用。

+0

没有挖掘到你的代码,也许某事像如果(键===“长度”){为( var'='; i

+0

这个属性在做'shift()'或'splice()'删除时也会改变。即删除的项目并不总是在数组的尾部,但是在每种情况下都会发生更改'length'属性的事件。 –

+0

适用于拼接和移位的另一个规则。 –

你可以听长度性质的变化,并手动删除元素:

set(key,value,target){ 
if(key==="length"){ 
    for(var i=0;i<value-target[key];i++){ 
    var removed=target.pop(); 
    ///change removed... 
    } 
return; 
} 
//change target[key] 
} 
+0

我明白你的想法,但没有使用弹出来避免循环。 –

+0

这是我的固定版本:https://jsfiddle.net/Andrey_Bushman/e8qvwa7v/1/ –