博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JavaScript继承详解(三)
阅读量:6956 次
发布时间:2019-06-27

本文共 7302 字,大约阅读时间需要 24 分钟。

在第一章中,我们使用构造函数和原型的方式在JavaScript的世界中实现了类和继承, 但是存在很多问题。这一章我们将会逐一分析这些问题,并给出解决方案。

        注:本章中的jClass的实现参考了的做法。

首先让我们来回顾一下第一章中介绍的例子:

function Person(name) { this.name = name; } Person.prototype = { getName: function() { return this.name; } }  function Employee(name, employeeID) { this.name = name; this.employeeID = employeeID; } Employee.prototype = new Person(); Employee.prototype.getEmployeeID = function() { return this.employeeID; }; var zhang = new Employee("ZhangSan", "1234"); console.log(zhang.getName()); // "ZhangSan"

 

 

修正constructor的指向错误

 

从上一篇文章中关于constructor的描述,我们知道Employee实例的constructor会有一个指向错误,如下所示:

var zhang = new Employee("ZhangSan", "1234"); console.log(zhang.constructor === Employee); // false console.log(zhang.constructor === Object); // true

我们需要简单的修正:

function Employee(name, employeeID) { this.name = name; this.employeeID = employeeID; } Employee.prototype = new Person(); Employee.prototype.constructor = Employee; Employee.prototype.getEmployeeID = function() { return this.employeeID; }; var zhang = new Employee("ZhangSan", "1234"); console.log(zhang.constructor === Employee); // true console.log(zhang.constructor === Object); // false

 

 

创建Employee类时实例化Person是不合适的

 

但另一方面,我们又必须依赖于这种机制来实现继承。 解决办法是不在构造函数中初始化数据,而是提供一个原型方法(比如init)来初始化数据。

// 空的构造函数 function Person() { } Person.prototype = { init: function(name) { this.name = name; }, getName: function() { return this.name; } } // 空的构造函数 function Employee() { } // 创建类的阶段不会初始化父类的数据,因为Person是一个空的构造函数 Employee.prototype = new Person(); Employee.prototype.constructor = Employee; Employee.prototype.init = function(name, employeeID) { this.name = name; this.employeeID = employeeID; }; Employee.prototype.getEmployeeID = function() { return this.employeeID; };

这种方式下,必须在实例化一个对象后手工调用init函数,如下:

var zhang = new Employee(); zhang.init("ZhangSan", "1234"); console.log(zhang.getName()); // "ZhangSan"

 

 

如何自动调用init函数?

 

必须达到两个效果,构造类时不要调用init函数和实例化对象时自动调用init函数。看来我们需要在调用空的构造函数时有一个状态标示。

// 创建一个全局的状态标示 - 当前是否处于类的构造阶段 var initializing = false; function Person() { if (!initializing) { this.init.apply(this, arguments); } } Person.prototype = { init: function(name) { this.name = name; }, getName: function() { return this.name; } } function Employee() { if (!initializing) { this.init.apply(this, arguments); } } // 标示当前进入类的创建阶段,不会调用init函数 initializing = true; Employee.prototype = new Person(); Employee.prototype.constructor = Employee; initializing = false; Employee.prototype.init = function(name, employeeID) { this.name = name; this.employeeID = employeeID; }; Employee.prototype.getEmployeeID = function() { return this.employeeID; };  // 初始化类实例时,自动调用类的原型函数init,并向init中传递参数 var zhang = new Employee("ZhangSan", "1234"); console.log(zhang.getName()); // "ZhangSan"

但是这样就必须引入全局变量,这是一个不好的信号。

 

 

如何避免引入全局变量initializing?

 

我们需要引入一个全局的函数来简化类的创建过程,同时封装内部细节避免引入全局变量。

// 当前是否处于创建类的阶段 var initializing = false; function jClass(baseClass, prop) { // 只接受一个参数的情况 - jClass(prop) if (typeof (baseClass) === "object") { prop = baseClass; baseClass = null; } // 本次调用所创建的类(构造函数) function F() { // 如果当前处于实例化类的阶段,则调用init原型函数 if (!initializing) { this.init.apply(this, arguments); } } // 如果此类需要从其它类扩展 if (baseClass) { initializing = true; F.prototype = new baseClass(); F.prototype.constructor = F; initializing = false; } // 覆盖父类的同名函数 for (var name in prop) { if (prop.hasOwnProperty(name)) { F.prototype[name] = prop[name]; } } return F; };

使用jClass函数来创建类和继承类的方法:

var Person = jClass({ init: function(name) { this.name = name; }, getName: function() { return this.name; } }); var Employee = jClass(Person, { init: function(name, employeeID) { this.name = name; this.employeeID = employeeID; }, getEmployeeID: function() { return this.employeeID; } });  var zhang = new Employee("ZhangSan", "1234"); console.log(zhang.getName()); // "ZhangSan"

OK,现在创建类和实例化类的方式看起来优雅多了。 但是这里面还存在明显的瑕疵,Employee的初始化函数init无法调用父类的同名方法。

 

 

如何调用父类的同名方法?

 

我们可以通过为实例化对象提供一个base的属性,来指向父类(构造函数)的原型,如下:

