深刻理解JavaScript程序設計

今天沒事情回顧了一下我在去年4-6月份學習JavaScript程序設計的筆記。發現書到用時方恨少,感受本身學的還不夠深,準備抽時間啃一下《JavaScript高級程序設計》,同時深刻了解一下avaScript程序設計中幾個比較難懂的模塊。javascript

1.預編譯html

預編譯主要用來解決全部的JavaScrip代碼執行順序的問題。javascript相對於其它語言來講是一種弱類型的語言,在其它如java語言中,程序的執行須要有編譯的階段,而在javascript中也有相似的「預編譯階段」,瞭解javascript的預編譯,將有助於在寫js代碼過程當中的思路總結。java

1.1 函數預編譯數組

舉個例子來說解一下(舉得例子比較難,不太好懂,但能很好的講解出預編譯的過程):瀏覽器

function test(a){   //在這裏a是形參
 console.log(a); var a = '123';  //a在被聲明而且賦值
 console.log(a); function a(){}; //a在這裏被函數聲明
 console.log(a); var b = function (){ return '函數b'; }(); console.log(b); } test(1);

這個例子很是的麻煩,它的形參,變量名,函數名用的都是 a,就會出現了覆蓋執行的問題,下面咱們來解決這個問題。服務器

若是是順序執行會依次打印:1 ,123,function a(){},函數b。實際結果輸出的是:function a(){},123,123,函數b。cookie

  

之因此會這樣,是由於在預編譯的時候執行前上下文規定了它執行的順序。閉包

預編譯主要分爲四部:框架

(1)創立AO對象。在函數上下文中,咱們用活動對象AO(activation object)來表示變量對象。函數

(2)找到形參和聲明變量,將變量名和形參名做爲AO的屬性名。值暫時爲undefined。

(3)將實參和形參統一。

(4)在函數體裏面找到函數聲明,值賦予函數體。

第一步:AO對象創建,AO{ };

第二步:a做爲形參出現一次,做爲聲明變量出現一次。b做爲聲明變量出現一次。同一個值聲明或者做爲形參屢次咱們只寫一個。因此如今AO就是:

AO{

  a:undefined,

  b:undefined

}

第三步:將實參和形參統一。實參就是test(1)裏面的1,形參是test(a)裏面的a,因此如今AO就是:

AO{

  a:1,

  b:undefined

}

第四步:在函數體裏面找到函數聲明,值賦予函數體。函數體只有a(),因此將函數賦值給a,因此如今AO就是:

AO{

  a:function a(){},

  b:undefined

}

預編譯結束,根據AO和頁面代碼開始執行,因此:

開始執行代碼的時候,AO是:
AO{
 a:function a(){},
 b:undefined
}
因此第一次打印是:function a(){};
代碼繼續執行到var a = '123';
因爲預編譯已經聲明,因此這一行只執行賦值語句 a='123';
因此第二次打印是:123
代碼繼續執行到function a(){};函數聲明已經在預編譯執行完畢,不執行
因此第三次打印仍是:123
代碼繼續執行到var b = function (){ return '函數b';}();
預編譯已經聲明,因此這一行只執行賦值語句 b = function (){ return '函數b';}();
因此第四次打印的是b,b是一個當即執行函數,函數當即執行的返回值就是:函數b

1.2 全局的預編譯

全局的預編譯會生成GO(global object)全局變量,除了沒有第三步,其餘的和函數的預編譯同樣,GO裏面的的值其實就是window裏面的值,都是全局變量。

結論:預編譯能解決全部的代碼執行順序的問題。即便不太明白JavaScript的預編譯也不是特別影響咱們平常對JavaScript的編寫,但明白之後咱們對JavaScript代碼執行的理解會有很大的幫助。

 

2.閉包

內部函數保存到外部致使閉包。閉包這個問題也不是特別的容易遇到。我開發將近一年了也就遇到了一次,數據顯示錯誤,當時我還覺得是本身代碼寫得有問題,最後一點點debugger,才發現是閉包問題,查閱資料才解決這個問題。

閉包產生的緣由是:內部函數保存到外部時做用域鏈沒有釋放。

做用域鏈簡介:[[scope]]就是咱們常說的做用域,它存儲了執行期上下文的集合。簡單的說,做用域就是變量與函數的可訪問範圍,即做用域控制着變量與函數的可見性和生命週期。在JavaScript中,變量的做用域有全局做用域和局部做用域兩種,局部做用域又稱爲函數做用域。做用域鏈包含了咱們在預編譯時候介紹的AO{}和GO{}的全部的值。在函數執行結束後做用域鏈會直接銷燬,防止佔用內存。

