2021高頻核心前端面試題總結


@

目錄


HTML

1. 如何理解HTML語義化

HTML5語義化

  • 讓人更容易讀懂(代碼結構清晰,增加代碼的可讀性)
  • 讓搜索引擎更容易讀懂(SEO),搜索引擎爬蟲會根據不同的標簽來賦予不同的權重

語義化標簽 : header nav main article section aside footer

2. 默認情況下,哪些HTML標簽是塊級元素、哪些是內聯元素

  • 塊級元素:display: block/table,有div div h1 h2 table ul ol li p
  • 內聯元素:display: inline/inline-block,有span img input button i b

3. HTML5 新增內容和 API

HTML5 新增內容和 API

  • classList 屬性
  • querySelector() 與 querySelectorAll()
  • getElementsByClassName()
  • 自定義數據屬性
  • 本地存儲
  • insertAdjacentHtml()、insertAdjacentText()、insertAdjacentElement()
  • 內容可編輯
  • 預加載

CSS

1. 盒子模型

CSS盒子模型

box-sizing屬性

CSS盒子模型包含2種:

  • W3C標准盒子模型(box-sizing: content-box ,默認),寬高不受 padding、border影響
  • IE怪異盒子模型 (box-sizing: border-box),寬高受 padding、border影響

2. margin 縱向重疊

  • 相鄰元素的margin-topmargin-bottom會重疊
  • 空內容的元素也會重疊

思考:如下代碼,AAABBB之間的間距是多少 ?

<style>
	p{
		font-size:16px;
		line-height:1;
		margin-top:10px;
		margin-bottom:15px
	}
</style>

<body>
    <p>AAA</p>
    <p></p>
    <p></p>
    <p></p>
    <p>BBB</p>
</body>

答案是 15px
在這里插入圖片描述

3. margin 負值

  • margin-top 負值,元素會上移
  • margin-left 負值,元素會左移
  • margin-right 負值,右側元素會左移,自身不受影響
  • margin-bottom 負值,下方元素會上移,自身不受影響
    在這里插入圖片描述

4. BFC(塊級格式化上下文)

3分鍾理解BFC

具有 BFC 特性的元素可以看作是隔離了的獨立容器,容器里面的元素不會在布局上影響到外面的元素

