整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          JavaScript 詳解:為什么寫好的代碼非常重要

          文將通過簡單的術語和真實世界的例子解釋 JavaScript 中 this 及其用途,并告訴你寫出好的代碼為何如此重要。

          1.this 適合你嗎?

          我看到許多文章在介紹 JavaScript 的 this 時都會假設你學過某種面向對象的編程語言,比如 Java、C++ 或 Python 等。但這篇文章面向的讀者是那些不知道 this 是什么的人。我盡量不用任何術語來解釋 this 是什么,以及 this 的用法。

          也許你一直不敢解開 this 的秘密,因為它看起來挺奇怪也挺嚇人的。或許你只在 StackOverflow 說你需要用它的時候(比如在 React 里實現某個功能)才會使用。

          在深入介紹 this 之前,我們首先需要理解函數式編程和面向對象編程之間的區別。

          2.函數式編程 vs 面向對象編程

          你可能不知道,JavaScript 同時擁有面向對象和函數式的結構,所以你可以自己選擇用哪種風格,或者兩者都用。

          我在很早以前使用 JavaScript 時就喜歡函數式編程,而且會像躲避瘟疫一樣避開面向對象編程,因為我不理解面向對象中的關鍵字,比如 this。我不知道為什么要用 this。似乎沒有它我也可以做好所有的工作。

          而且我是對的。

          在某種意義上 。也許你可以只專注于一種結構并且完全忽略另一種,但這樣你只能是一個 JavaScript 開發者。為了解釋函數式和面向對象之間的區別,下面我們通過一個數組來舉例說明,數組的內容是 Facebook 的好友列表。

          假設你要做一個 Web 應用,當用戶使用 Facebook 登錄你的 Web 應用時,需要顯示他們的 Facebook 的好友信息。你需要訪問 Facebook 并獲得用戶的好友數據。這些數據可能是 firstName、lastName、username、numFriends、friendData、birthday 和 lastTenPosts 等信息。

          const data = [
           {
           firstName: 'Bob',
           lastName: 'Ross',
           username: 'bob.ross', 
           numFriends: 125,
           birthday: '2/23/1985',
           lastTenPosts: ['What a nice day', 'I love Kanye West', ...],
           },
           ...
          ]
          

          假設上述數據是你通過 Facebook API 獲得的。現在需要將其轉換成方便你的項目使用的格式。我們假設你想顯示的好友信息如下:

          • 姓名,格式為`${firstName} ${lastName}`
          • 三篇隨機文章
          • 距離生日的天數

          3.函數式方式

          函數式的方式就是將整個數組或者數組中的某個元素傳遞給某個函數,然后返回你需要的信息:

          const fullNames = getFullNames(data)
          // ['Ross, Bob', 'Smith, Joanna', ...]
          

          首先我們有 Facebook API 返回的原始數據。為了將其轉換成需要的格式,首先要將數據傳遞給一個函數,函數的輸出是(或者包含)經過修改的數據,這些數據可以在應用中向用戶展示。

          我們可以用類似的方法獲得隨機三篇文章,并且計算距離好友生日的天數。

          函數式的方式是:將原始數據傳遞給一個函數或者多個函數,獲得對你的項目有用的數據格式。

          4.面向對象的方式

          對于編程初學者和 JavaScript 初學者,面向對象的概念可能有點難以理解。其思想是,我們要將每個好友變成一個對象,這個對象能夠生成你一切開發者需要的東西。

          你可以創建一個對象,這個對象對應于某個好友,它有 fullName 屬性,還有兩個函數 getThreeRandomPosts 和 getDaysUntilBirthday。

          function initializeFriend(data) {
           return {
           fullName: `${data.firstName} ${data.lastName}`,
           getThreeRandomPosts: function() {
           // get three random posts from data.lastTenPosts
           },
           getDaysUntilBirthday: function() {
           // use data.birthday to get the num days until birthday
           }
           };
          }
          const objectFriends = data.map(initializeFriend)
          objectFriends[0].getThreeRandomPosts() 
          // Gets three of Bob Ross's posts
          

          面向對象的方式就是為數據創建對象,每個對象都有自己的狀態,并且包含必要的信息,能夠生成需要的數據。

          5.這跟 this 有什么關系?

          你也許從來沒想過要寫上面的 initializeFriend 代碼,而且你也許認為,這種代碼可能會很有用。但你也注意到,這并不是真正的面向對象。

          其原因就是,上面例子中的 getThreeRandomPosts 或 getdaysUntilBirtyday 能夠正常工作的原因其實是閉包。因為使用了閉包,它們在 initializeFriend 返回之后依然能訪問 data。關于閉包的更多信息可以看看這篇文章:作用域和閉包(https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20%26%20closures/ch5.md)。

          還有一個方法該怎么處理?我們假設這個方法叫做 greeting。注意方法(與 JavaScript 的對象有關的方法)其實只是一個屬性,只不過屬性值是函數而已。我們想在 greeting 中實現以下功能:

          function initializeFriend(data) {
           return {
           fullName: `${data.firstName} ${data.lastName}`,
           getThreeRandomPosts: function() {
           // get three random posts from data.lastTenPosts
           },
           getDaysUntilBirthday: function() {
           // use data.birthday to get the num days until birthday
           },
           greeting: function() {
           return `Hello, this is ${fullName}'s data!`
           }
           };
          }
          

          這樣能正常工作嗎?

          不能!

          我們新建的對象能夠訪問 initializeFriend 中的一切變量,但不能訪問這個對象本身的屬性或方法。當然你會問,

          難道不能在 greeting 中直接用 data.firstName 和 data.lastName 嗎?

          當然可以。但要是想在 greeting 中加入距離好友生日的天數怎么辦?我們最好還是有辦法在 greeting 中調用 getDaysUntilBirthday。

          這時輪到 this 出場了!

          6.終于——this 是什么

          this 在不同的環境中可以指代不同的東西。默認的全局環境中 this 指代的是全局對象(在瀏覽器中 this 是 window 對象),這沒什么太大的用途。而在 this 的規則中具有實用性的是這一條:

          如果在對象的方法中使用 this,而該方法在該對象的上下文中調用,那么 this 指代該對象本身。

          你會說“在該對象的上下文中調用”……是啥意思?

          別著急,我們一會兒就說。

          所以,如果我們想從 greeting 中調用 getDaysUntilBirtyday 我們只需要寫 this.getDaysUntilBirthday,因為此時的 this 就是對象本身。

          附注:不要在全局作用域的普通函數或另一個函數的作用域中使用 this!this 是個面向對象的東西,它只在對象的上下文(或類的上下文)中有意義。

          我們利用 this 來重寫 initializeFriend:

          function initializeFriend(data) {
           return {
           lastTenPosts: data.lastTenPosts,
           birthday: data.birthday, 
           fullName: `${data.firstName} ${data.lastName}`,
           getThreeRandomPosts: function() {
           // get three random posts from this.lastTenPosts
           },
           getDaysUntilBirthday: function() {
           // use this.birthday to get the num days until birthday
           },
           greeting: function() {
           const numDays = this.getDaysUntilBirthday() 
           return `Hello, this is ${this.fullName}'s data! It is ${numDays} until ${this.fullName}'s birthday!`
           }
           };
          }
          

          現在,在 initializeFriend 執行結束后,該對象需要的一切都位于對象本身的作用域之內了。我們的方法不需要再依賴于閉包,它們只會用到對象本身包含的信息。

          好吧,這是 this 的用法之一,但你說過 this 在不同的上下文中有不同的含義。那是什么意思?為什么不一定會指向對象自己?

          有時候,你需要將 this 指向某個特定的東西。一種情況就是事件處理函數。比如我們希望在用戶點擊好友時打開好友的 Facebook 首頁。我們會給對象添加下面的 onClick 方法:

          function initializeFriend(data) {
           return {
           lastTenPosts: data.lastTenPosts,
           birthday: data.birthday,
           username: data.username, 
           fullName: `${data.firstName} ${data.lastName}`,
           getThreeRandomPosts: function() {
           // get three random posts from this.lastTenPosts
           },
           getDaysUntilBirthday: function() {
           // use this.birthday to get the num days until birthday
           },
           greeting: function() {
           const numDays = this.getDaysUntilBirthday() 
           return `Hello, this is ${this.fullName}'s data! It is ${numDays} until ${this.fullName}'s birthday!`
           },
           onFriendClick: function() {
           window.open(`https://facebook.com/${this.username}`)
           }
           };
          }
          

          注意我們在對象中添加了 username 屬性,這樣 onFriendClick 就能訪問它,從而在新窗口中打開該好友的 Facebook 首頁。現在只需要編寫 HTML:

          <button id="Bob_Ross">
           <!-- A bunch of info associated with Bob Ross -->
          </button> 
          

          還有 JavaScript:

          const bobRossObj = initializeFriend(data[0])
          const bobRossDOMEl = document.getElementById('Bob_Ross')
          bobRossDOMEl.addEventListener("onclick", bobRossObj.onFriendClick)
          

          在上述代碼中,我們給 Bob Ross 創建了一個對象。然后我們拿到了 Bob Ross 對應的 DOM 元素。然后執行 onFriendClick 方法來打開 Bob 的 Facebook 主頁。似乎沒問題,對吧?

          有問題!

          哪里出錯了?

          注意我們調用 onclick 處理程序的代碼是 bobRossObj.onFriendClick。看到問題了嗎?要是寫成這樣的話能看出來嗎?

          bobRossDOMEl.addEventListener("onclick", function() {
           window.open(`https://facebook.com/${this.username}`)
          })
          

          現在看到問題了嗎?如果把事件處理程序寫成 bobRossObj.onFriendClick,實際上是把 bobRossObj.onFriendClick 上保存的函數拿出來,然后作為參數傳遞。它不再“依附”在 bobRossObj 上,也就是說,this 不再指向 bobRossObj。它實際指向全局對象,也就是說 this.username 不存在。似乎我們沒什么辦法了。

          輪到綁定上場了!

          7.明確綁定 this

          我們需要明確地將 this 綁定到 bobRossObj 上。我們可以通過 bind 實現:

          const bobRossObj = initializeFriend(data[0])
          const bobRossDOMEl = document.getElementById('Bob_Ross')
          bobRossObj.onFriendClick = bobRossObj.onFriendClick.bind(bobRossObj)
          bobRossDOMEl.addEventListener("onclick", bobRossObj.onFriendClick)
          

          之前,this 是按照默認的規則設置的。但使用 bind 之后,我們明確地將 bobRossObj.onFriendClick 中的 this 的值設置為 bobRossObj 對象本身。

          到此為止,我們看到了為什么要使用 this,以及為什么要明確地綁定 this。最后我們來介紹一下,this 實際上是箭頭函數。

          8.箭頭函數

          你也許注意到了箭頭函數最近很流行。人們喜歡箭頭函數,因為很簡潔、很優雅。而且你還知道箭頭函數和普通函數有點區別,盡管不太清楚具體區別是什么。

          簡而言之,兩者的區別在于:

          在定義箭頭函數時,不管 this 指向誰,箭頭函數內部的 this 永遠指向同一個東西。

          嗯……這貌似沒什么用……似乎跟普通函數的行為一樣啊?

          我們通過 initializeFriend 舉例說明。假設我們想添加一個名為 greeting 的函數:

          function initializeFriend(data) {
           return {
           lastTenPosts: data.lastTenPosts,
           birthday: data.birthday,
           username: data.username, 
           fullName: `${data.firstName} ${data.lastName}`,
           getThreeRandomPosts: function() {
           // get three random posts from this.lastTenPosts
           },
           getDaysUntilBirthday: function() {
           // use this.birthday to get the num days until birthday
           },
           greeting: function() {
           function getLastPost() {
           return this.lastTenPosts[0]
           }
           const lastPost = getLastPost() 
           return `Hello, this is ${this.fullName}'s data!
           ${this.fullName}'s last post was ${lastPost}.`
           },
           onFriendClick: function() {
           window.open(`https://facebook.com/${this.username}`)
           }
           };
          }
          

          這樣能運行嗎?如果不能,怎樣修改才能運行?

          答案是不能。因為 getLastPost 沒有在對象的上下文中調用,因此getLastPost 中的 this 按照默認規則指向了全局對象。

          你說沒有“在對象的上下文中調用”……難道它不是從 initializeFriend 返回的內部調用的嗎?如果這還不叫“在對象的上下文中調用”,那我就不知道什么才算了。

          我知道“在對象的上下文中調用”這個術語很模糊。也許,判斷函數是否“在對象的上下文中調用”的好方法就是檢查一遍函數的調用過程,看看是否有個對象“依附”到了函數上。

          我們來檢查下執行 bobRossObj.onFriendClick() 時的情況。“給我對象 bobRossObj,找到其中的 onFriendClick 然后調用該屬性對應的函數”。

          我們同樣檢查下執行 getLastPost() 時的情況。“給我名為 getLastPost 的函數然后執行。”看到了嗎?我們根本沒有提到對象。

          好了,這里有個難題來測試你的理解程度。假設有個函數名為 functionCaller,它的功能就是調用一個函數:

          functionCaller(fn) {
           fn()
          }
          

          如果調用 functionCaller(bobRossObj.onFriendClick) 會怎樣?你會認為 onFriendClick 是“在對象的上下文中調用”的嗎?this.username有定義嗎?

          我們來檢查一遍:“給我 bobRosObj 對象然后查找其屬性 onFriendClick。取出其中的值(這個值碰巧是個函數),然后將它傳遞給 functionCaller,取名為 fn。然后,執行名為 fn 的函數。”注意該函數在調用之前已經從 bobRossObj 對象上“脫離”了,因此并不是“在對象的上下文中調用”的,所以 this.username 沒有定義。

          這時可以用箭頭函數解決這個問題:

          function initializeFriend(data) {
           return {
           lastTenPosts: data.lastTenPosts,
           birthday: data.birthday,
           username: data.username, 
           fullName: `${data.firstName} ${data.lastName}`,
           getThreeRandomPosts: function() {
           // get three random posts from this.lastTenPosts
           },
           getDaysUntilBirthday: function() {
           // use this.birthday to get the num days until birthday
           },
           greeting: function() {
           const getLastPost = () => {
           return this.lastTenPosts[0]
           }
           const lastPost = getLastPost() 
           return `Hello, this is ${this.fullName}'s data!
           ${this.fullName}'s last post was ${lastPost}.`
           },
           onFriendClick: function() {
           window.open(`https://facebook.com/${this.username}`)
           }
           };
          }
          

          上述代碼的規則是:

          在定義箭頭函數時,不管 this 指向誰,箭頭函數內部的 this 永遠指向同一個東西。

          箭頭函數是在 greeting 中定義的。我們知道,在 greeting 內部的 this 指向對象本身。因此,箭頭函數內部的 this 也指向對象本身,這正是我們需要的結果。

          9.結論

          this 有時很不好理解,但它對于開發 JavaScript 應用非常有用。本文當然沒能介紹 this 的所有方面。一些沒有涉及到的話題包括:

          • call 和 apply;
          • 使用 new 時 this 會怎樣;
          • 在 ES6 的 class 中 this 會怎樣。

          我建議你首先問問自己在這些情況下的 this,然后在瀏覽器中執行代碼來檢驗你的結果。

          想學習更多關 于this 的內容,可參考《你不知道的 JS:this 和對象原型》:

          • https://github.com/getify/You-Dont-Know-JS/tree/master/this%20%26%20object%20prototypes

          如果你想測試自己的知識,可參考《你不知道的JS練習:this和對象原型》:

          • https://ydkjs-exercises.com/this-object-prototypes

          原文:https://medium.freecodecamp.org/a-deep-dive-into-this-in-javascript-why-its-critical-to-writing-good-code-7dca7eb489e7

          作者:Austin Tackaberry,Human API 的軟件工程師

          譯者:彎月,責編:屠敏

          習目標:了解JavaScript是如何與HTML結合來創建動態網頁,網頁中嵌入JavaScript的不同方式,JavaScript的內容類型及其與<script>的關系

          <script>元素

          <script>是由Netscape創造出來,后來加到HTML規范中的。

          <script>有8個屬性:

          1、async:表示立即開始下載腳本,但不能阻止其他頁面動作,比如下載資源或者等待其他腳本加載。只對外部腳本文件有效。

          2、charset:使用src屬性指定代碼字符集。這個屬性很少用,因為大多數瀏覽器不在乎它的值。

          3、crossorigin;配置資源請求的CORS(跨源資源共享)設置。默認情況下不使用CORS。crossorigin = “anonymous”配置文件請求不用設置憑據標志。crossorigin = ”use-credentials“設置憑據標志,意味著出站請求會包含憑據。

          4、defer:表示腳本可以延遲到文檔全部解析和顯示后再執行。新版本中只能用于外部腳本。

          5、integrity:允許比對接收到的資源和指定的加密簽名以驗證子資源完整性(SRI,Subresource integrity),如果驗證簽名不匹配則腳本不會執行。這個屬性可以用于確保內容分發網絡(CDN,Content Delivery Network)不會提供惡意內容。

          6、language:此屬性已被廢止。

          7、src:表示包含外部要執行的代碼的外部文件。

          8、type:代替language,表示代碼塊中腳本語言的內容類型(也稱為MIME類型),按照慣例這個值始終都是”text/JavaScript“,盡管”text/JavaScript“和”text/ecmascript“都已經廢棄。JavaScript文件的MIME類型通常是”application/x-javascript“,不過給type屬性這個值的話可能會導致腳本被忽略。在非IE的瀏覽器中有效的值還有”application/JavaScript“和”application/ecmascript"。如果這個值是module,則代碼會被當成是ES6模塊,而且只有這時候代碼中才能出現import和export關鍵字。

          使用<script>的方式有內聯和外嵌兩種,只要把code寫入<script>code</script>中就好,code中要是包含字符串“<script>”,只要加上轉義字符“\”即可。

          如果要外嵌JavaScript代碼只要使用src屬性來鏈接外部文件即可如:

          <script src=“example.js”></script>

          XHTML 文檔中,可以忽略結束標簽寫成<script src=“example.js”/>即可,但是這在HTML中不能使用。

          標簽位置

          過去把JavaScript和CSS一起寫在head中,但是這意味著必須下載所有code并解析和解釋完成后才開始渲染頁面,對于JavaScript很多的頁面會導致頁面渲染速度過慢,為解決這個問題,JavaScript一般寫在body元素的頁面內容的最后邊,如下

          <html>

          <head></head>

          <body>

          message

          <script>code<\script>

          <\body>

          </html>

          推遲使用腳本

          在外聯JavaScript時可以使用defer屬性來推遲腳本的運行。可以寫成:

          <html>

          <head>

          <script defer src = "example.js">code<\script>

          </head>

          <body>

          message

          <\body>

          </html>

          異步執行腳本

          async屬性從腳本處理方式上與defer類似,但是不同的是標記async的腳本并不能保證腳本按照他們的出現順序執行,比如:

          <html>

          <head>

          <script sync src = "example1.js">code<\script>

          <script sync src = "example2.js">code<\script>

          </head>

          <body>

          message

          <\body>

          </html>

          不能保證example1比example2先執行。

          動態加載腳本

          除了<script>以外還可以用其他方式加載腳本。因為JavaScript可以使用DOM API,所以通過向DOM中動態地加入script元素同樣可以加載指定腳本。只要創建一個script元素并將其添加到DOM即可。

          let script = document.createElement('script');

          script.src = 'gibberish.js';

          document.head.appendChild(script);

          當然,在把 HTMLElement 元素添加到 DOM 且執行到這段代碼之前不會發送請求。默認情況下,以這種方式創建的<script>元素是以異步方式加載的,相當于添加了 async 屬性。不過這樣做可能會有問題,因為所有瀏覽器都支持 createElement()方法,但不是所有瀏覽器都支持 async 屬性。因此,如果要統一動態腳本的加載行為,可以明確將其設置為同步加載:

          let script = document.createElement('script');

          script.src = 'gibberish.js';

          script.async = false;

          document.head.appendChild(script);

          以這種方式獲取的資源對瀏覽器預加載器是不可見的。這會嚴重影響它們在資源獲取隊列中的優先級。根據應用程序的工作方式以及怎么使用,這種方式可能會嚴重影響性能。要想讓預加載器知道這些動態請求文件的存在,可以在文檔頭部顯式聲明它們:

          <link rel="preload" href="gibberish.js">

          XHTML中的變化

          可擴展超文本標記語言(XHTML,Extensible HyperText Markup Language)是將 HTML 作為 XML的應用重新包裝的結果。與 HTML 不同,在 XHTML 中使用 JavaScript 必須指定 type 屬性且值為text/javascript,HTML 中則可以沒有這個屬性。XHTML 雖然已經退出歷史舞臺,但實踐中偶爾可能也會遇到遺留代碼,為此本節稍作介紹。在 XHTML 中編寫代碼的規則比 HTML 中嚴格,這會影響使用<script>元素嵌入 JavaScript 代碼。下面的代碼塊雖然在 HTML 中有效,但在 XHML 中是無效的。

          <script type="text/javascript">

          function compare(a, b) {

          if (a < b) {

          console.log("A is less than B");

          } else if (a > b) {

          console.log("A is greater than B");

          } else {

          console.log("A is equal to B");

          }

          }

          </script>

          在 HTML 中,解析<script>元素會應用特殊規則。XHTML 中則沒有這些規則。這意味著 a < b語句中的小于號(<)會被解釋成一個標簽的開始,并且由于作為標簽開始的小于號后面不能有空格,這會導致語法錯誤。避免 XHTML 中這種語法錯誤的方法有兩種。第一種是把所有小于號(<)都替換成對應的 HTML實體形式(<)。結果代碼就是這樣的:

          <script type="text/javascript">

          function compare(a, b) {

          if (a < b) {

          console.log("A is less than B");

          } else if (a > b) {

          console.log("A is greater than B");

          } else {

          console.log("A is equal to B");

          }

          }

          </script>

          這樣代碼就可以在 XHTML 頁面中運行了。不過,缺點是會影響閱讀。好在還有另一種方法。第二種方法是把所有代碼都包含到一個 CDATA 塊中。在 XHTML(及 XML)中,CDATA 塊表示文檔中可以包含任意文本的區塊,其內容不作為標簽來解析,因此可以在其中包含任意字符,包括小于號,并且不會引發語法錯誤。使用 CDATA 的格式如下:

          <script type="text/javascript"><![CDATA[

          function compare(a, b) {

          if (a < b) {

          console.log("A is less than B");

          } else if (a > b) {

          console.log("A is greater than B");

          } else {

          console.log("A is equal to B");

          }

          }

          ]]></script>

          在兼容 XHTML 的瀏覽器中,這樣能解決問題。但在不支持 CDATA 塊的非 XHTML 兼容瀏覽器中則不行。為此,CDATA 標記必須使用 JavaScript 注釋來抵消:

          <script type="text/javascript">

          //<![CDATA[

          function compare(a, b) {

          if (a < b) {

          console.log("A is less than B");

          } else if (a > b) {

          console.log("A is greater than B");

          } else {

          console.log("A is equal to B");

          }

          }

          //]]>

          </script>

          這種格式適用于所有現代瀏覽器。雖然有點黑科技的味道,但它可以通過 XHTML 驗證,而且對XHTML 之前的瀏覽器也能優雅地降級。

          廢棄的語法

          自 1995 年 Netscape 2 發布以來,所有瀏覽器都將 JavaScript 作為默認的編程語言。type 屬性使用一個 MIME 類型字符串來標識<script>的內容,但 MIME 類型并沒有跨瀏覽器標準化。即使瀏覽器默認使用 JavaScript,在某些情況下某個無效或無法識別的 MIME 類型也可能導致瀏覽器跳過(不執行)相關代碼。因此,除非你使用 XHTML 或<script>標簽要求或包含非 JavaScript 代碼,最佳做法是不指定 type 屬性。在最初采用 script 元素時,它標志著開始走向與傳統 HTML 解析不同的流程。對這個元素需要應用特殊的解析規則,而這在不支持 JavaScript 的瀏覽器(特別是 Mosaic)中會導致問題。不支持的瀏覽器會把<script>元素的內容輸出到頁面上,從而破壞頁面的外觀。Netscape 聯合 Mosaic 拿出了一個解決方案,對不支持 JavaScript 的瀏覽器隱藏嵌入的 JavaScript 代碼。最終方案是把腳本代碼包含在一個 HTML 注釋中,像這樣:

          <script><!--

          function sayHi(){

          console.log("Hi!");

          }

          //--></script>

          使用這種格式,Mosaic 等瀏覽器就可以忽略<script>標簽中的內容,而支持 JavaScript 的瀏覽器則必須識別這種模式,將其中的內容作為 JavaScript 來解析。雖然這種格式仍然可以被所有瀏覽器識別和解析,但已經不再必要,而且不應該再使用了。在XHTML 模式下,這種格式也會導致腳本被忽略,因為代碼處于有效的 XML 注釋當中。

          行內代碼與外部文件

          雖然可以直接在 HTML 文件中嵌入 JavaScript 代碼,但通常認為最佳實踐是盡可能將 JavaScript 代碼放在外部文件中。不過這個最佳實踐并不是明確的強制性規則。推薦使用外部文件的理由如下。

          ? 可維護性。JavaScript 代碼如果分散到很多 HTML 頁面,會導致維護困難。而用一個目錄保存所有 JavaScript 文件,則更容易維護,這樣開發者就可以獨立于使用它們的 HTML 頁面來編輯代碼。

          ? 緩存。瀏覽器會根據特定的設置緩存所有外部鏈接的 JavaScript 文件,這意味著如果兩個頁面都用到同一個文件,則該文件只需下載一次。這最終意味著頁面加載更快。

          ? 適應未來。通過把 JavaScript 放到外部文件中,就不必考慮用 XHTML 或前面提到的注釋黑科技。包含外部 JavaScript 文件的語法在 HTML 和 XHTML 中是一樣的。在配置瀏覽器請求外部文件時,要重點考慮的一點是它們會占用多少帶寬。在 SPDY/HTTP2 中,預請求的消耗已顯著降低,以輕量、獨立 JavaScript 組件形式向客戶端送達腳本更具優勢。比如,第一個頁面包含如下腳本:

          <script src="mainA.js"></script>

          <script src="component1.js"></script>

          <script src="component2.js"></script>

          <script src="component3.js"></script>

          ...

          后續頁面可能包含如下腳本:

          <script src="mainB.js"></script>

          <script src="component3.js"></script>

          <script src="component4.js"></script>

          <script src="component5.js"></script>

          ...

          在初次請求時,如果瀏覽器支持 SPDY/HTTP2,就可以從同一個地方取得一批文件,并將它們逐個放到瀏覽器緩存中。從瀏覽器角度看,通過 SPDY/HTTP2 獲取所有這些獨立的資源與獲取一個大JavaScript 文件的延遲差不多。在第二個頁面請求時,由于你已經把應用程序切割成了輕量可緩存的文件,第二個頁面也依賴的某些組件此時已經存在于瀏覽器緩存中了。當然,這里假設瀏覽器支持 SPDY/HTTP2,只有比較新的瀏覽器才滿足。如果你還想支持那些比較老的瀏覽器,可能還是用一個大文件更合適。

          文檔模式

          IE5.5 發明了文檔模式的概念,即可以使用 doctype 切換文檔模式。最初的文檔模式有兩種:混雜模式(quirks mode)和標準模式(standards mode)。前者讓 IE 像 IE5 一樣(支持一些非標準的特性),后者讓 IE 具有兼容標準的行為。雖然這兩種模式的主要區別只體現在通過 CSS 渲染的內容方面,但對JavaScript 也有一些關聯影響,或稱為副作用。本書會經常提到這些副作用。

          IE 初次支持文檔模式切換以后,其他瀏覽器也跟著實現了。隨著瀏覽器的普遍實現,又出現了第三種文檔模式:準標準模式(almost standards mode)。這種模式下的瀏覽器支持很多標準的特性,但是沒有標準規定得那么嚴格。主要區別在于如何對待圖片元素周圍的空白(在表格中使用圖片時最明顯)。

          混雜模式在所有瀏覽器中都以省略文檔開頭的 doctype 聲明作為開關。這種約定并不合理,因為混雜模式在不同瀏覽器中的差異非常大,不使用黑科技基本上就沒有瀏覽器一致性可言。標準模式通過下列幾種文檔類型聲明開啟:

          <!-- HTML 4.01 Strict -->

          <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"

          "http://www.w3.org/TR/html4/strict.dtd">

          <!-- XHTML 1.0 Strict -->

          <!DOCTYPE html PUBLIC

          "-//W3C//DTD XHTML 1.0 Strict//EN"

          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

          <!-- HTML5 -->

          <!DOCTYPE html>

          準標準模式通過過渡性文檔類型(Transitional)和框架集文檔類型(Frameset)來觸發:

          <!-- HTML 4.01 Transitional -->

          <!DOCTYPE HTML PUBLIC

          "-//W3C//DTD HTML 4.01 Transitional//EN"

          "http://www.w3.org/TR/html4/loose.dtd">

          <!-- HTML 4.01 Frameset -->

          <!DOCTYPE HTML PUBLIC

          "-//W3C//DTD HTML 4.01 Frameset//EN"

          "http://www.w3.org/TR/html4/frameset.dtd">

          <!-- XHTML 1.0 Transitional -->

          <!DOCTYPE html PUBLIC

          "-//W3C//DTD XHTML 1.0 Transitional//EN"

          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

          <!-- XHTML 1.0 Frameset -->

          <!DOCTYPE html PUBLIC

          "-//W3C//DTD XHTML 1.0 Frameset//EN"

          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">

          準標準模式與標準模式非常接近,很少需要區分。人們在說到“標準模式”時,可能指其中任何一個。而對文檔模式的檢測(本書后面會討論)也不會區分它們。本書后面所說的標準模式,指的就是除混雜模式以外的模式。

          <noscript>元素

          針對早期瀏覽器不支持 JavaScript 的問題,需要一個頁面優雅降級的處理方案。最終,<noscript>元素出現,被用于給不支持 JavaScript 的瀏覽器提供替代內容。雖然如今的瀏覽器已經 100%支持JavaScript,但對于禁用 JavaScript 的瀏覽器來說,這個元素仍然有它的用處。<noscript>元素可以包含任何可以出現在<body>中的 HTML 元素,<script>除外。在下列兩種情況下,瀏覽器將顯示包含在<noscript>中的內容:

          ? 瀏覽器不支持腳本;

          ? 瀏覽器對腳本的支持被關閉。任何一個條件被滿足,包含在<noscript>中的內容就會被渲染。否則,瀏覽器不會渲染<noscript>中的內容。

          下面是一個例子:

          <!DOCTYPE html>

          <html>

          <head>

          <title>Example HTML Page</title>

          <script defer="defer" src="example1.js"></script>

          <script defer="defer" src="example2.js"></script>

          </head>

          <body>

          <noscript>

          <p>This page requires a JavaScript-enabled browser.</p>

          </noscript>

          </body>

          </html>

          這個例子是在腳本不可用時讓瀏覽器顯示一段話。如果瀏覽器支持腳本,則用戶永遠不會看到它。

          小結

          JavaScript 是通過<script>元素插入到 HTML 頁面中的。這個元素可用于把 JavaScript 代碼嵌入到HTML 頁面中,跟其他標記混合在一起,也可用于引入保存在外部文件中的 JavaScript。本章的重點可以總結如下。

          ? 要包含外部 JavaScript 文件,必須將 src 屬性設置為要包含文件的 URL。文件可以跟網頁在同一臺服務器上,也可以位于完全不同的域。

          ? 所有<script>元素會依照它們在網頁中出現的次序被解釋。在不使用 defer 和 async 屬性的情況下,包含在<script>元素中的代碼必須嚴格按次序解釋。

          ? 對不推遲執行的腳本,瀏覽器必須解釋完位于<script>元素中的代碼,然后才能繼續渲染頁面的剩余部分。為此,通常應該把<script>元素放到頁面末尾,介于主內容之后及</body>標簽之前。

          ? 可以使用 defer 屬性把腳本推遲到文檔渲染完畢后再執行。推遲的腳本原則上按照它們被列出的次序執行。

          ? 可以使用 async 屬性表示腳本不需要等待其他腳本,同時也不阻塞文檔渲染,即異步加載。異步腳本不能保證按照它們在頁面中出現的次序執行。

          ? 通過使用<noscript>元素,可以指定在瀏覽器不支持腳本時顯示的內容。如果瀏覽器支持并啟用腳本,則<noscript>元素中的任何內容都不會被渲染。

          .JavaScript概念

          ??JavaScript是一種基于原型的面向對象的語言。

          ??同時,因為JavaScript是腳本語言,而腳本語言又是一種解釋性的語言,

          ??所以JavaScript是逐行執行的同時也具有解釋型語言的特點:不需要編譯可以直接使用,由解釋器來負責解釋。

          2.JavaScript特點

          (1)面向對象

          ??JavaScript 語言標準已經明確說明,JavaScript 是一門面向對象的語言;

          ??JavaScript是否屬于“面向對象的語言”一直飽受爭議,一些爭論中,有人強調,JavaScript 并非“面向對象的語言”,而是“基于對象的語言”,這個說法也一度流傳甚廣。JavaScript 的對象設計跟目前主流基于類的面向對象差異非常大。而事實上,這樣的對象系統設計雖然特別,但是 JavaScript 提供了完全運行時的對象系統,這使得它可以模仿多數面向對象編程范式,所以它也是正統的面向對象語言。而因為與基于類的面向對象的差異,我們也稱JavaScript 為基于原型的面向對象。

          (2)腳本語言

          ??又被稱為動態語言,只在被調用時進行解釋或編譯。在程序的運行過程中逐行解釋執行。

          (3)解釋性語言

          ??不需要編譯可以直接使用,由宿主環境(瀏覽器)解釋執行。

          我自己是一名從事了多年開發的web前端老程序員,目前辭職在做自己的web前端私人定制課程,今年年初我花了一個月整理了一份最適合2019年學習的web前端學習干貨,各種框架都有整理,送給每一位前端小伙伴,想要獲取的可以關注我的頭條號并在后臺私信我:前端,即可免費獲取。

          (4)事件驅動

          ??JavaScript對用戶的響應,是以事件驅動的方式進行的。在網頁(Web Page)中執行了某種操作所產生的動作,被稱為“事件”(Event)。例如按下鼠標、移動窗口、選擇菜單等都可以被視為事件。當事件發生后,可能會引起相應的事件響應,執行某些對應的腳本,這種機制被稱為“事件驅動”。

          (5)動態性

          ??語言的動態性,是指程序在運行時可以改變其結構。在一個 JavaScript 對象中,要為一個屬性賦值時,我們不必事先創建一個變量,只需要在使用的時候做賦值操作即可。

          (6)弱類型&松散類型

          ?? 弱類型語言聲明數據時不需要指定數據類型,一個變量可以賦不同數據類型的值,不同類型數據在計算過程中會自動進行轉換,在參與運算的過程中,JavaScript會將其自動轉換為數據類型,比如JavaScript中布爾類型的數據可以直接參與運算。

          (7)單線程與異步處理共存

          ??單線程程序的執行順序是從上到下依次執行,一個程序中只可以執行一個程序。而異步處理不用阻塞當前線程來等待處理完成,而是允許后續操作,直至其它線程將處理完成。二者看起來相互矛盾,不能夠同時存在,可是JavaScript以一種巧妙地方式用單線程實現了異步處理的效果。

          ??在JavaScript的主線程和任務隊列中間還存在一個WebAPIs,這個WebAPIs是瀏覽器單獨為處理JavaScript的異步任務開辟的線程。當主線程棧中的函數需要異步處理的時候,主線程會把需要異步的部分推給WebAPIs,這部分異步由瀏覽器去執行,執行完異步之后將處理后的結果以事件的形式丟到任務隊列中,這個事件就是我們寫代碼時的“回調函數”。

          (8)跨平臺

          ??JavaScript依賴于瀏覽器本身,與操作環境無關。

          (9)安全性語言

          ??JavaScript是一種安全性語言,它不允許訪問本地的硬盤,并不能將數據存入到服務器上,不允許對網絡文檔進行修改和刪除,只能通過瀏覽器實現信息瀏覽或動態交互。從而有效地防止數據的丟失。

          3.javaScript的核心

          ??JavaScript 的三大核心為ECMAScript、DOM 和 BOM。

          ??ECMAScript(核心):

          ????描述了該語言的語法和基本對象,ECMAScript是一種由Ecma國際(前身為歐洲計算機制造商協會,European Computer Manufacturers Association)通過ECMA-262標準化的腳本程序設計語言。這種語言在萬維網上應用廣泛,它往往被稱為JavaScript或JScript,所以它可以理解為是JavaScript的一個標準,但實際上后兩者是ECMA-262標準的實現和擴展。

          ??DOM(文檔對象模型):

          ????描述了處理網頁內容的方法和接口;是W3C組織推薦的處理可擴展置標語言的標準編程接口。是一種與平臺和語言無關的應用程序接口(API),它可以動態地訪問程序和腳本,更新其內容、結構和www文檔的風格(目前,HTML和XML文檔是通過說明部分定義的)。

          ??BOM(瀏覽器對象模型):

          ????描述了與瀏覽器進行交互的方法和接口;用于描述這種對象與對象之間層次關系的模型,瀏覽器對象模型提供了獨立于內容的、可以與瀏覽器窗口進行互動的對象結構。BOM由多個對象組成,其中代表瀏覽器窗口的Window對象是BOM的頂層對象,其他對象都是該對象的子對象。

          4.javaScript的使用

          html頁面中寫在<script></script>標簽內

          引入外部.js文件

          ??引入的多個js文件,會按順序分開執行。同樣的,對于不同<script>標簽嵌入的JavaScript代碼,也會根據腳本標簽<script>的出現順序來分開執行。由于JavaScript通常需要操作DOM,所以,一般把JavaScript放在前或者文檔結尾處引入。若需要在中引入,可以通過修改window.onload或者document.ready事件,強制等到DOM加載完成后再執行相關函數。

          5.javaScript的執行順序

          ??JavaScript代碼執行分為兩個部分:

          預編譯階段,進行代碼的檢查裝載,同樣也是按從上到下按順序進行的。此階段只進行變量和函數的聲明,會跳過執行語句,不對變量進行賦值,變量的默認值為undefined。即使聲明是在調用的下方進行的,但瀏覽器仍然先聲明再調用(執行),這個現象叫做“聲明提升”。所以,即便一個函數的聲明在函數調用的下方,前面仍然可以正常執行這個函數,需要注意的是函數聲明的提升優先于變量聲明的提升。(js代碼的執行是讀一行代碼執行一行,但在執行之前系統會先對js進行全面掃描檢查是否存在低級的語法錯誤,并不會立即執行語句。)

          原文鏈接:https://blog.csdn.net/qq_28453669/article/details/103211948

          作者:Simeow


          主站蜘蛛池模板: 日韩一区二区视频在线观看| 一区视频免费观看| 日韩成人无码一区二区三区| 精品久久一区二区三区| 日韩色视频一区二区三区亚洲| 亚洲一区AV无码少妇电影☆| 国产福利电影一区二区三区久久老子无码午夜伦不 | 国产精品美女一区二区| 在线精品一区二区三区| 亚洲无圣光一区二区| 曰韩人妻无码一区二区三区综合部 | 另类免费视频一区二区在线观看| 国产另类ts人妖一区二区三区| 国产免费伦精品一区二区三区| 熟女少妇精品一区二区| 无码精品人妻一区二区三区人妻斩| 久久福利一区二区| 成人精品一区久久久久| 亚洲日韩国产一区二区三区| 一区二区三区国产| 亚洲国产AV一区二区三区四区| 无码毛片一区二区三区中文字幕 | 免费一区二区无码东京热| 久久精品道一区二区三区| av一区二区三区人妻少妇| 久久精品人妻一区二区三区| 麻豆一区二区在我观看| 精品3d动漫视频一区在线观看| 国产在线无码一区二区三区视频| 亚洲综合无码一区二区痴汉| 肥臀熟女一区二区三区| 欧美日韩精品一区二区在线视频 | 日韩三级一区二区| 国产精品一级香蕉一区| 亚洲国产精品一区二区第一页 | 日本精品一区二区三区在线视频一| 色窝窝无码一区二区三区| 一区二区三区精品高清视频免费在线播放 | 国产日韩精品一区二区在线观看| 2020天堂中文字幕一区在线观| 搜日本一区二区三区免费高清视频|