ifetask2 review

任務

掌握JavaScript基礎知識,可以使用JavaScript編寫一些複雜度不大的交互功能。javascript

1.建立第一個頁面交互

瞭解javascript是什麼

  • JavaScript 是一門跨平臺、面向對象的輕量級腳本語言。 在宿主環境(例如 web 瀏覽器)中, JavaScript 可以經過其所鏈接的環境提供的編程接口進行控制。
  • JavaScript 內置了一些對象的標準庫,好比數組(Array),日期(Date),數學(Math)和一套核心語句,包括運算符,流程控制符以及申明方式等。JavaScript 的核心部分能夠經過添加對象來擴展語言以適應不一樣用途;例如:
    客戶端的 JavaScript 經過提供控制瀏覽器及其文檔對象模型(DOM)的對象來擴展語言核心。例如:客戶端版本直接支持應用將元素放在HTML表單中而且支持響應用戶事件好比鼠標點擊、表單提交和頁面導航。
    服務端的 JavaScript 則經過提供有關在服務器上運行 JavaScript 的對象來可擴展語言核心。例如:服務端版本直接支持應用和數據庫通訊,提供應用不一樣調用間的信息連續性,或者在服務器上執行文件操做。

如何在HTML頁面上加載javascript代碼

  • 使用外鏈式,而後在HTML用<script src ="">引入
  • 使用內建式,<script></script>

標籤<script></script>位置

  • 阻塞:當瀏覽器在執行 JavaScript 代碼時,不能同時作其餘任何事情。
  • 腳本位置
    從 IE 八、Firefox 3.五、Safari 4 和 Chrome 2 開始都容許並行下載 JavaScript 文件,但JavaScript 下載過程仍然會阻塞其餘資源的下載,好比樣式文件和圖片。儘管腳本的下載過程不會互相影響,但頁面仍然必須等待全部 JavaScript 代碼下載並執行完成才能繼續。因爲腳本會阻塞頁面其餘資源的下載,所以推薦將全部<script></script>標籤儘量放到''標籤的底部,以儘可能減小對整個頁面下載的影響。
  • 組織腳本
    下載單個 100Kb 的文件將比下載 5 個 20Kb 的文件更快。也就是說,減小頁面中外鏈腳本的數量將會改善性能。
    能夠把多個文件合併成一個,這樣只須要引用一個<script></script>標籤,就能夠減小性能消耗。文件合併的工做可經過離線的打包工具或者一些實時的在線服務來實現。
    把一段內嵌腳本放在引用外鏈樣式表的link以後會致使頁面阻塞去等待樣式表的下載。這樣作是爲了確保內嵌腳本在執行時能得到最精確的樣式信息。所以,建議不要把內嵌腳本緊跟在<link標籤後面。
  • 無阻塞的腳本:
    問題:減小 JavaScript 文件大小並限制 HTTP 請求數在功能豐富的 Web 應用或大型網站上並不老是可行。下載單個較大的 JavaScript 文件只產生一次 HTTP 請求,卻會鎖死瀏覽器的一大段時間。
    方法:在頁面加載完成後才加載 JavaScript 代碼。這就意味着在 window 對象的 onload事件觸發後再下載腳本。
    延遲加載腳本:
    defer:任何帶有 defer 屬性的script元素在 DOM 完成加載以前都不會被執行
<html>
<head>
    <title>Script Defer Example</title>
</head>
<body>
    <script type="text/javascript" defer>
        alert("defer");
    </script>
    <script type="text/javascript">
        alert("script");
    </script>
    <script type="text/javascript">
        window.onload = function(){
            alert("load");
        };
    </script>
</body>
</html>

這段代碼在頁面處理過程當中彈出三次對話框。不支持 defer 屬性的瀏覽器的彈出順序是:「defer」、「script」、「load」。而在支持 defer 屬性的瀏覽器上,彈出的順序則是:「script」、「defer」、「load」。請注意,帶有 defer 屬性的script元素不是跟在第二個後面執行,而是在 onload 事件被觸發前被調用。
aysnc:可以異步地加載和執行腳本,不由於加載腳本而阻塞頁面的加載。可是有一點須要注意,在有 async 的狀況下,JavaScript 腳本一旦下載好了就會執行,因此頗有可能不是按照本來的順序來執行的。若是 JavaScript 腳本先後有依賴性,使用 async 就頗有可能出現錯誤。
動態腳本加載:php

