首頁 > web前端 > js教程 > 原生 JavaScript 取代 jQuery?

原生 JavaScript 取代 jQuery?

伊谢尔伦
發布: 2017-02-08 13:26:46
原創
1223 人瀏覽過

隨著 JavaScript 本身的完善,越來越多的人開始喜歡使用原生 JavaScript 開發取代各種函式庫,其中不少人發出了用原生 JavaScript 取代 jQuery 的聲音。這並不是壞事,但也看不見得就是好事。如果你真的想把 jQuery 從前端依賴函式庫中移除掉,我建議你慎重考慮。

首先 jQuery 是一個第三方函式庫。庫存在的價值之一在於它能大幅簡化開發。一般情況下,第三方函式庫都是由原生語言特性和基礎 API 函式庫實現的。因此,理論上來說,任何函式庫第三方函式庫都是可以用原生語言特性取代的,問題在於是否值得?

jQuery 的作用

引用一段jQuery 官網的話:

jQuery is a fast, small, and feature-rich library。 , event handling, animation, and Ajax much simpler with an easy-to-use API that works across much simpler with an easy-to-use這一段話很謙虛的介紹了jQuery 在處理DOM 和跨瀏覽器方面做出的貢獻。而事實上,這也正是我們選用 jQuery 的主要原因,並順帶使用了它帶來的一些工具,例如數組工具,Deferred 等。 對我來說,最常用的功能包括在DOM 樹中進行查詢

修改DOM 樹及DOM ferred 和Promise

物件和陣列處理

  • 還有一個一直在用卻很難在列清單時想到的——跨瀏覽器

  • 到底是誰在替代誰?

  • 上面提到的所有功能都能用原生程式碼來實現。從本質上來說,jQuery 就是用來代替原生實現,以達到減少程式碼,增強可讀性的目的的——所以,到底是用 jQuery 代替原生程式碼,還是用原生程式碼取代 jQuery?這個先後因果關係可否搞懂?
  • 我看到說用

    querySelectorAll()
  • 代替
  • $()

    的時候,不禁在想,用 jQuery 一個字符就能解決的,為什麼要寫十六個字符?大部分瀏覽器是有實作

    $()
  • ,但寫原生程式碼的時候你會考慮
  • $()

    的瀏覽器相容性嗎? jQuery 已經考慮了!

  • 我看到一大堆創建DOM 結構的原生JavaScript 程式碼的時候,不禁在想,用jQuery 只需要一個方法鏈就解決了,我什至可以用和HTML 結構類似的程式碼(包含縮排),例如
  • // 创建一个 ul 列表并加在 #container 中
    $("<ul>").append(
        $("<li>").append(
            $("<a>").attr("href", "#").text("first")),
        $("<li>").append(
            $("<a>").attr("href", "#").text("second")),
        $("<li>").append(
            $("<a>").attr("href", "#").text("third"))
    ).appendTo($("#container"));
    登入後複製

    這段程式碼用

    document.createElement()
  • 來實現完全沒有問題,只不過程式碼量要大得多,而且會出現大量重複(或類似)的程式碼。當然是可以把這些重複程式碼提取出來寫成函數的…不過 jQuery 已經做了。
  • 注,拼 HTML 的方法實在弱爆了,既容易出錯,又不易閱讀。如果有 ES6 的字串模板之後,就用它來寫 HTML 也是個不錯的主意。

就 DOM 操作這一部分來說,jQuery 仍然是一個非常好用的工具。這是 jQuery 取代了原生 JavaScript,以前如此,現在仍然如此。

沒落的 jQuery 工具函數

jQuery 2006 年被發明出來的時候,還沒有 ES5(2011年6月發布)。即使在 ES5 發布之後很長一段時間裡,也不是所有瀏覽器都支援。因此在這段時期,除了DOM 操作外,jQuery 的巨大貢獻在於解決跨瀏覽器的問題,以及提供方便的物件和陣列操作工具,例如each()index()filter等。 如今 ECMAScript 剛剛發布了 2017 的標準,瀏覽器標準混亂的問題也已經得到了很好的解決,前端界還出現了 Babel 這樣的轉譯工具和 TypeScript 之類的新語言。所以現在大家都盡可放心的使用各種新的語言特性,即使 ECMAScript 的相關標準還在製定中。在這段時期,jQuery 提供的大量工具方法都已經有了原生替代品——在使用上差別不大的情況下,確實寧願用原生實作。

事實上,jQuery 也極盡可能地採用原生實現,以提高執行效率。 jQuery 沒有放棄這些已有原生實作的工具函數/方法,主要還是因為向下相容,以及一如既往的提供瀏覽器相容性——畢竟不是每一個使用 jQuery 的開發者都會使用轉譯工具。

那麼,對於 JavaScript 開發者而言,jQuery 確實有許多工具方法可以被原生 JavaScript 函數/方法取代。例如

$.parseJSON()

可以用
JSON.parse()

替代,而且

JSON.stringify()

也彌補了 jQuery

  • $.extend() 的部分功能可以由 Object.assign() 替代`

  • $.fn 的一些数据处理工具方法,比如 each()index() 等都可以用 Array.prototype 中相应的工具方法替代,比如 forEach()indexOf() 等。

  • $.Deferred() 和 jQuery Promise 在某些情况下可以用原生 Promise 替代。它们在没有 ES6 之前也算是个不错的 Promise 实现。

  • ......

  • $.fn 就是 jQuery.prototype,也就是 jQuery 对象的原型。所以在其上定义的方法就是 jQuery 对象的方法。

    这些工具方法在原生 JavaScript 中已经逐渐补充完善,但它们仍然只是在某些情况下可以被替代……因为 jQuery 对象是一个特有的数据结构,针对 jQuery 自身创建的工具方法在作用于 jQuery 对象的时候会有一些针对性的实现——既然 DOM 操作仍然不能把 jQuery 抛开,那这些方法也就不可能被完全替换掉。

    jQuery 与原生 JavaScript 的结合

    有时候需要用 jQuery,有时候不需要用,该如何分辨?

    jQuery 的优势在于它的 DOM 处理、Ajax,以及跨浏览器。如果在项目中引入 jQuery,多半是因为对这些功能的需求。而对于不操作 DOM,也不需要考虑跨浏览器(比如用于转译工具)的部分,则考虑尽可能的用原生 JavaScript 实现。

    如此以来,一定会存在 jQuery 和原生 JavaScript 的交集,那么,就不得不说说需要注意的地方。

    jQuery 对象实现了部分数组功能的伪数组

    首先要注意的一点,就是 jQuery 对象是一个伪数组,它是对原生数组或伪数组(比如 DOM 节点列表)的封装。

    如果要获得某个元素,可以用 [] 运算符或 get(index) 方法;如果要获得包含所有元素的数组,可以使用 toArray() 方法,或者通过 ES6 中引入的 Array.from() 来转换。

    // 将普通数组转换成 jQuery 对象
    const jo = $([1, 2, 3]);
    jo instanceof jQuery;   // true
    Array.isArray(jo);      // false
    
    // 从 jQuery 对象获取元素值
    const a1 = jo[0];       // 1
    const a2 = jo.get(1);   // 2
    
    // 将 jQuery 对象转换成普通数组
    const arr1 = jo.toArray();      // [1, 2, 3]
    Array.isArray(arr1);            // true
    const arr2 = Array.from(jo);    // [1, 2, 3]
    Array.isArray(arr2);            // true
    登入後複製

    注意 each/mapforEach/map 回调函数的参数顺序

    jQuery 定义在 $.fn 上的 each()map() 方法与定义在 Array.prototype 上的原生方法 forEach()map() 对应,它们的参数都是回调函数,但它们的回调函数定义有一些细节上的差别。

    $.fn.each() 的回调定义如下:

    Function(Integer index, Element element )

    回调的第一个参数是数组元素所在的位置(序号,从 0 开始),第二个参数是元素本身。

    Array.prototype.forEach() 的回调定义是

    Function(currentValue, index, array)

    回调的第一个参数是数组元素本身,第二个参数才是元素所有的位置(序号)。而且这个回调有第三个参数,即整个数组的引用。

    请特别注意这两个回调定义的第一个参数和第二个参数,所表示的意义正好交换,这在混用 jQuery 和原生代码的时候很容易发生失误。

    对于 $.fn.map()Array.prototype.map() 的回调也是如此,而且由于这两个方法同名,发生失误的概率会更大。

    注意 each()/map() 中的 this

    $.fn.each()$.fn.map() 回调中经常会使用 this,这个 this 指向的就是当前数组元素。正是因为有这个便利,所以 jQuery 在定义回请贩时候没有把元素本身作为第一个参数,而是把序号作为第一个参数。

    不过 ES6 带来了箭头函数。箭头函数最常见的作用就是用于回调。箭头函数中的 this 与箭头函数定义的上下文相关,而不像普通函数中的 this 是与调用者相关。

    现在问题来了,如果把箭头函数作为 $.fn.each()$.fn.map() 的回调,需要特别注意 this 的使用——箭头函数中的 this 不再是元素本身。鉴于这个问题,建议若非必要,仍然使用函数表达式作为 $.fn.each()$.fn.map() 的回调,以保持原有的 jQuery 编程习惯。实在需要使用箭头函数来引用上下文 this 的情况下,千万记得用其回调定义的第二个参数作为元素引用,而不是 this

    // 将所有输入控制的 name 设置为其 id
    $(":input").each((index, input) => {
        // const $input = $(this) 这是错误的!!!
        const $input = $(input);
        $input.prop("name", $input.prop("id"));
    });
    登入後複製

    $.fn.map() 返回的并不是数组

    Array.prototype.map() 不同,$.fn.map() 返回的不是数组,而是 jQuery 对象,是伪数组。如果需要得到原生数组,可以采用 toArray()Array.from() 输出。

    const codes = $([97, 98, 99]);
    const chars = codes.map(function() {
        return String.fromCharCode(this);
    });     // ["a", "b", "c"]
    
    chars instanceof jQuery;    // true
    Array.isArray(chars);       // false
    
    const chars2 = chars.toArray();
    Array.isArray(chars2);      // true
    登入後複製

    jQuery Promise

    jQuery 是通过 $.Deferred() 来实现的 Promise 功能。在 ES6 以前,如果引用了 jQuery,基本上不需要再专门引用一个 Promise 库,jQuery 已经实现了 Promise 的基本功能。

    不过 jQuery Promise 虽然实现了 then(),却没有实现 catch(),所以它不能兼容原生的 Promise,不过用于 co 或者 ES2017 的 async/await 毫无压力。

    // 模拟异步操作
    function mock(value, ms = 200) {
        const d = $.Deferred();
        setTimeout(() => {
            d.resolve(value);
        }, ms);
        return d.promise();
    }
    登入後複製
    // co 实现
    co(function* () {
        const r1 = yield mock(["first"]);
        const r2 = yield mock([...r1, "second"]);
        const r3 = yield mock([...r2, "third"]);
        console.log(r1, r2, r3);
    });
    
    // ['first']
    // ['first', 'second']
    // ['first', 'second', 'third']
    登入後複製
    // async/await 实现,需要 Chrome 55 以上版本测试
    (async () => {
        const r1 = await mock(["first"]);
        const r2 = await mock([...r1, "second"]);
        const r3 = await mock([...r2, "third"]);
        console.log(r1, r2, r3);
    })();
    
    // ['first']
    // ['first', 'second']
    // ['first', 'second', 'third']
    登入後複製

    虽然 jQuery 的 Promise 没有 catch(),但是提供了 fail 事件处理,这个事件在 Deferred reject() 的时候触发。相应的还有 done 事件,在 Deferred resovle() 的时候触发,以及 always 事件,不论什么情况都会触发。

    与一次性的 then() 不同,事件可以注册多个处理函数,在事件触发的时候,相应的处理函数会依次执行。另外,事件不具备传递性,所以 fail() 不能在写在 then() 链的最后。

    结尾

    总的来说,在大量操作 DOM 的前端代码中使用 jQuery 可以带来极大的便利,也使 DOM 操作的相关代码更易读。另一方面,原生 JavaScript 带来的新特性确实可以替代 jQuery 的部分工具函数/方法,以降低项目对 jQuery 的依赖程序。

    jQuery 和原生 JavaScript 应该是共生关系,而不是互斥关系。应该在合适的时候选用合适的方法,而不是那么绝对的非要用谁代替谁。

    原生JS取代一些JQuery方法

    1.选取元素

    // jQuery
    var els = $(&#39;.el&#39;);
    // Native
    var els = document.querySelectorAll(&#39;.el&#39;);
    // Shorthand
    var $ = function (el) {
      return document.querySelectorAll(el);
    }
    登入後複製

    querySelectorAll方法返回的是NodeList对象,需要转换为数组。

    myList = Array.prototype.slice.call(myNodeList)
    登入後複製

    2.创建元素

    // jQuery
    var newEl = $(&#39;<div/>&#39;);
    // Native
    var newEl = document.createElement(&#39;div&#39;);
    登入後複製

    3.添加事件

    // jQuery
    $(&#39;.el&#39;).on(&#39;event&#39;, function() {
    });
    // Native
    [].forEach.call(document.querySelectorAll(&#39;.el&#39;), function (el) {
      el.addEventListener(&#39;event&#39;, function() {
      }, false);
    });
    登入後複製

    4.get/set属性

    // jQuery
    $(&#39;.el&#39;).filter(&#39;:first&#39;).attr(&#39;key&#39;, &#39;value&#39;);
    $(&#39;.el&#39;).filter(&#39;:first&#39;).attr(&#39;key&#39;);
    // Native
    document.querySelector(&#39;.el&#39;).setAttribute(&#39;key&#39;, &#39;value&#39;);
    document.querySelector(&#39;.el&#39;).getAttribute(&#39;key&#39;);
    登入後複製

    5.添加和移除样式Class

    DOM元素本身有一个可读写的className属性,可以用来操作class。

    HTML 5还提供一个classList对象,功能更强大(IE 9不支持)。

    // jQuery
    $(&#39;.el&#39;).addClass(&#39;class&#39;);
    $(&#39;.el&#39;).removeClass(&#39;class&#39;);
    $(&#39;.el&#39;).toggleClass(&#39;class&#39;);
    // Native
    document.querySelector(&#39;.el&#39;).classList.add(&#39;class&#39;);
    document.querySelector(&#39;.el&#39;).classList.remove(&#39;class&#39;);
    document.querySelector(&#39;.el&#39;).classList.toggle(&#39;class&#39;);
    登入後複製

    6.追加元素

    尾部追加元素:

    // jQuery
    $(&#39;.el&#39;).append($(&#39;<div/>&#39;));
    // Native
    document.querySelector(&#39;.el&#39;).appendChild(document.createElement(&#39;div&#39;));
    登入後複製

    头部追加元素:

    //jQuery
    $(‘.el’).prepend(&#39;<div></div>&#39;)
    //Native
    var parent = document.querySelector(&#39;.el&#39;);
    parent.insertBefore("<div></div>",parent.childNodes[0])
    登入後複製

    7.克隆元素

    // jQuery
    var clonedEl = $(&#39;.el&#39;).clone();
    // Native
    var clonedEl = document.querySelector(&#39;.el&#39;).cloneNode(true);
    登入後複製

    8.移除元素

    Remove
    // jQuery
    $(&#39;.el&#39;).remove();
    // Native
    remove(&#39;.el&#39;);
    function remove(el) {
      var toRemove = document.querySelector(el);
      toRemove.parentNode.removeChild(toRemove);
    }
    登入後複製

    9.获取父级元素

    // jQuery
    $(&#39;.el&#39;).parent();
    // Native
    document.querySelector(&#39;.el&#39;).parentNode;
    登入後複製

    10.获取上一个/下一个元素(Prev/next element)

    // jQuery
    $(&#39;.el&#39;).prev();
    $(&#39;.el&#39;).next();
    // Native
    document.querySelector(&#39;.el&#39;).previousElementSibling;
    document.querySelector(&#39;.el&#39;).nextElementSibling;
    登入後複製

    11.XHR and AJAX

    // jQuery
    $.get(&#39;url&#39;, function (data) {
    });
    $.post(&#39;url&#39;, {data: data}, function (data) {
    });
    // Native
    // get
    var xhr = new XMLHttpRequest();
    xhr.open(&#39;GET&#39;, url);
    xhr.onreadystatechange = function (data) {
    }
    xhr.send();
    // post
    var xhr = new XMLHttpRequest()
    xhr.open(&#39;POST&#39;, url);
    xhr.onreadystatechange = function (data) {
    }
    xhr.send({data: data});
    登入後複製

    12.清空子元素

    //jQuery
    $("#elementID").empty()
    //Native
    var element = document.getElementById("elementID")
    while(element.firstChild) element.removeChild(element.firstChild);
    登入後複製

    13.检查是否有子元素

    //jQuery
    if (!$("#elementID").is(":empty")){}
    //Native
    if (document.getElementById("elementID").hasChildNodes()){}
    登入後複製

    14.$(document).ready

    DOM加载完成,会触发DOMContentLoaded事件,等同于jQuery的$(document).ready方法。

    document.addEventListener("DOMContentLoaded", function() {    
       // ...
    });
    登入後複製

    15.数据储存

    jQuery对象可以储存数据。

    $("body").data("foo", 52);
    登入後複製

    HTML 5有一个dataset对象,也有类似的功能(IE 10不支持),不过只能保存字符串。

    element.dataset.user = JSON.stringify(user);
    element.dataset.score = score;
    登入後複製

    16.动画

    jQuery的animate方法,用于生成动画效果。

    $foo.animate(&#39;slow&#39;, { x: &#39;+=10px&#39; }
    登入後複製

    jQuery的动画效果,很大部分基于DOM。但是目前,CSS 3的动画远比DOM强大,所以可以把动画效果写进CSS,然后通过操作DOM元素的class,来展示动画。

    foo.classList.add(&#39;animate&#39;)
    登入後複製

    如果需要对动画使用回调函数,CSS 3也定义了相应的事件。

    el.addEventListener("webkitTransitionEnd", transitionEnded);
    el.addEventListener("transitionend", transitionEnded);
    登入後複製


    相關標籤:
    來源:php.cn
    本網站聲明
    本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
    熱門教學
    更多>
    最新下載
    更多>
    網站特效
    網站源碼
    網站素材
    前端模板