小白谈谈对JS原型链的理解

下面我将为你详细讲解JS原型链的完整攻略。

JS 原型链

JS 原型链是 JS 中实现继承的重要机制之一,它可以让我们避免代码冗余,提高代码的可维护性。在学习原型链之前,我们先了解一下 JS 中的构造函数和对象。

构造函数和对象

在 JS 中,我们可以通过构造函数来创建新的对象,其方法如下:

function Person(name) {
  this.name = name;
}

const person1 = new Person('Tom');
const person2 = new Person('John');

在上面的代码中,Person 就是一个构造函数。通过 new 关键字我们可以用这个构造函数来创建对象。其实这里的 Person 就是一个对象原型(也可以称之为类),它包含着一些属性和方法,这些属性和方法可以被 person1person2 继承和访问。当我们使用 new 加上构造函数时,JS 会自动帮我们完成以下操作:

  1. 在内存中新建一个对象;
  2. 将新建的对象的 __proto__ 属性指向构造函数的 prototype 对象;
  3. this 指向新建的对象;
  4. 执行构造函数的方法,将属性和方法添加到新建的对象上;
  5. 返回新建的对象。

原型链的基本概念

在 JS 中,每个对象都有一个隐藏的属性 __proto__,我们可以通过 Object.getPrototypeOf(object) 来获取对象的原型。原型对象也有自己的原型,如果一个对象的原型不是 Object.prototype,那么它的原型就是其原型对象的原型。这样就形成了一个链式结构,我们称之为原型链。如果在当前对象上找不到某个属性或方法,JS 引擎会沿着原型链依次向上查找,直到找到或者到达原型链尾部,这个过程称为原型链查找。

以下是一个示例:

function Person(name) {
  this.name = name;
}

Person.prototype.sayHi = function() {
  console.log(`Hi, my name is ${this.name}.`);
};

const person1 = new Person('Tom');
const person2 = new Person('John');

person1.sayHi();    // Hi, my name is Tom.
person2.sayHi();    // Hi, my name is John.

console.log(person1.__proto__ === Person.prototype);    // true
console.log(person2.__proto__ === Person.prototype);    // true

console.log(Person.prototype.__proto__ === Object.prototype);    // true

在上面的代码中,Person.prototype 就是 person1person2 的原型。当我们调用 person1.sayHi() 时,JS 引擎会沿着原型链依次向上查找,首先在 person1 对象上查找 sayHi 方法,找不到的话就到其原型 Person.prototype 上查找,如果还是找不到就继续往上查找,直到找到或者到达原型链尾部。最后找到 sayHi 方法后,JS 引擎会运行该方法并传入执行上下文中的 this 对象,这里指的就是 person1

构造函数的原型

在 JS 中,每个构造函数都有一个原型对象,该对象可以被该构造函数创建的所有实例共享。我们可以通过在构造函数上添加属性和方法,来让创建出来的实例共享这些属性和方法。例如:

function Person(name) {
  this.name = name;
}

Person.prototype.sayHi = function() {
  console.log(`Hi, my name is ${this.name}.`);
};

const person1 = new Person('Tom');
const person2 = new Person('John');

Person.prototype.nickName = 'XXX';   // 在 Person 构造函数的原型对象上添加属性 nickName

console.log(person1.nickName);   // XXX
console.log(person2.nickName);   // XXX

在上面示例中,我们在 Person.prototype 上添加了一个属性 nickName。这样在后面创建的 person1person2 对象上就都能访问这个属性了。这个属性只需要在构造函数的原型对象上添加一次就可以,不需要每个实例对象都添加一次。这也是 JS 中实现继承的重要机制之一。

继承

在 JS 中,我们可以通过原型链来实现继承。对于一个子类(或者继承类)来说,它应该也是一个构造函数,我们可以通过 call 或者 apply 方法在子类的构造函数中调用父类的构造函数,来继承父类的属性。同时我们还需要让子类的原型对象继承父类的原型对象,以便子类实例也能继承父类的方法。以下是一个示例:

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHi = function() {
  console.log(`Hi, my name is ${this.name} and I am ${this.age} years old.`);
};

function Student(name, age, grade) {
  Person.call(this, name, age);
  this.grade = grade;
}

Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

const tom = new Student('Tom', 18, 3);
tom.sayHi();   // Hi, my name is Tom and I am 18 years old.

在上面示例中,我们定义了两个构造函数 PersonStudentPerson 可以创建一个拥有 nameage 属性的对象,并且有一个 sayHi 方法。StudentPerson 的子类,它继承了 Person 的属性,并新添加了一个 grade 属性。在 Student 构造函数中,我们通过 call 方法来调用 Person 的构造函数,以便在创建 Student 实例对象时,能够同时设置 nameage 属性。同时我们还将 Student 的原型对象设置为 Person.prototype 的一个新实例,这里我们使用了 Object.create 方法。这样就可以实现让 Student 继承 Person 的方法和属性了。

示例说明

示例1:通过原型链实现继承

在之前的继承示例中,我们通过 Student.prototype = Person.prototype 来让 Student 继承 Person 的方法。但是这种方式有一个问题,那就是当我们修改 Student.prototype 时,会同时修改 Person.prototype。这个问题跟 JavaScript 的对象引用有关。解决这个问题的方法是让 Student.prototype 指向一个新的对象,这个新的对象的原型指向 Person.prototype

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHi = function() {
  console.log(`Hi, my name is ${this.name} and I am ${this.age} years old.`);
};

function Student(name, age, grade) {
  Person.call(this, name, age);
  this.grade = grade;
}

Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

const tom = new Student('Tom', 18, 3);
tom.sayHi();   // Hi, my name is Tom and I am 18 years old.

在上面的示例中,我们使用 Object.create 方法将 Student.prototype 的原型设置为一个新创建的对象,这个新的对象的原型指向了 Person.prototype。这样就可以实现 Student 继承 Person 的方法了,当我们修改 Student.prototype 时,不会影响到 Person.prototype

示例2:修改原型链

在 JS 中我们可以通过 Object.defineProperty(也可以使用 Object.prototype.__defineGetter__Object.prototype.__defineSetter__)方法向一个对象中添加一个属性,同时还可以设置属性的性质(描述符)。其中 writableenumerableconfigurable 分别表示属性是否可写、是否可枚举和是否可配置。我们还可以通过 Object.defineProperty 方法修改一个属性的值和性质,这样就可以实现对原型链的修改。

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHi = function() {
  console.log(`Hi, my name is ${this.name} and I am ${this.age} years old.`);
};

const tom = new Person('Tom', 18);
const john = new Person('John', 20);

console.log(tom.__proto__ === Person.prototype);   // true
console.log(john.__proto__ === Person.prototype);  // true

Object.defineProperty(Person.prototype, 'gender', {
  value: 'Male',
  writable: false,
  enumerable: true,
  configurable: false
});

console.log(tom.gender);   // Male
console.log(john.gender);  // Male

Person.prototype.gender = 'Female';   // 无法修改,因为 writable 为 false

console.log(tom.gender);   // Male
console.log(john.gender);  // Male

delete Person.prototype.gender;  // 无法删除,因为 configurable 为 false

console.log(tom.gender);   // Male
console.log(john.gender);  // Male

在上面的示例中,我们通过 Object.defineProperty 方法向 Person.prototype 中添加了一个属性 gender,这个属性的值为 Male,同时不允许修改和删除,因为 writableconfigurable 都设置为了 false。最后输出 tom.genderjohn.gender 都为 Male,这是因为 gender 属性属于 Person.prototype,所以它是在原型链中被共享的。在最后尝试修改 Person.prototype.gender 的值,由于 writablefalse,所以修改失败,再次尝试删除 Person.prototype.gender,由于 configurablefalse,删除失败。这样就保证了原型链中共享的属性不会被修改或者删除。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:小白谈谈对JS原型链的理解 - Python技术站

(0)
上一篇 2023年6月26日
下一篇 2023年6月26日

