对象
ECMA把对象定义为:无序的集合,其属性可以包含基本值、对象或者函数。
严格来说对象是一组没有特定顺序的值。对象的每个属性或方法都有一个名字,而每个名字都映射到一个值。
对象是基于一个引用类型创建的。
创建一个对象最简单的方式就是创建一个Object实例,例如:
let obj = new Object();
或者使用构造函数来创建,例如:
function Person(){}let p = new Person()
或者也可以用对象字面量的方式来创建,例如:
let person = {name: 'tom',age: 18,sex: 'male',say: function(){console.log('I can speak Engilsh')}}
属性类型
Javascript中有两种属性:数据属性和访问器属性。确切的说这两种属性都是用来描述对象的各种特征,因为这些特性是内部值,js不能直接访问,所以规范把它们放在两对儿方括号中 [[Enumerable]]。
**
数据属性
数据属性包含一个数据值的位置,这个位置可读、可写。
- [[Configurable]]: 表示能否通过delete删除属性从而定义新的属性,能否修改属性的特性,或者能否把属性修改为访问器属性,默认值为true.
- [[Enumerable]]: 表示能否通过for-in循环出属性,默认值为true.
- [[Writable]]: 表示能否修改属相的值。直接在对象上定义属性,这个特性的默认值为true.
- [[Value]]: 包含这个属性的数据值,访问器属性里面是没有这个特性的。默认为undefined;比如let person = {name:’tom’},这里设置了一个名叫name的属性,它的值是”tom”, [[Value]]特性将被设置为”tom”,其余的是三个特性都是默认值true。
下面就用具体的实例来理解上面的特性。首先这里要先谈的是Object.defineProperty()方法,这个方法接收三个参数:属性所在的对象、属性的名字、和一个描述符对象(也就是上面的四个数据属性,用来描述对象属性的特性),这里要注意了,采用Object.defineProperty方法创建属性时候,数据属性[[Configurable]]、[[Writable]]、[[Enumerable]]默认为false,这要和字面量直接声明属性时默认值相反, 但是如果只是Object.defineProperty改变原来已有属性的值则没有此限制,示例如下:
let person = {};Object.defineProperty(person,'name',{value: 'james'})console.log(person.name) // jamesperson.name = 'kobe';console.log(person.name) // 依然还是james
这里我们看到[[Writable]]特性的默认属性是false, 这里属相的值是不可写的。
let person = {};Object.defineProperty(person,'name',{writable: true,value: 'james'})console.log(person.name) // jamesperson.name = 'kobe';console.log(person.name) // 这里改为了kobe
将[[Writable]]特性的值改为true, 这里是属性值变为可写入。
let person = {};Object.defineProperty(person,'name',{value: 'james'})console.log(person.name) // jamesdelete person.nameconsole.log(person.name) // 依然还是james,没有删除
[[Configurable]]特性的默认属性是false, 这里属相的值是不可删除的。
let person = {};Object.defineProperty(person,'name',{configurable: true,value: 'james'})console.log(person.name) // jamesdelete person.nameconsole.log(person.name) // undefined,这里删除了
把[[Configurable]]设置为false,表示不能删除对象属性,对这个这个属性调用delete方法,非严格模式下什么也不会发生,严格模式下则会抛出错误,而且一旦把属性从configurable设置为false,以后就不能把它设置为true,此时在调用Object.defineProperty方法修改除writable、value之外的特性,都会导致错误。
let person = {};Object.defineProperty(person,'name',{configurable: false,value: 'james'})Object.defineProperty(person,'name',{configurable: true,value: 'james'}//这样就会报错了
我们可以这样理解,多次调用Object.defineProperty()方法修改同一个属性,但是在configurable特性设置为false之后就会有限制了。
访问器属性
访问器属性不包含数值,它包含一对函数(getter和setter), 在读取访问器属性时,会调用getter函数,这个函数负责返回有效的值;在写入访问器属性时,会调用setter函数并传入新值,访问器属性有如下四个特性:
- [[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,能否把属性修改为数据属性,默认为true;
- [[Enumerable]]:用来修饰对象属性的枚举特性,表示能否通过用for in循环返回属性,默认为true;
- [[Get]]:在读取属性时候调用的函数,默认为undefined;
- [[Set]]:在写入属性时候调用的函数,默认为undefined;
访问器属性不能直接定义,必须使用Object.defineProperty()来定义。示例如下:
let person = {_age : 18,edition : 1}Object.defineProperty(person,'age',{get: function(){return this._age;},set: function(newVal){if(newVal > 19){this._age = newVal;this.edition += newVal - 19}}})console.log(person.edition) // 1person.age = 20console.log(person.edition) // 2
以上例子中,person对象有两个默认属性:_age 和 edition ,其中_age前面加了下划线,用于表示只能通过对象方法去访问这个属性,即通过get方法返回属性值。如果直接访问person.edition的值,则不会调用get方法返回。person.age=20即是修改person的age属性值,会调用set方法,参数newValue就是设置的20这个值,在上面例子中set函数里还改变了属性edition的值。
这是使用访问器属性的常见方式,即设置一个属相的值会导致其它属性发生变化。
定义多个属性
采用Object.defineProperties()我们也可以为一个对象定义多个属性,这个方法有两个参数:第一个要定义属性的对象,第二个也是对象,表示要添加的多个属性和其对应的属性描述符,示例如下:
var person = {};Object.defineProperties(person,{_age:{writable:true,value:18},edition:{writable:true,value:1},age:{get:function(){return this._age},set:function(newVal){if(newVal > 18){this._age = newValthis.edition += newVal - 18}}}})
以上代码在person对象上定义了两个数据属性(_age和edition)和一个访问器属性(age)。
读取属性的特性
上面我们一直是在讨论如何设置属性的描述特性,那么对于数据属性和访问器属性里面的具体特性我们怎么读取呢?这里介绍另一个Object的方法:getOwnPropertyDescriptor方法,该方法接收两个参数:属性所在的对象和要读取其描述符的属性名称,它的返回是一个对象,示例如下:
var book = {};Object.defineProperties(book, {_year: {writable: true,value: 2004,},edition: {writable: true,value: 1,},year: {get: function () {return this._year;},set: function (newValue) {if (newValue > 2004) {this._year = newValue;this.edition += newValue - 2004;}}}});book.year = 2005;console.log(book.edition); //2var descriptor1 = Object.getOwnPropertyDescriptor(book, '_year');console.log(descriptor1.value); //2005console.log(descriptor1.configurable);//falseconsole.log(typeof descriptor1.get); //undefinevar descriptor2 = Object.getOwnPropertyDescriptor(book, 'year');console.log(descriptor2.value); //2005console.log(descriptor2.configurable);//falseconsole.log(typeof descriptor2.get); //function;
对于数据属性_year, value等于最初的值, configurable是false, 而get是undefined;
对于访问器属性year, value等于undefined, configurable是false, 而get是一个指向getter函数的指针。
