1. 程式人生 > >JavaScript權威指南(第6版)學習

JavaScript權威指南(第6版)學習

第一章 詞法結構 

  • 可選的分號

    javascript不會再所有的換行出新增分號,只有如下幾種情況會自動補全分號:

    1.上一條語句與下一條語句連線在一起出現解析錯誤時,會在上一條語句後面不全分號;

    栗子一:

       var a
       a
       =
       3
       console.log(a)

    解析過程:

    首先,var a a出現解析錯誤,在var a後面加分號;var a;

    其次,a=3console.log(a),解析出錯,在a=3後面加分號;a=3;

    最後,在console.log(a)後面加分號,console.log(a);

    最終解析為:

        var a;
       a=3;
       console.log(a);

   栗子二:

      var y=x+f
      (a+b).toString()

   解析過程:

    var y=x+f(a+b).toString(),不會出現錯誤,所以不會再第一條語句後加分號;最終結果為var y=x+f(a+b).toString();

    但是我們想要的結果是:

      var y=x+f;
      (a+b).toString();

   這個可能與我們處理邏輯不通,導致出錯。

  2.如果return,break,continue後面直接換行,會在後面自動補全分號;

   來個栗子:

  //我們要返回的是true
      {
       ......
          
       return
       true
      }

   解析的結果:

    {
      ......
      
      return;
      true;
    }
    //結果返回了null;

   3.++或者- -操作符,即可以作為字首,也可以作為字尾,但是如果不加分號,javascript會預設當做字首處理;

    來個栗子:

    a
    ++
    b
    /**最終的結果**/
    a;
    ++b;

第二章 型別,值,變數

    2.1 數字

    javascript不區分整數和浮點數,所有的數字均採用浮點數表示,採用IEEE 754標準定義的64位浮點格式表示數字,其表示範圍在[-253-253],如果超出了範圍,則無法保證低位數的精度;但是

     實際情況,javascript操作的整數則是基於32位的。

     整型值變數:

    採用十進位制和16進製表示,ECMAScript嚴格模式下禁止使用八進位制;十六進位制加字首0x或者0X;

    二進位制浮點數和四捨五入錯誤

