This is the fourth day of my participation in Gwen Challenge

preface

What? What are you gonna tell me, Delete? Ha, ha, ha, ha, ha, ha, ha, ha, ha, ha, ha, ha, ha, ha, ha, ha, ha, ha, ha. Have this time, might as well look at the react source, vue source. I said: React source code will look, but this is also very interesting.

Delete how much do you know

Here are a couple of questions

  1. What is the return value of delete
  2. Delete What is the return value of deleting a nonexistent property
  3. Can delete properties on the stereotype
  4. Can a variable be deleted?
  5. If you delete an array, the length of the array will change
  6. Which attributes cannot be deleted

Let’s test them one by one

1. What is the return value of delete

var a = {
    p1: 1
}

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

console.log(delete window); // false

Copy the code

As you can see from the above, delete returns a Boolean value, true on success, which includes deleting a nonexistent attribute. Return false on deletion failure.

2. What is the return value of delete that deletes a nonexistent attribute

As you can see from the first demo, deleting a nonexistent attribute also returns true

3. Can you delete attributes on the prototype

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
Copy the code

My code above is for convenience, you’d better not use __proto__ directly. Well, let me write a more serious example.

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
Copy the code

I told you, don’t care which way you write it, you just don’t believe it, it’s the same. You can’t delete properties from the prototype.

4. Check whether variables can be deleted

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

Obviously, you can’t get rid of it, so if you replace it with a function, you get the same result.

5. If you delete some data from the array, the length of the array will change

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]
Copy the code

Delete Deletes a piece of data without reducing the length of the array. The value of the corresponding data will become empty, not undefined or null. If you use new Array(2), you get [empty × 2]. They all mean the same thing.

Here we continue to extend 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
}

Copy the code

ForEach and in do nothing to uninitialized values. For details, see

No operation for uninitialized values (sparse arrays)

6. Which attributes cannot be deleted

Var const let and other variables, global variables.

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({});

Copy the code

The data attribute of the signals is false

In ES5 strict mode, an exception is thrown for objects whose Delete Configuable is false


// Built-in document, location, etc
Object.getOwnPropertyDescriptor(window."document");// { configurable: false }
console.log("delete".delete window.document); // false
console.log("delete".delete window.location); // false

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

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


// Various built-in prototypes
Object.getOwnPropertyDescriptor(Object."prototype") // { configurable: false }
console.log("delete".delete Object.prototype); // false


// Built-in Math functions
Object.getOwnPropertyDescriptor(Math."PI")  // { configurable: false }
console.log("delete".delete Math.PI); // false


// https://www.cnblogs.com/snandy/archive/2013/03/06/2944815.html
// Delete returns false for attributes of the re (source, global, ignoreCase, multiline, lastIndex) that are mentioned
// The actual test result, delete return true, but not delete
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

Copy the code

3. Properties on the prototype

4. Function parameters

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

delP(1);

Copy the code

5. Constants (NaN, Infinity, undefined)

delete NaN; // false
delete Infinity; // false
delete undefined; // false
Copy the code

6. function declaration

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

For more details

PDF ecMA-262_3rd_EDItion_december_1999. PDF Page 58 Objects that cannot be deleted by the delete operator in JavaScript

If we look at the definition of ES3, the definition of ES5 has changed.

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).Copy the code

Let me translate this briefly, which may not be quite correct.

  1. Execute unary expression
  2. If the first step is not referenced, return true
    console.log(delete xxxxxxxxxx)   //true
    console.log(delete "a")  // true
    console.log(delete {a:1})  // true
    console.log(delete 1) // true
Copy the code
  1. Apply to the object
  2. Take the property name
  3. Use the attribute name obtained in step 4 to delete the result returned in step 3
  4. Returns the result of step 5.

The Resuslt(1) is not the data itself, it is a reference address.

The subtotal

  1. Delete returns false, it must not have been deleted successfully
  2. Delete returns true, not necessarily successful

So delete returns true, and it’s best to check for yourself. Be sure.

Well, am I getting off topic, the topic of today is not how to use DELETE, but how to use DELETE carefully.

Compare performance

Let’s start by creating 10,000 objects, each with a total of 25 properties from P0 to P24. Then we delete the property and set it to undefined according to certain rules.

        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
Copy the code

We recorded about five execution event comparisons. As you can see, the delete time is not stable, and the performance is quite low.

Let’s not be surprised at this point. Let me change the code a little bit:

    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
Copy the code

Here, let’s do a twist. Del was faster than Set… And the speed of set is essentially unchanged. The sort attribute is a number, and the string attribute is a regular attribute. The first case above is all general attributes, and the second case is all sorted attributes.

General properties and Sort properties

The above code does not change much, except that the attribute name is changed from p0 format to 0 format. P0 is a general property, and 0 is a sorted property.

The numeric properties in the object are called sort properties, called Elements in V8.
String properties are called general properties, or properties in V8.


