JavaScript Oriented[探究面向對象的JavaScript高級語言特性]

JavaScript Oriented

探究面向對象的JavaScript高級語言特性javascript

Prologue . JavaScript Introduce

1.  JS

Abstract

  JavaScript是由Netscape公司工程師Brendan Eich研發的腳本語言,通過推廣和流行,兼容ECMA-262標準,至今用於描述HTML網頁行爲。(前端驗證,檢測,響應,觸發,控制等動態行爲)前端

Knowledge Tree

 

2.     About Document

  本文涉及到的概念有JavaScript概述,對象類型系統,原型鏈,做用域鏈以及上下文this,閉包,命名空間以及面向對象的高級語言特性應用。java

  JavaScript知識點龐雜且我的能力和學習時間有限,但願獲得有心者更多的鼓勵與啓發。web

 

Chapter one . Type Family

1.  That’s all Object things?

  從面向對象角度理解,JS確實一切皆對象。可是JS是函數式編程,從面向過程角度能夠理解爲一切皆函數,這多是JavaScript魅力所在。本文叫作面向JavaScript,偏向從面向對象角度理解。編程

  如何理解「一切皆對象」。通常來講咱們會從對象構造器和原型鏈解釋,本文後文中有詳細概述,這裏不做初步探討。瀏覽器

 

2.     Begin from GLOBAL

  這裏咱們從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 });
View Code

  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;

3. We Are Family

        該圖總結並參考了ECMAScript5規範,若有錯誤請指出。

        部分對象請參考W3C教程:http://www.w3school.com.cn/jsref/index.asp

       

Chapter two . User Object

  這一篇章進入對象的講解,講述在當前JS執行環境executable code(ECStack)中,如何建立一個原型實例對象。分配內存,造成做用域鏈與原型鏈。改變執行控制權並返回對象。

  總結:this new a object by prototype chain in ECStack, then this got return and leave ECStack.

1.     Context – ECStack

  JS引擎執行過程是JS對象的生命週期的交替的過程,JS引擎解析執行。當執行子函數時,會將引擎操做的控制權讓給子函數。子函數自己就是一個函數上下文。

    window.ECStack = [];//模擬執行環境,實際的執行環境包含window全局上下文

  備註:與執行上下文相關的做用域和參數對象在new小節講解。

  執行環境ECStack內存時序結構圖如圖所示。

 

2.     WHAT – object

  ECMAScript5規範豐富了用戶自定義Object對象,提供了Object對象的屬性特性。

 

  參考W3C教程:http://www.w3school.com.cn/js/pro_js_referencetypes.asp

  備註: JSON對象拓展,使用JSON. Stringify(/*Object*/)和JSON.parse(/*String*/)進行對象序列化,有時依賴引用對象的toJSON()方法。

3.     TYPE – prototype chain

  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找屬性就是找原型鏈最近的,找不着就找他爹。

4.     WHO – this

  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 |
View Code

  無狀態

    這一塊並無過多的代碼演示。我的有兩點理解。

    1.   A調用方法a,則a方法內的this爲A。
    2.   若A中沒有a方法,則a方法內的this爲A原型鏈上包含方法a最近的原型對象B。
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
View Code

 

    備註:判斷a是不是A的自由屬性,使用hasOwnProperty方法。

    備註:可使用with,apply,call,bind方法鏈接this環境上下文,實現屬性繼承,這裏不做過多說明。

5.     DO – new

Introduce

  new關鍵詞實例化一個對象。對象實例化過程,對this的不斷賦值,構造參數對象arguments,並維護實例對象的constructor屬性。

Step

  舉個慄砸。使用構造函數自定義對象:

  1.   function A(/* number*/ id, /* string*/name){ this.id = id; this.name = name;}
  2.   var a = new A(1);//a.__proto__ = A.prototype

分解步驟以下:

  •   執行1行時,加載A對象構造函數並放入內存,構造原型鏈

  1 A.prototype.constructor= A(){…}; 2 A() = new Function(); 3 Function() = new Object();  

  •   執行2行時,使用構造函數實例化a,並將A.prototype做用域給實例a

 1 Var a = new A(); 2 A.call(a, _args);//_args.id =1; 

  •   執行內存中構造函數A.prototype.constructor前,構造參數對象arguments對象

 1 arguments = A.prototype.slice.call(_args);;//傳入參數 2 arguments.callee == this;//參數對象調用者爲當前函數體 

  備註:arguments.length表示實際傳參數,arguments.callee.length表示指望傳參數

  •   執行內存中構造函數A.prototype.constructor時,根據參數賦值,繼承原型鏈中的屬性方法。
1 a.id = arguments[1];//值爲1
2 a.name = arguments[2];//值爲undefined
3 a.constructor = A.prototype.constructor = A(){…};//繼承
  •   返回值

 1 return a; 

Ways

新建對象方法

描述

備註

使用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(){}};

構造函數模式構造屬性

原型模式構造方法

6.     RESULT – return

  當構造函數無特殊聲明return返回值時,返回實例化對象。

 

Chapter three . closure

  函數閉包是一種技術,尤爲被Lambdas語言普遍應用。Java中可使用匿名函數的方式模擬閉包。

1.     Introduce

  閉包 = 閉包函數 + 一組自由變量與名稱綁定存儲區映射,實現函數外部訪問函數內部變量的技術。

  原理:若是一個函數包含內部函數,那麼它們均可以看到其中聲明的變量;這些變量被稱爲‘自由’變量。然而,這些變量能夠被內部函數捕獲,從高階函數中return實現‘越獄’,以供之後使用。惟一須要注意的是,捕獲函數必須在外部函數內定義。函數內沒有任何局部聲明以前(既不是被傳入,也不是局部聲明)使用的變量就是被捕獲的變量。

  備註:Javascript中20%以上的博客是關於閉包概念的,這裏再也不贅述。

  弊端:應用時需分析函數變量,檢查閉包是否會互相產生干擾,檢查閉包的實例是否相同。

  利端:閉包函數實例化時只執行一次,將不須要暴露在外層環境的變量封裝在內部,減小了外部變量。

Chapter four . inherit

  在ECMAScript中,只支持實現繼承而不支持簽名繼承(接口繼承)實現繼承基本是經過原型鏈繼承。

繼承方式

示例

原型鏈繼承

Sub.prototype = new Super();

構造函數繼承

function Sub(){ Super.call(this);}

組合繼承

 

原型式繼承

Object.create(prototype)

 

Chapter five . namespace

  以上咱們接觸了全局變量和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 = …; 

  同時,儘可能在匿名函數中聲明變量而非全局環境中,併爲代碼添加更多註釋。

Chapter six . reference

  火狐開發者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/

相關文章
相關標籤/搜索