JavaScript面向对象创建对象的五种模式详解

作者:函数茶馆时间:2026-10-28 13:25:43浏览:276

本文系统讲解JavaScript中创建自定义对象的五种核心模式。从基础的工厂模式出发,深入分析构造函数模式的实例识别优势与内存浪费问题,探讨原型模式的共享机制及其引用类型缺陷,最终引出结合两者优点的混合模式与动态原型模式,帮助开发者根据场景选择最优方案。

面向对象是一种核心编程思想。

在面向对象视角下,程序中的关键模块均被视为对象,每个对象拥有自身的属性与方法。通过将特定的属性与方法进行封装,不仅能在后续使用中保持极高的便利性,还能有效避免重复且繁琐的工作流程。下文将详细阐述在JavaScript中实现面向对象的具体方式。

工厂模式

工厂模式

作为软件工程中广为人知的设计模式,工厂模式在JavaScript中的应用尤为特殊。由于ECMAScript标准本身不支持直接创建类,因此该模式利用函数来封装特定接口以创建对象。其实现逻辑十分直观:在函数内部实例化一个对象,为其赋予相应的属性与方法,最后将该对象返回即可。


function createBlog(name, url) {
  var o = new Object();
  o.name = name;
  o.url = url;
  o.sayUrl= function() {
    alert(this.url);
  }
  return o;
}

var blog1 = createBlog('wuyuchang', 'https://www.jb51.net/');

工厂模式的实现方式直观且有效,成功解决了批量创建相似对象的需求。然而,该模式存在一个显著缺陷:无法识别对象的具体类型,因为所有通过工厂函数创建的对象类型均为Object,无法像Date或Array等原生对象那样被准确区分,这直接催生了构造函数模式的诞生。

构造函数模式

构造函数模式

在ECMAScript中,构造函数能够创建具有特定类型的对象,其行为类似于Array、Date等JavaScript原生对象。具体的实现方式如下所示:


function Blog(name, url) {
  this.name = name;
  this.url = url;
  this.alertUrl = function() {
    alert(this.url);
  }
}

var blog = new Blog('wuyuchang', 'https://www.jb51.net/');
console.log(blog instanceof Blog);  // true, 判断blog是否是Blog的实例,即解决了工厂模式中不能

对比工厂模式,此处的代码除了函数名称不同外,还存在多处关键差异:

1. 函数名首字母采用大写(尽管标准未强制要求,但这是构造函数的命名惯例)。 2. 并未显式创建新对象。 3. 直接将属性和方法赋值给this对象。 4. 省略了return语句。 5. 必须使用new关键字来创建对象。 6. 能够准确识别对象类型,这正是构造函数模式优于工厂模式的核心所在。

尽管构造函数模式功能强大,但也存在明显短板。其最大问题在于每次创建实例时,都会重新创建一遍方法。理论上,对象的属性可能各不相同,但方法逻辑是相同的,因此为每个实例单独创建完全相同的方法并无必要。我们可以尝试将函数定义在对象外部(或许读者已察觉此处的局限性,暂且不表)。


function Blog(name, url) {
  this.name = name;
  this.url = url;
  this.alertUrl = alertUrl;
}

function alertUrl() {
  alert(this.url);
}

var blog = new Blog('scjb51', 'http://sc.jb51.net/'),
  blog2 = new Blog('jb51', 'https://www.jb51.net/');
blog.alertUrl();  // http://sc.jb51.net/
blog2.alertUrl();  // https://www.jb51.net/

若将alertUrl定义为全局函数,虽然blog与blog2实例可以访问同一个函数,但这引发了新的问题:在全局作用域中定义了一个仅服务于Blog对象的函数,使得全局作用域变得臃肿且名不副实。更严重的是,若在全局作用域中定义大量仅供特定对象使用的方法,不仅浪费内存空间,更彻底破坏了面向对象封装性的初衷。因此,我们需要引入原型模式来解决这一问题。

原型模式

