Javascript王者歸來

BubbleM發表於2017-04-18

基於物件。JavaScript擁有物件,可以包含資料和處理資料的方法。物件可以包含其他物件。它沒有類,但卻有構造器可以做類能做的事,包括扮演類變數和方法的容器的角色。它沒有基於類的繼承,但它有基於原型的繼承。

目前絕大多數瀏覽器中都嵌入了某個版本的JavaScript直譯器。這樣它就允許可執行的內容以JS的形式在使用者客戶端瀏覽器中執行。

<noscript>您的瀏覽器不支援JavaScript,請檢查瀏覽器版本或者安全設定,謝謝!</noscript>

<noscript></noscript>是一種防禦性編碼,如果使用者瀏覽器不支援JS或者設定了過高的安全級別,那麼就會顯示出相應的提示資訊,避免在使用者不知情的情況下停止執行或者得到錯誤結果。

JS通過瀏覽器物件介面訪問和控制瀏覽器元素,通過DOM介面訪問和控制HTML文件,通過給文件定義“事件處理器”的方式響應由使用者觸發的互動行為。Document是JS訪問頁面文件物件的主要方式。Window提供了對瀏覽器、視窗、框架、對話方塊以及狀態列的訪問方法。

要避免定義過多的全域性變數。在JS中,利用閉包是一種代替臨時變數的好習慣。
在某些特定情況下,定義在函式體內部的匿名函式在執行的過程中形成“閉包”。

void是JS的一個特殊的運算子,它的作用是捨棄任何參數列達式的值,這意味著要求解析器並計算參數列達式內容,但是卻忽略其結果。如果刻意去檢查void運算的返回值,會發現它返回一個undefined標記(事實上任何一個不帶return指令的函式運算的預設返回值都是undefined)在瀏覽器的預設行為中,undefined阻止了頁面的跳轉。

undefined在一定情況下可以取代void(0);

undefined代表“無”;null代表“空”。空依然是一種存在,而無則是存在的對立面。由於JS沒有強制檢驗物件存在的機制,所以它承認無的概念。任何一個未經定義和使用的標識,均可以用“無”來表示。當你直接引用一個未宣告的標識,或者宣告瞭一個變數卻未對其進行賦值,那麼typeof操作返回的結果將是undefined。
在瀏覽器位址列輸入:

JavaScript:alert(typeof(x));   >>> undefined 未宣告
JavaScript:var x;alert(typeof(x));  >>> undefined 未賦值
JavaScript:var x=null;alert(typeof(x));   >>>object

客戶端JS的生命週期起始於瀏覽器開始裝載某個請求的特定資料,結束於瀏覽器發起一個新的請求(通常意味著頁面的跳轉或者重新整理)。

進一步細分:客戶端生命週期可以劃分為從頁面資料被裝載到頁面資料裝載完畢的初始化階段以及頁面資料裝載完畢一直到新的請求被髮起之前的執行階段。在前一個階段裡,JS程式碼被瀏覽器解析,執行環境被初始化,函式和閉包被建立,而那些可以被立即執行的指令被執行並實時地得到結果。在後一個階段裡,完成初始化的程式化環境進入了一個預設的等待訊息的迴圈,捕獲使用者操作引發的事件並做出正確響應。

將JS程式碼放<head> 頭部是在生命週期的初始化階段執行(更安全,指令不會對裝載期的文件內容產生影響,指令碼指令被註冊到body的onload事件中執行<body onload="PageLoad()"> 元素的onload事件將在元素被完全載入後由瀏覽器發起);放在<body> 是在執行期內執行。(可能會導致文件資料不能載入完全)

一個比較好的習慣就是把除宣告之外的所有的指令碼指令都放到執行階段來執行,這樣避免了因為初始化期間的DOM元素載入失敗或者低階的次序問題而導致指令碼失效。

提交表單操作是瀏覽器提供的一種資料互動的預設方式。現在更多的應用系統採用XML HTTP作為主要的資料互動方式。XML HTTP逐漸取代傳統的提交表單,標誌著Ajax技術的日趨成熟。

