11_继承的实现-对象函数原型的关系

在前面讲的原型链继承和借用构造函数继承两种实现继承的方式或多或少都存在一些问题,所以这里我们继续探究更好的实现继承的方式。

在实现更好的继承方式之前,我们需要先了解一下对象之间的继承方式

原型式继承函数

渊源

  • 这种模式要从道格拉斯·克罗克福德(Douglas Crockforf,著名的前端大师,JSON 的创立者)在 2006 年写的一篇文章说起:Prototype Inheritance in JavaScript(在 JS 中使用原型式继承)

  • 在这篇文章中,它介绍了一种继承方法,而且这种继承方法不是通过构造函数来实现的

  • 为了理解这种方式,我们先再次回顾一下 JavaScript 想实现继承的目的:重复利用另外一个对象的属性和方法

实现

首先要明确我们的目的: 让 info 对象的原型指向 obj 对象。于是 Douglas Crockforf 便实现了一个函数去做这件事情,问题就是怎么拿到原型呢?有同学可能会想到__proto__,但是前面说了这是浏览器的实现方式,存在兼容性问题

var obj = {
  name: "beanbag",
  age: 18,
};

var info = {};

// 原始方式
function createObject1(o) {
  function Fn() {}
  Fn.prototype = o;
  var newObj = new Fn();
  return newObj;
}

// 另一种方式
function createObject2(o) {
  var newObj = {};
  Object.setPrototypeOf(newObj, o);
  return newObj;
}

// 更简单的方式
var info = Object.create(obj);
console.log(info);
console.log(info.__proto__);

寄生式继承

  • 也是一种对象的继承方式
  • 寄生式继承是与原型式继承紧密相关的一种思想,并且同样是由道格拉斯·克罗克福德提出和推广的
  • 寄生式继承的思路是结合原型式继承和工厂模式的一种方式
  • 即创建一个封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再将这个对象返回
  • 其实就是单纯使用原型式继承不容易批量创造对象,加入了工厂模式

实现

var personObj = {
  running: function () {
    console.log("running");
  },
};

function createStudent(name) {
  var stu = Object.create(personObj);
  stu.name = name;
  stu.studying = function () {
    console.log("studying~");
  };
  return stu;
}

var stuObj1 = createStudent("beanbag");
var stuObj2 = createStudent("bean");

弊端

  • studying 函数在每次创造函数的时候都会被创造出来
  • 也存在工厂模式的弊端

寄生组合式继承

前面的寄生式继承和额原型式继承都是在对象上的继承,同理我们可以把它们映射到构造函数上,即我们的寄生组合式继承

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

Person.prototype.running = function () {
  console.log("running");
};

Person.prototype.eating = function () {
  console.log("eating");
};

function Student(name, age, friends, sno, score) {
  Person.call(this, name, age, friends);
  this.sno = sno;
  this.score = score;
}

Student.prototype = Object.create(Person.prototype);
// 为了保证类型是Student
Student.prototype.constructor = Student;

Student.prototype.studying = function () {
  console.log("studying");
};

var stu3 = new Student("beanbag", 18, ["bean", "bag"], "001", 100);
console.log(stu3);
  • 这里要注意一下类型的问题,它的类型实际上找的是constructor.name属性,这里由于我们将 Student 的原型指向了 Person 对象的原型,所以在找 stu3 的时候,去找__proto__,找到 Person 的 constructor,所以需要手动修复一下
  • 修复方式:
  1. Student.prototype.constructor = Student;
Object.defineProperty(Student.prototype, "constructor", {
  enumerable: false,
  configurable: true,
  writable: true,
  value: Student,
});
  • 还有一个问题,在实际开发中,如果每次要继承都得重复写这些代码,一般会封装一个工具函数

实现

// 两个工具函数
function createObject(o) {
  function Fn() {}
  Fn.prototype = o;
  return new Fn();
}

