深刻理解JS的面向對象(更新中)

面向對象是軟件開發方法。面向對象的概念和應用已超越了程序設計和軟件開發,擴展到如數據庫系統、交互式界面、應用結構、應用平臺、分佈式系統、網絡管理結構、CAD技術、人工智能等領域。面向對象是一種對現實世界理解和抽象的方法,是計算機編程技術發展到必定階段後的產物。
JavaScript的面向對象主要包含了兩塊:1)建立對象 2)繼承。接下來,咱們將走進JS對象的世界,將依次帶你深刻了解函數、閉包、原型、原型鏈,並經過它們,最終實現建立對象和繼承。數據庫

建立對象

若是如今有一個純牛奶,那麼咱們建立對象能夠這樣操做:編程

var milk ={
    name:'純牛奶',
    taste:'pure',
    price:4
}

圖片描述

那麼若是如今有4種不一樣口味的牛奶呢?也建立四個不一樣的對象嗎?milk一、milk二、milk三、milk4?如有一萬種呢,是否也建立一萬個對象?
顯然,這是不合理的。這裏,咱們就要引入一個概念——工廠模式。segmentfault

工廠模式

function createMilk(name,taste,price){
    return {
        name:name,
        taste:taste,
        price:price
    }
}

var milk1 = createMilk('純牛奶','pure',4);
var milk2 = createMilk('有機奶','organic',4);
var milk3 = createMilk('低脂奶','low-fat',4);

在這裏,咱們實用一個函數,傳入產品名、口味、價格3個參數,返回一個對象。這樣,咱們只要調用這個函數,並傳入不一樣的參數,便可構建不一樣的對象。這就是工廠模式。網絡

工廠模式有優勢也有缺點:
1)優勢:簡單易懂、常見且實用。
2)缺點:對於如何證實我是一個牛奶這個問題上,則沒法證實。//這句如若沒法理解的話,能夠暫時忽略,繼續往下看哦!閉包

函數

爲了更好的講解構造函數、原型、原型鏈等,建議你複習一下函數的一些基礎知識,若是你已經對函數有深刻的瞭解,能夠選擇跳過。戳這裏:JS函數的一些基礎知識分佈式

構造函數

還記得咱們以前在講述工廠模式的缺點時,所說的那句「對於如何證實我是一個牛奶這個問題上,則沒法證實。」嗎?接下來,就是見證奇蹟的時刻!函數

原生構造函數
var obj = new Object();
var add = new Function('a','b','return a+b');

console.log(obj instanceof Object);//true
console.log(add instanceof Function);//true

Object和Function都是原生的構造函數,在這裏咱們就可使用instanceof來判斷是否爲它的一個實例——即:證實了我就是一個牛奶哦!!學習

既然有原生的構造函數,那麼咱們能夠不能夠也本身定義構造函數呢?答案是能夠的。this

構造函數

通常來講,咱們能夠這樣定義構造函數:人工智能

//構造函數的函數名常大寫
//在這裏,咱們沒有顯示的建立對象,沒有return語句,卻將屬性和方法賦值給了this。
function Milk(name,taste,price){
    this.name = name;
    this.taste = taste;
    this.price = price;
}
//new操做符會默認的建立一個新對象,將function的this指向對象,而後將該對象賦值,對象就有了三個屬性。
var milk1 = new Milk('純牛奶','pure',4);
console.log(milk1 instanceof Milk);//true
構造函數的不足
function Milk(name,taste,price){
    this.name = name;
    this.taste = taste;
    this.price = price;
    this.say = function(){
        console.log('Hello World');
    };
}
var milk1 = new Milk('純牛奶','pure',4);
var milk2 = new Milk('純牛奶','pure',4);

假設咱們建立了一個Milk的構造函數,裏面除了屬性還帶有一個say的方法,當咱們new了兩個對象以後,兩個對象milk1和milk2是否都包含了功能相同的say方法呢?
這就是構造函數的不足之處:功能相同的函數,重複聲明消耗空間!看來,咱們的路尚未走到終點。

