JavaScript原型揭秘:深度解析对象构建艺术

发表时间: 2024-06-06 11:58

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

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

原型(Prototype)

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

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

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方法:

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上定义的,但kitty1和kitty2都能够访问到它,因为它们的原型链(__proto__)指向了Cat.prototype。这样,meow方法就成为了所有由Cat构造函数创建的对象所共享的功能,节省了内存并体现了面向对象编程中“继承”的概念。

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

实例与原型的关系

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

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

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

function Person(name) {    this.name = name;}Person.prototype.gender = "unknown"; // 在原型上定义gender属性

放在浏览器上访问:

现在,我们创建两个Person的实例person1和person2。

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。

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 的关系:f1和f2都是Foo函数的实例,它们的__proto__属性指向Foo.prototype。这意味着这两个函数实例可以从Foo.prototype继承属性和方法。
  2. o1 和 o2 的关系:o1和o2都是Object函数的实例,它们的__proto__属性指向Object.prototype。同样,这两个对象实例也可以从Object.prototype继承属性和方法。
  3. Foo 和 Object 的关系:Foo和Object都是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. Object 和 Function 的关系:Object和Function的__proto__属性都指向Function.prototype,因为它们都是函数,由Function构造函数创建。
  9. Foo 的关系:Foo的__proto__属性指向Function.prototype,因为它也是一个函数,由Function构造函数创建。

隐式原型与显式原型

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

特殊案例:无原型对象

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

示例代码解析

// 定义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);  // 输出: su7console.log(car1.lang);  // 输出: 5000console.log(Car);       // 输出: [Function: Car]console.log(Car.prototype === Car.__proto__);  // 输出: false

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

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



文章转自:
https://juejin.cn/post/7376690981859655714