JavaScript 面向对象编程:使用函数模拟类与成员访问控制

作者:异步漫步时间:2026-10-23 23:54:04浏览:569

本文深入解析 JavaScript 中如何使用函数模拟类,详细阐述公有成员、私有成员及特权成员的访问机制。通过闭包实现数据隐藏,利用 prototype 扩展共享方法,并探讨直接修改原型对已存在对象的影响,帮助开发者掌握 JS 面向对象编程的核心概念。

JavaScript 中类的模拟与基本概念

尽管 JavaScript 语言本身并未内置传统的类概念,但开发者可以通过函数结合闭包来模拟类的行为,包括定义类成员及私有成员(关于闭包的详细机制可参见 跨越边界: 闭包)。

跨越边界: 闭包

本文将采用直观易懂的方式介绍 JavaScript 中的“类”,避免陷入过于晦涩的理论原理。

既然使用函数来模拟类,那么创建类的关键字依然是 function。下面我们将以创建一个座标点类为例进行说明。

    function Point() {
      this.X = 0;
      this.Y = 0;
    };
 
    var zeroPoint = new Point();
    alert("当前座标值( X:" + zeroPoint.X + " , Y:" + zeroPoint.Y + " )");

        image

公有成员与实例化机制

非静态类成员通常需要通过对象实例进行访问,因此首先需要 new 一个 Point 类型的对象,再通过该对象访问 X 和 Y 轴的坐标值。从语法层面看,JavaScript 类对象的创建过程与 C# 等语言相似,但底层实现机制存在差异。这里的 new 操作创建了一个 Object 实例,随后执行 Point() 函数时,会将该新对象的 this 指向这个新创建的 Object。Point 函数中通过 this.X 和 this.Y 定义的两个属性属于公共成员,因此 Point 的实例对象可以直接访问它们。

私有成员与特权成员的定义

讨论类成员或对象成员时,必然涉及访问权限问题。在 JavaScript 的类模拟中,同样存在 public(公有)和 private(私有)成员的概念,但具体细节有所不同。JavaScript 中的私有成员是指在类外部无法通过对象直接操作的成员;值得注意的是,即使在类内部,私有成员也不一定对所有成员可见。通常情况下,只有私有成员之间可以互相访问,其他成员因权限不足无法操作这些“隐私”数据。然而,如果拥有特权,私有与公开的界限便不再严格。私有成员变量的声明方式与普通变量一致,使用 var 关键字;私有方法既可以通过 var 声明变量来接收方法对象,也可以像普通方法那样直接构建。

    function Lady() {
      var age = 30;
      var name = "菜花";
 
      var think = function() {
        alert("其实我今年" + age + "岁。");
      };
      
      function fancy(){
        alert("幻想变成20岁。");
      };
 
      this.Introduce = function() {
        alert("我叫" + name + " , 今年20岁。");
      };
    };
 
    var younglady = new Lady();
    alert(younglady.age);//结果undefined
    younglady.think(); //不支持
    younglady.fancy(); //不支持

上述代码展示了一个 Lady 类,其中 age、think 和 fancy 均为私有成员。think 和 fancy 方法可以访问 age 和 name,且这两个方法之间也可以互相调用。但由于它们是私有的,创建的 youngLady 对象无法直接调用 age、think 和 fancy,同样也无法访问 name。如果私有成员仅能互相调用,其存在的意义将大打折扣。为此,JavaScript 提供了特权成员机制,作为连接外部与私有成员的桥梁。特权成员属于公共成员的一种,公共成员包括普通公共成员、特权成员和对象公共成员。

特权成员是在类内部通过 this.XX 方式定义的成员。它们可以通过对象实例进行调用,同时能够访问私有成员,从而建立起访问私有成员的通道。

    function Lady() {
      var age = 30;
      this.Name = "菜花";
 
      var think = function() {
        alert("其实我今年" + age + "岁。");
      };
 
      function fancy() {
        alert("幻想变成20岁。");
      };
 
      this.Introduce = function() {
        alert("我叫" + this.Name + " , 今年" + age + "岁。");
      };
    };
 
    var younglady = new Lady();
    younglady.Introduce(); //Introduce

        image

通过原型链添加普通公共成员

普通公共成员的创建不在类定义内部进行,而是通过类的 prototype 属性来添加。所有普通公共成员都直接添加到类的 prototype 中,而 prototype 本质上是一个类似 JSON 对象的成员集合。在创建对象时,可以理解为将类 prototype 中的成员整体拷贝到了新的 Object 实例中。

    var younglady = new Lady();
    younglady.Introduce(); //Introduce
 
    Lady.prototype.Hobby = "上网";
    Lady.prototype.GetName = function() {
      return this.Name;
    };
    
    var lady2 = new Lady();
    alert(lady2.GetName());
    alert(lady2.Hobby);

上述代码通过 prototype 为 Lady 类添加了普通公共成员 GetName 方法和 Hobby 属性。由于这些是公共成员,它们可以与类定义中原本设定的特权成员互相访问。因为公共成员之间可以互相访问,我们对代码进行如下修改。

    var younglady = new Lady();
 
    Lady.prototype.Hobby = "上网";
    Lady.prototype.GetName = function() {
      return this.Name;
    };
 
    alert(younglady.GetName());
    alert(younglady.Hobby);

先创建 Lady 对象实例,随后再修改类成员,此时先前创建好的对象也会具备新的成员。这正是利用 prototype 作为类原型所带来的优势。简单理解,可以将 prototype 视为类对象的模板,对模板的修改会影响该类的所有对象实例。

在添加普通成员时,也可以进行批量添加,即直接用一个新的 JSON 对象赋值给 prototype。但需注意,这种操作实际上是替换了原有的 prototype 对象。在替换之前创建的对象引用的是旧的 prototype 对象,因此这些旧对象不会拥有新添加的 Hobby 和 GetName 成员。

    Lady.prototype = {
      Hobby: "上网",
      GetName: function() {
        return this.Name;
      }
    };
    var younglady = new Lady();
    alert(younglady.GetName());
    alert(younglady.Hobby);

对象实例成员的动态添加

除了在构建类时添加公共成员,还可以直接对对象实例进行成员操作。这在本系列第二篇文章中已有描述,此处做补充说明:直接添加到对象实例上的成员也是一种公共成员,这些成员可以与类中原有的公共成员互相访问。

    younglady.SetName = function(name) {
      this.Name = name;
    };
    younglady.SetName("菜明");
    alert(younglady.GetName());

以上简要介绍了类成员的相关概念,下次将讨论类继承的相关内容。(如有不当之处,欢迎指正)

作者最新文章