「萬字總結」熬夜總結50個JS的高階知識點,全都會你就是神!!!

Sunshine_Lin發表於2021-11-26

前言

大家好,我是林三心,基礎是進階的前提,上一篇,我給大家分享了我這一年來平時記錄的工作中碰到的50個JS基礎知識點,今天就給大家分享一下,我這一年來,工作中遇到的50個JS高階知識點吧!!!

知識點

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物件的內建屬性都屬於全域性作用域
  • 函式作用域:在固定的程式碼片段才能被訪問

image.png

作用域有上下級關係,上下級關係的確定就看函式是在哪個作用域下建立的。如上,fn作用域下建立了bar函式,那麼“fn作用域”就是“bar作用域”的上級。

作用域最大的用處就是隔離變數,不同作用域下同名變數不會有衝突。

什麼是作用域鏈?

一般情況下,變數取值到 建立 這個變數 的函式的作用域中取值

但是如果在當前作用域中沒有查到值,就會向上級作用域去查,直到查到全域性作用域,這麼一個查詢過程形成的鏈條就叫做作用域鏈

var x = 10;

function fn(){
    console.log(x);
}

function show(f){
    var x = 20;
    f();    // 10 
}

show(fn);

image.png

8、陣列的splice 與 slice 的區別?

方法引數描述
splicesplice(start, num, item1, item2, ...)從start索引開始,擷取num個元素,並插入item1、item2到原陣列裡,影響原陣列
sliceslice(start, end)從start開始,擷取到end - 1,如果沒有end,則擷取到左後一個元素,不影響原陣列

9、substr 和 substring 的區別?

方法引數描述
substrsubstr(start,length)返回從start位置開始length長度的子串
substringsubstring(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,它的狀態只會改變一次並且不可逆

推薦閱讀

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 的區別?

模組化代表應用特點
AMDrequire.js1、AMD的api預設一個當多個用
2、依賴前置,非同步執行
CMDsea.js1、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
    推薦文章CommonJS模組與ES6模組的區別

    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中如何將頁面重定向到另一個頁面?

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檢視的各個尺寸?

屬性說明
innerWidthinnerWidth 瀏覽器視窗可視區寬度(不包括瀏覽器控制檯、選單欄、工具欄)
innerHeightinnerWidth 瀏覽器視窗可視區高度(不包括瀏覽器控制檯、選單欄、工具欄)

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

截圖2021-07-25 下午7.42.59.png

1.2 相容性

基本在每一個瀏覽器都可以使用getBoundingClientRect
截圖2021-07-25 下午7.45.23.png

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%

截圖2021-07-25 下午9.11.41.png

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陣列就會有兩個成員。

截圖2021-07-25 下午9.31.02.png

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:目標元素的可見比例,即intersectionRectboundingClientRect的比例,完全可見時為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了。。。
截圖2021-07-25 下午9.44.42.png

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()
    }

找個網站測試下:

截圖2021-07-25 下午10.07.30.png

3.3 詳細引數

詳細引數可以看這裡,講的很詳細

3.4 相容性

一片綠啊,大膽放心使用吧!
截圖2021-07-25 下午10.08.43.png

4. getComputedStyle

4.1 是什麼

Window.getComputedStyle()方法返回一個物件,該物件在應用活動樣式表並解析這些值可能包含的任何基本計算後報告元素的所有CSS屬性的值。 私有的CSS屬性值可以通過物件提供的API或通過簡單地使用CSS屬性名稱進行索引來訪問。

window.getComputedStyle(element, pseudoElement)
  • element: 必需,要獲取樣式的元素。
  • pseudoElement: 可選,偽類元素,當不查詢偽類元素的時候可以忽略或者傳入 null。

截圖2021-07-25 下午10.23.01.png

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 相容性

一片綠油油。放心使用。
截圖2021-07-25 下午10.33.29.png

5. requestAnimationFrame

這篇文章講的不錯,介紹,用法,相容性,都說的明明白白:requestAnimationFrame理解與實踐

6. requestIdleCallback

