跳轉到內容

JavaScript/最佳實踐

來自華夏公益教科書,為開放世界提供開放書籍



本章將介紹當前 JavaScript 社群標準和每個程式設計師都應該瞭解的最佳實踐。

document.write

[編輯 | 編輯原始碼]

此方法已棄用。請使用 innerHTML 或 DOM 操作方法代替。

在 XHTML 中,document.write 無法正常工作,但可以使用 DOM 操作方法實現相同的效果 [1]

JavaScript 協議

[編輯 | 編輯原始碼]

儘量避免僅用於執行 JavaScript 程式碼的連結。

<a href="javascript:resumeFancyVersion()">See my résumé!</a>

相反,請考慮

<a href="resumePlainVersion.html" onclick="return !resumeFancyVersion()">See my résumé!</a>

啟用 JavaScript 的使用者將獲得包含 JavaScript 增強內容的版本(可能使用 DHTML),而沒有 JavaScript 的使用者將被重定向到靜態提供該內容的 XHTML 頁面。這比擁有缺少正常 href 屬性值的 <a> 元素更易於訪問。首先,它使用 正確的語言語義。其次,它為沒有 JavaScript 的使用者提供了訪問您內容的途徑。第三,它檢測函式執行是否成功,並在出現故障的情況下將啟用 JS 的讀者也重定向。

onclick 表示式計算為一個布林值。如果該函式成功執行了所需的效果並返回 true,則 onclick 將返回失敗,並且超連結不會執行。當該函式因任何原因而失敗時,false、null 或 undefined 值將計算為 true,並且不會阻止連結執行。或者,如果您不想提供靜態等效項,可以在語義要求較低的元素中嵌入 onclick 屬性

<strong onclick="resumeFancyVersion()">See my résumé!</strong>

因此,任何使用者代理在閱讀減少的 <a> 元素時都不會感到困惑。

電子郵件驗證

[編輯 | 編輯原始碼]

許多人使用 JavaScript 函式來立即捕獲表單條目中最常見的錯誤型別。例如,一些網頁表單要求人們兩次輸入相同的內容。其他網頁表單要求人們輸入電子郵件地址。然後他們快速檢查輸入的內容是否至少看起來像電子郵件地址

function isValidEmail(string) {
  // These comments use the following terms from RFC2822:
  // local-part, domain, domain-literal and dot-atom.
  // Does the address contain a local-part followed an @ followed by a domain?
  // Note the use of lastIndexOf to find the last @ in the address
  // since a valid email address may have a quoted @ in the local-part.
  // Does the domain name have at least two parts, i.e. at least one dot,
  // after the @? If not, is it a domain-literal?
  // This will accept some invalid email addresses
  // BUT it doesn't reject valid ones.
  var atSym = string.lastIndexOf("@");
  if (atSym < 1) { return false; } // no local-part
  if (atSym == string.length - 1) { return false; } // no domain
  if (atSym > 64) { return false; } // there may only be 64 octets in the local-part
  if (string.length - atSym > 255) { return false; } // there may only be 255 octets in the domain

  // Is the domain plausible?
  var lastDot = string.lastIndexOf(".");
  // Check if it is a dot-atom such as example.com
  if (lastDot > atSym + 1 && lastDot < string.length - 1) { return true; }
  // Check if could be a domain-literal.
  if (string.charAt(atSym + 1) == '[' && string.charAt(string.length - 1) == ']') { return true; }
  return false;
}

不幸的是,一些其他的“電子郵件驗證”JavaScript 函式拒絕了完全有效的電子郵件地址。例如,一些 錯誤地拒絕了 包含“+”號的有效地址。

原始的電子郵件地址語法 (RFC 821) 確實允許“+”號。 RFC 822 於同月(1982 年 8 月)釋出,也允許它們。當前版本的語法在 RFC2821/RFC2822 中給出。 RFC3696 的第 3 節提供了關於非典型有效電子郵件地址的有用示例。

