JavaScript面向对象的理解

来源:互联网 发布:green网络加速器下载 编辑:程序博客网 时间:2024/05/21 20:08

前言


1. 本文默认阅读者已有面向对象的开发思想,最好是使用过c++、java,本人Java不太熟悉,所以例子都是用C++来写的。
2. 本人不是专业网站开发人员,接触javascript一年多,自己也编写调试了一些代码,本文完全根据自己经验所写,只希望和朋友们分享。文章难免出错,希望大家指出,以便及时改正。

3. 代码测试环境:google chrome


正文:

所谓对象

为了内容完整,我先说一些面向对象的东西。话说为什么要有面向对象的思想?也就是好好的面向过程的程序设计不用,干嘛搞出个面向对象(OO)?我的理解是为了满足工程开发的需要,增加代码的重复利用率(通过继承等),可以提高开发速度。另外也符合人的思考逻辑,面向过程的代码相对比较难看,很难一眼看出来其中的逻辑,面向对象的代码较好维护。
好了,进入正题。JavaScript的开发方式我认为也只有两种,一是面向过程,二是面向对象。用面向过程就是来一个问题,写一个函数来解决,这就产生了很多的代码,而且你以前也得代码比较难重复利用(也可以重复利用,但是当你代码写多了,你记得清吗),当然对于相似的操作也可以写一个公共函数库(貌似JQuery就是的吧?不对请指出),这比较好容易理解。而面向对象呢?开发的过程中我们会发现,网页元素有很多操作都是相似的,而且网页中有很多模块都是差不多的,这就让人很容易联想到了用面向对象开发。
再来说说提高代码重复利用率的好处。

一、很显然可以加快开发速度。
   二、减少代码量,提高网页加载速度。首先JavaScript是脚本语言。什么是脚本语言呢,脚本语言就是需要有一个翻译器才能执行的。这个翻译器也叫执行器、执行引擎、执行宿主等等。它不是直接生成代码让cpu执行的,而是给执行器看的,执行器看懂了,然后执行器去通过cpu做那些事情。于是脚本和exe相比,速度较慢,因为exe是直接和cpu对话的。批处理bat,vb脚本vbs等都是脚本语言。vbs也可以用于网页哟,它和js有什么不同呢,这里就不说了,不是本文重点。脚本语言的代码的多少直接影响着网页加载速度,所以提高代码重复利用率,可以提高加载速度,有利于改善用户体验。


所谓this

好,都说得差不多了,那么怎么面向对象呢?刚学JS的时候,一个函数就是一个函数。比如

function a(){   alert("呵呵哒");}a();
定义完直接调用即可,很简单。但是,继续学,又看到了这样的代码:
function man( name ){  this.name = name;  this.sayName = function()  {alert(this.Name);  }}
于是乎晕了,哪来的this?又没有定义对象哪来的this?于是乎在网上搜索答案,最后知道了原来JS也可以面向对象。然后网上说用function声明的可以是函数,也可以是对象。这就让人有点乱了。本来脚本语言一般都是弱类型的语言,这个已经让人有点不习惯了,又来了一个既可以是函数又可以是对象的,头都大了。时隔半年后的今天,我在写程序C++的时候,顺便将JS的面向对象又想了一遍。貌似是这样的:
JS的面向对象和C++的面向对象是一样的。在C++里,我们要先声明一个类,然后再在.cpp文件中实现它。而在JavaScript里呢?如果还要声明一个类,然后再定义,那得有多少代码?于是乎JS省去了类的声明这个环节,直接将一个函数看成一个构造函数,在定义的同时也就声明了它即声明和定义是一起的,这和变量的使用也是一样的,如this.name = "呵呵",你只要直接给它赋值它就自动产生了。在JS里,所有的函数可以看成是构造函数 。这样做是为了减少代码量,从而提高网页加载速度。
但是,JS里的函数又和C++里的构造函数有不同的地方:C++构造函数是没有任何返回值的,而JS里可以有。为什么呢,因为为了简化代码,统一使用function来声明变量不是一样吗?何必多一个关键词呢?function本来就是声明函数的,不过要是想产生对象的话,它是作为构造函数来用的。
这样的话,我们再来看看this。我记得当我学this的时候学得是满头雾水。有的说是指向调用者,恩,对的,可是我还是没真正理解调用者是谁,为什么是调用者?网页中那么多DOM对象,有时候使用this的时候生怕使用错了,感觉总是不确定这个this到底指向谁,比如一个按钮的事件响应函数,它被调用时this就指向按钮,那么为什么呢,有什么用呢?有没有一目了然的判断方法?或者说它和C++里面的this是不是一样(编程也要追求融汇贯通,否则学语言就是死记了)?答案是肯定的:
首先你得了解C++里面的this是怎么回事。它是一个编译器给类的非静态成员函数加上去的一个默认的参数,这个类可能产生很多实例,但是所有实例共享这些函数,实例的成员变量一般是不同的,那么这些函数怎么判断谁是谁呢?答案就是this,每个对象调用类的成员函数时,它会带着一个指向自己的一个指针,把它交给类的成员变量,我们知道地址是唯一的,那么类的成员函数就根据这个地址就找到了这个实例所在的地方,从而就能对这个地方的内存进行操作。OK!那么JS也是一样的,它也有一个默认参数this,谁调用它,this就是这个调用者的对象指针(JS里说指针呢不太好,感觉应该说是句柄,更直接点说,这个this就是那个调用者)。这样如果你直接调用一个函数

