- 盒模型
頁面渲染時(shí),dom 元素所采用的 布局模型??赏ㄟ^box-sizing進(jìn)行設(shè)置。根據(jù)計(jì)算寬高的區(qū)域可分為: border-box - BFC
塊級(jí)格式化上下文,是一個(gè)獨(dú)立的渲染區(qū)域,讓處于 BFC 內(nèi)部的元素與外部的元素相互隔離,使內(nèi)外元素的定位不會(huì)相互影響。 - 居中布局
水平居中
行內(nèi)元素: text-align: center
塊級(jí)元素: margin: 0 auto
absolute + transform
flex + justify-content: center
垂直居中
line-height: height
absolute + transform
flex + align-items: center
table
水平垂直居中
absolute + transform
flex + justify-content + align-items
- 選擇器優(yōu)先級(jí)
!important > 行內(nèi)樣式 > #id > .class > tag > * > 繼承 > 默認(rèn)
選擇器 從右往左 解析
- 閉包
閉包屬于一種特殊的作用域,稱為 靜態(tài)作用域。它的定義可以理解為: 父函數(shù)被銷毀 的情況下,返回出的子函數(shù)的[[scope]]中仍然保留著父級(jí)的單變量對(duì)象和作用域鏈,因此可以繼續(xù)訪問到父級(jí)的變量對(duì)象,這樣的函數(shù)稱為閉包。
- 對(duì)象的拷貝
淺拷貝: 以賦值的形式拷貝引用對(duì)象,仍指向同一個(gè)地址,修改時(shí)原對(duì)象也會(huì)受到影響
Object.assign
展開運(yùn)算符(...)
深拷貝: 完全拷貝一個(gè)新對(duì)象,修改時(shí)原對(duì)象不再受到任何影響
JSON.parse(JSON.stringify(obj)): 性能最快
具有循環(huán)引用的對(duì)象時(shí),報(bào)錯(cuò)
當(dāng)值為函數(shù)、undefined、或symbol時(shí),無法拷貝
遞歸進(jìn)行逐一賦值
- new運(yùn)算符的執(zhí)行過程
新生成一個(gè)對(duì)象
鏈接到原型: obj.proto = Con.prototype
綁定this: apply
返回新對(duì)象(如果構(gòu)造函數(shù)有自己 retrun 時(shí),則返回該值)
- instanceof原理
能在實(shí)例的 原型對(duì)象鏈 中找到該構(gòu)造函數(shù)的prototype屬性所指向的 原型對(duì)象,就返回true。即:
- 繼承
在 JS 中,繼承通常指的便是 原型鏈繼承,也就是通過指定原型,并可以通過原型鏈繼承原型上的屬性或者方法。
- 模塊化
模塊化開發(fā)在現(xiàn)代開發(fā)中已是必不可少的一部分,它大大提高了項(xiàng)目的可維護(hù)、可拓展和可協(xié)作性。通常,我們 在瀏覽器中使用 ES6 的模塊化支持,在 Node 中使用 commonjs 的模塊化支持。
分類:
es6: import / export
commonjs: require / module.exports / exports
amd: require / defined
require與import的區(qū)別
require支持 動(dòng)態(tài)導(dǎo)入,import不支持,正在提案 (babel 下可支持)
require是 同步 導(dǎo)入,import屬于 異步 導(dǎo)入
require是 值拷貝,導(dǎo)出值變化不會(huì)影響導(dǎo)入值;import指向 內(nèi)存地址,導(dǎo)入值會(huì)隨導(dǎo)出值而變化
- 防抖與節(jié)流
防抖與節(jié)流函數(shù)是一種最常用的 高頻觸發(fā)優(yōu)化方式,能對(duì)性能有較大的幫助。
防抖 (debounce): 將多次高頻操作優(yōu)化為只在最后一次執(zhí)行,通常使用的場(chǎng)景是:用戶輸入,只需再輸入完成后做一次輸入校驗(yàn)即可。
節(jié)流(throttle): 每隔一段時(shí)間后執(zhí)行一次,也就是降低頻率,將高頻操作優(yōu)化成低頻操作,通常使用場(chǎng)景: 滾動(dòng)條事件 或者 resize 事件,通常每隔 100~500 ms執(zhí)行一次即可。
- ES6/ES7
由于 Babel 的強(qiáng)大和普及,現(xiàn)在 ES6/ES7 基本上已經(jīng)是現(xiàn)代化開發(fā)的必備了。通過新的語法糖,能讓代碼整體更為簡潔和易讀。
聲明
let / const: 塊級(jí)作用域、不存在變量提升、暫時(shí)性死區(qū)、不允許重復(fù)聲明
const: 聲明常量,無法修改
解構(gòu)賦值
class / extend: 類聲明與繼承
Set / Map: 新的數(shù)據(jù)結(jié)構(gòu)
異步解決方案:
Promise的使用與實(shí)現(xiàn)
generator:
yield: 暫停代碼
next(): 繼續(xù)執(zhí)行代碼
- babel編譯原理
babylon 將 ES6/ES7 代碼解析成 AST
babel-traverse 對(duì) AST 進(jìn)行遍歷轉(zhuǎn)譯,得到新的 AST
新 AST 通過 babel-generator 轉(zhuǎn)換成 ES5
- 數(shù)組(array)
map: 遍歷數(shù)組,返回回調(diào)返回值組成的新數(shù)組
forEach: 無法break,可以用try/catch中throw new Error來停止
filter: 過濾
some: 有一項(xiàng)返回true,則整體為true
every: 有一項(xiàng)返回false,則整體為false
join: 通過指定連接符生成字符串
push / pop: 末尾推入和彈出,改變?cè)瓟?shù)組, 返回推入/彈出項(xiàng)
unshift / shift: 頭部推入和彈出,改變?cè)瓟?shù)組,返回操作項(xiàng)
sort(fn) / reverse: 排序與反轉(zhuǎn),改變?cè)瓟?shù)組
concat: 連接數(shù)組,不影響原數(shù)組, 淺拷貝
slice(start, end): 返回截?cái)嗪蟮男聰?shù)組,不改變?cè)瓟?shù)組
splice(start, number, value...): 返回刪除元素組成的數(shù)組,value 為插入項(xiàng),改變?cè)瓟?shù)組
indexOf / lastIndexOf(value, fromIndex): 查找數(shù)組項(xiàng),返回對(duì)應(yīng)的下標(biāo)
reduce / reduceRight(fn(prev, cur), defaultPrev): 兩兩執(zhí)行,prev 為上次化簡函數(shù)的return值,cur 為當(dāng)前值(從第二項(xiàng)開始)
數(shù)組亂序:
- nextTick
在下次dom更新循環(huán)結(jié)束之后執(zhí)行延遲回調(diào),可用于獲取更新后的dom狀態(tài)
新版本中默認(rèn)是mincrotasks, v-on中會(huì)使用macrotasks
macrotasks任務(wù)的實(shí)現(xiàn):
setImmediate / MessageChannel / setTimeout
- 生命周期
init
initLifecycle/Event,往vm上掛載各種屬性
callHook: beforeCreated: 實(shí)例剛創(chuàng)建
initInjection/initState: 初始化注入和 data 響應(yīng)性
created: 創(chuàng)建完成,屬性已經(jīng)綁定, 但還未生成真實(shí)dom
進(jìn)行元素的掛載: mount()
是否有template: 解析成render function*.vue文件: vue-loader會(huì)將<template>編譯成render function
beforeMount: 模板編譯/掛載之前
執(zhí)行render function,生成真實(shí)的dom,并替換到dom tree中
mounted: 組件已掛載
update:
執(zhí)行diff算法,比對(duì)改變是否需要觸發(fā)UI更新
flushScheduleQueuewatcher.before: 觸發(fā)beforeUpdate鉤子 - watcher.run(): 執(zhí)行watcher中的 notify,通知所有依賴項(xiàng)更新UI
觸發(fā)updated鉤子: 組件已更新
actived / deactivated(keep-alive): 不銷毀,緩存,組件激活與失活
destroy:
beforeDestroy: 銷毀開始
銷毀自身且遞歸銷毀子組件以及事件監(jiān)聽
remove(): 刪除節(jié)點(diǎn)
watcher.teardown(): 清空依賴
vm.$off(): 解綁監(jiān)聽
destroyed: 完成后觸發(fā)鉤子
new Vue({})
// 初始化Vue實(shí)例
function _init() {
// 掛載屬性
initLifeCycle(vm)
// 初始化事件系統(tǒng),鉤子函數(shù)等
initEvent(vm)
// 編譯slot、vnode
initRender(vm)
// 觸發(fā)鉤子
callHook(vm, 'beforeCreate')
// 添加inject功能
initInjection(vm)
// 完成數(shù)據(jù)響應(yīng)性 props/data/watch/computed/methods
initState(vm)
// 添加 provide 功能
initProvide(vm)
// 觸發(fā)鉤子
callHook(vm, 'created')
// 掛載節(jié)點(diǎn)
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
// 掛載節(jié)點(diǎn)實(shí)現(xiàn)
function mountComponent(vm) {
// 獲取 render function
if (!this.options.render) {
// template to render
// Vue.compile = compileToFunctions
let { render } = compileToFunctions()
this.options.render = render
}
// 觸發(fā)鉤子
callHook('beforeMounte')
// 初始化觀察者
// render 渲染 vdom,
vdom = vm.render()
// update: 根據(jù) diff 出的 patchs 掛載成真實(shí)的 dom
vm._update(vdom)
// 觸發(fā)鉤子
callHook(vm, 'mounted')
}
// 更新節(jié)點(diǎn)實(shí)現(xiàn)
funtion queueWatcher(watcher) {
nextTick(flushScheduleQueue)
}
// 清空隊(duì)列
function flushScheduleQueue() {
// 遍歷隊(duì)列中所有修改
for(){
// beforeUpdate
watcher.before()
// 依賴局部更新節(jié)點(diǎn)
watcher.update()
callHook('updated')
}
}
// 銷毀實(shí)例實(shí)現(xiàn)
Vue.prototype.$destory = function() {
// 觸發(fā)鉤子
callHook(vm, 'beforeDestory')
// 自身及子節(jié)點(diǎn)
remove()
// 刪除依賴
watcher.teardown()
// 刪除監(jiān)聽
vm.$off()
// 觸發(fā)鉤子
callHook(vm, 'destoryed')
}
- 數(shù)據(jù)響應(yīng)(數(shù)據(jù)劫持)
看完生命周期后,里面的watcher等內(nèi)容其實(shí)是數(shù)據(jù)響應(yīng)中的一部分。數(shù)據(jù)響應(yīng)的實(shí)現(xiàn)由兩部分構(gòu)成: 觀察者( watcher ) 和 依賴收集器( Dep ),其核心是 defineProperty這個(gè)方法,它可以 重寫屬性的 get 與 set 方法,從而完成監(jiān)聽數(shù)據(jù)的改變。
Observe (觀察者)觀察 props 與 state
遍歷 props 與 state,對(duì)每個(gè)屬性創(chuàng)建獨(dú)立的監(jiān)聽器( watcher )
使用 defineProperty 重寫每個(gè)屬性的 get/set(defineReactive)
get: 收集依賴
Dep.depend()watcher.addDep()
set: 派發(fā)更新
Dep.notify()
watcher.update()
queenWatcher()
nextTick
flushScheduleQueue
watcher.run()
updateComponent()
大家可以先看下面的數(shù)據(jù)相應(yīng)的代碼實(shí)現(xiàn)后,理解后就比較容易看懂上面的簡單脈絡(luò)了。
let data = {a: 1}
// 數(shù)據(jù)響應(yīng)性
observe(data)
// 初始化觀察者
new Watcher(data, 'name', updateComponent)
data.a = 2
// 簡單表示用于數(shù)據(jù)更新后的操作
function updateComponent() {
vm._update() // patchs
}
// 監(jiān)視對(duì)象
function observe(obj) {
// 遍歷對(duì)象,使用 get/set 重新定義對(duì)象的每個(gè)屬性值
Object.keys(obj).map(key => {
defineReactive(obj, key, obj[key])
})
}
function defineReactive(obj, k, v) {
// 遞歸子屬性
if (type(v) == 'object') observe(v)
// 新建依賴收集器
let dep = new Dep()
// 定義get/set
Object.defineProperty(obj, k, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
// 當(dāng)有獲取該屬性時(shí),證明依賴于該對(duì)象,因此被添加進(jìn)收集器中
if (Dep.target) {
dep.addSub(Dep.target)
}
return v
},
// 重新設(shè)置值時(shí),觸發(fā)收集器的通知機(jī)制
set: function reactiveSetter(nV) {
v = nV
dep.nofify()
},
})
}
// 依賴收集器
class Dep {
constructor() {
this.subs = []
}
addSub(sub) {
this.subs.push(sub)
}
notify() {
this.subs.map(sub => {
sub.update()
})
}
}
Dep.target = null
// 觀察者
class Watcher {
constructor(obj, key, cb) {
Dep.target = this
this.cb = cb
this.obj = obj
this.key = key
this.value = obj[key]
Dep.target = null
}
addDep(Dep) {
Dep.addSub(this)
}
update() {
this.value = this.obj[this.key]
this.cb(this.value)
}
before() {
callHook('beforeUpdate')
}
}
- virtual dom 原理實(shí)現(xiàn)
創(chuàng)建 dom 樹
樹的diff,同層對(duì)比,輸出patchs(listDiff/diffChildren/diffProps)
沒有新的節(jié)點(diǎn),返回
新的節(jié)點(diǎn)tagName與key不變, 對(duì)比props,繼續(xù)遞歸遍歷子樹
對(duì)比屬性(對(duì)比新舊屬性列表):
舊屬性是否存在與新屬性列表中
都存在的是否有變化
是否出現(xiàn)舊列表中沒有的新屬性
tagName和key值變化了,則直接替換成新節(jié)點(diǎn)
渲染差異
遍歷patchs, 把需要更改的節(jié)點(diǎn)取出來
局部更新dom
// diff算法的實(shí)現(xiàn)梳理
function diff(oldTree, newTree) {
// 差異收集
let pathchs = {}
dfs(oldTree, newTree, 0, pathchs)
return pathchs
}
function dfs(oldNode, newNode, index, pathchs) {
let curPathchs = []
if (newNode) {
// 當(dāng)新舊節(jié)點(diǎn)的 tagName 和 key 值完全一致時(shí)
if (oldNode.tagName === newNode.tagName && oldNode.key === newNode.key) {
// 繼續(xù)比對(duì)屬性差異
let props = diffProps(oldNode.props, newNode.props)
curPathchs.push({ type: 'changeProps', props })
// 遞歸進(jìn)入下一層級(jí)的比較
diffChildrens(oldNode.children, newNode.children, index, pathchs)
} else {
// 當(dāng) tagName/key 修改了后,表示已經(jīng)是全新節(jié)點(diǎn),則沒必要繼續(xù)比了
curPathchs.push({ type: 'replaceNode', node: newNode })
}
}
// 構(gòu)建出整顆差異樹
if (curPathchs.length) {
if(pathchs[index]){
pathchs[index] = pathchs[index].concat(curPathchs)
} else {
pathchs[index] = curPathchs
}
}
}
// 屬性對(duì)比實(shí)現(xiàn)
function diffProps(oldProps, newProps) {
let propsPathchs = []
// 遍歷新舊屬性列表
// 查找刪除項(xiàng)、修改項(xiàng)、查找新增項(xiàng)
forin(olaProps, (k, v) => {
if (!newProps.hasOwnProperty(k)) {
propsPathchs.push({ type: 'remove', prop: k })
} else {
if (v !== newProps[k]) {
propsPathchs.push({ type: 'change', prop: k , value: newProps[k] })
}
}
})
forin(newProps, (k, v) => {
if (!oldProps.hasOwnProperty(k)) {
propsPathchs.push({ type: 'add', prop: k, value: v })
}
})
return propsPathchs
}
// 對(duì)比子級(jí)差異
function diffChildrens(oldChild, newChild, index, pathchs) {
// 標(biāo)記子級(jí)的刪除/新增/移動(dòng)
let { change, list } = diffList(oldChild, newChild, index, pathchs)
if (change.length) {
if (pathchs[index]) {
pathchs[index] = pathchs[index].concat(change)
} else {
pathchs[index] = change
}
}
// 根據(jù) key 獲取原本匹配的節(jié)點(diǎn),然后遞歸從頭開始對(duì)比
oldChild.map((item, i) => {
let keyIndex = list.indexOf(item.key)
if (keyIndex) {
let node = newChild[keyIndex]
// 進(jìn)一步遞歸對(duì)比
dfs(item, node, index, pathchs)
}
})
}
// 1、列表對(duì)比,根據(jù) key查找對(duì)應(yīng)的匹配項(xiàng)
// 2、對(duì)比出新舊列表的新增/刪除/移動(dòng)
function diffList(oldList, newList, index, pathchs) {
let change = []
let list = []
const newKeys = getKey(newList)
oldList.map(v => {
if (newKeys.indexOf(v.key) > -1) {
list.push(v.key)
} else {
list.push(null)
}
})
// 很直白的標(biāo)記刪除
for (let i = list.length - 1; i>= 0; i--) {
if (!list[i]) {
list.splice(i, 1)
change.push({ type: 'remove', index: i })
}
}
// 標(biāo)記新增以及移動(dòng)
newList.map((item, i) => {
const key = item.key
const index = list.indexOf(key)
if (index === -1 || key == null) {
// 新增
change.push({ type: 'add', node: item, index: i })
list.splice(i, 0, key)
} else {
// 移動(dòng)
if (index !== i) {
change.push({
type: 'move',
form: index,
to: i,
})
move(list, index, i)
}
}
})
return { change, list }
}
- Proxy 相比于 defineProperty 的優(yōu)勢(shì)
數(shù)組變化也能監(jiān)聽到
不需要深度遍歷監(jiān)聽
let data = { a: 1 }
let reactiveData = new Proxy(data, {
get: function(target, name){
// ...
},
// ...
})
- vue-router
modehash
history
跳轉(zhuǎn)
this.$router.push()
<router-link to=""></router-link>
占位
<router-view></router-view>
- vuex
state: 狀態(tài)中心
mutations: 更改狀態(tài)
actions: 異步更改狀態(tài)
getters: 獲取狀態(tài)
modules: 將state分成多個(gè)modules,便于管理