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 相关文章推荐
NiftyCube——轻松实现圆角边框
Feb 20 Javascript
JS 拼图游戏 面向对象,注释完整。
Jun 18 Javascript
js数组操作学习总结
Nov 04 Javascript
js出生日期 年月日级联菜单示例代码
Jan 10 Javascript
对 jQuery 中 data 方法的误解分析
Jun 18 Javascript
通过jquery-ui中的sortable来实现拖拽排序的简单实例
May 24 Javascript
基于JS实现仿百度百家主页的轮播图效果
Mar 06 Javascript
Angularjs中使用指令绑定点击事件的方法
Mar 30 Javascript
原生js的ajax和解决跨域的jsonp(实例讲解)
Oct 16 Javascript
详解javascript中的babel到底是什么
Jun 21 Javascript
jQuery操作attr、prop、val()/text()/html()、class属性
May 23 jQuery
JavaScript实现页面高亮操作提示和蒙板
Jan 04 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使用imagick模块实现图片缩放、裁剪、压缩示例
2014/04/17 PHP
分析PHP中单双引号的误区和双引号小隐患
2016/07/19 PHP
PHP判断是否是微信打开还是浏览器打开的方法
2019/02/27 PHP
兼容ie、firefox的图片自动缩放的css跟js代码分享
2012/01/21 Javascript
extjs关于treePanel+chekBox全部选中以及清空选中问题探讨
2013/04/02 Javascript
js 获取radio按钮值的实例
2013/08/17 Javascript
jquery选择器、属性设置用法经验总结
2013/09/08 Javascript
js鼠标点击图片切换效果代码分享
2015/08/26 Javascript
Jquery中request和request.form和request.querystring的区别
2015/11/26 Javascript
js判断主流浏览器类型和版本号的简单实现代码
2016/05/26 Javascript
canvas实现探照灯效果
2017/02/07 Javascript
angular实现IM聊天图片发送实例
2017/05/08 Javascript
js HTML5 canvas绘制图片的方法
2017/09/08 Javascript
vue slot 在子组件中显示父组件传递的模板
2018/03/02 Javascript
基于Nuxt.js项目的服务端性能优化与错误检测(容错处理)
2019/10/23 Javascript
Bootstrap简单实用的表单验证插件BootstrapValidator用法实例详解
2020/03/29 Javascript
python使用百度翻译进行中翻英示例
2014/04/14 Python
Python列表list数组array用法实例解析
2014/10/28 Python
Django 2.0版本的新特性抢先看!
2018/01/05 Python
python的Crypto模块实现AES加密实例代码
2018/01/22 Python
flask框架中勾子函数的使用详解
2018/08/01 Python
pandas使用apply多列生成一列数据的实例
2018/11/28 Python
Python判断telnet通不通的实例
2019/01/26 Python
Python第三方库face_recognition在windows上的安装过程
2019/05/03 Python
python实现在内存中读写str和二进制数据代码
2020/04/24 Python
CSS3制作彩色进度条样式的代码示例分享
2016/06/23 HTML / CSS
法国和欧洲海边和滑雪度假:Pierre & Vacances
2017/01/04 全球购物
泰国时尚电商:POMELO Fashion
2020/03/11 全球购物
网上卖盒饭创业计划书范文
2014/02/07 职场文书
xxx同志考察材料
2014/02/07 职场文书
美德少年事迹材料500字
2014/08/19 职场文书
计生办班子群众路线教育实践活动个人对照检查材料思想汇报
2014/10/04 职场文书
党员对十八届四中全会的期盼思想汇报范文
2014/10/17 职场文书
爱的教育读书笔记
2015/06/26 职场文书
mysql 如何获取两个集合的交集/差集/并集
2021/06/08 MySQL
Django框架之路由用法
2022/06/10 Python