html语义化标签
|
|
html5语义化标签
|
|
Record my life , my study , my think
|
|
|
|
上一篇javascript继承的简单实现一文中已经介绍了继承,但那篇只能算简介。本篇结合原型链详细介绍一下JavaScript的继承。
通常除非小应用,那像JavaScript继承一文中那样直接写写代码就行了。如果是大型应用或者库函数,对于继承这种稍显复杂的代码结构,通常会封装成一个inherit函数。例如:
现在我们来实现inherit。
模式一 : 默认模式,将原型对象指向父对象
例子:
原型链图:
见上面的结果为Adam。这就是该模式的缺点之一,即无法将子构造函数的参数给父构造函数。这个缺点很致命,因此通常我们不用该模式。即使你能保证父子构造函数都不需要参数,那从结果上看是OK的,但效率是低下的,例如你再创建一个子对象:
模式二 : 借用构造函数
该方法解决了模式一中无法通过子构造函数传递参数给父构造函数的问题:
例子
结果看出Child的参数顺利传入了,但say方法会报未定义的错。原因就是该模式并没有将prototype指向Parent,只不过借用了一下Parent的实现。因此看似是继承,其实不然,从原型链角度来看,两者毫无关系。Child的实例对象里自然就没有Parent原型中的say方法。图示如下:
总结一下该模式:子类只是借用了父类构造函数的实现,从结果上看,获得了一个父对象的副本。但子类对象和父类对象是完全独立的,不存在修改子类对象的属性值影响父对象的风险。缺点是该模式某种意义上讲,其实不是继承,无法从父类的prototype中获得任何东西
模式三 : 借用和设置原型
本模式是上面两个模式的结合体,借鉴了上面两种模式的特点:
这就是javascript继承的简单实现一文中推荐的继承模式。子对象既可获得父对象本身的成员副本,又能获得原型的引用。子对象能传参数给父构造函数,也能安全地修改自身属性。
例子:
该模式通常用用就可以了,但不是完美的。缺点和模式二的缺点二一样,多个子对象都会重复地创建父对象,效率不高。另外从例子的结果和图中都可以看出,有两个name属性,一个在父对象中,一个在子对象中。你delete子对象中的name后,父对象的name会显现出来,这可能会出bug。而且对效率狂来说,冗余的属性会看着不舒服。
模式四 : 借用和设置原型
为了克服模式三需要重复创建父对象的缺点,该模式不调用构造函数,即任何需要继承的成员都放到原型里,而不是放置在父构造函数的this中。等价于对象共享一个原型
例子:
从结果可以看出,该模式和模式三不同,现在你delete子对象的name属性,就不会将父对象的name属性显现出来了。原型链图:
该模式除了需要你仔细斟酌哪些属性和方法需要被继承,抽出来放到父类原型里。而且由于父子对象共享原型,因此双方修改时都要小心,如果子对象不小心修改了原型里的属性和方法,会影响到父对象,反之亦然。例如:
给子类原型增加一个setName方法。由于父子类共享原型,因此父类对象也自动获得了setName方法。
模式五 : 临时构造函数
为解决模式四中父子对象间耦合度较高的缺点,该模式断开父子对象间的原型的直接链接关系,但同时还能继续受益于原型链的好处
例子:
原型链图:
与模式四的差别就是,新定义了个空的临时构造函数F(),子类的原型指向该临时构造函数。这样修改子类原型时,实际修改的是修改到了临时构造函数F(),不会影响父类:
上面的例子和模式四中相同,但结果不同,子类原型上添加的新方法setName,父类对象无法访问。
该模式非常好,即有效率,还能实现父子解耦。本着精益求精的精神,再为该模式增加三个加分项:
加分项一 添加一个指向父类原型的引用,例如其他语言里的super:
这样如果你为子类原型添加setName方法后,希望父类对象也能获得该方法,可以:
子类给原型的新增方法setName1不会影响父类,父类对象无法使用setName1。但父类对象可以使用子类通过uber给原型的新增方法setName2。
加分项二 重置该构造函数的指针,以免在将来某个时候还需要该构造函数。如果不重置构造函数的指针,那么所有子对象会报告Parent()是它们的构造函数,这没有任何用处:
虽然我们很少用constructor属性,不改也不影响实际的使用,但作为完美主义者还是改一下吧:
加分项三 临时构造函数F()不必每次继承时都创建,仅创建一次以提高效率:
JavaScript作为一门语法比较松散的语言,在ES6之前并没有像C++/Java等传统OO语言一样有class关键字,也不能通过private,public等关键字来限定权限。本篇就介绍一下JavaScript是如何实现继承的。
JavaScript的继承可以分为两类:
基于对象的继承也叫原型继承。我们知道通过JavaScript字面量创建的对象都会连接到 Object.prototype ,因此我们用Object.prototype来实现继承。本质上是摒弃类,不调用构造函数,而是用Object.create()
,直接让新对象继承旧对象的属性。例如:
代码很简单,person有一个属性和一个方法。对象p1通过Object.create()来继承,第一个参数prototype指向person的prototype,这样对象p1就继承了person的属性和方法。
Object.create()还可以指定第二个参数,即数据属性,将其添加到新对象中。数据属性可设4个描述符value, writable,enumerable,configurable 。后3个看名字也能猜出意思,不指定的话默认为false。因为和本篇关系不大,就不跑题了,只看看设置value的情况:
用Object.create()相当于创建了一个全新的对象,你可以给该对象任意新增,重载它的属性和方法:
在person中并没有age这个属性,因此你调用person.getAge();将得到undefined。但在对象p3里新定义了age这个属性,于是就能正确地调用基类的getAge方法。另外子类重载了name的值,且新定义了location属性和getLocation方法。结果如上所示,不赘述。
基于类型的继承是通过构造函数依赖于原型的继承,而非依赖于对象。例如:
Student继承自Person。name虽然是在基类Person里被定义的,但用new调用Person的构造函数后,this将被绑定到子类Student对象上,因此name最终是定义在子类Student对象上的。结果如上所示,不赘述。
之所以定义getName,getAge等方法就是不想让用户直接访问name,age等属性。可惜上面两种继承均无法保护隐私,均可像p.name,p.age这样直接访问属性。如果认为这些属性的隐私非常重要,希望模拟出OO语言中private属性的效果,可以用函数模块化。
所谓函数模块化,本质上就是在函数内新建一个对象,新对象的方法里使用参数对象的属性,然后将新对象返回。此时新对象里是没有参数对象的属性的,达到了保护隐私的目的。代码如下:
|
|
进一步实现多层继承也非常方便,效果如下,不赘述: