javascript 面向对象全新理练之继承与多态


Posted in Javascript onDecember 03, 2009

1 又是几个基本概念
为什么要说又呢?
在讨论继承时,我们已经列出了一些基本概念了,那些概念是跟封装密切相关的概念,今天我们要讨论的基本概念,主要是跟继承与多态相关的,但是它们跟封装也有一些联系。
1.1 定义和赋值
变量定义是指用
var a;
这种形式来声明变量。
函数定义是指用
function a(...) {...}
这种形式来声明函数。
var a = 1;
是两个过程。第一个过程是定义变量 a,第二个过程是给变量 a 赋值。
同样
var a = function(...) {};
也是两个过程,第一个过程是定义变量 a 和一个匿名函数,第二个过程是把匿名函数赋值给变量 a。
变量定义和函数定义是在整个脚本执行之前完成的,而变量赋值是在执行阶段完成的。
变量定义的作用仅仅是给所声明的变量指明它的作用域,变量定义并不给变量初始值,任何没有定义的而直接使用的变量,或者定义但没有赋值的变量,他们的值都是 undefined。
函数定义除了声明函数所在的作用域外,同时还定义函数体结构。这个过程是递归的,也就是说,对函数体的定义包括了对函数体内的变量定义和函数定义。
通过下面这个例子我们可以更明确的理解这一点:

alert(a); 
alert(b); 
alert(c); 
var a = "a"; 
function a() {} 
function b() {} 
var b = "b"; 
var c = "c"; 
var c = function() {} 
alert(a); 
alert(b); 
alert(c);

猜猜这个程序执行的结果是什么?然后执行一下看看是不是跟你想的一样,如果跟你想的一样的话,那说明你已经理解上面所说的了。
这段程序的结果很有意思,虽然第一个 alert(a) 在最前面,但是你会发现它输出的值竟然是 function a() {},这说明,函数定义确实在整个程序执行之前就已经完成了。
再来看 b,函数 b 定义在变量 b 之前,但是第一个 alert(b) 输出的仍然是 function b() {},这说明,变量定义确实不对变量做什么,仅仅是声明它的作用域而已,它不会覆盖函数定义。
最后看 c,第一个 alert(c) 输出的是 undefined,这说明 var c = function() {} 不是对函数 c 定义,仅仅是定义一个变量 c 和一个匿名函数。
再来看第二个 alert(a),你会发现输出的竟然是 a,这说明赋值语句确实是在执行过程中完成的,因此,它覆盖了函数 a 的定义。
第二个 alert(b) 当然也一样,输出的是 b,这说明不管赋值语句写在函数定义之前还是函数定义之后,对一个跟函数同名的变量赋值总会覆盖函数定义。
第二个 alert(c) 输出的是 function() {},这说明,赋值语句是顺序执行的,后面的赋值覆盖了前面的赋值,不管赋的值是函数还是其它对象。
理解了上面所说的内容,我想你应该知道什么时候该用 function x(..) {…},什么时候该用 var x = function (…) {…} 了吧?
最后还要提醒一点,eval 中的如果出现变量定义和函数定义,则它们是在执行阶段完成的。所以,不到万不得已,不要用 eval!另外,即使要用 eval,也不要在里面用局部变量和局部方法!
1.2 this 和执行上下文
在前面讨论封装时,我们已经接触过 this 了。在对封装的讨论中,我们看到的 this 都是表示 this 所在的类的实例化对象本身。真的是这样吗?
先看一下下面的例子吧:
var x = "I'm a global variable!"; 
function method() { 
alert(x); 
alert(this.x); 
} 
function class1() { 
// private field 
var x = "I'm a private variable!"; 
// private method 
function method1() { 
alert(x); 
alert(this.x); 
} 
var method2 = method; 
// public field 
this.x = "I'm a object variable!"; 
// public method 
this.method1 = function() { 
alert(x); 
alert(this.x); 
} 
this.method2 = method; 
// constructor 
{ 
this.method1(); // I'm a private variable! 
// I'm a object variable! 
this.method2(); // I'm a global variable! 
// I'm a object variable! 
method1(); // I'm a private variable! 
// I'm a global variable! 
method2(); // I'm a global variable! 
// I'm a global variable! 
method1.call(this); // I'm a private variable! 
// I'm a object variable! 
method2.call(this); // I'm a global variable! 
// I'm a object variable! 
} 
} 
var o = new class1(); 
method(); // I'm a global variable! 
// I'm a global variable! 
o.method1(); // I'm a private variable! 
// I'm a object variable! 
o.method2(); // I'm a global variable! 
// I'm a object variable!

