Javascript面向對象的程序設計_理解對象

JS面向對象的程序設計_理解對象

前言:最近在細讀Javascript高級程序設計,對於我而言,中文版,書中不少地方翻譯的差強人意,因此用本身所理解的,嘗試解讀下。

若有紕漏或錯誤,會很是感謝您的指出。文中絕大部份內容引用自《JavaScript高級程序設計第三版》。函數

面向對象(Object-Oriented, OO)的計算機語言有一個標誌,就是它們都有一個類class的概念,經過類能夠建立任意多個具備相同屬性和方法的對象。ECMAScript中沒有類的概念,於是它的對象也與基於類的計算機語言中的對象不一樣。this

ECMA-262把對象定義爲: 「無序屬性的集合,其屬性能夠包含基本值、對象或者函數。」 也就是說對象是一組沒有特定順序的值。對象的每一個屬性或方法都有一個名字,而每一個名字都映射(map)到一個值。因此咱們能夠把ECMAScript中的對象想象成散列表,換句話說,就是名值對,其中值能夠是數據或者函數。每一個對象都是基於引用類型建立的,這個值能夠是原生類型,也能夠是開發人員定義的類型。翻譯

理解對象

若是咱們要建立自定義對象,最簡單的方式就是建立一個Object類型的實例對象,而後再爲它添加屬性和方法。設計

//使用new關鍵字從Object類型中實例化出一個對象。

var person = new Object();
person.name = "Shaw";
person.age = "Secret";
person.job = "Front-end Engineer";
person.sayName = function(){
    console.log(this.name);
    // 這裏的this指向person,call調用它的就是person
}

上述代碼,建立了一個person對象,併爲它添加了三個屬性(name, age, job)和一個方法 sayName()。其中,sayName() 方法用於顯示this.name(被解析爲person.name)的值。 code

如今,對象字面量成爲建立這種對象的首選模式。對象

// 以前的例子,能夠寫成這樣

var person = {
    name: "Shaw",
    age: "Secret",
    job: "Front-end Engineer",
    sayName: function(){
        console.log(this.name)
    }
}

這些屬性在建立時都帶有一些特性值(attribute ), Javascript經過特性來定義屬性(property)的行爲。描述特性的字符叫作descriptor。ip

屬性特性 PropertyAttribute

特性(attribute)描述了屬性(property)的各類特徵(也能夠理解爲特性值),ECMA-262第5版定義這些特性是爲了實現JavaScript引擎用的,所以在JavaScript中不能直接訪問到它們。爲了表示特性是內部值, 該規範把它們放在兩對方括號中,[[Enumerable]]。開發

ECMAScript定義對象的屬性分爲兩種:數據屬性和訪問器屬性。get

  1. 對象屬性的數據特性

數據特性包含一個數據值的位置。在這個位置能夠讀取和寫入值(I/O)。博客

數據特性的描述符(property attribute descriptor)

  • [[Configurable]], 可配置的: 表示可否經過delete刪除屬性從而從新定義屬性,可否修改屬性的特性或可否把屬性修改成訪問器屬性。像上面例子那樣直接在對象上定義的屬性,這些屬性的這個特性默認值爲true(能夠刪改定義屬性)。
  • [[Enumerable]], 枚舉: 表示可否經過for-in循環返回屬性。像上面例子那樣直接在對象上定義的屬性,對象的屬性的這個Enumerable特性值默認爲true。
  • [[Writable]], 改寫: 表示可否修改屬性的值。像上面例子那樣直接在對象上定義的屬性,對象的屬性的這個writable特性值默認爲true。
  • [[value]], 輸入讀取屬性值:讀取屬性值的時候,從這個位置開始讀; 寫入屬性的時候,把新值保存在這個位置。這個特性的默認值爲undefined。

對於上面的那個例子中直接在對象上定義的屬性, 屬性們的特性值[[Configurable]]、[[Enumberable]]、[[Writable]]的默認值都爲true,而[[value]]特性被設置爲指定的值。

//例子
var person = {
    name: "Shaw"
    //僞代碼
    //name: { value: "Shaw"}
};

//這裏建立一個名爲name的屬性,爲它指定的值是"Shaw"
//也就是說, [[value]]特性被設置爲「Shaw」,而對這個值的任何修改都將反應在這個位置。

要修改屬性默認的特性,必須使用ECMAScript5的Object.defineProperty()方法。 這個方法接收三個參數: 屬性所在的對象、屬性的名字和一個描述符對象。 其中, 描述符(descriptor)對象的屬性必須是: configurable、enumerable、writable和value。 設置其中的一或多個值,能夠修改對應的特性值。請看下面的例子:

var person = {};

