在Object [上]中我们介绍了物件的宣告、型态、拷贝等等特性,接下来我们继续介绍物件中都有哪些特性。
在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
这个方法,除非你想新增特殊型态的属性。
此特性代表你是否能够更改属性的值。
var myObject = {};
Object.defineProperty( myObject, "a", {
value: 2,
writable: false, // not writable!
configurable: true,
enumerable: true
} );
myObject.a = 3; // can't re-assignment
myObject.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特性控制着我们能否使用defineProperty(...)
重新改变属性的特性。
var myObject = {
a: 2
};
myObject.a = 3;
myObject.a; // 3
Object.defineProperty( myObject, "a", {
value: 4,
writable: true,
configurable: false, // not configurable!
enumerable: true
} );
myObject.a; // 4
myObject.a = 5;
myObject.a; // 5
Object.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; // 2
delete myObject.a;
myObject.a; // undefined
// configurable:false
Object.defineProperty( myObject, "a", {
value: 2,
writable: true,
configurable: false,
enumerable: true
} );
myObject.a; // 2
delete myObject.a;
myObject.a; // 2
在JS中delete
操作符仅用於移除物件的属性,如果一个物件中的属性引用的是某一个物件/函数的最後一个现存引用
,那们当你delete这个属性那们就代表移除了最後一个引用
,而这个没有被任何地方引用的物件/函数则会被回收掉,但如果还有其他地方引用则只是单纯地从这个物件中移除他们的Reference
总结来说delete仅仅是移除物件的属性而已
。
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
有时候我们在开发专案的时候会需要建立一些不希望被更改的物件,ES5中提供了一些方法可以让我做到让物件保持不被更改,但是直得注意的是:这些方法都是浅层不变性
,这意味着他只会固定住本体物件和他的直属属性性质,但是如果这个被固定住的物件中有引用其他物件的属性(阵列、物件、函数),那麽这些被Reference的物件属性并不会跟着被锁定住
。
通过将物件属性writable
与configurable
都设定为false让我们的物件保持不能被更改的状况。
var myObject = {};
Object.defineProperty( myObject, "FAVORITE_NUMBER", {
value: 42,
writable: false,
configurable: false
} );
如果要禁止添加属性到我们的物件中,可以使用Object.preventExtensions(...)
。
var myObject = {
a: 2
};
Object.preventExtensions( myObject );
myObject.b = 3;
myObject.b; // undefined
在非严格模式下b的属性添加会失败,否则会掷出TypeError。
使用Object.seal(..)
创建一个被封印
的物件,实际上是将这个物件使用Object.preventExtensions(...)
固定住不允许加入其他属性在对物件中的每一个属性标记为configurable:false
让物件中的元素也不能被删除或重新配置,但是可以对现有存在的值进行修改
。
使用Object.freeze(...)
创建一个冻结
的物件,实际上是将物件使用Object.seal(..)
使他不能添加、删除、重新配置新的属性,再对每个属性使用writable:false
让物件属性的值不能被更改,这个方法是最高级别
的不可变性。
关於对於物件属性的访问有个重要的细节。
var myObject = {
a: 2
};
myObject.a; // 2
使用myObject.a
来访问物件中的a属性,但这个操作不只是在物件中查找名称为a属性,实际上他会先在myObject
上执行一个[[Get]]
操作,他会检查物件并在物件中寻找有没有这个被请求的属性名称,如果找到就返回相对应的值,如果没能在物件中找到属性则会做另一个重要的事情(遍历[prototype]链),如果通过任何方法都找不到这个属性的时候便会返回undefined
。
var myObject = {
a: 2
};
myObject.b; // undefined
既然从一个属性中取得值存在一个内部定义的[[Get]]方法,那麽也会有一个内部定义的[[Put]]。
调用[[Put]]时会根据几个因素表现不同的行为,影响最大的辨识属性是否已经在存在於物件
,如果属性存在[[Put]]会检查:
访问器描述符号(Getters/Setters)
中的Setters,则直接调用Setters。writable
是false,在非严格模式下无声的失败,否则掷出TypeError。如果这个属性不存在於物件中则[[Put]]操作会更复杂,我们在後面会详细讲解。
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。
我们可以透过in
操作符来确认我的数性是否有存在在物件中。
var myObject = {
a : undefined
} ;
console.log(myObject.a); // undefined
console.log(myObject.b); // undefined
( "a" in myObject ) ; // true
( "b" in myObject ) ; // false
myObject . hasOwnProperty ( "a" ) ; // true
myObject . hasOwnProperty ( "b" ) ; // false
虽然myObject.a
与myObject.b
都是undefined,但是通过in
和hasOwnProperty(...)
就可以确认数性是否存在於物件当中。
前面我们在介绍属性的特性的时候有稍微介绍什麽是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); // true
myObject.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" ); // true
myObject.propertyIsEnumerable( "b" ); // false
myObject.propertyIsEnumerable( "c" ); // false
Object.keys( myObject ); // ["a"]
Object.getOwnPropertyNames( myObject ); // ["a", "b"]
也可以使用Object.keys(..)
他会返回所有enumerable = ture
的属性。
在阵列中迭代所有值得方法是使用标准的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
。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则代表是否迭代完成。
本章节中我们介绍了物件更多的特质,属性的特性、如何将物件变为不可变、物件中的取值与赋值等等,我们来做一些总结:
但是可以更改现有的属性的值
。setter
则调用setter。writable = false
,在严格模式下掷出TypeError否则无声的失败。取得指定属性的值的方法
而Setter是用来设定指定属性的值的方法
。in
与hasOwnProperty(...)
检查属性是否存在於物件中。propertyIsEnumerable(...)
测试数性是否存在於物件中别且检查这个属性使否是可迭代的。Object.keys(...)
他将会返回物件中所有可迭代的属性。for...of
迭代阵列中的值。@@iterator
的next(...)
。参考文献:You Don't Know JavaScript
<<: (33)试着学 Hexo-番外篇之更新 NexT 主题
str.format 是在 python 中常用的字串格式化招式,可以控制想要显示的资料型态、正负号...
在所有的阵列方法里还有 every() 跟 some() ,听说是很冷门的两种方法,确实我在练习上也...
你有听过「蜥蜴脑」吗?如果你读过 The Pragmatic Programmer,你应该还有印象。...
今天要来了解上一篇的各个步骤 1.创建资料类别 有个快速生成data class的插件,可以从Fil...
前言 今天跟大家分享「冰山理论」,是在探讨心理与沟通上的常见主题,试着分享我对冰山的概略理解,若有什...