學會合理利用禁忌所帶來的安全性,只有在必要的時候才去破解它們,是成為一個優秀程式設計師所必需掌握的技能。

利用JavaScript的常見攻擊有以下幾種:
1.偽造表單提交目的地,從而竊取資料。
HTML表單的提交有form的action屬性決定,而JS具備從客戶端修改form的action的能力。

<body>
    <form name="myForm" action="a.action">
        <input name="data" type="text" value="a"/>
        <input type="submit"/>
    </form>
</body>

以上程式碼定義了一個將資料提交到a.action的表單,在瀏覽器中訪問該頁面,點選提交表單按鈕,input框中的內容將被提交到a.action處理域。但是在提交表單之前,先清除瀏覽器中原位址列中的內容,並輸入JavaScript:document.myForm.action="b.action;void(0);" 之後回車,那麼資料實際將被提交到b.action處理域中。

2.偽造資料,繞過合法性驗證

<form name="myForm" action="a.html" onsubmit="return !/\s+/.test(document.myForm.textData.value) && /\d+/.test(document.myForm.numData.value) || alert('請輸入正確的格式!') || false;">
    <input name="textData" type="text"/>
    <input name="numData" type="text"/>
    <input type="submit"/>
</form>

以上程式碼對錶單的合法輸入進行校驗,要求使用者輸入的textData域非空,並且numData域為數字串,如果輸入非法的話則彈出提示資訊阻止表單的提交。
在瀏覽器 輸入JavaScript:document.myForm.onsubmit=function(){return true;};void(0); 輕而易舉地便讓表單校驗失效。
所以,依賴客戶端的校驗是不安全的。

3.採集資料,竊取網頁內容
將一些合法網站上的資料內容通過HTTP請求等方式獲取併發布到自己的網頁上。
最原始的資料採集源於瀏覽器對框架的支援。由於主流瀏覽器上支援任意個數的巢狀框架,因此,一些人將他人制作的網頁嵌入自己頁面的框架中,作為自己網站內容的一部分。網路上的“盜鏈”形式的資源竊取由來已久,防禦手段是在頁面上加入如下程式碼:

<script>
    if(self != top){
        top.location = self.location;
    }
</script>

利用JS的特性阻止頁面文件出現在框架內。當然也有對應手段同樣用JS使得上述程式碼失效(利用XML HTTP請求頁面之後將其中的這一段JS程式碼遮蔽掉)

安全:採用編碼後的資料作為內容,再通過特定的模板生成靜態的HTML/XML頁面文件,最後利用客戶端JS將文件動態輸出。
這裡寫圖片描述

絕招:JavaScript:document.body.outerHTML

這裡寫圖片描述

按照獨立功能的劃分將一組相互依賴的或者功能相近的模組寫在一個<script>塊內,將功能相對獨立彼此孤立的程式碼分開寫入多個<script>塊。至於一組通用的功能,更好的辦法是寫入一個獨立的檔案,甚至,如果必要的話,將他們封裝入一個物件中。
推薦一個比較好的規範:
對於頁面上的JavaScript程式碼來說,將全域性變數(如果有的話)和全域性變數的初始化放在一個單獨的<script> 標籤裡,置於<head><body> 之間,並且可以的話,儘量採用額外的屬性來標記它們,例如<script region="global"> 。將閉包和孤立函式放在<head> 標籤的末尾(急結束標記</head>之前)。
將頁面裝載期間執行的指令、DOM物件初始化以及DOM相關的全域性引用賦值操作放在<body> 標籤的末尾。
事實上應儘量避免全域性DOM引用,因為瀏覽器通常很難釋放它們消耗的記憶體,換句話說,儘量不要讓你的指令碼出現在結束標記</body>之前。
將需要引用到的外部JavaScript檔案按照相互依賴的先後次序放在<head>標籤中的<meta>標記之後,<title>標記之前。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <script type="text/javascript">引用外部CSS</script>
    <title>Document</title>
    <script>
        //放置函式和閉包
        function add(x,y){
            return x+y;
        }
    </script>
