Phương thức riêng tư của JavaScript


482

Để tạo một lớp JavaScript với một phương thức công khai, tôi sẽ làm một cái gì đó như:

function Restaurant() {}

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

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

Bằng cách đó, người dùng của lớp tôi có thể:

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

Làm cách nào để tôi tạo một phương thức riêng có thể được gọi bởi các phương thức buy_fooduse_restroomnhưng không phải bên ngoài bởi người dùng của lớp?

Nói cách khác, tôi muốn phương thức thực hiện của mình có thể thực hiện:

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

Nhưng điều này không nên làm việc:

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

Làm thế nào để tôi xác định private_stufflà một phương thức riêng tư để cả hai đều đúng?

Tôi đã đọc bài viết của Doug Crockford một vài lần nhưng có vẻ như các phương thức "riêng tư" có thể được gọi bằng các phương thức công khai và các phương thức "đặc quyền" có thể được gọi ra bên ngoài.

Câu trả lời:


403

Bạn có thể làm điều đó, nhưng nhược điểm là nó không thể là một phần của nguyên mẫu:

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

9
Việc giấu những người bên trong việc đóng cửa sẽ không đảm bảo quyền riêng tư cho tất cả các phiên dịch viên. Xem code.google.com/p/google-caja/wiki/ khăn
Mike Samuel

51
@mikeamuel - đúng, nhưng chỉ khi những người phiên dịch có lỗi trong đó :)
jvenema

133
Đây là một phương thức riêng tư, nhưng sẽ có xu hướng sử dụng nhiều bộ nhớ hơn so với phương thức nguyên mẫu thông thường, đặc biệt nếu bạn đang tạo ra nhiều đối tượng này. Đối với mỗi thể hiện đối tượng, nó tạo ra một hàm riêng biệt ràng buộc với đối tượng và không phải lớp. Ngoài ra, điều này không nhận được rác được thu thập cho đến khi chính đối tượng bị phá hủy.
Arindam

4
Nếu bạn tạo một đối tượng McDonalds () kế thừa từ Restaurant () McDonalds không thể ghi đè các phương thức riêng tư nếu bạn khai báo chúng theo cách này. Chà, thực ra là bạn có thể, nhưng nó sẽ không hoạt động nếu một số phương thức khác gọi là private, nó sẽ gọi phiên bản gốc của phương thức và bạn cũng không thể gọi phương thức cha. Cho đến nay tôi không tìm thấy một cách tốt để tuyên bố các phương thức riêng tư hoạt động tốt với thừa kế. Điều này và ý nghĩa hiệu suất làm cho đây không phải là một mẫu thiết kế rất tốt. Tôi sẽ khuyên bạn nên thực hiện một số loại tiền tố để chỉ ra các phương thức riêng tư, thích bắt đầu với một gạch chân.
Hoffmann

68
Các phương thức riêng tư không bị ghi đè - chúng là riêng tư.
17 trên 26

163

Sử dụng chức năng tự gọi và gọi

JavaScript sử dụng các nguyên mẫu và không có các lớp (hoặc phương thức cho vấn đề đó) như các ngôn ngữ hướng đối tượng. Một nhà phát triển JavaScript cần phải suy nghĩ bằng JavaScript.

Trích dẫn Wikipedia:

Không giống như nhiều ngôn ngữ hướng đối tượng, không có sự phân biệt giữa định nghĩa hàm và định nghĩa phương thức. Thay vào đó, sự phân biệt xảy ra trong khi gọi chức năng; khi một hàm được gọi là một phương thức của một đối tượng, thì từ khóa cục bộ của hàm này bị ràng buộc với đối tượng đó cho lời gọi đó.

Giải pháp sử dụng chức năng tự gọichức năng gọi để gọi "phương thức" riêng tư:

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

Hàm gọi cho phép chúng ta gọi hàm riêng với ngữ cảnh thích hợp ( this).


Đơn giản hơn với Node.js

Nếu bạn đang sử dụng node.js , bạn không cần IIFE vì bạn có thể tận dụng hệ thống tải mô-đun :

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

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

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

exports.MyObject = MyObject;

Tải tệp:

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

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


(thử nghiệm) ES7 với Toán tử Bind

Toán tử liên kết ::là một đề xuất ECMAScript và được thực hiện trong Babel ( giai đoạn 0 ).

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

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

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

Tải tệp:

import MyObject from './MyObject';

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

34
Đây là câu trả lời tốt nhất. Những lợi ích của phương pháp riêng tư, không có rác.
TaylorMac

1
@TaylorMac Ngoại trừ .callphần.
pishpish

1
@janje Hả? Đó là điểm của câu hỏi, không có riêng tư f()trong javascript (không phải lúc này).
Yves M.

2
@YvesM. Điểm của câu hỏi là chọn mô hình tốt nhất mô phỏng nó.
pishpish 17/2/2016

