JavaScript设计模式——前奏(封装和信息隐藏)
三:封装和信息隐藏:
信息隐藏用来进行解耦,定义一些私有的数据和方法。
封装是用来实现信息隐藏的技术,通过闭包实现私有数据的定义和使用。
接口在这其中扮演的角色是:提供一份记载着可公众访问的方法的契约,它定义了两个对象间可以具有的关系。
接下来介绍一下创建对象的基本模式:
基本模式有3种:门户大开型、用下划线表示方法和属性的私用性、闭包创建私用成员
举个例子,你要创建一个Book类,别人会创建实例并使用。
//Book(isbn,title,author) var theHobbit = new Book('0-12-34-2654-2','the hobbit','xin'); theHobbit.display();
1.门户大开型:
所谓门户大开,即,用一个函数来做构造器,所有方法和属性都是公开的。
1 //定义接口 2 var Publication = new Interface('Publication',['getIsbn','setIsbn','getTitle','setTitle','getAuthor','setAuthor','display']); 3 4 //implements Publication 5 var Book = function(isbn, title, author){ 6 this.setIsbn(isbn); 7 this.setTitle(title); 8 this.setAuthor(author); 9 } 10 Book.prototype = { 11 checkIsbn:function(isbn){ 12 //.....检查isbn的格式是否规范 13 //....... 14 }, 15 getIsbn:function(){ 16 return this.isbn; 17 }, 18 getTitle:function(){ 19 return this.title; 20 }, 21 getAuthor:function(){ 22 return this.author; 23 }, 24 setIsbn:function(isbn){ 25 if(!this.checkIsbn(isbn)) throw new Error('Book: Invalid ISBN'); 26 this.isbn = isbn; 27 }, 28 setTitle:function(title){ 29 this.title = title || "no title specified"; 30 }, 31 setAuthor:function(author){ 32 this.author = author || "no author specified"; 33 }, 34 display:function(){ 35 //.............. 36 alert(this.getIsbn()+this.getTitle()+this.getAuthor()) 37 } 38
这种方法看似很完善了,但是对于里面的属性还是可以进行修改的,isbn亦可能被赋值为一个无效值。
2.用一个函数来做构造器(用命名规范区别私用成员)
这个方法和上面的方法如出一辙,就是给私有成员加一个下划线。
就不做详细的讨论了。
3.作用域、嵌套和闭包
1 //implements Publication 2 var Book = function(newisbn, newtitle, newauthor){ 3 //私有成员 4 var isbn,title,author 5 6 //私有方法 7 function checkIsbn(isbn){ 8 //.....检查isbn的格式是否规范 9 //....... 10 } 11 12 //特权方法 13 this.getIsbn(){ 14 return isbn; 15 } 16 this.getTitle(){ 17 return title; 18 } 19 this.getAuthor(){ 20 return author; 21 } 22 this.setIsbn(){ 23 if(!checkIsbn(newisbn)) throw new Error('Book: Invalid ISBN'); 24 isbn = newisbn; 25 } 26 this.setTitle(){ 27 title = newtitle; 28 } 29 this.setAuthor(){ 30 author = newauthor; 31 } 32 33 //构造 34 this.setIsbn(newisbn); 35 this.setAuthor(newauthor); 36 this.setTitle(newtitle); 37 } 38 39 Book.prototype = { 40 display:function(){ 41 //.............. 42 }
这种方法存在一个问题。前面的门户大开型方法对象创建模式中,所有的方法都创建在原型对象上,因此不管生成多少对象实例,这些方法在内存中只会存在一份。而这种方法每次生成一个新的对象实例都会为每一个私用方法和特权方法生成一个新的副本。这种做法会浪费更多的内存。
这种对象创建模式不利于派生子类。(继承破坏封装.....)
最后总结一下:
私有属性和方法:函数有作用域,在函数内用var 关键字声明的变量在外部无法访问,私有属性和方法本质就是你希望在对象外部无法访问的变量。
特权属性和方法:创建属性和方法时使用的this关键字,因为这些方法定义在构造器的作用域中,所以它们可以访问到私有属性和方法;只有那些需要直接访问私有成员的方法才应该被设计为特权方法。
公有属性和方法:直接链在prototype上的属性和方法,不可以访问构造器内的私有成员,可以访问特权成员,子类会继承所有的共有方法。
公有静态属性和方法:最好的理解方式就是把它想象成一个命名空间,实际上相当于把构造器作为命名空间来使用。
1 /* -- 封装 -- */ 2 var classA =function(){ 3 //私有属性和方法 4 var name ='Xin'; 5 var method1 =function(){ 6 //... 7 } 8 //特权属性和方法 9 this.age ='20' ; 10 this.getName =function(){ 11 return name; 12 } 13 } 14 //共有静态属性和方法 15 classA._name ='Darren code'; 16 classA.alertName =function(){ 17 console.log(_name); 18 } 19 //共有属性和方法 20 classA.prototype = { 21 init:function(){ 22 //..... 23 }, 24 init2:function(){ 25 //..... 26 } 27 }
四:继承:
1.类式继承 (组合继承)
1 //超级类(父类) 2 function Persion(name){ 3 this.name = name; 4 } 5 Persion.prototype.getName = function(){ 6 return this.name; 7 } 8 9 //(子类) 10 function Author(name, books){ 11 Persion.call(this, name);//调用超类的构造函数,把name作为参数传过去 12 this.books = books; 13 } 14 Author.prototype = new Persion();//子类的原型指向超类的实例,同时就会拥有超类的原型所指向的内存所拥有的方法和属性。 15 Author.prototype.constructor = Author;//因为子类的原型对象等于超类的实例,所以prototype.constructor这个方法也等于超类构造函数,所以要重新指定constructor. 16 Author.prototype.getBooks = function(){ 17 return this.books; 18 }
首先创建构造函数,然后创建子类,通过call(this,arguments)来调用构造函数。然后设置原型链,js没有extend, so........ 就用prototype 来做继承。关于原型,不理解就自己看书吧,基础......要扎实........
子类的prototype是一个实例,就会拥有父类的prototype 属性,然后通过父类的prototype来找这块内存中存在的方法或者属性。
为了简化类的声明,可以把派生子类的整个过程包装在一个extend的函数中。
1 function extend(subClass,superClass){ 2 var F = function(){}; 3 F.prototype = superClass.prototype; 4 subClass.prototype = new F(); 5 subClass.prototype.constructor = subClass; 6 }
1 function extend(subClass,superClass){ 2 var F = function(){}; 3 F.prototype = superClass.prototype; 4 subClass.prototype = new F(); 5 subClass.prototype.constructor = subClass; 6 7 subClass.superclass = superClass.prototype;//添加superclass属性 8 if(superClass.prototype.constructor == Object.prototype.constructor){ 9 superClass.prototype.constructor = superClass; 10 } 11 } 12 13 function Author(name, books){ 14 Author.superclass.constructor.call(this, name); 15 this.books = books; 16 } 17 extend(Author, Persion); 18 Author.prototype.getBooks = function(){ 19 return this.books; 20 }
1 Author.prototype.getName = function(){ 2 var name = Author.superclass.constructor.getName.call(this); 3 return name + ",Author of" + this.getBooks().join(','); 4 }
//类式继承用到了原型链....原型链不懂的自己研究去吧.....
2.原型式继承
1 /* -- 原型式继承 -- */ 2 //clone()函数用来创建新的类Person对象 3 var clone =function(obj){ 4 var _f =function(){}; 5 //这句是原型式继承最核心的地方,函数的原型对象为对象字面量 6 _f.prototype = obj; 7 returnnew _f; 8 } 9 //先声明一个对象字面量 10 var Person = { 11 name:'Darren', 12 getName:function(){ 13 returnthis.name; 14 } 15 } 16 //不需要定义一个Person的子类,只要执行一次克隆即可 17 var Programmer = clone(Person); 18 //可以直接获得Person提供的默认值,也可以添加或者修改属性和方法 19 alert(Programmer.getName()) 20 Programmer.name ='Darren2' 21 alert(Programmer.getName()) 22 23 //声明子类,执行一次克隆即可 24 var Someone = clone(Programmer);
总结:
类式继承(组合继承):使用原型链继承共享的属性和方法,而通过借用构造函数( call( _ , _ ) )继承实例属性.
原型式继承:可以在不必预先定义构造函数的情况下实现继承,其本质是执行对给定对象的浅复制。而复制得到的副本还可以得到进一步改造。
类式继承,子类的原型指向一个父类的实例;原型式继承,子类的原型指向了父类的一个对象字面量(浅复制)。(子类都可能对父类的属性和方法进行修改。)
原型式继承会更省内存:被克隆出来的对象都共享每个属性和方法的唯一一份实例。而类式继承创建的每一个对象在内存中都有自己的一套属性(和私用方法)的副本。
最后:掺元类........