在 Javascript 中扩展对象

IT技术 javascript function object prototype extends
2021-01-30 11:58:49

我目前正在从 转换JavaJavascript,我很难弄清楚如何以我想要的方式扩展对象。

我在互联网上看到有几个人使用一种称为扩展对象的方法。代码如下所示:

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(...);

但我不知道如何使这个系统工作。如果不可能,请告诉我另一个扩展对象的替代方案。

6个回答

您想从 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
我有一个问题:Person()当你这样做时构造函数是如何被调用的new Robot()在我看来,您应该调用该基类构造函数而不是this.name = name;Robot()构造函数中执行...
2021-03-25 11:58:49
这个例子是完全错误的。通过这样做,你改变了 Person 的原型。这不是继承,您可能会在 Person 类中造成一团糟。请参阅建议使用 Object.create() 的答案。这是做事的正确方法。
2021-03-31 11:58:49
正如 Felix 所说,'Robot.prototype = Person.prototype;' 如果有人希望“机器人”类型拥有自己的原型实例,这是一个坏主意。添加新的 Robot 特定功能也会将其添加到 person。
2021-04-02 11:58:49
@AlexisWilke:是的,你应该打电话给Person.apply(this, arguments);. 最好使用Robot.prototype = Object.create(Person.prototype);而不是new Person();.
2021-04-04 11:58:49
@osahyoun 这个答案在谷歌搜索中排名很高。我真的建议您按照此处其他评论的建议修复代码并更正原型链。
2021-04-09 11:58:49

没有“new”关键字的世界。

使用 Object.create() 更简单的“散文式”语法。

*此示例针对 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 中,您需要设置接口,这些接口将在您创建Person原型的后代时进行扩展突变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实例化。您只需创建对象,然后扩展或变形它们。

此模式还提供不变性(部分或全部)getter/setter

整洁明了。它的简单性不会影响功能。继续阅读。

创建 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

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

当您RobotPerson原型分支后代时,您不会影响Skywalkeranakin

// 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(): 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)

达斯维达获得以下方法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

进一步阅读

可写性、可配置性和免费的 Getter 和 Setter!

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

为了说明它的用处,假设我们希望所有东西都Robot严格由金属制成(通过writable: false),并标准化powerConsumption值(通过 getter 和 setter)。


// 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'
- Object.create 在许多(旧的)浏览器中不存在,尤其是 Internet Explorer 8 及更低版本。- Object.create() 仍然调用你通过它传递的函数的构造函数。- 对于每个属性声明,您必须一遍又一遍地配置相同的设置(如示例代码中所示)。使用 Object.create 而不是new关键字并没有真正的好处
2021-03-14 11:58:49
我来自经典的 OOP 思维模式,这个答案对我帮助很大。关于代码的两个问题:1)今天的 ES2015Object.assign(Robot, {a:1}是你extend()方法的一个很好的替代品吗?2) 如何覆盖该greet()方法,使其返回相同的文本,但附加了“问候覆盖”?
2021-03-14 11:58:49
“受过经典训练的”程序员,你是什么意思?
2021-03-20 11:58:49
嗯,这应该与 ECMAScript 语言规范维护者进行讨论。我大体上同意,但我必须用我所拥有的来工作。
2021-03-29 11:58:49
1)#Object.assign看起来确实是一个不错的选择。但浏览器支持较低的atm。2) 您将使用__proto__对象属性来访问其原型的greet 函数。然后你使用传入的被调用者的范围调用原型 greet 函数。在这种情况下,该函数是一个控制台日志,所以不可能“附加”。但是通过这个例子,我认为你明白了。skywalker.greet = function() { this.__proto__.greet.call(this); console.log('a greet override'); }
2021-04-09 11:58:49

如果你还没有想出办法,可以使用 JavaScript 对象的 associative 属性来添加一个扩展函数,Object.prototype如下所示。

Object.prototype.extend = function(obj) {
   for (var i in obj) {
      if (obj.hasOwnProperty(i)) {
         this[i] = obj[i];
      }
   }
};

然后您可以使用此功能,如下所示。

var o = { member: "some member" };
var x = { extension: "some extension" };

o.extend(x);
哈罗德,感谢您强调这一事实。对于使用该函数的人来说,合并一个检查对象/数组并制作它们的副本的条件很重要。
2021-03-19 11:58:49
请注意,当使用“父”类中的对象/数组时,这将创建指向“子”类中原始对象的指针。详细说明:如果您的父类中有一个对象或数组,在扩展到该基类的子类中修改它,实际上会为扩展到同一基类的所有子类修改它。
2021-04-03 11:58:49

在 ES6 中,您可以使用扩展运算符,例如

var mergedObj = { ...Obj1, ...Obj2 };

请注意, Object.assign() 会触发 setter,而 spread 语法不会。

有关更多信息,请参阅链接,MDN -Spread Syntax


旧答案:

在 ES6 中,有Object.assign用于复制属性值。{}如果您不想修改目标对象(传递的第一个参数),用作第一个参数。

var mergedObj = Object.assign({}, Obj1, Obj2);

有关更多详细信息,请参阅链接,MDN - Object.assign()

如果您需要ES5Polyfill,该链接也提供了它。:)

不同的方法:Object.create

根据@osahyoun 的回答,我发现以下是从 Person 的原型对象“继承”的更好更有效的方法:

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

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

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

// Set Robot's prototype to Person's prototype by
// creating a new object that inherits from Person.prototype,
// and assigning it to Robot.prototype
Robot.prototype = Object.create(Person.prototype);

// Set constructor back to Robot
Robot.prototype.constructor = Robot;

创建新实例:

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

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

现在,通过使用Object.create

Person.prototype.constructor !== Robot

另请查看MDN文档。

@xPheRe 啊,我明白了,谢谢。我编辑了答案以反映这种变化
2021-03-23 11:58:49
不错的答案 +1,您可以查看 ECMAScript 6。关键字类和扩展可用:developer.mozilla.org/en-US/docs/Web/JavaScript/...
2021-03-24 11:58:49
@xPheRe,我想当我添加这个解决方案时,我更专注于证明一个观点。谢谢。
2021-03-31 11:58:49
只想说@GaretClaborn 它工作正常,但您没有将name参数传递给父构造函数,如下所示:jsfiddle.net/3brm0a7a/3(差异在第 8 行)
2021-04-06 11:58:49