要创建一个带有公共方法的JavaScript类,我可以这样做:

function Restaurant() {}

Restaurant.prototype.buy_food = function(){
   // something here
}

Restaurant.prototype.use_restroom = function(){
   // something here
}

这样,我类的用户就可以:

var restaurant = new Restaurant();
restaurant.buy_food();
restaurant.use_restroom();

如何创建一个私有方法,可以由buy_food和use_restroom方法调用,但不能由类的用户外部调用?

换句话说,我希望我的方法实现能够做到:

Restaurant.prototype.use_restroom = function() {
   this.private_stuff();
}

但这是行不通的:

var r = new Restaurant();
r.private_stuff();

如何将private_stuff定义为私有方法,使两者都成立?

我读过Doug Crockford的文章几次,但它似乎不像“私有”方法可以被公共方法调用,而“特权”方法可以被外部调用。


当前回答

以遵循Crockford私有或特权模式的任何解决方案为例。例如:

function Foo(x) {
    var y = 5;
    var bar = function() {
        return y * x;
    };

    this.public = function(z) {
        return bar() + x * z;
    };
}

在任何情况下,如果攻击者在JS上下文中没有“执行”权,他就没有办法访问任何“公共”或“私有”字段或方法。如果攻击者有访问权限,他可以执行以下一行代码:

eval("Foo = " + Foo.toString().replace(
    /{/, "{ this.eval = function(code) { return eval(code); }; "
));

注意,上面的代码对于所有构造函数类型-privacy都是通用的。这里的一些解决方案会失败,但应该清楚的是,几乎所有基于闭包的解决方案都可以通过不同的replace()参数像这样被破坏。

在此执行之后,任何用new Foo()创建的对象都将有一个eval方法,可以调用该方法来返回或更改构造函数闭包中定义的值或方法,例如:

f = new Foo(99);
f.eval("x");
f.eval("y");
f.eval("x = 8");

我能看到的唯一问题是,它不会工作的情况下,只有一个实例,它是在加载时创建的。但这样就没有理由真正定义原型了,在这种情况下,攻击者可以简单地重新创建对象,而不是构造函数,只要他有方法传递相同的参数(例如,它们是常量或从可用值计算出来的)。

在我看来,这使得Crockford的解决方案毫无用处。由于“隐私”很容易被打破,他的解决方案的缺点(可读性和可维护性降低,性能下降,内存增加)使得基于“无隐私”原型的方法成为更好的选择。

我通常使用前导下划线标记__private和_protected方法和字段(Perl风格),但在JavaScript中拥有隐私的想法只是表明它是一种被误解的语言。

因此,除了Crockford的第一句话外,我不同意他的观点。

那么如何在JS中获得真正的隐私呢?将所有需要私有的东西放在服务器端,并使用JS进行AJAX调用。

其他回答

这个呢?

var Restaurant = (function() {

 var _id = 0;
 var privateVars = [];

 function Restaurant(name) {
     this.id = ++_id;
     this.name = name;
     privateVars[this.id] = {
         cooked: []
     };
 }

 Restaurant.prototype.cook = function (food) {
     privateVars[this.id].cooked.push(food);
 }

 return Restaurant;

})();

在直接函数的作用域之外查找私有变量是不可能的。 没有重复的函数,节省内存。

缺点是私有变量的查找非常笨拙。“煮熟了”这个词很可笑。还有一个额外的“id”变量。

使用自调用函数和调用

JavaScript使用原型,不像面向对象语言那样有类(或方法)。JavaScript开发者需要用JavaScript思考。

维基百科:

与许多面向对象的语言不同,它们之间没有区别 函数定义和方法定义。而是区别 在函数调用期间发生;当函数作为方法调用时 对于一个对象,函数的本地this关键字绑定到该对象 对象用于该调用。

使用自调用函数和调用函数调用私有“方法”的解决方案:

var MyObject = (function () {
    
  // Constructor
  function MyObject(foo) {
    this._foo = foo;
  }

  function privateFun(prefix) {
    return prefix + this._foo;
  }
    
  MyObject.prototype.publicFun = function () {
    return privateFun.call(this, ">>");
  }
    
  return MyObject;

}());
var myObject = new MyObject("bar");
myObject.publicFun();      // Returns ">>bar"
myObject.privateFun(">>"); // ReferenceError: private is not defined

调用函数允许我们使用适当的上下文(this)调用私有函数。

使用Node.js更简单

如果你正在使用Node.js,你不需要IIFE,因为你可以利用模块加载系统:

function MyObject(foo) {
  this._foo = foo;
}
    
function privateFun(prefix) {
  return prefix + this._foo;
}

MyObject.prototype.publicFun = function () {
  return privateFun.call(this, ">>");
}
    
module.exports= MyObject;

加载文件:

var MyObject = require("./MyObject");
    
var myObject = new MyObject("bar");
myObject.publicFun();      // Returns ">>bar"
myObject.privateFun(">>"); // ReferenceError: private is not defined

(新!)未来JavaScript版本中的原生私有方法

TC39私有方法和JavaScript类的getter/setter建议是第3阶段。这意味着JavaScript很快就会在本地实现私有方法!

注意,JavaScript私有类字段在现代JavaScript版本中已经存在。

下面是一个如何使用它的例子:

class MyObject {

  // Private field
  #foo;
    
  constructor(foo) {
    this.#foo = foo;
  }

  #privateFun(prefix) {
   return prefix + this.#foo;
  }
    
  publicFun() {
    return this.#privateFun(">>");
  }

}

