JavaScript的面向对象的编程

来源:互联网 发布:红米note3手机壳淘宝 编辑:程序博客网 时间:2024/06/04 19:25

JavaScript是一个类C的语言,他的面向对象的东西相对于C++/Java比较奇怪,但是其的确相当的强大,本文主要从一个整体的角度来说明一下JavaScript的面向对象的编程。这篇文章主要基于ECMAScript 5,旨在介绍新技术。关于兼容性的东西,请看最后一节。

初探

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

  1. var name = 'Chen Hao';;  
  2. var email = 'haoel(@)hotmail.com';  
  3. var website = 'http://coolshell.cn'

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

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

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

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

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

  1. var doSomething = function(){  
  2.    alert('Hello World.');  
  3. }; 

于是,我们可以这么干:

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

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

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

  1. //我们可以看到, 其用function来做class。  
  2. var Person = function(name, email, website){  
  3.     this.name = name;  
  4.     this.email = email;  
  5.     this.website = website;  
  6.    
  7.     this.sayHello = function(){  
  8.         var hello = "Hello, I'm "+ this.name  + ", \n" +  
  9.                     "my email is: " + this.email + ", \n" +  
  10.                     "my website is: " + this.website;  
  11.         alert(hello);  
  12.     };  
  13. };  
  14.    
  15. var chenhao = new Person("Chen Hao", "haoel@hotmail.com",  
  16.                                      "http://coolshell.cn");  
  17. chenhao.sayHello(); 

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

  1. delete chenhao['email']  

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

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

属性配置 – Object.defineProperty

先看下面的代码:

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

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

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

Get/Set访问器

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

  1. var  age = 0;  
  2. Object.defineProperty( chenhao,  
  3.             'age', {  
  4.                       get: function() {return age+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.                     var d = new Date();  
  6.                     var y = d.getFullYear();  
  7.                     return ( y - this.age );  
  8.                 },  
  9.                 set: function(year) {  
  10.                     var d = new Date();  
  11.                     var y = d.getFullYear();  
  12.                     this.age = y - year;  
  13.                 }  
  14.             }  
  15. );  
  16.    
  17. alert(chenhao.birth_year);  
  18. chenhao.birth_year = 2000;  
  19. alert(chenhao.age); 

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

  1. var chenhao = {  
  2.     name: "Chen Hao",  
  3.     email: "haoel@hotmail.com",  
  4.     website: "http://coolshell.cn",  
  5.     age: 100,  
  6.     get birth_year() {  
  7.         var d = new Date();  
  8.         var y = d.getFullYear();  
  9.         return ( y - this.age );  
  10.     },  
  11.     set birth_year(year) {  
  12.         var d = new Date();  
  13.         var y = d.getFullYear();  
  14.         this.age = y - year;  
  15.     }  
  16.    
  17. };  
  18. alert(chenhao.birth_year);  
  19. chenhao.birth_year = 2000;  
  20. alert(chenhao.age); 

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

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

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

查看对象属性配置

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

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