function inheritPrototype(subType, superType) {
  // var prototype = Object.create(superType.prototype);
  subType.prototype = createObject(superType.prototype);
  Object.defineProperty(subType.prototype, "constructor", {
    enumerable: false,
    configurable: true,
    writable: true,
    value: subType,
  });
}

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

Person.prototype.running = function () {
  console.log("running");
};

Person.prototype.eating = function () {
  console.log("eating");
};

function Student(name, age, friends, sno, score) {
  Person.call(this, name, age, friends);
  this.sno = sno;
  this.score = score;
}

inheritPrototype(Student, Person);

Student.prototype.studying = function () {
  console.log("studying");
};

var stu3 = new Student("beanbag", 18, ["bean", "bag"], "001", 100);
console.log(stu3);

对象的方法补充

  1. hasOwnProperty

    • 对象是否有某一个属于自己的属性(不是在原型上的属性)
  2. in/for in 操作符

    • 判断某个属性是否在某个对象或者对象的原型上
  3. instanceof

    • 用于检测构造函数的 prototype,是否出现在某个实例对象的原型链上
  4. isPrototypeOf

    • 用于检测某个对象,是否出现在某个对象的原型链上
var obj = {
  name: "beanbag",
  age: 18,
};

var info = Object.create(obj, {
  address: {
    value: "深圳市",
    enumerable: true,
  },
});

// hasOwnProperty方法判断
console.log(info.hasOwnProperty("address")); // true
console.log(info.hasOwnProperty("name")); //false

// in操作符 (不管在当前对象还是原型中,只要存在都返回true)
console.log("address" in info); // true
console.log("name" in info); // true

// for in
for (var key in info) {
  console.log(key);
}
// instanceof
function Person() {}

function Student() {}

inheritPrototype(Student, Person);

var stu = new Student();

// Person的prototype是否出现在stu的原型链上
console.log(stu instanceof Student); // true
console.log(stu instanceof Person); // true
console.log(stu instanceof Object); // true
// isPrototypeOf
function Person() {}

var p = new Person();

console.log(Person.prototype.isPrototypeOf(p)); // true

var obj = {
  name: "beanbag",
  age: 18,
};

var info = Object.create(obj);

console.log(obj.isPrototypeOf(info)); // true

原型继承关系

原型继承关系图

乍一看上面这张图有点复杂,仔细分析一下其实还好:

  • 首先,从最左边开始看,f2 和 f1 是通过new Foo()出来的,所以 f2 和 f1 的隐式原型属性(__proto__)和构造函数 Foo 的显示原型属性(prototype)指向的是 Foo 的原型对象,这点没什么好讲的就是前面的内容
  • Foo 的原型对象有一个 constructor 属性指向 Foo 构造函数本身,接下来分析这个原型对象
    • 因为它是一个对象,所以也有一个隐式原型属性(__proto__),指向谁呢,因为它是通过 new Object 创建的,所以它的隐式原型属性和 Object 的显示原型属性指向一致,即 Object 的原型对象
  • Object 的原型对象即是原型链的尽头,它是一个对象,所以有隐式原型属性,指向的就是 null;
  • 再来看 Object 的构造函数,它是一个函数,所以它的显示原型属性(prototype)指向的就是 Object 构造函数的原型对象;
  • 那么 Object 的构造函数是怎么被创造出来的呢? 显然它是通过 new Function() 创造出来的 ,所以某种意义上,它又是一个对象,以它也有一个隐式原型对象(__proto__) 指向的就是 Function 构造函数的原型对象,注意一下这点就行
  • 同理,Function 构造函数它也是一个对象,它是通过 new 它自己被创建的,所以它的隐式原型属性(__proto__)执行的是它自己的原型对象

这里比较让人疑惑的就是 Object 构造函数的隐式原型是 Function 构造函数的原型,,它的显示原型属性是 Object 构造函数的原型对象,并且 Function 原型对象的隐式原型属性是 Object 构造函数的原型

Last Updated:
Contributors: hqchqc