vue源码研究学习笔记
vue2如何实现响应式
发布订阅模式+数据双向绑定=基本的响应式
实现的逻辑
// 订阅器模型
let Dep = {
// 订阅者列表
clientList: {},
// 订阅函数
listen: function (key, fn) {
(this.clientList[key] || (this.clientList[key] = [])).push(fn)
},
// 触发函数
trigger: function () {
let key = Array.prototype.shift.call(arguments),
fns = this.clientList[key]
if (!fns || fns.length === 0) return false
for (let i = 0, fn; fn = fns[i++];) {
fn.apply(this, arguments)
}
},
}
// 数据劫持
/**
*
* @param {data} 数据
* @param {tag} 目标
* @param {dataKey} 数据key
* @param {selector} 显示的dom
*/
let dataHi = ({ data, tag, dataKey, selector }) => {
let value = '',
el = document.querySelector(selector)
Object.defineProperty(data, dataKey, {
get() {
return value
},
set(val) {
value = val
Dep.trigger(tag, val)
}
})
// 订阅
Dep.listen(tag, val => {
el.innerHTML = val
})
}
引用的实例
<!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>vue2响应式原理</title>
<script src="./index.js"></script>
</head>
<body>
<div>数据响应</div>
<div class="box-1"></div>
<div class="box-2"></div>
<script>
let obj = {};
dataHi({
data: obj,
tag: "view-1",
dataKey: "one",
selector: ".box-1",
});
dataHi({
data: obj,
tag: "view-2",
dataKey: "two",
selector: ".box-2",
});
obj.one = "3333";
obj.two = "55555";
</script>
</body>
</html>
vue3响应式实现
<!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>vue3实现响应式</title>
</head>
<body>
<script>
let obj = {
name: "小明",
age: 18,
};
// 代理
const p = new Proxy(obj, {
// 读取
get(target, key) {
console.log(`读取p的属性${key}`);
return target[key];
},
// 设置
set(target, key, value) {
target[key] = value;
console.log(`设置p的属性${key}`);
return true;
},
// 移除属性
deleteProperty(target, key) {
return delete target[key];
},
});
</script>
</body>
</html>
渲染函数render()
一值多判断的情况下使用
<script>
export default {
props: {
type: {
type: String,
default: "primary",
},
text: {
type: String,
default: "primary",
},
},
// h => 原生js createElement()=> 创建真实元素 => 生成虚拟dom => 真实dom
render(h) {
return h(
"button",
{
class: "btn btn-" + this.type,
domProps: {},
},
this.text
);
},
};
</script>
<style scoped>
.btn {
width: 100px;
height: 40px;
background-color: #f5f5f5;
}
.btn-success{
background-color: #67C23A;
}
</style>
组件自动注册
实现的思路如下
- 将文件名首字母转大写
- 通过webpack内置方法获取所有的组件文件夹下的.vue文件
- 通过循环获取到当前的组件和组件的名称
- 通过Vue.component注册组件
// global.js
import Vue from 'vue'
// 首字母大写
const changeString = (str) => {
return str.charAt(0).toUpperCase() + str.slice(1);
}
// 动态引入文件
export const initComponent = () => {
/**
* 1、当前路径
* 2、是否匹配子集
* 3、匹配文件格式
*/
const requireContent = require.context('./', false, /\.vue$/i);
const componentsList = requireContent.keys() || [];
componentsList.forEach(fileName => {
// 当前组件
const currentComponent = requireContent(fileName)
// 截取当前组件的名称
const currentComponentName = changeString(fileName.replace(/^\.\//, '').replace(/\.\w+$/, ''))
// 组册组件
Vue.component(currentComponentName, currentComponent.default || currentComponent)
});
};
在项目入口main.js文件引入
// main.js
import {initComponent} from './components/common/global'
initComponent()
自定义指令实现控制按钮权限
通过按钮传入权限值,判断这个值是不是在权限列表里面,如果不在通过指令移除这个按钮
具体实现的代码例子如下:
// utils/Authority.js
import Vue from 'vue'
const checkAuth = (key) => {
// 假设这个是权限数组
let arr = ['1', '2', '3', '4', '5', '6']
let index = arr.indexOf(key)
if (index > -1) {
return true
} else {
return false
}
};
export const authBtn = () => {
Vue.directive('display-key', {
inserted(el, binding) {
// 指令值
let displayKey = binding.value
if (displayKey) {
// 权限列表包含这个指令值
let hasKey = checkAuth(displayKey)
if (!hasKey) {
// 如果没有这个权限,就移除这个元素
el.parentNode && el.parentNode.removeChild(el)
}
} else {
throw new Error('请传入key')
}
}
})
}
// main.js
import { authBtn } from './utils/Authority'
authBtn()
侦听整个对象的变化
演示的html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>侦测整个对象</title>
</head>
<body>
<script type="module">
import { Observer } from "./Observer.js";
let obj = new Observer({
name: "张三",
age: 18,
});
obj.value.age = 18888;
obj.value.h = {};
obj.value.h.a = 999
</script>
</body>
</html>
Observer.js
// Observer.js
export class Observer {
constructor(value) {
this.value = value;
if (Array.isArray(value)) {
// 执行数组逻辑
} else {
// 执行对象逻辑
this.walk(value);
}
}
walk(obj) {
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
}
function defineReactive(obj, key, value) {
if (arguments.length === 2) {
value = obj[key];
}
if (Object.prototype.toString.call(value) === '[object Object]') {
new Observer(value);
}
Object.defineProperty(obj, key, {
get() {
console.log(`${key}属性被读取`);
return value;
},
set(newValue) {
if (newValue === value) return;
value = newValue;
console.log(`${key}属性被修改为${newValue}`);
},
});
}
打赏作者
微信
支付宝