JavaScript: 認識 Object、原型、原型鏈與繼承。

目錄編程

  • 引用類型與對象
  • 類與對象
  • 成員組成
  • 成員訪問
  • 實例方法 / 屬性

引用類型與對象

JavaScript 存在着兩種數據類型:「基本數據類型」 與 「引用數據類型」。
「引用數據類型」 是一種數據結構,它將數據(屬性)與功能(方法)組織在一塊兒,引用類型的值就是「引用類型」的實例。數組

var obj = new Object();

其中 Object 就是一種「引用類型」,而 obj 則是對應 Object 類型的值(實例),實例繼承着對應引用類型的屬性和方法。ECMAScript 還提供了不少其它的引用類型,例如:Array、Date、Math、Number、String、RegExp、Boolean、Function 等。數據結構

在 JavaScript 中一切皆對象,這是由於其它的對象類型都繼承自 Object,固然也包括着上述的全部「引用類型」,所以 Object 在 JavaScript 中是一切對象的基礎對象。更直白的說,Window,Location、JSON、XMLHttpRequest、Array,Date 等全部其它對象在原型鏈上都繼承着 Object,均可以看做是對 Object 的擴展對象。編程語言

Location.__proto__.__proto__
Array.__proto__.__proto__
JSON.__proto__

/*
{
    constructor : ƒ Object()
    hasOwnProperty:ƒ hasOwnProperty()
    isPrototypeOf:ƒ isPrototypeOf()
    propertyIsEnumerable:ƒ propertyIsEnumerable()
    toLocaleString:ƒ toLocaleString()
    toString:ƒ toString()
    valueOf:ƒ valueOf()
    get __proto__:ƒ __proto__()
    set __proto__:ƒ __proto__()  
}
*/

Object 在 JavaScript 中有局部與全局兩種含義,全局層面 JavaScript 中一切皆對象,都是繼承至 OBJECT 都是 OBJECT 的擴展,不管是 Object、Location、Array、JSON ... 本質上都是 OBJECT。而做爲局部的含義 Object 就是單純指 Object 這個對象,既經過 Object() 這個構造方法實例獲得的鍵值對的無序組合體。函數式編程

對於 「引用數據類型」 和「對象」關係,「引用類型」 必然是對象,可是對象不必定就是「引用數據類型」,所以 Location、XMLHttpRequest 等雖然是對象,但並非一種「引用數據類型」。函數

雖然在 ECMAScript 中 Object 的實例並不具有多少功能,但對於應用程序中數據的存儲與傳輸而言,則是很是理想的選擇。this

類與對象

JavaScript 中的「對象」與 Java 這樣面嚮對象語言中的「類」,仍是有很大的區別,簡要能夠歸納出三點:編碼

建立prototype

Java中的對象必須由「類」來實例化,而 JavaScript 中對象能夠由字面量格式很是簡單的定義與建立,在 JavaScript 中對象就是很單純的數據與功能的集合。code

調用

Java 中類是不能夠直接調用的,只是用來實例化對象,就算是調用方法也只能調用類上的靜態方法,而在 JavaScript 中 充當類的構造函數是能夠做爲函數來調用的,例如:

Object(); //{}
String(); //""
Number(); //0

另外構造器方法上的靜態方法也能夠直接使用,例如:String.fromCharCode(65)。所以能夠看出 JS 對函數式編程支持更好!

繼承
在面向對象編程語言中 「類」 的重要概念就是繼承,由「類」實例化的對象便會繼承該類的方法與屬性,這種繼承關係更相似於派生以及父子關係,而在 JS 中繼承被更多的看做成具備相互關聯的關係,而這個關係的聯接就是咱們常說的「原型鏈(proto)」。

var Behavior = {
    eat: () => {
        alert('eat')
    }
};

var Tom = { name: 'my name is Tom', __proto__: Behavior };
var Bob = { name: 'my name is Bob', __proto__: Behavior };

Tom.name; // my name is Tom;
Tom.eat();
Bob.eat();

固然 JS 也支持面向對象開發語言的 new 運算符,例如常見的:

var obj = new Object();

但實際上這個 new 運算符更多的充當着語法糖角色,大體上來講,new 運算符主要有如下功能:

  • 新建並返回一個構造器函數的實例對象,Constructor {} ,而後將構造函數中的 this 對象複製到新建的實例對象 this 上。
  • 把構造函數的 prototype 對象複製到新建對象的 __proto__ 屬性上,以便繼承該構造器 prototype 對象上的屬性與方法,例如:
var obj = new Object();
    obj.__proto__ === Object.prototype; // true;

實例化獲得的 obj 對象其 __proto__ 屬性所指向關聯(繼承)的就是 Object() 構造器的 prototype 屬性(原型屬性)。
再好比一個完整的例子:

function Student(name) {
    this.name = name;
}

Student.prototype.say = function() { //將實例對象的 __proto__ 與自身的 prototype 進行了關聯。
    alert(this.name); //複製了this到實例對象上
}

var Tom = new Student('Tom');

Tom.say(); //Tome

此時 new 運算符作了兩件事,第一把 Student 中的 this 複製到新的實例對象 Tom 上因此 Tom 上也存在 name 屬性,而且值就是實例化過程當中傳入的 Tom。第二則是把 Studentprorotype 對象複製到 Tom 這個實例化對象的 __proto__ 屬性上。

console.log(Student.prototype);
console.log(Tom.__proto__);