The ECMAScript specification defines that numeric attributes should be sorted in ascending order by index value size, and string attributes in ascending order by the order in which they were created.

   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

Copy the code

Our numeric properties are set in the order of 3 ->2-> 1, and the actual output is traversed in the order of 1->2->3. Our string properties are set in order B->A->C, and the actual output is B->A->C.

So far, we know that our two chestnuts, one uses a numeric attribute (the sort attribute) and one uses a string attribute (the general attribute).

Pause for a moment: there is a saying that reverse deleting attributes does not cause the map to change. Why don’t you try it?

At this point, one would say, even so. What does it have to do with speed?

We’re not going to see it yet, but we’re going to come up with a new concept, hidden classes.

Hidden classes

Here’s how it looks in Google V8:

V8 runs JavaScript on the assumption that objects in JavaScript are static. Specifically, V8 makes two assumptions about each object:

  • Once the object is created, no new properties are added;
  • Properties are not deleted after the object is created.

Specifically, V8 creates a hidden class for each object that records some basic layout information for the object, including:

  • All properties contained in the object;
  • The offset of each property relative to the object.

With hidden classes, when V8 accesses an attribute in an object, it first looks for the offset of the attribute relative to its object in the hidden class. With the offset and attribute type, V8 can fetch the value of the attribute directly from memory without going through a series of look-up procedures. This greatly improves the efficiency of FINDING objects in V8.

Multiple objects share a hidden class

So, when two objects have the same shape, it must satisfy the following two points:

  • The same property name;
  • Equal number of attributes

The shape of an object can be changed during execution, and if the shape of an object changes, the hidden class changes with it. This means that V8 has to rebuild the new hidden class for the newly changed object, which is a significant overhead for V8’s execution efficiency.

If you look at the red, you should be pretty much there.

So how do you look at hidden classes? Using chrome’s developer tools, the Heap snapshot function in the Memory module:

.

Then search for the corresponding constructor, such asFoo

Here we wrap up the code to make it easier to find:

The verification process is simple:

  1. Create two Foo instances, take snapshot
  2. Delete the snapshot and then takge Snapshot
        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");
        })

Copy the code

Take a look at the before and after screenshots:

Before deleting:

After deleting:

You can see that the map of the object whose property was deleted using DELETE has changed.

Let’s adjust that

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

Copy the code

Just look at the screenshot after the delete operation:

The map does not change.

To borrow

Illustration of Google V8 summary sentence.

Try to avoid using the delete method. The delete method destroys the shape of the object, which also causes V8 to regenerate a new hidden class for the object.

Let’s test how much an attribute affects performance:

  1. Ten thousand pieces of data and three general attributes
Del: 7.614990234375 MS set: 3.297119140625 MS del: 8.5048828125 ms set: 3.344970703125 ms del: 7.107177734375 ms set: 2.950927734375 msCopy the code
  1. Ten thousand pieces of data and ten general attributes
Del: 9.324951171875 MS set: 3.31201171875 MS del: 9.4580078125 ms set: 3.0908203125 ms del: 9.501953125 ms set: 3.119873046875 msCopy the code
  1. 10,000 pieces of data and 25 general attributes
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 msCopy the code
  1. 10,000 pieces of data 40 general attributes
Del: 30.131103515625 MS set: 4.299072265625 MS del: 26.7041015625 MS set: 3.68701171875 MS del: 24.31005859375 MS set: 4.10888671875 msCopy the code

As you can see, the more properties there are, the more expensive the DELETE is, and the more expensive the simple set value is.

conclusion

From our tests, performing delete with the sort attribute does not cause the object’s hidden class to be changed. Conventional properties are not so lucky. So using DELETE to remove regular attributes is relatively expensive.

A quick recap:

  1. Delete can’t be deleted in many cases.
  2. When delete returns true, it does not necessarily mean that the deletion succeeded. Like properties on the prototype.
  3. Delete May cause hidden class changes in some scenarios, which may cause performance problems.

These are enough to make us cautious about using DELETE.

additional

The structure of the sort attribute also changes. Let’s first post a piece of code:

  1. The ordered numeric properties on an instance of Foo
  2. It was a reckless operation like a tiger
  3. To observe the changes in

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}-The ${Math.random()}`; }}var foo = new Foo();
foo.create();

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

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

Copy the code

Or look at the picture, compare to force:

Is it a surprise that the structure changes? Is it a surprise that the order changes when you go for in? The answer is no, so how did he do it?

Elements should use a contiguous storage structure by default, with direct subscript access to improve access speed. However, elements are optimized into a hash table when the ordinal number is very discontinuous or when the operation is too aggressive.

Refer to the reference

No operation for uninitialized values (sparse arrays) ECMA-262_5th_edition_december_2009.pdf Ecma-262_3rd_edition_december_1999.pdf Page 58 Objects that cannot be deleted by the delete operator in JavaScript