Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

原型,原型链和继承的关系,如何实现继承? #10

Open
JCHappytime opened this issue Feb 23, 2021 · 0 comments
Open

原型,原型链和继承的关系,如何实现继承? #10

JCHappytime opened this issue Feb 23, 2021 · 0 comments

Comments

@JCHappytime
Copy link
Owner

JCHappytime commented Feb 23, 2021

参考文章

  1. JavaScript中的继承(MDN)
  2. 说说原型,原型链和原型继承
  3. 深入理解JS原型,原型链,继承及new的实现原理
  4. 一篇文章理解JS继承——原型链/构造函数/组合/原型式/寄生式/寄生组合/Class extends

原型 prototype 和 proto

  • 每个对象都有一个__proto__属性,并且指向它的prototype对象;
  • 每个构造函数都有一个 prototype 原型对象;
  • prototype 原型对象里的constructor指向构造函数本身。
    三者的关系图如下:
    捕获
    可能大家跟我一样,对于prototype以及__proto__会有疑问,他们到底有什么作用呢?
    实例对象的__proto__指向构造函数的prototype,从而实现继承。prototype对象相当于特定类型所有实例对象都可以访问的公共容器。
function Person(nick, age) { // 构造函数Person
  this.nick = nick;
  this.age = age;
}

Person.prototype.sayName = function() {  // 在构造函数的原型上定义一个sayName()的方法
  console.log('我的名字是:' + this.nick);
}

var p1 = new Person('Jessica', 18);
var p2 = new Person('Jay', 20);

p1.sayName(); // 我的名字是:Jessica
p1.sayName(); // 我的名字是:Jay

p1.__proto__ === Person.prototype; // true
p2.__proto__ === Person.prototype; // true

Person.prototype.constructor === Person  //true

需要注意的是:

  • 当object.prototype.__proto__已被大多数浏览器厂商所支持的今天,其存在和确切行为仅在ECMScript 2015规范中被标准化为传统功能,以确保Web浏览器的兼容性。为了更好的支持,建议一般使用Objecy.getPrototypeOf()。
  • Object.create(null) 新建的对象是没有 proto 属性的。

原型链

请看下面的代码:

var arr = [1, 2, 3];
arr.valueOf();    // [1, 2, 3]

1614158197
按照之前的理论,如果自身没有该方法,我们应该无Array.prototype对象里面去找,但是你会发现 arr.__proto__上根本就没有 valueOf 方法,那它是从哪里来的呢?
我们来看一下Array.prototype中__proto__的构成:
1614158484
这里却有一个valueOf方法,为什么呢?

查找 valueOf 方法的过程

当试图访问一个对象属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,一次层层向上搜索,直到找到一个名字匹配的属性或者达到原型链的末尾。
查找 valueOf 的大致过程如下:

  1. 当前实例对象obj,查找obj的属性或方法,找到后返回;
  2. 没有找到,通过obj.proto,找到obj构造函数的prototype并且查找上面的属性和方法,找到后返回;
  3. 没有找到,把Array.prototype当做obj,重复上面的步骤;
    当然不会无限循环的找下去,原型链是有终点的,最后当找到Object.prototype时,Object.prototype.proto===null,就意味着查找结束了。一般的数组查找时对应的关系为(arr为一个实例对象,Array是此处arr的构造函数):
    arr.proto === Array.prototype; // true
    Array.prototype.proto === Object.prototype; // true
    arr.proto.proto == Object.prototype; // true

原型链的终点:
Object.prototype.proto === null; // true
所以对于这种情况,整个原型链的查找过程是:
arr ----> Array.prototype ----> Object.prototype ----> null
这就是我们常说的原型链,层层向上查找,最后还没有找到就返回undefined。

JavaScript的继承

什么是继承?

继承指的是一个对象直接使用另外一个对象的属性和方法。

由此可见,只要实现属性和方法的继承,就能达到继承的效果:

  • 得到一个对象的属性;
  • 得到一个对象的方法。

属性如何继承?

我们先创建一个Person类

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

// 方法定义在构造函数的原型上
Person.prototype.getName = function() { console.log(this.name); }

这个时候,我想创建一个Student类,我们希望它可以继承Person的所有属性,并且能够额外添加自己的特定属性;

  • 新的属性,grade-这个属性包含了学生的学习成绩。
    定义Student的构造函数:
function Student(name, age, grade) {
  Person.call(this, name, age);
  this.grade = grade;
}

属性的继承是通过在一个类内执行另外一个类的构造函数,通过call指定this为当前执行环境,这样就可以得到另外一个类的所有属性。
我们实例化这个类来看一下:

var student1 = new Student('Jessica', '29', '100');

student1.name;   // Jessica
student1.age;   // 29
student1.grade;   // 100

显然,student1成功的继承了Person的属性(name和age)。

方法如何继承呢?

我们需要让Student从Person的原型对象里继承方法。我们要怎么做呢?
我们都知道类的方法都定义在prototype里,那其实我们只需要把Person.prototype的备份赋值给Student.prototype即可。

Student.prototype = Object.create(Person.prototype)
  • Object.create简单说就是新建一个对象,使用现有的对象赋值给新建对象的__proto__。那为什么是备份呢?
    因为如果直接赋值,那会是引用关系,意味着修改Student. prototype,也会同时修改Person.prototype,如果子类继承后导致原来的父类变得可以修改了,这是极其不合理的。
  • 另外就是:在给 Student 类添加方法时,应该在修改 prototype 之后,否则会被覆盖掉,原因是赋值前后的属性值是不同的对象。
  • 还有一个问题:我们都知道 prototype 里面有一个属性 constructor 指向构造函数本身,但是因为我们是复制其他类的 prototype ,所以这个指向是不对的,也需要更正一下。如果不修改,会导致我们类型判断错误。
Student.prototype.constructor = Student;

我们整理一下,主要是prototype和constructor的问题。

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

Student.prototype.constructor
f Person(name, age) {
  this.name = name;
  this.age = age;
}
===================================
Student.prototype.constructor = Student;

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

所以,继承的最终方案是:

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

hasOwnProperty

在原型链上查询属性比较耗时,对性能也存在一定的影响,试图访问不存在的属性时会遍历整个原型链。遍历对象时,每个可枚举的属性都会被枚举出来。要检查是否具有自己定义的属性,而不是原型链上的属性,就必须使用 hasOwnProperty 方法。该方法时JavaScript中唯一处理属性并且不会遍历原型链的方法。

总结

  1. 每个对象都有一个__proto__属性,并且指向它的 prototype 原型对象;
  2. 每个构造函数都有一个 prototype 原型对象;
  3. prototype 原型对象里的 constructor 指向构造函数本身;

原型链

每个对象都有一个 proto,它指向它的 prototype 原型对象,而 prototype 原型对象又具有一个自己的 prototype 原型对象,这样层层往上直到一个对象的原型 prototype 为 null,这个查询的路径就是原型链。

JavaScript 中的继承

  1. 属性继承
function Person (name, age) {
    this.name = name
    this.age = age
}

// 方法定义在构造函数的原型上
Person.prototype.getName = function () { console.log(this.name)}

function Student(name, age, grade) {
    Person.call(this, name, age)
    this.grade= grade
}
  1. 方法继承
Student.prototype = Object.create(Person.prototype)
Student.prototype.constructor = Student
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant