封装是JavaScript中的一种方式,它通过捆绑相关属性和方法在一个单一的命名空间下来保持它们。它可以是一个函数、一个类或者一个对象。在JavaScript中,封装可以通过闭包、类和getter与setter来实现。
封装是面向对象编程语言中的一个基本概念,与继承和多态性一起。JavaScript是一种面向对象的编程语言。
封装用于隐藏数据以防止外界直接访问,并仅提供必要的数据访问权限以增强数据的完整性和安全性。
为什么需要封装?
让我们通过下面的例子讨论一下JavaScript中封装的需求。
例如,在你的代码中定义了下面的对象:
const car = {
Brand: "Honda city",
model: "sx",
year: 2016,
}
任何人都可以访问上面car
对象的属性,如下面所示:
car.Brand
此外,任何人也可以修改car
对象中任意属性的值,如下所示:
car.Brand = true;
这里,Brand
属性的值从字符串变成了布尔值。因此,需要保护对象的原始数据,并对外界提供有限的数据访问权限。
在这种情况下,封装的概念就显得尤为重要。
在JavaScript中实现封装的不同方式
有三种不同的方式可以实现封装:
我们将逐一了解每种实现封装的方法。
使用函数闭包实现封装
JavaScript中的函数闭包是一个概念,允许内部函数即使在外部函数执行后仍能访问外部函数中定义的变量。在外部函数的作用域之外无法访问外部函数中定义的变量,但可以使用内部作用域进行访问。
示例
在下面的代码中,shoppingCart()
函数是一个外部函数,其中包含了变量和函数。外部函数有自己的私有作用域。
cartItems[]
数组用于存储购物车中的项目。
add()
函数可以访问cartItems[]
数组并添加项目。
remove()
函数检查cartItems[]
是否包含需要移除的项目。如果是,则移除该项目。否则,打印消息提示无法移除该项目。
shoppingCart()
函数返回包含add()
和remove()
函数的对象。
创建shoppingCart()
函数的新实例后,可以使用add()
和remove()
函数来操作购物车数据。
<html>
<body>
<p id = "output"> </p>
<script>
let output = document.getElementById("output");
function shoppingCart() {
const cartItems = [];
function add(item) {
cartItems.push(item);
output.innerHTML += `${item.name} added to the cart. <br>`;
}
function remove(itemName) {
const index = cartItems.findIndex(item => item.name === itemName);
if (index !== -1) {
const removedItem = cartItems.splice(index, 1)[0];
output.innerHTML += `${removedItem.name} removed from the cart. <br>`;
} else {
output.innerHTML += `Item ${itemName} not found in the cart. <br>`;
}
}
return {
add,
remove,
};
}
const item1 = { name: 'Car', price: 1000000 };
const item2 = { name: 'Bike', price: 100000 };
const cart = shoppingCart();
cart.add(item1);
cart.add(item2);
cart.remove('Bike');
</script>
</body>
</html>
输出:
Car added to the cart.
Bike added to the cart.
Bike removed from the cart.
通过这种方式,没有人可以直接访问并修改cartItems[]
数组。
使用ES6类和私有变量实现封装
在JavaScript中,你可以使用类和私有变量来实现封装。
私有变量(字段)在JavaScript中
为了定义私有类变量,你可以写一个变量名后面加上‘#’符号。例如,name
在下面的代码中就是一个私有变量。
class car {
#name= "TATA";
}
如果你试图通过类的实例来访问name
,将会得到一个错误,提示私有字段不能在类外访问。
为了实现封装,你可以在类中定义私有变量,并通过不同的方法给予外界访问权限。
示例
在下面的例子中,我们定义了car
类。
car
类包含brand
、name
和milage
私有变量。
定义了getMilage()
方法来返回汽车的里程数,而setMilage()
方法用来设置里程数。
我们创建了car
类的对象,并使用方法来访问和修改私有字段。如果你试图直接访问类的私有字段,代码会抛出错误。
你也可以在类中定义更多的方法来访问和修改其他的私有字段。
<html>
<body>
<div id = "output1">The car mileage is: </div>
<div id = "output2">After updating the car mileage is: </div>
<script>
class Car {
#brand = "TATA"; // 私有字段
#name = "Nexon"; // 私有字段
#milage = 16; // 私有字段
getMilage() {
return this.#milage; // 访问私有字段
}
setMilage(milage) {
this.#milage = milage; // 修改私有字段
}
}
let carobj = new Car();
document.getElementById("output1").innerHTML += carobj.getMilage();
carobj.setMilage(20);
document.getElementById("output2").innerHTML += carobj.getMilage();
// carobj.#milage; 会抛出错误。
</script>
</body>
</html>
输出:
The car mileage is: 16
After updating the car mileage is: 20
使用getter与setter实现封装
JavaScript中的getter和setter可以分别使用get
和set
关键字来定义。Getter用来获取类的属性,而setter用来更新类的属性。
它们非常类似于类的方法,但使用get/set
关键字后跟方法名来定义。
示例
在下面的例子中,我们定义了User
类,包含三个名为username
、password
和isLoggedIn
的私有字段。
定义了名为username
的getter和setter来获取和设置用户名。这里可以看到getter和setter方法的名字是一样的。
之后,我们创建了类的对象,并使用getter和setter作为属性来访问和更新类的username
字段。
你也可以为类的其他字段创建getter和setter。
<html>
<body>
<div id = "output1">The initial username is: </div>
<div id = "output2">The new username is: </div>
<script>
class User {
#username = "Bob";
#password = "12345678";
#isLoggedIn = false;
get username() {
return this.#username;
}
set username(user) {
this.#username = user;
}
}
const user = new User();
document.getElementById("output1").innerHTML += user.username;
user.username = "Alice";
document.getElementById("output2").innerHTML += user.username;
</script>
</body>
</html>
输出:
The initial username is: Bob
The new username is: Alice
从以上所有示例中,你可以理解封装就是使变量私有化并限制其对外界的访问。
JavaScript中封装的好处
这里列出了JavaScript中封装的一些好处:
-
数据保护:封装允许你通过将它们私有化来控制类数据的访问。你可以仅暴露所需的数据和方法。因此,没有人可以意外地修改数据。此外,在更新数据时可以验证数据。如果新数据无效,可以抛出错误。
-
代码复用:类是对象的模板,你可以重复使用它来创建具有不同数据的对象。
-
代码维护:封装使得维护代码变得容易,因为每个对象都是独立的,如果你对一个对象做了修改,不会影响到其他代码。