<pre name="code" class="javascript">function a(){   alert( this );}a();

结果是很简单啦,因为Window是最高层的对象,那么所谓的全局函数就是Window对象的成员函数,所谓的全局变量就是Window的成员变量啦!说到这,可以看出来Js中的对象是层层嵌套的,也就是C++中的内部类,外部类的关系啦!

那么何时作为对象,何时作为函数?

答案是:可以作为纯对象,又可以作为纯函数,又可以同时作为对象和函数,具体返回值看你的调用方法。推荐一篇文章的参考链接:http://www.cnblogs.com/andyliu007/archive/2012/07/27/2795415.html。但是推荐归推荐,我对于这篇文章中的观点并不是完全赞同。下面是一段文章的截图:

根据作者的意思,我们可以认为:如果一个函数有返回值,那么以new的方式使用该函数时,得到的返回值与函数的返回值的类型有关,且当函数返回值是基本类型时,得到的返回值为一个object的对象;当函数返回值为一个引用类型的对象时,那么这个对象就是由这个对象的原型决定,至于是什么,并不清楚。那么请看以下代码:

function Test1() {this.id = 1;return 1000;}var myTest = new Test1();alert( myTest.id );                                                       //          可访问!alert( typeof myTest );
结果是



第一张图片的结果说明了返回的对象并不是函数返回值的prototype,而是一个Test1的对象。

如果返回一个对象呢?

function Test1() {this.id = 1;        return new String("我是返回值");}var myTest = new Test1();alert( myTest.id );//     无法访问alert( myTest.legth );alert( typeof myTest );




说明返回的是一个实质上是String类型而typeof是object的对象,你也可以显式地将new的返回值强制类型转换成String,也一切正常。

那么有没有可能是因为String是内置的类型才可以访问?返回普通的对象也是那样吗?请看下例

function Test1() {this.id = 1;return new Test2();}function Test2(){this.id = 2;}var myTest = new Test1();alert( myTest.id );alert( typeof myTest );
结果:



说明:

如果函数返回值是原始类型时,没用,new返回的还是这个函数的对象。

如果函数的返回值是对象时,那么new返回的就是这个对象。

不过你如果没有强制类型转换的话,那么typedef出来的类型将是object。

还有需要注意,原始类型的string和引用类型的String( 首字母大写 )是不一样的,一个是值,一个是类,类可以有很多属性和方法,原始类型没有。


this.name和name的区别

那么既然谈到了this.声明的变量,那么它和不用this.声明的变量有什么区别呢?先看一段C++示例代码:

A.h文件class A(){  public:    A();    ~A();  public:    int name ;}A.cpp文件A::A(){    this.name = 0;//成员变量,和对象同生命周期    int name1 = 1;//函数的局部变量,函数执行完,内存就会被其他内容覆盖}A::~A(){}
JS代码

function A(){    this.name = 0;//成员变量,会随对象一直存在    name1 = 1;//局部变量,会随对象一直存在(为什么这么说?测试出来的)}

可以看出:
1、JS里的对象没有过多的访问修饰符,只有默认的public,即都可以通过"对象.变量名"的形式在外部访问。
2、JS里的name1有两种解释方法

1)  看成它对应C++构造函数内的局部变量(很多文章都称之为局部变量,如 http://www.jb51.net/article/24101.htm)。这么想的话,那么就有:JS局部变量和C++中的局部变量不同,它和成员变量的生命周期一样。

2)  这里我们它想成它对应C++中用private修饰的变量(私有变量):外部不能通过对象访问,它的生命周期也和对象一样,正好。

我比较偏向 2),因为看成是构造函数的局部变量的话,那么一个类的构造函数是访问不了的,因为JS里根据变量的函数作用域可知,里面的函数可以访问外面函数的变量的,这样才能实现闭包,二者矛盾。所以1)的类比没有2)确切。

总结一下

函数里带this的变量相当于C++中public修饰的变量。

        函数里不带this的变量相当于C++中private修饰的变量。


所谓闭包

那么问题来了,有时候我们要访问变量name1啊,怎么办呢?不能通过"对象.变量名"的形式,因为它不是对象的成员变量。怎么办呢?

方法一:C++中是通过成员函数来读写私有变量的:

A.h文件class A(){  public:    A();    ~A();  public:    int name ;  private:    int name1;  public:    getName();}A.cpp文件A::A(){    this.name = 0;//成员变量,和对象同生命周期    this.name1 = 1;}A::~A(){}A::getName(){    return this.name1;}

那么同样,JS中你写一个成员函数来读取或者写入
function A(){this.name = 0;name1 = 1;this.getName1 = function(){ return name1; }}gN = new A();alert( gN.getName1() );

结果:

方法二:

将函数返回出来

function A(){this.name = 0;name1 = 1;getName1 = function(){ return name1; }
        return getName1;}gN = new A();alert( gN() );

结果


这种方法涉及到两次返回,不容易理解,但是这种方法在JS里比较有名,叫做闭包。不过我个人推荐用成员函数来返回局部变量(也可以叫做私有成员变量)。因为将一个函数返回出来保存在了"全局变量中,这导致对象始终在内存中"( 引用自 http://www.jb51.net/article/24101.htm )。通过成员函数方法返回的也一样,也是始终存在于内存。

new 和 this

何为new呢?看代码
function A(){    this.name = 1;}a = new A();
这个过程发生了什么呢?(还是按C++的过程来模拟、类比,如有错误请指教哈)
1、new一块内存区域。
2、将这块区域的内存的地址传递给构造函数A() ;
3、运行A(),对这块区域进行变量拷贝,再加一个__proto__属性指向基类。

那么这样,this的作用也就一目了然了,就是实例对象的内存地址。故call,apply这两个JS重要而且难懂的函数的第一个参数this就很容易理解了吧。
这里仅仅说一个方面,至于原型链的指向啊都不说了,具体可以看这篇文章:http://blog.csdn.net/zacklin/article/details/7896859。

关于继承

上面说到了call,和apply,下面说一下call方式实现的继承(类式继承)。
建议先看这篇文章http://segmentfault.com/a/1190000002440502,网站页面也很漂亮( 话说国内很多大网站那页面真有点难看 )。
下面是代码
function parent( name ){this.name = name;this.sayName = function(){alert(this.name);}}function child( name ){parent.call(this,name);alert(this.sayName);               //    相当于执行了一次this.name = "parent";this.sayName = function(){alert(this.name);}}c = new child("child");c.sayName();
结果:


为什么说"借用构造函数虽然解决了刚才两种问题,但没有原型,则复用无从谈起"呢?第一个alert提示说明了child对象中也有一个函数,所以喽,它没有使用parent的函数代码,即没有复用。所以这种继承会浪费内存。但是也不排除比较智能的执行器能够看出来两个函数一样就只保留一份函数也说不定,呵呵,应该不会这么智能吧?

再说说组合式继承

还是参考的这篇文章:http://segmentfault.com/a/1190000002440502。
组合式继承就是 原型继承+类式继承(就是call继承)。上面我们说了,call不仅会拷贝变量,而且会拷贝代码。那么为什么还用call呢?这么用不就行了:
function Parent(age){        this.name = ['mike','jack','smith'];                    //                     这里面只添加属性        this.age = age;}Parent.prototype.run = function ()                        //                     方法(成员函数)在这里(原型上)添加{        return this.name  + ' are both' + this.age;};function Child(age){        Parent.call(this,age);                                         //                        call继承属性}Child.prototype = new Parent();                           //                      原型链继承方法 var test = new Child(21);                                       //                      写new Parent(21)也行 alert(test.run());                                                       //                       mike,jack,smith are both21
即只用call来继承属性,用原型来继承方法。也就是假如现在有一个父类A,如果想让它被继承,那么最好将它的属性定义在构造函数里,将它的方法定义在它的prototype里,然后用call实现属性继承,用prototype直接继承A,从而继承方法。

还有寄生式继承:

名字不知道哪来的,反正看不出来什么意思。因为组合式继承有个小问题,就是多一次调用问题。怎了解决呢?只是将组合式继承稍微改动一下,即不直接继承自A,而是直接继承自A.prototype,因为prototype是已经new好的对象,没看出来?这也解释了为什么设置prototype时需要new,这和C++不同,C++只要声明就可以。看下例
Child.prototype = new Parent();                           //                      原型链继承方法
再上代码:
function A( name,age ){this.name = name;this.age  = age;}A.prototype.sayNameAge = function(){alert(this.name+"   "+this.age);}function B( name,age ){A.call( this,name,age );}B.prototype = A.prototype;B.constructor = B;<span style="white-space:pre"></span>//<span style="white-space:pre"></span>定位回来,都则就指向A,不知道为啥,求大神指教b = new B( "我是B",2 );b.sayNameAge();


结果:

注意:prototype的重定向会导致constructor的变化。所以需要重定向constructor。
感觉写得比那篇文章中的要简单呀,\*_*/。


再推荐一个链接:http://www.w3school.com.cn/js/pro_js_referencetypes.asp

最后

说了那么多,都是语法而已,假如某天语法变了,这些都没用了。但是思路是要有的。
在我看来,JS和C++/Java没太大区别,这也说明了编程语言都是相通的。
第一次写这么长的博文,写得乱,以后会修改,各位且凑合看哈!

0 0