相关文章

  • 浅谈VC中预编译的头文件放那里的问题分析

    我很乐意为大家提供有关“浅谈VC中预编译的头文件放那里的问题分析”的完整攻略。首先,我们需要明确,预编译头文件(Precompiled Header,PCH)是一种提高编译速度和性能的技术,将头文件预编译成一个二进制文件,并在后续编译过程中重复使用,而不是每次都重新编译头文件。那么,在VC中,预编译头文件应该放在哪里呢? 一般来说,VC的预编译头文件应该放在…

    other 2023年6月27日
    00
  • matlab中plot画图参数的设置

    在MATLAB中,plot函数是一种常用的绘图函数,用于绘制二维图形。plot函数可以接受多个参数,用于设置绘图的各种参数,例如线型、颜色、标记等。本文将对MATLAB中plot函数的参数进行详细的分析,并提供两个示例说明。 plot函数的参数 plot函数常用参数如下: x:表示要绘制的数据的x坐标。 y:要绘制的数据的y坐标。 LineSpec:表示线型…

    other 2023年5月9日
    00
  • 买iPhone哪个内存版本适合入手 苹果手机购买建议

    买iPhone哪个内存版本适合入手 苹果手机购买建议 苹果手机有不同的内存版本可供选择,选择适合自己的内存版本是购买iPhone时需要考虑的重要因素之一。以下是一些关于选择iPhone内存版本的建议和示例说明。 1. 考虑使用需求 首先,你需要考虑自己的使用需求。不同的内存版本适合不同类型的用户。以下是一些常见的使用需求和相应的内存版本建议: 基本使用者:如…

    other 2023年8月2日
    00
  • springboot自动重启的简单方法

    下面我来详细讲解如何使用Spring Boot实现自动重启的简单方法。 什么是Spring Boot自动重启? 在日常开发中,我们经常需要修改代码并重新启动应用程序才能看到更新后的效果,这个过程非常繁琐。而Spring Boot提供了一种自动重启的机制,可以在代码修改后自动重新编译并重启应用程序,从而节省开发人员的时间。 实现Spring Boot自动重启的…

    other 2023年6月27日
    00
  • eclipse怎么添加include目录? eclipse下include路径的设置方法

    以下是在Eclipse中添加include目录并设置路径的完整攻略: 添加include目录 打开Eclipse,在项目上右键单击,选择“Properties”打开项目属性界面; 在左侧面板选择“C/C++ Build”->“Settings”; 在右侧面板选择“Tool Settings”->“GCC C Compiler”; 在“Direct…

    other 2023年6月26日
    00
  • JavaScript写的一个自定义弹出式对话框代码

    以下是详细讲解 JavaScript 写一个自定义弹出式对话框的完整攻略。 一、简介 弹出式对话框是 Web 开发中常用的组件之一,可用于实现用户输入信息的提示、确认或错误等功能。JavaScript 可以实现一个自定义的弹出式对话框,方便开发者在应用中使用。 二、实现步骤 创建 HTML 结构 首先在 HTML 中创建一个用于弹出式对话框的容器。以下示例使…

    other 2023年6月25日
    00
  • Windows Server 2008 R2 服务器常用命令小结

    Windows Server 2008 R2 服务器常用命令小结 作为一名 Windows Server 系统管理员,熟练掌握一些常用的命令可以帮助我们更快速、高效地管理服务器。在本文中,我们将介绍几条常用的命令。 1. ipconfig命令 使用 ipconfig 命令可以查看本机的网络配置情况,其中包括 IP 地址、子网掩码、默认网关等信息。输入以下命令…

    other 2023年6月26日
    00
  • Unix文件系统和pwd命令实现详解

    Unix 文件系统和 pwd 命令实现详解 Unix 文件系统是一个树形结构的文件系统,是现代操作系统中应用广泛的文件系统之一。Unix 文件系统定义了文件的操作以及它们在系统中的位置。 Unix 文件系统的结构 Unix 文件系统中的每个文件和目录都有一个唯一的路径。路径的第一个部分是根目录 /。根目录下可以包含多个子目录。每个子目录可以包含文件、子目录和…

    other 2023年6月26日
    00
合作推广
合作推广
分享本页
返回顶部