Почему серия: не используйте удаление, если в этом нет необходимости

внешний интерфейс JavaScript
Почему серия: не используйте удаление, если в этом нет необходимости

Это 4-й день моего участия в Gengwen Challenge, смотрите подробности мероприятия:Обновить вызов

предисловие

Что, что ты собираешься мне сказать, удалить?
Здесь все могут смеяться, это же не один оператор, да еще и используется отдельно.
На этот раз лучше посмотреть исходный код реакции и исходный код vue.
Я сказал: я посмотрю исходный код реакции, но это тоже очень интересно.

удалить, как много вы знаете

Вот несколько вопросов

  1. какое значение возвращает удаление
  2. Какое значение возвращает функция удаления для удаления несуществующего свойства?
  3. Можно ли удалить свойства на прототипе
  4. Может удалять переменные
  5. Удалить определенные данные в массиве, изменится ли длина массива?
  6. Какие свойства нельзя удалить

Давайте посмотрим на это.

1. Какое значение возвращает удаление

var a = {
    p1: 1
}

console.log(delete a.p1); // true
console.log(delete a.p2); // true

console.log(delete window); // false

Из приведенного выше видно, что удаление возвращает логическое значение.Если удаление прошло успешно, оно возвращает true, что включает в себя удаление несуществующего свойства. Возвращает false при ошибке удаления.

2. Какое значение возвращает функция удаления для удаления несуществующего свойства?

Как видно из первой демонстрации, возвращаемое значение удаления несуществующего свойства также равноtrue

3. Можете ли вы удалить свойства на прототипе

var a = {
    p1: 10
}

a.__proto__ = {
    p2: 20
}

console.log("a.p2:before", a.p2);  // 20
console.log(delete a.p2);   // true
console.log("a.p2:after", a.p2); // 20

Мой код выше предназначен для избежания проблем, вам лучше не использовать его напрямую__proto__. Ну напишу более серьезный пример.

function Foo(){
    this.name = "name";
}
Foo.prototype.age = 20;

var foo = new Foo();

console.log("foo.p2:before", foo.age);  // 20
console.log(delete foo.age);   // true
console.log("foo.p2:after", foo.age); // 20

Я все сказал, неважно, как это написать, ты просто не веришь, а результат все тот же. Вы не можете удалить свойства прототипов.

4. Можно ли удалить переменные?

var a = 10;
console.log(delete a);  // false
console.log("a", a); // 10

Очевидно, что его нельзя удалить, если заменить его функцией, результат тот же.

5. При удалении определенных данных в массиве изменится ли длина массива?

var arr = [10,2,16];
console.log("length:before", arr.length);  // 3
console.log("delete", delete arr[1]);   // true
console.log("length:after",arr.length);  // 3
console.log("arr", arr); //  [10, empty, 16]

delete удаляет определенные данные из данных и не приводит к сокращению длины массива. Соответствующее значение данных станетempty, а не undefined или null. означает неинициализированный, вы используетеnew Array(2)получите[empty × 2]. Это все одно значение.

Здесь мы переходим кemptyРасширьте его немного.

var arr = [0];
arr[10] = 10;

arr.forEach(v=>console.log(v)); // 0 ,10

for(let p in arr){
    console.log(arr[p]);  // 0, 10
}

for(let p of arr){
    console.log(arr[p]);  // 0 ,undefined x 9, 10
}

forEachа такжеinНичего не делает с неинициализированными значениями. Подробнее см.

No operation for uninitialized values (sparse arrays)

6. Какие свойства нельзя удалить

1. Переменные, такие как var const let, глобальные переменные.

delete window  // false
var a;
delete a; // false

// 有意思的delete this
function a (){    
    this.a = 333;
    console.log("delete this:" , delete this);  // true
    console.log("a", this.a, this); // 333, {a:333}
}
a.call({});

И наоборот, глобальные переменные, к которым не применяются var, let или const, могут быть удалены.

g_novar = 100;
g_novar_fun = ()=>{};

(function () {
   g_novar_2 = 3; 
}())

delete g_novar;
delete g_novar_fun;
delete g_novar_2;

typeof g_novar;  // undefined
typeof g_novar_fun; // undefined
typeof g_novar_2;   // undefined

2. Свойства, чье свойство данных configurable равно false

Исключение будет вызвано непосредственно, если для параметра удаления configuable установлено значение false в строгом режиме ES5.


