javaScript面向对象

初探

我们知道JavaScript中的变量定义基本如下:

 
  1. varname='ChenHao';;
  2. varemail='haoel(@)hotmail.com';
  3. varwebsite='http://coolshell.cn';

如果要用对象来写的话,就是下面这个样子:

 
  1. varchenhao={
  2. name:'ChenHao',
  3. email:'haoel(@)hotmail.com',
  4. website:'http://coolshell.cn'
  5. };

于是,我就可以这样访问:

 
  1. //以成员的方式
  2. chenhao.name;
  3. chenhao.email;
  4. chenhao.website;
  5. //以hashmap的方式
  6. chenhao["name"];
  7. chenhao["email"];
  8. chenhao["website"];

关于函数,我们知道JavaScript的函数是这样的:

 
  1. vardoSomething=function(){
  2. alert('HelloWorld.');
  3. };

于是,我们可以这么干:

 
  1. varsayHello=function(){
  2. varhello="Hello,I'm"+this.name
  3. +",myemailis:"+this.email
  4. +",mywebsiteis:"+this.website;
  5. alert(hello);
  6. };
  7. //直接赋值,这里很像C/C++的函数指针
  8. chenhao.Hello=sayHello;
  9. chenhao.Hello();

相信这些东西都比较简单,大家都明白了。可以看到JavaScript对象函数是直接声明,直接赋值,直接就用了。runtime的动态语言。

还有一种比较规范的写法是:

 
  1. //我们可以看到,其用function来做class。
  2. varPerson=function(name,email,website){
  3. this.name=name;
  4. this.email=email;
  5. this.website=website;
  6. this.sayHello=function(){
  7. varhello="Hello,I'm"+this.name+",\n"+
  8. "myemailis:"+this.email+",\n"+
  9. "mywebsiteis:"+this.website;
  10. alert(hello);
  11. };
  12. };
  13. varchenhao=newPerson("ChenHao","[email protected]",
  14. "http://coolshell.cn");
  15. chenhao.sayHello();

顺便说一下,要删除对象的属性,很简单:

 
  1. deletechenhao['email']

上面的这些例子,我们可以看到这样几点:

  • Javascript 的数据和成员封装很简单。没有类完全是对象操作。纯动态!
  • Javascript function中的this指针很关键,如果没有的话,那就是局部变量或局部函数。
  • Javascript 对象成员函数可以在使用时临时声明,并把一个全局函数直接赋过去就好了。
  • Javascript 的成员函数可以在实例上进行修改,也就是说不同实例相同函数名的行为不一定一样。

属性配置 – Object.defineProperty

先看下面的代码:

 
  1. //创建对象
  2. varchenhao=Object.create(null);
  3. //设置一个属性
  4. Object.defineProperty(chenhao,
  5. 'name',{value:'ChenHao',
  6. writable:true,
  7. configurable:true,
  8. enumerable:true});
  9. //设置多个属性
  10. Object.defineProperties(chenhao,
  11. {
  12. 'email':{value:'[email protected]',
  13. writable:true,
  14. configurable:true,
  15. enumerable:true},
  16. 'website':{value:'http://coolshell.cn',
  17. writable:true,
  18. configurable:true,
  19. enumerable:true}
  20. }
  21. );

下面就说说这些属性配置是什么意思。

  • writable:这个属性的值是否可以改。
  • configurable:这个属性的配置是否可以改。
  • enumerable:这个属性是否能在for…in循环中遍历出来或在Object.keys中列举出来。
  • value:属性值。
  • get ()/set (_value):get 和 set 访问器。

Get/Set访问器

关于get/set访问器,它的意思就是用 get/set 来取代 value(其不能和 value 一起使用),示例如下:

 
  1. varage=0;
  2. Object.defineProperty(chenhao,
  3. 'age',{
  4. get:function(){returnage+1;},
  5. set:function(value){age=value;}
  6. enumerable:true,
  7. configurable:true
  8. }
  9. );
  10. chenhao.age=100;//调用set
  11. alert(chenhao.age);//调用get输出101(get中+1了);

我们再看一个更为实用的例子——利用已有的属性(age)通过get 和 set 构造新的属性(birth_year):

 
  1. Object.defineProperty(chenhao,
  2. 'birth_year',
  3. {
  4. get:function(){
  5. vard=newDate();
  6. vary=d.getFullYear();
  7. return(y-this.age);
  8. },
  9. set:function(year){
  10. vard=newDate();
  11. vary=d.getFullYear();
  12. this.age=y-year;
  13. }
  14. }
  15. );
  16. alert(chenhao.birth_year);
  17. chenhao.birth_year=2000;
  18. alert(chenhao.age);

