原型链与继承

123 阅读5分钟

一、原型

  • 所有的对象都有一个__proto__属性指向对象的原型对象 (生产环境不建议使用,因为标准里其实说的是每个对象都有[[Prototype]]而Object.getPrototypeOf方法用于获取对象的 [[Prototype]]
  • 所有的函数都有一个prototype属性指向函数的原型对象
function fn() {}
const obj = {
  name: "ad"
}
console.log("obj", Object.getPrototypeOf(obj));
console.log("fn", Object.getPrototypeOf(fn));
console.log("obj", obj.__proto__);
console.log("fn", fn.prototype);

二、原型链

1. 定义

原型链是一系列对象链接在一起形成的链式结构,每个对象都有一个内部属性 [[Prototype]](可以通过 __proto__ 访问,Object.getPrototypeOf方法用于获取对象的 [[Prototype]]),指向另一个对象或 null。当访问一个对象的属性时,如果该对象本身没有该属性,JavaScript 引擎会沿着原型链向上查找,直到找到该属性或到达原型链的末端(null)。

2. 查找顺序证明:
const obj1 = {
  obj1: "1",
  obj2: '1',
  obj3: '1'
}
const obj2 = {
  obj1: "1",
  obj2: '2',
}
Object.setPrototypeOf(obj2, obj1)
const obj3 = {
  obj1: "3",
}
Object.setPrototypeOf(obj3, obj2)
console.log(obj3.obj1); //3
console.log(obj3.obj2); //2
console.log(obj3.obj3); //1

三、继承

1. 要使用继承的原因:

实现代码的复用,避免重复代码的出现(两个字难受,不想再写写过的东西,能复制粘贴何必再写一遍,但是能不复制粘贴又为什么要复制粘贴)

2. 想要实现的效果:

复用父类的成员,包括属性成员和方法成员

4.继承的根本原理:

当访问一个对象的属性时,如果该对象本身没有该属性,JavaScript 引擎会沿着原型链向上查找,直到找到该属性或到达原型链的末端(null)

5. js继承方式(不算class):

原型链继承、构造函数继承、组合继承、型式继承、生式继承、生组合式继承

3.1. 原型链继承
  • 原理: 子类找不到的属性会去原型上找 (另一个辅助原理是new都做了什么)
  • 实现:将父类对象设置为子类的原型
function Father(name) {
  this.faname = name
}
function Son(name) {
  this.soname = name
}
const son = new Son("so")
Son.prototype = new Father("fa")  //方法一 
// Object.setPrototypeOf(son, new Father("fa")) //方法二 此方法麻烦不使用因为要每次都重新设置原型
//方法一和方法二都可以的理由是在设置原型之前 Son.prototype===son.__proto__ ==>ture
console.log(Son.prototype === son.__proto__);
console.log(son.faname);
  • 不足:子类所有实例共享一个原型对象,修改继承属性时会有干扰
function Father() {
  this.share = [1, 2, 3]
}
function Son() { }
Son.prototype = new Father()
const son = new Son()
const son1 = new Son()
console.log(son.share, son1.share);//[ 1, 2, 3 ] [ 1, 2, 3 ]
son.share.push(4)
console.log(son.share, son1.share); //[ 1, 2, 3, 4 ] [ 1, 2, 3, 4 ]
3.2 构造函数继承: 解决原型链继承的不足
  • 原理 :父类构造函数的this重新绑定并执行,使得父类构造函数中的属性添加到了子类对象上(相当于手动调用obj.[属性])
  • 实现: 子类构造函数里显示绑定父类构造函数的this并把父类构造函数当普通函数执行
function Father() {
  this.share = [1, 2, 3]
}
function Son() {
  Father.apply(this) 
}
const son = new Son()
const son1 = new Son()
console.log(son.share, son1.share);
son.share.push(4)
console.log(son.share, son1.share);
  • 不足: 只能继承父类的实例属性和方法,不能继承原型属性或者方法。
function Father() {
  this.share = [1, 2, 3]
}
Father.prototype.say = function () {
  console.log("say");
}
function Son() {
  Father.apply(this)
}
const son = new Son()
console.log(son.share);
son.say()//报错
3.3 组合继承(前两种组合): 解决构造函数继承的不足
function Father() {
  this.share = [1, 2, 3]
}
Father.prototype.say = function () {
  console.log("say");
}
function Son() {
  Father.apply(this)
}
Son.prototype = new Father()
Son.prototype.constructor = Son //重置构造器
const son = new Son()
const son1 = new Son()
console.log(son.share, son1.share);
son.share.push(4)
console.log(son.share, son1.share);
son.say()//不报错

不足: 父类构造函数会执行两次,并且以上继承的方法都是基于构造函数

3.4 原型式继承
  • 原理: ES5 里面的 Object.create 方法,这个方法接收两个参数:一是用作新对象原型的对象、二是为新对象定义额外属性的对象(可选参数),说白了也是设置原型
  • 实现: const son1 = Object.create(father)
const father = {
  name: 'father',
  friends: ['p1', 'p2', 'p3'],
  say: function () {
    console.log("say");
  }
}
const son1 = Object.create(father)
const son2 = Object.create(father)
console.log(son1.name, son1.friends, son2.name, son2.friends);
son1.say()
son2.say()
son1.name = "son"
son1.friends.push('p4')
console.log(son1.name, son1.friends, son2.name, son2.friends);
  • 不足: 同原型链继承,多个实例的引用类型属性指向相同的内存,存在篡改的可能
3.5 寄生式继承
  • 原理: 对父类对象进行增强(添加了属性或者方法)
  • 实现: 写个方法,里面调用Object.create() 并为新对象添加属性或方法,返回这个对象
const father = {
  name: 'father',
  friends: ['p1', 'p2', 'p3'],
  say: function () {
    console.log("say");
  }
}
function parasitism(father) {
  const temp = Object.create(father)
  temp.run = function () {
    console.log("run");
  }
  return temp
}
const son = parasitism(father)
son.run()
  • 不足: 和原型式继承一样
3.6 寄生组合式继承 (终极方案)
  • 原理: 把组合继承里的Son.prototype = new Father()替换为寄生函数调用

  • 实现: 把组合继承里的Son.prototype = new Father()替换为寄生函数调用

function parasitism(father, son) {
  // 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
  son.prototype = Object.create(father.prototype)
  child.prototype.constructor = son
}
function Father() {}
Father.prototype.say = function () {
  console.log("say");
}
function Son() {
  Father.call(this)
}
//替换调用寄生函数
parasitism(Father, Son)
Son.prototype.run = function () {
  console.log("run");
}
  • 不足 没有不足,非常完美,解决了所有问题
3.7 图片比较

4095a2c55494281a574fd3ccfc097237.png

四、new都做了什么

  1. 创建一个空对象 {}

  2. 将新对象的 [[Prototype]] 内部属性(可以通过 __proto__ 访问)设置为构造函数的 prototype 属性。

  3. 将构造函数内部的 this 绑定到新创建的对象。

  4. 执行构造函数中的代码,为新对象添加属性和方法。

  5. 如果构造函数没有显式返回一个对象,则返回新创建的对象。如果构造函数显式返回一个对象,则返回该对象。

function MyNew(Counstructor, ...args) {
  // 1. 创建一个新对象  // 2. 设置新对象的原型
  const obj = Object.create(Counstructor.prototype)
  // 3. 绑定构造函数的 `this` //4.执行构造函数
  const result = Constructor.apply(obj, args);
  // 4. 返回新对象
  // 如果构造函数返回一个对象,则返回该对象;否则返回新创建的对象
  return (result !== null && (typeof result === 'object' || typeof result === 'function')) ? result : obj;
}