Object.create() を使用したより単純な「散文のような」構文
そして、Javascript の真の原型的性質
*この例は、ES6 クラスと TypeScript 用に更新されています。
まず、Javascript はプロトタイプ言語であり、クラスベースではありません。その真の性質は、以下のプロトタイプ形式で表現されています。これは、非常に単純で、散文のようでありながら強力であることがわかるでしょう。
TLDR;
Javascript
const Person = {
name: 'Anonymous', // person has a name
greet: function() { console.log(`Hi, I am ${this.name}.`) }
}
const jack = Object.create(Person) // jack is a person
jack.name = 'Jack' // and has a name 'Jack'
jack.greet() // outputs "Hi, I am Jack."
TypeScript
Person
TypeScript では、プロトタイプの子孫を作成するときに拡張されるインターフェイスを設定する必要があります。ミューテーションpoliteGreet
は、子孫に新しいメソッドを追加する例を示していjack
ます。
interface Person {
name: string
greet(): void
}
const Person = {
name: 'Anonymous',
greet() {
console.log(`Hi, I am ${this.name}.`)
}
}
interface jack extends Person {
politeGreet: (title: 'Sir' | 'Mdm') => void
}
const jack: jack = Object.create(Person)
jack.name = 'Jack'
jack.politeGreet = function(title) {
console.log(`Dear ${title}! I am ${this.name}.`)
}
jack.greet() // "Hi, I am Jack."
jack.politeGreet('Sir') // "Dear Sir, I am Jack."
これにより、複雑なコンストラクター パターンが解消されます。新しいオブジェクトは古いオブジェクトから継承しますが、独自のプロパティを持つことができます。#greet()
新しいオブジェクト ( )から新しいオブジェクトにないメンバーを取得しようとするとjack
、古いオブジェクトPerson
がメンバーを提供します。
Douglas Crockford の言葉を借りれば、「オブジェクトはオブジェクトから継承されます。これ以上のオブジェクト指向はありません。」
new
コンストラクターもインスタンス化も必要ありません。オブジェクトを作成し、それらを拡張またはモーフィングするだけです。
このパターンは、不変性 (部分的または完全)とゲッター/セッターも提供します。
クリーンでクリアな。そのシンプルさは機能を妥協しません。読む。
Person の子孫/コピーを作成しますprototype
(技術的には よりも正確です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' // you can attach new properties.
anakin.greet() // 'Hi, my name is Anakin Skywalker and I am a human.'
Person.isPrototypeOf(Skywalker) // outputs true
Person.isPrototypeOf(anakin) // outputs true
Skywalker.isPrototypeOf(anakin) // outputs true
直接代入の代わりにコンストラクターを破棄するのが安全でないと感じる場合、一般的な方法の 1 つは、#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
// create a `Robot` prototype by extending the `Person` prototype:
const Robot = Object.create(Person)
Robot.type = 'robot'
に固有のアタッチ方法Robot
Robot.machineGreet = function() {
/*some function to convert strings to binary */
}
// Mutating the `Robot` object doesn't affect `Person` prototype and its descendants
anakin.machineGreet() // error
Person.isPrototypeOf(Robot) // outputs true
Robot.isPrototypeOf(Skywalker) // outputs false
Person
TypeScript では、インターフェイスを拡張する必要もあります。
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)
// for brevity, property assignments are skipped because you get the point by now.
Object.assign(darthVader, Robot)
Darth Vader は次のメソッドを取得しますRobot
。
darthVader.greet() // inherited from `Person`, outputs "Hi, my name is Darth Vader..."
darthVader.machineGreet() // inherited from `Robot`, outputs 001010011010...
他の奇妙なこととともに:
console.log(darthVader.type) // outputs robot.
Robot.isPrototypeOf(darthVader) // returns false.
Person.isPrototypeOf(darthVader) // returns true.
「現実の」主観性をエレガントに反映しています。
「彼は今や人間よりも機械であり、ひねくれていて邪悪です。」- オビワン・ケノービ
「私はあなたの中に良いところがあることを知っています。」- ルークスカイウォーカー
ES6 より前の「古典的な」同等のものと比較してください。
function Person (firstName, lastName, birthYear, type) {
this.firstName = firstName
this.lastName = lastName
this.birthYear = birthYear
this.type = type
}
// attaching methods
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'])
}
// confusing re-pointing...
Skywalker.prototype = Person.prototype
Skywalker.prototype.constructor = Skywalker
const anakin = new Skywalker('Anakin', '442 BBY')
// #isPrototypeOf won't work
Person.isPrototypeOf(anakin) // returns false
Skywalker.isPrototypeOf(anakin) // returns false
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')
// prototype chain inheritance checking is partially fixed.
Person.isPrototypeOf(anakin) // returns false!
Skywalker.isPrototypeOf(anakin) // returns true
参考文献
書き込み可能性、構成可能性、および無料のゲッターとセッター!
無料のゲッターとセッター、または追加の構成については、Object.create() の 2 番目の引数、別名 propertiesObject を使用できます。#Object.definePropertyおよび# Object.definePropertiesでも使用できます。
その有用性を説明するために、すべてRobot
を厳密に金属で作成し ( を使用writable: false
)、値を標準化powerConsumption
する (ゲッターとセッターを使用) とします。
// Add interface for Typescript, omit for Javascript
interface Robot extends Person {
madeOf: 'metal'
powerConsumption: string
}
// add `: Robot` for TypeScript, omit for Javascript.
const Robot: Robot = Object.create(Person, {
// define your property attributes
madeOf: {
value: "metal",
writable: false, // defaults to false. this assignment is redundant, and for verbosity only.
configurable: false, // defaults to false. this assignment is redundant, and for verbosity only.
enumerable: true // defaults to false
},
// getters and setters
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.')
}
}
})
// add `: Robot` for TypeScript, omit for Javascript.
const newRobot: Robot = Object.create(Robot)
newRobot.powerConsumption = '5MWh'
console.log(newRobot.powerConsumption) // outputs 5,000kWh
また、 のすべてのプロトタイプは、他のものにすることはRobot
できませmadeOf
ん。
const polymerRobot = Object.create(Robot)
polymerRobot.madeOf = 'polymer'
console.log(polymerRobot.madeOf) // outputs 'metal'