企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
学习能力 理解能力 自学能力 是否有技术热情 工作效率(读源码) 收益,不同,总结,理解的地方 ### 如何理解mvvm ### 如何实现mvvm ### 是否解读过vue的源码 ## 题目 1. 使用jquery和使用框架的区别(总结出来) 2. 说一下对mvvm的理解 3. vue中如何实现响应式(原理) 4. vue如何解析模板 5. vue的整个实现流程(看博客,看文章,不一定非要看源码) ### 使用jquery和使用框架的区别(总结出来) 1. jQuery todo-list ``` $text = $("#text"); $ul = $("#ul"); $btnSubmit = $("#btnSubmit"); $btnSubmit.click(function(){ var title = $text.val(); var $li =$('<li>'+title+"</li>"); var $ul.append($li); $txt.val(); }) ``` append(操作dom) 2. vue todo-list ``` <input v-model="title" /> <ul> <li v-for="item in list" >{{item}}</li> </ul> <div id ="app"></div> 定义容器 //初始化vue实例 var vm = new Vue({ el:"#app", data:{title:'',list:[]}, method:{ add:function(){ this.list.push(this.title); this.title = ""; } } }) ``` 3. jQuery和vue的区别 * **数据和视图的分离,解耦(开放封闭原则)--(对扩展开放,对修改封闭)** jquery数据和视图的处理根本没有分离, 不分离的坏处:开放封闭原则问题 vue的是分离的,数据在data中 * **以数据驱动视图,只关心数据变化,DOM操作被封装** jquery从最直接最底层的api进行操作dom,直接干预dom的修改 ### 对mvvm的理解 1. **mvc** view(视图,界面) Model(模型,数据源) Controller(控制器,控制view的变化和Model的变化) 一般:view发生变化到control到model,model再去改view 2. **mvvm** m(模型,数据:data,js对象) v(视图、模板:(视图和模型是分离的)view,最后对应的都是dom) vm(连接view和model vm实例 viewModel:**view和model之间的桥**) **view通过事件绑定操作model,model通过数据绑定操作view** view和model彻底分开 3. 关于ViewModel 不算创新,算是mvc的一种微创新 4. mvc和mvvm的区别 Controller负责在Controller里面把Model的数据赋值给View,比如在controller中写document.getElementById("box").innerHTML = data\[”title”\],只是还没有刻意建一个Model类出来而已。   MVVM与MVC最大的区别就是:**它实现了View和Model的自动同步**,也就是当Model的属性改变时,我们不用再自己手动操作Dom元素,来改变View的显示,而是**改变属性后该属性对应View层显示会自动改变**。 ### MVVM框架的三大要素(vue的三大要素) vue固定的模板并不是html,因此需要模板的解析 最终生成的都是DOM元素 监听数据变化怎么更新dom元素的 1. **响应式**:vue如何监听到data的每个属性的变化? 2. **模板(解析)引擎**:vue的模板如何被解析,指令如何处理? 3. **渲染**:vue的模板如何被渲染成html?一级渲染过程 ### vue中如何实现响应式 1. 什么是响应式? 2. **Object.defineProperty**(vue响应式的**核心函数**) 3. 模拟响应式 ***** 1. 什么是响应式 * 修改data属性之后,**vue立刻监听到**(如何监听到) * ***data属性被代理到vm***(this)上 ~~~ 修改data中数据 vm.name(this.name)可以直接修改(而不是vm.data.name this.data.name) ~~~ #### **Object.defineProperty(ES5 IE9+ 手机 兼容性没有问题)** 获取属性,设置属性,如何监听得到 ~~~ var obj = {}; var name ="peipei"; Object.defineProperty(obj,"name",{ get:function(){ console.log('get',name); return name; }, set:function(newVal){ console.log('set',newVal); name = newVal; } }); console.log(obj.name); obj.name="list";//set操作 obj.name//get操作 可重写get和set(获取和设置都是函数),加上自己的逻辑 ~~~ 模拟vue实现监听 ~~~ //vue实例 var vm \= new Vue({ el:'#app', data:{ price:100, name:'苹果' } }); ~~~ **模拟 1.defineProperty怎么用 2,.data怎么代理到vm中** ~~~ var vm \= {}; var data \= { price:100, name:'苹果' }; var key,value; for(key in data){ //命中闭包,新建一个函数,保证key的独立的作用域 //自执行函数 (function(key){ Object,defineProperty(vm,key,{ get:function(){ console.log('get'); return data\[key\]; }, set:function(newVal){ console.log('set'); data\[key\] \= newVal; } }) })(key); } ~~~ 1.监听数据变化 **defineProperty**,将设置和获取写成一个函数,在函数中进行监听 2. data的属性怎么代理到vm中,还是**defineProperty**(通过defineProperty给vm定义属性) ### **问题总结** 响应式: 1. 关键是理解Object.defineProperty 2. 将data的属性代理到vm上 ***** ### **vue中如何解析模板** 1. 模板是什么 ~~~ div v-model v-on:click v-for v-if @click ~~~ * 本质:字符串(像html) * 有逻辑 v-if v-for * 与html格式很像,但有很大区别 (**html是静态的,vue中的模板时动态的**) * 最终要转换为html来显示 * 模板**最终必须转换成js代码** 因为:1.**有逻辑**v-if v-for,**必须用js**(图灵完备)实现;2.**转换为html渲染页面**,必须用js实现(**js才能渲染页面**) 因此:**模板最终(必须)要转成一个js函数(render函数)(才能执行)** 2. render函数 * with的用法(自己不要用) ~~~ var obj = { name:'zhang', age:20, getAddress:function(){ alert('bbb') } }; //使用with function fun1(){ with(obj){ alert(name) alert(age) getAddress() } } fun1(); ~~~ 通过width把对象包起来,可以直接读取属性(实际开发不好用) vue使用width是为了使代码变的简洁 模板 ~~~ <div id="app"><p>{{price}}</p></div> ~~~ 生成的render函数 ~~~ //this为vm实例 _c 为vm._c _v为vm._v price为this.price //_c(创建元素) function render(){ with(this){ return _c( 'div', { attrs:{'id':'app'} }, //子元素 [ _c('p',[_v(_s(price))]) ] ) } } //翻译with function render1(){ return vm. _c( 'div', { attrs:{'id':'app'} }, //子元素 [ vm._c('p',[vm._v(vm._s(vm.price))]) ] ) } ~~~ vm._c:创建标签 createElement vm._v:创建文本节点 createTextVNode vm._s:toString(val) 总结: * **模板中所有信息都包含在了render函数中** * this即vm * price即this.price即this.price即data中的price * _c为this._c ### 其他问题 1. 从哪里可以看到render函数 **源码中搜索code.render** ~~~ var code = generate(ast, options); alert(code.render) return { ast: ast, render: code.render, staticRenderFns: code.staticRenderFns } ~~~ vue2.0开始,支持**预编译** **开发环境:写模板 编译打包: 生产环境下:js代码** 2. 复杂一点的例子,render函数是什么样子的 ~~~ with(this){//vm return \_c(//vm.\_c 'div', { attrs:{'id':'app'} }, [ _c('div', [_c( 'input',//双向数据绑定 {'directives':\[{//数据驱动view 赋值 name:'model',rawName:'v-model',value:(title),expression:'title' }\], 'domProps':{ 'value':(title), }, 'on':{ 'input':function($event){ if($event.target.composing) return; title = $event.target.title;//view修改data } } } ), _v(" "), _c('button', { 'on':{'click':add}//vm.add }, [_v('submit')\]//文本节点 ) ] ), _v(" "), _c('div', [ _c('ul',//vm._l(函数):返回数组,数组中遍历list,对每个元素创建li标签,vm.list _l((list),function(item){ return _c('li',[_v(_s(item))]) //vm._l 可查看当前方法的实现 } ~~~ 理解设计理念,不用太深究 **双向数据绑定:即能get又能set(v-model)** 3. v-if v-for v-on 都是怎么处理的 v-model:**v-model是双向数据绑定,即能get又能set(v-model)** v-on:click:绑定click事件, v-for: 针对li对数据遍历,每个元素都封装成li标签,将li标签汇成数组,写入ul标签 v-if:判断 4. render函数与vdom(集成在vue,react) **vdom**:**js模拟dom结构** vdom核心api: **h():通过标签,属性,子元素,创建vnode(虚拟节点)** **patch():将虚拟节点(vnode)渲染到空的dom元素上;用新的vnode和旧的vnode进行对比,对比结果(diff地方,有差异的地方),重新渲染到dom节点上** **patch(container,vnode)**将vnode渲染到空的容器上 **patch(vnode,newVnode)**:用新的和旧的进行对比,将不同的进行重新渲染 5. 模板生成html页面的问题,vm._c是什么,render函数返回了什么? 模板生成render函数之后,render函数和html有什么关系? render函数,返回(return vm._c) vm._c和h()函数基本一样, vm._c其实相当于snabbdom中的h函数,**render函数执行之后,返回的是vnode**, vm.render(): 返回vnode vm._update(vm.render()): **updateComponent中实现了vdom的patch** **页面首次渲染执行updateComponent;** **data中每次修改属性(响应式),执行updateComponent;** 可在defineProperty的set函数中执行updateComponent ~~~ vm._update(vnode){ const preVnode = vm._vnode; vm._vnode = vnode; if(!prevVnode){ //第一次,直接将vnode渲染到空的容器中 vm.$el = vm.__patch__(vm.$el,vnode); }else{ // 以后通过patch将有差异的地方进行渲染 vm.$el = vm.__patch__(prevVnode,vnode); } } function updateComponent(){ vm._update(vm._render()) } ~~~ ***** ### 总结 1. **模板:** * 本质是字符串,语法上有逻辑(v-for v-if),也可以嵌入js变量(name,age),像html但不是html * 模板必须转为js代码(因为有逻辑,渲染html,js变量)(前端只有js才能处理逻辑,只有js才能渲染html) 2. **render函数** * with语法 * vm._c h()函数 * 最终返回**vnode** 3. **updateComponent组件** * **patch** 首次渲染和对比渲染 ***** ### vue的整个实现流程 1. **第一步:解析模板成render函数** 开发环境一打包就是render函数,因此肯定是第一步 code.render 打印出 * width的应用 * 模板中的所有信息都被render函数包含 * 模板中用到的data中的属性,都变成了js变量 * 模板中v-model,v-for,v-on都变成了js逻辑 * render函数返回vnode节点 2. **第二步:响应式开始监听** js执行的时候才会发生 * Object.defineproperty 监听(get set) * 将data的属性代码到vm上(属性代理到this上,with(this)才能获取) 3. **第三步:首次渲染,显示页面,且绑定依赖** * 初次渲染,执行updateComponent,执行vm.render() * 执行render函数,会访问vm.list和vm.title * 会被响应式的get方法监听到 **为什么监听get?直接监听到set中不行吗?** 因为:data中很多属性,有些被用到,有些不被用到;被用到的走get,不被用到的不会走get;**未走到get的属性,set的时候我们也无需关心**;**因此会避免不必要的重复渲染**; * 执行updateComponent,会走到vdom的patch方法 * patch将vnode渲染成dom,初次渲染完成 4. **第四步:data属性变化,触发rerender** data发生变化,命中响应式,set会被监听到(会首先判断有没有走get,监听到set才会走updateComponent) * 修改属性,被响应式的set监听到 * set中执行updateComponent * updateComponent重新执行vm.render() * 生成vnode和prevVnode,通过patch进行对比 * 渲染到html中 ***** ### 自己补充 1. Vue不能检测数组或对象变动问题 由于javascript的限制,vue不能检测一下变动的数组 原因:vue对于object类型的data,直接用Object.defineproperty定义getter和setter,在setter节点对提交是否的更新 **而对于Array,数组的每个元素没有getter和setter,所以不能简单的defineProperty,而只能监听一些操作如push,pop等****每个数组的 prototype 单独 hack push 这些操作** * 利用索引设置一个项:vm.items[index] = newValue(不是响应性的) * 修改数组的长度:vm.items.length = newLength(不是响应性的) 解决办法(可触发更新): 1) **Vue.set(vm.items,index,newValue)** 2) **vm.items.splice(index,1,newValue)** 也可使用vm.$set实例方法,是全局方法Vue.set的一个别名 vm.$set(vm.items,index,newValue) 第二类:**vm.items.splice(newLength) ** 由于javascript的限制,vue不能检测对象属性的添加或删除 ~~~ var vm = new Vue({ data: { a: 1 } }) // `vm.a` 现在是响应式的 vm.b = 2 // `vm.b` 不是响应式的 ~~~ 对于已经创建的实例,**vue不能动弹添加跟级别的响应式属性**,但是可以使用Vue.set(object,key,value)方法,**向嵌套对象添加响应式属性**。 ~~~ var vm = new Vue({ data: { userProfile: { name: 'Anika' } } }) ~~~ ~~~ Vue.set(vm.userProfile, 'age', 27) vm.$set(vm.userProfile, 'age', 27) ~~~ 对已有对象赋予多个属性,使用Object.assign(),_.extend() 这种情况下,应该用两个对象的属性**创建一个新的对象**,不要这样 ~~~ Object.assign(vm.userProfile, { age: 27, favoriteColor: 'Vue Green' }) ~~~ 应该: ~~~ vm.userProfile = Object.assign({}, vm.userProfile, { age: 27, favoriteColor: 'Vue Green' }) ~~~ 还有第三种方法:深度拷贝 ~~~ vm.userProfile = JSON.parse(JSON.stringify(vm.userProfile)) ~~~ 但深度拷贝会有坑: 1. 如果obj里面有时间对象,则JSON.stringify后再JSON.parse的结果,时间将只是字符串的形式。而不是时间对象; 2. 如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象; 3. 如果obj里有函数,undefined,则序列化的结果会把函数或 undefined丢失; 4. 如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null 5. JSON.stringify()只能序列化对象的可枚举的自有属性,例如 如果obj中的对象是有构造函数生成的,  则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor; 6. 如果对象中存在循环引用的情况也无法正确实现深拷贝; 广为流传的深拷贝方式有 1. JSON方式 2. jQuery的 extend方式 3. es6 的Object.assign方式 4. vue的 vue.util.extend 方式 5. 递归拷贝 ### 浅拷贝 其实这段代码就是浅拷贝,有时候我们只是想备份数组,但是只是简单让它赋给一个变量,改变其中一个,另外一个就紧跟着改变,但很多时候这不是我们想要的 ~~~ var obj = { name:'wsscat', age:0 } var obj2 = obj; obj2['c'] = 5; console.log(obj);//Object {name: "wsscat", age: 0, c: 5} console.log(obj2);////Object {name: "wsscat", age: 0, c: 5} ~~~ ### 深拷贝 *数组* 对于数组我们可以使用`slice()`和`concat()`方法来解决上面的问题 *对象* 对象我们可以定义一个新的对象并遍历新的属性上去实现深拷贝 当然我们可以封装好一个方法来处理对象的深拷贝,代码如下 ~~~ var obj = { name: 'wsscat', age: 0 } var deepCopy = function(source) { var result = {}; for(var key in source) { if(typeof source[key] === 'object') { result[key] = deepCopy(source[key]) } else { result[key] = source[key] } } return result; } var obj3 = deepCopy(obj) obj.name = 'autumns'; console.log(obj);//Object {name: "autumns", age: 0} console.log(obj3);//Object {name: "wsscat", age: 0} ~~~