這篇文章講的不錯,介紹,用法,相容性,都說的明明白白:你應該知道的requestIdleCallback

7. DOMContentLoaded

7.1 是什麼

當初始的 HTML 文件被完全載入和解析完成之後,DOMContentLoaded 事件被觸發,而無需等待樣式表、影像和子框架的完全載入。

這時問題又來了,“HTML 文件被載入和解析完成”是什麼意思呢?或者說,HTML 文件被載入和解析完成之前,瀏覽器做了哪些事情呢?那我們需要從瀏覽器渲染原理來談談。

瀏覽器向伺服器請求到了 HTML 文件後便開始解析,產物是 DOM(文件物件模型),到這裡 HTML 文件就被載入和解析完成了。如果有 CSS 的會根據 CSS 生成 CSSOM(CSS 物件模型),然後再由 DOM 和 CSSOM 合併產生渲染樹。有了渲染樹,知道了所有節點的樣式,下面便根據這些節點以及樣式計算它們在瀏覽器中確切的大小和位置,這就是佈局階段。有了以上這些資訊,下面就把節點繪製到瀏覽器上。所有的過程如下圖所示:

截圖2021-07-25 下午10.49.44.png

現在你可能瞭解 HTML 文件被載入和解析完成前瀏覽器大概做了哪些工作,但還沒完,因為我們還沒有考慮現在前端的主角之一 JavaScript。

JavaScript 可以阻塞 DOM 的生成,也就是說當瀏覽器在解析 HTML 文件時,如果遇到 <script>,便會停下對 HTML 文件的解析,轉而去處理指令碼。如果指令碼是內聯的,瀏覽器會先去執行這段內聯的指令碼,如果是外鏈的,那麼先會去載入指令碼,然後執行。在處理完指令碼之後,瀏覽器便繼續解析 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。我們來看一看他們的區別。

image.png
同步指令碼(標籤中不含 async 或 defer): <script src="***.js" charset="utf-8"></script>

當 HTML 文件被解析時如果遇見(同步)指令碼,則停止解析,先去載入指令碼,然後執行,執行結束後繼續解析 HTML 文件。過程如下圖:

image.png

defer 指令碼:<script src="***.js" charset="utf-8" defer></script>

當 HTML 文件被解析時如果遇見 defer 指令碼,則在後臺載入指令碼,文件解析過程不中斷,而等文件解析結束之後,defer 指令碼執行。另外,defer 指令碼的執行順序與定義時的位置有關。過程如下圖:

image.png

async 指令碼:<script src="***.js" charset="utf-8" async></script>

當 HTML 文件被解析時如果遇見 async 指令碼,則在後臺載入指令碼,文件解析過程不中斷。指令碼載入完成後,文件停止解析,指令碼執行,執行結束後文件繼續解析。過程如下圖:

image.png

如果你 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 事件觸發的具體時間。

這兩個事件有啥區別呢?點選這個網頁你就能明白:https://testdrive-archive.azu...

解釋一下,當 HTML 文件解析完成就會觸發 DOMContentLoaded,而所有資源載入完成之後,load 事件才會被觸發。

另外需要提一下的是,我們在 jQuery 中經常使用的 $(document).ready(function() { // ...程式碼... }); 其實監聽的就是 DOMContentLoaded 事件,而 $(document).load(function() { // ...程式碼... }); 監聽的是 load 事件。

7.4 使用

document.addEventListener("DOMContentLoaded", function(event) {
      console.log("DOM fully loaded and parsed");
  });

7.5 相容性

綠油油一片,放心使用

截圖2021-07-25 下午11.00.35.png

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 相容性

    截圖2021-07-25 下午11.07.52.png

    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 相容性

截圖2021-07-25 下午11.18.15.png

結語

如果你覺得此文對你有一丁點幫助,點個贊,鼓勵一下林三心哈哈。或者可以加入我的摸魚群,我們一起好好學習啊啊啊啊啊啊啊,我會定時模擬面試,簡歷指導,答疑解惑,我們們互相學習共同進步!!
截圖2021-11-28 上午9.43.19.png

相關文章