在 Javascript 中扩展对象更简单的“类似散文”的语法,使用 Object.create()进一步阅读

我目前正在从 转换为 ,对我来说,要弄清楚如何以我想要的方式扩展对象有点困难。JavaJavascript

我在互联网上看到几个人使用一种叫做extend on object的方法。代码将如下所示:

var Person = {
   name : 'Blank',
   age  : 22
}

var Robot = Person.extend({
   name : 'Robo',
   age  : 4
)}

var robot = new Robot();
alert(robot.name); //Should return 'Robo'

有谁知道如何做到这一点?我听说你需要写

Object.prototype.extend = function(...);

但我不知道如何让这个系统工作。如果不可能,请向我展示另一种扩展对象的替代方法。


答案 1

您希望从 Person 的原型对象“继承”:

var Person = function (name) {
    this.name = name;
    this.type = 'human';
};

Person.prototype.info = function () {
    console.log("Name:", this.name, "Type:", this.type);
};

var Robot = function (name) {
    Person.apply(this, arguments);
    this.type = 'robot';
};

Robot.prototype = Person.prototype;  // Set prototype to Person's
Robot.prototype.constructor = Robot; // Set constructor back to Robot

person = new Person("Bob");
robot = new Robot("Boutros");

person.info();
// Name: Bob Type: human

robot.info();
// Name: Boutros Type: robot

答案 2

更简单的“类似散文”的语法,使用 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

在TypeScript中,您需要设置接口,这些接口将在您创建原型的后代时进行扩展。突变显示了在后代上附加新方法的示例。PersonpoliteGreetjack

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()  // "Hi, I am Jack."
jack.politeGreet('Sir') // "Dear Sir, I am Jack."

这免除了有时复杂的构造函数模式。新对象继承自旧对象,但能够具有自己的属性。如果我们尝试从新对象 () 中获取新对象缺少的成员,则旧对象将提供该成员。#greet()jackPerson

Douglas Crockford 的话来说:“对象继承自对象。还有什么比这更面向对象的呢?

你不需要构造函数,不需要实例化。您只需创建对象,然后扩展或变形它们。new

此模式还提供不可变性(部分或全部)以及 getters/setter。

干净,清晰。它的简单性不会影响功能。请继续阅读。

创建 Person 的后代/副本(技术上比 ) 更正确。prototypeclass

*注意:以下示例在 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

如果你觉得丢弃构造函数来代替直接赋值不太安全,一种常见的方法是附加一个方法:#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')

将原型分支到PersonRobot

当您从原型分支后代时,您不会影响和:RobotPersonSkywalkeranakin

// create a `Robot` prototype by extending the `Person` prototype:
const Robot = Object.create(Person)
Robot.type = '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(): void
}
const Robot: Robot = Object.create(Person)
Robot.machineGreet = function() { console.log(101010) }

你可以有Mixins -- 因为..达斯·维达是人类还是机器人?

const darthVader = Object.create(anakin)
// for brevity, property assignments are skipped because you get the point by now.
Object.assign(darthVader, 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

进一步阅读

可写性,可配置性以及免费的获取器和Setters!

对于免费的 getter 和 setters,或者额外的配置,你可以使用 Object.create() 的第二个参数,又名 propertiesObject。它也在#Object.defineProperty#Object.defineProperties中可用。

为了说明它的有用性,假设我们希望所有金属都严格由金属(via)和标准化值(通过getter和setters)制成。Robotwritable: falsepowerConsumption


// 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

机器人的所有原型都不能由其他东西制成

const polymerRobot = Object.create(Robot)
polymerRobot.madeOf = 'polymer'
console.log(polymerRobot.madeOf) // outputs 'metal'