Object.defineProperty(person, "name", {
    writable: false,
    value: "Shaw"
});

console.log(person.name); //"Shaw"
person.name = "roc";
console.log(person.name); // 仍是"Shaw", 由於經過Object.defineProperty()方法,設置該屬性的特性writable的值爲false,即不可改寫。

這個例子建立一個名爲name的屬性,它的值「Shaw"是不可改寫的,即只能讀取。若是嘗試爲它指定新值,賦值操做會被忽略;在嚴格模式下,賦值操做會拋出錯誤。

相似的規則也適用於屬性的可配置特性。例如:

var person = {};
Object.defineProperty(person, "name", {
    configurable: false,
    value: "Shaw"
});
delete person.name; // false, 由於configurable爲false; 
console.log(person.name);

把屬性的特性configurable設置爲false,意味着該屬性是不可配置的,也就是不能從對象中刪除這個屬性。若是對這個屬性調用delete, 在非嚴格模式下什麼都不會發生, 在嚴格模式下, 會拋出一個錯誤。 ==注意==, 一旦把屬性的特性定義爲不可配置的,就不能再把它變回可配置了。此時,再調用Object.defineProperty()方法,會拋出錯誤。

var person = {};
Object.defineProperty(person, "name", {
    configurable: false,
    value: "Shaw"
    //不指定的話, 其他的特性值爲默認值,也就是說特性writable的默認值爲 false,
    //特性 enumerable的默認值爲 false
})

//Uncaught TypeError: Cannot redefine property: name
Object.defineProperty(person, "name", {
    configurable: true,
    value: "Shaw"
})

換句說,能夠屢次調用Object.defineProperty()方法來修改該屬性, 可是設置屬性的特性configurable的值爲false(不可配置)後,其結果是不可逆的, 就會有限制了。

在調用Object.defineProperty()方法來建立一個新的屬性時候,若是不指定,configurable、writable、enumerable特性的默認值都爲false。

若是調用Object.defineProperty()方法只是修改已定義的屬性的特性, 則無此限制。 由於已經定義的屬性的特性configurable、enumerable、writable默認都爲true。好比以前的那個例子:

var person = {
    name: "Shaw"
    // 僞代碼 
    /*propertyAttribute = {
        configurable(descriptor): true,
        enumerable(descriptor): true,
        writable(descriptor): true,
        value(descriptor): "Shaw"*/
    }
}

多數狀況下, 咱們可能都用不到Object.defineProperty()提供的這些高級功能。

不過,理解這些概念對理解JavaScript對象有很大的幫助。

  1. 對象屬性的訪問器特性

顧名思義就是定義了對象內部,該屬性的特性是有權限訪問或設置對象內部其餘屬性。
訪問器屬性不包含數據值;它們包含一對兒getter和setter函數。

在讀取訪問器屬性時,會調用getter函數,這個函數負責返回有效的值; 在寫入訪問器屬性時,會調用setter函數並傳入新值,這個函數負責如何處理數據。 訪問器屬性有以下4個特性:

  • [[configurable]],是否可配置。 表示可否經過delete刪除屬性從而從新定義屬性,可否修改屬性的特性。或者可否把屬性修改成數據屬性。對於直接在對象上定義的屬性, 這個屬性的該特性的默認值爲true。
  • [[Enumerable]], 是否可枚舉。 表示可否經過for-in循環返回屬性。 對於直接在對象上定義的屬性,這個屬性的該特性的默認值爲true。
  • [[Get]], 讀取屬性函數。 讀取屬性時調用的函數。默認值爲undefined。
  • [[Set]], 設置屬性函數。 設置屬性時調用的函數。默認值爲undefined。

訪問器屬性不能直接定義, 必須使用Object.defineProperty()來定義。

var cat = {
    _age: 1, 
    adoptionYear: 2016
};

Object.defineProperty(cat, 'age', {
    get: function() {
        return this._age;
    },
    set: function(newAge) {
        if(newAge>1) {
            this._age = newAge; //5
            this.adoptionYear +=  newAge - 1;
        }
    }
});

window.alert(cat.age); //1
cat.age = 5;
window.alert(cat.age); //5
window.alert(cat.adoptionYear); // 2020

以上代碼建立了一個cat對象,並給它定義兩個默認的屬性: _age和adoptionYear。 _age 是一種約定俗成的記號,表示只能經過對象屬性的訪問器特性方法才能訪問到的屬性(能夠想象成對象的成員屬性)。 而屬性year的訪問器特性則包含一個get函數和set函數。get函數返回_age的值, set函數經過計算來肯定貓咪的收養年份。所以,把age的屬性值修改成5,而adoptionYear變爲2020,即已經收養了四年。這是使用訪問器屬性的常見方法,即設置一個屬性的值而後改變另一個屬性的值。

