[英]Object.defineProperty with constructor function and prototype
我剛剛發現了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“亞歷山大”
但是我對此感到非常困惑,例如這個得分很高的答案鼓勵上述方法,而這個答案說它會占用所有內存,因為將為我實例化的每個對象重新創建函數。 它建議采用以下方法:
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
方法,因為我覺得代碼很干凈,我可以將它與繼承一起使用。 但是,如果確實為每個對象重新創建了函數,我可能需要考慮第二種方法嗎?
有人可以解釋一下哪種方法適合我嗎?
根本不要使用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;
如果確實為每個對象重新創建了函數,我可能需要考慮第二種方法嗎?
是的,這是真的,但可以忽略不計。 您不應該過早地進行微觀優化。 您將知道何時真正需要它,然后您仍然可以輕松地交換實現。 在此之前,請選擇干凈簡單的代碼。
我參加聚會可能有點晚了,但我想在這里指出一些小事。
首先,您在構造函數中定義自己的屬性,這對於實例特定屬性和不應與其他實例共享的屬性很好,但不適用於 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 ,這是一個令人難以置信的強大工具,它非常先進和靈活,以至於這種工具甚至存在的可能性純屬巧合無限不可能。
該工具的存在本身就證明上帝的存在,對此我痛心地說,也證明上帝不存在。
不要在構造函數中創建 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中,對象只有屬性。 它們沒有方法,但它們具有指向 function 的屬性。
當您嘗試訪問對象( 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
。
接下來是繼承和方法定義。 這里有兩種思想流派; 一個用Object.create
創建的新對象覆蓋Base.prototype
,然后繼續創建方法:
// 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。然后您增加該值,並將其分配給您的實例。
你開始於:
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;
})();
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.