javaScript设计模式之面向对象编程(object-oriented programming,OOP)(二)
接上一篇
面向对象编程的理解?
答:面向对象编程,就是将你的需求抽象成一个对象,然后针对这个对象分析其特征(属性)与动作(方法)。这个对象我们称之为类。面向对象编程思想其中一个特点就是封装,就是把你需要的功能放在一个对象里。
一、封装
1.1创建一个类
在javascript中创建一个类,很容易,
方法一:首先,声明一个函数保存在变量里,然后,在这个函数内部通过this(函数内部自带的一个变量,用于指向当前这个对象)变量添加属性或者方法,来实现对类的添加属性或方法。
举个例子:
var Book = function(id,bookname,price){ this.id = id; this.bookname = bookname; this.price = price; }
方法二:可以通过类的原型添加属性或方法,有两种方法,不要混用
(1)为原型对象属性赋值的方法
Book.prototype.display = function(){ //展示这本书 };
(2)将对象赋值给类的原型对象
Book.prototype = { display:function(){} };
我们需要的方法和属性都封装在我们抽象的Book类里,当使用功能方法时,我们不能直接使用这个Book类,需要用关键字new来实例化新的对象。使用实例化对象的属性或者方法时,可以通过点语法来访问
var book = new Book(10,'javascript课本',50); console.log(book.bookname);
由于JavaScript的函数级作用域,声明在函数内部的变量以及方法在外界是访问不到的,通过此特性即可创建类的私有变量以及私有方法。然而在函数内部通过this创建的属性和方法,在类创建对象时时,每一个对象自身都拥有一份并且可以外部访问的。因此通过this创建,不但可以访问这些对象的共有属性与共有方法,而且还可以访问到类或者对象自身的私有属性和私有方法。
//私有属性,私有方法;特权方法,对象公有属性,对象公有方法;构造器
var Book = function(id,name,price){ //私有属性 var num = 1; //私有方法 function checkId(){}; //特权方法 this.getName = function(){}; this.getPrice = function(){}; this.setName = function(){}; this.setPrice = function(){}; //对象公有属性 this.id = id; //对象公有方法 this.copy = function(){}; //构造器 this.setName(name); this.setPrice(price); };
使用原生的prototype对象。
//静态类公有属性(对象不能访问) Book.isChinese = true; //类静态公有方法(对象不能访问) Book.resetTime = function(){ console.log('new time'); }; Book.prototype = { //公有属性 isJSBook:false, //公有方法 display:function(){} }
通过new关键字创建的对象时对新对象this的不断赋值,并将prototype指向类的prototype所指向的对象,而类的构造函数外面通过点语法定义的属性方法是不会添加到新创建的对象上去的。
var b = new Book(11,'JavaScript设计',50); console.log(b.num); //undefined console.log(b.isJSBook); //false console.log(b.id); //11 console.log(b.isChinese); //undefined
类的私有属性num,以及静态公有属性isChinese在新创建的b对象里访问不到。而类的公有属性isJSBook在b对象中却可以通过点语法访问到。
但是类的静态公有属性isChinese可以通过类的自身访问。
console.log(Book.isChinese); //true Book.resetTime(); //new time
1.2闭包实现
你对闭包的理解?
答:闭包是有权访问另外一个函数作用域中变量的函数,即在一个函数内部创建另外一个函数。我们将这个闭包作为创建对象的构造函数,这样它既是闭包又是可实例化对象的函数,即可访问到类函数作用域的变量,这个变量叫静态私有变量,静态私有方法。
二、继承
总结类: 发现每一个类都有3个部分:
1、第一部分是构造函数内的,供实例化对象复制用的;
2、第二部分是构造函数外的,直接通过点语法添加的,这是供类使用的,实例化对象时访问不到的;
3、第三部分是类的原型中的,实例化对象可以通过其原型链间接的访问到,也是为供所有的实例化对象所共有的。
JavaScript中没有继承这一个现有的机制,该如何实现呢?
(一)子类的原型对象--类式继承
比如:常见的类式继承
//类式继承 //声明父类 function SuperClass(){ this.superValue = true; } //为父类添加共有方法 SuperClass.prototype.getSuperValue = function(){ return this.superValue; }; //声明子类 function SubClass(){ this.subValue = false; } //继承父类 SubClass.prototype = new SuperClass(); //为子类添加共有方法 SubClass.prototype.getSubValue = function(){ return this.subValue; }
刚才封装,对比,继承里面声明了2个类,而且第二个类的原型prototype被赋予第一个类的实例。
类式继承需要将第一个类的实例赋值给第二个类的原型。
继承原理:新创建的对象不仅仅可以访问父类原型上的属性和方法,同样可以访问从父类构造函数中复制的属性和方法。将这个对象赋值给子类的原型,那么这个子类的原型同样可以访问父类原型上的属性和方法与从父类构造函数中复制的属性和方法。
var instance = new SubClass(); console.log(instance.getSuperValue()); //true console.log(instance.getSubValue()); //false
在js中,有一个关键字instanceof 来判断某一个对象是否为某一个类的实例,或者说某一个对象是否继承了某个类,这样可以判断对象和类之间的关系
instanceof如何知道对象和类之间的继承关系呢?
答;instanceof是通过判断对象的prototype链来确定这个对象是否是某一个类的实例,而不关心对象与类的自身结构。
console.log(instance instanceof SuperClass); //true console.log(instance instanceof SubClass); //true console.log(SubClass instanceof SuperClass); //false
为啥最后是false,SubClass继承SuperClass,为啥还是false;记住:instanceof是判断前面的对象是否是后边类(对象)的实例,它并不是表示两者的继承。
console.log(SubClass.prototype instanceof SuperClass); //true
类式继承的一个特点:你所创建的所有的对象都是谁的实例?
Object,正式JavaScript为我们提供的原生对象Object。创建的所有的对象都是Object的实例。
console.log(instance instanceof Object); //true
类式继承有两个缺点:
1、由于子类是通过其原型prototype对父类实例化,继承了父类。所以说父类中共有属性要是引用类型,就会在子类中被所有实例共用,因此一个子类的实例更改子类原型从父类构造函数中继承来的共有属性就会直接影响到其他子类。
2、由于子类实现的继承是靠原型的prototype对父类的实例化实现的,因此在创建父类的时候,是无法向父类传递参数的,因而在实例化父类的时候也无法对父类构造函数内的属性进行初始化。
(二)创造即继承--构造函数继承
除了类式继承以外还有构造函数继承
//构造函数式继承 //声明父类 function SuperClass(id){ //引用类型共有属性 this.books = ['JavaScript','html','css']; //值类型共有属性 this.id = id; } //父类声明原型的方法 SuperClass.prototype.showBooks = function(){ console.log(this.books); } //声明子类 function SubClass(id){ //继承父类 SuperClass.call(this,id); } //创建第一个子类的实例 var instance1 = new SubClass(10); //创建第二个子类的实例 var instance2 = new SubClass(11); instance1.books.push('设计模式'); console.log(instance1.books); //["JavaScript", "html", "css", "设计模式"] console.log(instance1.id); //10 console.log(instance2.books); //["JavaScript", "html", "css"] console.log(instance2.id); //11
注意:SuperClass.call(this.id);这个语句是构造函数式继承的精华,由于call这个方法可以更改函数的作用域,
由于这种类型的继承没有涉及到原型prototype,所以父类的原型方法自然就不会被子类继承,而如果想被子类继承就必须放在构造函数中,这样创造出来的每一个实例都会拥有一份而不能共用,这样就违背了代码复用的原则。
综上这两种模式优点,后来就有了组合式的继承
(三)集合优点--组合继承
总结一下:
(1)类式继承,通过子类的原型prototype对父类实例化来实现的
(2)构造函数继承,通过子类的构造函数作用环境执行一下父类的构造函数来实现的
//组合式的继承 //声明父类 function SuperClass(name){ //值类型共有属性 this.name = name; //引用类型共有属性 this.books = ['html','css','JavaScript']; }; //父类原型共有方法 SuperClass.prototype.getName = function(){ console.log(this.name); }; //声明子类 function SubClass(name,time){ //构造函数式继承父类name属性 SuperClass.call(this,name); //子类中新增共有属性 this.time = time; } //类式继承 子类原型继承父类 SubClass.prototype = new SuperClass(); //子类原型方法 SubClass.prototype.getTime = function(){ console.log(this.time); }
组合模式:在子类构造函数中执行父类构造函数,在子类原型上实例化父类。
这样就融合了类式继承和构造函数继承的优点。
var instance1 = new SubClass('js book',2014); instance1.books.push('设计模式'); console.log(instance1.books); //["html", "css", "JavaScript", "设计模式"] instance1.getName(); //js book instance1.getTime(); //2014 var instance2 = new SubClass('css book',2013); console.log(instance2.books); //["html", "css", "JavaScript"] instance2.getName(); //css book instance2.getTime(); //2013
子类的实例中更改父类继承下来的引用类型属性如books,根本就不会影响到其他实例。
但是我们在使用构造函数继承时执行了一遍父类的构造函数,而在实现子类原型的类式继承时又调用了一遍父类的构造器。因此父类构造函数调用了两遍,还不是最完美的方式。
(四)洁净的继承者--原型式继承
借助原型的prototype可以根据已有的对象创建一个新对象,同时不必创建新的自定义对象类型。
var inheritObject(item){ //声明一个过渡函数对象 function F(){}; //过渡对象的原型继承父对象 F.prototype = item; //返回过渡对象的一个实例,该实例的原型继承了父对象 return new F(); }
他是对类式继承的一个封装,其实其中的过渡对象就相当于类式继承中的子类,只不过在原型式中作为一个过渡对象出现,目的是为了创建要返回新的实例化对象。
当然如果你感觉有必要可以将F过渡缓存起来,不必每次都创建一个新过渡类F,随后就出现了Object.create()的方法。
var book = { name: 'js book', alikeBook: ['css book','html book'], }; var newBook = inheritObject(book); newBook.name = 'ajax book'; newBook.alikeBook.push('xml book'); var otherBook = inheritObject(book); otherBook.name = 'flash book'; otherBook.alikeBook.push('as book'); console.log(newBook.name); //ajax book console.log(newBook.alikeBook); //['css book','html book','xml book','as book'] console.log(otherBook.name); //flash book console.log(otherBook.alikeBook); //['css book','html book','xml book','as book'] console.log(book.name); //js book console.log(book.alikebook); //['css book','html book','xml book','as book']
跟类式继承一样,父类对象book中的值类型的属性被复制,引用类型的属性被共用。