Private and Protected Properties
JavaScript Private and Protected Properties and Methods (JavaScript私有和受保护的属性和方法)
One of the most complicated issues throughout the history of JavaScript is privacy. (JavaScript历史上最复杂的问题之一是隐私。)
In JavaScript, it’s not tricky to make functions and variables private. But it’s not possible to make object properties private. It can become a severe problem when it comes to managing the state of an instance. No private properties mean that each code with access to your instance may alter its state in any way. (在JavaScript中,将函数和变量设置为私有并不棘手。但无法将对象属性设置为私有。在管理实例的状态时,它可能会成为一个严重的问题。没有私有属性意味着可以访问实例的每个代码都可以以任何方式更改其状态。)
Most of the JavaScript codes today are written like this:
function Car() {
this.milage = 0;
}
Car.prototype.drive = function (miles) {
if (typeof miles == 'number' && miles > 0) {
this.milage += miles;
} else {
throw new Error('drive only accepts positive numbers');
}
};
Car.prototype.readMilage = function () {
return this.milage;
};
let bmw = new Car();
console.log(bmw);
bmw.milage = 'pwned';
console.log(bmw);
Here, the drive method takes a number incrementing the mileage property of the car instance. And like other methods, it checks to assure the input is valid before using it; otherwise it may end up with bad data.
But the difficulty is that the check doesn’t help to avoid bad data since anyone with access to the instance could manually change them milage property. (但困难在于,检查无助于避免不良数据,因为有权访问实例的任何人都可以手动更改其milage属性。)
One Step Closer
One Step Closer (更接近一步)
The truth is that properties can’t be made private, but to set properties on an instance is not the only way of managing its state. There might exist another object linked explicitly to the instance, which is responsible for storing its state. This second object might be private. (事实是,属性不能设置为私有,但为实例设置属性并不是管理其状态的唯一方法。可能存在显式链接到实例的另一个对象,该对象负责存储其状态。第二个对象可能是私有的。)
The possible example is here:
let Car = (function () {
// Create a store to hold private objects.
(//创建一个存储来保存私有对象。)
let privateCarStore = {};
let uId = 0;
function Car(milage) {
// Create an object to manage the state of this instance and use a unique
(//创建一个对象来管理此实例的状态,并使用唯一的)
// identifier to refer to it in private storage.
(//在专用存储中引用它的标识符。)
privateCarStore[this.id = uId++] = {};
// Store personal stuff in the private store
(//将个人物品存放在私人商店)
// instead of on `this`.
(//而不是“this”。)
privateCarStore[this.id].milage = milage || 0;
}
Car.prototype.drive = function (miles) {
if (typeof miles == 'number' && miles > 0)
(if (typeof miles = = 'number' & & miles > 0))
privateCarStore[this.id].milage += miles;
else
(adj.其他)
throw new Error('drive can only accept positive numbers');
};
Car.prototype.readMilage = function () {
return privateCarStore[this.id].milage;
};
return Car;
}());
The Problems
The Problems (问题)
This method works, but it has a range of downsides. (这种方法是有效的,但它有一系列的缺点。)
Here they are:
You have to type too much additional code. In the event of having tens or hundreds of modules, it will immediately become a burden. It is necessary to store an ID property on each instance. It’s both annoying and potentially conflicting. By applying privateCarStore[this.id] instead of this, you lose your access to the prototype of the instance. It’s impossible to reference private members in subclasses defined in different scopes. That’s because they aren’t considered protected members. Finally, it’s not memory efficient.
Private Properties
Private Properties (私人物业)
In the original car class example, you might notice a private property named milage that we already discovered wasn’t considered private. (在原始汽车类示例中,您可能会注意到我们已经发现名为milage的私有财产不被视为私人财产。)
The example below demonstrates the same class rewritten applying Private Parts. Take into consideration that although the code looks nearly identical, the privacy is real now:
let Car = (function () {
let cKey = PrivateParts.createKey();
function Car(milage) {
// Store the mileage property privately.
(//私密存放里程物业。)
cKey(this).milage = milage;
}
Car.prototype.drive = function (miles) {
if (typeof miles == 'number' && miles > 0) {
cKey(this).milage += miles;
} else {
throw new Error('drive only accepts positive numbers');
}
}
Car.prototype.readMilage = function () {
return cKey(this).milage;
}
return Car;
}());
The core difference is one line of the code that executes a method called createKey storing it on the underscore variable. The other difference is that in the previous examples, it was written this.milage, while now it is - cKey(this).milage. (核心区别在于一行代码执行名为createKey的方法,将其存储在下划线变量上。另一个区别是,在前面的示例中,写成this.milage ,而现在写成- cKey (this) .milage。)
You may guess that the underscore variable, which stores the result of the createKey method is a function taking the this object and returning its private instance. Here the idea is the same as in the second example. (您可能会猜到,存储createKey方法结果的下划线变量是获取this对象并返回其私有实例的函数。这里的想法与第二个示例相同。)
The underscore variable in the example above is known as the “key function”. It is possible to create new key functions by calling the createKey method. A key function accepts an object (“public instance”) and returns a new object (“private instance”). It is connected to the public instance; hence there is a possibility of storing private properties on it.
It works as the only way to access the private instance is via the key function, and the only way of accessing the key function is to be in the scope where it’s declared. (它的工作原理是,访问私有实例的唯一方法是通过key函数,访问key函数的唯一方法是在声明它的作用域中。)
So, in case you always define your classes/modules within a closure keeping your key function within that closure, you will manage to have private members. (因此,如果您总是在闭包中定义类/模块,并将关键函数保持在该闭包中,您将设法拥有私有成员。)
Private Methods
Private Methods (移动私有方法)
In JavaScript, private methods have always been semi-possible due to the dynamic this and the Function prototype methods such as call and apply. (在JavaScript中,由于动态this和函数原型方法(如调用和应用) ,私有方法一直是半可能的。)
Here is an example:
function privateMethod() {
this.doSomething();
}
// The public method can call the above function
// and retain the `this` context.
SomeClass.prototype.publicMethod = function () {
privateMethod.call(this);
}
But to use call or apply is not as beneficial as to directly invoke a private method on an object.Moreover, it will not allow you to chain multiple methods together. The solution to such a problem can become the Private Parts. (但是,使用call或apply并不像直接在对象上调用私有方法那样有益。此外,它不允许将多个方法链接在一起。 这种问题的解决方案可以成为私有部分。)
The function createKey accepts an optional argument, which, when passed, is used for controlling how private instances are created. In case the createKey is passed an object, that object is applied as the prototype for all newly created private instances. (函数createKey接受可选参数,该参数传递后用于控制私有实例的创建方式。如果createKey传递了一个对象,则该对象将应用为所有新创建的私有实例的原型。)
So, this object becomes a kind of “private prototype” as it’s in the prototype chain, but only the private instances can access it:
let privateMethods = {
privateMethodOne: function () { ...
},
privateMethodTwo: function () { ...
}
}
let cKey = PrivateParts.createKey(privateMethods);
SomeClass.prototype.publicMethod = function () {
// Now the private methods can be called
(//现在可以调用私有方法)
// directly on the private instances.
(//直接在私有实例上。)
cKey(this).privateMethodOne();
cKey(this).privateMethodTwo();
}
let privateMethods = Object.create(SomeClass.prototype);
privateMethods.privateMethodOne = function () { ...
};
privateMethods.privateMethodTwo = function () { ...
};
let cKey = PrivateParts.createKey(privateMethods);
There can be cases when a private method may need to invoke a public method. To make it work, privateMethods needs to have the public prototype in its prototype chain. You can achieve it quickly by applying Object.create for initiating the object of privateMethods:
The example below demonstrates what the prototype chain may look like for private instances that were created that way. So, both the public and private methods are accessible to private instances, and only the public methods can be accessible to ordinary instances:
// The private instance prototype chain.
cKey(this) >>> privateMethods >>> SomeClass.prototype
// The public instance prototype chain.
this >>> SomeClass.prototype
Protected Members and Class Hierarchies
Protected Members and Class Hierarchies (受保护的成员和类层次结构)
The key function of the Private Parts limits the access of private members to the scope where it’s defined. (私有部分的关键功能限制了私有成员对其定义范围的访问。)
But, in case your programs consist of subclasses defined in distinguished scopes or files altogether, then you need to find another solution. (但是,如果您的程序由在可分辨作用域或文件中定义的子类组成,则需要找到其他解决方案。)
The Private Parts is considered a low-level solution and can’t solve all the problems. (私有部分被认为是低级解决方案,无法解决所有问题。)
There exists a developer module, which is a classical inheritance implementation built to show off the power of the Private Parts. (有一个开发人员模块,这是一个经典的继承实现,旨在展示私有部分的强大功能。)
The developer module applies a closure for its class definitions. They allow the key functions to be passed to the matching subclasses still remaining inaccessible to the public. (开发人员模块对其类定义应用闭包。它们允许将关键函数传递给仍然无法被公众访问的匹配子类。)
Let’s take a look at this example:
let job = require('developer');
let Worker = job(function (prototype, cKey, protected) {
// PUBLIC
(公众)
prototype.init = function (name, age) {
cKey(this).name = name;
cKey(this).age = age;
};
prototype.vote = function (programmer) {
if (cKey(this).allowedToVote()) {
console.log(cKey(this).name + ' voted for ' + programmer);
} else {
throw new Error(cKey(this).name + ' is not allowed to vote.');
}
};
// PROTECTED
(adj.受保护)
protected.allowedToVote = function () {
return this.age > 18;
};
});
In this example, the worker class defines the public and protected methods using the passed key function to store data on the instance. (在此示例中, worker类使用传递的key函数定义public和protected方法,以在实例上存储数据。)
For subclassing the worker, it is necessary to call its subclass method. Two methods of this subclass (init and allowedToVote) are considered overridden and may call super. The method of vote is inherited. (要对worker进行子类化,需要调用其子类方法。此子类的两种方法( init和allowedToVote )被认为是重写的,可以调用super。投票方法是继承的。)
Here is an example:
let Developer = Worker.subclass(function (prototype, cKey, protected) {
prototype.init = function (name, age, develop) {
cKey(this).develop = develop;
prototype.super.init.call(this, name, age);
};
protected.allowedToVote = function () {
return cKey(this).develop != 'javascript' &&
(返回cKey (this) .develop! = 'javascript' & &)
protected.super.allowedToVote.call(this);
};
});
let programmer = new Develop('Jack', 25, 'javascript');
programmer.vote('C++'); // Throws: Jack is not develop to C++.
Also, the class definition provides two prototypes for defining methods: the public prototype and the protected prototype. The protected methods and properties can be accessed by applying the protected key (the cKey variable). The regular methods, as usual, can be accessed by using this.