javaScript構造函數、原型、面向對象編程

js最重要也是最核心的東西就是對象了,入行這麼長時間,一直對面向對象只知其一;不知其二。網上有不少介紹對象對象的內容,這裏也作了不少借鑑,javascript

尤爲是阮一峯老師的文章。我這裏寫的大多例子都是阮一峯老師文章的例子,可是加上了我本身的看法。JavaScript面向對象編程html

 

 

 

js最核心的東西就是對象,萬物皆對象。對象分爲普通對象函數對象。分區是看是否有function關鍵字。java

爲何會出現面向對象編程?git

場景:若是咱們把屬性和方法封裝成一個對象,或者從原型對象上生成一個實例對象,咱們如何作?github

技術大佬們爲了讓技術界更核心,開始了他們的研究:編程

 

第一:原始模式(最古老的模式)ide

這裏咱們建立一個對象Cat,把它當作其餘對象的原型。其餘對象都按照這個模式來建立。函數

而後咱們在下方建立了cat1和cat2。this

可是問題是咱們看不到cat1和cat2之間的關係,最重要的是若是有n的cat,咱們豈不是要寫到天荒地老?spa

 

 1 let Cat = {
 2     name:'',
 3     color:''
 4 }
 5 // 而後根據這個規格來實現兩個實例對象。
 6   let cat1 = {};
 7     cat1.name = '大毛';
 8     cat1.color = '黃色';
9   let cat2 = {}; 10 cat2.name = '二毛'; 11 cat2.color = '黑色';

 

因此,進化到了封裝成一個函數

 

第二:原始模式改進

咱們把Cat封裝爲一個函數,咱們每次只須要調用該函數就行。

問題是,咱們依然看不出cat1和cat2之間的關係?不能一眼看出他們之間的關聯。

function Cat(name,color){
    return {
        name:name,
        color:color
    }
}
let cat1 = Cat('大毛','黃色');
let cat2 = Cat('二毛','黑色');
console.log(cat1);//{ name: '大毛', color: '黃色' }
console.log(cat2);//{ name: '二毛', color: '黑色' }

 

 

第三:構造函數(重點!!!)

爲了解決從原型對象上生成實例的問題,js開發人員給咱們提供了一個構造函數模式。

那麼什麼是構造函數呢?

 

先了解幾個概念:

構造函數(constructor):其實就是一個普通的函數,只是內部使用了this關鍵字。(這裏咱們記住構造函數的英文--Constructor

實例對象:經過new操做符生成的對象就是實例對象。而且this變量會綁定在實例對象上。(記住:生成的是實例對象!!)

constructor:構造函數生成的實例對象,會有一個constructor屬性,指向該構造函數。(咱們能夠理解爲,實例對象是從構造函數上衍生出來的,固然要有它的烙印了!)

// 咱們先建立一個構造函數Cat
 function Cat(name,color){
     this.name = name;
     this.color = color;
 }
//  用new生成Cat的實例。cat1和cat2
 let cat1 = new Cat('大毛','黃色');
 let cat2 = new Cat('二毛','黑色');
//  看一下輸出結果
 console.log(cat1);//Cat { name: '大毛', color: '黃色' }
 console.log(cat2);//Cat { name: '二毛', color: '黑色' }

咱們看一下經過構造函數Cat建立出來的實例對象cat1和cat2身上的constructor屬性:

//  我使用的是構造函數生成的實例(Constructor),因此實例上得有個人烙印。屬性名稱就是Constructor
// 指向的就是他們的構造函數。須要讓別人知道,是誰建立了你。
console.log(cat1.constructor === Cat);//true
console.log(cat2.constructor === Cat);//true

看到這裏,咱們已經明白了什麼是構造函數,什麼是原型對象,什麼是實例對象,以及實例對象的constructor屬性。

獎勵一下本身!

看到這裏,說明咱們已經成功了一半了!

 

咱們知道了構造函數和實例對象的關係,那咱們如何驗證呢?

js提供了一個instanceof運算符。instance(例子的意思)。從字面量就能看出某某是某某的實例。

看一下下面的代碼就會很清晰了。

console.log(cat1 instanceof Cat);//true

咱們瞭解了instanceof,那你們腦海裏還知不知道js提供的其餘兩種驗證的方法呢?建議你們看一下js祕密花園,裏面講解的不少東西很清晰。

除了instanceof,咱們還了解到typeof,以及Object.prototype.toString方法。

typeof其實沒什麼做用,不能驗證類型(驗證類型不夠完全),只能驗證一個對象或者一個變量是否存在。是一個垃圾產品。

instanceof的做用就是咱們上面提到的驗證構造函數之間的關係。

Object.prototype.toString則是咱們常用的方法,來驗證類型。先看一下下面的例子:

console.log(Object.prototype.toString.call([]));//[object Array]
console.log(Object.prototype.toString.call({}));//[object Object]
console.log(Object.prototype.toString.call(null));//[object Null]
console.log(Object.prototype.toString.call(undefined));//[object Undefined]
console.log(Object.prototype.toString.call(function f(){}));//[object Function]
// 而後咱們能夠根據slice方法來截取字符串
// slice(8,-1)表示從第八位開始,截取到倒數第一位(負數從後往前截取。)。
console.log(Object.prototype.toString.call([]).slice(8,-1));//Array

經過熟悉,咱們又知道了js存在的三種驗證類型。

 

繼續接着咱們構造函數開始:

構造函數的方法很好用,可是存在一個浪費內存的問題,這句話怎麼理解呢?先看個例子。

咱們給構造函數Cat內部多增長一個type屬性,以及eat方法(運行在對象上的函數成爲方法)。

而後咱們根據Cat生成兩個實例對象,cat1和cat2。根據輸出結果咱們發現,都會存在type屬性和eat方法。

若是說我生成的實例上面不須要這個屬性或這個eat方法,這就存在了浪費內存的問題了。

 function Cat(name,color){
     this.name = name;
     this.color = color;
     this.type = '貓科動物';
     this.eat = function(){
         console.log('吃老鼠');
     };
 }

 let cat1 = new Cat('大毛','黃色');
 let cat2 = new Cat('二毛','黑色');
 console.log(cat1);//Cat { name: '大毛', color: '黃色', type: '貓科動物', eat: [Function] }
 console.log(cat2);//Cat { name: '二毛', color: '黑色', type: '貓科動物', eat: [Function] }
//  爲何是false?由於cat1和cat2是兩個不一樣的實例對象,而對象的比較是內存的比較,不是值的比較。
console.log(cat1.eat === cat2.eat);//false

 

那咱們如何解決呢?

 

第五:prototype

js規定,每一個構造函數都有一個prototype屬性,指向另一個對象。(咱們這裏能夠把它當作一個虛無的對象A)。A對象上面的全部屬性和方法,都會被構造函數

的實例繼承。因此這就意味着,咱們能夠把這些不變的屬性和方法放到這個對象A上面。

 咱們繼續使用構造函數Cat的例子:

 function Cat(name,color){
     this.name = name;
     this.color = color;
 }
 Cat.prototype.type = '貓科動物';
 Cat.prototype.eat = function(){
     console.log('吃老鼠');
 }
//  繼續生成實例
let cat1 = new Cat('大毛','黃色');
let cat2 = new Cat('二毛','黑色');
console.log(cat1.type);  //貓科動物
// 實例對象cat1和實例對象cat2上面的eat方法,都是繼承過來的,都指向咱們所說的對象A上面。
// 因此它們的指針是同樣的。
console.log(cat1.eat === cat2.eat);//true
console.log(cat1.type === cat2.type);//true

 

 到了這裏,咱們知道了每一個構造函數上面都會有一個prototype屬性。

就以咱們建立的構造函數Cat來講,就至關於 let A = Cat.prototype;

這個A就是一個對象,咱們Cat.prototype.type = "貓科動物",其實就是給該對象新增屬性罷了。

 

isPrototype 

 那咱們如何判斷呢?還記得咱們上面說過的instanceof,這裏有一個新的方法,isPrototype,來判斷某個prototype對象(A)和某個實例(cat)之間的關係。

console.log(Cat.prototype.isPrototypeOf(cat1));

 

hasOwnProperty() 

 常用for...in循環的應該知道該屬性,用來過濾繼承的屬性。

function Cat(name, color) {
    this.name = name;
    this.color = color;
}
// 咱們在其原型上增長兩個屬性
Cat.prototype.type = "貓科動物";
Cat.prototype.address = "杭州";
let cat1 = new Cat("大毛", "黑色");
cat1.lover = '二毛';
for (let i in cat1) {
    console.log(cat1[i]);
    // 輸出結果是以下,也就是說把繼承的屬性和自身的屬性所有遍歷出來了(這就是咱們不多使用for in 循環的緣由。)
    // 大毛
    // 黑色
    // 二毛
    // 貓科動物
    // 杭州
}
// 而後咱們使用hasOwnPrototype()來過濾
for (let j in cat1) {
    if (cat1.hasOwnProperty(j)) {
        console.log(cat1[j])
        // 大毛
        // 黑色
        // 二毛
    }
}

 

學到這裏,讓咱們回想一下如下幾個概念:

一、什麼是構造函數?

二、什麼是實例對象?它是如何創造出來的?

三、實例對象的constructor指向什麼?

四、構造函數的prototype屬性是什麼?

 

答案:

構造函數是一個普通函數,函數內經過this關鍵字來聲明變量。

實例對象同構new+構造函數生成的。構造函數的this指向生成的實例對象。

實例對象的constructor屬性指向構造函數。

構造函數的prototype屬性指向的是一個對象,該對象上面的全部屬性和方法,都會被構造函數建立的實例對象所繼承。

 

下面開始咱們的最重要部分,瞭解後面的知識,咱們差很少了解了原型,瞭解了構造函數,瞭解了面向對象

 先看下面的例子:

給構造函數Cat的原型對象上新增三個屬性。

function Cat(name,color){
    this.name = name;
    this.color = color;
}
Cat.prototype.address = '杭州';
Cat.prototype.lover = '小翠';
Cat.prototype.type = '貓科動物';

看到這裏,會有小夥伴問,爲何不寫在一塊兒?那咱們改變一下:

function Cat(name,color){
    this.name = name;
    this.color = color;
}
Cat.prototype = {
    address:'杭州',
    lover:'小翠',
    type:'貓科動物'
}
// 等價於
// Cat.prototype.address = '杭州';
// Cat.prototype.lover = '小翠';
// Cat.prototype.type = '貓科動物';

看到這裏是否是會明白點什麼?Cat.prototype就是咱們建立的原型對象,也就是咱們上面提到的A。

其實它還有一個默認的屬性,constructor屬性。

這種狀況下,全部的原型對象都會自動得到一個constructor(構造函數)屬性,這個屬性指向prototype所造的函數。

翻譯過來就是,A有一個默認的屬性,constructor。這個屬性是一個指針,指向Cat。

 

Cat.prototype.constructor = Cat;

看到constructor是否是感受很熟悉?

咱們以前也提到過,每一個構造函數生成的實例上面都有一個constructor屬性,指向該構造函數。

對比一下下面的代碼,看看有什麼發現?

cat1.constructor = Cat;
Cat.prototype.constructor = Cat;

 

cat1有constructor咱們知道,由於它是Cat的實例對象。

那麼Cat.prototype是怎麼回事呢?

其實Cat.prototype也是一個實例對象。咱們看一下下面的代碼

function Cat(){

};
console.log(Cat.prototype);//Cat {}
let cat1 = new Cat();
console.log(cat1);//Cat {}

發現什麼沒?Cat.prototype其實就是Cat的一個實例對象!!!因此它也有constructor屬性!!!!!

 

 

_proto_

js建立對象的時候,會有一個_proto_的內置屬性,用於指向建立它的構造函數的原型對象。

function Cat(){};
let cat1 = new Cat();
// 實例對象上面的__proto__指向的是構造函數的原型
console.log(cat1.__proto__ == Cat.prototype);

 

 

總結一下吧:

構造函數Cat

實例對象cat1

 

一、cat1經過new Cat()建立。

二、cat1.constructor = Cat;

三、Cat.prototype.constructor = Cat;

四、cat1.__proto__ = Cat.prototype;

 

再次聲明:

這裏我主要借鑑了阮一峯老師的JavaScript面向對象編程,加上我我本身的簡介。對理解面向對象感受熟悉不少。但願能幫助你們。

相關文章
相關標籤/搜索