js中对象的属性按照私有程度来说三种,第一种完全暴露型(fully exposed)只能提供公用成员,第二种方法使用下划线来表示方法或者属性的私用性。第三种是真正私用的成员,这些成员只能通过一些特权方法访问,可以通过闭包或者利用es6的语法实现
完全暴露
var Book = function(isbn, title, author){
isbn == undefined ? throw new Error('Book constructor requires an isbn');
this.isbn = isbn;
this.title = title || 'No title specified';
this.author = author || 'No author specified';
}
Book.prototype.display = function(){
...
}
这里的isbn如果在构造函数中没有定义,就会抛出错误,但我们也许需要对这些数据有更多的验证,那么就来到以下这种
var Book = function(isbn, title, author){
this.setIsbn(isbn);
this.setTitle(title);
this.setAuthor(author);
}
Book.prototype = {
checkIsbn: function(isbn){
return isbn == undefined;
},
setIsbn: function(isbn){
if(!this.checkIsbn) throw new Error('..');
this.isbn = isbn;
},
setTitle: function(title){
this.title = title || 'No title specified';
},
getTitle: function(){
return this.title;
},
...
}
var book = new Book('a', 'book1', 'sysuzhyupeng');
book.getTitle(); // 'book1'
book.title = 'book2';
book.getTitle(); // 'book2' 被改变了
尽管这样的set、get方法的设置很像一些面向对象语言如java,但没有使用private关键字导致了title、author这些属性还是公开的,这样对内部数据就无法进行保护,其他一起协作的人可能无意改动了这些属性
用命名规范区别私用成员
下划线是一种命名规范,它表明一个属性(或方法)仅供对象内部使用,直接访问它或者设置它可能导致意想不到的后果
var Book = function(isbn, title, author){
this.setIsbn(isbn);
this.setTitle(title);
this.setAuthor(author);
}
Book.prototype = {
_checkIsbn: function(isbn){
return isbn == undefined;
},
setIsbn: function(isbn){
if(!this._checkIsbn) throw new Error('..');
this._isbn = isbn;
},
setTitle: function(title){
this._title = title || 'No title specified';
},
getTitle: function(){
return this._title;
},
...
}
var book = new Book('a', 'book1', 'sysuzhyupeng');
book.getTitle(); // 'book1'
//然而我还是可以偷偷改变title
book._title = 'book2';
book.getTitle(); // 'book2'
缺点显而易见。
使用闭包实现私有变量
在js中,只有函数具有作用域,在一些强类型语言如java中的{}就能创建块级作用域,这在js中并不存在。那么私有属性本质而言就是你希望对象外部无法访问的变量,所以实现这种需求而求助于作用域是合情合理的
var Book = function(newIsbn, newTitle, newAuthor){
//private attributes
var isbn, title, author;
//private method
function checkIsbn(isbn){
...
}
//privileged methods
this.getTitle = function(){
//返回变量title,而不是this.title
return title;
}
this.setTitle = function(newTitle){
title = newTitle || 'No title specified'
}
//Constructor code
this.setTitle(newTitle);
}
//Public, non-privileged methods
Book.prototype = {
display: function(){
...
}
}
var book = new Book('a', 'book1', 'sysuzhyupeng');
book.getTitle(); // 'book1'
book.title // undefined,只能通过上面的方法访问
原本我们用对象的属性,而现在使用var而不是this声明这些变量,这意味着它们只存在于Book构造器中。那么我们使用get方法取这些变量的时候,形成了闭包。 需要访问这些私有变量的方法声明在Book中即可,这些方法称为特权方法,任何不需要直接访问私有属性的方法都可以像原来那样在Book.prototype中声明。用这种对象创建方式真正实现了私有变量,但有一些缺点。完全暴露型所有方法都在原型对象中,不管new了多次,生成多少对象实例,对于方法的记录内存中只需要一份,所以这里的闭包方法性能不好。其次,这个方法也不适用于派生子类,因为js的原型继承围绕原型,可以参考我的另一篇文章原型继承和应用,一旦实现继承,子类对象根本拿不到父类中的私有变量,导致了“继承破坏封装”
通过es6实现私有变量
在es5中就已经可以通过definedProperty方法中的访问器,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
var book = {
//不能是title,必须与title不同,这里使用常用命名规范,在前面加了下划线
_title: 'book1'
}
Object.defineProperty(book, 'title', {
get: function(){
//同样不能是title,使用_title
return this._title;
},
set: function(value){
value == 'book2' ?
this._title = value : ''
}
})
book.title // 'book1'
book.title = 'book3';
book.title // 'book1'
book.title = 'book2';
book.title // 'book2'
不方便的是这个是在创建了对象实例之后再define的,在es6中
class Book {
constructor(){
this._title = 'book1';
}
get title(){
return this._title;
}
set title(value){
value == 'book2' ?
this._title = value : ''
}
}
var book = new Book();
book.title = 'book3';
book.title // 'book1'
book.title = 'book2';
book.title // 'book2'
es6中同样是构造另一个属性_title,而在读写title多了一层拦截层,然后偷梁换柱成了_title,从而实现了私有变量。如果兼容了es6,这个方法就是目前看来最好的解决方案