封装可以被定义为对对象的内部数据表现形式和实现细节进行隐藏。通过封装可以强制实施信息隐藏。
在JavaScript中,并没有显示的声明私有成员的关键字等。所以要想实现封装/信息隐藏就需要从另外的思路出发。我们可以使用闭包的概念来创建只允许从对象内部访问的方法和属性,来达到封装的要求。
基本方式
一般来说,我们学用的有三种方法来达到封装的目的。
- 使用this.XXX来声明一个变量,然后再声明getXXX、setXXX等取值、赋值的方法。
- 使用this._XXX来声明一个变量,然后再声明getXXX、setXXX等取值、赋值的方法。
- 利用“函数作用域”这一个概念来做。
1. 门户大开型
<span style="font-size:14px;">var Book = function(isbn,title,author){
this.setIsbn(isbn);
this.setTitle(title);
this.setAuthor(author);
};
Book.prototype = {
setIsbn: function(isbn){
this.isbn = isbn;
},
getIsbn: function(){
return this.isbn;
},
setTitle: function(title){
this.title = title;
},
getTitle: function(){
return this.title;
},
setAuthor: function(author){
this.author = author;
},
getAuthor: function(){
return this.author;
}
};</span>
使用这种方法实现的封装,虽然实现了取值器与赋值器以保护私有属性。但是在实际使用中,私有属性依然可以从外部访问,所以从根本上讲,没有实现封装。
2. 用命名规范进行区别
<span style="font-size:14px;">var Book = function(isbn,title,author){
this.setIsbn(isbn);
this.setTitle(title);
this.setAuthor(author);
};
Book.prototype = {
setIsbn: function(isbn){
this._isbn = isbn;
},
getIsbn: function(){
return this._isbn;
},
setTitle: function(title){
this._title = title;
},
getTitle: function(){
return this._title;
},
setAuthor: function(author){
this._author = author;
},
getAuthor: function(){
return this._author;
}
};</span>
3. 使用函数作用域
<span style="font-size:14px;">var Book = function(newIsbn,newTitle,newAuthor){
var isbn,title,author;
this.setIsbn=function(newIsbn){
isbn = newIsbn;
};
this.getIsbn=function(){
return isbn;
};
this.setTitle=function(newTitle){
title = newTitle;
};
this.getTitle=function(){
return title;
};
this.setIsbn=function(newAuthor){
author = newAuthor;
};
this.getIsbn=function(){
return author;
};
}</span>
由于在JavaScript的函数中声明的变量是有作用域的,所以使用这种方法可以避免在外部直接访问私有属性。基本达到封装所要求的内容。
这里要注意的是,我们在函数的内部,可以使用this.XXX以及var来声明变量。区别是使用this.XXX声明的变量在外部是可以访问的。使用var声明的变量,由于受到函数作用域的保护,在函数的外部是无法直接访问的。
4. 使用函数作用域的变形
<span style="font-size:14px;">var Book = (function(){
// ...其他静态方法
return function(newIsbn,newTitle,newAuthor){
var isbn,title,author;
this.setIsbn=function(newIsbn){
isbn = newIsbn;
};
this.getIsbn=function(){
return isbn;
};
this.setTitle=function(newTitle){
title = newTitle;
};
this.getTitle=function(){
return title;
};
this.setIsbn=function(newAuthor){
author = newAuthor;
};
this.getIsbn=function(){
return author;
};
};
})();</span>
这种方法是直接返回一个构造器的执行。且这里的构造器是一个内嵌函数。
这种方法的优点是“在内存中只会存在一份。因为其他静态方法被声明在构造器之外,所以它们不是特权方法。”
判断一个方法是否应该被设计为静态方法的原则是“这个方法是否会访问私有属性”。如果它不需要,那么将其设计为静态方法会更有效率,因为它只会被创建一份。
常量
我们可以使用“只有取值器,没有赋值器”的方式来实现常量。
<span style="font-size:14px;">// 1.
var Book = function(){
var constants = ["key1": "1","key2": "2","key3": "3"];
this.getConstant = function(key){
return constants[key];
};
};
Book.getConstant("key1");
// 2.
var Book = (function(){
var constants = ["key1": "1","key2": "2","key3": "3"];
var con = function(){};
con.getConstant = function(name){
return constants[name];
};
return con;
})();
Book.getConstant("key1");</span>
利弊
利处
- 封装保护了内部数据的完整性;
- 封装使对象的重构更轻松;
- 弱化模块间的耦合,提高对象的可重用性;
- 有助于避免命名空间冲突;
- ……
弊处
- 私用方法很难测试;
- 必须与复杂的作用域链打交道,使错误调度更困难;
- 容易形成过度封装;
- JavaScript并不原生支持封装,所以在JavaScript中实现封装存在复杂性的问题;
- ……
作者:丛峻峰
Github:
https://github.com/congjf