javaScript設計模式之面向對象編程(object-oriented programming,OOP)(二)

接上一篇javascript

面向對象編程的理解?css

答:面向對象編程,就是將你的需求抽象成一個對象,而後針對這個對象分析其特徵(屬性)與動做(方法)。這個對象咱們稱之爲類。面向對象編程思想其中一個特色就是封裝,就是把你須要的功能放在一個對象裏。html

 

1、封裝java

1.1建立一個類ajax

在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閉包實現

你對閉包的理解?

答:閉包是有權訪問另一個函數做用域中變量的函數,即在一個函數內部建立另一個函數。咱們將這個閉包做爲建立對象的構造函數,這樣它既是閉包又是可實例化對象的函數,便可訪問到類函數做用域的變量,這個變量叫靜態私有變量,靜態私有方法。

 

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

類式繼承有兩個缺點:

一、因爲子類是經過其原型prototype對父類實例化,繼承了父類。因此說父類中共有屬性要是引用類型,就會在子類中被全部實例共用,所以一個子類的實例更改子類原型從父類構造函數中繼承來的共有屬性就會直接影響到其餘子類。

二、因爲子類實現的繼承是靠原型的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中的值類型的屬性被複制,引用類型的屬性被共用。

相關文章
相關標籤/搜索