用Javascript扩展对象


164

我目前正在从Java转换为Javascript,这对我来说很难找出如何以我想要的方式扩展对象。

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

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

但是我不知道如何使该系统正常工作。如果不可能,请告诉我另一种扩展对象的方法。


返回true;但是,这就是为什么我要问:)
维图兹

2
我建议去通过对MDN这个美丽tuutorial: - developer.mozilla.org/en/...
Pranav

如果在阅读了这些不错的文档之后,您仍然对extend函数感到好奇,我在这里设置了一个示例:jsfiddle.net/k9LRd
Codrin Eugeniu,

2
我还建议不要将其严格地视为“从Java到JavaScript的转换”,而更多地考虑为“学习一种具有与Java相似的语法的新语言Javascript”
Toni Leigh 2015年

Answers:


195

您要从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

4
我有一个问题:Person()构造函数在调用时如何调用new Robot()?在我看来,您应该调用该基类构造函数,而不是this.name = name;在该Robot()构造函数中执行...
Alexis Wilke 2014年

21
@AlexisWilke:是的,您应该致电Person.apply(this, arguments);。最好使用Robot.prototype = Object.create(Person.prototype);代替new Person();
Felix Kling 2014年

18
正如Felix所说,“ Robot.prototype = Person.prototype;” 如果有人希望“机器人”类型拥有自己的原型实例,这是一个坏主意。添加新的特定于机器人的功能还将其添加到人员中。
詹姆斯·威尔金斯

20
这个例子是完全错误的。通过这样做,您可以更改Person的原型。那不是继承,您有可能在Person类中放入巨大的混乱。请参阅建议使用Object.create()的答案。那是做事的正确方法。
nicolas-van 2014年

6
@osahyoun这个答案在Google搜索中排名很高。我真的建议您按照此处其他评论的建议修复代码并更正原型链。
raphaëλ

101

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

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

*此示例针对ES6类进行了更新。

首先,请记住Javascript是一种原型语言。它不是基于类的。因此,以原型形式书写可以揭示其真实本性,并且可以非常简单,类似散文并且功能强大。

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'

不,您不需要构造函数,不需要new实例化(请阅读为什么不使用的原因new),不super,不需要搞笑__construct。您只需创建对象,然后对其进行扩展或变形。

如果您了解getter和setter,请参阅“更多阅读”部分,以了解此模式如何以Javascript 最初打算的方式为您提供免费的getter和setter ,以及它们的功能强大。)

类似于散文的语法:基本原型

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

*正确的术语是prototypesdescendants。没有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

提供创建的“默认”方式的descendant一种#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')

以下方式具有较低的可读性:

与“经典”等效项进行比较:

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!

使用“经典”样式的代码可读性不是很好。

ES6类

诚然,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

分支基础原型

// create a `Robot` prototype by extending the `Person` prototype:
const Robot = Object.create(Person)
Robot.type = 'robot'
Robot.variant = '' // add properties for Robot prototype

附加的方法 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

检查继承

Person.isPrototypeOf(Robot) // outputs true
Robot.isPrototypeOf(Skywalker) // outputs false

您已经拥有了所需的一切!没有构造函数,没有实例化。干净,清晰的散文。

进一步阅读

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

对于免费的getter和setter或其他配置,可以使用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, 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

而且所有的原型Robot都不能是madeOf其他东西,因为writable: false

const polymerRobot = Object.create(Robot)

polymerRobot.madeOf = 'polymer'

console.log(polymerRobot.madeOf) // outputs 'metal'

Mixins(使用#Object.assign)-阿纳金·天行者

你能感觉到这要去哪里吗?

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.

好吧,达斯·维达(Darth Vader)是人还是机器确实是主观的:

“他现在比人更是机器,扭曲而邪恶。” -Obi-Wan Kenobi

“我知道你有好处。” - 卢克·天行者

额外-#Object.assign的语法略短

这种模式很可能会缩短语法。但是ES6#Object.assign可以缩短一些时间(要在旧版浏览器上使用polyfill,请参阅ES6上的MDN)。

//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.
})

7
反对不使用构造函数。
nsmarks,2015年

1
“经过经典培训的”程序员,这是什么意思?
佩特拉

1
我来自经典的OOP心态,这个答案对我很有帮助。关于代码的两个问题:1)今天的ES2015是否可以Object.assign(Robot, {a:1}替代您的extend()方法?2)如何重写greet()方法,使其返回相同的文本,但附加“欢迎重写”?
巴里·史塔斯

2
1)#Object.assign看起来确实是个不错的选择。但是浏览器支持的atm较低。2)您将使用__proto__对象的属性访问其原型的greet函数。然后调用传入的被调用者的作用域来调用原型greet函数。在这种情况下,该函数是控制台日志,因此无法“追加”。但是,通过这个例子,我认为您会有所作为。skywalker.greet = function() { this.__proto__.greet.call(this); console.log('a greet override'); }
Calvintwr '16