1
@changed sự thỏa hiệp tốt nhất để có các chức năng ẩn (không thể gọi được từ bên ngoài), sintax đẹp (không .call) và bộ nhớ nhỏ (không có chức năng trên ví dụ) là gì? Điều đó thậm chí còn tồn tại?
Ciprian Tomoiagă

161

Bạn có thể mô phỏng các phương thức riêng tư như thế này:

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

Thông tin thêm về kỹ thuật này tại đây: http://webreflection.blogspot.com/2008/04/natural-javascript-private-methods.html


7
Tôi cũng sẽ đề xuất trang web của Douglas Crockford như một tài nguyên về các phương thức riêng tư / công cộng và các thành viên javascript.crockford.com/private.html
Jared

10
Ông đề cập đến liên kết đó trong câu hỏi.
Gulzar Nazim

8
Nhược điểm của phương pháp này là bạn không thể có private_ ware () truy cập dữ liệu riêng tư khác trong Nhà hàng và các phương thức khác của Nhà hàng không thể gọi private_ ware (). Ưu điểm là nếu bạn không cần một trong những điều kiện tôi vừa đề cập, bạn có thể giữ use_restroom () trong nguyên mẫu.
17 trên 26 tháng

6
Đây phải là giải pháp và câu trả lời được chấp nhận vì tác giả rõ ràng đang sử dụng thuộc tính nguyên mẫu.
Gabriel Llamas

23
Với mẫu được đề xuất bởi @georgebrock, tất cả dữ liệu riêng tư sẽ được chia sẻ giữa tất cả các đối tượng nhà hàng. Điều đó gần giống với các biến và hàm riêng tĩnh trong OOP dựa trên lớp. Tôi cho rằng OP không muốn điều này. Để minh họa điều tôi muốn nói, tôi đã tạo ra một jsFiddle .
feklee

35

Trong những tình huống này khi bạn có API công khai và bạn muốn các phương thức / thuộc tính riêng tư và công khai, tôi luôn sử dụng Mô hình Mô-đun. Mẫu này đã được phổ biến trong thư viện YUI và các chi tiết có thể được tìm thấy ở đây:

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

Nó thực sự đơn giản và dễ dàng cho các nhà phát triển khác hiểu. Ví dụ đơn giản:

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

loại điều này sẽ không được phát hiện bởi tính năng tự động hoàn thành của IDE :(
Nhấp vào Upvote

19
Nhưng đây không phải là một lớp, vì vậy bạn không thể có 2 "trường hợp" này với các trạng thái khác nhau.
DevAntoine

xóa phần () và bạn có một "lớp". ít nhất là nơi bạn có thể khởi tạo các đồng vị khác nhau với các trạng thái khác nhau. mô hình mô-đun khá tốn bộ nhớ, mặc dù ...
oligofren

@DevAntoine Hãy xem các bình luận cho câu trả lời của 17 trên 26. Trong các lớp mở rộng JavaScript và các phương thức riêng không dễ dàng đi đôi với nhau. Đề nghị của tôi trong trường hợp này sẽ là đi với thành phần trên thừa kế. Tạo một nguyên mẫu có thể mở rộng với các phương thức tương tự như đối tượng bê tông kèm theo. Sau đó, nội bộ cho nguyên mẫu của bạn, bạn có thể quyết định khi nào gọi các phương thức trên đối tượng cụ thể của bạn.

Có bất kỳ nhược điểm nào khi gọi các biến công khai từ các hàm riêng như vậy aPrivateMethod = function() { MYLIB.aPublicProperty}không?
Hanna

21

Đây là lớp mà tôi đã tạo để hiểu những gì Douglas Crockford đã đề xuất trong trang web của mình Thành viên riêng trong 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

}

5
Cái đó = đây là một mô hình khá phổ biến, được phổ biến bởi Crockford đã nói ở trên trong cuốn sách "Javascript: Những phần tốt"
oligofren

8
thatđược sử dụng thay vì thisđể tránh các vấn đề phạm vi, khi các chức năng được liên kết với một đối tượng khác. Ở đây, bạn đang lưu trữ thistrong thatvà không bao giờ sử dụng nó một lần nữa đó là giống như không làm gì cả. Bạn nên thay đổi thisvới thattất cả các Constructorchức năng bên trong (không phải khai báo phương thức). Nếu employeeapplyed hoặc called theo một cách nào đó, các phương thức này có thể ném vì thissẽ bị ràng buộc không chính xác.
Maroshii

Ngoài ra, mọi trường hợp sẽ có một bản sao đầy đủ của các chức năng riêng tư, không hiệu quả. Trên thực tế là các phương thức công khai không thể truy cập vào các lớp riêng tư, khiến tôi muốn chuyển sang phi tiêu. Thật không may angulardart là siêu beta.
Ray Foss

Đâu là phương thức "constructor" trong này? Tôi sẽ đặt logic ở đâu mà thường được thực thi trong phương thức constructor của một lớp khi nó được khởi tạo?
BadHorsie

13

Tôi gợi lên điều này: EDIT: Thật ra, ai đó đã liên kết với một giải pháp giống hệt nhau. Tât nhiên!

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

1
Đây là một kỹ thuật hay, nhưng làm thế nào bạn cho phép các tham số trong hàm tạo của bạn? Ví dụ var getAwayVehicle = new Car(100);, nơi 100tốc độ và bạn muốn cảnh báo tốc độ. Cảm ơn!
Jason

1
Tìm ra nó, có thể có var Car = function(speed) { this.speed = speed; }và `return {constructor: Car, ...`
Jason

11

Tôi nghĩ rằng những câu hỏi như vậy xuất hiện nhiều lần vì sự thiếu hiểu biết về việc đóng cửa. Công thức là điều quan trọng nhất trong JS. Mỗi lập trình viên JS phải cảm nhận được bản chất của nó.

1. Trước hết chúng ta cần tạo phạm vi riêng biệt (đóng cửa).

function () {

}

2. Trong lĩnh vực này, chúng tôi có thể làm bất cứ điều gì chúng tôi muốn. Và sẽ không ai biết về nó.

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. Để thế giới biết về lớp nhà hàng của chúng tôi, chúng tôi phải trả lại từ việc đóng cửa.

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

4. Cuối cùng, chúng ta có:

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. Ngoài ra, phương pháp này có tiềm năng để kế thừa và tạo khuôn mẫu

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

Tôi hy vọng điều này sẽ giúp ai đó hiểu rõ hơn về chủ đề này


2
Những gì bạn có trong điểm 4. là tuyệt vời! Tôi nghĩ đó là câu trả lời duy nhất từ ​​tất cả những người ở đây, nơi bạn có được cả hiệu suất / mức tăng bộ nhớ khi sử dụng các phương thức trên nguyên mẫu VÀ các phương thức công khai này có quyền truy cập đầy đủ vào các thành viên riêng. +1
Hudon

7
Nó không hoạt động. Biến tên ở đây hoạt động như biến tĩnh và được chia sẻ bởi tất cả các phiên bản của Nhà hàng. Đây là jsbin: jsbin.com/oqewUWa/2/edit?js,output
Shital Shah

đó là một thử nghiệm tốt, nhưng như Shital đã chỉ ra, biến tên là lỗi.
oligofren

2
thêm 2c của tôi vào đây để khẳng định điều này không hoạt động, trông rất đẹp, nhưng như đã chỉ ra ở trên "tên" sẽ đóng vai trò là một biến tĩnh tức là được chia sẻ trên tất cả các trường hợp
Paul Carroll

10

Cá nhân, tôi thích mẫu sau để tạo các lớp trong 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(); }
        ...
    };
})();

Như bạn có thể thấy, nó cho phép bạn xác định cả thuộc tính lớp và thuộc tính thể hiện, mỗi thuộc tính có thể là công khai và riêng tư.


Bản giới thiệu

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

Xem thêm Fiddle này .


Điều này khiến tôi muốn sử dụng các lớp es6 và xem những gì nó cũng dịch mã
sheriffderek

9

Tất cả các đóng cửa này sẽ chi phí bạn. Hãy chắc chắn rằng bạn đã kiểm tra hàm ý tốc độ, đặc biệt là trong IE. Bạn sẽ thấy bạn tốt hơn với một quy ước đặt tên. Vẫn còn rất nhiều người dùng web công ty bị buộc phải sử dụng IE6 ...


34
Ai quan tâm, nghiêm túc chứ?
nowayyy

17
9% vẫn đang sử dụng IE6 không quan tâm đến tốc độ, tối ưu hóa và tất cả các tính năng HTML5 hiện đại. Vì vậy, đóng cửa sẽ không mất gì.
Gabriel Llamas

6
Bây giờ là 0,5% (tháng 8 năm 2012) w3schools.com/browsers/browsftexexplorer.asp
Lorenzo Polidori

7
@LorenzoPolidori w3schools người dùng! == Người dùng web công ty;]
WynandB

Một quy ước đặt tên (ví dụ: chuẩn bị một dấu gạch dưới) là cách để đi. Mã dễ bảo trì hơn và các phương thức vẫn được xác định trên nguyên mẫu. Ngày nay mặc dù ... tôi chỉ đánh dấu phương thức là riêng tư trong bản thảo.
David Sherret

5

Đừng quá dài dòng. Đó là Javascript. Sử dụng một quy ước đặt tên .

Sau nhiều năm làm việc trong các lớp es6, gần đây tôi đã bắt đầu làm việc với một dự án es5 (sử dụng allowJS vốn đã rất dài dòng). Tôi đã hơn và hơn tất cả các chiến lược được đề cập ở đây và về cơ bản tất cả đều sôi sục để sử dụng quy ước đặt tên :

  1. Javascript không có từ khóa phạm vi như private . Các nhà phát triển khác khi vào Javascript sẽ biết trước. Do đó, một quy ước đặt tên đơn giản là quá đủ. Một quy ước đặt tên đơn giản của tiền tố với dấu gạch dưới giải quyết vấn đề của cả thuộc tính riêng và phương thức riêng.
  2. Hãy tận dụng Nguyên mẫu vì lý do tốc độ, nhưng không cho phép dài dòng hơn thế. Chúng ta hãy cố gắng giữ cho "lớp" es5 trông gần giống với những gì chúng ta có thể mong đợi trong các ngôn ngữ phụ trợ khác (và coi mọi tệp là một lớp, ngay cả khi chúng ta không cần phải trả về một thể hiện).
  3. Hãy chứng minh với một tình huống mô-đun thực tế hơn (chúng ta sẽ sử dụng es5 cũ và các yêu cầu cũ).

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();
            }
            */
        }
    });

2
Cần lưu ý rằng cả ngôn ngữ Javascript và bất kỳ máy chủ trình duyệt thông thường nào đều không xác định bất kỳ đối tượng nào dựa trên các quy ước đặt tên để "ẩn" trạng thái riêng tư, do đó, trong khi bạn có thể đúng rằng các nhà phát triển sẽ nắm bắt khái niệm này, nó vẫn dẫn đến một điều rất không Cách tiếp cận OO để lập trình OO.
người làm giàu giàu có vào

Tôi có thể yêu cầu một tài liệu tham khảo tốt trong việc làm như vậy? Có những phần trong ví dụ này là mới đối với tôi. Và define, và constructorcấu trúc chính nó. Mặc dù tôi đồng ý chủ yếu về câu trả lời, tôi đã bắt đầu làm việc với JS với rất nhiều ảnh hưởng OOP và thậm chí đã nhảy quá sớm với TS vì tôi đã có kinh nghiệm trước với C #. Tôi nghĩ rằng tôi phải học những thứ này và phải hiểu mô hình thủ tục / mô hình thủ tục. (upvote, btw)
Cold Cerberus

1
@ColdCerberus đoạn mã này đang sử dụng es5. Bạn có thể thấy một bức tranh hoàn chỉnh về phương pháp này tại đây: gist.github.com/jonnyreeves/2474026 . Nhưng hãy lưu ý, bạn sẽ muốn áp dụng cách tiếp cận này và cập nhật nó thành sử dụng các lớp es6: googlechrome.github.io/samples/groupes-es6 và các mô-đun es6 (cú pháp nhập / xuất): hackernoon.com/
tựa

5

Bạn có thể làm điều này ngay bây giờ với các phương thức riêng tư es10 . Bạn chỉ cần thêm một #trước tên phương thức.

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

  getPrivateMessage() {
    return #privateMethod();
  }
}

2
Ngoại trừ đây là giai đoạn 3 và chưa chính thức là một phần của ngôn ngữ.
misterhtmlcss

3

Thực hiện bất kỳ các giải pháp mà theo Crockford tin hoặc priviledged mẫu. Ví dụ:

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

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

Trong mọi trường hợp kẻ tấn công không có quyền "thực thi" ngay trong bối cảnh JS, anh ta không có cách nào truy cập vào bất kỳ trường hoặc phương thức "công khai" hoặc "riêng tư" nào. Trong trường hợp kẻ tấn công có quyền truy cập đó, anh ta có thể thực hiện lệnh này:

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

Lưu ý rằng đoạn mã trên là chung cho tất cả các kiểu xây dựng-quyền riêng tư. Nó sẽ thất bại với một số giải pháp ở đây nhưng rõ ràng là khá nhiều tất cả các giải pháp dựa trên đóng có thể bị phá vỡ như thế này với các replace()tham số khác nhau .

Sau khi điều này được thực thi, bất kỳ đối tượng nào được tạo new Foo()sẽ có một evalphương thức có thể được gọi để trả về hoặc thay đổi các giá trị hoặc phương thức được xác định trong bao đóng của hàm tạo, ví dụ:

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

Vấn đề duy nhất tôi có thể thấy với điều này là nó sẽ không hoạt động trong trường hợp chỉ có một phiên bản và nó được tạo khi tải. Nhưng sau đó, không có lý do gì để thực sự xác định một nguyên mẫu và trong trường hợp đó, kẻ tấn công có thể chỉ cần tạo lại đối tượng thay vì hàm tạo miễn là anh ta có cách truyền các tham số giống nhau (ví dụ: chúng là hằng số hoặc được tính từ các giá trị khả dụng).

Theo tôi, điều này khá nhiều làm cho giải pháp của Crockford trở nên vô dụng.Vì "quyền riêng tư" dễ dàng bị phá vỡ nhược điểm của giải pháp của anh ấy (giảm khả năng đọc và bảo trì, giảm hiệu suất, tăng bộ nhớ) làm cho phương pháp dựa trên nguyên mẫu "không riêng tư" trở thành lựa chọn tốt hơn.

Tôi thường sử dụng dấu gạch dưới hàng đầu để đánh dấu __private_protectedcác phương thức và trường (kiểu Perl), nhưng ý tưởng về quyền riêng tư trong JavaScript chỉ cho thấy đó là ngôn ngữ bị hiểu sai như thế nào.

Vì vậy, tôi không đồng ý với Crockford ngoại trừ câu đầu tiên của anh ấy.

Vậy làm thế nào để bạn có được sự riêng tư thực sự trong JS? Đặt mọi thứ bắt buộc phải riêng tư ở phía máy chủ và sử dụng JS để thực hiện các cuộc gọi AJAX.


Đây là một vấn đề nghiêm trọng cần được biết đến nhiều hơn. Có một 'phòng thủ' chống lại cuộc tấn công này?
James

@ James Không ai mà tôi biết, tôi nghĩ đó là bản chất của con thú. Như tôi đã chỉ ra, bạn có thể di chuyển chức năng đến máy chủ nơi nó chạy trong một môi trường được bảo vệ. Điểm tôi muốn vượt qua trong câu trả lời của mình là giải pháp của Crockford không giúp ích gì, làm phức tạp mã một cách không cần thiết và che giấu sự cần thiết phải làm gì đó về nó.
Fozi

Nếu người dùng nhập mật khẩu bí mật, anh ta không thể thực hiện phía máy chủ này. Tại một số điểm, mật khẩu sẽ ở dạng var 'private'. Vì vậy, một kẻ tấn công có thể đọc nó? Tôi tin tưởng mã của mình và dù sao các tiêu chuẩn nhà của tôi không cho phép eval (). Kẻ tấn công có thể là một số trình cắm hoặc thư viện JavaScript độc hại của bên thứ ba mà tôi không kiểm tra đúng cách - vì vậy, tôi cần phải kiểm tra chúng. Kẻ tấn công cũng có thể là một cái gì đó giống như một quảng cáo ở bên không nên tương tác với mã của tôi. Tôi bảo vệ chống lại điều đó bằng cách gói tất cả mã của mình vào một ẩn danh (function () {allMyStuff}());để không tiết lộ gì trên toàn cầu.
James

@James Đây là nhận OT, nếu bạn muốn tiếp tục điều này, xin vui lòng mở một câu hỏi mới. Có, một kẻ tấn công có thể đọc mật khẩu. Từ biến "riêng tư" của bạn. Hoặc từ DOM. Hoặc anh ta có thể thay thế API AJAX. Hoặc anh ấy thay thế trang của bạn bằng một cái gì đó khác. Nếu anh ta không thể thực hiện bất kỳ điều nào ở trên thì không cần phải có "quyền riêng tư" của JS vì anh ta cũng không thể đọc bất kỳ biến JS nào của bạn. Vấn đề là "giải pháp" của Crockford mà mọi người đang sử dụng hiện tại không giúp ích gì cho vấn đề này.
Fozi 4/2/2015

Tôi tin rằng obfuscation mã giả có thể là một sự bảo vệ yếu chống lại cuộc tấn công này - khó sửa đổi cơ thể chức năng hơn khi bạn không thể phụ thuộc vào chức năng có một tên cố định; khó thực hiện hơn f.eval('nameOfVariable')khi bạn không biết 'nameOfVariable'...
Gershom


2

Nếu bạn muốn có đầy đủ các chức năng công cộng và riêng tư với khả năng cho các chức năng công cộng truy cập các chức năng riêng tư, mã bố cục cho một đối tượng như thế này:

function MyObject(arg1, arg2, ...) {
  //constructor code using constructor arguments...
  //create/access public variables as 
  // this.var1 = foo;

  //private variables

  var v1;
  var v2;

  //private functions
  function privateOne() {
  }

  function privateTwon() {
  }

  //public functions

  MyObject.prototype.publicOne = function () {
  };

  MyObject.prototype.publicTwo = function () {
  };
}

Ai đó có thể cho tôi biết tại sao điều này đã được bỏ phiếu xuống? Co vẻ tôt vơi tôi.
thomasrutter

10
Mỗi khi bạn làm một new MyObject, nguyên mẫu của MyObjectđược thay thế bằng các giá trị tương tự.
bpierre

2
-1. Không bao giờ gán cho .prototypebên trong các nhà xây dựng.
Bergi

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

Tương tự như georgebrock nhưng ít dài dòng hơn (IMHO) Có vấn đề gì khi thực hiện theo cách này không? (Tôi chưa thấy nó ở bất cứ đâu)

chỉnh sửa: Tôi nhận ra điều này là vô ích vì mỗi khởi tạo độc lập có bản sao riêng của các phương thức công khai, do đó làm suy yếu việc sử dụng nguyên mẫu.


2

