跳到主要内容

面向对象的程序设计

数据属性

数据属性会有四个特征:

  • 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 创建一个属性,则 configurableenumerablewritable 默认为 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