拋棄jQuery 深入原生的JavaScript

ourjs發表於2014-04-22

  雖然我已經做網站建設工作10多年了,但我從最近3年才開始更多地學習如何更好的將純JavaScript用於工作中,而不總是將jQuery考慮在第一位。現在我每天學習很多東西。這個過程讓我覺得Adtile的JavaScript SDK 更像是在建立一個開源工程,而不是“具體的工作”,不得不說,我很喜歡那樣。

  今天,我準備將在過去幾年學到的一些基礎東西與大家一起分享,這將可能幫你深入純 JavaScript的世界,讓你能更簡單的做出決定——jQuery在你下個工程中是否需要。

  逐步增強

  雖然像jQuery這樣的庫有助於解決許多瀏覽器之間不相容的問題,但當你一旦開始使用純JavaScript來完成所有工作的時候你確實會變得對他們很熟悉。為了避免寫包含瀏覽器修改和只能解決瀏覽器相容問題的JavaScript程式碼,我建議使用特徵檢測只將更現代化的瀏覽器作為目標來培養逐步增強的經驗。這並不意味著從像IE7這樣的瀏覽器上得不到任何東西,這隻能說明在JavaScript沒有增強的情況下他們得到一個更基礎的經驗。

  我們是怎麼做的

  我們有一個叫做”feature.js”的分離的JavaScript部分,它擁有所有的功能測試。真實的測試列表比這長多了,但讓我們稍晚點再回到這問題吧。為了消除一些老瀏覽器的不相容,我們使用如下兩個測試:

var feature = {
  addEventListener : !!window.addEventListener,
  querySelectorAll : !!document.querySelectorAll,
};

  然後,在主應用程式部分,我們檢測這些特性是否能被下面例子中簡單的“if”語句支援。如果不被支援,那麼瀏覽器將不會執行如下的任何程式碼:

if (feature.addEventListener && feature.querySelectorAll) {
  this.init();
}

  這兩個測試確保我們在JavaScript中使用CSS選擇器時有本地方法(querySelectorAll)可用,新增和刪除事件的簡便方法(addEventListener)且瀏覽器標準支援比IE8的好。閱讀有關這個方法的更多內容請訪問BBC blog的 “Cutting the mustard 文章。

  瀏覽器支援

  這兒有一個我們測試的哪些瀏覽器支援這個特性,且日後能保持執行JavaScript的粗略列表:

  • IE9+
  • Firefox 3.5+
  • Opera 9+
  • Safari 4+
  • Chrome 1+
  • iPhone and iPad iOS1+
  • Android phone and tablets 2.1+
  • Blackberry OS6+
  • Windows 7.5+
  • Mobile Firefox
  • Opera Mobile

  基礎原生JavaScript方法

  讓我們開始關注與jQuery相比,最基礎且需求頻繁的功能在純JavaScript中是如何工作的。對於每個例子,我都打算提供jQuery和純JavaScript兩種方法。

  Document Ready 事件

  在jQuery中,你們中的許多人可能過去常常像這樣使用 document.ready :

$(document).ready(function() {
  // Code
});

  但是你知道,你可以將所有的JavaScript放在頁面的底端,但他們確實是一回事嗎?JavaScript同樣擁有一個DOM內容載入事件的偵聽器,而不是使用jQuery的document.ready:

document.addEventListener("DOMContentLoaded", function() {
  // Code
}, false);

  選擇器API

  JavaScript的本地選擇器API非常優秀。它對CSS選擇器是有用的且jQuery提供的非常類似。如果你過去經常在jQuery中這樣寫:

var element = $("div");

  現在你可以用如下的語句來替代:

var element = document.querySelector("div");

  或者選擇所有div的某些內部容器:

var elements = document.querySelectorAll(".container div");

  你也可以針對特定元素進行查詢來找到它的子元素:

var navigation = document.querySelector("nav");
var links = navigation.querySelectorAll("a");

  很簡單,容易理解且現在不需要太多的程式碼,不是嗎?更遠一步來說,我們甚至可以自己建一個小型的JavaScript庫來進行簡單的DOM查詢。以下是Andrew Lunny已經想出來的一些東西

// This gives us simple dollar function and event binding
var $ = document.querySelectorAll.bind(document);
Element.prototype.on = Element.prototype.addEventListener;

