178

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

我在互联网上看到有几个人使用了一种名为 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(...);

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

4

17 回答 17

198

您想从 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
于 2012-05-03T11:55:08.443 回答
122

使用 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原型的后代时进行扩展。突变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实例化。您只需创建对象,然后扩展或变形它们。

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

干净利落。它的简单性不会影响功能。继续阅读。

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

当您Robot从原型分支后代时Person,您不会影响Skywalkerand 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(): 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

进一步阅读

可写性、可配置性和免费的获取器和设置器!

对于免费的 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'
于 2015-02-02T16:08:18.567 回答
51

如果您还没有想出办法,请使用 JavaScript 对象的关联属性将扩展函数添加到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);
于 2013-11-04T20:38:55.283 回答
37

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

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

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

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


旧答案:

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

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

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

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

于 2016-01-15T08:33:14.450 回答
31

不同的方法: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文档。

于 2014-08-04T16:42:37.897 回答
19

又过了一年,我可以告诉你还有另一个不错的答案。

如果您不喜欢原型设计以扩展对象/类的方式,请查看:https ://github.com/haroldiedema/joii

可能性的快速示例代码(以及更多):

var Person = Class({

    username: 'John',
    role: 'Employee',

    __construct: function(name, role) {
        this.username = name;
        this.role = role;
    },

    getNameAndRole: function() {
        return this.username + ' - ' + this.role;
    }

});

var Manager = Class({ extends: Person }, {

  __construct: function(name)
  {
      this.super('__construct', name, 'Manager');
  }

});

var m = new Manager('John');
console.log(m.getNameAndRole()); // Prints: "John - Manager"
于 2014-01-22T15:09:13.520 回答
14

仍在为简单和最佳方法而奋斗的人,可以Spread Syntax用于扩展对象。

var person1 = {
      name: "Blank",
      age: 22
    };

var person2 = {
      name: "Robo",
      age: 4,
      height: '6 feet'
    };
// spread syntax
let newObj = { ...person1, ...person2 };
console.log(newObj.height);

注意:请记住,最右边的属性将具有优先权。在此示例中,person2位于右侧,因此其中newObj将包含名称Robo

于 2018-10-08T11:02:53.667 回答
8

您可能需要考虑使用像underscore.js这样的辅助库,它有自己的extend().

通过查看它的源代码,这也是一种学习的好方法。带注释的源代码页面非常有用。

于 2012-05-03T12:45:39.983 回答
6

Mozilla '宣布' 从 ECMAScript 6.0 扩展的对象:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/extends

注意:这是一项实验性技术,是 ECMAScript 6 (Harmony) 提案的一部分。

class Square extends Polygon {
  constructor(length) {
    // Here, it calls the parent class' constructor with lengths
    // provided for the Polygon's width and height
    super(length, length);
    // Note: In derived classes, super() must be called before you
    // can use 'this'. Leaving this out will cause a reference error.
    this.name = 'Square';
  }

  get area() {
    return this.height * this.width;
  }

  set area(value) {
    this.area = value;     } 
}

该技术在 Gecko (Google Chrome / Firefox) - 03/2015 nightly builds 中可用。

于 2015-04-09T11:30:26.717 回答
4

在大多数项目中,都有一些对象扩展的实现:下划线、jquery、lodash:extend

还有纯 javascript 实现,它是 ECMAscript 6 的一部分:Object.assignhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

于 2015-03-14T18:26:40.020 回答
2
Function.prototype.extends=function(ParentClass) {
    this.prototype = new ParentClass();
    this.prototype.constructor = this;
}

然后:

function Person() {
    this.name = "anonym"
    this.skills = ["abc"];
}
Person.prototype.profile = function() {
    return this.skills.length // 1
};

function Student() {} //well extends fom Person Class
Student.extends(Person)

var s1 = new Student();
s1.skills.push("")
s1.profile() // 2

更新 01/2017:

请忽略我对 2015 年的回答,因为 Javascript 现在支持extends自 ES6 以来的关键字(Ecmasctipt6)

- ES6:

class Person {
   constructor() {
     this.name = "anonym"
     this.skills = ["abc"];
   }

   profile() {
    return this.skills.length // 1
   }

}

Person.MAX_SKILLS = 10;
class Student extends Person {


} //well extends from Person Class

//-----------------
var s1 = new Student();
s1.skills.push("")
s1.profile() // 2

- ES7:

class Person {
    static MAX_SKILLS = 10;
    name = "anonym"
    skills = ["abc"];

    profile() {
      return this.skills.length // 1
    }

}
class Student extends Person {


} //well extends from Person Class

//-----------------
var s1 = new Student();
s1.skills.push("")
s1.profile() // 2
于 2015-02-19T06:43:08.563 回答
2

简单易读的解决方案是使用扩展运算符

...

例如:

const obj1 = {a: "a"} const obj2 = {b: "b"} const result = {...obj1, ..obj2,} console.log("result", result) // must be {a: "a", b: "b"}
于 2021-09-07T11:55:35.623 回答
2

概括:

Javascript 使用一种称为原型继承的机制。在查找对象的属性时使用原型继承。当我们在 javascript 中扩展属性时,我们是从实际对象继承这些属性。它以下列方式工作:

  1. 当请求对象属性时,(例如myObj.foomyObj['foo'])JS 引擎将首先在对象本身上查找该属性
  2. 当在对象本身上找不到此属性时,它将爬上原型链查看原型对象。如果这里也没有找到这个属性,它将继续爬上原型链,直到找到该属性。如果未找到该属性,它将引发引用错误。

