天天看點

面向對象、工廠模式、構造函數、原型、原型鍊都不懂?看這一篇就夠了!面向對象:this:工廠方法(factory):構造函數(constructor):原型(prototype):構造函數+原型:

本文是部落客自己歸納曾學到的知識,以及分享工廠模式、構造函數、以及原型鍊之間的用途;有很多面試者都會遇到這些問題,但是一些小白來說,還是不清楚,這些知識點到底在技術中有什麼用途,到底是幹什麼的?今天我就簡單說一說,本篇部落格會從基本開始介紹,如果你對今天的内容所緻甚少,并且你有耐心看完,相信你會有超值的收獲!!!

小白們都對原型鍊迷迷糊糊,經常查閱相關文章,卻還是縷不清到底是幹什麼的。因為你不知道它到底有什麼用,又是如何會用到它,是以在直接查閱原型鍊的時候,忽略了它之前很重要的知識,是以你不懂。這個很重要的知識,就是構造函數!!!

本篇部落格要一環扣一環,決定幫大家捋清思路,是以我們從基本開始說,從頭到尾請跟住部落客的思路。

面向對象:

對象?無論在javascipt當中,還是java當中,或其他語言中,所提及的對象,絕對不是男女朋友的對象,是以我們凡人先忘掉感情視野,再體會一下這個詞:“對象”。

所謂對象,任何事物都可以稱作是對象。

比如:

醫生給病人治療,病人對于醫生來說,就是治療的對象。

我們去商場購買蘋果,蘋果對于我們來說,就是要購買的對象。

部落客寫的部落格,你們就是我要展示部落格的對象。

科學家要研制火箭,火箭對于科學家來說,就是要研制的對象

……

是以我們這些都是對象,有句英文說的好,Everything is object(萬物皆對象)。

知道對象了,那為什麼要“面向”呢?“面向對象又是什麼?”

這就是一種程式設計思想,面向對象程式設計。在我們的開發世界中,把所有的方法都封裝成一個一個的對象,大的功能都是由一個又一個的對象實作的,是以就叫面向對象程式設計思想。在程式員眼中,一切功能都是被拆分成小的單獨對象,組裝起來的。

在開發中,我們很容易可以創造出一個對象,例如:

var person = {};             // 字面量建立一個對象
var person1 = new Object();  // 構造一個對象           

兩種方法,但都是創造了一個對象。對象都有自己的屬性,比如人可以吃飯,睡覺,戀愛。這些都是我們人(對象)的屬性,同樣,我們也可以在上面的例子中,加入屬性:

var person = {
  name: 'villin',
  age: 18,
  exes: ["111", "222", "333"]
};           

我們也可以删除裡邊的屬性,比如把年齡删除:

delete person.age           

這些就是對象的含義,我們在開發中,也可以建立一個函數,這個函數就看作是一個對象,裡邊些不同的方法。面向對象很簡單,反複的看幾遍,就能把這個“對象”一詞了解明白。

this:

this,是屬于一個指向的關鍵字,一般是在函數内使用,誰調用了這個函數,this指向誰。這個不太好解釋,隻能體會的去了解。看下邊這個例子,在person中定義sayHi這個函數,是以this指向了person對象中的屬性。

var person = {}

person.name = 'villin';

person.sayHi = function () {
  console.log(this.name)
}

  person.sayHi();    // villin           

this重點:

1、在函數内,函數定義時無法确定this指向,是以記住:誰調用,指向誰!

2、函數如果以普通方式進行調用,this指向window

3、每個函數都有自己的this,隻要進入一個函數,this就可能發生變化

那麼如果在函數中還不想讓this指向window,還想使用傳過來的參數,我們習慣于将參數指派:

function aaa(name){
  this.name = name
}           

工廠方法(factory):

我們把上邊剛才的例子,放在一個函數中,并傳回這個值,就是工廠模式。具體為啥叫這個名字,我也不知道。我們封裝一個簡單的(工廠模式)函數,并且進行調用。這個函數很簡單,我标注的很詳細,仔細看,這一環很重要。

// 建立一個函數,接收兩個參數,一個name,一個age     (函數可以接收很多參數,具體幾個看你們自己的心情)

function person(name,age){
  // 1.建立一個對象
  var a = {};
  // 2.在對象上添加屬性和方法,将參數指派給它
  a.name = name;
  a.age = age;
  // 3.和上邊一樣,這個對象a中定義一個函數sayHi
  a.sayHi = function(){
    console.log("你好,我叫"+this.name+",我今年"+this.age+"歲")
  }
  // 4.建立的對象必須要傳回這個對象a,才能列印出函數中的内容;
  return a;
}           

好了,這個工廠模式的函數,就封裝好了,就可以多次調用這個封裝的方法了,例如:

