如何使用Traceur在ES6类中实现私有方法

155

我现在使用Traceur编译器来利用ES6特性。

我想从ES5实现这个东西:

function Animal() {
    var self = this,
        sayHi;

    sayHi  = function() {
        self.hi();
    };

    this.hi = function() {/* ... */}
}

目前,traceur不支持privatepublic关键字(来源于harmony)。而ES6类语法不允许在类体中使用简单的var(或let)语句。

我找到的唯一方法是在类声明之前模拟私有方法。像这样:

var sayHi = function() {
    // ... do stuff
};

class Animal {
...

虽然好过没有,但是按照预期,如果不每次applybind正确地传递this到私有方法中,你是无法通过的。

那么,在ES6类中有没有可能使用私有数据并兼容traceur编译器呢?


1
你考虑过6to5吗?我更喜欢它而不是traceur。我没有使用过这个特定的东西,但是看看这个片段 - Sampsa
1
@Sampsa 这是一个很好的工具,但我在你的片段中找不到关于双冒号(::)语法的任何信息。这是来自规范还是草案? - Boris Zagoruiko
实际上,这个问题并不是一个完全重复的问题,因为这个问题是关于私有方法的,而参考的问题是关于私有属性/字段的。 - bvdb
4
现在有一个私钥,其值为#。详情请见:https://github.com/tc39/proposal-class-fields - chitzui
9个回答

243

在当前的ECMAScript 6规范中没有privatepublicprotected关键字。

因此,Traceur不支持privatepublic。6to5(目前称为“Babel”)通过此提案实现了实验性支持(请参见此讨论)。但毕竟这只是一个提案。

因此,现在您可以通过WeakMap模拟私有属性(请参见此处)。另一种选择是使用Symbol,但它并不提供实际的隐私,因为属性可以通过Object.getOwnPropertySymbols轻松访问。

在我看来,此时最好的解决方案是使用伪隐私。如果您经常使用applycall来调用自己的方法,则该方法非常特定于对象。因此,值得在类中仅使用下划线前缀声明它:

class Animal {

    _sayHi() {
        // do stuff
    }
}

78
实际上,在许多语言中,所谓的“字段隐私”都是伪隐私。以Java为例,你总是可以使用反射获取一个对象的“私有”字段的值。这种隐私实际上只是一种设计模式——它给消费者提供了一个易于理解、可用工具/编译器强制执行的接口,告诉他们什么方法绝对不应该被调用。在方法名前加下划线是这种接口的一种完全合理的实现方式,即使它只是一个约定而非语言规范。 - davnicwil
3
不太适合任何函数迭代,尤其是如果人们选择不同的方式来标记哪些函数是私有的。"id"、"id"、"p_id"、"privateId"、"s_id"等等。对函数进行迭代的示例:Promise.promisifyAll(SomeClass.prototype) - gman
17
不要将下划线作为名称的第一个或最后一个字符。有时它被用来表示隐私,但实际上并不能提供隐私保护。如果隐私很重要,请使用封闭形式。避免使用表明缺乏能力的约定。——《JavaScript 编程语言的代码约定》(Douglas Crockford) - Carlos Araya
47
那段话是在ES6发布之前写的。作者基本上是说“不要使用编写类的常见方法,因为在JS中类不是一件事情。” 然而现在它们是一件事情了,所以现在使用它们的约定是有意义的。 - BlueRaja - Danny Pflughoeft
2
这是大家都讨厌的匈牙利命名法吗? - sarkiroka
显示剩余5条评论

91

您始终可以使用普通函数:

function myPrivateFunction() {
  console.log("My property: " + this.prop);
}

class MyClass() {
  constructor() {
    this.prop = "myProp";
    myPrivateFunction.bind(this)();
  }
}

new MyClass(); // 'My property: myProp'

7
Lambda表达式不会自动绑定this,因为它们根本没有this(即你无法绑定lambda表达式)。来源:http://blog.getify.com/arrow-this/ - atoth
1
MDN将其称为“词法绑定到this”。请参见:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_functions - Max
6
你的第二个代码示例不支持多个实例;myPrivateFunction 只有一个实例。 - John
3
不起作用的示例。尽管可以对“myPrivateFunction.call(this)”起作用。 - adamsko
1
与我使用的方法相同,只是我将bind替换为apply。这将使您可以将其视为类的函数(通过此上下文),而不是全局函数(即使它在技术上是)。如果您正在nodeJS中编写此代码,则不需要太担心。如果您很谨慎,您总是可以将其封装在IFFE中。 - Tigertron
显示剩余3条评论

62
尽管当前没有办法声明方法或属性为私有,ES6 模块不在全局命名空间中。因此,您在模块中声明但未导出的任何内容都将无法在程序的其他部分使用,但仍将在运行时对您的模块可用。因此,您有私有属性和方法 :)。