只要元素滿足下面任一條件即可觸發 BFC 特性:

  1. 根元素(即<html>標簽)
  2. 浮動元素 float 不為 none (為 leftright
  3. 絕對定位元素 position 不為 static 或 relative。(為 absolutefixed
  4. overflow 的值不為 visible 的塊元素(為 autoscrollhidden)
  5. display 的值為inline-blockflexgrid tabletable-celltable-caption...

同一BFC內:

  1. Box會在垂直方向上一個接一個的放置
  2. 垂直方向的距離由margin決定(屬於同一個BFC的兩個相鄰Box的margin會發生重疊,與方向無關)
  3. 每個元素的margin box的左邊, 與包含塊border box的左邊相接觸(對於從左往右的格式化,否則相反)。即使存在浮動也是如此
  4. BFC的區域不會與float的元素區域重疊
  5. 計算BFC的高度時,浮動子元素也參與計算
  6. BFC就是頁面上的一個隔離的獨立容器,容器里面的子元素不會影響到外面元素,反之亦然

應用:

  1. 分屬於不同的BFC時,可以防止margin重疊
  2. 清除內部浮動
  3. 自適應多欄布局

5. float布局

.fl {
    float: left;
}

.fr {
    float: right;
}

.clearfix {
    zoom: 1; // 兼容IE
}

.clearfix:after {
    content: '';
    display: block;
    clear: both;
    visibility: hidden;
    overflow: hidden;
}

6. flex布局

flex布局

flex平分布局

在這里插入圖片描述

7. 三欄布局

實現三欄布局的8種方式:

  1. 浮動布局
  2. 定位布局
  3. flex布局
  4. 表格布局
  5. 網格布局
  6. calc函數布局
  7. 聖杯布局
  8. 雙飛翼布局

面試常考的聖杯布局和雙飛翼布局:

  • 三欄布局,中間一欄最先加載和渲染(內容最重要)
  • 兩側內容固定,中間內容隨着寬度自適應
  • 一般用於PC端

8. CSS定位

css中position屬性詳解

思考:relativeabsolutefixed依據什么定位?

答案:

  • relative依據自身定位(相對的是它原本在文檔流中的位置而進行的偏移)。在最外層時,是以<body>標簽為定位原點的
  • absolute依據最近一層的定位元素定位(根據postionstatic的祖先類元素進行定位)。在無父級是postionstatic定位時,是以<html>作為原點定位
  • fixed根據窗口為原點進行偏移定位 (也就是說它不會根據滾動條的滾動而進行偏移)

9. 居中對齊實現方式

css居中常見方法總結

10. line-height的繼承問題

思考: 以下代碼中p標簽的行高是多少?

<style>
    body {
        font-size: 20px;
        line-height: 200%;
    }

    p {
        font-size: 16px;
    }
</style>

<p>AAA</p>

答案是 40px

在這里插入圖片描述

  • 寫具體數值,如body{ line-height: 30px;},則繼承該值 ( p 的行高就是30px
  • 寫比例,如body{ line-height: 2;} ,則繼承該比例 ( p 的行高就是16px*2 = 32pxp字體大小的2倍
  • 寫百分比(有坑),如body{ line-height: 200%;} ,則繼承計算出來的值 ( p 的行高就是20px*2 = 40pxbody字體大小的2倍

11. CSS長度單位

  1. px 固定的像素,一旦設置了就無法因為適應頁面大小而改變。
  2. em 相對於父元素的長度單位。(不常用)
  3. rem 相對於根<html>元素的長度單位。(常用)
  4. rpx 微信小程序的相對長度單位。小程序規定屏幕寬為750rpx。如在 iPhone6 上,屏幕寬度為375px,共有750個物理像素,則750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。(僅微信小程序)

12. CSS響應式媒體查詢

假如一個終端的分辨率小於 980px,那么可以這樣寫:

@media only screen and (max-width: 980px) {
  #head { … }
  #content { … }
  #footer { … }
}

假如我們要設定兼容 iPad 和 iPhone 的視圖,那么可以這樣設置:

/** iPad **/
@media only screen and (min-width: 768px) and (max-width: 1024px) {}
/** iPhone **/
@media only screen and (min-width: 320px) and (max-width: 767px) {}

媒體查詢一般配合rem單位實現響應式,因此rem具有階梯性的弊端

13. 網頁視口尺寸

  • 屏幕高度 window.screen.height (顯示器屏幕設備高度)
  • 網頁視口高度 window.innerHeight (去掉瀏覽器自身的頭部和底部后的高度,含滾動條高)
  • body高度 document.body.clientHeight (頁面內容的真實高度)

在這里插入圖片描述

神圖
在這里插入圖片描述
寬度同理 略~

14. CSS3 vw / vh

  • vw 網頁視口寬度的1% ( window.innerWidth = 1vw
  • vh 網頁視口高度的1% ( window.innerHeight = 1vh )
  • vmin 選取vwvh中最小的那個
  • vmax 選取vwvh中最大的那個

JS

1. ES6

查看ES6專輯

  1. ES6之 let 和 const 關鍵字

  2. ES6之 解構賦值

  3. ES6之 擴展運算符

  4. ES6之 字符串新特性

  5. ES6之 數值新特性

  6. ES6之 數組新特性

  7. ES6之 函數優化(默認參數、剩余參數、 箭頭函數)

  8. ES6之 Promise

  9. ES6之 async/await

  10. ES6之 class(類)

  11. ES6之 Map和Set

  12. ES6之 import和export

  13. ES6之 Generator 函數

  14. ES6之 for...of 循環

2. 數據類型與檢測

js檢測數據類型的幾種方法總結

JavaScript 數據類型:

  1. Number (基本類型)
  2. String (基本類型)
  3. Boolean (基本類型)
  4. null (基本類型)
  5. undefined (基本類型)
  6. symbol (ES6 - 基本類型)
  7. bigInt (ES10 - 基本類型)
  8. object (引用類型,包含 function、[ ]、{ })

基本類型的特點:直接存儲在棧(stack)內存中的數據
引用類型的特點:存儲的是該對象在棧中引用,真實的數據存放在堆(heap)內存中

在這里插入圖片描述

3. 深拷貝和淺拷貝

JavaScript中的深拷貝和淺拷貝

深拷貝和淺拷貝最根本的區別在於是否是真正獲取了一個對象的拷貝實體,而不是引用

淺拷貝只拷貝一層對象的屬性,而深拷貝則遞歸拷貝了所有層級。

  • 深拷貝在計算機中開辟了一塊新的內存地址用於存放拷貝的對象
  • 淺拷貝僅僅是指向被拷貝的內存地址,如果原地址中對象被改變了,那么淺拷貝出來的對象也會相應改變

在這里插入圖片描述

4. 原型與原型鏈(三座大山之一)

深入理解javascript之原型和原型鏈

prototype(顯式原型)

所有函數(僅限函數)擁有 prototype 屬性

prototype 對象用於放某同一類型實例的共享屬性和方法,實質上是為了內存着想。

Person.prototype.sayHello = function() {
    console.log('Hello!')
}
console.log(person1.sayHello === person2.sayHello) // true,同一個方法

在這里插入圖片描述


_proto _ (隱式原型)

所有對象擁有 _proto _ 屬性

_proto _ 指向誰?分以下三種情況:

/*1、字面量方式*/
var a = {};
console.log(a.constructor === Object); // true (即構造器Object)
console.log(a.__proto__ === a.constructor.prototype); // true
console.log(a.__proto__ === Object.prototype); // true

/*2、構造器方式*/
var A = function (){}; 
var a = new A();
console.log(a.constructor === A); // true(即構造器function A)
console.log(a.__proto__ === a.constructor.prototype); // true

/*3、Object.create()方式*/
var a1 = {a:1} 
var a2 = Object.create(a1);
console.log(a2.constructor === Object); // true  (即構造器Object)
console.log(a2.__proto__ === a1); // true 
console.log(a2.__proto__ === a2.constructor.prototype); //false(此處即為圖1中的例外情況)

在這里插入圖片描述


constructor ( 指向創建該對象的構造函數)

每個原型對象都可以通過對象.constructor 指向創建該對象的構造函數

function Person() {};
var person1 = new Person();
var person2 = new Person();

// 實例化對象的constructor屬性指向構造函數本身
person1.constructor === Person; 

// 構造函數的prototype屬性有個constructor屬性,指向構造函數本身
Person.prototype.constructor === Person;

// 由以上兩條得出
person1.constructor === Person.prototype.constructor

person1.__proto__ === Person.prototype

Person.constructor === Function; 
Function.constructor === Function; 

在這里插入圖片描述


原型鏈

在這里插入圖片描述

a.__proto__ === A.prototype
A.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null

下圖中由相互關聯的原型組成的鏈狀結構就是原型鏈,也就是藍色的這條線。
在這里插入圖片描述


原型與原型鏈的終極圖

這個圖要是看懂了,原型與原型鏈就基本摸清了。
在這里插入圖片描述


instanceof 原理

instanceof 只能用來判斷對象類型,原始類型不可以。並且所有對象類型 instanceof Object 都是 true

instanceof 的內部機制是通過判斷對象的原型鏈中是不是能找到類型的 prototype

class People {};
class Student extends People {};

let s1 = new Student();

console.log(s1 instanceof Student); // true
console.log(s1 instanceof People);  // true
console.log(s1 instanceof Object);  // true

console.log(s1.__proto__ === Student.prototype); // true
console.log(Student.prototype.__proto__ === People.prototype); // true
console.log(People.prototype.__proto__ === Object.prototype); // true

s1.__proto__ === Student.prototype                =>   s1 instanceof Student
Student.prototype.__proto__ === People.prototype  =>   Student.prototype instanceof People
People.prototype.__proto__ === Object.prototype   =>   People.prototype instanceof Object

Instanceof的判斷隊則是:沿着s1的__proto__這條線來找,同時沿着Student的prototype這條線來找,如果兩條線能找到同一個引用,即同一個對象,那么就返回true。如果找到終點還未重合,則返回false。這就很好地解釋了上述代碼的輸出結果啦。


繼承方式

JavaScript中的繼承方式

5. 作用域、this 和閉包 (三座大山之二)

作用域

在 Javascript 中,作用域分為 全局作用域函數作用域

全局作用域
代碼在程序的任何地方都能被訪問,window 對象的內置屬性都擁有全局作用域。

函數作用域
在固定的代碼片段才能被訪問
在這里插入圖片描述
作用域有上下級關系,上下級關系的確定就看函數是在哪個作用域下創建的。如上,fn作用域下創建了bar函數,那么“fn作用域”就是“bar作用域”的上級。

作用域最大的用處就是隔離變量不同作用域下同名變量不會有沖突


作用域鏈

ES6之塊級作用域

ES6之let關鍵字

變量取值:創建這個變量的函數的作用域中向上取值,而不是調用這個函數時向上取值
如果在當前作用域中沒有查到值,就會向上級作用域去查,直到查到全局作用域,這么一個查找過程形成的鏈條就叫做作用域鏈

思考:以下代碼輸出什么?

function create() {
    const a = 100;
    return function () {
        console.log(a);
    }
}
const fn = create();
const a = 200;
fn();  // 100
function print(fn) {
    const a = 200;
    fn();
}
const a = 100;
function fn() {
    console.log(a);
}
print(fn); // 100

創建的函數向上取值,而不是調用函數時向上取值


this

徹底理解JavaScript中this指向

this永遠指向的是最后調用它的對象,也就是看它執行的時候是誰調用的

特別注意:

  • 匿名函數的自我執行,沒有被上級對象調用,所以this指向window
  • setTimeout(function () { console.log(this) }); ,this指向window
  • setTimeout(() => { console.log(this) }); ,this指向上下文
  • 構造函數中的this,指向實例對象
  • bindcallapply 可以改變 this 指向

JavaScript中call,apply,bind方法的總結


閉包

JavaScript閉包

閉包就是指有權訪問另一個函數作用域中的變量的函數。

創建閉包的最常見的方式就是在一個函數內創建另一個函數,通過另一個函數訪問這個函數的局部變量

閉包的特性:

  1. 函數嵌套函數
  2. 函數內部可以引用外部的參數和變量
  3. 參數和變量不會被垃圾回收機制回收
function aaa() {  
    var a = 1;  
    return function(){
        alert(a++)
    };  
}         
var fun = aaa();  
fun();// 1 執行后 a++,,然后a還在~   a會長期駐扎在內存中
fun();// 2   
fun = null;//a被回收!! 

6. 異步 (三座大山之三)

單線程與多線程

  • JavaScript是單線程語言(可以說這是JavaScript最核心也是最基本的特性)
  • 瀏覽器的內核是多線程的

雖然JavaScript是單線程的,可是瀏覽器內部不是單線程的。
一些I/O操作、定時器的計時和事件監聽(click, keydown...)等都是由瀏覽器提供的其他線程來完成的。


同步與異步

  • 同步:是指在主線程上排隊執行的任務,只有前一個任務執行完畢,才能繼續執行下一個任務。
    當我們打開網站時,網站的渲染過程,比如元素的渲染,其實就是一個同步任務

  • 異步:是指不進入主線程,而進入任務隊列的任務,只有任務隊列通知主線程,某個異步任務可以執行了,該任務才會進入主線程。
    當我們打開網站時,像圖片加載等網絡請求(ajax、axios)、定時任務(setTimeout),其實就是一個異步任務

console.log(1);
alert(2); // 同步,會阻塞代碼的執行
console.log(3);
setTimeout(function(){
    console.log(1); // 異步,不會阻塞代碼的執行
},100)

console.log(2);

事件循環(Event Loop)

淺談事件循環Event Loop

事件循環機制:

  1. 首先判斷JS是同步還是異步,同步就進入主線程,異步就進入event table
  2. 異步任務在event table中注冊函數,當滿足觸發條件后,被推入消息隊列event queue
  3. 同步任務進入主線程后一直執行,直到主線程空閑時,才會去消息隊列中查看是否有可執行的異步任務,如果有就推入主線程中

在這里插入圖片描述

異步任務又可以分為:

  • macrotask(宏任務)
    等待執行棧和微任務隊列都執行完畢才會執行,並且在執行完每一個宏任務之后,會去看看微任務隊列有沒有新添加的任務,如果有,會先將微任務隊列中的任務清空,才會繼續執行下一個宏任務
    包括:script代碼塊,setTimeout,setInterval,I/O

  • microtask(微任務)
    當執行棧中的代碼執行完畢,會在執行宏任務隊列之前先看看微任務隊列中有沒有任務,如果有會先將微任務隊列中的任務清空才會去執行宏任務隊列
    包括:Promise,nextTick,callback,Object.observe,MutationObserver

執行的順序是 執行棧中的代碼 => 微任務 => 宏任務 => 微任務 => 宏任務 => ...

DOM事件也是基於Event Loop,但不是異步

異步任務的執行也是有先后順序的:

  1. 執行一個宏任務,過程中如果遇到微任務,就將其放到微任務的【事件隊列】里
  2. 當前宏任務執行完成后,會查看微任務的【事件隊列】,並將里面全部的微任務依次執行完

在這里插入圖片描述


Promise

徹底掌握 Promise

手寫一個Promise

Promise 是異步編程的一種解決方案,有三種狀態:

  • pending (等待態)
  • fulfiled (成功態)
  • rejected (失敗態)

一旦 Promiseresolvereject,不能再遷移至其他任何狀態(即狀態 immutable)。創造 promise 實例后,它會立即執行。

在這里插入圖片描述
基本過程:

  1. 初始化 Promise 狀態(pending);
  2. 立即執行 Promise 中傳入的 fn 函數,將Promise 內部 resolvereject 函數作為參數傳遞給 fn ,按事件機制時機處理;
  3. 執行 then(…) 注冊回調處理數組(then 方法可被同一個 promise 調用多次);
  4. Promise 里的關鍵是要保證,then方法傳入的參數 onFulfilledonRejected,必須在then方法被調用的那一輪事件循環之后的新執行棧中執行;

簡單用法:

let p = new Promise((resolve, reject) => {
    var num = Math.ceil(Math.random() * 10); //生成1-10的隨機數
    if (num <= 5) {
        resolve(num);
    } else {
        reject('數字太大了');
    }
})

// then的用法
p.then((data) => {
    console.log('resolve:' + data);
}, (err) => {
    console.log('reject:' + err);
})

// catch的用法
p.then((data) => { console.log('resolve:' + data); })
 .catch((err) => { console.log('reject:' + err); })

async與await

細說 async/await

核心:

  1. 執行 async 函數,默認返回一個 promise 對象
  2. await 相當於 promise 的 then
  3. try...catch 可捕獲異常,代替了 promise 的 catch
function dice(val) {
    return new Promise((resolve, reject) => {
        let sino = parseInt(Math.random() * 6 + 1);
        if (sino > 3) {
            val === '大' ? resolve(sino) : reject(sino);
        } else {
            val === '大' ? reject(sino) : resolve(sino);
        }
    })
}
async function test() {
	// try...catch 可捕獲異常,代替了 Promise 的 catch 
    try {
        //把await及獲取它的值的操作放在try里
        let n = await dice('大');  // await 相當於 Promise 的 then
        console.log('贏了' + n);
    } catch (error) {
        //失敗的操作放在catch里
        console.log('輸了' + error); // 相當於 Promise 的 catch
    }
}
test();

思考:以下代碼輸出順序

async function async1() {
    console.log(1); // 同步2
    await async2(); // 先執行async2(),再await 
    console.log(2); // 異步(await下面所有的代碼都是異步)
}

async function async2() {
    console.log(3); // 同步3
}

console.log(4); // 同步1

setTimeout(() => {
    console.log(5); // 異步2 宏任務
}, 0);

async1();

console.log(6); // 同步4

答案

4
1
3
6
2
5

await 下面所有的代碼都是異步


異步加載JS方式

1. 匿名函數自調動態創建script標簽加載js

(function(){
    var scriptEle = document.createElement("script");
    scriptEle.type = "text/javasctipt";
    scriptEle.async = true;
    scriptEle.src = "http://cdn.bootcss.com/jquery/3.0.0-beta1/jquery.min.js";
    var x = document.getElementsByTagName("head")[0];
    x.insertBefore(scriptEle, x.firstChild);		
 })();

2. async屬性

// async屬性規定一旦加載腳本可用,則會異步執行
<script type="text/javascript" src="xxx.js" async="async"></script>

3. defer屬性

// defer屬性規定是否對腳本執行進行延遲,直到頁面加載為止
<script type="text/javascript" src="xxx.js" defer="defer"></script>

7. DOM 與 BOM

DOM對象和BOM對象

DOM

DOM (Document Object Model)是 W3C 的標准,是指文檔對象模型(樹結構)。
DOM 定義了訪問和操作 HTML 文檔的標准方法。通過它,可以訪問HTML文檔的所有元素

1. HTML DOM 樹:
在這里插入圖片描述

2. DOM 節點:

根據 W3C 的 HTML DOM 標准,HTML 文檔中的所有內容都是節點(NODE):

  • 文檔節點:整個文檔(document對象)
  • 元素節點:每個 HTML 元素(element 對象)
  • 文本節點:HTML 元素內的文本(text對象)
  • 屬性節點:每個 HTML 屬性(attribute對象)
  • 注釋節點:注釋(comment對象)

3. DOM 查找:

// 根據標簽名獲取標簽合集
const div1 = document.getElementsByTagName("div"); // div1 div2 div3 div4 div5 (元素集合 HTMLCollection)
const div2 = document.querySelectorAll("div"); // div1 div2 div3 div4 div5 (節點集合 NodeList)

// 根據class屬性獲取
const div3 = document.getElementsByClassName("div"); // div1 div2 (元素集合 HTMLCollection)
const div4 = document.querySelectorAll(".div"); // div1 div2 (節點集合 NodeList)

// 根據id屬性值獲取
const div5 = document.getElementById("div"); // div3 (一個標簽)
const div6 = document.querySelectorAll("#div"); // div3 (節點集合 NodeList)

// 根據name屬性值獲取
const div7 = document.getElementsByName("div"); // div4 div5 (節點集合 NodeList)

// 根據標簽名獲取標第一個
const div8 = document.querySelector("div"); // div1 (一個標簽)

4. DOM 操作:

// 創建節點
var divEle = document.createElement("div");
var pEle = document.createElement("p");
var aEle = document.createElement("a");

// 添加節點
document.body.appendChild(divEle);  // 將上面創建的div元素加入到body的尾部
document.body.insertBefore(pEle, divEle);  // 在body下,將p元素添加到div元素前面

//替換節點
document.body.replaceChild(aEle, pEle);  // 在body下,用a元素替換p元素

//設置文本節點
aEle.innerText = "在干嘛"
divEle .innerHTML = "<p>在干嘛<p/>"

//設置屬性
divEle .setAttribute("class","list"); // 給div元素加上class='list'屬性

//獲取class值
divEle.className // 獲取div元素上的class

// 設置style樣式
divEle.style.color = "red"; // 把div元素的color樣式設置成red
divEle.style.margin = "10px"
divEle.style.width = "10px"
divEle.style.left = "10px"
divEle.style.position = "relative"

5. DOM 優化:

DOM 操作都是代價昂貴的操作,它會導致 WEB 應用程序的 UI 反應遲鈍。所以,應當盡可能減少這類過程的發生。

// 不緩存 DOM 查詢結果
for (let i = 0; i < document.getElementsByTagName("div").length; i++) {
    // 每次循環,都會計算length,頻繁進行 DOM 查詢
}

// 緩存 DOM 查詢結果
const div = document.getElementsByTagName("div");
const length = div.length;
for (let i = 0; i < length; i++) {
    // 只進行一次 DOM 查詢
}

將頻繁的 DOM 操作改成一次性操作:

var el = document.getElementById('mydiv');

// 未優化前的DOM操作,會導致三次重排
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
el.style.padding = '5px';

// 優化后的DOM操作,只會一次重排
el.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px;';

BOM

BOM(Browser Object Model)是指瀏覽器對象模型,可以對瀏覽器窗口進行訪問和操作
使用 BOM,開發者可以移動窗口、改變狀態欄中的文本以及執行其他與頁面內容不直接相關的動作。使 JavaScript 有能力與瀏覽器"對話"。

  1. Window 對象 (window.alert()window.open()window.setTimeout() ...)
  2. Navigator 對象(navigator.userAgent ...)
  3. Screen 對象 (screen.widthscreen.height ...)
  4. Location 對象 (location.hreflocation.reload()location.replace() ...)
  5. History 對象(history.forward()history.back() ...)

8. 事件流

JavaScript 事件流

事件傳播的順序對應瀏覽器的兩種事件流模型:

  • 冒泡型事件流中click事件傳播順序為 <div> => <body> => <html> => document (默認)
  • 捕獲型事件流中click事件傳播順序為 document => <html> => <body> => <div>
<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
	<div id="div">Click me!</div>
	<script>
        document.getElementById("div").addEventListener("click", (event) => {
            console.log('this is div');
        });

        document.body.addEventListener("click", (event) => {
            console.log('this is body');
        });

        document.documentElement.addEventListener("click", (event) => {
            console.log('this is html');
        });

        document.addEventListener("click", (event) => {
            console.log('this is document');
        });

		// 默認是事件捕獲,因此按順序輸出:
		// this is div
		// this is body
		// this is html
		// this is document
    </script>
</body>
</html>

事件捕獲

在這里插入圖片描述

事件冒泡(默認)

在這里插入圖片描述


DOM 標准事件流

在這里插入圖片描述
綁定事件時通過addEventListener函數,它有三個參數,第三個參數若是true,則表示采用事件捕獲,若是false(默認),則表示采用事件冒泡。

<div id="box1">box1
    <div id="box2">box2
        <div id="box3">box3</div>
    </div>
</div>

<script>    
    box1.addEventListener('click', function () {
        console.log('box1 捕獲階段');
    }, true);
    box2.addEventListener('click', function () {
        console.log('box2 捕獲階段');
    }, true);
    box3.addEventListener('click', function () {
        console.log('box3 捕獲階段');
    }, true);
    box1.addEventListener('click', function () {
        console.log('box1 冒泡階段');
    }, false);
    box2.addEventListener('click', function () {
        console.log('box2 冒泡階段');
    }, false);
    box3.addEventListener('click', function () {
        console.log('box3 冒泡階段');
    }, false);
</script>

在這里插入圖片描述
element.addEventListener(event, function, useCapture)

第三個參數useCapture,可選。布爾值,指定事件是否在捕獲或冒泡階段執行:

  • true - 事件句柄在捕獲階段執行
  • false(默認)- 事件句柄在冒泡階段執行

阻止事件冒泡/捕獲

使用 event.stopPropagation() 起到阻止捕獲和冒泡階段中當前事件的進一步傳播。

  • W3C的方法是: event.stopPropagation()
  • IE則是使用: event.cancelBubble = true
p.addEventListener("click", (event) => {
    event.stopPropagation(); // 阻止事件冒泡
    console.log('this is p');  // 只會輸出 'this is p'
});


document.addEventListener("click", (event) => {
    event.stopPropagation(); // 阻止事件捕獲
    console.log('this is document');  // 只會輸出 'this is document'
}, true);

兼容IE的寫法:

window.event? window.event.cancelBubble = true : event.stopPropagation();

阻止默認事件

  • W3C的方法是: event.preventDefault()
  • IE則是使用: event.returnValue = false

既然是說默認行為,當然是元素必須有默認行為才能被取消,如果元素本身就沒有默認行為,調用當然就無效了。

<a href="https://www.baidu.com/" id="a">阻止默認跳轉</a>

<script>
    document.getElementById("a").addEventListener("click", (event) => {
        event.preventDefault();
        console.log('已阻止a鏈接跳轉');
    });
</script>

事件代理/委托

事件代理的原理用到的就是事件冒泡和目標元素,把事件處理器添加到父元素,等待子元素事件冒泡,並且父元素能夠通過target(IE為srcElement)判斷是哪個子元素,從而做相應處理。

<ul id="color-list">
	<li>red</li>
	<li>orange</li>
	<li>yellow</li>
	<li>green</li>
	<li>blue</li>
	<li>indigo</li>
	<li>purple</li>
</ul>

<script>
    // 不使用事件代理
	(function(){
	    var colorList = document.getElementById("color-list");
	    var colors = colorList.getElementsByTagName("li");
	    for (var i = 0; i < colors.length; i++) {
	        colors[i].addEventListener('click', showColor); // 給每個li綁定一個點擊事件
	    };
	
	    function showColor(e) {
	        e = e || window.event;
	        var targetElement = e.target || e.srcElement;
	        console.log(targetElement.innerHTML);
	    }
	})();
	
     // 使用事件代理
	(function(){	
        var colorList = document.getElementById("color-list");
        colorList.addEventListener('click', showColor); // 通過冒泡,只需要給li的父級一個點擊事件

        function showColor(e) {
            e = e || window.event;
            var targetElement = e.target || e.srcElement;
            console.log(targetElement.innerHTML);
        }
	})();
</script>

9. 跨域

JS 跨域詳解

跨域是指從一個域名的網頁去請求另一個域名的資源。比如從 www.baidu.com 頁面去請求 www.google.com 的資源。
非同源,在請求數據時,瀏覽器會在控制台中報一個異常,提示拒絕訪問。它是由瀏覽器的同源策略造成的,是瀏覽器對JavaScript施加的安全限制


同源策略

同源策略是瀏覽器最核心也最基本的安全功能。如果缺少了同源策略,則瀏覽器的正常功能可能都會受到影響。可以說Web是構建在同源策略基礎之上的,瀏覽器只是針對同源策略的一種實現。

  • 同源:協議、域名、端口,三者全部相同,才是同源。
  • 跨域:協議、域名、端口,只要有一個的不同,就是跨域。

不存在跨域的情況(無視同源策略)

  1. 服務端請求服務端不存在跨域(瀏覽器請求服務器才存在同源策略)
  2. <img src="跨域的圖片地址"><img>標簽的 src 屬性不存在跨域)
  3. <link href="跨域的css地址"><link>標簽的 href 屬性不存在跨域)
  4. <script src="跨域的js地址"></script><script>標簽的 src 屬性不存在跨域)

常見的幾種跨域方法

  1. jsonp 跨域 (動態添加<script>標簽,利用src屬性跨域。 常用)
  2. CORS 跨域資源共享(由服務端實現。 常用且主流)
  3. node 代理跨域(利用proxyTable使本地的node服務器代理請求真正的服務器。 常用)
  4. document.domain + iframe 跨域
  5. postMessage 跨域

安全

  1. XSS 跨站腳本攻擊
  2. CSRF 跨站請求偽造

10. HTTP

淺談 HTTP

HTTP協議是一個基於 TCP/IP 通信協議來傳遞數據(HTML 文件, 圖片文件, 查詢結果等),用於從服務器傳輸超文本到本地瀏覽器的傳送協議。

TCP的三次握手四次揮手


HTTP三大特點

  • HTTP是無連接的:無連接的含義是限制每次連接只處理一個請求。服務器處理完客戶的請求,並收到客戶的應答后,即斷開連接。采用這種方式可以節省傳輸時間。
  • HTTP是媒體獨立的:這意味着,只要客戶端和服務器知道如何處理的數據內容,任何類型的數據都可以通過HTTP發送。客戶端以及服務器指定使用適合的MIME-type內容類型。
  • HTTP是無狀態的:無狀態是指協議對於事務處理沒有記憶能力。缺少狀態意味着如果后續處理需要前面的信息,則它必須重傳,這樣可能導致每次連接傳送的數據量增大。另一方面,在服務器不需要先前信息時它的應答就較快。

HTTP 消息結構

客戶端請求消息(Request Headers):
客戶端發送一個HTTP請求到服務器的請求消息包括以下格式:請求行(request line)、請求頭部(header)、空行和請求數據四個部分組成,下圖給出了請求報文的一般格式。
在這里插入圖片描述

服務器響應消息(Response Headers):
HTTP響應也由四個部分組成,分別是:狀態行、消息報頭、空行和響應正文。
在這里插入圖片描述


HTTP 狀態碼

HTTP狀態碼詳解

RFC 規定 HTTP 的狀態碼為三位數,被分為五類:

  • 1xx: 信息,服務器收到請求,需要請求者繼續執行操作
  • 2xx: 成功,操作被成功接收並處理 (200 - 請求成功)
  • 3xx: 重定向,需要進一步的操作以完成請求(302 - 資源(網頁等)被臨時轉移到其它URL,瀏覽器自動處理)
  • 4xx: 客戶端錯誤,請求包含語法錯誤或無法完成請求(404 - 請求的資源(網頁等)不存在)
  • 5xx: 服務器錯誤,服務器在處理請求的過程中發生了錯誤(500 - 內部服務器錯誤)

HTTP 請求方法

GET和POST最詳細的總結

方法 協議版本 描述
GET HTTP1.0 請求指定的頁面信息,並返回實體主體。(獲取數據
HEAD HTTP1.0 類似於 GET 請求,只不過返回的響應中沒有具體的內容,用於獲取報頭。
POST HTTP1.0 向指定資源提交數據進行處理請求(例如提交表單或者上傳文件)。數據被包含在請求體中。POST 請求可能會導致新的資源的建立和/或已有資源的修改。(新建數據
PUT HTTP1.1 從客戶端向服務器傳送的數據取代指定的文檔的內容。
DELETE HTTP1.1 請求服務器刪除指定的頁面。(刪除數據
CONNECT HTTP1.1 預留給能夠將連接改為管道方式的代理服務器。
OPTIONS HTTP1.1 允許客戶端查看服務器的性能。
TRACE HTTP1.1 回顯服務器收到的請求,主要用於測試或診斷。
PATCH HTTP1.1 是對 PUT 方法的補充,用來對已知資源進行局部更新 。(更新數據

HTTP 緩存

一文讀懂http緩存(超詳細)

當客戶端向服務器請求資源時,會先抵達瀏覽器緩存,如果瀏覽器有“要請求資源”的副本,就可以直接從瀏覽器緩存中提取而不是從原始服務器中提取這個資源。

常見的http緩存只能緩存get請求響應的資源,對於其他類型的響應則無能為力。

緩存的優點:

  • 減少了冗余的數據傳輸,節省了你的網絡費用。
  • 緩解了網絡瓶頸的問題。不需要更多的帶寬就能夠更快地加載頁面。
  • 降低了對原始服務器的要求。服務器可以更快地響應,避免過載的出現。
  • 降低了距離時延,因為從較遠的地方加載頁面會更慢一些。

強緩存與協商緩存:

  • 第一次請求資源時,服務器返回資源,並在respone header中回傳資源和資源標識Last-ModifiedEtag)。
  • 第二次請求資源時,瀏覽器會判斷 response headers 是否命中(cache-control屬性強緩存,如果命中,直接從本地讀取緩存(狀態碼200),不會向服務器發送請求。
  • 當強緩存沒有命中時,就把請求參數(含If-Modified-SinceIf-Not-Match)加到 request header 頭中傳給服務器,判斷協商緩存是否命中,如果命中(If-Modified-Since == Last-ModifiedIf-Not-Match == Etag)則服務器將請求返回(狀態碼304),不會返回資源,告訴瀏覽器從本地讀取緩存。
  • 當協商緩存沒有命中(If-Modified-Since != Last-ModifiedIf-Not-Match != Etag)時,服務器直接返回新的資源(狀態碼200)和新的資源標識(新的Last-Modified、新的Etag) 。

資源標識:

  • Last-Modified:資源的最后修改時間(只能精確到秒級)
  • Etag:資源的唯一標識,會優先使用(一個字符串,類似人類的指紋)

如果資源被重復生產,而內容不變,則 Etag 更精准

在這里插入圖片描述

區別:

  • 強緩存命中:不會請求服務器,直接請求緩存;(非常快)
  • 協商緩存命中:會請求服務器,不會返回內容,然后讀取緩存;(服務端緩存策略)
    在這里插入圖片描述

11. 手寫常見JS方法

手寫常見JS方法

  • 判斷數據類型
  • 深拷貝
  • 對象是否全等
  • 防抖
  • 節流
  • 數組拍平
  • 數組去重
  • new函數

工具

1. Git

Git詳細教程

在這里插入圖片描述

項目常用命令:

1. git init					// 在當前目錄新建一個Git代碼庫	
				
2. git branch dev-bing	    // 創建本地分支(dev-bing)

3. git checkout dev-bing	  // 切換到本地分支(dev-bing)
   git checkout -b dev-bind   // 創建並切換到本地分支(dev-bing)  相當於上面第2 + 第3 的簡寫

4. git branch // 查看分支

5. git push --set-upstream origin dev-bing	// 上傳本地當前分支代碼到master分支

6. git status	// 顯示有變更的文件  如果字體為紅色,則表示列出的是當前目錄所有還沒有被git管理的文件和被git管理且被修改但還未提交(git commit)的文件,也就是所有改動文件。

7. git diff           // 查看所有修改內容
   git diff test.txt  // 查看具體文件修改內容
   
8. git log          // 查看提交的記錄日志
   git log test.txt	// 查看具體文件提交的記錄日志

9. git stash  // 臨時保存,可跨分支 (只能在未add之前才能使用)

10. git stash pop  // 恢復之前緩存

11. git checkout .         // (有個點) 撤銷當前所有的修改
   git checkout test.txt  // 撤銷具體文件的修改

12. git add .		  // (有個點) 表示添加當前目錄下的所有文件和子目錄(或git add -A)
    git add test.txt  // 添加具體文件(test.txt)

13. git commit -m 'test' // 將文件上傳至遠程 master 分支並添加備注"test"

14. git pull origin dev-bing  // 從遠程倉庫下載到dev-bing倉庫   
    git pull  // 如果當前分支是dev-bing   git pull相當於git pull origin dev-bing
      
15. git push origin dev-bing // 從本地倉庫上傳到遠程倉庫(提交)

16. git checkout master // 切換到master主分支 

17. git merge --no-ff dev-bing  // 把dev-bing分支合並到master  :wq	

18. git push origin master    // 提交合並后的master分支
	git push -u origin master // 將本地的master分支推送到origin主機,同時指定origin為默認主機,后面就可以不加任何參數使用git push了。
	git push // 設置默認主機后(git push -u origin master)可簡寫

19. git checkout dev-bing  // 返回dev-bing分支

2. 瀏覽器

瀏覽器從輸入URL到渲染完頁面的整個過程

  1. 獲取IP地址
  2. TCP/IP三次握手建立連接
  3. 瀏覽器向web服務器發送http請求
  4. 瀏覽器渲染
  5. 四次揮手斷開連接

瀏覽器渲染過程

  1. DOM 樹:解析 HTML 構建 DOM(DOM 樹)
  2. CSS 樹:解析 CSS 構建 CSSOM(CSS 樹)
  3. 渲染樹:CSSOM 和 DOM 一起生成 Render Tree(渲染樹)
  4. 布局(layout):根據Render Tree瀏覽器就知道網頁中有哪些節點,以及各個節點與 CSS 的關系,從而知道每個節點的位置和幾何屬性(重排)
  5. 繪制(Paint):根據計算好的信息繪制整個頁面(重繪)

3. 其他

yarn

gulp

babel

vConsole


Vue

vue 3.0 超詳細入門教程

1. MVVM

對MVVM的理解

MVVM 分為 ModelViewViewModel 三者:

  • Model數據層,數據和業務邏輯都在Model層中定義。
  • View視圖層,也就是用戶界面,負責數據的展示。
  • ViewModel視圖數據層, ViewModel層通過雙向數據綁定將View層和Model層連接了起來(View和Model層的橋梁),使得View層和Model層的同步工作完全是自動的。
    在這里插入圖片描述
    Model和View並無直接關聯,而是通過ViewModel這個橋梁來進行聯系的,ViewModel就是View與Model的連接器,View與Model通過ViewModel實現雙向綁定。

2. 生命周期

Vue2生命周期

  1. beforeCreate:創建之前eldatamessage都還是undefined,不可用的)
  2. created:創建完畢(能讀取到數據data的值,但是DOM還沒生成)
  3. beforeMount:掛載之前(生成DOM,但此時{{ message }}還沒有掛載data中的數據)
  4. mounted:掛載完畢{{ message }}已經成功掛載渲染data的值)
  5. beforeUpdate:更新之前
  6. updated:更新完畢
  7. beforeDestroy:銷毀之前
  8. destroyed:銷毀完畢(實例與視圖的關系解綁,再修改message的值,視圖再也不會更新了)
  9. activated:keep-alive 組件激活時調用
  10. deactivated:keep-alive 組件停用時調用

注:

  • activateddeactivated 是比較特殊的兩個鈎子,需要keep-live配合使用
  • 當引入 keep-alive 的時候,頁面第一次進入,鈎子的觸發順序 created => mounted => activated,退出時觸發 deactivated。當再次進入(前進或者后退)時,只觸發activated

Vue3生命周期

  1. onBeforeMount
  2. onMounted
  3. onBeforeUpdate
  4. onUpdated
  5. onBeforeUnmount
  6. onUnmounted
  7. onActivated
  8. onDeactivated
  9. onErrorCaptured
  10. onRenderTracked
  11. onRenderTriggered

3. computed 與 watch

Vue中computed與watch

computed(計算屬性)

  1. 屬性的結果會被緩存(默認走緩存),當computed中的函數所依賴的屬性沒有發生改變的時候,那么調用當前函數的時候結果會從緩存中讀取,除非依賴的響應式屬性變化時才會重新計算;
  2. 不支持異步,當computed內有異步操作時無效,無法監聽數據的變化;
  3. computed中的函數必須用return返回最終的結果。computed更高效,優先使用;
  4. 當一個屬性受多個屬性影響的時候,一般用computed(例如:詳細地址 = 省+市+區+街道+樓棟+房號 );

watch(監聽屬性)

  1. 不支持緩存,數據變,直接會觸發相應的操作;
  2. 支持異步
  3. 監聽的函數接收兩個參數,第一個參數是最新的值(newVal);第二個參數是輸入之前的值(oldVal);
  4. 當一條數據影響多條數據的時候,一般使用watch(例如:搜索數據(異步) ,觸發一系列數據的變化);

4. v-if 與 v-show

v-if

  1. 是通過控制 DOM 元素的存在與否來控制元素的顯隱;
  2. 切換時,是對 DOM 元素進行一個創建和銷毀的動作
  3. 惰性的,如果初始條件為假,則什么也不做;只有在條件第一次變為真時才開始局部編譯;
  4. 有更高的切換消耗;

v-show

  1. 是通過設置 DOM 元素的 display 樣式,block 為顯示,none 為隱藏;
  2. 切換時,只是簡單的基於CSS切換
  3. 是在任何條件下(首次條件是否為真)都被編譯,然后被緩存,而且 DOM 元素保留;
  4. 有更高的初始渲染消耗;

基於以上區別,因此,如果需要非常頻繁地切換,則使用 v-show 較好如果在運行時條件很少改變,則使用 v-if 較好


5. data 必須是一個函數,而不是對象

如果 Vue 組件中的 data 是個對象,那么就像所有復用這個組件的地方,都在使用這個組件里面唯一的一個 data,所有使用組件的地方的 data 都會指向棧內這一個 data 的地址,那么會造成一個改 data 的值,所有其他組件的 data 都會被改變。

如果在函數中返回一個對象,那么在每次創建一個組件的時候,每次返回的都是一個新對象(Object的實例),在內存中同時開辟一塊空間給當前組件存放 data 。這樣就不會出現共用一個 data 的現象


6. diff 算法

Vue中的diff算法

diff 算法是一種優化手段。比如有時候我們修改了某個數據,如果直接渲染到真實 DOM 上會引起整個 DOM 樹的重繪和重排,這樣開銷是非常大的。

我們可以先根據真實 DOM 生成一棵虛擬DOM(virtual DOM)樹,當虛擬DOM某個節點的數據改變后,會生成一個新的 Vnode(虛擬節點)。然后 VnodeoldVnode 作對比,發現有不一樣的地方就直接修改在真實的 DOM 上,然后使 oldVnode 的值為Vnode。此時我們只更新我們修改的那一小塊 DOM,而不要更新整個 DOM。

diff的過程就是調用patch函數,比較新舊節點,一邊比較一邊給真實的DOM打補丁。
在采取 diff 算法比較新舊節點的時候,比較只會在同層級進行, 不會跨層級比較。
在這里插入圖片描述
概括起來就是:對操作前后的 DOM 樹同一層的節點進行對比,一層一層對比,然后再插入真實的dom中,重新渲染。


7. for 循環中 key 的作用

vue中列表循環需加 :key="唯一標識", 唯一標識可以是 item 里面 idindex 等。

  • key 的作用主要是為了更高效的對比虛擬DOM中每個節點是否是相同節點;
  • Vue 在 patch 過程中判斷兩個節點是否是相同節點, key 是一個必要條件,渲染一組列表時,key往往是唯一標識,所以如果不定義 key 的話,Vue 只能認為比較的兩個節點是同一個,哪怕它們實際上不是,這導致了頻繁更新元素,使得整個 patch 過程比較低效,影響性能;
  • 從源碼中可以知道,Vue 判斷兩個節點是否相同時主要判斷兩者的 key 和元素類型等,因此如果不設置 key ,它的值就是undefined,則可能永遠認為這是兩個相同的節點,只能去做更新操作,這造成了大量的 DOM 更新操作,明顯是不可取的。

不建議 用 index 作為 key,和沒寫基本上沒區別,因為不管你數組的順序怎么顛倒,index 都是 0, 1, 2 這樣排列,導致 Vue 會復用錯誤的舊子節點,做很多額外的工作


8. 雙向綁定

當一個Vue實例創建時,Vue會遍歷 data 選項的屬性,利用 Object.defineProperty 將它們轉為 getter/setter 並且在內部追蹤相關依賴,在屬性被訪問和修改時通知變化。每個組件實例都有相應的 watcher 程序實例,它會在組件渲染的過程中把屬性記錄為依賴,之后當依賴項的 setter 被調用時,會通知 watcher 重新計算,從而致使它關聯的組件得以更新。

在這里插入圖片描述
極簡版雙向綁定:

<input type="text" id="input" value="">
<span id="span"></span>
<script>
    let obj = {}
    Object.defineProperty(obj, 'text', { // 監聽obj的text屬性
        set(newVal = '') {
            document.getElementById('input').value = newVal;
            document.getElementById('span').innerHTML = newVal;
        }
    });

    document.addEventListener('keyup', function (e) {
        obj.text = e.target.value;
    })
</script>

v-model

vue 中雙向綁定是一個指令v-model,可以綁定一個動態值到視圖,同時視圖中變化能改變該值。

v-model 是語法糖,默認情況下相於:value@input

<template>
    <div id="app">
        {{username}} <br />
        <input type="text" v-model="username"></input>
        <!-- 等價於 -->
        <input type="text" :value="username" @input="username=$event.target.value">
    </div>
</template>

<script>
    export default {
        name: 'App',
        data() {
            return {
                username: ''
            }
        }
    }
</script>

9. 組件的通信

Vue組件通信方式總結

父組件向子組件傳值(props)

<div id="app">
	<!-- 1.通過 v-bind 將數據傳給子組件 -->
    <test :ss='name'></test>
</div>

<script>
    var a = new Vue({
        el:'#app',
        data:{
            name:'bing',
        },
        components: {
        	// 子組件<test>
            test:{
                props: ['ss'],   // 2.接收父組件傳遞過來的數據
                template:"<p>{{ss}}</p>"
            }
        }
    })
</script>

子組件向父組件傳值($emit)

<div id="app">
    <p>{{ total }}</p>
    <button-counter @increment="incrementTotal"></button-counter> <!-- 步驟3 -->
</div>

<script>
    Vue.component('button-counter', {
        template: '<button @click="increment">{{ counter }}</button>', // 步驟1
        data: function () {
            return {
                counter: '子組件的數據'
            } 
        },
        methods: {
            increment: function () {
            	// 通過 $emit 定義一個自定義事件 increment ,同時傳入 this.counter 參數,對外拋出
                this.$emit('increment', this.counter); // 步驟2
            }
        }
    });


    new Vue({
        el: '#app',
        data: {
            total: '父組件的數據:'
        },
        methods: {
        	// 能接收到子組件傳遞過來的數據(e)
            incrementTotal: function (e) { // 步驟4
                this.total = this.total + e[0]
                console.log(e);
            }
        }
    })
</script>

兄弟組件傳值(EventBus)

  1. 新創建一個 event-bus.js 文件
// event-bus.js
import Vue from 'vue'
export const EventBus = new Vue();
  1. A 頁面發送數據
<!-- A.vue -->
<template>
    <button @click="sendMsg()">-</button>
</template>

<script> 
import { EventBus } from "../event-bus.js";
export default {
  methods: {
    sendMsg() {
      EventBus.$emit("aMsg", '來自A頁面的消息'); // 對外發送數據
    }
  }
}; 
</script>
  1. B 頁面接收數據
<!-- B.vue -->
<template>
	<p>{{msg}}</p>
</template>

<script> 
import { EventBus } from "../event-bus.js";
export default {
	data() {
		return {
			msg: ''
		}
	},
	mounted() {
		EventBus.$on("aMsg", (msg) => { // 接收 A 發送來的消息
			this.msg = msg;
		});
	}
};
</script>
  1. 移除事件監聽
import { EventBus } from "../event-bus.js";
EventBus.$off('aMsg', {}); // 移除應用內所有對此某個事件的監聽
// 或
EventBus.$off(); // 移除所有事件監聽

Vuex

vuex詳解

  • state:定義了應用狀態的數據結構,可以在這里設置默認的初始狀態。
  • getter:可以認為是 store 的計算屬性(有緩存,只有當它的依賴值發生了改變才會被重新計算)。
  • mutation:是唯一更改 store 中狀態的方法,且必須是同步函數。
  • action:用於提交 mutation(再由mutation更改 store 中狀態),而不是直接變更狀態,可以包含任意異步操作。
  • module:可以將 store 分割成模塊(module)。每個模塊擁有自己的 statemutationactiongetter、甚至是嵌套子模塊

基本用法:

//創建一個 store
const store = new Vuex.Store({
    //state存儲應用層的狀態
    state: {
        count: 5 // 組件中通過 this.$store.state.count; 獲取
    },
    getters: {
        newCount: state => state.count * 3 // 組件中通過 this.$store.getters.newCount; 獲取
    },
    // mutations是修改state中數據的唯一途徑
    mutations: {
        increment(state, value) {
            state.count += value; // 組件中通過 this.$store.commit("increment", 'hello'); 修改
        }
    },
    actions: {
        getParamSync(store, value) {
            // 處理異步操作
            setTimeout(() => {
                store.commit('increment', value); // 組件中通過 this.$store.dispatch('getParamSync','hi'); 修改
            }, 1000);
        }
    }
});

module 用法

const moduleA = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的狀態
store.state.b // -> moduleB 的狀態

10. 路由

Vue路由導航 router-link 和 router.push

1. 不攜帶參數

// 字符串
<router-link to="login">to login</router-link>
<router-link to="/login">to login</router-link>

// 對象
<router-link :to="{path:'/login'}"> to login</router-link>

// 命名路由
<router-link :to="{name: 'Login'}"> to login</router-link>

2. 通過 query 攜帶參數:

  1. 地址欄變成 /login?color=red
  2. 可通過 {{$route.query.color}}this.$route.query.color 獲取參數
<router-link :to="{path: '/login', query: {color: 'red' }}"> to login</router-link>

<router-link :to="{name: 'Login', query: {color: 'red' }}"> to login</router-link>

3. 通過 params 攜帶參數:

  1. 地址欄變成 /login/red
  2. 可通過 {{$route.params.color}}this.$route.params.color 獲取參數
// 無法獲取參數  
// 報警告(Path "/login" was passed with params but they will be ignored. Use a named route alongside params instead.)
<router-link :to="{path: '/login', params: { color: 'red' }}"> to login</router-link>


// 通過 {{$route.params.color}} 或 this.$route.params.color 獲取參數。
<router-link :to="{name: 'Login', params: { color: 'red' }}"> to login</router-link>

此時 router.js 中需要設置 path: '/login/:color?'

// router.js

var router = new Router({
routes: [{
	path: '/login/:color?', //  :color? => ?問號的意思是該參數不是必傳項,不傳不會報錯
	name: 'Login',
	component: Login
}]

router.push

1. 不攜帶參數

// 字符串
router.push('/login')

// 對象
router.push({path:'/login'})

// 命名路由
router.push({name: 'Login'})

2. 通過 query 攜帶參數:

// 可通過 {{$route.query.color}} 或 this.$route.query.color 獲取參數
router.push({path: '/login', query: {color: 'red' }})

// 可通過 {{$route.query.color}} 或 this.$route.query.color 獲取參數
router.push({name: 'Login', query: {color: 'red' }})

3. 通過 params 攜帶參數:

// 無法獲取參數 
router.push({path:'/login', params:{ color: 'red' }})

// 通過 {{$route.params.color}} 或 this.$route.params.color 獲取參數。
router.push({name:'Login', params:{ color: 'red' }})

$router 和 $route 的區別

  1. $router :是指整個路由實例,你可以操控整個路由,用法如下:
    this.$router.go(-1);  // 向前或者向后跳轉n個頁面,n可為正整數或負整數
    this.$router.push('/'); // 跳轉到指定url路徑,history棧中會有記錄,點擊返回會跳轉到上個頁面
    this.$router.replace('/'); // 跳轉到指定url路徑,但是history棧中不會有記錄,點擊返回會跳轉到上上個頁面
    
  2. $route:是指當前路由實例$router跳轉到的路由對象;路由實例可以包含多個路由對象,它們是父子包含關系
    // 獲取路由傳遞過來的參數
    this.$route.params.userId  
    this.$route.query.userName 
    

router.js (含路由懶加載和路由鑒權)

router.js相關配置

import Vue from 'vue'
import Router from 'vue-router'
import { getStore } from 'js/store'

Vue.use(Router);

var router = new Router({
    routes: [
    {
        path: '*',
        redirect: '/'
    },
    {
        path: '/',
        name: '/',
        component: () => import('./views/Index.vue'), // vue路由懶加載  異步加載
        meta: {
            title: '首頁',
            requireAuth: false // 此字段為false,不需要做鑒權處理
        }
    },
    {
        path: '/test',
        name: 'test',
        component: () => import('./views/Test.vue'),// vue路由懶加載  異步加載
        meta: {
            title: 'test',
            requireAuth: true // 只要此字段為true,必須做鑒權處理
        }
    },
    {
        path: '/login',
        name: 'login',
        component: () => import('./views/Login.vue'),// vue路由懶加載  異步加載
        meta: {
            noNav: true // 不顯示nav
        }
    }]
});

let indexScrollTop = 0;

router.beforeEach((to, from, next) => {
    // 路由鑒權:在路由meta對象中由個requireAuth字段,只要此字段為true,必須做鑒權處理
    if (to.matched.some(res => res.meta.requireAuth)) {
        const token = getStore({ name: 'access_token', type: "string" });// 獲取localstorage中的access_token
        console.log(token);
        // 路由進入下一個路由對象前,判斷是否需要登陸
        if (!token) {
            // 未登錄
            next({
                path: '/login',
                query: {
                    redirect: to.path // 將跳轉的路由path作為參數,登錄成功后再跳轉到該路由
                }
            })
        } else {
            // 用戶信息是否過期
            let overdueTime = token.overdueTime;
            let nowTime = +new Date();
            // 登陸過期和未過期
            if (nowTime > overdueTime) {
                // 登錄過期的處理,君可按需處理之后再執行如下方法去登錄頁面
                // 我這里沒有其他處理,直接去了登錄頁面
                next({
                    path: '/login',
                    query: {
                        redirect: to.path
                    }
                })
            } else {
                next()
            }
        }
    } else {
        next()
    }
    if (to.path !== '/') {
        // 記錄現在滾動的位置
        indexScrollTop = document.body.scrollTop
    }
    document.title = to.meta.title || document.title
})

router.afterEach(route => {
    if (route.path !== '/') {
        document.body.scrollTop = 0
    } else {
        Vue.nextTick(() => {
            // 回到之前滾動位置
            document.body.scrollTop = indexScrollTop
        })
    }
})

export default router;

路由懶加載的不同寫法:

  1. webpack < 2.4版(vue-cli2)的懶加載
const Index = resolve => require(['./views/Index.vue'], resolve);
  1. webpack > 2.4版(vue-cli3)的懶加載
const Index = () => import('./views/Index.vue');

11. 其他

vue3中CompositionAPI

Vue中對axios的封裝(含攔截器)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM