我刚刚发现了Object.defineProperty
,因为我最熟悉C#我希望使用带有构造函数的访问器属性,例如:
function Base(id) {
var _id = id;
Object.defineProperty(this,"ID",{
get: function() { return _id; },
set: function(value) { _id = value; }
})
}
function Derived(id, name) {
var _name = name;
Base.call(this,id);
Object.defineProperty(this,"Name",{
get: function() { return _name; },
set: function(value) { _name = value; }
})
}
Derived.prototype = Object.create(Base.prototype);
Derived.constructor = Derived;
var b = new Base(2);
var d = new Derived(4,"Alexander");
console.log(b.ID);
console.log(d.ID, d.Name);
d.ID = 100;
console.log(d.ID, d.Name);

打印:
2
4"亚历山大"
100" Alexander"
但我对此非常困惑,例如this answer with a very high score鼓励采用上述方法,而this answer表示会占用所有内存,因为将为我实例化的每个对象重新创建函数。它建议采用以下方法:
var Base = function(id){this.__id = id}
Player.prototype = {
get ID(){
return this.__id;
},
set ID(value){
this.__id = value;
}
}
var p = new Player();
p.ID = 2;
alert(p.ID); // 4
然而,这种方法还会创建另一个公共属性__id
,这对我来说似乎不太理想(我的示例中的属性是"特权"因为它在javascript中被称为,所以不需要额外的公共财产)。
有人可以解释哪种方法适合我吗?现在我在javascript文档丛林中完全丢失。我非常喜欢Object.defineProperty
方法,因为代码对我来说非常干净,我可以将它用于继承。但是,如果为每个对象重新创建函数,我可能需要考虑第二种方法吗?
答案 0 :(得分:1)
有人可以解释哪种方法适合我吗?
根本不要使用Object.defineProperty
。你绝对不需要属性描述符,你的getter和setter也没有做任何特殊的事情。只需使用简单的普通属性即可。它会比您关注的任何其他内容更快,更优化。
function Base(id) {
this.ID = id;
}
function Derived(id, name) {
Base.call(this,id);
this.Name = name;
}
Derived.prototype = Object.create(Base.prototype);
Derived.prototype.constructor = Derived;
如果为每个对象重新创建函数,我可能需要考虑第二种方法吗?
是的,这是真的,但可以忽略不计。你不应该过早地进行微观优化。您将知道何时需要它,然后您仍然可以轻松交换实现。在那之前,请使用干净简单的代码。
答案 1 :(得分:1)
我参加聚会可能有点晚了,但我想在这里指出一些小事。
首先,您在构造函数中定义自己的属性,这对于实例特定属性和不应与其他实例共享的属性很好,但不适用于 getter、setter 和方法。相反,您应该在函数原型上定义这些。
我是这样理解你的问题
这很容易,并且适用于所有浏览器。首先我们将详细说明Object.defineProperty,以及派生的Object.create、Object.defineProperties。
现代 javascript 允许使用一些“语法糖”来声明类,但是每个自豪的开发人员都应该了解它的实际工作原理。
第一没有课程。 Javascript 没有类。它有原型,而不是类。 类类,所以在我天真固执的情况下,上过几节课去寻找真相,反驳又反驳——一路走到了谷底。没有课。
让 you
成为对象。如果 you
或 your prototype
不能回答问题,那么您的 prototypes´ prototype
将被询问,然后您的 prototypes´ prototypes´ prototype
将被询问,然后我们继续直到没有更多原型要问。
所有这些原型仍然是对象。这个算法说明了它:
// Friendly version
function askQuestion(askee, question) {
do {
if (askee.hasOwnProperty(question)) {
return askee[question];
}
} while (askee = Object.getPrototypeOf(askee))
}
为了说明“现代 javascript”的语法,我将离题:
class Tortoise extends Turtle {
constructor() {
while (this.__proto__) {
this.__proto__ = Object.getPrototypeOf(this.__proto__);
}
}
#privateProperty = "This isn't a comment"
get __proto__() { return Object.getPrototypeOf(this); }
set __proto__(to) { Object.setPrototypeOf(this, to); }
}
这不适用于所有浏览器。它也不适用于任何浏览器。只是为了展示语法。
有人声称上述内容只是老式 javascript 之上的语法糖,但当涉及到扩展本机对象时,它怀疑它的作用远不止表面上的那么简单。
即使“现代 javascript”(ECMAScript 6)允许我们编写上述类,您也应该理解它。
<块引用>此外,现代 javascript 与 Internet Explorer 11 的顽固性相结合,迫使我们使用 babel,这是一个非常棒的工具,具有令人难以置信的功能,令人难以置信的先进和灵活,这种工具甚至存在的可能性,纯属巧合,是无限不可能的。
工具本身的存在就是证明上帝的存在,对此我痛心地说,也证明上帝不存在。的
WTF??请参阅 here 和 there,以及 there。
不要在构造函数中创建 REUSABLE 属性。多个实例使用的函数不应在构造函数中赋值。
真实版
function Base(id) {
// GOOD
this.id = id;
// GOOD, because we need to create a NEW array for each
this.tags = [];
// Okay, but could also just be in the prototype
this.numberOfInteractions = 0;
// BAD
this.didInteract = function() {
this.numberOfInteractions++;
}
}
糖衣
class Base {
constructor(id) {
// GOOD
this.id = id;
// GOOD, because we need to create a NEW array for each
this.tags = [];
// Okay, but could also just be in the prototype
this.numberOfInteractions = 0;
// BAD
this.didInteract = function() {
this.numberOfInteractions++;
}
}
}
改进的真实版本
function Base(id) {
this.id = id;
this.tags = [];
}
Base.prototype.numberOfInteractions = 0;
Base.prototype.didInteract = function() {
this.numberOfInteractions++;
}
改进的糖衣版本
如果你坚持要加糖,并且想在写完代码后写更多的代码行和一些额外的劳动,你可以安装 babel 并像下面这样写你的代码。
它会生成稍大和稍慢的脚本文件 - 除非您真的不需要支持所有浏览器。
class Base {
constructor(id) {
this.id = id;
this.tags = [];
}
numberOfInteractions = 0;
didInteract() {
this.numberOfInteractions++;
}
}
EcmaScript 中的继承非常简单,一旦你真正理解它!
TLDR:如果你想让上面的类扩展另一个类,你可以这样做单行:
Object.setPrototypeOf(Base.prototype, Parent.prototype);
这应该适用于任何地方。它本质上是Base.prototype.__proto__ = Parent.prototype
。
Base.prototype
vs 实例原型
javascript 中的所有对象都有一个实例原型。为对象属性搜索“默认值”的是实例原型。
function MyConstructor() { /* example function or "class" */ }
上述语句创建了一个名为 MyConstructor
的对象,它有一个引用 Function.prototype
的实例原型。同时也是一个可以调用的函数。
这很重要:
MyConstructor instanceof Function;
// is TRUE because
Object.getPrototypeOf(MyConstructor) === Function.prototype
// this is NOT TRUE
MyConstructor.prototype === Function.prototype
因为这种细微的差别
var myInstance = new MyConstructor();
myInstance instanceof MyConstructor;
// this is TRUE because
Object.getPrototypeOf(myInstance) === MyInstance.prototype
现在,MyConstructor.prototype
只是一个空对象(它有一个引用 Object.prototype
的实例原型)。
访问属性
在javascript中,对象只有属性。它们没有方法,但具有指向函数的属性。
当您尝试访问对象(Base
的实例)上的属性时,引擎会像这样查找该属性:
检查位置 | 说明 |
---|---|
this.$HERE$ |
在您当地的房产 |
this.__proto__.$HERE$ |
__proto__ 是对 Base.prototype 的引用。 |
this.__proto__.__proto__.$HERE$ |
这将是您的父类原型。 |
this.__proto__.__proto__.__proto__.$HERE$ |
这将是您的祖父类原型。 |
...然后我们继续搜索原型链,直到没有更多的原型。 | 搜索是使用Object.prototype.hasOwnProperty 完成的,这意味着即使值为undefined ,搜索也会停止。 |
__proto__
是一个魔法属性,它已被弃用,取而代之的是 Object.getPrototypeOf()
。为简洁起见,我使用它。
通过原型链找到的任何属性,在返回给您之前都将绑定到this
。
接下来是继承和方法定义。这里有两种思想流派;一个用 Base.prototype
创建的新对象覆盖 Object.create
,然后继续创建方法:
// This works (but it overwrites the Base.prototype object)
Base.prototype = Object.create(ParentClass);
// Declare a method
Base.prototype.incrementMyValue = function() {
this.myValue++;
}
// A default value
Base.prototype.myValue = 123
// A getter
Object.defineProperty(Base.prototype, 'getMyValue', {get: function() {
return myValue;
}});
在上面的代码中,我想指出的是,当您访问instance.myValue
时,它将是未定义的,因此扫描原型链,您将获得123
。
如果您第一次调用 instance.incrementMyValue()
,原型链将被扫描并返回 123。然后您增加该值,并将它分配给您的实例< /em>.
您开始于:
instance // no .myValue exists
instance.__proto__.myValue = 123
调用:
instance.incrementValue();
你最终得到:
instance.myValue = 124;
instance.__proto__.myValue = 123
默认值仍然存在,但被实例本地 myValue
属性覆盖。
这个类拥有一切:
看哪,“Charm”类,从我希望发布的 Charm.js 库中借出想法:
var Derived = (function(){
/* Wrapped in a function, so we keep things private*/
/**
* CONSTRUCTOR
*/
function Derived(id, name) {
Base.call(this, id); // Calling the parent constructor
private(this).name = name; // Setting a TRUE private property
}
/**
* PRIVATE STATIC PROPERTIES
*/
var thisIsPrivateAndShared = 0;
/**
* PUBLIC STATIC PROPERTIES
*/
Derived.thisIsPublicAndShared = 0;
/**
* PRIVATE NON-STATIC PROPERTIES THROUGH WeakMap
*/
var secrets = new WeakMap();
function private(for) {
var private = secrets.get(for);
if (!private) {
private = {};
secrets.set(for, private);
}
return private;
}
/**
* Building the prototype
*/
Derived.prototype = Object.create(
/**
* EXTEND Base.prototype (instead of Object.prototype)
*/
Base.prototype,
/**
* Declare getters, setters, methods etc (or see below)
*/
{
/**
* GETTERS AND SETTERS FOR THE PRIVATE PROPERTY 'name'
*/
name: {
get: function() { // getter
return private(this).name;
},
set: function(value) { // setter
private(this).name = value;
}
},
/**
* A PUBLIC METHOD
*/
method: {value: function() {
}},
/**
* A PUBLIC PROPERTY WITH A DEFAULT VALUE
*/
age: {value: 42, writable: true}
});
/**
* I am too lazy to write property descriptors,
* unless I want a getter/setter/private properties,
* so I do this:
*/
Derived.prototype.lessWorkMethod = function() {
};
}
return Derived;
})();