小白谈谈对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日

相关文章

  • React中映射一个嵌套数组实现demo

    当在React中需要映射一个嵌套数组时,可以使用Array.map()方法结合JSX来实现。下面是一个完整的攻略,包含了两个示例说明。 步骤1:准备数据 首先,我们需要准备一个嵌套数组作为数据源。这个数组可以包含任意层级的嵌套,每个元素可以是一个对象或者其他数据类型。例如,我们准备了以下的嵌套数组作为示例数据: const data = [ { id: 1,…

    other 2023年7月28日
    00
  • 微信小程序开发之获取用户信息的两种方法

    微信小程序开发之获取用户信息的两种方法 在微信小程序开发中,获取用户信息是很常见的操作。本文将介绍微信小程序中获取用户信息的两种方法。 一、通过button获取用户信息 微信小程序提供了button组件,可以让用户点击授权获取用户信息。使用该方法需要注意以下几点: 需要在小程序管理后台设置“用户信息”权限 button组件需要设置open-type属性为“g…

    other 2023年6月26日
    00
  • 深入理解javascript作用域和闭包

    深入理解 JavaScript 作用域和闭包攻略 作用域(Scope) 作用域是指在程序中定义变量的区域,它决定了变量的可见性和生命周期。JavaScript 中有三种作用域:全局作用域、函数作用域和块级作用域。 全局作用域 全局作用域是在整个程序中都可访问的作用域。在全局作用域中定义的变量可以在程序的任何地方被访问。 示例: var globalVaria…

    other 2023年8月19日
    00
  • 苹果iOS8.1.3固件官方下载地址大全汇总介绍

    苹果iOS8.1.3固件官方下载地址大全汇总介绍 1. 了解iOS8.1.3固件 iOS8.1.3是苹果公司发布的一款操作系统固件,为iOS设备提供了一系列的更新和修复。在下载固件之前,我们需要了解一些基本信息。 发布日期:iOS8.1.3固件发布于2015年1月27日。 主要更新:该固件主要包含了一些性能改进、错误修复和安全增强。 兼容设备:iOS8.1.…

    other 2023年8月4日
    00
  • Sql Server 2005的1433端口打开局域网访问和进行远程连接

    首先,需要确认Sql Server 2005已经正确安装并且正常运行。然后,需要打开1433端口。 以下是Sql Server 2005打开1433端口的详细步骤: 打开Sql Server 2005配置管理器。 点击左侧面板上的“Sql Server 2005网络配置”。 在右侧面板上,找到“协议”选项卡。 找到“TCP/IP”协议选项,并确保其为“启用”…

    other 2023年6月27日
    00
  • java8–list转set

    在Java 8中,我们可以使用Stream API来将List转换为Set。以下是Java 8中将List转换为Set的详细攻略: 步骤1:创建List 首先我们需要创建List对象。我们可以使用ArrayList或LinkedList等Java集合类来创建List对象。以下是一个示例: List<String> list = new Array…

    other 2023年5月9日
    00
  • CSS伪类选择器和伪元素选择器

    CSS伪类选择器和伪元素选择器是CSS中非常重要的一部分,它们可以帮助我们更好地控制和定位HTML元素。本文将详细讲解CSS伪类选择器和伪元素选择器的作用和使用方法,并提供两个示例说明。 伪类选择器 伪类选择器是CSS中用于选择元素的一种方式,它可以根据元素的状态或位置来选择元素。常见的伪类选择器有:hover、:active、:focus等。 示例1:使用…

    other 2023年5月5日
    00
  • 使用php输出json文件

    以下是关于“使用PHP输出JSON文件”的完整攻略,包含两个示例。 使用PHP输出JSON文件 在PHP中,我们可以使用json_encode()函数将数据转换为JSON格式,并使用header()函数设置Content-Type头来输出JSON文件。以下是两个示例: 1. 输出数组 $data = array( "name" =>…

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