JavaScript原型探秘:构建对象与继承的艺术

大家好,我是睡个好jo,今天要给大家分享的是js中的原型。

在JavaScript中,原型(Prototype)机制是实现对象继承和属性共享的核心概念。这篇文章将会带着大家去了解并掌握原型的相关知识

原型(Prototype)

每个函数在JavaScript中都自带一个特殊的属性------prototype。这个属性是一个对象,用于定义通过该构造函数创建的所有实例所共享的属性和方法。简而言之,构造函数的prototype对象就是其所有实例对象的公共祖先。

当然,让我们通过一个具体的例子来说明prototype的工作原理。假设我们要创建一个表示动物的构造函数,特别是创建一个猫的类,我们可以这样做:

javascript 复制代码
function Cat(name, color) {
    this.name = name;
    this.color = color;
}

// 给Cat构造函数的prototype添加一个方法
Cat.prototype.meow = function() {
    console.log(this.name + ", the " + this.color + " cat, says 'Meow!'");
}

在这个例子中,Cat是一个构造函数,用于生成猫的实例。我们通过Cat.prototype给所有的猫实例添加了一个共享的方法meow()。这意味着,所有通过new Cat()创建的对象都将能够访问到meow方法,而不需要在每个实例中单独定义。

接下来,我们创建两个猫的实例并调用它们的meow方法:

javascript 复制代码
let kitty1 = new Cat("Whiskers", "gray");
let kitty2 = new Cat("Snowball", "white");

kitty1.meow(); // 输出: Whiskers, the gray cat, says 'Meow!'
kitty2.meow(); // 输出: Snowball, the white cat, says 'Meow!'

尽管meow方法是在Cat.prototype上定义的,但kitty1kitty2都能够访问到它,因为它们的原型链(__proto__)指向了Cat.prototype。这样,meow方法就成为了所有由Cat构造函数创建的对象所共享的功能,节省了内存并体现了面向对象编程中"继承"的概念。

以下内容需要先了解new,请先看你news什么new,stanley(是单例)

实例与原型的关系

当通过构造函数(使用new关键字)创建一个对象时,该对象会自动获得一个内部属性[[Prototype]](可通过__proto__访问,尽管非标准)。这个内部属性指向构造函数的prototype对象,使得实例能访问原型上的属性和方法。这些属性虽然对实例可见,但修改它们实际上会影响所有通过该构造函数创建的实例,因为它们是共享的。

但是实例不能改变原型对象上的属性和方法:

假设我们有一个构造函数Person,并在其原型上定义了一个属性gender。

Javascript 复制代码
function Person(name) {
    this.name = name;
}

Person.prototype.gender = "unknown"; // 在原型上定义gender属性

放在浏览器上访问: 现在,我们创建两个Person的实例person1和person2。

Javascript 复制代码
let person1 = new Person("Alice");
let person2 = new Person("Bob");

访问原型属性: 当我们访问person1.gender或person2.gender时,由于实例自身没有定义gender属性,JavaScript会沿着原型链找到Person.prototype.gender,因此两个实例都会显示"unknown"。

修改实例属性: 如果我们直接修改person1的gender属性,这实际上是在person1实例上新增了一个名为gender的属性,覆盖了从原型链上继承来的同名属性,但不影响person2或Person.prototype。

Javascript 复制代码
person1.gender = "female";
console.log(person1.gender); // 输出:"female"
console.log(person2.gender); // 输出:"unknown",未受影响
console.log(Person.prototype.gender); // 输出:"unknown",原型属性未变

总结

  • 直接在实例上修改一个从原型链上继承来的属性或方法,实际上是为该实例添加了一个新的局部属性或方法,而不是修改了原型对象上的那个。
  • 这种做法不会影响到其他通过同一原型链继承的对象,也不会改变原型对象本身的属性或方法。
  • 若要修改原型上的属性或方法,需直接在原型对象上操作,如Person.prototype.gender = "modified";这样会改变所有通过Person构造函数创建的对象的行为。

原型链