当我们想从 javascript 中的对象扩展时,我们可以简单地将这个对象链接到原型链中。有很多方法可以实现这一点,我将描述两种常用的方法。

例子:

1.Object.create()

Object.create()是一个将对象作为参数并创建一个新对象的函数。作为参数传递的对象将是新创建对象的原型。例如:

// prototype of the dog
const dogPrototype = {
  woof: function () { console.log('woof'); }
}

// create 2 dog objects, pass prototype as an argument
const fluffy = Object.create(dogPrototype);
const notFluffy = Object.create(dogPrototype);

// both newly created object inherit the woof 
// function from the dogPrototype
fluffy.woof();
notFluffy.woof();

2.显式设置原型属性

在使用构造函数创建对象时,我们可以为其原型对象属性设置添加属性。使用关键字时从构造函数创建的对象,new其原型设置为构造函数的原型。例如:

// Constructor function object
function Dog (name) {
   name = this.name;
}

// Functions are just objects
// All functions have a prototype property
// When a function is used as a constructor (with the new keyword)
// The newly created object will have the consturctor function's
// prototype as its prototype property
Dog.prototype.woof = function () {
  console.log('woof');
}

// create a new dog instance
const fluffy = new Dog('fluffyGoodBoyyyyy');
// fluffy inherits the woof method
fluffy.woof();

// can check the prototype in the following manner
console.log(Object.getPrototypeOf(fluffy));

于 2018-10-03T17:39:08.480 回答
0

虽然这项工作并非 100% 正确

// Parent
var Parent = function (name) {
  this.name = name;
  this.test = function () {
    console.log("parent test");
  }
  this.testOverride = function () {
    console.log("parent testOverride");
  }
}
// define a function extend
Parent.prototype.extend = function () {
  // parent properties to override or add
  var override = arguments[0];
  return function () {
    Parent.apply(this, arguments);
    // add and override properties
    Object.keys(override).forEach(el =>{
      this[el] = override[el];
    })
   }
}
// create a Child from the Parent and override
// the function "testOverride" and keep "test" unchanged
var Child = Parent.prototype.extend({
  y: 10,
  testOverride: function () { 
    console.log("child testOverride"); 
  }
});
// Create an object of type Parent
var p = new Parent("Parent");
// Create an object of type Child
var c = new Child("Child");
console.log(p.name);
// Parent
console.log(c.name);
// Child
p.test();
//parent test
p.testOverride();
//parent testOverride
c.test();
//parent test
c.testOverride();
//child testOverride
于 2021-07-04T16:47:05.747 回答
0

您可以简单地使用:

Object.prototype.extend = function(object) {
  // loop through object 
  for (var i in object) {
    // check if the extended object has that property
    if (object.hasOwnProperty(i)) {
      // mow check if the child is also and object so we go through it recursively
      if (typeof this[i] == "object" && this.hasOwnProperty(i) && this[i] != null) {
        this[i].extend(object[i]);
      } else {
        this[i] = object[i];
      }
    }
  }
  return this;
};

更新:我检查了this[i] != null因为null是一个对象

然后像这样使用它:

var options = {
      foo: 'bar',
      baz: 'dar'
    }

    var defaults = {
      foo: false,
      baz: 'car',
      nat: 0
    }

defaults.extend(options);

这很好的结果是:

// defaults will now be
{
  foo: 'bar',
  baz: 'dar',
  nat: 0
}
于 2016-02-23T13:36:00.600 回答
0

请添加拒绝投票的原因

  • 无需使用任何外部库来扩展

  • 在 JavaScript 中,一切都是对象(除了三种原始数据类型,甚至在需要时也会自动用对象包装)。此外,所有对象都是可变的。

JavaScript 中的类人

function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype = {
    getName: function() {
        return this.name;
    },
    getAge: function() {
        return this.age;
    }
}

/* Instantiate the class. */
var alice = new Person('Alice', 93);
var bill = new Person('Bill', 30);

修改特定的实例/对象

alice.displayGreeting = function() 
{
    alert(this.getGreeting());
}

修改类

Person.prototype.getGreeting = function() 
{
    return 'Hi ' + this.getName() + '!';
};

或者简单地说:extend JSON 和 OBJECT 都是一样的

var k = {
    name : 'jack',
    age : 30
}

k.gender = 'male'; /*object or json k got extended with new property gender*/

感谢罗斯·哈姆斯,达斯汀·迪亚兹

于 2016-04-20T14:24:07.203 回答
-1

这将使扩展您的属性创建一个具有对象参数原型的新对象,而无需更改传递的对象。

function extend(object) {
    if (object === null)
        throw TypeError;
    if (typeof object !== "object" && typeof object !== "function")
        throw TypeError;
    if (Object.create)
        return Object.create(object);
    function f() {}
    ;
    f.prototype = p;
    return new f();
}

但是如果你想在不修改参数的情况下扩展你的对象,你可以将 extendProperty 添加到你的对象中。

var Person{
//some code
extend: extendProperty
}

//Enforce type checking an Error report as you wish
    function extendProperty(object) {
        if ((object !== null && (typeof object === "object" || typeof object === "function"))){
            for (var prop in object) {
                if (object.hasOwnProperty(prop))
                    this[prop] = object[prop];
            }
        }else{
            throw TypeError; //Not an object
        }
    }
于 2018-01-19T10:18:38.900 回答