JavaScript权威指南模板.docx
- 文档编号:30077678
- 上传时间:2023-08-04
- 格式:DOCX
- 页数:21
- 大小:24.47KB
JavaScript权威指南模板.docx
《JavaScript权威指南模板.docx》由会员分享,可在线阅读,更多相关《JavaScript权威指南模板.docx(21页珍藏版)》请在冰豆网上搜索。
JavaScript权威指南模板
JavaScript权威指南-函数
函数本身就是一段JavaScript代码,定义一次但可能被调用任意次。
如果函数挂载在一个对象上,作为对象的一个属性,通常这种函数被称作对象的方法。
用于初始化一个新创建的对象的函数被称作构造函数。
相对于其他面向对象语言,在JavaScript中的函数是特殊的,函数即是对象。
JavaScript可以把函数赋值给变量,或者作为参数传递给其他函数,甚至可以给它们设置属性等。
JavaScript的函数可以嵌套在其他函数中定义,这样定义的函数就可以访问它们外层函数中的任何变量。
这也就是所谓的“闭包”,它可以给JavaScript带来强劲的编程能力。
1.函数定义
函数使用function关键字定义,有函数语句和函数表达式两种定义方式。
//一.函数语句类:
//打印对象所有属性名称和值。
functionprintprops(obj){
for(varkeyinobj){
console.log(key+":
"+obj[key]);
}
}
//计算阶乘的递归函数,函数名称将成为函数内部的一个局部变量。
functionfactorial(n){
if(n<=1)return1;
returnn*factorial(n);
}
//二.函数表达式类:
//计算n的平方的函数表达式。
这里将一个函数赋给一个变量。
varsquare=function(x){returnx*x;}
//兔子数列。
函数表达式也可以包含名称,方便递归。
varfoo=functionfoo(n){
if(n<=1)return1;
elsefoo(n-1)+foo(n-2);
}
//数组元素升序排列。
函数表达式也能作为参数传递给其他函数。
vardata=[5,3,7,2,1];
data.sort(function(a,b){returna-b;});
//函数表达式有时定义后立即调用。
vartensquared=(function(x){returnx*x;}(10));
函数命名
函数名称要求简洁、描述性强,因为这样可以极大改善代码的可读性,方便别人维护代码;函数名称通常是动词或以动词开头的词组。
通常来说,函数名编写有两种约定:
一种约定是函数名第一个单词首字母小写,后续单词首字母大写,就像likeThis();
当函数名包含多个单词时,另一种约定是用下划线来分割单词,就像like_this()。
项目中编写方法名时尽量选择一种保持代码风格一致。
还有,对于一些私有函数(不作为公用API的一部分),这种函数通常以一条下划线作为前辍。
2.函数调用
函数声明后需要通过调用才能被执行。
JavaScript中通常有4种方式来调用函数:
作为普通函数;
作为对象方法;
作为构造函数;
通过它们的call()和apply()方法间接调用。
下面就通过一些具体示例来演示上述4中函数的调用方式。
1.对于普通函数,通过调用表达式就可直接调用,这种方式很直接也很常见。
//定义一个普通函数。
varstrict=function(){return!
this;};//检测当前运行环境是否为严格模式。
//通过函数名直接调用。
console.log(strict());
注:
根据ES3和非严格的ES5对普通函数调用的规定,调用上下文(this)是全局对象;在严格模式下,调用上下文则是undefined。
2.通常,保存在对象属性里的JavaScript函数被称作“方法”。
//定义一个对象直接量。
varcalc={
a:
null,
b:
null,
add:
function(){//将函数保存在对象属性中。
returnthis.a+this.b;
}
};
//通过对象名调用方法。
calc.a=1,calc.b=2;
console.log(calc.add());
注:
对象方法中的调用上下文(this)不同于普通函数中的上下文。
这里this指代当前对象。
方法链:
当方法返回值是一个对象,那么这个对象还可以再调用它的方法。
每次调用的结果都是另外一个表达式的组成部分,这种方法调用方式最终会形成一个序列,也被称为“方法链”。
所以,在自己设计API的时候,当方法并不需要返回值时,最好直接返回this。
这样以后使用API就可以进行“链式调用”风格的编程。
需要注意的是,this是一个关键字,Javascript语法不允许给它赋值。
再者,关键字this没有作用域的限制,嵌套的函数不会从外层调用它的函数中继承this。
也就是说,如果嵌套函数作为方法调用,其this指向为调用它的对象。
如果嵌套函数作为函数调用,其this值不是全局对象就是undefined。
下面通过一段代码来具体说明。
varo={
m:
function(){//对象中的方法
varself=this;//将this的值保存在一个变量中
console.log(this===o);//输出true,表明this就是这个引用对象o
f();//调用嵌套函数f()
functionf(){//定义一个嵌套函数(**普通函数,非对象方法)
console.log(this===o);//输出false,this的值为全局对象或undefined
console.log(self===o);//输出true,变量self指外部函数的this值
}
}
}
3.如果函数或者防方法调用之前带有关键字new,它就构成构造函数调用。
构造函数调用会创建一个新的对象,构造函数通常不使用return,函数体执行完毕它会显示返回。
还有,创建的对象继承自构造函数的prototype属性,构造函数中使用this关键字来引用这个新创建的对象。
//与普通函数一样的定义方式。
functionPerson(name,age){
this.name=name;
this.age=age;
this.say=function(){
console.log("Mynameis"+this.name+",Iam"+this.age+"yearsold.");
}
}
//用关键字new调用构造函数,实例化对象。
varobj=newPerson("Lamb","21");
obj.say();//调用对象方法。
4.我们知道Javascript中的函数也是对象,所以函数对象也是可以包含方法的,其中call()和apply()两个方法可以用来间接地调用函数,这两个方法都可以显式指定调用函数里面的调用上下文this。
//定义一个打印函数。
functionprint(){
if(this.text){
alert(this.text);
}else{
alert("undefined");
}
}
//call方法间接调用方法,并指定其调用上下文。
print.call({text:
"hello"});
关于call()和apply()两个方法的用法以及区别下面详细讨论。
3.函数的实参和形参
JavaScript中的函数定义不需要指定函数形参的类型,调用函数时也不检查传入形参的个数。
这样,同时也会留下两个疑问给我们:
当调用函数时的实参个数和声明的形参个数不匹配的时候如何处理;
如何显式测试函数实参的类型,以避免非法的实参传入函数。
下面就简单介绍JavaScript是如何对上述两个问题做出处理的。
可选参数
当调用函数的时候传入的实参比函数定义时指定的形参个数要少,剩下的形参都将设置为undefined。
一般来说,为了保持函数较好的适应性,都会给省略的参数设置一个合理的默认值。
functiongetPropertyNames(obj,/*optional*/arr){
arr=arr||[];
for(varpropertyinobj){arr.push(property);}
returnarr;
}
需要注意的是,当使用这种可选实参来实现函数时,需要将可选实参放在实参列表的最后。
一般来书,函数定义中使用注释/*optional*/来强调形参是可选的。
实参对象
当调用函数时传入的参数个数超过了原本函数定义的形参个数,那么方法中可以通过实参对象来获取,标识符arguments是指向实参对象的引用。
实参对象是一个类数组对象,可以通过数字下标来访问传入函数的实参值。
实参对象有一个重要的用处,就是让函数可以操作任意数量的实参,请看下面的例子:
//返回传入实参的最大值。
functionmax(/*...*/){
varmax=Number.NEGATIVE_INFINITY;//该值代表负无穷大。
for(vari=0;i if(arguments[i]>max){ max=arguments[i]; } } returnmax; } //调用。 varlargest=max(10,45,66,35,21);//=>66 还有重要的一点,如果函数中修改arguments[]元素,同样会影响对应的实参变量。 除以上之外,实参对象还包含了两个属性callee和caller: callee是ECMAScript标准规范的,它指代当前正在执行的函数。 caller是非标准属性但是大多数浏览器都支持,它指代当前正在执行函数的函数。 //callee可以用来递归匿名函数。 varsum=function(x){ if(x<=1)return1; returnx+arguments.callee(x-1); } //调用函数b,方法a中打印结果为函数b。 vara=function(){ alert(a.caller); } varb=function(){ a(); } 注意,在ECMAScript5严格模式下,对这两个属性进行读写会产生一个类型错误。 实参类型 声明JavaScript函数时形参不需要指定类型,在形参传入函数体之前也不会做任何类型检查,但是JavaScript在必要的时候会进行类型转换,例如: functionmult(a,b){ returna*b; } functionconn(x,y){ returnx+y; } console.log(mult(3,"2"));//字符串类型自动转为数字类型,输出结果: 6 console.log(conn(3,"2"));//数字类型自动转为字符串类型,输出结果: "32" 上述的两种类型存在隐式转换关系所以JS可以自动转换,但是还存在其他情况: 比如,一个方法期望它第一个实参为数组,传入一个非数组的值就可能引发问题,这时就应当在函数体中添加实参类型检查逻辑。 4.作为值的函数 开篇提到过,在JavaScript中函数不仅是一种语法,函数即是对象,简单归纳函数具有的几种性质: 1.函数可以被赋值给一个变量; functionsquare(x){returnx*x;} vars=square;//现在s和square指代同一个函数对象 square(5);//=>25 s(5);//=>25 2.函数可以保存在对象的属性或数组元素中; vararray=[function(x){returnx*x;},20]; array[0](array[1]);//=>400 3.函数可以作为参数传入另外一个函数; //这里定义一些简单函数。 functionadd(x,y){returnx+y;} functionsubtract(x,y){returnx-y;} functionmultipty(x,y){returnx*y;} functiondivide(x,y){returnx/y;} //这里函数以上面某个函数做参数。 functionoperate(operator,num1,num2){ returnoperator(num1,num2); } //调用函数计算(4*5)-(2+3)的值。 varresult=operate(subtract,operate(multipty,4,5),operate(add,2,3)); console.log(result);//=>15 4.函数可以设置属性。 //初始化函数对象的计数器属性。 uniqueInteger.counter=0; //先返回计数器的值,然后计数器自增1。 functionuniqueInteger(){ returnuniqueInteger.counter+=1; } 当函数需要一个“静态”变量来在调用时保持某个值不变,最方便的方式就是给函数定义属性,而不是定义全局变量,因为定义全局变量会让命名空间变的杂乱无章。 5.作为命名空间的函数 函数中声明的变量只在函数内部是有定义,不在任何函数内声明的变量是全局变量,它在JavaScript代码中的任何地方都是有定义的。 JavaScript中没有办法声明只在一个代码块内可见的变量的。 基于这个原因,常常需要定义一个函数用作临时的命名空间,在这个命名空间内定义的变量都不会污染到全局变量。 //该函数就可看作一个命名空间。 functionmymodule(){ //该函数下的变量都变成了“mymodule”空间下的局部变量,不会污染全局变量。 } //最后需要调用命名空间函数。 mymodule(); 上段代码还是会暴露出一个全局变量: mymodule函数。 更为常见的写法是,直接定义一个匿名函数,并在单个表达式中调用它: //将上面mymodule()函数重写成匿名函数,结束定义并立即调用它。 (function(){ //模块代码。 }()); 6.闭包 闭包是JavaScript中的一个难点。 在理解闭包之前先要明白变量作用域和函数作用域链两个概念。 变量作用域: 无非就是两种,全局变量和局部变量。 全局变量拥有全局作用域,在任何地方都是有定义的。 局部变量一般是指在函数内部定义的变量,它们只在函数内部有定义。 函数作用域链: 我们知道JavaScript函数是可以嵌套的,子函数对象会一级一级地向上寻找所有父函数对象的变量。 所以,父函数对象的所有变量,对子函数对象都是可见的,反之则不成立。 需要知道的一点是,函数作用域链是在定义函数的时候创建的。 关于“闭包”的概念书本上定义很具体,但是也很抽象,很难理解。 简单的理解,“闭包”就是定义在一个函数内部的函数(这么说并不准确,应该说闭包是函数的作用域)。 varscope="globalscope";//全局变量 functioncheckscope(){ varscope="localscope";//局部变量 functionf(){returnscope;}//在作用域中返回这个值 returnf(); } checkscope();//=>"localscope" 上面一段代码就就实现了一个简单的闭包,函数f()就是闭包。 根据输出结果,可以看出闭包可以保存外层函数局部变量,通过闭包可以把函数内的变量暴露在全局作用域下。 闭包有什么作用呢? 下面一段代码是上文利用函数属性定义的一个计数器函数,其实它存在一个问题: 恶意代码可以修改counter属性值,从而让Integer函数计数出错。 //初始化函数对象的计数器属性。 uniqueInteger.counter=0; //先返回计数器的值,然后计数器自增1。 functionuniqueInteger(){ returnuniqueInteger.counter+=1; } 闭包可捕捉到单个函数调用的局部变量,并将这些局部变量用作私有状态,故我们可以利用闭包的特性来重写uniqueInteger函数。 //利用闭包重写。 varuniqueInteger=(function(){//定义函数并立即调用 varcounter=0;//函数的私有状态 returnfunction(){ returncounter+=1; }; })(); //调用。 uniqueInteger();//=>1 uniqueInteger();//=>2 uniqueInteger();//=>3 当外部函数返回后,其他任何代码都无法访问counter变量,只有内部的函数才能访问。 根据输出结果可以看出,闭包会使得函数中的变量都被保存在内存中,内存消耗大,所以要合理使用闭包。 像counter一样的私有变量在多个嵌套函数中都可以访问到它,因为这多个嵌套函数都共享同一个作用域链,看下面一段代码: functioncounter(){ varn=0; return{ count: function(){returnn+=1;}, reset: function(){n=0;} }; } varc=counter(),d=counter();//创建两个计时器 c.count();//=>0 d.count();//=>0能看出它们互不干扰 c.reset();//reset和count方法共享状态 c.count();//=>0因为重置了计数器c d.count();//=>1而没有重置计数器d 书写闭包的时候还需注意一件事,this是JavaScript的关键字,而不是变量。 因为闭包内的函数只能访问闭包内的变量,所以this必须要赋给that才能引用。 绑定arguments的问题与之类似。 varname="TheWindow"; varobject={ name: "MyObject", getName: function(){ varthat=this; returnfunction(){ returnthat.name; }; } }; console.log(object.getName()());//=>"MyObject" 到这里如果你还不明白我在说什么,这里推荐两篇前辈们写的关于“闭包”的文章。 阮一峰,学习Javascript闭包(Closure) russj,JavaScript闭包的理解 7.函数属性、方法和构造函数 前文已经介绍过,在JavaScript中函数也是对象,它也可以像普通对象一样拥有属性和方法。 length属性 在函数体里,arguments.length表示传入函数的实参的个数。 而函数本身的length属性表示的则是“形参”,也就是在函数调用时期望传入函数的实参个数。 functioncheck(args){ varactual=args.length;//参数的真实个数 varexpected=args.callee.length;//期望的实参个数 if(actual! =expected){//如果不同则抛出异常 throwError("Expected"+expected+"args;got"+actual); } } functionf(x,y,z){ check(arguments);//检查实参和形参个数是否一致。 returnx+y+z; } prototype属性 每个函数都包含prototype属性,这个属性指向一个对象的引用,这个对象也就是原型对象。 当将函数用作构造函数的时候,新创建的对象会从原型对象上继承属性。 call()方法和apply()方法 上文提到,这两个方法可以用来间接调用函数。 call()和apply()的第一个实参表示要调用函数的母对象,它是调用上下文,在函数内通过this来引用母对象。 假如要想把函数func()以对象obj方法的形式来调用,可以这样: func.call(obj); func.apply(obj); call()和apply()的区别之处是,第一个实参(调用上下文)之后的所有实参传入的方式不同。 func.call(obj,1,2);//实参可以为任意数量 func.apply(obj,[1,2]);//实参都放在了一个数组中 下面看一个有意思的函数,他能将一个对象的方法替换为一个新方法。 这个新方法“包裹”了原始方法,实现了AOP。 //调用原始方法之前和之后记录日志消息 functiontrace(o,m){ varoriginal=o[m];//在闭包中保存原始方法 o[m]=function(){//定义新方法 console.log(newDate(),"Entering: ",m);//输出日志消息 varresult=original.apply(o,arguments);//调用原始方法 console.log(newDate(),"Exiting: ",m);//输出日志消息 returnresult;//返回结果 } } 这种动态修改已有方法的做法,也被称作“猴子补丁(monkey-patching)”。 bind()方法 bind()方法是ES5中新增的方法,这个方法的主要作用是将函数绑定至某个对象。 该方法会返回一个新的函数,调用这个新的函数会将原始函数当作传入对象的方法来调用。 functionfunc(y){returnthis.x+y;}//待绑定的函数 varo={x: 1};//将要绑定的对象 varf=func.bind(o);//通过调用f()来调用o.func() f (2);//=>3 ES3中可以通过下面的代码来实现bind()方法: if(! Function.prototype.bind){ Function.prototype.bind=function(o/*,args*/){ //将this和arguments保存在变量中,以便在嵌套函数中使用。 varself=this,boundArgs=arguments; //bind()方法返回的是一个函数。 returnfunction(){ //创建一个参数列表,将传入bind()的第二个及后续的实参都传入这个函数。 varar
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- JavaScript 权威 指南 模板