面向對象,搞定對象

前言:此對象,非彼對象。今天想跟你們分享一下一個大的概念,面向對象。原本想繼續分享ES6的class,搜索文章,發現我ES5的原型方面的知識並不牢靠。因此,爲了能讓本身更加深入的瞭解ES6中的class,仍是回過頭來,先認認真真的學習ES5的面向對象的知識。在這裏,仍是想表達一下對 --JavaScript高級程序--由衷的感謝,每次閱讀,都能讓我體會到不一樣的感覺。這本書真的是前端開發工做人員的聖經。但願個人解讀能讓你對面向對象設計的更加深入的瞭解。javascript

什麼是對象

前端的小夥伴對對象的認識確定至關的深入的,由於咱們天天的開發就時時刻刻在建立、應用這對象。能夠說,每個需求、功能的實現,都離不開對象。(還沒對象的抓緊搞對象!)前端

var person = new Object;
person.name = 'hanson';
person.age = 18 ;
person.sayName = function (){
    return this.name
}
複製代碼

上面的代碼是最基本的建立對象的方法,平時開發中,基本上不會這麼寫。java

var person = {
   name: 'hanson',
   age: 18,
   sayName:function(){
       return this.name;
   }
}
複製代碼

上面的建立對象的方式,是咱們開發中常常用到的。瀏覽器

屬性類型

