js学习笔记知识点
用法
安全限制
JSONP
CORS
面向?qū)ο缶幊?br />創(chuàng)建對(duì)象
構(gòu)造函數(shù)
原型繼承
class繼承
AJAX
用法
AJAX不是JavaScript的規(guī)范,它只是一個(gè)哥們“發(fā)明”的縮寫:Asynchronous JavaScript and XML,意思就是用JavaScript執(zhí)行異步網(wǎng)絡(luò)請(qǐng)求
在現(xiàn)代瀏覽器上寫AJAX主要依靠XMLHttpRequest對(duì)象:
對(duì)于低版本的IE,需要換一個(gè)ActiveXObject對(duì)象:
如果你想把標(biāo)準(zhǔn)寫法和IE寫法混在一起,可以這么寫:
通過檢測window對(duì)象是否有XMLHttpRequest屬性來確定瀏覽器是否支持標(biāo)準(zhǔn)的XMLHttpRequest。注意,不要根據(jù)瀏覽器的navigator.userAgent來檢測瀏覽器是否支持某個(gè)JavaScript特性,一是因?yàn)檫@個(gè)字符串本身可以偽造,二是通過IE版本判斷JavaScript特性將非常復(fù)雜。
當(dāng)創(chuàng)建了XMLHttpRequest對(duì)象后,要先設(shè)置onreadystatechange的回調(diào)函數(shù)。在回調(diào)函數(shù)中,通常我們只需通過readyState === 4判斷請(qǐng)求是否完成,如果已完成,再根據(jù)status === 200判斷是否是一個(gè)成功的響應(yīng)。
XMLHttpRequest對(duì)象的open()方法有3個(gè)參數(shù),第一個(gè)參數(shù)指定是GET還是POST,第二個(gè)參數(shù)指定URL地址,第三個(gè)參數(shù)指定是否使用異步,默認(rèn)是true,所以不用寫。
注意,千萬不要把第三個(gè)參數(shù)指定為false,否則瀏覽器將停止響應(yīng),直到AJAX請(qǐng)求完成。如果這個(gè)請(qǐng)求耗時(shí)10秒,那么10秒內(nèi)你會(huì)發(fā)現(xiàn)瀏覽器處于“假死”狀態(tài)。
最后調(diào)用send()方法才真正發(fā)送請(qǐng)求。GET請(qǐng)求不需要參數(shù),POST請(qǐng)求需要把body部分以字符串或者FormData對(duì)象傳進(jìn)去。
安全限制
上面代碼的URL使用的是相對(duì)路徑。如果你把它改為'http://www.sina.com.cn/',再運(yùn)行,肯定報(bào)錯(cuò)。在Chrome的控制臺(tái)里,還可以看到錯(cuò)誤信息。
這是因?yàn)闉g覽器的同源策略導(dǎo)致的。默認(rèn)情況下,JavaScript在發(fā)送AJAX請(qǐng)求時(shí),URL的域名必須和當(dāng)前頁面完全一致。
完全一致的意思是,域名要相同(www.example.com和example.com不同),協(xié)議要相同(http和https不同),端口號(hào)要相同(默認(rèn)是:80端口,它和:8080就不同)。有的瀏覽器口子松一點(diǎn),允許端口不同,大多數(shù)瀏覽器都會(huì)嚴(yán)格遵守這個(gè)限制。
那是不是用JavaScript無法請(qǐng)求外域(就是其他網(wǎng)站)的URL了呢?方法還是有的,大概有這么幾種:
一是通過Flash插件發(fā)送HTTP請(qǐng)求,這種方式可以繞過瀏覽器的安全限制,但必須安裝Flash,并且跟Flash交互。不過Flash用起來麻煩,而且現(xiàn)在用得也越來越少了。
二是通過在同源域名下架設(shè)一個(gè)代理服務(wù)器來轉(zhuǎn)發(fā),JavaScript負(fù)責(zé)把請(qǐng)求發(fā)送到代理服務(wù)器:
'/proxy?url=http://www.sina.com.cn'
代理服務(wù)器再把結(jié)果返回,這樣就遵守了瀏覽器的同源策略。這種方式麻煩之處在于需要服務(wù)器端額外做開發(fā)。
JSONP
第三種方式稱為JSONP,它有個(gè)限制,只能用GET請(qǐng)求,并且要求返回JavaScript。這種方式跨域?qū)嶋H上是利用了瀏覽器允許跨域引用JavaScript資源:
JSONP通常以函數(shù)調(diào)用的形式返回,例如,返回JavaScript內(nèi)容如下:
這樣一來,我們?nèi)绻陧撁嬷邢葴?zhǔn)備好foo()函數(shù),然后給頁面動(dòng)態(tài)加一個(gè)<script>節(jié)點(diǎn),相當(dāng)于動(dòng)態(tài)讀取外域的JavaScript資源,最后就等著接收回調(diào)了。
以163的股票查詢URL為例,對(duì)于URL:http://api.money.126.net/data/feed/0000001,1399001?callback=refreshPrice,你將得到如下返回:
refreshPrice({"0000001":{"code": "0000001", … });
因此我們需要首先在頁面中準(zhǔn)備好回調(diào)函數(shù):
最后用getPrice()函數(shù)觸發(fā):
就完成了跨域加載數(shù)據(jù)。
CORS
如果瀏覽器支持HTML5,那么就可以一勞永逸地使用新的跨域策略:CORS了。
CORS全稱Cross-Origin Resource Sharing,是HTML5規(guī)范定義的如何跨域訪問資源。
了解CORS前,我們先搞明白概念:
Origin表示本域,也就是瀏覽器當(dāng)前頁面的域。當(dāng)JavaScript向外域(如sina.com)發(fā)起請(qǐng)求后,瀏覽器收到響應(yīng)后,首先檢查Access-Control-Allow-Origin是否包含本域,如果是,則此次跨域請(qǐng)求成功,如果不是,則請(qǐng)求失敗,JavaScript將無法獲取到響應(yīng)的任何數(shù)據(jù)。
用一個(gè)圖來表示就是:
假設(shè)本域是my.com,外域是sina.com,只要響應(yīng)頭Access-Control-Allow-Origin為http://my.com,或者是*,本次請(qǐng)求就可以成功。
可見,跨域能否成功,取決于對(duì)方服務(wù)器是否愿意給你設(shè)置一個(gè)正確的Access-Control-Allow-Origin,決定權(quán)始終在對(duì)方手中。
上面這種跨域請(qǐng)求,稱之為“簡單請(qǐng)求”。簡單請(qǐng)求包括GET、HEAD和POST(POST的Content-Type類型
僅限application/x-www-form-urlencoded、multipart/form-data和text/plain),并且不能出現(xiàn)任何自定義頭(例如,X-Custom: 12345),通常能滿足90%的需求。
無論你是否需要用JavaScript通過CORS跨域請(qǐng)求資源,你都要了解CORS的原理。最新的瀏覽器全面支持HTML5。在引用外域資源時(shí),除了JavaScript和CSS外,都要驗(yàn)證CORS。例如,當(dāng)你引用了某個(gè)第三方CDN上的字體文件時(shí):
如果該CDN服務(wù)商未正確設(shè)置Access-Control-Allow-Origin,那么瀏覽器無法加載字體資源。
對(duì)于PUT、DELETE以及其他類型如application/json的POST請(qǐng)求,在發(fā)送AJAX請(qǐng)求之前,瀏覽器會(huì)先發(fā)送一個(gè)OPTIONS請(qǐng)求(稱為preflighted請(qǐng)求)到這個(gè)URL上,詢問目標(biāo)服務(wù)器是否接受:
服務(wù)器必須響應(yīng)并明確指出允許的Method:
瀏覽器確認(rèn)服務(wù)器響應(yīng)的Access-Control-Allow-Methods頭確實(shí)包含將要發(fā)送的AJAX請(qǐng)求的Method,才會(huì)繼續(xù)發(fā)送AJAX,否則,拋出一個(gè)錯(cuò)誤。
由于以POST、PUT方式傳送JSON格式的數(shù)據(jù)在REST中很常見,所以要跨域正確處理POST和PUT請(qǐng)求,服務(wù)器端必須正確響應(yīng)OPTIONS請(qǐng)求。
面向?qū)ο缶幊?/h2> 創(chuàng)建對(duì)象
JavaScript對(duì)每個(gè)創(chuàng)建的對(duì)象都會(huì)設(shè)置一個(gè)原型,指向它的原型對(duì)象。
當(dāng)我們用obj.xxx訪問一個(gè)對(duì)象的屬性時(shí),JavaScript引擎先在當(dāng)前對(duì)象上查找該屬性,如果沒有找到,就到其原型對(duì)象上找,如果還沒有找到,就一直上溯到Object.prototype對(duì)象,最后,如果還沒有找到,就只能返回undefined。
例如,創(chuàng)建一個(gè)Array對(duì)象:
其原型鏈?zhǔn)?#xff1a;
arr —-> Array.prototype —-> Object.prototype —-> null
Array.prototype定義了indexOf()、shift()等方法,因此你可以在所有的Array對(duì)象上直接調(diào)用這些方法。
當(dāng)我們創(chuàng)建一個(gè)函數(shù)時(shí):
函數(shù)也是一個(gè)對(duì)象,它的原型鏈?zhǔn)?#xff1a;
foo —-> Function.prototype —-> Object.prototype —-> null
由于Function.prototype定義了apply()等方法,因此,所有函數(shù)都可以調(diào)用apply()方法。
很容易想到,如果原型鏈很長,那么訪問一個(gè)對(duì)象的屬性就會(huì)因?yàn)榛ǜ嗟臅r(shí)間查找而變得更慢,因此要注意不要把原型鏈搞得太長。
構(gòu)造函數(shù)
除了直接用{ … }創(chuàng)建一個(gè)對(duì)象外,JavaScript還可以用一種構(gòu)造函數(shù)的方法來創(chuàng)建對(duì)象。它的用法是,先定義一個(gè)構(gòu)造函數(shù):
你會(huì)問,咦,這不是一個(gè)普通函數(shù)嗎?
這確實(shí)是一個(gè)普通函數(shù),但是在JavaScript中,可以用關(guān)鍵字new來調(diào)用這個(gè)函數(shù),并返回一個(gè)對(duì)象:
注意,如果不寫new,這就是一個(gè)普通函數(shù),它返回undefined。但是,如果寫了new,它就變成了一個(gè)構(gòu)造函數(shù),它綁定的this指向新創(chuàng)建的對(duì)象,并默認(rèn)返回this,也就是說,不需要在最后寫return this;。
新創(chuàng)建的xiaoming的原型鏈?zhǔn)?#xff1a;
xiaoming —-> Student.prototype —-> Object.prototype —-> null
也就是說,xiaoming的原型指向函數(shù)Student的原型。如果你又創(chuàng)建了xiaohong、xiaojun,那么這些對(duì)象的原型與xiaoming是一樣的:
用new Student()創(chuàng)建的對(duì)象還從原型上獲得了一個(gè)constructor屬性,它指向函數(shù)Student本身:
看暈了吧?用一張圖來表示這些亂七八糟的關(guān)系就是:
紅色箭頭是原型鏈。注意,Student.prototype指向的對(duì)象就是xiaoming、xiaohong的原型對(duì)象,這個(gè)原型對(duì)象自己還有個(gè)屬性constructor,指向Student函數(shù)本身。
另外,函數(shù)Student恰好有個(gè)屬性prototype指向xiaoming、xiaohong的原型對(duì)象,但是xiaoming、xiaohong這些對(duì)象可沒有prototype這個(gè)屬性,不過可以用__proto__這個(gè)非標(biāo)準(zhǔn)用法來查看。
現(xiàn)在我們就認(rèn)為xiaoming、xiaohong這些對(duì)象“繼承”自Student。
不過還有一個(gè)小問題,注意觀察:
xiaoming和xiaohong各自的name不同,這是對(duì)的,否則我們無法區(qū)分誰是誰了。
xiaoming和xiaohong各自的hello是一個(gè)函數(shù),但它們是兩個(gè)不同的函數(shù),雖然函數(shù)名稱和代碼都是相同的!
如果我們通過new Student()創(chuàng)建了很多對(duì)象,這些對(duì)象的hello函數(shù)實(shí)際上只需要共享同一個(gè)函數(shù)就可以了,這樣可以節(jié)省很多內(nèi)存。
要讓創(chuàng)建的對(duì)象共享一個(gè)hello函數(shù),根據(jù)對(duì)象的屬性查找原則,我們只要把hello函數(shù)移動(dòng)到xiaoming、xiaohong這些對(duì)象共同的原型上就可以了,也就是Student.prototype:
修改代碼如下:
用new創(chuàng)建基于原型的JavaScript的對(duì)象就是這么簡單!
- 忘記寫new怎么辦
如果一個(gè)函數(shù)被定義為用于創(chuàng)建對(duì)象的構(gòu)造函數(shù),但是調(diào)用時(shí)忘記了寫new怎么辦?
在strict模式下,this.name = name將報(bào)錯(cuò),因?yàn)閠his綁定為undefined,在非strict模式下,this.name = name不報(bào)錯(cuò),因?yàn)閠his綁定為window,于是無意間創(chuàng)建了全局變量name,并且返回undefined,這個(gè)結(jié)果更糟糕。
所以,調(diào)用構(gòu)造函數(shù)千萬不要忘記寫new。為了區(qū)分普通函數(shù)和構(gòu)造函數(shù),按照約定,構(gòu)造函數(shù)首字母應(yīng)當(dāng)大寫,而普通函數(shù)首字母應(yīng)當(dāng)小寫,這樣,一些語法檢查工具如jslint將可以幫你檢測到漏寫的new。
最后,我們還可以編寫一個(gè)createStudent()函數(shù),在內(nèi)部封裝所有的new操作。一個(gè)常用的編程模式像這樣:
這個(gè)createStudent()函數(shù)有幾個(gè)巨大的優(yōu)點(diǎn):一是不需要new來調(diào)用,二是參數(shù)非常靈活,可以不傳,也可以這么傳:
如果創(chuàng)建的對(duì)象有很多屬性,我們只需要傳遞需要的某些屬性,剩下的屬性可以用默認(rèn)值。由于參數(shù)是一個(gè)Object,我們無需記憶參數(shù)的順序。如果恰好從JSON拿到了一個(gè)對(duì)象,就可以直接創(chuàng)建出xiaoming。
原型繼承
在傳統(tǒng)的基于Class的語言如Java、C++中,繼承的本質(zhì)是擴(kuò)展一個(gè)已有的Class,并生成新的Subclass。
由于這類語言嚴(yán)格區(qū)分類和實(shí)例,繼承實(shí)際上是類型的擴(kuò)展。但是,JavaScript由于采用原型繼承,我們無法直接擴(kuò)展一個(gè)Class,因?yàn)楦静淮嬖贑lass這種類型。
但是辦法還是有的。我們先回顧Student構(gòu)造函數(shù):
···javascript
function Student(props) {
this.name = props.name || 'Unnamed';
}
Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
}
但是,調(diào)用了Student構(gòu)造函數(shù)不等于繼承了Student,PrimaryStudent創(chuàng)建的對(duì)象的原型是:
new PrimaryStudent() —-> PrimaryStudent.prototype —-> Object.prototype —-> null
必須想辦法把原型鏈修改為:
new PrimaryStudent() —-> PrimaryStudent.prototype —-> Student.prototype —-> Object.prototype —-> null
這樣,原型鏈對(duì)了,繼承關(guān)系就對(duì)了。新的基于PrimaryStudent創(chuàng)建的對(duì)象不但能調(diào)用PrimaryStudent.prototype定義的方法,也可以調(diào)用Student.prototype定義的方法。
如果你想用最簡單粗暴的方法這么干:
是不行的!如果這樣的話,PrimaryStudent和Student共享一個(gè)原型對(duì)象,那還要定義PrimaryStudent干啥?
我們必須借助一個(gè)中間對(duì)象來實(shí)現(xiàn)正確的原型鏈,這個(gè)中間對(duì)象的原型要指向Student.prototype。為了實(shí)現(xiàn)這一點(diǎn),參考道爺(就是發(fā)明JSON的那個(gè)道格拉斯)的代碼,中間對(duì)象可以用一個(gè)空函數(shù)F來實(shí)現(xiàn):
用一張圖來表示新的原型鏈:
注意,函數(shù)F僅用于橋接,我們僅創(chuàng)建了一個(gè)new F()實(shí)例,而且,沒有改變?cè)械腟tudent定義的原型鏈。
如果把繼承這個(gè)動(dòng)作用一個(gè)inherits()函數(shù)封裝起來,還可以隱藏F的定義,并簡化代碼:
這個(gè)inherits()函數(shù)可以復(fù)用:
class繼承
在上面的章節(jié)中我們看到了JavaScript的對(duì)象模型是基于原型實(shí)現(xiàn)的,特點(diǎn)是簡單,缺點(diǎn)是理解起來比傳統(tǒng)的類-實(shí)例模型要困難,最大的缺點(diǎn)是繼承的實(shí)現(xiàn)需要編寫大量代碼,并且需要正確實(shí)現(xiàn)原型鏈。
有沒有更簡單的寫法?有!
新的關(guān)鍵字class從ES6開始正式被引入到JavaScript中。class的目的就是讓定義類更簡單。
我們先回顧用函數(shù)實(shí)現(xiàn)Student的方法:
如果用新的class關(guān)鍵字來編寫Student,可以這樣寫:
比較一下就可以發(fā)現(xiàn),class的定義包含了構(gòu)造函數(shù)constructor和定義在原型對(duì)象上的函數(shù)hello()(注意沒有function關(guān)鍵字),這樣就避免了Student.prototype.hello = function () {…}這樣分散的代碼。
最后,創(chuàng)建一個(gè)Student對(duì)象代碼和前面章節(jié)完全一樣:
- class繼承
用class定義對(duì)象的另一個(gè)巨大的好處是繼承更方便了。想一想我們從Student派生一個(gè)PrimaryStudent需要編寫的代碼量。現(xiàn)在,原型繼承的中間對(duì)象,原型對(duì)象的構(gòu)造函數(shù)等等都不需要考慮了,直接通過extends來實(shí)現(xiàn):
注意PrimaryStudent的定義也是class關(guān)鍵字實(shí)現(xiàn)的,而extends則表示原型鏈對(duì)象來自Student。子類的構(gòu)造函數(shù)可能會(huì)與父類不太相同,例如,PrimaryStudent需要name和grade兩個(gè)參數(shù),并且需要通過super(name)來調(diào)用父類的構(gòu)造函數(shù),否則父類的name屬性無法正常初始化。
PrimaryStudent已經(jīng)自動(dòng)獲得了父類Student的hello方法,我們又在子類中定義了新的myGrade方法。
ES6引入的class和原有的JavaScript原型繼承有什么區(qū)別呢?實(shí)際上它們沒有任何區(qū)別,class的作用就是讓JavaScript引擎去實(shí)現(xiàn)原來需要我們自己編寫的原型鏈代碼。簡而言之,用class的好處就是極大地簡化了原型鏈代碼。
你一定會(huì)問,class這么好用,能不能現(xiàn)在就用上?
現(xiàn)在用還早了點(diǎn),因?yàn)椴皇撬械闹髁鳛g覽器都支持ES6的class。如果一定要現(xiàn)在就用上,就需要一個(gè)工具把class代碼轉(zhuǎn)換為傳統(tǒng)的prototype代碼,可以試試Babel這個(gè)工具。
摘抄來源:http://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000
轉(zhuǎn)載于:https://www.cnblogs.com/luolei/p/7076256.html
總結(jié)
- 上一篇: 乌鲁木齐西府小院是毛坯房还是精装修?
- 下一篇: Scrapy 安装