Đây là những gì tôi thích nhất từ ​​trước đến nay về các phương thức / thành viên riêng tư / công khai và khởi tạo trong javascript:

đây là bài viết: http://www.seheads.com/?p=1090

và đây là ví dụ:

var Person = (function () {

    //Immediately returns an anonymous function which builds our modules 
    return function (name, location) {

        alert("createPerson called with " + name);

        var localPrivateVar = name;

        var localPublicVar = "A public variable";

        var localPublicFunction = function () {
            alert("PUBLIC Func called, private var is :" + localPrivateVar)
        };

        var localPrivateFunction = function () {
            alert("PRIVATE Func called ")
        };

        var setName = function (name) {

            localPrivateVar = name;

        }

        return {

            publicVar: localPublicVar,

            location: location,

            publicFunction: localPublicFunction,

            setName: setName

        }

    }
})();


//Request a Person instance - should print "createPerson called with ben"
var x = Person("ben", "germany");

//Request a Person instance - should print "createPerson called with candide"
var y = Person("candide", "belgium");

//Prints "ben"
x.publicFunction();

//Prints "candide"
y.publicFunction();

//Now call a public function which sets the value of a private variable in the x instance
x.setName("Ben 2");

//Shouldn't have changed this : prints "candide"
y.publicFunction();

//Should have changed this : prints "Ben 2"
x.publicFunction();

Mã thông báo: http://jsfiddle.net/northkildonan/kopj3dt3/1/


Cách tiếp cận này có một nhược điểm quan trọng - nếu bạn đã tạo 2 đối tượng, trong bộ nhớ sẽ có 2 phương thức tương tự (ví dụ Công khai), 1000 đối tượng sẽ ăn hết bộ nhớ của bạn.
Artem G

2

Các mô-đun là đúng trong hầu hết các trường hợp. Nhưng nếu bạn có hàng ngàn trường hợp, các lớp lưu bộ nhớ. Nếu việc lưu bộ nhớ là một mối quan tâm và các đối tượng của bạn chứa một lượng nhỏ dữ liệu riêng tư, nhưng có nhiều chức năng công khai, thì bạn sẽ muốn tất cả các chức năng công cộng sống trong .prototype để lưu bộ nhớ.

Đây là những gì tôi nghĩ ra:

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

Đối tượng privchứa các thuộc tính riêng tư. Nó có thể truy cập thông qua chức năng công cộng getPriv(), nhưng chức năng này trả về falsetrừ khi bạn vượt qua nó secretvà điều này chỉ được biết trong phần đóng chính.


Điều đó mô phỏng các thành viên được bảo vệ, các kiểu kế thừa từ nó cũng có thể truy cập vào các thành viên được bảo vệ. Tôi cũng thích mẫu này hơn mẫu riêng
HMR

2

Cái này thì sao?

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;

})();

Tra cứu biến riêng tư là không thể ngoài phạm vi của chức năng ngay lập tức. Không có sự trùng lặp các chức năng, tiết kiệm bộ nhớ.

Nhược điểm là việc tra cứu các biến riêng tư là khó privateVars[this.id].cookedhiểu khi gõ. Ngoài ra còn có một biến "id" thêm.


Điều này sẽ rời Restaurantnhư undefinedvì bạn đang không trở về bất cứ điều gì từ các chức năng ẩn danh.
dùng4815162342

Ở đâu và như thế nào? Giả sử tham chiếu đến Nhà hàng đã tạo bị mất, privateVars sẽ không có tham chiếu đến chủ sở hữu của nó. Các đồ thị tham khảo là acyclic. Tôi đang thiếu gì?
Evan Leis

Trên thực tế đây là câu trả lời duy nhất hỗ trợ các thuộc tính riêng bên cạnh các phương thức. Hai vấn đề duy nhất đã được ghi nhận trong câu trả lời.
pishpish

Tôi thấy rò rỉ bộ nhớ: khi một phiên bản Restaurantđã được thu gom rác, các giá trị của nó vẫn nằm trong privateVars. A WeakMapcó thể là một sự thay thế tốt cho Arraytrường hợp này.
Gershom

2

Bao bọc tất cả mã trong Hàm ẩn danh: Sau đó, tất cả các chức năng sẽ ở chế độ riêng tư, CHỈ được gắn vào windowđối tượng:

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

Dùng cái này :

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

VĨ CẦM


1

Tôi thích lưu trữ dữ liệu riêng tư trong một liên kết WeakMap. Điều này cho phép bạn giữ các phương thức công khai của bạn trên nguyên mẫu nơi chúng thuộc về. Đây dường như là cách hiệu quả nhất để xử lý vấn đề này đối với số lượng lớn đối tượng.

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

0

Các hàm riêng tư không thể truy cập các biến công khai bằng mô hình mô-đun


0

Vì mọi người đều đăng mã ở đây, nên tôi cũng sẽ làm điều đó ...

