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

问题描述 投票:0回答:5

问题:

如何以优雅的方式在

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,那是他们的错

javascript node.js class ecmascript-6 symbols
5个回答
3
投票

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

轻量级信息隐藏可以通过 ES2015 modules(只会导出您声明导出的内容)和 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

客户端.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

请记住这一点,尽管混淆已经足够,但可以考虑这种方法。


1
投票

在 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. 新实例的键中有“prot”。我们清除了子类构造函数中的属性,但它仍然会枚举。在这里使用 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();


0
投票

使用

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


0
投票

这可能不太优雅(因为它使用了

eval
), 但也许有些人对此感兴趣。

它通过 Proxy 提供对私有字段的访问。

概要
  • 使用“命名”的临时公共对象属性
    Proxy
    公开
    Symbol
  • 将公开的
    Proxy
    复制到子/扩展类的私有属性中。
  • 删除 (
    delete
    ) 临时公共对象属性。
  • 使用私有属性访问父类/扩展类的私有属性。
代码

    const Parent = class stat {
        #_myValue;
        static multiplicand = 2;
        constructor (guard, value) {
            this.#_myValue = value * stat.multiplicand;
            this[guard] = new Proxy(this, {
                get: function stat (target, property, receiver) {
                    return eval(`target.#_${property};`);
                },
                set: function stat (target, property, value) 
                    return eval(`target.#_${property} = value; true;`);
                }
            });
        }
        palue () { return this.#_myValue; }
    }
    const Child = class stat extends Parent {
        value () { return this.#parent.myValue; }
        #parent;
        static multiplicand = 3;
        constructor (value) {
            const guard = Symbol();
            super(guard, value);
            this.#parent = this[guard];
            delete this[guard];
            this.#parent.myValue *= stat.multiplicand;
        }
        isTrue () { return this.value() === this.palue(); }
    }
    const instance = new Child(4);
    console.log('true', instance.isTrue());

修补版本(或类似的东西)。

import {default as assert} from 'node:assert/strict';
const llog = console.log;
const log = /** /llog;//**/() => {};
const Parent = class statP
{
    static multiplicand = 2;
    #_myValue;
    constructor (guard, value)
    {
        log('parent', this)
        llog('parent.#_myValue[befor]', this.#_myValue)
        this.#_myValue = value * statP.multiplicand;
        llog('parent.#_myValue[after]', this.#_myValue)
        const handler = statP.#protectedSupport[0];
        log('handler', handler);
        this[guard] = new Proxy(this, handler);
    }
    palue ()
    {
        return this.#_myValue;
    }
    static #protectedSupport = [
        {
            get: function stat (target, property, receiver)
            {
                return eval(`
                    log("p0.  target ", target);
                    log("p0.property ", property);
                    log("p0.receiver ", receiver);
                    target.#_${property};
                `);
            },
            set: function stat (target, property, value)
            {
                return eval(`
                    log("p0.  target ", target);
                    log("p0.property ", property);
                    log("p0.   value ", value);
                    target.#_${property} = value;
                    true;
                `);
            },
        },
    ];
}
const Child = class statC extends Parent
{
    static multiplicand = 3;
    #_;
    // For autosuggestion of parent members, this might be an option:
    // #_ = {myValue: null,};
    constructor (value)
    {
        const guard = Symbol()
        super(guard, value);
        this.#_ = this[guard];
        delete this[guard];
        log('child.#_', this.#_)
        llog('child.#_.myValue[befor]', this.#_.myValue)
        this.#_.myValue *= statC.multiplicand;
        llog('child.#_.myValue[after]', this.#_.myValue)
    }
    value ()
    {
        return this.#_.myValue;
    }
}
let value = 4;
const instance = new Child(value);
assert.equal(true, instance.palue() === instance.value());
assert.equal(value * Parent.multiplicand * Child.multiplicand, instance.value());
log('value', value);
log('instance-value', instance.value());
log('instance-palue', instance.palue());
log('instance', instance);


-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;
© www.soinside.com 2019 - 2024. All rights reserved.