要创建一个带有公共方法的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的文章几次,但它似乎不像“私有”方法可以被公共方法调用,而“特权”方法可以被外部调用。


当前回答

使用自调用函数和调用

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;
}

其他回答

我更喜欢将私有数据存储在关联的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};

下面是我创建的类,用来理解Douglas Crockford在他的网站Private Members in JavaScript中的建议

function Employee(id, name) { //Constructor
    //Public member variables
    this.id = id;
    this.name = name;
    //Private member variables
    var fName;
    var lName;
    var that = this;
    //By convention, we create a private variable 'that'. This is used to     
    //make the object available to the private methods. 

    //Private function
    function setFName(pfname) {
        fName = pfname;
        alert('setFName called');
    }
    //Privileged function
    this.setLName = function (plName, pfname) {
        lName = plName;  //Has access to private variables
        setFName(pfname); //Has access to private function
        alert('setLName called ' + this.id); //Has access to member variables
    }
    //Another privileged member has access to both member variables and private variables
    //Note access of this.dataOfBirth created by public member setDateOfBirth
    this.toString = function () {
        return 'toString called ' + this.id + ' ' + this.name + ' ' + fName + ' ' + lName + ' ' + this.dataOfBirth; 
    }
}
//Public function has access to member variable and can create on too but does not have access to private variable
Employee.prototype.setDateOfBirth = function (dob) {
    alert('setDateOfBirth called ' + this.id);
    this.dataOfBirth = dob;   //Creates new public member note this is accessed by toString
    //alert(fName); //Does not have access to private member
}
$(document).ready()
{
    var employee = new Employee(5, 'Shyam'); //Create a new object and initialize it with constructor
    employee.setLName('Bhaskar', 'Ram');  //Call privileged function
    employee.setDateOfBirth('1/1/2000');  //Call public function
    employee.id = 9;                     //Set up member value
    //employee.setFName('Ram');  //can not call Private Privileged method
    alert(employee.toString());  //See the changed object

}
var TestClass = function( ) {

    var privateProperty = 42;

    function privateMethod( ) {
        alert( "privateMethod, " + privateProperty );
    }

    this.public = {
        constructor: TestClass,

        publicProperty: 88,
        publicMethod: function( ) {
            alert( "publicMethod" );
            privateMethod( );
        }
    };
};
TestClass.prototype = new TestClass( ).public;


var myTestClass = new TestClass( );

alert( myTestClass.publicProperty );
myTestClass.publicMethod( );

alert( myTestClass.privateMethod || "no privateMethod" );

类似于georgebrock,但不太啰嗦(恕我直言) 这样做有什么问题吗?(我哪儿都没见过)

编辑:我意识到这有点没用,因为每个独立的实例化都有自己的公共方法副本,从而破坏了原型的使用。

模块模式在大多数情况下是正确的。但是如果您有数千个实例,类可以节省内存。如果需要考虑节省内存,并且您的对象包含少量私有数据,但有大量公共函数,那么您将希望所有公共函数都驻留在.prototype中以节省内存。

这是我想到的:

var MyClass = (function () {
    var secret = {}; // You can only getPriv() if you know this
    function MyClass() {
        var that = this, priv = {
            foo: 0 // ... and other private values
        };
        that.getPriv = function (proof) {
            return (proof === secret) && priv;
        };
    }
    MyClass.prototype.inc = function () {
        var priv = this.getPriv(secret);
        priv.foo += 1;
        return priv.foo;
    };
    return MyClass;
}());
var x = new MyClass();
x.inc(); // 1
x.inc(); // 2

对象priv包含私有属性。它可以通过公共函数getPriv()访问,但该函数返回false,除非您将secret传递给它,并且只有在主闭包中才知道。

ES2021 / ES12 -私有方法

私有方法名以哈希前缀#开头,并且只能在定义它的类内部访问。

class Restaurant {

  // private method
  #private_stuff() {
    console.log("private stuff");
  }

  // public method
  buy_food() {
    this.#private_stuff();
  }

};

const restaurant = new Restaurant();
restaurant.buy_food(); // "private stuff";
restaurant.private_stuff(); // Uncaught TypeError: restaurant.private_stuff is not a function