</head>
<script>
    //這裡放置全域性變數
    var a = 100;
    var b = 200;
</script>
<body>
    <h1></h1>
    <script>
        //這裡放置程式實際執行的程式碼
        document.getElementByTagName("h1")[0].innerHTML = add(a,b);
    </script>
</body>
</html>

<script>標籤提供了一個不太常用的屬性defer,可以顯示生命指令碼在頁面裝載完成之後才被執行。defer最大的好處是當你嵌入一段指令碼時,不必考慮它所依賴的dom物件是否被成功裝載完成。不過不主張採用defer,因為當程式碼中有alert或者document.write類似的輸出時,defer的表現有些奇怪。

閉包經典

function A(){
            alert(1);
        }
        A();
        function A(){
            alert(2);
        }
        A();
        A = function(){
            alert(3);
        } 
        A();                          >>>>>2,2,3

依賴和衝突:
假如a.js中需要一個名叫x的物件,而這個x又在b.js中被定義,就產生了a.js對b.js的依賴;
假如c.js和d.js中都定義了一個叫做y的物件,而碰巧它們對y的解釋是不同的,就產生了c.js和d.js的衝突。

面向介面就是一種很好的避免衝突的模式。
對於管理依賴的問題,在檔案頭部進行充分的描述和預防性地檢測是一個比較妥當的方案。

常見的標準字符集有ASCII、ISO Latin-1、GBK、Unicode。其中ASCII是7位編碼的字符集,它基本上只適用於英語,8位的ISO-Latin-1則支援大部分拉丁語系的語種,而16位編碼的GBK和Unicode則充分支援了漢語系的東方語種。

有經驗的程式設計師僅僅通過ONLINE、Online、online就能區別出它們分別代表一個常量、一個類和一個區域性變數、屬性或者方法。

HTML標籤名和屬性並不區分大小寫。但是JavaScript訪問DOM元素時卻區分大小寫。這意味著HTML的不規範書寫方式可能直接影響到JavaScript的正確執行。

<button onclick="alert('a')">a</button>
<BUTTON onClick="alert('b')">b</BUTTON>

document.getElementsByTagName("button");//只能取到a,取不到b,因為JS對元素標籤的大小寫敏感。
而DOM屬性b.onClick為空,b.onclick才有正確的值,這是因為JS對於DOM事件型別名採用小寫。

行匹配原則:如果一行程式碼能夠成為完整的句子,不管它是否以分號結束,都將被作為預設了分號的完整句子來對待,這樣容易造成問題。

JavaScript是一種“弱型別”的語言,具體表現為JavaScript的變數可以儲存任何型別的值。即,資料型別和變數是不繫結的,變數的型別通常要到執行時才能決定。
建議對所有變數都遵循先宣告再賦值的原則。在一些特殊情況下,如某些物件導向和麵向介面方法以及閉包的某些特殊用法,允許不宣告直接使用全域性變數。閉包和函式是獨立的域。

對於未宣告也未賦初值的變數,如果直接使用,會丟擲一個系統級別的Error ,唯一的例外是typeof操作,對於typeof操作,任何一個未賦初值的識別符號,不論是否已經被宣告,都將返回一個字串 undefined作為結果。

//a未宣告
alert(typeof(a));   //undefined
alert(a);   //Error

typeof這個特性可以用來判斷和處理指令碼程式的某些特性之間的依賴。如判斷某個物件或者某個域是否已經被宣告過。

if(typeof(System) == "undefined"){
    System = new Object();
}
//判斷System.Core域是否已經被宣告過
if(typeof(System.Core) != "undefined"){
    do sth .. //如果已經宣告過了,做一些處理,以防止意外地覆蓋了原始域
}

