JavaScript原型链真的过时了吗?

争论不一

主要两种声音:

某大佬x: 没必要再考原型链了,说这种原型链问题实际在开发中很少遇到,目前大多使用 ES6 中的类,没有必要再问了
某大佬y: 认为仍然有必要,原型链是 js 最基础的内容,必须掌握。

我觉得争论这些不重要,就用下面一道我常考面试者的经典面试题你就知道原型链到底要掌握到什么程度?

给你一个 class 类用函数实现一下。我觉得如果你既然说了大家现在都在使用 ES6 中的类,但是类中的基本概念一定得清楚吧,以及它和函数之间的关系也得清楚。下面开始做这道题

初级版本题目

class Student{
   constructor(name,age,grade){
    this.name = name;
    this.age = age;
    this.grade = grade;
   } 
   
   displayInfo(){
    console.log(`Name: ${this.name}, Age: ${this.age}, Grade: ${this.grade}`);
   }
   // 静态方法适用于与实例无关的功能
   static compareAge(student1,student2){
    if (student1.age > student2.age) {
        console.log(`${student1.name} is older than ${student2.name}.`);
      } else if (student1.age < student2.age) {
        console.log(`${student1.name} is younger than ${student2.name}.`);
      } else {
        console.log(`${student1.name} and ${student2.name} are of the same age.`);
      }
   }
}

基于上面的 class 我们要实现对应的函数需要清楚的知识点

  1. class 类构造函数中声明的属性,是在 Student 实例中的—- function 实现时也需在实例上
  2. 类中的实例函数 display(Instance Method) 是定义在类的原型上,并且是不可枚举的—- function 实现时也需要在原型上
  3. 类中的静态方法 (Static Method) 是定义在类本身上的方法,而不是在类实例上的—- function 实现时应在函数本身,并且注意类中静态函数也是不可枚举的。

代码实现

function Student(name,age,grade){
  // 实例属性
  this.name = name;
  this.age = age;
  this.grade = grade;
}
Object.defineProperty(Student.prototype,'displayInfo',{
  value:function(){
    console.log(`Name: ${this.name}, Age: ${this.age}, Grade: ${this.grade}`);
  },
  enumerable:false,// 类中的函数是不可枚举的
})
Object.defineProperty(Student,
  'compareAge',function(student1,student2){
     if (student1.age > student2.age) {
        console.log(`${student1.name} is older than ${student2.name}.`);
      } else if (student1.age < student2.age) {
        console.log(`${student1.name} is younger than ${student2.name}.`);
      } else {
        console.log(`${student1.name} and ${student2.name} are of the same age.`);
      }
  }
)

考虑一个问题,如果我在 function Student 上增加一个非函数属性, 类中可以有对应的实现吗?

function Student(){

}
Student.prototype.name = '123'

在类中是没有对应实现的,即使写在构造函数中的实例属性也不是等价的。因为在原型链模式下,原型上的属性是所有实例共享的,而将属性写在类的构造函数中会使每个实例拥有自己的属性副本,这改变了属性的共享方式。

类中实现不了,但是可以给类的原型链增加非函数属性,手动修改 class的原型

class Student {}
Student.prototype.name = '123'// 所有实例共享

题目升级,增加父之继承关系

升级题目,子类 Student 继承 Person,除了类到函数转换,还需考虑处理继承(包括父类的构造函数调用)

class Person{
  constructor(sex){
    this.sex = sex;
  }
  getSex(){
    console.log('获取性别',this.sex);
    return this.sex;
  }
}
class Student extends Person{
   constructor(name,age,grade,sex){
    super(sex);// 调用父类的 constructor
    this.name = name;
    this.age = age;
    this.grade = grade;
   } 
   
   displayInfo(){
    console.log(`Name: ${this.name}, Age: ${this.age}, Grade: ${this.grade}`);
   }
   // 静态方法适用于与实例无关的功能
   static compareAge(student1,student2){
    if (student1.age > student2.age) {
        console.log(`${student1.name} is older than ${student2.name}.`);
      } else if (student1.age < student2.age) {
        console.log(`${student1.name} is younger than ${student2.name}.`);
      } else {
        console.log(`${student1.name} and ${student2.name} are of the same age.`);
      }
   }
}

除了前面类转换函数的知识点外,还需清楚点知识点

  1. 初级版题目知识点不再重复
  2. 构造函数定义:PersonStudent都作为构造函数定义,其中Student构造函数内部首先调用 Person.call(this, sex)来确保父类的构造函数被正确执行,并设置了 sex 属性。
  3. 设置原型和构造器:通过 Object.create(Person.prototype) 创建了一个新对象,这个对象的原型指向 Person.prototype,然后将这个对象赋值给 Student.prototype以建立原型链继承。接着,将 Student.prototype.constructor设置为 Student,以保证实例的 constructor 属性正确指向Student

代码实现:

// 定义Person构造函数
function Person(sex){
  this.sex = sex;
}

// 定义Person的getSex实例方法
Object.defineProperty(Person.prototype,'getSex',{
  value:function(){
    console.log('获取性别',this.sex);
    return this.sex;
  },
  enumerable:false
})

// 定义Student构造函数
function Student(name,age,grade,sex){
  // 显式调用父类的构造函数
  Person.call(this,sex);
  this.name = name;
  this.age = age;
  this.grade = grade;
}

// 建立继承关系,设置Student的原型为Person的prototype
Student.prototype = Object.create(Person.prototype);
// 设置构造器指向,确保 intanceof 运算符能正确识别实例
Student.prototype.constructor = Student;

Object.defineProperty(Student.prototype,'displayInfo',{
  value:function(){
    console.log(`Name: ${this.name}, Age: ${this.age}, Grade: ${this.grade}`);
  },
  enumerable:false,// 类中的函数是不可枚举的
})
Object.defineProperty(Student,
  'compareAge',function(student1,student2){
     if (student1.age > student2.age) {
        console.log(`${student1.name} is older than ${student2.name}.`);
      } else if (student1.age < student2.age) {
        console.log(`${student1.name} is younger than ${student2.name}.`);
      } else {
        console.log(`${student1.name} and ${student2.name} are of the same age.`);
      }
  }
)

再考虑一个问题:

实现继承关系时,Student.prototype = Object.create(Person.prototype); Object.create 的为什么是Person.prototype 而不是 Person。原因在于 JavaScript 的原型继承机制。Object.create() 方法创建一个新对象,使用现有的对象来提供新创建的对象的 __proto__(即原型)。这意味着你想要一个新对象继承自 Person 类的行为(即Person 的方法),而这些行为是定义在 Person.prototype 上的,而不是 Shape 这个构造函数对象本身。

JavaScript原型链真的过时了吗?
hhhh.png

前面这段逻辑有点绕,看一张图就理解了,- 总结来说:直接实例与其构造函数原型的关系:每个Person实例的__proto__指向Person.prototype,每个Student实例的__proto__指向Student.prototype

  • 继承关系中的原型链Student.prototype__proto__指向Person.prototype,使得Student实例可以访问Person原型上定义的方法。

总结

如果以上两道题我觉得能理解并正确做出来,再问一些复杂刁钻的原型链问题我觉得没必要了,学习原型链结合 class 一方面为了理解面向对象编程(OOP),还有就是理解继承和对象创建机制以及它在一些现代 JavaScript 框架中的应用,更好的理解这些框架的内部工作原理。


原文始发于微信公众号(前端工匠):JavaScript原型链真的过时了吗?

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/274299.html

(0)
李, 若俞的头像李, 若俞

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!