51、啟動GNU加速
硬件加速的工作原理
瀏覽器接收到一個頁面之后,將html解析成DOM樹,瀏覽器解析渲染「html」的過程 按著一定的規則執行,DOM樹和CSS樹結合后構成瀏覽器形成頁面的 渲染樹 ; 渲染樹中包含大量的渲染元素,每一個元素會被分配到一個圖層中,每個圖層又會被加載到GPU形成渲染紋理,而圖層在GPU中 transform
是不會觸發 repaint 的,這一點非常類似3D繪圖功能,最終這些使用 transform
的圖層都會由獨立的合成器進程進行處理。也正因為這個原因,避免了頻繁 repaint
。
使用 GPU 渲染元素
或許你會想,既然GPU加速這么好用,那我全用GPU好了,但實際上只有少數的幾個可以觸發GPU的硬件加速
- transform
- opacity
- filter
提高GPU的使用率,強制使用GPU渲染
因為2D transform
依舊會發生兩次 Paint
操作,我們其實可以避免這種現象產生:
.transform1 {transform: translateZ(0);
}
.transform2 {transform: rotateZ(360deg);
}
強行設置 3D transform
,瀏覽器識別到這是一個3D動畫,渲染前就創建了一個獨立圖層,圖層中的動畫則有GPU進行預處理并出發了硬件加速。
使用硬件加速的注意事項
使用硬件加速并不是十全十美的事情
- 過度使用引發的內存問題。
- 使用GPU渲染會影響字體的抗鋸齒效果。這是因為GPU和CPU具有不同的渲染機制。即使最終硬件加速停止了,文本還是會在動畫期間顯示得很模糊。
52、事件冒泡與事件捕獲
事件冒泡
和事件捕獲
分別由微軟
和網景
公司提出,這兩個概念都是為了解決頁面中事件流(事件發生順序)的問題。
考慮下面這段代碼,就不寫html->head,body之類的代碼了,自行腦補
<div id="outer"><p id="inner">Click me!</p>
</div>
事件冒泡
微軟提出了名為事件冒泡(event bubbling)
的事件流。事件冒泡可以形象地比喻為把一顆石頭投入水中,泡泡會一直從水底冒出水面。也就是說,事件會從最內層的元素開始發生,一直向上傳播,直到document對象。
因此在事件冒泡的概念下在p元素上發生click事件的順序應該是**p -> div -> body -> html -> document
**
事件捕獲
網景提出另一種事件流名為事件捕獲(event capturing)
。與事件冒泡相反,事件會從最外層開始發生,直到最具體的元素。
因此在事件捕獲的概念下在p元素上發生click事件的順序應該是**document -> html -> body -> div -> p
**
addEventListener的第三個參數
網景 和 微軟 曾經的戰爭還是比較火熱的,當時, 網景主張捕獲方式,微軟主張冒泡方式。后來 w3c 采用折中的方式,平息了戰火,制定了統一的標準——先捕獲再冒泡。
addEventListener的第三個參數就是為冒泡和捕獲準備的.
addEventListener有三個參數:
element.addEventListener(event, function, useCapture)
第一個參數是需要綁定的事件
第二個參數是觸發事件后要執行的函數
第三個參數默認值是false,表示在事件冒泡階段調用事件處理函數;如果參數為true,則表示在事件捕獲階段調用處理函數。
事件捕獲vs事件冒泡
當事件捕獲和事件冒泡一起存在的情況,事件又是如何觸發呢。
這里記被點擊的DOM節點為target節點
- document 往 target節點,捕獲前進,遇到注冊的捕獲事件立即觸發執行
- 到達target節點,觸發事件(對于target節點上,是先捕獲還是先冒泡則捕獲事件和冒泡事件的注冊順序,先注冊先執行)
- target節點 往 document 方向,冒泡前進,遇到注冊的冒泡事件立即觸發
總結下就是:
- 對于非target節點則先執行捕獲在執行冒泡
- 對于target節點則是先執行先注冊的事件,無論冒泡還是捕獲
<div id="s1">s1<div id="s2">s2</div>
</div>
<script>
s1.addEventListener("click",function(e){console.log("s1 冒泡事件");
},false);
s2.addEventListener("click",function(e){console.log("s2 冒泡事件");
},false);s1.addEventListener("click",function(e){console.log("s1 捕獲事件");
},true);s2.addEventListener("click",function(e){console.log("s2 捕獲事件");
},true);
</script>
當我們點擊s2的時候,執行結果如下:
s1 捕獲事件
my.html:19 s2 冒泡事件
my.html:27 s2 捕獲事件
my.html:16 s1 冒泡事件
這里大體分析下執行結果
點擊s2,click事件從document->html->body->s1->s2(捕獲前進)
這里在s1上發現了捕獲注冊事件,則輸出**“s1 捕獲事件”**
到達s2,已經到達目的節點,
s2上注冊了冒泡和捕獲事件,先注冊的冒泡后注冊的捕獲,則先執行冒泡,輸出**“s2 冒泡事件”**
再在s2上執行后注冊的事件,即捕獲事件,輸出**“s2 捕獲事件”**
下面進入冒泡階段,按照s2->s1->body->html->documen(冒泡前進)
在s1上發現了冒泡事件,則輸出**“s1 冒泡事件”**
防止冒泡和捕獲
w3c的方法是e.stopPropagation(),IE則是使用e.cancelBubble = true·
stopPropagation也是事件對象(Event)的一個方法,作用是阻止目標元素的冒泡事件,但是會不阻止默認行為。
取消默認事件
w3c的方法是e.preventDefault(),IE則是使用e.returnValue = false;·
preventDefault它是事件對象(Event)的一個方法,作用是取消一個目標元素的默認行為。既然是說默認行為,當然是元素必須有默認行為才能被取消,如果元素本身就沒有默認行為,調用當然就無效了。
return false
javascript的return false只會阻止默認行為,而是用jQuery的話則既阻止默認行為又防止對象冒泡。
IE瀏覽器兼容
IE瀏覽器對addEventListener兼容性并不算太好,只有IE9以上可以使用。
要兼容舊版本的IE瀏覽器,可以使用IE的attachEvent函數
object.attachEvent(event, function)
兩個參數與addEventListener相似,分別是事件和處理函數,默認是事件冒泡階段調用處理函數,要注意的是,寫事件名時候要加上"on"前綴(“onload”、"onclick"等)。
53、事件對象
什么是事件對象?
就是當你觸發了一個事件以后,對該事件的一些描述信息
每一個事件都會有一個對應的對象來描述這些信息,我們就把這個對象叫做 事件對象
瀏覽器給了我們一個 黑盒子,叫做 window.event ,就是對事件信息的所有描述
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
是相對于瀏覽器窗口來計算的,從瀏覽器可視區域左上角開始,即是以瀏覽器滑動條此刻的滑動到的位置為參考點,隨滑動條移動 而變化
var box = document.querySelector('.box')box.onclick = function (e) {//根據你瀏覽器的窗口來計算的,Y軸不包含導航地址欄和標簽欄這些console.log(e.clientY)}
pageX 和 pageY
?是相對于整個頁面的坐標點,不管有沒有滾動,都是相對于頁面拿到的坐標點 從頁面左上角開始,即是以頁面為參考點,不隨滑動條移動而變化
var box = document.querySelector('.box')box.onclick = function (e) {//根據你瀏覽器的窗口來計算的,Y軸不包含導航地址欄和標簽欄這些console.log(e.pageY)console.log(e.pageX)}
移動端ontouchstart等
ontouchstart、ontouchend、onclick這三個方法的執行順序是ontouchstart > ontouchend > onclick
除了執行順序不同以外,還有一個非常大的區別那就是onclick只在你快速點擊并放開才會被執行,如果你點擊一個區域,很遲才放開,那么onclick是不會執行的
事件綁定
事件綁定的兩種方法
-
DOM0級事件綁定
curEle.onclick=function(){}
;
-
DOM2級事件綁定
-
標準瀏覽器:
curEle.addEventListener('click',function(){},false)
-
IE6-8:
curEle.attachEvent('onclick',function(){})
-
DOM0于DOM2事件綁定的區別
DOM0事件綁定的原理
給當前元素的某一私有屬性(onXXX)賦值的過程
;(之前屬性默認值是null,如果我們賦值了一個函數,就相當于綁定了一個方法)- 當我們賦值成功(賦值一個函數),此時瀏覽器會把DOM元素和賦值的的函數建立關聯,以及建立DOM元素的行為監聽,當某一行為被用戶觸發,瀏覽器會把賦值的函數執行;
DOM0事件綁定的特點
:
- 只有DOM元素天生擁有這個私有屬性(onxxx事件私有屬性),我們賦值的方法才叫事件綁定,否則屬于設置自定義屬性
- 移除事件綁定的時候,我們只需要賦值為null;
- 在DOM0事件綁定中,只能給當前元素的某一個事件行為綁定一個方法,綁定多個方法,最后一次的綁定的會替換前面綁定的
DOM2事件綁定的原理
-
DOM2事件綁定使用的
addEventListener/attachEvent方法都是在eventTarget這個內置類的原型上定義的
,我們調用的時候,首先要通過原型鏈找到這個方法,然后執行完成事件綁定的效果 -
瀏覽器會給當前元素的某個事件行為開辟一個事件池(事件隊列)【瀏覽器有一個統一的事件池,每個元素綁定的行為都放在這里,通過相關標志區分】,當我們
通過 addEventListener/attachEvent進行事件綁定的時候,會把綁定的方法放在事件池中
; -
當元素的某一行為被觸發,瀏覽器回到對應事件池中,把當前放在事件池的所有方法按序依次執行
特點
-
所有DOM0支持的行為,DOM2都可以用,DOM2還支持DOM0沒有的事件行為(這樣說比較籠統)
(核心)【瀏覽器會把一些常用事件掛載到元素對象的私有屬性上,讓我們可以實現DOM0事件綁定,DOM2:凡是瀏覽器給元素天生設置的事件在DOM2中都可以使用】
例如:onDOMContentLoaded
(所有的DOM0和IE6-8的DOM2都不支持)onDOMContentLoaded//當前瀏覽器中的DOM結構加載完成,就會觸發這個事件
-
DOM2中可以給
當前元素的某一事件行為綁定多個不同方法
(因為綁定的所有方法都放在事件池中); -
事件的移除:
事件類型、綁定的方法、傳播階段三個完全一致,才可以完成移除
(因此在綁定方法時,盡量不要用匿名函數,否則不好移除)DOM0于DOM2事件綁定的區別DOM0事件綁定的原理
給當前元素的某一私有屬性(onXXX)賦值的過程
;(之前屬性默認值是null,如果我們賦值了一個函數,就相當于綁定了一個方法)- 當我們賦值成功(賦值一個函數),此時瀏覽器會把DOM元素和賦值的的函數建立關聯,以及建立DOM元素的行為監聽,當某一行為被用戶觸發,瀏覽器會把賦值的函數執行;
DOM0事件綁定的特點
:- 只有DOM元素天生擁有這個私有屬性(onxxx事件私有屬性),我們賦值的方法才叫事件綁定,否則屬于設置自定義屬性
- 移除事件綁定的時候,我們只需要賦值為null;
- 在DOM0事件綁定中,只能給當前元素的某一個事件行為綁定一個方法,綁定多個方法,最后一次的綁定的會替換前面綁定的
DOM2事件綁定的原理
-
DOM2事件綁定使用的
addEventListener/attachEvent方法都是在eventTarget這個內置類的原型上定義的
,我們調用的時候,首先要通過原型鏈找到這個方法,然后執行完成事件綁定的效果 -
瀏覽器會給當前元素的某個事件行為開辟一個事件池(事件隊列)【瀏覽器有一個統一的事件池,每個元素綁定的行為都放在這里,通過相關標志區分】,當我們
通過 addEventListener/attachEvent進行事件綁定的時候,會把綁定的方法放在事件池中
; -
當元素的某一行為被觸發,瀏覽器回到對應事件池中,把當前放在事件池的所有方法按序依次執行
特點
-
所有DOM0支持的行為,DOM2都可以用,DOM2還支持DOM0沒有的事件行為(這樣說比較籠統)
(核心)【瀏覽器會把一些常用事件掛載到元素對象的私有屬性上,讓我們可以實現DOM0事件綁定,DOM2:凡是瀏覽器給元素天生設置的事件在DOM2中都可以使用】
例如:onDOMContentLoaded
(所有的DOM0和IE6-8的DOM2都不支持)onDOMContentLoaded//當前瀏覽器中的DOM結構加載完成,就會觸發這個事件
-
DOM2中可以給
當前元素的某一事件行為綁定多個不同方法
(因為綁定的所有方法都放在事件池中); -
事件的移除:
事件類型、綁定的方法、傳播階段三個完全一致,才可以完成移除
(因此在綁定方法時,盡量不要用匿名函數,否則不好移除)
DOMContentLoaded和load的區別
- DOMContentLoaded
當初始的 HTML 文檔被完全加載和解析完成之后,DOMContentLoaded 事件被觸發,而無需等待樣式表、圖像和子框架的完成加載。
- load
load 僅用于檢測一個完全加載的頁面,頁面的html、css、js、圖片等資源都已經加載完之后才會觸發 load 事件。
54、函數防抖和節流
防抖(debounce)
所謂防抖,就是指觸發事件后在 n 秒內函數只能執行一次,如果在 n 秒內又觸發了事件,則會重新計算函數執行時間。
防抖函數分為非立即執行版和立即執行版。
非立即執行版:
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);}
}
立即執行版:
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)}
}
節流(throttle)
**所謂節流,就是指連續觸發事件但是在 n 秒中只執行一次函數。**節流會稀釋函數的執行頻率。
對于節流,一般有兩種方式可以實現,分別是時間戳版和定時器版。
時間戳版:
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;}}
}
定時器版:
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)}}
}
結合應用場景
防抖(debounce)
search搜索聯想,用戶在不斷輸入值時,用防抖來節約請求資源。
window觸發resize的時候,不斷的調整瀏覽器窗口大小會不斷的觸發這個事件,用防抖來讓其只觸發一次
節流(throttle)
鼠標不斷點擊觸發,mousedown(單位時間內只觸發一次)
監聽滾動事件,比如是否滑到底部自動加載更多,用throttle來判斷
55、實現兩端固定,中間自適應的布局
1).絕對定位法
絕對定位法原理是將左右兩邊使用absolute定位,因為絕對定位使其脫離文檔流,后面的center會自然流動到他們上面,然后使用margin屬性,留出左右元素的寬度,既可以使中間元素自適應屏幕寬度。
代碼如下:
<!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>實現三列寬度自適應布局</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).使用自身浮動法
<h3>使用自身浮動法定位</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,設置為display:flex;中間設置flex:1;但是盒模型默認緊緊挨著,可以使用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>
其中有一點要注意的是margin、padding、left等設為百分比時是相對父元素的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實現觀察者模式
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的路由方法實現
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))
}// 用個例子試用一下
var router = new Router()
router.route('/', function () {var body = document.getElementById('page-type')body.innerHTML = '首頁耶耶耶'
})
router.route('/news', function () {var body = document.getElementById('page-type')body.innerHTML = '新聞耶耶耶'
})
// 這里用history的路由方法實現
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))
}// 用個例子試用一下
var router = new Router()
router.route('/', function () {var body = document.getElementById('page-type')body.innerHTML = '首頁耶耶耶'
})
router.route('/news', function () {var body = document.getElementById('page-type')body.innerHTML = '新聞耶耶耶'
})
57、Object.getPrototypeOf() 方法用于獲取指定對象的原型對象(也就是__protp__的指向)
59、window中各類高度
網頁可見區域高:document.body.clientHeight
網頁正文全文高:document.body.scrollHeight
網頁可見區域高(包括邊線的高):document.body.offsetHeight
網頁被卷去的高:document.body.scrollTop
屏幕分辨率高:window.screen.height
每個HTML元素都具有clientHeight offsetHeight scrollHeight offsetTop scrollTop 這5個和元素高度、滾動、位置相關的屬性,單憑單詞很難搞清楚分別代表什么意思之間有什么區別。通過閱讀它們的文檔總結出規律如下:
clientHeight和offsetHeight屬性和元素的滾動、位置沒有關系它代表元素的高度,其中:
**clientHeight:**包括padding但不包括border、水平滾動條、margin的元素的高度。對于inline的元素這個屬性一直是0,單位px,只讀元素。
**offsetHeight:**包括padding、border、水平滾動條,但不包括margin的元素的高度。對于inline的元素這個屬性一直是0,單位px,只讀元素。
接下來討論出現有滾動條時的情況:
當本元素的子元素比本元素高且overflow=scroll時,本元素會scroll,這時:
scrollHeight: 因為子元素比父元素高,父元素不想被子元素撐的一樣高就顯示出了滾動條,在滾動的過程中本元素有部分被隱藏了,scrollHeight代表包括當前不可見部分的元素的高度。而可見部分的高度其實就是clientHeight,也就是scrollHeight>=clientHeight恒成立。在有滾動條時討論scrollHeight才有意義,在沒有滾動條時scrollHeight==clientHeight恒成立。單位px,只讀元素。
scrollTop: 代表在有滾動條時,滾動條向下滾動的距離也就是元素頂部被遮住部分的高度。在沒有滾動條時scrollTop==0恒成立。單位px,可讀可設置。
offsetTop: 當前元素頂部距離最近父元素頂部的距離,和有沒有滾動條沒有關系。單位px,只讀元素。
60、實現懶加載和預加載
<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標簽列表var imgs = document.querySelectorAll('img');// 獲取到瀏覽器頂部的距離function getTop(e){return e.offsetTop;}// 懶加載實現function lazyload(imgs){// 可視區域高度var h = window.innerHeight;//滾動區域高度var s = document.documentElement.scrollTop || document.body.scrollTop;for(var i=0;i<imgs.length;i++){//圖片距離頂部的距離大于可視區域和滾動區域之和時懶加載if ((h+s)>getTop(imgs[i])) {// 真實情況是頁面開始有2秒空白,所以使用setTimeout定時2s(function(i){setTimeout(function(){// 不加立即執行函數i會等于9// 隱形加載圖片或其他資源,//創建一個臨時圖片,這個圖片在內存中不會到頁面上去。實現隱形加載var temp = new Image();temp.src = imgs[i].getAttribute('data-src');//只會請求一次// onload判斷圖片加載完畢,真是圖片加載完畢,再賦值給dom節點temp.onload = function(){// 獲取自定義屬性data-src,用真圖片替換假圖片imgs[i].src = imgs[i].getAttribute('data-src')}},2000)})(i)}}}lazyload(imgs);// 滾屏函數window.onscroll =function(){lazyload(imgs);}
}
</script>
實現預加載的幾種辦法
- 使用HTML標簽
<img src="http://pic26.nipic.com/20121213/6168183 0044449030002.jpg" style="display:none"/>
1
- 使用Image對象
<script src="./myPreload.js"></script>
1//myPreload.js文件var image= new Image()image.src="http://pic26.nipic.com/20121213/6168183 004444903000 2.jpg"
61、編寫一個求和函數sum,使輸入sum(2)(3)或輸入sum(2,3),輸出結果都為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是一個W3C標準,全稱是"跨域資源共享"(Cross-origin resource sharing)。
它允許瀏覽器向跨源服務器,發出XMLHttpRequest
請求,從而克服了AJAX只能同源使用的限制。
CORS需要瀏覽器和服務器同時支持。目前,所有瀏覽器都支持該功能,IE瀏覽器不能低于IE10。
整個CORS通信過程,都是瀏覽器自動完成,不需要用戶參與。對于開發者來說,CORS通信與同源的AJAX通信沒有差別,代碼完全一樣。瀏覽器一旦發現AJAX請求跨源,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感覺。
因此,實現CORS通信的關鍵是服務器。只要服務器實現了CORS接口,就可以跨源通信。
瀏覽器將CORS請求分成兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。
只要同時滿足以下兩大條件,就屬于簡單請求。
(1) 請求方法是以下三種方法之一:
- HEAD
- GET
- POST
(2)HTTP的頭信息不超出以下幾種字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三個值
application/x-www-form-urlencoded
、multipart/form-data
、text/plain
簡單請求
基本流程
對于簡單請求,瀏覽器直接發出CORS請求。具體來說,就是在頭信息之中,增加一個Origin
字段。
下面是一個例子,瀏覽器發現這次跨源AJAX請求是簡單請求,就自動在頭信息之中,添加一個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
字段用來說明,本次請求來自哪個源(協議 + 域名 + 端口)。服務器根據這個值,決定是否同意這次請求。
如果Origin
指定的源,不在許可范圍內,服務器會返回一個正常的HTTP回應。瀏覽器發現,這個回應的頭信息沒有包含Access-Control-Allow-Origin
字段(詳見下文),就知道出錯了,從而拋出一個錯誤,被XMLHttpRequest
的onerror
回調函數捕獲。注意,這種錯誤無法通過狀態碼識別,因為HTTP回應的狀態碼有可能是200。
如果Origin
指定的域名在許可范圍內,服務器返回的響應,會多出幾個頭信息字段。
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
上面的頭信息之中,有三個與CORS請求相關的字段,都以Access-Control-
開頭。
(1)Access-Control-Allow-Origin
該字段是必須的。它的值要么是請求時Origin
字段的值,要么是一個*
,表示接受任意域名的請求。
(2)Access-Control-Allow-Credentials
該字段可選。它的值是一個布爾值,表示是否允許發送Cookie。默認情況下,Cookie不包括在CORS請求之中。設為true
,即表示服務器明確許可,Cookie可以包含在請求中,一起發給服務器。這個值也只能設為true
,如果服務器不要瀏覽器發送Cookie,刪除該字段即可。
(3)Access-Control-Expose-Headers
該字段可選。CORS請求時,XMLHttpRequest
對象的getResponseHeader()
方法只能拿到6個基本字段:Cache-Control
、Content-Language
、Content-Type
、Expires
、Last-Modified
、Pragma
。如果想拿到其他字段,就必須在Access-Control-Expose-Headers
里面指定。上面的例子指定,getResponseHeader('FooBar')
可以返回FooBar
字段的值。
withCredentials 屬性
上面說到,CORS請求默認不發送Cookie和HTTP認證信息。如果要把Cookie發到服務器,一方面要服務器同意,指定Access-Control-Allow-Credentials
字段。
Access-Control-Allow-Credentials: true
另一方面,開發者必須在AJAX請求中打開withCredentials
屬性。
var xhr = new XMLHttpRequest(); xhr.withCredentials = true;
否則,即使服務器同意發送Cookie,瀏覽器也不會發送。或者,服務器要求設置Cookie,瀏覽器也不會處理。
但是,如果省略withCredentials
設置,有的瀏覽器還是會一起發送Cookie。這時,可以顯式關閉withCredentials
。
xhr.withCredentials = false;
需要注意的是,如果要發送Cookie,Access-Control-Allow-Origin
就不能設為星號,必須指定明確的、與請求網頁一致的域名。同時,Cookie依然遵循同源政策,只有用服務器域名設置的Cookie才會上傳,其他域名的Cookie并不會上傳,且(跨源)原網頁代碼中的document.cookie
也無法讀取服務器域名下的Cookie。
非簡單請求
預檢請求
非簡單請求是那種對服務器有特殊要求的請求,比如請求方法是PUT
或DELETE
,或者Content-Type
字段的類型是application/json
。
非簡單請求的CORS請求,會在正式通信之前,增加一次HTTP查詢請求,稱為"預檢"請求(preflight)。
瀏覽器先詢問服務器,當前網頁所在的域名是否在服務器的許可名單之中,以及可以使用哪些HTTP動詞和頭信息字段。只有得到肯定答復,瀏覽器才會發出正式的XMLHttpRequest
請求,否則就報錯。
下面是一段瀏覽器的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請求的方法是PUT
,并且發送一個自定義頭信息X-Custom-Header
。
瀏覽器發現,這是一個非簡單請求,就自動發出一個"預檢"請求,要求服務器確認可以這樣請求。下面是這個"預檢"請求的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...
"預檢"請求用的請求方法是OPTIONS
,表示這個請求是用來詢問的。頭信息里面,關鍵字段是Origin
,表示請求來自哪個源。
除了Origin
字段,"預檢"請求的頭信息包括兩個特殊字段。
(1)Access-Control-Request-Method
該字段是必須的,用來列出瀏覽器的CORS請求會用到哪些HTTP方法,上例是PUT
。
(2)Access-Control-Request-Headers
該字段是一個逗號分隔的字符串,指定瀏覽器CORS請求會額外發送的頭信息字段,上例是X-Custom-Header
。
預檢請求的回應
服務器收到"預檢"請求以后,檢查了Origin
、Access-Control-Request-Method
和Access-Control-Request-Headers
字段以后,確認允許跨源請求,就可以做出回應。
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回應中,關鍵的是Access-Control-Allow-Origin
字段,表示http://api.bob.com
可以請求數據。該字段也可以設為星號,表示同意任意跨源請求。
Access-Control-Allow-Origin: *
如果服務器否定了"預檢"請求,會返回一個正常的HTTP回應,但是沒有任何CORS相關的頭信息字段。這時,瀏覽器就會認定,服務器不同意預檢請求,因此觸發一個錯誤,被XMLHttpRequest
對象的onerror
回調函數捕獲。控制臺會打印出如下的報錯信息。
XMLHttpRequest cannot load http://api.alice.com. Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.
服務器回應的其他CORS相關字段如下。
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
該字段必需,它的值是逗號分隔的一個字符串,表明服務器支持的所有跨域請求的方法。注意,返回的是所有支持的方法,而不單是瀏覽器請求的那個方法。這是為了避免多次"預檢"請求。
(2)Access-Control-Allow-Headers
如果瀏覽器請求包括Access-Control-Request-Headers
字段,則Access-Control-Allow-Headers
字段是必需的。它也是一個逗號分隔的字符串,表明服務器支持的所有頭信息字段,不限于瀏覽器在"預檢"中請求的字段。
(3)Access-Control-Allow-Credentials
該字段與簡單請求時的含義相同。
(4)Access-Control-Max-Age
該字段可選,用來指定本次預檢請求的有效期,單位為秒。上面結果中,有效期是20天(1728000秒),即允許緩存該條回應1728000秒(即20天),在此期間,不用發出另一條預檢請求。
4.3 瀏覽器的正常請求和回應
一旦服務器通過了"預檢"請求,以后每次瀏覽器正常的CORS請求,就都跟簡單請求一樣,會有一個Origin
頭信息字段。服務器的回應,也都會有一個Access-Control-Allow-Origin
頭信息字段。
下面是"預檢"請求之后,瀏覽器的正常CORS請求。
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
字段是瀏覽器自動添加的。
下面是服務器正常的回應。
Access-Control-Allow-Origin: http://api.bob.com Content-Type: text/html; charset=utf-8
上面頭信息中,Access-Control-Allow-Origin
字段是每次回應都必定包含的。
63、為什么在項目中data需要使用return返回數據呢?
組件是一個可復用的實例,當你引用一個組件的時候,組件里的data是一個普通的對象,所有用到這個組件的都引用的同一個data,就會造成數據污染。
不使用return包裹的數據會在項目的全局可見,會造成變量污染;使用return包裹后數據中變量只在當前組件中生效,不會影響其他組件。
data必須是函數的原因
當一個組件被定義, data 必須聲明為返回一個初始數據對象的函數,因為組件可能被用來創建多個實例。如果 data 仍然是一個純粹的對象,則所有的實例將共享引用同一個數據對象!通過提供 data 函數,每次創建一個新實例后,我們能夠調用 data 函數,從而返回初始數據的一個全新副本數據對象。
因為在JS 中只有函數才存在作用域,data是一個函數時,每個組件實例都有自己的作用域,每個實例相互獨立,不會相互影響!
64、微信小程序的更新機制 && 如何讓微信用戶更新小程序
啟動
小程序啟動會有兩種情況,一種是「冷啟動」,一種是「熱啟動」。 假如用戶已經打開過某小程序,然后在一定時間內再次打開該小程序,此時無需重新啟動,只需將后臺態的小程序切換到前臺,這個過程就是熱啟動;冷啟動指的是用戶首次打開或小程序被微信主動銷毀后再次打開的情況,此時小程序需要重新加載啟動。
更新機制
小程序冷啟動時如果發現有新版本,將會異步下載新版本的代碼包,并同時用客戶端本地的包進行啟動,即新版本的小程序需要等下一次冷啟動才會應用上。 如果需要馬上應用最新版本,可以使用 wx.getUpdateManager API 進行處理。
運行機制
- 小程序沒有重啟的概念
- 當小程序進入后臺,客戶端會維持一段時間的運行狀態,超過一定時間后(目前是5分鐘)會被微信主動銷毀
- 當短時間內(5s)連續收到兩次以上收到系統內存告警,會進行小程序的銷毀
小程序重新初始化時會觸發onLaunch事件. onLaunch事件會觸發在頁面onShow事件之前.獲取小程序更新版本可以寫在onLaunch里.
// 在app.js里寫下以下代碼onLaunch () {if (wx.canIUse('getUpdateManager')) {const updateManager = wx.getUpdateManager()updateManager.onCheckForUpdate(function (res) {console.log('onCheckForUpdate====', res)// 請求完新版本信息的回調if (res.hasUpdate) {console.log('res.hasUpdate====')updateManager.onUpdateReady(function () {wx.showModal({title: '更新提示',content: '新版本已經準備好,是否重啟應用?',success: function (res) {console.log('success====', res)// res: {errMsg: "showModal: ok", cancel: false, confirm: true}if (res.confirm) {// 新的版本已經下載好,調用 applyUpdate 應用新版本并重啟updateManager.applyUpdate()}}})})updateManager.onUpdateFailed(function () {// 新的版本下載失敗wx.showModal({title: '已經有新版本了喲~',content: '新版本已經上線啦~,請您刪除當前小程序,重新搜索打開喲~'})})}})}}
65、構造函數&&原型
構造函數
創建一個構造函數,專門用來創建Person對象的
? 構造函數就是一個普通的函數,創建方式和普通函數沒有區別,
? 不同的是構造函數習慣上首字母大寫
構造函數和普通函數的區別就是調用方式的不同
普通函數是直接調用,而構造函數需要使用new關鍵字來調用
構造函數的執行流程:
1.立刻創建一個新的對象
2.將新建的對象設置為函數中this,在構造函數中可以使用this來引用新建的對象
3.逐行執行函數中的代碼
4.將新建的對象作為返回值返回
使用同一個構造函數創建的對象,我們稱為一類對象,也將一個構造函數稱為一個類。
我們將通過一個構造函數創建的對象,稱為是該類的實例
this的情況:
1.當以函數的形式調用時,this是window
2.當以方法的形式調用時,誰調用方法this就是誰
3.當以構造函數的形式調用時,this就是新創建的那個對象
如果方法是在構造函數內部創建的, 也就是構造函數每執行一次就會創建一個新的方法,也就是所有實例的方法都是唯一的。這樣就導致了構造函數執行一次就會創建一個新的方法,執行10000次就會創建10000個新的方法,而10000個方法都是一摸一樣的。這是完全沒有必要,完全可以使所有的對象共享同一個方法,這就利用了prototype
prototype和_proto_
Javascript中所有的對象都是Object的實例,并繼承Object.prototype的屬性和方法,也就是說,Object.prototype是所有對象的爸爸。
在對象創建時,就會有一些預定義的屬性,其中定義函數的時候,這個預定義屬性就是prototype,這個prototype是一個普通的對象。
而定義普通的對象的時候,就會生成一個__proto__,這個__proto__指向的是這個對象的構造函數的prototype.
我們所創建的每一個函數,解析器都會向函數中添加一個屬性prototype
這個屬性對應著一個對象,這個對象就是我們所謂的原型對象
如果函數作為普通函數調用prototype沒有任何作用
當函數以構造函數的形式調用時,它所創建的對象中都會有一個隱含的屬性,
指向該構造函數的原型對象,我們可以通過__proto__來訪問該屬性
原型對象就相當于一個公共的區域,所有同一個類的實例都可以訪問到這個原型對象,
我們可以將對象中共有的內容,統一設置到原型對象中。
當我們訪問對象的一個屬性或方法時,它會先在對象自身中尋找,如果有則直接使用,
? 如果沒有則會去原型對象中尋找,如果找到則直接使用
? 以后我們創建構造函數時,可以將這些對象共有的屬性和方法,統一添加到構造函數的原型對象中,
? 這樣不用分別為每一個對象添加,也不會影響到全局作用域,就可以使每個對象都具有這些屬性和方法了
66、js中實例方法、靜態方法和原型方法
實例方法
構造函數中this上添加的成員 ,在Cat構造方法里面,定義在this中的變量和方法,只有實例才能訪問到:如this.name,this.move,this.eat這些都是實例擁有,無法通過Cat直接調用。
function Cat(name){this.name = namethis.move = function() {console.log('移動')}this.eat = function() {console.log(`${this.name}愛吃魚`)}
}
Cat.eat()
let cat=new Cat();
cat.eat() //tom愛吃魚 //這是實例方法
靜態方法
構造函數本身上添加的成員
下面的Cat.eat就是構造函數的靜態方法,不能通過實例調用
function Cat(name){this.move = function() {console.log(1)}
}
//直接定義在Cat構造函數中,實例不能調用
Cat.eat = function() {console.log(`${this.name}愛吃魚`)}
構造函數調用
Cat.eat() //Cat愛吃魚
Cat.move() //Cat.move is not a function
let cat = new Cat()
cat.eat() //cat.eat is not a function
原型方法
原型中的方法實例和構造函數都可以訪問到
function Cat() {
}
Cat.eat = function() {console.log('靜態方法')
}
Cat.prototype.eat = function() {console.log('原型方法')
}
let cat = new Cat()
Cat.eat() //靜態方法
Cat.prototype.eat() //原型方法,不用prototype就是打印靜態方法cat.eat() //原型方法
簡而言之,實例方法就是只有實例可以調用,靜態方法只有構造函數可以調用,原型方法是實例和構造函數都可以調用,是共享的方法。
像Promise.all和Promise.race這些就是靜態方法,Promise.prototype.then這些就是原型方法,new 出來的實例可以調用
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
此時person的name和age在man的prototype,但原型方法仍然在person的prototype,因為name和age是實例屬性,而say是實例方法
2、利用構造函數繼承
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
優點:
- 解決了原型鏈繼承中的子類共享父類屬性的問題
- 創建的子類實例可以向父類傳遞參數
- 可以實現多繼承,call改變父類的this
缺點:
- 實例是子類的實例,不是父類的
- 只能繼承父類的實例屬性和方法,不能繼承父類原型上的方法
- 無法實現函數復用,每個子類都有父類函數的屬性和方法的副本,當child調用Parent上的方法時,Parent內部的this指向的是child,Parent內部的this上的屬性和方法都被復制到了child上面,如果每個子類的實例都復制一遍父類的屬性和方法,就會占用很大的內存,而且當父類的方法發生改變了時,已經創建好的子類實例并不能更新方法,因為已經復制了原來的父類方法當成自己的方法了。
3、組合繼承(原型鏈繼承與構造繼承)
function Child(name) {Parent.call(this,name) //構造繼承 ,第二次調用父類
}
//原型鏈繼承
Child.prototype=new Parent()
Child.prototype.constructor=Child//因重寫原型而失去constructor屬性,所以要對constrcutor重新賦值var child=new Child("yzh") //子類的實例向父類傳遞參數,第一次調用父類
console.log(child.name)
child.introduce()
child.hobby("sing")
console.log(child instanceof Parent) //true
console.log(child instanceof Child) //true
優點:結合了原型鏈繼承和構造繼承的優點
- 子類可向父類傳參
- 實例既是子類的實例,也是父類的實例
- 多個實例之間不存在公用父類的引用屬性的問題
- 實例可以繼承父類實例的屬性和方法,也可以繼承原型上的屬性和方法
缺點:這種方式調用了兩次父類的構造函數,生成了兩份實例,相同的屬性既存在于實例中也存在于原型中
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);
其實寄生組合繼承和上面的組合繼承區別僅在于構造子類原型對象的方式上(a.和b.
),這里用到了Object.creat(obj)
方法,該方法會對傳入的obj對象進行淺拷貝,類似于:
function create(obj){function T(){};T.prototype = obj;return new T();
}
組合繼承(構造函數和原型的組合)會調用兩次父類構造函數的代碼,因此引入寄生組合式繼承,即通過借用構造函數來繼承屬性,通過原型鏈的方式來繼承方法,而不需要為子類指定原型而調用父類的構造函數,我們需要拿到的僅僅是父類原型的一個副本。因此可以通過傳入子類和父類的構造函數作為參數,首先創建父類原型的一個復本,并為其添加constrcutor,最后賦給子類的原型。這樣避免了調用兩次父類的構造函數,為其創建多余的屬性。
68、for in 和for of的區別
for in通常用來遍歷對象
for in 可以遍歷到該對象的原型方法method,如果不想遍歷原型方法和屬性的話,可以在循環內部判斷一下,hasOwnPropery方法可以判斷某屬性是否是該對象的實例屬性。
同樣可以通過ES5的Object.keys(myObject)獲取對象的實例屬性組成的數組,不包括原型方法和屬性。
for of通常用來遍歷數組
for in遍歷的是數組的索引(即鍵名),而for of遍歷的是數組元素值。
for of遍歷的只是數組內的元素,而不包括數組的原型屬性method和索引name
for…of適用遍歷數/數組對象/字符串/map/set等擁有迭代器對象的集合.但是不能遍歷對象,因為沒有迭代器對象.與forEach()不同的是,它可以正確響應break、continue和return語句.使用foreach遍歷數組的話,使用break不能中斷循環,使用return也不能返回到外層函數。
69、內置對象/宿主對象/自定義對象的區別?
1、內置對象:系統所提供的對象;如Object、Array、Math、Date等等。
2、宿主對象:JS所運行的環境提供的對象比如:BOM中的Window、DOM中的document。
3、自定義對象:自定義構造函數所創建的對象。
71、判斷一個對象是否存在
現在,我們要判斷一個全局對象myObj是否存在,如果不存在,就對它進行聲明。
if (!myObj) {var myObj = { };}
if (!window.myObj) {var myObj = { };}
上面這種寫法的缺點在于,在某些運行環境中(比如V8、Rhino),window未必是頂層對象。所以,考慮改寫成:
if (!this.myObj) {this.myObj = { };}
還可以使用typeof運算符,判斷myObj是否有定義。
if (typeof myObj == "undefined") {var myObj = { };}
這是目前使用最廣泛的判斷javascript對象是否存在的方法。
由于在已定義、但未賦值的情況下,myObj的值直接等于undefined,所以上面的寫法可以簡化:
if (myObj == undefined) {var myObj = { };}
上面的寫法在"精確比較"(===)的情況下,依然成立:
根據javascript的語言設計,undefined == null,所以比較myObj是否等于null,也能得到正確結果:
if (myObj == null) {var myObj = { };}
還可以使用in運算符,判斷myObj是否為頂層對象的一個屬性:
if (!('myObj' in window)) {window.myObj = { };}
最后,使用hasOwnProperty方法,判斷myObj是否為頂層對象的一個屬性:
if (!this.hasOwnProperty('myObj')) {this.myObj = { };}
72、Object.create(null) 和 {} 區別
Object.create(null)
沒有繼承任何原型方法,也就是說它的原型鏈沒有上一層。即沒有_proto_