// 内置document, location等
Object.getOwnPropertyDescriptor(window, "document");// { configurable: false }
console.log("delete", delete window.document); // false
console.log("delete", delete window.location); // false

// 数组长度
var arr = [];
Object.getOwnPropertyDescriptor(arr, "length");// { configurable: false }
console.log("delete", delete arr.length); // false

// 函数长度
function fn(){};
Object.getOwnPropertyDescriptor(fn, "length");// { configurable: false }
console.log("delete", delete fn.length); // false


// 各种内置原型
Object.getOwnPropertyDescriptor(Object, "prototype") // { configurable: false }
console.log("delete", delete Object.prototype); // false


// 内置Math的函数
Object.getOwnPropertyDescriptor(Math, "PI")  // { configurable: false }
console.log("delete", delete Math.PI); // false


// https://www.cnblogs.com/snandy/archive/2013/03/06/2944815.html
// 有提到正则对象的属性(source、global、ignoreCase、multiline、lastIndex)delete 返回 false
// 实际测试结果,删除返回true,但是没删除掉
var reg = /.*/;
Object.getOwnPropertyDescriptor(reg, "source") // undefined
console.log("delete", delete reg.source); // true
console.log("reg.source", reg.source); // .*
console.log("reg prototype source", reg.__proto__source); // "(?:)

delete reg.lastIndex // false
delete reg.global // true
delete reg.ignoreCase // true
delete reg.multiline // true

3. Свойства на прототипе

4. Функциональные параметры

function delP(){
    console.log("delete", delete arguments);  // false
    console.log("arguments", arguments);  //  0: 1
}

delP(1);

5. Некоторые константы (NaN, Infinity, undefined)

delete NaN; // false
delete Infinity; // false
delete undefined; // false

6. Объявление функции

function fn() {}
delete fn;
console.log(fn.toString()); // function fn() {}

подробнее

ECMA-262_5th_edition_december_2009.pdf ECMA-262_3rd_edition_december_1999.pdf58 страницОбъекты, которые нельзя удалить с помощью оператора удаления в JavaScript

Мы можем посмотреть на определение ES3, определение ES5 изменилось.

The delete Operator
The production UnaryExpression : delete UnaryExpression is evaluated as follows:
1. Evaluate UnaryExpression.
2. If Type(Result(1)) is not Reference, return true.
3. Call GetBase(Result(1)).
4. Call GetPropertyName(Result(1)).
5. Call the [[Delete]] method on Result(3), providing Result(4) as the property name to delete.
6. Return Result(5).

Я просто перевожу, может быть не правильно:

  1. выполнять унарные выражения
  2. Возвращает true, если на первый шаг не ссылаются
    console.log(delete xxxxxxxxxx)   //true
    console.log(delete "a")  // true
    console.log(delete {a:1})  // true
    console.log(delete 1) // true
  1. получить объект
  2. взять имя атрибута
  3. Используйте имя атрибута, полученное на четвертом шаге, чтобы удалить результат, возвращенный на третьем шаге.
  4. Вернуть результат пятого шага.

Результатом(1) здесь не должны быть сами данные, это похоже на адрес ссылки.

Резюме

  1. delete возвращает false, он не должен быть удален успешно
  2. удалить возвращает true, не обязательно удалить успех

Поэтому delete возвращает true, лучше проверить это самостоятельно. Надежный.

Ну, я отвлекся? Сегодняшняя тема не в том, чтобы рассказать вам, как использовать удаление, а в том, чтобы использовать удаление с осторожностью.

Сравните производительность

Сначала мы создаем 10 000 объектов, каждый объект имеет в общей сложности 25 свойств от p0 до p24. Затем следуем определенным правиламудалить атрибута такжеустановить свойство на неопределенное.

        function createObjects(counts = 10000) {

            var arr = [];

            for (let i = 0; i < counts; i++) {
                const obj = {};
                // for (let j = 0; j < pcounts; j++) {
                //     obj[`p${j}`] = `value-${i}-${j}`;
                // }
                arr.push({
                    "p0": `value-${i}-0`,
                    "p1": `value-${i}-1`,
                    "p2": `value-${i}-2`,
                    "p3": `value-${i}-3`,
                    "p4": `value-${i}-4`,
                    "p5": `value-${i}-5`,
                    "p6": `value-${i}-6`,
                    "p7": `value-${i}-7`,
                    "p8": `value-${i}-8`,
                    "p9": `value-${i}-9`,
                    "p10": `value-${i}-10`,
                    "p11": `value-${i}-10`,
                    "p12": `value-${i}-10`,
                    "p13": `value-${i}-10`,
                    "p14": `value-${i}-10`,
                    "p15": `value-${i}-10`,
                    "p16": `value-${i}-10`,
                    "p17": `value-${i}-10`,
                    "p18": `value-${i}-10`,
                    "p19": `value-${i}-10`,
                    "p20": `value-${i}-10`,
                    "p21": `value-${i}-10`,
                    "p22": `value-${i}-10`,
                    "p23": `value-${i}-10`,
                    "p24": `value-${i}-10`
                });
            }
            return arr;
        }


        const arr = createObjects();
        const arr2 = createObjects();


        console.time("del");
        for (let i = 0; i < arr.length; i++) {

            const rd = i % 25;
            delete arr[i][`p${rd}`]

        }
        console.timeEnd("del");


        console.time("set");
        for (let i = 0; i < arr2.length; i++) {

            const rd = i % 25;
            arr2[i][`p${rd}`] = undefined;

        }
        console.timeEnd("set");

// del: 31.68994140625 ms
// set: 6.875 ms

// del: 24.43310546875 ms 
// set: 3.7861328125 ms

// del: 79.622802734375 ms
// set: 3.876953125 ms

// del: 53.015869140625 ms
// set: 3.242919921875 ms

// del: 18.84619140625 ms
// set: 3.645751953125 ms

Мы записали около пяти сравнений событий выполнения. Видно, что время удаления нестабильно и производительность значительно ниже.

Пока не будем удивляться. Посмотрите, как я немного изменил код:

    function createObjects(counts = 10000) {

        var arr = [];

        for (let i = 0; i < counts; i++) {
            const obj = {};
            // for (let j = 0; j < pcounts; j++) {
            //     obj[`p${j}`] = `value-${i}-${j}`;
            // }
            arr.push({
                0: `value-${i}-0`,
                1: `value-${i}-1`,
                2: `value-${i}-2`,
                3: `value-${i}-3`,
                4: `value-${i}-4`,
                5: `value-${i}-5`,
                6: `value-${i}-6`,
                7: `value-${i}-7`,
                8: `value-${i}-8`,
                9: `value-${i}-9`,
                10: `value-${i}-10`,
                11: `value-${i}-10`,
                12: `value-${i}-10`,
                13: `value-${i}-10`,
                14: `value-${i}-10`,
                15: `value-${i}-10`,
                16: `value-${i}-10`,
                17: `value-${i}-10`,
                18: `value-${i}-10`,
                19: `value-${i}-10`,
                20: `value-${i}-10`,
                21: `value-${i}-10`,
                22: `value-${i}-10`,
                23: `value-${i}-10`,
                24: `value-${i}-10`
            });
        }
        return arr;
    }

    const arr = createObjects();
    const arr2 = createObjects();

    console.time("del");
    for (let i = 0; i < arr.length; i++) {

        const rd = i % 25;
        delete arr[i][rd]

    }
    console.timeEnd("del");


    console.time("set");
    for (let i = 0; i < arr2.length; i++) {

        const rd = i % 25;
        arr2[i][rd] = undefined;

    }
    console.timeEnd("set");


// del: 1.44189453125 ms
// set: 2.43212890625 ms

// del: 1.737060546875 ms 
// set: 3.10400390625 ms

// del: 1.281005859375 ms
// set: 2.85107421875 ms

// del: 1.338134765625 ms
// set: 1.877197265625 ms

// del: 1.3203125 ms
// set: 2.09912109375 ms

Здесь стиль изменился. del на самом деле быстрее, чем set. . . . . . , а скорость набора практически не изменилась.
Это примерно здесь常规属性а также排序属性Что ж, значение атрибута атрибута сортировки — это число, а значение атрибута значения атрибута — обычный атрибут. Первый случай выше — все обычные атрибуты, а второй случай — все атрибуты сортировки.

Общие свойства (свойства) и свойства сортировки (элемент)

Приведенный выше код не сильно меняется, то есть имя атрибута меняется сp0Формат изменен на0Формат.p0Формальные регулярные свойства,0является свойством сортировки.

Числовые свойства в объектах называются свойствами сортировки, а в V8 — элементами.
Строковые свойства называются обычными свойствами, а в V8 называются свойствами.