function loadScript(url, callback){
    var script = document.createElement ("script")
    script.type = "text/javascript";
    if (script.readyState){ //IE
        script.onreadystatechange = function(){
            if (script.readyState == "loaded" || script.readyState == "complete"){
                script.onreadystatechange = null;
                callback();
            }
        };
    } else { //Others
        script.onload = function(){
            callback();
        };
    }
    script.src = url;
    document.getElementsByTagName("head")[0].appendChild(script);
}
loadScript("script1.js", function(){
    alert("File is loaded!");
});
  • 減小 JavaScript 對性能的影響有如下幾種方法:
    將全部的script標籤放到頁面底部,也就是body閉合標籤以前,這能確保在腳本執行前頁面已經完成了渲染。
    儘量地合併腳本。頁面中的script標籤越少,加載也就越快,響應也越迅速。不管是外鏈腳本仍是內嵌腳本都是如此。
    採用無阻塞下載 JavaScript 腳本的方法:
    使用script標籤的 defer 屬性(僅適用於 IE 和 Firefox 3.5 以上版本);
    使用動態建立的script元素來下載並執行代碼;
    使用 XHR 對象下載 JavaScript 代碼並注入頁面中。

2.Javscript數據類型及語言基礎

  • 動態類型
    JavaScript 是一種弱類型或者說動態語言。這意味着你不用提早聲明變量的類型,在程序運行過程當中,類型會被自動肯定。這也意味着你可使用同一個變量保存不一樣類型的數據:
var foo = 42;    // foo is a Number now
var foo = "bar"; // foo is a String now
var foo = true;  // foo is a Boolean now
  • 數據類型
    最新的 ECMAScript 標準定義了 7 種數據類型:
    6 種 原始類型:
    Boolean
    Null
    Undefined
    Number
    String
    Symbol (ECMAScript 6 新定義)
    和 Object

特別的html

  1. 除 Object 之外的全部類型都是不可變的(值自己沒法被改變)。
  2. 一個沒有被賦值的變量會有個默認值 undefined。

實踐判斷各類數據類型的方法

// 判斷數組
function isArray(arr) {
    return Array.isArray(arr);
}
// 判斷函數
function isFunction(fn) {
    return Object.prototype.toString.call(fn) === '[object Function]';
}
  • Object.prototype.toString是一個將傳入對象轉化爲字符串的方法,當用call調用時,會獲取傳入的對象(this)的[[Class]]屬性的值,而後計算出三個字符串: "[object " + 屬性值 + ]"
  • JavaScript:Object.prototype.toString方法的原理

瞭解值類型和引用類型的區別,瞭解各類對象的讀取、遍歷方式:

值類型和引用類型的區別

  • 值類型
    聲明一個值類型變量,編譯器會在棧上分配一個空間,這個空間對應着該值類型變量,空間裏存儲的就是該變量的值。存儲在棧(stack)中的簡單數據段,也就是說,它們的值直接存儲在變量訪問的位置。java

  • 引用類型
    引用類型的實例分配在堆上,新建一個引用類型實例,獲得的變量值對應的是該實例的內存分配地址,這就像您的銀行帳號同樣。存儲在堆(heap)中的對象,也就是說,存儲在變量處的值是一個指針(point),指向存儲對象的內存處。
    爲變量賦值時,ECMAScript 的解釋程序必須判斷該值是原始類型,仍是引用類型。要實現這一點,解釋程序則需嘗試判斷該值是否爲 ECMAScript 的原始類型之一,即 Undefined、Null、Boolean、Number 和 String 型。因爲這些原始類型佔據的空間是固定的,因此可將他們存儲在較小的內存區域 - 棧中。這樣存儲便於迅速查尋變量的值。
    在許多語言中,字符串都被看做引用類型,而非原始類型,由於字符串的長度是可變的。ECMAScript 打破了這一傳統。
    若是一個值是引用類型的,那麼它的存儲空間將從堆中分配。因爲引用值的大小會改變,因此不能把它放在棧中,不然會下降變量查尋的速度。相反,放在變量的棧空間中的值是該對象存儲在堆中的地址。地址的大小是固定的,因此把它存儲在棧中對變量性能無任何負面影響。以下圖所示:c++

JavaScript中原始值包括:undefined,null,布爾值,數字和字符串。
引用類型主要指對象(包括數組和函數)。web

原始值是不可更改的。對象的值是可修改的。
原始值的比較是值的比較。對象的比較並不是值的比較。對象的值都是引用,對象的比較均是引用的比較,當且僅當他們都引用同一個基對象時,他們才相等。
參考:ajax

ECMAScript 原始值和引用值
對象的讀取、遍歷方式
參考:JavaScript 指南-使用對象正則表達式

  • 對象
    在javascript中,一個對象能夠是一個單獨的擁有屬性和類型的實體。咱們拿它和一個杯子作下類比。一個杯子是一個對象(物體),擁有屬性。杯子有顏色,圖案,重量,由什麼材質構成等等。一樣,javascript對象也有屬性來定義它的特徵。數據庫

  • 屬性
    一個 javascript 對象有不少屬性。一個對象的屬性能夠被解釋成一個附加到對象上的變量。對象的屬性和普通的 javascript 變量基本沒什麼區別,僅僅是屬性屬於某個對象。屬性定義了對象的特徵(譯註:動態語言面向對象的鴨子類型)。你能夠經過點符號來訪問一個對象的屬性。JavaScript 對象的屬性也能夠經過方括號訪問。編程

  • 枚舉
    你能夠在 for...in 語句中使用方括號標記以枚舉一個對象的全部屬性。爲了展現它如何工做,下面的函數當你將對象及其名稱做爲參數傳入時,顯示對象的屬性:

function showProps(obj, objName) {
  var result = "";
  for (var i in obj) {
    if (obj.hasOwnProperty(i)) {
        result += objName + "." + i + " = " + obj[i] + "\n";
    }
  }
  return result;
} 
var srcObj = {
    a: 1,
    b: {
        b1: ["hello", "hi"],
        b2: "JavaScript"
    }
};
 
console.log(showProps(srcObj,'srcObj'));
console:
 
srcObj.a = 2
srcObj.b = [object Object]

這裏使用 hasOwnProperty() 是爲了確保是本身的屬性而非繼承的屬性。

能夠以下寫,跳過這個對象的方法:

function showPropsWithoutFun(obj, objName) {
    var result = "";
    for (var i in obj) {
        if (!obj.hasOwnProperty(i)) {       //跳過繼承屬性
            continue;
        }
        if (typeof obj[i] === "function") { //跳過這個對象的方法
            continue;
        }
        result += objName + "." + i + "=" + obj[i] + "\n";
    }
    return result;
}

相關的方法還有:Object.keys(), Object.getOwnPropertyNames()

Object.keys() 方法會返回一個由給定對象的全部可枚舉自身屬性的屬性名組成的數組,數組中屬性名的排列順序和使用for-in循環遍歷該對象時返回的順序一致(二者的主要區別是 for-in 還會遍歷出一個對象從其原型鏈上繼承到的可枚舉屬性)。

Object.getOwnPropertyNames() 方法返回一個由指定對象的全部自身屬性的屬性名(包括不可枚舉屬性)組成的數組。

  • 建立對象
    建立對象的方式有三種:對象直接量,關鍵字 new,使用 Object.create() 方法。

Object.create() 方法建立一個擁有指定原型和若干個指定屬性的對象。

    • 繼承
      全部的 JavaScript 對象繼承於至少一個對象。被繼承的對象被稱做原型,而且繼承的屬性可能經過構造函數的 prototype 對象找到。

定義方法
一個方法 是關聯到某個對象的函數,或者簡單地說,一個方法是一個值爲某個函數的對象屬性。定義方法就象定義普通的函數,除了它們必須被賦給對象的某個屬性。例如:

objectName.methodname = function_name;
 
var myObj = {
  myMethod: function(params) {
    // ...do something
  }
};

深度克隆

// 使用遞歸來實現一個深度克隆,能夠複製一個目標對象,返回一個完整拷貝
// 被複制的對象類型會被限制爲數字、字符串、布爾、日期、數組、Object對象。不會包含函數、正則對象等
function cloneObject(src) {
    var o;
    if (Object.prototype.toString.call(src) === "[object Array]") {
        o = [];
    } else {
        o = {};
    }
    for (var i in src) {
        if (src.hasOwnProperty(i)) {
            if (typeof src[i] === "object") {
                o[i] = cloneObject(src[i]);
            } else {
                o[i] = src[i];
            }
        }
    }
    return o;
}

淺度克隆:基本類型爲值傳遞,對象仍爲引用傳遞。
深度克隆:全部元素或屬性均徹底克隆,並於原引用類型徹底獨立,即,在後面修改對象的屬性的時候,原對象不會被修改。
簡單來講,對於淺度克隆,講一個對象經過簡單的傳遞給另外一變量時,實際至關於給該對象增添了一個別名,這兩個名字都是指向同一個對象,當源對象更改時,複製的對象也隨之改變。對於深度克隆,則是新建了一個指針指向另外一個對象,這個對象是源對象的一個副本,但源對象更改時,副本不會隨之改變。

學習數組、字符串、數字等相關方法,在util.js中實現如下函數:

數組去重

  • 思路:
  1. 先建立一個空數組,記爲數組2,原數組記爲數組1
  2. 遍歷數組1,作一個判斷,當遍歷到的數組元素不爲空而且在數組2中找不到時該元素時,將該元素添加到數組2中
  3. 應用for循環遍歷,應用indexof值是否爲-1判斷元素是否能在數組2找到
  4. 返回數組2
  • 代碼實現:
//數組去重
function uniuqArr(arr) {
    var newArr = [];
    for (var i in arr) {
        if (newArr.indexOf(arr[i]) === -1) {
            newArr.push(arr[i]);
        }
    }
    return newArr;
}

