router
new Vue配置router , 會給vue實例對象創(chuàng)建兩個對象屬性,$router $route $router路由對象可以調(diào)用路由相關(guān)方法,$route存儲當(dāng)前路由相關(guān)數(shù)據(jù)
使用步驟
1 Vue.use(router)注冊路由插件
2 創(chuàng)建路由配置
3 vue實例中注冊router對象動態(tài)路由可以在路由配置中配置props:true 在組件中通過prop屬性聲明訪問對應(yīng)數(shù)據(jù)
hash模式 基于錨點 錨點 變化觸發(fā)onhashchange事件 改變的#后面的值
history模式 基于h5中的history.pushState() 改變地址欄地址 并把地址記錄到訪問歷史中(不會向服務(wù)器發(fā)送請求)history.popstate()(前進后退按鈕觸發(fā),獲取通過js前進后退history.go(n) / history.forward() )監(jiān)聽瀏覽器歷史的變化 history.replaceState() ie10后才支持 部署服務(wù)器刷新瀏覽器會出現(xiàn)404,原因是會去服務(wù)器請求對應(yīng)資源找不到,需要服務(wù)器配置對應(yīng)地址,找不到重定向到index.html
node服務(wù)器配置history
刷新頁面瀏覽器向服務(wù)器發(fā)送對應(yīng)地址請求,服務(wù)器接受請求找不到對應(yīng)資源,返回index.html ,瀏覽器根據(jù)請求地址(如/about)加載再去加載對應(yīng)路由進行顯示
const path = require('path')
// 導(dǎo)入處理 history 模式的模塊
const history = require('connect-history-api-fallback')
// 導(dǎo)入 express
const express = require('express')
const app = express()
// 注冊處理 history 模式的中間件
app.use(history())
// 處理靜態(tài)資源的中間件,網(wǎng)站根目錄 ../web
app.use(express.static(path.join(__dirname, '../web')))
// 開啟服務(wù)器,端口是 3000
app.listen(3000, () => {
console.log('服務(wù)器開啟,端口:3000')
})
nginx配置history
- nginx目錄運行 start nginx
- 重啟 nginx.exe -s reload
-nginx config配置
location / {
root html;
index index.html index.htm;
try_files $uri $uri/ /index.html; //嘗試匹配當(dāng)前路徑資源 找不到返回index.html 返回html客戶端根據(jù)index去匹配對應(yīng)路由地址進行顯示
}
自定義vueRouter
- Vue.use接受函數(shù)或者對象 ,傳入函數(shù)會調(diào)用該函數(shù),傳入對象,會調(diào)用對象的install方法
- VueRouter 是一個類 有個靜態(tài)方法install
let _Vue = ''
export default class VueRouter {
static install(Vue) { //第一個參數(shù)為vue的構(gòu)造函數(shù)
//1 判斷當(dāng)前插件是否被安裝
if (VueRouter.install.installed) {
return
}
VueRouter.install.installed = true
//2 把Vue的構(gòu)造函數(shù)記錄在全局
_Vue = Vue
//3 把創(chuàng)建Vue的實例傳入的router對象注入到Vue實例
// _Vue.prototype.$router = this.$options.router
_Vue.mixin({
beforeCreate() {
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
}
}
})
}
constructor(options) {
this.options = options // 路由配置
this.routeMap = {} // key對應(yīng)路由路勁 value對應(yīng)組件
this.data = _Vue.observable({
current: '/'
})
this.init()
}
init() {
this.createRouteMap(this.options)
this.initEvent()
this.createComponent()
}
initEvent() {
//監(jiān)聽前進后退
window.addEventListener('popstate', () => {
this.data.current = window.location.pathname
})
}
createRouteMap(options) {
options.routes.forEach(item => {
this.routeMap[item.path] = item.component
})
}
createComponent() {
const self = this
_Vue.component('router-link', {
render(h) {
return h('a', {
attrs: {
href: this.to
},
on: {
click: this.handleClick
}
}, [this.$slots.default])
},
props: {
to: String
},
methods: {
handleClick(e) {
// 改變地址欄參數(shù)
history.pushState({}, '', this.to)
// 切換對應(yīng)組件
this.$router.data.current = this.to
e.preventDefault()
}
}
})
_Vue.component('router-view', {
render(h) {
const currentComponent = self.routeMap[self.data.current]
if (!currentComponent) {
return h(self.routeMap['*'])
} else {
return h(currentComponent)
}
}
})
}
}
vue響應(yīng)式原理
1.vue2.0 Object.defineProperty給對象綁定get set數(shù)據(jù)劫持,讀取對象屬性,觸發(fā)get,設(shè)置對象屬性觸發(fā)set
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>defineProperty 多個成員</title>
</head>
<body>
<div id="app">
hello
</div>
<script>
// 模擬 Vue 中的 data 選項
let data = {
msg: 'hello',
count: 10
}
// 模擬 Vue 的實例
let vm = {}
proxyData(data)
function proxyData(data) {
// 遍歷 data 對象的所有屬性
Object.keys(data).forEach(key => {
// 把 data 中的屬性,轉(zhuǎn)換成 vm 的 setter/setter
Object.defineProperty(vm, key, {
enumerable: true,
configurable: true,
get () {
console.log('get: ', key, data[key])
return data[key]
},
set (newValue) {
console.log('set: ', key, newValue)
if (newValue === data[key]) {
return
}
data[key] = newValue
// 數(shù)據(jù)更改,更新 DOM 的值
document.querySelector('#app').textContent = data[key]
}
})
})
}
// 測試
vm.msg = 'Hello World'
console.log(vm.msg)
</script>
</body>
</html>
- vue3.0 Proxy ie不支持
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Proxy</title>
</head>
<body>
<div id="app">
hello
</div>
<script>
// 模擬 Vue 中的 data 選項
let data = {
msg: 'hello',
count: 0
}
// 模擬 Vue 實例
let vm = new Proxy(data, {
// 執(zhí)行代理行為的函數(shù)
// 當(dāng)訪問 vm 的成員會執(zhí)行
get (target, key) {
console.log('get, key: ', key, target[key])
return target[key]
},
// 當(dāng)設(shè)置 vm 的成員會執(zhí)行
set (target, key, newValue) {
console.log('set, key: ', key, newValue)
if (target[key] === newValue) {
return
}
target[key] = newValue
document.querySelector('#app').textContent = target[key]
}
})
// 測試
vm.msg = 'Hello World'
console.log(vm.msg)
</script>
</body>
</html>
發(fā)布訂閱模式
- $on 注冊事件 $emit發(fā)布事件
<!DOCTYPE html>
<html lang="cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>發(fā)布訂閱模式</title>
</head>
<body>
<script>
// 事件觸發(fā)器
class EventEmitter {
constructor () {
// { 'click': [fn1, fn2], 'change': [fn] }
this.subs = Object.create(null)
}
// 注冊事件
$on (eventType, handler) {
this.subs[eventType] = this.subs[eventType] || []
this.subs[eventType].push(handler)
}
// 觸發(fā)事件
$emit (eventType) {
if (this.subs[eventType]) {
this.subs[eventType].forEach(handler => {
handler()
})
}
}
}
// 測試
let em = new EventEmitter()
em.$on('click', () => {
console.log('click1')
})
em.$on('click', () => {
console.log('click2')
})
em.$emit('click')
</script>
</body>
</html>
觀察者模式
- 發(fā)布者Dep記錄所有訂閱者watcher所有更新操作,存儲在數(shù)組中,當(dāng)需要更新的時候調(diào)用notify遍歷數(shù)組,調(diào)用update方法更新
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>觀察者模式</title>
</head>
<body>
<script>
// 發(fā)布者-目標(biāo)
class Dep {
constructor () {
// 記錄所有的訂閱者
this.subs = []
}
// 添加訂閱者
addSub (sub) {
if (sub && sub.update) {
this.subs.push(sub)
}
}
// 發(fā)布通知
notify () {
this.subs.forEach(sub => {
sub.update()
})
}
}
// 訂閱者-觀察者
class Watcher {
update () {
console.log('update')
}
}
// 測試
let dep = new Dep() //發(fā)布者
let watcher = new Watcher() //訂閱者
dep.addSub(watcher)
dep.notify()
</script>
</body>
</html>
虛擬DOM
- snabbdom
- cnpm i snabbdom -D
- snabbdom 核心庫不能處理屬性樣式,需要使用對應(yīng)模塊
// import { h } from 'snabbdom'
import { init } from 'snabbdom/build/package/init'
import { thunk } from 'snabbdom/build/package/thunk'
import { h } from 'snabbdom/build/package/h'
let patch = init([]) //返回值對比兩個vnode的差異更新到真實DOM
//第一個參數(shù)標(biāo)簽加選擇器
//第二個參數(shù)為標(biāo)簽中的內(nèi)容
let vnode = h('div#container.cls', 'hello world')
let app = document.getElementById('app')
//第一個參數(shù):可以是DOM,內(nèi)部會把DOM轉(zhuǎn)換為vnode
//第二個參數(shù)vnode
//返回值vnode
// let oldValue = patch(app, vnode)
vnode = h('div','new Value')
vnode = h('div',[
h('h1','標(biāo)題'),
h('h2','二級標(biāo)題')
])
let oldValue = patch(app, vnode)
setTimeout(function(){
vnode = h('div',[
h('h1','標(biāo)題111'),
h('h2','二級標(biāo)題')
])
patch(oldValue,vnode)
},2000)
模塊
- attributes 設(shè)置DOM屬性 處理布爾類型
- props 設(shè)置DOM屬性 不處理布爾類型
- class 切換樣式
- dataset 設(shè)置自定義屬性
- eventlisters 注冊移除事件
- style設(shè)置行內(nèi)樣式 支持動畫
使用
// import { h } from 'snabbdom'
import { init } from 'snabbdom/build/package/init'
import { h } from 'snabbdom/build/package/h'
import { styleModule } from "snabbdom/build/package/modules/style"
import { eventListenersModule } from "snabbdom/build/package/modules/eventlisteners"
let patch = init([styleModule, eventListenersModule])
let vnode = h('div', {
style:
{ backgroundColor: '#aaa' },
on:{
click:function(){
alert('click')
}
}
},[
h('h1','hello'),
h('h2','二級標(biāo)題')
])
let app = document.getElementById('app')
patch(app,vnode)
snabbdom 源碼
核心
- 使用h函數(shù)創(chuàng)建js對象(vnode)描述真實DOM
- init()設(shè)置模塊,返回patch函數(shù)
- patch函數(shù)比較新舊兩個Vnode
- 把變化的內(nèi)容弄更新到真實DOM上
vscode 調(diào)試
ctrl+鼠標(biāo)左鍵 跳轉(zhuǎn)函數(shù)定義 alt+鍵盤方向左回退