详解Javascript继承的实现(3)

- Hide code //用来复制父类原型,由于父类原型上约定只写实例方法,所以复制的时候不必担心引用的问题 var copy = function (source) { var target = {}; for (var i in source) { if (source.hasOwnProperty(i)) { target[i] = source[i]; } } return target; } function Employee() { this.init.apply(this, arguments); } Employee.prototype = { constructor: Employee, 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() + '.'; } }; function Manager() { this.init.apply(this, arguments); } //将父类的原型方法复制到子类的原型上 Manager.prototype = copy(Employee.prototype); //子类还是需要修改constructor指向,因为从父类原型复制出来的对象的constructor还是指向父类的构造函数 Manager.prototype.constructor = Manager; Manager.prototype.init = function (name, salary, percentage) { Employee.prototype.init.apply(this, [name, salary]); this.percentage = percentage; }; Manager.prototype.getSalary = function () { return this.salary + this.salary * this.percentage; }; var e = new Employee('jason', 5000); var m = new Manager('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); //false console.log(e instanceof Employee); //true console.log(e instanceof Manager); //false

这么做了以后,当调用m.toString的时候其实调用的是Manager类自身原型上的方法,而不是Employee类的实例方法,缩短了在原型链上查找方法的距离。这个做法在性能上有很大的优点,但不好的是通过原型链维持的继承关系其实已经断了,子类的原型和子类的实例都无法再通过js原生的属性访问到父类的原型,所以这个调用console.log(m instanceof Employee)输出的是false。不过跟性能比起来,这个都可以不算问题:一是instanceOf的运算,几乎在javascript的开发里面用不到,至少我是没碰到过;二是通过复制方式完全能够把父类的实例方法继承下来,这就已经达到了继承的最大目的。

这个方法还有一个额外的好处是,解决了第2个问题最后提到的引入initializing全局变量的问题,如果是复制的话,就不需要在构建继承关系时,去调用父类的构造函数,那么也就没有必要在构造函数内先判断initializing才能去调用init方法,上面的代码中就已经去掉了initializing这个变量的处理。

4)在子类的构造函数和实例方法内如果想要调用父类的构造函数或者方法,显得比较繁琐:

- Hide code function SuperClass() {} SuperClass.prototype = { constructor: SuperClass, method1: function () {} } function SubClass() { //调用父类构造函数 SuperClass.apply(this); } SubClass.prototype = new SuperClass(); SubClass.prototype.constructor = SubClass; SubClass.prototype.method1 = function () { //调用父类的实例方法 SuperClass.prototype.method1.apply(this, arguments); } SubClass.prototype.method2 = function () {} SubClass.prototype.method3 = function () {} 每次都得靠apply借用方法来处理。要是能改成如下的调用就好用多了: - Hide code function SubClass() { //调用父类构造函数 this.base(); } SubClass.prototype = new SuperClass(); SubClass.prototype.constructor = SubClass; SubClass.prototype.method1 = function() { //调用父类的实例方法 this.base(); }

解决方式:如果要在每个实例方法里,都能通过this.base()调用父类原型上相应的方法,那么this.base就一定不是一个固定的方法,需要在每个实例方法执行期间动态地将this.base指定为父类原型的同名方法,能够做到这个实现的方式,就只有通过方法代理了,前面的Employee和Manager的例子可以改造如下:

- Hide code //用来复制父类原型,由于父类原型上约定只写实例方法,所以复制的时候不必担心引用的问题 var copy = function (source) { var target = {}; for (var i in source) { if (source.hasOwnProperty(i)) { target[i] = source[i]; } } return target; }; function Employee() { this.init.apply(this, arguments); } Employee.prototype = { constructor: Employee, 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() + '.'; } }; function Manager() { //必须在每个实例中添加baseProto属性,以便实例内部可以通过这个属性访问到父类的原型 //因为copy函数导致原型链断裂,无法通过原型链访问到父类的原型 this.baseProto = Employee.prototype; this.init.apply(this, arguments); } Manager.prototype = copy(Employee.prototype); //子类还是需要修改constructor指向,因为从父类原型复制出来的对象的constructor还是指向父类的构造函数 Manager.prototype.constructor = Manager; Manager.prototype.init = (function (name, func) { return function () { //记录实例原有的this.base的值 var old = this.base; //将实例的this.base指向父类的原型的同名方法 this.base = this.baseProto[name]; //调用子类自身定义的init方法,也就是func参数传递进来的函数 var ret = func.apply(this, arguments); //还原实例原有的this.base的值 this.base = old; return ret; } })('init', function (name, salary, percentage) { //通过this.base调用父类的init方法 //这个函数真实的调用位置是var ret = func.apply(this, arguments); //当调用Manager实例的init方法时,其实不是调用的这个函数 //而是调用上面那个匿名函数里面return的匿名函数 //在return的匿名函数里,先把this.base指向为了父类原型的同名函数,然后在调用func //func内部再通过调用this.base时,就能调用父类的原型方法。 this.base(name, salary); this.percentage = percentage; }); Manager.prototype.getSalary = function () { return this.salary + this.salary * this.percentage; }; var e = new Employee('jason', 5000); var m = new Manager('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); //false console.log(e instanceof Employee); //true console.log(e instanceof Manager); //false

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

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