这样做好像有点麻烦,你说,我为什么不写成下面这个样子:

 
  1. varchenhao={
  2. name:"ChenHao",
  3. email:"[email protected]",
  4. website:"http://coolshell.cn",
  5. age:100,
  6. getbirth_year(){
  7. vard=newDate();
  8. vary=d.getFullYear();
  9. return(y-this.age);
  10. },
  11. setbirth_year(year){
  12. vard=newDate();
  13. vary=d.getFullYear();
  14. this.age=y-year;
  15. }
  16. };
  17. alert(chenhao.birth_year);
  18. chenhao.birth_year=2000;
  19. alert(chenhao.age);

是的,你的确可以这样的,不过通过defineProperty ()你可以干这些事:

1)设置如writable,configurable,enumerable 等这类的属性配置。

2)动态地为一个对象加属性。比如:一些 HTML 的 DOM 对像。

查看对象属性配置

如果查看并管理对象的这些配置,下面有个程序可以输出对象的属性和配置等东西:

 
  1. //列出对象的属性.
  2. functionlistProperties(obj)
  3. {
  4. varnewLine="<br/>";
  5. varnames=Object.getOwnPropertyNames(obj);
  6. for(vari=0;i<names.length;i++){
  7. varprop=names[i];
  8. document.write(prop+newLine);
  9. //列出对象的属性配置(descriptor)动用getOwnPropertyDescriptor函数。
  10. vardescriptor=Object.getOwnPropertyDescriptor(obj,prop);
  11. for(varattrindescriptor){
  12. document.write("..."+attr+':'+descriptor[attr]);
  13. document.write(newLine);
  14. }
  15. document.write(newLine);
  16. }
  17. }
  18. listProperties(chenhao);

call,apply,bind和this

关于 Javascript 的 this 指针,和C++/Java 很类似。 我们来看个示例:(这个示例很简单了,我就不多说了)

 
  1. functionprint(text){
  2. document.write(this.value+'-'+text+'<br>');
  3. }
  4. vara={value:10,print:print};
  5. varb={value:20,print:print};
  6. print('hello');//this=>global,output"undefined-hello"
  7. a.print('a');//this=>a,output"10-a"
  8. b.print('b');//this=>b,output"20-b"
  9. a['print']('a');//this=>a,output"10-a"

我们再来看看call和apply,这两个函数的差别就是参数的样子不一样,另一个就是性能不一样,apply 的性能要差很多。(关于性能,可到 JSPerf 上去跑跑看看)

 
  1. print.call(a,'a');//this=>a,output"10-a"
  2. print.call(b,'b');//this=>b,output"20-b"
  3. print.apply(a,['a']);//this=>a,output"10-a"
  4. print.apply(b,['b']);//this=>b,output"20-b"

但是在 bind 后,this 指针,可能会有不一样,但是因为 Javascript 是动态的。如下面的示例

 
  1. varp=print.bind(a);
  2. p('a');//this=>a,output"10-a"
  3. p.call(b,'b');//this=>a,output"10-b"
  4. p.apply(b,['b']);//this=>a,output"10-b"

继承和重载

通过上面的那些示例,我们可以通过 Object.create ()来实际继承,请看下面的代码,Student 继承于 Object。

 
  1. varPerson=Object.create(null);
  2. Object.defineProperties
  3. (
  4. Person,
  5. {
  6. 'name':{value:'ChenHao'},
  7. 'email':{value:'[email protected]'},
  8. 'website':{value:'http://coolshell.cn'}
  9. }
  10. );
  11. Person.sayHello=function(){
  12. varhello="<p>Hello,Iam"+this.name+",<br>"+
  13. "myemailis:"+this.email+",<br>"+
  14. "mywebsiteis:"+this.website;
  15. document.write(hello+"<br>");
  16. }
  17. varStudent=Object.create(Person);
  18. Student.no="1234567";//学号
  19. Student.dept="ComputerScience";//系
  20. //使用Person的属性
  21. document.write(Student.name+''+Student.email+''+Student.website+'<br>');
  22. //使用Person的方法
  23. Student.sayHello();
  24. //重载SayHello方法
  25. Student.sayHello=function(person){
  26. varhello="<p>Hello,Iam"+this.name+",<br>"+
  27. "myemailis:"+this.email+",<br>"+
  28. "mywebsiteis:"+this.website+",<br>"+
  29. "mystudentnois:"+this.no+",<br>"+
  30. "mydepartentis:"+this.dept;
  31. document.write(hello+'<br>');
  32. }
  33. //再次调用
  34. Student.sayHello();
  35. //查看Student的属性(只有no、dept和重载了的sayHello)
  36. document.write('<p>'+Object.keys(Student)+'<br>');

