我参加聚会可能有点晚了,但我想在此指出几件小事。
首先,您在构造函数中定义自己的属性,这对于实例特定属性和不应与其他实例共享的属性很好,但不适用于 getter、setter 和方法。相反,您应该在函数的原型上定义它们。
我理解你这样的问题
你如何制作真正的私人财产?
这很简单,并且适用于所有浏览器。首先我们将详细介绍 Object.defineProperty,以及派生的 Object.create、Object.defineProperties。
javascript object定义的prototype
现代 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))
}
ECMAScript 6(无聊)
为了说明“现代 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++;
}
}
继承ABC
EcmaScript 中的继承非常简单,只要你真正了解它!
TLDR:如果你想让上面的类扩展另一个类,你可以这样做:
Object.setPrototypeOf(Base.prototype, Parent.prototype);
这应该适用于任何地方。它本质上是Base.prototype.__proto__ = Parent.prototype。
Base.prototype 与 实例原型
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 的实例)上的属性时,引擎会像这样查找该属性:
| Checked location |
Description |
this.$HERE$ |
On your local properties |
this.__proto__.$HERE$ |
__proto__ is a reference to Base.prototype. |
this.__proto__.__proto__.$HERE$ |
This would be your parent class prototype. |
this.__proto__.__proto__.__proto__.$HERE$ |
This would be your grand-parent class prototype. |
| ...and on we go, searching through the prototype chain, until there are no more prototypes. |
The search is done using Object.prototype.hasOwnProperty, which means that even if the value is undefined, the search will stop. |
__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 属性覆盖。
old-school 类继承定义
这门课什么都有:
看,“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;
})();