在這個定義對象屬性的訪問器特性以前,Object.defineProperty(object, 'property', { get: function() {} , set: function(){}}) 。 要建立對象屬性的訪問器特性,通常都使用兩個非標準方法: __defineGetter__() 和 __defineSetter__(), ==注意這裏是雙下劃線==。 這兩個方法最初是由Firefox引入的,後來Safari 3,Chrome 1和Opera9.5也給出相同的實現。 使用這兩個遺留的方法,也能夠實現對象屬性的訪問器特性設置。

// 使用例子以下
var cat = {
    _age: 1,
    adoptionYear: 2016
}

cat.__defineGetter__('age', function(){
    return this._age;
});

cat.__defineSetter__('age', function(newAge){
    if(newAge > 1) {
        this._age = newAge;
        this.adoptionYear += this._age - 1;
    }
})

alert(cat.age); //1
alert(cat.adoptionYear); //2016
cat.age = 5;
alert(cat.age); //5
alert(cat.adoptionYear); //2020

同時定義對象多個屬性的特性的方法

爲對象同時定義多個屬性特性的可能性很大,ECMAScript5又定義了一個Object.defineProperties(object, ObjectDescriptor)方法,用這個方法能夠經過一次性定義對象的多個屬性特性。 該方法接收兩個對象參數: 第一個對象參數是要添加和修改屬性特性值的對象,第二個對象參數的屬性與第一個對象中要添加或修改的屬性一一對應。

// 仍是代碼比較直觀

var cat = {};
Object.defineProperties(cat, {
    _name: {
        writable: true,
        value: 'Meow'
    },
    _age: {
        writable: true,
        value: 1
    },
    adoptionYear: {
        writable: true,
        value: 1
    },
    age: {
        get: function() {
            return this._age;
        },
        set: function(newAge) {
            if(newAge <= 1) {
                this._age = 1;
            };
            this._age = newAge;
            this.adoptionYear += newAge -1;
        }
    }
})

alert(cat.age); // 1
cat.age = 3;
alert(cat.age); // 3
alert(cat.adoptionYear); // 3 從小就收養了。

讀取屬性的特性

ECMAScript5定義了Object.getOwnPropertyDescriptor(object, 'property')方法, 該方法取得給定屬性的特性描述符。

這個方法接收兩個參數:

  1. 第一個參數爲: 屬性所在的對象。
  2. 第二個參數爲: 須要讀取其特性值(PropertyAttribute) 的對象屬性。

該方法的返回值是一個對象,因此能夠經過.語法讀取到值,若是是屬性的訪問器特性,返回的這個對象的屬性有:configurable、enumerable、get, set; 若是是數據屬性, 這個對象的屬性有configurable、enumerable、writable和value。

var person = {};
Object.defineProperties(person, {
    _name: {
        writable: true,
        value: '請使用person.name = ?,給我定義一個名字呀!'
    },
    _age: {
        writable: true,
        value: '請使用person.age = ?,給我定義一個年齡呀!'
    },
    _gender: {
        writable: true,
        value: '請使用person.gender = ?,給我定義一個性別呀!'
    },
    name: {
        get: function(){
            return this._name;
        },
        set: function(newName) {
            this._name = newName;
        }
    },
    age: {
        get: function(){
            return this._age;
        },
        set: function(newAge) {
            this._age = newAge;
        }
    },
    gender: {
        get: function(){
            return this._gender;
        },
        set: function(newGender) {
            this._gender = newGender;
        }
    }
});

var objPropAttrDescriptor = Object.getOwnPropertyDescriptor(person, '_age');
alert(objPropAttrDescriptor.value); //請使用person.gender = ?,給我定義一個性別呀!
alert(objPropAttrDescriptor.configurable); //false;
alert(typeof objPropAttrDescriptor.get); // undefined;

var objPropAttrDescriptor = Object.getOwnPropertyDescriptor(person, 'age');
alert(objPropAttrDescriptor.value);// undefined;
alert(objPropAttrDescriptor.configurable); //false;
alert(typeof objPropAttrDescriptor.get); // function

在JavaScript中,能夠針對任何對象 - 包括DOM和BOM對象,使用Object.getOwnPropertyDescript(object, 'objProperty')方法,。

總結

若是英語水平足夠好的話,建議看英文原版書籍或者國外大師的博客。畢竟翻譯過來的文字,不少東西都變了味, 並且看英文,會讓你注意力更加集中,不容易跑神。

接下來我會按期翻譯一些國外的精品博客。 立個Flag,促使本身進步。

相關文章
相關標籤/搜索