ES5继承
每创建一个构造函数,则该函数都会自动带有一个 prototype
属性。该属性是一个指针,指向一个对象,该对象称之为原型对象。
原型对象上默认有一个属性 constructor
,该属性也是一个指针,指向其关联的构造函数。
通过构造函数产生的实例对象,都拥有一个内部指向,指向了原型对象。其实例能够访问原型对象上的所有属性和方法。
构造函数、原型和实例的关系:每一个构造函数都有一个原型对象 prototype
,每一个原型对象都有一个指向构造函数的指针 constructor
,而每一个实例都包含一个指向原型对象的内部指针。
原型继承 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 function Parent ( ) { this .name = 'parent' ; this .parentObj = { info : 'parent中的对象' }; this .action = function ( ) { return this .name; } } Parent.prototype.getName = function ( ) { return this .name; } function Child ( ) { this .name = 'child' ; } Child.prototype = new Parent(); const child = new Child();console .log(child.name); const child1 = new Child();child.parentObj.info = 'child中的对象' ; console .log(child1.parentObj.info);
关键点: 子类原型等于父类的实例 Child.prototype = new Person()
特点:
实例可继承的属性有:实例的构造函数的属性,父类构造函数的属性,父类原型上的属性。(新实例不会继承父类实例的属性)
注意事项:
新实例无法向父类构造函数传参
继承单一
所有新实例都会共享父类实例的属性。(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改)
构造函数继承(call或apply) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function Parent ( ) { this .name = 'parent' ; this .action = function ( ) { return this .name; }, this .arr = [1 ,2 ]; } Parent.prototype.getName = function ( ) { return this .name; } function Child ( ) { this .name = 'Child' ; Parent.apply(this ); } const child1 = new Child();const child2 = new Child();child2.arr.push(3 ); child.getName(); console .log('child1 --->' , child1);console .log('child2 --->' , child2);
关键点: 用 call
或 apply
将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行(复制))Parent.call(this, 'reng')
特点:
只继承了父类构造函数的属性,没有继承父类原型的属性
解决了原型链继承的注意事项(缺点)1,2,3
可以继承多个构造函数的属性(call可以多个)
在子实例中可以向父实例传参
注意事项:
只能继承父类构造函数的属性
无法实现构造函数的复用。(每次用每次都要重新调用)
每个新实例都有构造函数的副本,臃肿
组合继承(call + prototype) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function Parent ( ) { this .name = 'parent' ; this .action = function ( ) { return this .name; }, this .arr = [1 ,2 ]; } Parent.prototype.getName = function ( ) { return this .name; } function Child ( ) { this .name = 'child' ; Parent.call(this ); } Child.prototype = new Parent(); Child.prototype.constructor = Child; const child1 = new Child();console .log(child1)
关键点: 结合了两种模式的优点–向父类传参(call)和复用(prototype)
特点:
可以继承父类原型上的属性,可以传参,可复用
每个新实例引入的构造函数属性是私有的
注意事项:
调用了两次父类的构造函数(耗内存)
子类的构造函数会代替原型上的那个父类构造函数(call相当于拿到了父类构造函数的副本)
原型式继承 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 function Person (name ) { this .name = name; } Person.prototype.job = 'IT' ; Person.prototype.sayHello = function ( ) { console .log('Hello ' + this .name); } var person = new Person('zylucky' );person.sayHello(); function objFn (o ) { o.objFnPrototype = "我是 objFnPrototype" ; function F ( ) {}; F.prototype = o; return new F(); } const op = new Person();const oa = objFn(op);console .log(oa instanceof Person); console .log(oa.job); const a = objFn({ name : "name1" }); console .log(a.name); console .log(a.objFnPrototype);
关键点: 用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了可以随意增添属性的实例或对象。Object.create()就是这个原理。
特点:
类似于复制一个对象,用函数来包装
注意事项:
所有的实例都会继承原型上的属性
无法实现复用。(新实例属性都是后面添加的)
Object.create()方法规范了原型式继承。 这个方法接收两个参数,一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。
1 2 3 4 5 6 7 8 9 10 11 var anotherPerson = Object .create(new Person());console .log(anotherPerson.job); console .log(anotherPerson instanceof Person); var anotherPerson = Object .create(new Person(), { name : { value : 'zylucky' } }); anotherPerson.sayHello();
寄生式继承 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function Person (name ) { this .name = name; } ... function object (obj ) { function F ( ) {} F.prototype = obj; return new F(); } var sup = new Person();function subobject (obj ) { var sub = object(obj); sub.name = 'zylucky' ; return sub; } var sup2 = subobject(sup);console .log(sup2.name); console .log(sup2 instanceof Person);
关键点: 就是给原型式继承外面套个壳子。
特点:
没有创建自定义类型,因为只是套了个壳子,返回对象,这个函数顺理成章就成了创建的新对象。
注意事项:
没用到原型,无法复用
寄生组合式继承 它跟组合继承一样,都比较常用。
寄生: 在函数内返回对象然后调用
组合:
函数的原型等于另一个实例
在函数中用apply或call引入另一个构造函数,可传参
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 function Person (name ) { this .name = name; } ... function object (obj ) { function F ( ) {} F.prototype = obj; return new F(); } var obj = object(Person.prototype);function Sub ( ) { this .age = 100 ; Person.call(this ); } Sub.prototype = obj; console .log(Sub.prototype.constructor); obj.constructor = Sub; console .log(Sub.prototype.constructor); var sub1 = new Sub();console .log(sub1.job); console .log(sub1 instanceof Person);
重点: 修复了组合继承的问题
在上面的问题中,你可能发现了这么一个注释obj.constructor = Sub; // 一定要修复实例。为什么要修正子类的构造函数的指向呢?
因为在不修正这个指向的时候,在获取构造函数返回的时候,在调用同名属性或方法取值上可能造成混乱。比如下面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function Car ( ) { }Car.prototype.orderOneLikeThis = function ( ) { return new this .constructor(); } Car.prototype.advertise = function ( ) { console .log("I am a generic car." ); } function BMW ( ) { }BMW.prototype = Object .create(Car.prototype); BMW.prototype.constructor = BMW; BMW.prototype.advertise = function ( ) { console .log("I am BMW with lots of uber features." ); } var x5 = new BMW();var myNewToy = x5.orderOneLikeThis();myNewToy.advertise();
ES6继承 在 ES6 中,直接使用 extends 关键字来实现 JavaScript 继承,Class之间通过使用extends关键字,这比通过修改原型链实现继承,要方便清晰很多。并且 babel 编辑之后,它采用的也是 寄生组合继承的方式
,这种方式是较优的解决继承的方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Parent2 { constructor (name ) { this .name = name; } getName = function ( ) { return this .name; } } class Child2 extends Parent2 { constructor (name, age ) { super (name); this .age = age; } } const child3 = new Child2('parent' , 22 );const child4 = new Child2('parent1' , 25 );console .log(child3.getName());console .log(child3);console .log(child4);
子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工,如果不调用super方法,子类就得不到this对象。因此,只有调用super之后,才可以使用this关键字。
prototype
和 __proto__
一个继承语句同时存在两条继承链:一条实现属性继承,一条实现方法的继承。
1 2 3 class A extends B {}A.__proto__ === B; A.prototype.__proto__ == B.prototype;
总结 总结ES5
ES5的继承可以用下图概括:
总结ES6
ES6的继承可以用下图概括: