Javascript 简明教程

JavaScript - Encapsulation

What is Encapsulation?

JavaScript 中的封装是一种通过捆绑相关属性和方法到单个命名空间下,来保持这些属性和方法的私有性。它可以是函数、类或对象。在 JavaScript 中,可以使用闭包、类及访问器和设置器实现封装。

封装是面向对象编程语言中的一个基本概念,连同继承和多态性。JavaScript 是一种面向对象的编程语言。

它用于隐藏数据,避免外界访问,仅向需要的数据提供访问权限,从而提高数据的完整性和安全性。

What is the need for encapsulation?

让我们通过以下示例讨论在 JavaScript 中封装的必要性。

例如,你在代码中定义了以下对象。

const car = {
   Brand: "Honda city",
   model: "sx",
   year: 2016,
}

如下所示,任何人都可以访问 car 对象的属性。

car.Brand

此外,如下所示,任何人都可以更改 car 对象的任何属性的值。

car.Brand = true;

此处,Brand 属性的值从字符串更改为布尔。因此,需要保护对象的原始数据,并限制外部世界对数据的访问。

在这种情况下,封装的概念进入了画面。

Different Ways to Achieve Encapsulation in JavaScript

有三种不同的方法来实现封装。

  1. Using the function closures

  2. Using the ES6 classes

  3. 使用 getter 和 setter

此处,我们将逐一学习每种实现封装的方法。

Achieving Encapsulation Using the Function Closures

JavaScript 函数闭包是一个允许内部函数访问外部函数中定义的变量的概念,即使在执行了外部函数之后也是如此。在外部函数中定义的变量不能在其功能范围之外访问,但可以使用内部范围访问。

Example

在以下代码中,shoppingCart() 函数是一个外部函数,包含变量和函数。外部函数有它自己的私有范围。

carItems[] 数组用于存储购物车的物品。

add() 函数可以访问 carItems[] 数组并添加项目。

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

    // Defining items
    const item1 = { name: 'Car', price: 1000000 };
    const item2 = { name: 'Bike', price: 100000 };
    // Create a new Shopping cart
    const cart = shoppingCart();
    // Adding items to the cart
    cart.add(item1);
    cart.add(item2);
    // Remove bike from the cart
    cart.remove('Bike');
  </script>
</body>
</html>
Car added to the cart.
Bike added to the cart.
Bike removed from the cart.

通过这种方式,没有人可以直接访问和修改 cartItem[] 数组。

Achieving Encapsulation Using ES6 Classes and Private Variables

在 JavaScript 中,您可以使用类和私有变量来实现封装。

Private Variables (Fields) in JavaScript

要定义私有类变量,您可以在变量名前面写“#”符号。例如,“name”是以下代码中的私有变量。

class car {
    #name= "TATA";
}

如果您尝试通过类的实例访问 name,它将给您一个错误,指出类外部无法访问私有字段。

要实现封装,可以在类中定义私有变量,并使用不同的方法授予它们对外部世界的访问。

Example

在下方的示例中,我们定义了汽车类。

汽车类包含 "品牌"、"名称" 和 "英里数" 私有变量。

定义 getMilage() 方法可返回汽车的英里数, 而 setMilage() 方法用来设置汽车英里数。

我们创建了汽车类的对象,并用方法访问和修改私有字段。如果你试图访问类的私有字段,代码将抛出一个错误。

你也可以定义类中的更多方法来访问和修改其他私有字段。

<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"; // Private field
      #name = "Nexon"; // Private field
      #milage = 16;    // Private field

      getMilage() {
        return this.#milage; // Accessing private field
      }

    setMilage(milage) {
      this.#milage = milage; // Modifying private field
    }
    }

    let carobj = new Car();
    document.getElementById("output1").innerHTML += carobj.getMilage();
    carobj.setMilage(20);
    document.getElementById("output2").innerHTML += carobj.getMilage();
    // carobj.#milage);  will throw an error.
  </script>
</body>
</html>
The car mileage is: 16
After updating the car mileage is: 20

Achieving Encapsulation Using the Getters and Setters

JavaScript 的 getter 和 setter 可以分别使用 get 和 set 关键字定义。getter 用于获取类属性,而 setter 用于更新类属性。

它们和类方法非常相似,但使用 get/set 关键字后跟方法名定义。

Example

在下面的示例中,我们定义了 User 类,其中包含三个私有字段,分别为用户名、密码和 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

从上述所有内容,你可以理解封装使变量变为私有的,并限制其对外部世界的访问。

Benefits of Encapsulation in JavaScript

在此,我们列出 JavaScript 中封装的一些好处 −

  1. Data protection − 封装允许你通过使类数据变为私有来控制其访问。你只能公开所需数据和方法。因此,没有人可以错误修改数据。此外,你可以在更新数据时验证数据。如果新数据无效,你可以抛出一个错误。

  2. Code reusability − 类是对象的模板,你可以重复使用它来创建具有不同数据的对象。

  3. Code Maintenance − 封装使得维护代码变得容易,因为每个对象都是独立的,如果你对一个对象进行更改,它不会影响其他代码。