vue 源码分析(尚硅谷视频学习笔记)
下面是我邊看視頻變記錄的重點(diǎn)難點(diǎn),詳細(xì)具體有條理的看我轉(zhuǎn)載的那篇尚硅谷課件,課件有的內(nèi)容我基本不重復(fù)寫(xiě)到
1.類數(shù)組和數(shù)組
用document.getElementsByClassName()方法或者jQuery方法獲取的標(biāo)簽集合是類數(shù)組,不是真正的數(shù)組。使用instanceof可以知道lis instanceof Object為true; lis instanceof Array為false。假如用lis接收,那么lis可以使用lis[index]的方式獲取某一個(gè)標(biāo)簽對(duì)象,但是不可以使用Array(數(shù)組)原型鏈上的屬性和方法。如forEach、splice、join等。
2.類數(shù)組使用forEach遍歷所有標(biāo)簽對(duì)象(類數(shù)組轉(zhuǎn)數(shù)組)
ES6: Array.from(lis)
ES5:[].slice.call(lis) 或者 Array.prototype.slice.call(lis)
3.節(jié)點(diǎn)類型(nodeType)
常用的:
Document
Element
Attribute
Text
node.nodeType獲取節(jié)點(diǎn)類型(Number)
4.定義屬性O(shè)bject.defineProperty(obj, prop, descriptior)
在descriptior內(nèi)定義set和get方法,可以實(shí)現(xiàn)屬性的監(jiān)聽(tīng)和獲取。
IE8不支持此語(yǔ)法,此方法是Vue實(shí)現(xiàn)數(shù)據(jù)綁定等操作的核心方法。
屬性描述符:
1.數(shù)據(jù)描述符:
configurable: true/false 是否可以重新define
enumerable: true/false 是否可以枚舉(for…in / keys())
value: 指定初始值
writable: true/false value 是否可以修改
2.訪問(wèn)描述符:
get: 回調(diào)函數(shù), 用來(lái)得到當(dāng)前屬性值
set: 回調(diào)函數(shù), 用來(lái)監(jiān)視當(dāng)前屬性值的變化
5.判斷是否是自身屬性(obj.hasOwnProperty(pro))
JS學(xué)過(guò),返回布爾值。
6.DocumentFragment: 文檔碎片(高效批量更新多個(gè)節(jié)點(diǎn))
內(nèi)存中保存n個(gè)element的容器對(duì)象(不與界面關(guān)聯(lián)),如果更新fragment中的某個(gè)element,界面不變。
多次更新界面變成一次更新界面,減少更新界面的次數(shù)。
一個(gè)節(jié)點(diǎn)只能有一個(gè)父親
使用步驟:
7.數(shù)據(jù)代理
vue 數(shù)據(jù)代理: 通過(guò)vm 對(duì)象來(lái)代理data 對(duì)象中所有屬性的操作
vm._data.name === vm.name
8.set和get執(zhí)行的斷點(diǎn)調(diào)試
打斷點(diǎn)后觀察可知:
1.vm對(duì)象對(duì)data中的數(shù)據(jù)實(shí)現(xiàn)數(shù)據(jù)代理
2.vm中觸發(fā)回調(diào)函數(shù)執(zhí)行,函數(shù)中調(diào)用_propxy()方法,里面是用Object.defineProperty(obj, prop, descriptior)實(shí)現(xiàn)。
3.修改屬性值調(diào)用Object.defineProperty(obj, prop, descriptior)中的descriptior對(duì)象中的set方法,數(shù)據(jù)被存入data。
4.頁(yè)面中顯示數(shù)據(jù)調(diào)用get方法從data中取數(shù)據(jù)。
9.大佬仿Vue的main.js
/* 相關(guān)于Vue的構(gòu)造函數(shù)*/ function MVVM(options) {// 將選項(xiàng)對(duì)象保存到vmthis.$options = options;// 將data對(duì)象保存到vm和datq變量中var data = this._data = this.$options.data;//將vm保存在me變量中var me = this;// 遍歷data中所有屬性O(shè)bject.keys(data).forEach(function (key) { // 屬性名: name// 對(duì)指定屬性實(shí)現(xiàn)代理me._proxy(key);});// 對(duì)data進(jìn)行監(jiān)視observe(data, this);// 創(chuàng)建一個(gè)用來(lái)編譯模板的compile對(duì)象this.$compile = new Compile(options.el || document.body, this) }MVVM.prototype = {$watch: function (key, cb, options) {new Watcher(this, key, cb);},// 對(duì)指定屬性實(shí)現(xiàn)代理_proxy: function (key) {// 保存vmvar me = this;// 給vm添加指定屬性名的屬性(使用屬性描述)Object.defineProperty(me, key, {configurable: false, // 不能再重新定義enumerable: true, // 可以枚舉// 當(dāng)通過(guò)vm.name讀取屬性值時(shí)自動(dòng)調(diào)用get: function proxyGetter() {// 讀取data中對(duì)應(yīng)屬性值返回(實(shí)現(xiàn)代理讀操作)return me._data[key];},// 當(dāng)通過(guò)vm.name = 'xxx'時(shí)自動(dòng)調(diào)用set: function proxySetter(newVal) {// 將最新的值保存到data中對(duì)應(yīng)的屬性上(實(shí)現(xiàn)代理寫(xiě)操作)me._data[key] = newVal;}});} };10.模板解析
利用到fragment。
1.將元素節(jié)點(diǎn)轉(zhuǎn)移到內(nèi)存中
2.在內(nèi)存中生成需要的元素節(jié)點(diǎn)(init,初始化,涉及到遞歸),遍歷fragment中所有的子元素節(jié)點(diǎn)。利用正則匹配將空文本節(jié)點(diǎn)、數(shù)據(jù)節(jié)點(diǎn)等區(qū)分進(jìn)行操作。
3.最后一次性添加到頁(yè)面
10.1{{data}}如何變成具體數(shù)值
正則中的子匹配():
()中的可以匹配到data的內(nèi)容并儲(chǔ)存起來(lái)。
Vue中有一套用來(lái)更新標(biāo)簽的指令方法。
調(diào)用函數(shù)的層次很深。。。
11. compile.js
function Compile(el, vm) {// 保存vm到compile對(duì)象this.$vm = vm;// 保存el元素到compile對(duì)象this.$el = this.isElementNode(el) ? el : document.querySelector(el);// 如果el元素存在if (this.$el) {// 1. 取出el中所有子節(jié)點(diǎn), 封裝在一個(gè)framgment對(duì)象中this.$fragment = this.node2Fragment(this.$el);// 2. 編譯fragment中所有層次子節(jié)點(diǎn)this.init();// 3. 將fragment添加到el中this.$el.appendChild(this.$fragment);} }Compile.prototype = {node2Fragment: function (el) {//創(chuàng)建空的fragmentvar fragment = document.createDocumentFragment(),child;// 將原生節(jié)點(diǎn)拷貝到fragment(將el中所有子節(jié)點(diǎn)轉(zhuǎn)移到fragment)while (child = el.firstChild) {fragment.appendChild(child);}//返回fragmentreturn fragment;},init: function () {// 編譯fragment(編譯所有層次的子節(jié)點(diǎn))this.compileElement(this.$fragment);},compileElement: function (el) {// 得到所有子節(jié)點(diǎn)var childNodes = el.childNodes,// 保存compile對(duì)象me = this;// 遍歷所有子節(jié)點(diǎn)(text/element)[].slice.call(childNodes).forEach(function (node) {// 得到節(jié)點(diǎn)的文本內(nèi)容var text = node.textContent;// 正則對(duì)象(匹配大括號(hào)表達(dá)式)var reg = /\{\{(.*)\}\}/; // {{name}}// 如果是元素節(jié)點(diǎn)if (me.isElementNode(node)) {// 編譯元素節(jié)點(diǎn)的指令屬性me.compile(node);// 如果是一個(gè)大括號(hào)表達(dá)式格式的文本節(jié)點(diǎn)} else if (me.isTextNode(node) && reg.test(text)) {// 編譯大括號(hào)表達(dá)式格式的文本節(jié)點(diǎn)me.compileText(node, RegExp.$1); // RegExp.$1: 表達(dá)式 name}// 如果子節(jié)點(diǎn)還有子節(jié)點(diǎn)if (node.childNodes && node.childNodes.length) {// 遞歸調(diào)用實(shí)現(xiàn)所有層次節(jié)點(diǎn)的編譯me.compileElement(node);}});},compile: function (node) {// 得到所有標(biāo)簽屬性節(jié)點(diǎn)var nodeAttrs = node.attributes,me = this;// 遍歷所有屬性[].slice.call(nodeAttrs).forEach(function (attr) {// 得到屬性名: v-on:clickvar attrName = attr.name;// 判斷是否是指令屬性if (me.isDirective(attrName)) {// 得到表達(dá)式(屬性值): testvar exp = attr.value;// 得到指令名: on:clickvar dir = attrName.substring(2);// 事件指令if (me.isEventDirective(dir)) {// 解析事件指令compileUtil.eventHandler(node, me.$vm, exp, dir);// 普通指令} else {// 解析普通指令compileUtil[dir] && compileUtil[dir](node, me.$vm, exp);}// 移除指令屬性node.removeAttribute(attrName);}});},compileText: function (node, exp) {// 調(diào)用編譯工具對(duì)象解析compileUtil.text(node, this.$vm, exp);},isDirective: function (attr) {return attr.indexOf('v-') == 0;},isEventDirective: function (dir) {return dir.indexOf('on') === 0;},isElementNode: function (node) {return node.nodeType == 1;},isTextNode: function (node) {return node.nodeType == 3;} };// 指令處理集合(包含多個(gè)解析指令的方法的工具對(duì)象) var compileUtil = {// 解析: v-text/{{}}text: function (node, vm, exp) {this.bind(node, vm, exp, 'text');},// 解析: v-htmlhtml: function (node, vm, exp) {this.bind(node, vm, exp, 'html');},// 解析: v-modelmodel: function (node, vm, exp) {//實(shí)現(xiàn)數(shù)據(jù)的初始化顯示和創(chuàng)建對(duì)應(yīng)的watcherthis.bind(node, vm, exp, 'model');var me = this,//得到表達(dá)式的值val = this._getVMVal(vm, exp);//給節(jié)點(diǎn)綁定input事件監(jiān)聽(tīng)(輸入改變時(shí))node.addEventListener('input', function (e) {//得到輸入的最新值var newValue = e.target.value;//如果沒(méi)有變化,直接結(jié)束if (val === newValue) {return;}//將最新value保存給表達(dá)式所對(duì)應(yīng)的屬性me._setVMVal(vm, exp, newValue);val = newValue;});},// 解析: v-classclass: function (node, vm, exp) {this.bind(node, vm, exp, 'class');},// 真正用于解析指令的方法bind: function (node, vm, exp, dir) {/*實(shí)現(xiàn)初始化顯示*/// 根據(jù)指令名(text)得到對(duì)應(yīng)的更新節(jié)點(diǎn)函數(shù)var updaterFn = updater[dir + 'Updater'];// 如果存在調(diào)用來(lái)更新節(jié)點(diǎn)updaterFn && updaterFn(node, this._getVMVal(vm, exp));// 創(chuàng)建表達(dá)式對(duì)應(yīng)的watcher對(duì)象new Watcher(vm, exp, function (value, oldValue) {/*更新界面*/// 當(dāng)對(duì)應(yīng)的屬性值發(fā)生了變化時(shí), 自動(dòng)調(diào)用, 更新對(duì)應(yīng)的節(jié)點(diǎn)updaterFn && updaterFn(node, value, oldValue);});},// 事件處理eventHandler: function (node, vm, exp, dir) {// 得到事件名/類型: clickvar eventType = dir.split(':')[1],// 根據(jù)表達(dá)式得到事件處理函數(shù)(從methods中): test(){}fn = vm.$options.methods && vm.$options.methods[exp];// 如果都存在if (eventType && fn) {// 綁定指定事件名和回調(diào)函數(shù)的DOM事件監(jiān)聽(tīng), 將回調(diào)函數(shù)中的this強(qiáng)制綁定為vmnode.addEventListener(eventType, fn.bind(vm), false);}},// 從vm得到表達(dá)式對(duì)應(yīng)的value_getVMVal: function (vm, exp) {var val = vm._data;exp = exp.split('.');exp.forEach(function (k) {val = val[k];});return val;},_setVMVal: function (vm, exp, value) {var val = vm._data;exp = exp.split('.');exp.forEach(function (k, i) {// 非最后一個(gè)key,更新val的值if (i < exp.length - 1) {val = val[k];} else {val[k] = value;}});} };// 包含多個(gè)用于更新節(jié)點(diǎn)方法的工具對(duì)象 var updater = {// 更新節(jié)點(diǎn)的textContenttextUpdater: function (node, value) {node.textContent = typeof value == 'undefined' ? '' : value;},// 更新節(jié)點(diǎn)的innerHTMLhtmlUpdater: function (node, value) {node.innerHTML = typeof value == 'undefined' ? '' : value;},// 更新節(jié)點(diǎn)的classNameclassUpdater: function (node, value, oldValue) {//靜態(tài)class屬性的值var className = node.className;className = className.replace(oldValue, '').replace(/\s$/, '');var space = className && String(value) ? ' ' : '';//將靜態(tài)class屬性的值與動(dòng)態(tài)class值進(jìn)行合并后設(shè)置為新的className屬性值node.className = className + space + value;},// 更新節(jié)點(diǎn)的valuemodelUpdater: function (node, value, oldValue) {node.value = typeof value == 'undefined' ? '' : value;} };12. 事件指令解析
內(nèi)部實(shí)現(xiàn)事件處理的回調(diào)函數(shù)的this一定要用bind()方法指定為vm,因?yàn)榉椒ǘ级x在methods中。
使用了原生的addEventListener(eventType, callback, boolean)方法。
13.一般指令解析
最后有移除指令屬性。
14. 數(shù)據(jù)劫持是Vue中用來(lái)實(shí)現(xiàn)數(shù)據(jù)綁定的一種技術(shù)
基本思想: 通過(guò)defineProperty()來(lái)監(jiān)視data 中所有屬性(任意層次)數(shù)據(jù)的變化, 一旦變
化就去更新界面。
15. vm中的set和data中的set不一樣
vm中的set主要是用來(lái)實(shí)現(xiàn)數(shù)據(jù)代理,當(dāng)this.xxx(即vm.xxx)改變時(shí),vm中的set會(huì)被調(diào)用,去改變data中xxx的值,從而data中set被調(diào)用,更新界面,主要用來(lái)實(shí)現(xiàn)數(shù)據(jù)綁定。
16.實(shí)現(xiàn)數(shù)據(jù)劫持的函數(shù)內(nèi)部重寫(xiě)定義data中數(shù)據(jù),為其添加set和get方法
Observer、Observe、walk等方法
17.編譯模板最后都會(huì)經(jīng)過(guò)bind方法,除了事件指令(添加事件監(jiān)聽(tīng))
一般指令和大括號(hào)表達(dá)式:bind,創(chuàng)建watcher,監(jiān)視器。
事件指令:eventHandler,添加事件監(jiān)聽(tīng)。
18.回調(diào)函數(shù)三個(gè)注意
1.什么是調(diào)用
2.用來(lái)做什么
3.this指向什么
19.Dep和Watcher
Dep:
它的實(shí)例什么時(shí)候創(chuàng)建?
答:初始化的給data的屬性進(jìn)行數(shù)據(jù)劫持時(shí)創(chuàng)建的。
個(gè)數(shù)?
答:與data中的屬性一一對(duì)應(yīng)。
Dep的結(jié)構(gòu)?
答:id:標(biāo)識(shí)。
subs:[] //n個(gè)相關(guān)的watcher的容器
Watcher:
它的實(shí)例什么時(shí)候創(chuàng)建?
答:初始化的解析大括號(hào)表達(dá)式/一般指令時(shí)創(chuàng)建。
個(gè)數(shù)?
答:與模板中表達(dá)式(非事件指令)一一對(duì)應(yīng)。
Watcher結(jié)構(gòu)?
答:
this.cb = cb; // callback 用于更新界面的回調(diào)
this.vm = vm; //vm
this.exp = exp; //對(duì)應(yīng)的表達(dá)式
this.depIds = {}; // {0: d0, 1: d1, 2: d2} 相關(guān)的n個(gè)dep的容器對(duì)象
this.value = this.get(); //當(dāng)前表達(dá)式對(duì)應(yīng)的value
Dep與Watcher之間的關(guān)系
什么關(guān)系?
答:多對(duì)多的關(guān)系
Dep --> n個(gè)watcher的情況:模板中寫(xiě)多個(gè){{name}}或v-text="name"等(屬性在模板中多次被使用)
Watcher --> n個(gè)Dep()的情況:a.b(一個(gè)表達(dá)式只對(duì)應(yīng)一個(gè)watcher)對(duì)應(yīng)兩個(gè)Dep()(多層表達(dá)式)
如何建立的?
答:
1.在數(shù)據(jù)劫持的get方法(data屬性的get方法)中建立Dep和Watcher的關(guān)系。
2.Dep先創(chuàng)建(在Observer數(shù)據(jù)劫持的時(shí)候創(chuàng)建),Watcher后創(chuàng)建(在模板解析的時(shí)候創(chuàng)建)。
3.在創(chuàng)建Watcher的時(shí)候建立Dep和Watcher的關(guān)系。在創(chuàng)建Watcher內(nèi)部調(diào)用get()方法(vm的get方法)。
4.Dep.target一開(kāi)始為null,當(dāng)創(chuàng)建Watcher的時(shí)候,.Watcher中的get()方法將Dep.target設(shè)置為Watcher。并且調(diào)用getVMVal()方法,導(dǎo)致get()方法調(diào)用,執(zhí)行dep.depend()方法,再執(zhí)行Dep.target.addDep(this)(進(jìn)入watcher.js文件中)首先給Dep的subs添加Watcher,再為Watcher的depIds添加Dep的id,并將dep保存到depIds中。
5.當(dāng)屬性改變的時(shí)候,會(huì)通過(guò)subs通知所有關(guān)聯(lián)的watcher。
6.關(guān)系只在初始化的時(shí)候建立。
20.改變變量時(shí)代碼內(nèi)部引起的變化
v.name = ‘a(chǎn)bc’ --> data中的name屬性值變化 --> name的set()調(diào)用 --> dep(set方法中有一句dep.notify()語(yǔ)句) --> 通知所有相關(guān)的watcher(subs使用forEach方法,sub.update())–>cb()(調(diào)用回調(diào)函數(shù)更新界面this.cb.call(this, value, oldVal))–> updater
21. observer.js
function Observer(data) {// 保存data對(duì)象this.data = data;// 走起this.walk(data); }Observer.prototype = {walk: function(data) {//保存observer對(duì)象var me = this;// 遍歷data中所有屬性O(shè)bject.keys(data).forEach(function(key) {// 針對(duì)指定屬性進(jìn)行處理me.convert(key, data[key]);});},convert: function(key, val) {// 對(duì)指定屬性實(shí)現(xiàn)響應(yīng)式數(shù)據(jù)綁定this.defineReactive(this.data, key, val);},defineReactive: function(data, key, val) {// 創(chuàng)建與當(dāng)前屬性對(duì)應(yīng)的dep對(duì)象(dependency,依賴)var dep = new Dep();// 間接遞歸調(diào)用實(shí)現(xiàn)對(duì)data中所有層次屬性的劫持var childObj = observe(val);// 給data重新定義屬性(添加set/get)Object.defineProperty(data, key, {enumerable: true, // 可枚舉configurable: false, // 不能再defineget: function() {// 建立dep與watcher的關(guān)系if (Dep.target) {dep.depend();}// 返回屬性值return val;},set: function(newVal) {if (newVal === val) {return;}val = newVal;// 新的值是object的話,進(jìn)行監(jiān)聽(tīng)childObj = observe(newVal);// 通過(guò)depdep.notify();}});} };function observe(value, vm) {// value必須是對(duì)象, 因?yàn)楸O(jiān)視的是對(duì)象內(nèi)部的屬性if (!value || typeof value !== 'object') {return;}// 創(chuàng)建一個(gè)對(duì)應(yīng)的觀察都對(duì)象return new Observer(value); };var uid = 0;function Dep() {// 標(biāo)識(shí)屬性this.id = uid++;// 相關(guān)的所有watcher的數(shù)組this.subs = []; }Dep.prototype = {addSub: function(sub) {this.subs.push(sub);},depend: function() {Dep.target.addDep(this);},removeSub: function(sub) {var index = this.subs.indexOf(sub);if (index != -1) {this.subs.splice(index, 1);}},notify: function() {// 通知所有相關(guān)的watcher(一個(gè)訂閱者)this.subs.forEach(function(sub) {sub.update();});} };Dep.target = null;22. watcher.js
function Watcher(vm, exp, cb) {this.cb = cb; // callback //更新界面的回調(diào)函數(shù)this.vm = vm;this.exp = exp; //表達(dá)式this.depIds = {}; // {0: d0, 1: d1, 2: d2} 包含所有相關(guān)的dep的容器對(duì)象this.value = this.get(); //得到表達(dá)式的初始值,保存 }Watcher.prototype = {update: function () {this.run();},run: function () {// 得到最新的值var value = this.get();// 得到舊值var oldVal = this.value;// 如果不相同if (value !== oldVal) {this.value = value;// 調(diào)用回調(diào)函數(shù)更新對(duì)應(yīng)的界面this.cb.call(this.vm, value, oldVal);}},addDep: function (dep) {//判斷dep與watcher的關(guān)系是否已經(jīng)建立if (!this.depIds.hasOwnProperty(dep.id)) {// 建立dep到watcherdep.addSub(this);// 建立watcher到dep的關(guān)系this.depIds[dep.id] = dep;}}, //得到表達(dá)式的值,建立dep與watcher的關(guān)系get: function () {//給dep指定當(dāng)前的watcherDep.target = this;// 獲取當(dāng)前表達(dá)式的值, 內(nèi)部會(huì)導(dǎo)致屬性的get()調(diào)用,建立dep與watcher的關(guān)系var value = this.getVMVal();//去除dep中指定的當(dāng)前的watcher,實(shí)現(xiàn)只要初始化建立關(guān)系。Dep.target = null;return value;},//得到表達(dá)式對(duì)應(yīng)的值getVMVal: function () {var exp = this.exp.split('.');var val = this.vm._data;exp.forEach(function (k) {val = val[k];});return val;} }; /*const obj1 = {id: 1} const obj12 = {id: 2} const obj13 = {id: 3} const obj14 = {id: 4}const obj2 = {} const obj22 = {} const obj23 = {} // 雙向1對(duì)1 // obj1.o2 = obj2 // obj2.o1 = obj1// obj1: 1:n obj1.o2s = [obj2, obj22, obj23]// obj2: 1:n obj2.o1s = {1: obj1,2: obj12,3: obj13 } */23. MVVM 原理圖
23.1 初始化階段
23.2更新階段
24. 雙向數(shù)據(jù)綁定(v-model)
使用Dom監(jiān)聽(tīng)實(shí)現(xiàn)。
24.1 v-model指令的實(shí)現(xiàn)
1.實(shí)現(xiàn)頁(yè)面初始化顯示,并對(duì)當(dāng)前表達(dá)式添加watcher對(duì)象。
2.保存當(dāng)前的compile。
3.添加事件監(jiān)聽(tīng),addEventListener。
4.改變data的值,更新界面。
總結(jié)
以上是生活随笔為你收集整理的vue 源码分析(尚硅谷视频学习笔记)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: fataexception matlab
- 下一篇: Vue.js尚硅谷视频学习笔记(第一章: