負責任地編寫JavaScript代碼(一)

原文地址:alistapart.com/article/res…
原文做者:Jeremy Wagner
譯者:劉輝
聲明:本翻譯僅作學習交流使用,轉載請註明來源javascript

從統計數據上看,JavaScript是性能的關鍵。以如今的趨勢,中等大小的頁面很快就會至少發送 400 KB 的 JavaScript,而這僅僅是傳輸時的大小,而且仍是壓縮後的。php

不幸的是,雖然減小資源傳輸時間是整個性能的重要組成部分,可是壓縮並不會影響瀏覽器處理整個腳本所需的時間。若是服務器發送了 400 KB 的壓縮 JavaScript,則解壓縮後瀏覽器須要處理的實際大小超過 1 MB。如何應對這些繁重的工做負載,取決於設備自己。 關於各類設備如何處理大批量JavaScript的文章不少,但事實是,在不一樣的設備之間,即便是微不足道的處理時間也會有相差很大差距。css

以個人這個一次性項目爲例,該項目提供約 23 KB 的未壓縮 JavaScript。 在 2017 年中的 MacBook Pro 上,Chrome 耗時約 25 毫秒。可是,在諾基亞 2 Android手機上,該數字迅速增長到 190 毫秒。這不是很短的時間,可是在任何一種狀況下,頁面的交互速度都至關快。html

如今有個大問題:你如何看待諾基亞 2 在這些普通頁面上的表現呢?它有些卡,即便是網絡鏈接很快,瀏覽網頁也是一種耐心的練習,由於 JavaScript 驅動的網頁會花費很長的時間。java

圖1

圖 1. 在一個頁面上瀏覽諾基亞 2 Android 手機的性能時間表概述,其中過多的 JavaScript 阻塞了主線程。react

儘管設備和網絡都在不斷進步,可是 JavaScript 的不斷膨脹吞噬了這些收益。咱們須要負責任地使用 JavaScript。首先要了解咱們正在構建的內容以及構建方式。git

「網站」和「應用」

怪異的命名可能讓咱們不能準確的認識事物的本質。蜜蜂和黃蜂差別很大,可是有時候咱們會把蜜蜂說成黃蜂。然而蜜蜂是益蟲,黃蜂不是。github

網站和 WEB 應用程序的區別並不像黃夾克和蜜蜂之間的區別那麼明顯,可是若是把一個網站和一個功能齊全的 WEB 應用程序搞混,開發者和使用者都會很是痛苦。 若是你要爲企業建立一個信息網站,則不太可能使用重量級的框架來管理DOM的變化或者使用客戶端路由。使用不合適的工具不但會給用戶形成損失,還會下降效率。web

當咱們構建一個 WEB 應用程序時,必需要注意:咱們正在安裝的模塊可能會帶來數百(甚至數千)個依賴,其中一些甚至不肯定是否是安全的。 咱們還要編寫複雜的配置來打包。在這種瘋狂卻無處不在的開發環境中,咱們須要摸清它們來確保構建的內容是快速且可訪問的。 若是你對此不夠了解,請在項目的根目錄中運行 npm ls --prod,看看是否能識別該列表中的全部內容。即便這樣,也不能保證第三方腳本徹底沒有問題,我相信您的網站中至少有一些這樣的腳本。chrome

咱們很容易忘記,網站和 WEB 應用程序所處的環境是同樣的。二者都承受着來自各類各樣的網絡和設備的相同的環境壓力。當咱們決定構建「應用程序」時,這些限制不會忽然消失,用戶的手機也不會得到神奇的新功能。

咱們有責任評估誰在使用咱們的產品,並認識到他們訪問互聯網的條件可能與咱們預想的條件不一樣。咱們須要知道咱們要實現的目標,而後才能夠構建出能夠達到目標的產品,即便構建起來並不使人興奮

這意味着須要從新評估對 JavaScript 的依賴,以及使用 JavaScript 的方式。排斥 HTML 和 CSS 會讓咱們走向不可持續的開發方式,從而損害性能和可訪問性。

不要讓框架迫使您陷入不可持續的模式

在團隊合做中,我發現了一些奇怪的代碼,這些團隊依賴於框架來幫助他們提升生產力。這些奇怪代碼的共同特徵是會致使可訪問性和性能變差。如下面的 React 組件爲例:

import React, { Component } from "react";
import { validateEmail } from "helpers/validation";