原型链是JavaScript实现对象属性查找的机制。当访问一个对象的属性时,如果该对象自身没有这个属性,JavaScript引擎会继续在其[[Prototype]](即隐式原型)所指向的对象中查找。这一过程会持续进行,沿着原型链逐级向上查找,直到找到该属性或到达原型链的末端(null)。

分析原型链

我们来根据以下图片来分析:

分析结果

  1. f1 和 f2 的关系f1f2都是Foo函数的实例,它们的__proto__属性指向Foo.prototype。这意味着这两个函数实例可以从Foo.prototype继承属性和方法。
  2. o1 和 o2 的关系o1o2都是Object函数的实例,它们的__proto__属性指向Object.prototype。同样,这两个对象实例也可以从Object.prototype继承属性和方法。
  3. FooObject 的关系FooObject都是Function函数的实例,它们的__proto__属性分别指向Function.prototype。这意味着这两个函数实例可以从Function.prototype继承属性和方法。
  4. Function 的关系Function函数的__proto__属性指向Function.prototype,这是由于Function是由内置的Function构造函数创建的,所以遵循同样的规则。
  5. Function.prototype 的关系Function.prototype__proto__属性指向null,表明这是原型链的终点。
  6. Object.prototype 的关系Object.prototype__proto__属性也指向null,这也是原型链的终点。
  7. Foo.prototype 的关系Foo.prototype__proto__属性指向Object.prototype,这是因为Foo.prototype是一个普通的对象,也是由Object函数创建的,所以遵循Object的原型链。
  8. ObjectFunction 的关系ObjectFunction__proto__属性都指向Function.prototype,因为它们都是函数,由Function构造函数创建。
  9. Foo 的关系Foo__proto__属性指向Function.prototype,因为它也是一个函数,由Function构造函数创建。

隐式原型与显式原型

  • 隐式原型(__proto__ :每个对象(除null外)都有一个内部属性,即隐式原型,它指向创建该对象的构造函数的prototype对象。
  • 显式原型(prototype:主要存在于函数对象上,用于定义实例共享的属性和方法。

特殊案例:无原型对象

并非所有JavaScript对象都有原型。使用Object.create(null)可以创建一个没有原型的对象,即该对象的__proto__null,这样的对象不继承任何属性或方法,是真正的"纯净"对象。

示例代码解析

javascript 复制代码
// 定义Car构造函数,为其原型添加属性
Car.prototype.name = 'su7';  // 品牌
Car.prototype.lang = 5000;  // 长度
Car.prototype.height = 1400;    // 高度 

function Car(color, owner) {
    this.color = color;
    this.owner = owner; 
}

// 创建Car实例
let car1 = new Car('pink', 'ii');
let car2 = new Car('orange', 'jj');
let car3 = new Car();

// 输出验证
console.log(car1.name);  // 输出: su7
console.log(car1.lang);  // 输出: 5000
console.log(Car);       // 输出: [Function: Car]
console.log(Car.prototype === Car.__proto__);  // 输出: false

在上述示例中,我们定义了一个Car构造函数,并通过其prototype属性为所有Car实例添加了共享属性。通过new Car()创建的每个实例都能访问这些共享属性。最后一行输出false是因为Car.prototypeCar函数的原型对象,而Car.__proto__实际上是Function.prototype,它们指向不同的对象。

通过这些概念和示例,我们可以更深刻地理解JavaScript的原型机制,它是实现面向对象编程、实现继承和属性复用的重要基石。

相关推荐
Fan_web2 分钟前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常4 分钟前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇1 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr1 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho2 小时前
【TypeScript】知识点梳理(三)
前端·typescript
安冬的码畜日常3 小时前
【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
开发语言·前端·javascript·信息可视化·数据可视化·d3.js
小白学习日记3 小时前
【复习】HTML常用标签<table>
前端·html
丁总学Java4 小时前
微信小程序-npm支持-如何使用npm包
前端·微信小程序·npm·node.js
yanlele4 小时前
前瞻 - 盘点 ES2025 已经定稿的语法规范
前端·javascript·代码规范