【问题标题】:How to declare private variables and private methods in es6 class [duplicate]如何在es6类中声明私有变量和私有方法[重复]
【发布时间】:2016-04-24 11:41:43
【问题描述】:

在 es5 中我们使用构造函数

function Person(name,gender){

    var initial ="";    // we use var key word to make variable private

    function getNameWithInitial(){ // this is the private method to get name with initial
        console.log(this);
        initial = this.gender ==="male"?"Mr. ":"Mrs. ";
        return initial + this.name;
    }


    this.name = name;
    this.gender = gender;
    this.getName = function(){
        return getNameWithInitial.call(this);
    }


}


var manas = new Person("Manas","male");

console.log(manas.getName());

我的问题是如何在es6类中声明私有变量和私有方法

【问题讨论】:

  • js中没有私有方法,但可以有局部变量/函数。
  • 您展示的代码演示了protected 方法,而不是private 方法。无论如何,旧方法在 ES6 中仍然有效。
  • “旧的方式在 ES6 中仍然有效” ---- 你能给我看一个在 es6 中创建所谓的受保护方法的例子吗

标签: javascript node.js ecmascript-6


【解决方案1】:

实现此目的的一种方法是使用另一个 ES2015 功能,称为 modules

您可能已经熟悉 AMD 模块或 commonJS 模块(由 Nodejs 使用)。 ES6 / ES2015 为 JS 带来了一个标准——我们将它们称为 ES6 模块,但它们现在是 JS 语言的一部分。一旦有了模块,您就可以对私有函数和对象变量进行信息隐藏。请记住,只有您“导出”的内容对客户端调用代码可见。

让我们完成您的示例代码。这是第一个剪辑:

person.js

 const getNameWithInitial = function () {
      let initial = this._gender === 'male' ?
      'Mr. ' :
      'Mrs. ';
      return initial + this._name;
    }

  export class Person {

    constructor(name, gender) {
        this._name = name;
        this._gender = gender;
      }

      get name() {
          return getNameWithInitial.call(this);
        }
    }
  }

client.js

import {Person} from './person';
const manas = new Person('Manas', 'male');
console.log(manas.name);  // this calls what was your getName function

现在,getNameWithInitial 函数实际上是私有的,因为它没有被导出,所以 client.js 看不到它。

但是,Person 类仍然存在问题,因为它是导出的。目前,您可以走到 manas 对象并执行以下操作:

manas._name = 'Joe'

通过像 _name 这样的属性,我们可以组合模块和symbols。这是 ES6+/ES2015 提供的一种强大而轻量级的信息隐藏技术。

Symbol 是一种新的内置类型。每个新的符号值都是唯一的。因此可以用作对象的键。

如果客户端调用代码不知道用于访问该密钥的符号,他们就无法获取它,因为该符号没有被导出。

让我们看看我们修改后的代码,利用符号和模块来隐藏类属性。

person.js

const s_name = Symbol();
const s_gender = Symbol();

const getNameWithInitial = function () {
  let initial = this[s_gender] === 'male' ?
    'Mr. ' :
    'Mrs. ';
  return initial + this[s_name];
}

export class Person {

  constructor(name, gender) {
    this[s_name] = name;
    this[s_gender] = gender;
  }

  get name() {
    return getNameWithInitial.call(this);
  }
}

所以,现在客户不能只做:

 manas._name = 'Joe' 

因为 _name 没有用作名称值的键。

但是,符号是通过 Object.getOwnPropertySymbols 等反射功能暴露的,因此请注意,使用此技术它们不是“完全”私有的。

import {Person} from './person';
const manas = new Person('Manas', 'male');
const vals = Object.getOwnPropertySymbols(manas);
manas[vals[0]] = 'Joanne';
manas[vals[1]] = 'female';

外卖消息 - 模块通常是隐藏某些内容的好方法,因为如果不导出,则无法在模块外部使用,并且与私有存储的符号一起用作键,然后类属性也可以隐藏(但是不是严格私人的)。今天使用模块可以通过构建工具获得,例如。 webpack / browserify 和 babel。

【讨论】:

  • 但是:Object.getOwnPropertySymbols( person ).map( symbol => person[symbol] ); // [ name, gender ] 当然,键名不再是人类可读的,但它们也不比Object.keys(person).map( key => person[key] ); 更安全。不要使用此方法,如果您要在闭包中存储要隐藏的数据,并且它真的确实需要保持隐藏
  • 更新答案 - 你是对的,这只是一种有效的信息隐藏策略,它不保证隐私。 Babel 文档清楚地说明了这一点 - babeljs.io/docs/learn-es2015
  • 为什么不能简单地在Person 类之外声明var namevar gender,然后从Person 的函数中访问它们?这些变量不会被导出,所以它们不是真正私有的吗?
  • @Jez 因为使用new 运算符实例化的每个Person 实例都将在相同的namegender 引用上创建一个闭包,从而导致不同的实例在相同的数据会产生意想不到的结果。
  • @leo 您将这些私有变量放在类也是其中一部分的闭包中。然后,创建的任何实例都将拥有自己的私有变量。更好的是,使用“let”而不是 var 来创建这些变量。