javascript採用的是IEEE 754格式表示浮點數,它是一種二進位制表示方法,它可以精確的表示類似於1/2,1/4,1/8,.....等,但是我們常用的十進位制分數,二進位制浮點數無法表示類似於0.1這樣的小數,但是表示的值及其接近0.1

                       十進位制           二進位制
            0.1              0.0001 1001 1001 1001 ...
            0.2              0.0011 0011 0011 0011 ...
            0.3              0.0100 1100 1100 1100 ...
            0.4              0.0110 0110 0110 0110 ...
            0.5              0.1
            0.6              0.1001 1001 1001 1001 ...
      所以比如 1.1,其程式實際上無法真正的表示 ‘1.1',而只能做到一定程度上的準確,這是無法避免的精度丟失:1.09999999999999999
      
           
      /**栗子一**/
      var x=1.0-0.9;
      var y=0.1;
      console.log(x===y)//false
      console.log(x)//0.09999999999999998
           
      //那如何來避免這類 1.0-0.9 != 0.1 的非bug型問題發生呢?下面給出一種目前用的比較多的解決方案, 在判斷浮點運算結果前對計算結果進行精度縮小,因為在精度縮小的過程總會自動四捨五入: 
       
      //通過isEqual工具方法判斷數值是否相等,精度digist必須在0-20之間
      function isEqual(number1, number2, digits){
           digits = digits == undefined? 10: digits; // 預設精度為10
          return number1.toFixed(digits) === number2.toFixed(digits);
      }
      console.log(isEqual(1.0-0.9, 0.1));  //true
      
      //例如結合律。對於任意實數
            x,y,z總滿足(x+y)+z=x+(y+z)
            浮點數就不一定:
            (0.1+0.2)+0.3; //0.6000000000000001
             0.1+(0.2+0.3); //0.6
    /**
    *    浮點數的精確運算
    **/     
        //加法函式,用來得到精確的加法結果
        //說明:javascript的加法結果會有誤差,在兩個浮點數相加的時候會比較明顯。這個函式返回較為精確的加法結果。
        //呼叫:accAdd(arg1,arg2)
        //返回值:arg1加上arg2的精確結果
        function accAdd(arg1,arg2){
          var r1,r2,m;
          try{r1=arg1.toString().split(".")[1].length}catch(e){r1=0}
          try{r2=arg2.toString().split(".")[1].length}catch(e){r2=0}
          m=Math.pow(10,Math.max(r1,r2))
          return (arg1*m+arg2*m)/m
        }
        //給Number型別增加一個add方法,呼叫起來更加方便。
        Number.prototype.add = function (arg){
          return accAdd(arg,this);
        }
         
        //減法函式,用來得到精確的減法結果
        //說明:javascript的加法結果會有誤差,在兩個浮點數相加的時候會比較明顯。這個函式返回較為精確的減法結果。
        //呼叫:accSub(arg1,arg2)
        //返回值:arg1減去arg2的精確結果
        function accSub(arg1,arg2){
          var r1,r2,m,n;
          try{r1=arg1.toString().split(".")[1].length}catch(e){r1=0}
          try{r2=arg2.toString().split(".")[1].length}catch(e){r2=0}
          m=Math.pow(10,Math.max(r1,r2));
          //last modify by deeka
          //動態控制精度長度
          n=(r1>=r2)?r1:r2;
          return ((arg1*m-arg2*m)/m).toFixed(n);
        }
         
        //除法函式,用來得到精確的除法結果
        //說明:javascript的除法結果會有誤差,在兩個浮點數相除的時候會比較明顯。這個函式返回較為精確的除法結果。
        //呼叫:accDiv(arg1,arg2)
        //返回值:arg1除以arg2的精確結果
        function accDiv(arg1,arg2){
          var t1=0,t2=0,r1,r2;
          try{t1=arg1.toString().split(".")[1].length}catch(e){}
          try{t2=arg2.toString().split(".")[1].length}catch(e){}
          with(Math){
            r1=Number(arg1.toString().replace(".",""))
            r2=Number(arg2.toString().replace(".",""))
            return (r1/r2)*pow(10,t2-t1);
          }
        }
        //給Number型別增加一個div方法,呼叫起來更加方便。
        Number.prototype.div = function (arg){
          return accDiv(this, arg);
        }
         
        //乘法函式,用來得到精確的乘法結果
        //說明:javascript的乘法結果會有誤差,在兩個浮點數相乘的時候會比較明顯。這個函式返回較為精確的乘法結果。
        //呼叫:accMul(arg1,arg2)
        //返回值:arg1乘以arg2的精確結果
        function accMul(arg1,arg2) {
          var m=0,s1=arg1.toString(),s2=arg2.toString();
          try{m+=s1.split(".")[1].length}catch(e){}
          try{m+=s2.split(".")[1].length}catch(e){}
          return  Number(s1.replace(".",""))*Number(s2.replace(".",""))/Math.pow(10,m)
        }
        //給Number型別增加一個mul方法,呼叫起來更加方便。
        Number.prototype.mul = function (arg){
          return accMul(arg, this);
        }
        <br>//驗證一下:
        console.log(accAdd(1.79, 0.12));  //1.91
        console.log(accSub(2.01, 0.12));  //1.89
        console.log(accDiv(0.69, 10));    //0.069<br>console.log(accMul(1.01, 1.3));   //1.313  

   注:詳細參考連結:http://www.cnblogs.com/ppforever/p/5011660.html

   Math常用方法:

        Math.round(num)//四捨五入
        Math.ceil(num)//向上求整
        Math.floor(num)//向下取整
        Math.random()//[0,1)隨機數
        num.toFixed(digest)//對num保留digest位小數
        Math.pow(2,53)//2^53
        Math.pow(2,1/3)//2的立方根
        Math.sqrt(2)//2的平方根
        Math.max(x,y)//取最大值
        Math.min(x,y)//取最小值
        Math.PI//圓周率

    2.2 null與undefined

      typeof null ==='object'//空物件
      typeof undefined === 'undefined'//未初始化
      null==undefined//true;
      null===undefined//false

    javascript中返回undefined的場景:

       //變數未初始化
       var a;  a===undefined
       //函式沒有傳遞實參導致形參未空
       function fun(x){
        x===undefined;
       }
       //return;     
      (function f(){
        return;
       })()===undefined
       但是在定義變數時,對於沒有初始化的變數最好使用null;

    2.3全域性變數

    在javascript中,全域性變數定義的方式有三種:
    (1)在函式外部定義
        var a=1;
        function(){}
    (2)在函式內部未定義但是賦值
        (function f(){
           a=1;//此處未用var 宣告,表示全域性變數
        })(); 
    (3)在window上定義屬性
        window.a=1;
    javascript中連等號的執行順序:從右向左賦值
        A=B=C;
        等價於
        B=C;
        A=B;
     來個栗子:
     猜一猜,下面的列印結果是什麼?
     (function(){
      var a=b={n:1};
      a.n=2;
      console.log(a);
      console.log(b);
    })();
    console.log(b);
    console.log(a);
    
    //答案:{n:2},{n:2},{n:2},Uncaught ReferenceError: a is not defined
    a是區域性變數,b是全域性變數。
    參考連結:
    http://www.tuicool.com/articles/vUFbeu
    http://www.jb51.net/article/36548.htm

  2.4 包裝物件

    javascript為什麼要引入包裝物件?

    在回答這個問題之前先來個栗子熱熱身:

        var str='test';
        str.name='awng';
        console.log(str.name);
        
        //結果是'test'
        
        javascript中有三種包裝物件,String、Number、Boolean,對應的基本型別就是string,number,boolean。在使用基本型別時,我們希望能向物件型別一樣,通過.或者[]來訪問屬性或者方法時,此時就要藉助包裝物件。
        比如:
        var str='test';
        console.log(str.length)//4
        我們知道,str時基本型別,不是物件型別,沒有length屬性,此處過程:
            1.是想建立一個臨時的String物件var temp=new String('test');
            2.在使用零食物件操作屬性length;temp.length;
            3.最後在console.log()執行完後銷燬temp.
         此時在回頭看看栗子
         由於在str.name='awng';執行完成後,臨時物件被銷燬;
         在執行console.log(str.name)時候又會重新建立一個臨時物件,該臨時物件的name屬性為undefined;

    2.5 型別轉換

        typeof Number('111')==='number'
        typeof String('111')==='string'
        typeof Boolean('111')==='boolean'
        
        //轉換成整型,radix:0X代表十六進位制,0代表八進位制 
        parseInt(string, radix)
        1.忽略字串前面的空格,直至找到第一個非空字元
                    2.如果第一個字元不是數字符號或者負號,返回NaN
                    3.如果第一個字元是數字,則繼續解析直至字串解析完畢或者遇到一個非數字符號為止
                    4.如果上步解析的結果以0開頭,則將其當作八進位制來解析;如果以0x開頭,則將其當作十六進位制來解析
                    5.如果指定radix引數,則以radix為基數進行解析
                parseFloat(string)函式,將字串轉換為浮點數型別的數值。
                
                
                引用型別轉換為布林,始終為true
                引用型別轉換為字串
                1.優先呼叫toString方法(如果有),看其返回結果是否是原始型別,如果是,轉化為字串,返回。
                2.否則,呼叫valueOf方法(如果有),看其返回結果是否是原始型別,如果是,轉化為字串,返回。
                3.其他報錯。
                引用型別轉化為數字
                1.優先呼叫valueOf方法(如果有),看其返回結果是否是基本型別,如果是,轉化為數字,返回。
                2.否則,呼叫toString方法(如果有),看其返回結果是否是基本型別,如果是,轉化為數字,返回。
                3.其他報錯。
                    栗子:
                    var a = {};
                    console.dir(a.toString());  // "[object Object]"
                    console.dir(a.valueOf());  // 物件本身
                    var b = [1, 2, 3];
                    console.dir(b.toString());  // "1,2,3"
                    console.dir(b.valueOf());  // 物件本身
                    var c = [[1],[2]];
                    console.dir(c.toString());  // "1,2"
                    console.dir(c.valueOf());  // 物件本身
                    var d = function() {return 2};
                    console.dir(d.toString());  // "function() {return 2}"
                    console.dir(d.valueOf());  // 物件本身
                
                雙等號= =,如果兩邊型別不同,會有隱式轉換髮生。
                1,null和undefined,相等。
                2,數字和字串,轉化為數字再比較。
                3,如果有true或false,轉換為1或0,再比較。
                4,如果有引用型別,優先呼叫valueOf。
                5,其餘都不相等。
                栗子:
                console.log([[2]] == '2')
                原因如下:
                    [[2]]的valueOf是物件本身,不是基本型別。
                    嘗試呼叫toString的結果是'2'。
                    因此變成了'2'和數字2的比較。根據第2條,相等。
                參考文章:http://www.jb51.net/article/103830.htm

    2.6 javascript作用域和變數提升

       先來幾個栗子嚐嚐:

        栗子1:
        var foo = 1;  
        function bar() {  
          if (!foo) {  
            var foo = 10;  
          }  
           alert(foo);  
        }  
        bar(); 
        
        //10
        
        栗子2:
        var a = 1;  
        function b() {  
                a = 10;  
                return;  
                function a() {}  
                }  
                b();  
                alert(a);
                
                //1
                以上栗子表現出,在javascript中,無論執行中程式碼是否能夠執行到,函式宣告和變數宣告總是會被直譯器悄悄地被“提升”到方法體的最頂部;
                栗子3:
                function foo() {  
                    var x = 1;  
                    if (x) {  
                        (function () {  
                            var x = 2;  
                        }());  
                    }  
                   console.log(x);
                }  
                
                //1
                javascript沒有塊狀作用域,但是可以通過函式來模仿塊狀作用域
                
                栗子4:
                (function(){
                    
                    var a=1;  
                    function a(){     
                    }  
                    console.log(a);
                    
                })();
                
                //1
                函式的提升優先於變數的提升
                
                栗子5:
                baz();// valid
                spam();//ReferenceError "spam is not defined"
                var baz = function spam() {}; // 命名函式,只有baz被提升,spam不會被提升。
                
                總結:
                如果變數在函式體類宣告,則它是函式作用域。否則,它是全域性作用域(作為global的屬性)。
                變數將會在執行進入作用域的時候被建立。
                塊不會定義新的作用域,只有函式宣告和程式(譯者以為,就是全域性性質的程式碼執行)才會創造新的作用域。
                變數在建立的時候會被初始化為undefined。
                如果變數宣告語句裡面帶有賦值操作,則賦值操作只有被執行到的時候才會發生,而不是建立的時候。

 參考連結:http://blog.csdn.net/sunxing007/article/details/9034253#

 第三章 表示式和運算子

    3.1 陣列的初始化

      var arr=new Array(5)//建構函式,[undefined × 5]
      var arr=[1,2,3];//字面量
      //初始化可以用,隔開,自動填充undefined,陣列最後一個是逗號,不會建立一個新元素
            arr=[,,,,];
            console.log(arr)//[undefined*4]
      
      //可以跳過中間的索引,給指定的索引賦值,javascript沒有陣列越界;
      var arr=[1,2,3]
      arr[5]=10;
      console.log(arr)//[1,2,3,undefined,10]
      //可以通過length屬性,刪除後面的字串
      arr.length=2;
      console.log(arr)//[1,2]
      arr.length=5;
      console.log(arr)//[1,2,undefined*3]

   陣列中for迴圈和for in 的區別

   來個栗子:

    var arr=['a','b','c'];
    for(var i=0;i<arr.length;i++){//i為索引,是整型1,2,3
         console.log(i+"==="+typeof i +"===="+arr[i]);
    }
    
    0===number====a
    1===number====b
    2===number====c
    
    for(var a in arr){//a是物件的key,字串型別'0','1','2'
        console.log(a+"==="+typeof a +"===="+arr[a]);
    }
    
    0===string====a
    1===string====b
    2===string====c
    
    for in 是將陣列當做物件處理,陣列也是物件,輸出的value都是相同的;

   再來個栗子:

    Array.prototype.d='d';
   var arr=['a','b','c'];
    for(var i=0;i<arr.length;i++){//i為索引,是整型1,2,3
         console.log(i+"==="+typeof i +"===="+arr[i]);
    }
    
    0===number====a
    1===number====b
    2===number====c
    
    for(var a in arr){//a是物件的key,字串型別'0','1','2'
        console.log(a+"==="+typeof a +"===="+arr[a]);
    }
    
    0===string====a
    1===string====b
    2===string====c
    d===string====d//原型上的屬性
    
    總結:for in 會便利自身個原型上的屬性;但是可以通過hasOwnProperty排除原型上的屬性
    for(var a in arr){
        if(arr.hasOwnProperty(a)){
            console.log(a+"==="+typeof a +"===="+arr[a]);
        }
    }
    
    0===string====a
    1===string====b
    2===string====c

   這樣難道for 與 for in 相同了嗎?答案是no

   再來個栗子,我保證這是陣列最後一個栗子了,哈哈:

    既然陣列也是物件,我能不能給它新增屬性呢?
    var arr=[1,2,3]
    arr['-10']=-10;
    console.log(arr)
    猜猜結果是什麼?
    //[1,2,3]
    神馬情況,我新增的屬性怎麼沒有了!!!
    我們通過for迴圈來看看
    for(var i=0;i<arr.length;i++){
        console.log(a[i]);
    }
    //1,2,3
    果然是沒有新增的屬性!!!
    不急,我們來檢視arr的所有屬性
    Object.keys(arr)
    //["0", "1", "2", "-10"]
    尼瑪,明明有呀,怎麼就沒顯示出來呢,我們在用for in 試試看
    for(var a in arr){
        if(arr.hasOwnProperty(a)){
            console.log(a+"==="+typeof a +"===="+arr[a]);
        }
    }
    0===string====1
    1===string====2
    2===string====3
    -10===string====-10
    我去,終於出現了。

    3.2 陣列常用的操作

    陣列元素的新增

    arrayObj. push([item1 [item2 [. . . [itemN ]]]]);// 將一個或多個新元素新增到陣列結尾,並返回陣列新長度
    arrayObj.unshift([item1 [item2 [. . . [itemN ]]]]);// 將一個或多個新元素新增到陣列開始,陣列中的元素自動後移,返回陣列新長度
    arrayObj.splice(insertPos,0,[item1[, item2[, . . . [,itemN]]]]);//將一個或多個新元素插入到陣列的指定位置,插入位置的元素自動後移,返回""。

        陣列元素的刪除

    arrayObj.pop(); //移除最後一個元素並返回該元素值
    arrayObj.shift(); //移除最前一個元素並返回該元素值,陣列中元素自動前移
    arrayObj.splice(deletePos,deleteCount); //刪除從指定位置deletePos開始的指定數量deleteCount的元素,陣列形式返回所移除的元素

        陣列的擷取和合並

    arrayObj.slice(start, [end]); //以陣列的形式返回陣列的一部分,注意不包括 end 對應的元素,如果省略 end 將複製 start 之後的所有元素
    arrayObj.concat([item1[, item2[, . . . [,itemN]]]]); //將多個數組(也可以是字串,或者是陣列和字串的混合)連線為一個數組,返回連線好的新的陣列

    陣列元素的排序

    arrayObj.reverse(); //反轉元素(最前的排到最後、最後的排到最前),返回陣列地址
    arrayObj.sort(); //對陣列元素排序,返回陣列地址

    陣列的拷貝

    arrayObj.slice(0); //返回陣列的拷貝陣列,注意是一個新的陣列,不是指向
    arrayObj.concat(); //返回陣列的拷貝陣列,注意是一個新的陣列,不是指向

    陣列元素的字串化

    arrayObj.join(separator); //返回字串,這個字串將陣列的每一個元素值連線在一起,中間用 separator 隔開。

3.3  陣列型別判斷

    typeof arr ==='object' && arr instanceof Array
    typeof arr ==='object' && arr.constructor ===Array
    Object.prototype.toString.call(arr)==='[object Array]'