// This is how you use it
$(".element")[0].on("touchstart", handleTouch, false);

  遍歷DOM

  用純JavaScript來遍歷DOM比起用jQuery來說有一些困難。但也不是太困難。下面是一些簡單的例子:

// Getting the parent node
var parent = document.querySelector("div").parentNode;
// Getting the next node
var next = document.querySelector("div").nextSibling;
// Getting the previous node
var next = document.querySelector("div").previousSibling;
// Getting the first child element
var child = document.querySelector("div").children[0];
// Getting the last child
var last = document.querySelector("div").lastElementChild;

  新增和刪除樣式名(class name)

  使用jQuery,新增、刪除和檢查一個元素是否有確定的類是很簡單的事。用純JavaScript會有一些複雜,但也不是太複雜。給元素一個叫做“foo”的類且替換目前所有的類:

// Select an element
var element = document.querySelector(".some-class");
// Give class "foo" to the element
element.className = "foo";

  在不替換目前類的前提下增加類:

element.className += " foo";

  從html元素中移除”no-js”類且用”js”來替代:

<html class="no-js">
<head>
  <script>
    document.documentElement.className = "js";
  </script>

  這相當簡單,對不對?下一步,只移除某些類稍微有點複雜。我一直在單獨部分使用這個叫做util.js的小助手函式。它有兩個引數:元素和你想移除的類:

// removeClass, takes two params: element and classname
function removeClass(el, cls) {
  var reg = new RegExp("(\\s|^)" + cls + "(\\s|$)");
  el.className = el.className.replace(reg, " ").replace(/(^\s*)|(\s*$)/g,"");
}

  然後,在主應用程式部分,我一直像這樣使用:

removeClass(element, "foo");

  如果針對某些類你同樣想檢查一個元素,那麼就像jQuery的hasClass一樣。你可能需要把這些程式碼加入到你的utils工具中:

// hasClass, takes two params: element and classname
function hasClass(el, cls) {
  return el.className && new RegExp("(\\s|^)" + cls + "(\\s|$)").test(el.className);
}

  然後可以這樣使用:

// Check if an element has class "foo"
if (hasClass(element, "foo")) {

  // Show an alert message if it does
  alert("Element has the class!");
}

  HTML5 的  classList API 簡介

  如果你只需要支援像IE10+,Chrome,FireFox,Opera和Safari這樣較現代的瀏覽器,那麼你可以開始使用HTML5的classList功能,它讓增加和刪除類變得更簡單。

  這是我在我們最新的開發者文件中最終做的事,隨著功能的開發,這更像是UI的加強,且如果這不是現在的,其實際上並不是能打破經驗的一些東西。

  通過下面簡單的”if”語句,你可以檢測出瀏覽器是否支援這個功能:

if ("classList" in document.documentElement) {
  // classList is supported, now do something with it
}

  用classList來新增、刪除、轉換類:

// Adding a class
element.classList.add("bar");
// Removing a class
element.classList.remove("foo");
// Checking if has a class
element.classList.contains("foo");
// Toggle a class
element.classList.toggle("active");

  使用classList的另一個好處是它比使用原始的類名屬性表現得更好。如果你有像這樣的元素:

<div id="test" class="one two three"></div>

  你想操作哪一個:

var element = document.querySelector("#test");
addClass(element, "two");
removeClass(element, "four");

  這些被類名屬性讀和寫的方法將觸發瀏覽器重繪。但這並不是我們是否應該用相應的classList方法的情況:

var element = document.querySelector("#test");
element.classList.add("two");
element.classList.remove("four");

  用classList之後,最基本的類名屬性僅在必要的時候進行更改。新增一個已經存在的類和移除一個不存在的類時,根本沒有牽涉到類名屬性,這意味著我們剛剛避免了兩次重繪。

  事件監聽器

  新增和移除一個元素的事件監聽器在純JavaScript和jQuery中都一樣比較簡單。當你必須要新增多個事件監聽器時,事情就變得有點複雜了,但我會詳細解釋給你的。最簡單的一個例子,當你單擊一個元素時彈出一個警告資訊,如下程式碼所述:
element.addEventListener("click", function() {
  alert("You clicked");
}, false);

  為了讓頁面的所有元素都實現這個功能,我們必須依次重複每個元素且給他們新增事件監聽器:

// Select all links
var links = document.querySelectorAll("a");

// For each link element
[].forEach.call(links, function(el) {

  // Add event listener
  el.addEventListener("click", function(event) {
    event.preventDefault();
    alert("You clicked");
  }, false);

});

  JavaScript有關事件監聽器一個最偉大的功能就是“addEventListener” 能攜帶一個作為第二個引數的物件,這將會讓它自動的尋找一個叫做“handleEvent”的方法然後呼叫它。Ryan Seddon在它的文章中已經徹底地介紹了這個方法,所以我只打算給一個最簡單的例子,你可以通過在它的部落格中學到更多的東西:

var object = {
  init: function() {
    button.addEventListener("click", this, false);
    button.addEventListener("touchstart", this, false);
  },
  handleEvent: function(e) {
    switch(e.type) {
      case "click":
        this.action();
        break;
      case "touchstart":
        this.action();
        break;
    }
  },
  action: function() {
    alert("Clicked or touched!");
  }
};

// Init
object.init();

  操作DOM

  用純JavaScript來操作DOM剛開始聽起來就像一個可怕的想法,但比使用jQuery其實它並沒有複雜多少。下面,我們會有一個例子,選擇DOM的元素,克隆它,用JavaScript來操作克隆的樣式,然後用被操縱的東西來替代原始的元素。

// Select an element
var element = document.querySelector(".class");

// Clone it
var clone = element.cloneNode(true);

// Do some manipulation off the DOM
clone.style.background = "#000";

// Replaces the original element with the new cloned one
element.parentNode.replaceChild(clone, element);

  在DOM中,如果除了附加在<body>中新建立div,你不想替代任何東西,那麼你可以這樣做:

document.body.appendChild(clone);

  如果你覺得你想了解更多不同的DOM方法,我建議你可以拜讀一下 Peter-Paul Koch的DOM Core tables

  進一步深入

  在這兒我想再多分享兩個我最近發現的先進技術。這些都是我們在建立Adtile的時候需要的功能,因此你也會覺得它們很有用。

  在JS中決定響應圖片的最大寬度

  這是我最愛的之一,且如果你需要用JavaScript操作流體圖片時這非常有用。由於瀏覽器預設返回當前被調整過大小的圖片,我們必須要想一些其它的辦法。幸運的是,現代瀏覽器目前已有解決的方案了:

var maxWidth = img.naturalWidth;

  這將會給我們提供最大寬度100%畫素的圖片,且IE9,Chrome,Firefox,Safari和Opera都支援這個方法。我們也可以保留這個特性然後通過載入圖片到記憶體中新增老瀏覽器的支援:

// Get image's max-width:100%; in pixels
function getMaxWidth(img) {
  var maxWidth;

  // Check if naturalWidth is supported
  if (img.naturalWidth !== undefined) {
    maxWidth = img.naturalWidth;

  // Not supported, use in-memory solution as fallback
  } else {
    var image = new Image();
    image.src = img.src;
    maxWidth = image.width;
  }

  // Return the max-width
  return maxWidth;
}

  你應該注意到在檢查寬度前,圖片必須完全被載入。這是我們一直使用的用於確定它們有尺寸的方法:

function hasDimensions(img) {
  return !!((img.complete && typeof img.naturalWidth !== "undefined") || img.width);
}

  判斷一個元素是否在檢視視窗中

  通過使用getBoundingClientRect方法,你可以獲取頁面中任何元素的位置。以下是一個簡單的函式來表明它有多簡單和多強大。這個函式有一個引數,那就是你想要檢查的元素。當元素為可見時,函式將返回true:

// Determine if an element is in the visible viewport
function isInViewport(element) {
  var rect = element.getBoundingClientRect();
  var html = document.documentElement;
  return (
    rect.top >= 0 &&
    rect.left >= 0 &&
    rect.bottom <= (window.innerHeight || html.clientHeight) &&
    rect.right <= (window.innerWidth || html.clientWidth)
  );
}

  上面的函式可以在給窗體新增一個”滾動”事件監聽器,然後呼叫isInViewport()方法時使用。

  結論

  在你的下個專案中是否使用jQuery很大程度上取決於你所建的專案。如果你的專案需要大量的前端程式碼,那麼你應該使用jQuery。然而,當你構建一個JavaScript外掛或庫時,你應該只考慮純JavaScript。使用純JavaScript意味著更少的請求和更少的資料載入。這同樣意味著你不必強迫開發人員在他們的專案中加jQuery。

  原文:blog.adtile.me

相關文章