JavaScript 六种常见对象创建模式详解与对比
本文深入解析JavaScript中六种常见的对象创建模式,包括工厂模式、构造函数模式、原型模式、构造函数与原型组合模式、动态原型模式、寄生构造函数模式以及稳妥构造函数模式。通过对比各模式的优缺点、内存占用及[[wp:a1]]操作符的行为,帮助开发者根据具体场景选择最合适的对象实例化方式,避免常见的内存浪费和原型污染问题。
本文通过具体实例,对JavaScript中几种常见的对象构造模式进行对比分析,旨在为开发者提供实用的参考。
工厂模式与构造函数模式
1. 工厂模式
该模式的主要缺陷在于无法解决对象识别问题。由于函数内部通过 new Object() 创建对象,导致所有返回的对象在类型上均被识别为 Object。
function Factory(name,age)
{
var o=new Object();
o.name=name;
o.age=age;
o.what=what;//用函数引用的方式消除重复创建相同函数的弊端,节省资源.函数引用可以修改this的指向,函数调用不可以!
return o;
}
what=funciton()
{
alert(this.name+this.age);
}
var o=Factory("12",12);
o.what();//what中的this指向o对象
此时,对象的 constructor 属性指向 Object,且所有通过工厂模式返回的对象均为 Object 类型,因此使用 instanceof 操作符来判断对象具体类型变得没有意义。
console.log(o.constructor);
//打印function Object() { [native code] }
console.log(o instanceof Object);
//而且这时候所有的对象都是Object类型的
2. 构造函数模式
function Person(name,age)
{
this.name=name;
this.age=age;
this.sayName=function(){ alert(this.name);}//相当于this.sayName=new Function("alert(this.name)")
}
var p1=new Person("xx",12);
var p2=new Person("yy",13);
alert(p1.sayName==p2.sayName)//内存地址不一样!返回false
构造函数模式相比工厂模式的改进之处在于,它能正确返回对象的实际类型,使得 instanceof 操作符能够返回正确的结果。然而,其缺点同样明显:如果像前例那样在构造函数内部定义方法(如 sayName),每个对象实例都会拥有独立的 sayName 方法。由于在 ECMAScript 中函数也是对象,每次定义函数实际上都实例化了一个新的 Function 对象,造成内存浪费。
针对上述问题,可以对方法进行如下优化:
function Person(name,age)
{
this.name=name;
this.age=age;
this.sayName=sayName;//函数引用的方法,共享了同一个sayName,p1,p2的内存地址一样,p1.sayName==p2.sayName返回true
}
function sayName()
{
alert(this.name);
}
这种优化方式的缺点在于,全局函数 sayName 只能通过特定对象调用(如 p1.sayName),导致全局函数名不副实;此外,若对象需要定义大量方法,则必须声明大量全局函数,这使得自定义引用类型失去了封装性。
原型模式及其局限性
3. 原型模式
原型模式存在两个主要局限:(1)无法通过构造函数参数向原型属性动态传值,导致实例缺乏个性化;修改原型属性的值会干扰所有实例。(2)当原型属性为引用类型时,若在一个实例上修改该属性,将影响所有其他实例。
综上所述,原型模式的特点是“牵一发而动全身”,无论是属性值还是引用类型的值,修改一处都会波及所有实例。但与纯构造函数模式相比,原型模式在资源共享方面具有优势。
(注:此处原文逻辑衔接,强调原型模式在共享方法上的优势,尽管存在属性共享的缺陷。)
person1.sayName===person2.sayName//两者的引用是一样的
构造函数与原型组合及动态原型模式
4. 构造函数与原型组合模式
该模式的核心思想是:使用构造函数定义实例属性(个性),使用原型模式定义共享方法(共性)。
function Person(name,age)
{
this.name=name;
this.age=age;
this.friends=['liangklfang','qinliang'];
}
//用原型定义共性
Person.prototype={
constructor:Person,
sayName:function()
{
console.log(this.name);
}
}
var person1=new Person('liangklfang',"12");
var person2=new Person('liangklf',"14");
console.log(person1.sayName===person2.sayName);
//共性是函数,打印true
console.log(person1.friends===person2.friends);
//friends是个性,打印false
此外,还可以对构造函数与原型组合模式进行进一步优化,即所谓的动态原型模式。
function Book(title,page)
{
this.title=title;
this.page=page;
if(typeof Book.isLock=="undefined")
//第一次的时候,Book.isLock是undefined,给原型绑定函数,以后就不需要了,他相比于构造函数原型模式的优点在于把所有的逻辑全部封装到构造函数里面!
{
alert("Enter!");
Book.prototype.what=function()
{
alert(this.title+this.pages);
}
Book.isLock=true;
}
}
//下面的两次调用alert("Enter!")只会调用因此i,因为第一次已经通过Book.isLock设置为true了!相当于静态方法!;
var b1=new Book("me",12);
b1.what();
var b2=new Book("he",13);
b2.what();
在动态原型模式中,可以直接在 this 中进行检查,而无需给函数对象额外添加属性,从而保持代码的整洁性。
function Book(title,page)
{
this.title=title;
this.page=page;
if(typeof this.sayName!='function')
//第二次构造对象的时候会在原型中查找到sayName!
{
Book.prototype.sayName=function()
{
console.log(this.title);
}
}
}
寄生与稳妥构造函数模式
5. 寄生构造函数模式
除了使用 new 操作符外,该模式与工厂设计模式几乎完全相同。它适用于特殊场景,例如需要为对象创建带有额外方法的构造函数。由于无法直接修改 Array 等内置对象的构造函数,此模式提供了一种替代方案。
function SpecialArray()
{
var value=new Array();
value.push.apply(value,arguments);
value.toPipedString=function()
{
return this.join("|");
}
return value;
}
寄生构造函数的特点类似于 Java 中的装饰模式:它对原有对象进行“装饰”,并返回装饰后的对象。例如,此处对 Array 对象进行了装饰,添加了 toPipedString 的 String 方法。其缺点是无法依赖 instanceof 操作符来确定对象类型,因为返回的对象与未包装类中创建的对象在类型标识上无法区分。
6. 稳妥构造函数模式
该模式的特点是:对象没有公共属性,且其方法不引用 this。因此,instanceof 操作符失效。与寄生构造函数模式的不同之处在于,稳妥构造函数不使用 new 来创建对象,同时实例方法也不引用 this。从实现机制上看,这实际上是一种闭包的应用。
function Person(name,age,job)
{
var o=new Object();
o.sayName=funciton(){alert(name)}//这里实例方法没有引用this,除了sayName不会有方法访问传入到构造函数中的原始数据!
return o;
}
var friend=Person("xx",12,"teacher");
friend.name="female";//即使可以为这个对象修改了属性name
friend.sayName();//不会被修改,依然弹出xx。不是female。但是,如果把上面的修改成:o.sayName=function(){alert(this.name)}//那么就会弹出female,也就是friend.name被修改成功了,如果没有this,那么name的值一直引用的是原来的参数值!
感兴趣的朋友还可以使用在线HTML/CSS/JavaScript代码运行工具:http://tools.jb51.net/code/HtmlJsRun测试运行上述js代码。
更多关于JavaScript相关内容感兴趣的读者可查看本站专题:《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》
希望本文所述对大家JavaScript程序设计有所帮助。
JS处理含Emoji或中文的字符串转数组乱码问题
2026-11-05 16:12:28
JS怎么使用展开运算符合并对象
2026-11-05 14:42:18
ES6+环境下判断JS对象为空的简洁写法
2026-11-05 12:12:23
怎么通过replacer函数自定义JS中JSON的格式化输出
2026-11-05 11:19:44
JS正则表达式基础:字面量与RegExp构造函数的区别及常用元字符
2026-11-05 11:16:58
