详解Javascript继承的实现(2)

用init方法来完成构造功能,就可以保证在设置子类原型时(Manager.prototype = new Employee()),父类的实例化操作一定不会出错,唯一不好的是在调用类的构造函数来初始化实例的时候,必须在调用构造函数后手动调用init方法来完成实际的构造逻辑:

- Hide code var e = new Employee(); e.init('jason', 5000); var m = new Manager(); m.init('tom', 8000, 0.15); console.log(e.toString()); //jason's salary is 5000. console.log(m.toString()); //tom's salary is 9200. console.log(m instanceof Manager); //true console.log(m instanceof Employee); //true console.log(e instanceof Employee); //true console.log(e instanceof Manager); //false

要是能把这个init的逻辑放在构造函数内部就好了,可是这样的话就会违背前面说的构造函数无参无逻辑的原则。换一种方式来考虑,这个原则的目的是为了保证在实例化父类作为子类原型的时候,调用父类的构造函数不会出错,那么就可以稍微打破一下这个原则,在类的构造函数里添加少量的并且一定不会有问题的逻辑来解决:

- Hide code //添加一个全局标识initializing,表示是否正在进行子类的构建和类的继承 var initializing = false; //可自动调用init方法的父类构造函数 function Employee() { if (!initializing) { this.init.apply(this, arguments); } } Employee.prototype = { constructor: Employee, //把构造逻辑搬到init方法中来 init: function (name, salary) { this.name = name; this.salary = salary; }, getName: function () { return this.name; }, getSalary: function () { return this.salary; }, toString: function () { return this.name + '\'s salary is ' + this.getSalary() + '.'; } }; //可自动调用init方法的子类构造函数 function Manager() { if (!initializing) { this.init.apply(this, arguments); } } //表示开始子类的构建和类的继承 initializing = true; //此时调用new Emplyee(),并不会调用Employee.prototype.init方法 Manager.prototype = new Employee(); Manager.prototype.constructor = Manager; //表示结束子类的构建和类的继承,之后调用new Employee或new Manager都会自动调用init实例方法 initializing = false; //把构造逻辑搬到init方法中来 Manager.prototype.init = function (name, salary, percentage) { //借用父类的init方法,实现属性继承(name, salary) Employee.prototype.init.apply(this, [name, salary]); this.percentage = percentage; }; Manager.prototype.getSalary = function () { return this.salary + this.salary * this.percentage; };

调用结果仍然和前面的例子一样。但是这个实现还有一个小问题,它引入了一个全局变量initializing,要是能把引入这个全局变量就好了,这个其实很好解决,只要我们把关于类的构建跟继承,封装成一个模块,然后把这个变量放在模块的内部,就没有问题了。

3)在构造子类的时候,是把子类的原型设置成了父类的一个实例,这个是不符合语义的,继承应该发生在类与类之间,而不是类与实例之间。之所以要用父类的一个实例来作为子类的原型:

- Hide code SubClass.prototype = new SuperClass();

完全是因为父类的这个实例,指向父类的原型,而子类的实例又会指向子类的原型,所以最终子类的实例就能通过原型链访问到父类原型上的方法。这个做法虽然能实现实例方法的继承,但是它不符合语义,而且它还有一个很大的问题就是会增加原型链的长度,导致子类在调用父类方法时,必须通过原型链的查找到父类的方法才行。要是继承层次较深,会对js的执行性能有些影响。

解决方式:在解决这个问题之前,先想想继承能帮我们解决什么问题:从父类复用已有的实例属性和实例方法。在javascript面向对象编程中,一直有一个原则就是,实例属性都写在构造函数或者实例方法里面,实例方法写在原型上面,也就是说类的原型,按照这个原则来说,就是用来写实例方法的,而且是只用来写实例方法,那么我们完全可以在构建子类时,通过复制的方式将父类原型的所有方法全部添加到子类的原型上,不一定要把父类的一个实例设置成子类的原型,这样就能将原型链的长度大大地缩短,借助一个简短的copy函数,我们就能轻松对前面的代码进行改造:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wgydxg.html