原型prototype

什麼是原型?

原型是函數的一個屬性,是一個對象。若是函數做爲構造函數使用,那麼這個構造函數的全部實例,都共享這個原型對象。
「注」以前咱們回憶函數時,回憶到,函數有三個常見屬性:name,length和prototype喔!若是遺忘,能夠戳這裏:JS函數的一些基礎知識

原型詳解

1)constructor
原型的constructor是一個對象,咱們能夠這樣簡單的驗證一下:

Object.prototype.constructor === Object //true

2)讀寫

function Milk() {}

Milk.prototype.name = '純牛奶';
Milk.prototype.taste = 'pure';
Milk.prototype.price = 4;
Milk.prototype.say = function(){
    console.log('Hello World');
};
var milk1 = new Milk();

運行的結果以下:
圖片描述

經過這種方式,能夠解決內存問題。但也會所以而共享name,taste,price和say(),尤爲是共享name,taste和price,會產生問題。

3)isPrototypeOf
咱們能夠經過isPrototypeOf來進行原型的斷定,以下:

function Milk() {}

Milk.prototype.name = '純牛奶';
Milk.prototype.taste = 'pure';
Milk.prototype.price = 4;
Milk.prototype.say = function(){
    console.log('Hello World');
};
var milk1 = new Milk();
console.log(Milk.prototype.isPrototypeOf(milk1));//true
原型、構造函數和實例之間的關係

圖

原型是函數的一個屬性,是一個對象。若是函數做爲構造函數使用,那麼這個構造函數的全部實例,都共享這個原型對象。

原型的不足

原型的不足,本質上是共享的缺陷。咱們能夠看以下一段代碼:

var price = 10;
var priceCopy = price;
priceCopy = 20;
console.log(price,priceCopy);//10,20

咱們再看以下一段代碼:

var taste = ['pure','organic'];
var tasteCopy = taste;
tasteCopy.push('low fat');
console.log(taste,tasteCopy);//["pure", "organic", "low fat"],["pure", "organic", "low fat"]

由此咱們可見:

  • 對於基本類型,price和priceCopy是在內存中分別挖兩塊地方存儲,所以,priceCopy的值改變的時候,並不影響price的值;
  • 而對於引用類型,name和nameCopy是調用同一個引用,引用同一個數據,當咱們在nameCopy添加一個名稱的時候,引用的數據就添加了一個值,name也就所以而受到了影響,這就是數據的污染。

共享會污染數據類型,所以原型建立對象也會污染數據類型。咱們看下面一段代碼:

function Milk(){}
Milk.prototype.taste = ['pure','organic'];

var m1 = new Milk();
var m2 = new Milk();

m2.taste.push('low fat');

console.log('m1',m1.taste);//["pure", "organic", "low fat"]
console.log('m2',m2.taste);//["pure", "organic", "low fat"]

經過這段代碼,咱們能夠清楚的瞭解到原型建立對象主要的不足具體表如今:

  • 原型建立對象會產生共享的問題
  • 不能再額外傳遞參數進去。

構造函數結合原型

構造函數有必定的優缺點,原型也有必定的優缺點,若是咱們把二者優勢結合,將會是一種不錯的建立對象的方式。咱們看以下的代碼:

function Milk(name,taste,price){//構造函數獨享屬性
    this.name = name;
    this.taste = taste;
    this.price = price;
}
Milk.prototype.say = function(){//原型共享方法
    console.log(this.name);
}
var m1 = new Milk('純牛奶','pure','4');
m1.say();//純牛奶

在這段代碼中,咱們使用構造函數來獨享屬性,以免原型建立對象會產生的共享問題,固然,咱們也使用原型共享方法,從而達到拒絕功能相同的函數致使的重複聲明消耗空間問題。

構造函數結合原型的一些細節問題

