讲解原型设计模式之前,先了解原型和原型链的概念。
JavaScript 中的原型链是一个非常重要的概念,它涉及到对象之间的继承机制。在 JavaScript 中,每个对象都有一个内部属性,称为 [[Prototype]],它指向该对象的原型。这个原型本身也可能是另一个对象,因此也有自己的原型,以此类推。这种关系链被称为原型链。
一、原型和原型链
1、显式原型与隐式原型
prototype
• 定义: prototype 是构造函数(如 Person)的一个属性。
• 作用: 它定义了由该构造函数创建的所有实例所共享的方法和属性。
• 指向: 它是一个对象,包含构造函数的原型链上的方法和属性。
proto
定义: proto 是任意 JavaScript 对象的一个内部属性。 作用: 它指向创建该对象的构造函数的 prototype。 指向: 通过 proto 可以访问对象的原型链。
两者指向同一个指针地址,因此两者是相等了 Person.prototype === Person.__proto__ // true
2、原型链的作用:
可以实现继承、共享属性 上面例子中体现了属性共享
// 父级原型
function Person(){
this.name = 'parent'
}
Person.prototype.getName = function(){
console.log(this.name)
}
// 子
function Child(){}
Child.prototype = new Person();
var child1 = new Child()
console.log(child1.getName()) // 继承了父亲的getName属性
输出结果:
parent
原型模式
JavaScript 的原型模式依赖于 原型链 的概念来实现对象之间的继承。每个 JavaScript 对象都拥有一个隐藏的 __proto__ 属性,这个属性指向该对象的原型对象(即它的“父对象”)。通过 __proto__ 属性将对象和它的原型相连,形成一条链,称为“原型链”。
当我们访问一个对象的属性或方法时,JavaScript 引擎会按照以下步骤查找:
- 查找自身属性或方法
首先,JavaScript 会检查对象本身是否定义了这个属性或方法。如果找到,就直接返回。 - 沿着原型链向上查找
如果对象本身没有该属性或方法,JavaScript 会通过对象的__proto__指针,访问其原型对象。如果原型对象上定义了这个属性或方法,就直接使用。 - 逐级查找直到原型链顶端
如果原型对象也没有找到,JavaScript 会继续沿着原型链向上查找,直到到达原型链的终点(null)。当查找到顶端时,若仍未找到对应的属性或方法,JavaScript 将返回undefined。
创建对象与原型链继承
在 JavaScript 中,我们可以使用 Object.create() 方法来指定一个原型对象,并基于该原型创建一个新的对象。新对象会继承原型对象的属性和方法,其 __proto__ 指针将指向原型对象。
此外,JavaScript 还提供了操作原型对象的工具:
- 使用
Object.getPrototypeOf(obj)获取一个对象的原型。 - 使用
Object.setPrototypeOf(obj, prototype)修改一个对象的原型。
这些工具为我们操作对象的原型提供了灵活性。
需要额外注意
通过原型链的机制,多个对象实例可以共享原型上的属性和方法,这种特性提升了代码的复用性。然而,我们需要注意以下几点:
- 共享属性的潜在问题
如果原型上的属性是引用类型(如数组或对象),所有继承该原型的实例会共享这个引用类型的属性。对一个实例的修改可能会影响其他实例。 - 实例独立属性的定义
如果需要实例间属性相互独立,可以在实例上重新定义这些属性,而不是依赖原型链上的定义。
为什么在Js中运用如何广泛
JavaScript 的原型继承是内建机制
原型链继承是 JavaScript 中的核心机制,它是语言本身提供的继承方式。与其他面向对象编程语言中的类继承不同,JavaScript 使用原型链来处理继承关系,因此原型链继承是 JavaScript 中自然且直接的继承方式。
- 原型是默认的继承结构:每个对象在创建时都有一个内建的
__proto__属性,它指向该对象的原型对象。原型链提供了一种“对象-对象”的继承方式,而不需要额外的类定义或关键字。这使得原型链继承成为 JavaScript 中一种本质的继承方式。
2. 原型链继承是实现面向对象编程的基础
JavaScript 本身是基于原型而非类的语言,虽然 ES6 引入了 class 语法,但底层依然依赖原型链机制。在 JavaScript 中,原型链继承是面向对象编程的基础,所有通过 new 创建的实例都隐式地继承自其构造函数的原型对象。因此,原型链继承为实现面向对象的设计模式(如继承、封装、多态)提供了基础。
- 面向对象的概念: 原型链让 JavaScript 支持继承、共享行为和属性,这对于构建大型、可扩展的应用程序非常重要。
3. 代码复用与共享
原型链继承可以让多个实例共享相同的属性和方法,这不仅避免了代码重复,还节省了内存。JavaScript 主要用于前端开发,许多现代 Web 应用程序要求高效和可重用的代码。原型链的机制使得在不同实例之间共享功能变得非常简单且高效。
- 减少内存消耗: 由于方法和属性是定义在原型对象上而非每个实例上,所有实例共享同一个方法,这大大减少了内存占用。
演示代码
-
原型对象(
customerPrototype) :我们定义了一个原型对象customerPrototype,里面包含了客户的两个方法:buy(product):模拟购买商品,将商品添加到purchases数组中。showPurchases():展示客户的购买记录。
-
clone()方法:这个方法允许我们基于现有的客户实例创建一个新客户,克隆新对象时,purchases数组是独立的,但方法(如buy和showPurchases)是共享的。 -
创建客户实例:
customer1是通过Object.create(customerPrototype)创建的一个新对象,它继承了customerPrototype的方法。每个客户的purchases数组是独立的,因此客户之间的购买记录不会互相影响。- 使用
customer1.clone()创建了一个新的客户customer2。customer2是基于customer1克隆出来的,它继承了customerPrototype上的方法和行为,但是购买记录是独立的。
-
查看购买记录:最后,我们调用了
showPurchases()方法,查看每个客户的购买记录。每个客户都可以独立管理自己的购买记录
// 原型对象:小卖部客户的原型
const customerPrototype = {
// 方法:购买商品
buy(product) {
this.purchases.push(product);
console.log(`${this.name} 购买了 ${product}`);
},
// 方法:查看购买记录
showPurchases() {
console.log(`${this.name} 的购买记录:`);
this.purchases.forEach((item, index) => {
console.log(`${index + 1}. ${item}`);
});
},
// 克隆方法,用于创建新客户
clone() {
const clone = Object.create(this); // 创建一个新对象,基于当前原型对象
clone.purchases = []; // 初始化购买记录为一个空数组
return clone;
}
};
// 创建一个客户实例
const customer1 = Object.create(customerPrototype);
customer1.name = "小明";
customer1.purchases = []; // 每个客户的购买记录是独立的
// 客户1购买商品
customer1.buy("可乐");
customer1.buy("薯片");
// 创建另一个客户实例
const customer2 = customer1.clone(); // 使用原型模式克隆出一个新客户
customer2.name = "小红";
// 客户2购买商品
customer2.buy("苹果");
customer2.buy("牛奶");
// 查看客户1和客户2的购买记录
customer1.showPurchases(); // 小明的购买记录
customer2.showPurchases(); // 小红的购买记录
运行结果
总结
JavaScript 的原型模式通过原型链实现了对象之间的属性和方法继承。通过 __proto__,我们可以在对象实例之间共享功能,同时也能够实现属性和方法的逐级查找。原型链的继承机制使得代码更加高效和模块化,同时也带来了一些设计上的灵活性。合理使用这种模式,能够显著提高代码的重用性和维护性。