为什么是这样的结果呢?
那就先来看看什么是执行上下文吧。那什么是执行上下文呢?
如果当前正在执行的是一个方法,则执行上下文就是该方法所附属的对象,如果当前正在执行的是一个创建对象(就是通过 new 来创建)的过程,则创建的对象就是执行上下文。
如果一个方法在执行时没有明确的附属于一个对象,则它的执行上下文是全局对象(顶级对象),但它不一定附属于全局对象。全局对象由当前环境来决定。在浏览器环境下,全局对象就是 window 对象。
定义在所有函数之外的全局变量和全局函数附属于全局对象,定义在函数内的局部变量和局部函数不附属于任何对象。
那执行上下文跟变量作用域有没有关系呢?
执行上下文与变量作用域是不同的。
一个函数赋值给另一个变量时,这个函数的内部所使用的变量的作用域不会改变,但它的执行上下文会变为这个变量所附属的对象(如果这个变量有附属对象的话)。
Function 原型上的 call 和 apply 方法可以改变执行上下文,但是同样不会改变变量作用域。
要理解上面这些话,其实只需要记住一点:
变量作用域是在定义时就确定的,它永远不会变;而执行上下文是在执行时才确定的,它随时可以变。
这样我们就不难理解上面那个例子了。this.method1() 这条语句(注意,这里说的还没有进入这个函数体)执行时,正在创建对象,那当前的执行上下文就是这个正在创建的对象,所以 this 指向的也是当前正在创建的对象,在 this.method1() 这个方法执行时(这里是指进入函数体),这个正在执行的方法所附属的对象也是这个正在创建的对象,所以,它里面 this.x 的 this 也是同一个对象,所以你看的输出就是 I'm a object variable! 了。
而在执行 method1() 这个函数时(是指进入函数体后),method1() 没有明确的附属于一个对象,虽然它是定义在 class1 中的,但是他并没有不是附属于 class1 的,也不是附属于 class1 实例化后的对象的,只是它的作用域被限制在了 class1 当中。因此,它的附属对象实际上是全局对象,因此,当在它当中执行到 alert(this.x) 时,this.x 就成了我们在全局环境下定义的那个值为 “I'm a global variable!” 的 x 了。
method2() 虽然是在 class1 中定义的,但是 method() 是在 class1 之外定义的,method 被赋值给 method2 时,并没有改变 method 的作用域,所以,在 method2 执行时,仍然是在 method 被定义的作用域内执行的,因此,你看到的就是两个 I'm a global variable! 输出了。同样,this.method2() 调用时,alert(x) 输出 I'm a global variable! 也是这个原因。
因为 call 会改变执行上下文,所以通过 method1.call(this) 和 method2.call(this) 时,this.x 都变成了 I'm a object variable!。但是它不能改变作用域,所以 x 仍然跟不使用 call 方法调用时的结果是一样的。
而我们后面执行 o.method1() 时,alert(x) 没有用 this 指出 x 的执行上下文,则 x 表示当前执行的函数所在的作用域中最近定义的变量,因此,这时输出的就是 I'm a private variable!。最后输出 I'm a object variable! 我想不用我说大家也知道为什么了吧?
2 继承和多态
2.1 从封装开始
前面我们说了,封装的目的是实现数据隐藏。
但是更深一层来说,在 javascript 中进行封装还有以下几个好处:
1、隐身实现细节,当私有部分的实现完全重写时,并不需要改变调用者的行为。这也是其它面向对象语言要实现封装的主要目的。
2、javascript 中,局部变量和局部函数访问速度更快,因此把私有字段以局部变量来封装,把私有方法以局部方法来封装可以提高脚本的执行效率。
3、对于 javascript 压缩混淆器(据我所知,目前最好的 javascript 分析、压缩、混淆器就是 JSA)来说,局部变量和局部函数名都是可以被替换的,而全局变量和全局函数名是不可以被替换的(实际上,对于 javascript 脚本解析器工作时也是这样的)。因此,不论对于开源还是非开源的 javascript 程序,当私有字段和私有方法使用封装技术后,编写代码时就可以给它们定义足够长的表意名称,增加代码的可读性,而发布时,它们可以被替换为一些很短的名称(一般是单字符名称),这样就可以得到充分的压缩和混淆。及减少了带宽占用,又可以真正实现细节的隐藏。
所以,封装对于 javascript 来说,是非常有用的!
那么在 javascript 实现继承是为了什么呢?
2.2 为什么要继承
在其它面向对象程序设计语言中,继承除了可以减少重复代码的编写外,最大的用处就是为了实现多态。尤其是在强类型语言中,尤为如此:
1、在强类型语言中,一个变量不能够被赋予不同类型的两个值,除非这两种类型与这个变量的类型是相容的,而这个相容的关系就是由继承来实现的。
2、在强类型语言中,对一个已有的类型无法直接进行方法的扩充和改写,要扩充一个类型,唯一的方法就是继承它,在它的子类中进行扩充和改写。
因此,对于强类型的面向对象语言,多态的实现是依赖于继承的实现的。
而对于 javascript 语言来说,继承对于实现多态则显得不那么重要:
1、在 javascript 语言中,一个变量可以被赋予任何类型的值,且可以用同样的方式调用任何类型的对象上的同名方法。
2、在 javascript 语言中,可以对已有的类型通过原型直接进行方法的扩充和改写。
所以,在 javascript 中,继承的主要作用就是为了减少重复代码的编写。
接下来我们要谈的两种实现继承的方法可能大家已经都很熟悉了,一种是原型继承法,一种是调用继承法,这两种方法都不会产生副作用。我们主要讨论的是这两种方法的本质和需要注意的地方。
2.3 原型继承法
在 javascript 中,每一个类(函数)都有一个原型,该原型上的成员在该类实例化时,会传给该类的实例化对象。实例化的对象上没有原型,但是它可以作为另一个类(函数)的原型,当以该对象为原型的类实例化时,该对象上的成员就会传给以它为原型的类的实例化对象上。这就是原型继承的本质。
原型继承也是 javascript 中许多原生对象所使用的继承方法。
function parentClass() { 
// private field 
var x = "I'm a parentClass field!"; 
// private method 
function method1() { 
alert(x); 
alert("I'm a parentClass method!"); 
} 
// public field 
this.x = "I'm a parentClass object field!"; 
// public method 
this.method1 = function() { 
alert(x); 
alert(this.x); 
method1(); 
} 
} 
parentClass.prototype.method = function () { 
alert("I'm a parentClass prototype method!"); 
} 
parentClass.staticMethod = function () { 
alert("I'm a parentClass static method!"); 
} 
function subClass() { 
// private field 
var x = "I'm a subClass field!"; 
// private method 
function method2() { 
alert(x); 
alert("I'm a subClass method!"); 
} 
// public field 
this.x = "I'm a subClass object field!"; 
// public method 
this.method2 = function() { 
alert(x); 
alert(this.x); 
method2(); 
} 
this.method3 = function() { 
method1(); 
} 
} 
// inherit 
subClass.prototype = new parentClass(); 
subClass.prototype.constructor = subClass; 
// test 
var o = new subClass(); 
alert(o instanceof parentClass); // true 
alert(o instanceof subClass); // true 
alert(o.constructor); // function subClass() {...} 
o.method1(); // I'm a parentClass field! 
// I'm a subClass object field! 
// I'm a parentClass field! 
// I'm a parentClass method! 
o.method2(); // I'm a subClass field! 
// I'm a subClass object field! 
// I'm a subClass field! 
// I'm a subClass method! 
o.method(); // I'm a parentClass prototype method! 
o.method3(); // Error!!! 
subClass.staticMethod(); // Error!!!

上面这个例子很好的反映出了如何利用原型继承法来实现继承。
Javascript 相关文章推荐
从父页面读取和操作iframe中内容方法
Jul 25 Javascript
js获取URL的参数的方法(getQueryString)示例
Sep 29 Javascript
javascript处理表单示例(javascript提交表单)
Apr 28 Javascript
使用ajaxfileupload.js实现ajax上传文件php版
Jun 26 Javascript
Javascript模拟加速运动与减速运动代码分享
Dec 11 Javascript
学习使用bootstrap基本控件(table、form、button)
Apr 12 Javascript
easyui导出excel无法弹出下载框的快速解决方法
Nov 10 Javascript
详解如何在项目中使用jest测试react native组件
Feb 09 Javascript
JavaScript实现动态添加、移除元素或属性的方法分析
Jan 03 Javascript
JavaScript常用事件介绍
Jan 21 Javascript
webpack-url-loader 解决项目中图片打包路径问题
Feb 15 Javascript
JS实现求字符串中出现最多次数的字符和次数示例
Jul 05 Javascript
javascript 面向对象全新理练之数据的封装
Dec 03 #Javascript
jquery的ajax从纯真网(cz88.net)获取IP地址对应地区名
Dec 02 #Javascript
jQuery 跨域访问问题解决方法
Dec 02 #Javascript
IE与firefox下Dhtml的一些区别小结
Dec 02 #Javascript
checkbox全选/取消全选以及checkbox遍历jQuery实现代码
Dec 02 #Javascript
两种WEB下的模态对话框 (asp.net或js的分别实现)
Dec 02 #Javascript
JavaScript Object的extend是一个常用的功能
Dec 02 #Javascript
You might like
PHP比较运算符的详细介绍
2015/09/29 PHP
php抽象类和接口知识点整理总结
2019/08/02 PHP
css结合js制作下拉菜单示例代码
2014/02/27 Javascript
jQuery实现瀑布流布局
2014/12/12 Javascript
使用VS开发 Node.js指南
2015/01/06 Javascript
JavaScript获取网页中第一个链接ID的方法
2015/04/03 Javascript
js变形金刚文字特效代码分享
2015/08/20 Javascript
jQuery实现checkbox列表的全选、反选功能
2016/11/24 Javascript
JavaScript实现汉字转换为拼音的库文件示例
2016/12/22 Javascript
windows 下安装nodejs 环境变量设置
2017/02/02 NodeJs
详解RequireJS按需加载样式文件
2017/04/12 Javascript
Redux实现组合计数器的示例代码
2018/07/04 Javascript
JS document form表单元素操作完整示例
2020/01/13 Javascript
vue webpack build资源相对路径的问题及解决方法
2020/06/04 Javascript
javascript运行机制之执行顺序理解
2020/08/03 Javascript
[04:40]2016国际邀请赛中国区预选赛全程TOP10镜头集锦
2016/07/01 DOTA
CentOS7下python3.7.0安装教程
2018/07/30 Python
使用python3实现操作串口详解
2019/01/01 Python
python进程和线程用法知识点总结
2019/05/28 Python
jupyter 中文乱码设置编码格式 避免控制台输出的解决
2020/04/20 Python
使用scrapy ImagesPipeline爬取图片资源的示例代码
2020/09/28 Python
python实现代码审查自动回复消息
2021/02/01 Python
苹果中国官方网站:Apple中国
2016/07/22 全球购物
大学生优秀的自我评价分享
2013/10/22 职场文书
成龙洗发水广告词
2014/03/14 职场文书
植物生产学专业求职信
2014/08/08 职场文书
毕业生实习证明
2014/09/19 职场文书
农村党支部书记四风问题个人对照检查材料
2014/09/21 职场文书
县委常委班子对照检查材料思想汇报
2014/09/28 职场文书
加强作风建设心得体会
2014/10/22 职场文书
2014年依法行政工作总结
2014/11/19 职场文书
办公室岗位职责
2015/02/04 职场文书
酒店办公室主任岗位职责
2015/04/01 职场文书
代理词怎么写
2015/05/25 职场文书
创业计划书详解
2019/07/19 职场文书
python必学知识之文件操作(建议收藏)
2021/05/30 Python