原型链
-
构造函数 / 原型 / 实例
的关系 - 每个构造函数 (constructor) 都有一个原型对象 (prototype),原型对象都包含一个指向构造函数的指针,而实例(instance) 都包含一个指向原型对象内部的指针
- 如果试图使用某个对象 (实例) 的某个属性或方法,会首先在对象内部寻找该属性,如果找不到去该对象的原型(instance.prototype)里面去找,如果还找不到就继续沿着
__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 的继承不行
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,结果如下
// 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
: 判断对象是不是某个构造函数的实例