使用Object.create()的更简单的“类似散文”的语法
以及JavaScript的真正原型性质
*此示例已更新为ES6类和TypeScript。
首先,JavaScript是一种原型语言,而不是基于类的。它的真正本质在下面的原型形式中得以表达,你可能会发现它非常简单、类似散文,但却非常强大。
简而言之;
JavaScript
const Person = {
name: 'Anonymous',
greet() { console.log(`Hi, I am ${this.name}.`) }
}
const jack = Object.create(Person)
jack.name = 'Jack'
jack.greet()
TypeScript
在TypeScript中,您需要设置接口,这些接口将在您创建
Person
原型的子类时进行扩展。一个名为
politeGreet
的变异显示了在子类
jack
上附加新方法的示例。
interface IPerson extends Object {
name: string
greet(): void
}
const Person: IPerson = {
name: 'Anonymous',
greet() {
console.log(`Hi, I am ${this.name}.`)
}
}
interface IPolitePerson extends IPerson {
politeGreet: (title: 'Sir' | 'Mdm') => void
}
const PolitePerson: IPolitePerson = Object.create(Person)
PolitePerson.politeGreet = function(title: string) {
console.log(`Dear ${title}! I am ${this.name}.`)
}
const jack: IPolitePerson = Object.create(Person)
jack.name = 'Jack'
jack.politeGreet = function(title): void {
console.log(`Dear ${title}! I am ${this.name}.`)
}
jack.greet()
jack.politeGreet('Sir')
这样可以避免有时候复杂的构造模式。新对象继承自旧对象,但能够拥有自己的属性。如果我们尝试从新对象(`#greet()`)中获取一个新对象`jack`所缺少的成员,旧对象`Person`将提供该成员。
用
Douglas Crockford的话来说:“对象继承自对象。还有什么比这更面向对象的呢?”
你不需要构造函数,也不需要`new`实例化。你只需创建对象,然后扩展或改变它们。
这种模式还提供了
部分或完全的不可变性,以及
获取器/设置器。
简洁明了。它的简单性并不妥协功能。请继续阅读。
创建一个`Person`原型的后代/副本(从技术上讲,比`class`更正确)。
*注意:以下示例是使用JS编写的。要使用Typescript编写,只需按照上面的示例设置接口进行类型定义。
const Skywalker = Object.create(Person)
Skywalker.lastName = 'Skywalker'
Skywalker.firstName = ''
Skywalker.type = 'human'
Skywalker.greet = function() { console.log(`Hi, my name is ${this.firstName} ${this.lastName} and I am a ${this.type}.`
const anakin = Object.create(Skywalker)
anakin.firstName = 'Anakin'
anakin.birthYear = '442 BBY'
anakin.gender = 'male'
anakin.greet()
Person.isPrototypeOf(Skywalker)
Person.isPrototypeOf(anakin)
Skywalker.isPrototypeOf(anakin)
如果你觉得直接赋值比使用构造函数更不安全,一种常见的方法是附加一个
#create
方法:
Skywalker.create = function(firstName, gender, birthYear) {
let skywalker = Object.create(Skywalker)
Object.assign(skywalker, {
firstName,
birthYear,
gender,
lastName: 'Skywalker',
type: 'human'
})
return skywalker
}
const anakin = Skywalker.create('Anakin', 'male', '442 BBY')
将Person
原型分支到Robot
当你将Robot
派生自Person
原型时,不会影响Skywalker
和anakin
:
const Robot = Object.create(Person)
Robot.type = 'robot'
附上
Robot
独有的附加方法。
Robot.machineGreet = function() {
}
anakin.machineGreet()
Person.isPrototypeOf(Robot)
Robot.isPrototypeOf(Skywalker)
在TypeScript中,您还需要扩展
Person
接口:
interface Robot extends Person {
machineGreet(): void
}
const Robot: Robot = Object.create(Person)
Robot.machineGreet = function() { console.log(101010) }
而且你可以使用混入(Mixins)——因为...达斯·维达(Darth Vader)是人类还是机器人?
const darthVader = Object.create(anakin)
Object.assign(darthVader, Robot)
达斯·维达掌握了机器人的方法。
darthVader.greet()
darthVader.machineGreet()
除了其他奇怪的事情之外:
console.log(darthVader.type)
Robot.isPrototypeOf(darthVader)
Person.isPrototypeOf(darthVader)
这个优雅地反映了“现实生活”主观性:
“他现在更像机器而不是人,扭曲而邪恶。” - 奥比-旺·克诺比
“我知道你内心有善良。” - 卢克·天行者
与ES6之前的“经典”等效进行比较:
function Person (firstName, lastName, birthYear, type) {
this.firstName = firstName
this.lastName = lastName
this.birthYear = birthYear
this.type = type
}
Person.prototype.name = function() { return firstName + ' ' + lastName }
Person.prototype.greet = function() { ... }
Person.prototype.age = function() { ... }
function Skywalker(firstName, birthYear) {
Person.apply(this, [firstName, 'Skywalker', birthYear, 'human'])
}
Skywalker.prototype = Person.prototype
Skywalker.prototype.constructor = Skywalker
const anakin = new Skywalker('Anakin', '442 BBY')
Person.isPrototypeOf(anakin)
Skywalker.isPrototypeOf(anakin)
ES6 类
与使用对象相比稍显笨重,但代码可读性还可以:
class Person {
constructor(firstName, lastName, birthYear, type) {
this.firstName = firstName
this.lastName = lastName
this.birthYear = birthYear
this.type = type
}
name() { return this.firstName + ' ' + this.lastName }
greet() { console.log('Hi, my name is ' + this.name() + ' and I am a ' + this.type + '.' ) }
}
class Skywalker extends Person {
constructor(firstName, birthYear) {
super(firstName, 'Skywalker', birthYear, 'human')
}
}
const anakin = new Skywalker('Anakin', '442 BBY')
Person.isPrototypeOf(anakin)
Skywalker.isPrototypeOf(anakin)
进一步阅读
可写性、可配置性和自由的获取器和设置器!
对于自由的获取器和设置器,或者额外的配置,您可以使用Object.create()的第二个参数,也称为propertiesObject。它也可以在#Object.defineProperty和#Object.defineProperties中使用。
为了说明它的有用性,假设我们希望所有的机器人
都严格由金属制成(通过writable: false
),并标准化powerConsumption
的值(通过获取器和设置器)。
interface Robot extends Person {
madeOf: 'metal'
powerConsumption: string
}
const Robot: Robot = Object.create(Person, {
madeOf: {
value: "metal",
writable: false,
configurable: false,
enumerable: true
},
powerConsumption: {
get() { return this._powerConsumption },
set(value) {
if (value.indexOf('MWh')) return this._powerConsumption = value.replace('M', ',000k')
this._powerConsumption = value
throw new Error('Power consumption format not recognised.')
}
}
})
const newRobot: Robot = Object.create(Robot)
newRobot.powerConsumption = '5MWh'
console.log(newRobot.powerConsumption)
所有的
Robot
的原型都不能由其他材料制成:
const polymerRobot = Object.create(Robot)
polymerRobot.madeOf = 'polymer'
console.log(polymerRobot.madeOf)
extend
函数感到好奇,我在这里设置了一个示例:http://jsfiddle.net/k9LRd/ - Codrin Eugeniu