Skip to main content

对象

  • 对象属性模型的相关方法
    • Object.getOwnPropertyDescriptor(): 获取某个属性的描述对象。
    • Object.defineProperty(): 通过描述对象,定义某个属性。
    • Object.defineProperties(): 通过描述对象,定义多个属性。
  • 控制对象状态的方法
    • Object.preventExtensions(): 防止对象扩展。
    • Object.isExtensible(): 判断对象是否可扩展。
    • Object.seal():禁止对象配置。
    • Object.isSealed(): 判断一个对象是否可配置。
    • Object.freeze(): 冻结一个对象。
    • Object.isFrozen(): 判断一个对象是否被冻结。
  • 原型链相关方法
    • Object.create(): 该方法可以指定原型对象和属性,返回一个新的对象。
    • Object.getPrototypeOf(): 获取对象的 prototype 对象。

Object.defineProperty

Object.defineProperty(obj, prop, descriptor);

参数说明:

  • obj:必须。目标对象
  • prop:必须。定义或修改的属性的名字
  • descriptor:必须。目标属性所拥有的特性

返回值:传入函数的对象,即第一个参数 obj

针对属性,我们可以给这个属性设置一些特性,比如是否只读不可以写;是否可以被 for…inObject.keys() 遍历。

给对象的属性添加特性描述,目前提供两种形式:数据描述和存取器描述。

数据描述

数据属性分为四种: configurableenumerablewritablevalue

caution

在不使用 Object.defineProperty 来定义对象时,四种的默认值都是 true ,如果使用该方法时,默认值都是 false 。

configurable 字段配置对象属性是否可以删除属性:

let person = {};
Object.defineProperty(person, 'name', {
configurable: false,
enumerable: true,
writable: true,
value: 'robbie'
});
delete person.name;
console.log(person); //{name: 'robbie'}

enumerable 配置属性是否是可枚举类型使用 for…inObject.keys() ):

let person = {};
Object.defineProperty(person, 'name', {enumerable: false, value: 'robbie'});
Object.keys(person); // []

writable 用来配置属性是否可修改:

let person = {};
Object.defineProperty(person, 'name', {writable: false, value: 'robbie'});
person.name = 'sherry';
console.log(person); //{name: 'robbie'}

value 配置属性值:

let person = {};
Object.defineProperty(person, 'name', {value: 'robbie'});
console.log(person); // {name: "robbie"}

存取器描述

当设置或获取对象的某个属性的值的时候,可以提供 getter/setter 方法。

  • getter 是一种获得属性值的方法
  • setter 是一种设置属性值的方法

在特性中使用 get / set 属性来定义对应的方法。

let obj = {log: ['a', 'b', 'c']};

Object.defineProperty(obj, 'latest', {
get: function () {
return this.log[this.log.length - 1];
}
});

Object.defineProperty(obj, 'add', {
set: function (v) {
this.log.push(v);
return this.log;
}
});

console.log(obj.latest); // 'c'
obj.add = 'd';
console.log(obj.log); // ["a", "b", "c", "d"]

或者直接在对象里面进行 get / set :

let obj = {
log: ['a', 'b', 'c'],
get latest() {
return this.log[this.log.length - 1];
},
set add(v) {
this.log.push(v);
return this.log;
}
};

console.log(obj.latest); // 'c'
obj.add = 'd';
console.log(obj.log); // ["a", "b", "c", "d"]
caution

get 或 set 不是必须成对出现,任写其一就可以。如果不设置方法,则 get 和 set 的默认值为 undefined

freeze v.s seal

Object.freeze: 不能增加和删除属性,不可以修改属性值(属性值为对象的除外)

let app = {
name: 'ts',
test: {
name: 'kimi'
}
};

Object.freeze(app);
app.test.name = 'robbie';
console.log(app.test); //{name: 'robbie'}

Object.freeze(app.test);
delete app.name; //false
app; // {name: "ts", test: {...}}

Object.seal: 不能增加和删除,但可以修改属性

const App = {
name: 'ts'
};

Object.seal(App);

App.name = 'asgh';

console.log('name', App.name); // 'asgh'

rest v.s spread

  • = 左边是 Rest,右边是 Spread
  • 函数参数也是 Rest,解构的值要是可迭代的(iterable)。

Rest Parameter […rest]

let myName = ['Marina', 'Magdy', 'Shafiq'];
const [firstName, ...familyName] = myName;
console.log(firstName); // Marina ;
console.log(familyName); // [ "Magdy" , "Shafiq"] ;
function myData(...args) {
console.log(args); // ["Marina",24,"Front-End Developer"]
}
myData('Marina', 24, 'Front-End Developer');

Rest 就像是把多个值压缩成一个值,好像大喊一声,“其余的人都给我过来!”。

Spread Operator […spread]

let myName = ['Marina', 'Magdy', 'Shafiq'];
let newArr = [...myName, 'FrontEnd', 24];
console.log(newArr); // ["Marina" , "Magdy" , "Shafiq" , "FrontEnd" , 24 ] ;

Spread 就像是把一个值解压成多个值,好像大喊一声,“原地解散!”。

Destructuring Assignment

Intuitive and flexible destructuring of Objects into individual variables during assignment.

Shallow Matching

ES6:

let {a, b = 1, c: cc, d: dd = 1} = obj;

ES5:

var a = obj.a;
var b = obj.b === void 0 ? 1 : obj.b;
var cc = obj.c;
var dd = obj.d === void 0 ? 1 : obj.d;
tip

The void operator is often used merely to obtain the undefined primitive value, usually using void(0) (which is equivalent to void 0).

let obj = {
a: 0,
b: null,
c: false,
d: '',
e: NaN,
f: undefined
};

