闭包的力量:在面向对象中实现真正的私有变量

87 阅读4分钟

闭包的力量:在面向对象中实现真正的私有变量

在面向对象编程中,数据封装与私有性是构建可靠代码的核心。JavaScript 里,闭包独特的作用域机制,成为实现私有变量的关键。

一、什么是闭包

闭包是指一个函数与其词法环境的组合。简单来说,闭包可以访问并记住定义它的作用域中的变量,即使该函数在其外部被调用。例如:

function outer() {
    let count = 0;
    return function inner() {
        count++;
        console.log(count);
    };
}

const counter = outer();
counter(); // 输出 1
counter(); // 输出 2

在这个例子中outer函数执行,创建了一个包含count的作用域,返回一个函数inner。即使outer执行完毕其内部的作用域也不会被销毁,因为返回的函数中仍然引用了这个变。而这个inner 函数就是一个闭包,它“记住”了 outer 函数中的 count 变量,并可以在后续调用中修改和访问它。

二、面向对象中的私有变量问题

  • 在传统的面向对象语言如 Java 或 C++ 中,我们可以通过 private 关键字来限制类成员的访问权限。例如:

    public class Counter {
        private int count = 0;
    	public String name = 'xiaoming'
        public void increment() {
            count++;
        }
    
        public int getCount() {
            return count;
        }
    }
    

    在这个 Java 类中,count 是私有的,只能通过 increment()getCount() 方法访问。使用public修饰的变量可以被实例化的Counter对象直接调用,例如如:

    Counte cur = new Counter();
    System.out.printlen(cur.nam); // 结果为xiaoming
    System.out.printlen(cur.count); // 报错
    // 通过get方法访问
    System.out.printlen(cur.getCount); // 结果为0
    
  • 然而,在 JavaScript 这样的基于原型的语言中,并没有原生的 private 关键字(ES6+ 的 # 私有字段是新特性,但早期版本不支持)。所以早期JavaScript 构造函数创建对象实例中的属性都是公开的,若需要实现私有变量,可以通过闭包来实现

    function Person(name,age){
      this.name = name;
      this.age = age;
    }
    
    const p1 = new Person('张三',18);
    console.log(p1.name); // 张三
    console.log(p1.age); // 18
    

三、使用闭包实现私有变量

闭包的一个典型用途就是用来模拟类的私有变量。我们可以借助闭包的作用域机制来封装数据,从而防止外部直接访问。

  • 使用工厂函数与闭包创建对象

    在下面代码中调用createCounter()时,创建了一个新的作用域。在这个作用域中声明了count变量并初始化为0,然后返回了一个对象赋值给counter,在这个对象中有两个方法,一个是修改count,一个是返回count。在外界无法通过counter来访问count,因为count不是counter对象的属性。只能通过counter里面的方法来访问,因为这些方法里面引用了count。

    function createCounter() {
        let count = 0; // 私有变量
    
        return {
            increment: function() {
                count++;
            },
            getCount: function() {
                return count;
            }
        };
    }
    
    const counter = createCounter();
    counter.increment();
    console.log(counter.getCount()); // 输出 1
    
    // 尝试直接访问 count 会失败:
    console.log(counter.count); // undefined
    

    在这个例子中,count 变量仅能通过返回的对象方法访问,无法从外部直接读取或修改,实现了类似私有变量的效果。

  • 使用构造函数与闭包实现私有变量

    除了工厂函数,也可以结合构造函数和闭包来创建对象:

    function Counter() {
        let count = 0;
    
        this.increment = function() {
            count++;
        };
    
        this.getCount = function() {
            return count;
        };
    }
    
    const counter = new Counter();
    counter.increment();
    console.log(counter.getCount()); // 输出 1
    console.log(counter.count); // undefined
    

    虽然每个实例都会有自己的闭包副本,这可能会带来一定的内存开销,但这种方法确保了每个对象的私有状态独立且安全。

  • 闭包实现私有变量的优势

    1. 真正的私有性 相比于使用下划线 _variable 约定或 Symbol 属性名等“伪私有”,闭包提供的私有变量是真正不可见、不可修改的。
    2. 封装性更强 数据完全隐藏在内部函数中,只有暴露的方法才能操作这些数据,符合面向对象设计中的封装原则。
    3. 适用于模块化设计 在模块模式(Module Pattern)中,闭包常用于创建具有私有状态的模块,增强代码的可维护性和安全性。
  • 注意事项与性能考量

    1. 内存占用:每个闭包都会保留对其外部作用域的引用,可能导致内存占用增加,尤其是在大量对象实例化时。
    2. 调试困难:由于私有变量无法直接访问,调试时可能难以查看其值。
    3. 继承受限:闭包私有变量不能被子类继承,因此不适合复杂的继承结构。