в ECMAScriptСпецификация определяет, что числовые свойства должны быть отсортированы в порядке возрастания в соответствии с размером значения индекса, а строковые свойства должны быть отсортированы в порядке возрастания в соответствии с порядком их создания.

   function Foo() {
            this[3] = '3'        
            this["B"] = 'B'
            this[2] = '2'
            this[1] = '1'
            this["A"] = 'A'
            this["C"] = 'C'
        }

        var foo = new Foo()

        for (key in foo) {
            console.log(`key:${key}  value:${foo[key]}`)
        }

// key:1  value:1
// key:2  value:2
// key:3  value:3
// key:B  value:B
// key:A  value:A
// key:C  value:C

Порядок наших настроек числового атрибута 3 -> 2 -> 1, а при фактическом обходе вывода это 1-> 2-> 3; Наш порядок установки строковых свойств — B->A->C, а фактический вывод — B->A->C.

На данный момент мы знаем два наших каштана, один из которых использует числовой атрибут (атрибут сортировки), а другой использует строковый атрибут (обычный атрибут).

Пауза:Есть поговорка, что атрибут обратного удаления не приведет к изменению карты. Хочешь попробовать.

Говоря об этом, все скажут, даже если это так. Имеет какое-то отношение к скорости?

Это еще не очевидно, мы должны предложить новую концепцию, скрытый класс.

скрытый класс

Графический Google V8 описывается так:

В процессе выполнения JavaScript V8 предполагает, что объекты в JavaScript являются статическими, в частности, V8 делает следующие два допущения для каждого объекта:

  • После создания объекта новые свойства добавляться не будут;
  • Свойства не удаляются после создания объекта.

В частности, V8 создает скрытый класс для каждого объекта.Скрытый класс объекта записывает некоторую базовую информацию о макете объекта, включая следующие два момента:

  • Все свойства, содержащиеся в объекте;
  • Смещение каждого свойства относительно объекта.

После наличия скрытого класса, когда V8 получает доступ к атрибуту в объекте, он сначала переходит к скрытому классу, чтобы найти смещение атрибута относительно его объекта.Со смещением и типом атрибута V8 вы можете напрямую получить соответствующий атрибут значение из памяти без выполнения ряда процессов поиска, что значительно повышает эффективность поиска объектов V8.

Несколько объектов имеют общий скрытый класс

Затем, при каких обстоятельствах форма двух объектов одинакова, должны быть выполнены следующие два пункта:

  • то же имя свойства;
  • равное количество свойств

Во время выполнения,Форма объекта может быть изменена.Если форма объекта изменится, скрытый класс также изменится, а это означает, что V8 должен перестроить новый скрытый класс для вновь измененного объекта., что приводит к большим накладным расходам для эффективности выполнения V8.

Когда вы увидите красную часть, у вас почти должен быть ответ.

Как просмотреть скрытые классы? Используя инструменты разработчика Chrome, функция моментального снимка кучи модуля памяти:

.

Затем найдите соответствующий конструктор, напримерFoo

Здесь для облегчения поиска мы просто оборачиваем код: Сначала посмотрите на общие свойства:

Процесс проверки прост:

  1. Сначала создайте два экземпляра Foo, сделайте снимок
  2. Выполните операцию удаления, а затем сделайте снимок
        function Foo() {
            this.create = (counts = 10000, prefix = "") => {
                this.arr = createObjects(counts, prefix);
            }
        }


        function createObjects(counts = 10000, prefix = "") {
            var arr = [];
            for (let i = 0; i < counts; i++) {
                arr.push({
                    "p0": `${prefix}-value-${i}-0`,
                    "p1": `${prefix}-value-${i}-1`,
                    "p2": `${prefix}-value-${i}-2`                   
                });
            }
            return arr;
        }


        var counts = 2;

        var foo1 = new Foo();
        var foo2 = new Foo();

        foo1.create(counts, "del");
        foo2.create(counts, "set");

        var propertiesCount = 3;

        document.getElementById("btnDelete").addEventListener("click", () => {

            console.time("del");
            for (let i = 0; i < foo1.arr.length; i++) {

                const rd = i % propertiesCount;
                delete foo1.arr[i][`p${rd}`];

            }
            console.timeEnd("del");


            console.time("set");
            for (let i = 0; i < foo2.arr.length; i++) {

                const rd = i % propertiesCount;
                foo2.arr[i][`p${rd}`] = undefined;

            }
            console.timeEnd("set");
        })

