原型与继承
原型
我们创建的每一个函数,都可以有一个prototype属性,该属性指向一个对象。这个对象,就是原型。 实例化的对象,都有一个__proto__属性
当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象( object )都有一个私有属性(称之为 proto )指向它的构造函数的原型对象(prototype )。该原型对象也有一个自己的原型对象( proto ) ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。
原型链
是一种关系,实例对象和原型对象之间的关系,通关系是通过过原型(proto)来联系的
继承方式
- 原型继承
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
2
3
4
5
6
7
8
9
10
11
12
13
缺点:子类无法通过父类创建私有属性
- 借用构造函数继承
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
2
3
4
5
6
7
8
9
10
11
12
13
缺点:不能继承构造函数的原型上的属性和方法
- 组合继承 : 原型继承 + 借用构造函数继承
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
2
3
4
5
6
7
8
9
10
11
12
13
14
缺点:不够优雅
- 寄生组合继承
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
知识点
- 构造函数中的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
2
3
4
5
6
7
8
9
10
11
- 面向对象编程思想:根据需求,分析对象,找到对象有什么特征和行为,通过代码的方式来实现需求,要想实现需求,就要创建对象,要想创建对象,就应该先有构造函数,然后通过构造函数来创建对象,对象通过调用属性和方法来实现需求
使用不同方式创建对象和生成原型链
- 对象字面量创建的对象
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- 使用构造器创建的对象
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
2
3
4
5
6
7
8
9
10
11
12
13
14
- 使用 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
2
3
4
5
6
7
8
9
10
11
12
13
- 使用 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
用于拓展原型链的方法
下面列举四种用于拓展原型链的方法,以及他们的优势和缺陷。下列四个例子都创建了完全相同的 inst 对象(所以在控制台上的输出也是一致的),为了举例,唯一的区别是他们的创建方法不同。
- 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
2
3
4
5
6
7
8
9
10
11
- 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
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- 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
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
2
3
4
5
6
7
8
9
10
11
12
13
14
- 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
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
2
3
4
5
6
7
8
9
10
11