歡迎關注前端早茶,與廣東靚仔攜手共同進階
前端早茶專注前端,一起結伴同行,緊跟業界發展步伐~
知識點
1、undeclared 與 undefined 的區別?
- undefined:聲明了變量,但是沒有賦值
- undeclared:沒有聲明變量就直接使用
var a; //undefined b; // b is not defined
2、let & const與 var 的區別?
- var存在變量提升,可重復聲明同一變量,聲明的變量均可改
- let沒有變量提升,不可重復聲明同一變量,聲明的變量均可改
- const沒有變量提升,不可重復聲明同一變量,聲明的基本數據類型不可改,引用類型可改屬性,不可只聲明變量而不賦值
3、暫時性死區問題
var a = 100; if(1){ a = 10; //在當前塊作用域中存在a使用let/const聲明的情況下,給a賦值10時,只會在當前作用域找變量a, // 而這時,還未到聲明時候,所以控制台Error:Cannot access 'a' before initialization let a = 1; }
4、獲取DOM元素有哪些方法
方法 | 描述 | 返回類型 |
---|---|---|
document.getElementById(id) | 通過id獲取dom | 符合條件的dom對象 |
document.getElementsByTagName(tagName) | 通過標簽名獲取dom | 符合條件的所有dom對象組成的類數組 |
document.getElementsByClassName(class) | 通過class獲取dom | 符合條件的所有dom對象組成的類數組 |
document.getElementsByName(name) | 通過標簽的屬性name獲取dom | 符合條件的所有dom對象組成的類數組 |
document.querySelector(選擇器) | 通過選擇器獲取dom | 符合條件的第一個dom對象 |
document.querySelectorAll(選擇器) | 通過選擇器獲取dom | 符合條件的所有dom對象組成的類數組 |
5、操作DOM元素有哪些方法
標題 | 描述 |
---|---|
createElement | 創建一個標簽節點 |
createTextNode | 創建一個文本節點 |
cloneNode(deep) | 復制一個節點,連同屬性與值都復制,deep為true時,連同后代節點一起復制,不傳或者傳false,則只復制當前節點 |
createDocumentFragment | 創建一個文檔碎片節點 |
appendChild | 追加子元素 |
insertBefore | 將元素插入前面 |
removeChild | 刪除子元素 |
replaceChild | 替換子元素 |
getAttribute | 獲取節點的屬性 |
createAttribute | 創建屬性 |
setAttribute | 設置節點屬性 |
romoveAttribute | 刪除節點屬性 |
element.attributes | 將屬性生成類數組對象 |
6、DOM的類型有哪幾種?
12種
元素節點 Node.ELEMENT_NODE(1)
屬性節點 Node.ATTRIBUTE_NODE(2)
文本節點 Node.TEXT_NODE(3)
CDATA節點 Node.CDATA_SECTION_NODE(4)
實體引用名稱節點 Node.ENTRY_REFERENCE_NODE(5)
實體名稱節點 Node.ENTITY_NODE(6)
處理指令節點 Node.PROCESSING_INSTRUCTION_NODE(7)
注釋節點 Node.COMMENT_NODE(8)
文檔節點 Node.DOCUMENT_NODE(9)
文檔類型節點 Node.DOCUMENT_TYPE_NODE(10)
文檔片段節點 Node.DOCUMENT_FRAGMENT_NODE(11)
DTD聲明節點 Node.NOTATION_NODE(12)
7、JS的作用域及作用域鏈
什么是作用域呢?
在 Javascript 中,作用域分為 全局作用域
和 函數作用域
- 全局作用域:代碼在程序任何地方都能訪問,window對象的內置屬性都屬於全局作用域
- 函數作用域:在固定的代碼片段才能被訪問
作用域有上下級關系,上下級關系的確定就看函數是在哪個作用域下創建的。如上,fn作用域下創建了bar函數,那么“fn作用域”就是“bar作用域”的上級。
作用域最大的用處就是隔離變量,不同作用域下同名變量不會有沖突。
什么是作用域鏈?
一般情況下,變量取值到 創建 這個變量 的函數的作用域中取值
但是如果在當前作用域中沒有查到值,就會向上級作用域去查,直到查到全局作用域,這么一個查找過程形成的鏈條就叫做作用域鏈
var x = 10; function fn(){ console.log(x); } function show(f){ var x = 20; f(); // 10 } show(fn);
8、數組的splice 與 slice 的區別?
方法 | 參數 | 描述 |
---|---|---|
splice | splice(start, num, item1, item2, ...) | 從start索引開始,截取num個元素,並插入item1、item2到原數組里,影響原數組 |
slice | slice(start, end) | 從start開始,截取到end - 1,如果沒有end,則截取到左后一個元素,不影響原數組 |
9、substr 和 substring 的區別?
方法 | 參數 | 描述 |
---|---|---|
substr | substr(start,length) | 返回從start位置開始length長度的子串 |
substring | substring(start,end) | 返回從start位置開始到end位置的子串(不包含end) |
10、includes 比 indexOf好在哪?
includes可以檢測NaN
,indexOf不能檢測NaN
,includes內部使用了Number.isNaN
對NaN
進行了匹配
11、下面代碼輸出的結果?
for(var i = 0; i < 3; i++){ setTimeout(function(){ console.log(i); },0); };
答案:3,3,3
解決方法
for(let i = 0; i < 3; i++){ setTimeout(function(){ console.log(i); },0); }; // 0 1 2
for (var i = 0; i < 3; i++) { (function(i) { setTimeout(function () { console.log(i); }, 0, i) })(i) }; // 0 1 2
12、什么是Promise?解決了什么問題?
有什么用呢?
- 1、解決回調地獄問題
- 2、代碼可讀性提高
- 3、你可以信任Promise,它的狀態只會改變一次並且不可逆
推薦閱讀
- 是什么:推薦阮一峰大佬的文章Promise 對象
- 原理:推薦我這篇手寫Promise原理,最通俗易懂的版本!!!【閱讀:1.2w,點贊:466】
13、什么是async/await?解決了什么問題?
對於async/await,我總結為一句話async/await是generator + Promise的語法糖,它用同步方式執行異步代碼
推薦閱讀
- async/await的用法:阮一峰大佬的文章async和await
- async/await的原理:推薦我的這篇7張圖,20分鍾就能搞定的async/await原理!為什么要拖那么久?【閱讀:2.1w,點贊:630】
14、常用的正則表達式有哪些?
看我這篇文章有了這25個正則表達式,代碼效率提高80%【閱讀:1.6w 點贊:830】
15、JS延遲加載的方法有哪些?
- 1、
<script async src="script.js"></script>
:給script標簽加async屬性,則加載和渲染后續文檔元素的過程將和script.js
的加載與執行並行進行(異步) - 2、
<script defer src="script.js"></script>
:給script標簽加defer屬性,加載后續文檔元素的過程將和script.js
的加載並行進行(異步),但是script.js
的執行要在所有元素解析完成之后,DOMContentLoaded
事件觸發之前完成 - 3、動態創建script標簽:等到
DOMContentLoaded
事件觸發時,生成一個script標簽,渲染到頁面上上 - 4、setTimeout定時器延遲代碼執行
16、new操作符為什么能創建一個實例對象?
分析一下new的整個過程:
- 1、創建一個空對象
- 2、繼承構造函數的原型
- 3、this指向obj,並調用構造函數
- 4、返回對象
簡單實現一下new:
function myNew (fn, ...args) { // 第一步:創建一個空對象 const obj = {} // 第二步:繼承構造函數的原型 obj.__proto__ = fn.prototype // 第三步:this指向obj,並調用構造函數 fn.apply(obj, args) // 第四步:返回對象 return obj }
17、什么是文檔碎片?
- 是什么:一個容器,用於暫時存放創建的dom元素,使用
document.createDocumentFragment()
創建 - 有什么用:將需要添加的大量元素 先添加到文檔碎片 中,再將文檔碎片添加到需要插入的位置,大大減少dom操作,提高性能
例子
var oFragmeng = document.createDocumentFragment(); for(var i=0;i<10000;i++) { var op = document.createElement("span"); var oText = document.createTextNode(i); op.appendChild(oText); //先附加在文檔碎片中 oFragmeng.appendChild(op); } //最后一次性添加到document中 document.body.appendChild(oFragmeng);
18、async/await如何檢測報錯?
推薦這篇async await 更優雅的錯誤處理【閱讀量:1.5w,點贊:210】
19、宏任務與微任務有哪些?
宏任務
# | 瀏覽器 | Node |
---|---|---|
I/O | ✅ | ✅ |
setTimeout | ✅ | ✅ |
setInterval | ✅ | ✅ |
setImmediate | ❌ | ✅ |
requestAnimationFrame | ✅ | ❌ |
微任務
# | 瀏覽器 | Node |
---|---|---|
Promise.prototype.then catch finally | ✅ | ✅ |
process.nextTick | ❌ | ✅ |
MutationObserver | ✅ | ❌ |
20、宏任務與微任務的執行順序?說說EventLoop?
看看我這篇setTimeout+Promise+Async輸出順序?很簡單呀!
21、Object.defineProperty(target, key, options),options可傳什么參數?
- value:給target[key]設置初始值
- get:調用target[key]時觸發
- set:設置target[key]時觸發
- writable:規定target[key]是否可被重寫,默認false
- enumerable:規定了key是否會出現在target的枚舉屬性中,默認為false
- configurable:規定了能否改變options,以及刪除key屬性,默認false,具體詳細請看Object.defineProperty函數的configurable配置
22、什么是防抖?什么是節流?
操作 | 描述 | 場景 |
---|---|---|
防抖 | 頻繁去觸發一個事件,但是只觸發最后一次。以最后一次為准 | 1、電腦息屏時間,每動一次電腦又重新計算時間 2、input框變化頻繁觸發事件可加防抖 3、頻繁點擊按鈕提交表單可加防抖 |
節流 | 頻繁去觸發一個事件,但是只能每隔一段時間觸發一次 | 1、滾動頻繁請求列表可加節流 2、游戲里長按鼠標,但是動作都是每隔一段時間做一次 |
23、什么是高階函數?簡單實現一個?
高階函數:英文叫Higher-order function。JavaScript的函數其實都指向某個變量。既然變量可以指向函數,函數的參數能接收變量,那么一個函數就可以接收另一個函數作為參數,這種函數就稱之為高階函數。
// 簡單的高階函數 function add(x, y, f) { return f(x) + f(y); } //用代碼驗證一下: add(-5, 6, Math.abs); // 11
像數組的map、reduce、filter
這些都是高階函數
24、什么是函數柯里化?簡單實現一個?
柯里化,英語:Currying(果然是滿滿的英譯中的既視感),是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,並且返回接受余下的參數而且返回結果的新函數的技術。
// 普通的add函數 function add(x, y) { return x + y } // Currying后 function curryingAdd(x) { return function (y) { return x + y } } add(1, 2) // 3 curryingAdd(1)(2) // 3
好處
- 1、參數復用
// 正常正則驗證字符串 reg.test(txt) // 普通情況 function check(reg, txt) { return reg.test(txt) } check(/\d+/g, 'test') //false check(/[a-z]+/g, 'test') //true // Currying后 function curryingCheck(reg) { return function(txt) { return reg.test(txt) } } var hasNumber = curryingCheck(/\d+/g) var hasLetter = curryingCheck(/[a-z]+/g) hasNumber('test1') // true hasNumber('testtest') // false hasLetter('21212') // false
- 2、延遲執行
其實Function.prototype.bind
就是科里化的實現例子
function sayKey(key) { console.log(this[key]) } const person = { name: 'Sunshine_Lin', age: 23 } // call不是科里化 sayKey.call(person, 'name') // 立即輸出 Sunshine_Lin sayKey.call(person, 'age') // 立即輸出 23 // bind是科里化 const say = sayKey.bind(person) // 不執行 // 想執行再執行 say('name') // Sunshine_Lin say('age') // 23
25、什么是compose?簡單實現一個?
簡單的compose函數
const compose = (a , b) => c => a( b( c ) );
例子:統計單詞個數
const space = (str) => str.split(' ') const len = (arr) => arr.length // 普通寫法 console.log(len(space('i am linsanxin'))) // 3 console.log(len(space('i am 23 year old'))) // 6 console.log(len(space('i am a author in juejin'))) // 7 // compose寫法 const compose = (...fn) => value => { return fn.reduce((value, fn) => { return fn(value) }, value) } const computedWord = compose(space, len) console.log(computedWord('i am linsanxin')) // 3 console.log(computedWord('i am 23 year old')) // 6 console.log(computedWord('i am a author in juejin')) // 7
26、箭頭函數與普通函數的區別?
- 1、箭頭函數不可作為構造函數,不能使用new
- 2、箭頭函數沒有自己的this
- 3、箭頭函數沒有arguments對象
- 4、箭頭函數沒有原型對象
27、Symbol的應用場景?
應用場景1:使用Symbol來作為對象屬性名
平常我們對象的屬性都是字符串
const obj = { name: 'Sunshine_Lin', age: 23 } console.log(obj['name']) // 'Sunshine_Lin' console.log(obj['age']) // 23
其實也可以用Symbol來當做屬性名
const gender = Symbol('gender') const obj = { name: 'Sunshine_Lin', age: 23, [gender]: '男' } console.log(obj['name']) // 'Sunshine_Lin' console.log(obj['age']) // 23 console.log(obj[gender]) // 男
但是Symbol作為屬性的屬性不會被枚舉出來,這也是JSON.stringfy(obj)
時,Symbol屬性會被排除在外的原因
console.log(Object.keys(obj)) // [ 'name', 'age' ] for(const key in obj) { console.log(key) // name age } console.log(JSON.stringify(obj)) // {"name":"Sunshine_Lin","age":23}
其實想獲取Symbol屬性也不是沒辦法。
// 方法一 console.log(Object.getOwnPropertySymbols(obj)) // [ Symbol(gender) ] // 方法二 console.log(Reflect.ownKeys(obj)) // [ 'name', 'age', Symbol(gender) ]
應用場景2:使用Symbol來替代常量
有以下場景
// 賦值 const one = 'oneXin' const two = 'twoXin' function fun(key) { switch (key) { case one: return 'one' break; case two: return 'two' break; } }
如果變量少的話還好,但是變量多的時候,賦值命名很煩,可以利用Symbol的唯一性
const one = Symbol() const two = Symbol()
應用場景3:使用Symbol定義類的私有屬性
以下例子,PASSWORD屬性無法在實例里獲取到
class Login { constructor(username, password) { const PASSWORD = Symbol() this.username = username this[PASSWORD] = password } checkPassword(pwd) { return this[PASSWORD] === pwd } } const login = new Login('123456', 'hahah') console.log(login.PASSWORD) // 報錯 console.log(login[PASSWORD]) // 報錯 console.log(login[PASSWORD]) // 報錯
28、AMD 和 CMD 的區別?
模塊化 | 代表應用 | 特點 |
---|---|---|
AMD | require.js | 1、AMD的api默認一個當多個用 2、依賴前置,異步執行 |
CMD | sea.js | 1、CMD的api嚴格區分,推崇職責單一 2、依賴就近,按需加載,同步執行 |
29、Commonjs 和 ES6 Module的區別
取自阿里巴巴淘系技術前端團隊
的回答:
- 1、Commonjs是拷貝輸出,ES6模塊化是引用輸出
- 2、Commonjs是運行時加載,ES6模塊化是編譯時輸出接口
- 3、Commonjs是單個值導出,ES6模塊化可以多個值導出
- 4、Commonjs是動態語法可寫在函數體中,ES6模塊化靜態語法只能寫在頂層
- 5、Commonjs的this是當前模塊化,ES6模塊化的this是undefined
30、為什么Commonjs不適用於瀏覽器
var math = require('math'); math.add(2, 3);
第二行math.add(2, 3),在第一行require('math')之后運行,因此必須等math.js加載完成。也就是說,如果加載時間很長,整個應用就會停在那里等。
這對服務器端不是一個問題,因為所有的模塊都存放在本地硬盤,可以同步加載完成,等待時間就是硬盤的讀取時間。但是,對於瀏覽器,這卻是一個大問題,因為模塊都放在服務器端,等待時間取決於網速的快慢,可能要等很長時間,瀏覽器處於"假死"狀態。
因此,瀏覽器端的模塊,不能采用"同步加載"(synchronous),只能采用"異步加載"(asynchronous)。這就是AMD規范誕生的背景。
31、常用的ES6-ES12的語法有哪些?
請看我這篇文章基礎很好?總結了38個ES6-ES12的開發技巧,倒要看看你能拿幾分?【閱讀量:4w,點贊:1.8k】
32、(a == 1 && a == 2 && a == 3) 有可能是 true 嗎?
請看我這篇文章(a == 1 && a == 2 && a == 3) 有可能是 true 嗎?
33、函數的length是多少?
請看我這篇文章95%的人都回答不上來的問題:函數的length是多少?
35、JS中的 MUL 函數
MUL表示數的簡單乘法。在這種技術中,將一個值作為參數傳遞給一個函數,而該函數將返回另一個函數,將第二個值傳遞給該函數,然后重復繼續。例如:xyz可以表示為
const mul = x => y => z => x * y * z console.log(mul(1)(2)(3)) // 6
36、深度遍歷廣度遍歷的區別?
對於算法來說 無非就是時間換空間 空間換時間
- 1、深度優先不需要記住所有的節點, 所以占用空間小, 而廣度優先需要先記錄所有的節點占用空間大
- 2、深度優先有回溯的操作(沒有路走了需要回頭)所以相對而言時間會長一點
- 3、深度優先采用的是堆棧的形式, 即先進后出
- 4、廣度優先則采用的是隊列的形式, 即先進先出
37、JS中的設計模式有哪些?
推薦這篇文章:JavaScript設計模式【閱讀:4.4w,點贊:1250】
38、forEach如何跳出循環?
forEach是不能通過break
或者return
來實現跳出循環的,為什么呢?實現過forEach的同學應該都知道,forEach的的回調函數形成了一個作用域,在里面使用return
並不會跳出,只會被當做continue
那怎么跳出循環呢?可以利用try catch
function getItemById(arr, id) { var item = null; try { arr.forEach(function (curItem, i) { if (curItem.id == id) { item = curItem; throw Error(); } }) } catch (e) { } return item; }
39、JS中如何將頁面重定向到另一個頁面?
-
1、使用 location.href:window.location.href =“www.onlineinterviewquestions.com/”
-
2、使用 location.replace: window.location.replace(" www.onlineinterviewquestions.com/;");
40、實現一遍常用的JS原生方法?
推薦我這篇:3小時實現了這30個JS原生方法【閱讀:1.2w,點贊:488】
41、鼠標事件有哪些?
注明:鼠標左中右鍵看
event
對象上的button
屬性,對應1、2、3
事件 | 說明 |
---|---|
click | 單機鼠標左鍵觸發,右鍵無效,當用戶焦點在按鈕並按下Enter,也會觸發 |
dbclick | 雙擊鼠標左鍵觸發,右鍵無效 |
mousedown | 單機鼠標任意一個按鍵都觸發 |
mouseout | 鼠標指針位於某個元素上且將要移出元素邊界時觸發 |
mouseover | 鼠標指針移出某個元素到另一個元素上時觸發 |
mouseup | 鼠標指針移出某個元素到另一個元素上時觸發 |
mouseover | 松開任意鼠標按鍵時觸發 |
mousemove | 鼠標在某個元素上時持續發生 |
mouseenter | 鼠標進入某個元素邊界時觸發 |
mouseleave | 鼠標離開某個元素邊界時觸發 |
42、鍵盤事件有哪些?
注明:
event
對象上的keyCode
屬性,是按下的按鍵的ASCLL值
,通過這個值可辨別是按下哪個按鍵。ASCLL
表在此ASCII碼一覽表,ASCII碼對照表
事件 | 說明 |
---|---|
onkeydown | 某個鍵盤按鍵被按下時觸發 |
onkeyup | 某個鍵盤按鍵被松開時觸發 |
onkeypress | 某個按鍵被按下時觸發,不監聽功能鍵,如ctrl,shift |
43、JS中鼠標事件的各個坐標?
屬性 | 說明 | 兼容性 |
---|---|---|
offsetX | 以當前的目標元素左上角為原點,定位x軸坐標 | 除Mozilla外都兼容 |
offsetY | 以當前的目標元素左上角為原點,定位y軸坐標 | 除Mozilla外都兼容 |
clientX | 以瀏覽器可視窗口左上角為原點,定位x軸坐標 | 都兼容 |
clientY | 以瀏覽器可視窗口左上角為原點,定位y軸坐標 | 都兼容 |
pageX | 以doument對象左上角為原點,定位x軸坐標 | 除IE外都兼容 |
pageY | 以doument對象左上角為原點,定位y軸坐標 | 除IE外都兼容 |
screenX | 以計算機屏幕左上頂角為原點,定位x軸坐標(多屏幕會影響) | 全兼容 |
screenY | 以計算機屏幕左上頂角為原點,定位y軸坐標 | 全兼容 |
layerX | 最近的絕對定位的父元素(如果沒有,則為 document 對象)左上頂角為元素,定位 x 軸坐標 | Mozilla 和 Safari |
layerY | 最近的絕對定位的父元素(如果沒有,則為 document 對象)左上頂角為元素,定位 y 軸坐標 | Mozilla 和 Safari |
44、JS中元素視圖的各個尺寸?
屬性 | 說明 |
---|---|
offsetLeft | 獲取當前元素到定位父節點的left方向的距離 |
offsetTop | 獲取當前元素到定位父節點的top方向的距離 |
offsetWidth | 獲取當前元素 width + 左右padding + 左右border-width |
offsetHeight | 獲取當前元素 height + 上下padding + 上下border-width |
clientWidth | 獲取當前元素 width + 左右padding |
clientHeight | 獲取當前元素 height + 上下padding |
scrollWidth | 當前元素內容真實的寬度,內容不超出盒子寬度時為盒子的clientWidth |
scrollHeight | 當前元素內容真實的高度,內容不超出盒子高度時為盒子的clientHeight |
45、Window視圖的各個尺寸?
屬性 | 說明 |
---|---|
innerWidth | innerWidth 瀏覽器窗口可視區寬度(不包括瀏覽器控制台、菜單欄、工具欄) |
innerHeight | innerWidth 瀏覽器窗口可視區高度(不包括瀏覽器控制台、菜單欄、工具欄) |
46、Document文檔視圖的各個尺寸?
屬性 | 說明 |
---|---|
document.documentElement.clientWidth | 瀏覽器窗口可視區寬度(不包括瀏覽器控制台、菜單欄、工具欄、滾動條) |
document.documentElement.clientHeight | 瀏覽器窗口可視區高度(不包括瀏覽器控制台、菜單欄、工具欄、滾動條) |
document.documentElement.offsetHeight | 獲取整個文檔的高度(包含body的margin) |
document.body.offsetHeight | 獲取整個文檔的高度(不包含body的margin) |
document.documentElement.scrollTop | 返回文檔的滾動top方向的距離(當窗口發生滾動時值改變) |
document.documentElement.scrollLeft | 返回文檔的滾動left方向的距離(當窗口發生滾動時值改變) |
9個高級的JavaScript方法
1. getBoundingClientRect
1.1 是什么
Element.getBoundingClientRect()
方法返回元素的大小及其相對於視口的位置。返回的是一個對象,對象里有這8個屬性:left,right,top,bottom,width,height,x,y
1.2 兼容性
基本在每一個瀏覽器都可以使用getBoundingClientRect
1.3 判斷元素是否在可視區域
這是getBoundingClientRect
最常應用的場景了,判斷一個元素是否完整出現在視口里
// html <div id="box"></div> body { height: 3000px; width: 3000px; } #box { width: 300px; height: 300px; background-color: red; margin-top: 300px; margin-left: 300px; } // js const box = document.getElementById('box') window.onscroll = function () { // box完整出現在視口里才會輸出true,否則為false console.log(checkInView(box)) } function checkInView(dom) { const { top, left, bottom, right } = dom.getBoundingClientRect() console.log(top, left, bottom, right) console.log(window.innerHeight, window.innerWidth) return top >= 0 && left >= 0 && bottom <= (window.innerHeight || document.documentElement.clientHeight) && right <= (window.innerWidth || document.documentElement.clientWidth) }
根據這個用處,咱們可以實現:懶加載和無限滾動
1.4 缺點?
- 1、每次scroll都得重新計算,性能耗費大
- 2、引起
重繪回流
2. IntersectionObserver
2.1 是什么
IntersectionObserver
接口 提供了一種異步觀察目標元素與其祖先元素或頂級文檔視窗(viewport)交叉狀態的方法。祖先元素與視窗(viewport)被稱為根(root)
通俗點說就是:IntersectionObserver
是用來監聽某個元素與視口的交叉狀態
的。交叉狀態是什么呢?請看下圖,一開始整個元素都在視口內,那么元素與視口的交叉狀態就是100%,而我往下滾動,元素只有一半顯示在視口里,那么元素與視口的交叉狀態為50%:
2.2 用法
// 接收兩個參數 callback option var io = new IntersectionObserver(callback, option); // 開始觀察(可觀察多個元素) io.observe(document.getElementById('example1')); io.observe(document.getElementById('example2')); // 停止觀察某個元素 io.unobserve(element); // 關閉觀察器 io.disconnect();
2.3 callback
callback
一般有兩種觸發情況。一種是目標元素剛剛進入視口(可見),另一種是完全離開視口(不可見)。
var io = new IntersectionObserver( entries => { console.log(entries); } );
callback
函數的參數(entries
)是一個數組,每個成員都是一個IntersectionObserverEntry
對象。舉例來說,如果同時有兩個被觀察的對象的可見性發生變化,entries
數組就會有兩個成員。
2.4 IntersectionObserverEntry對象
{
time: 3893.92, rootBounds: ClientRect { bottom: 920, height: 1024, left: 0, right: 1024, top: 0, width: 920 }, boundingClientRect: ClientRect { // ... }, intersectionRect: ClientRect { // ... }, intersectionRatio: 0.54, target: element }
屬性解析:
time
:可見性發生變化的時間,是一個高精度時間戳,單位為毫秒target
:被觀察的目標元素,是一個 DOM 節點對象rootBounds
:根元素的矩形區域的信息,getBoundingClientRect()
方法的返回值,如果沒有根元素(即直接相對於視口滾動),則返回null
boundingClientRect
:目標元素的矩形區域的信息intersectionRect
:目標元素與視口(或根元素)的交叉區域的信息intersectionRatio
:目標元素的可見比例,即intersectionRect
占boundingClientRect
的比例,完全可見時為1
,完全不可見時小於等於0
2.5 option
講講第二個參數option里比較重要的兩個屬性:threshold和root
首先講講threshold
:
threshold
屬性決定了什么時候觸發回調函數。它是一個數組,每個成員都是一個門檻值,默認為[0]
,即交叉比例(intersectionRatio
)達到0
時觸發回調函數。
new IntersectionObserver(
entries => {/* ... */},
{
threshold: [0, 0.25, 0.5, 0.75, 1]
}
);
用戶可以自定義這個數組。比如,[0, 0.25, 0.5, 0.75, 1]
就表示當目標元素 0%、25%、50%、75%、100% 可見時,會觸發回調函數。
再說說root
:
IntersectionObserver API 支持容器內滾動。root
屬性指定目標元素所在的容器節點(即根元素)。注意,容器元素必須是目標元素的祖先節點。
new IntersectionObserver(
entries => {/* ... */},
{
threshold: [0, 0.25, 0.5, 0.75, 1],
root: document.getElementById('#container')
}
);
2.6 完整例子
body {
height: 3000px; width: 3000px; } #box1 { width: 300px; height: 300px; background-color: red; margin-top: 100px; margin-left: 300px; } #box2 { width: 300px; height: 300px; background-color: red; margin-top: 100px; margin-left: 300px; } <div id="box1"></div> <div id="box2"></div> const io = new IntersectionObserver(entries => { console.log(entries) }, { threshold: [0, 0.25, 0.5, 0.75, 1] // root: xxxxxxxxx }) io.observe(document.getElementById('box1')) io.observe(document.getElementById('box2'))
2.7 使用場景
- 1、可以像
getBoundingClientRect
那樣判斷元素是否在視口里,並且好處是,不會引起重繪回流 - 2、同理,有了第一點功能,就可以做
懶加載和無限滾動
功能了
2.8 缺點
想兼容IE的就別考慮這個API了。。。
3. createNodeIterator
3.1 結識這個API
我是怎么認識這個API的呢?我面試的時候被問到了:說一說,如何遍歷輸出頁面中的所有元素
。我第一時間肯定想到使用循環遞歸去輸出。面試官:行吧,回家等消息吧。
后來我回家一查,才知道了createNodeIterator
這個API
3.2 解題
那如何使用createNodeIterator
對頁面中所有元素進行遍歷輸出呢?
const body = document.getElementsByTagName('body')[0] const it = document.createNodeIterator(body) let root = it.nextNode() while(root) { console.log(root) root = it.nextNode() }
找個網站測試下:
3.3 詳細參數
詳細參數可以看這里,講的很詳細
3.4 兼容性
一片綠啊,大膽放心使用吧!
4. getComputedStyle
4.1 是什么
Window.getComputedStyle()
方法返回一個對象,該對象在應用活動樣式表並解析這些值可能包含的任何基本計算后報告元素的所有CSS屬性的值。 私有的CSS屬性值可以通過對象提供的API或通過簡單地使用CSS屬性名稱進行索引來訪問。
window.getComputedStyle(element, pseudoElement)
element
: 必需,要獲取樣式的元素。pseudoElement
: 可選,偽類元素,當不查詢偽類元素的時候可以忽略或者傳入 null。
4.2 使用
搭配getPropertyValue
可以獲取到具體樣式
// html #box { width: 300px; height: 300px; background-color: yellow; } <div id="box"></div> const box = document.getElementById('box') const styles = window.getComputedStyle(box) // 搭配getPropertyValue可以獲取到具體樣式 const height = styles.getPropertyValue("height") const width = styles.getPropertyValue("width") console.log(height, width) // ’300px‘ '300px'
4.3 兼容性
一片綠油油。放心使用。
5. requestAnimationFrame
這篇文章講的不錯,介紹,用法,兼容性,都說的明明白白:requestAnimationFrame理解與實踐
6. requestIdleCallback
這篇文章講的不錯,介紹,用法,兼容性,都說的明明白白:你應該知道的requestIdleCallback
7. DOMContentLoaded
7.1 是什么
當初始的 HTML 文檔被完全加載和解析完成之后,DOMContentLoaded
事件被觸發,而無需等待樣式表、圖像和子框架的完全加載。
這時問題又來了,“HTML 文檔被加載和解析完成”是什么意思呢?或者說,HTML 文檔被加載和解析完成之前,瀏覽器做了哪些事情呢?那我們需要從瀏覽器渲染原理來談談。
瀏覽器向服務器請求到了 HTML 文檔后便開始解析,產物是 DOM(文檔對象模型),到這里 HTML 文檔就被加載和解析完成了。如果有 CSS 的會根據 CSS 生成 CSSOM(CSS 對象模型),然后再由 DOM 和 CSSOM 合並產生渲染樹。有了渲染樹,知道了所有節點的樣式,下面便根據這些節點以及樣式計算它們在瀏覽器中確切的大小和位置,這就是布局階段。有了以上這些信息,下面就把節點繪制到瀏覽器上。所有的過程如下圖所示:
現在你可能了解 HTML 文檔被加載和解析完成前瀏覽器大概做了哪些工作,但還沒完,因為我們還沒有考慮現在前端的主角之一 JavaScript。
JavaScript 可以阻塞 DOM 的生成,也就是說當瀏覽器在解析 HTML 文檔時,如果遇到
<body>
<script type="text/javascript">
console.log(document.getElementById('ele')); // null
</script>
<div id="ele"></div>
<script type="text/javascript">
console.log(document.getElementById('ele')); // <div id="ele"></div>
</script>
</body>
另外,因為 JavaScript 可以查詢任意對象的樣式,所以意味着在 CSS 解析完成,也就是 CSSOM 生成之后,JavaScript 才可以被執行。
到這里,我們可以總結一下。當文檔中沒有腳本時,瀏覽器解析完文檔便能觸發 DOMContentLoaded 事件;如果文檔中包含腳本,則腳本會阻塞文檔的解析,而腳本需要等 CSSOM 構建完成才能執行。在任何情況下,DOMContentLoaded 的觸發不需要等待圖片等其他資源加載完成。
7.2 異步腳本
我們到這里一直在說同步腳本對網頁渲染的影響,如果我們想讓頁面盡快顯示,那我們可以使用異步腳本。HTML5 中定義了兩個定義異步腳本的方法:defer 和 async。我們來看一看他們的區別。
同步腳本(標簽中不含 async 或 defer):
當 HTML 文檔被解析時如果遇見(同步)腳本,則停止解析,先去加載腳本,然后執行,執行結束后繼續解析 HTML 文檔。過程如下圖:
defer 腳本:
當 HTML 文檔被解析時如果遇見 defer 腳本,則在后台加載腳本,文檔解析過程不中斷,而等文檔解析結束之后,defer 腳本執行。另外,defer 腳本的執行順序與定義時的位置有關。過程如下圖:
async 腳本:
當 HTML 文檔被解析時如果遇見 async 腳本,則在后台加載腳本,文檔解析過程不中斷。腳本加載完成后,文檔停止解析,腳本執行,執行結束后文檔繼續解析。過程如下圖:
如果你 Google "async 和 defer 的區別",你可能會發現一堆類似上面的文章或圖片,而在這里,我想跟你分享的是 async 和 defer 對 DOMContentLoaded 事件觸發的影響。
defer 與 DOMContentLoaded
如果 script 標簽中包含 defer,那么這一塊腳本將不會影響 HTML 文檔的解析,而是等到 HTML 解析完成后才會執行。而 DOMContentLoaded 只有在 defer 腳本執行結束后才會被觸發。 所以這意味着什么呢?HTML 文檔解析不受影響,等 DOM 構建完成之后 defer 腳本執行,但腳本執行之前需要等待 CSSOM 構建完成。在 DOM、CSSOM 構建完畢,defer 腳本執行完成之后,DOMContentLoaded 事件觸發。
async 與 DOMContentLoaded
如果 script 標簽中包含 async,則 HTML 文檔構建不受影響,解析完畢后,DOMContentLoaded 觸發,而不需要等待 async 腳本執行、樣式表加載等等。
7.3 DOMContentLoaded 與 load
在回頭看第一張圖:
與標記 1 的藍線平行的還有一條紅線,紅線就代表 load 事件觸發的時間,對應的,在最下面的概覽部分,還有一個用紅色標記的 "Load:1.60s",描述了 load 事件觸發的具體時間。
這兩個事件有啥區別呢?點擊這個網頁你就能明白:testdrive-archive.azu...
解釋一下,當 HTML 文檔解析完成就會觸發 DOMContentLoaded,而所有資源加載完成之后,load 事件才會被觸發。
另外需要提一下的是,我們在 jQuery 中經常使用的 (document).ready(function()//...代碼...);其實監聽的就是DOMContentLoaded事件,而(document).ready(function() { // ...代碼... }); 其實監聽的就是 DOMContentLoaded 事件,而 (document).ready(function()//...代碼...);其實監聽的就是DOMContentLoaded事件,而(document).load(function() { // ...代碼... }); 監聽的是 load 事件。
7.4 使用
document.addEventListener("DOMContentLoaded", function(event) { console.log("DOM fully loaded and parsed"); });
7.5 兼容性
綠油油一片,放心使用
8. MutationObserver
8.1 是什么
MutationObserver
是一個內建對象,它觀察 DOM 元素,並在檢測到更改時觸發回調。
8.2 用法
// 選擇需要觀察變動的節點
const targetNode = document.getElementById('some-id');
// 觀察器的配置(需要觀察什么變動)
const config = { attributes: true, childList: true, subtree: true };
// 當觀察到變動時執行的回調函數
const callback = function(mutationsList, observer) {
// Use traditional 'for loops' for IE 11
for(let mutation of mutationsList) {
if (mutation.type === 'childList') {
console.log('A child node has been added or removed.');
}
else if (mutation.type === 'attributes') {
console.log('The ' + mutation.attributeName + ' attribute was modified.');
}
}
};
// 創建一個觀察器實例並傳入回調函數
const observer = new MutationObserver(callback);
// 以上述配置開始觀察目標節點
observer.observe(targetNode, config);
// 之后,可停止觀察
observer.disconnect();
8.3 config
config
是一個具有布爾選項的對象,該布爾選項表示“將對哪些更改做出反應”:
childList
——node
的直接子節點的更改,subtree
——node
的所有后代的更改,attributes
——node
的特性(attribute),attributeFilter
—— 特性名稱數組,只觀察選定的特性。characterData
—— 是否觀察node.data
(文本內容)
其他幾個選項:
attributeOldValue
—— 如果為true
,則將特性的舊值和新值都傳遞給回調(參見下文),否則只傳新值(需要attributes
選項),characterDataOldValue
—— 如果為true
,則將node.data
的舊值和新值都傳遞給回調(參見下文),否則只傳新值(需要characterData
選項)。
8.4 兼容性
9. Promise.any
9.1 是什么
Promise.any()
接收一個Promise
可迭代對象,只要其中的一個 promise
成功,就返回那個已經成功的 promise
。如果可迭代對象中沒有一個 promise
成功(即所有的 promises
都失敗/拒絕),就返回一個失敗的 promise 和AggregateError
類型的實例,它是 Error 的一個子類,用於把單一的錯誤集合在一起。本質上,這個方法和Promise.all()
是相反的。
9.2 用法(例子)
const promise1 = new Promise((resolve, reject) => {
setTimeout(reject, 100, 'promise 1 rejected');
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 400, 'promise 2 resolved at 400 ms');
});
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 700, 'promise 3 resolved at 800 ms');
});
(async () => {
try {
let value = await Promise.any([promise1, promise2, promise3]);
console.log(value);
} catch (error) {
console.log(error);
}
})();
9.3 兼容性
歡迎關注前端早茶,與廣東靚仔攜手共同進階
前端早茶專注前端,一起結伴同行,緊跟業界發展步伐~