通用上面这个示例,我们可以看到,Person 里的属性并没有被真正复制到了 Student 中来,但是我们可以去存取。这是因为 Javascript 用委托实现了这一机制。其实,这就是 Prototype,Person 是 Student 的 Prototype。

当我们的代码需要一个属性的时候,Javascript 的引擎会先看当前的这个对象中是否有这个属性,如果没有的话,就会查找他的 Prototype 对象是否有这个属性,一直继续下去,直到找到或是直到没有 Prototype 对象。

为了证明这个事,我们可以使用 Object.getPrototypeOf ()来检验一下:

 
  1. Student.name='aaa';
  2. //输出aaa
  3. document.write('<p>'+Student.name+'</p>');
  4. //输出ChenHao
  5. document.write('<p>'+Object.getPrototypeOf(Student).name+'</p>');

于是,你还可以在子对象的函数里调用父对象的函数,就好像 C++ 里的 Base::func () 一样。于是,我们重载 hello 的方法就可以使用父类的代码了,如下所示:

 
  1. //新版的重载SayHello方法
  2. Student.sayHello=function(person){
  3. Object.getPrototypeOf(this).sayHello.call(this);
  4. varhello="mystudentnois:"+this.no+",<br>"+
  5. "mydepartentis:"+this.dept;
  6. document.write(hello+'<br>');
  7. }

这个很强大吧。

组合

上面的那个东西还不能满足我们的要求,我们可能希望这些对象能真正的组合起来。为什么要组合?因为我们都知道是这是 OO 设计的最重要的东西。不过,这对于 Javascript 来并没有支持得特别好,不好我们依然可以搞定个事。

首先,我们需要定义一个 Composition 的函数:(target 是作用于是对象,source 是源对象),下面这个代码还是很简单的,就是把 source 里的属性一个一个拿出来然后定义到 target 中。

 
  1. functionComposition(target,source)
  2. {
  3. vardesc=Object.getOwnPropertyDescriptor;
  4. varprop=Object.getOwnPropertyNames;
  5. vardef_prop=Object.defineProperty;
  6. prop(source).forEach(
  7. function(key){
  8. def_prop(target,key,desc(source,key))
  9. }
  10. )
  11. returntarget;
  12. }

有了这个函数以后,我们就可以这来玩了:

 
  1. //艺术家
  2. varArtist=Object.create(null);
  3. Artist.sing=function(){
  4. returnthis.name+'startssinging...';
  5. }
  6. Artist.paint=function(){
  7. returnthis.name+'startspainting...';
  8. }
  9. //运动员
  10. varSporter=Object.create(null);
  11. Sporter.run=function(){
  12. returnthis.name+'startsrunning...';
  13. }
  14. Sporter.swim=function(){
  15. returnthis.name+'startsswimming...';
  16. }
  17. Composition(Person,Artist);
  18. document.write(Person.sing()+'<br>');
  19. document.write(Person.paint()+'<br>');
  20. Composition(Person,Sporter);
  21. document.write(Person.run()+'<br>');
  22. document.write(Person.swim()+'<br>');
  23. //看看Person中有什么?(输出:sayHello,sing,paint,swim,run)
  24. document.write('<p>'+Object.keys(Person)+'<br>');

Prototype和继承

我们先来说说 Prototype。我们先看下面的例程,这个例程不需要解释吧,很像C语言里的函数指针,在C语言里这样的东西见得多了。

 
  1. varplus=function(x,y){
  2. document.write(x+'+'+y+'='+(x+y)+'<br>');
  3. returnx+y;
  4. };
  5. varminus=function(x,y){
  6. document.write(x+'-'+y+'='+(x-y)+'<br>');
  7. returnx-y;
  8. };
  9. varoperations={
  10. '+':plus,
  11. '-':minus
  12. };
  13. varcalculate=function(x,y,operation){
  14. returnoperations[operation](x,y);
  15. };
  16. calculate(12,4,'+');
  17. calculate(24,3,'-');