在demo=function b(){ num++;console.log }時,num時函數a裏面的變量,不是全局變量。爲何demo在執行時num時a裏面的值?經過這個例子咱們能夠發現函數a的做用域鏈並無銷燬。這就是閉包。若是不太明白,我再給你們舉個例子:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
    <script type="text/javascript">
        function create(){ var arr = new Array(); //定義個空數組
            for(var i=0;i<10;i++){ arr[i] = function(){ //給數組賦的值是一個函數
                    return i; }; } return arr; //這裏返回的是數組,數組的值是一個函數 function (){return i;}
 } var Arr = create(); for(var j=0;j<10;j++){ console.log(Arr[j]()); //這裏咱們把數組遍歷執行一下 打印結果 10 10 10 10 10 10 10 10 10 10
 } </script> 
    </body>
</html>

打印的是 10 10 10 10 10 10 10 10 10 10,若是你認爲應該打印 1 2 3 4 5 6 7 8 9 10 說明你可能還不是特別理解閉包。

由於在 var Arr = create()時 這個函數已經執行完畢時返回了一個數組,裏面包含了是個數組function (){return i;},可是這個時候create()的做用域鏈並無釋放,因此裏面定義的i仍是等於10;因此再遍歷的時候只會打印十個10。

咱們在控制檯看一下:

在這裏咱們看到了[[scope]]做用域,它有兩個值一個Global {} 這是咱們都知道是全局變量對象,另外一個是Closure (create){i=0},Closure就是閉包的意思,也是閉包產生的。這是函數create的局部變量,咱們能夠清晰的看到i=10,函數create的做用域鏈沒有釋放。因此只會打印十個10。

[[scope]]這個東西若是不是閉包,當函數執行完後就銷燬了,你根本看不到它。

記住兩句話就能解決閉包的問題,1.內部函數保存到外部就是閉包。2.閉包的函數做用域鏈沒有刪除,一直存在。

閉包的危害:閉包會攜帶了函數的做用域,因此會佔用更多的內存。因爲函數做用域沒有釋放可能致使結果和咱們預期不同。

閉包的優勢:任何事情都有兩面性,有危害就有好處,要否則它不會一直存在。例如:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
    <script type="text/javascript">
        function create(){ var count = 1; function add(){ count++; console.log(count) } return add; } var Add = create(); Add(); // 2 
 Add(); // 3
    </script> 
    </body>
</html>

經過調用函數Add,咱們實現了數據的累加,count原本是局部變量,如今在函數create外面也能正常調用count屬性。實現了外部函數調用局部變量。

 

3.面向對象的程序設計

3.1對象的建立

面向對象的語言有一個一個標誌,那就是都有類的概念,而且能夠經過類來建立多個具備相同屬性的和方法的對象。

在JavaScript裏把對象定義爲:「無序屬性的集合,其屬性能夠包含基本值、對象或者函數。」

咱們能夠經過建立Object實例,而後再添加屬性或方法。也能夠直接在建立對象時就帶有一些特徵值。舉個例子:

這兩種方式均可以建立對象,第二種更加的方便一點。

3.2對象的屬性類型

ECMAScript中有兩種屬性:數據訪問和訪問器屬性。

數據屬性就是控制對象屬性的刪除,修改,遍歷,查取。

[[Configurable]]:表示可否經過delete刪除。

[[Enumerable]]:表示可否經過for-in遍歷。

[[Writable]]:表示可否修改屬性的值。

[[Value]]:表示這個屬性的數據值。

前三個的若是不指定,默認值都是true,[[Value]]是添加屬性賦予的值。

只有經過Object.defineProperty()方法才能控制。這個方法須要接受三個參數:對象名,屬性名和一個描述符對象。如圖所示:

咱們能夠從控制檯看到這個,這個方法是[native code]源代碼,而且也能看到它的傳參。多數狀況下咱們都接觸不到這些數據屬性的這些高級功能,不過對於咱們理解JavaScript對象卻很是有用。

