javascript继承
ES6之前,JavaScript本质上不能算是一门面向对象的语言,因为它对于封装,继承,多态这些面向对象语言的特点并没有在语言层面上提供原生的支持。但是它引入了原型(prototype)的概念,可以让我们以另一种方式模仿类,并通过原型链的方式实现了父类子类共享属性的继承以及身份确认机制。其实面向对象概念本质上不是某种语言的特质,而是一种设计思想,如果你深韵面向对象的编程思想,即使用c这种面向过程的语言也能写出面向对象的代码(典型的代表就是windows NT内核实现),而JavaScript亦是如此,正式由于JavaScript本身对编程没有一个语言上的支持标准,所以才有了五花八门的“类继承”代码,所幸,es6增加了class,extends,static等关键字用以在语言层面上支持面向对象,但是还是有些保守,我们先列举出es6之前常见的几种继承方案。
ES6之前的继承
原型赋值继承
简而言之,就是直接将父类的一个实例赋值给子类的原型,如下实例:
如代码中1处所示,这种方式是直接new了一个父类的实例,然后赋值给子类的原型,这样也就相当于直接将父类的原型中的方法和属性以及挂在this上的各种方法属性全赋值给了子类的原型,简单粗暴,因为man本身没有getClassName方法,这种继承方式下,所有的子类实例会共享一个父类对象的实例,这种方案最大的问题就是子类无法通过父类创建私有属性。比如每一个Person都有一个名字,我们在初始化每一个Man的时候要指定一个不同的名字,然后子类将这个名字传递给父类,对于每隔man来说,保存在相应的person中的name应该是不同的,但是这种方式根本做不到。所以,这种继承方式,在实战中基本不用!
调用构造函数方式
这里在子类的构造函数里用子类实例的this去调用父类的构造函数,从而达到继承父类属性的效果,这样以来,每new一个子类实例,构造函数执行完后,都会有自己的一份资源(name),但是这种办法只能继承父类构造函数中声明的实例属性,并没有继承父类原型的属性和方法,所以就找不到getName方法,所以最后一行代码报错,为了同时继承父类原型,从而诞生了组合继承方式:
组合继承
这个例子很简单,这样不仅会继承构造函数中的属性,也会继承父类原型链中的属性,但是有个问题,Man.prototype = new Person();这句话执行后,Man的原型如下:
也就是说Man的原型中已经有了name属性,而之后创建man1时传给构造函数的name则是通过this重新定义了一个name属性,相当于只是覆盖掉了name原型中的属性(原型中的name依然还在),这样很不优雅。
分离组合继承
这是目前es5中主流的继承方式,也叫作寄生组合继承
这里用到了Object.create(obj)方法,该方法会对传入的obj对象进行浅拷贝,和上面组合继承的主要区别就是:将父类的原型复制给子类原型,这种做法很清晰:
1.构造函数中继承父类属性/方法,并初始化父类。
2.子类原型和父类原型建立联系。
还有一个问题就是constructor属性,我们来看一下:
contstructor是类的构造函数,我们发现Person和Man的实例的constructor都指向Person,当然,这并不会改变instanceof的结果,但是对于需要用到constructor的场景,就会有问题,所以一般会加上这么一句:
综合来看,es5下,这种方式就是首选,也是实际上最流行的。
此文至此,es5下的继承方式就讲完了。
ES6继承方法
ES6引入了class,extends,super,static(部分为ES2016标准)
ES6明确规定,class内部只有静态方法,没有静态属性,所以1处是有问题的,ES7有一个静态属性的提案,目前babel转码器支持,熟悉Java的可能对上面的代码感觉很亲切,几乎是自解释的。我们大概解释一下,按照代码中的标号对应:
1.constructor为构造函数,一个类有一个,相当于es5中的构造函数标准化,负责一些初始化工作,如果没有定义,js vm会定义一个空的默认的构造函数。
2.实例方法,ES6中可以不加“function”关键字,class内定义的所有函数都会置于该类的原型当中,所以class本身是一个语法糖。
3.构造函数中通过super()调用父类构造函数,如果有super方法,super方法必须是构造函数中第一个执行的语句,this关键字在调用super后才可以使用。
4.静态方法,在类定义的外部只能通过类名调用,内部可以通过this调用,并且静态函数是会被继承的,如示例中,sTest在Person中定义的静态方法可以通过Man.sTest()来调用。
es6和es5继承的区别
大多数浏览器的es5实现之中,每一个对象都有__proto__属性。指向对应的构造函数的prototype属性,Class作为构造函数的语法糖,同时有prototype和__proto__属性,因此同时存在两条继承链。
1.子类的__proto__属性,表示构造函数的继承,总是指向父类。
2.子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性
上面代码中,子类B的__proto__属性指向父类A,子类B的prototype属性指向父类A的prototype属性。这样的结果是因为,类的继承是按照下面模式实现的:
因此得到了上面的结果。
这两条继承链可以这样理解作为一个对象,子类(B)的原型(__proto__属性)是父类(A);作为一个构造函数,子类(B)的原型(prototype属性)是父类的实例。
es6继承的不足
1.不支持静态属性,(除函数)。
2.class中不能定义私有变量和函数。class中定义的所有函数都会被放到原型当中,都会被子类继承,而属性都会作为实例属性挂在this上,如果子类想定义一个私有的方法或定义一个private变量,便不能在class花括号中定义,这真的很不方便。
总结一下,和es5相比,es6在语言层面上提供了面向对象的部分支持,虽然大多数时候只是一个语法糖,但是用起来很方便,语义化更强,更直观,同时也给JavaScript继承提供了一个标准的方式。还有很重要的一点就是es6支持原生对象继承
网友评论