原型与继承

原型

我们创建的每一个函数,都可以有一个prototype属性,该属性指向一个对象。这个对象,就是原型。 实例化的对象,都有一个__proto__属性

当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象( object )都有一个私有属性(称之为 proto )指向它的构造函数的原型对象(prototype )。该原型对象也有一个自己的原型对象( proto ) ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。

原型链

是一种关系,实例对象和原型对象之间的关系,通关系是通过过原型(proto)来联系的

继承方式

  1. 原型继承
function Person(name,age){
    this.name = name;
    this.age = age;
}
Person.prototype.eat = function(){
    console.log('eat');
}  
function Child(sex){
    this.sex = sex;
}
Child.prototype = new Person('xinxin',18);
var oChild = new Child('女');
console.log('oChild',oChild.name); // xinxin
1
2
3
4
5
6
7
8
9
10
11
12
13

缺点:子类无法通过父类创建私有属性

  1. 借用构造函数继承
function Person(name){
    this.name = name;
}  
Person.prototype.eat = function(){
    console.log('eat');
}  
function Child(sex,name){
    Person.call(this,name);
    this.sex = sex;
}
var oChild = new Child('女','xinxin');
console.log(oChild.name); // xinxin
console.log(oChild.eat());// Uncaught TypeError: oChild.eat is not a function
1
2
3
4
5
6
7
8
9
10
11
12
13

缺点:不能继承构造函数的原型上的属性和方法

  1. 组合继承 : 原型继承 + 借用构造函数继承
function Person(name){
    this.name = name;
}  
Person.prototype.eat = function(){
    console.log('eat');
}  
function Child(sex,name){
    Person.call(this,name);
    this.sex = sex;
}
Child.prototype = new Person();
var oChild = new Child('女','xinxin');
console.log(oChild.name); // xinxin
oChild.eat()// eat
1
2
3
4
5
6
7
8
9
10
11
12
13
14

缺点:不够优雅

  1. 寄生组合继承
function Person(name){
    this.name = name;
}  
Person.prototype.eat = function(){
    console.log('eat');
}  
function Child(sex,name){
    Person.call(this,name);
    this.sex = sex;
}
Child.prototype = Object.create(Person.prototype);
Child.prototype.constructor = Child;
var oChild = new Child('女','xinxin');
console.log(oChild.name); // xinxin
oChild.eat()// eat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  1. es6 class extends继承

constructor中的为父类构造函数,类中的方法为父类构造函数原型上的方法,方法前面加上static关键字的,表示为不能为实例对象共享的方法。

class Point {
    constructor(x, y) {
        this.x = x
        this.y = y
    }
    
    toString() {
        return this.x + '' + this.y
    }
}

class ColorPoint extends Point {
    constructor(x, y, color) {
        super(x, y) //调用父类的constructor(x, y)
        this.color = color
    }
    
    toString() {
        return this.color + ' ' + super.toString() // 调用父类的toString()
    }
}

var colorPoint = new ColorPoint('1', '2', 'red')

console.log(colorPoint.toString())  // red 12
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

知识点

  1. 构造函数中的this就是实例对象,原型对象中方法中的this就是实例对象
function Person(name){
  console.log(this);
  this.name = name;
}
Person.prototype.eat = function(){
  console.log(this;)
}
var per = new Person('xinxin');
console.log(per);

// 三者打印出的结果是一样的
1
2
3
4
5
6
7
8
9
10
11
  1. 面向对象编程思想:根据需求,分析对象,找到对象有什么特征和行为,通过代码的方式来实现需求,要想实现需求,就要创建对象,要想创建对象,就应该先有构造函数,然后通过构造函数来创建对象,对象通过调用属性和方法来实现需求

使用不同方式创建对象和生成原型链

  1. 对象字面量创建的对象
var o = {a: 1};

// o 这个对象继承了 Object.prototype 上面的所有属性
// o 自身没有名为 hasOwnProperty 的属性
// hasOwnProperty 是 Object.prototype 的属性
// 因此 o 继承了 Object.prototype 的 hasOwnProperty
// Object.prototype 的原型为 null
// 原型链如下:
// o ---> Object.prototype ---> null

var a = ["yo", "whadup", "?"];

// 数组都继承于 Array.prototype 
// (Array.prototype 中包含 indexOf, forEach 等方法)
// 原型链如下:
// a ---> Array.prototype ---> Object.prototype ---> null

function f(){
  return 2;
}

// 函数都继承于 Function.prototype
// (Function.prototype 中包含 call, bind等方法)
// 原型链如下:
// f ---> Function.prototype ---> Object.prototype ---> null
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
  1. 使用构造器创建的对象
function Graph() {
  this.vertices = [];
  this.edges = [];
}

Graph.prototype = {
  addVertex: function(v){
    this.vertices.push(v);
  }
};

var g = new Graph();
// g 是生成的对象,他的自身属性有 'vertices' 和 'edges'。
// 在 g 被实例化时,g.[[Prototype]] 指向了 Graph.prototype。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  1. 使用 Object.create 创建的对象
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
3
4
5
6
7
8
9
10
11
12
13
  1. 使用 class 关键字创建的对象
"use strict";

class Polygon {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

class Square extends Polygon {
  constructor(sideLength) {
    super(sideLength, sideLength);
  }
  get area() {
    return this.height * this.width;
  }
  set sideLength(newLength) {
    this.height = newLength;
    this.width = newLength;
  }
}

var square = new Square(2);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

用于拓展原型链的方法

下面列举四种用于拓展原型链的方法,以及他们的优势和缺陷。下列四个例子都创建了完全相同的 inst 对象(所以在控制台上的输出也是一致的),为了举例,唯一的区别是他们的创建方法不同。

  1. New-initialization
function foo(){}
foo.prototype = {
  foo_prop: "foo val"
};
function bar(){}
var proto = new foo;
proto.bar_prop = "bar val";
bar.prototype = proto;
var inst = new bar;
console.log(inst.foo_prop);
console.log(inst.bar_prop);
1
2
3
4
5
6
7
8
9
10
11
  1. Object.create
function foo(){}
foo.prototype = {
  foo_prop: "foo val"
};
function bar(){}
var proto = Object.create(
  foo.prototype
);
proto.bar_prop = "bar val";
bar.prototype = proto;
var inst = new bar;
console.log(inst.foo_prop);
console.log(inst.bar_prop);
1
2
3
4
5
6
7
8
9
10
11
12
13
function foo(){}
foo.prototype = {
  foo_prop: "foo val"
};
function bar(){}
var proto = Object.create(
  foo.prototype,
  {
    bar_prop: {
      value: "bar val"
    }
  }
);
bar.prototype = proto;
var inst = new bar;
console.log(inst.foo_prop);
console.log(inst.bar_prop)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  1. Object.setPrototypeOf
function foo(){}
foo.prototype = {
  foo_prop: "foo val"
};
function bar(){}
var proto = {
  bar_prop: "bar val"
};
Object.setPrototypeOf(
  proto, foo.prototype
);
bar.prototype = proto;
var inst = new bar;
console.log(inst.foo_prop);
console.log(inst.bar_prop);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function foo(){}
foo.prototype = {
  foo_prop: "foo val"
};
function bar(){}
var proto;
proto=Object.setPrototypeOf(
  { bar_prop: "bar val" },
  foo.prototype
);
bar.prototype = proto;
var inst = new bar;
console.log(inst.foo_prop);
console.log(inst.bar_prop)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  1. proto
function foo(){}
foo.prototype = {
  foo_prop: "foo val"
};
function bar(){}
var proto = {
  bar_prop: "bar val",
  __proto__: foo.prototype
};
bar.prototype = proto;
var inst = new bar;
console.log(inst.foo_prop);
console.log(inst.bar_prop);
1
2
3
4
5
6
7
8
9
10
11
12
13
var inst = {
  __proto__: {
    bar_prop: "bar val",
    __proto__: {
      foo_prop: "foo val",
      __proto__: Object.prototype
    }
  }
};
console.log(inst.foo_prop);
console.log(inst.bar_prop)
1
2
3
4
5
6
7
8
9
10
11

视频参考