JavaScript面向对象编程:从对象创建到原型共享
本文详解JavaScript中面向对象编程的核心实践。从基础的对象字面量创建与属性访问方式入手,深入讲解构造函数的定义与使用。重点剖析传统构造函数导致的内存浪费问题,通过引入初始化标志位尝试优化,最终揭示利用prototype属性实现方法共享的标准方案,帮助开发者理解并掌握JS中的对象复用机制。
JavaScript面向对象编程概述
面向对象是一种独立于具体编程语言的思维模式。例如在C#中,若在静态类的静态方法内按过程式风格调用一系列静态函数,这很难被视为面向对象编程;反之,像jQuery和ExtJS这样优秀的JavaScript库,其设计处处体现了面向对象的思想。本文不讨论JavaScript是否属于面向对象语言,而是直接聚焦于如何在JavaScript中实际应用这种编程思想。
JavaScript对象的基础创建与访问
面向对象编程的首要前提是存在“对象”。在JavaScript中,创建一个对象的操作非常直观:
代码如下:
var o={}; 上述代码创建了一个空对象o。我们可以随时向该对象添加新的属性和方法:
代码如下:
o.name="object name";
o.showName=function(){
alert(o.name);
} 尽管动态添加属性可行,但大多数开发者更习惯在定义对象时,将属性和方法直接封装在一对花括号{}内部:
代码如下:
var o = {
name: "object name",
showName: function() {
alert(o.name);
}
} 访问对象的属性和方法主要有两种途径。第一种是常规的点语法:
代码如下:
alert(o.name);
o.showName(); 这种写法十分常见,C#等语言调用对象属性与方法时同样采用此方式。另一种则是JavaScript特有的访问方式,即使用字符串形式的键名作为索引进行访问:
代码如下:
alert(o["name"]);
o["showName"](); 这类似于孔乙己所言“茴字的茴有几种写法”,虽然可行,但在实际开发中,极少有人使用索引方式调用对象的属性或方法。
构造函数与对象实例化
除了自定义的属性和方法,我们的对象还有一个constructor属性以及toString()等方法。这些属性和方法是从Object内置对象来的,所有的对象都会有这些属性和方法。其中constructor属性指向构造出该对象的构造函数。我们没有使用构造函数来创建对象,事实上,js的解释器会使用Object构造函数。如果我们自己定义了构造函数,那么便可以通过该构造函数来创建对象,这样可以使得创建的对象具有相同的属性和方法,这便开始有点面向对象的味道了。好,我们从一个简单的例子开始看看如何创建一个构造函数吧:
代码如下:
function Person(name, sex, age) {
this.name = name;
this.sex = sex;
this.age = age;
this.showInfo = function() {
alert("姓名:" + this.name + " 性别:" + this.sex + " 年龄:" + this.age);
}
} 我们定义了一个名字叫Person的构造函数,该构造函数有三个属性和一个方法,通过构造函数来产生一个对象并调用方法也非常简单:
代码如下:
var zhangsan = new Person("张三", "男", 18);
zhangsan.showInfo(); 运行后我们可以看到弹出一个对话框,显示出这个叫张三的人的信息:

我们还可以看看对象的constructor属性来看看zhangsan的构造函数是不是我们定义好的Person:
代码如下:
alert(zhangsan.constructor); 结果如图:

可以看到,正是我们的Person构造函数。
内存优化与原型链的应用
不过,这里还是有点问题,每一次我们构造一个对象,都会在内存中为属性和方法分配内存空间,而事实上,所有的对象完全可以用同一个方法,并不需要有多个方法的副本,这样有些浪费内存空间。既然意识到了这个问题,让我们来想想如何解决吧。一个很自然的想法是,既然我们只想为方法分配一次内存空间,那么我们可以设置一个值用来标识方法的内存空间是否已经分配,按照这个思路,我们将构造函数做如下修改:
代码如下:
function Person(name, sex, age) {
this.name = name;
this.sex = sex;
this.age = age;
if (typeof Person._initialized == "undefined") {
this.showInfo = function() {
alert("姓名:" + this.name + " 性别:" + this.sex + " 年龄:" + this.age);
}
Person._initialized = true;
}
} 这里,我们用一个成员_initialized来指示是否已经对方法进行了内存空间的分配。当第一个对象构造的时候_initialized未被定义,所以我们的判断语句为真,这时会对方法进行了定义并分配内存空间,然后把_initialized的值设置为true,用以表明方法的内存空间已经分配了。第二个对象构造的时候则不会再进入判断,因而也不会再一次分配内存空间。似乎没什么问题,运行一下看看,张三的信息依然正常显示。虽然不辛苦,不过解决了一个小问题,还是庆祝下吧,来盘回锅肉,我要大快朵颐。还没开吃,一个叫李四的MM也想让电脑弹出她的个人信息。OK,很简单,再构造一个对象,然后调用showInfo方法就可以了:
代码如下:
var lisi = new Person("李四", "女", 28);
lisi.showInfo(); 为了照顾MM,还把这段放在了张三的前边。MM的信息正确显示出来了,可是张三的资料不见了。这下张三不乐意了,排名放在MM后边也罢了,但好歹得有名字啊。这可苦了我这编程人员,回锅肉看来没办法吃了,先改bug吧。打开firebug,看到MM的信息显示之后出现错误,提示为:zhangsan.showInfo is not a function。设置断点看看,构造zhangsi对象以后发现并没有showInfo这个方法。原来showInfo方法虽然只有一个,但是存在于第一个对象之中,第二个对象并不能访问。那么,究竟如何才能让同一个构造函数产生的对象共用同一个函数呢?javascript中的prototype给我们提供了这个功能。根据javascript的规范中描述,每一个构造函数都有一个prototype属性用于实现继承和属性的共享。我们的showInfo方法也可以看作是一个属性,该属性指向一个函数的引用。现在我们使用prototype来使得我们的方法可以共享,代码的改动很简单,把this.showInfo改成Person.prototype.showInfo就可以了,改动之后的代码如下:
代码如下:
function Person(name, sex, age) {
this.name = name;
this.sex = sex;
this.age = age;
if (typeof Person._initialized == "undefined") {
Person.prototype.showInfo = function() {
alert("姓名:" + this.name + " 性别:" + this.sex + " 年龄:" + this.age);
}
Person._initialized = true;
}
} 使用该构造函数生成两个对象:
代码如下:
var lisi = new Person("李四", "女", 28);
lisi.showInfo();
var zhangsan = new Person("张三", "男", 18);
zhangsan.showInfo(); 运行之后先显示李四的信息,然后是张三的信息。现在两个人都满意了,可惜我的回锅肉已经凉了
JavaScript闭包与类(Class)的区别及适用场景对比
2026-11-05 15:48:57
JavaScript入门要学ES6吗?let与箭头函数等新特性解析
2026-11-05 14:49:16
JavaScript 8种数据类型详解:基本类型与引用类型的区别
2026-11-05 14:25:15
怎么在JavaScript中使用async/await优化Ajax异步流程
2026-11-05 13:56:15
JavaScript replace回调函数实现动态正则替换逻辑
2026-11-05 13:52:13