/*
{
    say: ƒ()
    constructor: ƒ Student(name)
    __proto__: Object
}
*/

從上例能夠看出 Tom.__proto__Student.prototype 指向的都是同一個原型對象,因此就很是好理解經過 new 運算符是如何實現繼承關係的了。
下面是該原型對象上的具體屬性與方法:

  • say : 原型對象上的方法,能夠被其實例對象繼承。
  • constructor : 用於說明當前的原型對象屬於那個構造函數,同時也是爲了實現一個閉環的循環訪問(構造函數經過 prototype 訪問原型對象,原型對象再經過 constructor 屬性訪問所依附的構造函數 )。
  • proto : 該原型對象(prototype)上的原型鏈屬性(用於說明該原型對象自身的繼承關係)。

通常來講實例對象的 __proto__ 屬性保存的是其構造函數的 prototype 原型對象,而構造函數的原型對象自己也經過 __proto__ 來講明其自身的繼承關係,一樣的,原型對象的繼承對象也含有 __proto__ 屬性,依次類推咱們即可以得出一個 __proto__ 屬性串聯出的 「繼承鏈—>原型鏈」。

以一個實例的數組對象爲例:

Array()
    length: 0
    __proto__: Array(0){
        
        map:ƒ map()
        filter:ƒ filter()
        push:ƒ push()
        indexOf:ƒ indexOf()
        sort:ƒ sort()
        ...
        constructor:ƒ Array()
            __proto__:Object{
                constructor:ƒ Object()
                hasOwnProperty:ƒ hasOwnProperty()
                isPrototypeOf:ƒ isPrototypeOf()
                ...
                __proto__ : null
            }
    }

從中咱們能夠看出 JavaScript Object 對象是一切其它對象的最底層最基礎的對象,其它對象都是繼承自 Object,而 Object 繼承的則是 null。
從示例中 Array() 方法產生的匿名實例數組,其 __proto__ 屬性指向的是 Array 構造函數的 prototype 對象,而 Array 構造函數的 prototype 對象又繼承自 Object() 構造函數的 prototype 原型對象。因此這種鏈式的繼承關係能夠以下圖所示:

目前咱們能夠簡單的總結下:在 JS 中構造函數相似與面向對象中「類」的關鍵點就在於方法(函數)具備 prototype 原型對象,它能夠做爲一個方法與屬性的公共載體,用於該構造函數的實例對象經過 __proto__ 屬性進行繼承,而且其自己也經過一個 constructor 屬性再循環訪問到自身所依附的構造函數。因爲普通對象不存在 prototype 對象,因此也就沒法充當「類」的角色,可是對象自身 __proto__ 屬性天生就是用來主動繼承的,因此經過手動修改對象的 __proto__ 屬性也能夠很靈活的調整對象與對象之間的繼承關係。

成員組成

前面提到過 Object 類型的實例是數據與方法的集合,由無序的鍵值對(key/value)構成,所以對其中每條 key/value 就能夠看作成是這個對象實例的成員,而 key 則是對象的屬性名或方法名,value 則是對象的屬性值或具體的功能方法。

在 ECMAScript 中 key 名能夠由任何屬於 Unicode 編碼的字符組成,但經常使用的仍是數字與字母。

成員訪問

在 ECMAScript 中成員的訪問主要有兩種方式:

  • "點" 表示法
  • "[]" 方括號表示法

它們的區別是 「點」 表示法更通用,更直觀,更快捷。但缺點則是沒法訪問含有特殊字符、關鍵字、保留字的成員名。

Person.first name // Syntax Error

此時,括號表示法的強大就體現了出來。

Person ['first name'];
Person ['first' + 'name'];

實例方法 / 屬性

constructor

保存着建立當前實例對象的構造函數。

new Object().constructor; // "ƒ Object() { [native code] }"

這說明當前對象的構造函數就是 Object()。

hasOwnProperty()
接收一個參數 key ,判斷這個成員是否爲實例對象私有的方法與屬性,而非繼承着原型。

function Test() {}
Test.prototype.custom = 1;

var test = new Test();

test.custom_private = 1;

console.log(test.hasOwnProperty('custom')); // false;
console.log(test.hasOwnProperty('custom_private')); // true;

isPrototypeOf()
接收一個對象做爲參數,判斷這個對象是否繼承於指定的原型對象。

Test.prototype.isPrototypeOf(test) // true

propertyIsEnumerable()
接收一個參數 key,判斷這個成員在指定的對象上是否可枚舉(遍歷訪問)。

console.log(test.propertyIsEnumerable('custom')); // false;
console.log(test.propertyIsEnumerable('custom_private')); // true;

toLocaleString()
把對象轉換爲符合本地區格式的字符串。

toString()
把對象轉換爲字符串。

test.toString(); //  "[object Object]"

在 JavaScript 中也只有 Object 的 toString() 方法纔會返回這種格式,所以利用這個特性,咱們能夠在轉換格式統一的基礎上進行類型的判斷。

Object.prototype.toString.call({})      //"[object Object]"
Object.prototype.toString.call([])      //"[object Array]"
Object.prototype.toString.call("")      //"[object String]"
Object.prototype.toString.call(1)       //"[object Number]"
Object.prototype.toString.call(true)    //"[object Boolean]"
Object.prototype.toString.call(null)    //"[object Null]"
Object.prototype.toString.call(undefined)//"[object Object]"

valueOf() 返回對象自身。

相關文章
相關標籤/搜索