變數的作用域
對變數的查詢總是從當前域開始,遞迴向上查詢各級巢狀的父域,最後到達全域性。

閉包

function step(a){
    return function(x){
        return x+a;//內層的閉包可以訪問外層的區域性引數a
    }
}
var b = step(10);
alert(b(20));//得到30
var c = step(20);
var c = step(20);
alert(c(20)); //得到40

算數運算子
“+”總是把物件轉換成可以進行加法運算或者進行連線操作的數值或字串,這一轉換是通過呼叫物件方法valueOf()或toString()來執行。
“+”可以較方便地將一個數值轉換成字串,具體操作是將這個數值加上一個空串。

var a = 2.96;
var b = 1.0;
alert(a + b);//得到數值3.96
alert(a + "" + b);//得到字串"2.961"

“-”總是對兩個運算子進行代數減法操作,如果運算數是非數值的,會試圖將它們轉換成數值,這一轉換和呼叫parseFloat方法的數值轉換類似,但是通常效率更高。如果轉換失敗,將會得到特殊的值NaN.
“-“通常用來將字串快速轉換成數值,具體操作是將這個字串減去一個數值0.

var a = "2.96";
alert(a + 1);//得到字串"2.961"
alert(a - 0 + a);//得到數值3.96

運算子的隱式數值轉換和parseFloat並不完全相同。對於字串來說,前者是一種完全匹配,而後者是一種解析過程;對應布林常量來說,前者總是將true轉換為數值1,將false轉換為數值0,後者則得到NaN;對於物件來說,前者總是先試圖呼叫物件的valueOf()方法進行轉換,如果失敗再呼叫 toString()方法,而後者則直接嘗試呼叫物件的toString()方法。

JavaScript:parseFloat("123abc");//得到數值123
JavaScript:"123abc"-0;//得到NaN

“/”JavaScript不區分數值的精度型別。因此當你用一個整數來除另外一個整數時,得到的結果可能是浮點數,如果你希望得到整數商,你就可能需要呼叫Math.floor()方法來進行精度處理。當除法的除數為0時,運算結果通常為Infinity,而如果是0/0,則結果將得到NaN。

“%”取模運算也適用於浮點數,運算結果的精度和兩個運算數中精度最高的那個相同,-4.3%2.11得到-0.08

關係運算子
兩個運算子中只要有一個是NaN或undefined,或者被轉換成NaN或undefined,那麼運算子“>””<”“>=””<=” “==”都返回false.
JS物件的深度比較 132

null == undefined  >>>true
null === undefined  >>>false
null >= null      null <= null  >>>>true

邏輯與&&操作符時  null\NaN\0\undefined都會被轉為false
對任何表示式a連續使用兩次”!”運算子(即!!a)都可以將它轉換成一個布林值

逗號運算子
 先計算左邊的運算數,再計算右邊的運算數,並將右邊運算數的計算結果作為表示式的值返回。

x = (i=0,j=1,k=2)
等價於
i=0;
j=1;
x=k=2;

物件運算子:(in、instanceof、new、delete、. 、[])

var point = { x:1,y:1 };//定義一個物件
var has_x_coord = "x" in point;//值為true
var has_y_coord = "y" in point;//值為true
var has_z_coord = "z" in point;//值為false
var ts = "toString" in point;//繼承屬性:值為true

instanceof要求其左邊的運算數是一個物件,右邊的運算數是物件類的名字。如果該運算子左邊的物件是右邊類或者它的派生類的一個例項,它返回false,否則返回true。

alert("" instanceof Object);
alert({} instanceof Object);
alert([] instanceof Array);

如果instanceof運算子的坐運算數不是物件,或者右運算數是一個物件,而不是一個類,它將返回false。另外如果它的右運算數根本不是一個物件,它將丟擲一個系統級別的異常。

delete 用var語句宣告的變數也不能被刪除。如果delete使用的運算數是一個不存在的屬性,它將返回true.

typeof對物件、陣列和null會返回object

void運算子
可以出現在任何型別的運算元之前,作用是捨棄運算數的值,返回undefined作為表示式的值。考慮到向後相容性,用表示式void(0)比使用undefined屬性更好。

while(true)會建立一個無限迴圈
預設函式名的函式被作為一個匿名函式或者閉包使用。
with語句用來暫時修改預設的作用域。在實際應用中,with語句的作用是減少程式程式碼的輸入,特別是當程式碼作用於一些深層次的物件時,例如

frames[1].document.forms[0].name.value = "";
frames[1].document.forms[0].address.value = "";
frames[1].document.forms[0].email.value = "";
可以寫成:
with(frames[1].document.form[0]){
    name.value = "";
    address.value = "";
    email.value = "";
}

資料型別
在JS中,要將變數中的整數型別用十六進位制方式表示,可以通過呼叫number的toString(16)方法來完成。
toString()方法,它接受一個從2到36的整數作為引數,作用是把數值表示成引數所指定進位制的數。
var x = 33;
var y = x.toString(2); //y的值為字串"100001"

也可以用數值常量直接呼叫toString方法,但是必須採用括號將數值常量括起來作為表示式使用。
var y = (255).toString(16); //y是“FF”
當一個浮點數值大於所能表示的最大值時,其結果是一個特殊的無窮大,JS將它輸出為Infinity.負無窮大為-Infinity。
當JS表示式計算中出現無法用數值表示的“數值型”結果時,它總是返回一個NaN.NaN可以理解為一個無法用數值表示的特殊“數值”。NaN這個符號同任何數值包括它自己在內都不相等,所以只能用一個特殊的isNaN()方法來檢測這個值。
內建函式isFinite()當數值為+-Infinity或NaN時返回false,否則總是返回true,可以用它來判定常規數值。

字串
charAt(index)方法可以獲取index位置上的字元;
charCodeAt(index)方法則可以獲取index位置上字元的Unicode編碼;
substring和slice方法可以抽取子串;
indexOf(char)方法可以查詢指定字元在字串中第一次出現的位置;
search方法可以查詢和匹配子串;
replace方法可以完成子串的替換;

var str = "www.51js.com";
        //indexOf得到字串中字元的索引
        var pos = str.indexOf(".");
        //substring獲取從指定索引位置開始的子串
        document.write(str.substring(pos+1) + "<br/>");//51js.com
        //split分割字串
        var parts = str.split(".");
        document.write(parts[1] + "<br/>");//51js
        //search匹配字串,返回匹配處的索引
        pos = str.search("51js");
        document.write(pos + "<br/>");//4
        //replace替換字串
        str = str.replace("51js","無憂指令碼");
        document.write(str);//www.無憂指令碼.com

數值
1.可以使用建構函式Array()來建立一個陣列 var a = new Array();
2.採用陣列常量建立陣列 var table = [base,base+1,0.9];
由於JS是一種動態型別語言,因此陣列元素不必具有相同的型別。

var a = new Array(1,2,3); 這裡初始化一個新陣列,a[4][0]==1;a[4][1]==2;a[4][2]==3;

JS並不直接從語法上支援多維陣列,但是由於它的陣列元素還可以是陣列,利用這一特性可以輕鬆實現多維陣列這樣的結構。
var a = new Array(10); 如果只給建構函式傳遞一個引數,並且這個引數是數值的話,該引數指定的是陣列的長度。 建立的是具有10個未初始化的元素的新陣列。
length屬性可讀可寫,如果設定陣列的length為0,可以清楚陣列中的所有元素(不包括那些下標不為整數的陣列元素)
陣列常量中還可以直接存放未初始化的元素,只要在逗號之間省去該元素的值就可以了。var sparseArray = [1,,,,,5];

物件
訪問物件的屬性可以通過物件運算子”.”來訪問,也可以通過集合運算子”[]”來訪問;
point.x和point[“x”]的兩種訪問方式是等價的,前者將point當作一個物件,x是它的屬性;而後者將point當作一個雜湊表,”x”是它的索引。