let {a = 123, b = 123, c = 123, d = 123, e = 123, f = 123} = obj;

Only f is 123:

a; // 0
b; // null
c; // false
d; // ""
e; // NaN
f; // 123
caution

You can also use it as a param in function, but please wrap the brace with ().

const getA = ({a}) => {};
caution

Do not use ab-cd pattern as the key of object, otherwise it will cause error when using destructure asssignment:

let a = {"ab-cd": 123};
let {ab-cd} = a; // Uncaught SyntaxError: Unexpected token '-'
let {'ab-cd'} = a // Uncaught SyntaxError: Unexpected token '}'

And you have to use another valid key to avoid this error:

let {'ab-cd': b} = a;
b; // 123
let {'xy-z': c = 1} = a;
c; // 1

Deep Matching

ES6:

let {
a: {aa: aa1},
b: b1
} = obj;

ES5:

var aa1 = obj.a.aa;
var b1 = obj.b;
tip

Note that the left part is the object's key, the right is new variable.

变量作为对象的 key

对象中的 this

属性的可枚举性

对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor 方法可以获取该属性的描述对象。

let obj = {foo: 123};
Object.getOwnPropertyDescriptor(obj, 'foo');
// {
// value: 123,
// writable: true,
// enumerable: true,
// configurable: true
// }

描述对象的 enumerable 属性,称为“可枚举性”,如果该属性为 false ,就表示某些操作会忽略当前属性。

目前,有四个操作会忽略 enumerable 为 false 的属性。

  • for...in 循环:只遍历对象自身的和继承的可枚举的属性。
  • Object.keys():返回对象自身的所有可枚举的属性的键名。
  • JSON.stringify():只串行化对象自身的可枚举的属性。
  • Object.assign(): 忽略 enumerable 为 false 的属性,只拷贝对象自身的可枚举的属性。

这四个操作之中,前三个是 ES5 就有的,最后一个 Object.assign() 是 ES6 新增的。

caution

只有 for...in 会遍历继承的属性,其他三个都会忽略继承的属性,只处理对象自身的属性。

实际上,引入“可枚举”(enumerable)这个概念的最初目的,就是让某些属性可以规避掉 for...in 操作,不然所有内部属性和方法都会被遍历到。比如,对象原型的 toString 方法,以及数组的 length 属性,就通过“可枚举性”,从而避免被 for...in 遍历到。

Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerable;
// false

Object.getOwnPropertyDescriptor([], 'length').enumerable;
// false

上面代码中, toStringlength 属性的 enumerable 都是 false,因此 for...in 不会遍历到这两个继承自原型的属性。

另外,ES6 规定,所有 Class 的原型的方法都是不可枚举的。

Object.getOwnPropertyDescriptor(
class {
foo() {}
}.prototype,
'foo'
).enumerable;
// false

总的来说,操作中引入继承的属性会让问题复杂化,大多数时候,我们只关心对象自身的属性。

tip

尽量不要用 for...in 循环,而用 Object.keys()代替。

属性的遍历

五大方法

ES6 一共有 5 种方法可以遍历对象的属性。

(1) for...in

for...in 循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。

(2) Object.keys(obj)

Object.keys 返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。

(3) Object.getOwnPropertyNames(obj)

Object.getOwnPropertyNames 返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。

(4) Object.getOwnPropertySymbols(obj)

Object.getOwnPropertySymbols 返回一个数组,包含对象自身的所有 Symbol 属性的键名。

(5) Reflect.ownKeys(obj)

Reflect.ownKeys 返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。基本就是 getOwnPropertyNamesObject.getOwnPropertySymbols 的合体。

caution

不管是否可枚举,不管是不是 Symbol,只要是对象自身的,Reflect.ownKeys() 都会遍历。

caution

Reflect.ownKeys() 方法的第一个参数必须是对象,否则会报错。

key 的顺序

对象 key 的顺序问题,你首先需要知道三点:

let obj = {
m: function () {},
b: '',
2: '',
1: '',
[Symbol('b')]: '',
[Symbol('a')]: '',
3: '',
a: ''
};

Object.keys(obj); //  ["1", "2", "3", "m", "b", "a"]
Reflect.ownKeys(obj); // ["1", "2", "3", "m", "b", "a", Symbol(b), Symbol(a)]

前面说的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则:

  • 首先遍历所有 integer-like keys (包括 1, 和 '1') 按照升序排列,按照升序排列。
  • 其次遍历所有 normal keys (包括加 '' 和不加 ''),按照定义的顺序排列。
  • 最后遍历所有 symbol keys,按照定义的顺序排列。
caution

如果对象的 key 为 integer-like ,千万不要依赖其定义的顺序。

caution

如果在 Chrome 控制台上直接输入 obj 然后回车,打印出来的顺序还和 m 值的类型有关。这个顺序并不完全符合以上规则,但这只是控制台的表现,对实际的代码并无影响。

比如 m 为 函数的时候,打印出 {1: "", 2: "", 3: "", b: "", a: "", Symbol(b): "", Symbol(a): "", m: ƒ} , 而 m 为字符串或数组的时候顺序却又在 3 和 b 之间。

参考资料

  1. 属性的可枚举性和遍历,作者:阮一峰
  2. stackoverflow: Does JavaScript Guarantee Object Property Order?
  3. ES6: What is the difference between Rest and Spread? By Marina Shafiq
  4. 【译】JS 解构的五种有趣用法
  5. 理解 Object.defineProperty 的作用,作者:戎马
  6. ECMAScript 6 — New Features: Destructuring Assignment, By Dr. Ralf S. Engelschall
  7. MDN: void operator