Vue响应式框架设计

226 阅读3分钟

Vue响应式框架设计

一、课程分类

Vue核心技术栈。

React全家桶。

Vue3+TS开发

扩展课程:前端架构,3D实战、服务器搭建、websocket通信

二、Vuejs扩展内容

  1. 响应式原理
  2. 项目配置,跨域
  3. 项目打包上线

三、响应式原理设计

(1)创建一个初始项目

npm init -y

(2)下载vue包

npm i vue@2.6.10

(3)html文件中引入vue

<script src="./node_modules/vue/dist/vue.js"></script>

(4)创建vue实例

<div id="app">
   <p>
      {{username}}
   </p>
</div>
<script>
const app = new Vue({
   el:"#app",
   data(){
       return{
           username:"xiaowang"
      }
  }
})
</script>

(5)研究app对象

console.log(app)

需要重点关注对象属性

$attrs:获取到当前传递得参数

$listeners:获取所有当前组件接受得自定义事件

$children:获取当前组件所有子组件

$parent:获取当前组件得父组件

$options:这个组件,这个实例得vue参数信息

$refs:获取所有ref引用节点

四、数据劫持的设计

  1. 数据定义后页面更新
  2. 页面数据切换操作data的数据

(1) 数据劫持

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>Document</title>
</head>
<body>
   <script>
       const user = {
           username:"xiaowang",
           password:123
      }
       //使用了username属性
       let username = user.username
       // user.username = "xiaofeifei"
       Object.defineProperty(user,"username",{
           get(){
               console.log("使用了username这个属性");
               return username
          },
           set(val){
               console.log("修改了username属性",val);
          }
      })
​
       
​
       console.log(user.username);
       user.username = "xiaofeifei"
       console.log(user.password);
   </script>
</body>
</html>

参数:

第一个参数劫持的对象

第二个参数持节的对象属性

第三个参数执行对象,里面get和set方法

(2)修改代码,指定劫持某个属性

<!DOCTYPE html>
<html lang="en"><head>
   <meta charset="UTF-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>Document</title>
</head><body>
   <script>
       const user = {
           username"xiaowang",
           password123
      }
​
​
       function defineProperty(data, key, value) {
           Object.defineProperty(data, key, {
               get() {
                   console.log(`使用了${key}这个属性`);
                   return value
              },
               set(val) {
                   console.log(`修改了${key}属性`, val);
                   value = val
              }
          })
      }
       defineProperty(user,"username",user["username"])
       console.log(user.username);
       user.username = "xiaofeifei"
       console.log(user.username);
   </script>
</body></html>

(3)优化代码,实现对象属性自动劫持

<!DOCTYPE html>
<html lang="en"><head>
   <meta charset="UTF-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>Document</title>
</head><body>
   <script>
       const user = {
           username"xiaowang",
           password123
      }
​
​
       function defineProperty(data, key, value) {
           Object.defineProperty(data, key, {
               get() {
                   console.log(`使用了${key}这个属性`);
                   return value
              },
               set(val) {
                   console.log(`修改了${key}属性`, val);
                   value = val
              }
          })
      }
​
       //获取对象有多少个属性
       // console.log(Object.keys(user));
       Object.keys(user).forEach(key=>{
           defineProperty(user,key,user[key])
      })
​
​
       // defineProperty(user,"username",user["username"])
       console.log(user.username);
       console.log(user.password);
   </script>
</body></html>

五、构造自己的框架

(1)创建一个vue.js文件

后续所有的代码我们都放在这个vuejs文件中,以后页面直接使用vuejs文件

<script src="./myvue/vue.js"></script>

(2)创建Observer类进行数据劫持

/**
* auth:xuchaobo
* time:20230704
* msg:进行响应式的数据变化,数据劫持工作
* Observer专门用于数据劫持
* 设计原则:单一职责,一个类或者一个函数,只做一件事
*/
class Observer {
   //创建这个类的时候,通过构造器获取参数
   constructor(data) {
       this.data = data
       //创建这个实例,调用walk
       this.walk()
  }
   //实现数据劫持的函数
   defineProperty(data, key, value) {
       Object.defineProperty(data, key, {
           get() {
               console.log(`使用了${key}这个属性`);
               return value
          },
           set(val) {
               console.log(`修改了${key}属性`, val);
               value = val
          }
      })
  }
   walk() {
       Object.keys(this.data).forEach(key => {
           this.defineProperty(this.data, key, this.data[key])
      })
  }
}

(3)创建Vue类,进行数据劫持和渲染

