题外话这里大家可能要笑了,这不就一个操作符吗,还用单独来讲。 delete你了解多少这里提几个问题
我们就挨个来验证一下 1. delete的返回值是什么 var a = { p1: 1}console.log(delete a.p1); // trueconsole.log(delete a.p2); // trueconsole.log(delete window); // false 从上面可以看出delete返回的是布尔值,如果删除成功返回真,这里包括删除一个不存在的属性。 删除失败返回false。 2. delete删除不存在的属性返回值是什么 3. 能不能删除原型上的属性 var a = { p1: 10} a.__proto__ = { p2: 20}console.log("a.p2:before", a.p2); // 20console.log(delete a.p2); // trueconsole.log("a.p2:after", a.p2); // 20 我上面的代码是为了省事,你最好不要直接使用 function Foo(){ this.name = "name"; } Foo.prototype.age = 20;var foo = new Foo();console.log("foo.p2:before", foo.age); // 20console.log(delete foo.age); // trueconsole.log("foo.p2:after", foo.age); // 20 我都说了,不要在乎哪个写法,结果你就是不信,结果还是一样的。 4. 能否删除变量 var a = 10;console.log(delete a); // falseconsole.log("a", a); // 10 显然,是删除不掉,你换成函数,结果也是一样的。 5. 删除数组某个数据,数组长度会不会变 var arr = [10,2,16];console.log("length:before", arr.length); // 3console.log("delete", delete arr[1]); // trueconsole.log("length:after",arr.length); // 3console.log("arr", arr); // [10, empty, 16] delete删除数据的某个数据,并不会导致数组的长度变短。 这里我们接着对 var arr = [0]; arr[10] = 10; arr.forEach(v=>console.log(v)); // 0 ,10for(let p in arr){ console.log(arr[p]); // 0, 10}for(let p of arr){ console.log(arr[p]); // 0 ,undefined x 9, 10}
6. 哪些属性不能被删除
delete window // falsevar a;delete a; // false// 有意思的delete thisfunction a (){ this.a = 333; console.log("delete this:" , delete this); // true console.log("a", this.a, this); // 333, {a:333}} a.call({});
// 内置document, location等Object.getOwnPropertyDescriptor(window, "document");// { configurable: false }console.log("delete", delete window.document); // falseconsole.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") // undefinedconsole.log("delete", delete reg.source); // trueconsole.log("reg.source", reg.source); // .*console.log("reg prototype source", reg.__proto__source); // "(?:)delete reg.lastIndex // falsedelete reg.global // truedelete reg.ignoreCase // truedelete reg.multiline // true
function delP(){ console.log("delete", delete arguments); // false console.log("arguments", arguments); // 0: 1} delP(1);
delete NaN; // falsedelete Infinity; // falsedelete undefined; // false
function fn() {}delete fn;console.log(fn.toString()); // function fn() {} 更多细节
我们可以看一下ES3的定义, ES5的定义是有变动的。 The delete OperatorThe 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). 我简单翻译一下,可能不太正确哈:
console.log(delete xxxxxxxxxx) //true console.log(delete "a") // true console.log(delete {a:1}) // true console.log(delete 1) // true
这里的Resuslt(1)本身应该不是数据本身,类似一个引用地址吧。 小结一下
额,我是不是跑题了,今天的主题,不是告诉你如何使用delete,而是谨慎用delete。 比较一下性能我们先创建1万个对象,每个对象都有p0到p24 一共25个属性。 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还快了。。。。。。 常规属性 (properties) 和排序属性 (element)这里就要提出几个概念: 上面的代码变化不多,就是属性名称从 对象中的数字属性称为排序属性,在 V8 中被称为 elements。 字符串属性就被称为常规属性,在 V8 中被称为 properties。 在 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; 到这里为止,我们知道我们的两个栗子,一个使用的是数字属性(排序属性),一个使用的是字符串属性(常规属性)。 暂停一下: 说到这里,大家还会说,就算是这样。和速度有毛关系? 现在是还看不来,我们还要提出一个新的概念,隐藏类。 隐藏类
V8 在运行 JavaScript 的过程中,会假设 JavaScript 中的对象是静态的,具体地讲,V8 对每个对象做如下两点假设:
具体地讲,V8 会为每个对象创建一个隐藏类,对象的隐藏类中记录了该对象一些基础的布局信息,包括以下两点:
有了隐藏类之后,那么当 V8 访问某个对象中的某个属性时,就会先去隐藏类中查找该属性相对于它的对象的偏移量,有了偏移量和属性类型,V8 就可以直接去内存中取出对于的属性值,而不需要经历一系列的查找过程,那么这就大大提升了 V8 查找对象的效率。 多个对象共用一个隐藏类 那么,什么情况下两个对象的形状是相同的,要满足以下两点:
在执行过程中,对象的形状是可以被改变的,如果某个对象的形状改变了,隐藏类也会随着改变,这意味着 V8 要为新改变的对象重新构建新的隐藏类,这对于 V8 的执行效率来说,是一笔大的开销。 看到红色部分,你就应该差不多得到答案了。 那如何查看隐藏类呢? 。 然后再搜索对应的构造函数,比如 这里为了方便查找,我们简单包装一下代码: 验证流程很简单:
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"); }) 看看执行前后的截图: 执行删除前: 执行删除后: 可以看出使用delete删除属性的对象的map发生了变化。 我们调整一下 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; } 就只看删除操作后的截图吧: map没有变化。 借用
尽量避免使用 delete 方法。delete 方法会破坏对象的形状,同样会导致 V8 为该对象重新生成新的隐藏类。 我们接下来再测试一下属性多少对性能的影响:
del: 7.614990234375 msset: 3.297119140625 msdel: 8.5048828125 msset: 3.344970703125 msdel: 7.107177734375 msset: 2.950927734375 ms
del: 9.324951171875 msset: 3.31201171875 msdel: 9.4580078125 msset: 3.0908203125 msdel: 9.501953125 msset: 3.119873046875 ms
del: 15.0390625 msset: 5.799072265625 msdel: 16.137939453125 msset: 5.30615234375 msdel: 15.543701171875 msset: 5.489990234375 msdel: 20.700927734375 msset: 3.203125 ms
del: 30.131103515625 msset: 4.299072265625 msdel: 26.7041015625 msset: 3.68701171875 msdel: 24.31005859375 msset: 4.10888671875 ms 可以看到属性越多,delete的消耗越大。 总结从我们测试来看,使用排序属性执行delete,并未导致对象的隐藏类被改变。 我们简单回顾一下:
这几条,就足以让我们谨慎使用delete。 额外的排序属性的结构也是会变化的。
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()}`; } 还是看图,比较给力: 是不是惊喜的发现结构变化啦,那是不是 答案: 参考引用
|
|