Vue响应式框架设计
一、课程分类
Vue核心技术栈。
React全家桶。
Vue3+TS开发
扩展课程:前端架构,3D实战、服务器搭建、websocket通信
二、Vuejs扩展内容
- 响应式原理
- 项目配置,跨域
- 项目打包上线
三、响应式原理设计
(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引用节点
四、数据劫持的设计
- 数据定义后页面更新
- 页面数据切换操作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",
password: 123
}
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",
password: 123
}
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>
六、观察者模式
观察者模式是一种设计模式
设计模型:就是一种代码规则,当你按照这种规范来写代码,采用对应设计思想。
换句话说:前人总结出来一系列开发技巧、规则。应用自己项目中。利用这种思想来解决我们问题。使用这种设计模式
有两个非常重要的元素:
- 发布者:发布消息的人(商家)
- 订阅者:接受消息的人(用户)
一个发布者可能对应多个订阅者
对应我们代码:
使用了{{}} 意味着这个标签跟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