前言
在Object [上]中我们介绍了物件的宣告、型态、拷贝等等特性,接下来我们继续介绍物件中都有哪些特性。
Property Descriptors
在ES5之前JS没有提供一个可以区分物件属性特徵的方法,但在ES5中可以透过property descriptor
观察物件属性的特性。
var myObject = {a: 2};Object.getOwnPropertyDescriptor( myObject, "a" );// {// value: 2,// writable: true,// enumerable: true,// configurable: true// }
我们对myObject
中的属性a使用getOwnPropertyDescriptor
,可以得到除了value的另外三个特质:writable
、enumerable
、configurable
。
既然我们可以拿到这个数性的特质,那们我们也可透过Object.defineProperty(...)
来添加新的特性或是修改原本的特性。
var myObject = {};Object.defineProperty( myObject, "a", {value: 2,writable: true,configurable: true,enumerable: true} );myObject.a; // 2
一班如果你要加入一个正常的属性到物件中是不必要使用Object.defineProperty
这个方法,除非你想新增特殊型态的属性。
Writable
此特性代表你是否能够更改属性的值。
var myObject = {};Object.defineProperty( myObject, "a", {value: 2,writable: false, // not writable!configurable: true,enumerable: true} );myObject.a = 3; // can't re-assignmentmyObject.a; // 2
对于myObject中的a属性设定为不可写入
则我们无法再次赋值给他,如果这个程式在严格模式下执行,会掷出TypeError
。
"use strict";var myObject = {};Object.defineProperty( myObject, "a", {value: 2,writable: false, // not writable!configurable: true,enumerable: true} );myObject.a = 3; // TypeError : Cannot change a non-writable property.
Configurable
Configurable特性控制着我们能否使用defineProperty(...)
重新改变属性的特性。
var myObject = {a: 2};myObject.a = 3;myObject.a;// 3Object.defineProperty( myObject, "a", {value: 4,writable: true,configurable: false,// not configurable!enumerable: true} );myObject.a;// 4myObject.a = 5;myObject.a;// 5Object.defineProperty( myObject, "a", {value: 6,writable: true,configurable: true,enumerable: true} ); // TypeError
一旦将物件属性的configurable
设定为false后,就算不是处于严格模式下,再次尝试更改数性的设置就会掷出TypeError,这意味着当你设置属性的configurable为false后就不可逆转
。
当你将这个属性的configurable
设定为false后可以避免这个属性被delete
操作符移除。
var myObject = {a: 2};myObject.a;// 2delete myObject.a;myObject.a;// undefined// configurable:falseObject.defineProperty( myObject, "a", {value: 2,writable: true,configurable: false,enumerable: true} );myObject.a;// 2delete myObject.a;myObject.a;// 2
在JS中delete
操作符仅用于移除物件的属性,如果一个物件中的属性引用的是某一个物件/函数的最后一个现存引用
,那们当你delete这个属性那们就代表移除了最后一个引用
,而这个没有被任何地方引用的物件/函数则会被回收掉,但如果还有其他地方引用则只是单纯地从这个物件中移除他们的Reference
总结来说delete仅仅是移除物件的属性而已
。
Enumerable
Enumerable特性决定了我们是否可以对这个元素进行列举操作(for...in),如果将这个特性设定为false则for...in loop终究无法读取到这个属性
Object.defineProperty( myObject, "a", {value: 2,writable: true,configurable: ture,enumerable: false} );myObject.b = 2;myObject.b = 4;for(let prop in myObject){ console.log(prop); // disable property a}// b// c
Immutability
有时候我们在开发专案的时候会需要建立一些不希望被更改的物件,ES5中提供了一些方法可以让我做到让物件保持不被更改,但是直得注意的是:这些方法都是浅层不变性
,这意味着他只会固定住本体物件和他的直属属性性质,但是如果这个被固定住的物件中有引用其他物件的属性(阵列、物件、函数),那么这些被Reference的物件属性并不会跟着被锁定住
。
Object Constant
通过将物件属性writable
与configurable
都设定为false让我们的物件保持不能被更改的状况。
var myObject = {};Object.defineProperty( myObject, "FAVORITE_NUMBER", {value: 42,writable: false,configurable: false} );
Prevent Extensions
如果要禁止添加属性到我们的物件中,可以使用Object.preventExtensions(...)
。
var myObject = {a: 2};Object.preventExtensions( myObject );myObject.b = 3;myObject.b; // undefined
在非严格模式下b的属性添加会失败,否则会掷出TypeError。
Seal
使用Object.seal(..)
创建一个被封印
的物件,实际上是将这个物件使用Object.preventExtensions(...)
固定住不允许加入其他属性在对物件中的每一个属性标记为configurable:false
让物件中的元素也不能被删除或重新配置,但是可以对现有存在的值进行修改
。
Freeze
使用Object.freeze(...)
创建一个冻结
的物件,实际上是将物件使用Object.seal(..)
使他不能添加、删除、重新配置新的属性,再对每个属性使用writable:false
让物件属性的值不能被更改,这个方法是最高级别
的不可变性。
[[Get]]
关于对于物件属性的访问有个重要的细节。
var myObject = {a: 2};myObject.a; // 2
使用myObject.a
来访问物件中的a属性,但这个操作不只是在物件中查找名称为a属性,实际上他会先在myObject
上执行一个[[Get]]
操作,他会检查物件并在物件中寻找有没有这个被请求的属性名称,如果找到就返回相对应的值,如果没能在物件中找到属性则会做另一个重要的事情(遍历[prototype]链),如果通过任何方法都找不到这个属性的时候便会返回undefined
。
var myObject = {a: 2};myObject.b; // undefined
[[Put]]
既然从一个属性中取得值存在一个内部定义的[[Get]]方法,那么也会有一个内部定义的[[Put]]。
调用[[Put]]时会根据几个因素表现不同的行为,影响最大的辨识属性是否已经在存在于物件
,如果属性存在[[Put]]会检查:
访问器描述符号(Getters/Setters)
中的Setters,则直接调用Setters。如果这个属性的writable
是false,在非严格模式下无声的失败,否则掷出TypeError。不符合以上两个则像正常赋值一样设置属性。如果这个属性不存在于物件中则[[Put]]操作会更複杂,我们在后面会详细讲解。
Getters & Setters
ES5中新增了一个方法来覆盖这些默认操作的一部分,不是针对每个属性而不是物件,就是通过getters
和setters
,Getter 是用来取得指定属性的值的方法
而Setter是用来设定指定属性的值的方法
。
var myObject = {// define a getter for `a`get a() {return 2;}};myObject.a = 3;myObject.a; // 2
我们仅为a定义了一个getter,就算之后向再次对a赋值也不会成功,他会无声的将赋予的值丢弃而不会掷出错误
,我们可以多新增一个setters让我们可以更改a的值。
var myObject = {// define a getter for `a`get a() {return this.b;},// define a setter for `a`set a(val) {this.b = val * 2;}};myObject.a = 2;myObject.a; // 4
当我们重新赋予值给a,他会调用myObject中的set(...),他将我们给的值*2后暂存在一个变量b,之后再透过get(...)将这个值传回给a。
Existence
我们可以透过in
操作符来确认我的数性是否有存在在物件中。
var myObject = { a : undefined } ;console.log(myObject.a); // undefinedconsole.log(myObject.b); // undefined( "a" in myObject ) ; // true ( "b" in myObject ) ; // falsemyObject . hasOwnProperty ( "a" ) ; // true myObject . hasOwnProperty ( "b" ) ; // false
虽然myObject.a
与myObject.b
都是undefined,但是通过in
和hasOwnProperty(...)
就可以确认数性是否存在于物件当中。
Enumeration
前面我们在介绍属性的特性的时候有稍微介绍什么是enumerable
,现在我们来更仔细的介绍一下这个特性。
var myObject = { };Object.defineProperty(myObject,"a",{ value: 2, enumerable: true, // make `a` enumerable, as normal });Object.defineProperty(myObject,"b",{ value: 3 enumerable: false, // make `b` NON-enumerable });myObject.b; // 3("b" in myObject); // truemyObject.hasOwnProperty( "b" ); // true// .......for (let prop in myObject) {console.log( prop );}// "a"
我们在myObject中建立了两个属性(a与b),我们使用hasOwnProperty(...)
确认b是确实存在于myObject中,但是由于他的enumerable
设定为false,所以使用for...in无法将它打印出来。
除了使用for...in测试数性是否为enumerable,也可以使用propertyIsEnumerable(..)
直接测试这个属性是否存在于物件中并且他的enumerable为何。
var myObject = { };Object.defineProperty(myObject,"a",{ value: 2, enumerable: true, // make `a` enumerable, as normal });Object.defineProperty(myObject,"b",{ value: 3 enumerable: false, // make `b` NON-enumerable });myObject.propertyIsEnumerable( "a" ); // truemyObject.propertyIsEnumerable( "b" ); // falsemyObject.propertyIsEnumerable( "c" ); // falseObject.keys( myObject ); // ["a"]Object.getOwnPropertyNames( myObject ); // ["a", "b"]
也可以使用Object.keys(..)
他会返回所有enumerable = ture
的属性。
Iteration
在阵列中迭代所有值得方法是使用标準的for loop
var myArray = [1, 2, 3];for (let i = 0; i < myArray.length; i++) {console.log( myArray[i] );}// 1 2 3
上面的程式中并不是迭代了阵列中的值,而是迭代了阵列的index
,而我们透过迭代获得的index取得阵列中对应的数(muyArray[i])。
在ES5中提供了一些对于阵列的迭代方法,forEach(...)
、every(...)
、some(...)
。
false
。some(...)会持续迭代到最后或着return一个true
。如果想直接得到迭代值而不是阵列的index,在ES6中提供了一个新语法for...of
它可以用来迭代阵列或物件。
const myArray = [1, 2, 3];for(let val of myArray){ console.log(val);}// 1// 2// 3
for...of被要求要迭代的东西需要提供一个迭代器物件
,每一次循环中都会调用这个迭代器物件的next(...)
method 。
在阵列中拥有内建的迭代器物件(@@iterator)所以可以轻易地使用for...of进行迭代,我们也可手动建立一个@@interator来看看他是如何运作的。
var myArray = [ 1, 2, 3 ];var it = myArray[Symbol.iterator]();it.next(); // { value:1, done:false }it.next(); // { value:2, done:false }it.next(); // { value:3, done:false }it.next(); // { done:true }
迭代器的next(...) return 一组物件{value: ... , done: ...},物件中的value属性代表当前迭代着值而done则代表是否迭代完成。
结论
本章节中我们介绍了物件更多的特质,属性的特性、如何将物件变为不可变、物件中的取值与赋值等等,我们来做一些总结:
物件中的数性都有三种特性:writable:控制这个属性是否能被更改。Configurable:控制这个属性是否能更改特性配置。Enumerable:控制这个属性是否能被迭代。当我们需要建立一个不可变的物件时,可以透过以下办法达到:将属性的 writable与configurable 设定为false,让这个属性不能被更改。使用Object.preventExtensions(...)可以防止物件添加新属性。使用Object.seal(...)可以防止添加新属性并且将数性标记为configurable:false(即不可更改属性设置),但是可以更改现有的属性的值
。使用Object.freeze(...)可以建立最高级别的不可变物件,即不可新增属性也不可更改现有属性。当访问物件中的属性则会调用[[Get]],会在物件中找到这个属性的名字,若找到则回传相对应的值,若没有则回传undefined。当设定物件的属性会调用[[Put]]:当属性是setter
则调用setter。当这个属性的writable = false
,在严格模式下掷出TypeError否则无声的失败。不符合上面两点则正常赋值。Getter 是用来取得指定属性的值的方法
而Setter是用来设定指定属性的值的方法
。可以使用in
与hasOwnProperty(...)
检查属性是否存在于物件中。可以使用propertyIsEnumerable(...)
测试数性是否存在于物件中别且检查这个属性使否是可迭代的。可以使用Object.keys(...)
他将会返回物件中所有可迭代的属性。可以使用for...of
迭代阵列中的值。for...of迭代实际上是执行阵列中内建@@iterator
的next(...)
。参考文献:You Don't Know JavaScript