實現一個類trim方法

  • 一開始的,用了很笨的兩個循環,分別從頭遍歷和從尾遍歷判斷空格是否存在及查找空格位置。雖然勉強去除了首尾空格,但沒有辦法去除Tab
//比較笨的方法
function simpleTrimW(str) {
    var newStr = "";
    var pos = str.indexOf(" ");
    while(pos > -1) {
        var pos_previous = pos;
        pos = str.indexOf(" ", pos + 1);
        if (pos != pos_previous +1 ) {  //說明中間隔了非空格字符
            break;
        }
    }
    var posTwo = str.lastIndexOf(" ");
    console.log(posTwo);
    while(posTwo > -1) {
        var posTwo_pre = posTwo;
        posTwo = str.lastIndexOf(" ", posTwo - 1);
        if (posTwo != posTwo_pre - 1) {
            break;
        }
    }
    return(str.slice(pos_previous + 1, posTwo_pre));
}
var str = "    hi ed  ";
console.log(simpleTrim(str)); //hi ed
  • 另外的,無心中發現了去除全部空格的方法,利用concat方法鏈接非空格字符,組成新字符串
//去除全部空格和製表符
function simpleTrim1(str) {
    var newStr = "";
    for (var i = 0; i < str.length; i++) {
 
        if (str[i] != " " && str [i] != "\t") {
           newStr = newStr.concat(str[i]); //中間出現的也會被去除
        }
    }
    console.log(newStr);
}
var str = "   h i           io  \t o";
console.log(str);
simpleTrim1(str); // hiioo
  • 接下來實現一個真正符合要求的trim函數
function simpleTrim(str) {
    var newStr = "";
    for (var i = 0; i < str.length; i++) {
        if (str.charAt(i) != " " && str.charAt(i) != "\t") {
            break;
        }
    }
    for (var j = str.length - 1; j >= 0; j--) {
        if (str.charAt(j) != " " && str.charAt(j) != "\t") {
            break;
        }
    }
    newStr = str.slice(i, j+1);
    console.log(newStr);
}

一樣是用了兩個循環遍歷,一個從頭開始,一個從尾開始,找到第一個非空格非Tab的字符就跳出遍歷。巧妙在於用了兩個變量i和j來循環,跳出循環時獲得了i和j的值,即字符串中從頭開始和從尾開始的第一個非空格非Tab的字符索引值,從而能夠利用slice方法切割字符串,注意slice中第二個參數要加1。

  • 精簡代碼

思路:利用正則匹配開頭和結尾的空白字符,將其替換爲空字符

function trim(str) {
    return str.replace(/^\s+|\s+$/g, '');
}

遍歷數組執行函數

function each(arr, fn) {
    for (var i in arr) {  // 遍歷數組
        fn(arr[i],i);     // 針對數組中每個元素執行fn函數
    }
}
 
var arr = ['java', 'c', 'php', 'html'];
function output(item) {
    console.log(item) //java c php html
}
each(arr, output);
 
var arr = ['java', 'c', 'php', 'html'];
function output(item, index) {
    console.log(index + ': ' + item)
}
each(arr, output);//0: java
                  // 1: c
                  // 2: php
                  // 3: html

也可使用forEach方法

function eachA(arr, fn) {
    arr.forEach(fn);
}
//var arr = ['java', 'c++', 'php', 'html'];
function output(item, index) {
    console.log(index + ': ' + item)
}
//eachA(arr, output); //0: java, 1: c++, 2: php, 3: html

獲取一個對象裏面第一層元素的數量,返回一個整數

// 獲取一個對象裏面第一層元素的數量,返回一個整數
function getObjectLength(obj) {
    var n = 0;
    for (var key in obj) {
      //  console.log(key); //a, b, c
        n++;
    }
    return n;
}
 
/*
// 使用示例
var obj = {
    a: 1,
    b: 2,
    c: {
        c1: 3,
        c2: 4
    }
};
console.log(getObjectLength(obj)); // 3
*/
  • 思路:獲取的是第一層,因此用一個循環,遍歷第一層的key值,每找到一個元素數量便加一

學習正則表達式,在util.js完成如下代碼:

  • 思路:郵箱地址的組成
// 判斷是否爲郵箱地址
function isEmail(emailStr) {
    // math_phys.d.d@VIP.163.com
    // 用戶名必須以字母開頭,不能以點結尾
    // 考慮域名的級聯
    var pattern = /^(\w+\.)*\w+@\w+(\.\w+)+$/;
    return pattern.test(emailStr);
}
// 判斷是否爲手機號
function isMobilePhone(phone) {
    var pattern = /^(\+\d{1,4})?\d{7,11}$/;
    return pattern.test(phone);
}

3.DOM

先來一些簡單的,在你的util.js中完成如下任務:

增長樣式:先判斷是否存在舊樣式,不存在直接將新樣式賦給樣式名,存在則在舊樣式基礎上增長新樣式。

// 爲element增長一個樣式名爲newClassName的新樣式
function addClass(element, newClassName) {
    var oldClassName = element.className;
    element.className = oldClassName === "" ? newClassName : oldClassName + " " + newClassName;
}

移除樣式:

首先經過元素的className屬性取出原來的全部類名,注意當有多個類名時,輸出是一個字符串

<div id="div" class="one two three">test</div>
<script>
        div = document.getElementById('div');
        console .log(originalClassName = div.className); // one two three
</script>

因爲oldclassName是動態可變的,因此要用動態的正則表達式匹配該樣式,\b是匹配單詞邊界,在這裏要使用轉義字符,最後把匹配到的樣式名利用replace方法替換爲空。

// 移除element中的樣式oldClassName
function removeClass(element, oldClassName) {
    var originalClassName = element.className; // 取原來的全部類名
    var pattern = new RegExp("\\b" + oldClassName + "\\b"); // 匹配指定類名
    element.className = originalClassName.replace(pattern,''); // 移除樣式
}

判斷是否爲同一級元素

很簡單,只需判斷父元素是否相同便可。

// 判斷siblingNode和element是否爲同一個父元素下的同一級的元素,返回bool值
function isSiblingNode(element, siblingNode) {
    return element.parentNode === siblingNode.parentNode;
}

獲取元素相對於瀏覽器窗口的位置

Window 尺寸
有三種方法可以肯定瀏覽器窗口的尺寸(瀏覽器的視口,不包括工具欄和滾動條)。
對於Internet Explorer、Chrome、Firefox、Opera 以及 Safari:
window.innerHeight - 瀏覽器窗口的內部高度
window.innerWidth - 瀏覽器窗口的內部寬度
對於 Internet Explorer 八、七、六、5:
document.documentElement.clientHeight
document.documentElement.clientWidth
或者
document.body.clientHeight
document.body.clientWidth
實用的 JavaScript 方案(涵蓋全部瀏覽器):
實例
var w=window.innerWidth
|| document.documentElement.clientWidth
|| document.body.clientWidth;

var h=window.innerHeight
|| document.documentElement.clientHeight
|| document.body.clientHeight;

getBoundingClientRect方法返回一個包含left,top,bottom,right四個屬性的對象,能夠獲取元素的相對位置,再加上滾動條的位置,就能獲得元素的絕對位置。

// 獲取element相對於瀏覽器窗口的位置,返回一個對象{x, y}
function getPosition(element) {
    var pos = {};
    pos.x = element.getBoundingClientRect(element).left + Math.max(document.documentElement.scrollLeft,
        document.body.scrollLeft);
    pos.y = element.getBoundingClientRect(element).top + Math.max(document.documentElement.scrollTop,
        document.body.scrollTop);
    return pos;
}

offsetWidth/offsetHeight: 元素內容 + 內邊距 + 邊框(不包括外邊距、滾動條)
clientWidth/clientHeight: 元素內容 + 內邊距 ;不包括邊框(IE下實際包括)、外邊距、滾動條部分
offsetLeft/offsetTop: 該元素的左上角(邊框外邊緣)與已定位的父容器(offsetParent對象)左上角的距離 ----實際就是外邊距加上父容器內邊距
clientLeft/clientTop: 內邊距的邊緣和邊框的外邊緣之間的水平和垂直距離,也就是邊框寬度
scrollWidth和scrollHeight是元素的內容區域加上內邊距加上溢出尺寸,當內容正好和內容區域匹配沒有溢出時,這些屬性與clientWidth和clientHeight相等
scrollLeft和scrollTop是指元素滾動條位置,它們是可寫的

接下來挑戰一個mini$,它和以前的$是不兼容的,它應該是document.querySelector的功能子集,在不直接使用document.querySelector的狀況下,在你的util.js中完成如下任務:

  • 這部分感受是在封裝一個小的jQuery...想了好久才勉強寫出了一些代碼,特別是組合查詢很難,網上參考了別人的代碼,大概思路是先得寫一個$(selector)的輔助函數,而後須要在$(selector)裏調用該函數。因爲document.getElementsByClassName是HTML5新增的方法,在IE8及如下不能使用,因此這個輔助函數應該是document.getElementById、document.getElementsByTagName的封裝。能夠利用swith語句將兩個方法結合在一塊兒放在一個函數裏面。

取類名的方法

先建一個空列表,用來存放擁有指定樣式名稱的DOM對象,而後取得全部元素(document.getElementsByTagName返回帶有指定標籤名的全部元素,當傳入的標籤名爲*時取所有元素),接着遍歷全部元素,利用getAttribute方法,判斷元素是否有class屬性,若是找到了有class屬性的元素,因爲類不一樣於id,須要考慮一個元素多個類名的狀況,因爲getAttribute方法返回一個字符串,先要將字符串切割成數組,從而能獲得類名個數,而後再遍歷該元素的類名,當有一個元素類名與指定的樣式名同樣時,說明找到了擁有指定className的對象,將其壓入列表,最後獲得了樣式定義包含樣式名的對象列表。在這裏用了兩個for循環,外部的for循環是遍歷全部元素找出有class屬性的元素,內部的for循環是遍歷有class屬性的元素找出有指定class名的元素。

  • getAttribute() 方法返回指定屬性名的屬性值。
  • slice() 方法切割字符串,返回一個數組
function getClassName(name) {
                var result = [];
                var allChildren = null;
                var currAttr = null;
                allChildren = document.getElementsByTagName('*');
                for (var i = 0; i < allChildren.length; i++) {
                    currAttr =allChildren[i].getAttribute('class');
                    if (currAttr !== null) {
                        console.log(currAttr);
                        var currAttrsArr = currAttr.split(/\s+/); //類名可能不止一個樣式
                        console.log(currAttrsArr);
                        for (var j = 0; j < currAttrsArr.length; j++) {
                            if (name === currAttrsArr[j]) { // 無論有多少個類名,只有有一個找到符合的就能夠
                                result.push(allChildren[i]);
                            }
                        }
                    }
                }
                //console.log(result);
        }

輔助函數myQuery

  • myQuery()應該至少傳入一個參數,即對應於$(selector)中的參數。又因爲組合查詢時是在特定父節點下,因此應該還要有一個表示查詢範圍的參數。
  • 傳入的selector參數能夠看出是由兩部分組成的字符串,第一部分是一個特殊符號,#.[,表明了進行id\class\tagName\attribute,第二部分是是對應的名稱。思路是取得特殊符號,做爲swith語句的case分支。代碼以下:
function myQuery(selector, root) {
    var signal = selector[0];
    var allChildren = null;
    var content = selector.substr(1); // 選擇器名稱
    var currAttr = null; // 是否有class屬性的判據
    var result = []; // 查找的結果
    root = root || document; // 沒有定義父節點,在整個document中查找

    switch(signal) {
        case '#':
            result.push(document.getElementById(content)); // id只有一個,查找範圍document
            break;
        case '.':
            allChildren = root.getElementsByTagName('*');  // class不僅一個,查找範圍root,取出範圍下全部子元素
            for (var i = 0; i < allChildren.length; i++) {  // 遍歷全部子元素
                currAttr = allChildren[i].getAttribute('class'); // 判斷是否有class屬性,返回屬性值
                if (currAttr !== null) {
                    currAttrsArr = currAttr.split(/\s+/);  // 將屬性值分割成數組
                    for(var j = 0; j < currAttrsArr.length; j++) { // 遍歷屬性值
                    if (content === currAttrsArr[j]) {  // 當選擇器名稱與某個屬性值相同時
                        result.push(allChildren[i]);  // 將對應擁有該屬性的元素壓入result
                    }
                }
            }
        }
            break;
        case '[':
            if (content.search('=') === -1) { // 只有屬性沒有值
                allChildren = root.getElementsByTagName('*');
                for (var i = 0; i < allChildren.length; i++) {
                    if(allChildren[i].getAttribute(selector.slice(1, -1)) !== null) {
                        result.push(allChildren[i]);
                    }
                }
            } else { //既有屬性,又有值
                allChildren = root.getElementsByTagName("*");
                var pattern = /\[(\w+)\s*\=\s*(\w+)\]/; //爲了分離等號先後的內容
                var cut = selector.match(pattern); //分離後的結果,爲數組
                //alert(cut);
                //console.log(cut);
                var key = cut[1]; //鍵
                var value = cut[2]; //值
                for (i = 0; i < allChildren.length; i++) {
                    if (allChildren[i].getAttribute(key) == value) {
                        result.push(allChildren[i]);
                    }
                }
            }
          break;
        default: // 開頭不是特殊字符,即查找tagName
            result = root.getElementsByTagName(selector);
            break;
    }
    return result;
}
  • match方法
    match() 方法可傳入字符串值或正則表達式。
    match() 方法將檢索字符串 stringObject,以找到一個或多個與 regexp 匹配的文本。這個方法的行爲在很大程度上有賴於 regexp 是否具備標誌 g。
    若是 regexp 沒有標誌 g,那麼 match() 方法就只能在 stringObject 中執行一次匹配。若是沒有找到任何匹配的文本, match() 將返回 null。不然,它將返回一個數組,其中存放了與它找到的匹配文本有關的信息。該數組的第 0 個元素存放的是匹配文本,而其他的元素存放的是與正則表達式的子表達式匹配的文本。除了這些常規的數組元素以外,返回的數組還含有兩個對象屬性。index 屬性聲明的是匹配文本的起始字符在 stringObject 中的位置,input 屬性聲明的是對 stringObject 的引用。
    若是 regexp 具備標誌 g,則 match() 方法將執行全局檢索,找到 stringObject 中的全部匹配子字符串。若沒有找到任何匹配的子串,則返回 null。若是找到了一個或多個匹配子串,則返回一個數組。不過全局匹配返回的數組的內容與前者大不相同,它的數組元素中存放的是 stringObject 中全部的匹配子串,並且也沒有 index 屬性或 input 屬性。
    注意:在全局檢索模式下,match() 即不提供與子表達式匹配的文本的信息,也不聲明每一個匹配子串的位置。若是您須要這些全局檢索的信息,可使用 RegExp.exec()。
    舉個例子,如今頁面有一個div元素,用setAttribute設置了屬性'date = 2016'。調用myQuery函數時,當正則表達式沒有全局匹配時,輸出的cut以下:

