1.1. 继承(prototype)
JavaScript 中的继承可以通过原型链来实现,调用对象上的一个方法,由于方法在JavaScript 对象中是对另一个函数对象的引用,因此解释器会在对象中查找该属性,如果没有找到,则在其内部对象 prototype 对象上搜索,由于 prototype 对象与对象本身的结构是一样的,因此这个过程会一直回溯到发现该属性,则调用该属性,否则,报告一个错误。
关于原型继承,我们不妨看一个小例子,1.1-1:
function Base(){
this.baseFunc = function(){
alert("base behavior");
}
}
function Middle(){
this.middleFunc = function(){
alert("middle behavior");
}
}
Middle.prototype = new Base();
function Final(){
this.finalFunc = function(){
alert("final behavior");
}
}
Final.prototype = new Middle();
function test(){
var obj = new Final();
obj.baseFunc();
obj.middleFunc();
obj.finalFunc();
}
在 function test 中,我们 new 了一个 Final 对象,然后依次调用 obj.baseFunc,由于 obj 对象上并无此方法,则按照上边提到的规则,进行回溯,在其原型链上搜索,由于Final 的原型链上包含 Middle,而 Middle 上又包含 Base,因此会执行这个方法,这样就实现了类的继承。
1.1. 封装
我们可以通过JavaScript 的函数实现封装,封装的好处在于未经授权的客户
代码无法访问到我们不公开的数据,我们来看这个例子,1.2-1:
function Person(name){
//private variable
var address = "The Earth";
//public method
this.getAddress = function(){
return address;
}
//public variable
this.name = name;
}
//public
Person.prototype.getName = function(){
returnthis.name;
}
//public
Person.prototype.setName = function(name){
this.name = name;
}
首先声明一个函数,作为模板,用面向对象的术语来讲,就是一个类。用 var 方式声明的变量仅在类内部可见,所以 address 为一个私有成员,访问 address 的唯一方法是通过我们向外暴露的 getAddress 方法,而 get/setName,均为原型链上的方法,因此为公开的。
我们可以做个测试:
var jack = new Person("jack");
alert(jack.name);//jack
alert(jack.getName());//jack
alert(jack.address);//undefined
alert(jack.getAddress());//TheEarth
直接通过jack.address来访问address变量会得到undefined。我们只能通过jack.getAddress 来访问。这样,address 这个成员就被封装起来了。
另外需要注意的一点是,我们可以为类添加静态成员,这个过程也很简单,只需要为函数对象添加一个属性即可。比如,下面例子1.2-2:
function Person(name){
//private variable
var address = "The Earth";
//public method
this.getAddress = function(){
return address;
}
//public variable
this.name = name;
}
Person.TAG = "javascript-core";//静态变量
alert(Person.TAG);
也就是说,我们在访问Person.TAG 时,不需要实例化Person 类。这与传统的面向对象语言如Java 中的静态变量是一致的。
1.1. 原型对象
原型(prototype),是 JavaScript 特有的一个概念,通过使用原型,JavaScript 可以建立其传统 OO 语言中的继承,从而体现对象的层次关系。JavaScript 本身是基于原型的,每个对象都有一个 prototype 的属性来,这个 prototype 本身也是一个对象,因此它本身也可以有自己的原型,这样就构成了一个链结构。访问一个属性的时候,解析器需要从下向上的遍历这个链结构,直到遇到该属性,则返回属性对应的值,或者遇到原型为 null 的对象(JavaScript的基对象 Object 的构造器的默认prototype 有一个 null 原型),如果此对象仍没有该属性,则返回 undefined.
下面我们看一个具体的例子,1.3-1:
//声明一个对象base
function Base(name){
this.name = name;
this.getName = function(){
returnthis.name;
}
}
//声明一个对象child
function Child(id){
this.id = id;
this.getId = function(){
returnthis.id;
}
}
//将child的原型指向一个新的base对象
Child.prototype = new Base("base");
//实例化一个child对象
var c1 = new Child("child");
//c1本身具有getId()方法
alert(c1.getId());
//由于c1从原型链上"继承"到了getName方法,因此可以访问
alert(c1.getName());
输出结果
child base
由于遍历原型链的时候,是有下而上的,所以最先遇到的属性值最先返回,通过这种机制可以完成重载的机制。
1.1. 闭包 用createClass谈闭包
1.1.1. 闭包及其作用
闭包是指函数可以使用函数之外定义的变量!
JavaScript之中,变量的作用范围一般就是所在函数的范围,也就是所在函数的大括号包围的范围!
这么来看的话,闭包就是一种改变变量作用范围的方式!使用闭包可以在原本的函数作用范围之外来使用函数之内的变量!
来看一个简单的例子1.4.1-1(出自ECMAScript手册):
var sMessage = "hello world";
function sayHelloWorld() {
alert(sMessage);
}
sayHelloWorld();
在方法中,使用全局变量就是一个简单的闭包实例!
分析:
在上面这段代码中,脚本被载入内存后,并没有为函数 sayHelloWorld() 计算变量 sMessage 的值。该函数捕获 sMessage 的值只是为了以后的使用,也就是说,解释程序知道在调用该函数时要检查 sMessage 的值。检查的时候,先去自己的作用范围查找是否有sMessage这个局部变量,如果没有找到,则会去比自己的作用范围之外去查找,最终在找到全局变量并赋值!sMessage 将在函数调用 sayHelloWorld() 时(最后一行)被赋值,显示消息 "hello world"。
1.1.1. 闭包的使用---createFunction
上面的例子是一个简单的闭包,只包含了一层闭包!如果是多层闭包,也就是在一个函数中定义另一个函数,在内层使用外层变量和全局变量的话就是多层闭包!这样会使闭包变得复杂!
看下面的createFunction例子1.4.2-1:
var globalVar = 8;
function createFunction(arg1, arg2){
function exampleReturned(innerArg){
return ((arg1+ arg2)/(innerArg + globalVar));
}
returnexampleReturned;
}
var exampleFunction= createFunction(2, 4);
/*
exampleFunction=function(innerArg){return(6)/(8+innerArg);}
*/
分析:
这里,函数createFunction()包括函数exampleReturned() 。内部函数是一个闭包,因为它将获取外部函数的参数arg1和arg2以及全局变量globalVar 的值。同时,它还有自己的局部变量innerArg! exampleReturned() 的最后一步返回了((arg1 + arg2)/(innerArg + globalVar))的值。
在全局调用外层函数的时候,arg1=2,arg2=4,外层函数通过传入的参数创建一个新的函数并返回这个函数!所以,exampleFunction=function(innerArg){return(6)/(8+innerArg);}
调用exampleFunction(4),运行结果:
1.1.1. 总结
可以看到,闭包是 ECMAScript 中非常强大多用的一部分,可用于执行复杂的计算。其本质就是改变变量的作用范围!
1.2. 事件的机制
1.2.1. 事件及其作用
事件是可以被JavaScript 侦测到的行为。
一般在JavaScript应用程序中,既有数据来源,又有这些数据的视觉表现---HTML,要在这两个方面之间进行同步就必须通过与用户交互,并据此来来更新用户界面。而事件就是粘合应用程序所有用户交互的胶水!
现在的浏览器基本都提供了在特定交互动作发生时引发的一系列事件,比如用户移动鼠标,敲击键盘或离开页面等,这些我们称为DOM元素的事件属性。我们可以给这些事件注册一些函数(为这些事件属性绑定处理函数),一旦事件发生就会执行!
1.2.2. 事件及事件的处理函数(回调函数)
JavaScript之中对事件采取异步回调的方式进行处理!
也就是说,一个DOM元素触发某个特定事件的时候,你可以指派一个回调函数来处理它,指派的过程就是为事件绑定事件的处理函数的过程!提供这个回调函数(方法体就是需要执行的代码)的引用,其它的让浏览器来处理!
看一个例子1.5.2-1:
//注册一个函数,当页面加载完毕的时候调用
window.οnlοad=loaded;
//页面载入完毕之后调用的函数
function loaded(){
//页面载入完毕,执行这个函数的方法体:弹出一个提示框
alert("Open!");
}
分析:
上面例子1.5.2-1之中,第一句绑定到事件监听函数(window的onload属性)上的事件处理函数(loaded函数)。浏览器在页面加载完毕后,触发window的onload事件,浏览器调用关联的处理函数loaded并执行!结果就是弹出提示框,显示”Open”!
1.1. 匿名函数
1.1.1. 匿名函数
从字面理解,匿名函数就是没有名字的函数。实际上,匿名函数就是一段完成某件事的代码而已!其存在意义在于,有时候我们需要使用函数完成某件事,但是这个函数是临时性的,那就没有理由专门为其生成一个顶层的函数对象!
1.1.2. 匿名函数的例子
来看一个例子1.6.2-1
//定义一个map函数,包含两个参数,存放数据的array,操作数据的函数func
function map(array, func){
var res = []; //存放操作之后的数据
for ( var i = 0, len = array.length;i < len; i++){
res.push(func(array[i])); //遍历操作数据,把结果存储到res中
}
return res; //返回操作之后的数据
}
//调用map函数,操作数据的函数是在原来数据基础上加1
var mapped = map([1, 3, 5, 7,8], function (n){
return n = n + 1;
});
print(mapped);//打印操作之后的数据分析:
map函数的第二个参数为一个函数,可以看出在调用map函数的时候,第二个参数我们传递了一个匿名函数进去!我们可以分析一下这里为什么使用了匿名函数?
这个函数对 map 的第一个参数 ( 数组 ) 中的每一个都有作用,但是对于 map 之外的代码可能没有任何意义,因此,我们无需为其专门定义一个函数,匿名函数已经足够。
1.1. 谈一个js函数的私有性和公开性
1.1.1. js函数
在ECMAScript之中,函数也是功能完整的对象(类)。
任何函数都应看做Function类的实例!
既然函数也是对象,那么他们肯定也有属性和方法!这些属性和方法比如也可以限定其是否可以被访问以及访问的方式!所以就有了私有属性,私有方法和公开属性,公开方法!
1.1.2. 例子
首先,来看一个私有属性和私有方法的例子,1.7.2-1:
var demo1=function(){
function privateMethod(){//定义一个私有方法
alert("privatemethod")
}
privateMethod();//私有方法可以在函数作用域范围内使用
var privateVar="私有变量只有在函数或者对象作用域范围内能访问"
}
privateMethod();//在函数外部调用会出错
var demoObject=new demo1(); //实例化一个demo1对象
demoObject.privateMethod();//这样子还是不能调用
结论:
可以看出在函数外面不可以使用私有方法和私有属性(未给出)!
接下来,来看一个特权方法的例子,1.7.2-2:
var demo2=function(){
var name=”Demo2--name”;//私有属性
function showname(){//私有方法
alert(name);
}
this.show=function(){//通过使用this关键字定义一个特权方法。
showname(); //在特权方法中访问私有方法;
}
}
var demoObject2=new demo2(); //实例化一个demo1对象
demoObject2.show();
结论:
可以看出特权方法可以访问私有方法,同时特权方法可以在外部调用!也就是说可以通过访问特权函数来使用部分私有方法!
最后,来看一个公开方法的例子,1.7.2-3:
var demo3=function(){
function showname(){//私有方法
alert(this.name);//这里this.name定义了一个公开属性
}
this.show=function(){//通过使用this关键字定义一个特权方法
showname(); //在特权方法中访问私有方法;
}
}
demo3.prototype.setname=function(str){//定义一个公开方法
name=str;//为name属性赋值
}
var demoObject3=new demo3(); //实例化一个demo1对象
demoObject3.setname(“Demo3--name”);//调用公有方法赋值
demoObject3.show();//调用特权方法
结论:可以看出公开方法可以从对象外部直接调用!
1.1.1. 总结
私有属性就是在对象内部使用”var”来声明的变量,它只能被私有方法和特权方法访问!
私有方法是在对象的构造函数里声明,或者使用”var”来定义的函数,它能被特权方法和私有函数访问调用!
公开属性就是通过this.variableName来定义的属性,在对象外部可以进行读写!
公开方法通过ClassName.prototype.methodName=function(){...}来定义而且可以从对象外部来调用!
特权方法可以访问私有变量和方法,而它本身可以被公有方法和外界访问!使用this关键字来定义!
私有之后,就对外封装起来了!----封装性!