繁体   English   中英

ES6 类中的受保护属性(使用符号?)

[英]Protected properties in ES6 classes (using Symbols?)

问题:

您将如何以优雅的方式在ES6类中实现受保护的属性? 只能从子类内部访问

我不是在搜索诸如“ES 没有受保护/包属性”之类的响应。 它已经是众所周知的。 我想要一个更好更干净的解决方法来模拟受保护的属性。

我不想增加安全性 只为API所有最终用户提供一个更清晰的公开界面


示例:

我有以下API :(节点

我的类.js:

let Symbols = {
    _secret: Symbol("_secret")
};
class MyClass {
    constructor() {
        this.public = "This is public";
        this[Symbols._secret] = "This is private";
    }
}
// Set the Symbols to a static propietry so any class can access it and extend it
MyClass[Symbol.for("_Symbols")] = Symbols;
module.exports = MyClass

我的孩子类.js:

let MyClass = require("./my-class.js");

// extends protected properties with own properties
Symbols = Object.assign({}, MyClass[Symbol.for("_Symbols")] , {
    _childSecret = Symbol("_childSecret")
});

class MyChildClass extends MyClass {
    constructor() {
        super();
        this[Symbols._childSecret] = "This is also private";
        console.log(this[Symbols._secret]); //logs "this is private"
        console.log(this[Symbols._childSecret]); //logs "this is also private"
    }
}
// Set the Symbols to a static propietry so any class can access it and extend it
MyClass[Symbol.for("_Symbols")] = Symbols;
module.exports = MyChildClass;

要使用该类:

let MyChildClass = require("./my-child-class.js");
var c = new MyChildClass();

优点:

  • 暴露的API更干净。 API的最终用户可以查看公开的方法。

问题:

  • 代码在基类中“漂亮”,但在子类中不是那么漂亮。 有什么办法可以改善秩序吗?

  • 任何可以访问Symbol.for("_Symbols")都可以访问 API 的所有受保护/私有属性。 编辑:我不介意这一点。这对我来说不是问题,因为如果有人想破坏访问内部符号的 API,那是他们的错

声明:使用模块和符号是 ES2015+ 中的一种信息隐藏技术(但使用符号的类属性将被隐藏,而不是严格私有 - 根据 OP 问题和假设)。

轻量级信息隐藏可以通过 ES2015模块(只会导出您声明为导出的内容)和 ES2015 符号的组合来实现。 Symbol是一种新的内置类型。 每个新的 Symbol 值都是独一无二的。 因此可以用作对象上的键。

如果客户端调用代码不知道用于访问该密钥的符号,则他们无法获得它,因为该符号未导出。 示例:

车辆.js

const s_make = Symbol();
const s_year = Symbol();

export class Vehicle {

  constructor(make, year) {
    this[s_make] = make;
    this[s_year] = year;
  }

  get make() {
    return this[s_make];
  }

  get year() {
    return this[s_year];
  }
}

并使用模块 Vehicle.js

客户端.js

import {Vehicle} from './vehicle';
const vehicle1 = new Vehicle('Ford', 2015);
console.log(vehicle1.make); //Ford
console.log(vehicle1.year); // 2015

然而,符号虽然是独一无二的,但实际上并不是私有的,因为它们是通过像 Object.getOwnPropertySymbols 这样的反射特性公开的...

const vals = Object.getOwnPropertySymbols(vehicle1);
vehicle1[vals[0]] = 'Volkswagon';
vehicle1[vals[1]] = 2013;
console.log(vehicle1.make); // Volkswagon
console.log(vehicle1.year); // 2013

请记住这一点,尽管在混淆足够的情况下,也可以考虑这种方法。

在 ES6 中可以使用WeakMap 方法的变体来保护私有属性

基本技术是:

  1. 为每个类存储对实例保护数据的私有弱引用。
  2. 在超级构造函数中创建受保护的数据。
  3. 将受保护的数据从超级构造函数传递给子类构造函数。

简单演示(旨在清晰但不是理想的功能,请参阅下文以进行改进)。 这会在父类中设置受保护的数据并在子类中访问它。 如果没有方法公开它,类之外的任何东西都不能访问它:

 // Define parent class with protected data const Parent = (()=>{ const protData = new WeakMap(); class Parent { constructor () { // Create and store protected data for instance protData.set(this,{ prop: 'myProtectedProperty', meth () { return 'myProtectedMethod'; } }); // If called as super pass down instance + protected data if(new.target!==Parent){ this.prot = protData.get(this); } } setText (text) { const prot = protData.get(this); prot.text = text; } getText () { const prot = protData.get(this); return prot.text; } } return Parent; // Expose class definition })(); // Define child class with protected data const Child = (()=>{ const protData = new WeakMap(); class Child extends Parent { constructor (...args) { super(...args); protData.set(this,this.prot); // Store protected data for instance this.prot = undefined; // Remove protected data from public properties of instance } getTextChild () { const prot = protData.get(this); return prot.text; } } return Child; // Expose class definition })(); // Access protected data const child = new Child(); child.setText('mytext'); console.log(child.getText()); // 'mytext' console.log(child.getTextChild()); // 'mytext'

这里有几个细节可以改进:

  1. 这不适用于进一步的子类。 我们清除第一个子类中的受保护数据,以便进一步的构造函数不会收到它。
  2. 新实例的键中有“保护”。 我们在子类构造函数中清除了该属性,但它仍然会枚举。 在这里使用delete很诱人,但delete 很慢

求解任意数量的子类很容易。 如果我们被称为超级,只需保留受保护的数据:

if(new.target!==Child)this.prot=undefined;

对于剩余的属性,我喜欢的解决方案是在基类中创建一个全新的实例,并使用绑定this分别传递实例和受保护的数据。 然后你就有了一个完全干净的实例并且没有删除性能命中。 您必须在构造函数中使用一些习语才能使其工作,但这是完全可能的。

这是解决这些问题的最终解决方案:

 // Protected members in ES6 // Define parent class with protected data const Parent = (()=>{ const protData = new WeakMap(); let instanceNum = 0; class Parent { constructor (...args) { // New instance since we will be polluting _this_ // Created as instance of whichever class was constructed with _new_ const inst = Object.create(this.constructor.prototype); // .. do normal construction here *on inst* // If called as super pass down instance + protected data if(new.target!==Parent){ protData.set(inst,{ // Create and store protected data for instance instanceNum: ++instanceNum }); this.inst=inst; // Pass instance this.prot=protData.get(inst); // Pass protected data } // If called directly return inst as construction result // (or you could raise an error for an abstract class) else return inst; } sayInstanceNum () { const prot = protData.get(this); console.log('My instance number is: '+prot.instanceNum); } setInstanceNumParent (num) { const prot = protData.get(this); prot.instanceNum = num; } } return Parent; // Expose class definition })(); // Define child class with protected data const Child = (()=>{ const protData = new WeakMap(); class Child extends Parent { constructor (...args) { super(...args); protData.set(this.inst,this.prot); // Store protected data for instance // If called directly return inst as construction result, // otherwise leave inst and prot for next subclass constructor if(new.target===Child)return this.inst; } celebrateInstanceNum () { const prot = protData.get(this); console.log('HONKYTONK! My instance number is '+prot.instanceNum+'! YEEHAWW!'); } setInstanceNumChild (num) { const prot = protData.get(this); prot.instanceNum = num; } } return Child; // Expose class definition })(); // Define grandchild class with protected data const Grandchild = (()=>{ const protData = new WeakMap(); class Grandchild extends Child { constructor (...args) { super(...args); protData.set(this.inst,this.prot); // Store protected data for instance // If called directly return inst as construction result, // otherwise leave inst and prot for next subclass constructor if(new.target===Grandchild)return this.inst; } adoreInstanceNum () { const prot = protData.get(this); console.log('Amazing. My instance number is '+prot.instanceNum+' .. so beautiful.'); } setInstanceNumGrandchild (num) { const prot = protData.get(this); prot.instanceNum = num; } } return Grandchild; // Expose class definition })(); // Create some instances to increment instance num const child1 = new Child(); const child2 = new Child(); const child3 = new Child(); const grandchild = new Grandchild(); // Output our instance num from all classes grandchild.sayInstanceNum(); grandchild.celebrateInstanceNum(); grandchild.adoreInstanceNum(); // Set instance num from parent and output again grandchild.setInstanceNumParent(12); grandchild.sayInstanceNum(); grandchild.celebrateInstanceNum(); grandchild.adoreInstanceNum(); // Set instance num from child and output again grandchild.setInstanceNumChild(37); grandchild.sayInstanceNum(); grandchild.celebrateInstanceNum(); grandchild.adoreInstanceNum(); // Set instance num from grandchild and output again grandchild.setInstanceNumGrandchild(112); grandchild.sayInstanceNum(); grandchild.celebrateInstanceNum(); grandchild.adoreInstanceNum();

你的做法毫无意义。

符号不提供任何安全性,因为它们是公开的。 您可以使用Object.getOwnPropertySymbols轻松获取它们。

因此,如果您不关心安全性而只想简单,请使用普通的_secret属性。

class MyClass {
  constructor() {
    this.public = "This is public";
    this._secret = "This is private";
  }
}
module.exports = MyClass;
let MyClass = require("./my-class.js");
class MyChildClass extends MyClass {
  constructor() {
    super();
    this._childSecret = "This is also private";
    console.log(this._secret); // "this is private"
    console.log(this._childSecret); // "this is also private"
  }
}
module.exports = MyChildClass;

使用#表示私有(例如#someProperty ),使用_表示受保护(例如_someProperty ),公共没有前缀。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM