Мир без нового ключевого слова.
И более простой прозаический синтаксис с Object.create ().
* Этот пример обновлен для классов ES6 и TypeScript.
Во-первых, на самом деле Javascript - это язык-прототип, а не на основе классов. Его истинная природа выражена в приведенной ниже прототипной форме, которая, как вы, возможно, заметили, очень проста, похожа на прозу, но все же действенна.
TL; DR;
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."
Это устраняет иногда запутанный шаблон конструктора. Новый объект наследует старый, но может иметь свои собственные свойства. Если мы попытаемся получить член из нового объекта (#greet()
), которого не хватает новому объекту jack
, старый объект Person
предоставит член.
Выражаясь словами Дугласа Крокфорда: объекты наследуются от объектов. Что может быть более объектно-ориентированным, чем это?
Вам не нужны ни конструкторы, ни new
создание экземпляров ( прочтите, почему не следует использовать new
), ни super
, ни самодельного __construct
. Вы просто создаете объекты, а затем расширяете или изменяете их.
Этот шаблон также предлагает неизменность (частичную или полную) и геттеры / сеттеры.
Машинопись
Эквивалент TypeScript выглядит так же:
interface Person {
name: string,
greet: Function
}
const Person = {
name: 'Anonymous',
greet: function(): void { console.log(`Hi, I am ${this.name}.` }
}
const jack: Person = Object.create(Person)
jack.name = 'Jack'
jack.greet()
Прозаический синтаксис: Person
protoype
const Person = {
//attributes
firstName : 'Anonymous',
lastName: 'Anonymous',
birthYear : 0,
type : 'human',
//methods
name() { return this.firstName + ' ' + this.lastName },
greet() {
console.log('Hi, my name is ' + this.name() + ' and I am a ' + this.type + '.' )
},
age() {
// age is a function of birth time.
}
}
const person = Object.create(Person). // that's it!
Чисто и ясно. Его простота не влияет на возможности. Читать дальше.
Создание потомка / копии Person
Примечание. Правильными терминами являются prototypes
и их descendants/copies
. Нет ни classes
, ни instances
.
const Skywalker = Object.create(Person)
Skywalker.lastName = 'Skywalker'
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
Если вы чувствуете себя менее безопасно, выбрасывая конструкторы вместо прямых заданий, справедливый момент. Один из распространенных способов - прикрепить #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
В TypeScript вам также потребуется расширить Person
интерфейс:
interface Robot extends Person {
machineGreet: Function
}
const Robot: Robot = Object.create(Person)
Robot.machineGreet = function(): void { console.log(101010) }
И у вас могут быть миксины - потому что ... Дарт Вейдер - человек или робот?
const darthVader = Object.create(anakin)
// for brevity, property assignments are skipped because you get the point by now.
Object.assign(darthVader, Robot)
Дарт Вейдер использует методы 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, которые стали более популярными и совместимы с браузерами (или не имеют отношения к babel):
Классы 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
По общему признанию, некоторые из этих проблем устраняются классами ES6. Когда я писал свой ответ пять лет назад, классы ES6 только зарождались и теперь созрели для использования.
Но под капотом классов ES6 скрывается истинная прототипная природа Javascript. Поэтому, естественно, я был разочарован его реализацией.
Тем не менее, нельзя сказать, что классы ES6 плохие. Он предоставляет множество новых функций и стандартизирован в удобочитаемой форме. Хотя на самом деле не следовало использовать операторы class
и new
, чтобы запутать всю проблему.
дальнейшее чтение
Возможность записи, настраиваемость и бесплатные методы получения и установки!
Для бесплатных методов получения и установки или дополнительной конфигурации вы можете использовать второй аргумент Object.create (), также известный как propertiesObject. Он также доступен в # Object.defineProperty и # Object.defineProperties.
Чтобы проиллюстрировать его полезность, предположим, что мы хотим, чтобы все Robot
были строго сделаны из металла (через writable: false
) и стандартизировали powerConsumption
значения (через геттеры и сеттеры).
const Robot = Object.create(Person, {
// define your property attributes
madeOf: {
value: "metal",
writable: false,
configurable: false,
enumerable: true
},
// 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.')
}
}
})
const newRobot = 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'
person
Calvintwr
schedule
02.02.2015
extend
, я привел пример здесь: jsfiddle. net / k9LRd - person Codrin Eugeniu   schedule 03.05.2012