原型链和常见继承

什么是原型链:

  每个对象都可以有一个原型_proto_,这个原型还可以有它自己的原型,以此类推,形成一个原型链。查找特定属性的时候,我们先去这个对象里去找,如果没有的话就去它的原型对象里面去,如果还是没有的话再去向原型对象的原型对象里去寻找...... 这个操作被委托在整个原型链上,这个就是我们说的原型链了。
原型指针:
  • prototype: prototype属性,它是函数所独有的,它是从一个函数指向一个对象。它的含义是函数的原型对象,也就是这个函数(其实所有函数都可以作为构造函数)所创建的实例的原型对象; 这个属性是一个指针,指向一个对象,这个对象的用途就是包含所有实例共享的属性和方法(我们把这个对象叫做原型对象);
  • __proto__: __proto__ 是原型链查询中实际用到的,它总是指向 prototype,换句话说就是指向构造函数的原型对象,它是对象独有的。注意,因为JS中,包括函数,万物皆对象,所有也有这个属性;
  • constructor: 每个函数都有一个原型对象,该原型对象有一个constructor属性,指向创建对象的函数本身。此外,我们还可以使用constructor属性,所有的实例对象都可以访问constructor属性,constructor属性是创建实例对象的函数的引用。我们可以使用constructor属性验证实例的原型类型(与操作符instanceof非常类似)。由于constructor属性仅仅是原始构造函数的引用,因此我们可以使用该属性创建新的对象。

阅读下面代码:

// 让我们从一个函数里创建一个对象o,它自身拥有属性a和b的:
let f = function() {
  this.a = 1;
  this.b = 2;
}
let o = new f(); // {a: 1, b: 2}
// 在f函数的原型上定义属性
f.prototype.b = 3;
f.prototype.c = 4;
console.log(o);
console.log(o.a, o.b, o.c, o.d);
  • a是o的自身属性,该属性值为 1;
  • b是o的自身属性,该属性值为 2,原型上b属性会发生"属性遮蔽",不会被访问到;
  • c不是o的自身属性,就看它的原型链上有没有,c是o.[[Prototype]]的属性,值为 4;
  • d不是o的自身属性,原型链上也没有这个属性,返回 undefined;

完整的原型链示意图:

所有的对象都是继承自Object,所以Person和Function的prototype的__proto__都指向Object的prototype。

常见的6种继承方式:

继承的父类

function Person(name) {
  this.name = name;
  this.read() = function() {
    console.log(this.name);
  }
}
Person.prototype.age = 10;
  • 原型链继承
// 原型继承
function Student() {}
Student.prototype = new Person()
var student1 = new Student();
var student2 = new Student();
console.log(student1.name, student2.name);
student1.name.push('tiger'); // 原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会发生变化
console.log(student1.name, student2.name);

重点:让新实例的原型等于父类的实例。

特点:实例可继承的属性有:实例的构造函数的属性,父类构造函数属性,父类原型的属性。(新实例不会继承父类实例的属性!)

缺点:1、新实例无法向父类构造函数传参。2、继承单一。3、所有新实例都会共享父类实例的属性。

  • 构造函数继承
// 构造函数继承
function Student() {
  Person.call(this);
  this.name = ['cat'];
}
var student1 = new Student();
console.log(student1.age)

重点:用.call()和.apply()将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行复制)

特点:1、只继承了父类构造函数的属性,没有继承父类原型的属性。2、解决了原型链继承缺点。3、可以继承多个构造函数属性(call多个)。4、在子实例中可向父实例传参。

缺点:1、只能继承父类构造函数的属性。2、无法实现构造函数的复用。(每次用每次都要重新调用)3、每个新实例都有父类构造函数的副本,臃肿。

  • 组合继承
// 组合继承
function Student() {
  Person.call(this);
}
Student.prototype = new Person();
var student1 = new Student();
console.log(student1);  // 从下图可以看出,继承了两份相同的属性

重点:结合了两种模式的优点,传参和复用。

特点:1、可以继承父类原型上的属性,可以传参,可复用。2、每个新实例引入的构造函数属性是私有的。

缺点:调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数。

  • 原型式继承
// 原型式继承
function content(obj) {   // 封装一个容器,用来输出对象和承载继承的原型 
  function F() {};
  F.prototype = obj;   // 继承了传入的参数
  return new F();
}
var student = new Person();   // 拿到父类实例
var student1 = content(student);
console.log(student1)

重点:用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象。object.create()就是这个原理。

特点:类似于复制一个对象,用函数来包装。

缺点:1、所有实例都会继承原型上的属性。2、无法实现复用。(新实例属性都是后面添加的)

  • 寄生式继承
// 寄生式继承
function content(obj) {
  function F() {};
  F.prototype = obj;
  return new F();
}
var Student = new Person();
// 原型式继承外,再套一个壳子传递参数
function subObject(obj) {   var student = content(Student);   return student; }
// 函数经过声明后,成为可添加属性的对象
var student1 = subObject(Student); console.log(student1)

重点:就是给原型式继承外面套了个壳子。

优点:没有创建自定义类型,因为只是套了个壳子返回对象(这个),这个函数顺理成章就成了创建的新对象。

缺点:没用到原型,无法复用。

  • 寄生组合式继承
// 寄生组合继承
// 寄生 function content(obj) {   function F() {};   F.prototype = obj;   return new F(); } var con = content(Person.prototype);
// 组合
function Student() {   Person.call(this); } Student.prototype = con; con.constructor = Student(); var student1 = new Student(); console.log(student1);

寄生:在函数内返回对象然后调用

组合:1、函数的原型等于另一个实例。2、在函数中用apply或者call引入另一个构造函数,可传参。

posted @ 2021-10-12 17:57  辉太狼`  阅读(55)  评论(0编辑  收藏  举报