Tôi thích Crockford vì anh ấy đã giới thiệu các mẫu hướng đối tượng thực trong Javascript. Nhưng anh cũng nghĩ ra một sự hiểu lầm mới, đó là "cái đó".

Vậy tại sao anh ta lại sử dụng "that = this"? Nó không có gì để làm với các chức năng riêng tư cả. Nó phải làm với các chức năng bên trong!

Bởi vì theo Crockford, đây là mã lỗi:

Function Foo( ) {
    this.bar = 0; 
    var foobar=function( ) {
        alert(this.bar);
    }
} 

Vì vậy, ông đề nghị làm điều này:

Function Foo( ) {
    this.bar = 0;
    that = this; 
    var foobar=function( ) {
        alert(that.bar);
    }
}

Vì vậy, như tôi đã nói, tôi khá chắc chắn rằng Crockford đã sai lời giải thích của anh ấy về điều đó và điều này (nhưng mã của anh ấy chắc chắn là đúng). Hay anh ta chỉ đang đánh lừa thế giới Javascript, để biết ai đang sao chép mã của mình? Tôi không biết ... Tôi không có trình duyệt, D

BIÊN TẬP

À, đó là tất cả những gì về: 'var that = this;' nghĩa là gì trong JavaScript?

Vì vậy, Crockie đã thực sự sai với lời giải thích của mình .... nhưng đúng với mã của anh ấy, vì vậy anh ấy vẫn là một chàng trai tuyệt vời. :))


0

Nói chung tôi đã thêm Object riêng _ tạm thời vào đối tượng. Bạn phải mở ex riêng tư trong "Trình tạo năng lượng" cho phương thức. Nếu bạn gọi phương thức từ nguyên mẫu, bạn sẽ có thể ghi đè lên phương thức nguyên mẫu

  • Tạo một phương thức công khai có thể truy cập trong "Trình tạo năng lượng": (ctx là bối cảnh đối tượng)

    ctx.test = GD.Fabric.open('test', GD.Test.prototype, ctx, _); // is a private object
  • Bây giờ tôi có openPrivacy này:

    GD.Fabric.openPrivacy = function(func, clss, ctx, _) {
        return function() {
            ctx._ = _;
            var res = clss[func].apply(ctx, arguments);
            ctx._ = null;
            return res;
        };
    };

0

Đây là những gì tôi đã làm việc:

Cần một loại mã đường mà bạn có thể tìm thấy ở đây . Cũng hỗ trợ bảo vệ, kế thừa, ảo, công cụ tĩnh ...

;( function class_Restaurant( namespace )
{
    'use strict';

    if( namespace[ "Restaurant" ] ) return    // protect against double inclusions

        namespace.Restaurant = Restaurant
    var Static               = TidBits.OoJs.setupClass( namespace, "Restaurant" )


    // constructor
    //
    function Restaurant()
    {
        this.toilets = 3

        this.Private( private_stuff )

        return this.Public( buy_food, use_restroom )
    }

    function private_stuff(){ console.log( "There are", this.toilets, "toilets available") }

    function buy_food     (){ return "food"        }
    function use_restroom (){ this.private_stuff() }

})( window )


var chinese = new Restaurant

console.log( chinese.buy_food()      );  // output: food
console.log( chinese.use_restroom()  );  // output: There are 3 toilets available
console.log( chinese.toilets         );  // output: undefined
console.log( chinese.private_stuff() );  // output: undefined

// and throws: TypeError: Object #<Restaurant> has no method 'private_stuff'

0
Class({  
    Namespace:ABC,  
    Name:"ClassL2",  
    Bases:[ABC.ClassTop],  
    Private:{  
        m_var:2  
    },  
    Protected:{  
        proval:2,  
        fight:Property(function(){  
            this.m_var--;  
            console.log("ClassL2::fight (m_var)" +this.m_var);  
        },[Property.Type.Virtual])  
    },  
    Public:{  
        Fight:function(){  
            console.log("ClassL2::Fight (m_var)"+this.m_var);  
            this.fight();  
        }  
    }  
});  

https://github.com/emonying/JSClass


0

Tôi đã tạo một công cụ mới để cho phép bạn có các phương thức riêng tư thực sự trên nguyên mẫu https://github.com/TremayneChrist/ProtectJS

Thí dụ:

var MyObject = (function () {

  // Create the object
  function MyObject() {}

  // Add methods to the prototype
  MyObject.prototype = {

    // This is our public method
    public: function () {
      console.log('PUBLIC method has been called');
    },

    // This is our private method, using (_)
    _private: function () {
      console.log('PRIVATE method has been called');
    }
  }

  return protect(MyObject);

})();

// Create an instance of the object
var mo = new MyObject();

// Call its methods
mo.public(); // Pass
mo._private(); // Fail

1
Bạn có thể giải thích làm thế nào nó hoạt động, xin vui lòng? Làm thế nào / nơi bạn có thể gọi _privatephương thức?
Bergi

0

Bạn phải đặt một bao đóng xung quanh hàm xây dựng thực tế của bạn, nơi bạn có thể xác định các phương thức riêng tư của mình. Để thay đổi dữ liệu của các thể hiện thông qua các phương thức riêng tư này, bạn phải cung cấp cho chúng "cái này" với chúng, dưới dạng đối số hàm hoặc bằng cách gọi hàm này bằng .apply (this):

var Restaurant = (function(){
    var private_buy_food = function(that){
        that.data.soldFood = true;
    }
    var private_take_a_shit = function(){
        this.data.isdirty = true;   
    }
    // New Closure
    function restaurant()
    {
        this.data = {
            isdirty : false,
            soldFood: false,
        };
    }

    restaurant.prototype.buy_food = function()
    {
       private_buy_food(this);
    }
    restaurant.prototype.use_restroom = function()
    {
       private_take_a_shit.call(this);
    }
    return restaurant;
})()

// TEST:

var McDonalds = new Restaurant();
McDonalds.buy_food();
McDonalds.use_restroom();
console.log(McDonalds);
console.log(McDonalds.__proto__);

Trên thực tế, nó không hoạt động. Mỗi người new Restaurantsẽ có người restaurantxây dựng riêng và "nguyên mẫu" hoàn toàn bị lạm dụng.
Bergi

@Bergi. Thật ra, bạn rất đúng. Nó sẽ hoạt động nhưng cũng sẽ là một con lợn ressource (nó có được gọi như vậy không?). Tôi chỉnh sửa câu trả lời của tôi về điều đó.
Flex Elektro Deimling

Cảm ơn các cập nhật. Không biết nên gọi phiên bản trước là gì (nhưng "bug" :-)
Bergi

0

Tôi biết đó là một chút quá muộn nhưng làm thế nào về điều này?

var obj = function(){
    var pr = "private";
    var prt = Object.getPrototypeOf(this);
    if(!prt.hasOwnProperty("showPrivate")){
        prt.showPrivate = function(){
            console.log(pr);
        }
    }    
}

var i = new obj();
i.showPrivate();
console.log(i.hasOwnProperty("pr"));

0

Có rất nhiều câu trả lời cho câu hỏi này, nhưng không có gì phù hợp với nhu cầu của tôi. Vì vậy, tôi đã đưa ra giải pháp của riêng mình, tôi hy vọng nó hữu ích cho ai đó:

function calledPrivate(){
    var stack = new Error().stack.toString().split("\n");
    function getClass(line){
        var i = line.indexOf(" ");
        var i2 = line.indexOf(".");
        return line.substring(i,i2);
    }
    return getClass(stack[2])==getClass(stack[3]);
}

class Obj{
    privateMethode(){
        if(calledPrivate()){
            console.log("your code goes here");
        }
    }
    publicMethode(){
        this.privateMethode();
    }
}

var obj = new Obj();
obj.publicMethode(); //logs "your code goes here"
obj.privateMethode(); //does nothing

Như bạn có thể thấy hệ thống này hoạt động khi sử dụng loại lớp này trong javascript. Theo như tôi tìm ra thì không có phương pháp nào được bình luận ở trên.


1
Tò mò: Có phải bạn thực sự cần phải thực hiện chức năng nhưng làm cho nó không hoạt động trong thời gian chạy - thay vì ẩn nó khỏi các cuộc gọi bên ngoài như tất cả / hầu hết các câu trả lời khác làm gì? Nếu vậy, tại sao? Bạn nghĩ gì về lợi ích của phương pháp này? Đối với tôi, đây dường như chỉ là một chi phí hoạt động không cần thiết, một API không rõ ràng và, có lẽ, chắc chắn sẽ gây ra địa ngục gỡ lỗi, nhưng tôi luôn mở ra những quan điểm mới ...
JHH

2
@JHH thành thật mà nói, tôi khá nhiều mặt tái mét khi nhìn lại điều này. Chi phí nói chung hoàn toàn không xứng đáng, mặc dù đối với tôi nó không thành vấn đề khủng khiếp vì tôi đã không thực hiện nhiều cuộc gọi đến các lớp học này. Lý do tôi đã làm theo cách này chỉ là nó tương đối sạch trong cách bạn viết và gọi các hàm. Tôi đã không hiểu các biểu tượng và theo thời gian, nhưng bây giờ tôi làm vậy, tôi nghĩ rằng đó thường là cách để đi khi sử dụng các lớp học. Tôi đang xem xét loại bỏ câu trả lời này cùng nhau. Tôi đã đăng một số câu trả lời ngu ngốc, nhưng hey, bạn sống và học hỏi.
thegunmaster

Cảm ơn vì bạn đã phản hồi! Tôi không chắc là mình đã hiểu nhầm điều gì. Nhưng vâng, tất cả chúng ta đều sống và học hỏi!
JHH

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.