ECMA-262定義了一些爲實現JavaScript引擎用的屬性,不能直接訪問,爲了表示特殊,把他們放在兩對兒方括號中。例如[[[Enumerable]]。函數

數據屬性

通常數據屬性的值都爲基礎數據類型。學習

  • [[Configurable]] 表示可否經過delete刪除屬性從而重新定義...(一旦修改,不能反悔)
  • [[Enumerable]] 表示可否經過for-in循環返回屬性...(默認爲true)
  • [[Writable]] 表示可否修改屬性值...(false:不能改,true:能改)
  • [[Value]] 包含屬性的數據值...(就是上例中的‘hanson’)

若是想修改默認屬性呢?

ECMAScript給我提供了一個方法,Object.defineProperty()方法。這個方法接受三個參數:屬性所在對象、屬性的名字和一個描述對象。舉個例子就明白了。ui

var person = {};
Object.defineProperty(person,'name',{
    Writable:false,
    value:'hanson'
})
console.log(person.name) //'hanson'
person.name = 'hansheng';
console.log(person.name) // 'hanson'
複製代碼

通俗易懂,不容許修改,那你就改不了。完成仍是建議你們看書,這裏就不過多去解讀了,畢竟還要找對象。。。this

訪問器屬性

除了數據屬性前兩了屬性,多了[[get]]和[[set]]兩個屬性。
不用看,就單從字面上,想象一下,也能明白,這是設置和獲取值。spa

  • [[get]]: 在讀取屬性時,調用的函數,默認undefined。
  • [[set]]: 在寫入屬性時,調用的函數,默認undefeated。

舉個例子都懂了!prototype

var book = {
    _year:2018,
    edition:1
};
Object.defineProperty(book,"year",{
    get : function(){
        return this._year;
    }
    set : function(newValue){
        if(newValue > 2018){
            this._year = newValue;
            this.edition += newValue - 2018
        }
    }
})
book.year = 2019;
console.log(book.edition) //2
複製代碼

那若是想獲取一下這些描述屬性呢?ECMAScript也給咱們提供了一個方法:Object.getOwnPropertyDescriptor();

var hanson = {};
Object.defineProperties(book,{ //給對象定義多個屬性,用到Object.defineProperties();
    _year:{
        value:18
    },
    name:{
        value:'hanson'
    }
})
var descriptor = Object.getOwnPropertyDescriptor(book,"_year");
console.log(descriptor.value) // 18
複製代碼

建立對象

雖然上面兩種建立對象的方式,均可以用來建立單個對象,可是有個明顯的缺點,使用同一接口建立不少對象,會產生大量的重複代碼。 (建立個person、建立個book。。。建立無數對象)

工廠模式

工廠模式抽象了建立具體對象的過程。來看例子。

function creatObj(name,age){
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.sayName = function(){
        return this.name;
    }
    return obj;
}
var person1 = creatObj('hanson',18);
var person2 = creatObj('hansheng','20');
複製代碼

高級程序一書說,這種模式雖然解決了建立多個類似對象的問題,可是沒有解決對象識別的問題。爲何沒解決對象識別問題?很簡單。

console.log(person1.constructor) //Object
console.log(person2.constructor) //Obejct
console.log(person1.constructor === person2.constructor) //false
複製代碼

執行相同的邏輯,實例的構造函數應相同。都能獲得Object是全部的實力的構造函數都是Object。爲了解決上面的問題,出現了構造函數模式。 說到底,仍是由於,每次調用都建立了一個新的對象,person1與person2是相對獨立的兩個個體。

構造函數模式

爲了解決上述問題,出現了構造函數。

function Person (name,age){
 // let this = new Object()
    this.name = name;
    this.age = age;
    this.sayName = function(){
        return this.name;
    }
    //return this
}
var person1 = new Person('hanson',18);
var person2 = new Person('hansheng',20);
複製代碼

對比:

  • 沒有顯式建立對象;
  • 直接將屬性和方法賦給了this;
  • 沒有return語句;

要建立Person實例,要使用New操做符。調用構造函數的過程以下:

  • 使用new這個關鍵詞來建立對象
  • 在構造函數內部把新建立出來的對象賦予給this
  • 在構造函數內部把新建立(未來new的對象)的屬性方法綁到this上
  • 默認是返回新建立的對象(很重要)
  • 特別須要注意的是,若是顯式return一個對象數據類型,那麼未來new的對象,就是顯式return的對象

咱們來看一下是否解決了工廠模式的問題。

console.log(person1.constructor) // Person
console.log(person2.constructor) //Person
console.log(person1.constructor === person2.constructor) //true
複製代碼

完美解決工廠模式的問題。

問你一下

第一個:
function Person1(name,age){
    this.name = name
    this.age = age
    
    return "1"
}
let p = new Person("hanson",18);
第二個:
function Person2(name,age){
    this.name = name
    this.age = age
    
    return [1,2,3]
}

let p = new Person("hanson",18)
複製代碼

連個函數分別返回什麼?
答案是:
person1返回:{name:'hanson',age:18}
person2返回:[1,2,3]
解釋:
如今你能知道爲何嗎?還記得使用構造函數建立person實例麼,默認狀況,構造函數無return語句,默認返回一個對象(this),若是強行return,返回基礎數據類型,那麼執行後會自動忽略,若是返回對象,則直接返回,替換實例對象。
繼續。。。 難道構造函數就沒有缺點麼?固然不是,接着往下看。
仍是剛纔的例子。

function Person (name,age){
 // let this = new Object()
    this.name = name;
    this.age = age;
    this.sayName = function(){
        return this.name;
    }
    //return this
}
var person1 = new Person('hanson',18);
var person2 = new Person('hansheng',20);
複製代碼

咱們改造一下,可能看得更明白。

function Person (name,age){
// let this = new Object()
   this.name = name;
   this.age = age;
   this.sayName = new Function(){return this.name}
}
var person1 = new Person('hanson',18);
var person2 = new Person('hansheng',20);
console.log(person1.sayName === person2sayName) //false
複製代碼

明顯,在調用sayName的時候,建立了新的函數,也就是對象,那麼person1.sayName與person2.sayName確定不一樣。這樣,每次調用,咱們都會新建立一個sayName函數。
有的同窗問題,咱們能夠把這個函數單獨拿出來啊,用的時候再去調用就行了啊!

function Person (name,age){
 // let this = new Object()
    this.name = name;
    this.age = age;
    this.sayName = sayName;
}
function sayName(name){
    return this.name = name;
}
複製代碼

這樣的方法能夠暫時解決如今的需求,可是,若是在世紀開發中,咱們須要調用更多的方法,難道咱們都要寫到全局中嗎?很明顯,這種方法在世紀的開發中,並非最優解。怎麼辦呢?讓我更近一步,瞭解一下原型模式。

原型模式

如何解決調用構造函數內的方法時,每次都建立新函數或者要在全局中建立多個函數的問題。咱們引入原型模式,簡單明瞭仍是來看代碼。

function Person (){};
Person.prototype.name = 'hanson';
Person.prototype.age = 18;
Person.prototype.sayName = function(){
    return this.name;
}
let person1 = new Person();
let person2 = new Person();
console.log(person1.sayName === person2.sayName) //true
複製代碼

嘿嘿,解決上述問題。雖然解決了,可是咱們得知道緣由。

定義

咱們建立的每個函數都有一個prototype(原型)屬性,這個屬性是一個指針,指向一個對象,二這個對象的用途是包含能夠由特定類型的全部實例共享的屬性和方法。這個是官方給的解釋。我給你翻譯一下: 全部對象實例,能夠共享原型對象上面的屬性和方法。
上面的例子,就證實了這段結論。person1和person2的sayName方法指向相同。爲了加深理解,咱們來看個圖。

很清晰,雖然我也挺喜歡高級程序設計的圖,可是總以爲,這個更圓潤有沒有。 這個圖中,有一點沒有體現,就是,其實實例P也有一個屬性,[[prototype]],(不少瀏覽器的_proto_)在全部實現中,都沒法訪問到。可是,咱們能夠經過isPrototype()方法來肯定對象之間是否存在這種關係。

console.log(Person.prototype.isPrototype(person1) //true
複製代碼

若是咱們要給原型對象添加大量屬性方法時,咱們不斷的Person.prototype.xxx = xxx、Person.prototype.xxxx = xxxx,這樣也是很繁瑣,那麼咱們該怎麼解決這個問題?

function Person(name){
    this.name = name
}
// 讓Person.prototype指針指向一個新的對象
Person.prototype = {
    eat:function(){
        console.log('吃飯')
    },
    sleep:function(){
        console.log('睡覺')
    }
}
複製代碼

須要注意的是,這樣的改變,雖然代碼在可讀性,便利性方面有了很大的提升,可是,Person.prototype.constructor屬性再也不指向Person了。由於,每建立一個函數,就會同時建立他的prototype屬性。這個Person.prototype也會建立屬於它的constructor屬性。咱們這裏使用的語法,本質上徹底重寫了prototype對象。解決的辦法很簡單,在Person.prototype對象上,加上contructor:Person就能夠了。

Person.prototype = {
    constructor:Person,
    eat:function(){
        console.log('吃飯')
    },
    sleep:function(){
        console.log('睡覺')
    }
}
複製代碼

和原型對象有關幾個經常使用方法

// 1.hasOwnProperty 在對象自身查找屬性而不到原型上查找
function Person(){
    this.name = 'hanson'
}

let p = new Person()

let key = 'name'
if((key in p) && p.hasOwnProperty(key)){
    // name僅在p對象中
}

// 2.isPrototypeOf 判斷一個對象是不是某個實例的原型對象
function Person(){
    this.name = 'hanson'
}

let p = new Person()

let obj = Person.prototype 
obj.isPrototypeOf(p) // true

複製代碼

組合使用構造函數模式和原型模式

建立自定義類型的最多見方式,就是組合使用構造函數模式與原型模式。構造函數模式用於定義實例屬性,而原型模式用於定義方法和共享屬性。

function Person (name,age){
 // let this = new Object()
    this.name = name;
    this.age = age;
    this.firends = ['xiaoqiang','xiaoyu','xian'];
}
Person.prototype = {
   constructor:Person,
   sayName:()=>this.name
}
let person1 = new Person('hanson',18);
let person2 = new Person('hansheng',19);
person1.friends.push('teacher sha');
console.log(person1.friends) // ['xiaoqiang','xiaoyu','xian','teacher sha']
console.log(person2.friends) // ['xiaoqiang','xiaoyu','xian']
console.log(person1.friends === person2.friends) //false;
console.log(person1.sayName == person2.sayName) //true
複製代碼

在這個例子中,實例屬性都是在構造函數中定義的,而由全部實例共享的屬性constructor和方法sayName則是在原型中綁定的。目前在ESMAScript中,使用最廣發、認同度最高的一種建立自定義類型的方法。高級程序設計還介紹了寄生構造函數模式和 穩妥構造函數模式,感興趣的小夥伴可自行前往查看。


「對象」都有了,一段時間後,是否是得考慮家產「繼承」的問題呢?彆着急,下一次,咱們就來講說這個「家產」繼承!千萬不要錯過哦。若是以爲小編的內容能讓你有所啓發,至少能讓你加深一下記憶,記得給點贊、關注哦!有問題的小夥伴能夠在下面留言,看到後,必定給回覆~咱們下期再見嘍~

相關文章
相關標籤/搜索