开题答辩终于结束了,又要开始我的前端面试学习啦!!!
1.v-model双向绑定原理
class Vue{constructor(options){this.$options = optionsthis.$watchEvent = {}if(typeof options.beforeCreate == 'function'){options.beforeCreate.bind(this)()}// 这是datathis.$data = options.datathis.proxyData()this.observe()if(typeof options.created == 'function'){options.beforeCreate.bind(this)()}if(typeof options.beforeMount == 'function'){options.beforeCreate.bind(this)()}// 这是节点this.$el = document.querySelector(options.el)// 模板渲染this.compile(this.$el)if(typeof options.mounted == 'function'){options.beforeCreate.bind(this)()}}// 1.给vue大对象赋属性,来自于data中// 2.data中的属性值和vue大对象的属性双向(劫持)proxyData(){for(let key in this.$data){Object.defineProperty(this,key,{get(){return this.$data[key]},set(val){this.$data[key] = val}})}}// 触发data中的数据发生变化来执行watch中的updateobserve(){for(let key in this.$data){let value = this.$data[key]let that = thisObject.defineProperty(this.$data,key,{get(){return value},set(val){value = valif(that.$watchEvent[key]){that.$watchEvent[key].forEach((item,index) => {item.update()})}}})}}compile(node){node.childNodes.forEach((item.index) => {// 元素节点if(item.nodeType == 1){// 判断元素节点是否绑定了@clickif(item.hasAttribute('@click')){// @click后绑定的属性值let vmKey = item.getAttribute('@click').trim()item.addEventListener('click',(event) => {this.eventFn = this.$options.methods[vmKey].bind(this)this.eventFn(event)})}// 判断元素节点是否添加了v-modelif(item.hasAttribute('v-model')){let vmKey = item.getAttribute('v-model').trim();if(this.hasOwnProperty(vmKey)){item.value = this[vmKey];}item.addEventListener('input',(event) => {this[vmKey] = item.value;})}if(item.childNodes.length>0){this.compile(item)}}// 这里是文本节点,如果有{{}}就替换成数据if(item.nodeType == 3){// 正则匹配let reg = /\{\{(.*?)\}\}/glet text = item.textContent// 给节点赋值item.textContent = text.replacce(reg,(match,vmKey) => {vmKey = vmKey.trim()if(this.hasOwnProperty(vmKey)){let watch = new Watch(this,vmKey,item,'textContent')if(this.$watchEvent[vmKey]){this.$watchEvent[vmKey].push(watch)}else{this.$watchEvent[vmKey] = []this.$watchEvent[vmKey].push(watch)}}return this.$data[vmKey]})}})} }class Watch{constructor(vm,key,node,attr){// 对象this.vm = vm// 属性名称this.key = key// 节点this.node = node// 改变文本节点内容的字符串this.attr = attr}//执行改变(update)操作update(){this.node[this.attr] = this.vm[this.key]}}
通过Object.defineProperty劫持数据发生的改变,如果数据发生改变了(在set中进行赋值的),触发update方法进行更新节点内容({{ str }}),从而实现了数据的双向绑定原理。
2.diff算法
功能:提升性能
虚拟dom ====> 其实就是数据(把dom数据化)
<script type="text/javascript">let box = document.getElementById("box");// 第一种:操作domconsole.time('a');for(let i = 0; i <= 10000; i++){box.innerHTML = i;}console.timeEnd('a');// 第二种:数据化console.time('b');let num = 0;for(let i = 0; i <= 10000; i++){num = i;}bpx.innerHTMML = num;console.timeEnd('b'); </script>
对于这两种方法,直接操作dom比较费时,将其数据化可以节省很多时间。
操作dom:73ms,但是数据化:0.28ms
主流:snabbdom、virtual-dom
2.1搭建环境:
npm init -y cnpm install webpack@5 webpack-cli@3 webpack-dev-server@3 -S cnpm install snabbdom -S 新建webpack.config.js 配置webpack.config.js
2.2 虚拟节点和真实节点
虚拟节点:
{children: undefineddata:{}elm:h1key:undefinedsel:"h1"text:"你好h1" }
这是对于h1的虚拟节点,其中虚拟节点在表示时用h('h1',{},"你好h1")
下面就是对于ul的虚拟节点:
{children:[0:{children:undefineddata:{}elm:likey:undefinedsel:"li"text:"a"}1:{...}2:{...}]data:{}elm:ulkey:undefinedsel:"ul"text:undefined}
真实节点:
<h1>你好h1</h1>
2.3 新老节点替换的规则
1、如果新老节点不是同一个节点名称,那么就暴力删除旧的节点,创建插入新的节点
2、只能同级比较,不能跨层比较。如果跨层那么就暴力删除旧的节点,创建插入新的节点。
3、如果是相同节点,又分很多情况
3.1 新节点没有children
如果新的节点没有children,那就证明新的节点是文本,那么直接把旧的替换成新的文本
3.2 新节点有children
新的有children,旧的也有children ===》就是diff算法的核心了
新的有children,旧的没有 ===》创建元素添加(把旧的内容删除清空,增加新的)
***注意:如果要提升性能,一定要加入key,key是唯一标识,在更改前后,确认是不是同一个节点。
const container = document.getElementById("container"); const btn = document.getElementById("btn");const vnode1 = h('h1',{},'你好'); patch(container,vnode1);const vnode2 = h('div',{},'hi');btn.onclick = function(){patch(vnode1,vnode2); }
添加key,这样就可以提升性能:
const container = document.getElementById("container"); const btn = document.getElementById("btn");const vnode1 = h('ul',{},[h('li',{key:'a'},'a'),h('li',{key:'b'},'b'),h('li',{key:'c'},'c') ]); patch(container,vnode1);const vnode2 = h('ul',{},[h('li',{key:'c'},'c'),h('li',{key:'b'},'b'),h('li',{key:'a'},'a') ]);btn.onclick = function(){patch(vnode1,vnode2); }
3.手写diff算法-生成虚拟dom
index.js
import h from './dom/h' let vnode1 = h('div',{},'你好吖'); console.log(vnode1)-----运行后,应该得到----- {children:undefineddata:{}elm:undefinedkey:undefinedsel:"div"text:"你好吖" }------------------------------- let vnode2 = h('ul',{},[h('li',{},'a'),h('li',{},'b'),h('li',{},'c') ]) console.log(vnode2) -----运行后,应该得到----- {children:[0:{children:undefineddata:{}elm:likey:undefinedsel:"li"text:"a"}1:{...}2:{...}]data:{}elm:ulkey:undefinedsel:"ul"text:undefined}-------------------------------
创建h.js
import vnode from './vnode' export default function(sel, data, params){// h函数的 第三个参数是字符串类型【意味着:他没有子元素】if( typeof params == 'string'){return vnode(undefined, data,undefined, sel, params);}else if(Array.isArray(params)){// h函数的 第三个参数是数组类型【意味着:他有子元素】let children = [];for(let item of params){children.push(item);}return vnode(children, data,undefined, sel, undefined)} }
创建vnode.js
export default function vnode(children, data, elm, sel, text){return {children, data, elm, sel, text} }
4.手写diff算法-patch不是同一个节点
旧的节点为真实的节点,要将其转换为真实的虚拟节点
//index.html<div id="container">这里是container </div>//index.jsimport h from './dom/h' import patch from './dom/patch' // 获取真实的dom节点 let container = document.getElementById('container');// 虚拟节点 let vnode1 = h('h1',{},''你好吖);patch(container,vnode1);
//patch.js import vnode from './vnode' export default function(oldVnode, newVnode){// 如果oldVnode 没有sel,就证明是非虚拟节点(就让他变成虚拟节点)if(oldVnode.sel == undefined){oldVnode = vnode([], //children{}, //dataoldVnode, //elmoldVnode.tagName.toLowerCase(), //selundefined // text)}// 判断 旧的虚拟节点 和 新的虚拟节点 是不是同一个节点if(oldVnode.sel === newVnode.sel){// 判断的条件就复杂了(很多了)}else{ // 不是同一个节点,那么就暴力删除旧的节点,创建插入新的节点// 把新的虚拟节点 创建为 dom节点let newVnodeElm = createElement(newVnode);// 获取旧的虚拟节点 .elm就是真正节点let oldVnodeElm = oldVnode.elm;// 创建新的节点if(newVnodeElm){oldVnodeElm.parentNode.insertBefore(newVnodeElm,oldVnodeElm);}// 删除旧节点oldVnodeElm.parentNode.removeChild(oldVnode);}}
// createElement.js // vnode 为新节点,就是要创建的节点 export default function createElement(vnode){// 创建dom节点let domNode = document.createElement(vnode.sel);// 判断有没有子节点 children 是不是为undefinedif(vnode.children == undefined){domNode.innerText = vnode.text;} else if(Array.isArray(vnode.children)){// 说明内部有子节点,需要递归创建节点for(let child of vnode.children){let childDom = createElement(child);domNode.appendChild(childDom);}}// 补充elm属性vnode.elm = domNode;return domNode; }
5.手写diff算法-相同节点有没有children
//patch.js import vnode from './vnode' export default function(oldVnode, newVnode){// 如果oldVnode 没有sel,就证明是非虚拟节点(就让他变成虚拟节点)if(oldVnode.sel == undefined){oldVnode = vnode([], //children{}, //dataoldVnode, //elmoldVnode.tagName.toLowerCase(), //selundefined // text)}// 判断 旧的虚拟节点 和 新的虚拟节点 是不是同一个节点if(oldVnode.sel === newVnode.sel){// 判断的条件就复杂了(很多了)patchVnode(oldVnode,newVnode);}else{ // 不是同一个节点,那么就暴力删除旧的节点,创建插入新的节点// 把新的虚拟节点 创建为 dom节点let newVnodeElm = createElement(newVnode);// 获取旧的虚拟节点 .elm就是真正节点let oldVnodeElm = oldVnode.elm;// 创建新的节点if(newVnodeElm){oldVnodeElm.parentNode.insertBefore(newVnodeElm,oldVnodeElm);}// 删除旧节点oldVnodeElm.parentNode.removeChild(oldVnode);}}
//patchVnode.js import createElement from './createElement' export default function patchVnode(oldVnode,newVnode){// 判断新节点有没有childrenif(newVnode.children === undefined){// 没有子节点// 新的节点的文本 和 旧节点的文本内容是不是一样的if(newVnode.text !== oldVnode.text){oldVnode.elm.innerText = newVnode.text}}else{// 新的有子节点// 新的虚拟节点有, 旧的虚拟节点有if(oldVnode.children !== undefined && oldVnode.children.length > 0){// 最复杂的情况了,diff核心了 console.log('新旧节点都有children');}else{ // 新的虚拟节点有,旧的虚拟节点“没有”// 把旧节点的内容 清空oldVnode.elm.innerHTML = '';// 遍历新的 子节点,创建dom元素,添加到页面for( let child of newVnode.children){let childDom = createElement(child);oldVnode.elm.appendChild(childDom);}}} }
6.diff算法核心-理论部分
每次都从1开始,不满足就依次往下执行
1.旧前 和新前
匹配:旧前的指针++、新前的指针++
2.旧后 和新后
匹配:旧后的指针--、新后的指针--
3.旧前 和 新后
匹配:旧前的指针++、新后的指针--
4.旧后 和 新前
匹配:旧后的指针--、新前的指针++
5.以上都不满足条件 ===》 查找
新的指针++,新的添加到页面上并且新在旧的节点中有,要给旧的复制成undefined
6.创建或删除
旧的指针指向空,新还有,新就要创建
旧的指针不指向空,新的指针指向空,旧就要删除
注意:若对于旧的指针加或者减指向的是undefined,直接继续加或者减
7.手写diff算法-判断前四种情况
// index.js // 获取到了真实的dom节点 const container = document.getElementById("container"); // 获取到了按钮 const btn = document.getElementById("btn");// 虚拟节点 const vnode1 = h('ul',{},[h('li',{key:'a'},'a'),h('li',{key:'b'},'b'),h('li',{key:'c'},'c') ]); patch(container,vnode1);const vnode2 = h('ul',{},[h('li',{key:'c'},'c'),h('li',{key:'b'},'b'),h('li',{key:'a'},'a') ]);btn.onclick = function(){patch(vnode1,vnode2); }
// vnode.js export default function vnode(children, data, elm, sel, text){let key = data.key;return {children, data, elm, key,sel, text} }
//patchVnode.js import createElement from './createElement' import updateChildren from './updateChildren' export default function patchVnode(oldVnode,newVnode){// 判断新节点有没有childrenif(newVnode.children === undefined){// 没有子节点// 新的节点的文本 和 旧节点的文本内容是不是一样的if(newVnode.text !== oldVnode.text){oldVnode.elm.innerText = newVnode.text}}else{// 新的有子节点// 新的虚拟节点有, 旧的虚拟节点有if(oldVnode.children !== undefined && oldVnode.children.length > 0){// 最复杂的情况了,diff核心了 console.log('新旧节点都有children');updateChildren()}else{ // 新的虚拟节点有,旧的虚拟节点“没有”// 把旧节点的内容 清空oldVnode.elm.innerHTML = '';// 遍历新的 子节点,创建dom元素,添加到页面for( let child of newVnode.children){let childDom = createElement(child);oldVnode.elm.appendChild(childDom);}}} }
// updateChildren.js import patchVnode from './patchVnode' // 判断两个虚拟节点是否为同一个节点 function sameVnode(vnode1,vnode2){return vnode1.key == vnode2.key; } // 参数一:真实的dom节点 // 参数二:旧的虚拟节点 // 参数三:新的虚拟节点 export default (parentElm, oldCh, newCh) => {let oldStartIdx = 0; //旧前的指针let oldEndIdx = oldCh.length-1; //旧后的指针let newStartIdx = 0; //新前的指针let newEndIdx = newCh.length-1; //新后的指针let oldStartVnode = oldCh[oldStartIdx]; //旧前虚拟节点let oldEndVnode = oldCh[oldEndIdx]; //旧后虚拟节点let newStartVnode = newCh[newStartIdx]; //新前虚拟节点let newEndVnode = newCh[newEndIdx]; //新后虚拟节点while( oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx){if( sameVnode(oldStartVnode,newStartVnode)){// 第一种情况:旧前 和 新前console.log("1")patchVnode(oldStartVnode,newStartVnode);if(newStartVnode) newStartVnode.elm = oldStartVnode?.elm;oldStartVnode = oldCh[++oldStartIdx];newStartVnode = newCh[++newStartIdx];}else if(sameVnode(oldEndVnode,newEndVnode)){// 第二种情况:旧后 和 新后console.log("2")patchVnode(oldEndVnode,newEndVnode);if(newEndVnode) newEndVnode.elm = oldEndVnode?.elm;oldEndVnode= oldCh[--oldEndIdx];newEndVnode= newCh[--newEndIdx];}else if(sameVnode(oldStartVnode,newEndVnode)){// 第三种情况:旧前 和 新后console.log("3")patchVnode(oldStartVnode,newEndVnode);if(newEndVnode) newEndVnode.elm = oldStartVnode?.elm;// 把旧前指定的节点移动到旧后指向的节点的后面parentElm.insertBefore(oldStartVnode.elm,oldEndVnode.elm.nextSibling);oldStartVnode= oldCh[++oldStartIdx];newEndVnode= newCh[--newEndIdx];}else if(sameVnode(oldEndVnode,newStartVnode)){// 第四中情况:旧后 和 新前console.log("4")patchVnode(oldEndVnode,newStartVnode);if(newStartVnode) newStartVnode.elm = newEndVnode?.elm;// 把旧后指定的节点移动到旧前指向的节点的前面parentElm.insertBefore(oldEndVnode.elm,oldStartVnode.elm);oldEndVnode= oldCh[--oldEndIdx];newStartVnode= newCh[++newStartIdx];}else{// 第五种情况:以上都不满足条件 ===》 查找}} }
8.手写diff算法-判断第五种情况
// updateChildren.js import patchVnode from './patchVnode' import createElement from './createElement' // 判断两个虚拟节点是否为同一个节点 function sameVnode(vnode1,vnode2){return vnode1.key == vnode2.key; } // 参数一:真实的dom节点 // 参数二:旧的虚拟节点 // 参数三:新的虚拟节点 export default (parentElm, oldCh, newCh) => {let oldStartIdx = 0; //旧前的指针let oldEndIdx = oldCh.length-1; //旧后的指针let newStartIdx = 0; //新前的指针let newEndIdx = newCh.length-1; //新后的指针let oldStartVnode = oldCh[oldStartIdx]; //旧前虚拟节点let oldEndVnode = oldCh[oldEndIdx]; //旧后虚拟节点let newStartVnode = newCh[newStartIdx]; //新前虚拟节点let newEndVnode = newCh[newEndIdx]; //新后虚拟节点while( oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx){if( oldStartVnode == undefined){oldStartVnode = oldCh[++oldStartIdx];}if( oldEndVnode == undefined){oldEndVnode = oldCh[--oldEndIdx];}else if( sameVnode(oldStartVnode,newStartVnode)){// 第一种情况:旧前 和 新前console.log("1")patchVnode(oldStartVnode,newStartVnode);if(newStartVnode) newStartVnode.elm = oldStartVnode?.elm;oldStartVnode = oldCh[++oldStartIdx];newStartVnode = newCh[++newStartIdx];}else if(sameVnode(oldEndVnode,newEndVnode)){// 第二种情况:旧后 和 新后console.log("2")patchVnode(oldEndVnode,newEndVnode);if(newEndVnode) newEndVnode.elm = oldEndVnode?.elm;oldEndVnode= oldCh[--oldEndIdx];newEndVnode= newCh[--newEndIdx];}else if(sameVnode(oldStartVnode,newEndVnode)){// 第三种情况:旧前 和 新后console.log("3")patchVnode(oldStartVnode,newEndVnode);if(newEndVnode) newEndVnode.elm = oldStartVnode?.elm;// 把旧前指定的节点移动到旧后指向的节点的后面parentElm.insertBefore(oldStartVnode.elm,oldEndVnode.elm.nextSibling);oldStartVnode= oldCh[++oldStartIdx];newEndVnode= newCh[--newEndIdx];}else if(sameVnode(oldEndVnode,newStartVnode)){// 第四中情况:旧后 和 新前console.log("4")patchVnode(oldEndVnode,newStartVnode);if(newStartVnode) newStartVnode.elm = newEndVnode?.elm;// 把旧后指定的节点移动到旧前指向的节点的前面parentElm.insertBefore(oldEndVnode.elm,oldStartVnode.elm);oldEndVnode= oldCh[--oldEndIdx];newStartVnode= newCh[++newStartIdx];}else{// 第五种情况:以上都不满足条件 ===》 查找console.log('5');// 创建一个对象,存虚拟节点的(判断新旧有没有相同节点)const keyMap = {};for(let i = oldStartIdx;i<=oldEndIdx;i++){const key = oldCh[i]?.key;if( key ) keyMap[key] = i;}// 在旧节点中寻找新前指向的节点let idxInOld = keyMap[newStartVnode.key];// 如果有,说明数据在新旧虚拟节点中都存在if(idxInOld){const elmMove = oldCh[idxInOld];patchVnode(elMove,newStartVnode);// 处理过的节点,在旧虚拟节点的数组中,设置为undefinedoldCh[idxInOld] = undefined;parentElm.insertBefore(elMove.elm,oldStartVnode.elm);}else{// 如果没有找到 ==》 说明是一个新的节点【创建】parentElm.insertBefore( createElement(newStartVnode),oldStartVnode.elm);}// 新数据(指针) +1newStartVnode = newCh[++newStartIdx];}}// 结束while 只有两种情况 (新增和删除)// 1.oldStartIdx > oldEndIdx// 2.newStartIdx > newEndIdxif(oldStartIdx > oldEndIdx){// 进入新增操作const before = newCh[newEndIdx+1] ? newCh[newEndIdx+1].elm : null;for( let i=newStartIdx; i<=newEndIdx;i++){parentElm.insertBefore(createElement(newCh[i],before));} } else {// 进入删除操作for(let i=oldStartIdx;i<=oldEndIdx;i++){parentElm.removeChild(oldCh[i].elm);}} }
9.谈一下MVVM框架
web1.0时代
文件全在一起,也就是前端和后端的代码全在一起
问题:
1.前端和后端都是一个人开发。(技术没有侧重点或者责任不够细分)
2.项目不好维护
3.html、css、js页面的静态内容没有,后端是没有办法工作的(没办法套数据)
web2.0时代
ajax出现了,就可以:前端和后端数据分离了
解决问题:后端不用等前端页面弄完没,后端做后端的事情(写接口),前端布局、特效、发送请求
问题:
1.html、css、js都在一个页面中,单个页面可能内容也比较多的(也会出现不好维护的情况)
出现前端框架MVC、MVVM
解决问题:可以把一个“特别大”页面,进行拆分(组件化),单个组件进行维护
什么是MVVM
Model-View-ViewModel的简写
view:视图【dom ==》 在页面中展示的内容】
model:模型【数据层:vue中的data数据】
viewModel:视图模型层【就是vue源码】
学了快一周的源码了,煎熬