Table of Contents generated with DocToc (opens new window)
# JavaScript原型链和继承
# JavaScript原型链
阮一峰老师的《undefined与null的区别》 (opens new window):
null表示"没有对象",即此处不应该有值.
undefined表示"缺少值",即此处应该有一个值,但是还没有定义.
# 什么是原型链
原型链是一种机制,指的是JavaScript实例对象(object)包括原型对象都有一个内置的__proto__属性指向创建它的构造函数的原型对象,即prototype属性.

# prototype属性
可以用prototype访问的只有function类型,其他类型只能用getPrototypeOf或者_proto_,其他类型也都是通过function生成的(String,Number……涉及到隐式创建对象)
来验证一下
const obj = {
name: 'zs',
sayHi: function () {
console.log(`Hello ${this.name}`)
}
}
obj.sayHi() // Hello zs
const me = Object.create(obj)
obj.name = 'ls'
obj.sayHi() // Hello ls
me.sayHi() // Hello ls
console.log(obj === me.__proto__) // true
console.log(obj.prototype === me.__proto__) // false
2
3
4
5
6
7
8
9
10
11
12
13
对象的原型只是一个引用,指向另外一个对象。对象原型之间的嵌套组成了原型链,原型链的作用是维护访问对象属性的查询,确定访问权限。
# setPrototypeOf 与 Object.create区别
mdn (opens new window)关于Object.setPrototypeOf()的解释:
Object.setPrototypeOf() 方法设置一个指定的对象的原型 ( 即, 内部[[Prototype]]属性)到另一个对象或 null。
Object.setPrototypeOf(A.prototype,B.prototype)1即让A的原型指向B
mdn (opens new window)关于Object.create()的解释:
**
Object.create()**方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。
Object.create就是将新对象的__proto__指向括号内参数A.prototype = Object.create(A.prototype)1新创建的A的原型指向B.prototype
具体的区别可以看这篇文章 (opens new window)
# new关键字
mdn (opens new window)关于new关键字进行的操作解释:
- 创建一个空的简单JavaScript对象(即**
{}**);- 链接该对象(即设置该对象的构造函数)到另一个对象 ;
- 将步骤1新创建的对象作为**
this**的上下文 ;- 如果该函数没有返回对象,则返回**
this**。
var a = new A()
代码运行的时候,内部实际执行的是
// 1.首先创建一个空对象
var o = new Object()
// 2.将空对象的原型赋值为构造函数的原型
o._proto_ = A.prototype
// 3.更改构造函数内部this,指向新创建的空对象
A.call(o)
2
3
4
5
6
最后返回,返回的时候会进行一个判断,如果构造函数(这里即A)设置了返回值,并且返回值是一个Object类型的话,直接就返回该Object,否则默认返回新创建的空对象(即o)
function Super(name, age) {
this.name = name
this.age = age
return { name: 'ls', age: 20 }
}
const su = new Super('zs', 10)
console.log(su) // {name: "ls", age: 20}
2
3
4
5
6
7
8
结论: 通过new操作符,Super创建出来的实例可以访问到构造函数中的属性和构造函数原型链中的属性.实例与构造函数通过原型链连接了起来
# ES5和ES6的继承
阮一峰《构造函数的继承》 (opens new window)
# es5继承
es5之前即构造函数的继承
- 构造函数继承
- prototype模式
- 直接继承prototype,即原型替换
- 利用空对象作为中介
- 拷贝继承
# prototype模式

function Super() {}
function Sub() {}
Sub.prototype = new Super() // 继承了Super
/* 继承是通过创建Super的实例,并将该实例赋给Sub.prototype实现的,本质是重写原型对象,用一个新类型
的实例替换,此时Sub.prototype.constructor是指向Super的 */
Sub.prototype.constructor == Super // true
Sub.prototype.constructor = Sub // Super => Sub.prototype => Sub
/* 任何一个prototype对象都有一个constructor属性,指向它的构造函数。
如果没有"Sub.prototype = new Super();"这一行,Sub.prototype.constructor是指向Sub的;
加了这一行以后,Sub.prototype.constructor指向Super。所以需要让Sub.prototype.constructor重新指向Sub*/
var p = new Sub()
Sub.prototype.constructor === Sub // true
p.constructor === Sub // true
p.__proto__ === Sub.prototype // true
Sub.prototype.__proto__ == Super.prototype // true
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
es5中这种继承,实质上是将子类的原型设置为父类的实例。
# es6继承
es6中的class语法其实只是语法糖,本质上还是通过原型链来继承,只是方便了书写
示例
class Animal {
constructor() {
this.species = "动物";
}
sayHi() {
return console.log("hi");
}
}
class Cat extends Animal {
constructor(name, color) {
super();
this.name = name;
this.color = color;
}
}
var animal = new Animal();
var cat1 = new Cat("大毛", "黄色");
Animal.prototype.sayHi(); //hi
animal.sayHi(); //hi
cat1.sayHi(); //hi
console.log(cat1.species); // 动物

