HTML面試題
- 你是如何理解HTML語義化的?
- 比較簡單的回答:我理解的語義化就是 標簽用在合適的位置,比如段落要用
p
標簽,標題要用h1-h6
標簽. - 更細點的回答:我理解的HTML語義化是正確的標簽做正確的事情,能夠便于開發者閱讀和寫出更優雅的代碼的同時,讓網絡爬蟲更好的解析。
- 為什么要做到語義化?
- 有利于SEO,有利于搜索引擎爬蟲更好的解析我們的頁面,從而獲取更多的有效信息,提升網頁的權重。
- 在沒有CSS的時候,能夠清晰看出網頁的結構,增強可讀性。
- 便于團隊合作開發和維護,提高開發效率
<!DOCTYPE> 文檔聲明,它不是HTML標簽,是一個指示web瀏覽關于頁面使用哪個HTML版本編寫的指令。<!DOCTYPE> 聲明必須位于文檔的第一行,位于<html>標簽之前。
<!DOCTYPE html>
-
<html lang='en'>lang屬性設定文檔語言。
作用:SEO搜索引擎優化;有助于閱讀障礙人士,通過讀屏器閱讀頁面
還可以是
<html lang="zh-CN">
5.meta標簽的幾種用法。
- meta指定文檔編碼
//這行代碼的意思是,文檔用UTF-8編碼的,瀏覽器解析的時候用UTF-8編碼解析。
<meta charset="UTF-8">
- 適配移動頁面
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
- 添加頁面描述
<meta name="description" content="騰訊網(www.qq.com)是中國最瀏覽量最大的門戶網站">
6.你用過哪些 HTML5標簽。
內容性的標簽:
<header> 網頁的頭部
<nav> 網頁的導航
<section> 標簽定義文檔中的節(比如章節、頁眉、頁腳或文檔中的其他部分。)
<article> 標簽的內容獨立于文檔的其余部分。比如外部的一篇文章,一個博客,論文等。
<aside> 網頁側邊欄
<footer> 網頁的頁腳
功能性的標簽
<canvas> 通過腳本繪制圖像
<Audio> 播放音頻
<Video> 播放視頻
- 什么是H5?
H5是中國人制造的一個專有名詞
實際上是指移動端頁面,從某種意義上來說它是 HTML5,微信網頁,移動PPT的母級。
CSS面試題
- 兩種盒模型。
盒模型一共有兩種模式:
- 標準盒模型
標準盒模型下 盒子的總寬度/高度=width/height+padding+border+margin,標準盒模型下 width是指content的寬度 - 怪異盒模型
怪異盒模型下 盒子的總寬度/高度=width/height+margin,怪異盒模型下 width 指的是內容content+padding+border的寬度------IE6,7,8 不設置 <!DOCTYPE html> 情況下,是怪異盒模型,如果加了就是標準盒模型 - 具體是使用哪一種盒模型,用box-sizing來設置
- 兼容性: box-sizing現代瀏覽器都支持,但IE家族只有IE8版本以上才支持
box-sizing:content-box;
box-sizing:border-box;
目前使用此屬性,需要加前綴,Mozilla需要加上-moz-,Webkit內核需要加上-webkit-,Presto內核-o-,IE8-ms-
-webkit-box-sizing:content-box;
-moz-box-sizing:birder-box;
標準盒模型的缺點是,盒子的寬度和高度計算復雜(兩子元素并排例子)
怪異盒模型的優點,當確定了width/height之后,可以隨意修改padding和border的厚度值,而不用擔心父容器被撐爆。
- 如何實現水平和垂直居中?
- 元素水平居中
//行內元素
text-align:center
//塊級元素
margin-left: auto;
margin-right:auto;
- 元素垂直居中
//方案1 position-----元素固定寬高的情況下
<div class="out">
out
<div class="in">in</div>
</div>
<style>
.out{
width:300px;
height:300px;
background:#ccc;
color:red;
position:relative;
}
.in{
width:100px;
height:100px;
background:pink;
color:blue;
position:absolute;
left:50%;
top:50%;
margin-left:-50px;
margin-top:-50px;
}
</style>
//方案2 元素寬高不固定的情況下,把上邊的 margin-left:(寬度/2);
margin-top:(高度/2) 換成 transform:translate(-50%,-50%);
<div class="out">
out
<div class="in">測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測試測</div>
</div>
<style>
.out{
width:300px;
height:300px;
background:#ccc;
color:red;
position:relative;
}
.in{
background:pink;
color:blue;
position:absolute;
left:50%;
top:50%;
transform:translate(-50%,-50%);
}
</style>
//方案3 flex
<div class="out">
<div class="in"></div>
</div>
<style>
.out{
width:300px;
height:300px;
background:#ccc;
display:flex; //flex布局
justify-content:center; //規定項目在主軸上居中對齊(水平居中)
align-items:center; //規定項目在交叉軸上居中對齊(垂直居中)
}
.in{
width:100px;
height:100px;
background:pink;
}
</style>
//方案4 table-cell
給父元素設置 display:table; 父元素變成塊級表格元素(相當于table);
給子元素設置 display:table-cell;使子元素變成表格單元格(相當于td),然后設置 vertical-align:center; text-align:center;
<div class="out">
<div class="in">哈哈哈哈哈哈哈哈哈哈</div>
</div>
<style>
.out{
width:300px;
height:300px;
background:#ccc;
display:table;
}
.in{
background:pink;
display:table-cell;
vertical-align:middle;
text-align:center;
}
</style>
- flex怎么用?常用的屬性有哪些?
flex彈性布局,為盒模型提供最大的靈活性,任何一個容器,都可以指定為flex布局;
.box{
display:flex;
}
//行內元素也可以設置為flex
.box{
display:inline-flex;
}
注意設為flex布局后,子元素的float,clear,vertical-align屬性將失效。
采用flex布局的元素,成為flex容器。它的所有子元素自動成為容器成員,稱為flex項目。
常用的,設置到容器上的屬性有:
1.flex-direction:屬性決定主軸的方向(即項目的排列方向)。
.box{
flex-direction:row | row-reverse | column | column-reverse
}
--------------------------------------------------------------------------------
2. flex-wrap:默認情況下,項目都排在一條線上。這個屬性定義,如果一條軸線拍不下,如何換行。
.box{
flex-wrap:nowrap | wrap | wrap-reverse
}
--------------------------------------------------------------------------------
3. justify-content:屬性定義了項目在主軸上的對齊方式。
.box{
justify-content:flex-start | flex-end | center | space-between | space-around
}
--------------------------------------------------------------------------------
4.align-items:屬性定義項目在交叉軸上如何對齊
.box{
align-items:flex-start | flex-end | center | baseline | stretch
}
baseline 項目的第一行文字的基線對齊
stretch(默認值) 如果項目未設置高度或設為auto,將占滿整個容器的高度。
--------------------------------------------------------------------------------
5. align-content:屬性定義了多根軸線的對齊方式。如果項目只有一根軸線,該屬性不起作用。
.box {
align-content: flex-start | flex-end | center | space-between | space-around | stretch;
}
--------------------------------------------------------------------------------
6. flex-flow 是flex-direction和 flex-wrap的簡寫,默認是 row nowrap
設置到項目上的屬性:
1.order 屬性定義項目的排列順序。數值越小,排列越靠前。默認為0
.item {
order: 1;
}
2.flex-grow屬性定義項目的放大比例,默認為0.即如果有剩余空間,也不放大。
.item {
flex-grow: <number>; /* default 0 */
}
3.flex-shrink 屬性定義了項目的縮小比例。默認為1,如果空間不足,項目將縮小。
.item {
flex-shrink: <number>; /* default 1 */
}
如果所有項目的flex-shrink屬性都為1,當空間不足時,都將等比例縮小。如果一個項目的flex-shrink屬性為0,其他項目都為1,則空間不足時,前者不縮小。
負值對該屬性無效。
4. flex-basis 屬性定義了在分配多余空間之前,項目占據的主軸空間。默認為auto.即項目本來的大小。它可以設為跟width或height屬性一樣的值(比如350px),則項目將占據固定空間。
.item {
flex-basis: <length> | auto; /* default auto */
}
5. flex 屬性是flex-grow,flex-shrink,flex-basis,默認值為0 1 auto
該屬性有兩個快捷值:auto (1 1 auto) 和 none (0 0 auto)。
建議優先使用這個屬性,而不是單獨寫三個分離的屬性,因為瀏覽器會推算相關值。
6.align-self 屬性 允許單個項目有與其他項目不一樣的對齊方式,可覆蓋align-items屬性,默認值為auto,表示繼承父元素的align-items屬性,如果沒有父元素,則等同于stretch。
.item {
align-self: auto | flex-start | flex-end | center | baseline | stretch;
}
- BFC是什么?
(Block Formatting Context)塊級格式化上下文。BFC就是頁面上的一個隔離的獨立容器,容器里面的子元素不會影響到外面的元素,反之也如此.并且在一個BFC中,塊盒與行盒(行盒由一行中所有的內聯元素所組成)都會垂直的沿著其父元素的邊框排列。
這個可以具體回答,比如給一個div設置 overflow:hidden 它里邊的浮動元素就會被包裹起來。
如何觸發BFC?
1. 根元素 即HTML元素
2. float的值,不為none
3. overflow的值,不為visible
4. display的值,為inline-block,table-cell ,table-caption
5. position 的值,為absolute 或fixed
- 選擇器的優先級?
CSS2的時候 (如果面試官更傾向于這個就這樣回答)
!important 優先級最高 權值無窮大
style 寫在元素行的內聯樣式其次 權值1000
id 權值100
class/偽類/屬性 權值10
標簽選擇器 權值1
通配符 權值0
更準確的說法
1.越具體,優先級越高
2.寫在后面的,會覆蓋寫在前面的
3.important優先級最高,但是要少用
6.清除浮動的方法:
方法
1:給父元素設置高度
2:給父元素設置overflow:hidden
3: 最佳實踐
.clearfix::after{
content:'';
display:block;
clear:both;
}
.clearfix加到容器上,里邊的子元素浮動就被清除了
原生JS面試題
1. ES6語法知道哪些?分別怎么用
A-------新增聲明命令 let 和 const, let表示變量 const表示常量
特點:
1. 都是塊級作用域,以{}代碼塊作為作用域范圍,只能在代碼塊里面使用。
2. 不存在變量提升,只能先聲明再使用,否則會報錯。在代碼塊內,在聲明變量之前,該變量都是不可用的,這在語法上成為'暫時性死區'
3. 在一個代碼塊內,不允許重復聲明
4. const聲明的是一個只讀常量,在聲明的時候就要賦值,否則會擺錯,(如果const聲明的是一個對象,對象所包含的值是可以修改的,抽象一點說,對象所指的地址是不能夠改變的,變量成員是可以修改的)
B--------模板字符串
用一對反引號(``)標識,它可以當做普通字符串使用,也可以用來定義多行字符串。也可以在字符串中嵌入變量,JS表達式,或函數。需要寫在${}中
var str = `abc
def
gh`;
console.log(str);
let name = '明';
function a(){
return 'ming'
}
console.log(`我的名字叫做${name},年齡${17+5}歲,我的性別是${'男'},游戲ID:${a()})
C------函數的擴展
- 函數的默認參數
ES6為參數提供了默認值,在定義函數的時候,便初始化了這個參數,以便在參數沒有被傳遞進去的時候使用。
function A(a,b=1){
console.log(a+b)
console.log(a);
console.log(b);
}
A(1); // 2, 1,1
A(2,3); //5,2,3
- 箭頭函數
ES6中提供了一種簡潔的函數的寫法,箭頭函數。
//只有一個參數時候,可以省略參數的括號。
//如果沒有參數,或者參數在2個及兩個以上,必須帶括號
//當代碼只有一行,并且有立即返回值的時候,可以省略花括號{}
var f = a => a+1
var sayHi = ()=>{
console.log('hi')
}
var sum = (num1,num2)=>{
console.log(num1+mun2)
}
箭頭函數的特點
箭頭函數的this是在定義函數的時候綁定,而不是在執行函數的時候綁定。
所謂在定義函數的時候綁定的意思是,箭頭函數的this是繼承自父執行上下文。箭頭函數沒有自己的this.
D---------對象的擴展
- 屬性的簡寫
ES6允許在對象之中,直接寫變量,這時屬性名為變量名,屬性的值為變量的值。
var foo = 'bar';
var baz = {foo}; //相當于 var bar={foo:foo}
- 方法的簡寫
省略冒號和function關鍵字
var o = {
sayHi:function(){
console.log('hi')
}
}
相當于
var o = {
sayHi(){
console.log('hi')
}
}
- Object.keys()方法,獲取對象的所有屬性名和方法名。不包括原型的內容,返回一個數組
var person = {name:"john",age:20,study(){alert('study')}};
console.log(Object.keys(person)) // ["name", "age", "study"]
console.log(Object.keys(['aa','bb','cc']); //["0", "1", "2"]
console.log(Object.keys('abcdef')); //["0", "1", "2", "3", "4", "5"]
- Object.assign()方法
這個方法將多個原對象的屬性和方法,合并到了目標對象上面。
可以接收多個參數,第一個參數是目標對象,后邊都是源對象
var target ={}
var obj1 = {
name:'petter',
age:20,
sex:'女'
}
var obj2 = {
sex:'男',
score:100
}
Object.assign(target,obj1,obj2);
console.log(target);
//{age: 20,name: "petter", score: 100, sex: "男"}
E---------- for...of循環
是遍歷所有數據結構的統一方法。for...of循環可以使用的范圍包括數組,Set,Map結構,某寫類數組對象(arguments, DOM NodeList對象)以及字符串。
var arr = ["水星","金星","地球","火星"];
for(var s of arr){
console.log(s); //"水星" "金星" "地球" "火星"
}
F--------import 和 export
ES6標準中,JavaScript 原生支持模塊(module)了,這種將JS代碼分割成不同功能的小塊進行模塊化,將不同功能的代碼分別寫在不同的文件中,各模塊只需要導出公共接口部分,然后通過模塊的導入方式可以在其他地方使用。
export 用于對外輸出本模塊(一個文件可以看做是一個模塊)的變量接口
import 用于在一個模塊中加載另外一個含有export接口的模塊
import和export命令只能在模塊的頂部,不能在代碼塊之中。
G-----Promise對象
Promise是異步編程的一種解決方案,將異步操作以同步操作的流程表達出來,避免了層層嵌套的回調函數。
它有三種狀態,分別是 pending-進行中,resolved-已完成,rejected-已失敗
Promise構造函數接收一個參數,這個參數是函數,并且這個函數傳入兩個參數,分別是 resolve 和 reject,分別是異步操作執行成功后的回調函數,和異步操作執行失敗后的回調函數。(按照標準來講,resolve是將Promise的狀態置為fullfiled,reject是將Promise的狀態置為rejected。)
所以我們用Promise的時候一般是包在一個函數中,在需要的時候去運行這個函數,如:
function runAsync(){
var p = new Promise((resolve,reject)=>{
setTimeout(function(){
console.log('執行完成');
resolve('隨便什么數據')
},2000)
})
return p;
}
runAsync();
在我們包裝好的函數最后,會return出Promise對象,也就是說,執行這個函數我們得到了一個Promise對象。
Promise對象上有then 和 catch方法。
runAsync().then(function(data){
console.log(data) //'隨便什么數據'
//后面可以用傳過來的數據做些其他操作
//......
})
在runAsync()的返回上直接調用then方法,then接收一個參數,是函數,并且會拿到我們在runAsync中調用resolve時傳的的參數。
這時候你應該有所領悟了,原來then里面的函數就跟我們平時的回調函數一個意思,能夠在runAsync這個異步任務執行完成之后被執行。這就是Promise的作用了,簡單來講,就是能把原來的回調寫法分離出來,在異步操作執行完后,用鏈式調用的方式執行回調函數。
而Promise的優勢在于,可以在then方法中繼續寫Promise對象并返回,然后繼續調用then來進行回調操作。
鏈式操作的用法
從表面上看,Promise只是能夠簡化層層回調的寫法,而實質上,Promise的精髓是“狀態”,用維護狀態、傳遞狀態的方式來使得回調函數能夠及時調用,它比傳遞callback函數要簡單、靈活的多。所以使用Promise的正確場景是這樣的:
function runAsync1(){
var p = new Promise(function(resolve, reject){
//做一些異步操作
setTimeout(function(){
console.log('異步執行1完成');
resolve('隨便什么數據1');
}, 2000);
});
return p;
}
function runAsync2(){
var p = new Promise(function(resolve, reject){
//做一些異步操作
setTimeout(function(){
console.log('異步執行2完成');
resolve('隨便什么數據2');
}, 2000);
});
return p;
}
function runAsync3(){
var p = new Promise(function(resolve, reject){
//做一些異步操作
setTimeout(function(){
console.log('異步執行3完成');
resolve('隨便什么數據3');
}, 2000);
});
return p;
}
runAsync1()
.then(function(data){
console.log(data);
return runAsync2()
})
.then(function(data){
console.log(data);
return runAsync3()
})
.then(function(data){
console.log(data)
})
//2秒后打印
"異步執行1完成"
"隨便什么數據1"
//又2秒后打印
"異步執行2完成"
"隨便什么數據2"
//又2秒后打印
"異步執行3完成"
"隨便什么數據3"
在then方法中,可以直接return 數據,而不是Promise對象,在后邊的then中就可以接受到數據了。
runAsync1()
.then(function(data){
console.log(data);
return runAsync2()
})
.then(function(data){
console.log(data);
return "直接返回數據"
})
.then(function(data){
console.log(data)
})
//2秒后打印
"異步執行1完成"
"隨便什么數據1"
//又2秒后打印
"異步執行2完成"
"隨便什么數據2"
"直接返回數據"
reject的用法
我們前面的例子都是只有“執行成功”的回調,還沒有“失敗”的情況
reject就是把Promise的狀態設置為 rejected,這樣我們在then中就能捕捉到,然后執行失敗情況的回調。
function getNumber(){
return new Promise((resolve,reject)=>{
setTimeout(function(){
var num = Math.ceil(Math.random()*10) //生成0-10之間的整數
if(num<=5){
resolve(num)
}else{
reject('數字太大了')
}
},2000)
})
}
getNumber()
.then(
function(data){
console.log('resolved')
console.log(data);
},
function(reason,data){
console.log('rejected')
console.log(reason)
}
)
getNumber函數用來異步獲取一個數字,2秒后執行完成,如果數字小于等于5,我們認為是“成功”了,調用resolve修改Promise的狀態。否則我們認為是“失敗”了,調用reject并傳遞一個參數,作為失敗的原因。 運行getNumber并且在then中傳了兩個參數,then方法可以接受兩個參數,第一個對應resolve的回調,第二個對應reject的回調。所以我們能夠分別拿到他們傳過來的數據
catch 的用法
我們知道 Promise除了有一個then方法,還有一個catch方法,它是干什么的呢?
其實它跟then方法的第二個參數的作用是一樣的。用來指定reject的回調。
它的用法:
getNumber()
.then(
function(data){
console.log('resolved')
console.log(data);
}
)
.catch(
function(reason){
console.log('rejected')
console.log(reason)
}
)
效果和寫在then的第二個參數里面一樣。不過它還有另外一個作用,在執行resolve的回調(也就是then方法的第一個參數)時,如果拋出異常(代碼出錯了),那么并不會報錯卡死JS,而是會進入到catch方法中
getNumber()
.then(function(data){
console.log('resolved');
console.log(data);
console.log(somedata); //此處的somedata未定義
})
.catch(function(reason){
console.log('rejected');
console.log(reason);
在 resolve的會調用,我們console.log(somedata),而somedata是未定義的,如果不用Promise,代碼運行到這里,就直接在控制臺報錯了,不往下運行了。但是在這里會得到這樣的結果:
也就是說進到catch方法里面去了,而且把錯誤原因傳到了reason參數中。即便是有錯誤的代碼也不會報錯了,這與我們的try/catch語句有相同的功能。
all方法
Promise.all()提供了并行執行異步操作的能力。并且在所有異步操作執行完成以后,才執行回調。
Promise
.all([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
console.log(results);
});
Promise.all(),接收一個數組作為參數,數組里的元素最終都返回一個Promise對象。這樣,三個異步的操作就并行執行了,等到他們都執行完以后,才會進入到then里邊,那么三個異步操作執行以后返回的數據哪里去了呢? all會把所有異步操作的結果放進一個數組,并且把數組傳遞給then,就是上邊的results.
所以上邊代碼輸出的結果是:
有了all方法,你就可以并行執行多個異步操作,并且在一個回調中處理所有的返回數據,有一個場景很適合用這個方法。
比如一些游戲類的素材比較多的應用,打開網頁時預先加載,需要用到各種資源,比如圖片,flash,以及各種靜態文件。所有都加載完以后,再進行頁面的初始化。
race的用法
Promise.all方法,實際上是誰跑的慢,以誰為準執行回調。那么相對的就有另外一個方法,以誰跑的塊,以誰為準執行回調。
就是race方法,這個詞本來就是賽跑的意思。race的用法與all一樣。我們修改下上邊的計時器的時間:
function runAsync1(){
var p = new Promise(function(resolve, reject){
//做一些異步操作
setTimeout(function(){
console.log('異步執行1完成');
resolve('隨便什么數據1');
}, 1000);
});
return p;
}
function runAsync2(){
var p = new Promise(function(resolve, reject){
//做一些異步操作
setTimeout(function(){
console.log('異步執行2完成');
resolve('隨便什么數據2');
}, 3000);
});
return p;
}
function runAsync3(){
var p = new Promise(function(resolve, reject){
//做一些異步操作
setTimeout(function(){
console.log('異步執行3完成');
resolve('隨便什么數據3');
}, 2000);
});
return p;
}
Promise
.race(runAsync1(),runAsync2(),runAsync3())
.then(function(results){
console.log(results);
})
//1秒后打印
"異步執行1完成"
//再過1秒后打印
"異步執行3完成"
//再過2秒后打印
"異步執行2完成"
這個race有什么用呢?使用場景還是很多的,比如我們可以用race給某個異步請求設置超時時間,并且在超時后執行相應的操作,代碼如下:
//請求某個圖片資源
function requestImg(){
var p = new Promise(function(resolve, reject){
var img = new Image();
img.onload = function(){
resolve(img);
}
img.src = 'xxxxxx';
});
return p;
}
//延時函數,用于給請求計時
function timeout(){
var p = new Promise(function(resolve, reject){
setTimeout(function(){
reject('圖片請求超時');
}, 5000);
});
return p;
}
Promise
.race([requestImg(), timeout()])
.then(function(results){
console.log(results);
})
.catch(function(reason){
console.log(reason);
});
requestImg函數會異步請求一張圖片,我把地址寫為"xxxxxx",所以肯定是無法成功請求到的。timeout函數是一個延時5秒的異步操作。我們把這兩個返回Promise對象的函數放進race,于是他倆就會賽跑,如果5秒之內圖片請求成功了,那么遍進入then方法,執行正常的流程。如果5秒鐘圖片還未成功返回,那么timeout就跑贏了,則進入catch,報出“圖片請求超時”的信息。運行結果如下:
H-------解構賦值
ES6允許按照一定的模式,從數組和對象中提取值,對變量進行賦值。這杯成為解構賦值。
- 數組的解構賦值;
數組中的值會被自動解析到對應接收該值的變量中,數組的解構賦值要一一對應,如果有對應不上的,就是undefined
let [a,b,c,d] = [1,2,3];
console.log(a); 1
console.log(b); 2
console.log(c); 3
console.log(d); undefined
-對象的結構賦值
對象的結構賦值與數組有一個重要的不同,數組的元素是按照次序排列的,變量的取值由它的位置決定。而對象的屬性是沒有次序的,變量必須與屬性同名,才能取到正確的值。
var person = {name:'小明',age:20,sex:'男'};
var {name,age,sex} = person;
console.log(name);
console.log(age);
console.log(sex);
//如果想要變量名和屬性名不同,可以這樣寫
let {name:foo,age:baz} = {name:'小明',age:20,sex:'男'}
console.log(foo); '小明'
console.log(baz); 20
I--------set數據結構
Set的數據結構,類似數組,所有的數據都是唯一的,沒有重復的值,它本身是一個構造函數;
var arr = [1,2,2,3,4,4];
var s = new Set(arr);
console.log(s); //{1,2,3,4}
屬性和方法:
size 數據的長度
add() 添加某個值,返回 Set 結構本身。
delete() 刪除某個值,返回一個布爾值,表示刪除是否成功。
has() 查找某條數據,返回一個布爾值。
clear() 清除所有成員,沒有返回值。
console.log(s.size); //4
console.log(s.add(5)); //{1,2,3,4,5}
console.log(s.delete(1)); //true
console.log(s.has(2)); //true
s.clear();
2.函數節流和函數防抖
前提條件:一個函數在短時間內被多次執行。
但是這種短時間內多次重復執行,通常情況下是沒有必要的。我們需要優化這種高頻執行的JS。
優化的方法就是 函數節流 和 函數防抖
函數節流:
讓函數在特定的時間之內只執行一次。特定時間內如果多次觸發函數,只有一次生效。
應用場景 :下拉加載。
函數防抖:
頻繁觸發某一事件,其中兩次觸發間隔時間大于等于特定時間,才執行代碼一次。如果在特定時間內,又觸發了一次事件,那么重新開始計算函數執行時間。簡單的說,一個動作連續觸發,只執行最后一次。
應用場景:搜索框搜索輸入,比如用戶輸入字母間隔超過2秒再發送請求。
函數節流的例子
<button id="btn">大招<button>
<script>
var cd = false; //cd用來記錄大招是否冷卻,默認是沒有冷卻
function attack(){
console.log('發起攻擊')
}
var button = document.queryselector('#btn');
button.onclick = function(){
if(cd){
//點擊大招按鈕的時候判斷一下,如果大招冷卻,給出提示。
console.log('大招冷卻,無法攻擊!')
}else{
//如果大招沒有冷卻,發起攻擊
attack();
cd = true; //發起攻擊后,技能狀態調整為冷卻
var timer = setTimeout(function(){
cd = false; //3秒鐘后,技能冷卻結束
},3000)
}
}
</script>
//函數防抖例子
//公交關門
function close(){
console.log('關門');
}
var button = document.querySelector('#btn');
var timer = null ; //定時器一開始是空的
button.onclick = function(){
//如果點擊了按鈕,發現上一個計時器還存在,那么就把它清除掉。
if(timer){
window.clearTimeout(timer);
}
//如果5秒種之內沒有再點,就設置一個定時器,并執行關門函數,并且把定時器清除掉。
timer = setTimeout(function(){
close();
timer= null;
},5000)
}
3.手寫AJAX
var xhr = new XMLHttpRequest();
xhr.open('GET','/xxx',true);
xhr.onreadystatechange = function(){
if(xhr.readystate === 4){
console.log('請求完成')
if(xhr.status>=200&&xhr.status<300||xhr.status === 304){
console.log('請求成功')
}else{
console.log('請求失敗')
}
}
}
xhr.send();
4.這段代碼里的this是什么?
主要是看函數怎么調用的!
fn() // this指向 window/global
obj.fn() // this 指向 obj
fn.call(xx) //this 指向 xx
fn.bind(xx) //this指向 xx
new Fn() //this指向 新的對象
fn = ()=>{} //this 指向外邊的this
判斷的通用方法
function fn(a,b){
console.log(this)
}
fn(1,2);//這句等價于下邊
fn.call(undefined,1,2);
fn.apply(undefined,[1,2]);
注意:
在嚴格模式下 'use strict' ,此時 fn里的this,就是call和apply的第一個參數,也就是 undefined
在非嚴格模式下,也就是不用 'use strict' ,call和apply里的第一個參數如果是undefined或者是null,那么this會默認替換為 window
在看一個例子:
var obj = {
fn:function(a,b){
console.log(this)
},
child:{
fn2:function(){
console.log(this)
}
}
}
obj.fn(1,2); 等價于
obj.fn.call(obj,1,2);
obj.fn.apply(obj,[1,2]); //所以this是obj
obj.child.fn2();等價于
obj.child.fn2.call(obj.child);
obj.child.fn2.apply(obj.child); //所以this是 obj.child
5. 閉包和立即執行函數是什么?
閉包:
就是能讀取其他函數內部變量的函數,在JS中,只有函數內部的子函數,能夠讀取函數內部的局部變量。所以閉包可以理解為,定義在一個函數內部的函數。閉包的缺點:
讓變量始終保持在內容中,內存消耗會很大。什么是詞法作用域?
詞法作用域就是函數創建的時候所在的作用域。
所以,函數在執行的時候,使用的變量,會先從自己內部去找,如果自己內部沒有定義,就到自己的詞法作用域(scopes)去找。
function car(){
var speed = 0;
function fn(){
speed++;
console.log(speed);
}
return fn;
}
var speedUp = car();
speedUp(); //1
speedUp(); //2
// 如果在car函數里,再聲明一個函數fn,并返回fn,fn()內部又使用了car聲明的變量,然后調用car函數并賦值給 一個全局變量speedUp
// 因為speedUp 屬于全局作用域,而全局作用域在頁面沒有關閉的時候,是不會銷毀的。所以也就導致了fn函數不會被銷毀
// 執行speedUp就相當于 執行fn(),而fn()函數內部有用到局部變量speed,按照作用域鏈來尋找,fn()函數內沒有聲明
// 繼續往上一級找,fn()函數是聲明在car()內的,而speed是在car內聲明的
// 所以第一次執行 speedUp() 的時候,結果是1;執行之后,speed是沒有被銷毀的
// 再次執行就是2
//再看個例子
var fnArr = [];
for(var i=0;i<10;i++){
fnArr[i] = function(){
return i;
}
}
console.log(fnArr[1]); // 這個時候數組里存的都是函數 function(){ return i}
console.log(fnArr[2]()) //10
console.log(fnArr[5]()) //10
console.log(fnArr[7]()) //10
//為什么會輸出10呢? 分析一下
fnArr[2]本身是一個函數,加括號,表示執行它指向的那個函數。
函數內部 return i,用到了i這個變量,所以會從自己內部去找這個變量,發現沒有聲明i,然后在去函數聲明的作用域去找,函數是在for里聲明的,但是for本身并不是函數,所以function函數聲明的作用域是全局作用域。而全局作用域里,for循環完以后,i已經變為10了,所以fnArr[i]()會輸出10.
那么如果我們想輸出 0,1,2,3,4,5,....怎么辦呢?
用立即執行函數
var fnArr = [];
for(var i=0;i<10;i++){
fnArr[i] =( function(j){
return j;
})(i)
}
console.log(fnArr[0]) //0
console.log(fnArr[5]) //5
console.log(fnArr[7]) //7
再看個例子
for(var i=0;i<5;i++){
setTimeout(function(){
console.log(i)
},2000)
}
//這里2秒后會輸出5個5
for(var i=0;i<5;i++){
(function(j){
setTimeout(function(){
console.log(j)
},2000)
})(i)
}
//這里2秒后會輸出 0,1,2,3,4
再熟悉一下作用域鏈
var x = 10
function foo(){
console.log(x);
}
foo(); //10
function bar(){
var x = 20;
foo();
}
bar(); //10
執行bar就是執行foo,foo輸出x,先從自己內部去找
發現沒有聲明x,然后到foo聲明的作用域去找
foo是聲明在全局作用域的,而全做作用域下,有聲明變量x,所以輸出10
//下邊之所以輸出30 是因為foo是在bar內部聲明的。
var x = 10;
bar(); //30
function bar(){
var x = 30;
function foo(){
console.log(x);
}
foo();
}
6.什么是JSONP,什么是CORS,什么是跨域?
什么是跨域?
--------是指不同源的網站之間的訪問。
同源策略
---------提到跨域,就不得不說一下同源策略,同源策略是NetScape提出的瀏覽器的一種安全策略,也就是指a網站,不能隨便讀取b網站的內容。
所謂同源:
--------指的是,協議、域名、端口、相同
最常見的應用是,當我們調用ajax接口時,如果不設置跨域,瀏覽器會報錯。這證明使用XMLHttpRequest對象不能發送跨域請求。
有疑惑的小伙伴肯定會問,那我用a標簽請求其他網站,是不是就是跨域了呢?
這里要明白跨域是指在當前域下調用其他域下的東西,而鏈接則是直接跳轉到對方的域下了,跟你之前的域名毫無關系。
如果想從你的網站跨域去另一個網站拿到一些資源,有一個前提是另外一個網站的服務器支持跨域,這個需要在服務器端設置,不同服務器的設置方法不一樣,我們在這里不多說,就看客戶端跨域如何解決?
解決跨域最常見的方式是 jsonp。
先弄清楚 json和jsonp的區別
json (JavaScript Object Notation)是一種輕量級的數據交換格式,用來在瀏覽器和服務器之間交換數據。
jsonp (JSON With Padding) 就是打包在函數中調動的json,或者包裹的json
json 是一種數據格式;jsonp是一種數據調用方式
//json
{
"name":"小明"
}
//jsonp
callback({
"name":"小明"
})
jsonp是jquery給我們封裝的一個方法,使用方法如下:
$.ajax({
ulr:'xxx',
type:'GET',
dataType:'jsonp',
success:function(data){
console.log(data)
}
})
上邊的代碼是當我們調用外部的一個接口時,通過設置jquery里的ajax方法里的datatype屬性的值為jsonp,就可以成功調用這個接口了。
首先我們需要明白,在頁面上直接發起一個跨域的ajax請求是不可以的,但是,在頁面上引入不同域上的js腳本卻是可以的,就像你可以在自己的頁面上使用<img src=""> 標簽來隨意顯示某個域上的圖片一樣。
看下怎么利用<script src="">來完成一個跨域請求。
<div class="container">
<ul class="news"></ul>
<button class="show">show news</button>
</div>
$('.show').addEventListener('click', function () {
var script = document.createElement('script');
script.src = 'http://127.0.0.1:8080/getNews?callback=appendHtml';
document.head.appendChild(script);
// document.head.removeChild(script); //為了保持代碼的整潔,直接刪除了新增的標簽,但是標簽的作用已經起到了
})
function appendHtml(news) {
var html = '';
for (var i = 0; i < news.length; i++) {
html += '<li>' + news[i] + '</li>';
}
console.log(html);
$('.news').innerHTML = html;
}
jsonp跨域的原理:
- 使用script標簽發送請求,這個標簽支持跨域訪問
- 在script標簽里,給服務器傳遞一個callback
- callback的值對應到頁面上定義的一個全局函數(為什么是全局函數呢?因為服務器接收到callback函數后,會返回頁面中的script去尋找,如果不寫到全局作用域中,根本找不到)。
4.服務器端返回的是一個函數的調用,調用的時候會把返回數據作為參數包裹在這個函數里。
缺點: jsonp只能解決get方式的跨域。如果傳輸的數據比較大,這種方式就不行了。
cors跨域
cors (cross origin resource sharing) 全稱 '跨域資源共享'
相對于jsonp, cors支持所有類型的HTTP請求,并且實施起來非常簡單。
cors背后的基本思想是,使用自定義的HTTP頭部允許瀏覽器和服務器互相了解對方,從而決定請求和響應成功與否。
cors原理:
當使用XMLHttpRequest發送請求的時候,瀏覽器發現該請求不符合同源策略,會給該請求加一個請求頭 origin,后臺會進行一系列的處理,如果確定接受請求,則在返回結果中,加入一個響應頭 Access-Control-allow-origin;瀏覽器判斷該響應頭中是否包含 origin的值,如果有,則瀏覽器會處理響應,我們就拿到數據。如果沒有,則瀏覽器會直接駁回,我們就拿不到數據。
IE10以上支持,現代瀏覽器也都支持cors
JSONP和CORS的區別?
- JSONP只支持GET請求,而CORS支持所有類型的HTTP請求。
- 使用CORS,開發者可以使用普通的XMLHttpRequest對象發起請求和獲得數據。比起JSONP有更好的錯誤處理。
3.JSONP主要被老的瀏覽器支持,它們往往不支持CORS,而絕大多數的現代瀏覽器都已經支持了CORS
7. async/await 怎么用? 怎么捕獲異常?
async
是異步的縮寫。而await 可以認為是 async wait的縮寫。
async
作為一個關鍵字放在函數前面,用來聲明這個函數是一個異步函數
。既然是異步函數就意味著該函數的執行不會阻塞后邊的代碼;
async函數返回的是一個Promise對象
。
await用于等待一個異步方法執行完成
,await等待的是一個表達式,而表達式的計算結果是一個Promise對象或者其他值。
(await相當于運算符,用于組成表達式,await表達式的運算結果取決于它等到的東西)
如果等到的不是一個Promise對象,那么await表達式的運算結果就是它等到的東西。
如果它等到的是一個Promise對象,await就忙起來了,它會阻塞后面的代碼,等待Promise對象resolve,然后得到resolve的值。作為await表達式的運算結果。
await只能用到async函數中。
function sayHi(name){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('Hi'+name);
},2000)
})
}
async function test(){
var result = await sayHi('小明');
console.log(result);
}
test();
async和 await捕獲異常
,需要用到 try/catch
的方式:
因為await后面跟著的是 Promise對象,當有異常的情況下,會被Promise對象內部的catch捕獲。而await就是一個then的語法糖,并不會捕獲異常,那么就需要借助
try/catch
來捕獲異常,并進行相應的邏輯處理。
function sayHi(name){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
//生成0-10的隨機整數
var num = Math.ceil(Math.random()*10);
if(num>5){
//如果隨機數大于5,就resolve出結果
resolve('Hi'+name);
}else{
//否則就拋出錯誤
reject('出錯了');
}
},2000)
})
}
async function test(){
try{
var result = await sayHi('小明');
console.log(result);
}catch(error){
console.log(error)
}
}
test();
8.如何實現深拷貝?
關鍵字:
1、遞歸
2、判斷類型
3、檢查循環引用(環)
4、不可能拷貝 _proto_
拷貝分為淺拷貝和深拷貝
在這之前,我們先弄清楚兩個概念。
- 基本數據類型
Number ,String,Boolean,Null,Undefined 都是基本數據類型
基本數據類型時按值訪問的,因為可以直接操作保存在保存在變量中的實際值。
var a = 10;
var b = a; //b只是保存了a復制的一個副本。所以 a和b是互相獨立的。
console.log(a); //10
console.log(b); //10
//a值的改變不會影響b的值
a = 20;
console.log(a); //20
console.log(b); //10
- 引用數據類型
引用數據類型也就是對象類型 Object type.比如 Object,Array,Function,Data等。
JavaScript的引用數據類型是保存在堆內存中的對象。
與其他語言不同的是,你不可以直接訪問堆內存空間中的位置和操作堆內存空間。只能操作對象在棧內存中的引用地址。
所以,引用類型數據在棧內存中保存的是對象在堆內存中的引用地址,通過這個引用地址可以快速查找到保存在堆內存中的對象。
var obj1 = new Object();
var obj2 = obj1;
obj2.name = '小明';
console.log(obj1.name);
說明這兩個引用數據類型指向了同一個堆內存對象。obj1賦值給onj2,實際上這個堆內存對象在棧內存的引用地址復制了一份給了obj2,但是實際上他們共同指向了同一個堆內存對象。
實際上改變的是堆內存對象。
下面我們來演示這個引用數據類型賦值過程:
好了,上邊兩個概念了解清楚以后,我們進入正題。
對象的淺拷貝
對象淺拷貝比較簡單,就是將一個變量賦值給另外一個變量
var obj1 = {
name:"小明",
age:20
}
var obj2 = obj1;
console.log(obj2.age) //20
obj2經過淺拷貝,擁有了obj1的屬性
封裝淺拷貝方法
var easyCopy = function(oldObj){
//constructor 屬性應用了該對象的構造函數
var newObj = oldObj.constructor === Array?[]:{};
if(typeof oldObj != 'object') return;
for(var key in oldObj){
if(oldObj.hasOwnProperty(key)){
newObj[key] = oldObj[key]
}
}
return newObj;
}
var obj1 = {
name:'小明',
age:20
}
var obj2 = easyCopy(obj1);
console.log(obj2)
淺拷貝存在的問題:
我們知道,引用數據類型的賦值,其實是變量a把對象在堆內存中的地址,復制了一份給變量b,這個時候,a和b指向的都是同一個對象。通過a或者b都可以改變堆內存中的對象(比如添加刪除屬性),a和b是可以相互影響的。所以這種淺拷貝會帶來BUG。
對象的深拷貝
深拷貝的目的就是為了實現 復制出2個完全相同,卻又完全獨立,互不干擾的對象。
var deepCopy = function(obj){
var cloneObj = Array.isArray(obj)? []:{};
if(obj && typeof obj === 'object' ){
for(var key in obj){
if(obj.hasOwnProperty(key)){
cloneObj[key] = typeof obj[key] === 'object'?deepCopy(obj[key]):obj[key]
}
}
}
return cloneObj;
}
var obj = {
name:'小紅',
age:18,
friend:{
name:'小明',
sex:'男',
study:["數學","英文","物理"]
}
}
var obj2 = deepCopy(obj);
console.log(obj2)
obj.friend = {
name:'小明明明',
sex:"女"
}
console.log(obj);
console.log(obj2);
深拷貝的缺點:雖然深拷貝能夠避免淺拷貝出現的問題。但是卻會帶來性能上的問題,如果一個對象非常復雜且數據龐大,所消耗的性能將會是很可觀的。
關于for in
for..in可以用來遍歷任何一個對象,它會將對象上的所有的屬性全部遍歷出來,包裹原型鏈上的屬性。所以上邊代碼中需要hasOwnProperty來判斷這個屬性到底是不是對象本身的屬性。因為數組也是對象,for..in也可以用來遍歷數組,但是for in損耗性能較多,所以盡量不要用它來遍歷數組。
關于遞歸
一個方法,重復調用自身的情況,叫做遞歸。
需要注意的是,一定要有一個條件來結束遞歸,否則將會陷入無限的循環。
var num = 0;
function recursion(){
if(num < 50){
num++;
recursion()
}
}
recursion()
9 如何用正則實現trim()?
trim()方法去掉字符串兩端的空格,無論有多少個空格都會去掉,字符串中間的空格不會被去掉。
function trim(string){
return string.replace(/^\s+|\s+$/g);
}
var str = ' ab cd ef ';
var res = replace(str);
console.log(res); //'ab cd ef'
10.不用class如何實現繼承,用class如何實現繼承?
//不用class
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.printName =function(){
console.log(this.name);
}
// 繼承屬性
function Mail(name,age,sex){
Person.call(this,name,age);
this.sex = sex;
}
// 繼承方法
//Object.create 創建一個空對象,空對象的_proto_指向Person.prototype
Mail.prototype = Object.create(Person.prototype);
Mail.prototype.printSex = function(){
console.log(this.sex);
}
//因為Mail.prototype此時是指向Person.prototype的,所以Mail.prototype.contructor是指向 Person的。我們需要修改它的指向。
Mail.prototype.contructor = Mail
var john = new Mail('約翰',20,'男');
console.log(john.name);
john.printName();
john.printSex();
//用class
class Person {
constructor(name,age){
this.name = name;
this.age = age;
}
sayAge(){
console.log(`i am ${this.age}`)
}
}
class Student extends Person{
constructor(name,age,score){
supre(name,age);
this.socre = score;
}
sayScore(){
console.log(`i get ${this.score}`);
}
}
11.如何實現數組去重?
//雙循環去重
function fn(arr){
if(Array.isArray(arr)){
//聲明一個新的數組,然后把要遍歷的數組的第一個元素放入這個新數組
var newArr = [arr[0]];
//然后從第二個元素開始遍歷老的數組,同時遍歷新數組
//把老數組和新數組的已有元素做對比,如果不相等,就放入新數組。
for(let i=1;i<arr.length;i++){
let flag = true;
for(let j=0;j<newArr.length;j++){
if(arr[i] === newArr[j]){
flag = false;
break;
}
}
if(flag){
newArr.push(arr[i])
}
}
}
return newArr
}
var arr = [1,1,2,2,3,4,5,5];
var arr2 = fn(arr);
console.log(arr2) [1, 2, 3, 4, 5]
//indexOf去重
//兩個關鍵點 1、聲明新數組 2、判斷老數組里的元素是否在新數組里,沒有就push進新數組
function fn(arr){
if(!Array.isArray(arr)){
console.log('type error');
return
}
var newArr = [];
for(let i=0;i<arr.length;i++){
if(newArr.indexOf(arr[i]) == -1){
newArr.push(arr[i])
}
}
return newArr;
}
var arr = [1,1,2,2,3,4,5,5];
var arr2 = fn(arr);
console.log(arr2) [1, 2, 3, 4, 5]
//set 去重
//ES6?新增了一個數據類型 set,set的一個最大的特點就是數據不重復。
//Set()函數可以接收一個數組(或者類數組對象)作為參數來初始化。
function fn(arr){
if(!Array.isArray(arr)){
console.log('type error');
return;
}
return [...new Set(arr)]
}
var arr = [1,1,2,2,3,4,5,5];
var arr2 = fn(arr);
console.log(arr2) [1, 2, 3, 4, 5]
DOM面試題
1. DOM事件模型是什么?
- 事件冒泡-----事件開始時,由最具體的元素接收,然后逐級向上傳播到較為不具體的元素。
- 事件捕獲------不太具體的節點更早的接收事件,而最具體的元素最后接收事件,和事件冒泡相反。
- DOM事件流------DOM2級事件規定,事件流包括三個階段,事件捕獲階段,處于目標階段,事件冒泡階段。首先發生的是事件捕獲,為截取事件提供機會,然后是實際目標接受事件,最后是事件冒泡
Opera、Firefox、Chrome、Safari都支持DOM事件流,IE不支持事件流,只支持事件冒泡
事件處理程序是什么?
響應某個事件的函數,就叫做事件處理程序
- 事件處理程序分為4種
//HTML事件處理程序
<input type="button" name="btn" value="點擊" onclick="alert('clicked')"
//DOM0級事件處理程序
<button id="box">點擊</button>
var box = document.querySelector('#box');
box.onclick = function(){
console.log('DOM0級事件處理程序')
}
box.onclick = null //刪除綁定的事件
//DOM2級事件處理程序
<button id="box">點擊</button>
var box = document.querySelector('#box');
box.addEventListener('click',function(){
console.log('DOM2級事件處理程序')
},true)
//刪除綁定的事件
box.removeEventListener('click',函數名,boolean)
//IE事件處理程序
function showMes(){
console.log('我是IE事件處理程序')
}
var box = document.getElementById('box');
box.attatchEvent('onclick',showMes);
//刪除綁定的事件處理程序
box.detachEvent('onclick',showMes);
跨瀏覽器事件處理程序
function showMes(){
console.log('我被點擊了')
}
var box = document.getElementById('box');
var EventUtil = {
addHandler:function(element,type,handle){
if(element.addEventListener){
element.addEventListener(type,handle,false);
}else if(element.attatchEvent){
element.attatchEvent('on'+type,handle)
}else{
element['on'+type] = handle
}
},
removeHandler:function(element,type,handle){
if(element.removeEventListener){
element.removeEventListener(type,handle,false);
}else if(element.detachEvent){
element.detachEvent('on'+type,handle);
}else{
element['on'+type] = null;
}
}
}
EventUtil.addHandler(box,'click',showMes);
EventUtil.removeHandler(box,'click',showMes);
事件對象
在觸發DOM上的事件時,都會產生一個事件對象Event
Event 對象代表事件的狀態,比如事件在其中發生的元素、鍵盤按鍵的狀態、鼠標的位置、鼠標按鈕的狀態。
兼容DOM的瀏覽器,會將一個對象,傳入事件處理程序中。
例如:
var box = document.querySelector('#box');
box.addEventListener('click',function(event){
console.log(event.type) //輸出事件的類型
})
event對象的屬性
event.target 獲取事件的類型
event.type 獲取事件的目標(就是時間綁定到哪個元素上了)
...等等
event對象的屬性
event.stopPropagation :
不再派發事件。就是終于事件在傳播過程的捕獲,目標處理,冒泡階段的傳播,事件不再被派到其他節點。
event.preventDefault :
通知瀏覽器不要執行與事件相關的默認動作。比如a標簽
IE事件對象
window.event
常見的屬性
window.event.type 相當于 event.type
window.event.srcElement 相當與 event.target
window.event.cancleBubble 相當于 event.stopPropagation()
window.event.returnValue 相當于 event.preventDefault()
2.移動端的觸摸事件了解嗎?
touch 觸摸事件,有4種之分
1. touch start 手指觸摸到屏幕會出發
2. touch move 當手指在屏幕上移動時會觸發
3. touch end 當手指離開屏幕時會觸發
4. touch cancel 可由系統進行的觸發,比如手指觸摸屏幕的時候,突然alert了一下,則可以觸發該事件。
tap事件
觸碰事件,一般用于代替click
tap:手指碰一下屏幕會觸發
longTap :手指長按屏幕會觸發
singleTap:手指碰一下屏幕會觸發
doubleTap:手指雙擊屏幕會觸發
swipe事件
滑動事件
swipe:手指在屏幕上滑動時觸發
swipeLeft:手指在屏幕上向左滑動時會觸發
swipeRight:手指在屏幕上向右滑動時會觸發
swipeUp:手指在屏幕上向上滑動時會觸發
swipeDown:手指在屏幕上向下滑動時會觸發
模擬swipe事件:思路 記錄兩次touchmove的位置差,如果后一次在前一次的右邊,說明向右滑了。
3.事件委托(也叫事件代理)是什么?
事件委托就是利用事件冒泡,只指定一個事件處理程序,就可以管理某一類型的所有事件。
舉個取快遞的例子:
有三個同事預計會在周一收到快遞。為簽收快遞,有兩種辦法:一是三個人在公司門口等快遞;二是委托給前臺MM代為簽收。
前臺MM收到快遞后,她會判斷收件人是誰,然后按照收件人的要求簽收,甚至代為付款。
這種方案還有一個優勢,那就是即使公司里來了新員工(不管多少),前臺MM也會在收到寄給新員工的快遞后核實并代為簽收。
這個例子包含2層意思
1.現在委托前臺的同事,是可以代為簽收的(即程序中,現有的DOM節點是有事件的)
2.新員工也是可以被前臺代為簽收的(即程序中,新添加的DOM節點也是有事件的)。
<div class="container">
<div class="box">box1</div>
<div class="box">box2</div>
</div>
<button class="add">add</button>
var con = document.querySelector('.container');
var box = document.querySelectorAll('.box');
var addBtn = document.querySelector('.add');
//給每個box都綁定一個事件
// box.forEach(function(node){
// node.onclick = function(){
// console.log(this.innerText);
// }
// })
//用父級div做事件代理
con.onclick = function(e){
if(e.target.classList.contains('box')){
console.log(e.target.innerText)
}
}
//有了事件代理以后,哪怕新增的box,也會被綁定事件。
var i = 4;
addBtn.onclick =function(){
var box = document.createElement('div');
box.classList.add('box');
box.innerText = 'box' + i++;
con.appendChild(box);
}