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中的值類型的屬性被復制,引用類型的屬性被共用。
javaScript設計模式之面向對象編程(object-oriented programming,OOP)(二)