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

时间:2016-08-04 22:54:29

标签: javascript node.js class ecmascript-6 symbols

问题:

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

  

我没有搜索“ES没有受保护/包裹”这样的回复   属性“。它已经知道了。我想要一个漂亮而清洁的解决方法   模仿受保护的属性。

我不想添加安全性。只有更清晰的公开界面才能API的所有最终用户。

示例:

我有以下API :( 节点

my-class.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

my-child-class.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,那就是他们的错误)< / p>

4 个答案:

答案 0 :(得分:1)

你的方法毫无意义。

符号不提供任何安全性,因为它们是公开的。您可以使用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;

答案 1 :(得分:1)

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

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

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

<强> vehicle.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

<强> client.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

请牢记这一点,虽然混淆就足够了,但可以考虑采用这种方法。

答案 2 :(得分:1)

使用WeakMap method for private properties的变体在ES6中可以使用受保护的属性。

基本技术是:

  1. 为每个类存储对实例受保护数据的私有弱引用。
  2. 在超级构造函数中创建受保护的数据。
  3. 将受保护的数据从超级构造函数传递给子类构造函数。
  4. 简单演示(旨在明确但不是理想的功能,请参阅下面的改进)。这将在父类中设置受保护的数据,并在子类中访问它。如果没有方法暴露它,那么类之外的任何东西都无法访问它:

    &#13;
    &#13;
    // 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'
    &#13;
    &#13;
    &#13;

    这里有一些可以改进的细节:

    1. 这不适用于其他子类。我们清除了第一个子类中的受保护数据,因此进一步的构造函数将不会接收它。
    2. 新实例有&#39; prot&#39;在它的钥匙。我们清除了子类构造函数中的属性,但它仍然会枚举。这里使用deletedelete is very slow
    3. 很有吸引力

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

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

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

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

      &#13;
      &#13;
      // 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();
      &#13;
      &#13;
      &#13;

答案 3 :(得分:0)

#用作私人(例如#someProperty), 将_用于受保护(例如_someProperty), 没有公共前缀。