这是我参与更文挑战的第4天,活动详情查看: 更文挑战
此文最初版本我发布在博客园 Why系列:谨慎使用delete, 经过整理和加工,再发布到掘金。
前言
啥,你要给我讲啥, delete? 哈哈哈哈哈哈哈哈哈
这里大家可能要笑了,这不就一个操作符吗,还用单独来讲。
有这时间,还不如去看看react源码,vue源码。
我说:react源码会去看的,但是这个也很挺有意思的。
delete你了解多少
这里提几个问题
- delete的返回值是什么
- delete删除不存在的属性返回值是什么
- 能不能删除原型上的属性
- 能否删除变量
- 删除数组某个数据,数组长度会不会变
- 哪些属性不能被删除
我们就挨个来验证一下
1. delete的返回值是什么
var a = {
p1: 1
}
console.log(delete a.p1); // true
console.log(delete a.p2); // true
console.log(delete window); // false
复制代码
从上面可以看出delete返回的是布尔值,如果删除成功返回真,这里包括删除一个不存在的属性。 删除失败返回false。
2. delete删除不存在的属性返回值是什么
从第一个demo看出,删除不存在的属性返回值也是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
并不会对未初始化的值进行任何操作。
具体可以参见
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({});
复制代码
2. 数据属性configurable为false的属性
ES5严格模式中delete configuable为false的对象时会直接抛异常
// 内置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.pdf 58页
JavaScript中delete操作符不能删除的对象
我们可以看一下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).
复制代码
我简单翻译一下,可能不太正确哈:
- 执行一元表达式
- 如果第一步返回的未被引用,返回真
console.log(delete xxxxxxxxxx) //true
console.log(delete "a") // true
console.log(delete {a:1}) // true
console.log(delete 1) // true
复制代码
- 取到对象
- 取属性名
- 用第四步获得的属性名,在第三步返回的结果上进行删除操作
- 返回第五步返回的结果。
这里的Resuslt(1)本身应该不是数据本身,类似一个引用地址吧。
小结一下
- delete 返回false, 一定是没删除成功
- delete 返回true, 不一定删除成功
所以,delete返回true,最好自己再动手检查一下。万无一失。
额,我是不是跑题了,今天的主题,不是告诉你如何使用delete,而是谨慎用delete。
比较一下性能
我们先创建1万个对象,每个对象都有p0到p24 一共25个属性。
然后我们按照一定的规则删除属性和设置属性为undefined。
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
复制代码
我们记录了大约五次执行事件对比。
可以看出来delete 时间不稳定,而且性能低不少。
到这里,我们还不要惊讶。看我稍微改动一下代码:
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还快了。。。。。。 ,而set的速度实际基本没有什么变化。
这里就要说到 常规属性
和排序属性
啦, 排序属性的属性值为数字,属性值为字符串的属于常规属性。上面第一种情况就是全部是常规属性,第二种情况全部是排序属性。
常规属性 (properties) 和排序属性 (element)
上面的代码变化不多,就是属性名称从p0
格式修改为了0
格式。
p0
正式常规属性,0
是排序属性。