原型链继承
方法:子构造函数的prototype指向为父构造函数的实例,因为原型链是proto的链表,父构造函数的实例的proto指向父构造函数实例的原型。
function Parent(){
this.name = 'johe'
}
Parent.prototype.getName = function(){
console.log(this.name)
}
function Child(){
}
//原型必须是对象,所以为Parent的实例
Child.prototype = new Parent()
var child1 = new Child()
child1.getName()
问题:
- 引用类型的属性被所有实例共享
- 创建Child的实例时,不能向parent传参
function Parent () {
this.names = ['kevin', 'daisy'];
}
function Child () {
}
Child.prototype = new Parent();
var child1 = new Child();
child1.names.push('yayu');
console.log(child1.names); // ["kevin", "daisy", "yayu"]
var child2 = new Child();
console.log(child2.names); // ["kevin", "daisy", "yayu"]
这是因为Parent在实例化之后,成为了Child的原型,原型上的属性和方法是共享的。
借用构造函数(经典继承)
调用父构造函数
function Parent(){
this.names = ['kevin','daisy'];
}
function Child(){
Parent.call(this);
}
var Child1 = new Child()
Child1.names.push('johe')
var Child2 = new Child()
//['kevin','daisy']
Child2.names
优点:
- 避免了引用类型的属性被所有实例共享
- 可以在Child中向Parent传参
缺点: - 方法都在构造函数中定义,每次创建实例都会创建一遍方法
- instanceof检验为false
function Parent(name){
this.name = name;
}
function Child(name){
Parent.call(this,name)
}
var Child1 = new Child('johe')
//johe
Child1.name
组合继承(原型链继承和经典继承)
优点:借用构造函数继承解决了传参问题和实例属性被共享的问题,原型链继承能够满足共享方法不被重复创建。
缺点:调用了两次父构造函数
function Parent(name){
this.name = name
this.names = ['johe']
}
Parent.prototype.getName = function(){
return this.name
}
function Child(name,age){
//调用父级的构造方法,实现实例属性
Parent.call(this,name)
this.age = age
}
//这里不用参数是因为子构造函数调用父构造函数时已实现实例属性,有实例属性的情况下不会从原型链中查找
Child.prototype = new Parent()
var child1 = new Child('johe',18)
child1.names.push("johe2")
console.log(child1.name);//johe
console.log(child1.age);18
//["johe","johe2"]
console.log(child1.names);
var child2 = new Child('child2',19)
console.log(child2.name);//child2
console.log(child2.age);19
//["johe"]
console.log(child1.names);
原型式继承(Object.create)
Object.create的模拟实现
function createObj(o){
function F();
F.protoype = o;
return new F()
}
缺点:
包含引用类型的属性值始终都会共享响应的值,这点跟原型链继承一样。
寄生组合式继承(组合继承优化)
组合继承的最大缺点就是会调用两次父构造函数
一次是设置子类型实例的原型:
Child.protoype = new Parent()
一次是创建子类型实例的时候:
function Child(name,age){
Parent.call(this,name)
}
var child1 = new Child('johe',18)
这个时候Child.prototype这个对象内的属性其实是没用的,因为子类型实例已经调用了父构造函数进行了属性实例化。
所以就用到了寄生组合式继承,让Child.prototype间接的访问到Parent.prototype
function Parent(name){
this.name = name
this.names = ['johe']
}
Parent.prototype.getName = function(){return this.name}
function Child(name,age){
Parent.call(this,name)
this.age = age
}
function F(){}
F.prototype = Parent.prototype
Child.prototype = new F()
var child1 = new Child('johe',18)
封装继承方法:
function Parent(name){
this.name = name
this.names = ['johe']
}
Parent.prototype.getName = function(){return this.name}
function Child(name,age){
Parent.call(this,name)
this.age = age
}
function createObject(o){
function F(){}
F.prototype = o
return new F();
}
function setPrototype(child,parent){
var prototype = createObject(parent)
prototype.constructor = Child
Child.protoype = prototype
}
setPrototype(Child,Parent)
这种方式的高效率体现它只调用了一次 Parent 构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式
这里为什么不直接使用Child.prototype=Parent.prototype,是因为Parent.constructor应该指向Parent,并且如果我们需要给Child实例的原型设置方法和属性时,会影响到Parent的实例,这明显是不合理的。
ES6的继承
ECMAScript6 引入了一套新的关键字用来实现 class。使用基于类语言的开发人员会对这些结构感到熟悉,但它们是不同的。JavaScript 仍然基于原型。这些新的关键字包括 class, constructor,static,extends 和 super。
class Fruits{
constructor(type,size){
this.type = type;
this.size = size;
}
static sayHello(){
console.log("hello");
}
sayType(){
console.log('my type is '+this.type);
return this.type;
}
}
class Banana extends Fruits{
constructor(type,size,color){
super(type,size);
this.color = color;
}
sayColor(){
console.log('my color is '+ this.color);
return this.color;
}
}
let fruit = new Fruits("fruit","small");
let banana = new Banana("banana","small","yellow");
//parent: Fruits {type:'fruit',size:'small'}
console.log('parent:',fruit);
//child: Banana {type:'banana',size:'small',color:'yellow' }
console.log('child:',banana);
//hello
console.log(Fruits.sayHello());
console.log(Banana.sayHello());
//true
console.log(Fruits.hasOwnProperty("sayHello"));
//false
console.log(Banana.hasOwnProperty("sayHello"));
//false
console.log(fruit.hasOwnProperty("sayType"))
//true
console.log(Fruits.prototype.hasOwnProperty("sayType"));
//my type is banana
console.log(banana.sayType());
//false
console.log(banana.hasOwnProperty("sayType"));
//Fruits {}
console.log(banana.__proto__);
首先查看class语法糖做了什么:
- 将构造函数内的this属性实例化
- 将构造函数外的非static函数给设置到构造函数的原型对象中,共享属性
- static函数设置为构造函数的属性
用ES5实现就是组合方式来创建对象:
function Fruits(type,size){
this.type = type;
this.size = size;
}
Fruits.sayHello = function(){
console.log('Hello');
}
Fruits.prototype = {
constructor:Fruits,
sayType:function(){
console.log('my type is' +this.type);
return this.type;
}
}
再看看extends做了什么:
- 继承父类的实例属性(调用父类的构造函数)
- 继承父类的共享属性(原型链上有父类的原型对象)
- 子类构造函数的proto指向父类构造函数(两个都是对象)(继承静态函数)
//true
console.log(Banana.__proto === Fruits);
//true
console.log(banana instance of Fruits);
es6继承的es5实现
知道extends做了什么之后,我们可以知道其转化成es5就是寄生组合式继承(组合=原型链继承+借用构造函数),并且设置子类构造函数的proto为父类构造函数.
function Fruits(type,size){
this.type = type;
this.size = size;
}
Fruits.sayHello = function(){
console.log('Hello');
}
Fruits.prototype = {
constructor:Fruits,
sayType:function(){
console.log('my type is' +this.type);
return this.type;
}
}
function Banana(type,size,color){
Fruits.call(this,type,size);
this.color = color;
}
function createObject(Parent){
function Empty(){};
Empty.prototype = Parent.prototype;
return new Empty();
}
Banana.prototype = createObject(Fruits);
Banana.prototype.constructor = Banana;
Banana.prototype.sayColor = function(){
console.log(this.color);
}
Banana.__proto__ = Fruits;
通过babeljs转码成ES5来查看,更严谨的实现。
//es6版本
class Parent{
constructor(name){
this.name = name;
}
static sayHello(){
console.log('hello');
}
sayName(){
console.log('my name is ' + this.name);
return this.name;
}
}
class Child extends Parent{
constructor(name, age){
super(name);
this.age = age;
}
sayAge(){
console.log('my age is ' + this.age);
return this.age;
}
}
// 对转换后的代码进行了简要的注释
"use strict";
// 主要是对当前环境支持Symbol和不支持Symbol的typeof处理
function _typeof(obj) {
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
_typeof = function _typeof(obj) {
return typeof obj;
};
} else {
_typeof = function _typeof(obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
}
return _typeof(obj);
}
// _possibleConstructorReturn 判断Parent。call(this, name)函数返回值 是否为null或者函数或者对象。
function _possibleConstructorReturn(self, call) {
if (call && (_typeof(call) === "object" || typeof call === "function")) {
return call;
}
return _assertThisInitialized(self);
}
// 如何 self 是void 0 (undefined) 则报错
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return self;
}
// 获取__proto__
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
// 寄生组合式继承的核心
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function");
}
// Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。
// 也就是说执行后 subClass.prototype.__proto__ === superClass.prototype; 这条语句为true
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
writable: true,
configurable: true
}
});
if (superClass) _setPrototypeOf(subClass, superClass);
}
// 设置__proto__
function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
// instanceof操作符包含对Symbol的处理
function _instanceof(left, right) {
if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
return right[Symbol.hasInstance](left);
} else {
return left instanceof right;
}
}
function _classCallCheck(instance, Constructor) {
if (!_instanceof(instance, 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;
}
// ES6
var Parent = function () {
function Parent(name) {
_classCallCheck(this, Parent);
this.name = name;
}
_createClass(Parent, [{
key: "sayName",
value: function sayName() {
console.log('my name is ' + this.name);
return this.name;
}
}], [{
key: "sayHello",
value: function sayHello() {
console.log('hello');
}
}]);
return Parent;
}();
var Child = function (_Parent) {
_inherits(Child, _Parent);
function Child(name, age) {
var _this;
_classCallCheck(this, Child);
// Child.__proto__ => Parent
// 所以也就是相当于Parent.call(this, name); 是super(name)的一种转换
// _possibleConstructorReturn 判断Parent.call(this, name)函数返回值 是否为null或者函数或者对象。
_this = _possibleConstructorReturn(this, _getPrototypeOf(Child).call(this, name));
_this.age = age;
return _this;
}
_createClass(Child, [{
key: "sayAge",
value: function sayAge() {
console.log('my age is ' + this.age);
return this.age;
}
}]);
return Child;
}(Parent);
var parent = new Parent('Parent');
var child = new Child('Child', 18);
console.log('parent: ', parent); // parent: Parent {name: "Parent"}
Parent.sayHello(); // hello
parent.sayName(); // my name is Parent
console.log('child: ', child); // child: Child {name: "Child", age: 18}
Child.sayHello(); // hello
child.sayName(); // my name is Child
child.sayAge(); // my age is 18
从babel转译我们可以认识到几点:
- 尽量使用Object.create来创造父构造函数的实例
//创建一个superClass的实例,constructor属性指向subClass
subClass.prototype = Object.create(superClass&&superClass.prototype,{
constructor:{
value:subClass,
writable:true,
configurable:trues
}
});
//替代方案
function Empty(){};
Empty.prototype = superClass.prototype;
subClass.protoype = new Empty();
subClass.protoype.constructor = subClass;
- 尽量使用Object.setPrototypeOf而不是proto
Object.setPrototypeOf(subClass,superClass);
//替代方案,不推荐
subClass.__proto__ = superClass.__proto__
模拟一下转换实现:
class Fruit{
constructor(name){
this.name = name;
}
static sayHello(){
console.log("hello");
}
sayName(){
console.log(this.name);
}
}
class Banana extends Fruit{
constructor(name,color){
super(name);
this.color = color;
}
sayColor(){
console.log(this.color);
}
}
//转换实现:
function createPropertiesByObject(target,props){
for(var i =0;i