我再給你們舉個例子:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <script type="text/javascript">
        //直接建立對象加屬性
        var person = { name : 'liu', age : 23, job : 'programmer', myName : function(){ console.log(person.name); } } /*經過這個方法我從新給person這個對象的name屬性賦值,並禁用刪除修改*/ Object.defineProperty(person,'name',{ writable : false, //禁用修改
 configurable : false, //禁用刪除
 value : 'liuzhou'        //從新賦值
 }) alert(person.name); // 'liuzhou'
 person.name = 'liu'; alert(person.name); // 'liuzhou'
        delete person.name; alert(person.name); // 'liuzhou'
        </script>
    </body>
</html>

若是有興趣能夠複製去看一下,經過這個方法我從新給person這個對象的name屬性賦值,並禁用刪除修改。因此下面的刪除,修改就不能實現了。

訪問器屬性主要包含一對getter和setter函數。

函數讀取會調用getter函數,函數寫入會調用setter函數。訪問器屬性也不能直接定義,須要Object.defineProperty()方法來定義。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <script type="text/javascript">
        //直接建立對象加屬性
        var person = { name : 'liu', age : 23, edition:1. } /*經過這個方法我新建一個year屬性,若是大於2018,讓person的age和adition都加一*/ Object.defineProperty(person,'year',{ //對象的year屬性
 get:function(){ }, set:function(newValue){ //形參,叫什麼名字都行
                if(newValue>2018){ this.age += 1; person.edition += 1; } } }) person.year = 2019; //增長year屬性會調用set方法;
 console.log(person); </script>
    </body>
</html>

打印效果圖:

這是訪問器屬性的常見方式,即設置的值會致使其餘屬性發生變化。咱們也能夠經過Object.defineProperties()一次定義多個屬性,若是感興趣本身能夠百度去看一下。

 

4.ECMAScript的核心

 ECMAScript是JavaScript的核心所在,ECMAScript又包括最重要的兩個部分BOM(瀏覽器對象模型)和DOM(文檔對象模型)。

 4.1 BOM(瀏覽器對象模型)。

 window是js中很是重要的對象,因爲window是ECMAScript規定的Global對象,因此咱們在全局定義的任何一個變量,函數和對象,window都能訪問到.

例如:咱們在全局定義一個a並給它賦值100;

<script type="text/javascript">
    var a = 100; console.log('a = ' + a); console.log('window.a = ' + window.a); console.log('this.a = ' + this.a); </script>

咱們打印了三個值,發現了這三個值都是同樣的,在全局window就是global,this也指向global,因此都打印了100;

咱們又在控制檯打印了一下window,發現了除了咱們定義的a,還有不少的方法,這些Window 對象表示一個瀏覽器窗口或一個框架。在客戶端 JavaScript 中,Window 對象是全局對象,全部的表達式都在當前的環境中計算。也就是說,要引用當前

窗口根本不須要特殊的語法,能夠把那個窗口的屬性做爲全局變量來使用。例如,能夠只寫 alert,而沒必要寫 window.alert。

簡單介紹一下window的基本方法:

alert():警告框;confirm():確認框。點擊肯定返回true,點擊取消返回false;

setTimeOut(要執行的函數,時間):指定時間後執行單次方法;setInterval(要執行的函數,時間):每隔指定時間執行一次方法(不清除執行屢次);

clearInterval(id):關閉interval執行的函數;clearTimeout(id):關閉指定的timeout執行的函數;

open():使用此方法能夠打開當前頁面的子頁面,可設置子頁面窗口的大小,close():在子頁面中使用此方法,關閉使用open打開的子頁面。

focue():input框聚焦事件;blue():input失焦事件;

print():打印當前頁面,有的遊覽器預覽打印頁面,有的直接打印。

BOM特殊的幾個對象

location(): 這是比較經常使用的方法。它既是window的對象也是BOM的對象,

location的屬性名及其說明:

host:服務器名稱和端口號;hostname:不帶端口號的服務好;

href:當前頁面的完整的URL; prot:端口號;protocol:頁面協議。'http' or 'https'

跳轉頁面:window.location = "http://baidu.com";   location.href = "http://baidu.com";

navigator對象的屬性一般用來檢測顯示網頁的瀏覽器類型,好比:瀏覽器名稱,版本,cookie是否使用......

history對象保存着用戶上網記錄,可使用go()方法在歷史記錄中跳轉。能夠向前也能夠後退,

history.go(-1), history.back(),history.forward(),history.go(1),history.go(0), 1,forward()表明前進一頁,-1,back()表示後退一頁,0表示刷新頁面。

history.go('inex.html');跳到指定頁面。自定義前進後退按鈕時經常使用。

1.2 DOM(文檔對象模型)

相關文章
相關標籤/搜索