这是一个示例(在 test.js 文件中)。

function tryMe1(a) {
  console.log(a + 2);
}

var tryMe2 = 1234;

class myModule {
  tryMe3(a) {
    console.log(a + 100);
  }

  getTryMe1(a) {
    tryMe1(a);
  }

  getTryMe2() {
    return tryMe2;
  }
}

// Exports just myModule class. Not anything outside of it.
export default myModule; 

在另一个文件中

import MyModule from './test';

let bar = new MyModule();

tryMe1(1); // ReferenceError: tryMe1 is not defined
tryMe2; // ReferenceError: tryMe2 is not defined
bar.tryMe1(1); // TypeError: bar.tryMe1 is not a function
bar.tryMe2; // undefined

bar.tryMe3(1); // 101
bar.getTryMe1(1); // 3
bar.getTryMe2(); // 1234

4
如果你想在私有方法中使用类上下文 (this),则应该在类内部使用 tryMe1(1).bind(this)。但是如果你使用箭头函数,则会失败。 - JacopKane
@JacopKane 但是这样你就必须将bind(this)的结果分配给this的一个属性(破坏了隐私),或者每次都调用bind(this),这可能会导致性能问题 - 更不用说绑定函数比普通函数慢 - John
完全同意@John的看法,虽然没有上下文私有方法并不是很有用,因为如果没有属性访问,它根本不是一个“方法”。它只是一个独立的实用函数。我想唯一的解决方法就是将上下文的引用作为参数传递。只是随口说说。 - JacopKane
猜猜如果我创建超过1个myModule实例并向其中添加一些setter方法来更改所有实例共享的tryMe2变量,会发生什么。 - Serg
@Sergey - 什么? - Gen1-1

25
您可以使用Symbol。
var say = Symbol()

function Cat(){
  this[say]() // call private methos
}

Cat.prototype[say] = function(){ alert('im a private') }

附言:alexpods不正确。由于继承存在名称冲突,他获得了保护而不是私有。

实际上,您可以使用var say = String(Math.random())来替代Symbol。

在ES6中:

var say = Symbol()

class Cat {

  constructor(){
    this[say]() // call private
  }

  [say](){
    alert('im private')
  }

}

export var say = Symbol(); 可以导出 say 符号变量。Cat[say]() 也可以访问 [say]() {} 方法。 - Lin Du
7
私有变量并不是防止黑客攻击的保护措施,而是防止子类意外重写属性的保护措施。 - Maxmaxmaximus

20

希望这对你有所帮助. :)

I. 在IIFE(立即调用函数表达式)内声明变量和函数,这些只能在匿名函数中使用。(当你需要为ES6更改代码时,可以使用“let,const”关键字而不使用'var'。)

let Name = (function() {
  const _privateHello = function() {
  }
  class Name {
    constructor() {
    }
    publicMethod() {
      _privateHello()
    }
  }
  return Name;
})();

二、WeakMap对象可以有效地解决内存泄漏的问题。

