1.这个是vue官方提供的,下面将手写一个 ,实现双向绑定
<div id="A"> // v-html 实现数据改变视图 <div v-html="mess"></div> //v-model 实现视图改变数据 <input type="text" v-model="mess"> {{mess}}+{{name}} </div> <div id="B">{{mess}}+{{name}}</div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"> </script> //引入vue开发模式,里面不可写代码,不然页面不显示 <script> var data = { name:'绑定A', mess:'hello vue' } var vm = new Vue({ el:'#A', //挂载,作用是选择挂载哪一个div,绑定不同的id显示的div也不同 data //实例属性,可以这么写,也可以写在这里面 :data:{} }) console.log(vm); //打印的结果显示,里面有vue的许多属性和方法
1-1总结:
Vue是一个构造函数,new Vue实例化构造函数,产生实例对象,VM。data是实例对象上面的一个属性,实例化的时候将data作为实例成员(就是说这个属性只能通过实例对象调用,Vue.拿到的是underfined)添加到实例对象VM上。而实例对象VM可以访问Vue构造函数上面的所有属性和方法(静态成员)。
一句话,实例对象VM就是沟通视图(页面)和数据之间的桥梁。
2.疑问:
2-1. 如何实现数据变了,视图也跟着变?
核心是:如何知道对象的属性值被修改了?
2-2.如何实现一个数据变了,多处视图跟着变?
2-3.如何实现视图到数据的改变?
这个简单点,可监听输入事件,然后修改数据。本篇文章不写了,就利用个addEventlistener监听个input事件好了,然后在回调里面做事。
0.重要前提:已掌握Object.defineproperty(obj,prop,{set,get})
3.目标1:写出构造器的基本架子,完成参数传递与获取
写出构造器的基本架子,完成参数传递与获取
实现数据拦截
-在代码外部,让MVVM实例可以操作data选项中的属性
-在代码内部,能通过this.属性名的方式去操作data选项中的属性
目标: 写出构造器的基本架子,完成参数传递与获取 function MVVM(option){ const {el,data} = option for(let key in data){ //此循环的目的和作用是使实例对象可以访问data中的属性值 //把data添加到实例对象VM Object.defineproperty(this,key,{ //this指向实例对象VM set : function(newval){comnsole.log('有人修改了key,值为newval')}, get :function(){return data[key]} }) } console.log(el,data) } var vm = new MVVM({ el:'#app', data:{ a:1222 } })
4.目标2 在构造器运行完成之后,页面上应该显示初始数据
思路:
遍历根节点,依次找出其下所有的dom子节点,分析是否有v-html,v-model属性,进行特殊处理
4-1 怎么遍历根节点?
const rootDom = document.querySelector(el) 拿根节点。 rootDom.children可以拿到他的子节点 拿到的是伪数组Array.from转成真数组,然后遍历 Array.from(rootDom.children).foreach(item=>{})
4-2 怎么判断DOM上,是否有某个属性?
DOM元素.hasAttribute('属性名')
getAttribute() 方法返回指定属性名的属性值。
console.log(item.hasAttribute('v-html'))
4-3 怎么把属性值取出来,在数据项找到这个值,并显示出来?
关键代码如下:
let key = item.hasAttribute('v-html') console.log(key) item.innerHTML=this[key]
完整版代码如下:页面上显示初始数据
<body> <div id="A"> <div v-html="mess"></div> <div v-html="mess"></div> <div v-html="mess"></div> <input type="text" v-model="mess"> {{mess}}+{{name}} </div> <div id="B">{{mess}}+{{name}}</div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"> </script> <script> //目标:构造器运行完成后,页面上显示初始数据 function MVVM(option){ const {el,data}= option //对象解构 // console.log(el,data); for(let key in data){ //把data中的属性添加到MVVM的实力对象上 Object.defineProperty(this,key,{ set:function(newVal){ if(newVal !== data[key]){ data[key]=newVal } }, get :function(){ return data[key] } }) } const rootDom = document.querySelector(el) // console.log(rootDom.children); Array.from(rootDom.children).forEach(node=>{ // console.log(node.hasAttribute('v-html')); if(node.hasAttribute('v-html')){ //1.把v-html这个属性值取出来 //2.在数据项找到这个值,并显示出来 let key = node.getAttribute('v-html') // console.log(key); node.innerHTML=this[key] } }) } var vm = new MVVM({ el:'#A', data:{ name:'绑定A', mess:'hello vue' } }) </script> </body> </html>
5.目标3:实现数据驱动视图
思路:
使用观察者模式:
4-1 把属性名当成事件名
4-2 对每一个dom节点都创建一个观察者(函数)
当属性值变化时,发布指定的事件,来通知观察者们
细分:
5-1 引入观察者代码
这是封装的一个函数:
function EventBus(){ //构造函数 类比Vue this.guanjia = { // a:[] } } //添加观察者fn(收小弟,有分工,只响应某个事件) EventBus.prototype.$on = function(eventname,fn){ //如果在事件中心已经注册过 if(this.guanjia[eventname]){ //先判断有没没有这个事件名,没有去注册,有就添加观察者 this.guanjia[eventname].push(fn) }else(this.guanjia[eventname]=[fn]) } //发布事件,通知相关的观察者去执行 EventBus.prototype.$emit = function(eventname){ //在原型上添加方法 if(this.guanjia[eventname]){ this.guanjia[eventname].forEach(item=>item()) } }
无语啊,报这个错,原因是foeEach中的e没大写!
5-2 实例化一个观察者实例
创建一个观察者实例 这个观察者实例与MVVM的构造器无关,它是独立存在的 let eventCenter = new EventBus()
5-3 在模板编译时添加观察者
5-4 发布事件
ec.$emit(key)
完整代码如下:
<body> <div id="A"> <div v-html="mess"></div> <div v-html="mess"></div> <div v-html="mess"></div> <input type="text" v-model="mess"> {{mess}}+{{name}} </div> <div id="B">{{mess}}+{{name}}</div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"> </script> <script> function EventBus(){ //构造函数 类比Vue this.guanjia = { // a:[] } } //添加观察者fn(收小弟,有分工,只响应某个事件) EventBus.prototype.$on = function(eventname,fn){ //如果在事件中心已经注册过 if(this.guanjia[eventname]){ //先判断有没没有这个事件名,没有去注册,有就添加观察者 this.guanjia[eventname].push(fn) }else(this.guanjia[eventname]=[fn]) } //发布事件,通知相关的观察者去执行 EventBus.prototype.$emit = function(eventname){ //在原型上添加方法 if(this.guanjia[eventname]){ this.guanjia[eventname].forEach(fn=>fn()) } } </script> <script> //实例化一个观察者模式 let ec = new EventBus() //目标:构造器运行完成后,页面上显示初始数据 function MVVM(option){ const {el,data}= option //对象解构 // console.log(el,data); for(let key in data){ //把data中的属性添加到MVVM的实力对象上 Object.defineProperty(this,key,{ set:function(newVal){ if(newVal !== data[key]){ console.log(`有人修改了${key},值为${newVal}`); data[key]=newVal //1保存新值 //2 发布事件 ec.$emit(key) } }, get :function(){ console.log('有人获取mess的属性值'); return data[key] } }) } const rootDom = document.querySelector(el) // console.log(rootDom.children); Array.from(rootDom.children).forEach(node=>{ // console.log(node.hasAttribute('v-html')); if(node.hasAttribute('v-html')){ //1.把v-html这个属性值取出来 //2.在数据项找到这个值,并显示出来 let key = node.getAttribute('v-html') // console.log(key); node.innerHTML=this[key] //添加一个观察者,数据变化时,更新视图 ec.$on(key,()=>{ node.innerHTML=this[key] }) } }) } var vm = new MVVM({ el:'#A', data:{ name:'绑定A', mess:110 } }) </script> </body> </html>
6.总结:
最重要最核心的就是Object.defineproperty(obj,prop,{set,get})的使用。
可以说,当它出现时,其实就结束了