【解决方案2】:

如果您想要类似 ES5 的解决方案,这很简单; constructor 简单地变成了持有闭包的东西,然后你添加任何应该记住私有状态的方法/对象。
原型方法无法访问初始构造函数的闭包,无需使用一些特权 getter:

class Person {

  constructor ({ name, age, deepDarkSecret }) {
    const bribe = () => console.log(`Give me money, or everybody will know about ${ redactRandom(deepDarkSecret) }`);

    Object.assign(this, { name, age }); // assigning publicly accessible values
    Object.assign(this, { bribe }); // assign "privileged" methods (functions with closure-reference to initial values)
  }

  recallSecret () {
    console.log("I'm just a prototyped method, so I know nothing about any secret, unless I use a privileged function...");
    this.bribe();
  }
}

如果您查看它为您带来了什么,您会注意到它与您的示例并没有太大区别(只是使用“更干净”的花里胡哨;尤其是在添加原型/静态方法时)。下面还是原型。

如果您有幸使用任何类型的模块/导出(ES6 是理想的),那么使用另一种数据类型,您可以拥有真正的隐私,而不必自己清理

看起来有点老套。它可能会变得不那么难看,并有望成为未来更清洁的基础,但如果您想要基于实例的私有访问,即使是原型方法,然后制作一个WeakMap ,在您从中导出类的模块内。

使用this 作为您的密钥。 现在所有原型方法都可以对弱映射进行闭包访问,而不是制作具有闭包访问权限的特权函数...
...缺点是任何时候您想要私有变量,都必须在 WeakMap 中查找它们,而不是从 this 中拉出/拉出它们。

const personalBaggage = new WeakMap();

class Person {
  constructor ({ name, age, darkestMoment }) {
    const privates = { name, age, darkestMoment };
    personalBaggage.add(this, privates);
  }

  recallDarkestHour () {
    const { darkestMoment } = personalBaggage.get(this);
    console.log(darkestMoment);
  }
}

export default Person;

只要您没有丢失 this 内部的引用,这应该可以正常工作。
与使用 Symbol 不同,无论您如何努力,都无法获得对私有对象的引用。
如果您知道对象上的符号,则可以使用符号查找属性。
获取任何对象上的符号列表只是一个函数调用。 Symbol 不是为了保密;这是为了避免命名冲突,并定义可用于任何对象/函数的通用符号,但不会在循环中被拾取,不会被意外调用或覆盖,等等。

将私有数据包存储在以 this 为键的弱映射中,您可以访问完全隐藏的数据集,该数据集在该模块之外完全无法访问。
更好的是,weakmap 中的键/值不会阻止 GC 清理它们。
通常,如果将它们留在数组中,它们会留在那里。如果一个对象最后被使用的地方是一个weakmap中的key,那么它会被收集并且weakmap的值会被自动擦除(在原生ES6中;内存红利不能被polyfill,但是object可以)。

【讨论】:

  • 与此处给出的答案非常相似:stackoverflow.com/a/34418487/1882064
  • @arcseldon 非常接近;也就是说,在接受的答案中,您必须为要隐藏的每个成员/方法创建一个 WeakMap 实例,而在我的示例中,您只创建一个地图并从中解构您需要的内容。当然,两者都有其优点和缺点。这对我来说感觉不那么笨重了。
【解决方案3】:

这里有一篇关于这个主题的非常好的帖子。在您提出问题之前,我对在 ES6 中执行此操作知之甚少,但在查看此内容之后,它看起来真的很酷。因此,您创建了一个索引到该函数的符号。

这是直接从他们那里得到的代码。

var Person = (function() {
    var nameSymbol = Symbol('name');

    function Person(name) {
        this[nameSymbol] = name;
    }

    Person.prototype.getName = function() {
        return this[nameSymbol];
    };

    return Person;
}());

var p = new Person('John');

Private properties in JavaScript

【讨论】:

  • 它们并非无法从外部访问;试图通过文本名称获取它们的人无法访问它们,并且它们不包含在迭代中,但是您仍然可以从对象中获取所有键,检查 .toString 中的键并使用列表键来查找值。 Symbol 非常有用,只是不要认为它在一个依赖闭包来保持关闭以防止篡改的项目中为您带来安全。
【解决方案4】:

您可以使用Symbol 来完成它。

const _name = Symbol();

class Person {
  constructor(name) {
    this[_name] = name;
  } 
}

const person = new Person('John');

alert(person.name);
// => undefined

【讨论】:

  • Object.getOwnPropertySymbols( person ).map( symbol => person[symbol] ); // ["John"] 并不是说​​它对其他事情不是非常有用......但同时,如果这就是你想要的,它也不是非常私密。
猜你喜欢
  • 2015-10-11
  • 2023-04-02
  • 2023-04-11
  • 1970-01-01
  • 2016-04-24
  • 2017-08-04
  • 2013-09-06
  • 2011-03-24
  • 2011-04-04
相关资源
最近更新 更多