在WeakMap中存储的变量将在实例被删除时被移除。请参阅此文章。(管理ES6类的私有数据

let Name = (function() {
  const _privateName = new WeakMap();
})();

III. 把所有东西放在一起。

let Name = (function() {
  const _privateName = new WeakMap();
  const _privateHello = function(fullName) {
    console.log("Hello, " + fullName);
  }

  class Name {
    constructor(firstName, lastName) {
      _privateName.set(this, {firstName: firstName, lastName: lastName});
    }
    static printName(name) {
      let privateName = _privateName.get(name);
      let _fullname = privateName.firstName + " " + privateName.lastName;
      _privateHello(_fullname);
    }
    printName() {
      let privateName = _privateName.get(this);
      let _fullname = privateName.firstName + " " + privateName.lastName;
      _privateHello(_fullname);
    }
  }

  return Name;
})();

var aMan = new Name("JH", "Son");
aMan.printName(); // "Hello, JH Son"
Name.printName(aMan); // "Hello, JH Son"

13

你有没有考虑过使用工厂函数呢?在JavaScript中,它们通常是类或构造函数的更好的选择。以下是一个示例说明它的工作原理:

function car () {

    var privateVariable = 4

    function privateFunction () {}

    return {

        color: 'red',

        drive: function (miles) {},

        stop: function() {}

        ....

    }

}

由于闭包,您可以在返回的对象内访问所有私有函数和变量,但无法从外部访问它们。


3
对我而言,这是最糟糕的解决方案。你没有创建一个真正的ES6类,只是返回了一个POJO。此外,每次调用工厂时都会创建私有闭包,导致内存占用率更高,性能更慢。 - waldgeist
2
重点是避免创建一个类。事实上,许多JS开发人员认为在JavaScript中创建类是一种反模式;由于语言的本质以及关键字'this'引入的风险。就性能而言,除非您一次创建成千上万个这些对象,否则它并不真的很重要;在那种情况下,即使使用类,您也可能会遇到性能问题。 - Nicola Pedretti
嗯,但这个回答和问题有什么关系呢? - waldgeist
12
使用工厂函数是使用类的有效替代方案,可简化私有/公共方法的创建。如果未来的读者不认为使用类的解决方案令人满意,他们了解到有一个解决问题的选择,将会很有价值。 - Nicola Pedretti

11

正如alexpods所说,ES6中没有专门的方法来实现这一点。但是,对于那些感兴趣的人,还有一个关于绑定运算符的提案,可以实现这种语法:

function privateMethod() {
  return `Hello ${this.name}`;
}

export class Animal {
  constructor(name) {
    this.name = name;
  }
  publicMethod() {
    this::privateMethod();
  }
}

再次强调,这只是一个提议。不同的人可能会有不同的看法。


5
我想出了一种更好的解决方案,可以实现以下功能:
  • 不需要使用'this._'、that/self、weakmaps、symbols等。代码清晰简单。

  • 私有变量和方法真正私有,并且具有正确的'this'绑定。

  • 完全不使用'this',这意味着代码更加清晰,错误率更低。

  • 公共接口清晰,与实现分离作为私有方法的代理。

  • 允许轻松组合。

使用此方法,您可以做到:

function Counter() {
  // public interface
  const proxy = {
    advance,  // advance counter and get new value
    reset,    // reset value
    value     // get value
  }
 
  // private variables and methods
  let count=0;
    
  function advance() {
    return ++count;
  }
     
  function reset(newCount) {
    count=(newCount || 0);
  }
     
  function value() {
    return count;
  }
    
  return proxy;
}
     
let counter=Counter.New();
console.log(counter instanceof Counter); // true
counter.reset(100);
console.log('Counter next = '+counter.advance()); // 101
console.log(Object.getOwnPropertyNames(counter)); // ["advance", "reset", "value"]
<script src="https://cdn.rawgit.com/kofifus/New/7987670c/new.js"></script>

请查看New的代码及更详细的示例,包括构造函数和组合。


1
这个模式一直是最有逻辑的。你不仅可以获得提升和私有变量,而且可以将函数转换为闭包,并通过在对象中返回每个函数来公开公共方法。此外,您可以通过绑定到方法来利用this作用域,如果您不想调用new,只需将其执行为IIFE即可。确实是编写代码的好方法。 - User_coder

4

正如Marcelo Lazaroni所说,虽然目前没有办法将方法或属性声明为私有的,但ES6模块不在全局命名空间中。因此,您在模块中声明但未导出的任何内容都将不可用于程序的任何其他部分,但在运行时仍将对该模块可用。

但是他的示例没有展示私有方法如何访问类实例的成员。Max向我们展示了通过绑定或在构造函数中使用lambda方法来访问实例成员的一些好例子,但我想再添加一种更简单的方式:将实例作为参数传递给私有方法。以这种方式进行操作会使Max的MyClass看起来像这样:

function myPrivateFunction(myClass) {
  console.log("My property: " + myClass.prop);
}

class MyClass() {
  constructor() {
    this.prop = "myProp";
  }
  testMethod() {
    myPrivateFunction(this);
  }
}
module.exports = MyClass;

你如何做实际上取决于个人喜好。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接