js深刻學習繞不開的原型知識

最近在看underscore源碼,涉及到js原型相關的知識,因而重溫了一遍,在此作下記錄。html

js原型是其語法的一個難點,也是一個重點,要深刻學習js必須掌握的點。要想讀懂別人的框架和庫,瞭解這些基礎知識是必不可少的。 js原型主要爲了提取公共屬性和方法,實現對象屬性和方法的繼承。說到原型,可能就有幾個相關的詞:prototype、__proto__、constructor、instanceof。下面經過這幾個關鍵詞來說解原型。數組

一、基礎概念

說到原型,先說一個概念,js裏函數(function)是一種特殊的對象,有個說法是js裏一切都是對象,這個說法不正確,要排除一些特殊類型,如:undefined, null (雖然null的typeof, toString返回類型是object,歷史遺留bug)。瀏覽器

js裏全部的對象都有__proto__屬性,能夠稱爲隱式原型,這個屬性在低版本ie瀏覽器中沒法讀取到。一個對象的隱式原型指向構造該對象的構造函數的原型,使得該對象可以繼承其構造函數原型上的屬性和方法。框架

函數對象除了具備__proto__這個屬性外,還有一個專有屬性prototype。這個屬性指向一個對象(命名a對象),從該函數實例化的對象(命名b對象)。b對象能共享a對象的全部屬性和方法。反過來a對象的constructor屬性又指向這個函數。函數

constructor 屬性是專門爲 function 而設計的,它存在於每個 function 對象的prototype 屬性中。這個 constructor 保存了指向 function 的一個引用。學習

constructor和prototype被設計時是構造函數和原型間相互指向,能夠當作互逆的,因爲實例繼承了prototype對象上的屬性(包括constructor),故實例的constructor也是指向構造函數。雖然他們倆是互逆,可是二者沒有必然聯繫,修改其中一個的指向,另外一個並不會變。因此在js中經過原型來繼承時,通常替換原型時,會附帶替換掉constructor的指向。測試

// constructor與prototype
Object.prototype === Object.prototype
Object.prototype.constructor === Object

// 實例指向構造函數
var a = new Object({});
a.constructor === Object;

// 修改一個指向
function a() {}
a.prototype = {
  say: function () {
    console.log('hello world');
  }
}
a.prototype.constructor === Object //true
// 由於a.prototype從新賦值時,直接是賦值的一個對象,
// 這個對象沒有經過構造函數來生成,默認就會以new Object方式。故構造函數就是Object。
// 因此通常要手動從新指向構造函數
a.prototype.constructor = a;
複製代碼

constructor設計初是被用來判斷對象類型的,因爲其易變性,通常不使用它來作判斷,使用instanceof來替代它。ui

instanceof運算符,它用來判斷一個構造函數的prototype屬性所指向的對象是否存在另一個要檢測對象的原型鏈上。通常用來判斷一個實例是否從一個構造函數實例化過來,用一個函數模擬instanceof函數:this

function _instanceof(A, B) {
  var O = B.prototype;
  A = A.__proto__;
  while (true) {
    if (A === null) // 循環查找原型鏈,一直到Object.prototype.__proto__ = null
      return false; // 退出循環
    if (O === A)
      return true;
    A = A.__proto__;
  }
}
複製代碼

說了這麼可能是不是以爲有點繞,拿出個人殺手鐗,祭出我收藏的一張圖,該圖很清晰的解釋了這些關係。看了這張圖後,瞬間理清了原型,廢話很少說,上圖:spa

js原型

二、兩個特殊對象

看了上面的圖後相信整個原型比較清晰了,下面說說整個原型中幾個特殊對象。

2.一、第一個特殊的對象就是Function

js裏的內置對象Object、Array、Function、Date、Math、Number、String、Boolean、RegExp等都是構造函數對象,能夠經過new實例化對象出來。其__proto__屬性都指向Function.prototype。Function這個特殊對象,是上面其餘函數對象的構造函數。

這裏有一條鏈,以Array爲例:

123.png

js中上面寫的這些對象能夠當作是從Function構造函數new出來的對象(實例),只不過與Object,Array構造函數 new出來的對象有點不一樣,實例化出來的對象是函數對象。因此有如下等式成立。

Array.__proto__ === Function.prototype // true
Object.__proto__ === Function.prototype // true

Array.constructor === Function // true
Object.constructor === Function // true
複製代碼

因爲實例化的Array、Object等屬於函數對象,它就有prototype屬性,故給每一個函數對象配了個原型,如:Array.prototype、Object.prototype,從Array、Object等實例化的對象能夠完成一些相同的功能,故給這些對象內置了不少方法,讓全部實例化的對象都具有這些方法,故在原型上掛載了不少方法。好比Array.prototype的方法:push、shift、unshift、concat等。 還有個特例以下:

Function.__proto__ === Function.prototype
複製代碼

Function 這個函數既能夠當作構造函數,也能夠當作實例後的函數對象。

2.二、第二個個特殊的對象就是Object.prototype

不論是構造函數、原型、仍是實例化對象,其都屬於對象,對象的原型最初來源都是Object.prototype這個原型對象,故:

Function.prototype.__proto__ === Object.prototype
Array.prototype.__proto__ === Object.prototype

Object.prototype.__proto__ === null
複製代碼

而Object.prototype這個對象的__proto__屬性就爲null了。 最後上一張我本身畫的關於原型的圖:

window畫圖畫的,忽略它的醜.png


三、原型相關方法

既然說到了原型鏈,來講一下幾個相關屬性,hasOwnProperty、isPrototypeOf、in。這幾個屬性在原型概念中常常用到。

js的原型主要實現了屬性和方法的繼承,既然有繼承,屬性和方法就有本身的和繼承來的之分。那麼怎麼去區分呢?

3.一、hasOwnProperty()方法用來判斷某個對象是否含有指定的自身屬性。不包括原型鏈上的屬性

var a = {name: 'li'};
a.hasOwnProperty('hasOwnProperty'); // false
a.hasOwnProperty('name'); // true
複製代碼

hasOwnProperty屬性繼承自Object.prototype,故返回false, name則是建立時,自帶的,故返回false

3.二、isPrototypeOf方法測試一個對象是否存在另外一個對象的原型鏈上。

var o = {}
var a = function () {}
var b = new a();
a.prototype = o;
var c = new a();
o.isPrototypeOf(b); // false
o.isPrototypeOf(c); // true
複製代碼

3.三、in方法也是檢測一個對象中是否有每一個屬性。

與hasOwnProperty不一樣的是,它會遍歷該對象上全部可枚舉屬性,包括原型鏈上的屬性,有就返回true,沒有就返回false;

function a() {
    this.name = 'li'
}
a.prototype = {age: 20};
var b = new a();

for (var key in b) {
    if (b.hasOwnProperty(key)) {
        console.log('自身屬性'+ key);
    } else {
        console.log('繼承屬性'+ key);
    }
}
上面的方法常常區分一個對象中的屬性是自身屬性仍是繼承屬性。
複製代碼

for...in 循環只遍歷可枚舉屬性,使用內置構造函數,像 Array、Object、Number、Boolean、String等構造函數的原型上的屬性都不可枚舉。 如:Object.prototype.toString方法。 固然若是toString方法被重寫,仍是能夠遍歷的,如:

function animal() {
  this.name = 'lilei'
}
animal.prototype.toString = function () {
  console.log('animal say');
}
var cat = new animal();
for (var key in cat) {
  console.log(key); // name, toString
}
複製代碼

可是在 IE < 9 瀏覽器中(萬惡的 IE),Object、Array等構造函數的原型上的屬性即便被重寫了,仍是不能被枚舉到。

3.3.1對象的可枚舉

(1)、說到可枚舉,你可能想到了一個函數,沒錯就是propertyIsEnumerable函數,他是Object.prototype上的一個方法,他能檢測一個屬性在其對象上是否能夠枚舉。 該方法只能檢測對象的自有屬性,對於其原型鏈上的屬性始終返回false,這一點要與for ... in 中的可枚舉區分開。

function a() {
 this.name = 'liming';
}

a.prototype.say = function () {
 console.log(1);
}

var b = new a();

b.propertyIsEnumerable('name') // true
b.propertyIsEnumerable('say') // false
複製代碼

(2)、對象的屬性分爲可枚舉和不可枚舉之分,說了這麼多,其實它們是由屬性的enumerable值決定的。如經過Object.defineProperty函數建立的屬性,能夠添加該字段來決定該屬性是否可枚舉。

var a = {name: 'xiao ming'}
Object.defineProperty(a, "gender", {
    value: "male",
    enumerable: false
});
a.propertyIsEnumerable('name') // true
a.propertyIsEnumerable('gender') // false
複製代碼

(3)、到此應該已經結束了,可是我仍是想提到一個函數,Object.keys。該函數返回一個對象的key的數組。看個例子

function q() {
   this.name = 'lilei'
}
q.prototype.say = function () {
   console.log('say');
}
var a = new q();
Object.defineProperty(a, "gender", {
    value: "male",
    enumerable: false
});
Object.keys(a) // ['name']
複製代碼

說明該方法返回該對象自有的可枚舉屬性。

(4)、我還想說一下JSON.stringify方法,咱們都只到JSON.stringify方法能夠序列化對象。你有經過它克隆對象沒?

var a = {name: 'liming'}

var b = JSON.parse(JSON.stringify(a)) // {name: 'liming'}
複製代碼

沒錯對於簡單的對象,咱們能夠這樣克隆,可是他能保存對象裏的全部屬性嗎? 咱們來看一下:

function f() {
  this.name = 'lilei';
  this.like= undefined;
  this.say = function () {}
}
f.prototype.age = 20;
var a = new f();
Object.defineProperty(a, "gender", {
    value: "male",
    enumerable: false
});
var b = JSON.parse(JSON.stringify(a)) // {name: 'lilei'}
複製代碼

顯然該方法並不能保存對象裏全部屬性,事實上stringify只能保存該對象本身的可枚舉屬性,不能保存其原型上的屬性,而且本身的屬性也必須知足如下要求: 一、stringify只能保存基礎類型的:數字、字符串、布爾值、null四種,不支持undefined。 二、stringify方法不支持函數; 三、除了RegExp、Error對象,JSON語法支持其餘全部對象; 關於其詳細內容,請看這篇文章傳送門

四、結語:

你可能感受文章後面說了一堆方法好像跟原型沒多大關聯,確實關聯性不是很大,可是它們方法內部都涉及到了對象的屬性遍歷,對象屬性遍歷天然就聯繫到原型鏈上的屬性是否可遍歷,屬性的可枚舉性等一系列概念,因此就把它們都提了一下。

本人能力有限,以上內容爲我的理解,若有錯誤,歡迎指正。

相關文章
相關標籤/搜索