// 定義三個p,分别傳入兩個參數,一個name,一個age
var p1 = person("villin",25)
var p2 = person("xiaoming",26)
var p3 = person("xiaoli",18)

// 分别調用三個方法
p1.sayHi()
p2.sayHi()
p3.sayHi()           

列印結果如下:

面向對象、工廠模式、構造函數、原型、原型鍊都不懂?看這一篇就夠了!面向對象:this:工廠方法(factory):構造函數(constructor):原型(prototype):構造函數+原型:

我們有個方法

instanceof

,是可以檢測一個對象是否為一個構造函數的執行個體,剛才寫的我們可以檢測一下:

console.log(p1 instanceof person)  // false           

傳回為false,說明p1并不是person函數的執行個體,那麼如果我在建立一個其他的函數,也是檢測不出來的,這樣在項目中,如果很多個函數,我們就無法得知,這個對象是哪個函數的執行個體,是以工廠模式的缺點,就是無法判斷生成的對象,是哪個構造函數的執行個體。

解決辦法:使用構造函數的方法來建立對象!!!

延伸:執行個體和繼承。
   什麼是執行個體?舉個例子:我們剛才說過,萬物皆對象,那麼動物就是個對象,我們再具體一點,貓、狗、老鼠這些就是動物的執行個體。是以person函數就是一個對象,那麼p1、p2、p3是否是person的執行個體,就用instanceof來驗證;
   那麼貓,狗等都繼承了動物的特征,比如動物是活的,有眼睛耳朵鼻子等,貓狗也有這些,這就是繼承,繼承了動物的特征。但是不能反過來說動物也繼承了貓狗的特征,這是不對的,因為貓有毛,但是魚沒有。
   萬物皆對象,是以執行個體(像貓和狗)也是對象,但是對象未必是執行個體(例如動物是個大的架構,沒有被細分成執行個體)。           

構造函數(constructor):

我們接着上邊的案例來說,同樣我們建立一個函數person,但是這個函數名首字母要大寫,代表構造函數,但并非規定。

// 函數名大寫
function Person(name,age){
  // 省略了建立對象 (和工廠模式對比)
  // 省略了添加屬性和方法 (和工廠模式對比)
  // 1.指派指針this
  this.name = name;
  this.age = age;
  this.sayHi = function () {
   console.log("你好,我叫"+this.name+",我今年"+this.age+"歲")
  }
  // return傳回對象,也省了 (和工廠模式對比)
}           

接下來我們重新構造(new)這個Person對象,并傳入相應的參數:

p1 = new Person("villin",25)  // 不要忘記大寫
p2 = new Person("xiaoming",26)
p3 = new Person("xiaoli",18)

// 分别調用三個方法
p1.sayHi()
p2.sayHi()
p3.sayHi()           

列印結果,同樣如此:

面向對象、工廠模式、構造函數、原型、原型鍊都不懂?看這一篇就夠了!面向對象:this:工廠方法(factory):構造函數(constructor):原型(prototype):構造函數+原型:

我們再次嘗試用

instanceof

驗證一下

console.log(p1 instanceof Person)  // true           

顯然,可以被驗證了,這也說明了,構造函數p1為Person的執行個體。如果項目中不光有Person函數,或其他,也都可以驗證。(自己要回頭去看一下工廠模式和構造函數的差別)

不過構造函數依然伴有缺點:

細心發現,我們剛才構造了三個函數p1~p3,分别構造了三次,這樣每次構造一次,就會生成一個sayHi的函數(好奇者可以分别列印p1、p2、p3在控制台看一下,會發現生成了三次相同的函數),并且這些函數對象内的方法,其代碼是一模一樣的,都是(你好,我的名字是,年齡)等,這樣重複太多,複用性不高,太浪費資源。

想解決這個問題,我們就需要把這個sayHi公用的函數存放到一個位置,這個位置要確定每個對象都能通路到。

那麼這個位置就是:prototype(原型)。

原型(prototype):

1、prototype中文含義就是“原型”,隻要是函數,都有自己的原型prototype。

2、當用構造函數建立對象(new Xxx())時,浏覽器在新建立的對象上添加了一個屬性__proto__(前後是雙下劃線。不要直接使用)。

是以要記住,對象中有一個屬性是__proto__。函數也是對象,是以它也有__proto__,但是函數還多了一個prototype屬性。

這個對象到底長什麼樣,我們列印一下這個對象p1(剛剛的p1)看一下。

function Person(name,age){
  // 省略了建立對象 (和工廠模式對比)
  // 省略了添加屬性和方法 (和工廠模式對比)
  // 1.指派指針this
  this.name = name;
  this.age = age;
  this.sayHi = function () {
   console.log("你好,我叫"+this.name+",我今年"+this.age+"歲")
  }
  // return傳回對象,也省了 (和工廠模式對比)
}

