JavaScript私有方法

Way*_*Kao 454 javascript oop private-methods

要使用公共方法创建JavaScript类,我会执行以下操作:

function Restaurant() {}

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

Restaurant.prototype.use_restroom = function(){
   // something here
}
Run Code Online (Sandbox Code Playgroud)

这样我班级的用户可以:

var restaurant = new Restaurant();
restaurant.buy_food();
restaurant.use_restroom();
Run Code Online (Sandbox Code Playgroud)

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

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

Restaurant.prototype.use_restroom = function() {
   this.private_stuff();
}
Run Code Online (Sandbox Code Playgroud)

但这不应该奏效:

var r = new Restaurant();
r.private_stuff();
Run Code Online (Sandbox Code Playgroud)

我如何定义private_stuff为私有方法,所以这两个都适用?

我已经阅读了Doug Crockford的几次写法,但看起来似乎不能通过公共方法调用"私有"方法,并且可以在外部调用"特权"方法.

17 *_* 26 377

你可以做到,但缺点是它不能成为原型的一部分:

function Restaurant() {
    var myPrivateVar;

    var private_stuff = function() {  // Only visible inside Restaurant()
        myPrivateVar = "I can set this here!";
    }

    this.use_restroom = function() {  // use_restroom is visible to all
        private_stuff();
    }

    this.buy_food = function() {   // buy_food is visible to all
        private_stuff();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这是一个私有方法,但往往比常用的原型方法消耗更多的内存,特别是如果你创建了很多这些对象.对于每个对象实例,它创建一个绑定到对象而不是类的单独函数.此外,在对象本身被销毁之前,这不会被垃圾收集. (128认同)
  • 私人方法不应该被覆盖 - 它们是私有的. (65认同)
  • @mikesamuel - 是的,但只有当那些翻译有bug时:) (48认同)
  • 将封闭物藏在封闭物内并不能保证所有口译员的隐私.请参阅http://code.google.com/p/google-caja/wiki/EvalBreaksClosureEncapsulation (9认同)
  • 如果你把一个对象麦当劳()从餐厅(),如果你用这种方式宣告他们麦当劳不能重写私有方法继承.嗯,其实你可以,但它不会工作,如果其他方法调用私有,它会调用该方法的原始版本,你也不能调用父类的方法.到目前为止,我还没有找到一种很好的方法来声明适用于继承的私有方法.这和性能的影响使得这根本不是一个非常好的设计模式.我建议做一些前缀来表示私有方法,比如用下划线开始. (4认同)
  • 如果你想要私有方法(不是私有数据),那么简单地在原型之外声明它们并像这样调用它们:myFoo.apply(this,args).然后,您可以保持公共API小,同时仍然具有适用于您的本地对象的私有方法. (4认同)
  • 请考虑使用**自调用函数**+**调用**,如下所述:http://stackoverflow.com/a/25172901/1480391 (4认同)

geo*_*ock 159

你可以像这样模拟私有方法:

function Restaurant() {
}

Restaurant.prototype = (function() {
    var private_stuff = function() {
        // Private code here
    };

    return {

        constructor:Restaurant,

        use_restroom:function() {
            private_stuff();
        }

    };
})();

var r = new Restaurant();

// This will work:
r.use_restroom();

// This will cause an error:
r.private_stuff();
Run Code Online (Sandbox Code Playgroud)

有关此技术的更多信息,请访问:http://webreflection.blogspot.com/2008/04/natural-javascript-private-methods.html

  • 使用@georgebrock提出的模式,*所有*私有数据将在*all*restaurant对象之间共享.这类似于基于类的OOP中的静态私有变量和函数.我认为OP确实*不*想要这个.为了说明我的意思,我创建了[a jsFiddle](http://jsfiddle.net/feklee/zRLSh/1/). (23认同)
  • 他在问题中提到了这个链接. (10认同)
  • 这种方法的缺点是你不能让private_stuff()访问Restaurant中的其他私有数据,而其他Restaurant方法也不能调用private_stuff().好处是如果你不需要我提到的任何一个条件,你可以在原型中保留use_restroom(). (8认同)
  • 我还建议Douglas Crockford的网站作为私人/公共方法的资源和成员http://javascript.crockford.com/private.html (7认同)
  • 这应该是解决方案和接受的答案,因为作者显然正在使用prototype属性. (6认同)
  • @feklee:公共方法被定义为在许多实例之间共享的原型对象的属性.这里我们使用私有方法完全相同:所有实例都可以访问一个共享的`function`对象.你是正确的指出这种技术不适用于私人数据,但这不是问题所要求的. (4认同)
  • @georgebrock:从OP接受的答案判断,他确实希望私有成员*每个对象*,而不是原型对象.他发布的用例也暗示了这一点.例如,洗手间的状态通常是每个餐馆. (2认同)

Yve*_* M. 153

使用自调用功能和调用

JavaScript使用原型,并且没有像面向对象语言那样的类(或方法).JavaScript开发人员需要在JavaScript中思考.

维基百科报价:

与许多面向对象的语言不同,函数定义和方法定义之间没有区别.相反,区别发生在函数调用期间; 当一个函数作为一个对象的方法被调用时,该函数的local 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
Run Code Online (Sandbox Code Playgroud)

呼叫功能可以让我们调用私有函数与适当的上下文(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, '>>');
}

exports.MyObject = MyObject;
Run Code Online (Sandbox Code Playgroud)

加载文件:

var MyObject = require('./MyObject').MyObject;

var myObject = new MyObject('bar');
myObject.publicFun();      // Returns '>>bar'
myObject.privateFun('>>'); // ReferenceError: private is not defined
Run Code Online (Sandbox Code Playgroud)


(实验)ES7与绑定操作员

绑定运算符::是ECMAScript 提议,在Babel(阶段0)中实现.

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

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

function privateFun (prefix) {
  return prefix + this._foo;
}
Run Code Online (Sandbox Code Playgroud)

加载文件:

import MyObject from './MyObject';

let myObject = new MyObject('bar');
myObject.publicFun();      // Returns '>>bar'
myObject.privateFun('>>'); // TypeError: myObject.privateFun is not a function
Run Code Online (Sandbox Code Playgroud)

  • 这是最好的答案.私人方法的好处,没有垃圾. (32认同)
  • @YvesM.问题的关键是选择模拟它的最佳模式. (2认同)

小智 35

在这种情况下,如果你有一个公共API,并且你想要私有和公共方法/属性,我总是使用模块模式.这个模式在YUI库中很流行,详情可以在这里找到:

http://yuiblog.com/blog/2007/06/12/module-pattern/

这非常简单,其他开发人员也很容易理解.举个简单的例子:

var MYLIB = function() {  
    var aPrivateProperty = true;
    var aPrivateMethod = function() {
        // some code here...
    };
    return {
        aPublicMethod : function() {
            aPrivateMethod(); // okay
            // some code here...
        },
        aPublicProperty : true
    };  
}();

MYLIB.aPrivateMethod() // not okay
MYLIB.aPublicMethod() // okay
Run Code Online (Sandbox Code Playgroud)

  • 但这不是一个类,所以你不能拥有不同状态的2个"实例". (19认同)

Sar*_*ath 21

这是我创建的类,用于理解Douglas Crockford在其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

}
Run Code Online (Sandbox Code Playgroud)

  • 当函数绑定到不同的对象时,使用`that`代替`this`来避免作用域问题.在这里,你将`this`存储在`that`中,而不再使用它,这与完全没有使用它相同.你应该在`Constructor`内部函数(不是方法声明)中用`that`来改变`this`.如果`employee`是`apply`ed或`call`ed这些方法可能会抛出,因为`this`将被错误地绑定. (8认同)
  • 那个=这是一个非常普遍的模式,由前面提到的Crockford在他的书"Javascript:The good parts"中推广 (5认同)

