Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
文將通過簡單的術(shù)語和真實世界的例子解釋 JavaScript 中 this 及其用途,并告訴你寫出好的代碼為何如此重要。
我看到許多文章在介紹 JavaScript 的 this 時都會假設(shè)你學過某種面向?qū)ο蟮木幊陶Z言,比如 Java、C++ 或 Python 等。但這篇文章面向的讀者是那些不知道 this 是什么的人。我盡量不用任何術(shù)語來解釋 this 是什么,以及 this 的用法。
也許你一直不敢解開 this 的秘密,因為它看起來挺奇怪也挺嚇人的。或許你只在 StackOverflow 說你需要用它的時候(比如在 React 里實現(xiàn)某個功能)才會使用。
在深入介紹 this 之前,我們首先需要理解函數(shù)式編程和面向?qū)ο缶幊讨g的區(qū)別。
你可能不知道,JavaScript 同時擁有面向?qū)ο蠛秃瘮?shù)式的結(jié)構(gòu),所以你可以自己選擇用哪種風格,或者兩者都用。
我在很早以前使用 JavaScript 時就喜歡函數(shù)式編程,而且會像躲避瘟疫一樣避開面向?qū)ο缶幊蹋驗槲也焕斫饷嫦驅(qū)ο笾械年P(guān)鍵字,比如 this。我不知道為什么要用 this。似乎沒有它我也可以做好所有的工作。
而且我是對的。
在某種意義上 。也許你可以只專注于一種結(jié)構(gòu)并且完全忽略另一種,但這樣你只能是一個 JavaScript 開發(fā)者。為了解釋函數(shù)式和面向?qū)ο笾g的區(qū)別,下面我們通過一個數(shù)組來舉例說明,數(shù)組的內(nèi)容是 Facebook 的好友列表。
假設(shè)你要做一個 Web 應(yīng)用,當用戶使用 Facebook 登錄你的 Web 應(yīng)用時,需要顯示他們的 Facebook 的好友信息。你需要訪問 Facebook 并獲得用戶的好友數(shù)據(jù)。這些數(shù)據(jù)可能是 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', ...], }, ... ]
假設(shè)上述數(shù)據(jù)是你通過 Facebook API 獲得的。現(xiàn)在需要將其轉(zhuǎn)換成方便你的項目使用的格式。我們假設(shè)你想顯示的好友信息如下:
函數(shù)式的方式就是將整個數(shù)組或者數(shù)組中的某個元素傳遞給某個函數(shù),然后返回你需要的信息:
const fullNames = getFullNames(data) // ['Ross, Bob', 'Smith, Joanna', ...]
首先我們有 Facebook API 返回的原始數(shù)據(jù)。為了將其轉(zhuǎn)換成需要的格式,首先要將數(shù)據(jù)傳遞給一個函數(shù),函數(shù)的輸出是(或者包含)經(jīng)過修改的數(shù)據(jù),這些數(shù)據(jù)可以在應(yīng)用中向用戶展示。
我們可以用類似的方法獲得隨機三篇文章,并且計算距離好友生日的天數(shù)。
函數(shù)式的方式是:將原始數(shù)據(jù)傳遞給一個函數(shù)或者多個函數(shù),獲得對你的項目有用的數(shù)據(jù)格式。
對于編程初學者和 JavaScript 初學者,面向?qū)ο蟮母拍羁赡苡悬c難以理解。其思想是,我們要將每個好友變成一個對象,這個對象能夠生成你一切開發(fā)者需要的東西。
你可以創(chuàng)建一個對象,這個對象對應(yīng)于某個好友,它有 fullName 屬性,還有兩個函數(shù) 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
面向?qū)ο蟮姆绞骄褪菫閿?shù)據(jù)創(chuàng)建對象,每個對象都有自己的狀態(tài),并且包含必要的信息,能夠生成需要的數(shù)據(jù)。
你也許從來沒想過要寫上面的 initializeFriend 代碼,而且你也許認為,這種代碼可能會很有用。但你也注意到,這并不是真正的面向?qū)ο蟆?/p>
其原因就是,上面例子中的 getThreeRandomPosts 或 getdaysUntilBirtyday 能夠正常工作的原因其實是閉包。因為使用了閉包,它們在 initializeFriend 返回之后依然能訪問 data。關(guān)于閉包的更多信息可以看看這篇文章:作用域和閉包(https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20%26%20closures/ch5.md)。
還有一個方法該怎么處理?我們假設(shè)這個方法叫做 greeting。注意方法(與 JavaScript 的對象有關(guān)的方法)其實只是一個屬性,只不過屬性值是函數(shù)而已。我們想在 greeting 中實現(xiàn)以下功能:
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 中加入距離好友生日的天數(shù)怎么辦?我們最好還是有辦法在 greeting 中調(diào)用 getDaysUntilBirthday。
這時輪到 this 出場了!
this 在不同的環(huán)境中可以指代不同的東西。默認的全局環(huán)境中 this 指代的是全局對象(在瀏覽器中 this 是 window 對象),這沒什么太大的用途。而在 this 的規(guī)則中具有實用性的是這一條:
如果在對象的方法中使用 this,而該方法在該對象的上下文中調(diào)用,那么 this 指代該對象本身。
你會說“在該對象的上下文中調(diào)用”……是啥意思?
別著急,我們一會兒就說。
所以,如果我們想從 greeting 中調(diào)用 getDaysUntilBirtyday 我們只需要寫 this.getDaysUntilBirthday,因為此時的 this 就是對象本身。
附注:不要在全局作用域的普通函數(shù)或另一個函數(shù)的作用域中使用 this!this 是個面向?qū)ο蟮臇|西,它只在對象的上下文(或類的上下文)中有意義。
我們利用 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!` } }; }
現(xiàn)在,在 initializeFriend 執(zhí)行結(jié)束后,該對象需要的一切都位于對象本身的作用域之內(nèi)了。我們的方法不需要再依賴于閉包,它們只會用到對象本身包含的信息。
好吧,這是 this 的用法之一,但你說過 this 在不同的上下文中有不同的含義。那是什么意思?為什么不一定會指向?qū)ο笞约海?/p>
有時候,你需要將 this 指向某個特定的東西。一種情況就是事件處理函數(shù)。比如我們希望在用戶點擊好友時打開好友的 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 首頁。現(xiàn)在只需要編寫 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 創(chuàng)建了一個對象。然后我們拿到了 Bob Ross 對應(yīng)的 DOM 元素。然后執(zhí)行 onFriendClick 方法來打開 Bob 的 Facebook 主頁。似乎沒問題,對吧?
有問題!
哪里出錯了?
注意我們調(diào)用 onclick 處理程序的代碼是 bobRossObj.onFriendClick。看到問題了嗎?要是寫成這樣的話能看出來嗎?
bobRossDOMEl.addEventListener("onclick", function() { window.open(`https://facebook.com/${this.username}`) })
現(xiàn)在看到問題了嗎?如果把事件處理程序?qū)懗?bobRossObj.onFriendClick,實際上是把 bobRossObj.onFriendClick 上保存的函數(shù)拿出來,然后作為參數(shù)傳遞。它不再“依附”在 bobRossObj 上,也就是說,this 不再指向 bobRossObj。它實際指向全局對象,也就是說 this.username 不存在。似乎我們沒什么辦法了。
輪到綁定上場了!
我們需要明確地將 this 綁定到 bobRossObj 上。我們可以通過 bind 實現(xiàn):
const bobRossObj = initializeFriend(data[0]) const bobRossDOMEl = document.getElementById('Bob_Ross') bobRossObj.onFriendClick = bobRossObj.onFriendClick.bind(bobRossObj) bobRossDOMEl.addEventListener("onclick", bobRossObj.onFriendClick)
之前,this 是按照默認的規(guī)則設(shè)置的。但使用 bind 之后,我們明確地將 bobRossObj.onFriendClick 中的 this 的值設(shè)置為 bobRossObj 對象本身。
到此為止,我們看到了為什么要使用 this,以及為什么要明確地綁定 this。最后我們來介紹一下,this 實際上是箭頭函數(shù)。
你也許注意到了箭頭函數(shù)最近很流行。人們喜歡箭頭函數(shù),因為很簡潔、很優(yōu)雅。而且你還知道箭頭函數(shù)和普通函數(shù)有點區(qū)別,盡管不太清楚具體區(qū)別是什么。
簡而言之,兩者的區(qū)別在于:
在定義箭頭函數(shù)時,不管 this 指向誰,箭頭函數(shù)內(nèi)部的 this 永遠指向同一個東西。
嗯……這貌似沒什么用……似乎跟普通函數(shù)的行為一樣啊?
我們通過 initializeFriend 舉例說明。假設(shè)我們想添加一個名為 greeting 的函數(shù):
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 沒有在對象的上下文中調(diào)用,因此getLastPost 中的 this 按照默認規(guī)則指向了全局對象。
你說沒有“在對象的上下文中調(diào)用”……難道它不是從 initializeFriend 返回的內(nèi)部調(diào)用的嗎?如果這還不叫“在對象的上下文中調(diào)用”,那我就不知道什么才算了。
我知道“在對象的上下文中調(diào)用”這個術(shù)語很模糊。也許,判斷函數(shù)是否“在對象的上下文中調(diào)用”的好方法就是檢查一遍函數(shù)的調(diào)用過程,看看是否有個對象“依附”到了函數(shù)上。
我們來檢查下執(zhí)行 bobRossObj.onFriendClick() 時的情況。“給我對象 bobRossObj,找到其中的 onFriendClick 然后調(diào)用該屬性對應(yīng)的函數(shù)”。
我們同樣檢查下執(zhí)行 getLastPost() 時的情況。“給我名為 getLastPost 的函數(shù)然后執(zhí)行。”看到了嗎?我們根本沒有提到對象。
好了,這里有個難題來測試你的理解程度。假設(shè)有個函數(shù)名為 functionCaller,它的功能就是調(diào)用一個函數(shù):
functionCaller(fn) { fn() }
如果調(diào)用 functionCaller(bobRossObj.onFriendClick) 會怎樣?你會認為 onFriendClick 是“在對象的上下文中調(diào)用”的嗎?this.username有定義嗎?
我們來檢查一遍:“給我 bobRosObj 對象然后查找其屬性 onFriendClick。取出其中的值(這個值碰巧是個函數(shù)),然后將它傳遞給 functionCaller,取名為 fn。然后,執(zhí)行名為 fn 的函數(shù)。”注意該函數(shù)在調(diào)用之前已經(jīng)從 bobRossObj 對象上“脫離”了,因此并不是“在對象的上下文中調(diào)用”的,所以 this.username 沒有定義。
這時可以用箭頭函數(shù)解決這個問題:
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}`) } }; }
上述代碼的規(guī)則是:
在定義箭頭函數(shù)時,不管 this 指向誰,箭頭函數(shù)內(nèi)部的 this 永遠指向同一個東西。
箭頭函數(shù)是在 greeting 中定義的。我們知道,在 greeting 內(nèi)部的 this 指向?qū)ο蟊旧怼R虼耍^函數(shù)內(nèi)部的 this 也指向?qū)ο蟊旧恚@正是我們需要的結(jié)果。
this 有時很不好理解,但它對于開發(fā) JavaScript 應(yīng)用非常有用。本文當然沒能介紹 this 的所有方面。一些沒有涉及到的話題包括:
我建議你首先問問自己在這些情況下的 this,然后在瀏覽器中執(zhí)行代碼來檢驗?zāi)愕慕Y(jié)果。
想學習更多關(guān) 于this 的內(nèi)容,可參考《你不知道的 JS:this 和對象原型》:
如果你想測試自己的知識,可參考《你不知道的JS練習:this和對象原型》:
原文: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結(jié)合來創(chuàng)建動態(tài)網(wǎng)頁,網(wǎng)頁中嵌入JavaScript的不同方式,JavaScript的內(nèi)容類型及其與<script>的關(guān)系
<script>是由Netscape創(chuàng)造出來,后來加到HTML規(guī)范中的。
<script>有8個屬性:
1、async:表示立即開始下載腳本,但不能阻止其他頁面動作,比如下載資源或者等待其他腳本加載。只對外部腳本文件有效。
2、charset:使用src屬性指定代碼字符集。這個屬性很少用,因為大多數(shù)瀏覽器不在乎它的值。
3、crossorigin;配置資源請求的CORS(跨源資源共享)設(shè)置。默認情況下不使用CORS。crossorigin = “anonymous”配置文件請求不用設(shè)置憑據(jù)標志。crossorigin = ”use-credentials“設(shè)置憑據(jù)標志,意味著出站請求會包含憑據(jù)。
4、defer:表示腳本可以延遲到文檔全部解析和顯示后再執(zhí)行。新版本中只能用于外部腳本。
5、integrity:允許比對接收到的資源和指定的加密簽名以驗證子資源完整性(SRI,Subresource integrity),如果驗證簽名不匹配則腳本不會執(zhí)行。這個屬性可以用于確保內(nèi)容分發(fā)網(wǎng)絡(luò)(CDN,Content Delivery Network)不會提供惡意內(nèi)容。
6、language:此屬性已被廢止。
7、src:表示包含外部要執(zhí)行的代碼的外部文件。
8、type:代替language,表示代碼塊中腳本語言的內(nèi)容類型(也稱為MIME類型),按照慣例這個值始終都是”text/JavaScript“,盡管”text/JavaScript“和”text/ecmascript“都已經(jīng)廢棄。JavaScript文件的MIME類型通常是”application/x-javascript“,不過給type屬性這個值的話可能會導(dǎo)致腳本被忽略。在非IE的瀏覽器中有效的值還有”application/JavaScript“和”application/ecmascript"。如果這個值是module,則代碼會被當成是ES6模塊,而且只有這時候代碼中才能出現(xiàn)import和export關(guān)鍵字。
使用<script>的方式有內(nèi)聯(lián)和外嵌兩種,只要把code寫入<script>code</script>中就好,code中要是包含字符串“<script>”,只要加上轉(zhuǎn)義字符“\”即可。
如果要外嵌JavaScript代碼只要使用src屬性來鏈接外部文件即可如:
<script src=“example.js”></script>
XHTML 文檔中,可以忽略結(jié)束標簽寫成<script src=“example.js”/>即可,但是這在HTML中不能使用。
過去把JavaScript和CSS一起寫在head中,但是這意味著必須下載所有code并解析和解釋完成后才開始渲染頁面,對于JavaScript很多的頁面會導(dǎo)致頁面渲染速度過慢,為解決這個問題,JavaScript一般寫在body元素的頁面內(nèi)容的最后邊,如下
<html>
<head></head>
<body>
message
<script>code<\script>
<\body>
</html>
在外聯(lián)JavaScript時可以使用defer屬性來推遲腳本的運行。可以寫成:
<html>
<head>
<script defer src = "example.js">code<\script>
</head>
<body>
message
<\body>
</html>
async屬性從腳本處理方式上與defer類似,但是不同的是標記async的腳本并不能保證腳本按照他們的出現(xiàn)順序執(zhí)行,比如:
<html>
<head>
<script sync src = "example1.js">code<\script>
<script sync src = "example2.js">code<\script>
</head>
<body>
message
<\body>
</html>
不能保證example1比example2先執(zhí)行。
除了<script>以外還可以用其他方式加載腳本。因為JavaScript可以使用DOM API,所以通過向DOM中動態(tài)地加入script元素同樣可以加載指定腳本。只要創(chuàng)建一個script元素并將其添加到DOM即可。
let script = document.createElement('script');
script.src = 'gibberish.js';
document.head.appendChild(script);
當然,在把 HTMLElement 元素添加到 DOM 且執(zhí)行到這段代碼之前不會發(fā)送請求。默認情況下,以這種方式創(chuàng)建的<script>元素是以異步方式加載的,相當于添加了 async 屬性。不過這樣做可能會有問題,因為所有瀏覽器都支持 createElement()方法,但不是所有瀏覽器都支持 async 屬性。因此,如果要統(tǒng)一動態(tài)腳本的加載行為,可以明確將其設(shè)置為同步加載:
let script = document.createElement('script');
script.src = 'gibberish.js';
script.async = false;
document.head.appendChild(script);
以這種方式獲取的資源對瀏覽器預(yù)加載器是不可見的。這會嚴重影響它們在資源獲取隊列中的優(yōu)先級。根據(jù)應(yīng)用程序的工作方式以及怎么使用,這種方式可能會嚴重影響性能。要想讓預(yù)加載器知道這些動態(tài)請求文件的存在,可以在文檔頭部顯式聲明它們:
<link rel="preload" href="gibberish.js">
可擴展超文本標記語言(XHTML,Extensible HyperText Markup Language)是將 HTML 作為 XML的應(yīng)用重新包裝的結(jié)果。與 HTML 不同,在 XHTML 中使用 JavaScript 必須指定 type 屬性且值為text/javascript,HTML 中則可以沒有這個屬性。XHTML 雖然已經(jīng)退出歷史舞臺,但實踐中偶爾可能也會遇到遺留代碼,為此本節(jié)稍作介紹。在 XHTML 中編寫代碼的規(guī)則比 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>元素會應(yīng)用特殊規(guī)則。XHTML 中則沒有這些規(guī)則。這意味著 a < b語句中的小于號(<)會被解釋成一個標簽的開始,并且由于作為標簽開始的小于號后面不能有空格,這會導(dǎo)致語法錯誤。避免 XHTML 中這種語法錯誤的方法有兩種。第一種是把所有小于號(<)都替換成對應(yīng)的 HTML實體形式(<)。結(jié)果代碼就是這樣的:
<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 塊表示文檔中可以包含任意文本的區(qū)塊,其內(nèi)容不作為標簽來解析,因此可以在其中包含任意字符,包括小于號,并且不會引發(fā)語法錯誤。使用 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>
這種格式適用于所有現(xiàn)代瀏覽器。雖然有點黑科技的味道,但它可以通過 XHTML 驗證,而且對XHTML 之前的瀏覽器也能優(yōu)雅地降級。
自 1995 年 Netscape 2 發(fā)布以來,所有瀏覽器都將 JavaScript 作為默認的編程語言。type 屬性使用一個 MIME 類型字符串來標識<script>的內(nèi)容,但 MIME 類型并沒有跨瀏覽器標準化。即使瀏覽器默認使用 JavaScript,在某些情況下某個無效或無法識別的 MIME 類型也可能導(dǎo)致瀏覽器跳過(不執(zhí)行)相關(guān)代碼。因此,除非你使用 XHTML 或<script>標簽要求或包含非 JavaScript 代碼,最佳做法是不指定 type 屬性。在最初采用 script 元素時,它標志著開始走向與傳統(tǒng) HTML 解析不同的流程。對這個元素需要應(yīng)用特殊的解析規(guī)則,而這在不支持 JavaScript 的瀏覽器(特別是 Mosaic)中會導(dǎo)致問題。不支持的瀏覽器會把<script>元素的內(nèi)容輸出到頁面上,從而破壞頁面的外觀。Netscape 聯(lián)合 Mosaic 拿出了一個解決方案,對不支持 JavaScript 的瀏覽器隱藏嵌入的 JavaScript 代碼。最終方案是把腳本代碼包含在一個 HTML 注釋中,像這樣:
<script><!--
function sayHi(){
console.log("Hi!");
}
//--></script>
使用這種格式,Mosaic 等瀏覽器就可以忽略<script>標簽中的內(nèi)容,而支持 JavaScript 的瀏覽器則必須識別這種模式,將其中的內(nèi)容作為 JavaScript 來解析。雖然這種格式仍然可以被所有瀏覽器識別和解析,但已經(jīng)不再必要,而且不應(yīng)該再使用了。在XHTML 模式下,這種格式也會導(dǎo)致腳本被忽略,因為代碼處于有效的 XML 注釋當中。
雖然可以直接在 HTML 文件中嵌入 JavaScript 代碼,但通常認為最佳實踐是盡可能將 JavaScript 代碼放在外部文件中。不過這個最佳實踐并不是明確的強制性規(guī)則。推薦使用外部文件的理由如下。
? 可維護性。JavaScript 代碼如果分散到很多 HTML 頁面,會導(dǎo)致維護困難。而用一個目錄保存所有 JavaScript 文件,則更容易維護,這樣開發(fā)者就可以獨立于使用它們的 HTML 頁面來編輯代碼。
? 緩存。瀏覽器會根據(jù)特定的設(shè)置緩存所有外部鏈接的 JavaScript 文件,這意味著如果兩個頁面都用到同一個文件,則該文件只需下載一次。這最終意味著頁面加載更快。
? 適應(yīng)未來。通過把 JavaScript 放到外部文件中,就不必考慮用 XHTML 或前面提到的注釋黑科技。包含外部 JavaScript 文件的語法在 HTML 和 XHTML 中是一樣的。在配置瀏覽器請求外部文件時,要重點考慮的一點是它們會占用多少帶寬。在 SPDY/HTTP2 中,預(yù)請求的消耗已顯著降低,以輕量、獨立 JavaScript 組件形式向客戶端送達腳本更具優(yōu)勢。比如,第一個頁面包含如下腳本:
<script src="mainA.js"></script>
<script src="component1.js"></script>
<script src="component2.js"></script>
<script src="component3.js"></script>
...
后續(xù)頁面可能包含如下腳本:
<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 文件的延遲差不多。在第二個頁面請求時,由于你已經(jīng)把應(yīng)用程序切割成了輕量可緩存的文件,第二個頁面也依賴的某些組件此時已經(jīng)存在于瀏覽器緩存中了。當然,這里假設(shè)瀏覽器支持 SPDY/HTTP2,只有比較新的瀏覽器才滿足。如果你還想支持那些比較老的瀏覽器,可能還是用一個大文件更合適。
IE5.5 發(fā)明了文檔模式的概念,即可以使用 doctype 切換文檔模式。最初的文檔模式有兩種:混雜模式(quirks mode)和標準模式(standards mode)。前者讓 IE 像 IE5 一樣(支持一些非標準的特性),后者讓 IE 具有兼容標準的行為。雖然這兩種模式的主要區(qū)別只體現(xiàn)在通過 CSS 渲染的內(nèi)容方面,但對JavaScript 也有一些關(guān)聯(lián)影響,或稱為副作用。本書會經(jīng)常提到這些副作用。
IE 初次支持文檔模式切換以后,其他瀏覽器也跟著實現(xiàn)了。隨著瀏覽器的普遍實現(xiàn),又出現(xiàn)了第三種文檔模式:準標準模式(almost standards mode)。這種模式下的瀏覽器支持很多標準的特性,但是沒有標準規(guī)定得那么嚴格。主要區(qū)別在于如何對待圖片元素周圍的空白(在表格中使用圖片時最明顯)。
混雜模式在所有瀏覽器中都以省略文檔開頭的 doctype 聲明作為開關(guān)。這種約定并不合理,因為混雜模式在不同瀏覽器中的差異非常大,不使用黑科技基本上就沒有瀏覽器一致性可言。標準模式通過下列幾種文檔類型聲明開啟:
<!-- 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)來觸發(fā):
<!-- 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">
準標準模式與標準模式非常接近,很少需要區(qū)分。人們在說到“標準模式”時,可能指其中任何一個。而對文檔模式的檢測(本書后面會討論)也不會區(qū)分它們。本書后面所說的標準模式,指的就是除混雜模式以外的模式。
針對早期瀏覽器不支持 JavaScript 的問題,需要一個頁面優(yōu)雅降級的處理方案。最終,<noscript>元素出現(xiàn),被用于給不支持 JavaScript 的瀏覽器提供替代內(nèi)容。雖然如今的瀏覽器已經(jīng) 100%支持JavaScript,但對于禁用 JavaScript 的瀏覽器來說,這個元素仍然有它的用處。<noscript>元素可以包含任何可以出現(xiàn)在<body>中的 HTML 元素,<script>除外。在下列兩種情況下,瀏覽器將顯示包含在<noscript>中的內(nèi)容:
? 瀏覽器不支持腳本;
? 瀏覽器對腳本的支持被關(guān)閉。任何一個條件被滿足,包含在<noscript>中的內(nèi)容就會被渲染。否則,瀏覽器不會渲染<noscript>中的內(nèi)容。
下面是一個例子:
<!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。本章的重點可以總結(jié)如下。
? 要包含外部 JavaScript 文件,必須將 src 屬性設(shè)置為要包含文件的 URL。文件可以跟網(wǎng)頁在同一臺服務(wù)器上,也可以位于完全不同的域。
? 所有<script>元素會依照它們在網(wǎng)頁中出現(xiàn)的次序被解釋。在不使用 defer 和 async 屬性的情況下,包含在<script>元素中的代碼必須嚴格按次序解釋。
? 對不推遲執(zhí)行的腳本,瀏覽器必須解釋完位于<script>元素中的代碼,然后才能繼續(xù)渲染頁面的剩余部分。為此,通常應(yīng)該把<script>元素放到頁面末尾,介于主內(nèi)容之后及</body>標簽之前。
? 可以使用 defer 屬性把腳本推遲到文檔渲染完畢后再執(zhí)行。推遲的腳本原則上按照它們被列出的次序執(zhí)行。
? 可以使用 async 屬性表示腳本不需要等待其他腳本,同時也不阻塞文檔渲染,即異步加載。異步腳本不能保證按照它們在頁面中出現(xiàn)的次序執(zhí)行。
? 通過使用<noscript>元素,可以指定在瀏覽器不支持腳本時顯示的內(nèi)容。如果瀏覽器支持并啟用腳本,則<noscript>元素中的任何內(nèi)容都不會被渲染。
.JavaScript概念
??JavaScript是一種基于原型的面向?qū)ο蟮恼Z言。
??同時,因為JavaScript是腳本語言,而腳本語言又是一種解釋性的語言,
??所以JavaScript是逐行執(zhí)行的同時也具有解釋型語言的特點:不需要編譯可以直接使用,由解釋器來負責解釋。
2.JavaScript特點
(1)面向?qū)ο?/strong>
??JavaScript 語言標準已經(jīng)明確說明,JavaScript 是一門面向?qū)ο蟮恼Z言;
??JavaScript是否屬于“面向?qū)ο蟮恼Z言”一直飽受爭議,一些爭論中,有人強調(diào),JavaScript 并非“面向?qū)ο蟮恼Z言”,而是“基于對象的語言”,這個說法也一度流傳甚廣。JavaScript 的對象設(shè)計跟目前主流基于類的面向?qū)ο蟛町惙浅4蟆6聦嵣希@樣的對象系統(tǒng)設(shè)計雖然特別,但是 JavaScript 提供了完全運行時的對象系統(tǒng),這使得它可以模仿多數(shù)面向?qū)ο缶幊谭妒剑运彩钦y(tǒng)的面向?qū)ο笳Z言。而因為與基于類的面向?qū)ο蟮牟町悾覀円卜QJavaScript 為基于原型的面向?qū)ο蟆?/p>
(2)腳本語言
??又被稱為動態(tài)語言,只在被調(diào)用時進行解釋或編譯。在程序的運行過程中逐行解釋執(zhí)行。
(3)解釋性語言
??不需要編譯可以直接使用,由宿主環(huán)境(瀏覽器)解釋執(zhí)行。
我自己是一名從事了多年開發(fā)的web前端老程序員,目前辭職在做自己的web前端私人定制課程,今年年初我花了一個月整理了一份最適合2019年學習的web前端學習干貨,各種框架都有整理,送給每一位前端小伙伴,想要獲取的可以關(guān)注我的頭條號并在后臺私信我:前端,即可免費獲取。
(4)事件驅(qū)動
??JavaScript對用戶的響應(yīng),是以事件驅(qū)動的方式進行的。在網(wǎng)頁(Web Page)中執(zhí)行了某種操作所產(chǎn)生的動作,被稱為“事件”(Event)。例如按下鼠標、移動窗口、選擇菜單等都可以被視為事件。當事件發(fā)生后,可能會引起相應(yīng)的事件響應(yīng),執(zhí)行某些對應(yīng)的腳本,這種機制被稱為“事件驅(qū)動”。
(5)動態(tài)性
??語言的動態(tài)性,是指程序在運行時可以改變其結(jié)構(gòu)。在一個 JavaScript 對象中,要為一個屬性賦值時,我們不必事先創(chuàng)建一個變量,只需要在使用的時候做賦值操作即可。
(6)弱類型&松散類型
?? 弱類型語言聲明數(shù)據(jù)時不需要指定數(shù)據(jù)類型,一個變量可以賦不同數(shù)據(jù)類型的值,不同類型數(shù)據(jù)在計算過程中會自動進行轉(zhuǎn)換,在參與運算的過程中,JavaScript會將其自動轉(zhuǎn)換為數(shù)據(jù)類型,比如JavaScript中布爾類型的數(shù)據(jù)可以直接參與運算。
(7)單線程與異步處理共存
??單線程程序的執(zhí)行順序是從上到下依次執(zhí)行,一個程序中只可以執(zhí)行一個程序。而異步處理不用阻塞當前線程來等待處理完成,而是允許后續(xù)操作,直至其它線程將處理完成。二者看起來相互矛盾,不能夠同時存在,可是JavaScript以一種巧妙地方式用單線程實現(xiàn)了異步處理的效果。
??在JavaScript的主線程和任務(wù)隊列中間還存在一個WebAPIs,這個WebAPIs是瀏覽器單獨為處理JavaScript的異步任務(wù)開辟的線程。當主線程棧中的函數(shù)需要異步處理的時候,主線程會把需要異步的部分推給WebAPIs,這部分異步由瀏覽器去執(zhí)行,執(zhí)行完異步之后將處理后的結(jié)果以事件的形式丟到任務(wù)隊列中,這個事件就是我們寫代碼時的“回調(diào)函數(shù)”。
(8)跨平臺
??JavaScript依賴于瀏覽器本身,與操作環(huán)境無關(guān)。
(9)安全性語言
??JavaScript是一種安全性語言,它不允許訪問本地的硬盤,并不能將數(shù)據(jù)存入到服務(wù)器上,不允許對網(wǎng)絡(luò)文檔進行修改和刪除,只能通過瀏覽器實現(xiàn)信息瀏覽或動態(tài)交互。從而有效地防止數(shù)據(jù)的丟失。
3.javaScript的核心
??JavaScript 的三大核心為ECMAScript、DOM 和 BOM。
??ECMAScript(核心):
????描述了該語言的語法和基本對象,ECMAScript是一種由Ecma國際(前身為歐洲計算機制造商協(xié)會,European Computer Manufacturers Association)通過ECMA-262標準化的腳本程序設(shè)計語言。這種語言在萬維網(wǎng)上應(yīng)用廣泛,它往往被稱為JavaScript或JScript,所以它可以理解為是JavaScript的一個標準,但實際上后兩者是ECMA-262標準的實現(xiàn)和擴展。
??DOM(文檔對象模型):
????描述了處理網(wǎng)頁內(nèi)容的方法和接口;是W3C組織推薦的處理可擴展置標語言的標準編程接口。是一種與平臺和語言無關(guān)的應(yīng)用程序接口(API),它可以動態(tài)地訪問程序和腳本,更新其內(nèi)容、結(jié)構(gòu)和www文檔的風格(目前,HTML和XML文檔是通過說明部分定義的)。
??BOM(瀏覽器對象模型):
????描述了與瀏覽器進行交互的方法和接口;用于描述這種對象與對象之間層次關(guān)系的模型,瀏覽器對象模型提供了獨立于內(nèi)容的、可以與瀏覽器窗口進行互動的對象結(jié)構(gòu)。BOM由多個對象組成,其中代表瀏覽器窗口的Window對象是BOM的頂層對象,其他對象都是該對象的子對象。
4.javaScript的使用
html頁面中寫在<script></script>標簽內(nèi)
引入外部.js文件
??引入的多個js文件,會按順序分開執(zhí)行。同樣的,對于不同<script>標簽嵌入的JavaScript代碼,也會根據(jù)腳本標簽<script>的出現(xiàn)順序來分開執(zhí)行。由于JavaScript通常需要操作DOM,所以,一般把JavaScript放在前或者文檔結(jié)尾處引入。若需要在中引入,可以通過修改window.onload或者document.ready事件,強制等到DOM加載完成后再執(zhí)行相關(guān)函數(shù)。
5.javaScript的執(zhí)行順序
??JavaScript代碼執(zhí)行分為兩個部分:
預(yù)編譯階段,進行代碼的檢查裝載,同樣也是按從上到下按順序進行的。此階段只進行變量和函數(shù)的聲明,會跳過執(zhí)行語句,不對變量進行賦值,變量的默認值為undefined。即使聲明是在調(diào)用的下方進行的,但瀏覽器仍然先聲明再調(diào)用(執(zhí)行),這個現(xiàn)象叫做“聲明提升”。所以,即便一個函數(shù)的聲明在函數(shù)調(diào)用的下方,前面仍然可以正常執(zhí)行這個函數(shù),需要注意的是函數(shù)聲明的提升優(yōu)先于變量聲明的提升。(js代碼的執(zhí)行是讀一行代碼執(zhí)行一行,但在執(zhí)行之前系統(tǒng)會先對js進行全面掃描檢查是否存在低級的語法錯誤,并不會立即執(zhí)行語句。)
原文鏈接:https://blog.csdn.net/qq_28453669/article/details/103211948
作者:Simeow
*請認真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。