在旧的JavaScript引擎上运行这些代码可能需要JavaScript编译器/编译器。

PS:如果你想知道为什么是#前缀,请阅读这篇文章。

(已弃用)ES7绑定操作符

警告:绑定操作符TC39命题接近死亡https://github.com/tc39/proposal-bind-operator/issues/53#issuecomment-374271822

绑定操作符::是一个ECMAScript提议,在Babel(阶段0)中实现。

export default class MyObject {
  constructor (foo) {
    this._foo = foo;
  }

  publicFun () {
    return this::privateFun(">>");
  }
}

function privateFun (prefix) {
  return prefix + this._foo;
}

别啰嗦了。这是Javascript。使用命名约定。

在es6类工作了多年之后,我最近开始了一个es5项目(使用requireJS,它看起来已经非常冗长了)。我已经反复研究了这里提到的所有策略,基本上都可以归结为使用命名约定:

Javascript doesn't have scope keywords like private. Other developers entering Javascript will know this upfront. Therefore, a simple naming convention is more than sufficient. A simple naming convention of prefixing with an underscore solves the problem of both private properties and private methods. Let's take advantage of the Prototype for speed reasons, but lets not get anymore verbose than that. Let's try to keep the es5 "class" looking as closely to what we might expect in other backend languages (and treat every file as a class, even if we don't need to return an instance). Let's demonstrate with a more realistic module situation (we'll use old es5 and old requireJs).

my-tooltip.js

    define([
        'tooltip'
    ],
    function(
        tooltip
    ){

        function MyTooltip() {
            // Later, if needed, we can remove the underscore on some
            // of these (make public) and allow clients of our class
            // to set them.
            this._selector = "#my-tooltip"
            this._template = 'Hello from inside my tooltip!';
            this._initTooltip();
        }

        MyTooltip.prototype = {
            constructor: MyTooltip,

            _initTooltip: function () {
                new tooltip.tooltip(this._selector, {
                    content: this._template,
                    closeOnClick: true,
                    closeButton: true
                });
            }
        }

        return {
            init: function init() {
               new MyTooltip();  // <-- Our constructor adds our tooltip to the DOM so not much we need to do after instantiation.
            }

            // You could instead return a new instantiation, 
            // if later you do more with this class.
            /* 
            create: function create() {
               return new MyTooltip();
            }
            */
        }
    });

将所有代码包装在匿名函数中:然后,所有函数将是私有的,只有函数附加到窗口对象:

(function(w,nameSpacePrivate){
     w.Person=function(name){
         this.name=name;   
         return this;
     };

     w.Person.prototype.profilePublic=function(){
          return nameSpacePrivate.profile.call(this);
     };  

     nameSpacePrivate.profile=function(){
       return 'My name is '+this.name;
     };

})(window,{});

用这个:

  var abdennour=new Person('Abdennour');
  abdennour.profilePublic();

小提琴

我更喜欢将私有数据存储在关联的WeakMap中。这允许你将你的公共方法保留在原型上。对于大量的对象,这似乎是处理这个问题的最有效的方法。

const data = new WeakMap();

function Foo(value) {
    data.set(this, {value});
}

// public method accessing private value
Foo.prototype.accessValue = function() {
    return data.get(this).value;
}

// private 'method' accessing private value
function accessValue(foo) {
    return data.get(foo).value;
}

export {Foo};