第6章 面向对象程序设计
ECMAScript把对象定义为:“无序属性的集合,其属性可以包含基本值、对象或者函数。”
无论是用Object实例创建一个对象,还是对象字面量创建一个对象,它们都有属性和方法。这些属性在创建时都带有一些特征值,JavaScript通过这些特征值来定义它们的行为。
6.1.1 属性的类型
ECMAScript第5版在定义只有内部才用的特征时,描述了属性的各种特征。定义这些特征是为了实现JavaScript用的,因此在JavaScript中不能直接访问它们。为了表示特性是内部值,该规范把它们放在了两对方括号中[[]]。
ECMAScript 中有两种属性:数据属性和访问器属性。
1.数据属性
数据属性包含一个数据值的位置。在这个位置可以读取和写入值。数据属性有4个描述其行为的特性。
[[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。直接在对象上定义的默认是true
[[Enumerable]]:表示能否通过 for-in循环返回属性。直接在对象上定义的默认是true
[[Writable]]:表示能否修改属性的值。直接在对象上定义的默认是true
[[Value]]:包含这个属性的数据值。读取属性值的时候,从这个位置读,写入的时候,把新值保存在这个位置。这个特性的默认值为undefined。
例: var person = { name: “Jason”};
对于这个例子,它的[[Configurable]] [[Enumerable]] [[Writable]]特性都被设置为true,而[[Value]] 特性被设置为指定的值—Jason。
要修改属性默认的特性,必须使用ECMAScript5 的 Object.defineProperty()方法。这个方法接收三个参数:属性所在的对象、属性的名字和一个描述符对象 。其中描述符对象的属性必须是:configurable、enumerable、writable和value。设置其中的一或者多个值,可以修改对应的特性值。
例:
var person = {};
Object.defineProperty(person, "name", {
writable: false, //设置不能修改属性的值
value: "Jason Liu"
});
console.log(person.name); // Jason liu
person.name = "IronMan";
console.log(person.name); // Jason Liu
Object.defineProperty(person, age, {
configurable: false, //设置为不能从对象中删除属性。
value: 23
});
console.log(person.age);// 23
delete person.age;
console.log(person.age); // 23
在调用Object.defineProperty()方法创建一个新的属性时,如果不指定,configurable、enumerable、writable特性的默认值都是false。
2.访问器属性
访问器的属性不包含数据值;它们包含一对儿 getter 和 setter函数(不是必须)。在读取访问器属性时,会调用getter函数,这个函数负责返回有效的值;在写入访问器属性时,会调用setter函数并传入新值,这个函数负责决定如何处理数据。访问器有四个特征:
[[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特征性,或者能否把属性修改为数据属性。对于直接定义在对象上的,默认值是true。
[[Enumerable]]:表示能否通过 for-in 循环返回属性。对于直接在对象上定义的属性,这个特征的默认值是true。
[[Get]]:在读取属性时调用的函数。默认值为 undefined。
[[Set]]:在写入属性时调用的函数,默认值是undefined。
访问器属性不能直接定义,必须使用Object.defineProperty()方法。
例:
var book = {
_year:2018,
edition:1
};
Object.defineProperty(book, "year", {
get:function() {
return this._year;
},
set:function(newValue) {
if(newValue >2004) {
this._year = newValue;
this.edition += newValue -2004;
}
}
});
book.year =2006;
console.log(book.edition); // 3
console.log(book._year);// 2006
这是使用访问器属性的常见方式,即设置一个属性的值会导致其它属性发生变化。
6.1.2 定义多个属性
Object.defineProperties() 通过描述符一次定义多个属性
例:
var book = {};
Object.defineProperties(book, {
_year: {
writable:true,
value:2017
},
edition: {
writable:true,
value:1
},
year: {
get:function()
return this._year
},
set:function(newValue) {
if(newValue >2015) {
this._year = newValue;
this.edition += newValue -2014;
}
}
}
});
book.year =2016;
console.log(book.edition); // 3
console.log(book.year); //2016
6.2.3 原型模式
创建的每个函数都有一个 prototype(原型) 属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含由特定类型的所有实例共享的属性和方法。prototype就是通过调用构造函数而创建的那个实例对象的原型对象。
1.理解原型对象
无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个 prototype 属性,这个属性指向函数的原型对象。在默认情况下,所有的原型对象都会自动获得一个 constructor(构造函数)属性,这个属性是一个指向 prototype 属性所在函数的指针。
创建自定义的构造函数之后,其原型对象默认值会取得 constructor 属性;至于其它方法,则都是默认从 Object 继承而来的。当调用构造函数创建一个新实例后,该实例的内部将包含一个指针,指向构造函数的原型对象。
isPrototypeOf() 如果 [[Prototype]] 指向调用 isPrototypeOf() 方法的对象,那么这个方法就会返回true。
Object.getPrototypeOf() 返回 [[Prototype]] 的值。
每当代码读取某个对象的某某个属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索首先从对象的实例本身开始。如果在实例中找到了具有给定名字的属性,则返回该属性的值。如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到了,则返回这个属性的值。
当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性。添加这个属性只能阻止我们访问原型中的那个属性。即使将这个属性设置为null,也只会在实例中设置这个属性,而不会恢复其指向原型的连接。但是使用 delete 操作符则可以完全删除实例属性,从而能够重新访问原型中的属性。
hasOwnProperty() 检测属性是存在于实例中还是存在与原型中。
hasOwnProperty() 方法2. 原型与in操作符
有两种方式使用 in 操作符:单独使用和在for-in 循环中使用。单独使用时,in 操作符会通过对象能够访问给定属性时返回true,无论该属性存在实例中还是原型中。
in操作符利用 in操作符和 hasOwnProperty() 方法判断通过对象能够访问的属性是存在与原型还是实例当中。
Object.keys()获取对象上所有可枚举的实例属性。返回值是对象上所有可枚举的实例属性组成的一个数组。
Object.getOwnPropertyName() 获取所有实例属性,无论是否能枚举。