class Super {}
class Sub extends Super {}
const p = new Sub()
Sub.prototype.constructor === Sub // true
p.constructor === Sub // true
p.__proto__ === Sub.prototype // true
Sub.__proto__ === Super // true
Sub.prototype.__proto__ === Super.prototype // true
2
3
4
5
6
7
8
9
10
11
12
es6规定:子类的_proto_属性指向父类,这是es5中没有的操作
es6 extends关键字 (opens new window)
上面的代码通过babel转译后多出了_inherits和_createClass函数。
// _inherits
function _inherits(subClass, superClass) {
if (typeof superClass !== 'function' && superClass !== null) {
throw new TypeError('Super expression must either be null or a function')
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: { value: subClass, writable: true, configurable: true }
})
if (superClass) _setPrototypeOf(subClass, superClass)
}
2
3
4
5
6
7
8
9
10
# es6和es5的转化
关于es6 class是es5 原型的语法糖
class Person {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
}
let a = new Person('seven');
console.log(a.name);
console.log(a.getName());
console.log(Object.getPrototypeOf(a) === Person.prototype);
2
3
4
5
6
7
8
9
10
11
12
用Babel转成es5
"use strict";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
var Person = /*#__PURE__*/function () {
function Person(name) {
_classCallCheck(this, Person);
this.name = name;
}
_createClass(Person, [{
key: "getName",
value: function getName() {
return this.name;
}
}]);
return Person;
}();
var a = new Person('seven');
console.log(a.name);
console.log(a.getName());
console.log(Object.getPrototypeOf(a) === Person.prototype);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# class 语法糖
- es5 function 写法
function Father(name = 'mr', age = 18) {
this.name = name
this.age = age
}
Father.prototype.sayHi = function () {
console.log('Hi')
}
const fa = new Father('zs', 20)
// console.log(fa) // Father { name: 'zs', age: 20 }
// fa.sayHi() // Hi
function Son(sex = 1) {
this.sex = sex
}
Son.prototype = new Father() // Son.prototype == Father的实例, 构成继承关系
// 所以 Faher的实例的方法,例如sayHi() Son.prototype 也有
/* 继承是通过创建Super的实例,并将该实例赋给Sub.prototype实现的,本质是重写原型对象,用一个新类型
的实例替换,此时Son.prototype.constructor是指向Father的 */
// Son.prototype.sayHi() // Hi
Son.prototype.constructor = Son // 让Son.prototype.constructor重新指向Son
const p = new Son(0)
console.log(p.name)
// 到目前为止,通过new Son构造出来的实例还是只能拥有自己的属性
// 如果想要继承父类的属性,需要指定子类的constructor =
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- es6 class
class Father {
constructor(name, age) {
this.name = name
this.age = age
}
sayHi() {
console.log('Hi')
}
}
const fa = new Father('zs', 20)
console.log(fa) // Father { name: 'zs', age: 20 }
fa.sayHi() // Hi
2
3
4
5
6
7
8
9
10
11
12
13
# constructor 方法
constructor 方法是类的构造函数,是一个默认方法,通过 new 命令创建对象实例时,自动调用该方法。一个类必须有 constructor 方法,如果没有显式定义,一个默认的 consructor 方法会被默认添加。所以即使你没有添加构造函数,也是会有一个默认的构造函数的。一般 constructor 方法返回实例对象 this ,但是也可以指定 constructor 方法返回一个全新的对象,让返回的实例对象不是该类的实例。
# super 关键字
引用MDN (opens new window)的说法:
super关键字用于访问和调用一个对象的父对象上的函数。
在构造函数中使用时,
super关键字将单独出现,并且必须在使用this关键字之前使用。super关键字也可以用来调用父对象上的函数。
← 作用域与闭包 相等性判断与隐式转换 →