在 Javascript 中扩展对象
Posted
技术标签:
【中文标题】在 Javascript 中扩展对象【英文标题】:Extending an Object in Javascript 【发布时间】:2012-05-12 21:40:31 【问题描述】:我目前正在从Java
转换为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(...);
但我不知道如何使这个系统工作。如果不可能,请告诉我另一个扩展对象的替代方法。
【问题讨论】:
返回真;但这就是我问的原因:) 我建议在 MDN 上阅读这个漂亮的教程:- developer.mozilla.org/en/… 如果您在阅读了那些不错的文档后仍然对extend
函数感到好奇,我在这里设置了一个示例:jsfiddle.net/k9LRd
我还建议不要将其严格地视为“从 Java 转换为 JavaScript”,而应将其视为“学习一种与 Java 具有相似语法的新语言 Javascript”
【参考方案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
【讨论】:
我有一个问题:当您执行new Robot()
时,如何调用Person()
构造函数?在我看来,您应该调用该基类构造函数,而不是在 Robot()
构造函数中执行 this.name = name;
...
@AlexisWilke:是的,你应该打电话给Person.apply(this, arguments);
。最好使用Robot.prototype = Object.create(Person.prototype);
而不是new Person();
。
正如菲利克斯所说,'Robot.prototype = Person.prototype;'如果有人希望“机器人”类型拥有自己的原型实例,那么这是一个坏主意。添加新的机器人特定功能也会将其添加到人员。
这个例子是完全错误的。通过这样做,您可以更改 Person 的原型。这不是继承,您可能会在 Person 类中造成巨大的混乱。请参阅建议使用 Object.create() 的答案。这才是做事的正确方式。
@osahyoun 这个答案在谷歌搜索中排名很高。我真的建议您按照此处其他 cmets 的建议修复代码并更正原型链。【参考方案2】:
没有“new”关键字的世界。
使用 Object.create() 并使用更简单的“散文式”语法。
*此示例针对 ES6 类和 TypeScript 进行了更新。
首先,Javascript 是 prototypal language,不是基于类的。它的真实性质以下面的原型形式表达,您可能会发现它非常简单,像散文,但功能强大。
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's words: "Objects inherit from objects. What could be more object-oriented than that?"
您不需要构造函数,也不需要 new
实例化。您只需创建对象,然后扩展或变形它们。
此模式还提供immutability (partial or full) 和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
当你从Person
原型分支Robot
后代时,你不会影响Skywalker
和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!
对于免费的 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
关键字并没有真正的好处。
“受过经典训练的”程序员,你这是什么意思?
我来自经典的 OOP 思维方式,这个答案对我帮助很大。关于代码的两个问题:1)今天的 ES2015 Object.assign(Robot, a:1
是您的 extend()
方法的好替代品吗? 2) 如何覆盖greet()
方法,使其返回相同的文本,但附加“a greet override”?
1) #Object.assign
看起来确实是个不错的选择。但浏览器支持较低的atm。 2) 您将使用对象的__proto__
属性来访问其原型的问候功能。然后你调用原型 greet 函数并传入被调用者的范围。在这种情况下,该函数是一个控制台日志,因此不可能“附加”。但是通过这个例子,我认为你明白了。 skywalker.greet = function() this.__proto__.greet.call(this); console.log('a greet override');
嗯,这是应该与 ECMAScript 语言规范维护者进行的讨论。我大体上同意,但我必须使用我所拥有的。【参考方案3】:
如果您还没有想出办法,请使用 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);
【讨论】:
请注意,在“父”类中使用对象/数组时,这将创建指向“子”类中原始对象的指针。详细说明:如果您的父类中有一个对象或数组,则在扩展该基类的子类中对其进行修改,实际上将针对在同一基类上扩展的所有子类进行修改。 Harold,感谢您强调这一事实。对于使用该函数的人来说,加入一个检查对象/数组并复制它们的条件是很重要的。【参考方案4】:在 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,该链接也提供了它。 :)
【讨论】:
【参考方案5】:不同的方法: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 文档。
【讨论】:
只想说@GaretClaborn它工作正常,但你没有将name
参数传递给父构造函数,像这样:jsfiddle.net/3brm0a7a/3(区别在第8行)跨度>
@xPheRe 啊,我明白了,谢谢。我编辑了答案以反映这种变化
@xPheRe,我想当我添加这个解决方案时,我更专注于证明一个观点。谢谢。
不错的答案 +1,您可以查看 ECMAScript 6。关键字类和扩展可用:developer.mozilla.org/en-US/docs/Web/JavaScript/…【参考方案6】:
又过了一年,我可以告诉你还有另一个不错的答案。
如果您不喜欢原型设计以扩展对象/类的方式,请查看: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 个月的时间,直到 2 年结束 :P 不管怎样,JOII 3.0 即将发布 :) 3 年后制作。 有趣的概念,但语法看起来真的很难看。你最好等待 ES6 类变得稳定 我完全同意@sleepycal。但不幸的是,在所有主要/常见浏览器都实现这一点之前,至少还需要 5 年时间。所以在那之前,这将不得不做......【参考方案7】:还在为最简单最好的方法而苦苦挣扎的人,可以使用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。
【讨论】:
【参考方案8】:您可能需要考虑使用像 underscore.js 这样的辅助库,它有 it's own implementation of extend()
。
通过查看它的源代码,这也是一种学习的好方法。 annotated source code page 非常有用。
【讨论】:
一个 underscore.js 的_.extend()
工作原理的示例使其功能非常清晰:lostechies.com/chrismissal/2012/10/05/…【参考方案9】:
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 中提供了这项技术。
【讨论】:
【参考方案10】:在大多数项目中都有一些对象扩展的实现:下划线、jquery、lodash:extend。
还有纯 javascript 实现,它是 ECMAscript 6 的一部分:Object.assign: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
【讨论】:
“纯 javascript 实现”不是指仅用 JavaScript 实现的东西,而不是环境提供的可以本地实现的功能吗? @binki,我的意思是原生 javascript 实现 - ECMAScript 2015 (ES6) 标准的一部分【参考方案11】: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
2017 年 1 月更新:
请忽略我对 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
【讨论】:
通过在覆盖构造函数之前调用new ParentClass()
,您已经执行了父构造函数。如果你问我,我认为那不是正确的行为......【参考方案12】:
总结:
Javascript 使用一种称为原型继承 的机制。在查找对象的属性时使用原型继承。当我们在 javascript 中扩展属性时,我们是从实际对象继承这些属性。它的工作方式如下:
-
当请求对象属性时(例如
myObj.foo
或myObj['foo']
),JS 引擎将首先在对象本身上查找该属性
当在对象本身上找不到此属性时,它会爬上原型链查看原型对象。如果这里也没有找到这个属性,它将继续爬上原型链,直到找到该属性。如果未找到该属性,则会引发引用错误。
当我们想在 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));
【讨论】:
【参考方案13】:简单易读的解决方案是使用扩展运算符
...
例如:
const obj1 = a: "a" const obj2 = b: "b" const result = ...obj1, ..obj2, console.log("result", result) // must be a: "a", b: "b"
【讨论】:
【参考方案14】:您可以简单地使用:
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
【讨论】:
【参考方案15】:请添加拒绝投票的原因
无需使用任何外部库进行扩展
在 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*/
感谢罗斯·哈姆斯,达斯汀·迪亚兹
【讨论】:
【参考方案16】:虽然这项工作并非 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
【讨论】:
【参考方案17】:这将使您的属性扩展创建一个新的对象,其中包含对象参数原型,而不会更改传递的对象。
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
【讨论】:
以上是关于在 Javascript 中扩展对象的主要内容,如果未能解决你的问题,请参考以下文章