本文使用正体字书写
纲
抓住渔网的总绳,拿起衣服的领子
如在拿起一件衣服时,拿起衣领和拿起衣角的差距
説明
本文性質: 基於Vue2.6.11及其源碼的學習報告
本文内容:Vue源碼學習、Vue生態功能開發學習
本文結構
- Vue的核心邏輯的實現
- Vue的具體實現
- Vue的重要組成
- Vue的核心插件
Vue源碼部分有改編,僅供參考
目錄
- 綱
- 生命週期
- 組件
- 指令
- 數據交流等重要要素
- 路由
- 狀態管理器
甲. 纲
Vue是數據驅動視圖渲染的MVVM
Vue的核心工作内容:數據、視圖、渲染
數據:監聽與視圖相關的數據
視圖:
渲染:解析HTML標簽并且渲染到頁碼
此三點是MVVC框架的核心邏輯,不同框架之間其具體實現不盡相同
關係
視圖的渲染的輸出
數據是渲染的動力
渲染連接起數據和視圖的橋梁:使數據變化引發視圖變化
1.數據
數據的核心是:監聽數據
- 監聽形式:存取描述符--Object.defineProperty
- 依賴數據:需要被監聽的數據
- 主要源碼位置:
src/core/observer
監聽形式
- 數據監聽的邏輯--index.js
class Observer { // 該類遞歸地把對象的所有成員都轉爲被監聽的數據
constructor(value) {
this.value = value
this.dep = new Dep() // 依賴收集的相關邏輯
this.vmCount = 0 // 先忽略
def(value, '__ob__', this) // __ob__:標記--已被監聽的數據
if (Array.isArray(value)) {
// 數組預處理
(('__proto__' in {}) &&
protoAugment(value, arrayMethods)
) ||
copyAugment(
value,
arrayMethods,
Object.getOwnPropertyNames(arrayMethods));
this.observeArray(value) // 數組的監聽處理
} else {
// 非數組成員的監聽形式
this.walk(value);
}
}
walk(obj) { // 把對象的每個成員都執行監聽處理
Object.keys(obj)
.forEach(key => defineReactive(obj, key));
}
}
// 工具
function def(obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
});
}
// defineReactive函數:關於對象的監聽實現
// observeArray函數:關於數組的監聽的具體實現
const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);
2. 對象object的監聽的邏輯:工具函數defineReactive的邏輯
function defineReactive(
obj,
key,
) {
var val = obj[key];
(typeof val === 'object') && (new Observer(val)); //遞歸完成深監聽
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
console.log(`讀取數據--${key}`);
// 佔位符:依賴數據收集的相關内容
return value
},
set(newVal) {
if (newVal === value || (newVal !== newVal && value !== value)) {
return; // 相同值和 NaN的處理
}
console.log(`修改數據--${key}`);
// 佔位符:依賴數據變化時,執行回調--視圖更新的相關内容
val = newVal
}
})
}
- 數組成員的監聽
· 文件位置:array.js
· 成員存取描述符不能用於數組成員
· 數組成員可通過 重寫數組方法來實現監聽
// 直接在數組原型對象上寫
function arrayMethods() {
methodsToPatch.forEach(method => {
Object.defineProperty(Array.prototype, method, {
configurable: true,
writable: true,
value: function mutator(...args) {
console.log("調用數組方法");
// 觸發回調函數--視圖更新
return original.apply(this, args);
}
});
});
}
· Vue的實現:影響範圍最小化--修改依賴數據的[原型鏈](https://juejin.im/post/6859558780762325006)
const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
];
methodsToPatch.forEach(method => {
const original = arrayProto[method];
Object.defineProperty(arrayMethods, method, {
configurable: true,
writable: true,
value:function mutator(...args) {
console.log("調用數組方法");
// 觸發回調函數--視圖更新
return original.apply(this, args);
}
});
}
· 原型連接: 其邏輯是實現數據監聽類Observer的預處理部分
Mvvm1.prototype.deps = function (data) {
let warcher = new Watcher();
for (let key in data) {
warcher.setDom(this.$el, key);
// 響應式實現
Object.defineProperty(this, key, {
get() {
return data[key];
},
set(nv) {
setTimeout(() => {
data[key] = nv;
warcher.notify(key, nv);
}, 0)
});
}
// 初始化頁面
warcher.updata(data);
};
let mv1 = new Mvvm1({
el: "#app1",
data: {
msg: "默認msg",
info: "默認info"
}
});
### 依賴數據
文件位置:`dep.js`
功能要求:
1. 依賴收集:把視圖裏需要用到的數據收集起來
2. 依賴回調:在依賴數據發生變化時,觸發相關回調函數--視圖更新
功能實現:
1. 通過[發佈訂閲模式](https://juejin.im/post/6860669403827503112/),創建一個依賴管理器Dep
> 依賴數據的添加,通知,刪除
2. 通過[中介模式](https://juejin.im/post/6860669403827503112/),創建一個依賴感知器Watcher
> 連接依賴管理器、依賴回調、依賴數據
> 依賴數據變化時,通知依賴感知器,由依賴感知器執行回調
3. 數據鏈接
> 1. 對象:在存取描述符裏進行依賴添加和通知
> 2. 數組:在存描述符裏進行依賴添加,修改數組的方法裏通知變化
4.
class Dep {
constructor() {
this.id = uid++;
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
removeSub(sub) {
remove(this.subs, sub);
}
depend() { // 依賴添加
Dep.target && this.addSub(Dep.target);
}
notify() { // 依賴通知
this.subs.slice()
.forEach(sub => sub.update());
}
}
class Watcher {
constructor(vm, expOrFn, cb,) {
this.vm = vm;
this.cb = cb;
this.id = ++uid;
this.getter = parsePath(expOrFn);
this.value = this.get();
}
get() {
window.target = this;
const vm = this.vm
let value = this.getter.call(vm, vm)
window.target = undefined;
return value
}
update() {
const oldValue = this.value;
this.value = this.get();
this.cb.call(this.vm, this.value, oldValue);
}
}
const bailRE = /[^\w.$]/;
function parsePath(path) {
if (bailRE.test(path)) {
return
}
const segments = path.split('.')
return function (obj) {
for (let i = 0; i < segments.length; i++) {
if (!obj) return
obj = obj[segments[i]]
}
return obj
}
}
### 細節完善
雙刃劍及解決方法
甲、非響應式的操作
當變量是引用類型時,該變量的值是地址--若地址沒有改變,不會引發響應式
⑴引擎限制:執行順序
自定義的指令,組件等要在創建實例前生成
⑵JS自身限制: 在vue完成實例第二步的初始化後,
直接在data對象上進行增加的變量,是非響應式的
無法通過vm.$delete刪除data上的變量
①、響應式是在第二步的初始化階段使用屬性描述性形成的
Ⅰ、限制:修改數組元素的操作是非響應式的
解決: VUE對数组方法進行了重寫--更新检测,也會觸發響應式(所有可修改數組的方法)
增刪:splice()、push()、pop()、shift()、unshift()
排序:sort()、reverse()
Ⅱ、限制: 直接用索引设置数组项、修改数组的长度时,是非響應式的
例如:vm.items [ indexOfItem ] = newValue
vm.items.length = newLength
解決: 方法1、要使用vue.$set()
如:異步獲取的數據,需要使用vm.set動態添加
但使用 async 可以 實現同步的異步請求,而無該限制
方法2、 或者在這兩種操作後,調用被Vue重寫的數組方法
Ⅲ、限制:在把一個變量(對象類型)賦值給data的一個變量,該變量是響應式的;
但在給該變量新添加屬性時,是非響應式的
解決: 1、先在該變量上添加屬性後再賦值
2、使用 vue.$set() 對該變量新添加屬性
this.$set(this.someObject, 'b' , 2 )
3、为已有对象赋值多个新屬性:
this.someObject = Object.assign( { }, this.someObject, { a: 1, b: 2 } )
而Object.assign(this.someObject, { a: 1, b: 2 }) 的非響應式的
Ⅳ、限制: 在建立觀察後,無法在根數據對象上(data之類)添加響應式數據
解決:預留變量,并且设置初始值
⑶、vue.$set()使用:
vm.$set( target, propertyName/index, value )
参数:
{Object | Array} target
{string | number} propertyName/index
{any} value
返回值:设置的值
⑷、使用 Object.freeze(),会阻止修改现有的 property--响应系统无法追踪变化
## 2. 視圖
### 虛擬節點樹
**爲了實現在操作節點樹上的性能優化,來描述節點樹DOM**
文件位置:`src/core/vdom`
**組成:**
1. 虛擬節點
> 以JS對象形式,通過定義少量的重要的節點屬性來描述節點樹
2. 對比算法Diff
> 優化節點更新的邏輯
function createElement(domobj) {
let tempDom = document.createElement(domobj.tagName);
if (domobj.childrens && domobj.length != 0) {
for (let i = 0; i < domobj.childrens.length; i++) {
let td = createElement(domobj.childrens[i]);
tempDom.appendChild(td);
}
}
return tempDom;
}
let domTree = createEle(domObj);
虛擬節點VNode
文件:vnode.js
創建類VNode
虛擬節點的屬性要足夠少而且能正確描述真实的節點樹
class VNode {
constructor(
tag,
data,
children,
text,
elm,
context,
componentOptions,
asyncFactory
) {
this.tag = tag
this.data = data /*節點對應的對象,含數據信息,VNodeData類型*/
this.children = children /*子節點是數組*/
this.text = text /*節點文本*/
this.elm = elm /*虛擬節點對應的dom節點*/
this.ns = undefined /*節點的名字空間*/
this.context = context /*組件節點對應的Vue實例*/
this.fnContext = undefined /*函數式組件對應的Vue實例*/
this.fnOptions = undefined
this.fnScopeId = undefined
this.key = data && data.key /*節點的標誌,用以優化*/
this.componentOptions = componentOptions /*組件的option選項*/
this.componentInstance = undefined /*節點對應的組件的實例*/
this.parent = undefined /*父節點*/
/*是否為原生HTML或普通文本,innerHTML的時候為真,textContent的時候為假*/
this.raw = false
this.isStatic = false /*靜態節點標誌*/
this.isRootInsert = true /*是否作為跟節點插入*/
this.isComment = false /*是否為註釋節點*/
this.isCloned = false /*是否為克隆節點*/
this.isOnce = false /*是否有v-once指令*/
this.asyncFactory = asyncFactory
this.asyncMeta = undefined
this.isAsyncPlaceholder = false
}
get child() {
return this.componentInstance
}
}
實例:六種節點類型
> 註釋節點 文本節點 元素節點 組件節點 函數式組件節點 克隆節點
1. 注釋節點
const createEmptyVNode = (text) => {
const node = new VNode()
node.text = text // 注釋的信息
node.isComment = true // 為註釋節點
return node
}
對比算法
1.先同级比较,在比较子节点
2.先判断一方有儿子一方没儿子的情况
3.比较都有儿子的情况
4.递归比较子节点
3. 渲染
- 視圖、數據、渲染的關聯
- 在視圖渲染之前,把模板template編譯成VNode並緩存下來
- 在數據變化時,對比新舊節點,進行必要的重新渲染
抽象語法樹
解析模板
優化模板
生成代碼
乙. 生命周期
生命周期的四個階段:初始化階段、容器創建挂載階段、數據更新階段、實例銷毀階段
每个Vue实例在被创建时要经过初始化过程:
如设置数据监听、编译模板、将实例挂载到節點樹并在数据变化时更新節點樹等
包括运行生命周期钩子函数
// src/core/instance/index.js function Vue(options) {
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
// src/core/instance/init.js
Vue.prototype._init = function (options) {
.......
const vm = this
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
實驗數據:在鈎子函數,用debugger或打印輸出:
beforeCreate:在實例初始化之後,數據觀察和事件配置之前被調用
created:在實例創建完成后調用
beforeMount:在挂載開始之前被調用:相關的render函數首次被調用
mounted:el被新創建的vm.$el替換,並挂載到實例之後調用
beforeUpdata:數據更新之後被調用,重新渲染虛擬節點樹之前
updated:虛擬節點樹已經渲染到頁面,數據更新完成
beforeDestroy:實例銷毀之前調用。此時實例尚未銷毀
destroyed:實例銷毀後被調用,實例的東西被解除,包括事件監聽等;但數據保留
第一階段:初始化
⑴、 new Vue:搭建運行環境,整合構建實例的相關參數
①、生成了一個VUE的樣板對象
⑵、 初始化第一步
①、初始化VUE環境事件--實現VUE的環境功能的方法定義
如構成響應式原理、數據監視、watch/event 事件回调
②、初始化鈎子函數
③、此時完成了VUE的環境搭建
Ⅰ、在$options中有相關配置
1、該屬性是以實參所構成的相關值的對象
如:鈎子函數
Ⅱ、但實例是沒有裝載相關配置
beforeCreate: 完成了VUE的環境搭建,還沒有創建實例--裝載配置
⑶、初始化第二步:完成實例的創建
①、參數注入:數據、監視、計算屬性、方法庫等
②、響應式數據劫持生成:執行$options.data的函數:mergedInstanceDataFn()
該函數生成$data數據倉庫:
根據$options.data創建數據劫持對象:this.\$data
將變量定義到當前實例根屬性上:this.msg
③、
created:此時實例已經創建完畢,還沒有挂載容器,但已可以進行數據操作
new Vue({
el: "#app",
data: {
msg: "默認msg",
},
created() {
console.log("實例: ", this);
console.log("$el: ", this.$el);//undefined
console.log("$options: ", this.$options);//根據配置參數創建構成實例的相關對象值
console.log("$data: ", this.$data);//$options.data創建數據劫持對象
console.log("msg: ", this.msg);//將變量定義到該實例根屬性上
},
});
第一階段已經完成VUE環境事件和實例的初始化
第二階段:創建並挂載容器
第二階段進行實例與容器的連接:
根據提供的頁面結構生成與當前實例存在關係的相關容器結構
⑴、獲取“el”值:
①、當沒有指定“el”值時,要手動調用挂載函數;否則程序在此處停止
Ⅰ、vm.$mount( el );
Ⅱ、vm.$mount( ); 空值,則此時必須有模板--template;否則程序在此處停止
②、el作用:該實例與頁面容器的關係--該實例的作用的頁面位置及範圍
Ⅰ、可以取標簽的id、class;多餘符合的多個class,則取第一個元素
Ⅱ、el原理是使用document.querySelector
Ⅲ、$el是指該實例所創建的節點樹的根節點
⑵:創建虛擬節點樹
①、啓動編譯函數Complie:生成虛擬節點樹,完成數據關聯與操作
Ⅰ、根據模板/el生成虛擬節點樹,即將模板轉爲JS對象
Ⅱ、
Ⅲ、已經完成指令等内容的執行、
②、執行render函數: 生成節點樹,將虛擬節點樹提供給該函數
同時完成數據關聯
一個節點樹結構的生成
beforeMount:此時生成的節點樹要在下一步才添加到頁面中
⑶:挂載容器:
①、 vm.$el = 虛擬節點樹 :此時el為模板或者原來的el
②、將虛擬節點樹 ,轉化爲節點樹
若是組件,則是替換組件標簽(組件在之前都沒有el)
mounted:此時容器和實例已經插件完畢
適合用於初始化數據請求
此時節點樹還沒有寫入頁面中
第三階段:更新數據
把節點樹寫入頁面,進入更新階段
這兩個周期都發生在數據改變之後:所以是監控數據變化後的虛擬節點樹的重新渲染
在數據變化時,插入鈎子函數:alert(),以阻斷生命周期的運行,以查看頁面改變情況
VUE中是:异步更新队列
beforeUpdata:此時接收到頁面數據變化,但還沒有把頁面重新渲染
重新渲染虛擬節點樹並寫入頁面
updated:此時已經把頁面重新渲染了
也是$nextTick的執行階段
watch: 在數據變化之前 是
$watch:若監控多個變量,則傳入回調函數的值是新舊返回值
可以在回調函數内部直接調用unwatch()取消觀察函數
第四階段:銷毀實例
VUE的功能消失,但數據還保持著
銷毀VUE實例--VUE的功能;由其構建的數據,不會被銷毀
模板及渲染函數
⑴、關係:el定位、template模板、render渲染
定位 模板 渲染
作用 定位頁面位置 創建節點樹模板 把虛擬節點樹渲染成爲節點樹
適用場景 只new Vue()中使用 組件化 模塊化
優先級 最低 第二 最高
1、模板會替代定位指向的標簽;
2、傳入渲染函數的模板會替換模板、以及定位頁面位置的標簽
丙. 組件
自定義標簽及其標簽功能
VUE實例相當於一個組件--根組件
每一個組件都是獨立的
構造器繼承函數Vue.extend
const a = Vue.extend({
template: `
標題
`
});
①、參數--選擇項options:
Ⅰ、不能使用el,el只能在Vue構造器中使用
Ⅱ、應該有template
Ⅲ、data數據封裝在由函數return語句生成的對象裏
Ⅳ、其他如Vue構造器
②、繼承構造器
Ⅰ、Vue繼承函數生成并且返回一個繼承構造器
Ⅱ、繼承構造器不接收參數
③、使用
Ⅰ、作爲獨立頁面功能插件
1、使用new調用繼承構造器生成vue實例
⑴、因爲沒有el:該實例停在第二階段的第一步
2、 挂載$mount
⑴、調用 .$mount( )挂載,無定位el
①、調用挂載可以外部挂載,也可以在created中挂載
②、實例完成第二階段,此後該實例的$el指向渲染出來的節點樹
③、使用appendChild( “ vm.$el” )
⑵、調用 .$mount(“頁面定位” )
①、挂載并添加到頁面,進入生命周期的第三階段
3、插件案例:彈出框
Ⅱ、作爲組件配置項,傳入組件定義器
注冊組件
⑵、組件定義器component
利用html5的template創建一個自定義標簽
Vue.component--創建全局組件
①、定義形式
Vue.component("組件名字", 組件配置 ) ;
Ⅰ、特點
在頁面使用組件時,自動new調用
Ⅱ、參數1: 定義組件名字,也是該組件在頁面的標簽名
Ⅲ、參數2:配置組件内容,兩種形式
1.、合并寫法:
i、可以是Vue.extend的返回值、
ii、如Vue.extend參數形式
2、分離式寫法:創建位置與脚本分離,然後通過標簽的id屬性來獲取
i、 脚本形式
標題1
ii、 HTML5 模板標簽形式
標題1
iii、使用id連接,并且注冊
Vue.component("a-com", {
template: "#ad"
});
Ⅵ、 let x = Vue.component("組件名字" ) ; 獲取一個已經注冊的組件
⑶、創建局部該組件
Ⅰ、組件模板可以采取合并和分離式寫法
Ⅱ、書寫地方
①、在組件的 components:屬性中添加
②、VUE實例中的局部組件
const app = new Vue({
el: "#appp",
data: {
msg: "amtf"
},
components: {
aa : aa // 使用vue.extend的返回值、組件定義器的返回值
bb: { 組件配置 }
}
});
0.局部組件:只在該實例中使用,不可在其他VUE實例中使用
1.通過Vue構造函數内部注冊:通過 components: { aa }
- 鍵是組件的標簽名;值是組件配置
3.全局組件與局部組件出現重名時,局部組件有更高優先級
組件會在生命周期mounted前一步把節點樹渲染到頁面,同時把該組件標簽賦值給$el
⑷、使用組件
①、生成的組件,只能添加在VUE實例的容器内
可以直接在 new Vue()定位的頁面中
可以直接在其他組件的template中
②、注意循環調用組件
③、設置忽略自定義標簽名字:Vue.config.ignoredElements
限制
⑴、組件的data
①、必須是由函數返回的對象,否則報錯
②、若要引用同一個對象,則可以返回一個對象標識符,或者可以不適應函數封裝
⑵、每個組件必須要有一個根元素
⑶、組件名:
①、多單詞、
②、單詞大寫開頭
③、駝峰、或者橫綫連接
若使用駝峰,則在標簽上使用橫綫連接
⑷、編譯作用域:
父级模板里的所有内容都是在父级作用域中编译的;
子模板里的所有内容都是在子作用域中编译的
①、在VUE實例中,組件變量取該實例的變量
②、在組件模板中,取該組件的變量
⑸、組件template必須要有一個跟標簽,其他標簽放在該標簽中
組件的生命周期
组件的调用顺序都是先父后子, 渲染完成的顺序是先子后父
组件的销毁操作是先父后子, 销毁完成的顺序是先子后父
⑴、在初始化階段到挂載階段
Ⅰ、原理:每一個組件都有其生命周期
①、在將節點樹渲染到頁面時--mount的前一步,
得保證已經獲取該組件的全部節點樹,所以此時要進入内層組件
②、在獲取了全部節點樹后開始渲染到頁面
③、在有内層嵌套時,得先完成内層組件的節點樹的渲染
組件構建完成beforeMount后會檢查是否有子/兄弟組件,有則進入子/兄弟組件的構建
Ⅱ、具體順序
父beforeCreate => 。。父beforeMount => 子① beforeCreate => 。。 子①beforeMount =>
子② beforeCreate => 。。 子②beforeMount => 子①mounted => 子②mounted => 父mounted
①、所有組件的beforeMount及之前的生命周期順序:
1.由頁面定義順序自上而下
2.由外層組件到内層組件
- 一旦進入組件的生命周期,要先完成該組件的這三個周期再進入下一個組件
②、所有組件的beforeMount執行完畢后:
1.由元素定義順序自上而下,非頁面定義順序
2.由内層組件到外層組件
3.執行完mounted才進入下一個組件
- 子組件在第二階段的第三步:
把子組件的創建好的虛擬節點樹賦值給$el,
并且替換在頁面中定義在父組件内的標簽位置
⑵、更新階段
①、子组件更新过程:父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated
②、父组件更新过程:父beforeUpdate -> 父updated
⑶、銷毀階段
父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed
⑷、組件獨有生命周期
keep-alive
keep-alive是Vue的内置组件,其中的生命周期只会走一遍,并且会增加activated和deactivaed生命周期,用以其他的业务逻辑。
通常用来包裹动态切换的路由或组件,防止组件频繁地创建和销毁,从而达到性能优化的效果。
keep-alive常用的属性
include:字符串或正则表达式。匹配的组件会被缓存。
exclude:字符串或正则表达式。匹配的组件都不会被缓存。
max:数字。最多可以缓存多少组件实例。
activated活跃状态
当前组件显示时执行的生命周期
deactivated缓存状态
当前组件缓存时执行的生命周期
errorCaptured
当捕获一个来自子孙组件的错误时被调用。
此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。
• 当keep-alive 缓存组件才会有的生命周期的钩子函数
• activated deactivated
• errorCaptured 当子孙组件出错时,会调用这个钩子函数
組件閒數據交流
甲、組件閒的數據交流
單向數據交流 父到子 屬性綁定 props 原理 vm.$attrs 響應式
子到父 事件綁定 vm.$emit 原理 vm._event 無響應式
組件閒 事件總綫 vm.$on 原理 vm.$emit、vm.$on 無響應式
雙向數據交流 組件閒
直
⑴、父子組件:組件甲添加到組件乙中
1、組件甲添加到在組件乙的components中、或者直接把一個組件的標簽寫在實例中
2、VUE實例是頂層父組件:root
3、組件標簽上定義的屬性,都會向子組件傳遞
4、組件的事件綁定與屬性綁定:
Ⅰ、兩者在不同生命周期中生成
事件綁定:Vue事件環境在第一階段的第一步完成初始化
屬性綁定:第二階段的第二步--組件編譯過程,創建虛擬節點樹
Ⅱ、①、事件綁定:Vue事件環境在第一階段的第一步完成初始化
i、子組件標簽上的事件,作爲該子組件模板的事件環境
ii、定義在子組件的事件庫--_events
iii、事件庫形式: { 事件1:[invoker函數],事件2:[invoker函數]。。。 }
值是以數組形式保存著一個元素--invoker函數
通過執行invoker()調用所有該事件所綁定的函數
iiii、在子組件模板上可觸發事件庫裏存儲的父組件方法
通過傳參可以把子組件的數據傳遞到父組件的方法中
iiiii、_events是實例是事件庫、$event是被觸發的事件、event是原生JS事件
②、屬性綁定:添加在子組件其標簽上的屬性
i、相當於直接添加在子組件模板的根標簽上的屬性
ii、同時也把數據傳遞到了子組件的模板上--跨組件的數據傳遞
5、直接進行組件閒數據訪問: 把組件當作對象進行訪問,直接訪問組件數據
JS沒有私有變量、vue配置
①、獲取子組件數據:
this.$children:獲取子組件數組集合
this.$refs:獲取有res屬性的子組件對象集合
②、獲取父組件數據:
this.$parent:獲取父組件
this.$root:獲取根組件
③、中央事件總綫
乙、父到子傳遞
原理:模板與子組件的$attrs屬性
1.添加在模板根標簽上的屬性,相當於添加在子組件跟標簽上
子組件
子組件
2、來自父組件的數據會被傳到子組件模板上
⑴.相當於添加到模板跟標簽上
子組件
⑵.子組件的非DOM屬性的自定義屬性, 會放入子組件的$attrs屬性中
子組件
①、在子組件模板上獲取$attrs屬性:this.$attrs
該屬性獲取的屬性保留著數據類型
1、在組件生命周期的編譯函數Complie中直接獲取父組件的相關數據,并賦值給.$attrs
2、然後在構成頁面時,將$attrs寫入頁面的標簽上
3、而這是在JS環境執行的
②、在子組件模板上調用$attrs屬性:如計算屬性
computed: {
getMsg(){
return this.$attrs.attr1
}
③、$attrs屬性與屬性綁定具有響應式
單向父到子:$attrs屬性的規範使用:數據攔截
攔截原先放在在$attrs的自定義屬性,改放入prop中
⑴、props數據攔截形式:
①、在子組件標簽上:以props的變量作爲綁定參數,獲取父組件的數據
②、在子組件模板上不需要進行相關操作,就可以直接使用數據
Ⅰ、傳遞靜態值,直接賦值
Ⅱ、傳遞動態值,使用v-bind
Ⅲ、可傳入一個對象的全部變量
可以使用v-bind而不帶參數,但prop中要有相應的變量名字
⑵、props書寫形式:props:{ a: String... } 、props: [ “a” ] (數組:以字符串形式)
prop使用對象字面量形式定義時,可以對值有更多配置选项
①、類型限制type: String、Number、Boolean、
Array、Object、Date、Function、Symbol、
自定义构造器--此使用instanceof來判斷
②、指定默認值default:若默認值是对象或数组,使用函數返回該值
1、vue使用undefined判斷是否使用默認值
2、傳入:空、未定義、未傳值時調用默認值
3、引用類型,對應的prop需要使用函數封裝返回
③、 是否必選required:布爾,定义该 prop 是否是必填
與默認值同時存在時,只在傳入值爲未定義時,會取到默認值
④、自定义验证規則validator:函數,会将该 prop 的值作为唯一的参数
Ⅰ、返回值:布爾 , 若沒有返回值,則為假
⑤、验证prop值是在一个组件实例创建之前
实例 property (如 data、computed 等) 在 default 或 validator 函数中是不可用的
⑶、本组件实例访问props的變量,如同data
①、props是只讀的,不能寫入,響應式
②、
⑷、其他:
①、特殊屬性如,class屬性,直接添加,$attrs只會去自定義屬性
②、使用驼峰命名法的 prop 名需要轉爲等价的短横线命名
丙、子到父傳遞
子組件傳參到父組件:監聽子組件事件--$emit.event
⑴、原理:事件綁定
在子組件上綁定事件,不會被添加到子組件模板的跟標簽上
而是定義在該組件的事件庫--_events
⑵、子組件可以調用父組件的方法,并且把子組件的數據傳給父組件的方法
如,在子組件模板上調用來自父組件的方法,并傳入來自子組件模板的數據
methods: {
adclick() {
this._events.click[0](this.msg)
} }
①、綁定在子組件上的方法,若有括號,將不能傳參,只能觸發父組件方法
⑶、Vue規範使用:vm.$emit--只會觸發對應VUE實例事件庫中定義的事件
vm.$emit( 事件名字 [, 剩餘參數] )
①、子組件標簽上添加自定義事件名
0.事件名字
Ⅰ、不要使用標簽定義的規範的已知事件名,如
Ⅱ、多單詞
Ⅲ、一般使用:間隔
1.若要傳參到父組件方法,則不能帶括號
2.自定義事件名字不要使用駝峰
②、在子組件的模板上創建事件,綁定調用函數
③、通過調用函數時,調用this.$emit方法觸發子組件事件庫的事件
可以通過執行函數觸發this.$emit
methods: {
afn(e) {this.$emit("aclick",e);
}
- this.$emit的參數1事件被觸發,對於觸發了綁定子組件該事件的父組件的方法
- 在子組件模板上直接調用,不需要this: 如@input="$emit('abc',msg)"
④、非響應式:子組件模板上數據變化,不會自動觸發$emit
⑤、将原生事件绑定到子组件
在子組件上綁定時,使用事件修飾符:.native,取消這種情況
⑷、子組件上的v-model
丁、中央事件總綫:
event BUS
⑴、原理:VUE實例可以使用$on方法在事件倉庫中定義事件
在組件裏使用$emit來調用事件倉庫中的事件,并且傳參
⑵、使用步驟
①、創建事件vm1.$on(“事件名字”,回調函數)
一個在vm1實例中創建的事件
回調函數使用箭頭函數,以正確獲取指向該組件的this值
該事件觸發時,會在創建的環境中執行
②、觸發事件:vm1.$emit( “事件名字”[,剩餘參數] )方法
⑶、中央總綫:上面兩種方法的主體
①、使用一個空VUE實例
②、該組件的跟組件:this.$root、該組件的原型上
⑷、定義的位置及時機
①、接收數據的組件創建事件
②、傳送數據的組件觸發事件
③、$on必須優先$emit定義
④、若要自動調用
$on可定義在beforeMount
$emit可定義在mounted
戊、響應式的雙向數據傳遞:組件閒
⑴. 使用prop與$emit可以實現父子組件閒雙向傳遞
⑵. 結合計算屬性與v-model,props
Ⅰ、定義流程:
①、在子組件模板上,使用props定義來自父組件的數據
②、在子組件模板上,定義計算屬性:
取值操作返回props的變量
賦值操作中調用$emit
③、在子組件模板上,使用v-model綁定計算屬性:
實現取值賦值時觸發get、set,
進而有條件觸發父組件方法,實現更新數據
④、在子組件上使用prop屬性來獲取父組件數據
prop獲取的數據再傳入計算屬性
⑤、在子組件上創建事件并且綁定父組件方法
⑥、定義父組件方法來修改父組件數據
Ⅱ、執行流程:
首先子組件模板上的v-model綁定的計算屬性發生賦值操作時,觸發set
接著在set函數中觸發$emit
進而觸發綁定在子組件上的父組件方法,并且傳參以實現修改父組件的數據
再而在子組件上使用prop屬性所綁定的父組件的數據發生了變化
最後該數據變化觸發計算屬性,實現數據更新
⑶. v-bing.sync修飾符
是方法二的語法糖
Ⅰ、定義流程:
①、在子組件模板上,使用props定義來自父組件的數據
②、在子組件模板上,定義計算屬性:
取值操作返回props的變量
賦值操作中調用$emit(“update:屬性名”,nv)
此屬性名來自prop的變量
③、在子組件模板上,使用v-model綁定計算屬性:
實現取值賦值時觸發get、set,
進而有條件觸發父組件方法,實現更新數據
④、在子組件上使用prop屬性來獲取父組件數據,并且使用.sync修飾符
prop獲取的數據同樣再傳入計算屬性
Ⅱ、執行流程: 如方法二
Ⅲ.sync語法糖:
1.在父組件中為對應的屬性進行賦值操作
- 在子組件中的事件庫中,新增一個自定義事件
update:屬性名(來自prop的)
⑷. 使用引用類型
父組件數據封裝在對象裏
Ⅰ、定義流程:
①、在子組件模板上,使用props定義來自父組件的對象
②、在子組件模板上,使用v-model綁定對象的值:
③、在子組件上使用prop屬性來獲取父組件對象
④、父組件的數據包裝在對象裏
Ⅱ、執行流程: 直接修改
Ⅲ、非單向數據流,無法監視
⑸、獨立數據監控對象
Vue.observable方法
1、原理:該方法通過Object.defineProperty完成數據劫持和響應式,
生成并返回一個被劫持的響應式的對象
2、作用:為項目的數據統一管理維護提供接口--建立外層作用域的響應式數據倉庫
3、定義流程: 傳入對象,并且用一個變量接受返回值
let a = Vue.observable(對象)
4、使用流程: 在該作用域中,所有組件都可以直接使用: a.x、a.y
組件閒的切換:動態組件
通過Vue的
⑴、使用keep-alive保持組件的狀態,取消切換組件時重新渲染
分發内容:插槽
向組件實例添加標簽:在組件中添加標簽:
⑴、默認值:在標簽内部放入其他標簽作爲默認值
⑵、具名插槽:具有先後順序
在插槽上給插槽添加name屬性,在元素上使用v-slot:賦值name屬性來指定插槽
簡寫:#
⑶、作用域插槽:子組件在父組件中使用子組件的數據
三步驟
①、在子組件模板的插槽標簽上,使用綁定屬性來獲取子組件的指定數據
②、在父組件的子組件的插槽内:把一個新變量賦值給帶值的v-solt
③、該新變量對象:值是子組件的數據,鍵是子組件的綁定屬性名
指令
21
22
21
21
const Mvvm2 = function (options) {
this.$el = document.querySelector(options.el);
this._data = options.data;
let subsDom = {};
for (let key in this._data) {
let selStr = `[mv-text="${key}"]`;
subsDom[key] = this.$el.querySelectorAll(selStr);
}
for (let key in this._data) {
for (let i = 0; i < subsDom[key].length; i++) {
subsDom[key][i].textContent = this._data[key];
}
Object.defineProperty(this, key, {
get() {
return this._data[key];
},
set(nv) {
this._data[key] = nv;
for (let i = 0; i < subsDom[key].length; i++) {
subsDom[key][i].textContent = this._data[key];
}
}
});
}
};
let vm2 = new Mvvm2({
el: "#app2",
data: {
msg: "默認msg2",
info: "默認info2"
}
});
改良
// 1.、該實例對象存儲存儲所有綁定了指令的節點、 2.、原型方法提供節點樹和更新頁面
var Watcher = function () { } //
// 存儲所有綁定了該指令的節點, 按綁定的具體數據進行劃分
Watcher.prototype.setDom = function (rootDom, key) {
let selStr = `[mv-text="${key}"]`;
this[key] = rootDom.querySelectorAll(selStr);
};
// 把數據渲染到頁面
Watcher.prototype.notify = function (key, nv) {
let domList = this[key];
for (let i = 0; i < domList.length; i++) {
domList[i].textContent = nv;
}
};
// 數據變化時,調用該方法
Watcher.prototype.updata = function (data) {
for (let key in data) {
this.notify(key, data[key]);
}
};
let Mvvm1 = function (options) {
// 初始化:獲取節點和數據
this.init(options.el, options.data);
};
Mvvm1.prototype.init = function (el, data) {
this.$el = document.querySelector(el);
this._data = data;
this.deps(this._data); // 生成響應式
};
乙、 异步更新队列
特點: 更新節點樹是异步执行的
侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更
如果同一个 watcher 被多次触发,只会被推入到队列中一次
在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作
限制: 一些需要在賦值操作后,立即利用新值進行操作的内容
如:在一個按鈕中調用該方法,其兩次打印結果都是舊值--異步的数据变更
fn(e) {
console.log(this.$refs.a.innerHTML);
this.msg = "111";
console.log(this.$refs.a.innerHTML);
该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件循环“tick”中更新
解決:vm.$nextTick
为了在数据变化之后等待 Vue 完成更新節點樹,可以在数据变化之后立即使用vm.$nextTick
監控頁面的節點更新,在頁面節點更新后調用該函數,
此方法執行時間如同生命周期的 updated
如更新數據後,在此方法内創建輪播圖實例、echarts