面向对象的程序设计
数据属性
数据属性会有四个特征:
configurable
是否锁定属性特征,默认是true
,一旦为false
则无法更改。enumerable
是否可迭代,默认为true
。writable
是否可写,默认为true
。value
数据值,默认为undefined
。
可以用 Object.defineProperty(object, property, attribute)
方法来设置属性特征:
var person = {}
Object.defineProperty(preson, "name", {
writable: false,
value: "Nicholas"
})
alert(person.name) // Nicholas
person.name = "Greg"
alert(person.name) // Nicholas
如果利用 Object.defineProperty
创建一个属性,则 configurable
、enumerable
、writable
默认为 false
。
访问器属性
通过在属性名前加上下划线表示其为一个访问器属性:
var book = {
_year: 2004,
edition: 1
}
访问器会有四个特征:
configurable
是否锁定属性特征,默认是true
,一旦为false
则无法更改。enumerable
是否可迭代,默认为true
。get
读取属性的时候调用的函数。set
写入属性的时候调用的函数。
var book = {
_year: 2004,
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 = 2005
console.log(book.edition) // 2
定义多个属性
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
}
}
}
})
获得属性的特征
可以用 Object.getOwnPropertyDescripor
方法获取属性特征。
创建对象
工厂模式
function createPerson(name, age, job) {
var obj = new Object()
obj.name = name
obj.age = age
obj.job = job
return obj
}
// 该对象类型为 Object
var person = createPerson("Nicholas", 29, "Software Engineer")
构造函数模式
function Person(name, age, job) {
// 不用创建对象,使用 this
this.name = name
this.age = age
this.job = job
// 无 return 语句
}
var person = new Person("Nicholas", 29, "Software Engineer")
// 对象会有个 constructor 属性指向 Person
alert(person.constructor == Person) // true
// 对象类型为 Person ,或者 Object
alert(person instanceof Person) // true
原型模式
每一个函数都会有一个 prototype
属性,他可以拥有静态的属性。
function Person() {}
Person.prototype.name = "Nicholas"
Person.prototype.age = 29
Person.prototype.job = "Software Engineer"
Person.prototype.sayName = function() {
alert(this.name)
}
在访问属性的时候会遵循:自身是否拥有此属性,如果没有,则返回原型中的该属性,如果有,返回自身属性。
var person1 = new Person()
var person2 = new Person()
alert(person1.name) // "Nicholas"
person1.name = "Talaxy"
alert(person1.name) // "Talaxy"
alert(person2.name) // "Nicholas"
// 删除 name 属性,注意:无法通过实例删除原型中的属性
delete person1.name
alert(person1.name) // "Nicholas"
prototype
本身会有个 constructor
属性指向构造函数。
可以用 prototype.isPrototypeOf(obj)
来判断对象是否连接了原型。
可以用 Object.getPrototypeOf(obj)
来获取对象原型。
可以用 obj.hasOwnProperty(property)
来查看该属性是否自身拥有。
判断是否能访问属性,可以用 in
操作符:
var person = new Person()
alert("name" in person) // true
使用 for-in 时,访问的是对象可访问且可枚举的属性,其中原型中的属性在这种情况下永远可枚举。
除了 for-in ,也可以用 Object.keys(obj)
来获取可枚举但是是自身的实例属性。
可以用 Object.getOwnPropertyNames(obj)
来获取所有自身属性。
当然,也可以这么声明原型,但是 constructor
属性就不再指向构造函数了:
function Person() {}
Person.prototype = {
// 当然,可以手动添加 constructor ,但是这种情况下 constructor 的可枚举为 true
// constructor: Person
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName: function() {
alert(this.name)
}
}
// 可以这样补救
Object.defineProperty(Person.prototype, "constructor", {
enumerable: false,
value: Person
})
注意,如果重写了构造函数的原型,那么多已有实例不会指向新原型。实例不会指向构造函数!
使用 prototype
,可以在已有构造函数上(不仅是自定义,也可以是原生的)扩展方法。
但一般不要去修改原生对象的原型,防止属性或者函数覆盖。
组合构造函数模式和原型模式
function Person(name, age, job) {
this.name = name
this.age = age
this.job = job
this.friends = []
}
Person.prototype = {
constructor: Person,
sayName: function() {
alert(this.name)
}
}
或者:
function Person(name, age, job) {
this.name = name
this.age = age
this.job = job
this.friends = []
// 如果方法不存在,就添加
if (typeof this.sayName != "function") {
Person.prototype.sayName = function() {
alert(this.name)
}
}
}
继承
通过将子构造函数的原型设置为父实例,可以实现继承:
function SuperType() {
this.info = true
}
function SubType() {
this.value = false
}
// 注意,这个时候 SubType 原型的 constructor 变为 SuperType 了
SubType.prototype = new SuperType()
// 对象的原型指向了 SubType 原型,SubType 原型又指向了 SuperType 原型
var obj = new SubType()
alert(obj.info) // true
alert(obj instanceof Object) // true
alert(obj instanceof SuperType) // true
alert(obj instanceof SubType // true
继承实现原理:原型链,见书上 P164 。
上面代码中有个痛苦的问题:info
属性对于所有 SubType
实例是共用的,但也是有解决方案的:
function SuperType() {
this.info = true
}
function SubType() {
// 借用父类的构造函数,当然,也可以在这里再写一遍父类的构造函数内容
// 但是别忘了,在父类构造函数中定义的函数将无法实现复用
SuperType.call(this)
}
原型式继承
可以将一个对象(也可以是原型)作为新对象的原型,同时还可以附加别的属性:
var origin = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
}
var person = Object.create(origin, {
name: {
value: "Greg"
}
})
寄生组合式继承
在将子类的原型设置为父类实例,同时又使用父类的构造方法的时候,会发现子类原型的属性都会处于屏蔽状态。
举个例子:
function SuperType() {
this.info = true
}
function SubType() {
// 让自身也获得和父类一样的属性
SuperType.call(this)
}
// 子类的原型虽然能获得父类的原型,但是也把父类的属性拷贝了一份
SubType.prototype = new SuperType()
// 此时的 s 会有两个 info 属性,分别来自子类和父类,虽然父类被屏蔽了,但是造成了资源浪费
var s = new SubType()
而我们要做的就是,SubType
的原型要去除父类实例中的属性,我们可以通过这样一个函数:
// 如果 object 的参数为 SuperType.prototype ,
// 那么则返回一个无属性,但是原型是 SuperType.prototype 的对象
// 其实这里的 object 函数和 Object.create 函数是一致的,只不过 create 还能添加额外的属性
function object(origin) {
function F() {}
F.prototype = origin
return new F()
}
// 我们将该对象作为 SubType 的原型,但是别忘了将 constructor 设置为 SubType
var prototype = object(SubType.prototype)
prototype.constructor = SubType
// 此时的 prototype 将不会包含父类构造函数中的属性
SubType.prototype = prototype