在學習來構造函數結合原型建立對象的基礎之上,咱們來關心一些細節性的問題,以便於咱們深刻了解構造函數結合原型。如,構造函數和原型上的屬性是否會覆蓋,優先順序又是什麼?再如,如何判斷屬性是在原型上仍是在構造函數之上呢?
1)屬性的覆蓋
咱們經過以下兩段代碼,總結關於構造函數結合原型的屬性覆蓋:

//1
function Milk(name,taste,price){//構造函數獨享屬性
    this.name = name;
    this.taste = taste;
    this.price = price;
}
Milk.prototype.name = '牛奶';
Milk.prototype.say = function(){//原型共享方法
    console.log(this.name);
}

var m1 = new Milk('純牛奶','pure','4');
console.log(m1.name);//純牛奶
//2
function Milk(name,taste,price){//構造函數獨享屬性
    //this.name = name;
    this.taste = taste;
    this.price = price;
}
Milk.prototype.name = '牛奶';
Milk.prototype.say = function(){//原型共享方法
    console.log(this.name);
}

var m1 = new Milk('純牛奶','pure','4');
console.log(m1.name);//牛奶

從兩端代碼中,咱們對比得知,實例上的屬性會覆蓋原型上的屬性。即:會先在實例中查找,如沒有,則再在原型上查找。
2)屬性的判斷
(1)in操做符
咱們能夠經過以下三段代碼,總結關於in操做符的知識,即:只要對象裏有值,即不管是在構造函數之上仍是在原型之上均返回true,若都不在,則返回false。

//1
function Milk(name,taste,price){//構造函數獨享屬性
    this.name = name;
    this.taste = taste;
    this.price = price;
}
Milk.prototype.say = function(){//原型共享方法
    console.log(this.name);
}

var m1 = new Milk('純牛奶','pure','4');
console.log('name' in m1);//ture
//2
function Milk(name,taste,price){//構造函數獨享屬性
    //this.name = name;
    this.taste = taste;
    this.price = price;
}
Milk.prototype.name = '牛奶';
Milk.prototype.say = function(){//原型共享方法
    console.log(this.name);
}

var m1 = new Milk('純牛奶','pure','4');
console.log('name' in m1);//ture
//3
function Milk(name,taste,price){//構造函數獨享屬性
    //this.name = name;
    this.taste = taste;
    this.price = price;
}
//Milk.prototype.name = '牛奶';
Milk.prototype.say = function(){//原型共享方法
    console.log(this.name);
}

var m1 = new Milk('純牛奶','pure','4');
console.log('name' in m1);//false

(2)hasOwnProperty
咱們能夠經過以下兩段代碼,總結關於in操做符的知識,即:判斷是在實例上仍是原型上,掛在實例上返回true,反之false。

//1
function Milk(name,taste,price){//構造函數獨享屬性
    this.name = name;
    this.taste = taste;
    this.price = price;
}
//Milk.prototype.name = '牛奶';
Milk.prototype.say = function(){//原型共享方法
    console.log(this.name);
}

var m1 = new Milk('純牛奶','pure','4');
console.log(m1.hasOwnProperty('name'));//true
//2
function Milk(name,taste,price){//構造函數獨享屬性
    //this.name = name;
    this.taste = taste;
    this.price = price;
}
Milk.prototype.name = '牛奶';
Milk.prototype.say = function(){//原型共享方法
    console.log(this.name);
}

var m1 = new Milk('純牛奶','pure','4');
console.log(m1.hasOwnProperty('name'));//false

小結

建立對象這個板塊中,咱們從工廠模式開始講起,再到構造函數,接着到原型,最後到比較完善的構造函數結合原型,在接下來的繼承板塊中,咱們將講述原型鏈、繼承以及最佳方式的相關知識,好好複習!

繼承(更新中)

固然,最最最最最後,若是您喜歡這片文章,能夠瘋狂點贊或者收藏喔!!?

相關文章
相關標籤/搜索