面向对象类(参考https://www.cnblogs.com/chengzp/p/extends.html)
类与实例
类的声明
生成实例
类与继承
如何是想继承
继承的几种方式
1、类与实例
/** * 类的声明 */ function Animal (){ this.name = 'name' } /** * ES6中的class的声明 */ class Animal2{ constructor () { this.name = 'name' } } /** * s实例化 */ console.log(new Animal(),new Animal2())
2、类与继承
/** * 借助构造函数实现继承 */ function Parent1(){ this.name='parent1' } Parent1.prototype.say = function(){}// 但是Parent1原型链上的东西没有被子类继承 function Child1(){ Parent1.call(this)//call apply都能用,将父级的构造函数this指向自构造函数的实例上,则父类所有属性,子类都有 this.type='child1' } console.log(new Child1)
可以看到,生成Child1里面有了父级的属性name,实现了继承。为什么就实现继承了呢?
因为在Child1里执行了这句 Parent1.call(this); 如果对this不理解的话,建议看看这个JavaScript作用域和闭包
在子类的函数体里执行父级的构造函数,同时改变函数运行的上下文环境(也就是this的指向),使this指向Child1这个类,从而
导致了父类的属性都会挂载到子类这个类上去,如此便实现了继承。但这种继承的方法有一个缺点,它只是把父类中的属性继承了,但
父类的原型中的属性继承不了
- 构造函数中定义方法,所有实例都会创建一个函数,复用性差
/** * 借助原型链实现继承 */ function Parent2() { this.name='parent2' this.play=[1,2,3] } function Child2(){ this.type = 'child2' } Child2.prototype = new Parent2()//子类原型对象赋值为父类实例对象,让子类的实例能访问到父类的原型对象上 console.log(new Child2) var s1=new Child2() var s2 = new Child2() console.log(s1.play,s2.play) s1.play.push(4) //修改一个另一个也会变,因为共用一个原型链 console.log(s1.play,s2.play)
- 子类的原型重写为父类的一个实例对象,子类拥有父类原型对象中的所有属性和方法,子类同名属性和方法会覆盖父类
- 子类的原型对象拥有父类实例对象的所有引用类型的属性(实例属性)
- 子类实例对引用类型的变更会影响到所有其他的实例中的该引用变量
/** * 组合方式实现继承 */ function Parent3(){ this.name='parent' this.play=[1,2,3] } function Child3(){ Parent3.call(this) //第一次执行 this.type = 'child3' } Child3.prototype = new Parent3() //第二次执行 var s3 = new Child3() var s4 = new Child3() s3.play.push(4) console.log(s3.play,s4.play)
//优点:解决了以上两种方式的缺点, 缺点:父级构造函数执行了两次
可以看出,修改某个实例的属性,并不会引起父类的属性的变化。
这种方式的继承把构造函数和原型链的继承的方式的优点结合起来,并弥补了二者的不足,功能上已经没有缺点了。
但这种方法仍不完美,因为创建一个子类的实例的时候,父类的构造函数执行了两次。
每一次创建实例,都会执行两次构造函数这是没有必要的,因为在继承构造函数的时侯,也就是Parent3.call(this)的时候,
parnet的属性已经在child里运行了,外面原型链继承的时候就没有必要再执行一次了
/** * 组合继承的优化1 */ function Parent4(){ this.name='parent4' this.play=[1,2,3] } function Child4(){ Parent4.call(this) this.type = 'child4' } Child4.prototype = Parent4.prototype //优化在这里 var s5 = new Child4() var s6 = new Child4() console.log(s5,s6) console.log(s5 instanceof Child4,s5 instanceof Parent4) console.log(s5.constructor) //为Parent4
在这种继承方式中,并没有把直接把子类的原型指向父类,而是指向了父类的原型。这样就避免了父类构造函数的二次执行,
从而完成了针对组合方式的优化。但还是有一点小问题,
可以看到s5是new Child4()出来的,但是他的constructor却是Parent4.
这是因为Child4这个类中并没有构造函数,它的构造函数是从原型链中的上一级拿过来的,也就是Parent4。所以说到这里,终于能把最完美的继承方式接受给大家啦。
/** * 组合继承优化2 */ function Parent5(){ this.name='parent5' this.play=[1,2,3] } function Child5(){ Parent5.call(this) this.type = 'child5' } Child5.prototype = Object.create(Parent5.prototype)//中间对象 把子类原型对象指向中间对象 Child5.prototype.constructor = Child5 //把Child5的原型的构造函数指向自己 var s7 =new Child5() console.log(s7 instanceof Child5, s7 instanceof Parent5) //true true console.log(s7.constructor) //Child5
本例中通过把子类的原型指向Object.create(Parent5.prototype),实现了子类和父类构造函数的分离,
但是这时子类中还是没有自己的构造函数,所以紧接着又设置了子类的构造函数,由此实现了完美的组合继承。