1 、js數據類型--objectjavascript
在JavaScript中,數據類型能夠分爲原始類型以及引用類型。其中原始類型包括string,number, boolean, null, undefined, symbol(ES6新增,表示獨一無二的值),這6種數據類型是按照值進行分配的,是存放在棧(stack)內存中的簡單數據段,能夠直接訪問,數據大小肯定,內存空間大小能夠分配。引用類型包括function,object,array等能夠可使用new建立的數據,又叫對象類型,他們是存放在堆(heap)內存中的數據,如var a = {}
,變量a實際保存的是一個指針,這個指針指向對內存中的數據 {}
。html
傳送門:更多symbol的用法能夠看阮一峯ECMAScript 6 入門java
講到數據,那不得不講的就是變量,JavaScript中的變量具備動態類型這一特性,這意味着相同的變量可用做不一樣的類型:es6
var x; // x 爲 undefined x = 6; // x 爲 number x = "hfhan"; // x 爲 string
JavaScript中能夠用typeof 操做符來檢測一個數據的數據類型,可是須要注意的是typeof null
結果是object, 這是個歷史遺留bug:web
typeof 123; // "number" typeof "hfhan"; // "string" typeof true; // "boolean" typeof null; // "object" 獨一份的不同凡響 typeof undefined; // "undefined" typeof Symbol("hfhan"); // "symbol" typeof function(){}; // "function" typeof {}; // "object"
先理解下什麼是宿主環境:由web瀏覽器或是桌面應用系統造就的js引擎執行的環境即宿主環境。segmentfault
ECMA-262 把本地對象(native object)定義爲「獨立於宿主環境的 ECMAScript 實現提供的對象」。數組
本地對象包含但不限於Object、Function、Array、String、Boolean、Number、Date、RegExp、各類錯誤類對象(Error、EvalError、RangeError、ReferenceError、SyntaxError、TypeError、URIError)promise
注意:這裏的Object、Function、Array等不是指構造函數,而是指對象的類型瀏覽器
ECMA-262 把內置對象(built-in object)定義爲「由 ECMAScript 實現提供的、獨立於宿主環境的全部對象,在 ECMAScript 程序開始執行時出現」。這意味着開發者沒必要明確實例化內置對象,它已被實例化了。ECMA-262 只定義了兩個內置對象,即 Global 和 Math (它們也是本地對象,根據定義,每一個內置對象都是本地對象)。app
其中Global對象是ECMAScript中最特別的對象,由於實際上它根本不存在,但你們要清楚,在ECMAScript中,不存在獨立的函數,全部函數都必須是某個對象的方法。相似於isNaN()、parseInt()和parseFloat()方法等,看起來都是函數,而實際上,它們都是Global對象的方法。並且Global對象的方法還不止這些。有關Global對象的具體方法和屬性,感興趣的同窗能夠看一下這裏:JavaScript 全局對象參考手冊
對於web瀏覽器而言,Global有一個代言人window,可是window並非ECMAScripta規定的內置對象,由於window對象是相對於web瀏覽器而言的,而js不只僅能夠用在瀏覽器中。
Global與window的關係能夠看這裏:概念區分:JavaScript中的global對象,window對象以及document對象
能夠看出,JavaScript中真正的內置對象其實只有兩個:Global 和 Math,但是觀看網上的文章資料,千篇一概的都在講JavaScript的11大內置對象(不是說只有11個,而是經常使用的有11個:Object、Function、Array、String、Boolean、Number、Date、RegExp、Error、Math、Global,ES6中出現的Set 、Map、Promise、Proxy等應該也算是比較經常使用的),這是不嚴謹的,JavaScript中本地對象、內置對象和宿主對象一文中,把本地對象、內置對象統稱爲「內部對象」,算是比較貼切的。
更多「內部對象」能夠查看MDN>JavaScript>引用>內置對象 內容,或者經過瀏覽器控制檯打印window來查找。
由ECMAScript實現的宿主環境提供的對象,能夠理解爲:瀏覽器提供的對象。全部的BOM和DOM都是宿主對象。
顧名思義,就是開發人員本身定義的對象。JavaScrip容許使用自定義對象,使JavaScript應用及功能獲得擴充
對象的類型不能使用typeof來判斷,由於除了Function外其餘類型的對象所獲得的結果全爲"object"
typeof function(){}; // "function" typeof {}; // "object" typeof new RegExp; // "object" typeof new Date; // "object" typeof Math; // "object" typeof new Error; // "object" …
一個使用最多的檢測對象類型的方法是 Object.prototype.toString
:
Object.prototype.toString.apply(new Function); // "[object Function]" Object.prototype.toString.apply(new Object); // "[object Object]" Object.prototype.toString.apply(new Date); // "[object Date]" Object.prototype.toString.apply(new Array); // "[object Array]" Object.prototype.toString.apply(new RegExp); // "[object RegExp]" Object.prototype.toString.apply(new ArrayBuffer); // "[object ArrayBuffer]" Object.prototype.toString.apply(Math); // "[object Math]" Object.prototype.toString.apply(JSON); // "[object JSON]" var promise = new Promise(function(resolve, reject) { resolve(); }); Object.prototype.toString.apply(promise); // "[object Promise]" …
構造函數是描述一類對象統一結構的函數——至關於圖紙
上面咱們已經知道了,JavaScript中的對象有很對種類型,好比Function、Object、Array、Date、Set等等,那麼咱們如何去建立這些類型的數據?
生成一個函數能夠經過function關鍵字:
function a(){ console.log(1) } //或者 var b = function(){ console.log(2) }
此外建立一個對象(類型爲Object的對象),能夠經過{}
;建立一個數組,能夠經過[]
;建立一個正則對象能夠經過/.*/
。可是那些沒有特殊技巧
的對象,就只能老老實實使用構造函數來建立了。
JavaScript 語言中,生成實例對象的傳統方法是經過構造函數,即咱們經過函數來建立對象,這也證實了函數在JavaScript中具備很是重要的地位,所以說函數是一等公民。
JavaScript中的對象在使用的時候,大部分都須要先進行實例化(除了已經實例化完成的Math對象以及JSON對象):
var a = new Function("console.log('a') "); //構造函數建立Function對象 var b = new Object({a:1}); //構造函數建立Object對象 var c = new Date(); //構造函數建立Date對象 var d = new Set(); //構造函數建立Set對象 var e = new Array(10); //構造一個初始長度爲10的數組對象
能夠看出,只要使用new關鍵字來實例化一個構造函數就能夠建立一個對象了,JavaScript中內部對象的構造函數是瀏覽器已經封裝好的,咱們能夠直接拿過來使用。
使用構造函數建立的數據全是對象,即便用new關鍵字建立的數據全是對象,其中new作了4件事:
1)、先建立空對象 2)、用空對象調用構造函數,this指向正在建立的空對象 按照構造函數的定義,爲空對象添加屬性和方法 3)、將新建立對象的__proto__屬性指向構造函數的prototype對象。 4)、將新建立對象的地址,保存到等號左邊的變量中
除了瀏覽器自己自帶的構造函數,咱們還可使用一個普通的函數來建立對象:
function Person(){}; var p1 = new Person()
這個例子中Person就是一個普普統統的空函數,可是他依然能夠做爲構造函數來建立對象,咱們打印下p1
的類型,能夠看出使用自定義的構造函數,所建立的對象類型爲 Object
Object.prototype.toString.apply(p1); // "[object Object]"
實際上並不存在建立構造函數的特殊語法,其與普通函數惟一的區別在於調用方法。對於任意函數,使用new操做符調用,那麼它就是構造函數,又叫工廠函數;不使用new操做符調用,那麼它就是普通函數。
按照慣例,咱們約定構造函數名以大寫字母開頭,普通函數以小寫字母開頭,這樣有利於顯性區分兩者。例如上面的new Object (),new Person ()。
原型是指原型對象,原型對象從哪裏來?
每一個函數在被建立的時候,會同時在內存中建立一個空對象,每一個函數都有一個prototype 屬性,這個屬性指向這個空對象,那麼這個空對象就叫作函數的原型對象,而每個原型對象中都會有一個constructor屬性,指向該函數
function b(){console.log(1)}; b.prototype.constructor === b; // true
抽象理解:構造函數是妻子,原型對象是丈夫,prototype是找丈夫,constructor是找妻子。
手動更改函數的原型對象:
var a = {a:1}; b. prototype = a; //更改b的原型對象爲a a. constructor; // function Object() { [native code] }
爲何這裏a. constructor不指向b函數?
這是由於變量a所對應的對象是事先聲明好的,不是跟隨函數一塊兒建立的,因此他沒有constructor屬性,這時候尋找constructor屬性就會到父對象上去找,而全部對象默認都繼承自Object. Prototype,因此最後找的就是Object. Prototype. Constructor,也就是Object函數。
剛纔講到了繼承,繼承又是怎麼一回事呢?
全部對象都有一個__proto__ 屬性,這個屬性指向其父元素,也就是所繼承的對象,通常爲構造函數的prototype對象。
prototype 是函數獨有的;__proto__ 是全部對象都有的,是繼承的。調用一個對象的某一屬性,若是該對象上沒有該屬性,就會去其原型鏈上找。
好比上例中,調用p.a,對象p上找不到a屬性,就會去找p.__proto__.a,p.__proto__.a也找不到,就會去找p.__proto__.__proto__.a,依次類推,直到找到Object.prototype.a也沒找到,就會返回undefined。
原型鏈是由各級子對象的__proto__屬性連續引用造成的結構,全部對象原型鏈的頂部都是Object.prototype。
咱們知道,當子對象被實例化以後再去修改構造函數的prototype屬性是不會改變子對象與原型對象的繼承關係的,可是經過修改子對象的__proto__屬性,咱們能夠解除子對象與原型對象之間的繼承關係。
var A = function(){}; // 構造函數 A.prototype = {a:1}; // 修改原型對象 var a = new A; // 實例化子對象a,此時a繼承自{a:1} a.a // 1 A.prototype = {a:2} // 更該構造函數的原型對象 a.a // 1 此時,a還是繼承自{a:1} a.__proto__ = {a:3} // 修改a的原型鏈 a.a // 3 此時,a繼承自{a:3}
一切誕生於虛無!
上面講了,全部對象原型鏈的頂部都是Object.prototype,那麼Object.prototype是怎麼來的,憑空造的嗎?還真是!
Object.prototype.__proto__ === null; // true
上面講了,咱們能夠經過修改對象的__proto__屬性來更改繼承關係,可是,Object.prototype的__proto__屬性不容許更改,這是瀏覽器對Object.prototype的保護措施,修改Object.prototype的__proto__屬性會拋出錯誤。同時,Object.prototype.__proto__
也只能進行取值操做,由於null 和 underfined沒有對應的包裝類型,所以不能調用任何方法及屬性
在控制檯打印下Object.prototype.__proto__的保護屬性:
Object.getOwnPropertyDescriptor(Object.prototype,"__proto__");
注:保護屬性及getOwnPropertyDescriptor爲ES5中內容。
能夠看到,其numerable、configurable屬性均爲false,也就是Object.prototype.__proto__屬性不可刪除,不可修改屬性特性,而且屬性作了get、set的處理。
Object.prototype與Function.prototype是原型鏈中最難理解也是最重要的兩個對象。下面咱們用抽象的方法來理解這兩個對象:
天地伊始,萬物初開,誕生了一個對象,不知其姓名,只知道他的類型爲"[object Object]",他是一切對象的先祖,爲初代對象,繼承於虛無(null)。
後來,又誕生了一個對象,也不知其姓名,只知道他的類型爲"[object Function]",他是一切函數的先祖,繼承於對象先祖,爲二代對象。
經年流轉,函數先祖發揮特長,製造出了一系列的函數,如Object、Function、Array、Date、String、Number等,都說龍生九子各有不一樣,這些函數雖然說各個都貌美如花,神統統天,但功能上仍是有很大的區別的。
其中最須要關注的是Object以及Function。原來函數先祖在創造Function的時候,悄悄的把Function的prototype屬性指向了本身,也把本身的constructor屬性指向了Function。若是說Function是函數先祖爲本身創造的妻子,那麼Object就是函數先祖爲對象先祖創造的妻子,一樣的,Object的prototype屬性指向了對象先祖,對象先祖也把本身的constructor屬性指向Object,表示他贊成了這門婚事。
此後,世人都稱對象先祖爲Object.prototype,函數先祖爲Function.prototype。
從上能夠看出,對象先祖是一開始就存在的,而不是同Object一塊兒被建立的,因此手動更改Object.prototype的指向後:
Object.prototype = {a:1}; //修改Object.prototype的指向 var a = {}; //經過字面量建立對象 a.a //undefined 此時a仍然繼承於對象先祖 var b = new Object(); //經過new來建立對象 b.a //結果是???
這裏我本來覺得會打印1,可是實際上打印的仍是undefined,而後在控制檯打印下Object.prototype,發現Object.prototype仍然指向對象先祖,也就是說Object.prototype = {a:1}
指向更改失敗,我猜想和上面Object.prototype的__proto__屬性不容許更改,緣由是同樣的,是瀏覽器對Object.prototype的保護措施。
在控制檯打印下Object.prototype的保護屬性:
Object.getOwnPropertyDescriptor(Object,"prototype");
能夠看到,其writable、enumerable、configurable屬性均爲false,也就是其prototype屬性不可修改,不可刪除,不可修改屬性特性。
其實不光Object.prototype不能修改,Function. Prototype、String. Prototype等內部對象都不容許修。
咱們繼續往下看
由於Object、Function、Array、String等都繼承自Function.prototype,因此有
Object.__proto__ === Function.prototype; // true Function.__proto__ === Function.prototype; // true Array.__proto__ === Function.prototype; // true String.__proto__ === Function.prototype; // true
全部的對象都繼承於Object.prototype,因此有
Function.prototype.__proto__ === Object.prototype; // true Array.prototype.__proto__ === Object.prototype; // true String.prototype.__proto__ === Object.prototype; // true
當咱們自定義一個對象的時候,這個對象在整個原型鏈上的位置是怎麼樣的呢?
這裏咱們不對對象的建立方式多作討論,僅以構造函數爲例
當咱們使用字面量建立一個對象的時候,其父對象默認爲對象先祖,也就是Object.prototype
var a = {}; a.__proto__ === Object.prototype; // true
上面講了,自定義構造函數所建立的對象他的類型均爲"[object Object]",在函數創建的時候,會在內存中同步創建一個空對象,其過程能夠看做:
function F(){}; // prototype 賦值 F.prototype = {},此時{}繼承於Object.prototype
當咱們使用構造函數建立一個對象時,會把構造函數的prototype屬性賦值給子對象的__proto__屬性,即:
var a = new F(); //__proto__賦值 a.__proto__ = F.prototype;
由於F.prototype繼承於Object.prototype,因此有
a.__proto__.__proto__ === Object.prototype; // true
綜上咱們能夠看出,原型鏈就是根據__proto__維繫的由子對象-父對象的一條單向通道,不過要理解這條通道,咱們還須要理解構造對象,類,prototype,constructor等,這些都是原型鏈上的美麗的風景。
最後但願你們能夠在javascript的大道上肆意馳騁。