1、原型链继承(把自己的原型prototype 指向父类),问题:引用类型属性在不改变内存块的前提下会共享。至于说不能传参,只能说在创建子类实例时不能向父类实例传参,但在原型链继承时是可以向父类传参的。
/*
原型链继承 重点:让新实例的原型等于父类的实例。
特点:父类的构造函数中的属性和方法,以及原型中的属性和方法都放入了子类的原型中,都可以访问得到。全继承。
问题:
1、父类中(构造函数和原型上的)对象属性,在多子类实例中是共享的。
子类中修改父类对象属性,不改变内存块(堆内存)的前提下是多子类实例共享的。
比如:instance1.colors.push("yellow");instance1子类 实例在不改变父类构造函数的内存块(堆内存)时,colors对象属性是共享的。instance2.colors也包含yellow, 但这一点只针对类型对象属性,属性为基本数据类型多子类实例就不会共享。
改变父类构造函数的内存块(堆内存)时,属性是不共享的,比如:instance1.colors = ['yellow'],那么instance2.colors的值还是父类的值,父类属性未共享。
*/
// 父类实例
function SuperType(param){
this.colors = ["red","green","blue"]; // 父类构造函数属性
this.name = {a:'多子类实例属性共享'}; // 父类构造函数属性
this.param = param; //可以传参
this.run = function(){ // 父类构造函数方法
console.log(this.name)
}
}
SuperType.prototype.eat = function() { // 父类原型方法
console.log(this.name)
}
SuperType.prototype.age = {num:18} // 父类原型属性
// 子类实例
function SubType(){}
SubType.prototype = new SuperType('可以传参,不共享'); // 重点:让新实例的原型等于父类的实例。
var instance1 = new SubType();
instance1.colors.push("yellow"); // 不改变父类构造函数的内存块(堆内存)
instance1.name.b = 'instance1'// 不改变父类构造函数的内存块(堆内存)
instance1.age = {a:1} // 改变父类构造函数的内存块(堆内存),把age指向了一块新的内存块,所以别的子实例不共享instance1.age属性。
var instance2 = new SubType();
instance2.colors.push("yellow22");
instance2.name.c = 'instance2'
console.log(instance2.colors,instance1.colors); // ["red", "green", "blue", "yellow", "yellow22"] (5) ["red", "green", "blue", "yellow", "yellow22"]
console.log(instance2.name,instance1.name); // {a: "多子类实例属性共享", b: "instance1", c: "instance2"} {a: "多子类实例属性共享", b: "instance1", c: "instance2"}
console.log('改就了属性内存块',instance2.age,instance1.age); // 改就了属性内存块 {num: 18} {a: 1}
function SubType1(){}
SubType1.prototype = new SuperType(); // 重点:让新实例的原型等于父类的实例。
var instance3 = new SubType1();
console.log(instance1.param,instance3.param); // 可以传参,不共享 undefined
2、借用构造函数(构造函数继承),改变子实例this指向(借用父类构造函数) > 特点:解决原型链继承问题,引用类型属性不共享。可以在创建子类实例时给父类传参。 问题:无法继承父类原型方法及属性,通过prototype新增的无法继承。
/*
借用构造函数(构造函数继承),改变子实例this指向(借用父类构造函数)
特点:解决原型链继承问题1,对象线上服务类型属性不共享。
问题:无法继承父类原型方法及属性,通过prototype新增的无法继承。
*/
function SuperType(name){
this.colors = ["red","green","blue"];
this.name = name;
this.run = function(){
console.log('父类构造函数方法',this.name)
}
}
SuperType.prototype.eat = function(){
console.log('父类原型方法',this.name)
}
SuperType.prototype.kg = '父类原型属性'
// 改变子类this指向(借用父类构造函数)
const Child = function (){
// 这里可以用call/apply/bind来改变this指向。
SuperType.call(this,'hjm')
}
const subC = new Child()
//修改引用类型父属性,多个子类实例不会共享。
subC.colors.push('#FFFFFF')
subC.run() // 父类构造函数方法 hjm
// subC.eat() // 无法访问父类原型方法及属性,报错 subC.eat is not a function
console.log('无法访问父类原型方法及属性',subC.kg) // undefined
// 改变子类this指向(借用父类构造函数)
const Child1 = function (){
SuperType.call(this)
}
const subC1 = new Child1()
console.log('修改引用类型父属性,多个子类实例不会共享',subC.colors,subC1.colors) //修改引用类型父属性,多个子类实例不会共享 (4) ["red", "green", "blue", "#FFFFFF"] (3) ["red", "green", "blue"]
console.log(subC1.name,subC.name) // undefined "hjm" 多个子实例传参不会共享
备注:(向父类传参问题):创建子类实例时不能向父类实例传参,但在原型链继承时是可以向父类传参的,而且传入参数不会被共享。而构造函数继承传参也只是在改变this指向时传参给父类。
3、原型链组合式继承(常用),原型链继承(prototype)和构造函数继承(call)组合使用,解决问题:父类引用类型属性不共享,并且可以继承父类原型属性和方法。缺点:调用了两次父类构造函数(耗内存),最优方案为寄生组合式继承【借用构造函数(call) + 寄生式(Object.create)】,只会调用一次父类构造函数。
// 组合式继承
function SuperType(name){
this.name = name;
this.colors = ["red","green","blue"];
}
SuperType.prototype.sayName = function(){
console.log(this.name);
}
function SubType(name,age){
//继承属性
SuperType.call(this,name);
this.age = age;
}
//继承方法
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function(){
console.log(this.age);
};
var instance1 = new SubType("zgx",19);
instance1.colors.push("yellow");
console.log(instance1.colors); //"red,green,blue,yellow"
instance1.sayName(); //"zgx"
instance1.sayAge(); //19
var instance2 = new SubType("lyh",18);
console.log(instance2.colors); //"red,green,blue"
instance2.sayName(); //"lyh"
instance2.sayAge(); //18
4、原型寄生式继承、寄生式继承(套壳、用一个函数包装一个对象、浅拷贝), 问题:子类实例没有原型,所有子类不能添加原型属性和方法。只能添加到隐式原型proto上。原型寄生式继承、寄生式继承这两种继承方式是没有意义的,因为只是对父类做了一次深拷贝。
/*
原型寄生式继承 :
重点:用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象。Object.create()就是这个原理。
原型寄生式继承就是手动实现了Object.create方法。而寄生式继承是直接用Object.create来进行继承。
*/
function CreateObj(o) {
function F() {}
F.prototype = o; // 创建一个新实例,并把原型指向子类构造函数。
F.prototype.name = 'hjm'
console.log(o === F.prototype,o,F.prototype);
return new F();
}
var person = {
name: 'xiaopao',
age: 18,
friend: ['daisy', 'kelly']
}
var person1 = CreateObj(person);
person1.__proto__.myName = '胡' // 问题:子类实例没有原型,所有子类不能添加属性和方法。只能添加到隐式原型上__proto__。
var person2 = CreateObj(person);
person1.friend.push('taylor');
person2.name = '222'
person2.age = 20
console.log(person1.friend,person1.name,person2.name);
console.log(person1.age,person2.age);
// person1.name == hjm, 因为我们找对象上的属性时,总是先找实例上对象,没有找到的话再去原型对象上的属性。实例对象和原型对象上如果有同名属性,总是先取实例对象上的值。先原型后实例构造函数。
console.log(person1.myName,person2.myName);
/*
寄生式继承:
重点:Object.create
原型寄生式继承就是手动实现Object.create方法来进行继承。
*/
var person3 = Object.create(person);
person3.friend = [1]
console.log(person3.friend,person1.friend,person2.friend) // [1] (3) ["daisy", "kelly", "taylor"] (3) ["daisy", "kelly", "taylor"]
console.log(person3.prototype,person1.prototype,person2.prototype) // undefined 问题:没有原型
5、寄生组合式继承(es5最优方案,es6 class继承也是此方案的语法糖),子类实例可以给父类传参,父类引用类型属性不会被多个子类实例一改同改,有原型链(prototype)可访问父类原型链上的方法和属性,只会创建一次父类构造函数。
/*
寄生组合式继承:
重点:和原型链组合式继承差不多,就是把Child.prototype = new Parent()换成Object.create,解决了没有原型的问题。
*/
let Parent = function(name){
this.name = name
this.staff = [1]
}
Parent.prototype.sayName = function(){
console.log(this.name)
}
Parent.prototype.showStaff = function(){
console.log(this.staff)
}
let Child = function(name){
Parent.call(this, name)
}
// 实现原型链继承,使子类实例有父类的原型上的属性和方法。并解决多次创建父类构造函数的问题。
// 1、子类原型指向Object.create(父类原型)的新对象 ,解决多次创建父类构造函数的问题。
Child.prototype = Object.create( Parent.prototype )
// 2、子类构造函数指向子类
Child.prototype.constructor = Child
let child1 = new Child('第一个子类实例传参')
let child2 = new Child('第二个子类实例传参')
// 创建子类 实例时可以对父类进行传参,在于借用构造函数继承,call
child1.sayName()
child2.sayName()
// 子类实例对父类引用属性修改,不会影响其它子类。
child1.staff.push(2)
child1.showStaff()
child2.showStaff()
6、ES6 class继承
// es6 class继承
class Parent {
constructor(a){
this.filed1 = a;
}
filed2 = [1,2,3,4];
func1 = function(){
console.log(this.filed1)
}
}
class Child extends Parent {
constructor(a,b) {
super(a);
this.filed3 = b;
}
filed4 = 1;
func2 = function(){
console.log(this.filed1,this.filed3)
}
}
const sub = new Child(...[1,2]) // 可以传参
sub.func1() // 可以访问父类方法
sub.func2()
sub.filed2.push(666) // 不影响其它子类实例
console.log(sub.filed2) // [1, 2, 3, 4, 666]
const sub1 = new Child()
console.log(sub1.filed2) // [1, 2, 3, 4]
ES6 class详解 :es6转换es5详解
// 转换前父类
class Parent {
constructor(a){
this.filed1 = a;
}
filed2 = [1,2,3,4];
func1 = function(){
console.log(this.filed1)
}
}
// 转换后父类
function _classCallCheck(instance,parent){ //es6class类的调用需要使用new的
//instanceOf判断一个实例是否属于某种类型,也可以用来判断原型链
if(!(instance instanceof parent)){
new TypeError("Cannot call a class as a function");
}
}
const es5Parent = function Parent(a){ //可见class类的底层还是构造函数
//_classCallCheck方法判断当前函数调用前是否有new关键字
_classCallCheck(this,Parent); //this指向new出来的空对象
this.filed1=a;
this.filed2 = [1,2,3,4];
this.func1 = function(){
console.log(this.filed1)
}
}
// 转换前继承
class Child extends Parent {
constructor(a,b) {
super(a);
this.filed3 = b;
}
filed4 = 1;
func2 = function(){
console.log(this.filed1,this.filed3)
}
}
// 转换后继承
const es5Child = function (_Parent) {
_inherits(es5Child, _Parent); //子类继承父类的proptype , ***重点1***
function es5Child(a, b) { //闭包保存父类的引用,在闭包中做子类的引用
_classCallCheck(this, es5Child);//判断当前函数是否是使用new关键字调用
//Child.__proto__ || Object.getPrototypeOf(Child)实际上是父构造函数(_inherits最后的操作),
//然后通过call将其调用方改为当前this,并传递参数 , ***重点2***
const _this = _possibleConstructorReturn(this,
(es5Child.__proto__ || Object.getPrototypeOf(es5Child)).call(this, a));
_this.filed4 = 1;
_this.func2 = function () {
console.log(this.filed1,this.filed3)
};
_this.filed3 = b;
return _this;
}
return es5Child;
}(Parent);
function _inherits(sub,parent){
//当parent不为函数时必须为空,或者是当parent不为空时必须为函数,否则类型错误
if(typeof parent != 'function' && parent != null){
throw new TypeError(
"Super expression must either be null or a function,not " + typeof superClass
);
}
/* ---------- 重点1 > 寄生组合继承------------ */
sub.prototype=Object.create(parent&&parent.prototype,{//寄生组合继承
constructor:{ value: sub, enumerable: false, writable: true, configurable: true }
})
if(parent){
Object.setPrototypeOf ? Object.setPrototypeOf(sub,parent):sub.__proto__ =parent
}
}
function _possibleConstructorReturn(self, call) {
if (!self) {//校验this是否被初始化
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
/* ---------- 重点2 > 借用构造函数继承------------ */
return call && (typeof call === "object" || typeof call === "function") ? call : self;
}
【总结】
1、 ES5
-
原型链继承: 父类引用类型属性会共享,一改同改。子类实例时不能给父类传参。关键: 子类原型指向父类(prototype)
-
借用构造函数继承: 子类不能访问父类的原型属性及方法。关键: 改变子类this指向(call)
-
原型链组合式继承(常用): 解决前两种继承所产生的问题,但在call和prototype时会调用两次父类构造函数,影响内存。
-
原型寄生式继承 和 寄生式继承: 子类实例没有原型,所有子类不能添加原型属性和方法。关键: 手写和调用Object.create方法,子类原型指向Object.create(父类原型)的新对象 ,解决多次创建父类构造函数的问题。
-
寄生组合式继承(最优方案): 【call + Object.create + 子类实例构造函数指向自己(子类实例)】子类实例可以给父类传参,父类引用类型属性不会被多个子类实例一改同改,有原型链(prototype)可访问父类原型链上的方法和属性,只会创建一次父类构造函数。
2、ES6
-
class的底层依然是构造函数,通过call + Object.create + 子类实例构造函数指向自己(子类实例)实现的,就是es5寄生组合式继承的抽象,和es5寄生组合式继承语法糖不一样。
网友评论