當全局匹配時,輸出的cut以下:

此時會看到數組cut存放的只是匹配串,因而下面的key和value就爲空,輸出的result就是全部元素了。

$(selector)函數

// 實現一個簡單的Query
function $(selector) {
    if (!selector) {
        return null;
    }
 
    if (selector == document) {
        return document;
    }
 
    selector = selector.trim(); //去除首尾空格
    if (selector.indexOf(" ") !== -1) {    // 存在空格,即組合查詢
        var selectorArr = selector.split(/\s+/);
 
        var rootScope = myQuery(selectorArr[0]);  // 找出在第一個查詢條件下的全部子節點,做爲第二次查詢的範圍
        var i = null;
        var j = null;
        var result = [];
 
        for (i = 1; i < selectorArr.length; i++) {  // i從1開始,rootScope即i=0時
            for (j = 0; j < rootScope.length; j++) {
                result.push(myQuery(selectorArr[i], rootScope[j]));
            }
        }
        return result[0][0];
    } else {  // 單一查詢
        return myQuery(selector,document)[0];
    }
 
}

4.事件

咱們來繼續用封裝本身的小jQuery庫來實現咱們對於JavaScript事件的學習,仍是在你的util.js,實現如下函數:

// 給一個element綁定一個針對event事件的響應,響應函數爲listener
function addEvent(element, event, listener) {
    // your implement
}
 
// 例如:
function clicklistener(event) {
    ...
}
addEvent($("#doma"), "click"    , a);
 
// 移除element對象對於event事件發生時執行listener的響應
function removeEvent(element, event, listener) {
    // your implement
}
// 給一個element綁定一個針對event事件的響應,響應函數爲listener
function addEvent(element, event, listener) {
    if (element.addEventListener) {   // IE8+
        element.addEventListener(event,listener);
    } else if (element.attachEvent) {  // IE8如下
        element.attachEvent("on" + event, listener);
    } else {
        element["on" + event] = listener;  // dom0級
    }
}
 
// 例如:
//function clicklistener(event) {
//    ...
//}
addEvent($("#doma"), "click", a);
 
// 移除element對象對於event事件發生時執行listener的響應
function removeEvent(element, event, listener) {
    if (element.removeEventListener) {
        element.removeEventListener(event,listener);
    } else if (element.detachEvent) {
        element.detachEvent("on" + event, listener);
    } else {
        element["on" + event] = null;
    }
}
 
// 實現對click事件的綁定
function addClickEvent(element, listener) {
    addEvent(element, "click", listener);
}
 
// 實現對於按Enter鍵時的事件綁定
function addEnterEvent(element, listener) {
    element.onkeydown = function(e) {
        e = e || window.event;
        if (e.keyCode === 13) {
            listener();
        }
    }
}

接下來咱們把上面幾個函數和$作一下結合,把他們變成$對象的一些方法

addEvent(element, event, listener) -> $.on(element, event, listener);
removeEvent(element, event, listener) -> $.un(element, event, listener);
addClickEvent(element, listener) -> $.click(element, listener);
addEnterEvent(element, listener) -> $.enter(element, listener);
$.on = addEvent;
$.un = removeEvent;
$.click = addClickEvent;
$.enter = addEnterEvent;
//考慮這樣一個場景,咱們須要對一個列表裏全部的<li>增長點擊事件的監聽
        function clickListener(event) {
            console.log(event);
        }
        //方法一:針對每個item去綁定事件,這樣顯然是一件很麻煩的事情。
        $.click($("#item1"), clickListener);
        $.click($("#item2"), clickListener);
        $.click($("#item3"), clickListener);
 
        //方法二:經過本身寫的函數,取到id爲list這個ul裏面的全部li,而後經過遍歷給他們綁定事件。這樣咱們就不須要一個一個去綁定了。
        each($("#list2").getElementsByTagName('li'), function(li) {
            addClickEvent(li, clickListener);
        });
        // 再此基礎上考慮另外一個場景,加了一個按鈕,當點擊按鈕時,改變list裏面的項目。這個時候你再點擊一下li,綁定事件再也不生效了。
        function renderList() {
            $("#list").innerHTML = '<li>new item</li>';
            /* 須要從新綁定事件
            // 當加入
            // each($("#list").getElementsByTagName('li'), function(item) {
                $.click(item, clickListener);
            });
             點擊按鈕時綁定事件纔會從新生效*/
        }
 
 
        function init() {
            each($("#list").getElementsByTagName('li'), function(item) {
                $.click(item, clickListener);
            });
 
            $.click($("#btn"), renderList);
        }
        init();
// 方法三:事件代理,改變DOM結構或內容不須要從新綁定事件
        // 先簡單一些
        function renderList() {
            $("#list").innerHTML = '<li>new item</li>';
        }
        $.click($("#btn"), renderList);
        function delegateEvent(element,tag,eventName,listener){
            addEvent(element, eventName, function(event){
                var target = event.target || event.srcElement;
                if(target.tagName.toLowerCase() == tag.toLowerCase()) {
                    listener.call(target, event);
                }
            });
        }
        $.delegate = delegateEvent;
        var clickHandle = function (event) {
            alert(event);
        }
 
        // 使用示例
        // 仍是上面那段HTML,實現對list這個ul裏面全部li的click事件進行響應
        $.delegate($("#list"), "li", "click", clickHandle);
        // 這時改變DOM結構不須要從新綁定事件了。

事件代理

簡單來說,當須要爲多個子元素添加事件時,能夠把事件加在父元素上,在DOM2級事件的冒泡階段父元素事件被觸發,因而底下全部子元素也一併添加了事件。

5.BOM

// 實現如下函數
// 判斷是否爲IE瀏覽器,返回-1或者版本號
function isIE() {
    // your implement
}
 
// 設置cookie
function setCookie(cookieName, cookieValue, expiredays) {
    // your implement
}
 
// 獲取cookie值
function getCookie(cookieName) {
    // your implement
}
//判斷是否爲IE瀏覽器,返回-1或者版本號
function isIE() {
    var s = navigator.userAgent.toLowerCase();
    console.log(s);
    //ie10的信息:
    //mozilla/5.0 (compatible; msie 10.0; windows nt 6.2; trident/6.0)
    //ie11的信息:
    //mozilla/5.0 (windows nt 6.1; trident/7.0; slcc2; .net clr 2.0.50727; .net clr 3.5.30729; .net clr 3.0.30729; media center pc 6.0; .net4.0c; .net4.0e; infopath.2; rv:11.0) like gecko
    var ie = s.match(/rv:([\d.]+)/) || s.match(/msie ([\d.]+)/);
    if (ie) {
        return ie[1];
    } else {
        return -1;
    }
}
// 設置cookie
function setCookie(cookieName, cookieValue, expiredays) {
    var oDate = new Date();
    oDate.setDate(oDate.getDate + expiredays);
    document.cookie = cookieName + '=' + cookieValue + ';expires=' + oDate;
}
// 獲取cookie
function getCookie(cookieName) {
    var arr = document.cookie.split("; ");   // 多條cookie以分號加空格隔開
    for (var i = 0; i < arr.length; i++) {
        var arr2 = arr[i].split("=");
        if (arr2[0] === cookieName) {
            return arr2[1];
        }
    }
    return "";
}
// 移除cookie
function removeCookie(cookieName) {
    setCookie(cookieName, "1", -1); // 把有效時間設置爲-1
}

6.AJAX

學習Ajax,並嘗試本身封裝一個Ajax方法。實現以下方法:

//
function ajax(url, options) {
    // your implement
}
 
// 使用示例:
ajax(
    'http://localhost:8080/server/ajaxtest',
    {
        data: {
            name: 'simon',
            password: '123456'
        },
        onsuccess: function (responseText, xhr) {
            console.log(responseText);
        }
    }
);
function ajax(url, options) {
 
    var dataResult; //結果data
 
    // 處理data
    if (typeof(options.data) === 'object') {
        var str = '';
        for (var c in options.data) {
            str = str + c + '=' + options.data[c] + '&';
        }
        dataResult = str.substring(0, str.length - 1);
    }
 
    // 處理type
    options.type = options.type || 'GET';
 
    //獲取XMLHttpRequest對象
    var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
 
    // 發送請求
    xhr.open(options.type, url, true);
    if (options.type == 'GET') {
        xhr.send(null);
    } else {
        xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
        xhr.send(dataResult);
    }
 
    // readyState
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            if (xhr.status === 200) {
                if (options.onsuccess) {
                    options.onsuccess(xhr.responseText, xhr.responseXML);
                }
            } else {
                if (options.onfail) {
                    options.onfail();
                }
            }
        }
    };
}
相關文章
相關標籤/搜索