Частные методы JavaScript - PullRequest
       147

Частные методы JavaScript

435 голосов
/ 11 сентября 2008

Чтобы создать класс 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 как закрытый метод, чтобы оба они выполнялись?

Я прочитал рецензию Дуга Крокфорда несколько раз, но не похоже, что "частные" методы могут вызываться открытыми методами, а "привилегированные" методы могут вызываться извне.

Ответы [ 29 ]

362 голосов
/ 11 сентября 2008

Вы можете сделать это, но недостатком является то, что он не может быть частью прототипа:

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();
    }
}
159 голосов
/ 11 сентября 2008

Вы можете симулировать приватные методы, как это:

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();

Подробнее об этой технике здесь: http://webreflection.blogspot.com/2008/04/natural-javascript-private-methods.html

147 голосов
/ 07 августа 2014

Использование функции самостоятельного вызова и вызов

JavaScript использует прототипы и не имеет классов (или методов в этом отношении), таких как объектно-ориентированные языки. Разработчик JavaScript должен думать на JavaScript.

Цитата из Википедии:

В отличие от многих объектно-ориентированных языков, нет различия между определение функции и определение метода. Скорее различие происходит во время вызова функции; когда функция вызывается как метод объекта, локальное ключевое слово функции связано с этим объект для этого вызова.

Решение с использованием самовозвратной функции и функции вызова для вызова частного «метода»:

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, '>>');
}

exports.MyObject = MyObject;

Загрузить файл:

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

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


(экспериментальная) ES7 с оператором связывания

Оператор связывания :: является предложением ECMAScript и реализован в Babel ( stage 0 ).

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

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

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

Загрузить файл:

import MyObject from './MyObject';

let myObject = new MyObject('bar');
myObject.publicFun();      // Returns '>>bar'
myObject.privateFun('>>'); // TypeError: myObject.privateFun is not a function
35 голосов
/ 11 сентября 2008

В этих ситуациях, когда у вас есть общедоступный 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
20 голосов
/ 15 февраля 2012

Вот класс, который я создал, чтобы понять, что Дуглас Крокфорд предложил на своем сайте Частные пользователи в 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

}
13 голосов
/ 01 сентября 2009

Я наколдовал это: РЕДАКТИРОВАТЬ: На самом деле, кто-то связал с тем же решением. Duh!

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!'
10 голосов
/ 14 августа 2013

Я думаю, что такие вопросы возникают снова и снова из-за непонимания замыканий. Закрытие - самая важная вещь в JS. Каждый программист JS должен чувствовать суть этого.

1. Прежде всего нам нужно сделать отдельную область (закрытие).

function () {

}

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

3. Чтобы мир узнал о нашем ресторанном классе, мы должны вернуть его из закрытия.

var Restaurant = (function () {
    // Restaurant definition
    return Restaurant
})()

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
})()

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')

Надеюсь, это поможет кому-то лучше понять эту тему

9 голосов
/ 08 июня 2017

Лично я предпочитаю следующий шаблон для создания классов в 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(); }
        ...
    };
})();

Как видите, он позволяет определять как свойства класса, так и свойства экземпляра, каждое из которых может быть открытым и закрытым.


Демо

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();

См. Также эту скрипку .

8 голосов
/ 15 сентября 2008

Все это закрытие обойдется вам. Убедитесь, что вы протестировали значения скорости, особенно в IE. Вы найдете, что вам лучше с соглашением об именах. Все еще есть много корпоративных пользователей, которые вынуждены использовать IE6 ...

3 голосов
/ 18 августа 2014

Возьмите любое из решений, которые следуют шаблону Крокфорда private или priviledged . Например:

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

Обратите внимание, что приведенный выше код является общим для всех типов данных конструктора. Он потерпит неудачу с некоторыми решениями здесь, но должно быть ясно, что почти все решения, основанные на замыканиях, могут быть разбиты таким образом с различными параметрами replace().

После этого любой объект, созданный с помощью new Foo(), будет иметь метод eval, который можно вызывать для возврата или изменения значений или методов, определенных в замыкании конструктора, например ::

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

Единственная проблема, с которой я могу столкнуться, это то, что она не будет работать в тех случаях, когда существует только один экземпляр, и он создается при загрузке. Но тогда нет никакой причины фактически определять прототип, и в этом случае злоумышленник может просто воссоздать объект вместо конструктора, если у него есть способ передать те же самые параметры (например, они являются постоянными или вычисляются из доступных значений).

На мой взгляд, это в значительной степени делает решение Крокфорда бесполезным. Поскольку «конфиденциальность» легко нарушается, недостатки его решения (снижение читабельности и удобства обслуживания, снижение производительности, увеличение памяти) делают «нет конфиденциальность »метод на основе прототипа лучший выбор.

Обычно я использую начальные подчеркивания для обозначения __private и _protected методов и полей (стиль Perl), но идея конфиденциальности в JavaScript просто показывает, как это неправильно понимается.

Поэтому я не согласен с Крокфордом , за исключением его первого предложения.

Так как же получить реальную конфиденциальность в JS? Поместите все, что требуется, чтобы быть частным на стороне сервера, и используйте JS для выполнения вызовов AJAX.

...