Thế giới không có từ khóa "mới".
Và cú pháp "giống như văn xuôi" đơn giản hơn với Object.create ().
* Ví dụ này được cập nhật cho các lớp ES6.
Trước hết, hãy nhớ rằng Javascript là một ngôn ngữ nguyên mẫu . Nó không dựa trên lớp học. Do đó, viết ở dạng nguyên mẫu cho thấy bản chất thực sự của nó, và có thể rất đơn giản, giống như văn xuôi và mạnh mẽ.
TLDR;
const Person = { name: 'Anonymous' } // person has a name
const jack = Object.create(Person) // jack is a person
jack.name = 'Jack' // and has a name 'Jack'
Không, bạn không cần nhà xây dựng, không cần new
khởi tạo ( đọc lý do tại sao bạn không nên sử dụngnew
), không super
, không hài hước buồn cười __construct
. Bạn chỉ cần tạo Đối tượng và sau đó mở rộng hoặc biến đổi chúng.
( Nếu bạn biết về getters và setters, hãy xem phần "Đọc thêm" để xem cách mẫu này cung cấp cho bạn getters và setters miễn phí theo cách mà Javascript ban đầu dự định và sức mạnh của chúng .)
Cú pháp giống như văn xuôi: Base 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!
Nhìn thoáng qua, trông rất dễ đọc.
Mở rộng, tạo ra một hậu duệ của Person
* Các điều khoản chính xác là prototypes
, và của họ descendants
. Không có classes
, và không cần 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
Một cách để cung cấp một cách "mặc định" để tạo một descendant
, là bằng cách đính kèm một #create
phương thức:
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')
Những cách dưới đây có khả năng đọc thấp hơn:
So sánh với tương đương "cổ điển":
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')
Person.isPrototypeOf(anakin) // returns false!
Skywalker.isPrototypeOf(anakin) // returns false!
Khả năng đọc mã bằng cách sử dụng kiểu "cổ điển" là không tốt.
Lớp ES6
Phải thừa nhận rằng, một số vấn đề trong số này đã bị các lớp ES6 xóa bỏ, nhưng vẫn:
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
Phân nhánh nguyên mẫu cơ sở
// create a `Robot` prototype by extending the `Person` prototype:
const Robot = Object.create(Person)
Robot.type = 'robot'
Robot.variant = '' // add properties for Robot prototype
Đính kèm các phương thức duy nhất để Robot
// Robots speak in binaries, so we need a different greet function:
Robot.machineGreet = function() { /*some function to convert strings to binary */ }
// morphing the `Robot` object doesn't affect `Person` prototypes
anakin.greet() // 'Hi, my name is Anakin Skywalker and I am a human.'
anakin.machineGreet() // error
Kiểm tra thừa kế
Person.isPrototypeOf(Robot) // outputs true
Robot.isPrototypeOf(Skywalker) // outputs false
Bạn đã có mọi thứ bạn cần rồi! Không xây dựng, không khởi tạo. Văn xuôi sạch sẽ, rõ ràng.
đọc thêm
Khả năng ghi, Cấu hình và Getters và Setters miễn phí!
Đối với getters và setters miễn phí hoặc cấu hình bổ sung, bạn có thể sử dụng đối số thứ hai của Object.create () aka propertyObject. Nó cũng có sẵn trong # Object.defineProperty và # Object.defineProperIES .
Để minh họa mức độ mạnh mẽ của nó, giả sử chúng ta muốn tất cả Robot
được làm hoàn toàn bằng kim loại (thông qua writable: false
) và chuẩn hóa powerConsumption
các giá trị (thông qua getters và setters).
const Robot = Object.create(Person, {
// define your property attributes
madeOf: {
value: "metal",
writable: false,
configurable: false,
enumerable: true
},
// getters and setters, how javascript had (naturally) intended.
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
Và tất cả các nguyên mẫu Robot
không thể là madeOf
cái gì khác bởi vì writable: false
.
const polymerRobot = Object.create(Robot)
polymerRobot.madeOf = 'polymer'
console.log(polymerRobot.madeOf) // outputs 'metal'
Mixins (sử dụng # Object.assign) - Anakin Skywalker
Bạn có thể cảm nhận được nơi này đang diễn ra ...?
const darthVader = Object.create(anakin)
// for brevity, property assignments are skipped because you get the point by now.
Object.assign(darthVader, Robot)
Darth Vader có các phương thức Robot
:
darthVader.greet() // inherited from `Person`, outputs "Hi, my name is Darth Vader..."
darthVader.machineGreet() // inherited from `Robot`, outputs 001010011010...
Cùng với những điều kỳ lạ khác:
console.log(darthVader.type) // outputs robot.
Robot.isPrototypeOf(darthVader) // returns false.
Person.isPrototypeOf(darthVader) // returns true.
Chà, dù Darth Vader là đàn ông hay máy móc thì thực sự chủ quan:
"Bây giờ anh ta là cỗ máy nhiều hơn con người, xoắn và ác." - Obi-Wan Kenobi
"Tôi biết có tốt trong bạn." - Luke Skywalker
Thêm - Cú pháp ngắn hơn một chút với # Object.assign
Trong tất cả khả năng, mẫu này rút ngắn cú pháp của bạn. Nhưng ES6 # Object.assign có thể rút ngắn thêm một số (Để polyfill sử dụng trên các trình duyệt cũ hơn, xem MDN trên ES6 ).
//instead of this
const Robot = Object.create(Person)
Robot.name = "Robot"
Robot.madeOf = "metal"
//you can do this
const Robot = Object.create(Person)
Object.assign(Robot, {
name: "Robot",
madeOf: "metal"
// for brevity, you can imagine a long list will save more code.
})