对象直接量(也称为对象初始化程序)是由一个列表构成的,这个列表的元素使用冒号分隔的属性/值对,元素之间用逗号隔开,整个列表包含在花括号之中。
Var point={x:2.3,y:-1.2};
对象直接量也可以嵌套
Var rectangle={upperLeft:{x:2,y:2},lowerRight:{x:4,y:4};}
对象直接量中的属性值不必是常量,他可以是任意的javascript表达式:
Var square={upperLeft:{x:point.x,y:point.y},
lowerRight:{x:{point.x+side},y:{point.y+side}}}
数组的创建
使用构造函数Array()来创建数组。:
Var a =new Array();
A[0]=1.2;
A[1]=”javascript”;
A[2]=true;
A[3]={x:1,y:3};
Var a=new Array(1.2,”javascript”,true,{x:1,y:3});
数组直接量(数组初始化程序)是一个封闭在方括号中的序列,序列中的元素由逗号分开。括号内的值将被依次付给数组元素,下表从0开始。
Var a=[1.2,”javascript”,true,{x:1,y:3}];
与对象直接量一样,数组直接量也可以被嵌套:
Var matrix=[[1,2,3],[4,5,6],[7,8,9]];
与对象直接量相同,数组直接量中的元素不必仅限于常量,他可以是任意的表达式。
Var base=1024;
Var table=[base,base+1,base+2,base+3];
数组直接量中还可以存放未定义的元素,只要再逗号之间省去该元素的值就可以了。
Var sparesArray=[1,,,,5];
Null和undefined
Null是一个独一无二的值,有别于其他所有的值。如果一个变量的值为null,那么你就会知道它的值不是有效的对象、数组、数字、字符串和布尔值。
Undefined 在你使用了一个并未声明的变量时,又或者是用了一个并不存在的对象属性时,返回的就是这个值。
虽然undefined和null得知不同,但是==运算符却将两者看作相等:my.prop==null,如果属性my.prop并不存在,或者它存在但是值为null,那么这个比较表达式的值为true。如果必须区分null和undefined,可以使用===运算符或者typeof运算符。
运算符“.”左边的运算数是一个对象,右边的运算数是一个标识符(属性名)。右边的运算数既不能是字符串,也不能是存放字符串的变量,而应该是属性或方法的直接量名,而且不需要指明类型。
非常有用的with语句
在实际应用中,使用with语句可以减少大量的输入。例如,在客户端的javascript中,深度嵌套的对象层次很常用。例如,你可以输入如下的表达式来访问一个HTML表单的元素:
Frames[1].document.forms[0].a
对象字面量:
对象字面量提供了一中非常方便地创建新对象值的方法。一个对象字面量就是包围在一对花括号中的零或多个“名/值”对。对象字面量可以出现在任何允许表达式出现的地方。
Var empty_object={};
Var stooge={
"first-name":"Jerome",
"last-name":"Howard"
};
属性名可以是包括空字符创在内的任何字符串。在对象字面量中,如果属性名是一个合法的javascript标识符且不是保留字,并不强制要求用引号括住属性名。所以用引号括住“first-name”是必须的,但是否括住first_name则是可选的。逗号用来分隔多个“名/值”对。
属性值可以吃年糕包括另一个对象字面量在内的任意表达式中获得。对象是可以嵌套的:
Var flight={
Airline:"Oceanic",
Numbber:815,
Departure:{
IATA:"SYD",
Time:"2004-09-22 14:55",
City:"Syfney"
},
Arrival:{
IATA:"LAX",
Time:"2004-09-23 10:42",
City:"Los Angeles"
}
};
检索:
药检所对象包含的值,可以采用在[]后缀中括住一个字符串表达式。如果字符串表达式是一个常数,而且他是一个合法的javascript标识符而并非保留字,那么也可以用 . 表示法代替。优先使用. 表示法,因为它更紧凑且可读性更好。
Stooge["first-name"] //"Joe"
flight.departure.IATA //"SYD"
如果尝试检索一个并不存在的成员元素的值,将返回一个undefined值。尝试检索一个undefined值将会导致TypeError异常,这是可以通过&&运算符来避免错误。||运算符可以用来填充默认值:
Var middle=stoooge["middle-name"]||"(none)";
Var status=flight.status||"unknown";
Flight.equipment //undifined
Flight.equipment.model //throw "TypeError"
Flight.equipment && flight.equipment.model //undefined
更新:
对象中的值可以通过赋值语句来更新。如果属性名已经存在于对象中,那么这个属性的值被替换。
Stooge['first-name']='Jerome';
如果对象之前并没有那个属性,那么该水洗那个就被扩充到该对象中。
Stooge['middle-name']='Lester';
Stooge.nickname='Curly';
Flight.equipment={model:'Boeing 777'};
Flight.status="overdue";
引用: 对象通过引用来传递。他们永远不会被拷贝。
Var x=stooge;
X.nickname='Curly';
Var nick=stooge.nickname;//因为x和stooge实质相同一个对象的,所以nick为‘Curly’
Var a={}, b={} ,c={};//a,b和c引用一个不同的空对象
枚举
For in语句可用来遍历一个对象中的所有属性名。该枚举过程将会列出所有的属性---宝库函数和你可能不关心的原型中的属性---所以有必要过滤掉那些你不想要的值。最为常用的过滤器是hasOwnProperty方法,以及使用typeof来排除函数:
Var name;
For(name in another_stooge[name]!=='function'){
Document.writeln(name+':'+another_stooge[name]);
}
属性名出线的顺序是不确定的,因此要对任何可能出现的顺序有所准备。如果你想要确保属性以特定的顺序出现,最好的办法就是完全避免使用for in语句,而是创建一个数组
对象直接量(也称为对象初始化程序)是由一个列表构成的,这个列表的元素使用冒号分隔的属性/值对,元素之间用逗号隔开,整个列表包含在花括号之中。
Var point={x:2.3,y:-1.2};
对象直接量也可以嵌套
Var rectangle={upperLeft:{x:2,y:2},lowerRight:{x:4,y:4};}
对象直接量中的属性值不必是常量,他可以是任意的javascript表达式:
Var square={upperLeft:{x:point.x,y:point.y},
lowerRight:{x:{point.x+side},y:{point.y+side}}}
数组的创建
使用构造函数Array()来创建数组。:
Var a =new Array();
A[0]=1.2;
A[1]=”javascript”;
A[2]=true;
A[3]={x:1,y:3};
Var a=new Array(1.2,”javascript”,true,{x:1,y:3});
数组直接量(数组初始化程序)是一个封闭在方括号中的序列,序列中的元素由逗号分开。括号内的值将被依次付给数组元素,下表从0开始。
Var a=[1.2,”javascript”,true,{x:1,y:3}];
与对象直接量一样,数组直接量也可以被嵌套:
Var matrix=[[1,2,3],[4,5,6],[7,8,9]];
与对象直接量相同,数组直接量中的元素不必仅限于常量,他可以是任意的表达式。
Var base=1024;
Var table=[base,base+1,base+2,base+3];
数组直接量中还可以存放未定义的元素,只要再逗号之间省去该元素的值就可以了。
Var sparesArray=[1,,,,5];
Null和undefined
Null是一个独一无二的值,有别于其他所有的值。如果一个变量的值为null,那么你就会知道它的值不是有效的对象、数组、数字、字符串和布尔值。
Undefined 在你使用了一个并未声明的变量时,又或者是用了一个并不存在的对象属性时,返回的就是这个值。
虽然undefined和null得知不同,但是==运算符却将两者看作相等:my.prop==null,如果属性my.prop并不存在,或者它存在但是值为null,那么这个比较表达式的值为true。如果必须区分null和undefined,可以使用===运算符或者typeof运算符。
运算符“.”左边的运算数是一个对象,右边的运算数是一个标识符(属性名)。右边的运算数既不能是字符串,也不能是存放字符串的变量,而应该是属性或方法的直接量名,而且不需要指明类型。
非常有用的with语句
在实际应用中,使用with语句可以减少大量的输入。例如,在客户端的javascript中,深度嵌套的对象层次很常用。例如,你可以输入如下的表达式来访问一个HTML表单的元素:
Frames[1].document.forms[0].a
函数对象
Javascript中,函数就是对象。对象是“名/值”对的集合并拥有一个连到原型对象的隐藏连接。对象字面量产生的对象连接到object.propotype。函数对象连接到Function.prototype(该原型对象本身连接到object.prototype)。每个函数在创建时附有两个附加的隐藏属性:函数的上下文和实现函数行为的代码。每个函数在创建时也虽带有一个protoptype属性。它的值是一个拥有constructor属性且值为该函数的对象。这和隐藏链接到function.prototype完全不同。函数可以存放在变量、对象和数组中,函数可以被当做参数传递给其他函数,函数也可以再返回函数。因为函数是对象,所以函数可以拥有方法。
函数字面量
函数对象可以通过函数字面量来创建。
//创建一个名为add的变量,并用来把两个数字相加的函数赋值给他。
Var add=function(a,b){
Return a+b;
};
函数字面量包括四个部分。第一个部分是保留字function。
第二个部分是函数名,他可以被省略。函数可以用他的名字来递归地调用自己。此名字也能被调试器和开发工具用来识别函数。如果没有跟函数命名,比如上面这个例子,他会被认为是匿名函数。
第三部分是包围在圆括号中的一组参数。其中每个参数用逗号分隔。这些名称被定义为函数中的变量。它们不像普通变量那样被初始化为undefined,而是在该函数被调用时初始化为实际提供的参数的值。
第四部分是包围在花括号中的一组语句。这些语句是函数的主体。他们在函数被调用时执行。
函数字面量可以出现在任何允许表达式出现的地方。函数也可以被定义在其它函数中。通过函数字面量创建的函数对象包含一个连接。这被称为闭包。他是javascript强大表现力的根基。
调用
调用一个函数将暂停当前函数的执行,传递控制权和参数给新函数。除了声明时定义的形式参数,每个函数接收两个附加的参数:this和arguments。参数this在面向对象编程中非常重要,他的只取决于调用的模式。在javascript中一共有四种调用模式:方法调用模式、函数调用模式、构造器调用模式和apply调用模式。这些模式在如何初始化关键参数this上存在差异。调用运算符是跟在任何产生一个函数值的表达式之后的一对圆括号。圆括号内可以包含零个或多个用逗号隔开的表达式。每个表达式产生一个参数值。每个参数值被赋予函数声明时定义的形式参数名。当实际参数(arguments)的个数与形式参数(parameters)的个数不匹配时不会导致运行时的错误。如果实际参数值过多了,超出的参数值将被忽略。如果实际参数值过少,确实得知将会被替换为undefined。对参数值不会进行类型检查:任何类型的纸都可以被传递给参数。
1. 方法调用模式
当一个函数被保存为对象的一个属性时,我们称他为一个方法。当一个方法被调用时,this被绑定到该对象。如果一个调用表达式包含一个属性存取表达式(即一个 . 点表达式或[subcript]下标表达式),那么它被当做一个方法来调用。
//创建myObjiect.他有一个value属性和一个increment方法。
//increment方法接受一个可选的参数。如果参数不是数字,那么默认使用数字1
。
Var myObject={
Value:0;
Increment:function(inc){
This.value+=typeof inc===’number’?inc:1;
}
};
myObject.increment();
document.writeln(myObject.value); //1
myObjet.increment(2);
document.writeln(myObject.value); //3
方法可以使用this去访问对象,所以他能从对象中取值或修改该对象。This到对象的绑定发生在调用的时候。通过this可取得他们所属对象的上下文的方法成为公共方法。
2. 函数调用模式
当一个函数并非一个对象的属性时,那么她被当做一个函数来调用:
Var sum=add(3,4); //sum的值为7
当函数一此模式调用时,this被绑到全局对象。这是语言设计上的一个错误。
3. 构造器调用模式
Javascript是一门基于原型继承的语言。这意味着对象可以直接从其它对象继承属性。该语言是无类别的。 如果在一个函数前面带上new来调用,那么将创建一个隐藏连接到该函数的prototype成员的新对象,同时this将会被绑定到那个新对象上。
New前缀也会改变return语句的行为。
// 创建一个名为Quo的构造器函数。他构造一个带有status属性的对象。
Var Quo =function(string){
This.status=string;
};
//给Quo的所有实例提供一个名为get_status的公共方法。
Quo.prototype.get_status=function(){
Return this.status;
};
//构造一个Quo实例
Var myQuo=new Quo(“confused”);
Document.writeln(myQuo.get_status());//令人困惑
目的就是结合new前缀调用的函数被称为构造器函数。按照约定,它们保存在以大写格式命名的变量里。如果调用构造器函数是没有在前面加上new,肯能会发生非常糟糕的事情。
Apply调用模式
Javascript是一门函数式的面向对象编程语言,所以函数可以拥有方法。
Apply方法让我们构建一个参数数组并用其去调用函数。它也允许我们选择this的值。Apply方法解说两个参数。第一个是被绑定给this的值。第二个就是一个参数数组。
//构造一个包含两个数字的数组,并将它们相加
Var array=[3,4];
Var sum=add.apply(null,array);//sum的值为7
//构造一个包含status成员的对象。
Var statusObject={status:’A_OK’};
//statusObject并没有继承自Quo.prototype,但我们可以在statusObject上调用//get_status方法,尽管statusObject并没有一个名为get_status的方法。
Var status=Quo.prototype.get_status.apply(statusObject);
//stats 值为A-ok‘’
参数
当函数被调用时,会得到一个“免费”奉送的参数,那就是arguments数组。通过他函数可以访问所有它被调用时传给它的参数列表,包括那些没有被反配给函数声明是定义的形式参数。这是编写一个无需指定参数个数的函数成为可能;
//构造一个将很多个值相加的函数
//注意该函数内部定义的变量sum不会与函数外面的sum产生冲突。
//该函数只会看到内部的那个变量。
Var sum=function(){
Var I,sum=0;
For(i=0;i<arguments.length;i+=1){
Sum+arguments[i];
}
Return sum;
};
Document.writeln(sum(4,8,15,16,23,42)); /108
因为语言的一个设计错误,arguments并不是一个真正的数组。他只是一个“类似数组”的对象。Arguments拥有一个length属性,但缺少所有数组方法。
给类型增加方法
Javascript允许给语言的基本类型增加方法。通给object.prototype添加方法来使该方法对所有对象可用:
Function.prototype.method=function(name,func){
This.prototype[name]=func;
Return this;
}
通过给Function.prototype增加一个method方法,我们就不必键入prototype这个属性名。这个缺点也就掩盖了。通过给Number.prottotype添加一个integer方法来改善它。并根据数字的正负来判断是使用Math.celing 还是Math.floor。
Number.method(‘integer’,function()){
Return Math[this<0?’ceiling’:’floor’](this);
};
Document.writeln((-10/3).integer());//-3
Javascript缺少一个移除字符串末端空白的方法。那是一个很容易修复的疏忽;
String.method(‘trim’,function(){
Return this.replace(/^/s+|/s+$/g,’’)
});
Document.writeln(‘”’+” neat “.trim()+’”’);
基本类型的原型是公共的结构,所以在类库混用时务必小心。一个保险的做法是旨在确定没有该方法时才添加它。
//有条件的增加一个方法
Function.prototye.method=function(name,func){
If(!this.prototype[name]){
This.prototype[name]=func;
}
}
闭包(closure)
作用域的好处是内部函数可以访问定义它们的外部参数和变量(除了this和arguments)。这是一件非常好的事情。
和以对象字面量形式去初始化myObject不同,我们通过调用一个函数的形式去初始化myObject,该函数将返回一个对象字面量。此函数定义了一个value变量。该变量对increment河getValue方法总是可用的,但函数的作用域使得他对其他的程序来说是不可见的。
Var myObject=function(){
Var value=0;
Return {
Increment:function(inc){
Value+=typeof inc==’number’?inc:1;
},
getValue:function(){
return value;
}
}
}();
我们并没有把一个函数赋值给myObject。我们是把调用该函数后返回的结果赋值给它。注意最后一行的()。该函数返回一个包含两个方法的对象,并且这些方法享有访问value变量的特权。
//创建一个名为quo的构造函数
//他构造出带有get_status方法和status私有属性的一个对象。
Var quo=function(status){
Return{
Get_status:function(){
Return status;
}
};
};
//构造一个quo实例
Var myQuo=quo(“amazed”);
Document.writeln(myQuo.get_status());
这个quo函数被设计成无须在前面加上new来使用,所以名字也没有首写字母大写。当我们调用quo时,他返回包含get_status方法的一个新对象。该对象的一个引用保存在myQuo中及时quo已经返回了,但get_status方法仍然享有访问quo对象的status属性的特权。Get_status方法并不是访问该参数的一个拷贝;他访问的就是该对象本身。这是可能的,因为该函数可以访问他被创建时所处的上下文环境。这被称为闭包。
//定义一个函数,它设置一个DOM节点为黄色,然后把她渐变为白色
Var fade=function(node){
Var level=1;
Var step=function(){
Var hex=level.toString(16);
Node.style.backgroundColor=’#FFFF’+hex+hex;
If(llevel<15){
Level+=1;
setTimeout(step,100);
}
};
setTimeout(step,100);
};
Fade(document.body);
调用fade,把ocument.body作为参数传递给他(HTML<body>标签所创建的节点)。Fade函数设置level为1。它定义了一个step函数;接着调用setTimeout,并传递step函数和一个时间(100毫秒)给它。然后他返回,fade函数结束。
为了避免下面的问题,理解内部函数能访问外部函数的实际变量而无需复制是很重要的;
//构造一个函数,用错误的方式给一个数组中的节点设置事件处理程序。
//当点击一个节点时,按照预想应弹出一个对话框显示节点的序号
//但当总是会显示节点的数目
Var add_the_handlers=function(nodes){
Var I;
For(i=0;i<nodes.length;i+=1){
Nodes[i].onclick=function(e){
Alert(i);
}
}
};
Add_the_handlers函数目的是给每个时间处理器一个唯一值(i)。他未能达到目的是因为事件处理器函数绑定了便量i,而不是函数在构造时的变量i的值。
//构造一个函数,用正确的方式给一个数组中的节点设置事件处理程序。
//你点击一个接待你,将会弹出一个对话框显示节点的序号。
Var add_the_handlers=function(nodes){
Var I;
For(i=0;i<nodes.length;i+=1){
Nodes[i].onClick=function(i){
Return fuinction(e){
Alert(e);
};
}(i);
}
};
现在,我们定义了一个函数并立即传递i进去执行,而不是把一个函数赋值给onclick。那个函数将返回一个事件处理器函数。这个事件处理器函数绑定的是传进去的i值,而不是定义在add_the_handlers函数里的i值。那个被返回的函数被赋值给onclick。
回调(callbacks):
函数可以让不连续事件的处理变得更容易。例如:假定有这么一个序列,由用户交互开始,向服务器发送请求,最终显示服务器的响应。最淳朴的写法:
Requet=prepare_the_requset();
Responde=send_request_synchronously(request);
Display(response);
这种方式的问题在于网络上的同步请求将会导致客户端进入假死状态。如果网络传输或服务器很慢,响应性的降低是不可接受的。
更好的方式是发起异步的请求,提供一个当服务器的响应达到时将被调用的回调函数。一步函数立即返回,这样客户端不会被阻塞。
Request=prepare_the_request();
Send_request_asynchronously(request,function(response){
Display(response);
});
我们传递了一个函数作为参数给send_request_asynchronously函数,他将在受到响应时被调用。
模块(Module):
模块是一个提供几口却隐藏状态与实现的函数或对象。通过使用函数去产生模块,我闷几乎可以完全摒弃全局变量的使用,从而缓解这个javascript的最为糟糕的特性之一所带来影响。
假定我们想要给String增加一个deentityify方法。他的任务是寻找字符串中的HTML字符实体并替换为他们对应的字符。在一个对象中保存字符实体的名字和它们对应的字符是有意义的。可以把它放到一个全局变量中,但全局变量是魔鬼。定义在该函数本身,但是那有运行时的损耗,因为该函数在每次被执行的时候该字面量都会被求值一次。理想的方式是将其放入一个闭包,而且也许还能提供一个增加更多字符实体的扩展方法。
String.method(‘deentityify’,function(){
//字符实体表。它映射字符实体的名字到对应的字符
Var entity={
Quot:’”’,
Lt:’<’,
Gt:’>’
};
//返回deentityify方法。
Return function(){
//这才是deentityify方法。它调用字符串的repalce方法,
//查找’&’开头和’;’结束的字符串。如果这些字符可以在字符实体表中找到,
//那么就将该字符实体替换为映射表中的值。他用到了一个正则表达式。
Return this.replace(/&([^&;]+);/g,
Function(a,b){
Var r=entity[b];
Return typeof r===’string’:r:a;
}
);
};
}())
主义最后一行,我们用()运算法立刻调用我们刚构造出来的函数。这个调用所创建并返回的函数才是deentityify方法。
Document.writeln(‘<">’.deentityify()); //<”>
模块模式利用了函数作用域和闭包来创建绑定对象与私有成员的关联,在这个例子中,只有deentityify方法有权访问字符实体表这个数据对象。
模块模式的一般形式是:一个定义了私有变量和函数的函数;利用闭包创建可以访问私有变量和函数的特权函数;最后返回这个特权函数,或者把它们保存到一个可以访问的地方。使用模块模式就可以摒弃全局变量的使用。它促进了信息隐藏和其它优秀的设计实践。对于应用程序的封装,或者构造其他单例对象,模块模式非常有效。
模块模式也可以用来产生安全对象。假定我们想要构造一个用来产生序列号的对象:
Var serial_maker=function(){
//返回一个用来产生位移字符串的对象。
//唯一字符串有两个部分组成:前缀+序列号。
//该对象包含一个设置前缀的方法,一个设置序列号的方法,
//和产生位移字符串的gensym方法
Var prefix=’’;
Var seq=0;
Return {
Set_prefix:function(p){
Prefix=string(p);
},
Set_seq:function(s){
Seq=s
},
Gensym:function(){
Var result=prefix+seq;
Seq+=1;
Return result;
}
};
};
Var seqer=serial_maker();
Seqer.set_prefix(‘Q’);
Seqer.set_seq(1000);
Var unique=seqer.gensym(); //unique is “Q1000”
级联(cascade)
有一些方法没有返回值。一些设置或修改对象的某个状态却不返回任何值的方法就是典型的例子。如果我们让这些方法返回this而不是undefined,就可以启用级联。在一个级联中,我们可以在单独一条的语句中依次调用同一个对象的很多方法。一个启用级联的Ajax类库可能允许我们以这样的形式去编码:
getElement(‘myBoxDiv’).
Move(350,150).
Width(100).
Height(100).
Color(‘red’).
Border(‘10px outset’).
Padding(‘4px’).
appendText(“Please stand by”).
On(‘mousedown’,function(m){
This.startDrag(m,this.getNith(m));
}).
On(‘mousemove’,’drag’).
On(‘mouseup’,’stopDrag’).
Later(2000,function(){
This.
Color(‘yellow’).
setHTML(“What hath God wraught?”).
slide(400,40,200,200);
}).
Tip(‘This box is resizeable’);
套用 :
函数也是值,从而我们可以用有趣的方式去操作函数值。套用允许我们将函数与传递给他们的参数相结合去产生一个新的函数。
Var add1=add.curry(1);
Document.writeln(add1(6)); //7
Add1是把1传给add函数的curry方发后创建的一个函数。Add1函数把传递给它的参数的值加1。通过给Function.prototype添加功能来实现:
Function.methd(‘curry’,function(){
Var args=arguments, that=this;
Return function(){
Return that.apply(mull,args.concat(arguments));
};
)} //有些事不对头
Function.method(‘curry’,function(){
Var slice=Array.prototype.slice,
Args=slice.apply (arguments),
That=this;
Return function(){
Return that.apply(null,args.conct(slice.apply(arguments)));
};
});
记忆(memoization)
函数可以用对象去记住先前操作的结果,从而能避免无谓的运算。这种优化被称为记忆。Javascript的对象和数组要实现这种优化是非常方便的。
用递归函数计算fibonacci数列。
Var Fibonacci=function(n){
Return n<2?n:Fibonacci(n-1)+Fibonacci(n-2);
};
For(var i=0;i<=10;i+=1){
Document.writeln(‘//’+i+’:’+Fibonacci(i));
}
这是可以工作的,但他做了很多无谓的工作。Fibonacci函数被调用了453次。我们调用了11次,而它自身调用了442次去计算可能已被刚计算过的值。如果我们让函数具备记忆功能,就可以显著地减少它们的运算量。
我们在一个名为demo的数组里保存我们的存储结果,存储结果可以隐藏在闭包中。当我们的函数被调用时,这个函数首先看是否已经知道存储的结果,如果知道,就立即返回这个存储结果。
Var Fibonacci=function(){
Var memo=[0,1];
Var fib=function(n){
Var result=meno[n];
If(typeof result!==’number’){
Result =fib(n-1)+fib(n-2);
Memo[n]=result;
}
Return result;
};
Return fib;
}();
这个函数返回同样的结果,但他只被调用了29次。我们调用了他11次,他自身调用了18次去取得之前存储的结果。
我们可以把这种形式一般化,编写一个函数来帮助我们构造带记忆功能的函数。Memoizer函数将取得一个初始的memo数组和fundamental函数。他返回一个管理memo存储和在需要时调用fundamental函数的shell函数。我们传递这个shell函数和该函数的参数给fundamental函数;
Var memeizer=function(memo,fundamental){
Var shell=function(n){
Var result=meno[n];
If(typeof result!==’number’){
Result=fundamental(shell,n);
Memo[n]=result;
}
Return result;
};
Return shell;
};
现在,我们可以使用memoizer来定义fibonacci函数 ,提供其初始的memo数组和fundamental函数;
Var Fibonacci=memoizer([0,1],function(shell,n){
Return shell(n-1)+shell(n-2);
});
通过设计能产生出其它函数的函数,可以极大减少我们必须要做的工作。产生一个可记忆的阶乘函数,我们只需提供基本的阶乘公式即可:
Var factorial =memoizer([1,1],function(shell,n){
Return n*shell(n-1);
});
伪类
当一个函数对象被创建时,function构造器产生的函数对象会运行类似这样的代码:
This.prototype={constructor:this};
新函数对象被赋予一个prototype属性,其值是包含一个constructor属性且属性值为该新函数对象。该prototype对象是存放继承特征的地方。因为javascript语言没有提供一种方法确定那个函数是打算用来做构造器的,所以每个函数都会得到一个prototype对象。Constructor属性没什么用。重要的是prototype对象。
当采用构造器调用模式,及使用new前缀去调用一个函数时,这将修改函数执行的方式。如果new运算符是一个方法而不是一个运算符,他可能会这样执行:
Function.method(‘new’,function(){
//创建一个新对象,他继承自构造器函数的原型对象。
Var that=Object.beget(this.prototype)
//调用构造器函数,绑定this到新对象上。
Var other=this.apply(that,arguments);
//如果它的返回值不是一个对象,就返回该新对象。
Return (typeof other===’object’&&other)that;
});
我们可以定义一个构造器并扩充它的原型
Var Mammal=function(name){
This.name=name;
};
Mammal.prototype.get_name=function(){
Return this.name;
};
Mammal.prototype.says=function(){
Return this.saying||’’;
};
现在,我们可以构造一个实例:
Var myMammal=new Mammal(‘Herb the Mammal’);
Var name=myMammal.get_name();// ‘Herb the Mammal’
我们可以构造另一个伪类来继承Mammal,这是通过定义它的constructor函数并替换它的prototype为一个Mammal的实例来实现的。
Var Cat=function(name){
This.name=name;
This.saying=’meow’;
};
//替换Cat.prototype为一个新的Mammal是实例
Cat.prototype=new Mammal();
//扩充原型对象,增加purr和get_name方法。