那么,我们能不能把这些东西封装起来呢,我们需要使用 prototype。看下面的示例:

 
  1. varCal=function(x,y){
  2. this.x=x;
  3. this.y=y;
  4. }
  5. Cal.prototype.operations={
  6. '+':function(x,y){returnx+y;},
  7. '-':function(x,y){returnx-y;}
  8. };
  9. Cal.prototype.calculate=function(operation){
  10. returnthis.operations[operation](this.x,this.y);
  11. };
  12. varc=newCal(4,5);
  13. c.calculate('+');
  14. c.calculate('-');

这就是 prototype 的用法,prototype 是 javascript 这个语言中最重要的内容。网上有太多的文章介始这个东西了。说白了,prototype 就是对一对象进行扩展,其特点在于通过“复制”一个已经存在的实例来返回新的实例,而不是新建实例。被复制的实例就是我们所称的“原型”,这个原型是可定制的(当然,这里没有真正的复制,实际只是委托)。上面的这个例子中,我们扩展了实例 Cal,让其有了一个 operations 的属性和一个 calculate 的方法。

这样,我们可以通过这一特性来实现继承。还记得我们最最前面的那个 Person 吧, 下面的示例是创建一个 Student 来继承 Person。

 
  1. functionPerson(name,email,website){
  2. this.name=name;
  3. this.email=email;
  4. this.website=website;
  5. };
  6. Person.prototype.sayHello=function(){
  7. varhello="Hello,Iam"+this.name+",<br>"+
  8. "myemailis:"+this.email+",<br>"+
  9. "mywebsiteis:"+this.website;
  10. returnhello;
  11. };
  12. functionStudent(name,email,website,no,dept){
  13. varproto=Object.getPrototypeOf;
  14. proto(Student.prototype).constructor.call(this,name,email,website);
  15. this.no=no;
  16. this.dept=dept;
  17. }
  18. //继承prototype
  19. Student.prototype=Object.create(Person.prototype);
  20. //重置构造函数
  21. StudentStudent.prototype.constructor=Student;
  22. //重载sayHello()
  23. Student.prototype.sayHello=function(){
  24. varproto=Object.getPrototypeOf;
  25. varhello=proto(Student.prototype).sayHello.call(this)+'<br>';
  26. hello+="mystudentnois:"+this.no+",<br>"+
  27. "mydepartentis:"+this.dept;
  28. returnhello;
  29. };
  30. varme=newStudent(
  31. "ChenHao",
  32. "[email protected]",
  33. "http://coolshell.cn",
  34. "12345678",
  35. "ComputerScience"
  36. );
  37. document.write(me.sayHello());

兼容性

上面的这些代码并不一定能在所有的浏览器下都能运行,因为上面这些代码遵循 ECMAScript 5 的规范,关于 ECMAScript 5 的浏览器兼容列表,你可以看这里“ES5浏览器兼容表”。

本文中的所有代码都在 Chrome 最新版中测试过了。

下面是一些函数,可以用在不兼容 ES5 的浏览器中:

Object.create ()函数

 
  1. functionclone(proto){
  2. functionDummy(){}
  3. Dummy.prototype=proto;
  4. DummyDummy.prototype.constructor=Dummy;
  5. returnnewDummy();//等价于Object.create(Person);
  6. }
  7. varme=clone(Person);

defineProperty ()函数

 
  1. functiondefineProperty(target,key,descriptor){
  2. if(descriptor.value){
  3. target[key]=descriptor.value;
  4. }else{
  5. descriptor.get&&target.__defineGetter__(key,descriptor.get);
  6. descriptor.set&&target.__defineSetter__(key,descriptor.set);
  7. }
  8. returntarget
  9. }

keys ()函数

 
  1. functionkeys(object){varresult,key
  2. result=[];
  3. for(keyinobject){
  4. if(object.hasOwnProperty(key))result.push(key)
  5. }
  6. returnresult;
  7. }

Object.getPrototypeOf() 函数

 
  1. functionproto(object){
  2. return!object?null
  3. :'__proto__'inobject?object.__proto__
  4. :/*notexposed?*/object.constructor.prototype
  5. }

bind函数

 
  1. varslice=[].slice
  2. functionbind(fn,bound_this){varbound_args
  3. bound_args=slice.call(arguments,2)
  4. returnfunction(){varargs
  5. args=bound_args.concat(slice.call(arguments))
  6. returnfn.apply(bound_this,args)}
  7. }

你可能感兴趣的:(JavaScript)