【IT168 技术】文章的目的是让更多的程序员深入理解JavaScript的一些概念,其实关于这些,我们在可以在网上看到很多类似的内容,所以现在该是向深入理解的方向靠拢的时候了。
一.function
从一开始接触到js就感觉好灵活,每个人的写法都不一样,比如一个function就有N种写法。如:functionshowMsg(){},varshowMsg=function(){},showMsg=function(){}。似乎没有什么区别,都是一样的嘛,真的是一样的吗,大家看看下面的例子:
//声明式,定义代码先于函数执行代码被解析
functiont1(){dwn("t1");
t1();
functiont1(){dwn("newt1");}
t1();
//引用式,在函数运行中进行动态解析
vart1=function(){dwn("newnewt1");}
t1();
vart1=function(){dwn("newnewnewt1");}
t1();
//以上输出:newt1,newt1,newnewt1,newnewnewt1
可能想着应该是输出t1,newt1,newnewt1,newnewnewt1,结果却并不是这样,应该理解这句话:声明式,定义代码先于函数执行代码被解析。如果深入一步,应该说是scope链问题,实际上前面两个方法等价于window.t1,可以理解为t1是window的一个公有属性,被赋了两次值,以最后一次赋值为最终值。而后面两个方法,可以理解为是t1是个变量,第四个方法的var去掉之后的结果仍然不会改变。
然而,当第四个方法改成functiont1(){}这样的声明式时,结果变成了newnewnewt1,newnewnewt1,newnewt1,newnewt1。前面两个按照我的理解可以很好的理解为什么是这个答案,第三个也可以理解,但是最后一个输出让我比较纠结,希望有高手出现解答一下。另外匿名函数还有(function(){...})()这样的写法,最后一个括号用于参数输入还有vart1=newfunction(){..}这样的声明,实际上t1已经是一个对象了。
{
vartemp=100;//私有成员
this.temp=200;//公有成员,这两个概念会在第三点以后展开说明
returntemp+this.temp;
}
alert(typeof(t2));//object
alert(t2.constructor());//300
除此之外,还有使用系统内置函数对象来构建一个函数,例:
vart3=newFunction('vartemp=100;this.temp=200;returntemp+this.temp;');
alert(typeof(t3));//function
alert(t3());//300
二.创建对象
首先我们理解一下面向对象编程(Object-OrientedProgramming,OOP),使用OOP技术,常常要使用许多代码模块,每个模块都提供特定的功能,每个模块都是孤立的,甚至与其它模块完全独立。这种模块化编程方法提供了非常大的多样性,大大增加了代码的重用机会。可以举例进一步说明这个问题,假定计算机上的一个高性能应用程序是一辆一流赛车。如果使用传统的编程技巧,这辆赛车就是一个单元。
如果要改进该车,就必须替换整个单元,把它送回厂商,让汽车专家升级它,或者购买一个新车。如果使用OOP技术,就只需从厂商处购买新的引擎,自己按照说明替换它,而不必用钢锯切割车体。不过大部分的论点是,JavaScript并不是直接的面向对象的语言,但是通过模拟可以做到很多面向对象语言才能做到的事,如继承,多态,封装,JavaScript都能干。
//newObject,实例化一个Object
vara=newObject();
a.x=1,a.y=2;
//对象直接量
varb={x:1,y:2};
//定义类型
functionPoint(x,y){//类似于C#中的类this.x=x;this.y=y; }
varp=newPoint(1,2);//实例化类
第一种方法通过构造基本对象直接添加属性的方法来实现,第二种和第一种差不多,可以看成是第一种方法的快捷表示法。第三种方法中,可以以”类“为基础,创造多个类型相同的对象。
三.对象属性的封装(公有和私有)
以例子来说明:
varm_elements=[];//私有成员,在对象外无法访问 m_elements=Array.apply(m_elements,arguments);
//此处模拟getter,使用时alist.length;
//等价于getName()方式:this.length=function(){returnm_elements.length;},使用时alist.length();
//公有属性,可以通过"."运算符或下标来访问 this.length={
valueOf:function(){
returnm_elements.length;
},
toString:function(){
returnm_elements.length;
}
}
//公有方法,此方法使用得alert(alist)相当于alert(alist.toString())
this.toString=function(){ returnm_elements.toString();
}
//公有方法
this.add=function(){
m_elements.push.apply(m_elements,arguments);
}
//私有方法如下形式,这里涉及到了闭包的概念,接下来继续说明
//varadd=function()或functionadd()
//{
//m_elements.push.apply(m_elements,arguments);
//}
}
varalist=newList(1,2,3);
dwn(alist);//=alert(alist.toString()),输出1,2,3
dwn(alist.length);//输出3
alist.add(4,5,6);
dwn(alist);//输出1,2,3,4,5,6
dwn(alist.length);//输出6
四.属性和方法的类型
JavaScript里,对象的属性和方法支持4种不同的类型:privateproperty(私有属性),dynamicpublicproperty(动态公有属性),staticpublicproperty/prototypeproperty(静态公有属性或原型属性),staticproperty(静态属性或类属性)。私有属性对外界完全不具备访问性,可以通过内部的getter和setter(都是模拟);动态公有属性外界可以访问,每个对象实例持有一个副本,不会相互影响;原型属性每个对象实例共享唯一副本;类属性不作为实例的属性,只作为类的属性。以下是例子:
varp=100;//privateproperty this.x=10;//dynamicpublicproperty
}
myClass.prototype.y=20;
//要想成为高级JavaScript阶段,prototype和闭包必须得理解和适当应用
myClass.z=30;//staticproperty
vara=newmyClass();
dwn(a.p)//undefined
dwn(a.x)//10
dwn(a.y)//20
a.x=20;
a.y=40;
dwn(a.x);//20
dwn(a.y);//40
delete(a.x);//删除对象a的属性x
delete(a.y);//删除对象a的属性y
dwn(a.x);//undefined
dwn(a.y);//20静态公有属性y被删除后还原为原型属性y
dwn(a.z);//undefined类属性无法通过对象访问
dwn(myClass.z);
五.原型(prototype)
这里只讲部分,prototype和闭包都不是几句话都能讲清楚的,如果这里可以给你一些启蒙,则万幸矣。习语”照猫画虎“,这里的猫就是原型,虎是类型,可以表示成:虎.prototype=某只猫or虎.prototype=new猫()。因为原型属性每个对象实例共享唯一副本,所以当实例中的一个调整了一个原型属性的值时,所有实例调用这个属性时都将发生变化,这点需要注意,以下是原型关系的类型链:
ClassA.prototype=newObject();
functionClassB(){ } ClassB.prototype=newClassA();
functionClassC(){ } ClassC.prototype=newClassB();
varobj=newClassC();
dwn(objinstanceofClassC);//true
dwn(objinstanceofClassB);//true
dwn(objinstanceofClassA);//true
dwn(objinstanceofObject);//true
带默认值的Point对象:
functionPoint2(x,y){
if(x)this.x=x;
if(y)this.y=y;
}
//设定Point2对象的x,y默认值为0 Point2.prototype.x=0;
Point2.prototype.y=0;
//p1是一个默认(0,0)的对象
varp1=newPoint2();//可以写成varp1=newPoint2也不会出错,WHY
//p2赋值
varp2=newPoint2(1,2);
dwn(p1.x+","+p1.y);//0,0
dwn(p2.x+","+p2.y);//1,2
delete对象的属性后,原型属性将回到初始化的状态: functionClassD(){
this.a=100;
this.b=200;
this.c=300 }
ClassD.prototype=newClassD();//将ClassD原有的属性设为原型,包括其值 ClassD.prototype.reset=function(){//将非原型属性删除
for(vareachinthis){
deletethis[each];
}
}
vard=newClassD();
dwn(d.a);//100
d.a*=2;
d.b*=2;
d.c*=2;
dwn(d.a);//200
dwn(d.b);//400
dwn(d.c);//600
d.reset();//删掉非原型属性,所有回来原型
dwn(d.a);//100
dwn(d.b);//200
dwn(d.c);//300
六,继承
如果两个类都是同一个实例的类型,那么它们之间存在着某种关系,我们把同一个实例的类型之间的泛化关系称为继承。C#和JAVA中都有这个,具体的理解就不说了。在JavaScript中,并不直接从方法上支持继承,但是就像前面说的,可以模拟。
方法可以归纳为四种:构造继承法,原型继承法,实例继承法和拷贝继承法。融会贯通之后,还有混合继续法,这是什么法,就是前面四种挑几种混着来。以下例子来源于王者归来,其中涉及到了apply,call和一些Array的用法,有兴趣的可以自己在园子里搜索一下。
1.构造继续法例子:
functionCollection(size)
{ this.size=function(){returnsize};//公有方法,可以被继承 } Collection.prototype.isEmpty=function(){//静态方法,不能被继承 returnthis.size()==0; }
//定义一个ArrayList类型,它"继承"Collection类型 functionArrayList()
{
varm_elements=[];//私有成员,不能被继承 m_elements=Array.apply(m_elements,arguments);
//ArrayList类型继承Collection this.base=Collection;
this.base.call(this,m_elements.length); this.add=function()
{
returnm_elements.push.apply(m_elements,arguments);
}
this.toArray=function()
{ returnm_elements; }
}
ArrayList.prototype.toString=function()
{
returnthis.toArray().toString();
}
//定义一个SortedList类型,它继承ArrayList类型 functionSortedList()
{
//SortedList类型继承ArrayList this.base=ArrayList;
this.base.apply(this,arguments); this.sort=function()
{
vararr=this.toArray();
arr.sort.apply(arr,arguments);
}
}
//构造一个ArrayList
vara=newArrayList(1,2,3);
dwn(a);
dwn(a.size());//a从Collection继承了size()方法 dwn(a.isEmpty);//但是a没有继承到isEmpty()方法
//构造一个SortedList
varb=newSortedList(3,1,2);
b.add(4,0);//b从ArrayList继承了add()方法
dwn(b.toArray());//b从ArrayList继承了toArray()方法
b.sort();//b自己实现的sort()方法
dwn(b.toArray());
dwn(b);
dwn(b.size());//b从Collection继承了size()方法
2.原型继承法例子
functionPoint(dimension)
{
this.dimension=dimension;
}
//定义一个Point2D类型,"继承"Point类型 functionPoint2D(x,y)
{
this.x=x;
this.y=y;
}
Point2D.prototype.distance=function()
{
returnMath.sqrt(this.x*this.x+this.y*this.y);
}
Point2D.prototype=newPoint(2);//Point2D继承了Point
//定义一个Point3D类型,也继承Point类型 functionPoint3D(x,y,z)
{ this.x=x; this.y=y; this.z=z; } Point3D.prototype=newPoint(3);//Point3D也继承了Point
//构造一个Point2D对象
varp1=newPoint2D(0,0);
//构造一个Point3D对象
varp2=newPoint3D(0,1,2);
dwn(p1.dimension);
dwn(p2.dimension);
dwn(p1instanceofPoint2D);//p1是一个Point2D
dwn(p1instanceofPoint);//p1也是一个Point
dwn(p2instanceofPoint);//p2是一个Point
3.实例继承法例子
在说此法例子之前,说说构造继承法的局限,如下:
{
this.base=Date;
this.base.apply(this,arguments);
}
vardate=newMyDate();
alert(date.toGMTString);//undefined,date并没有继承到Date类型,所以没有toGMTString方法
核心对象的某些方法不能被构造继承,原因是核心对象并不像我们自定义的一般对象那样在构造函数里进行赋值或初始化操作换成原型继承法呢?,如下:
MyDate.prototype=newDate();
vardate=newMyDate();
现在,换成实例继承法:
{
varinstance=newDate();//instance是一个新创建的日期对象
instance.printDate=function(){
document.write("<p>"+instance.toLocaleString()+"</p>");
}//对instance扩展printDate()方法 returninstance;//将instance作为构造函数的返回值返回
}
varmyDate=newMyDate();
dwn(myDate.toGMTString());//这回成功输出了正确的时间字符串,看来myDate已经是一个Date的实例了,继承成功 myDate.printDate();//如果没有returninstance,将不能以下标访问,因为是私有对象的方法
4,拷贝继承法例子
{
for(vareachinobj)
{
this.prototype[each]=obj[each];
//对对象的属性进行一对一的复制,但是它又慢又容易引起问题
//所以这种“继承”方式一般不推荐使用
}
}
varPoint2D=function(){ //…… }
Point2D.extends(newPoint()) { //…… }
这种继承法似乎是用得很少的。
5,混合继承例子
{
this.x=x;
this.y=y;
}
functionColorPoint2D(x,y,c)
{
Point2D.call(this,x,y);//这里是构造继承,调用了父类的构造函数
//从前面的例子看过来,这里等价于
//this.base=Point2D;
//this.base.call(this,x,y);
this.color=c;
}
ColorPoint2D.prototype=newPoint2D();//这里用了原型继承,让ColorPoint2D以Point2D对象为原型