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面向對象編程,加上我我本身的簡介。對理解面向對象感受熟悉不少。但願能幫助你們。