Search code examples
javascriptarrayses6-proxy

How to get the deleted item (for post-handling by proxy) if `length` property of array was decreased directly?


JavaScript (ECMAScript 6)

I wrote a proxy for Array item. I do it for solving the problem... I show this problem on the base of such example:

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

I want each item of children array will update own parent property when the item would be added into that array or removed from it.

Below is located my decision with unit tests. I appologize for Russian comments (you can translate with google translate those that are of interest to you).

Also I placed my code on jsFiddle here (this resource shows code row numbers).

You can see one of my tests is failed... This is happen when I remove item by direct decreasing the length property of array. I see deleteProperty handler doesn't catch this event.

I.e. I my problem is exists for such variant of item deleting:

myarray.length -= 1;

At this case last item will be removed from array but its parent property will not be updated to null value.

How can I catch such case by the proxy?

Thank you.

/* 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 {
	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 {
	margin: 0;
	padding: 0;
}


/** Header (excluding toolbar) */

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

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

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

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

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

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

#qunit-banner {
	height: 5px;
}

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

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


/** Toolbar */

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


/** Tests: Pass/Fail */

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

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

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

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

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

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

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

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

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

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

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

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

	background-color: #FFF;

	border-radius: 5px;
}

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

.qunit-collapsed {
	display: none;
}

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

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

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

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

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

#qunit-tests ins {
	color: #500;
	background-color: #FFCACA;
	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 {
	padding: 5px;
	background-color: #FFF;
	border-bottom: none;
	list-style-position: inside;
}

/*** Passing Styles */

#qunit-tests li li.pass {
	color: #3C510C;
	background-color: #FFF;
	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 {
	color: #710909;
	background-color: #FFF;
	border-left: 10px solid #EE5757;
	white-space: pre;
}

#qunit-tests > li:last-child {
	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 {
	background-color: #EBECE9;
}

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

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

/** Result */

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

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

/** Fixture */

#qunit-fixture {
	position: absolute;
	top: -10000px;
	left: -10000px;
	width: 1000px;
	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 is my fixed variant. It works.


Solution

  • You could listen to changes of the length property, and remove the elements manually:

    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]
    }