js实现继承
一、尝试实现原型继承
1 | function Person(){ |
当定义一个Person函数的时候,同时也创建了一个Person原型,该原型通过其construtor属性引用函数的本身。同时下面也扩展了Person原型,加入了相关的方法。后面我们也定义了一个Ninja函数,并且该函数原型也有一个constructor属性引用函数本身。为了实现继承,将Ninja的原型赋为Person的实例。之后每创建一个Ninja对象的时候,新创建的Ninja对象将设置为Ninja的原型属性所指向的对象,即Person。
当访问Ninja对象的dance方法时,js首先会查找Ninja对象本身。没有就会找他的原型即Person。Person不具有dance方法就会再接着查找Person对象的原型,这时候找到了dance方法。执行instance操作符的时候,可以判定函数是否继承原型链上的对象功能。
其实实现继承还可以直接使用Person的原型对象作为Ninja的原型,如Ninja.prototype = Person.prototype。这样做会导致Person原型上发生的所有变化都被同步到Ninja原型上(Person原型和Ninja原型是同一个对象),会有一定的副作用。
constructor属性的问题
仔细观察可以发现,上述代码中,会存在丢失Ninja与Ninja初始原型的之间的关联,这是一个问题。如果利用constructor检查一个对象是否由某一个函数创建的时候,去检查Ninja会得到的是Person。显然这个答案是错误的。
配置对象的属性
在js中,对象是通过属性描述的,可以通过配置一下关键字:
- configurable——如果设置为true,则可以修改或删除属性。
- enumerate——如果如果设置为true,则可在for-in循环对象属性出现
- value——指定其属性的值
- writable——如果为true,可以通过赋值语句修改属性值
- get——定义getter函数,当访问属性时发生调用,不能与value和writable同时使用。
- set——定义setter函数,当对属性赋值的时候调用,不能与value和writable同时使用。
当然,可以使用内置的Object.defineProperty方法属性定义一个属性:
1 | var ninja = {}; |
利用上面的方法,就可以解决constructor被覆盖的问题,即通过Object.defineProperty在Ninja.defineProperty上定义一个constructor属性。
instanceof操作符
在上面的例子中,虽然Ninja的原型是Person,但是通过instanceof还是可以发现ninja instanceof Ninja的返回值是true,说明ninja也是Ninja的实例。在这个过程中,javascript首先检查Ninja函数的原型——new Person()对象,检查他是否存在ninja的原型链上。由于new Person()对象确实是ninja的原型,所以表达式为true。实际上,ninja实例的原型链是由new Person()对象和Person的原型组成的。
ES6语法中使用class关键字:
1 | class test{ |
从上面的代码可以看出,ES6语法支持使用类似其他面向对象语言的方式用class来定义一个类。但实际上,class只是个语法糖,底层仍是基于原型的实现。上述定义类可转换成下面的ES5代码:
1 | function Ninja(name){ |
同时在ES6中还支持定义像Java一样的静态方法。
1 | class test{ |
实现继承
js也可以像java一样利用extends来实现继承:
1 |
在student构造器中通过关键字super调用基类Person的构造函数。