call,apply,bind和this

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

  1. function print(text){  
  2.     document.write(this.value + ' - ' + text+ '<br>');  
  3. }  
  4.    
  5. var a = {value: 10, print : print};  
  6. var b = {value: 20, print : print};  
  7.    
  8. print('hello');// this => global, output "undefined - hello"  
  9.    
  10. a.print('a');// this => a, output "10 - a"  
  11. b.print('b'); // this => b, output "20 - b"  
  12.    
  13. 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.    
  4. print.apply(a, ['a']); // this => a, output "10 - a"  
  5. print.apply(b, ['b']); // this => b, output "20 - b" 

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

  1. var p = 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. var Person = Object.create(null);  
  2.    
  3. Object.defineProperties  
  4. (  
  5.     Person,  
  6.     {  
  7.         'name'  : {  value: 'Chen Hao'},  
  8.         'email'  : { value : 'haoel@hotmail.com'},  
  9.         'website': { value: 'http://coolshell.cn'}  
  10.     }  
  11. );  
  12.    
  13. Person.sayHello = function () {  
  14.     var hello = "<p>Hello, I am "+ this.name  + ", <br>" +  
  15.                 "my email is: " + this.email + ", <br>" +  
  16.                 "my website is: " + this.website;  
  17.     document.write(hello + "<br>");  
  18. }  
  19.    
  20. var Student = Object.create(Person);  
  21. Student.no = "1234567"; //学号  
  22. Student.dept = "Computer Science"; //系  
  23.    
  24. //使用Person的属性  
  25. document.write(Student.name + ' ' + Student.email + ' ' + Student.website +'<br>');  
  26.    
  27. //使用Person的方法  
  28. Student.sayHello();  
  29.    
  30. //重载SayHello方法  
  31. Student.sayHello = function (person) {  
  32.     var hello = "<p>Hello, I am "+ this.name  + ", <br>" +  
  33.                 "my email is: " + this.email + ", <br>" +  
  34.                 "my website is: " + this.website + ", <br>" +  
  35.                 "my student no is: " + this. no + ", <br>" +  
  36.                 "my departent is: " + this. dept;  
  37.     document.write(hello + '<br>');  
  38. }  
  39. //再次调用  
  40. Student.sayHello();  
  41.    
  42. //查看Student的属性(只有 no 、 dept 和 重载了的sayHello)  
  43. document.write('<p>' + Object.keys(Student) + '<br>'); 

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

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

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

  1. Student.name = 'aaa';  
  2.    
  3. //输出 aaa  
  4. document.write('<p>' + Student.name + '</p>');  
  5.    
  6. //输出 Chen Hao  
  7. 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.     var hello = "my student no is: " + this. no + ", <br>" +  
  5.                 "my departent is: " + this. dept;  
  6.     document.write(hello + '<br>');  

这个很强大吧。

组合

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

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

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

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

  1. //艺术家  
  2. var Artist = Object.create(null);  
  3. Artist.sing = function() {  
  4.     return this.name + ' starts singing...';  
  5. }  
  6. Artist.paint = function() {  
  7.     return this.name + ' starts painting...';  
  8. }  
  9.    
  10. //运动员  
  11. var Sporter = Object.create(null);  
  12. Sporter.run = function() {  
  13.     return this.name + ' starts running...';  
  14. }  
  15. Sporter.swim = function() {  
  16.     return this.name + ' starts swimming...';  
  17. }  
  18.    
  19. Composition(Person, Artist);  
  20. document.write(Person.sing() + '<br>');  
  21. document.write(Person.paint() + '<br>');  
  22.    
  23. Composition(Person, Sporter);  
  24. document.write(Person.run() + '<br>');  
  25. document.write(Person.swim() + '<br>');  
  26.    
  27. //看看 Person中有什么?(输出:sayHello,sing,paint,swim,run)  
  28. document.write('<p>' + Object.keys(Person) + '<br>'); 

Prototype和继承

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

  1. var plus = function(x,y){  
  2.     document.write( x + ' + ' + y + ' = ' + (x+y) + '<br>');  
  3.     return x + y;  
  4. };  
  5.    
  6. var minus = function(x,y){  
  7.     document.write(x + ' - ' + y + ' = ' + (x-y) + '<br>');  
  8.     return x - y;  
  9. };  
  10.    
  11. var operations = {  
  12.     '+': plus,  
  13.     '-': minus  
  14. };  
  15.    
  16. var calculate = function(x, y, operation){  
  17.     return operations[operation](x, y);  
  18. };  
  19.    
  20. calculate(12, 4, '+');  
  21. calculate(24, 3, '-'); 

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

  1. var Cal = function(x, y){  
  2.     this.x = x;  
  3.     this.y = y;  
  4. }  
  5.    
  6. Cal.prototype.operations = {  
  7.     '+': function(x, y) { return x+y;},  
  8.     '-': function(x, y) { return x-y;}  
  9. };  
  10.    
  11. Cal.prototype.calculate = function(operation){  
  12.     return this.operations[operation](this.x, this.y);  
  13. };  
  14.    
  15. var c = new Cal(4, 5);  
  16.    
  17. c.calculate('+');  
  18. c.calculate('-'); 

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

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

  1. function Person(name, email, website){  
  2.     this.name = name;  
  3.     this.email = email;  
  4.     this.website = website;  
  5. };  
  6.    
  7. Person.prototype.sayHello = function(){  
  8.     var hello = "Hello, I am "+ this.name  + ", <br>" +  
  9.                 "my email is: " + this.email + ", <br>" +  
  10.                 "my website is: " + this.website;  
  11.     return hello;  
  12. };  
  13.    
  14. function Student(name, email, website, no, dept){  
  15.     var proto = Object.getPrototypeOf;  
  16.     proto(Student.prototype).constructor.call(this, name, email, website);  
  17.     this.no = no;  
  18.     this.dept = dept;  
  19. }  
  20.    
  21. // 继承prototype  
  22. Student.prototype = Object.create(Person.prototype);  
  23.    
  24. //重置构造函数  
  25. StudentStudent.prototype.constructor = Student;  
  26.    
  27. //重载sayHello()  
  28. Student.prototype.sayHello = function(){  
  29.     var proto = Object.getPrototypeOf;  
  30.     var hello = proto(Student.prototype).sayHello.call(this) + '<br>';  
  31.     hello += "my student no is: " + this. no + ", <br>" +  
  32.              "my departent is: " + this. dept;  
  33.     return hello;  
  34. };  
  35.    
  36. var me = new Student(  
  37.     "Chen Hao",  
  38.     "haoel@hotmail.com",  
  39.     "http://coolshell.cn",  
  40.     "12345678",  
  41.     "Computer Science"  
  42. );  
  43. document.write(me.sayHello()); 

