如下代碼均通過本身測試,能夠複製直接看效果。注意引入Vue文件javascript
所有代碼等全部都更新完成以後會上傳GitHub或者碼雲.我會抓緊時間更新的css
Vue基礎部分和手寫Vue部分(解釋還沒更新出來)已經上傳.須要請點擊這裏html
VueX和VueRouter 部分更新也上傳了。須要點擊這裏vue
VueX差了輔助函數之類。VueRouter基本完成。後續可能會有單元測試和SSRjava
render
>template
>data
的插值表達式<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>基本概念</title>
<script src="vue.js"></script>
</head>
<body>
<h1>顯示優先級</h1>
<ul>
<li>第一個是render 有render方法就輸出render的渲染結果</li>
<li>第二個是template 有template方法就輸出template的內容</li>
<li>最後一個是data,若是二者不存在 則輸出data裏面的插值表達式</li>
{{ }} 當這裏面放的是一個表達式的時候,會輸出表達式的結果 緣由 會轉化成一個函數 render
</ul>
<p>指令修飾符,有好多 本身官網看</p>
<div id="app">
{{ msg }}
</div>
<script> let vm = new Vue({ el: '#app', data() { return { msg: '我是data', } }, template: '<div>我是template</div>', render(h) { return h('div', ['我是render']) }, method: { fn(e) { // 不添加括號自動添加事件源, 添加括號 手動傳入事件源 console.log('內部已經使用bind 綁定了this ,再次綁定也沒什麼用') console.log(this) }, }, }) </script>
</body>
</html>
複製代碼
v-model
其實是一個 語法糖node
<input type="text" :value = 'msg' @input="handleInput">
<!-- 其實是上述的語法糖-->
<input type="text" v-model="msg">
複製代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>v-model</title>
<script src="vue.js"></script>
</head>
<body>
<div id="app">
{{ msg }}
<p>@input</p>
<input type="text" :value = 'msg' @input="handleInput">
<p>這個是@chage</p>
<input type="text" :value = 'msg' @change="handleInput">
<p>v-model 是上面@input的語法糖</p>
<input type="text" v-model="msg">
<p>@input 和@change 區別 一個是 聚焦的時候 一個是 失去焦點的時候</p>
<p>下拉列表</p>
{{ selected }}<br>
<select v-model="selected">
<option value="" disabled>請選擇</option>
<option value="1">a</option>
<option value="2">b</option>
<option value="3">c</option>
</select>
<p>下拉列表多選 這樣綁定的值必須是一個列表</p>
{{ selectedMore }}<br>
<select v-model="selectedMore" multiple>
<option value="" disabled>請選擇</option>
<option value="1">a</option>
<option value="2">b</option>
<option value="3">c</option>
</select>
<p>複選框</p>
{{ checked }}<br>
游泳 <input v-model="checked" type="checkbox" value="游泳">
洗澡 <input v-model="checked" type="checkbox" value="洗澡">
睡覺 <input v-model="checked" type="checkbox" value="睡覺">
<p>單選框</p>
{{ radioed }}<br>
男 <input type="radio" value="男" v-model="radioed">
女 <input type="radio" value="女" v-model="radioed">
<p>v-model 修飾符</p>
<p>{{ attr }}</p>
<input type="number" v-model.number="attr">
<p>{{ attrText }}做用相似@chage</p>
<input type="text" v-model.lazy="attrText">
<p>{{ attrText }} 去除空格</p>
<input type="text" v-model.trim="attrText">
</div>
<script> let vm = new Vue({ el: '#app', data() { return { msg: '我是data', selected:'', selectedMore:[], checked:[], radioed:'', attr:0, attrText:'' } }, methods: { handleInput(e){ this.msg = e.target.value } }, }) </script>
</body>
</html>
複製代碼
觀測值的變化 執行對應函數react
三種寫法:git
deep
屬性,代表要深度遍歷immediate
屬性,代表 當即執行name
屬性,執行methods
的這個方法<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
{{ msg }}
{{ name }}
</div>
<script src="vue.js"></script>
<script> let vm = new Vue({ el: '#app', data() { return { msg: { a: '123' }, name:'456' } }, methods: { fn() { console.log('這是methods') }, }, // 第一種 // watch:{ // msg:{ // handler(oldValue,newValue){ // console.log(oldValue,newValue) // 若是是對象的不到老值 // }, // deep: true // 若是是對象繼續深度遍歷 // } // } watch: { msg: [ // { // handler(oldValue, newValue) { // console.log(oldValue, newValue) // 若是是對象的不到老值 // }, // immediate: true, // 當即執行 // }, // 'fn', // 不知道爲何不行 ], name:'fn' }, }) setTimeout(() => { vm.msg.a = '456' vm.name = '789' }, 1000) </script>
</body>
</html>
複製代碼
常用get
,可是還有一個set
es6
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
全選: <input type="checkbox" v-model="checkAll">
<hr>
<input type="checkbox" v-for="check in checks" v-model="check.check">
</div>
<script src="vue.js"></script>
<script> let vm = new Vue({ el: '#app', data() { return { checks: [{ check: true }, { check: true }, { check: true }], } }, computed: { checkAll: { get() { // 有一個不知足 返回false 而且不往下進行 return this.checks.every((item) => item.check) }, set(newValue) { this.checks.forEach(item => item.check = newValue) }, }, }, }) </script>
</body>
</html>
複製代碼
過濾器,將屬性進行格式化後在進行展現github
分爲 全局和 局部兩種
會接受兩個參數,一個是要格式化的數據,一個是格式化的規則
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://unpkg.com/dayjs@1.8.21/dayjs.min.js"></script>
<script src="vue.js"></script>
</head>
<body>
<div id="app">
<p>局部</p>
{{ timer | format1('YYYY:MM:DD') }}
<p>全局</p>
{{ timer | format('YYYY:MM:DD') }}
</div>
<script> Vue.filter('format', function (timer, format) { return dayjs(timer).format(format) }) let vm = new Vue({ el:'#app', data() { return { timer: 123456789, } }, filters:{ format1(timer, format){ return dayjs(timer).format(format) } } }) </script>
</body>
</html>
複製代碼
一樣分爲 局部 和 全局
使用的時候 在想要使用的標籤上添加 v-xxx
xxx爲指令名字就能夠
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>指令</title>
<script src="vue.js"></script>
</head>
<body>
<div id="app">
<p>自動獲取焦點</p>
<input type="text" v-focus>
<p>點擊顯示 日曆效果</p>
<div v-click-outside="hide">
<input type="text" @focus="show">
<div v-if="isShow">
日曆顯示 時間
</div>
</div>
<h1>指令有生命週期.有鉤子</h1>
<ul>
<li>bind 綁定上的時候會執行一次</li>
<li>inserted 插入的時候</li>
<li>update 當引用數據發生變化的時候</li>
<li>componentUpdate 模板更新</li>
<li>unbind 解除綁定</li>
<li>默認寫成一個函數 bind+update</li>
</ul>
<h1>指令傳入三個參數的含義</h1>
<ul>
<li>el 當前元素 </li>
<li>bindings 有關指令的各個屬性</li>
<li>vNode 虛擬節點</li>
<li>vNode.context Vue實例</li>
</ul>
</div>
<script> // 全局註冊指令 Vue.directive('focus', { // 當被綁定的元素插入到 DOM 中時…… inserted: function (el) { // 聚焦元素 el.focus() }, }) let vm = new Vue({ el: '#app', // 局部指令 directives: { clickOutside: { bind(el, bindings, vNode) { el.handler = function (e) { // console.log(e.target) console.log(vNode.context) if (!el.contains(e.target)) { vNode.context[bindings.expression]() } } document.addEventListener('click', el.handler) }, unbind(el) { document.removeEventListener('click', el.handler) }, }, }, data() { return { isShow: false, } }, methods: { show() { this.isShow = true }, hide() { this.isShow = false }, }, }) </script>
</body>
</html>
複製代碼
介紹一些經常使用的 實例屬性
$mount()
掛載,參數寫要掛載的節點。若是不寫,則掛載的$el
屬性上,能夠手動掛載(好比寫Message彈框)$options
獲取用戶寫的配置$watch
跟watch 用法同樣<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>實例屬性</title>
<script src="vue.js"></script>
</head>
<body>
<div id="app">
{{ msg }}
</div>
<script> let vm = new Vue({ // el:'#app', data() { return { msg: '我是data', } }, template: '<div>我是template</div>', render(h) { return h('div', ['我是render']) }, }) vm.$mount() // 掛載 提供值了就掛載到對應的 節點上 // 不提供就掛載到$el 屬性上 表明要手動掛載 console.log(vm.$el) // 獲取真實節點 document.body.appendChild(vm.$el) console.log(vm.$options) // 用戶參數 console.log(vm.$watch('msg', function (oldValue, newValue) { console.log(oldValue, newValue) })) // 就是 watch 另外一種寫法 批量更新 只更新一次 內部有隊列 </script>
</body>
</html>
複製代碼
動畫分爲兩種,一種是CSS
動畫,一種是js
動畫。各位按照需求選擇
由於我的推薦使用CSS
做動畫,因此JS
版本就再也不寫出來了。有興趣的朋友能夠點擊這裏
css版本
就是把 要作動畫的DOM元素用transition
包裹一下
而後記住一下6個名字,分別對應動畫不一樣的週期
.v-enter
進入動畫時候.v-enter-active
進入動畫過程當中.v-enter-to
進入動畫進行到最後.v-leave
這個沒有實際意義,爲了美感.v-leave-active
離開動畫過程當中.v-leave-to
離開動畫結束<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>動畫</title>
<script src="../vue.js"></script>
</head>
<body>
<div id="app">
<p>transiton 能夠有name 屬性 給更名字,這樣一些 v-leave 則變成的 name-leave</p>
<transition>
<div v-show="isShow" class="box" style=" width: 100px;height: 100px;">
</div>
</transition>
<button @click="handleShow">點我</button>
<p>transition Vue動畫標籤 transition-group 動畫組</p>
</div>
<script> let vm = new Vue({ el: '#app', data() { return { isShow: false, } }, methods: { handleShow() { this.isShow = !this.isShow }, }, }) </script>
<style> .box { background-color: red } /*進入動畫時候的顏色*/ .v-enter { background-color: blue; } /*動畫過程當中*/ .v-enter-active { transition: all 2s linear; } /*動畫進行到最後*/ .v-enter-to { background-color: yellow; } /* 進行完以後會變成紅色*/ /*這個沒有實際意義,爲了美感*/ .v-leave { background-color: purple; } .v-leave-active{ transition: all 2s linear; } .v-leave-to{ background-color: blue; } </style>
</body>
</html>
複製代碼
與上一個不同的是,這個數多組動畫。
區別 使用了 transition-group
動畫名稱
enter-class
enter-active-class
enter-to-class
(2.1.8+)leave-class
leave-active-class
leave-to-class
(2.1.8+)<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>動畫</title>
<script src="../vue.js"></script>
<link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">
</head>
<body>
<div id="app">
<p>vue中動畫組</p>
<input type="text" v-model="content">
<transition-group enter-active-class="animated bounceInLeft" leave-active-class="animated bounceOutRight" >
<li v-for="arr in computedArr" :key="arr">{{ arr }}</li>
</transition-group>
</div>
<script> let vm = new Vue({ el: '#app', data() { return { content:'', arrs:['abc','basd','zxcw','awqec','kjea'] } }, methods: { handleShow() { this.isShow = !this.isShow }, }, computed:{ computedArr(){ return this.arrs.filter(item => item.includes(this.content)) } } }) </script>
<style> li{ width: 200px; background-color: blue; line-height: 35px; } </style>
</body>
</html>
複製代碼
我總結了 一下,大概如下幾種
props
+emit
provide
+inject
單項 數據流$parent
+$children
直接觸發父/子類的事件$broadcast
+ $dispatch
本身在原型上寫的$attrs
+$listeners
經過全部屬性和方法的集合獲取$bus
相似Vuex
Vuex
Vue
插件// parents
<template>
<div>
<h1>Parent</h1>
<h2>第一種</h2>
<Son :money="money" :changMoney="changMoney"></Son>
<p>第二中方法 click2是本身定義的名字,不是原生事件</p>
<Son :money="money" @click2="changMoney"></Son>
</div>
</template>
<script>
import Son from './Son'
export default {
name: 'Parent',
data() {
return {
money: 200,
}
},
components: {
Son
},
methods: {
changMoney(value) {
this.money = value
},
changMoney2(val) {
this.money += val
},
},
}
</script>
// son
<template>
<div>
<h1>Son</h1>
<p>子組件接收到以後,利用props 屬性接受,而後能夠直接使用</p>
<p>子組件可使用父組件傳遞的屬性和函數</p>
我是爸爸給個人錢{{ money }}
<h2>第一種</h2>
<button @click="changMoney(500)">改變父親錢數</button>
<h2>第二種方法</h2>
<button @click="change">改變父親錢數2</button>
</div>
</template>
<script>
export default {
props:{
money: {
type:Number,
default:100
},
changMoney:{
type:Function,
default: ()=>{}
}
},
methods:{
change(){
this.$emit('click2',300)
}
}
}
</script>
複製代碼
第一種是 傳遞一個屬性還有一個函數,子代接收到以後,能夠在使用
第二種是 利用$emit
, 直接觸發 在父級定義的函數
特別注意,這個click2
不是原生的,你把它叫作 a , b 之類等均可以
provide
+inject
官方建議:
provide
和inject
主要在開發高階插件/組件庫時使用。並不推薦用於普通應用程序代碼中。
這個就比較簡單。相似於react
的redux
// parent
<template>
<div>
<h1>Parent</h1>
<h1> 關於son2 跨代通信</h1>
<Son2 @eat="eat"></Son2>
</div>
</template>
<script>
import Son2 from './Son2'
export default {
provide(){
return {parent:this}
},
name: 'Parent',
data() {
return {
money: 200,
}
},
components: {
Son2,
},
methods: {
eat(){
console.log('patent中的eat方法')
}
},
}
</script>
// son2
<template>
<div>
Son2
<GrandSon></GrandSon>
</div>
</template>
<script>
import GrandSon from './GrandSon'
export default {
name:'Son2',
components: {
GrandSon,
},
}
</script>
// grandSon
<template>
<div>
GrandSon
<p>跨代通信的值</p>
{{ parent.money }}
<button @click="$parent.$emit('eat')">觸發父組件的eat方法</button>
</div>
</template>
<script>
export default {
inject:['parent']
}
</script>
複製代碼
寫一個Son2
的做用,就是讓你們明白,隔代也是能夠的。一個提供,一個接收以後就可使用
$parent
+$children
這個我就直接用上面的代碼了。這個比較簡單。就是經過$parent
/$children
找到它的父/子級。而後 使用或者觸發他們的屬性或者方法
$broadcast
+ $dispatch
再次引用官方的話
$dispatch
和$broadcast
已經被棄用。請使用更多簡明清晰的組件間通訊和更好的狀態管理方案,如:Vuex。
固然,咱們仍是介紹一些這兩個方法,各位看須要使用(小聲bb一下,我以爲Vuex
真香)
// 在main.js上
import Vue from 'vue'
import App from './App';
/** * 找父節點觸發事件 * @param eventName * @param ComName * @param Value */
Vue.prototype.$dispatch = function (eventName, ComName = '', Value = '') {
let parent = this.$parent;
while (parent) {
if (ComName && parent.$options.name === ComName) {
parent.$emit(eventName, Value)
return
} else {
parent.$emit(eventName, Value)
parent = parent.$parent
}
}
}
/** * 找子節點觸發事件 * @param eventName * @param ComName * @param value */
Vue.prototype.$broadcast = function (eventName, ComName = '', value = '') {
let children = this.$children // 獲取得是數組
function broadcast(children) {
for (let i = 0; i < children.length; i++) {
let child = children[i]
if (ComName === child.$options.name) {
child.$emit(eventName, value)
return
} else {
if (child.$children) {
broadcast(child)
}
}
}
}
broadcast(children)
}
複製代碼
這兩個方法利用了$parent
和$children
。不斷獲取父/子節點,觸發相對應的事件。
我這個$dispatch
的else
寫的是,若是不是這個組件的事件,我也觸發了。其實應該把這句刪除。只 繼續往上找就能夠
使用
// 直接這樣使用就好
<button @click="$parent.$emit('eat')">觸發父組件的eat方法</button>
複製代碼
$attrs
+$listeners
// APP.vue
<template>
<div>
<Test :a="1" :b="2" :c="3" :d="4" @click="click"></Test>
</div>
</template>
<script>
import Test from './test'
export default {
data() {
return {
msg: 'hello',
}
},
components: {
Test,
},
methods:{
click(){
console.log('我是APP中的click')
}
}
}
</script>
// test.vue
<template>
<div>
我是test
<h1>使用$attrs能夠得到,可是會綁定在DOM元素上</h1>
<ul>
<li>設置 <strong>inheritAttrs:false </strong>就不會綁定了</li>
<li>當props接收後,arrts將不會顯示已經被接收的</li>
{{ $attrs }}
<li>這樣子代傳遞</li>
<button @click="$listeners.click">觸發APP中的click</button>
<test2 v-bind="$attrs" v-on="$listeners"></test2>
</ul>
</div>
</template>
<script>
import test2 from './test2';
export default {
props:['a'],
name:'Test',
inheritAttrs:false,
components:{
test2
}
}
</script>
//test2.vue
<template>
<div>
<h1>我是test2</h1>
{{ $attrs }}
<button @click="$listeners.click">觸發APP中的click</button>
</div>
</template>
<script>
export default {
name: 'test2',
}
</script>
複製代碼
注意
props
接收的不會被綁定),能夠在子類中使用inheritAttrs:false,
來設置取消綁定$attrs.x
/$listeners.x
使用v-bind="$attrs" v-on="$listeners"
,就能夠把沒有被props
接收過的都傳給下一代使用$bus
就是掛載了一個Vue
實例
// APP.vue
<template>
<div>
<h1>子組件如何監聽父組件的mounted</h1>
<p>組件掛載, 先掛載父組件 -》渲染子組件,子mounted -》 父mounted</p>
<p>能夠實現任意組件之間的通信,但只適合小規模的</p>
<bus></bus>
</div>
</template>
<script>
import bus from './$bus使用';
export default {
data() {
return {
msg: 'hello',
}
},
mounted(){
this.$bus.$emit('監聽事件','hello')
},
components: {
bus
},
}
</script>
// $bus使用
<template>
<div>
bus
{{ $bus.a }}
</div>
</template>
<script>
export default {
name: 'bus',
mounted() {
// 發佈訂閱模式 能夠屢次訂閱
this.$bus.$on('監聽事件', function (value) {
console.log(value)
})
},
beforeDestroy(){
// 解綁組件
this.$bus.$off('監聽組件')
}
}
</script>
複製代碼
請日後面看
<template>
<div>
<h1>插槽</h1>
<test1>
我是標籤裏面的內容
</test1>
<h1>具名插槽</h1>
<p>新版寫法只能夠用 template</p>
<p>這裏插值表達式的數據用的是父類的</p>
<test1>
<!-- 老版本寫法-->
<!-- <div slot="header">asd</div>-->
<!-- <div slot="footer">qwe</div>-->
<template v-slot:header>header {{ msg }}<br></template>
<template v-slot:footer>footer</template>
</test1>
<h1>做用域插槽</h1>
<p>這樣用的是子類的數據</p>
<test1>
<!-- 老版本寫法-->
<!-- <div slot="header" slot-scope="{a,b}">{{ a }}{{ b }}</div>-->
<template v-slot:header="{a,b}" >{{ a }},{{ b }}</template>
</test1>
</div>
</template>
<script>
import test1 from './test1';
export default {
data() {
return {
msg: 'hello',
}
},
components:{
test1
}
}
</script>
// test1
<template>
<div>
<h1>我是test1</h1>
<slot></slot>
<slot name="header" a="1" b="2"></slot>
<slot name="footer"></slot>
</div>
</template>
<script>
export default {
name: 'test1',
}
</script>
複製代碼
這個比較簡單,就再也不多多敘述。強調一點,新老版本區別
template
進心包裹div
等看完上面的內容能夠嘗試模仿寫一下 element-ui
的表單組件。他們使用了async-validator
做爲校驗。
一樣有一個簡單版本Vue數據響應式和編譯原理分析 和 模擬實戰.這個版本沒有用到虛擬Dom
等。
虛擬dom
。我的也總結了一篇幫你深刻了解虛擬DOM和DOM-diff,但願能幫到各位
僅僅是一個簡單的實現。可是實現了 部分指令
完整部分(即此次總結的,帶上虛擬dom
等等),這個內容因爲太多(標題細分太多。很差去尋找)。我另寫了一篇文章,還在整理中,1號大概能夠放出來。
貼一個圖證實一下。實在是考慮的太多,因此寫出來比較慢
推薦一下本身的另外一篇文章Vuex的簡單實現,感受這一篇寫的相對簡單一點
這個就很少作解釋了。不太熟練的朋友能夠先去看官方文檔
給出一下個人數據定義
// store/index.js
import Vue from 'vue'
// import Vuex from 'vuex'
import Vuex from './../vuex2'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
age: 10
},
strict: true,
getters: {
myAge(state) {
return state.age + 30
}
},
mutations: {
// 同步更改state 在嚴格模式下不可使用異步
change(state, payload) {
state.age += payload
}
},
actions: {
// 異步更改state
asyncChange({ commit }, payload) {
setTimeout(()=>{
commit('change', payload)
}, 1000)
}
}
})
export default store
複製代碼
Vuex
做爲一個 插件,首先執行的是install
方法,咱們但願的是,任何組件均可以訪問到這裏面的數據。組件的渲染是由父到子的,因此咱們既能夠先進行判斷。若是它是根節點,就把這個屬性掛載到根節點上,若是不是,就找他父級的這個屬性,而後掛載到這個Vue實例上
// 官方Api 會把Vue做爲參數傳入
const install = (_vue)=>{
Vue = _vue
Vue.mixin({ // 先父後子
beforeCreate() {
if (this.$options.store) { // 跟節點
this.$store = this.$options.store
} else { // 往子節點傳遞
this.$store = this.$parent && this.$parent.$store
}
}
})
}
複製代碼
咱們平時使用的過程是是這樣的
const store = new Vuex.Store({
state:{
}
})
複製代碼
因此咱們發現,咱們其實是new
了一個VueX 的 Store 類。接下來咱們開始寫這個類。
let Vue
class Store{
constructor(options) {
this.state = options.state
this.mutations = {}
this.actions = {}
this.getters = {}
this.actions = {}
}
}
// 下面是install 方法
複製代碼
再其餘組件中使用
// VuexUse.vue
<template>
<div>
VueX中的屬性{{$store.state.age}}
<button @click="change2">=100</button>
</div>
</template>
<script>
export default {
name: 'VuexUse',
mounted() {
console.log(this.$store)
},
methods: {
change2() {
this.$store.state.age = 100
console.log(this.$store)
}
}
}
</script>
複製代碼
你們會看到 輸出10.當點擊按鈕的時候。再次打印,會發現數據已經發生變化,可是視圖並無刷新。咱們應該讓數據更新以後,視圖也跟着刷新。這時候咱們就應該想到用Vue
的特性。咱們改造一下剛纔的代碼
let Vue
class Store{
constructor(options) {
this.state = new Vue({ data: { state: options.state } }).state
this.mutations = options.mutations || {}
this.actions = options.actions || {}
this.getters = {}
}
}
// 下面是install 方法
複製代碼
這樣咱們就實現了數據改變,就刷新視圖
在VueX
,中,更改狀態通常須要這兩個方法,一個是同步,一個是異步,咱們來實現一下這兩個方法
// 使用的時候
change() {
this.$store.commit('xxx', 10)
},
複製代碼
因此,這兩個方法是寫在Store類裏面的
let Vue
class Store{
constructor(options) {
this.state = new Vue({ data: { state: options.state } }).state
this.mutations = options.mutations || {}
this.actions = options.actions || {}
this.getters = {}
}
commit = (mutationName, payload)=>{
this.mutations[mutationName](this.state, payload)
}
dispatch = (actionName, payload)=>{
this.actions[actionName](this, payload)
}
}
複製代碼
commit
,我以爲你們均可以看懂,就是找到用戶定義的mutations
,把參數傳入,就能夠執行了。
dispatch
,爲何要傳入this
? 緣由,在定義的時候,使用的是ES6
的解構賦值,因此這裏要把this
傳入
注意,這兩種方法還可使用 柯里化
來實現,這樣傳值的時候只用傳入 payload
,更方便一點
首先咱們要明白,getter
是做什麼用的。我 我的理解,須要對訪問數據進行必定處理。也就是咱們訪問這個屬性的時候,獲得這個函數的返回結果。
let Vue
class Store{
constructor(options) {
this.state = new Vue({ data: { state: options.state } }).state
this.mutations = options.mutations || {}
this.actions = options.actions || {}
// 這下面是修改的部分
options.getters && this.handleGetters(options.getters)
}
handleGetters(getters) {
this.getters = {}
Object.keys(getters).forEach(key=>{
Object.defineProperty(this.getters, key, {
get: ()=>{
return getters[key](this.state)
}
})
})
}
}
複製代碼
解釋一下handleGetters
這一段代碼
這段代碼相對比較簡單,這樣就實現了getters
import Vue from 'vue'
// import Vuex from 'vuex'
import Vuex from './../vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
age: 10
},
strict: true,
getters: {
myAge(state) {
return state.age + 30
}
},
mutations: {
change(state, payload) {
state.age += payload
}
},
actions: {
// 異步更改state
asyncChange({ commit }, payload) {
setTimeout(()=>{
commit('change', payload)
}, 1000)
}
},
modules: {
a: {
namespaced: true,
state: {
num: 'a1'
},
mutations: {
// 同步更改state 在嚴格模式下不可使用異步
change(state, payload) {
console.log(state, payload) // 是本身這個模塊內的state
console.log('a')
}
}
},
b: {
state: {
num: 'b1'
},
mutations: {
// 同步更改state 在嚴格模式下不可使用異步
change(state, payload) {
console.log('b')
}
},
modules: {
c: {
namespaced: true,
state: {
num: 'c1'
},
mutations: {
// 同步更改state 在嚴格模式下不可使用異步
change(state, payload) {
console.log('c')
}
}
}
}
}
}
})
export default store
複製代碼
接下來這一部分可能會難理解一點。我盡力把我學習到給你們清楚的講出來。這部分會對以前的代碼進行大量修改
先改造一下咱們的Store
,變會最開始的樣子
class ModuleCollection {
}
let Vue
class Store{
constructor(options) {
this.state = options.state
this.mutations = {}
this.actions = {}
this.getters = {}
this.modules = new ModuleCollection(options)
console.log('收集完成的模塊')
console.log(this.modules)
}
}
// 下面是install 方法
複製代碼
如今,咱們須要模塊化,因此咱們要寫一個方法來 格式化數據,變成咱們想要的樣子
思路,咱們要把這邊模塊進行遍歷 註冊,若是模塊下面還有子類,則繼續遍歷。 核心方法, reduce
/**
* 循環對象的值
* @param obj
* @param cb
*/
function forEach(obj, cb) {
Object.keys(obj).forEach(key=>{
cb(key, obj[key])
})
}
class ModuleCollection {
constructor(options) {
this.register([], options)
}
register(path, rootModule) {
// 格式化模塊
const rawModule = {
_raw: rootModule, //原來的modules
_children: {}, // 孩子
state: rootModule.state // 原來的數據
}
// 雙向記錄 把格式化以後的數據記錄下來
rootModule.rawModule = rawModule
// 判斷是否是根的存在
if (!this.root) {
// 第一次確定不存在
this.root = rawModule
} else {
// 核心 返回的是各個module 對應的格式化後的模塊
const parentModel = path.slice(0, -1).reduce((root, current)=>{
console.log(current)
return root._children[current]
}, this.root)
/----------------------------------------------------/
parentModel._children[path[path.length - 1]] = rawModule
}
// 遞歸,遍歷子代。核心邏輯
if (rootModule.modules) {
forEach(rootModule.modules, (moduleName, module)=>{
this.register(path.concat(moduleName), module)
})
}
}
}
複製代碼
主要解釋一下 /-------------/
上下的代碼。上面的parentModel
,指的是模塊
parentModel
是this.root
,rawModule
是a
模塊的定義parentModel
是this.root
,rawModule
是b
模塊的定義parentModel
是b
模塊,rawModule
是c
模塊的定義打印一下 this.modules
如今咱們就把全部的模塊進行了 格式化。接下來。咱們就要對 咱們格式化後的數據進行安裝。使他們能夠訪問獲得
總結一下,這個函數的做用就是把 咱們傳入的modules
進行一個格式化,而且將模塊進行分類。
這個函數的做用 循環遍歷子節點,安裝 state action mutation getters
/** * 安裝 state action mutation getters 並 * @param store Vuex 中的store * @param rootState 根state * @param path 路徑 * @param rawModule 原模塊 */
function installModule(store, rootState, path, rawModule) {
// 安裝state
if (path.length > 0) { // 證實是子節點
const parentState = path.slice(0, -1).reduce((root, current)=>{
return rootState[current]
}, rootState)
// 官方API。
// 向響應式對象中添加一個 property,並確保這個新 property 一樣是響應式的,且觸發視圖更新
Vue.set(parentState, path[path.length - 1], rawModule.state)
}
// rawModule 裏面有
// _raw 原來的模塊
// _children 孩子
// state 原來的state
// 安裝getters
// 注意狀態的使用,要使用本模塊的狀態
const getters = rawModule._raw.getters || {}
if (getters) {
forEach(getters, (getterName, getterFun)=>{
Object.defineProperty(store.getters, getterName, {
get: ()=>getterFun(rawModule.state)
})
})
}
// mutations跟actions 差很少。都是把 全部的模塊的函數存在 root根模塊中 使用的時候直接遍歷
const mutations = rawModule._raw.mutations || {}
if (mutations) {
forEach(mutations, (mutationName, mutationFun)=>{
// 寫一個發佈訂閱模式
const arr = store.mutations[mutationName] || (store.mutations[mutationName] = [])
arr.push((payload)=>{
mutationFun(rawModule.state, payload)
})
})
}
const actions = rawModule._raw.actions || {}
if (actions) {
forEach(actions, (actionName, actionsFun)=>{
const arr = store.actions[actionName] || (store.actions[actionName] = [])
arr.push((payload)=>{
actionsFun(store, payload)
})
})
}
// 遍歷子節點
forEach(rawModule._children, (moduleName, rawModule)=>{
// console.log(rawModule) // 一個個子節點
installModule(store, rootState, path.concat(moduleName), rawModule)
})
}
複製代碼
store
和 rootState
始終是。Vuex
中的store
和根上面的state
patch
是[]
,rawModule
是根
模塊的定義patch
是['a']
,rawModule
是a
模塊的定義patch
是['b']
,rawModule
是b
模塊的定義
b
下面還有modules
,因此patch
是['b',‘c’]
,rawModule
是c
模塊的定義命名空間這個就簡單了。只須要在每一個方法前面加上x/
就能夠了
function installModule(store, rootState, path, rawModule) {
// 命名空間的實現 獲取命名
let root = store.modules.root // 拿到的是格式化以後的結果
const nameSpace = path.reduce((str, current)=>{
root = root._children[current]
str = str + (root._raw.namespaced ? current + '/' : '')
return str
}, '')
// 安裝state 這裏沒有發生變化
if (path.length > 0) {
// 證實是子節點
const parentState = path.slice(0, -1).reduce((root, current)=>{
return rootState[current]
}, rootState)
Vue.set(parentState, path[path.length - 1], rawModule.state)
}
// rawModule 裏面有
// _raw 原來的模塊
// _children 孩子
// state 原來的state
// 安裝getters 把方法前面加上 命名
const getters = rawModule._raw.getters || {}
if (getters) {
forEach(getters, (getterName, getterFun)=>{
Object.defineProperty(store.getters, nameSpace + getterName, {
get: ()=>getterFun(rawModule.state) // 使用模塊中的狀態
})
})
}
const mutations = rawModule._raw.mutations || {}
if (mutations) {
forEach(mutations, (mutationName, mutationFun)=>{
// 寫一個發佈訂閱模式
const arr = store.mutations[nameSpace + mutationName] || (store.mutations[nameSpace + mutationName] = [])
arr.push((payload)=>{
mutationFun(rawModule.state, payload)
})
})
}
const actions = rawModule._raw.actions || {}
if (actions) {
forEach(actions, (actionName, actionsFun)=>{
const arr = store.actions[nameSpace + actionName] || (store.actions[nameSpace + actionName] = [])
arr.push((payload)=>{
actionsFun(store, payload)
})
})
}
// 遍歷子節點
forEach(rawModule._children, (moduleName, rawModule)=>{
// console.log(rawModule) // 一個個子節點
installModule(store, rootState, path.concat(moduleName), rawModule)
})
}
複製代碼
從''(空)
字符串開始。根節點不須要命名空間
class Store {
constructor(options) {
this.state = new Vue({ data: { state: options.state } }).state
this.mutations = {}
this.actions = {}
this.getters = {}
// 模塊收集 並格式化
this.modules = new ModuleCollection(options)
console.log('收集完成的模塊')
console.log(this.modules)
// 模塊的安裝並訪問 store rootState path 根模塊 安裝所有模塊
installModule(this, this.state, [], this.modules.root)
}
// 模塊開發完以後的寫法
commit = (mutationName, payload)=>{
this.mutations[mutationName].forEach(fn=>fn(payload))
}
dispatch = (actionName, payload)=>{
this.actions[actionName].forEach(fn=>fn(payload))
}
/** * 自定義註冊 module * @param moduleName * @param module */
registerModule(moduleName, module) {
if (!Array.isArray(moduleName)) {
moduleName = [moduleName]
}
this.modules.register(moduleName, module)
console.log(this.modules.root)
// 安裝當前模塊
installModule(this, this.state, moduleName, module.rawModule)
}
}
複製代碼
思路很簡單,就是把 註冊的module
,進行格式化以後。再進行安裝就能夠
注意,要安裝位置要肯定好哦
跟Vuex
同樣,也寫過一篇比較簡單的實現 VueRouter的簡單實現,感受這一篇寫的相對簡單一點
這部分我我的以爲本身掌握的不是特別好。因此 講述的不太清楚。僅提供一個思路。
install
方法在咱們的日常使用過程當中,除了router-link
和router-view
。最經常使用的可能就是this.$router.push(xx)
。因此咱們仍是跟VueX
的作法差很少。在每個實例上掛在一個屬性
const install = (Vue)=>{
Vue.mixin({
beforeCreate() {
if (this.$options.router) {
// console.log(this) // 指的是一個new Vue
this._routerRoot = this // 把vue實例掛載到這個屬性上
this._router = this.$options.router // 用戶傳入得 router
// 路由的初始化
this._router.init(this)
} else {
this._routerRoot = this.$parent && this.$parent._routerRoot
}
}
})
}
export default install
複製代碼
以上代碼只作了兩件事。
index
文件首先咱們應該分析。咱們這個主文件應該有什麼。在咱們平常使用的過程當中,通常是import VueRouter from 'vue-router'
因此
VueRoter
類。install
.。VueRoter
類的constructor
中,咱們應該對用戶傳入的數據進行處理。還有就是分析它路由模式
init
方法,要能夠監聽到路由變換,而後跳轉到對應的 路由。渲染對應的組件分析完以後。咱們就開始着手寫
我先把大致框架給你們展現一下
import install from './install'
import createMatcher from './createMatcher'
import HashHistory from './history/hash'
class VueRouter {
constructor(options) {
// matcher 匹配器 處理樹形結構 將他扁平化
// 返回兩個方法 addStore match 匹配對應結果
this.matcher = createMatcher(options.routes || [])
// 內部須要用 hash history 進行路由的初始化工做
// base 表示基類,實現全部路由的公告方法都放在基本類上 保證不一樣路由API 有相同的使用方法
this.history = new HashHistory(this)
}
push(location) {
}
init(app) {
// app 是頂層Vue 實例
// 獲取到路徑 並進行跳轉 並渲染對應組件
// 匹配一次完成後,監聽路有變化,完成以後的更新操做
}
}
VueRouter.install = install
export default VueRouter
複製代碼
裏面出現的方法在下面都會有所解釋
import createRouteMap from './createRouteMap'
import { createRoute } from './history/base'
export default function createMatcher(routes) {
// 開始扁平化數據
const { pathList, pathMap } = createRouteMap(routes)
// 重載s
function addRoute(routes) {
createRouteMap(routes, pathList, pathMap)
}
function match(location) {
console.log('create裏面的match' + location)
// 從pathMap獲取的location
const record = pathMap[location]
// console.log(record)
return createRoute(record, {
path: location
})
}
return {
addRoute, match
}
}
複製代碼
咱們先經過createRouteMap
方法,把傳入的routes(即用戶傳入的配置)
進行一個格式化處理,獲得一個pathList(地址的列表)
和pathMap(地址映射,裏面有地址,組件等等屬性)
官方API中,有一個交addRotes
,也就是再添加進一組路由。
咱們仍是利用createRouteMap
方法。這個方法具體是什麼樣的看下面
match
方法的做用是匹配器,匹配傳入的location(地址)
。返回相對應的 記錄
export default function createRouteMap(routes, oldPathList, oldPathMap) {
const pathList = oldPathList || []
const pathMap = oldPathMap || Object.create(null)
// Object.create(null) 和 {} 區別 前者沒有原型鏈
// 數組扁平化
routes.forEach(route=>{
addRouteRecord(route, pathList, pathMap)
}
)
return {
pathList, pathMap
}
}
function addRouteRecord(route, pathList, pathMap, parent) {
const path = parent ? parent.path + '/' + route.path : route.path
const record = {
path,
component: route.component,
parent
// todo
}
if (!pathList[route]) {
pathList.push(path)
pathMap[path] = record
}
if (route.children) {
route.children.forEach(route=>{
addRouteRecord(route, pathList, pathMap, record)
})
}
}
複製代碼
Object.create(null) 和 {} 區別 前者沒有原型鏈
{} 會存在一堆的屬性
**Object.create(null)**不存在這些addRouteRecord
是這個的核心方法。它的工做是
about/a
。沒有就是自己record
route(即每一項路由)
是否在pathList
裏面。在了就跳過。不在就添加進去。 **這個方法就實現了addRoutes
**的做用這個方法及其關鍵!!!!
緣由:好比咱們渲染about/a
這個路徑的組件。咱們是否是必須得渲染about
,這樣才能夠渲染a
。
因此這個方法的主要做用就是。把路徑的父級也都存下來
export function createRoute(record, location) {
const res = [] // 若是匹配到路徑 就放進來
if (record) {
while (record) {
res.unshift(record)
record = record.parent
}
} // 把父級路徑也存放下來
console.log(res, location)
return {
...location,
matched: res
}
}
複製代碼
這個即解釋this.history = new HashHistory(this)
爲何要單獨列出來?由於有不一樣的 路由模式,可是有公共的處理方法。固然還須要有不一樣的方法來處理不一樣的路由。
咱們這裏只考慮hash
export function createRoute(record, location) {
const res = [] // 若是匹配到路徑 就放進來
if (record) {
while (record) {
res.unshift(record)
record = record.parent
}
} // 把父級路徑也存放下來
console.log(res, location)
return {
...location,
matched: res
}
}
class History {
constructor(router) {
this.router = router
this.current = createRoute(null, {
path: '/'// 默認路徑
})
}
transitionTo(location, cb) { // 最好屏蔽一下,以防止屢次調用
console.log(location, cb)
// 獲得路徑 開始匹配對應的模板
const r = this.router.match(location)
this.current = r // 對當前路徑進行更新
// eslint-disable-next-line eqeqeq
if (location === this.current.path && r.matched.length === this.current.matched) {
return
}
this.cb && this.cb(r)
cb && cb()
}
setupListeners() {
window.addEventListener('hashchange', ()=>{
this.transitionTo(window.location.hash.slice(1))
})
}
listen(cb) {
this.cb = cb
}
}
export default History
複製代碼
能夠看出 這個base.js
作了幾件事
listen
這個等會再說transitionTo
中間的if
判斷。是爲了防止屢次調用的。
hash.js
import History from './base'
function ensureSlash() {
if (window.location.hash) {
return
}
window.location.hash = '/'
}
class HashHistory extends History {
constructor(router) {
super(router) // super === parent.call(this) 向父級傳遞router
this.router = router
ensureSlash() // 確保有hash值
}
getCurrentLocation() {
return window.location.hash.slice(1) // 除了# 號後面的路徑
}
}
export default HashHistory
複製代碼
這個就比較簡單了。就再也不解釋了
import install from './install'
import createMatcher from './createMatcher'
import HashHistory from './history/hash'
class VueRouter {
constructor(options) {
// matcher 匹配器 處理樹形結構 將他扁平化
// 返回兩個方法 addStore match 匹配對應結果
this.matcher = createMatcher(options.routes || [])
// 內部須要用 hash history 進行路由的初始化工做
// base 表示基類,實現全部路由的公告方法都放在基本類上 保證不一樣路由API 有相同的使用方法
this.history = new HashHistory(this)
}
match(location) { // 做了一層封裝 返回匹配結果
return this.matcher.match(location)
}
push(location) {
this.history.transitionTo(location, ()=>{
window.location.hash = location// 這樣的話 要渲染兩遍 一邊transitionTo 一邊是hash的監聽
}) // hash沒有改變 要改變hash
}
init(app) {
// app 是頂層Vue 實例
// console.log(app)
// 獲取到路徑 並進行跳轉 並渲染對應組件
// 匹配一次完成後,監聽路有變化,完成以後的更新操做
const history = this.history
const setupHashListener = ()=>{ // 監聽以後回調
history.setupListeners() // 監聽路由變化 父類
}
history.transitionTo( // 跳轉方法 父類
history.getCurrentLocation(), // 獲取當前路徑 分路由 因此是子類
setupHashListener
)
// 訂閱好 而後路由 屬性變化 更新此方法
history.listen((route)=>{
app._route = route
})
}
}
VueRouter.install = install
export default VueRouter
複製代碼
改造完以後的index.js
作的事,
_route
的函數(這時候 _route
還不是動態的)import RouterView from './components/router-view'
import RouterLink from './components/router-link'
const install = (Vue)=>{
Vue.mixin({
beforeCreate() {
if (this.$options.router) {
// console.log(this) // 指的是一個new Vue
this._routerRoot = this
this._router = this.$options.router // 用戶傳入得 router
// 路由的初始化
this._router.init(this)
// 將current 定義成 響應式的。數據改變則刷新視圖
console.log(this._router)
// 給當前實例建立了 _route 屬性, 取自this._router.history.current
Vue.util.defineReactive(this, '_route', this._router.history.current)
// 定義以後 更新_route
} else {
this._routerRoot = this.$parent && this.$parent._routerRoot
}
}
})
Object.defineProperty(Vue.prototype, '$route', {
get() {
console.log(this._routerRoot._route)
return this._routerRoot._route
}
})
Object.defineProperty(Vue.prototype, '$router', {
get() {
return this._routerRoot._router
}
})
Vue.component('RouterView', RouterView)
Vue.component('RouterLink', RouterLink)
}
export default install
複製代碼
回到install
方法,初始化以後。把_route
設置成動態(有get
和set
)。
以後數據發生改變,視圖就會刷新。
export default {
functional: true, // 函數式組件 沒有狀態 沒有this
render(h, { parent, data }) { // 裏面有不少options 這是經過解構賦值出來的
// console.log(options)
const route = parent.$route // 被放到了vue 原型上
console.log(route)
let depth = 0
// $vnode表示佔位符Vnode
while (parent) {
if (parent.$vnode && parent.$vnode.data.routerView) {
depth++
}
parent = parent.$parent
}
data.routerView = true
const record = route.matched[depth]
console.log(record)
if (!record) {
return h()
}
return h(record.component, data)
}
}
複製代碼
這段代碼中最難理解的就是 depth
。
route
是屬性。 這段代碼在history/base.js
。createRoute
返回的結果中有一個match
。裏面存放了全部的 父級路徑
routerView
的解釋。自定義屬性。看他是不是根節點。 第一次進來的時候 ,渲染App
組件(裏面放有RouterView
)。若是存在,證實要渲染的是下一個節點了
router
是方法
export default {
props: {
to: {
type: String,
require: true
},
tag: {
type: String,
default: 'a'
}
},
methods: {
handle() {
this.$router.push(this.to)
}
},
render(h) {
const tag = this.tag
return <tag onclick = { this.handle } > { this.$slots.default } < /tag>
}
}
複製代碼
我這裏用的是jsx
語法。以爲看不懂的能夠直接用RouterLink.vue
正常寫來代替
....
router.beforeEach((to, from, next)=>{
console.log(1)
setTimeout(()=>{
next()
}, 1000)
})
router.beforeEach((to, from, next)=>{
console.log(2)
setTimeout(()=>{
next()
}, 1000)
})
export default router
複製代碼
這個就比較像express
和koa
裏面的了。
簡單寫個思路就是這樣。
// 儲存
let deps = []
// 放置
beforeXxx(cb){
this.deps.push(cb)
}
// 使用
// 在視圖更新或者跳轉前
this.deps.forEach(dep=>{
dep()
})
複製代碼
index。js
文件夾下(VueRouter/index
) 作初始化工做寫過的我就直接用省略號代替了
class VueRouter {
constructor(options) {
....
this.beforeEachs = []
}
match(location) { // 做了一層封裝 返回匹配結果
....
}
push(location) {
....
}
init(app) {
....
}
beforeEach(cb) {
this.beforeEachs.push(cb)
}
}
複製代碼
先把全部的beforeEach
都存起來。在跳轉以前執行它
再道history/base
文件夾下,找到transitionTo
方法.裏面的這三句代碼是跳轉路由的
this.current = r // 對當前路徑進行更新
this.cb && this.cb(r)
cb && cb()
複製代碼
下面的代碼是 如何實現beforeEach
的運行的
// 1. 取到咱們 收集到的隊列
const queue = this.router.beforeEachs
// 輔助函數
const iterator = (hook, next)=>{
hook(r, this.current, next)
}
// 運行隊列
runQueue(queue, iterator, ()=>{
this.upDateRoute(r, cb)
})
// 這個方法是對上面三句更新方法的封裝
upDateRoute(r, cb) {
this.current = r
this.cb && this.cb(r)
cb && cb()
}
// runQueue 函數定義 其實就是一個遞歸調用
function runQueue(queue, iterator, callback) {
function step(index) {
if (index === queue.length) {
return callback()
}
const hook = queue[index]
iterator(hook, ()=>step(index + 1))
}
step(0)
}
複製代碼
思路:
beforeEach
隊列for
。JS單線程,用for
的話異步會 出現問題// history/bsae。js
export function createRoute(record, location) {
...
}
function runQueue(queue, iterator, callback) {
function step(index) {
if (index === queue.length) {
return callback()
}
const hook = queue[index]
iterator(hook, ()=>step(index + 1))
}
step(0)
}
class History {
constructor(router) {
...
}
transitionTo(location, cb) {
console.log(location, cb)
const r = this.router.match(location)
if (location === this.current.path && r.matched.length === this.current.matched) {
return
}
const queue = this.router.beforeEachs
const iterator = (hook, next)=>{
hook(r, this.current, next)
}
runQueue(queue, iterator, ()=>{
this.upDateRoute(r, cb)
})
}
upDateRoute(r, cb) {
this.current = r // 對當前路徑進行更新
this.cb && this.cb(r)
cb && cb()
}
setupListeners() {
...
}
listen(cb) {
...
}
}
export default History
複製代碼
這個會實現了以後。其餘幾個鉤子函數也就能夠進行實現了。方式大致都差很少。即經過回調函數來執行
推薦一下花褲衩大佬寫的權限登錄的文章。 手摸手,帶你用vue擼後臺 系列二(登陸權限篇)
是我目前看到的關於路由權限 講解最好的一篇文章
本身的總結正在努力
阮一峯老師這一本書關於這部分已經寫的很好了。我就再也不多作敘述了。詳情點擊這裏地址
let obj = {
name: {
achen: {
name: '阿琛',
age: 22,
},
},
sex:'男',
arr: ['吃', '喝', '玩'],
}
let handler = {
// target就是原對象,key是鍵
get(target, key) {
// 懶代理 若是取到了這個對象纔會觸發,沒有取到就不會代理
if (typeof target[key]=== 'object'){
// 遞歸調用
return new Proxy(target[key],handler)
}
console.log('收集')
// return target[key] 老方法
return Reflect.get(target,key)
},
set(target, key, value) {
console.log('觸發更新')
let oldValue = target[key]
console.log(oldValue,value,key)
if (!oldValue){
console.log('設置屬性')
}else if (oldValue!==value){
console.log('修改屬性')
}
// target[key] = value
// 有返回值
return Reflect.set(target,key,value)
},
}
// 兼容性差,可是能夠代理13中方法
// defineProperty 他只能對特定屬性進行攔截
// 攔截的是整個對象
let proxy = new Proxy(obj,handler)
// proxy.sex = 'nv'
// console.log(proxy.sex)
// 數組
// proxy.arr.push(132) // 先走一次obj 再收集 push length 在改值
// proxy.arr[0] = 100 // 直接觸發
proxy.xxx = 100
複製代碼