原型模式

在JavaScript中,每个函数都拥有一个prototype(原型)属性,该属性是一个指针,指向一个对象。这个原型对象的用途在于包含可以由特定类型的所有实例共享的属性和方法。使用原型模式的核心优势在于,它允许所有对象实例共享其所包含的属性及方法,从而节省内存。


function Blog() {
}

Blog.prototype.name = 'wuyuchang';
Blog.prototype.url = 'http://tools.jb51.net/';
Blog.prototype.friend = ['fr1', 'fr2', 'fr3', 'fr4'];
Blog.prototype.alertInfo = function() {
  alert(this.name + this.url + this.friend );
}

// 以下为测试代码
var blog = new Blog(),
  blog2 = new Blog();
blog.alertInfo();  // wuyuchanghttp://tools.jb51.net/fr1,fr2,fr3,fr4
blog2.alertInfo();  // wuyuchanghttp://tools.jb51.net/fr1,fr2,fr3,fr4

blog.name = 'wyc1';
blog.url = 'http://***.com';
blog.friend.pop();
blog2.name = 'wyc2';
blog2.url = 'http://+++.com';
blog.alertInfo();  // wyc1http://***.comfr1,fr2,fr3
blog2.alertInfo();  // wyc2http://+++.comfr1,fr2,fr3

原型模式同样存在局限性。首先,它省略了构造函数传递初始化参数的环节,导致所有实例在默认情况下获取相同的属性值,这在实际开发中往往不够灵活。但这并非原型模式的最大弊端,其最大问题源于“共享”这一本质:由于属性是共享的,如果一个实例修改了引用类型的属性,其他实例也会随之改变。因此,我们通常不单独使用原型模式,而是将其与构造函数模式结合使用。

混合模式

混合模式(原型模式 + 构造函数模式)


function Blog(name, url, friend) {
  this.name = name;
  this.url = url;
  this.friend = friend;
}

Blog.prototype.alertInfo = function() {
  alert(this.name + this.url + this.friend);
}

var blog = new Blog('wuyuchang', 'http://tools.jb51.net/', ['fr1', 'fr2', 'fr3']),
  blog2 = new Blog('wyc', 'http://**.com', ['a', 'b']);

blog.friend.pop();
blog.alertInfo();  // wuyuchanghttp://tools.jb51.net/fr1,fr2
blog2.alertInfo();  // wychttp://**.coma,b

在混合模式中,构造函数模式负责定义实例属性,而原型模式则用于定义方法和共享属性。这样,每个实例都拥有自己的一份实例属性副本,同时又共享着方法,从而在功能实现与内存节省之间取得了最佳平衡。此外,该模式还支持传递初始参数。由于其优点众多,混合模式已成为ECMAScript中使用最广泛、认可度最高的创建自定义对象的方法。

动态原型模式

动态原型模式

动态原型模式将所有信息封装在构造函数内部。它通过在构造函数中初始化原型(仅在第一个对象实例化时执行)来实现。判断方法是否有效,从而决定是否需要初始化原型,是实现这一模式的关键机制。


function Blog(name, url) {
  this.name = name;
  this.url = url;

  if (typeof this.alertInfo != 'function') {
    // 这段代码只执行了一次
    alert('exe time');
    Blog.prototype.alertInfo = function() {
      alert(thia.name + this.url);
    }
  }
}

var blog = new Blog('wuyuchang', 'http://tools.jb51.net'),
  blog2 = new Blog('wyc', 'http:***.com');

观察上述示例,'exe time'窗口仅会弹出一次。这意味着当blog实例初始化时,原型得到初始化;而后续创建blog2时,无需再次初始化原型。采用这种模式创建对象,在功能性和效率上均可视为近乎完美。

本文内容参考《JavaScript高级程序设计》第3版,语言表述已进行简化,示例代码亦重新编写。若有任何疑问或建议,欢迎留言回复,作者将根据反馈持续更新博客内容。

作者最新文章