// 当前是否处于创建类的阶段 var initializing = false; function jClass(baseClass, prop) { // 只接受一个参数的情况 - jClass(prop) if (typeof (baseClass) === "object") { prop = baseClass; baseClass = null; } // 本次调用所创建的类(构造函数) function F() { // 如果当前处于实例化类的阶段,则调用init原型函数 if (!initializing) { // 如果父类存在,则实例对象的base指向父类的原型 // 这就提供了在实例对象中调用父类方法的途径 if (baseClass) { this.base = baseClass.prototype; } this.init.apply(this, arguments); } } // 如果此类需要从其它类扩展 if (baseClass) { initializing = true; F.prototype = new baseClass(); F.prototype.constructor = F; initializing = false; } // 覆盖父类的同名函数 for (var name in prop) { if (prop.hasOwnProperty(name)) { F.prototype[name] = prop[name]; } } return F; };

调用方式:

var Person = jClass({ init: function(name) { this.name = name; }, getName: function() { return this.name; } }); var Employee = jClass(Person, { init: function(name, employeeID) { // 调用父类的原型函数init,注意使用apply函数修改init的this指向 this.base.init.apply(this, [name]); this.employeeID = employeeID; }, getEmployeeID: function() { return this.employeeID; }, getName: function() { // 调用父类的原型函数getName return "Employee name: " + this.base.getName.apply(this); } });  var zhang = new Employee("ZhangSan", "1234"); console.log(zhang.getName()); // "Employee name: ZhangSan"

 

目前为止,我们已经修正了在第一章手工实现继承的种种弊端。 通过我们自定义的jClass函数来创建类和子类,通过原型方法init初始化数据, 通过实例属性base来调用父类的原型函数。

唯一的缺憾是调用父类的代码太长,并且不好理解, 如果能够按照如下的方式调用岂不是更妙:

var Employee = jClass(Person, { init: function(name, employeeID) { // 如果能够这样调用,就再好不过了 this.base(name); this.employeeID = employeeID; } });

 

优化jClass函数

 

// 当前是否处于创建类的阶段 var initializing = false; function jClass(baseClass, prop) { // 只接受一个参数的情况 - jClass(prop) if (typeof (baseClass) === "object") { prop = baseClass; baseClass = null; } // 本次调用所创建的类(构造函数) function F() { // 如果当前处于实例化类的阶段,则调用init原型函数 if (!initializing) { // 如果父类存在,则实例对象的baseprototype指向父类的原型 // 这就提供了在实例对象中调用父类方法的途径 if (baseClass) { this.baseprototype = baseClass.prototype; } this.init.apply(this, arguments); } } // 如果此类需要从其它类扩展 if (baseClass) { initializing = true; F.prototype = new baseClass(); F.prototype.constructor = F; initializing = false; } // 覆盖父类的同名函数 for (var name in prop) { if (prop.hasOwnProperty(name)) { // 如果此类继承自父类baseClass并且父类原型中存在同名函数name if (baseClass && typeof (prop[name]) === "function" && typeof (F.prototype[name]) === "function") {  // 重定义函数name -  // 首先在函数上下文设置this.base指向父类原型中的同名函数 // 然后调用函数prop[name],返回函数结果  // 注意:这里的自执行函数创建了一个上下文,这个上下文返回另一个函数, // 此函数中可以应用此上下文中的变量,这就是闭包(Closure)。 // 这是JavaScript框架开发中常用的技巧。 F.prototype[name] = (function(name, fn) { return function() { this.base = baseClass.prototype[name]; return fn.apply(this, arguments); }; })(name, prop[name]);  } else { F.prototype[name] = prop[name]; } } } return F; };

此时,创建类与子类以及调用方式都显得非常优雅,请看:

var Person = jClass({ init: function(name) { this.name = name; }, getName: function() { return this.name; } }); var Employee = jClass(Person, { init: function(name, employeeID) { this.base(name); this.employeeID = employeeID; }, getEmployeeID: function() { return this.employeeID; }, getName: function() { return "Employee name: " + this.base(); } });  var zhang = new Employee("ZhangSan", "1234"); console.log(zhang.getName()); // "Employee name: ZhangSan"

 

至此,我们已经创建了一个完善的函数jClass, 帮助我们在JavaScript中以比较优雅的方式实现类和继承。

在以后的章节中,我们会陆续分析网上一些比较流行的JavaScript类和继承的实现。 不过万变不离其宗,那些实现也无非把我们这章中提到的概念颠来簸去的“炒作”, 为的就是一种更优雅的调用方式。

转载于:https://www.cnblogs.com/jenry/archive/2012/01/19/2327616.html

你可能感兴趣的文章
查看Linux服务器网卡流量小脚本shell
查看>>
ubuntu12.04LTS安装
查看>>
数据表的左右连接
查看>>
Linux ./configure --prefix命令
查看>>
Android 图片放大和缩小
查看>>
Elasticsearch- 分词查询
查看>>
RDMA技术分析
查看>>
EL(Expression Language)表达式概述
查看>>
交换机的基本配置
查看>>
使用kubernetes 滚动升级
查看>>
3.3办公网络布线
查看>>
RocketMQ的一些特性
查看>>
scrapy之异步写入数据库
查看>>
贪吃蛇
查看>>
现代图像处理技术试题
查看>>
ffmpeg的使用
查看>>
Oracle 白皮书-Oracle Data Guard 快速启动故障切换指南(1)
查看>>
通过案例学调优之--和 SHARED POOL 相关的主要 Latch
查看>>
sql server 数据库索引
查看>>
centos 7中 yum install java 没有javac的解决方法
查看>>