JavaScript 内部属性

前言

之前在JavaScript 原型链机制Object.getOwnPropertyNames -> Object.keys的时候提到枚举属性

1
2
3
const arr = [1, 2, 3];
console.log(Object.getOwnPropertyNames(arr)); // [ '0', '1', '2', 'length' ]
console.log(Object.keys(arr)); // [ '0', '1', '2' ]

当时并没有写明白是怎么做到的,实际上每个对象里面的属性,都有几个不同的属性,可以讲成属性的属性(property.attributes),在例子里面发现了,Array.length是不可枚举的,实际上 JavaScript 的基本包装类型原型属性都是不可枚举的,比如ObjectArrayString等等,都不可枚举。

创建枚举属性

在 javaScript 里面,如果直接赋值或者属性初始化的属性,enumerable默认为true,但是如果通过Object.defineProperty,定义的属性就可以设置不同的内部属性,enumerable默认为false

1
2
3
4
5
6
7
8
9
10
11
12
13
function Foo() {
this.bar = "bar";
}

const foo = new Foo();
Object.defineProperty(foo, "baz", {
value: "baz"
// 默认 false,所以不写一样
// enumerable: false
});

console.log(Object.getOwnPropertyNames(foo)); // [ 'bar', 'baz' ]
console.log(Object.keys(foo)); // [ 'bar' ]

基本设置就是用法就是这样,如果不要枚举,那就不写就好了。

enumerable 坑点

如果不了解一些方法操作符的运作,可以会变成一个坑点,下面这三个就是比较常用而且之前没提到的坑

1
2
3
4
5
6
7
8
9
10
11
// hasOwnProperty 只要是自身的我全都要
console.log(foo.hasOwnProperty("baz")); // true

// 我只要自身和继承的可枚举属性
for (const prop in foo) {
console.log(prop);
}
// bar

// 我只要自身的可枚举属性
console.log(JSON.stringify(foo)); // {"bar":"bar"}

其实实际应用最常会碰到的就是Array.length的问题,剩下的比较少机会碰到吧,关于这一块其实是个大坑,相关 api 十分之多,但是应用场景十分之少,所以我不建议记住太多其他的,如果感兴趣偶尔去MDN 属性的可枚举性和所有权就好了

Object.defineProperty

既然提到了Object.defineProperty也介绍一下,这个方法顾名思义是可以精确添加或者修改对象的属性,其实不仅仅可以定义enumerable,还能定义是否可以配置或者删除(configurable),什么类型的描述符。
在对象里面目前存在两种属性描述符,数据描述符存取描述符

  • 数据描述符就是平常我们在用的有具体值的,可以写或者不可以写的。
  • 存取描述符有点类似一个属性方法,由getter-setter组成。

可选属性

  • configurable
    当为true材可以改变这个描述符类型,比如本来是数据描述符,然后改成存取描述符,同时也决定这个属性是否允许被删除。默认false
  • enumerable
    当为true才是枚举属性。默认false
  • 数据描述符特有属性
    • value
      对应具体值。默认underfined
    • writable
      决定是否可以改变值。默认false
  • 存取描述符特有属性
    • get
      一个getter方法,当访问属性的时候被调用。默认underfined
    • writable
      一个setter方法,当改变属性的时候被调用。默认underfined

特有属性之间互斥,如果都没有默认是数据描述符。假如出现互斥同时有valuewritablegetset就会报错。

configurable 例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const foo = {};
Object.defineProperty(foo, "bar", {
get() {
return "bar";
},
configurable: false
});

// throws a TypeError
Object.defineProperty(foo, "bar", { configurable: true });
// throws a TypeError
Object.defineProperty(foo, "bar", { enumerable: true });
// throws a TypeError
Object.defineProperty(foo, "bar", { set() {} });
// throws a TypeError
Object.defineProperty(foo, "bar", {
get() {
return "foo";
}
});
// throws a TypeError
Object.defineProperty(foo, "bar", { value: "baz" });

console.log(foo.bar); // logs 1
// throws a TypeError
delete foo.bar;
console.log(foo.bar); // logs 1

只要配置了false,无法做任何更改,也无法删除

writable 例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const foo = {};
Object.defineProperty(foo, "bar", {
value: "bar",
writable: true
});

console.log(foo.bar); // bar
foo.bar = "baz";
console.log(foo.bar); // baz

Object.defineProperty(foo, "bar", {
value: "bar",
writable: false
});

console.log(foo.bar); // bar
// throws a TypeError
foo.bar = "baz";

writable决定了是否可以修改属性,如果false报错

存取描述符例子

1
2
3
4
5
6
7
8
9
10
11
12
13
const foo = {};
Object.defineProperty(foo, "bar", {
get() {
return this._bar;
},
set(value) {
this._bar = value;
}
});

console.log(foo.bar); // undefined
foo.bar = "bar";
console.log(foo.bar); // bar

其实存取描述符就就是gettersetter,当作两个方法,里面不一定要绑定this的属性,也可以绑定别的,如果要绑定this要考虑继承问题this指向。

总结

这方面总括而言基本平时用不上,最多也就可能面试能用上…更何况我搜寻了一下网上的面试题,基本也没有提到……了解即可我认为。