原型链 JavaScript
主要通过原型链实现继承。原型链的构建是通过将一个类型的实例赋值给另一个构造函数的原型实现的
内部指针__proto__
遵循ECMAScript标准,someObject.[[Prototype]]
符号是用于指向 someObject
的原型;
这个等同于 JavaScript
的非标准但许多浏览器实现的属性 __proto__
;
从 ECMAScript 6 开始,[[Prototype]]
可以通过Object.getPrototypeOf()
和Object.setPrototypeOf()
访问器来访问;
但它不应该与构造函数 func
的 prototype
属性相混淆
被构造函数创建的实例对象的 [[prototype]]
指向 func
的 prototype
属性。
Object.prototype
属性表示Object
的原型对象。
基本概念
JavaScript 对象有一个私有属性(称之为 [[Prototype]]
),它指向它的 原型对象(prototype
);
该 prototype
对象 又具有一个自己的 prototype
,层层向上直到一个对象的原型为 null
,
根据定义,null
没有原型,并作为这个原型链中的最后一个环节
两种继承方式
接口继承:只继承方法签名(由于函数没有签名,ECMAScript
无法实现此继承)
实现继承:继承实际方法(ECMAScript只支持实现继承)
实现继承的方法 将原型链作为实现继承的方法。
基本思想:利用原型让一个引用类型继承另一个引用类型的属性和方法
JavaScript中的类(class)继承
构造函数内的,这是供实例化对象复制用的
构造函数外的,直接通过点语法添加的,这是供类使用,实例化对象是访问不到的
类的原型中,实例化对象可以通过其原型链间接访问到,也是为供所有实例化对象所共用的
类式继承(子类的原型对象) 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 26 27 28 29 30 31 32 function SuperClass ( ){ this .supperValue = true ; this .books = ["大数据" ,"人工智能" ,"区块链" ,"Kubernetes" ]; } SuperClass .prototype .getSuperValue = function ( ){ return this .supperValue ; } function SubClass ( ){ this .subValue = false ; } SubClass .prototype = new SuperClass ();SubClass .prototype .getSubValue = function ( ){ return this .subValue ; } var instance1 = new SubClass ();var instance2 = new SubClass ();console .log (instance2.books );instance1.books .push ("Docker" ); console .log (instance2.books );
缺点
instance1
的修改伤害了instance2
的books
属性
创建父类的时候,无法向父类传参数,,也因此无法对父类的构造函数内的属性进行初始化
构造函数继承 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 26 27 28 function SuperClass (id ){ this .id = id; this .books = ["大数据" ,"人工智能" ,"区块链" ,"Kubernetes" ]; } SuperClass .prototype .getBooks = function ( ){ return this .books ; } function SubClass (id ){ SuperClass .call (this ,id) } var instance1 = new SubClass (10 );var instance2 = new SubClass (20 );instance1.books .push ("Docker" ); console .log (instance1.books );console .log (instance1.id );console .log (instance2.books );console .log (instance2.id );instance1.getBooks ();
缺点
这种类型的继承没有涉及原型prototype
,所以父类的原型方法不会被继承
如果想要被子类继承,就必须放在构造函数中,这样创建出来的每个实例都会单独拥有一份,而不能共用。违背了代码复用原则。
组合继承 类式继承同构造函数继承组合使用
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 26 27 28 29 30 31 32 33 34 35 36 37 38 function SuperClass (name ){ this .name = name; this .books = ["大数据" ,"人工智能" ,"区块链" ,"Kubernetes" ]; } SuperClass .prototype .getName = function ( ){ console .log (this .name ); } function SubClass (name,time ){ SuperClass .call (this ,name); this .time = time; } SubClass .prototype = new SuperClass ();SubClass .prototype .getTime = function ( ){ console .log (this .time ); } var instance1 = new SubClass ("软件工程" ,2014 );instance1.books .push ("Docker" ); console .log (instance1.books );instance1.getName (); instance1.getTime (); var instance2 = new SubClass ("计算机与科学技术" ,2015 );console .log (instance2.books );instance2.getName (); instance2.getTime ();
优点
子类的实例中更改父类继承下来的引用类型,不会影响其他实例
子类实例化过程又能将参传递到父类的构造函数
缺点
使用构造函数继承时执行了一遍父类构的造函数,而实现子类原型的类式继承时又调用了一遍父类构造函数。因此构造函数调用了两遍
原型式继承
要求必须有一个对象可以作为另一个对象的基础
本质是对传入的对象执行了一次浅复制
是对类式继承的封装,类式继承中的问题在这里也会出现。
其中的过度对象就相当于类式继承中的子类,在这里作为一个对象出现
目的为了创建要返回的新实例化对象
ES5
的Object.create()
的方法规范了化了原型式继承
在传入一个参数的情况下与ES5
的Object.create()
与inheritObject
方法行为类似
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 26 27 28 29 30 31 32 function inheritObject (o ){ function F ( ){} F.prototype = o; return new F (); } var book = { name :"小明" , alikeBook :["C语言" ,"c++程序设计" ,"java程序设计" ,"数据结构" ] } var person1 = inheritObject (book);person1.name = "小刚" ; person1.alikeBook .push ("sql server数据库" ); var person2 = inheritObject (book);person2.name = "小花" ; person2.alikeBook .push ("Android程序开发" ); console .log (person1.name );console .log (person1.alikeBook );console .log (person2.name );console .log (person2.alikeBook );console .log (book.name );console .log (book.alikeBook );
缺点
类式继承中的问题在这里出现,引用类型的属性被共用
寄生式继承
寄生式继承是对原型式继承的第二次封装,第二次对继承对象进行拓展
创建的对象不仅仅有父类的属性和方法,而且还添加新的属性和方法
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 function inheritObject (o ){ function F ( ){} F.prototype = o; return new F (); } function createBook (obj ){ var clone = inheritObject (obj); clone.getName = function ( ){ console .log (this .name ) } return clone; } var book = { name :"小明" , alikeBook :["C语言" ,"c++程序设计" ,"java程序设计" ,"数据结构" ] } var person1 = createBook (book);person1.name = "小刚" ; person1.alikeBook .push ("sql server数据库" ); var person2 = createBook (book);person2.name = "小花" ; person2.alikeBook .push ("Android程序开发" ); person1.getName (); console .log (person1.alikeBook );person2.getName (); console .log (person2.alikeBook );console .log (book.name );console .log (book.alikeBook );
缺点
使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率,这一点与构造函数模式类似
寄生组合式继承 对子类原型的处理,被赋予了父类原型的一个引用,这是一个对象。
注意: 子类再想添加原型方法必须通过prototype.
对象,通过点语法的形式一个一个添加方法,否则直接赋予对象就会覆盖掉从父类原型继承的对象
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 function inheritObject (o ){ function F ( ){} F.prototype = o; return new F (); } function inheritPrototype (subClass,superClass ){ var prototype = inheritObject (superClass.prototype ); prototype.constructor = subClass; subClass.prototype = prototype; } function SuperClass (name ){ this .name = name; this .books = ["数据结构" ,"数据库设计" ,"操作系统" ]; } SuperClass .prototype .getName = function ( ){ console .log (this .name ); } function SubClass (name,time ){ SuperClass .call (this ,name); this .time = time; } inheritPrototype (SubClass ,SuperClass );SubClass .prototype .getTime = function ( ){ console .log (this .time ); } var instance1 = new SubClass ("软件工程" ,2015 );var instance2 = new SubClass ("计算机与科学" ,2016 );instance1.books .push ("c#程序设计" ); console .log (instance1.books );console .log (instance2.books );instance2.getName (); instance2.getTime ();
继承原理
优点
只调用了一次SuperClass
构造函数
并且避免了在SubClass.prototype
上面创建不必要的、多余属性。
与此同时原型链还能保持不变
Class
继承Class
通过 extends
关键字实现继承
ES6
规定通过super
调用父类的方法时,super
会绑定子类的this
super的作用
super
作为函数调用时代表父类的构造函数,ES6
要求子类的构造函数必须执行一次super
函数
super
作为对象用在静态方法之中,这时super
将指向父类,而不是父类的原型对象
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 class SuperClass { constructor (xAxis,yAxis,name){ this .name = name||"SuperClass" ; this .xAxis = xAxis; this .yAxis = yAxis; } static getName (msg ) { console .log (`static : ${this .name} =>` +msg); } getName (msg ){ console .log (`instance: ${this .name} =>` +msg); } printCoordinate ( ){ return `当前坐标是:(${this .xAxis} ,${this .yAxis} )` ; } } SuperClass .prototype .getTime = function (time ){ console .log ("当前时间是:" ,time); } class SubClass extends SuperClass { constructor (xAxis,yAxis,name,time) { super (xAxis, yAxis); this .name = name; super .getTime (new Date ()); } static getName (msg ) { super .getName (msg) } getName (msg ){ super .getName (msg) } printCoordinate ( ){ console .log (`${this .name} :${super .printCoordinate()} ` ); } } var instance1 = new SuperClass (10 ,20 );var instance2 = new SubClass (100 ,200 ,"小明" );console .log (instance1.printCoordinate ());instance2.printCoordinate (); SubClass .getName ("区块链" );instance2.getName ("持续部署" );
类的prototype
属性和__proto__
属性 Class
作为构造函数的语法糖,同时有prototype
属性和__proto__
属性,因此同时存在两条继承链
子类的__proto__
属性表示构造函数的继承,总是指向父类
子类的prototype
属性的__proto__
属性表示方法的继承,总是指向父类的prototype
属性
Object.setPrototypeOf()
Object.setPrototypeOf()
方法设置一个指定的对象的原型 ( 即, 内部[[Prototype]]
属性)到另一个对象或 null
Object.setPrototypeOf()
是ECMAScript 6
最新草案中的方法,相对于Object.prototype.__proto__
,它被认为是修改对象原型更合适的方法
继承的实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Object .setPrototypeOf = function (obj,proto ){ obj.__proto__ = proto; return obj; } class A {}class B {}Object .setPrototypeOf (B.prototype ,A.prototype );Object .setPrototypeOf (B,A);const instance1 = new A ();const instance2 = new B ();B.__proto__ === A B.prototype .__proto__ === A.prototype instance1.__proto__ === instance2.__proto__ .__proto__
可理解为:
作为一个对象,子类(B
)的原型(__proto__
属性)是父类A
作为一个构造函数,子类(B
)的原型(prototype
属性)是父类的实例
子类实例的__proto__
属性的__proto__
属性指向父类的__proto__
属性,也就是子类的原型的原型是父类的原型
原生构造函数的继承 原生构造函数如:Array/Error/Object/...
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 26 27 28 29 30 class VersionArray extends Array { constructor ( ) { super (); this .history = [ [0 ,0 ] ]; } commit () { this .history .push (this .slice ()); } revert ( ){ this .splice (0 ,this .length ,...this .history [this .history .length -1 ]); } } var instance1 = new VersionArray ();instance1.push (1 ); instance1.push (2 ); instance1.history ; instance1.commit (); instance1.history ; instance1.push (3 ); instance1; instance1.revert () instance1
相关链接
__proto__
MDN