完全理解Javascript中的原型链与继承

JavaScript语言不像面向对象的编程语言中有类的概念,因此也就没有类之间直接的继承,JavaScript中只有对象,使用函数模拟类,基于对象之间的原型链来实现继承关系,
ES6的语法中新增了class关键字,但也只是语法糖,内部仍是经过函数和原型链来对类和继承进行实现。编程

1 原型链

1.1 原型链定义

JavaScript对象上都有一个内部指针[[Prototype]],指向它的原型对象,而原型对象的内部指针[[Prototype]]也指向它的原型对象,直到原型对象为null,这样造成的链条就称为原型链。数组

这样在访问对象的属性时,会如今本身的属性中查找,若是不存在则会到上一层原型对象中查找。编程语言

注意:
根据 ECMAScript 标准,someObject.[[Prototype]] 符号是用于指派 someObject 的原型。这个等同于 JavaScript 的 proto 属性(现已弃用)。从 ECMAScript 6 开始, [[Prototype]] 能够用Object.getPrototypeOf()和Object.setPrototypeOf()访问器来访问。函数

例如:学习

var obj2 = {
    height: 170
}
var obj3 = {
    name: 'obj3'
}
Object.setPrototypeOf(obj3, obj2);
console.log(obj3.height); // 170
var isproto = Object.getPrototypeOf(obj3) === obj2;
console.log(isproto); // true

1.2 不一样方法建立对象与生成原型链

1.2.1 使用 Object.create 建立对象

ECMAScript 5 中引入了一个新方法:Object.create()。能够调用这个方法来建立一个新对象。新对象的原型就是调用 create 方法时传入的第一个参数。this

例如:spa

var a = {a: 1};
// a ---> Object.prototype ---> null
 
var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (继承而来)
 
var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null
 
var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined, 由于d没有继承Object.prototype

1.2.2 使用构造函数建立对象

在 JavaScript 中,构造函数其实就是一个普通的函数,通常函数名首字母大写。当使用 new 操做符 来做用这个函数时,它就能够被称为构造方法(构造函数)。
例如:prototype

function Person (name, age) {
    this.name = name;
    this.age = age;
}
 
Person.prototype = {
    sayName: function () {
        console.log(this.name);
    }
}
var person1 = new Person('yangyiliang', 23);
person1.sayName(); // yangyiliang

使用构造函数建立对象,经历了以下三个关键步骤:设计

var temp = {}; //1  建立空对象
Person.call(temp, 'yangyiliang', 23); //2  以空对象为this执行构造函数
Object.setPrototypeOf(temp, Person.prototype); //3  将构造函数的prototype 设置为空对象的原型
return temp;

1.2.3 使用字面量方法建立对象

使用字面量方法建立的对象,根据对象的类型,他们的原型都会指向相应JavaScript内置构造函数的prototype,和直接使用内置构造函数建立对象生成的原型链相同,例如:指针

var o = {a: 1};
 
// o这个对象继承了Object.prototype上面的全部属性
// 因此能够这样使用 o.hasOwnProperty('a').
// hasOwnProperty 是Object.prototype的自身属性。
// Object.prototype的原型为null。
// 原型链以下:
// o ---> Object.prototype ---> null
 
var a = ["yo", "whadup", "?"];
 
// 数组都继承于Array.prototype
// (indexOf, forEach等方法都是从它继承而来).
// 原型链以下:
// a ---> Array.prototype ---> Object.prototype ---> null
 
function f(){
  return 2;
}
 
// 函数都继承于Function.prototype
// (call, bind等方法都是从它继承而来):
// f ---> Function.prototype ---> Object.prototype ---> null

2 继承

在面向对象的语言当中,继承关系应该指的是父类和子类之间的关系,子类继承父类的属性和方法,在JavaScript当中是父构造函数和子构造函数之间的关系。

类自己是对象的抽象形式,类的使用价值最后也是在于经过它可以建立对象,
因此子类可以继承父类的属性和方法的意义,就是经过子类建立出来的对象可以继承经过父类建立出来的对象的属性和方法。

clipboard.png

而这种对象之间的继承关系,就是经过原型链实现。

在1.2.2节中,咱们学习到了经过构造函数建立对象的三个重要步骤,其中的一步是把构造函数的prototype对象设置为建立对象的原型。

所以咱们将父类的实例对象做为子类的prototype即可以达到继承的目的,以下图所示:

继承的实现

function Person (name, age) {
    this.name = name;
    this.age = age
}
 
Person.prototype.sayName = function () {
    console.log('my name is ' + this.name);
}
 
function Student (name, age, school) {
    Person.call(this, name, age);
    this.school = school;
}
 
Student.prototype = Object.create(Person.prototype);
 
Student.prototype.saySchool = function () {
    console.log('my school is ' + this.school);
}

上面代码实现的继承,遵循了几个原则:

  • 一、由于构造函数建立的对象将公用同一个原型,因此将每一个对象独有的属性写在构造函数中,将对象之间能够公用的方法写在构造函数的prototype中,也就是对象的原型中
  • 二、子构造函数继承父构造函数作了两个地方的工做,一是在子构造函数中利用call,调用父构造函数的方法,二是利用Object.create方法建立一个以父构造函数的prototype为原型的对象。

利用Object.create而不是直接用new 建立一个实例对象的目的是,减小一次调用父构造函数的执行。

  • 三、先经过prototype属性指向父构造函数的实例,而后再向prototype添加想要放在原型上的方法。

最后上一张js高级程序设计第三版中的一张源于原型链继承的图

clipboard.png

利用class实现继承

下面利用ES6引入的新语法糖,class、extends关键字对上述实现继承的代码进行改写:

class Person {
    constructor (name, age) {
        this.name = name;
        this.age = age;
    }
 
    sayName () {
        console.log('my name is ' + this.name);
    }
}
 
class Student extends Person {
    constructor (name, age, school) {
        super(name, age);
        this.school = school;
    }
 
    saySchool () {
        console.log('my school is ' + this.school);
    }
}
  • class里的constructor 对应原来的构造函数
  • class里面的其余方法都是写在原来构造函数的prototype中的
  • 子类直接经过extends 关键字进行继承
  • 子类中能够经过super来调用父类中的方法

本文部份内容来自 https://developer.mozilla.org...

后续

function A() {
}
//函数默认会有一个prototype对象而且具备constructor属性指向他自己

var a = new A()

a instanceof A
function A() {
}

function B() {
}

var proto = {}

B.prototype = proto

A.prototype = proto
var a = new A()
a instanceof B //true
a instanceof Object //true

instanceof  是遍历a 的原型链 寻找是否有和 B.prototype  是同一个对象的__proto__  若是找到就为true
相关文章
相关标签/搜索