驗證後,許多 JavaScript 程式設計師使用 encodeURIComponent() 對電子郵件地址進行編碼,以解決某些無法正確處理加號的客戶端語言。 [1][2]

用於電子郵件地址的引用規則的複雜性使得對地址的本地部分或域字面量進行完整測試變得不切實際。鑑於不一定存在與有效本地部分相對應的真實郵箱,那麼在複雜的驗證指令碼上花費多少額外的下載時間呢?

根據 RFC2822 有效的示例

[編輯 | 編輯原始碼]
  • me@example.com
  • a.nonymous@example.com
  • name+tag@example.com
  • a.name+tag@example.com
  • me.example@com
  • "spaces must be quoted"@example.com
  • !#$%&'*+-/=.?^_`{|}~@[1.0.0.127]
  • !#$%&'*+-/=.?^_`{|}~@[IPv6:0123:4567:89AB:CDEF:0123:4567:89AB:CDEF]
  • me(this is a comment)@example.com – 註釋不受鼓勵,但 RFC2822 未禁止。

根據 RFC2822 無效的示例

[編輯 | 編輯原始碼]
  • me@
  • @example.com
  • me.@example.com
  • .me@example.com
  • me@example..com
  • me\@example.com
  • spaces\ must\ be\ within\ quotes\ even\ when\ escaped@example.com
  • a\@mustbeinquotes@example.com

測試頁面

[編輯 | 編輯原始碼]

註釋:此程式碼的設計錯誤,會導致拒絕某些實際上有效的電子郵件。如果對有效和無效電子郵件的更改被接受,則應同時審查以下程式碼。

可以使用以下測試頁面測試電子郵件驗證函式。將三個檔案儲存在同一個目錄中,然後在 Web 瀏覽器中開啟 validEmail.htm

validEmail.js

function isValidEmail(string) {
  // These comments use the following terms from RFC2822:
  // local-part, domain, domain-literal and dot-atom.
  // Does the address contain a local-part followed an @ followed by a domain?
  // Note the use of lastIndexOf to find the last @ in the address
  // since a valid email address may have a quoted @ in the local-part.
  // Does the domain name have at least two parts, i.e. at least one dot,
  // after the @? If not, is it a domain-literal?
  // This will accept some invalid email addresses
  // BUT it doesn't reject valid ones.
  var atSym = string.lastIndexOf("@");
  if (atSym < 1) { return false; } // no local-part
  if (atSym == string.length - 1) { return false; } // no domain
  if (atSym > 64) { return false; } // there may only be 64 octets in the local-part
  if (string.length - atSym > 255) { return false; } // there may only be 255 octets in the domain

  // Is the domain plausible?
  var lastDot = string.lastIndexOf(".");
  // Check if it is a dot-atom such as example.com
  if (lastDot > atSym + 1 && lastDot < string.length - 1) { return true; }
  //  Check if could be a domain-literal.
  if (string.charAt(atSym + 1) == '[' && string.charAt(string.length - 1) == ']') { return true; }
  return false;
}

function testIsValidEmail(string) {
  alert("'" + string + "' is " + (isValidEmail(string) ? "" : "NOT ") + " a valid email address.");
}

function checkSamples() {
  var validAddress = [
    'me@example.com',
    'a.nonymous@example.com',
    'name+tag@example.com',
    'name\\@tag@example.com',
    'spaces\\ are\\ allowed@example.com',
    '"spaces may be quoted"@example.com',
    '!#$%&\'*+-/=.?^_`{|}~@[1.0.0.127]',
    '!#$%&\'*+-/=.?^_`{|}~@[IPv6:0123:4567:89AB:CDEF:0123:4567:89AB:CDEF]',
    'me(this is a comment)@example.com'
  ];
  var invalidAddress = [
    'me@',
    '@example.com',
    'me.@example.com',
    '.me@example.com',
    'me@example..com',
    'me.example@com',
    'me\\@example.com'
  ];

  var results = new StringBuffer();

  var handlesValidAddressesCorrectly = true;
  results.append('<table border="1">');
  results.append('<caption>Does the function accept all the valid sample addresses?</caption>');
  results.append('<tr><th>Valid address</th><th>Function returns</th></tr>');
  for (var i = 0; i < validAddress.length; i++) {
    results.append('<tr><td>');
    results.append(validAddress[i]);
    results.append('</td><td>');
    if (isValidEmail(validAddress[i])) {
      results.append('<span class="good">true</span>');
    } else {
      handlesValidAddressesCorrectly = false;
      results.append('<strong class="fail">false</strong>');
    }
    results.append('</td></tr>');
  }
  results.append('</table>');

  var handlesInvalidAddressesCorrectly = true;
  results.append('<table border="1">');
  results.append('<caption>Does the function reject all the invalid sample addresses?</caption>');
  results.append('<tr><th>Valid address</th><th>Function returns</th></tr>');
  for (var i = 0; i < invalidAddress.length; i++) {
    results.append('<tr><td>');
    results.append(invalidAddress[i]);
    results.append('</td><td>');
    if (!isValidEmail(invalidAddress[i])) {
      results.append('<span class="good">false</span>');
    } else {
      handlesInvalidAddressesCorrectly = false;
      results.append('<em class="warning">true</em>');
    }
    results.append('</td></tr>');
  }
  results.append('</table>');

  var conclusion;
  if (handlesValidAddressesCorrectly) {
    if (handlesInvalidAddressesCorrectly) {
      conclusion = '<p><strong class="good">The function works correctly with all the sample addresses.</strong></p>';
    } else {
      conclusion = '<p><em class="warning">The function incorrectly accepts some invalid addresses.</em></p>';
    }
  } else {
    conclusion = '<p><strong class="fail">The function incorrectly rejects some valid addresses.</strong></p>';
  }

  document.getElementById('testResults').innerHTML = conclusion + results.toString();
}

function StringBuffer() {
  this.buffer = "";
}

StringBuffer.prototype.append = function(string) {
  this.buffer += string;
  return this;
};

StringBuffer.prototype.toString = function() {
  return this.buffer;
};

validEmail.css

body {
  background-color: #fff;
  color: #000
}

table {
  margin-bottom: 2em
}

caption {
  margin-top: 2em
}

.good {
  background-color: #0f0;
  color: #000
}

.warning {
  background-color: #fc9;
  color: #000
}

.fail {
  background-color: #f00;
  color: #fff
}

validEmail.htm

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Valid email test</title>
    <link rel="stylesheet" href="validEmail.css">
    <script src="validEmail.js"></script>
  </head>
  <body onload="checkSamples()">
    <h1>Unit test for email address validation functions</h1>
    <h2>Interactive test</h2>
    <form action="#">
      <fieldset>
        <legend>Email address</legend>
        <label for="emailAddress">Email</label>
        <input type="text" size="40" value="" name="email" id="emailAddress">
        <input type="button" name="validate" value="Check address"
          onclick="testIsValidEmail(this.form.email.value)">
      </fieldset>
    </form>
    <h2>Selected samples</h2>
    <p>This section shows the results of testing the function with sample strings.
      The sample includes both valid strings and invalid strings
      according to <a href="http://www.faqs.org/rfcs/rfc2822.html">RFC2822</a>.
    </p>
    <div id="testResults">You need to enable JavaScript for this unit test to work.</div>
  </body>
</html>

use strict

[編輯 | 編輯原始碼]

許多 JavaScript 程式設計師建議透過在任何其他語句之前放置確切的語句 "use strict"; 來啟用 ECMAScript 5 的嚴格模式:[3][4][5]

  "use strict";
  function 

進一步閱讀

[編輯 | 編輯原始碼]

JavaScript 最佳實踐

其他語言中的最佳實踐

[編輯 | 編輯原始碼]

參考文獻

[編輯 | 編輯原始碼]
華夏公益教科書