小智 13

我想到了这个:编辑:实际上,有人已经链接到相同的解决方案.咄!

var Car = function() {
}

Car.prototype = (function() {
    var hotWire = function() {
        // Private code *with* access to public properties through 'this'
        alert( this.drive() ); // Alerts 'Vroom!'
    }

    return {
        steal: function() {
            hotWire.call( this ); // Call a private method
        },
        drive: function() {
            return 'Vroom!';
        }
    };
})();

var getAwayVechile = new Car();

hotWire(); // Not allowed
getAwayVechile.hotWire(); // Not allowed
getAwayVechile.steal(); // Alerts 'Vroom!'
Run Code Online (Sandbox Code Playgroud)


D-M*_*arc 12

您现在可以使用es10 私有方法执行此操作。您只需要#在方法名称前添加一个。

class ClassWithPrivateMethod {
  #privateMethod() {
    return 'hello world';
  }

  getPrivateMessage() {
    return #privateMethod();
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 不过这是第三阶段,尚未正式成为该语言的一部分。 (4认同)
  • 在发表此评论时,现在 [92.24% 的浏览器支持](https://caniuse.com/mdn-javascript_classes_private_class_fields)。 (2认同)
  • 截至 2022 年,这应该是选定的答案,至少对于 Node.js LTS 来说是这样 (2认同)

imo*_*mos 10

我认为这些问题一次又一次地出现,因为对闭包缺乏了解.Сlosures是JS中最重要的事情.每个JS程序员都必须感受到它的本质.

1.首先我们需要独立的范围(关闭).

function () {

}
Run Code Online (Sandbox Code Playgroud)

2.在这方面,我们可以做任何我们想做的.没有人会知道它.

function () {
    var name,
        secretSkills = {
            pizza: function () { return new Pizza() },
            sushi: function () { return new Sushi() }
        }

    function Restaurant(_name) {
        name = _name
    }
    Restaurant.prototype.getFood = function (name) {
        return name in secretSkills ? secretSkills[name]() : null
    }
}
Run Code Online (Sandbox Code Playgroud)

3.为了让全世界了解我们的餐厅类,我们必须从封闭处归还.

var Restaurant = (function () {
    // Restaurant definition
    return Restaurant
})()
Run Code Online (Sandbox Code Playgroud)

4.最后,我们有:

var Restaurant = (function () {
    var name,
        secretSkills = {
            pizza: function () { return new Pizza() },
            sushi: function () { return new Sushi() }
        }

    function Restaurant(_name) {
        name = _name
    }
    Restaurant.prototype.getFood = function (name) {
        return name in secretSkills ? secretSkills[name]() : null
    }
    return Restaurant
})()
Run Code Online (Sandbox Code Playgroud)

5.此外,该方法对于继承和模板潜力

// Abstract class
function AbstractRestaurant(skills) {
    var name
    function Restaurant(_name) {
        name = _name
    }
    Restaurant.prototype.getFood = function (name) {
        return skills && name in skills ? skills[name]() : null
    }
    return Restaurant
}

// Concrete classes
SushiRestaurant = AbstractRestaurant({ 
    sushi: function() { return new Sushi() } 
})

PizzaRestaurant = AbstractRestaurant({ 
    pizza: function() { return new Pizza() } 
})

var r1 = new SushiRestaurant('Yo! Sushi'),
    r2 = new PizzaRestaurant('Dominos Pizza')

r1.getFood('sushi')
r2.getFood('pizza')
Run Code Online (Sandbox Code Playgroud)

我希望这有助于人们更好地理解这个主题

  • 它不起作用.这里的name变量就像静态变量一样,并由Restaurant的所有实例共享.这是jsbin:http://jsbin.com/oqewUWa/2/edit?js,output (7认同)
  • 你在第4点有什么了不起的!我认为这是所有这些中唯一的答案,你可以获得在原型上使用方法的性能/内存增益,这些公共方法可以完全访问私有成员.+1 (2认同)
  • 在这里添加我的2c以重申这不起作用,看起来不错,但如上所述,"name"将作为静态变量,即在所有实例之间共享 (2认同)

Joh*_*ers 10

就个人而言,我更喜欢以下模式在JavaScript中创建类:

var myClass = (function() {
    // Private class properties go here

    var blueprint = function() {
        // Private instance properties go here
        ...
    };

    blueprint.prototype = { 
        // Public class properties go here
        ...
    };

    return  {
         // Public class properties go here
        create : function() { return new blueprint(); }
        ...
    };
})();
Run Code Online (Sandbox Code Playgroud)

如您所见,它允许您定义类属性和实例属性,每个属性都可以是公共属性和私有属性.


演示

var Restaurant = function() {
    var totalfoodcount = 0;        // Private class property
    var totalrestroomcount  = 0;   // Private class property
    
    var Restaurant = function(name){
        var foodcount = 0;         // Private instance property
        var restroomcount  = 0;    // Private instance property
        
        this.name = name
        
        this.incrementFoodCount = function() {
            foodcount++;
            totalfoodcount++;
            this.printStatus();
        };
        this.incrementRestroomCount = function() {
            restroomcount++;
            totalrestroomcount++;
            this.printStatus();
        };
        this.getRestroomCount = function() {
            return restroomcount;
        },
        this.getFoodCount = function() {
            return foodcount;
        }
    };
   
    Restaurant.prototype = {
        name : '',
        buy_food : function(){
           this.incrementFoodCount();
        },
        use_restroom : function(){
           this.incrementRestroomCount();
        },
        getTotalRestroomCount : function() {
            return totalrestroomcount;
        },
        getTotalFoodCount : function() {
            return totalfoodcount;
        },
        printStatus : function() {
           document.body.innerHTML
               += '<h3>Buying food at '+this.name+'</h3>'
               + '<ul>' 
               + '<li>Restroom count at ' + this.name + ' : '+ this.getRestroomCount() + '</li>'
               + '<li>Food count at ' + this.name + ' : ' + this.getFoodCount() + '</li>'
               + '<li>Total restroom count : '+ this.getTotalRestroomCount() + '</li>'
               + '<li>Total food count : '+ this.getTotalFoodCount() + '</li>'
               + '</ul>';
        }
    };

    return  { // Singleton public properties
        create : function(name) {
            return new Restaurant(name);
        },
        printStatus : function() {
          document.body.innerHTML
              += '<hr />'
              + '<h3>Overview</h3>'
              + '<ul>' 
              + '<li>Total restroom count : '+ Restaurant.prototype.getTotalRestroomCount() + '</li>'
              + '<li>Total food count : '+ Restaurant.prototype.getTotalFoodCount() + '</li>'
              + '</ul>'
              + '<hr />';
        }
    };
}();

var Wendys = Restaurant.create("Wendy's");
var McDonalds = Restaurant.create("McDonald's");
var KFC = Restaurant.create("KFC");
var BurgerKing = Restaurant.create("Burger King");

Restaurant.printStatus();

Wendys.buy_food();
Wendys.use_restroom();
KFC.use_restroom();
KFC.use_restroom();
Wendys.use_restroom();
McDonalds.buy_food();
BurgerKing.buy_food();

Restaurant.printStatus();

BurgerKing.buy_food();
Wendys.use_restroom();
McDonalds.buy_food();
KFC.buy_food();
Wendys.buy_food();
BurgerKing.buy_food();
McDonalds.buy_food();

Restaurant.printStatus();
Run Code Online (Sandbox Code Playgroud)

另见这个小提琴.


小智 8

所有这些关闭将花费你.确保测试速​​度影响,尤其是在IE中.您会发现最好使用命名约定.还有很多企业网站用户被迫使用IE6 ......

  • 谁在乎呢? (31认同)
  • 仍然使用IE6的9%不关心速度,优化和所有现代HTML5功能.所以关闭不会花费任何东西. (17认同)
  • 现在是0.5%(2012年8月)http://www.w3schools.com/browsers/browsers_explorer.asp (6认同)
  • @LorenzoPolidori w3schools用户!==企业网站用户;] (6认同)

pro*_*mer 5

别这么啰嗦。它是 JavaScript。使用命名约定

在 es6 类工作多年后,我最近开始研究一个 es5 项目(使用已经很冗长的 requireJS)。我已经一遍又一遍地提到这里提到的所有策略,基本上都归结为使用命名约定

  1. Javascript 没有像private. 其他使用 Javascript 的开发人员会预先知道这一点。因此,一个简单的命名约定就足够了。下划线前缀的简单命名约定解决了私有属性和私有方法的问题。
  2. 出于速度原因,让我们利用 Prototype,但不要再冗长了。让我们尽量让 es5 “类”看起来尽可能接近我们在其他后端语言中的预期(并将每个文件视为一个类,即使我们不需要返回一个实例)。
  3. 让我们用一个更现实的模块情况来演示(我们将使用旧的 es5 和旧的 requireJs)。

我的工具提示.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();
            }
            */
        }
    });
Run Code Online (Sandbox Code Playgroud)

  • 应该注意的是,无论是 Javascript 语言还是任何典型的浏览器主机都没有定义任何依赖命名约定来“隐藏”私有状态的对象,因此尽管开发人员掌握了这个概念可能是对的,但它仍然会导致非常非面向对象编程的面向对象方法。 (2认同)

t_d*_*m93 5

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
Run Code Online (Sandbox Code Playgroud)