p1 = new Person("villin",25)

console.log(p1) // 列印p1           
面向對象、工廠模式、構造函數、原型、原型鍊都不懂?看這一篇就夠了!面向對象:this:工廠方法(factory):構造函數(constructor):原型(prototype):構造函數+原型:

可以看到,我們沒有列印函數,僅僅是列印出的p1,那為什麼也會有name、age、以及sayHi屬性?這就是繼承,因為new構造出來的是Person函數的執行個體,是以它繼承了它構造的函數Person中的屬性。

我們可以看到這個對象p1是帶有自己的一個屬性__proto__的,并且,這個對象的__proto__指向了它的構造函數的prototype(每個函數都是有prototype的屬性的,隻是沒列印而已,自己印在自己的腦海裡),并且__proto__也會繼承prototype中的屬性。

這些就是概念性的東西,要試着去了解,如果不了解多看幾遍,一定要慢慢的讀,部落客寫這些,也是慢慢的寫的。

那麼既然每個構造函數都會繼承它原來函數中prototype的屬性,那麼就利用這一點,寫一個可以公用的方法。

讓他們的執行個體公用這個sayHi函數,我們開始利用這個原型prototype去更改構造函數的不足之處:

function Person() {}  //單獨設一個空的函數,它有一個屬性是prototype

  // 公共的部分
  // 我們可以直接更改Person的原型prototype
  Person.prototype.name = 'villin';
  Person.prototype.age = 26;

  Person.prototype.sayHi = function () {
    console.log("你好,我叫"+this.name+",我今年"+this.age+"歲")
    }

 var p1 = new Person();
 var p2 = new Person();
 p1.sayHi();
 p2.sayHi();

// 你好,我叫villin,我今年26歲
// 你好,我叫villin,我今年26歲           

可以看到,雖然還是建立了兩個函數,但是p1和p2都複用了prototype中的屬性,是以列印的東西都是一樣的。

原型鍊:

疑問:同學可能會發現,我并沒有在p1和p2中定義任何的屬性,直接調用p1.sayHi為什麼可以把原型prototype中的sayHi列印出來?

p1中并沒有sayHi這個屬性。這就引入了一個詞叫:原型鍊。

當通路一個對象時,會在對象内進行查找這個屬性,比如之前定義過的this.sayHi屬性。像本次,對象内沒有定義sayHi屬性,那麼就會進入p1的原型__proto__中查找,因為__proto__指向了Person中prototype,并繼承了其中的屬性,是以prototype中有啥,__proto__就有啥,是以就找到了。這一條鍊式查找就是原型鍊。我們列印一下p1看一下:

面向對象、工廠模式、構造函數、原型、原型鍊都不懂?看這一篇就夠了!面向對象:this:工廠方法(factory):構造函數(constructor):原型(prototype):構造函數+原型:

不過,這個方法還是有些缺點,因為p1和p2都繼承了相同的屬性,列印出來的結果都是一樣的,這并不是我們想要的,我們想要的是,公用一些方法外,可以傳遞想要列印的參數,想列印出什麼值,就列印出什麼值。

是以,最後的解決方案:組合(構造+原型)

構造函數+原型:

如果以上的幾個方法都看明白了,這個就很簡單,隻需要在原型中定義想要得到的公用方法,在函數中定義this指向,并接收參數即可:

// 需要定義兩個函數,一個是構造函數Person,一個是在Person的prototype中定義公用方法sayHi函數。

function Person(name, age, exes) {
  //将所有的參數屬性,放在構造方式中
  this.name = name;
  this.age = age;
  this.exes = exes;
}

Person.prototype.sayHi = function () {
  // this會指向Person内
  console.log("你好,我叫"+this.name+",我今年"+this.age+"歲")
}           

然後照常傳遞參數,正常列印:

p1 = new Person("villin",25)
p2 = new Person("xiaoli",18)

// 你好,我叫villin,我今年25歲
// 你好,我叫xiaoli,我今年18歲
           

這樣既有公用的繼承函數方法,又可以傳遞參數,可以獲得自己想要的資料。

我們最後可以在驗證一下是否是Person的執行個體問題。

console.log(p1 instanceof person)  // true           

這就是今天所寫的内容,在最後,補充一下知識,也能會成為面試的考點:

問題:

構造函數在new的時候,JS引擎做了那些事情?(new之後發生了什麼?)

  1. 建立了一個對象
  2. 将構造函數内的this,指向這個對象
  3. 執行構造函數
  4. 傳回這個對象

部落客聯系方式:

e-mail:[email protected]

weChat:VillinWeChat

歡迎提出寶貴意見