Взгляните на скриншоты до и после выполнения:

Перед выполнением удаления:

После выполнения удаления:

Видно, что карта объекта, свойства которого удаляются с помощью удаления, изменилась.

Давайте настроим

        function createObjects(counts = 10000, prefix = "") {
            var arr = [];
            for (let i = 0; i < counts; i++) {
                arr.push({
                    0: `${prefix}-value-${i}-0`,
                    1: `${prefix}-value-${i}-1`,
                    2: `${prefix}-value-${i}-2`                   
                });
            }
            return arr;
        }

Просто посмотрите на скриншот после операции удаления: После выполнения удаления:

карта не изменилась.

занимать

Предложение, которое иллюстрирует сводку Google V8.

Старайтесь избегать использования метода удаления. Метод удаления разрушает форму объекта, снова заставляя V8 регенерировать новый скрытый класс для объекта.

Давайте проверим, сколько свойств влияет на производительность:

  1. 10 000 единиц данных, 3 общих атрибута
del: 7.614990234375 ms
set: 3.297119140625 ms

del: 8.5048828125 ms
set: 3.344970703125 ms

del: 7.107177734375 ms
set: 2.950927734375 ms
  1. 10 000 единиц данных и 10 общих атрибутов
del: 9.324951171875 ms
set: 3.31201171875 ms

del: 9.4580078125 ms
set: 3.0908203125 ms

del: 9.501953125 ms
set: 3.119873046875 ms
  1. 10 000 единиц данных, 25 общих атрибутов
del: 15.0390625 ms
set: 5.799072265625 ms

del: 16.137939453125 ms
set: 5.30615234375 ms

del: 15.543701171875 ms
set: 5.489990234375 ms

del: 20.700927734375 ms
set: 3.203125 ms
  1. 10 000 единиц данных и 40 общих атрибутов
del: 30.131103515625 ms
set: 4.299072265625 ms

del: 26.7041015625 ms
set: 3.68701171875 ms

del: 24.31005859375 ms
set: 4.10888671875 ms

Видно, что чем больше атрибутов, тем больше расход на удаление, а стоимость простой установки значения не сильно возрастает.

Суммировать

Из наших тестов выполнение удаления со свойством сортировки не приводило к изменению скрытого класса объекта. Обычные атрибуты не так удачливы. Таким образом, стоимость использования удаления для удаления обычных свойств относительно велика.

Кратко рассмотрим:

  1. удалить Много раз это не может быть удалено.
  2. Когда удаление возвращает true, это не обязательно означает, что удаление прошло успешно. Такие, как свойства на прототипе.
  3. удалить В некоторых случаях скрытый класс будет изменен, что может вызвать проблемы с производительностью.

Эти статьи этого достаточно, чтобы мы использовали осторожность Удалить.

дополнительный

Также меняется структура свойства сортировки.Давайте сначала вставим кусок кода:

  1. Упорядоченные числовые свойства экземпляров Foo
  2. Свирепый поступок, как тигр
  3. наблюдать за изменениями

function Foo() {
    this.create = (counts = 10, prefix = "") => {
        createPropertes.call(this, counts);
    }
}

function createPropertes(counts = 10) {
    for (let i = 0; i < counts; i++) {
        this[i] = `${i}-${Math.random()}`;
    }
}

var foo = new Foo();
foo.create();

document.getElementById("btnDelete").addEventListener("click", () => {
    actions();
    console.log("actions", " done");
})

function actions() {
    foo[100000] = `${100000}-${Math.random()}`;
    foo[100] = `${100}-${Math.random()}`;
    delete foo[9];
    foo[2] = `2-${Math.random()}`;
}

Или посмотрите на картинку, она мощнее:

Удивительно ли обнаружить, что структура изменилась, не так ли?for in, порядок также изменится. Ответ - нет, так как же он это сделал?

Отвечать:elements默认应该采用连续的存储结构,直接下标访问,提升访问速度。但当elements的序号十分不连续时或者操作过猛,会优化成为hash表。

ссылка

No operation for uninitialized values (sparse arrays)
ECMA-262_5th_edition_december_2009.pdf
ECMA-262_3rd_edition_december_1999.pdf58 страниц
Объекты, которые нельзя удалить с помощью оператора удаления в JavaScript