??HTML
1. 如何理解HTML語義化
- 讓人更容易讀懂(代碼結(jié)構(gòu)清晰耗美,增加代碼的可讀性)
- 讓搜索引擎更容易讀懂(SEO)京髓,搜索引擎爬蟲會(huì)根據(jù)不同的標(biāo)簽來賦予不同的權(quán)重
語義化標(biāo)簽 : header
nav
main
article
section
aside
footer
2. 默認(rèn)情況下,哪些HTML標(biāo)簽是塊級元素商架、哪些是內(nèi)聯(lián)元素
- 塊級元素:
display: block/table
堰怨,有div
div
h1
h2
table
ul
ol
li
p
等 - 內(nèi)聯(lián)元素:
display: inline/inline-block
,有span
img
input
button
i
b
等
3. ??HTML5 新增內(nèi)容和 API
- classList 屬性
- querySelector() 與 querySelectorAll()
- getElementsByClassName()
- 自定義數(shù)據(jù)屬性
- 本地存儲(chǔ)
- insertAdjacentHtml()蛇摸、insertAdjacentText()备图、insertAdjacentElement()
- 內(nèi)容可編輯
- 預(yù)加載
??CSS
1. 盒子模型
CSS盒子模型包含2種:
- W3C標(biāo)準(zhǔn)盒子模型(
box-sizing: content-box
,默認(rèn))赶袄,寬高不受 padding揽涮、border影響 - IE怪異盒子模型 (
box-sizing: border-box
),寬高受 padding饿肺、border影響
2. margin 縱向重疊
- 相鄰元素的
margin-top
和margin-bottom
會(huì)重疊 - 空內(nèi)容的元素也會(huì)重疊
思考:如下代碼蒋困,AAA
和BBB
之間的間距是多少 ?
<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 負(fù)值
-
margin-top
負(fù)值敬辣,元素會(huì)上移 -
margin-left
負(fù)值雪标,元素會(huì)左移 -
margin-right
負(fù)值零院,右側(cè)元素會(huì)左移,自身不受影響 -
margin-bottom
負(fù)值村刨,下方元素會(huì)上移告抄,自身不受影響
margin 負(fù)值
4. ??BFC(塊級格式化上下文)
具有 BFC 特性的元素可以看作是隔離了的獨(dú)立容器,容器里面的元素不會(huì)在布局上影響到外面的元素
只要元素滿足下面任一條件即可觸發(fā) BFC 特性:
- 根元素(即
<html>
標(biāo)簽) - 浮動(dòng)元素 float 不為 none (為
left
嵌牺、right
) - 絕對定位元素 position 不為 static 或 relative打洼。(為
absolute
、fixed
) - overflow 的值不為 visible 的塊元素(為
auto
逆粹、scroll
拟蜻、hidden
) - display 的值為
inline-block
、flex
枯饿、grid
酝锅、table
奢方、table-cell
蟋字、table-caption
...
同一BFC內(nèi):
- Box會(huì)在垂直方向上一個(gè)接一個(gè)的放置
- 垂直方向的距離由margin決定(屬于同一個(gè)BFC的兩個(gè)相鄰Box的margin會(huì)發(fā)生重疊稿蹲,與方向無關(guān))
- 每個(gè)元素的margin box的左邊, 與包含塊border box的左邊相接觸(對于從左往右的格式化鹊奖,否則相反)苛聘。即使存在浮動(dòng)也是如此
- BFC的區(qū)域不會(huì)與float的元素區(qū)域重疊
- 計(jì)算BFC的高度時(shí),浮動(dòng)子元素也參與計(jì)算
- BFC就是頁面上的一個(gè)隔離的獨(dú)立容器忠聚,容器里面的子元素不會(huì)影響到外面元素设哗,反之亦然
應(yīng)用:
- 分屬于不同的BFC時(shí),可以防止margin重疊
- 清除內(nèi)部浮動(dòng)
- 自適應(yīng)多欄布局
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布局
7. ??三欄布局
- 浮動(dòng)布局
- 定位布局
- flex布局
- 表格布局
- 網(wǎng)格布局
- calc函數(shù)布局
- 圣杯布局
- 雙飛翼布局
面試常考的圣杯布局和雙飛翼布局:
- 三欄布局两蟀,中間一欄最先加載和渲染(內(nèi)容最重要)
- 兩側(cè)內(nèi)容固定网梢,中間內(nèi)容隨著寬度自適應(yīng)
- 一般用于PC端
8. CSS定位
思考:relative
颜启、absolute
川抡、fixed
依據(jù)什么定位?
答案:
-
relative
:依據(jù)自身定位(相對的是它原本在文檔流中的位置而進(jìn)行的偏移)气笙。在最外層時(shí)谭期,是以<body>
標(biāo)簽為定位原點(diǎn)的胀瞪。 -
absolute
:依據(jù)最近一層的定位元素定位(根據(jù)postion
非static
的祖先類元素進(jìn)行定位)帆谍。在無父級是postion
非static
定位時(shí),是以<html>
作為原點(diǎn)定位。 -
fixed
:根據(jù)窗口為原點(diǎn)進(jìn)行偏移定位 (也就是說它不會(huì)根據(jù)滾動(dòng)條的滾動(dòng)而進(jìn)行偏移)
9. 居中對齊實(shí)現(xiàn)方式
10. line-height的繼承問題
思考: 以下代碼中p
標(biāo)簽的行高是多少橡疼?
<style>
body {
font-size: 20px;
line-height: 200%;
}
p {
font-size: 16px;
}
</style>
<p>AAA</p>
答案是 40px
- 寫具體數(shù)值,如
body{ line-height: 30px;}
,則繼承該值 (p
的行高就是30px
) - 寫比例睹栖,如
body{ line-height: 2;}
曼氛,則繼承該比例 (p
的行高就是16px*2 = 32px
) p字體大小的2倍 - 寫百分比(有坑)构舟,如
body{ line-height: 200%;}
苦蒿,則繼承計(jì)算出來的值 (p
的行高就是20px*2 = 40px
) body字體大小的2倍
11. CSS長度單位
-
px
固定的像素,一旦設(shè)置了就無法因?yàn)檫m應(yīng)頁面大小而改變。 -
em
相對于父元素的長度單位弄跌。(不常用) -
rem
相對于根<html>
元素的長度單位格仲。(常用) -
rpx
微信小程序的相對長度單位圈盔。小程序規(guī)定屏幕寬為750rpx容诬。如在 iPhone6 上,屏幕寬度為375px纽什,共有750個(gè)物理像素,則750rpx = 375px = 750物理像素友雳,1rpx = 0.5px = 1物理像素稿湿。(僅微信小程序)
12. CSS響應(yīng)式媒體查詢
假如一個(gè)終端的分辨率小于 980px神帅,那么可以這樣寫:
@media only screen and (max-width: 980px) {
#head { … }
#content { … }
#footer { … }
}
假如我們要設(shè)定兼容 iPad 和 iPhone 的視圖揪漩,那么可以這樣設(shè)置:
/** 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
單位實(shí)現(xiàn)響應(yīng)式血柳,因此rem
具有階梯性
的弊端
13. 網(wǎng)頁視口尺寸
- 屏幕高度 window.screen.height (顯示器屏幕設(shè)備高度)
- 網(wǎng)頁視口高度 window.innerHeight (去掉瀏覽器自身的頭部和底部后的高度沃斤,含滾動(dòng)條高)
- body高度 document.body.clientHeight (頁面內(nèi)容的真實(shí)高度)
神圖
寬度同理 略~
14. CSS3 vw / vh
-
vw
網(wǎng)頁視口寬度的1% (window.innerWidth = 1vw
) -
vh
網(wǎng)頁視口高度的1% (window.innerHeight = 1vh
) -
vmin
選取vw
和vh
中最小的那個(gè) -
vmax
選取vw
和vh
中最大的那個(gè)
??JS
1. ??ES6新特性
2. 數(shù)據(jù)類型與檢測
JavaScript 數(shù)據(jù)類型:
- Number (基本類型)
- String (基本類型)
- Boolean (基本類型)
- null (基本類型)
- undefined (基本類型)
- symbol (ES6 - 基本類型)
- bigInt (ES10 - 基本類型)
- object (引用類型昙读,包含 function环础、[ ]缎除、{ })
基本類型的特點(diǎn):直接存儲(chǔ)在棧(stack)內(nèi)存中的數(shù)據(jù)
引用類型的特點(diǎn):存儲(chǔ)的是該對象在棧中引用严就,真實(shí)的數(shù)據(jù)存放在堆(heap)內(nèi)存中
3. ??深拷貝和淺拷貝
深拷貝和淺拷貝最根本的區(qū)別在于是否是真正獲取了一個(gè)對象的拷貝實(shí)體,而不是引用伴找。
淺拷貝只拷貝一層對象的屬性盈蛮,而深拷貝則遞歸拷貝了所有層級。
- 深拷貝在計(jì)算機(jī)中開辟了一塊新的內(nèi)存地址用于存放拷貝的對象
- 淺拷貝僅僅是指向被拷貝的內(nèi)存地址技矮,如果原地址中對象被改變了抖誉,那么淺拷貝出來的對象也會(huì)相應(yīng)改變
4. ??原型與原型鏈(三座大山之一)
prototype(顯式原型)
所有函數(shù)(僅限函數(shù))擁有 prototype
屬性
prototype
對象用于放某同一類型實(shí)例的共享屬性和方法,實(shí)質(zhì)上是為了內(nèi)存著想衰倦。
Person.prototype.sayHello = function() {
console.log('Hello!')
}
console.log(person1.sayHello === person2.sayHello) // true袒炉,同一個(gè)方法
_proto _ (隱式原型)
所有對象擁有 _proto _
屬性
_proto _
指向誰?分以下三種情況:
/*1樊零、字面量方式*/
var a = {};
console.log(a.constructor === Object); // true (即構(gòu)造器Object)
console.log(a.__proto__ === a.constructor.prototype); // true
console.log(a.__proto__ === Object.prototype); // true
/*2我磁、構(gòu)造器方式*/
var A = function (){};
var a = new A();
console.log(a.constructor === A); // true(即構(gòu)造器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 (即構(gòu)造器Object)
console.log(a2.__proto__ === a1); // true
console.log(a2.__proto__ === a2.constructor.prototype); //false(此處即為圖1中的例外情況)
constructor ( 指向創(chuàng)建該對象的構(gòu)造函數(shù))
每個(gè)原型對象都可以通過對象.constructor
指向創(chuàng)建該對象的構(gòu)造函數(shù)
function Person() {};
var person1 = new Person();
var person2 = new Person();
// 實(shí)例化對象的constructor屬性指向構(gòu)造函數(shù)本身
person1.constructor === Person;
// 構(gòu)造函數(shù)的prototype屬性有個(gè)constructor屬性驻襟,指向構(gòu)造函數(shù)本身
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
下圖中由相互關(guān)聯(lián)的原型組成的鏈狀結(jié)構(gòu)就是原型鏈夺艰,也就是藍(lán)色的這條線。
原型與原型鏈的終極圖
這個(gè)圖要是看懂了沉衣,原型與原型鏈就基本摸清了郁副。
instanceof 原理
instanceof
只能用來判斷對象類型,原始類型不可以豌习。并且所有對象類型instanceof Object
都是 true
instanceof
的內(nèi)部機(jī)制是通過判斷對象的原型鏈中是不是能找到類型的 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的判斷隊(duì)則是:沿著s1的proto這條線來找拔疚,同時(shí)沿著Student的prototype這條線來找,如果兩條線能找到同一個(gè)引用既荚,即同一個(gè)對象稚失,那么就返回true。如果找到終點(diǎn)還未重合恰聘,則返回false句各。這就很好地解釋了上述代碼的輸出結(jié)果啦。
繼承方式
5. ??作用域憨琳、this 和閉包 (三座大山之二)
作用域
ES5只有 全局作用域 和 函數(shù)作用域诫钓,ES6增加塊級作用域
全局作用域
代碼在程序的任何地方都能被訪問,window 對象
的內(nèi)置屬性都擁有全局作用域篙螟。
函數(shù)作用域
在固定的代碼片段才能被訪問
塊級作用域
let 和 const 命令聲明變量具有塊級作用域:
作用域有上下級關(guān)系,上下級關(guān)系的確定就看函數(shù)是在哪個(gè)作用域下創(chuàng)建的问拘。如上遍略,fn作用域下創(chuàng)建了bar函數(shù),那么“fn作用域”就是“bar作用域”的上級骤坐。
作用域最大的用處就是隔離變量绪杏,不同作用域下同名變量不會(huì)有沖突。
作用域鏈
變量取值:到創(chuàng)建這個(gè)變量的函數(shù)的作用域中向上取值纽绍,而不是調(diào)用這個(gè)函數(shù)時(shí)向上取值蕾久,
如果在當(dāng)前作用域中沒有查到值,就會(huì)向上級作用域去查拌夏,直到查到全局作用域僧著,這么一個(gè)查找過程形成的鏈條就叫做作用域鏈。
思考:以下代碼輸出什么障簿?
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
從創(chuàng)建的函數(shù)向上取值盹愚,而不是調(diào)用函數(shù)時(shí)向上取值
this
this永遠(yuǎn)指向的是最后調(diào)用它的對象,也就是看它執(zhí)行的時(shí)候是誰調(diào)用的
特別注意:
- 匿名函數(shù)的自我執(zhí)行站故,沒有被上級對象調(diào)用皆怕,所以this指向window
-
setTimeout(function () { console.log(this) });
,this指向window -
setTimeout(() => { console.log(this) });
西篓,this指向上下文 - 構(gòu)造函數(shù)中的this愈腾,指向?qū)嵗龑ο?/li>
-
bind
、call
岂津、apply
可以改變 this 指向
JavaScript中call,apply,bind方法的總結(jié)
閉包
閉包就是指有權(quán)訪問另一個(gè)函數(shù)作用域中的變量的函數(shù)虱黄。
創(chuàng)建閉包的最常見的方式就是在一個(gè)函數(shù)內(nèi)創(chuàng)建另一個(gè)函數(shù),通過另一個(gè)函數(shù)訪問這個(gè)函數(shù)的局部變量
閉包的特性:
- 函數(shù)嵌套函數(shù)
- 函數(shù)內(nèi)部可以引用外部的參數(shù)和變量
- 參數(shù)和變量不會(huì)被垃圾回收機(jī)制回收
function aaa() {
var a = 1;
return function(){
alert(a++)
};
}
var fun = aaa();
fun();// 1 執(zhí)行后 a++寸爆,礁鲁,然后a還在~ a會(huì)長期駐扎在內(nèi)存中
fun();// 2
fun = null;//a被回收Q纹邸!
6. ??異步 (三座大山之三)
單線程與多線程
- JavaScript是單線程語言(可以說這是JavaScript最核心也是最基本的特性)
- 瀏覽器的內(nèi)核是多線程的
雖然JavaScript是單線程的仅醇,可是瀏覽器內(nèi)部不是單線程的冗美。
一些I/O操作、定時(shí)器的計(jì)時(shí)和事件監(jiān)聽(click, keydown...)等都是由瀏覽器提供的其他線程來完成的析二。
同步與異步
同步:是指在主線程上排隊(duì)執(zhí)行的任務(wù)粉洼,只有前一個(gè)任務(wù)執(zhí)行完畢,才能繼續(xù)執(zhí)行下一個(gè)任務(wù)叶摄。
當(dāng)我們打開網(wǎng)站時(shí)属韧,網(wǎng)站的渲染過程,比如元素的渲染蛤吓,其實(shí)就是一個(gè)同步任務(wù)異步:是指不進(jìn)入主線程宵喂,而進(jìn)入任務(wù)隊(duì)列的任務(wù),只有任務(wù)隊(duì)列通知主線程会傲,某個(gè)異步任務(wù)可以執(zhí)行了锅棕,該任務(wù)才會(huì)進(jìn)入主線程。
當(dāng)我們打開網(wǎng)站時(shí)淌山,像圖片加載等網(wǎng)絡(luò)請求(ajax裸燎、axios)、定時(shí)任務(wù)(setTimeout)泼疑,其實(shí)就是一個(gè)異步任務(wù)
console.log(1);
alert(2); // 同步德绿,會(huì)阻塞代碼的執(zhí)行
console.log(3);
setTimeout(function(){
console.log(1); // 異步,不會(huì)阻塞代碼的執(zhí)行
},100)
console.log(2);
事件循環(huán)(Event Loop)
事件循環(huán)機(jī)制:
- 首先判斷JS是同步還是異步,同步就進(jìn)入主線程,異步就進(jìn)入
event table
- 異步任務(wù)在
event table
中注冊函數(shù),當(dāng)滿足觸發(fā)條件后,被推入消息隊(duì)列(event queue
) - 同步任務(wù)進(jìn)入主線程后一直執(zhí)行,直到主線程空閑時(shí),才會(huì)去消息隊(duì)列中查看是否有可執(zhí)行的異步任務(wù),如果有就推入主線程中
異步任務(wù)又可以分為:
macrotask(宏任務(wù))
:
等待執(zhí)行棧和微任務(wù)隊(duì)列都執(zhí)行完畢才會(huì)執(zhí)行退渗,并且在執(zhí)行完每一個(gè)宏任務(wù)之后移稳,會(huì)去看看微任務(wù)隊(duì)列有沒有新添加的任務(wù),如果有氓辣,會(huì)先將微任務(wù)隊(duì)列中的任務(wù)清空秒裕,才會(huì)繼續(xù)執(zhí)行下一個(gè)宏任務(wù)
包括:script代碼塊,setTimeout钞啸,setInterval几蜻,I/Omicrotask(微任務(wù))
:
當(dāng)執(zhí)行棧中的代碼執(zhí)行完畢,會(huì)在執(zhí)行宏任務(wù)隊(duì)列之前先看看微任務(wù)隊(duì)列中有沒有任務(wù)体斩,如果有會(huì)先將微任務(wù)隊(duì)列中的任務(wù)清空才會(huì)去執(zhí)行宏任務(wù)隊(duì)列
包括:Promise梭稚,nextTick,callback絮吵,Object.observe弧烤,MutationObserver
執(zhí)行的順序是 執(zhí)行棧中的代碼 => 微任務(wù) => 宏任務(wù) => 微任務(wù) => 宏任務(wù) => ...
。
DOM事件也是基于Event Loop蹬敲,但不是異步
異步任務(wù)的執(zhí)行也是有先后順序的:
- 執(zhí)行一個(gè)宏任務(wù),過程中如果遇到微任務(wù),就將其放到微任務(wù)的【事件隊(duì)列】里
- 當(dāng)前宏任務(wù)執(zhí)行完成后,會(huì)查看微任務(wù)的【事件隊(duì)列】,并將里面全部的微任務(wù)依次執(zhí)行完
Promise
Promise
是異步編程的一種解決方案暇昂,有三種狀態(tài):
-
pending
(等待態(tài)) -
fulfiled
(成功態(tài)) -
rejected
(失敗態(tài))
一旦 Promise
被 resolve
或 reject
莺戒,不能再遷移至其他任何狀態(tài)(即狀態(tài) immutable
)。創(chuàng)造 promise
實(shí)例后急波,它會(huì)立即執(zhí)行从铲。
基本過程:
- 初始化
Promise
狀態(tài)(pending
); - 立即執(zhí)行
Promise
中傳入的fn
函數(shù)澄暮,將Promise
內(nèi)部resolve
名段、reject
函數(shù)作為參數(shù)傳遞給fn
,按事件機(jī)制時(shí)機(jī)處理泣懊; - 執(zhí)行
then(…)
注冊回調(diào)處理數(shù)組(then
方法可被同一個(gè)promise
調(diào)用多次)伸辟; -
Promise
里的關(guān)鍵是要保證,then
方法傳入的參數(shù)onFulfilled
和onRejected
馍刮,必須在then
方法被調(diào)用的那一輪事件循環(huán)之后的新執(zhí)行棧中執(zhí)行信夫;
簡單用法:
let p = new Promise((resolve, reject) => {
var num = Math.ceil(Math.random() * 10); //生成1-10的隨機(jī)數(shù)
if (num <= 5) {
resolve(num);
} else {
reject('數(shù)字太大了');
}
})
// 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
核心:
- 執(zhí)行 async 函數(shù),默認(rèn)返回一個(gè) promise 對象
- await 相當(dāng)于 promise 的 then
- 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 {
//把a(bǔ)wait及獲取它的值的操作放在try里
let n = await dice('大'); // await 相當(dāng)于 Promise 的 then
console.log('贏了' + n);
} catch (error) {
//失敗的操作放在catch里
console.log('輸了' + error); // 相當(dāng)于 Promise 的 catch
}
}
test();
思考:以下代碼輸出順序
async function async1() {
console.log(1); // 同步2
await async2(); // 先執(zhí)行async2(),再await
console.log(2); // 異步(await下面所有的代碼都是異步)
}
async function async2() {
console.log(3); // 同步3
}
console.log(4); // 同步1
setTimeout(() => {
console.log(5); // 異步2 宏任務(wù)
}, 0);
async1();
console.log(6); // 同步4
答案
4
1
3
6
2
5
await 下面所有的代碼都是異步
異步加載JS方式
1. 匿名函數(shù)自調(diào)動(dòng)態(tài)創(chuàng)建script標(biāo)簽加載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屬性(異步加載腳本)
瀏覽器解析到 HTML 里的該行 script標(biāo)簽
碎乃,發(fā)現(xiàn)指定為 async
,會(huì)異步下載解析執(zhí)行腳本惠奸。不會(huì)阻塞其他資源文件的下載
<!-- async是HTML5屬性 -->
<script type="text/javascript" src="xxx.js" async="async"></script>
3. defer屬性(延遲加載腳本)
瀏覽器解析到 HTML 里的該行 script標(biāo)簽
梅誓,發(fā)現(xiàn)指定為 defer
,會(huì)暫緩下載解析執(zhí)行腳本佛南。等到頁面加載完畢后梗掰,才加載腳本(更精確地說,是在 DOM樹 構(gòu)建完成后嗅回,在 window.onload
觸發(fā)前及穗,加載 defer 的腳本)。
<script type="text/javascript" src="xxx.js" defer="defer"></script>
7. DOM 與 BOM
DOM
DOM (Document Object Model)是 W3C 的標(biāo)準(zhǔn)绵载,是指文檔對象模型(樹結(jié)構(gòu))埂陆。
DOM 定義了訪問和操作 HTML 文檔的標(biāo)準(zhǔn)方法。通過它娃豹,可以訪問HTML文檔的所有元素焚虱。
1. HTML DOM 樹:
2. DOM 節(jié)點(diǎn):
根據(jù) W3C 的 HTML DOM 標(biāo)準(zhǔn),HTML 文檔中的所有內(nèi)容都是節(jié)點(diǎn)(NODE):
- 文檔節(jié)點(diǎn):整個(gè)文檔(document對象)
- 元素節(jié)點(diǎn):每個(gè) HTML 元素(element 對象)
- 文本節(jié)點(diǎn):HTML 元素內(nèi)的文本(text對象)
- 屬性節(jié)點(diǎn):每個(gè) HTML 屬性(attribute對象)
- 注釋節(jié)點(diǎn):注釋(comment對象)
3. DOM 查找:
// 根據(jù)標(biāo)簽名獲取標(biāo)簽合集
const div1 = document.getElementsByTagName("div"); // div1 div2 div3 div4 div5 (元素集合 HTMLCollection)
const div2 = document.querySelectorAll("div"); // div1 div2 div3 div4 div5 (節(jié)點(diǎn)集合 NodeList)
// 根據(jù)class屬性獲取
const div3 = document.getElementsByClassName("div"); // div1 div2 (元素集合 HTMLCollection)
const div4 = document.querySelectorAll(".div"); // div1 div2 (節(jié)點(diǎn)集合 NodeList)
// 根據(jù)id屬性值獲取
const div5 = document.getElementById("div"); // div3 (一個(gè)標(biāo)簽)
const div6 = document.querySelectorAll("#div"); // div3 (節(jié)點(diǎn)集合 NodeList)
// 根據(jù)name屬性值獲取
const div7 = document.getElementsByName("div"); // div4 div5 (節(jié)點(diǎn)集合 NodeList)
// 根據(jù)標(biāo)簽名獲取標(biāo)第一個(gè)
const div8 = document.querySelector("div"); // div1 (一個(gè)標(biāo)簽)
4. DOM 操作:
// 創(chuàng)建節(jié)點(diǎn)
var divEle = document.createElement("div");
var pEle = document.createElement("p");
var aEle = document.createElement("a");
// 添加節(jié)點(diǎn)
document.body.appendChild(divEle); // 將上面創(chuàng)建的div元素加入到body的尾部
document.body.insertBefore(pEle, divEle); // 在body下懂版,將p元素添加到div元素前面
//替換節(jié)點(diǎn)
document.body.replaceChild(aEle, pEle); // 在body下鹃栽,用a元素替換p元素
//設(shè)置文本節(jié)點(diǎn)
aEle.innerText = "在干嘛"
divEle .innerHTML = "<p>在干嘛<p/>"
//設(shè)置屬性
divEle .setAttribute("class","list"); // 給div元素加上class='list'屬性
//獲取class值
divEle.className // 獲取div元素上的class
// 設(shè)置style樣式
divEle.style.color = "red"; // 把div元素的color樣式設(shè)置成red
divEle.style.margin = "10px"
divEle.style.width = "10px"
divEle.style.left = "10px"
divEle.style.position = "relative"
5. DOM 優(yōu)化:
DOM 操作都是代價(jià)昂貴的操作,它會(huì)導(dǎo)致 WEB 應(yīng)用程序的 UI 反應(yīng)遲鈍躯畴。所以民鼓,應(yīng)當(dāng)盡可能減少這類過程的發(fā)生薇芝。
// 不緩存 DOM 查詢結(jié)果
for (let i = 0; i < document.getElementsByTagName("div").length; i++) {
// 每次循環(huán),都會(huì)計(jì)算length丰嘉,頻繁進(jìn)行 DOM 查詢
}
// 緩存 DOM 查詢結(jié)果
const div = document.getElementsByTagName("div");
const length = div.length;
for (let i = 0; i < length; i++) {
// 只進(jìn)行一次 DOM 查詢
}
將頻繁的 DOM 操作改成一次性操作:
var el = document.getElementById('mydiv');
// 未優(yōu)化前的DOM操作夯到,會(huì)導(dǎo)致三次重排
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
el.style.padding = '5px';
// 優(yōu)化后的DOM操作,只會(huì)一次重排
el.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px;';
BOM
BOM(Browser Object Model)是指瀏覽器對象模型,可以對瀏覽器窗口進(jìn)行訪問和操作闽坡。
使用 BOM账嚎,開發(fā)者可以移動(dòng)窗口、改變狀態(tài)欄中的文本以及執(zhí)行其他與頁面內(nèi)容不直接相關(guān)的動(dòng)作逼争。使 JavaScript 有能力與瀏覽器"對話"。
-
Window 對象 (
window.alert()
劝赔、window.open()
誓焦、window.setTimeout()
...) -
Navigator 對象(
navigator.userAgent
...) -
Screen 對象 (
screen.width
、screen.height
...) -
Location 對象 (
location.href
着帽、location.reload()
杂伟、location.replace()
...) -
History 對象(
history.forward()
、history.back()
...)
8. ??事件流
事件傳播的順序?qū)?yīng)瀏覽器的兩種事件流模型:
-
冒泡型事件流中click事件傳播順序?yàn)?
<div> => <body> => <html> => document
(默認(rèn)) -
捕獲型事件流中click事件傳播順序?yàn)?
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');
});
// 默認(rèn)是事件捕獲仍翰,因此按順序輸出:
// this is div
// this is body
// this is html
// this is document
</script>
</body>
</html>
事件捕獲
事件冒泡(默認(rèn))
DOM 標(biāo)準(zhǔn)事件流
綁定事件時(shí)通過addEventListener函數(shù)赫粥,它有三個(gè)參數(shù),第三個(gè)參數(shù)若是true予借,則表示采用事件捕獲越平,若是false(默認(rèn)),則表示采用事件冒泡灵迫。
<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)
第三個(gè)參數(shù)useCapture
秦叛,可選。布爾值瀑粥,指定事件是否在捕獲或冒泡階段執(zhí)行:
-
true
- 事件句柄在捕獲階段執(zhí)行 -
false
(默認(rèn))- 事件句柄在冒泡階段執(zhí)行
阻止事件冒泡/捕獲
使用 event.stopPropagation()
起到阻止捕獲和冒泡階段中當(dāng)前事件的進(jìn)一步傳播挣跋。
- W3C的方法是:
event.stopPropagation()
- IE則是使用:
event.cancelBubble = true
p.addEventListener("click", (event) => {
event.stopPropagation(); // 阻止事件冒泡
console.log('this is p'); // 只會(huì)輸出 'this is p'
});
document.addEventListener("click", (event) => {
event.stopPropagation(); // 阻止事件捕獲
console.log('this is document'); // 只會(huì)輸出 'this is document'
}, true);
兼容IE的寫法:
window.event? window.event.cancelBubble = true : event.stopPropagation();
阻止默認(rèn)事件
- W3C的方法是:
event.preventDefault()
- IE則是使用:
event.returnValue = false
既然是說默認(rèn)行為,當(dāng)然是元素必須有默認(rèn)行為才能被取消狞换,如果元素本身就沒有默認(rèn)行為避咆,調(diào)用當(dāng)然就無效了。
<a id="a">阻止默認(rèn)跳轉(zhuǎn)</a>
<script>
document.getElementById("a").addEventListener("click", (event) => {
event.preventDefault();
console.log('已阻止a鏈接跳轉(zhuǎn)');
});
</script>
事件代理/委托
事件代理的原理用到的就是事件冒泡和目標(biāo)元素哀澈,把事件處理器添加到父元素牌借,等待子元素事件冒泡,并且父元素能夠通過target(IE為srcElement)判斷是哪個(gè)子元素割按,從而做相應(yīng)處理膨报。
<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); // 給每個(gè)li綁定一個(gè)點(diǎn)擊事件
};
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的父級一個(gè)點(diǎn)擊事件
function showColor(e) {
e = e || window.event;
var targetElement = e.target || e.srcElement;
console.log(targetElement.innerHTML);
}
})();
</script>
9. ??跨域
跨域是指從一個(gè)域名的網(wǎng)頁去請求另一個(gè)域名的資源。比如從 www.baidu.com
頁面去請求 www.google.com
的資源现柠。
非同源院领,在請求數(shù)據(jù)時(shí),瀏覽器會(huì)在控制臺(tái)中報(bào)一個(gè)異常够吩,提示拒絕訪問比然。它是由瀏覽器的同源策略造成的,是瀏覽器對JavaScript施加的安全限制周循。
同源策略
同源策略是瀏覽器最核心也最基本的安全功能强法。如果缺少了同源策略,則瀏覽器的正常功能可能都會(huì)受到影響湾笛∫樱可以說Web是構(gòu)建在同源策略基礎(chǔ)之上的,瀏覽器只是針對同源策略的一種實(shí)現(xiàn)嚎研。
- 同源:協(xié)議蓖墅、域名、端口临扮,三者全部相同论矾,才是同源。
- 跨域:協(xié)議杆勇、域名贪壳、端口,只要有一個(gè)的不同蚜退,就是跨域寥袭。
不存在跨域的情況(無視同源策略)
- 服務(wù)端請求服務(wù)端不存在跨域(瀏覽器請求服務(wù)器才存在同源策略)
-
<img src="跨域的圖片地址">
(<img>
標(biāo)簽的src
屬性不存在跨域) -
<link href="跨域的css地址">
(<link>
標(biāo)簽的href
屬性不存在跨域) -
<script src="跨域的js地址"></script>
(<script>
標(biāo)簽的src
屬性不存在跨域)
常見的幾種跨域方法
- jsonp 跨域 (動(dòng)態(tài)添加
<script>
標(biāo)簽,利用src屬性跨域关霸。 常用) - CORS 跨域資源共享(由服務(wù)端實(shí)現(xiàn)。 常用且主流)
- node 代理跨域(利用
proxyTable
使本地的node服務(wù)器代理請求真正的服務(wù)器杰扫。 常用) - document.domain + iframe 跨域
- postMessage 跨域
安全
10. HTTP
HTTP協(xié)議是一個(gè)基于 TCP/IP
通信協(xié)議來傳遞數(shù)據(jù)(HTML 文件, 圖片文件, 查詢結(jié)果等)队寇,用于從服務(wù)器傳輸超文本到本地瀏覽器的傳送協(xié)議。
HTTP三大特點(diǎn)
- HTTP是無連接的:無連接的含義是限制每次連接只處理一個(gè)請求章姓。服務(wù)器處理完客戶的請求佳遣,并收到客戶的應(yīng)答后,即斷開連接凡伊。采用這種方式可以節(jié)省傳輸時(shí)間零渐。
- HTTP是媒體獨(dú)立的:這意味著,只要客戶端和服務(wù)器知道如何處理的數(shù)據(jù)內(nèi)容系忙,任何類型的數(shù)據(jù)都可以通過HTTP發(fā)送诵盼。客戶端以及服務(wù)器指定使用適合的MIME-type內(nèi)容類型。
- HTTP是無狀態(tài)的:無狀態(tài)是指協(xié)議對于事務(wù)處理沒有記憶能力风宁。缺少狀態(tài)意味著如果后續(xù)處理需要前面的信息洁墙,則它必須重傳,這樣可能導(dǎo)致每次連接傳送的數(shù)據(jù)量增大戒财。另一方面热监,在服務(wù)器不需要先前信息時(shí)它的應(yīng)答就較快。
HTTP 消息結(jié)構(gòu)
客戶端請求消息(Request Headers):
客戶端發(fā)送一個(gè)HTTP請求到服務(wù)器的請求消息包括以下格式:請求行(request line)饮寞、請求頭部(header)孝扛、空行和請求數(shù)據(jù)四個(gè)部分組成,下圖給出了請求報(bào)文的一般格式幽崩。
服務(wù)器響應(yīng)消息(Response Headers):
HTTP響應(yīng)也由四個(gè)部分組成苦始,分別是:狀態(tài)行、消息報(bào)頭歉铝、空行和響應(yīng)正文盈简。
HTTP 狀態(tài)碼
RFC 規(guī)定 HTTP 的狀態(tài)碼為三位數(shù),被分為五類:
- 1xx: 信息太示,服務(wù)器收到請求柠贤,需要請求者繼續(xù)執(zhí)行操作
- 2xx: 成功,操作被成功接收并處理 (200 - 請求成功)
- 3xx: 重定向类缤,需要進(jìn)一步的操作以完成請求(302 - 資源(網(wǎng)頁等)被臨時(shí)轉(zhuǎn)移到其它URL臼勉,瀏覽器自動(dòng)處理)
- 4xx: 客戶端錯(cuò)誤,請求包含語法錯(cuò)誤或無法完成請求(404 - 請求的資源(網(wǎng)頁等)不存在)
- 5xx: 服務(wù)器錯(cuò)誤餐弱,服務(wù)器在處理請求的過程中發(fā)生了錯(cuò)誤(500 - 內(nèi)部服務(wù)器錯(cuò)誤)
HTTP 請求方法
方法 | 協(xié)議版本 | 描述 |
---|---|---|
GET | HTTP1.0 | 請求指定的頁面信息宴霸,并返回實(shí)體主體。(獲取數(shù)據(jù)) |
HEAD | HTTP1.0 | 類似于 GET 請求膏蚓,只不過返回的響應(yīng)中沒有具體的內(nèi)容瓢谢,用于獲取報(bào)頭。 |
POST | HTTP1.0 | 向指定資源提交數(shù)據(jù)進(jìn)行處理請求(例如提交表單或者上傳文件)驮瞧。數(shù)據(jù)被包含在請求體中氓扛。POST 請求可能會(huì)導(dǎo)致新的資源的建立和/或已有資源的修改。(新建數(shù)據(jù)) |
PUT | HTTP1.1 | 從客戶端向服務(wù)器傳送的數(shù)據(jù)取代指定的文檔的內(nèi)容论笔。 |
DELETE | HTTP1.1 | 請求服務(wù)器刪除指定的頁面采郎。(刪除數(shù)據(jù)) |
CONNECT | HTTP1.1 | 預(yù)留給能夠?qū)⑦B接改為管道方式的代理服務(wù)器。 |
OPTIONS | HTTP1.1 | 允許客戶端查看服務(wù)器的性能狂魔。 |
TRACE | HTTP1.1 | 回顯服務(wù)器收到的請求蒜埋,主要用于測試或診斷。 |
PATCH | HTTP1.1 | 是對 PUT 方法的補(bǔ)充最楷,用來對已知資源進(jìn)行局部更新 整份。(更新數(shù)據(jù)) |
HTTP 緩存
當(dāng)客戶端向服務(wù)器請求資源時(shí)待错,會(huì)先抵達(dá)瀏覽器緩存,如果瀏覽器有“要請求資源”的副本皂林,就可以直接從瀏覽器緩存中提取而不是從原始服務(wù)器中提取這個(gè)資源忆家。
常見的http緩存只能緩存get請求響應(yīng)的資源,對于其他類型的響應(yīng)則無能為力袭蝗。
緩存的優(yōu)點(diǎn):
- 減少了冗余的數(shù)據(jù)傳輸闰蚕,節(jié)省了你的網(wǎng)絡(luò)費(fèi)用童番。
- 緩解了網(wǎng)絡(luò)瓶頸的問題丽已。不需要更多的帶寬就能夠更快地加載頁面。
- 降低了對原始服務(wù)器的要求。服務(wù)器可以更快地響應(yīng)煤蚌,避免過載的出現(xiàn)。
- 降低了距離時(shí)延,因?yàn)閺妮^遠(yuǎn)的地方加載頁面會(huì)更慢一些巍实。
強(qiáng)緩存與協(xié)商緩存:
- 第一次請求資源時(shí)令漂,服務(wù)器返回資源,并在
respone header
中回傳資源和資源標(biāo)識(shí)(Last-Modified
丸边、Etag
)叠必。 - 第二次請求資源時(shí),瀏覽器會(huì)判斷
response headers
是否命中強(qiáng)緩存妹窖,如果命中纬朝,直接從本地讀取緩存(狀態(tài)碼200),不會(huì)向服務(wù)器發(fā)送請求骄呼。(命中強(qiáng)緩存:cache-control: max-age=31536000
=> 最大緩存時(shí)間365天共苛、Expires有效期未過期) - 當(dāng)強(qiáng)緩存沒有命中時(shí)(
cache-control: no-cache
、Pragma: no-cache
)蜓萄,就把請求參數(shù)(含If-Modified-Since
隅茎、If-Not-Match
)加到request header
頭中傳給服務(wù)器,判斷協(xié)商緩存是否命中嫉沽,如果命中(If-Modified-Since == Last-Modified
患膛、If-Not-Match == Etag
)則服務(wù)器將請求返回(狀態(tài)碼304),不會(huì)返回資源耻蛇,告訴瀏覽器從本地讀取緩存踪蹬。 - 當(dāng)協(xié)商緩存沒有命中(
If-Modified-Since != Last-Modified
、If-Not-Match != Etag
)時(shí)臣咖,服務(wù)器直接返回新的資源(狀態(tài)碼200)和新的資源標(biāo)識(shí)(新的Last-Modified
跃捣、新的Etag
) 。
資源標(biāo)識(shí):
- Last-Modified:資源的最后修改時(shí)間(只能精確到秒級)
- Etag:資源的唯一標(biāo)識(shí)夺蛇,會(huì)優(yōu)先使用(一個(gè)字符串疚漆,類似人類的指紋)
如果資源被重復(fù)生產(chǎn),而內(nèi)容不變刁赦,則 Etag 更精準(zhǔn)
區(qū)別:
- 強(qiáng)緩存命中:不會(huì)請求服務(wù)器娶聘,直接請求緩存;(非成趼觯快)
-
協(xié)商緩存命中:會(huì)請求服務(wù)器丸升,不會(huì)返回內(nèi)容,然后讀取緩存牺氨;(服務(wù)端緩存策略)
區(qū)別
11. 手寫常見JS方法
- 判斷數(shù)據(jù)類型
- 深拷貝
- 對象是否全等
- 防抖
- 節(jié)流
- 數(shù)組拍平
- 數(shù)組去重
- new函數(shù)
??工具
1. Git
項(xiàng)目常用命令:
1. git init // 在當(dāng)前目錄新建一個(gè)Git代碼庫
2. git branch dev-bing // 創(chuàng)建本地分支(dev-bing)
3. git checkout dev-bing // 切換到本地分支(dev-bing)
git checkout -b dev-bind // 創(chuàng)建并切換到本地分支(dev-bing) 相當(dāng)于上面第2 + 第3 的簡寫
4. git branch // 查看分支
5. git push --set-upstream origin dev-bing // 上傳本地當(dāng)前分支代碼到master分支
6. git status // 顯示有變更的文件 如果字體為紅色狡耻,則表示列出的是當(dāng)前目錄所有還沒有被git管理的文件和被git管理且被修改但還未提交(git commit)的文件,也就是所有改動(dòng)文件猴凹。
7. git diff // 查看所有修改內(nèi)容
git diff test.txt // 查看具體文件修改內(nèi)容
8. git log // 查看提交的記錄日志
git log test.txt // 查看具體文件提交的記錄日志
9. git stash // 臨時(shí)保存,可跨分支 (只能在未add之前才能使用)
10. git stash pop // 恢復(fù)之前緩存
11. git checkout . // (有個(gè)點(diǎn)) 撤銷當(dāng)前所有的修改
git checkout test.txt // 撤銷具體文件的修改
12. git add . // (有個(gè)點(diǎn)) 表示添加當(dāng)前目錄下的所有文件和子目錄(或git add -A)
git add test.txt // 添加具體文件(test.txt)
13. git commit -m 'test' // 將文件上傳至遠(yuǎn)程 master 分支并添加備注"test"
14. git pull origin dev-bing // 從遠(yuǎn)程倉庫下載到dev-bing倉庫
git pull // 如果當(dāng)前分支是dev-bing git pull相當(dāng)于git pull origin dev-bing
15. git push origin dev-bing // 從本地倉庫上傳到遠(yuǎn)程倉庫(提交)
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主機(jī)夷狰,同時(shí)指定origin為默認(rèn)主機(jī),后面就可以不加任何參數(shù)使用git push了郊霎。
git push // 設(shè)置默認(rèn)主機(jī)后(git push -u origin master)可簡寫
19. git checkout dev-bing // 返回dev-bing分支
2. ??瀏覽器
瀏覽器從輸入U(xiǎn)RL到渲染完頁面的整個(gè)過程
- 獲取IP地址
- TCP/IP三次握手建立連接
- 瀏覽器向web服務(wù)器發(fā)送http請求
- 瀏覽器渲染
- 四次揮手?jǐn)嚅_連接
瀏覽器渲染過程
- DOM 樹:解析 HTML 構(gòu)建 DOM(DOM 樹)
- CSS 樹:解析 CSS 構(gòu)建 CSSOM(CSS 樹)
- 渲染樹:CSSOM 和 DOM 一起生成 Render Tree(渲染樹)
- 布局(layout):根據(jù)Render Tree瀏覽器就知道網(wǎng)頁中有哪些節(jié)點(diǎn)沼头,以及各個(gè)節(jié)點(diǎn)與 CSS 的關(guān)系,從而知道每個(gè)節(jié)點(diǎn)的位置和幾何屬性(重排)
- 繪制(Paint):根據(jù)計(jì)算好的信息繪制整個(gè)頁面(重繪)
3. 其他
yarn
gulp
babel
vConsole
??Vue
1. MVVM
MVVM 分為 Model
书劝、 View
进倍、 ViewModel
三者:
-
Model
:數(shù)據(jù)層,數(shù)據(jù)和業(yè)務(wù)邏輯都在Model層中定義庄撮。 -
View
:視圖層背捌,也就是用戶界面,負(fù)責(zé)數(shù)據(jù)的展示洞斯。 -
ViewModel
:視圖數(shù)據(jù)層毡庆, ViewModel層通過雙向數(shù)據(jù)綁定將View層和Model層連接了起來(View和Model層的橋梁),使得View層和Model層的同步工作完全是自動(dòng)的烙如。
MVVM
Model和View并無直接關(guān)聯(lián)么抗,而是通過ViewModel這個(gè)橋梁來進(jìn)行聯(lián)系的,ViewModel就是View與Model的連接器亚铁,View與Model通過ViewModel實(shí)現(xiàn)雙向綁定蝇刀。
2. ??生命周期
Vue2生命周期
-
beforeCreate:創(chuàng)建之前(
el
、data
和message
都還是undefined
,不可用的) -
created:創(chuàng)建完畢(能讀取到數(shù)據(jù)
data
的值,但是DOM
還沒生成) -
beforeMount:掛載之前(生成
DOM
,但此時(shí){{ message }}
還沒有掛載data
中的數(shù)據(jù)) -
mounted:掛載完畢(
{{ message }}
已經(jīng)成功掛載渲染data
的值) - beforeUpdate:更新之前
- updated:更新完畢
- beforeDestroy:銷毀之前
-
destroyed:銷毀完畢(實(shí)例與視圖的關(guān)系解綁徘溢,再修改
message
的值吞琐,視圖再也不會(huì)更新了) - activated:
keep-alive
組件激活時(shí)調(diào)用 - deactivated:
keep-alive
組件停用時(shí)調(diào)用
注:
-
activated
和deactivated
是比較特殊的兩個(gè)鉤子捆探,需要keep-live
配合使用 - 當(dāng)引入
keep-alive
的時(shí)候,頁面第一次進(jìn)入站粟,鉤子的觸發(fā)順序created
=>mounted
=>activated
黍图,退出時(shí)觸發(fā)deactivated
。當(dāng)再次進(jìn)入(前進(jìn)或者后退)時(shí)奴烙,只觸發(fā)activated
助被。
Vue3生命周期
- onBeforeMount
- onMounted
- onBeforeUpdate
- onUpdated
- onBeforeUnmount
- onUnmounted
- onActivated
- onDeactivated
- onErrorCaptured
- onRenderTracked
- onRenderTriggered
3. ??computed 與 watch
computed(計(jì)算屬性)
- 屬性的結(jié)果會(huì)被緩存(默認(rèn)走緩存),當(dāng)
computed
中的函數(shù)所依賴的屬性沒有發(fā)生改變的時(shí)候切诀,那么調(diào)用當(dāng)前函數(shù)的時(shí)候結(jié)果會(huì)從緩存中讀取揩环,除非依賴的響應(yīng)式屬性變化時(shí)才會(huì)重新計(jì)算; -
不支持異步幅虑,當(dāng)
computed
內(nèi)有異步操作時(shí)無效丰滑,無法監(jiān)聽數(shù)據(jù)的變化; -
computed
中的函數(shù)必須用return
返回最終的結(jié)果翘单。computed
更高效吨枉,優(yōu)先使用; -
當(dāng)一個(gè)屬性受多個(gè)屬性影響的時(shí)候哄芜,一般用
computed
(例如:詳細(xì)地址 = 省+市+區(qū)+街道+樓棟+房號 )貌亭;
watch(監(jiān)聽屬性)
- 不支持緩存,數(shù)據(jù)變认臊,直接會(huì)觸發(fā)相應(yīng)的操作圃庭;
- 支持異步;
- 監(jiān)聽的函數(shù)接收兩個(gè)參數(shù)失晴,第一個(gè)參數(shù)是最新的值(newVal)剧腻;第二個(gè)參數(shù)是輸入之前的值(oldVal);
-
當(dāng)一條數(shù)據(jù)影響多條數(shù)據(jù)的時(shí)候涂屁,一般使用
watch
(例如:搜索數(shù)據(jù)(異步) 书在,觸發(fā)一系列數(shù)據(jù)的變化);
4. ??v-if 與 v-show
v-if
- 是通過控制 DOM 元素的存在與否來控制元素的顯隱拆又;
- 切換時(shí)儒旬,是對 DOM 元素進(jìn)行一個(gè)創(chuàng)建和銷毀的動(dòng)作;
- 是惰性的帖族,如果初始條件為假栈源,則什么也不做;只有在條件第一次變?yōu)檎鏁r(shí)才開始局部編譯;
- 有更高的切換消耗竖般;
v-show
- 是通過設(shè)置 DOM 元素的
display
樣式甚垦,block
為顯示晦毙,none
為隱藏微酬; - 切換時(shí)已骇,只是簡單的基于CSS切換误债;
- 是在任何條件下(首次條件是否為真)都被編譯,然后被緩存迄埃,而且 DOM 元素保留男杈;
- 有更高的初始渲染消耗;
基于以上區(qū)別调俘,因此,如果需要非常頻繁地切換旺垒,則使用 v-show
較好彩库;如果在運(yùn)行時(shí)條件很少改變,則使用 v-if
較好先蒋。
5. data 必須是一個(gè)函數(shù)骇钦,而不是對象
如果 Vue 組件中的 data 是個(gè)對象,那么就像所有復(fù)用這個(gè)組件的地方竞漾,都在使用這個(gè)組件里面唯一的一個(gè) data眯搭,所有使用組件的地方的 data 都會(huì)指向棧內(nèi)這一個(gè) data 的地址,那么會(huì)造成一個(gè)改 data 的值业岁,所有其他組件的 data 都會(huì)被改變鳞仙。
如果在函數(shù)中返回一個(gè)對象,那么在每次創(chuàng)建一個(gè)組件的時(shí)候笔时,每次返回的都是一個(gè)新對象(Object的實(shí)例)棍好,在內(nèi)存中同時(shí)開辟一塊空間給當(dāng)前組件存放 data 。這樣就不會(huì)出現(xiàn)共用一個(gè) data 的現(xiàn)象
6. ??diff 算法
diff 算法是一種優(yōu)化手段允耿。比如有時(shí)候我們修改了某個(gè)數(shù)據(jù)借笙,如果直接渲染到真實(shí) DOM 上會(huì)引起整個(gè) DOM 樹的重繪和重排,這樣開銷是非常大的较锡。
我們可以先根據(jù)真實(shí) DOM 生成一棵虛擬DOM(virtual DOM)樹业稼,當(dāng)虛擬DOM某個(gè)節(jié)點(diǎn)的數(shù)據(jù)改變后,會(huì)生成一個(gè)新的 Vnode
(虛擬節(jié)點(diǎn))蚂蕴。然后 Vnode
和 oldVnode
作對比低散,發(fā)現(xiàn)有不一樣的地方就直接修改在真實(shí)的 DOM 上,然后使 oldVnode
的值為Vnode
掂墓。此時(shí)我們只更新我們修改的那一小塊 DOM谦纱,而不要更新整個(gè) DOM。
diff的過程就是調(diào)用patch函數(shù)跨嘉,比較新舊節(jié)點(diǎn)吃嘿,一邊比較一邊給真實(shí)的DOM打補(bǔ)丁梦重。
在采取 diff 算法比較新舊節(jié)點(diǎn)的時(shí)候亮瓷,比較只會(huì)在同層級進(jìn)行, 不會(huì)跨層級比較。
概括起來就是:對操作前后的 DOM 樹同一層的節(jié)點(diǎn)進(jìn)行對比蚓胸,一層一層對比除师,然后再插入真實(shí)的dom中,重新渲染锹安。
7. for 循環(huán)中 key 的作用
vue中列表循環(huán)需加 :key="唯一標(biāo)識(shí)"
倚舀, 唯一標(biāo)識(shí)可以是 item
里面 id
、 index
等痕貌。
- key 的作用主要是為了更高效的對比虛擬DOM中每個(gè)節(jié)點(diǎn)是否是相同節(jié)點(diǎn);
- Vue 在 patch 過程中判斷兩個(gè)節(jié)點(diǎn)是否是相同節(jié)點(diǎn), key 是一個(gè)必要條件风罩,渲染一組列表時(shí),key往往是唯一標(biāo)識(shí)芯侥,所以如果不定義 key 的話泊交,Vue 只能認(rèn)為比較的兩個(gè)節(jié)點(diǎn)是同一個(gè),哪怕它們實(shí)際上不是柱查,這導(dǎo)致了頻繁更新元素廓俭,使得整個(gè) patch 過程比較低效,影響性能;
- 從源碼中可以知道唉工,Vue 判斷兩個(gè)節(jié)點(diǎn)是否相同時(shí)主要判斷兩者的 key 和元素類型等研乒,因此如果不設(shè)置 key ,它的值就是undefined,則可能永遠(yuǎn)認(rèn)為這是兩個(gè)相同的節(jié)點(diǎn)淋硝,只能去做更新操作雹熬,這造成了大量的 DOM 更新操作,明顯是不可取的谣膳。
不建議 用 index
作為 key
竿报,和沒寫基本上沒區(qū)別,因?yàn)椴还苣銛?shù)組的順序怎么顛倒继谚,index
都是 0, 1, 2 這樣排列烈菌,導(dǎo)致 Vue 會(huì)復(fù)用錯(cuò)誤的舊子節(jié)點(diǎn),做很多額外的工作
8. 雙向綁定
當(dāng)一個(gè)Vue實(shí)例創(chuàng)建時(shí),Vue會(huì)遍歷 data 選項(xiàng)的屬性芽世,利用 Object.defineProperty
將它們轉(zhuǎn)為 getter/setter
并且在內(nèi)部追蹤相關(guān)依賴挚赊,在屬性被訪問和修改時(shí)通知變化。每個(gè)組件實(shí)例都有相應(yīng)的 watcher
程序?qū)嵗闷埃鼤?huì)在組件渲染的過程中把屬性記錄為依賴荠割,之后當(dāng)依賴項(xiàng)的 setter
被調(diào)用時(shí),會(huì)通知 watcher
重新計(jì)算蔑鹦,從而致使它關(guān)聯(lián)的組件得以更新举反。
極簡版雙向綁定:
<input type="text" id="input" value="">
<span id="span"></span>
<script>
let obj = {}
Object.defineProperty(obj, 'text', { // 監(jiān)聽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 中雙向綁定是一個(gè)指令v-model
室囊,可以綁定一個(gè)動(dòng)態(tài)值到視圖融撞,同時(shí)視圖中變化能改變該值饶火。
v-model
是語法糖肤寝,默認(rèn)情況下相于:value
和@input
:
<template>
<div id="app">
{{username}} <br />
<input type="text" v-model="username"></input>
<!-- 等價(jià)于 -->
<input type="text" :value="username" @input="username=$event.target.value">
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
username: ''
}
}
}
</script>
9. ??組件的通信
父組件向子組件傳值(props)
<div id="app">
<!-- 1.通過 v-bind 將數(shù)據(jù)傳給子組件 -->
<test :ss='name'></test>
</div>
<script>
var a = new Vue({
el:'#app',
data:{
name:'bing',
},
components: {
// 子組件<test>
test:{
props: ['ss'], // 2.接收父組件傳遞過來的數(shù)據(jù)
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: '子組件的數(shù)據(jù)'
}
},
methods: {
increment: function () {
// 通過 $emit 定義一個(gè)自定義事件 increment ,同時(shí)傳入 this.counter 參數(shù)义桂,對外拋出
this.$emit('increment', this.counter); // 步驟2
}
}
});
new Vue({
el: '#app',
data: {
total: '父組件的數(shù)據(jù):'
},
methods: {
// 能接收到子組件傳遞過來的數(shù)據(jù)(e)
incrementTotal: function (e) { // 步驟4
this.total = this.total + e[0]
console.log(e);
}
}
})
</script>
兄弟組件傳值(EventBus)
- 新創(chuàng)建一個(gè)
event-bus.js
文件
// event-bus.js
import Vue from 'vue'
export const EventBus = new Vue();
- A 頁面發(fā)送數(shù)據(jù)
<!-- A.vue -->
<template>
<button @click="sendMsg()">-</button>
</template>
<script>
import { EventBus } from "../event-bus.js";
export default {
methods: {
sendMsg() {
EventBus.$emit("aMsg", '來自A頁面的消息'); // 對外發(fā)送數(shù)據(jù)
}
}
};
</script>
- B 頁面接收數(shù)據(jù)
<!-- 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 發(fā)送來的消息
this.msg = msg;
});
}
};
</script>
- 移除事件監(jiān)聽
import { EventBus } from "../event-bus.js";
EventBus.$off('aMsg', {}); // 移除應(yīng)用內(nèi)所有對此某個(gè)事件的監(jiān)聽
// 或
EventBus.$off(); // 移除所有事件監(jiān)聽
Vuex
- state:定義了應(yīng)用狀態(tài)的數(shù)據(jù)結(jié)構(gòu),可以在這里設(shè)置默認(rèn)的初始狀態(tài)溉瓶。
-
getter:可以認(rèn)為是
store
的計(jì)算屬性(有緩存嚷闭,只有當(dāng)它的依賴值發(fā)生了改變才會(huì)被重新計(jì)算)灾锯。 -
mutation:是唯一更改
store
中狀態(tài)的方法顺饮,且必須是同步函數(shù)兼雄。 -
action:用于提交
mutation
(再由mutation
更改store
中狀態(tài))赦肋,而不是直接變更狀態(tài),可以包含任意異步操作趣避。 -
module:可以將 store 分割成模塊(module)程帕。每個(gè)模塊擁有自己的
state
愁拭、mutation
敛苇、action
枫攀、getter
来涨、甚至是嵌套子模塊
基本用法:
//創(chuàng)建一個(gè) store
const store = new Vuex.Store({
//state存儲(chǔ)應(yīng)用層的狀態(tài)
state: {
count: 5 // 組件中通過 this.$store.state.count; 獲取
},
getters: {
newCount: state => state.count * 3 // 組件中通過 this.$store.getters.newCount; 獲取
},
// mutations是修改state中數(shù)據(jù)的唯一途徑
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 的狀態(tài)
store.state.b // -> moduleB 的狀態(tài)
10. 路由
Vue路由導(dǎo)航 router-link 和 router.push
router-link
1. 不攜帶參數(shù)
// 字符串
<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
攜帶參數(shù):
- 地址欄變成
/login?color=red
- 可通過
{{$route.query.color}}
或this.$route.query.color
獲取參數(shù)
<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
攜帶參數(shù):
- 地址欄變成
/login/red
- 可通過
{{$route.params.color}}
或this.$route.params.color
獲取參數(shù)
// 無法獲取參數(shù)
// 報(bào)警告(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 獲取參數(shù)技羔。
<router-link :to="{name: 'Login', params: { color: 'red' }}"> to login</router-link>
此時(shí) router.js
中需要設(shè)置 path: '/login/:color?'
// router.js
var router = new Router({
routes: [{
path: '/login/:color?', // :color? => ?問號的意思是該參數(shù)不是必傳項(xiàng)藤滥,不傳不會(huì)報(bào)錯(cuò)
name: 'Login',
component: Login
}]
router.push
1. 不攜帶參數(shù)
// 字符串
router.push('/login')
// 對象
router.push({path:'/login'})
// 命名路由
router.push({name: 'Login'})
2. 通過 query
攜帶參數(shù):
// 可通過 {{$route.query.color}} 或 this.$route.query.color 獲取參數(shù)
router.push({path: '/login', query: {color: 'red' }})
// 可通過 {{$route.query.color}} 或 this.$route.query.color 獲取參數(shù)
router.push({name: 'Login', query: {color: 'red' }})
3. 通過 params
攜帶參數(shù):
// 無法獲取參數(shù)
router.push({path:'/login', params:{ color: 'red' }})
// 通過 {{$route.params.color}} 或 this.$route.params.color 獲取參數(shù)。
router.push({name:'Login', params:{ color: 'red' }})
route 的區(qū)別
-
$router
:是指整個(gè)路由實(shí)例,你可以操控整個(gè)路由标沪,用法如下:this.$router.go(-1); // 向前或者向后跳轉(zhuǎn)n個(gè)頁面金句,n可為正整數(shù)或負(fù)整數(shù) this.$router.push('/'); // 跳轉(zhuǎn)到指定url路徑违寞,history棧中會(huì)有記錄坞靶,點(diǎn)擊返回會(huì)跳轉(zhuǎn)到上個(gè)頁面 this.$router.replace('/'); // 跳轉(zhuǎn)到指定url路徑,但是history棧中不會(huì)有記錄拍冠,點(diǎn)擊返回會(huì)跳轉(zhuǎn)到上上個(gè)頁面
-
$route
:是指當(dāng)前路由實(shí)例$router
跳轉(zhuǎn)到的路由對象庆杜;路由實(shí)例可以包含多個(gè)路由對象晃财,它們是父子包含關(guān)系// 獲取路由傳遞過來的參數(shù) this.$route.params.userId this.$route.query.userName
router.js (含路由懶加載和路由鑒權(quán))
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,不需要做鑒權(quán)處理
}
},
{
path: '/test',
name: 'test',
component: () => import('./views/Test.vue'),// vue路由懶加載 異步加載
meta: {
title: 'test',
requireAuth: true // 只要此字段為true钢猛,必須做鑒權(quán)處理
}
},
{
path: '/login',
name: 'login',
component: () => import('./views/Login.vue'),// vue路由懶加載 異步加載
meta: {
noNav: true // 不顯示nav
}
}]
});
let indexScrollTop = 0;
router.beforeEach((to, from, next) => {
// 路由鑒權(quán):在路由meta對象中由個(gè)requireAuth字段命迈,只要此字段為true壶愤,必須做鑒權(quán)處理
if (to.matched.some(res => res.meta.requireAuth)) {
const token = getStore({ name: 'access_token', type: "string" });// 獲取localstorage中的access_token
console.log(token);
// 路由進(jìn)入下一個(gè)路由對象前征椒,判斷是否需要登陸
if (!token) {
// 未登錄
next({
path: '/login',
query: {
redirect: to.path // 將跳轉(zhuǎn)的路由path作為參數(shù)迂尝,登錄成功后再跳轉(zhuǎn)到該路由
}
})
} else {
// 用戶信息是否過期
let overdueTime = token.overdueTime;
let nowTime = +new Date();
// 登陸過期和未過期
if (nowTime > overdueTime) {
// 登錄過期的處理垄开,君可按需處理之后再執(zhí)行如下方法去登錄頁面
// 我這里沒有其他處理溉躲,直接去了登錄頁面
next({
path: '/login',
query: {
redirect: to.path
}
})
} else {
next()
}
}
} else {
next()
}
if (to.path !== '/') {
// 記錄現(xiàn)在滾動(dòng)的位置
indexScrollTop = document.body.scrollTop
}
document.title = to.meta.title || document.title
})
router.afterEach(route => {
if (route.path !== '/') {
document.body.scrollTop = 0
} else {
Vue.nextTick(() => {
// 回到之前滾動(dòng)位置
document.body.scrollTop = indexScrollTop
})
}
})
export default router;
路由懶加載的不同寫法:
- webpack < 2.4版(vue-cli2)的懶加載
const Index = resolve => require(['./views/Index.vue'], resolve);
- webpack > 2.4版(vue-cli3)的懶加載
const Index = () => import('./views/Index.vue');
11. 其他