前端知识总结(三)
51、啟動(dòng)GNU加速
硬件加速的工作原理
瀏覽器接收到一個(gè)頁(yè)面之后,將html解析成DOM樹,瀏覽器解析渲染「html」的過程 按著一定的規(guī)則執(zhí)行,DOM樹和CSS樹結(jié)合后構(gòu)成瀏覽器形成頁(yè)面的 渲染樹 ; 渲染樹中包含大量的渲染元素,每一個(gè)元素會(huì)被分配到一個(gè)圖層中,每個(gè)圖層又會(huì)被加載到GPU形成渲染紋理,而圖層在GPU中 transform 是不會(huì)觸發(fā) repaint 的,這一點(diǎn)非常類似3D繪圖功能,最終這些使用 transform 的圖層都會(huì)由獨(dú)立的合成器進(jìn)程進(jìn)行處理。也正因?yàn)檫@個(gè)原因,避免了頻繁 repaint。
使用 GPU 渲染元素
或許你會(huì)想,既然GPU加速這么好用,那我全用GPU好了,但實(shí)際上只有少數(shù)的幾個(gè)可以觸發(fā)GPU的硬件加速
- transform
- opacity
- filter
提高GPU的使用率,強(qiáng)制使用GPU渲染
因?yàn)?D transform 依舊會(huì)發(fā)生兩次 Paint 操作,我們其實(shí)可以避免這種現(xiàn)象產(chǎn)生:
.transform1 {transform: translateZ(0); } .transform2 {transform: rotateZ(360deg); }強(qiáng)行設(shè)置 3D transform ,瀏覽器識(shí)別到這是一個(gè)3D動(dòng)畫,渲染前就創(chuàng)建了一個(gè)獨(dú)立圖層,圖層中的動(dòng)畫則有GPU進(jìn)行預(yù)處理并出發(fā)了硬件加速。
使用硬件加速的注意事項(xiàng)
使用硬件加速并不是十全十美的事情
- 過度使用引發(fā)的內(nèi)存問題。
- 使用GPU渲染會(huì)影響字體的抗鋸齒效果。這是因?yàn)镚PU和CPU具有不同的渲染機(jī)制。即使最終硬件加速停止了,文本還是會(huì)在動(dòng)畫期間顯示得很模糊。
52、事件冒泡與事件捕獲
事件冒泡和事件捕獲分別由微軟和網(wǎng)景公司提出,這兩個(gè)概念都是為了解決頁(yè)面中事件流(事件發(fā)生順序)的問題。
考慮下面這段代碼,就不寫html->head,body之類的代碼了,自行腦補(bǔ)
事件冒泡
微軟提出了名為事件冒泡(event bubbling)的事件流。事件冒泡可以形象地比喻為把一顆石頭投入水中,泡泡會(huì)一直從水底冒出水面。也就是說,事件會(huì)從最內(nèi)層的元素開始發(fā)生,一直向上傳播,直到document對(duì)象。
因此在事件冒泡的概念下在p元素上發(fā)生click事件的順序應(yīng)該是**p -> div -> body -> html -> document**
事件捕獲
網(wǎng)景提出另一種事件流名為事件捕獲(event capturing)。與事件冒泡相反,事件會(huì)從最外層開始發(fā)生,直到最具體的元素。
因此在事件捕獲的概念下在p元素上發(fā)生click事件的順序應(yīng)該是**document -> html -> body -> div -> p**
addEventListener的第三個(gè)參數(shù)
網(wǎng)景 和 微軟 曾經(jīng)的戰(zhàn)爭(zhēng)還是比較火熱的,當(dāng)時(shí), 網(wǎng)景主張捕獲方式,微軟主張冒泡方式。后來 w3c 采用折中的方式,平息了戰(zhàn)火,制定了統(tǒng)一的標(biāo)準(zhǔn)——先捕獲再冒泡。
addEventListener的第三個(gè)參數(shù)就是為冒泡和捕獲準(zhǔn)備的.
addEventListener有三個(gè)參數(shù):
element.addEventListener(event, function, useCapture)
第一個(gè)參數(shù)是需要綁定的事件
第二個(gè)參數(shù)是觸發(fā)事件后要執(zhí)行的函數(shù)
第三個(gè)參數(shù)默認(rèn)值是false,表示在事件冒泡階段調(diào)用事件處理函數(shù);如果參數(shù)為true,則表示在事件捕獲階段調(diào)用處理函數(shù)。
事件捕獲vs事件冒泡
當(dāng)事件捕獲和事件冒泡一起存在的情況,事件又是如何觸發(fā)呢。
這里記被點(diǎn)擊的DOM節(jié)點(diǎn)為target節(jié)點(diǎn)
總結(jié)下就是:
- 對(duì)于非target節(jié)點(diǎn)則先執(zhí)行捕獲在執(zhí)行冒泡
- 對(duì)于target節(jié)點(diǎn)則是先執(zhí)行先注冊(cè)的事件,無論冒泡還是捕獲
當(dāng)我們點(diǎn)擊s2的時(shí)候,執(zhí)行結(jié)果如下:
s1 捕獲事件 my.html:19 s2 冒泡事件 my.html:27 s2 捕獲事件 my.html:16 s1 冒泡事件這里大體分析下執(zhí)行結(jié)果
點(diǎn)擊s2,click事件從document->html->body->s1->s2(捕獲前進(jìn))
這里在s1上發(fā)現(xiàn)了捕獲注冊(cè)事件,則輸出**“s1 捕獲事件”**
到達(dá)s2,已經(jīng)到達(dá)目的節(jié)點(diǎn),
s2上注冊(cè)了冒泡和捕獲事件,先注冊(cè)的冒泡后注冊(cè)的捕獲,則先執(zhí)行冒泡,輸出**“s2 冒泡事件”**
再在s2上執(zhí)行后注冊(cè)的事件,即捕獲事件,輸出**“s2 捕獲事件”**
下面進(jìn)入冒泡階段,按照s2->s1->body->html->documen(冒泡前進(jìn))
在s1上發(fā)現(xiàn)了冒泡事件,則輸出**“s1 冒泡事件”**
防止冒泡和捕獲
w3c的方法是e.stopPropagation(),IE則是使用e.cancelBubble = true·
stopPropagation也是事件對(duì)象(Event)的一個(gè)方法,作用是阻止目標(biāo)元素的冒泡事件,但是會(huì)不阻止默認(rèn)行為。
取消默認(rèn)事件
w3c的方法是e.preventDefault(),IE則是使用e.returnValue = false;·
preventDefault它是事件對(duì)象(Event)的一個(gè)方法,作用是取消一個(gè)目標(biāo)元素的默認(rèn)行為。既然是說默認(rèn)行為,當(dāng)然是元素必須有默認(rèn)行為才能被取消,如果元素本身就沒有默認(rèn)行為,調(diào)用當(dāng)然就無效了。
return false
javascript的return false只會(huì)阻止默認(rèn)行為,而是用jQuery的話則既阻止默認(rèn)行為又防止對(duì)象冒泡。
IE瀏覽器兼容
IE瀏覽器對(duì)addEventListener兼容性并不算太好,只有IE9以上可以使用。
要兼容舊版本的IE瀏覽器,可以使用IE的attachEvent函數(shù)
object.attachEvent(event, function)
兩個(gè)參數(shù)與addEventListener相似,分別是事件和處理函數(shù),默認(rèn)是事件冒泡階段調(diào)用處理函數(shù),要注意的是,寫事件名時(shí)候要加上"on"前綴(“onload”、"onclick"等)。
53、事件對(duì)象
什么是事件對(duì)象?
就是當(dāng)你觸發(fā)了一個(gè)事件以后,對(duì)該事件的一些描述信息
每一個(gè)事件都會(huì)有一個(gè)對(duì)應(yīng)的對(duì)象來描述這些信息,我們就把這個(gè)對(duì)象叫做 事件對(duì)象
瀏覽器給了我們一個(gè) 黑盒子,叫做 window.event ,就是對(duì)事件信息的所有描述
box.onclick = function (e) {//兼容寫法e = e || window.event }offsetX 和 offsetY
var box = document.querySelector('.box')box.onclick = function (e) {//兼容寫法console.log(e.offsetX)}clientX 和 clientY
是相對(duì)于瀏覽器窗口來計(jì)算的,從瀏覽器可視區(qū)域左上角開始,即是以瀏覽器滑動(dòng)條此刻的滑動(dòng)到的位置為參考點(diǎn),隨滑動(dòng)條移動(dòng) 而變化
var box = document.querySelector('.box')box.onclick = function (e) {//根據(jù)你瀏覽器的窗口來計(jì)算的,Y軸不包含導(dǎo)航地址欄和標(biāo)簽欄這些console.log(e.clientY)}pageX 和 pageY
?是相對(duì)于整個(gè)頁(yè)面的坐標(biāo)點(diǎn),不管有沒有滾動(dòng),都是相對(duì)于頁(yè)面拿到的坐標(biāo)點(diǎn) 從頁(yè)面左上角開始,即是以頁(yè)面為參考點(diǎn),不隨滑動(dòng)條移動(dòng)而變化
var box = document.querySelector('.box')box.onclick = function (e) {//根據(jù)你瀏覽器的窗口來計(jì)算的,Y軸不包含導(dǎo)航地址欄和標(biāo)簽欄這些console.log(e.pageY)console.log(e.pageX)}移動(dòng)端ontouchstart等
ontouchstart、ontouchend、onclick這三個(gè)方法的執(zhí)行順序是ontouchstart > ontouchend > onclick
除了執(zhí)行順序不同以外,還有一個(gè)非常大的區(qū)別那就是onclick只在你快速點(diǎn)擊并放開才會(huì)被執(zhí)行,如果你點(diǎn)擊一個(gè)區(qū)域,很遲才放開,那么onclick是不會(huì)執(zhí)行的
事件綁定
事件綁定的兩種方法
-
DOM0級(jí)事件綁定
- curEle.onclick=function(){};
-
DOM2級(jí)事件綁定
-
標(biāo)準(zhǔn)瀏覽器:curEle.addEventListener('click',function(){},false)
-
IE6-8:curEle.attachEvent('onclick',function(){})
-
DOM0于DOM2事件綁定的區(qū)別
DOM0事件綁定的原理
- 給當(dāng)前元素的某一私有屬性(onXXX)賦值的過程;(之前屬性默認(rèn)值是null,如果我們賦值了一個(gè)函數(shù),就相當(dāng)于綁定了一個(gè)方法)
- 當(dāng)我們賦值成功(賦值一個(gè)函數(shù)),此時(shí)瀏覽器會(huì)把DOM元素和賦值的的函數(shù)建立關(guān)聯(lián),以及建立DOM元素的行為監(jiān)聽,當(dāng)某一行為被用戶觸發(fā),瀏覽器會(huì)把賦值的函數(shù)執(zhí)行;
DOM0事件綁定的特點(diǎn):
- 只有DOM元素天生擁有這個(gè)私有屬性(onxxx事件私有屬性),我們賦值的方法才叫事件綁定,否則屬于設(shè)置自定義屬性
- 移除事件綁定的時(shí)候,我們只需要賦值為null;
- 在DOM0事件綁定中,只能給當(dāng)前元素的某一個(gè)事件行為綁定一個(gè)方法,綁定多個(gè)方法,最后一次的綁定的會(huì)替換前面綁定的
DOM2事件綁定的原理
-
DOM2事件綁定使用的 addEventListener/attachEvent方法都是在eventTarget這個(gè)內(nèi)置類的原型上定義的,我們調(diào)用的時(shí)候,首先要通過原型鏈找到這個(gè)方法,然后執(zhí)行完成事件綁定的效果
-
瀏覽器會(huì)給當(dāng)前元素的某個(gè)事件行為開辟一個(gè)事件池(事件隊(duì)列)【瀏覽器有一個(gè)統(tǒng)一的事件池,每個(gè)元素綁定的行為都放在這里,通過相關(guān)標(biāo)志區(qū)分】,當(dāng)我們通過 addEventListener/attachEvent進(jìn)行事件綁定的時(shí)候,會(huì)把綁定的方法放在事件池中;
-
當(dāng)元素的某一行為被觸發(fā),瀏覽器回到對(duì)應(yīng)事件池中,把當(dāng)前放在事件池的所有方法按序依次執(zhí)行
特點(diǎn)
-
所有DOM0支持的行為,DOM2都可以用,DOM2還支持DOM0沒有的事件行為(這樣說比較籠統(tǒng))
(核心)【瀏覽器會(huì)把一些常用事件掛載到元素對(duì)象的私有屬性上,讓我們可以實(shí)現(xiàn)DOM0事件綁定,DOM2:凡是瀏覽器給元素天生設(shè)置的事件在DOM2中都可以使用】
例如:onDOMContentLoaded(所有的DOM0和IE6-8的DOM2都不支持)onDOMContentLoaded//當(dāng)前瀏覽器中的DOM結(jié)構(gòu)加載完成,就會(huì)觸發(fā)這個(gè)事件
-
DOM2中可以給當(dāng)前元素的某一事件行為綁定多個(gè)不同方法(因?yàn)榻壎ǖ乃蟹椒ǘ挤旁谑录刂?#xff09;;
-
事件的移除:事件類型、綁定的方法、傳播階段三個(gè)完全一致,才可以完成移除(因此在綁定方法時(shí),盡量不要用匿名函數(shù),否則不好移除)DOM0于DOM2事件綁定的區(qū)別
DOM0事件綁定的原理
- 給當(dāng)前元素的某一私有屬性(onXXX)賦值的過程;(之前屬性默認(rèn)值是null,如果我們賦值了一個(gè)函數(shù),就相當(dāng)于綁定了一個(gè)方法)
- 當(dāng)我們賦值成功(賦值一個(gè)函數(shù)),此時(shí)瀏覽器會(huì)把DOM元素和賦值的的函數(shù)建立關(guān)聯(lián),以及建立DOM元素的行為監(jiān)聽,當(dāng)某一行為被用戶觸發(fā),瀏覽器會(huì)把賦值的函數(shù)執(zhí)行;
DOM0事件綁定的特點(diǎn):
- 只有DOM元素天生擁有這個(gè)私有屬性(onxxx事件私有屬性),我們賦值的方法才叫事件綁定,否則屬于設(shè)置自定義屬性
- 移除事件綁定的時(shí)候,我們只需要賦值為null;
- 在DOM0事件綁定中,只能給當(dāng)前元素的某一個(gè)事件行為綁定一個(gè)方法,綁定多個(gè)方法,最后一次的綁定的會(huì)替換前面綁定的
DOM2事件綁定的原理
-
DOM2事件綁定使用的 addEventListener/attachEvent方法都是在eventTarget這個(gè)內(nèi)置類的原型上定義的,我們調(diào)用的時(shí)候,首先要通過原型鏈找到這個(gè)方法,然后執(zhí)行完成事件綁定的效果
-
瀏覽器會(huì)給當(dāng)前元素的某個(gè)事件行為開辟一個(gè)事件池(事件隊(duì)列)【瀏覽器有一個(gè)統(tǒng)一的事件池,每個(gè)元素綁定的行為都放在這里,通過相關(guān)標(biāo)志區(qū)分】,當(dāng)我們通過 addEventListener/attachEvent進(jìn)行事件綁定的時(shí)候,會(huì)把綁定的方法放在事件池中;
-
當(dāng)元素的某一行為被觸發(fā),瀏覽器回到對(duì)應(yīng)事件池中,把當(dāng)前放在事件池的所有方法按序依次執(zhí)行
特點(diǎn)
-
所有DOM0支持的行為,DOM2都可以用,DOM2還支持DOM0沒有的事件行為(這樣說比較籠統(tǒng))
(核心)【瀏覽器會(huì)把一些常用事件掛載到元素對(duì)象的私有屬性上,讓我們可以實(shí)現(xiàn)DOM0事件綁定,DOM2:凡是瀏覽器給元素天生設(shè)置的事件在DOM2中都可以使用】
例如:onDOMContentLoaded(所有的DOM0和IE6-8的DOM2都不支持)onDOMContentLoaded//當(dāng)前瀏覽器中的DOM結(jié)構(gòu)加載完成,就會(huì)觸發(fā)這個(gè)事件
-
DOM2中可以給當(dāng)前元素的某一事件行為綁定多個(gè)不同方法(因?yàn)榻壎ǖ乃蟹椒ǘ挤旁谑录刂?#xff09;;
-
事件的移除:事件類型、綁定的方法、傳播階段三個(gè)完全一致,才可以完成移除(因此在綁定方法時(shí),盡量不要用匿名函數(shù),否則不好移除)
DOMContentLoaded和load的區(qū)別
- DOMContentLoaded
當(dāng)初始的 HTML 文檔被完全加載和解析完成之后,DOMContentLoaded 事件被觸發(fā),而無需等待樣式表、圖像和子框架的完成加載。
- load
load 僅用于檢測(cè)一個(gè)完全加載的頁(yè)面,頁(yè)面的html、css、js、圖片等資源都已經(jīng)加載完之后才會(huì)觸發(fā) load 事件。
54、函數(shù)防抖和節(jié)流
防抖(debounce)
所謂防抖,就是指觸發(fā)事件后在 n 秒內(nèi)函數(shù)只能執(zhí)行一次,如果在 n 秒內(nèi)又觸發(fā)了事件,則會(huì)重新計(jì)算函數(shù)執(zhí)行時(shí)間。
防抖函數(shù)分為非立即執(zhí)行版和立即執(zhí)行版。
非立即執(zhí)行版:
function debounce(func,wait){let timeout=null;return function(){let context=this;let args=arguments;if(timeout) clearTimeout(timeout);timeout=setTimeout(()=>{func.apply(context,args);},wait);} }立即執(zhí)行版:
function debounce(func,wait) {let timeout;return function () {let context = this;let args = arguments;if (timeout) clearTimeout(timeout);let callNow = !timeout;timeout = setTimeout(() => {timeout = null;}, wait)if (callNow) func.apply(context, args)} }節(jié)流(throttle)
**所謂節(jié)流,就是指連續(xù)觸發(fā)事件但是在 n 秒中只執(zhí)行一次函數(shù)。**節(jié)流會(huì)稀釋函數(shù)的執(zhí)行頻率。
對(duì)于節(jié)流,一般有兩種方式可以實(shí)現(xiàn),分別是時(shí)間戳版和定時(shí)器版。
時(shí)間戳版:
function throttle(func, wait) {let previous = 0;return function() {let now = Date.now();let context = this;let args = arguments;if (now - previous > wait) {func.apply(context, args);previous = now;}} }定時(shí)器版:
function throttle(func, wait) {let timeout;return function() {let context = this;let args = arguments;if (!timeout) {timeout = setTimeout(() => {timeout = null;func.apply(context, args)}, wait)}} }結(jié)合應(yīng)用場(chǎng)景
防抖(debounce)
search搜索聯(lián)想,用戶在不斷輸入值時(shí),用防抖來節(jié)約請(qǐng)求資源。
window觸發(fā)resize的時(shí)候,不斷的調(diào)整瀏覽器窗口大小會(huì)不斷的觸發(fā)這個(gè)事件,用防抖來讓其只觸發(fā)一次
節(jié)流(throttle)
鼠標(biāo)不斷點(diǎn)擊觸發(fā),mousedown(單位時(shí)間內(nèi)只觸發(fā)一次)
監(jiān)聽滾動(dòng)事件,比如是否滑到底部自動(dòng)加載更多,用throttle來判斷
55、實(shí)現(xiàn)兩端固定,中間自適應(yīng)的布局
1).絕對(duì)定位法
絕對(duì)定位法原理是將左右兩邊使用absolute定位,因?yàn)榻^對(duì)定位使其脫離文檔流,后面的center會(huì)自然流動(dòng)到他們上面,然后使用margin屬性,留出左右元素的寬度,既可以使中間元素自適應(yīng)屏幕寬度。
代碼如下:
<!DOCTYPE html> <html><head><meta charset="UTF-8"><title>layout_box</title><link rel="stylesheet" type="text/css" href="../css/layout_box.css"></head><body><h3>實(shí)現(xiàn)三列寬度自適應(yīng)布局</h3><div id = "left">我是左邊</div><div id = "right">我是右邊</div><div id = "center">我是中間</div></body> </html>css代碼:
html,body{ margin: 0px;width: 100%; } h3{height: 100px;margin:20px 0 0;} #left,#right{width: 200px;height: 200px; background-color: #ffe6b8;position: absolute;top:120px;} #left{left:0px;} #right{right: 0px;} #center{margin:2px 210px ;background-color: #eee;height: 200px; }2).使用自身浮動(dòng)法
<h3>使用自身浮動(dòng)法定位</h3> <div id = "left_self">我是左邊</div> <div id = "right_self">我是右邊</div> <div id = "center_self">我是中間</div> #left_self,#right_self{ width: 200px;height: 200px; background-color: #ffe6b8 } #left_self {float: left;} #right_self{float: right;} #center_self{margin: 0 210px;height: 200px; background-color: #a0b3d6}css3新特性
在外圍包裹一層div,設(shè)置為display:flex;中間設(shè)置flex:1;但是盒模型默認(rèn)緊緊挨著,可以使用margin控制外邊距。
代碼:
<div id = "box"><div id = "left_box"></div><div id = "center_box"></div><div id = "right_box"></div></div> #box{width:100%;display: flex; height: 100px;margin: 10px;} #left_box,#right_box{width: 200px;height: 100px; margin: 10px; background-color: lightpink} #center_box{ flex:1; height: 100px;margin: 10px; background-color: lightgreen}grid
<div class="container"><div class="left"></div><div class="center"></div><div class="right"></div></div> .container {display: grid;width: 100%;grid-template-rows: 100px;grid-template-columns: 300px auto 300px;}.left {background: red;}.center {background: yellow;}.right {background: blue;}表格布局
<section class="layout table"><style>.layout.table .left-center-right {width: 100%;display: table;height: 100px;}.layout.table .left-center-right>div {display: table-cell;}.layout.table .left {width: 300px;background: red;}.layout.table .center {background: yellow;}.layout.table .right {width: 300px;background: blue;}</style><article class="left-center-right"><div class="left"></div><div class="center"><h1>表格布局的解決方案</h1><p>1.這是布局的中間部分</p><p>2.這是布局的中間部分</p></div><div class="right"></div></article> </section>圣杯布局
<style>body {min-width: 550px;}* {margin: 0;}#container {padding-left: 200px; padding-right: 150px;}#container .column {float: left;height: 300px;}#center {width: 100%;background: red;}#left {width: 200px;background: blue;margin-left: calc(-100% + -200px);}#right {width: 150px;background: pink;margin-right: -100%;}#footer {clear: both;}</style> </head> <body><div id="header"></div><div id="container"><div id="center" class="column"></div><div id="left" class="column"></div><div id="right" class="column"></div></div><div id="footer"></div> </body>其中有一點(diǎn)要注意的是margin、padding、left等設(shè)為百分比時(shí)是相對(duì)父元素的width
雙飛翼布局
<style>body {min-width: 500px;}* {margin: 0;}#center {padding-left: 200px; padding-right: 150px;}.column {float: left;height: 300px;}#container {width: 100%;background: red;}#left {width: 200px;background: blue;margin-left: -100%;}#right {width: 150px;background: pink;margin-left: -150px;}#footer {clear: both;}</style> </head> <body><div id="header"></div><div id="container" class="column"><div id="center"></div></div><div id="left" class="column"></div><div id="right" class="column"></div><div id="footer"></div> </body>55、JS實(shí)現(xiàn)觀察者模式
class Sub {constructor(){this.observe=[];}attach(val){this.observe.push(val);}getState(){return this.state;}setState(val){this.state=val;this.observe.forEach(watcher=>{watcher.update()})} } class Observe {constructor(sub){this.sub=sub;this.sub.attach(this);}update(){console.log('更新了'+this.sub.getState)} }56、手撕前端路由
// 這里用hash的路由方法實(shí)現(xiàn) function Router() {this.routes = {}this.curUrl = ''this.init() }Router.prototype.route = function (path, cb) {this.routes[path] = cb || function () {} }Router.prototype.refresh = function () {this.curUrl = location.hash.slice(1) || '/'this.routes[this.curUrl] && this.routes[this.curUrl]() }Router.prototype.init = function () {window.addEventListener('load', this.refresh.bind(this))window.addEventListener('hashchange', this.refresh.bind(this)) }// 用個(gè)例子試用一下 var router = new Router() router.route('/', function () {var body = document.getElementById('page-type')body.innerHTML = '首頁(yè)耶耶耶' }) router.route('/news', function () {var body = document.getElementById('page-type')body.innerHTML = '新聞耶耶耶' }) // 這里用history的路由方法實(shí)現(xiàn) function Router() {this.routes = {}this.curUrl = ''this.init() }Router.prototype.route = function (path, cb) {this.routes[path] = cb || function () {} }Router.prototype.refresh = function () {this.curUrl = location.hash.slice(1) || '/'this.routes[this.curUrl] && this.routes[this.curUrl]() }Router.prototype.init = function () {window.addEventListener('load', this.refresh.bind(this))window.addEventListener('popstate', this.refresh.bind(this)) }// 用個(gè)例子試用一下 var router = new Router() router.route('/', function () {var body = document.getElementById('page-type')body.innerHTML = '首頁(yè)耶耶耶' }) router.route('/news', function () {var body = document.getElementById('page-type')body.innerHTML = '新聞耶耶耶' })57、Object.getPrototypeOf() 方法用于獲取指定對(duì)象的原型對(duì)象(也就是__protp__的指向)
59、window中各類高度
網(wǎng)頁(yè)可見區(qū)域高:document.body.clientHeight
網(wǎng)頁(yè)正文全文高:document.body.scrollHeight
網(wǎng)頁(yè)可見區(qū)域高(包括邊線的高):document.body.offsetHeight
網(wǎng)頁(yè)被卷去的高:document.body.scrollTop
屏幕分辨率高:window.screen.height
每個(gè)HTML元素都具有clientHeight offsetHeight scrollHeight offsetTop scrollTop 這5個(gè)和元素高度、滾動(dòng)、位置相關(guān)的屬性,單憑單詞很難搞清楚分別代表什么意思之間有什么區(qū)別。通過閱讀它們的文檔總結(jié)出規(guī)律如下:
clientHeight和offsetHeight屬性和元素的滾動(dòng)、位置沒有關(guān)系它代表元素的高度,其中:
**clientHeight:**包括padding但不包括border、水平滾動(dòng)條、margin的元素的高度。對(duì)于inline的元素這個(gè)屬性一直是0,單位px,只讀元素。
**offsetHeight:**包括padding、border、水平滾動(dòng)條,但不包括margin的元素的高度。對(duì)于inline的元素這個(gè)屬性一直是0,單位px,只讀元素。
接下來討論出現(xiàn)有滾動(dòng)條時(shí)的情況:
當(dāng)本元素的子元素比本元素高且overflow=scroll時(shí),本元素會(huì)scroll,這時(shí):
scrollHeight: 因?yàn)樽釉乇雀冈馗?#xff0c;父元素不想被子元素?fù)蔚囊粯痈呔惋@示出了滾動(dòng)條,在滾動(dòng)的過程中本元素有部分被隱藏了,scrollHeight代表包括當(dāng)前不可見部分的元素的高度。而可見部分的高度其實(shí)就是clientHeight,也就是scrollHeight>=clientHeight恒成立。在有滾動(dòng)條時(shí)討論scrollHeight才有意義,在沒有滾動(dòng)條時(shí)scrollHeight==clientHeight恒成立。單位px,只讀元素。
scrollTop: 代表在有滾動(dòng)條時(shí),滾動(dòng)條向下滾動(dòng)的距離也就是元素頂部被遮住部分的高度。在沒有滾動(dòng)條時(shí)scrollTop==0恒成立。單位px,可讀可設(shè)置。
offsetTop: 當(dāng)前元素頂部距離最近父元素頂部的距離,和有沒有滾動(dòng)條沒有關(guān)系。單位px,只讀元素。
60、實(shí)現(xiàn)懶加載和預(yù)加載
<body><div class="imglist"><img src="./loading.gif" alt="" class="lazy" data-src="./1.jpg"><img src="./loading.gif" alt="" class="lazy" data-src="./2.jpg"><img src="./loading.gif" alt="" class="lazy" data-src="./3.jpg"><img src="./loading.gif" alt="" class="lazy" data-src="./4.jpg"><img src="./loading.gif" alt="" class="lazy" data-src="./5.jpg"><img src="./loading.gif" alt="" class="lazy" data-src="./6.jpg"><img src="./loading.gif" alt="" class="lazy" data-src="./7.jpg"><img src="./loading.gif" alt="" class="lazy" data-src="./8.jpg"></div> </body> <script>// onload是等所有的資源文件加載完畢以后再綁定事件 window.onload = function(){// 獲取圖片列表,即img標(biāo)簽列表var imgs = document.querySelectorAll('img');// 獲取到瀏覽器頂部的距離function getTop(e){return e.offsetTop;}// 懶加載實(shí)現(xiàn)function lazyload(imgs){// 可視區(qū)域高度var h = window.innerHeight;//滾動(dòng)區(qū)域高度var s = document.documentElement.scrollTop || document.body.scrollTop;for(var i=0;i<imgs.length;i++){//圖片距離頂部的距離大于可視區(qū)域和滾動(dòng)區(qū)域之和時(shí)懶加載if ((h+s)>getTop(imgs[i])) {// 真實(shí)情況是頁(yè)面開始有2秒空白,所以使用setTimeout定時(shí)2s(function(i){setTimeout(function(){// 不加立即執(zhí)行函數(shù)i會(huì)等于9// 隱形加載圖片或其他資源,//創(chuàng)建一個(gè)臨時(shí)圖片,這個(gè)圖片在內(nèi)存中不會(huì)到頁(yè)面上去。實(shí)現(xiàn)隱形加載var temp = new Image();temp.src = imgs[i].getAttribute('data-src');//只會(huì)請(qǐng)求一次// onload判斷圖片加載完畢,真是圖片加載完畢,再賦值給dom節(jié)點(diǎn)temp.onload = function(){// 獲取自定義屬性data-src,用真圖片替換假圖片imgs[i].src = imgs[i].getAttribute('data-src')}},2000)})(i)}}}lazyload(imgs);// 滾屏函數(shù)window.onscroll =function(){lazyload(imgs);} } </script>實(shí)現(xiàn)預(yù)加載的幾種辦法
- 使用HTML標(biāo)簽
- 使用Image對(duì)象
61、編寫一個(gè)求和函數(shù)sum,使輸入sum(2)(3)或輸入sum(2,3),輸出結(jié)果都為5
function sum(){var num = arguments[0];if(arguments.length == 1){return function(sec){return num+sec;}}else{for(var i = 1; i < arguments.length; i++){num += arguments[i];}return num;} }62、CORS
CORS是一個(gè)W3C標(biāo)準(zhǔn),全稱是"跨域資源共享"(Cross-origin resource sharing)。
它允許瀏覽器向跨源服務(wù)器,發(fā)出XMLHttpRequest請(qǐng)求,從而克服了AJAX只能同源使用的限制。
CORS需要瀏覽器和服務(wù)器同時(shí)支持。目前,所有瀏覽器都支持該功能,IE瀏覽器不能低于IE10。
整個(gè)CORS通信過程,都是瀏覽器自動(dòng)完成,不需要用戶參與。對(duì)于開發(fā)者來說,CORS通信與同源的AJAX通信沒有差別,代碼完全一樣。瀏覽器一旦發(fā)現(xiàn)AJAX請(qǐng)求跨源,就會(huì)自動(dòng)添加一些附加的頭信息,有時(shí)還會(huì)多出一次附加的請(qǐng)求,但用戶不會(huì)有感覺。
因此,實(shí)現(xiàn)CORS通信的關(guān)鍵是服務(wù)器。只要服務(wù)器實(shí)現(xiàn)了CORS接口,就可以跨源通信。
瀏覽器將CORS請(qǐng)求分成兩類:簡(jiǎn)單請(qǐng)求(simple request)和非簡(jiǎn)單請(qǐng)求(not-so-simple request)。
只要同時(shí)滿足以下兩大條件,就屬于簡(jiǎn)單請(qǐng)求。
(1) 請(qǐng)求方法是以下三種方法之一:
- HEAD
- GET
- POST
(2)HTTP的頭信息不超出以下幾種字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三個(gè)值application/x-www-form-urlencoded、multipart/form-data、text/plain
簡(jiǎn)單請(qǐng)求
基本流程
對(duì)于簡(jiǎn)單請(qǐng)求,瀏覽器直接發(fā)出CORS請(qǐng)求。具體來說,就是在頭信息之中,增加一個(gè)Origin字段。
下面是一個(gè)例子,瀏覽器發(fā)現(xiàn)這次跨源AJAX請(qǐng)求是簡(jiǎn)單請(qǐng)求,就自動(dòng)在頭信息之中,添加一個(gè)Origin字段。
GET /cors HTTP/1.1 Origin: http://api.bob.com Host: api.alice.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0...上面的頭信息中,Origin字段用來說明,本次請(qǐng)求來自哪個(gè)源(協(xié)議 + 域名 + 端口)。服務(wù)器根據(jù)這個(gè)值,決定是否同意這次請(qǐng)求。
如果Origin指定的源,不在許可范圍內(nèi),服務(wù)器會(huì)返回一個(gè)正常的HTTP回應(yīng)。瀏覽器發(fā)現(xiàn),這個(gè)回應(yīng)的頭信息沒有包含Access-Control-Allow-Origin字段(詳見下文),就知道出錯(cuò)了,從而拋出一個(gè)錯(cuò)誤,被XMLHttpRequest的onerror回調(diào)函數(shù)捕獲。注意,這種錯(cuò)誤無法通過狀態(tài)碼識(shí)別,因?yàn)镠TTP回應(yīng)的狀態(tài)碼有可能是200。
如果Origin指定的域名在許可范圍內(nèi),服務(wù)器返回的響應(yīng),會(huì)多出幾個(gè)頭信息字段。
Access-Control-Allow-Origin: http://api.bob.com Access-Control-Allow-Credentials: true Access-Control-Expose-Headers: FooBar Content-Type: text/html; charset=utf-8上面的頭信息之中,有三個(gè)與CORS請(qǐng)求相關(guān)的字段,都以Access-Control-開頭。
(1)Access-Control-Allow-Origin
該字段是必須的。它的值要么是請(qǐng)求時(shí)Origin字段的值,要么是一個(gè)*,表示接受任意域名的請(qǐng)求。
(2)Access-Control-Allow-Credentials
該字段可選。它的值是一個(gè)布爾值,表示是否允許發(fā)送Cookie。默認(rèn)情況下,Cookie不包括在CORS請(qǐng)求之中。設(shè)為true,即表示服務(wù)器明確許可,Cookie可以包含在請(qǐng)求中,一起發(fā)給服務(wù)器。這個(gè)值也只能設(shè)為true,如果服務(wù)器不要瀏覽器發(fā)送Cookie,刪除該字段即可。
(3)Access-Control-Expose-Headers
該字段可選。CORS請(qǐng)求時(shí),XMLHttpRequest對(duì)象的getResponseHeader()方法只能拿到6個(gè)基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必須在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader('FooBar')可以返回FooBar字段的值。
withCredentials 屬性
上面說到,CORS請(qǐng)求默認(rèn)不發(fā)送Cookie和HTTP認(rèn)證信息。如果要把Cookie發(fā)到服務(wù)器,一方面要服務(wù)器同意,指定Access-Control-Allow-Credentials字段。
Access-Control-Allow-Credentials: true另一方面,開發(fā)者必須在AJAX請(qǐng)求中打開withCredentials屬性。
var xhr = new XMLHttpRequest(); xhr.withCredentials = true;否則,即使服務(wù)器同意發(fā)送Cookie,瀏覽器也不會(huì)發(fā)送。或者,服務(wù)器要求設(shè)置Cookie,瀏覽器也不會(huì)處理。
但是,如果省略withCredentials設(shè)置,有的瀏覽器還是會(huì)一起發(fā)送Cookie。這時(shí),可以顯式關(guān)閉withCredentials。
xhr.withCredentials = false;需要注意的是,如果要發(fā)送Cookie,Access-Control-Allow-Origin就不能設(shè)為星號(hào),必須指定明確的、與請(qǐng)求網(wǎng)頁(yè)一致的域名。同時(shí),Cookie依然遵循同源政策,只有用服務(wù)器域名設(shè)置的Cookie才會(huì)上傳,其他域名的Cookie并不會(huì)上傳,且(跨源)原網(wǎng)頁(yè)代碼中的document.cookie也無法讀取服務(wù)器域名下的Cookie。
非簡(jiǎn)單請(qǐng)求
預(yù)檢請(qǐng)求
非簡(jiǎn)單請(qǐng)求是那種對(duì)服務(wù)器有特殊要求的請(qǐng)求,比如請(qǐng)求方法是PUT或DELETE,或者Content-Type字段的類型是application/json。
非簡(jiǎn)單請(qǐng)求的CORS請(qǐng)求,會(huì)在正式通信之前,增加一次HTTP查詢請(qǐng)求,稱為"預(yù)檢"請(qǐng)求(preflight)。
瀏覽器先詢問服務(wù)器,當(dāng)前網(wǎng)頁(yè)所在的域名是否在服務(wù)器的許可名單之中,以及可以使用哪些HTTP動(dòng)詞和頭信息字段。只有得到肯定答復(fù),瀏覽器才會(huì)發(fā)出正式的XMLHttpRequest請(qǐng)求,否則就報(bào)錯(cuò)。
下面是一段瀏覽器的JavaScript腳本。
var url = 'http://api.alice.com/cors'; var xhr = new XMLHttpRequest(); xhr.open('PUT', url, true); xhr.setRequestHeader('X-Custom-Header', 'value'); xhr.send();上面代碼中,HTTP請(qǐng)求的方法是PUT,并且發(fā)送一個(gè)自定義頭信息X-Custom-Header。
瀏覽器發(fā)現(xiàn),這是一個(gè)非簡(jiǎn)單請(qǐng)求,就自動(dòng)發(fā)出一個(gè)"預(yù)檢"請(qǐng)求,要求服務(wù)器確認(rèn)可以這樣請(qǐng)求。下面是這個(gè)"預(yù)檢"請(qǐng)求的HTTP頭信息。
OPTIONS /cors HTTP/1.1 Origin: http://api.bob.com Access-Control-Request-Method: PUT Access-Control-Request-Headers: X-Custom-Header Host: api.alice.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0..."預(yù)檢"請(qǐng)求用的請(qǐng)求方法是OPTIONS,表示這個(gè)請(qǐng)求是用來詢問的。頭信息里面,關(guān)鍵字段是Origin,表示請(qǐng)求來自哪個(gè)源。
除了Origin字段,"預(yù)檢"請(qǐng)求的頭信息包括兩個(gè)特殊字段。
(1)Access-Control-Request-Method
該字段是必須的,用來列出瀏覽器的CORS請(qǐng)求會(huì)用到哪些HTTP方法,上例是PUT。
(2)Access-Control-Request-Headers
該字段是一個(gè)逗號(hào)分隔的字符串,指定瀏覽器CORS請(qǐng)求會(huì)額外發(fā)送的頭信息字段,上例是X-Custom-Header。
預(yù)檢請(qǐng)求的回應(yīng)
服務(wù)器收到"預(yù)檢"請(qǐng)求以后,檢查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,確認(rèn)允許跨源請(qǐng)求,就可以做出回應(yīng)。
HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 01:15:39 GMT Server: Apache/2.0.61 (Unix) Access-Control-Allow-Origin: http://api.bob.com Access-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: X-Custom-Header Content-Type: text/html; charset=utf-8 Content-Encoding: gzip Content-Length: 0 Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Content-Type: text/plain上面的HTTP回應(yīng)中,關(guān)鍵的是Access-Control-Allow-Origin字段,表示http://api.bob.com可以請(qǐng)求數(shù)據(jù)。該字段也可以設(shè)為星號(hào),表示同意任意跨源請(qǐng)求。
Access-Control-Allow-Origin: *如果服務(wù)器否定了"預(yù)檢"請(qǐng)求,會(huì)返回一個(gè)正常的HTTP回應(yīng),但是沒有任何CORS相關(guān)的頭信息字段。這時(shí),瀏覽器就會(huì)認(rèn)定,服務(wù)器不同意預(yù)檢請(qǐng)求,因此觸發(fā)一個(gè)錯(cuò)誤,被XMLHttpRequest對(duì)象的onerror回調(diào)函數(shù)捕獲。控制臺(tái)會(huì)打印出如下的報(bào)錯(cuò)信息。
XMLHttpRequest cannot load http://api.alice.com. Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.服務(wù)器回應(yīng)的其他CORS相關(guān)字段如下。
Access-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: X-Custom-Header Access-Control-Allow-Credentials: true Access-Control-Max-Age: 1728000(1)Access-Control-Allow-Methods
該字段必需,它的值是逗號(hào)分隔的一個(gè)字符串,表明服務(wù)器支持的所有跨域請(qǐng)求的方法。注意,返回的是所有支持的方法,而不單是瀏覽器請(qǐng)求的那個(gè)方法。這是為了避免多次"預(yù)檢"請(qǐng)求。
(2)Access-Control-Allow-Headers
如果瀏覽器請(qǐng)求包括Access-Control-Request-Headers字段,則Access-Control-Allow-Headers字段是必需的。它也是一個(gè)逗號(hào)分隔的字符串,表明服務(wù)器支持的所有頭信息字段,不限于瀏覽器在"預(yù)檢"中請(qǐng)求的字段。
(3)Access-Control-Allow-Credentials
該字段與簡(jiǎn)單請(qǐng)求時(shí)的含義相同。
(4)Access-Control-Max-Age
該字段可選,用來指定本次預(yù)檢請(qǐng)求的有效期,單位為秒。上面結(jié)果中,有效期是20天(1728000秒),即允許緩存該條回應(yīng)1728000秒(即20天),在此期間,不用發(fā)出另一條預(yù)檢請(qǐng)求。
4.3 瀏覽器的正常請(qǐng)求和回應(yīng)
一旦服務(wù)器通過了"預(yù)檢"請(qǐng)求,以后每次瀏覽器正常的CORS請(qǐng)求,就都跟簡(jiǎn)單請(qǐng)求一樣,會(huì)有一個(gè)Origin頭信息字段。服務(wù)器的回應(yīng),也都會(huì)有一個(gè)Access-Control-Allow-Origin頭信息字段。
下面是"預(yù)檢"請(qǐng)求之后,瀏覽器的正常CORS請(qǐng)求。
PUT /cors HTTP/1.1 Origin: http://api.bob.com Host: api.alice.com X-Custom-Header: value Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0...上面頭信息的Origin字段是瀏覽器自動(dòng)添加的。
下面是服務(wù)器正常的回應(yīng)。
Access-Control-Allow-Origin: http://api.bob.com Content-Type: text/html; charset=utf-8上面頭信息中,Access-Control-Allow-Origin字段是每次回應(yīng)都必定包含的。
63、為什么在項(xiàng)目中data需要使用return返回?cái)?shù)據(jù)呢?
組件是一個(gè)可復(fù)用的實(shí)例,當(dāng)你引用一個(gè)組件的時(shí)候,組件里的data是一個(gè)普通的對(duì)象,所有用到這個(gè)組件的都引用的同一個(gè)data,就會(huì)造成數(shù)據(jù)污染。
不使用return包裹的數(shù)據(jù)會(huì)在項(xiàng)目的全局可見,會(huì)造成變量污染;使用return包裹后數(shù)據(jù)中變量只在當(dāng)前組件中生效,不會(huì)影響其他組件。
data必須是函數(shù)的原因
當(dāng)一個(gè)組件被定義, data 必須聲明為返回一個(gè)初始數(shù)據(jù)對(duì)象的函數(shù),因?yàn)榻M件可能被用來創(chuàng)建多個(gè)實(shí)例。如果 data 仍然是一個(gè)純粹的對(duì)象,則所有的實(shí)例將共享引用同一個(gè)數(shù)據(jù)對(duì)象!通過提供 data 函數(shù),每次創(chuàng)建一個(gè)新實(shí)例后,我們能夠調(diào)用 data 函數(shù),從而返回初始數(shù)據(jù)的一個(gè)全新副本數(shù)據(jù)對(duì)象。
因?yàn)樵贘S 中只有函數(shù)才存在作用域,data是一個(gè)函數(shù)時(shí),每個(gè)組件實(shí)例都有自己的作用域,每個(gè)實(shí)例相互獨(dú)立,不會(huì)相互影響!
64、微信小程序的更新機(jī)制 && 如何讓微信用戶更新小程序
啟動(dòng)
小程序啟動(dòng)會(huì)有兩種情況,一種是「冷啟動(dòng)」,一種是「熱啟動(dòng)」。 假如用戶已經(jīng)打開過某小程序,然后在一定時(shí)間內(nèi)再次打開該小程序,此時(shí)無需重新啟動(dòng),只需將后臺(tái)態(tài)的小程序切換到前臺(tái),這個(gè)過程就是熱啟動(dòng);冷啟動(dòng)指的是用戶首次打開或小程序被微信主動(dòng)銷毀后再次打開的情況,此時(shí)小程序需要重新加載啟動(dòng)。
更新機(jī)制
小程序冷啟動(dòng)時(shí)如果發(fā)現(xiàn)有新版本,將會(huì)異步下載新版本的代碼包,并同時(shí)用客戶端本地的包進(jìn)行啟動(dòng),即新版本的小程序需要等下一次冷啟動(dòng)才會(huì)應(yīng)用上。 如果需要馬上應(yīng)用最新版本,可以使用 wx.getUpdateManager API 進(jìn)行處理。
運(yùn)行機(jī)制
- 小程序沒有重啟的概念
- 當(dāng)小程序進(jìn)入后臺(tái),客戶端會(huì)維持一段時(shí)間的運(yùn)行狀態(tài),超過一定時(shí)間后(目前是5分鐘)會(huì)被微信主動(dòng)銷毀
- 當(dāng)短時(shí)間內(nèi)(5s)連續(xù)收到兩次以上收到系統(tǒng)內(nèi)存告警,會(huì)進(jìn)行小程序的銷毀
小程序重新初始化時(shí)會(huì)觸發(fā)onLaunch事件. onLaunch事件會(huì)觸發(fā)在頁(yè)面onShow事件之前.獲取小程序更新版本可以寫在onLaunch里.
// 在app.js里寫下以下代碼onLaunch () {if (wx.canIUse('getUpdateManager')) {const updateManager = wx.getUpdateManager()updateManager.onCheckForUpdate(function (res) {console.log('onCheckForUpdate====', res)// 請(qǐng)求完新版本信息的回調(diào)if (res.hasUpdate) {console.log('res.hasUpdate====')updateManager.onUpdateReady(function () {wx.showModal({title: '更新提示',content: '新版本已經(jīng)準(zhǔn)備好,是否重啟應(yīng)用?',success: function (res) {console.log('success====', res)// res: {errMsg: "showModal: ok", cancel: false, confirm: true}if (res.confirm) {// 新的版本已經(jīng)下載好,調(diào)用 applyUpdate 應(yīng)用新版本并重啟updateManager.applyUpdate()}}})})updateManager.onUpdateFailed(function () {// 新的版本下載失敗wx.showModal({title: '已經(jīng)有新版本了喲~',content: '新版本已經(jīng)上線啦~,請(qǐng)您刪除當(dāng)前小程序,重新搜索打開喲~'})})}})}}65、構(gòu)造函數(shù)&&原型
構(gòu)造函數(shù)
創(chuàng)建一個(gè)構(gòu)造函數(shù),專門用來創(chuàng)建Person對(duì)象的
? 構(gòu)造函數(shù)就是一個(gè)普通的函數(shù),創(chuàng)建方式和普通函數(shù)沒有區(qū)別,
? 不同的是構(gòu)造函數(shù)習(xí)慣上首字母大寫
構(gòu)造函數(shù)和普通函數(shù)的區(qū)別就是調(diào)用方式的不同
普通函數(shù)是直接調(diào)用,而構(gòu)造函數(shù)需要使用new關(guān)鍵字來調(diào)用
構(gòu)造函數(shù)的執(zhí)行流程:
1.立刻創(chuàng)建一個(gè)新的對(duì)象
2.將新建的對(duì)象設(shè)置為函數(shù)中this,在構(gòu)造函數(shù)中可以使用this來引用新建的對(duì)象
3.逐行執(zhí)行函數(shù)中的代碼
4.將新建的對(duì)象作為返回值返回
使用同一個(gè)構(gòu)造函數(shù)創(chuàng)建的對(duì)象,我們稱為一類對(duì)象,也將一個(gè)構(gòu)造函數(shù)稱為一個(gè)類。
我們將通過一個(gè)構(gòu)造函數(shù)創(chuàng)建的對(duì)象,稱為是該類的實(shí)例
this的情況:
1.當(dāng)以函數(shù)的形式調(diào)用時(shí),this是window
2.當(dāng)以方法的形式調(diào)用時(shí),誰調(diào)用方法this就是誰
3.當(dāng)以構(gòu)造函數(shù)的形式調(diào)用時(shí),this就是新創(chuàng)建的那個(gè)對(duì)象
如果方法是在構(gòu)造函數(shù)內(nèi)部創(chuàng)建的, 也就是構(gòu)造函數(shù)每執(zhí)行一次就會(huì)創(chuàng)建一個(gè)新的方法,也就是所有實(shí)例的方法都是唯一的。這樣就導(dǎo)致了構(gòu)造函數(shù)執(zhí)行一次就會(huì)創(chuàng)建一個(gè)新的方法,執(zhí)行10000次就會(huì)創(chuàng)建10000個(gè)新的方法,而10000個(gè)方法都是一摸一樣的。這是完全沒有必要,完全可以使所有的對(duì)象共享同一個(gè)方法,這就利用了prototype
prototype和_proto_
Javascript中所有的對(duì)象都是Object的實(shí)例,并繼承Object.prototype的屬性和方法,也就是說,Object.prototype是所有對(duì)象的爸爸。
在對(duì)象創(chuàng)建時(shí),就會(huì)有一些預(yù)定義的屬性,其中定義函數(shù)的時(shí)候,這個(gè)預(yù)定義屬性就是prototype,這個(gè)prototype是一個(gè)普通的對(duì)象。
而定義普通的對(duì)象的時(shí)候,就會(huì)生成一個(gè)__proto__,這個(gè)__proto__指向的是這個(gè)對(duì)象的構(gòu)造函數(shù)的prototype.
我們所創(chuàng)建的每一個(gè)函數(shù),解析器都會(huì)向函數(shù)中添加一個(gè)屬性prototype
這個(gè)屬性對(duì)應(yīng)著一個(gè)對(duì)象,這個(gè)對(duì)象就是我們所謂的原型對(duì)象
如果函數(shù)作為普通函數(shù)調(diào)用prototype沒有任何作用
當(dāng)函數(shù)以構(gòu)造函數(shù)的形式調(diào)用時(shí),它所創(chuàng)建的對(duì)象中都會(huì)有一個(gè)隱含的屬性,
指向該構(gòu)造函數(shù)的原型對(duì)象,我們可以通過__proto__來訪問該屬性
原型對(duì)象就相當(dāng)于一個(gè)公共的區(qū)域,所有同一個(gè)類的實(shí)例都可以訪問到這個(gè)原型對(duì)象,
我們可以將對(duì)象中共有的內(nèi)容,統(tǒng)一設(shè)置到原型對(duì)象中。
當(dāng)我們?cè)L問對(duì)象的一個(gè)屬性或方法時(shí),它會(huì)先在對(duì)象自身中尋找,如果有則直接使用,
? 如果沒有則會(huì)去原型對(duì)象中尋找,如果找到則直接使用
? 以后我們創(chuàng)建構(gòu)造函數(shù)時(shí),可以將這些對(duì)象共有的屬性和方法,統(tǒng)一添加到構(gòu)造函數(shù)的原型對(duì)象中,
? 這樣不用分別為每一個(gè)對(duì)象添加,也不會(huì)影響到全局作用域,就可以使每個(gè)對(duì)象都具有這些屬性和方法了
66、js中實(shí)例方法、靜態(tài)方法和原型方法
實(shí)例方法
構(gòu)造函數(shù)中this上添加的成員 ,在Cat構(gòu)造方法里面,定義在this中的變量和方法,只有實(shí)例才能訪問到:如this.name,this.move,this.eat這些都是實(shí)例擁有,無法通過Cat直接調(diào)用。
function Cat(name){this.name = namethis.move = function() {console.log('移動(dòng)')}this.eat = function() {console.log(`${this.name}愛吃魚`)} } Cat.eat() let cat=new Cat(); cat.eat() //tom愛吃魚 //這是實(shí)例方法靜態(tài)方法
構(gòu)造函數(shù)本身上添加的成員
下面的Cat.eat就是構(gòu)造函數(shù)的靜態(tài)方法,不能通過實(shí)例調(diào)用
function Cat(name){this.move = function() {console.log(1)} } //直接定義在Cat構(gòu)造函數(shù)中,實(shí)例不能調(diào)用 Cat.eat = function() {console.log(`${this.name}愛吃魚`)} 構(gòu)造函數(shù)調(diào)用 Cat.eat() //Cat愛吃魚 Cat.move() //Cat.move is not a function let cat = new Cat() cat.eat() //cat.eat is not a function原型方法
原型中的方法實(shí)例和構(gòu)造函數(shù)都可以訪問到
function Cat() { } Cat.eat = function() {console.log('靜態(tài)方法') } Cat.prototype.eat = function() {console.log('原型方法') } let cat = new Cat() Cat.eat() //靜態(tài)方法 Cat.prototype.eat() //原型方法,不用prototype就是打印靜態(tài)方法cat.eat() //原型方法簡(jiǎn)而言之,實(shí)例方法就是只有實(shí)例可以調(diào)用,靜態(tài)方法只有構(gòu)造函數(shù)可以調(diào)用,原型方法是實(shí)例和構(gòu)造函數(shù)都可以調(diào)用,是共享的方法。
像Promise.all和Promise.race這些就是靜態(tài)方法,Promise.prototype.then這些就是原型方法,new 出來的實(shí)例可以調(diào)用
67、JS中繼承的幾種方式
1.原型鏈繼承
function Person (name, age) {this.name = name;this.age = age; } Person.prototype.say = function(){console.log('hello, my name is ' + this.name); }; function Man() { } Man.prototype = new Person('pursue'); var man1 = new Man(); man1.say(); //hello, my name is pursue var man2 = new Man(); console.log(man1.say === man2.say);//true console.log(man1.name === man2.name);//true此時(shí)person的name和age在man的prototype,但原型方法仍然在person的prototype,因?yàn)閚ame和age是實(shí)例屬性,而say是實(shí)例方法
2、利用構(gòu)造函數(shù)繼承
function Person (name, age) {this.name = name;this.age = age; } Person.prototype.say = function(){console.log('hello, my name is ' + this.name); }; function Man(name, age) {Person.apply(this, arguments); } //Man.prototype = new Person('pursue'); var man1 = new Man('joe'); var man2 = new Man('david'); console.log(man1.name === man2.name);//false man1.say(); //say is not a function優(yōu)點(diǎn):
缺點(diǎn):
3、組合繼承(原型鏈繼承與構(gòu)造繼承)
function Child(name) {Parent.call(this,name) //構(gòu)造繼承 ,第二次調(diào)用父類 } //原型鏈繼承 Child.prototype=new Parent() Child.prototype.constructor=Child//因重寫原型而失去constructor屬性,所以要對(duì)constrcutor重新賦值var child=new Child("yzh") //子類的實(shí)例向父類傳遞參數(shù),第一次調(diào)用父類 console.log(child.name) child.introduce() child.hobby("sing") console.log(child instanceof Parent) //true console.log(child instanceof Child) //true優(yōu)點(diǎn):結(jié)合了原型鏈繼承和構(gòu)造繼承的優(yōu)點(diǎn)
缺點(diǎn):這種方式調(diào)用了兩次父類的構(gòu)造函數(shù),生成了兩份實(shí)例,相同的屬性既存在于實(shí)例中也存在于原型中
4、寄生組合繼承
function Person (name, age) {this.name = name;this.age = age;} Person.prototype.say = function(){console.log('hello, my name is ' + this.name); }; function Man(name, age) {Person.apply(this, arguments); } Man.prototype = Object.create(Person.prototype);//a. Man.prototype.constructor = Man;//b. var man1 = new Man('pursue'); var man2 = new Man('joe'); console.log(man1.say == man2.say); console.log(man1.name == man2.name);其實(shí)寄生組合繼承和上面的組合繼承區(qū)別僅在于構(gòu)造子類原型對(duì)象的方式上(a.和b.),這里用到了Object.creat(obj)方法,該方法會(huì)對(duì)傳入的obj對(duì)象進(jìn)行淺拷貝,類似于:
function create(obj){function T(){};T.prototype = obj;return new T(); }組合繼承(構(gòu)造函數(shù)和原型的組合)會(huì)調(diào)用兩次父類構(gòu)造函數(shù)的代碼,因此引入寄生組合式繼承,即通過借用構(gòu)造函數(shù)來繼承屬性,通過原型鏈的方式來繼承方法,而不需要為子類指定原型而調(diào)用父類的構(gòu)造函數(shù),我們需要拿到的僅僅是父類原型的一個(gè)副本。因此可以通過傳入子類和父類的構(gòu)造函數(shù)作為參數(shù),首先創(chuàng)建父類原型的一個(gè)復(fù)本,并為其添加constrcutor,最后賦給子類的原型。這樣避免了調(diào)用兩次父類的構(gòu)造函數(shù),為其創(chuàng)建多余的屬性。
68、for in 和for of的區(qū)別
for in通常用來遍歷對(duì)象
for in 可以遍歷到該對(duì)象的原型方法method,如果不想遍歷原型方法和屬性的話,可以在循環(huán)內(nèi)部判斷一下,hasOwnPropery方法可以判斷某屬性是否是該對(duì)象的實(shí)例屬性。
同樣可以通過ES5的Object.keys(myObject)獲取對(duì)象的實(shí)例屬性組成的數(shù)組,不包括原型方法和屬性。
for of通常用來遍歷數(shù)組
for in遍歷的是數(shù)組的索引(即鍵名),而for of遍歷的是數(shù)組元素值。
for of遍歷的只是數(shù)組內(nèi)的元素,而不包括數(shù)組的原型屬性method和索引name
for…of適用遍歷數(shù)/數(shù)組對(duì)象/字符串/map/set等擁有迭代器對(duì)象的集合.但是不能遍歷對(duì)象,因?yàn)闆]有迭代器對(duì)象.與forEach()不同的是,它可以正確響應(yīng)break、continue和return語句.使用foreach遍歷數(shù)組的話,使用break不能中斷循環(huán),使用return也不能返回到外層函數(shù)。
69、內(nèi)置對(duì)象/宿主對(duì)象/自定義對(duì)象的區(qū)別?
1、內(nèi)置對(duì)象:系統(tǒng)所提供的對(duì)象;如Object、Array、Math、Date等等。
2、宿主對(duì)象:JS所運(yùn)行的環(huán)境提供的對(duì)象比如:BOM中的Window、DOM中的document。
3、自定義對(duì)象:自定義構(gòu)造函數(shù)所創(chuàng)建的對(duì)象。
71、判斷一個(gè)對(duì)象是否存在
現(xiàn)在,我們要判斷一個(gè)全局對(duì)象myObj是否存在,如果不存在,就對(duì)它進(jìn)行聲明。
if (!myObj) {var myObj = { };} if (!window.myObj) {var myObj = { };}上面這種寫法的缺點(diǎn)在于,在某些運(yùn)行環(huán)境中(比如V8、Rhino),window未必是頂層對(duì)象。所以,考慮改寫成:
if (!this.myObj) {this.myObj = { };}還可以使用typeof運(yùn)算符,判斷myObj是否有定義。
if (typeof myObj == "undefined") {var myObj = { };}這是目前使用最廣泛的判斷javascript對(duì)象是否存在的方法。
由于在已定義、但未賦值的情況下,myObj的值直接等于undefined,所以上面的寫法可以簡(jiǎn)化:
if (myObj == undefined) {var myObj = { };}上面的寫法在"精確比較"(===)的情況下,依然成立:
根據(jù)javascript的語言設(shè)計(jì),undefined == null,所以比較myObj是否等于null,也能得到正確結(jié)果:
if (myObj == null) {var myObj = { };}還可以使用in運(yùn)算符,判斷myObj是否為頂層對(duì)象的一個(gè)屬性:
if (!('myObj' in window)) {window.myObj = { };}最后,使用hasOwnProperty方法,判斷myObj是否為頂層對(duì)象的一個(gè)屬性:
if (!this.hasOwnProperty('myObj')) {this.myObj = { };}72、Object.create(null) 和 {} 區(qū)別
Object.create(null)沒有繼承任何原型方法,也就是說它的原型鏈沒有上一層。即沒有_proto_
總結(jié)
- 上一篇: 黑马乐优商城Java57期
- 下一篇: webstorm前端常用快捷键