原型链
-
构造函数/原型/实例
的关系 -
每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,实例有一个指向原型对象的指针
构造函数 --(prototype)--> 原型对象 --(constructor)--> 构造函数
构造函数 --(new 操作符)--> 实例对象 --(constructor)--> 构造函数
实例对象 --(__proto__)--> 原型对象
- 如果试图使用某个对象(实例)的某个属性或方法,会首先在对象内部寻找该属性,如果找不到去该对象的原型(
instance.__proto__
)里面去找,如果还找不到就继续沿着__proto__
这个链条往上找,直到找到Object.prototype
为止 - javascript 里面一切皆对象,所以都可以从这个链条去出发
- JavaScript 的继承不同于传统面向对象是靠类实现继承,而是通过原型链实现继承
ES5 继承
- 拷贝式继承(通过深拷贝实现继承)
-
原型式继承
- 缺点:只能继承原型方法
-
借用构造函数继承
- 缺点:只能继承实例属性
-
组合式继承
- 缺点:无论在什么情况下,都会调用两次构造函数(创建父类实例的时候,在子类构造函数内不调用父类构造函数时)
- 组合寄生式继承 (比较完美的继承,但不能继承父类静态方法、静态属性)
function Parent() {}
function Child() {
// 继承父类实例属性
Parent.call(this) // 如果父类有参数写在 this 后面
}
// 继承父类原型方法
Child.prototype = Object.create(Parent.prototype)
// 修正子类原型的 constructor 指向
Child.prototype.constructor = Child
两个注意点:
-
Object.create(proto, [propertiesObject])
MDN创建一个新对象,使用现有的对象来提供新创建的的对象的 __proto__ Object.create(null) // 创建一个没有原型的空对象 第二个参数可添加属性描述符 js高级程序设计用一下代码代替的这个方法 function createObject(P) { var F = function() {} F.prototype = P.prototype return new F() }
-
为什么要修正子类原型的 constructor 指向? 阮一峰
简单总结一下: 任何一个 prototype 对象都有一个 constructor 属性,指向它的构造函数 更重要的是,每一个实例也有一个 constructor 属性,默认调用 prototype 的 constructor 属性 如果没有修正的那行代码,结果如下 var c = new C() c.constructor === Child // false Child.prototype.constructor === Child // false c.constructor === Parent // true Child.prototype.constructor === Parent // true 这显然会导致继承链的混乱(c 明明是用构造函数Child生成的),因此我们必须手动纠正
ES6 继承
ES6 的继承本质上还是借助原型链实现继承
// 借助 class extends 关键字
class Parent {
static sayAge() {
return '18'
}
constructor(name) {
this.name = 'name'
}
}
class Child extends Parent {
constructor(name, age) {
/**
* 如果写 constructor 必须调用 super 方法,这是因为子类自己的 this 对象,必须先通过父类构造函数完成塑造
* 不写 super 就得不到 this对象,new 的时候就会报错
* Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
*
* ES5 实质上先创造子类的实例对象 this,然后再将父类的方法添加到 this 上面 (Parent.call(this))
* ES6 实质上先将父类实例对象的属性和方法,加到 this 上面(必须先调用 super 方法),然后用子类构造函数修改 this
*/
super(name, age)
this.age = age
}
}
// 注意点:es6 的继承可以继承父类的静态方法和静态属性,而ES5的继承不行
// 经过 extends 之后,Child.__proto__ ==== Parent // true
// Parent.__proto__ 返回 f() { [native code] }
// Parent.__proto__.__proto__ === Object.prototype
Babel 转码后的 ES6 继承代码
// 为方便观看,对代码做了一些美化和省略处理
"use strict";
// 检查子类是否调用了 super 方法
function _possibleConstructorReturn(self, call) {
if (call && (_typeof(call) === "object" || typeof call === "function")) {
return call;
}
return _assertThisInitialized(self);
}
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError(
"this hasn't been initialised - super() hasn't been called"
);
}
return self;
}
// 获取子类的原型链指向的对象即父类
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf
? Object.getPrototypeOf
: function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
// 核心继承方法
function _inherits(subClass, superClass) {
// ...
// 同 es5 继承的那一段
subClass.prototype = Object.create(superClass.prototype, {
constructor: {
value: subClass, // 修正 constructor 指向
writable: true,
configurable: true
}
});
// 实现静态属性和方法的继承 原理为:Child.__proto__ = Parent
// 即子类(子类现在相当于实例)的在往上的 prototype = Parent,即子类可以使用父类的静态属性和方法
if (superClass) _setPrototypeOf(subClass, superClass);
}
function _setPrototypeOf(o, p) {
_setPrototypeOf =
Object.setPrototypeOf ||
function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
// ... 省略类的创建及检测等方法
var Parent =
/*#__PURE__*/
(function() {
_createClass(Parent, null, [
{
key: "sayAge",
value: function sayAge() {
return "18";
}
}
]);
function Parent(name) {
_classCallCheck(this, Parent);
this.name = "name";
}
return Parent;
})();
var Child =
/*#__PURE__*/
(function(_Parent) {
_inherits(Child, _Parent);
function Child(name, age) {
var _this;
_classCallCheck(this, Child);
// 下面一行代码即 调用 super 的效果,如果不调用 super 子类将没有 this
_this = _possibleConstructorReturn(this, _getPrototypeOf(Child).call(this, name, age));
/***
* 如果注释源码中的 super 将编译为如下代码
* _this.age = age;
* return _possibleConstructorReturn(_this);
* 因为没有调用 super 子类还没有 this,所以下一行直接报错
*
* 如果源码中不写 _this.age = age
* 将直接进入 _assertThisInitialized 方法,然后报错,没有调用 super 方法
*/
_this.age = age;
return _this;
}
return Child;
})(Parent);
var c = new Child();
// 如果不用 babel转码,直接在浏览器里运行,不写 super,结果如下和 babel 转义后报错信息不一样
// 原生报错信息: Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
- 原生构造函数不可通过
extends
实现继承 ES6阮一峰 - 因为子类拿不到 原生父类内部的对象,即是通过
call
也不行
new 运算符
上面说了继承,那产生实例的操作符 new
是什么原理?
var obj = {}
obj.__proto__ = Child.prototype
F.call(obj)
// 1. 创建一个空对象
// 2. 将这个空对象的 __proto__ 属性 指向了构造函数的 prototype 属性 上 ==> 继承原型属性方法
// 3. 将构造函数的 this 指针替换成了 obj(实例),再调用 构造函数 ===> 继承实例属性方法
与原型链有关的几个方法
-
hasOwnProperty
: 该方法只会查找对象本身是否有某属性,不会去原型链上寻找 -
A.isPropertyOf(instanceA)
: 判断 A 是不是 instanceA 的原型对象 -
instanceof
: 判断对象是不是某个构造函数的实例 -
__proto__
只是浏览器厂商的私有实现,规范并不支持,规范支持Object.getPrototypeOf 和 Object.setPrototypeOf
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。