JSON
JSON是由JavaScript發展而來的一種簡單的資料交換協議,它的資料格式就是一個合法的JS物件常量。{a:100,b:" abc",c:true,d:[1,2,3]}一個簡單的用JSON形式表示的資料物件

函式
由於函式具有被呼叫時建立封閉環境的特性,因此在某些情況下它又被稱為“閉包”。
JS允許在函式體中定義並返回另一個函式,這個巢狀定義在函式體內的函式在呼叫時建立“閉包” 由於JS擁有閉包,因此它具有明顯的functional(函式式)的特徵
匿名函式:
function關鍵字後的識別符號表示的是喊出常量的名字,它可以預設。預設名稱的函式是匿名函式,它可以被直接呼叫、賦值給某個變數或者出現在某些表示式中。
匿名函式最直接的用法是將它作為常量直接賦給物件屬性,作為物件的方法。

null:
null常常被看作物件型別的一個特殊值,代表物件為”空”或者變數沒有引用任何物件。但是注意typeof(null)的值是object null本身不是物件,不要把它看作特殊的物件,它是JS的關鍵字
undefined:
表示”無值”,typeof(undefined)的值是undefined 一個未定義的變數,或者已經宣告但還未賦值的變數,又或者一個並不存在的物件屬性,它們的值都是undefined.
undefined == null >>>true 用處:my.prop == null 如果屬性my.prop並不存在或者它存在但是值為null,那麼這個表示式的值為true
要明確區分null和undefined,可以使用===或者typeof
只宣告這個變數,並不初始化它,就可以確保它的值為undefined。另外,void運算提供另一種獲取undefined值的方法。

值與引用的相互轉換:裝箱和拆箱
JS為基本資料型別提供了對應的引用型別物件,使得這些基本資料型別可以進行值和引用型別的轉換。把基本資料型別轉換為對應的引用型別的操作被稱為裝箱boxing,把引用型別轉換為對應的值型別,被稱為拆箱unboxing。
JS中為數值提供的型別是Number,為字串提供的型別是String,為布林型提供的是Boolean。
把一個值裝箱,就是用這個值來構造一個相應的包裝物件。

var a = 10,b = "JavaScript",c = true;
var o_a = new Number(a);
var o_b = new String(b);
var o_c = new Boolean(c);

o_a、o_b、o_c分別是a、b、c對應的包裝物件,他們的值就是a、b、c的原始值。o_a、o_b、o_c是引用型別,對它們進行比較時,是對引用進行比較,而不是對內容進行比較。typeof作用於它們的結果都是object.
裝箱最大的作用就是將值作為物件來處理。

var b = (255).toString(16);  //隱含了一個型別轉換,將數值255轉換為物件Number(255)

對包裝物件拆箱非常簡單,只要呼叫它們的valueOf()方法,就能得到原始值。事實上,Number、String、Boolean的另一個用處是直接作為普通函式呼叫它們,可以返回相應的值型別,如Number(10)返回數值10.
程式操作值型別資料通常比操作引用型別資料快得多(不要額外定址)

JS的弱型別,不是指JS的資料沒有型別區別,而是指JS的變數不關心它們儲存的內容的資料型別。
執行時型別的識別:typeof、instanceof運算子、constructor屬性、甚至自己實現的機制
強制型別轉換:parseInt() parseFloat() toString()、通過建構函式強制轉換var num = 100;var str = String(num);//強制轉換為String

浮點數精度問題
JS存在嚴重的浮點數精度缺陷alert(86217.8+45.6)結果不是86263.4 而是86263.40000000001
在進行浮點運算前,一個合理的做法是事先確定好問題的精度範圍,JS提供了幾個取整的函式,來限定解的精度。分別是Math.floor() 取比當前數值小的最大整數、Math.round() 四捨五入、Math.ceil() 取比當前數值大的最小整數 、Number物件提供的toFixed() 保留幾位小數。

相關文章