手写MVVM构造器

简介: 手写MVVM构造器

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就是沟通视图(页面)和数据之间的桥梁。

image.png

image.png

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>

image.pngimage.png

image.png

5.目标3:实现数据驱动视图


思路:

使用观察者模式:

4-1 把属性名当成事件名

4-2 对每一个dom节点都创建一个观察者(函数)

当属性值变化时,发布指定的事件,来通知观察者们

细分

image.png

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())
    }
}

image.png

无语啊,报这个错,原因是foeEach中的e没大写!

5-2 实例化一个观察者实例

创建一个观察者实例
这个观察者实例与MVVM的构造器无关,它是独立存在的
let  eventCenter = new EventBus()

5-3 在模板编译时添加观察者

image.png

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>

image.png

6.总结:


最重要最核心的就是Object.defineproperty(obj,prop,{set,get})的使用。

可以说,当它出现时,其实就结束了image.png


相关文章
|
4月前
|
Java 开发者 容器
Java反射机制--手写springioc
这是一个非常基础的Spring IoC容器的实现方法。当然,真正的Spring IoC容器功能远不止这些,它还支持依赖注入、生命周期管理、配置方法等更高级和复杂的功能。但是通过这个简单的例子,你可以理解IoC容器的基本原理以及反射在其中的作用。在实际应用中,你通常会使用Spring框架提供的IoC容器,这样可以更加专注业务逻辑的实现,而不需要自己维护这样一个容器。
49 0
|
4月前
|
设计模式 测试技术
依赖注入与工厂设计模式的区别
【8月更文挑战第22天】
73 0
|
7月前
|
索引
10 # 手写 every 方法
10 # 手写 every 方法
54 0
|
7月前
|
索引
09 # 手写 some 方法
09 # 手写 some 方法
53 0
|
设计模式 Java Spring
用300行代码手写1个Spring框架,麻雀虽小五脏俱全
为了解析方便,我们用application.properties来代替application.xml文件,具体配置内容如下:
47 0
|
Java 数据库连接 数据库
手写数据库连接池,让抽象工厂不再抽象
在讲解抽象工厂之前,我们要了解两个概念:产品等级结构和产品族,如下图所示。
74 0
|
设计模式 Java
如何手写一个JDK动态代理?
如何手写一个JDK动态代理?
100 0
如何手写一个JDK动态代理?
|
SQL XML Java
MyBatis框架:第七章:注解使用方式和参数传递及#{}和${}
MyBatis框架:第七章:注解使用方式和参数传递及#{}和${}
345 0
|
设计模式 SQL 缓存
设计模式:单例、原型和生成器
设计模式:单例、原型和生成器
151 0