前言
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() );
结果
new 和 this
function A()
{this.name = 1;
}
a = new A();
这个过程发生了什么呢?(还是按C++的过程来模拟、类比,如有错误请指教哈)
关于继承
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();
结果:
再说说组合式继承
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,从而继承方法。
还有寄生式继承:
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();
最后