探究面向對象的JavaScript高級語言特性javascript
JavaScript是由Netscape公司工程師Brendan Eich研發的腳本語言,通過推廣和流行,兼容ECMA-262標準,至今用於描述HTML網頁行爲。(前端驗證,檢測,響應,觸發,控制等動態行爲)前端
本文涉及到的概念有JavaScript概述,對象類型系統,原型鏈,做用域鏈以及上下文this,閉包,命名空間以及面向對象的高級語言特性應用。java
JavaScript知識點龐雜且我的能力和學習時間有限,但願獲得有心者更多的鼓勵與啓發。web
從面向對象角度理解,JS確實一切皆對象。可是JS是函數式編程,從面向過程角度能夠理解爲一切皆函數,這多是JavaScript魅力所在。本文叫作面向JavaScript,偏向從面向對象角度理解。編程
如何理解「一切皆對象」。通常來講咱們會從對象構造器和原型鏈解釋,本文後文中有詳細概述,這裏不做初步探討。瀏覽器
這裏咱們從JS最特殊的內置對象GLOBAL開始。閉包
介紹app |
GLOBAL是ECMAScript5規範中兩個內置對象其中之一,表示全局對象。ide |
||
做用函數式編程 |
任何不屬於JavaScript其餘對象的屬性和方法都屬於GLOBAL。 |
||
實現 |
JavaScript對其並無明確的實現。瀏覽器將GLOBAL做爲宿主對象Window的一部分實現。 JSEngine的起始階段就是實例化一個Window對象。 這也解釋了(this === window) == true; |
||
屬性 |
預約義對象 |
Object Array Function Boolean String Number Date RegExp Error EvalError RangeError ReferenceError SyntaxError TypeError URIError(做爲函數原型的構造器) |
|
全局屬性 |
undefined NaN Infinity … |
||
全局函數 |
編解碼 |
decodeURI()/decodeURIComponent()/encodeURI()/ encodeURIComponent()/escape()/unescape() |
|
轉換 |
getClass()/Number()/String()/parseFloat()/parseInt() |
||
判斷 |
isFinite()/isNaN() |
||
執行 |
eval() |
||
用戶定義 |
如var _temp_val = {}; // window._temp_val = {};全局變量混亂問題 |
||
Tips:全局屬性能夠直接使用而不用[window. ] Object,同時除了用戶定義覺得的屬性不能delete |
在JS引擎啓動並實例化window對象時,JS原生對象和瀏覽器對象所有做爲預約義對象放置在window中。
當咱們須要新建對象時,首先對象原型將會依據這些預約義對象被構建。
舉個小慄砸:(這裏有一些構造器和原型鏈的應用,對後面章節的梳理有幫助。)
1 var window.namespaceA = [];//聲明命名空間 2 namespaceA.A = {…};//聲明函數,JS引擎加載時將A.prototype丟到堆內存 3 var a = Object.create(A.prototype,{ …args });
JS引擎行爲:
Object() = window.Object.prototype.constructor;//Object構造函數 Function() = window.Function.prototype.constructor;//Function構造函數 A() = window.namespaceA.A.prototype.constructor; // A構造函數 a = new A({ …args});//以A對象爲原型實例化引用a a.[[prototype]] = A.prototype;
、
該圖總結並參考了ECMAScript5規範,若有錯誤請指出。
部分對象請參考W3C教程:http://www.w3school.com.cn/jsref/index.asp
這一篇章進入對象的講解,講述在當前JS執行環境executable code(ECStack)中,如何建立一個原型實例對象。分配內存,造成做用域鏈與原型鏈。改變執行控制權並返回對象。
總結:this new a object by prototype chain in ECStack, then this got return and leave ECStack.
JS引擎執行過程是JS對象的生命週期的交替的過程,JS引擎解析執行。當執行子函數時,會將引擎操做的控制權讓給子函數。子函數自己就是一個函數上下文。
window.ECStack = [];//模擬執行環境,實際的執行環境包含window全局上下文
備註:與執行上下文相關的做用域和參數對象在new小節講解。
執行環境ECStack內存時序結構圖如圖所示。
ECMAScript5規範豐富了用戶自定義Object對象,提供了Object對象的屬性特性。
參考W3C教程:http://www.w3school.com.cn/js/pro_js_referencetypes.asp
備註: JSON對象拓展,使用JSON. Stringify(/*Object*/)和JSON.parse(/*String*/)進行對象序列化,有時依賴引用對象的toJSON()方法。
JS是元解釋型語言,當JS引擎執行JS代碼時,會分析語法結構,並將獲得的對象結構放置在堆內存中,稱爲原型。內存中的全部對象都包含一個屬性[[prototype]]指針指向堆內存的原型,FireFox,Chrome等瀏覽器將該屬性定義爲__proto__可顯示調用。
醬:Foo.__proto__ à Foo.prototype
同時原型對象存在原型,造成一條原型鏈。當JS引擎實例化window對象構造全局上下文this時,堆內存中生成以下原型鏈:
圖中每個矩形都是一個原型對象。每一個原型對象都有本身的構造器函數。
醬:Foo.prototype.constructor == Foo();//注意這裏是函數,只是JS容許寫成Foo。
我將JAVA與JavaScript作類比,雖然這樣可能不太穩當。
JavaScript概念 |
Java概念 |
預約義對象,如Object |
API對象,如java.lang.Object |
內核 |
JVM |
內核初始化window時,原型對象Object.prototype加載到堆內存中 |
JVM啓動後,類信息Object.class加載到方法區中。 |
Object()//Object.prototype. constructor構造函數 |
Object構造函數 |
var obj = new Object(); |
Object obj = new Object(); |
obj.__proto__ == Object.prototype |
obj.getClass() == Object.class; |
Constructor構造函數概念是一個動態概念,意味着運行時內核中的原型對象才包含構造函數。理解預約義對象,原型對象,原型對象構造函數和實例化對象後,須要引伸一個概念:如何斷定對象類型。
關鍵字 |
類型 |
返回值 |
說明 |
typeof |
一元運算符 |
字符串 |
基本類型undefined string number Boolean 引用類型object 函數類型 function。 不返回null的緣由:object JS類型值是存在32 BIT 單元裏,32位有1-3位表示TYPE TAG,其它位表示真實值。object的標記位爲000。而null標記位爲00,最終體現的類型仍是object.. |
instanceof |
二元運算符 |
布爾值 |
判斷除undefined null一個變量是否某個對象的實例,就是看該對象是否在該變量的原型鏈上。 |
constructor |
函數 |
構造函數 |
var temp = obj.constructor.toString(); temp.replace(/^function (\w+)\(\).+$/,'$1'); |
prototype |
對象 |
原型類型 |
Object.prototype.toString.call(obj).slice(8,-1).toLowerCase(); |
這時候咱們能夠把本節原型鏈內存結構圖補齊啦:
當查詢對象的屬性時,若該對象不存在該屬性,則在該對象的原型鏈中向上查找對象原型是否存在該屬性。若存在,則返回該屬性,若直到原型爲null時仍未找到該屬性,返回undefined。
javascript找屬性就是找原型鏈最近的,找不着就找他爹。
this是當前執行環境上下文的一個屬性。JavaScript是單線程的,意味着JS內核執行JS代碼切換上下文,this就會改變。若JS執行到代碼片斷A,說明包含片斷A的上一級內容B正在被加載/實例化,那麼B就是this。
ECStack = { VO : {…}, this : thisValue};
初始狀態
當瀏覽器加載請求頁面時,卸載原始事件,解析並渲染HTML,驅動當前事件並綁定當前窗口對象。每一個窗口都有本身的全局對象,當頁面被加載後,window對象實例化並綁定this。
1 function main(){ 2 window = new Window(); 3 this.call(window);//擴展this環境對象,其實只有函數對象纔有call方法 4 |
無狀態
這一塊並無過多的代碼演示。我的有兩點理解。
1 function Foo () {} 2 Foo.prototype.foo = "bar"; 3 Foo.prototype.logFoo = function () { 4 eval("console.log(this.foo)"); //logs "bar" 5 } 6 var foo = new Foo (); 7 foo.logFoo();//foo調用logFoo方法,只有foo原型有logFoo方法,因此Foo.prototype爲this
備註:判斷a是不是A的自由屬性,使用hasOwnProperty方法。
備註:可使用with,apply,call,bind方法鏈接this環境上下文,實現屬性繼承,這裏不做過多說明。
new關鍵詞實例化一個對象。對象實例化過程,對this的不斷賦值,構造參數對象arguments,並維護實例對象的constructor屬性。
舉個慄砸。使用構造函數自定義對象:
分解步驟以下:
1 A.prototype.constructor= A(){…}; 2 A() = new Function(); 3 Function() = new Object();
1 Var a = new A(); 2 A.call(a, _args);//_args.id =1;
1 arguments = A.prototype.slice.call(_args);;//傳入參數 2 arguments.callee == this;//參數對象調用者爲當前函數體
備註:arguments.length表示實際傳參數,arguments.callee.length表示指望傳參數
1 a.id = arguments[1];//值爲1 2 a.name = arguments[2];//值爲undefined 3 a.constructor = A.prototype.constructor = A(){…};//繼承
1 return a;
新建對象方法 |
描述 |
備註 |
使用JS內存中內置對象的構造方法 |
var str = new String(「str」); |
只能實例化內置對象 |
使用JSON字面量 |
var o = {‘name’ :‘sapphire’}; |
方便快捷的方式 |
工廠模式 |
自定義函數中建立對象並返回 |
抽象方式 |
構造函數模式 |
function Foo(_args){…}; var foo = new Foo(_args); |
經常使用方式 方法沒法共享 |
原型模式 |
function Foo(id){Foo.prototype.id = id}; |
切斷已存在實例與原型對象關係 屬性共享 |
組合模式 |
function Foo(){…}; Foo.prototype = {fun : function(){}}; |
構造函數模式構造屬性 原型模式構造方法 |
當構造函數無特殊聲明return返回值時,返回實例化對象。
函數閉包是一種技術,尤爲被Lambdas語言普遍應用。Java中可使用匿名函數的方式模擬閉包。
閉包 = 閉包函數 + 一組自由變量與名稱綁定存儲區映射,實現函數外部訪問函數內部變量的技術。
原理:若是一個函數包含內部函數,那麼它們均可以看到其中聲明的變量;這些變量被稱爲‘自由’變量。然而,這些變量能夠被內部函數捕獲,從高階函數中return實現‘越獄’,以供之後使用。惟一須要注意的是,捕獲函數必須在外部函數內定義。函數內沒有任何局部聲明以前(既不是被傳入,也不是局部聲明)使用的變量就是被捕獲的變量。
備註:Javascript中20%以上的博客是關於閉包概念的,這裏再也不贅述。
弊端:應用時需分析函數變量,檢查閉包是否會互相產生干擾,檢查閉包的實例是否相同。
利端:閉包函數實例化時只執行一次,將不須要暴露在外層環境的變量封裝在內部,減小了外部變量。
在ECMAScript中,只支持實現繼承而不支持簽名繼承(接口繼承)實現繼承基本是經過原型鏈繼承。
繼承方式 |
示例 |
原型鏈繼承 |
Sub.prototype = new Super(); |
構造函數繼承 |
function Sub(){ Super.call(this);} |
組合繼承 |
|
原型式繼承 |
Object.create(prototype) |
以上咱們接觸了全局變量和this的概念,前端JS開發中,不規範的命名規則會致使JS全局變量混亂甚至衝突。
下面這段代碼示例規範變量命名空間
1 var GLOBAL = {}; 2 GLOBAL.namespace = function(str){ 3 var arr = str.split('.'); 4 var start = 0; 5 if(arr[0] == 'GLOBAL'){ start = 1; } 6 for(var i = start; i < arr.length; i++){ 7 GLOBAL[arr[i]] = GLOBAL[arr[i]] || {}; 8 GLOBAL = o[arr[i]]; 9 } 10 };
假設a變量完成A功能中A1子功能。b變量完成A功能中A2子功能。c變量完成B功能。那麼
1 GLOBAL.namespace('A.A1'); A.A1.a = …; 2 GLOBAL.namespace('A.A2'); A.A2.b = …; 3 GLOBAL.namespace('B'); B.c = …;
同時,儘可能在匿名函數中聲明變量而非全局環境中,併爲代碼添加更多註釋。
火狐開發者JS文檔 https://developer.mozilla.org/en-US/docs/Web/JavaScript/A_re-introduction_to_JavaScript
IBM開發者社區 http://www.ibm.com/developerworks/cn/web/
有關ECMA-262我的站點 http://dmitrysoshnikov.com/