class Vue{
   constructor(options){
       this.$options = options;
       this.$data = options.data()
       this.$el = options.el
       //$data中所有数据都要数据劫持
       new Observer(this.$data)
       //proxy目的是将$data的数据劫持并放在this对象身上
       this.proxy()
       //模板编译
       new Complier(this.$el,this.$data)
  }
   //$data数据存放所有的数据
   //会将data中数据,挂载this身上
   proxy(){
       Object.keys(this.$data).forEach(key=>{
           // 劫持this(vue),key,默认给this添加key
           Object.defineProperty(this,key,{
               get(){
                   return this.$data[key]
              },
               set(val){
                   this.$data[key] = val
              }
          })
      })
  }
}

(4)创建模板编译类

/**
* auth:
* time:
* msg:
*/
class Complier{
   //$el:"#app"
   // {username:"xiaowang",password:"123"}
   constructor(el,data){
       this.$el = document.querySelector(el);
       this.$data = data
       this.compiler()
  }
   compiler(){
       //伪数组 [<p>,<p>]
      [...this.$el.children].forEach(item=>{
           //进行正则表达式匹配 {{Xiaowang8-123}}
           if(/{{([a-zA-Z0-9]+)}}/.test(item.innerHTML)){
               const key = RegExp.$1.trim()
               item.innerHTML = this.$data[key]
          }
      })
  }
}

(5)页面中创建Vue实例,并进行数据页面更新

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>Document</title>
   <script src="./myvue/vue.js"></script>
</head>
<body>
   <div id="app">
       <p>{{username}}</p>
       <p>{{password}}</p>
       <p>123</p>
   </div>
   <script>
       /**
        * Vue实例中,对象还是函数都可以支持
        * 但是在组件中只能是函数
      */
       const app = new Vue({
           el:"#app",
           data(){
               return {
                   username:"xiaozhang",
                   password:"6666"
              }
          }
      })
       // console.log(app);
       // app.username = "xiaofeifei"
   </script>
</body>
</html>

六、观察者模式

观察者模式是一种设计模式

设计模型:就是一种代码规则,当你按照这种规范来写代码,采用对应设计思想。

换句话说:前人总结出来一系列开发技巧、规则。应用自己项目中。利用这种思想来解决我们问题。使用这种设计模式

有两个非常重要的元素:

  1. 发布者:发布消息的人(商家)
  2. 订阅者:接受消息的人(用户)

一个发布者可能对应多个订阅者

对应我们代码:

使用了{{}} 意味着这个标签跟Vue产生了关联。需要标记这个标签,将他作为订阅者。

在项目中提供一个发布责,一旦数据产生变化,发布者通知订阅者更新页面。

(1)改造compile

compiler() {
       //伪数组 [<p>,<p>]
      [...this.$el.children].forEach(item => {
           //进行正则表达式匹配 {{Xiaowang8-123}}
           if (/{{([a-zA-Z0-9]+)}}/.test(item.innerHTML)) {
               const key = RegExp.$1.trim()
               //实际上底层并不是直接innerHTML
               // item.innerHTML = this.$data[key]
               const render = () => item.innerHTML = this.$data[key]
               //render方法应该交给订阅者进行管理
               // render()
               new Watcher(render)
          }
      })
  }

(2)创建watcher类

/**
* Watcher订阅者
*/
class Watcher {
   constructor(callback) {
       Dep.target = this
       this.callback = callback
       this.update()
       Dep.target = null
  }
   update() {
       //这一步并不是直接修改,更新虚拟dom
       this.callback()
  }
}

(3)创建Dep类

/**
* Dep代表发布者
*/
class Dep {
   constructor() {
       //数组中存放订阅者
       this.subs = []
  }
   notify() {
       this.subs.forEach(item => {
           item.update()
      })
  }
}

(4)依赖收集和通知

class Observer {
   //创建这个类的时候,通过构造器获取参数
   constructor(data) {
       this.data = data
       //创建这个实例,调用walk
       this.walk()
  }
   //实现数据劫持的函数
   defineProperty(data, key, value) {
       const dep = new Dep()
       Object.defineProperty(data, key, {
           get() {
               if (Dep.target) {
                   dep.subs.push(Dep.target)
              }
               //依赖收集:当页面中使用了这个属性,那就以为产生一个watcher
               console.log(`使用了${key}这个属性`);
               return value
          },
           set(val) {
               //更新页面:一旦修改某个属性,Dep通知watcher进行更新
               console.log(`修改了${key}属性`, val);
               value = val
               dep.notify()
          }
      })
  }
   walk() {
       Object.keys(this.data).forEach(key => {
           this.defineProperty(this.data, key, this.data[key])
      })
  }
}
​

(5)页面进行数据更新

app.username = "xiaowang"

七、视频地址

https://www.bilibili.com/list/324321614?sid=3416326&desc=1&oid=955623766&bvid=BV15W4y1f7Uo