兼容性

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

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

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

Object.create ()函数

  1. function clone(proto) {  
  2.     function Dummy() { }  
  3.    
  4.     Dummy.prototype             = proto;  
  5.     DummyDummy.prototype.constructor = Dummy;  
  6.    
  7.     return new Dummy(); //等价于Object.create(Person);  
  8. }  
  9.    
  10. var me = clone(Person); 

defineProperty ()函数

  1. function defineProperty(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.    
  9.     return target  

keys ()函数

  1. function keys(object) { var result, key  
  2.     result = [];  
  3.     for (key in object){  
  4.         if (object.hasOwnProperty(key))  result.push(key)  
  5.     }  
  6.    
  7.     return result;  

Object.getPrototypeOf() 函数

  1. function proto(object) {  
  2.     return !object?                null  
  3.          : '__proto__' in object?  object.__proto__  
  4.          : /* not exposed? */      object.constructor.prototype  

bind函数

  1. var slice = [].slice  
  2.    
  3. function bind(fn, bound_this) { var bound_args  
  4.     bound_args = slice.call(arguments, 2)  
  5.     return function() { var args  
  6.         args = bound_args.concat(slice.call(arguments))  
  7.         return fn.apply(bound_this, args) }  
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 九州娱乐提款未到账怎么办 法院拍卖后不足的余款怎么办 购车后余款没拿怎么办? 抵押房屋被执行后余款怎么办 苏宁无敌券过期怎么办 被亚马逊自营跟卖怎么办 苏宁易购绑定手机后解绑不了怎么办 手机qq注册号码忘了怎么办 原创头条号被限制推荐了怎么办 为什么打开app有广告怎么办 苹果手机浏览器总是弹出广告怎么办 手机上打开页面出现广告怎么办 电脑下面的任务栏变宽了怎么办 酷派手机总是出现广告怎么办 电脑弹出的热点新闻关闭不了怎么办 京东老是弹广告怎么办 电脑右下角出现无法显示网页怎么办 电脑右下角广告关不了怎么办 qq邮箱被冻结了怎么办 手机qq群自动发广告怎么办 济宁学院考研和上课冲突怎么办 考研但是大四上课多怎么办 试管促排卵泡少怎么办 京东退货卖家拒收怎么办 京东退货被卖家拒收怎么办 期货平台跑路了怎么办 浮云牧场没房了怎么办 融资股票停牌了怎么办 买入的股票停牌怎么办 淘宝抢到便宜货老板不发货怎么办 微信代购买到假货了怎么办 微信代购收到假货怎么办 苹果商店下载很慢怎么办 谷歌商店下载东西慢怎么办 买家说少发货了怎么办 人肉代购被海关扣了怎么办 韩国代购被海关扣了怎么办 爱奇艺开通自动续费忘了账号怎么办 小米手机云储存空间不足怎么办 路由器被黑了打不开网页怎么办 致人轻伤跑了怎么办