JavaScript极简入门教程(二):对象和函数
阅读本文需要有其他语言的编程经验。
JavaScript中的简单类型包括:
1.数字
2.字符串
3.布尔(true和false)
4.null
5.undefined
此外的其他类型均是对象(我们不要被typeof操作符的返回值所迷惑),例如:
1.函数
2.数组
3.正则表达式
4.对象(对象自然也是对象)
对象基础
在JavaScript中,对象是属性的集合(对象为关联数组),每个属性包括:
1.属性名,必须为字符串
2.属性值,可以为除了undefined之外的任何值
通过对象literal创建对象:
//通过对象literal{}创建空对象 varempty_object={};
对象的属性名和属性值:
varstooge={ //"first-name"为属性名,"Jerome"为属性值 "first-name":"Jerome", //"last-name"为属性名,"Howard"为属性值 "last-name":"Howard" };
如果属性名是合法的标识符,那么可以省略引号:
varflight={ airline:"Oceanic", number:815, departure:{ IATA:"SYD", time:"2004-09-2214:55", city:"Sydney" }, arrival:{ IATA:"LAX", time:"2004-09-2310:42", city:"LosAngeles" } };
我们看一下属性访问的例子:
varowner={name:"Name5566"}; owner.name;//"Name5566" owner["name"];//"Name5566" owner.job;//undefined owner.job="coder";//或者owner["job"]="coder";
如果属性名不是合法标识符,那么需要用引号包裹。不存在的属性值为undefined。对象是通过引用而非按值传递:
varx={}; varowner=x; owner.name="Name5566"; x.name;//x.name==="Name5566"
这里x和owner引用同一个对象。
对象的属性可以使用delete操作符删除:
deleteobj.x;//删除对象obj的x属性
对象的原型(prototype)
每一个对象都被链接了一个原型对象(prototypeobject),对象能够从原型对象中继承属性。我们通过对象literal创建一个对象,它的原型对象为Object.prototype对象(Object.prototype对象本身没有原型对象)。我们在创建对象的时候,可以设置对象的原型对象(之后再讨论具体的设置方法)。在尝试获取(而非修改)对象的某个属性时,如果该对象不存在此属性,那么JavaScript会尝试从此对象的原型对象中获取此属性,如果原型对象中没有该属性,那么再从此原型对象的原型对象中查找,以此类推,直到Object.prototype原型对象。相比获取属性而言,我们修改对象的某个属性时,不会影响原型对象。
函数基础
在JavaScript中函数也是对象,其链接到Function.prototype原型对象(Function.prototype链接到Object.prototype)。函数存在一个名为prototype的属性,其值的类型为对象,此对象存在一个属性constructor,constructor的值为此函数:
varf=function(){} typeoff.prototype;//'object' typeoff.prototype.constructor;//'function' f===f.prototype.constructor;//true
函数是对象,你可以像使用对象一样使用函数,也就是说,函数可以保存在变量、数组中,可以作为参数传递给函数,函数内部可以定义函数。附带提及一下,函数有两个被隐藏的属性:
1.函数的上下文
2.函数的代码
函数的创建如下:
varf=functionadd(a,b){ returna+b; } console.log(f);//输出[Function:add]
关键字function后的函数名是可选的,我们制定函数名主要出于几个目的:
1.为了递归调用
2.被调试器、开发工具等用来标识函数
很多时候我们并不需要函数名,没有函数名的函数被叫做匿名函数。有括号包裹的为参数列表。JavaScript不要求实参和形参匹配,例如:
varadd=function(a,b){ returna+b; } add(1,2,3);//实参和形参不匹配
如果实参过多,那么多余的实参会被忽略,如果实参过少,那么未被赋值的形参的值为undefined。函数一定有一个返回值,如果没有通过return语句指定返回值,那么函数返回值为undefined。
一个函数和其访问的外部变量组成一个闭包。这是JavaScript的关键魅力所在。
函数调用
每个函数被调用时,会接收到两个额外的参数:
1.this
2.arguments
this的值和具体调用的模式有关,在JavaScript中有四种调用模式:
1.方法调用模式。对象的属性如果是函数,则称其为方法。如果一个方法通过o.m(args)被调用,this为对象o(由此可见,在调用时,this和o才进行绑定),例如:
varobj={ value:0, increment:function(v){ this.value+=(typeofv==='number'?v:1); } }; obj.increment();//this===obj
2.函数调用模式。如果一个函数不是一个对象的属性,那么它将作为一个函数被调用,这时候this被绑定到全局对象上,例如:
message='HelloWorld'; varp=function(){ console.log(this.message); } p();//输出'HelloWorld'
这种行为有时候让人疑惑,看一个例子:
obj={ value:0, increment:function(){ varhelper=function(){ //对全局对象中的value加1 this.value+=1; } //helper被作为一个函数来调用 //因此this为全局对象 helper(); } }; obj.increment();//obj.value===0
我们期望的结果应该是:
obj={ value:0, increment:function(){ varthat=this; varhelper=function(){ that.value+=1; } helper(); } }; obj.increment();//obj.value===1
3.构造函数调用模式。意图使用new前缀的函数被叫做构造函数,例如:
//Test被叫做构造函数 varTest=function(string){ this.message=string; } varmyTest=newTest("HelloWorld");
一个函数前面可以加上new来调用(这样的函数通常大写开头),加上new之后将创建一个链接到此函数的prototype属性的对象,且构造函数中this为此对象。
4.apply调用模式。函数的apply方法被用于调用函数,其有两个参数,第一个为this,第二个为参数数组,例如:
varadd=function(a,b){ returna+b; } varret=add.apply(null,[3,4]);//ret===7
函数调用时,我们能够访问一个名为arguments的类数组(非真正的JavaScript数组),其包含了所有的实参,这样我们就能实现变长参数:
varadd=function(){ varsum=0; for(vari=0;i<arguments.length;++i){ sum+=arguments[i]; } returnsum; } add(1,2,3,4);
异常
现在来说说JavaScript的异常处理机制。我们使用throw语句来抛出异常,try-cache语句来捕获并处理异常:
varadd=function(a,b){ if(typeofa!=='number'||typeofb!=='number'){ //抛出异常 throw{ name:'TypeError', message:'addneedsnumbers' }; } returna+b; } //捕获并处理异常 try{ add("seven"); //e为抛出的异常对象 }catch(e){ console.log(e.name+':'+e.message); }
为JavaScript类型添加属性
JavaScript中大多数类型存在构造函数:
1.对象的构造函数为Object
2.数组的构造函数为Array
3.函数的构造函数为Function
4.字符串的构造函数为String
5.数字的构造函数为Number
6.布尔的构造函数为Boolean
7.正则表达式的构造函数为RegExp
我们可以向构造函数的prototype添加属性(常添加方法),使得此属性对相关变量可用:
Number.prototype.integer=function(){ returnMath[this<0?'ceil':'floor'](this); } (1.1).integer();//1
作用域
JavaScript需要通过函数来构建作用域:
function(){ //... }();
这里创建并执行了一个匿名函数。通过作用域能够隐藏不希望暴露的变量:
varobj=function(){ //隐藏value,外部无法访问 varvalue=0; return{ //仅此方法可以修改value increment:function(){ value+=1; }, //仅此方法可以读取value getValue:function(){ returnvalue; } }; }(); obj.increment(); obj.getValue()===1;
继承
JavaScript实现继承的方式很多。
在创建对象时,我们可以设置对象关联的原型对象,我们这样做:
//创建一个对象o,其原型对象为{x:1,y:2} varo=Object.create({x:1,y:2});
Object.create方法被定义在ECMAScript5中,如果你使用ECMAScript3时可以自己实现一个create方法:
//如果未定义Object.create方法 if(typeofObject.create!=='function'){ //创建Object.create方法 Object.create=function(o){ varF=function(){}; F.prototype=o; //创建一个新对象,此对象的原型对象为o returnnewF(); }; }
通过Object.create方法我们进行基于原型继承:一个新对象直接继承一个旧对象的属性(相对于基于类的继承,这里无需类的存在,对象直接继承对象)。范例:
varmyMammal={ name:'HerbtheMammal', get_name:function(){ returnthis.name; }, says:function(){ returnthis.saying||''; } }; //继承myMammal varmyCat=Object.create(myMammal); myCat.name='Henrietta'; myCat.saying='meow'; myCat.purr=function(n){ vari,s=''; for(i=0;i<n;i+=1){ if(s){ s+='-'; } s+='r'; } returns; }; myCat.get_name=function(){ returnthis.says()+''+this.name+''+this.says(); };
上面的代码很简单,但是没法保护私有成员。我们可以使用模块模式。在模块模式中,某类对象由一个函数产生,并利用函数作用域保护私有成员不被外部访问:
//mammal函数,用于构造mammal对象 varmammal=function(spec){ //that为构造的对象 varthat={}; //公有方法get_name可被外部访问 that.get_name=function(){ //spec.name外部无法直接访问 returnspec.name; }; //公有方法says可被外部访问 that.says=function(){ //spec.saying外部无法直接访问 returnspec.saying||''; }; returnthat; }; //创建mammal对象 varmyMammal=mammal({name:'Herb'}); //cat函数,用于构造cat对象 varcat=function(spec){ spec.saying=spec.saying||'meow'; //cat继承自mammal,因此先构造出mammal对象 varthat=mammal(spec); //添加公有方法purr that.purr=function(n){ vari,s=''; for(i=0;i<n;i+=1){ if(s){ s+='-'; } s+='r'; } returns; }; //修改公有方法get_name that.get_name=function(){ returnthat.says()+''+spec.name+ ''+that.says(); returnthat; }; }; //创建cat对象 varmyCat=cat({name:'Henrietta'});
在模块模式中,继承是通过调用构造函数来实现的。另外,我们还可以在子类中访问父类的方法:
Object.prototype.superior=function(name){ varthat=this,method=that[name]; returnfunction(){ returnmethod.apply(that,arguments); }; }; varcoolcat=function(spec){ //获取子类的get_name方法 varthat=cat(spec),super_get_name=that.superior('get_name'); that.get_name=function(n){ return'like'+super_get_name()+'baby'; }; returnthat; };