1
嗯,这是ECMAScript语言规范维护者应该进行的讨论。我通常都同意,但是我必须努力解决。

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

18
请注意,当使用“父”类中的对象/数组时,这将创建指向“子”类中原始对象的指针。详细说明:如果在父类中有一个对象或数组,则在扩展于该基类的子类中对其进行修改,实际上将为在同一基类上扩展的所有子类对其进行修改。
哈罗德

哈罗德(Harold),感谢您强调这一事实。对于使用该功能的人来说,合并条件是很重要的,该条件检查对象/数组并对其进行复制。
tomilay 2014年

30

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


2
只是想说@GaretClaborn,它可以正常工作,但是您没有将name参数传递给父构造函数,像这样:jsfiddle.net/3brm0a7a/3(差异在第8行)
xPheRe

1
@xPheRe啊,我明白了,谢谢。我编辑了答案以反映这一变化
Garet Claborn

1
@xPheRe,我想我在添加此解决方案时会更注重证明一点。谢谢。
Lior Elrom '16

1
尼斯回答+1,你可以看看在ECMAScript 6.关键词类和扩展可用:developer.mozilla.org/en-US/docs/Web/JavaScript/...
本杰明凄美

26

在ES6中,您可以使用像

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

请注意,Object.assign()触发设置器,而扩展语法则不会。

有关更多信息,请参见链接,MDN-扩展语法


旧答案:

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

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

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

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


18

一年后,我可以告诉您另一个好答案。

如果您不喜欢为了扩展对象/类而进行原型设计的方式,请查看以下内容: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"

好吧,我还有2个月的时间才过去了两年:P无论哪种方式,JOII 3.0即将发布:)
Harold

1
做到三年后。

有趣的概念,但是语法看起来很丑陋。您最好等待ES6课程稳定下来
sleepycal 2015年

我完全同意@sleepycal。但不幸的是,所有主流/通用浏览器都至少要再过5年才能实现此功能。因此,在那之前,这必须做...
Harold

12

仍在努力寻求简单最佳方法的人,可以使用它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



6

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

https://developer.mozilla.org/zh-CN/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(谷歌浏览器/ Firefox)中提供了此技术-2015年3月3日每晚生成。


4

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

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


“纯javascript实现”不是指仅用JavaScript实现的东西,不是指可能由本机实现的环境提供的功能吗?
2015年

1
@binki,我的意思是本机javascript实现-ECMAScript 2015(ES6)标准的一部分
Cezary Daniel Nowak

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

1
通过new ParentClass()在覆盖构造函数之前进行调用,您已经执行了父构造函数。如果您问我,我认为这不是正确的行为……
Harold

1

摘要:

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

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

当我们想从javascript对象扩展时,我们可以简单地在原型链中链接该对象。有很多方法可以实现这一点,我将介绍2种常用方法。

例子:

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.明确设置原型属性

使用构造函数创建对象时,我们可以将add属性设置为其原型对象属性。使用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));


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
}

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() + '!';
};

或简单地说:扩展JSON和OBJECT都相同

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

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

多亏罗斯的伤害,达斯坦·迪亚兹


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

-2

原型制作是一种不错的方法,但是原型有时很危险,并且可能导致错误。我更喜欢将其封装到基础对象中,就像Ember.js对它的Ember.Object.extend和Ember.Object.reopen所做的那样。使用起来更安全。

我创建了一个要点,说明如何设置 Ember.Object所使用的类似的东西。

这是链接:https : //gist.github.com/WebCloud/cbfe2d848c80d4b9e9bd


9
Prototyping is a nice way, but prototype is quite dangerous sometimes and can lead to bugs.你是什​​么意思?在JavaScript中使用原型链会导致错误吗?这就像在Java上使用类会导致错误,而且绝对没有道理。
HMR 2014年

@HMR他说的是,扩展环境提供的对象原型会导致脆弱的代码,这可能会与将来的核心JavaScript语言功能发生冲突。如果您通过扩展Object的原型将有用的实用程序函数添加到所有内容中,则该函数可能与将来的JavaScript函数具有相同的名称,并在将来运行时导致代码爆炸。例如,假设您向实例添加了一个repeat()函数Object并对其进行了调用String,然后您的JavaScript运行时更新为ES6?
2015年

@binki谢谢您的输入。您正在谈论更改不“拥有”的类的原型,从而打破封装参考:developer.mozilla.org/en/docs/Web/JavaScript/…JS没有私有变量,因此您的API公开了实现成员,通常可以通过约定解决(以下划线开头的成员名称)。不知道这是op的主要问题还是语法令人困惑,并且很多人不理解它。
HMR 2015年

@HMR,我可能错了,但是我认为“但是原型很危险”指的是臭名昭著的原型框架,该框架可能会滥用prototype语言功能
2015年

进行原型设计很危险,因为如果您使用的是未创建的对象,那么您并不总是知道将它们用作原型的副作用。例如看这个小提琴:jsfiddle.net/fo6r20rg
Arkain 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.