JavaScript面向对象编程:从原始模式到Prototype模式的封装演进
本文深入解析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程序设计有所帮助。
JS split切割后出现空字符串怎么过滤
2026-11-05 16:00:30
在 React 或 Vue 项目中封装使用 JS 节流防抖
2026-11-05 15:27:29
JavaScript中怎么将带小数的字符串转为整数?
2026-11-05 14:32:38
怎么用JavaScript策略模式重构电商促销计算逻辑?
2026-11-05 14:01:09
JS使用constructor判断数据类型可靠吗
2026-11-05 13:49:41
