js原型相关知识

64 阅读7分钟

讲解原型设计模式之前,先了解原型和原型链的概念。 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 引擎会按照以下步骤查找:

  1. 查找自身属性或方法
    首先,JavaScript 会检查对象本身是否定义了这个属性或方法。如果找到,就直接返回。
  2. 沿着原型链向上查找
    如果对象本身没有该属性或方法,JavaScript 会通过对象的 __proto__ 指针,访问其原型对象。如果原型对象上定义了这个属性或方法,就直接使用。
  3. 逐级查找直到原型链顶端
    如果原型对象也没有找到,JavaScript 会继续沿着原型链向上查找,直到到达原型链的终点(null)。当查找到顶端时,若仍未找到对应的属性或方法,JavaScript 将返回 undefined

创建对象与原型链继承

在 JavaScript 中,我们可以使用 Object.create() 方法来指定一个原型对象,并基于该原型创建一个新的对象。新对象会继承原型对象的属性和方法,其 __proto__ 指针将指向原型对象。

此外,JavaScript 还提供了操作原型对象的工具:

  • 使用 Object.getPrototypeOf(obj) 获取一个对象的原型。
  • 使用 Object.setPrototypeOf(obj, prototype) 修改一个对象的原型。

这些工具为我们操作对象的原型提供了灵活性。

需要额外注意

通过原型链的机制,多个对象实例可以共享原型上的属性和方法,这种特性提升了代码的复用性。然而,我们需要注意以下几点:

  1. 共享属性的潜在问题
    如果原型上的属性是引用类型(如数组或对象),所有继承该原型的实例会共享这个引用类型的属性。对一个实例的修改可能会影响其他实例。
  2. 实例独立属性的定义
    如果需要实例间属性相互独立,可以在实例上重新定义这些属性,而不是依赖原型链上的定义。

为什么在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 数组是独立的,但方法(如 buyshowPurchases)是共享的。

  • 创建客户实例

    • customer1 是通过 Object.create(customerPrototype) 创建的一个新对象,它继承了 customerPrototype 的方法。每个客户的 purchases 数组是独立的,因此客户之间的购买记录不会互相影响。
    • 使用 customer1.clone() 创建了一个新的客户 customer2customer2 是基于 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();  // 小红的购买记录

运行结果

image.png

总结

JavaScript 的原型模式通过原型链实现了对象之间的属性和方法继承。通过 __proto__,我们可以在对象实例之间共享功能,同时也能够实现属性和方法的逐级查找。原型链的继承机制使得代码更加高效和模块化,同时也带来了一些设计上的灵活性。合理使用这种模式,能够显著提高代码的重用性和维护性。