class SignupForm extends Component {
  constructor (props) {
    super(props);

    this.handleSubmit = this.handleSubmit.bind(this);
    this.updateEmail = this.updateEmail.bind(this);
    this.state.email = "";
  }

  updateEmail (event) {
    this.setState({
      email: event.target.value
    });
  }

  handleSubmit () {
    // If the email checks out, submit
    if (validateEmail(this.state.email)) {
      // ...
    }
  }

  render () {
    return (
      
        Enter your email:
        
        Sign Up
      
    );
  }
}

複製代碼

這裏有一些值得注意的可訪問性問題:

  1. 不使用 form 元素的表單不是表單。確實,你能夠經過在父 div 中指定 role="form" 來對此進行說明,可是若是您要構建表單(確定看起來像一個表單),請使用具備適當操做和方法屬性的 form 元素。 action 屬性相當重要,由於它能夠確保表單在缺乏 JavaScript 的狀況下仍然能夠執行某些操做,固然,只要組件是由服務器呈現的。

  2. span 元素不能替代 label 元素,label 元素可以提供可訪問性而 span 元素不能。

  3. 若是咱們打算在提交表單以前在客戶端作某事,那麼咱們應該將綁定到 button 元素的 onClick 的邏輯移到 form 元素的 onSubmit 上。

  4. 順便說一下,全部支持 HTML5 的瀏覽器,包括 IE10,都提供表單驗證控件,爲何還要使用 JavaScript 來驗證電子郵件地址?這裏可使用瀏覽器原生的驗證功能,但請注意,要使其與屏幕閱讀器配合使用,還須要一點技巧

  5. 儘管不是可訪問性問題,可是此組件不依賴任何狀態或生命週期方法,這意味着能夠將其重構爲無狀態功能組件,這樣可讓 JavaScript 更少。

瞭解了這些內容,咱們能夠重構此組件:

import React from "react";

const SignupForm = props => {
  const handleSubmit = event => {
    // Needed in case we're sending data to the server XHR-style
    // (but will still work if server-rendered with JS disabled).
    event.preventDefault();

    // Carry on...
  };
  
  return (
    <form method="POST" action="/signup" onSubmit={handleSubmit}> <label for="email" class="email-label">Enter your email:</label> <input type="email" id="email" required /> <button>Sign Up</button> </form> ); }; 複製代碼

如今這個組件不只 JavaScript 減小了,並且可訪問性也提升了不少。瀏覽器爲咱們提供了不少免費的功能,咱們應該優先使用瀏覽器原生的功能。

這並非說只有在使用框架時纔會出現沒法訪問的模式,而是對 JavaScript 的惟一偏心最終會在咱們對 HTML 和 CSS 的理解上出現差距。這些知識鴻溝一般會致使咱們甚至可能沒有意識到的錯誤。框架是提升咱們生產力的有用工具,可是不管咱們選擇使用哪一種工具,核心網絡技術的持續學習對於創造良好的體驗都是必不可少的。

依靠 web 平臺,您將走得更快,更遠

當咱們討論框架問題時,必須說 web 平臺自己就是一個強大的框架。如上一部分所示,咱們能夠更好地依靠已經成熟的模式和瀏覽器自身特性。重複造輪子來替代它們只會讓咱們本身更痛苦。

單頁應用

開發者最容易掉入的陷阱之一就是盲目採用單頁應用「SPA」模型,即便該模型不適合該項目。是的,經過 SPA 的客戶端路由,用戶確實能夠得到更好的體驗,可是你會失去什麼呢? 瀏覽器本身的導航功能(儘管是同步的)提供了不少好處。 好比,根據規範來管理歷史記錄。 沒有 JavaScript 的用戶(不管是否由他們本身選擇)都不會徹底失去訪問權限。 要使 SPA 在沒有 JavaScript 的狀況下仍然可用,服務器端渲染就成了你必須考慮的事情。

圖2

圖2.慢網絡環境下一個示例應用程序加載的比較。左側的應用徹底取決於 JavaScript 來呈現頁面。右側的應用程序在服務器上呈現響應,但隨後使用客戶端映射將組件附加到現有的服務器提供的標記上。

若是客戶端路由沒法讓人們知道頁面上的內容已更改,則可訪問性也會受到損害。這會使那些依靠輔助技術瀏覽頁面的人沒法肯定頁面上發生了什麼改變,解決這個問題是一項艱鉅的任務。

而後是咱們的老對手:系統開銷。不少客戶端路由庫很是小,可是當你的項目使用ReactReact Router,甚至再加上一個狀態管理庫做爲基礎時,你將接受大約135KB永遠沒法優化的代碼。請仔細考慮這樣的構建方式以及客戶端路由是否真的有必要。一般狀況下,能不用就不用。

若是擔憂導航性能,能夠用 rel = prefetch 來預加載同源的文檔。 預加載的文檔在緩存中,跳轉時當即可用,所以對改善頁面的感知加載性能具備顯著做用。因爲預加載的優先級較低,所以它們與關鍵資源搶帶寬的可能性也較小。

圖3

圖3.在初始頁面上預加載了 writing/ 的 HTML。 當用戶請求 writing/ 時,會當即從瀏覽器緩存中加載其HTML。

連接預加載的主要缺點是你須要意識到它可能會形成浪費。 Quicklink是Google的一個很小的連接預加載腳本,它經過檢查當前客戶端是否處於慢網絡環境或啓用了數據保護程序模式,來判斷是否進行預加載,而且默認狀況下不進行跨域的預加載。

不管咱們是否使用客戶端路由,Service workers 能夠極大地提高回頭用戶的體驗。當咱們用 Service workers 預緩存路由時,咱們將得到與連接預加載相同的好處,可是對請求和響應的控制程度更高。不管你是否將你的站點視爲「應用程序」,向其添加Service workers都是當今存在的最負責任的 JavaScript 用法之一。

JavaScript 並不是佈局難題的解決方案

若是咱們打算經過安裝第三方模塊來解決佈局問題,那應該先想一想,「我到底要作什麼?」 CSS 旨在完成此工做,而且不須要任何抽象便可有效使用。 現在,JavaScript模塊試圖解決的大多數佈局問題,例如盒子放置,對齊和調整大小,管理文本溢出,甚至整個佈局系統,均可以使用 CSS 解決。 像 Flexbox 和 Grid 這樣的現代佈局引擎獲得了很好的支持,所以咱們不須要使用任何佈局框架來做爲構建項目的基礎。CSS 原本就是框架。咱們能夠經過feature queries,漸進式的加強佈局,這並不困難

/* Your mobile-first, non-CSS grid styles goes here */

/* The @supports rule below is ignored by browsers that don't support CSS grid, _or_ don't support @supports. */
@supports (display: grid) {
  /* Larger screen layout */
  @media (min-width: 40em) {
    /* Your progressively enhanced grid layout styles go here */
  }
}


複製代碼

使用 JavaScript 解決方案來解決佈局和展現問題並非什麼新鮮事。 咱們在 2009 年就是這麼幹的,網站在每一個瀏覽器裏看起來都應該徹底同樣,無論是在 IE6 裏仍是在更強大的瀏覽器裏。 若是咱們在 2019 年仍然追求這個,那應從新評估咱們的開發目標。 總會有一些咱們必須支持的瀏覽器沒法完成那些現代瀏覽器所能完成的工做。 全部平臺上的所有視覺均等只是徒勞的追求,漸進加強纔是的主要目標。

我不是要殺死JavaScript

沒錯,我對 JavaScript 沒有惡意。它給了我一份職業,並且,若是我對本身說實話,那將是十多年的享受之源。像任何長期的關係同樣,我花的時間越多,對它的瞭解就越多。這是一種成熟的,功能豐富的語言,並且隨着時間的流逝,它只會變得愈來愈有能力和更優雅。

可是,JavaScript 讓我感到有些矛盾。我對 JavaScript 持批判的態度,或許更準確地說,我對於把 JavaScript 做爲構建 WEB 的首要手段的趨勢持批判態度。當我拆開一個捆成一團的聖誕樹燈同樣的東西時,很明顯,JavaScript 已經氾濫成災。

在後續的系列文章中,我將提供更多實用的建議來阻止過分的使用 JavaScript,以便爲 WEB 構建的內容對每一個地方的每一個人均可用。一些建議是預防性的,一些則是以毒攻毒的,不管哪一種,都是爲了相同的目標。我相信咱們全部人都喜歡 WEB,並但願經過 WEB 作正確的事,可是我但願咱們思考如何使它對全部人更具彈性和包容性。


若是你以爲這篇內容對你有價值,請點贊,並關注咱們的官網和咱們的微信公衆號(WecTeam),每週都有優質文章推送:

WecTeam
相關文章
相關標籤/搜索