JavaScript面向对象编程:从原始模式到Prototype模式的封装演进

作者:函数茶馆时间:2026-10-24 03:58:31浏览:246

本文深入解析JavaScript面向对象编程中的封装技术。从生成实例的原始模式出发,逐步演进至构造函数模式与Prototype模式。详细阐述如何利用[[wp:a7]]变量、[[[wp:a8]]运算符及[[wp:a29]]属性解决内存浪费与实例关联问题,并通过isPrototypeOf、hasOwnProperty及[[wp:a43]]运算符验证原型关系,帮助开发者掌握高效的JS对象封装方法。

本文通过具体实例解析JavaScript面向对象编程中的封装技术,旨在为读者提供清晰的实践参考。

JavaScript本质上是一种基于对象的语言,其生态中几乎所有元素均可视为对象。然而,由于语法层面缺失class(类)的定义机制,它并不完全符合传统意义上严格的面向对象编程(OOP)标准。

若需将“属性”(property)与“方法”(method)整合为单一对象,甚至基于原型对象构建具体实例,应如何操作?

封装

一、 生成实例对象的原始模式

假设我们将“猫”定义为一个对象,其包含“名字”与“颜色”两个基础属性。


  var Cat = {

    name : '',
               color : ''
      } 

 

依据该原型对象的规范,现需创建两个具体的实例对象。


  var cat1 = {}; // 创建一个空对象

    cat1.name = "大毛"; // 按照原型对象的属性赋值

    cat1.color = "黄色";

  var cat2 = {};

    cat2.name = "二毛";

    cat2.color = "黑色";

这是最基础的封装形式,即将两个属性整合至一个对象中。然而,该方法存在显著缺陷:一是当需要创建多个实例时,代码编写繁琐;二是实例与原型之间缺乏明确的关联标识,无法直观体现其来源联系。

二、 原始模式的改进

通过封装函数,可有效解决代码重复的问题。


  function Cat(name,color) {

    return {

      name:name,

      color:color

    }

  }

生成实例对象的过程,实质上等同于调用该函数。


  var cat1 = Cat("大毛","黄色");

  var cat2 = Cat("二毛","黑色");

此方法的核心局限在于,cat1与cat2之间仍缺乏内在逻辑联系,无法体现它们源自同一原型对象的事实。

三、 构造函数模式

为解决从原型对象生成实例的难题,JavaScript引入了构造函数(Constructor)模式。

所谓“构造函数”,本质上是一个普通函数,但其内部使用了this变量。对构造函数应用new运算符即可生成实例,此时this变量将绑定至该实例对象上。

例如,猫的原型对象可定义如下:


  function Cat(name,color){

    this.name=name;

    this.color=color;

  }

基于上述定义,即可生成具体的实例对象。


  var cat1 = new Cat("大毛","黄色");

  var cat2 = new Cat("二毛","黑色");

  console.log(cat1.name); // 大毛

  console.log(cat1.color); // 黄色

此时,cat1与cat2将自动包含一个constructor属性,该属性指向其对应的构造函数。


  console.log(cat1.constructor == Cat); //true

  console.log(cat2.constructor == Cat); //true

JavaScript还提供了instanceof运算符,用于验证原型对象与实例对象之间的继承关系。


  console.log(cat1 instanceof Cat); //true

  console.log(cat2 instanceof Cat); //true

四、构造函数模式的问题

尽管构造函数模式实用,但存在内存浪费的隐患。

观察如下代码,我们为Cat对象新增了一个固定属性type(种类)及一个方法eat(吃)。此时,原型对象Cat的结构变为:


  function Cat(name,color){

    this.name = name;

    this.color = color;

    this.type = "猫科动物";

    this.eat = function(){console.log("吃老鼠");};

  }

继续采用相同方式生成实例:


  var cat1 = new Cat("大毛","黄色");

  var cat2 = new Cat ("二毛","黑色");

  console.log(cat1.type); // 猫科动物

  cat1.eat(); // 吃老鼠

表面看似无误,实则存在重大弊端。对于每个实例对象,type属性与eat()方法均包含完全一致的内容。每次生成新实例,都会重复创建这些内容,导致内存占用增加,既低效又不环保。


  console.log(cat1.eat == cat2.eat); //false

能否使type属性与eat()方法仅在内存中生成一次,并让所有实例指向该内存地址?答案是肯定的。

五、 Prototype模式

JavaScript规定,每个构造函数均拥有一个prototype属性,该属性指向另一个对象。该对象的所有属性与方法,均会被构造函数的实例所继承。

这意味着,我们可以将不变的属性与方法直接定义在prototype对象上。


  function Cat(name,color){

    this.name = name;

    this.color = color;

  }

  Cat.prototype.type = "猫科动物";

  Cat.prototype.eat = function(){console.log("吃老鼠")};

随后生成实例。


  var cat1 = new Cat("大毛","黄色");

  var cat2 = new Cat("二毛","黑色");

  console.log(cat1.type); // 猫科动物

  cat1.eat(); // 吃老鼠

此时,所有实例的type属性与eat()方法,实际上共享同一个内存地址,均指向prototype对象,从而显著提升了运行效率。


  console.log(cat1.eat == cat2.eat); //true

六、 Prototype模式的验证方法

为配合prototype属性的使用,JavaScript定义了一系列辅助方法以协助验证。

6.1 isPrototypeOf()

该方法用于判断某个proptotype对象与特定实例之间的关系。


  console.log(Cat.prototype.isPrototypeOf(cat1)); //true

  console.log(Cat.prototype.isPrototypeOf(cat2)); //true

6.2 hasOwnProperty()

每个实例对象均具备hasOwnProperty()方法,用于判断某一属性是本地属性还是继承自prototype对象的属性。


  console.log(cat1.hasOwnProperty("name")); // true

  console.log(cat1.hasOwnProperty("type")); // false

6.3 in运算符

in运算符可用于判断实例是否包含某一属性,无论该属性是否为本地属性。


  console.log("name" in cat1); // true

  console.log("type" in cat1); // true

此外,in运算符还可用于遍历对象的所有属性。


  for(var prop in cat1) { console.log("cat1["+prop+"]="+cat1[prop]); }

感兴趣的用户可使用在线HTML/CSS/JavaScript代码运行工具:http://tools.jb51.net/code/HtmlJsRun测试上述代码的运行效果。

更多关于JavaScript相关内容感兴趣的读者可查看本站专题:《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》

希望本文所述对大家JavaScript程序设计有所帮助。

作者最新文章