序文:
vue-next已经出新版,vue3.0和vue2.0对于mvvm的实现是有什么区别呢?我们往下看
vue2.0时代
2016年5月份推出了Vue2.0时代
而es6在2015年6月已经推出
当前推出的proxy可以实现对对象的属性监听,但是由于大部分浏览器还不支持proxy
我也想用啊,可是实力不允许啊…
所以vue2.0还是使用了es5的Object.defineProperty去处理
首先,我们来看下Object.defineProperty 是如何去实现的
/**
* 实现双向绑定的原理
* 数据劫持
*/
function observe(data){
if(typeof data !== 'object' || data === null) {
return data;
}
// 遍历的所有的数据,监听数据属性变化
for(let key in data) {
defineReactive(data, key, data[key]);
}
}
/**
* 监听数据的变化 原谅我这边用了3.0的名字,可以拿这个和3.0做个对比
*/
function defineReactive(data, key, value){
observe(value); // 内部元素也需要数据劫持
Object.defineProperty(data, key, {
get(){
return value;
},
set(newVal){
updateView();
value = newVal;
},
})
}
function updateView(){
console.log('视图发生更新');
}
Object.defineProperty可以监听对象数据变化哎,好开心,我的mvvm框架已经实现了
试了下数组,GG了,数组怎么没监听呢,呜呜呜~
数组该怎么玩呢???嗯嗯嗯~数组基本上都是通过操作方法来变更的话,我可以从原型入手啊
那我们再来实现下vue2.0里面如何对数组做操作的呢
//监听的数组对象方法
const OAM = ['push','unshift','shift','splice','reverse','pop','sort']
/**
* 实现双向绑定的原理
* 数据劫持
*/
function observe(data){
if(typeof data !== 'object' || data === null) {
return data;
}
// 如果是数组,则重写数组方法,并重新指向原型
if(Array.isArray(data)) {
overrideArrayProto(data);
}
// 遍历的所有的数据,监听数据属性变化
for(let key in data) {
defineReactive(data, key, data[key]);
}
}
/**
* 监听数据的变化
*/
function defineReactive(data, key, value){
observe(value); // 内部元素也需要数据劫持
Object.defineProperty(data, key, {
get(){
return value;
},
set(newVal){
updateView();
value = newVal;
},
})
}
/**
* 数组方法劫持
* @param {*} data
*/
function overrideArrayProto(data){
const originalProto = Array.prototype; // 数组的原型
const newProto = Object.create(originalProto); // 根据原来的数组原型重写原型方法
OAM.forEach(method => {
newProto[method] = function(){
let inserted;
updateView();
let args = [].slice.call(arguments);
originalProto[method].apply(this, args);
switch(method) {
case 'push':
case 'unshift':
inserted = args.slice(0);
break;
case 'splice':
inserted = args.slice(2);
break;
default:
break;
}
// 如果是新增的数据,也需要进行监听
inserted && observe(inserted);
}
})
data.__proto__ = newProto; // 将原型指向到重写过的新的原型上
}
function updateView(){
console.log('视图发生更新');
}
这是2、0的实现,那么3.0呢,是不是废铁变青铜呢?
vue3.0时代
proxy都出来这么久了,浏览器该要都支持了吧,好,我要换装备了,新装备给换上
// 判断是否是object对象
function isObject(value) {
return typeof value === 'object' && value !== null;
}
// 判断当前是否包含当前属性
function hasOwn(target, key){
return target.hasOwnProperty(key);
}
let toProxy = new WeakMap(); // 弱引用映射表 es6 放置的是 原对象:代理过的对象
let toRaw = new WeakMap(); // 被代理过的对象:原对象 es6语法
/**
* 数据监听劫持数据
* @param {*} data
*/
function observe(data){
if(typeof data !== 'object' || data === null) {
return data;
}
let proxy = toProxy.get(data); // 如果已经代理过了 就将代理过的结果返回即可
if(proxy) {
return proxy;
}
// 如果将代理后的数据继续代理则返回当前的代理对象
if(toRaw.has(data)) {
return data;
}
// proxy handler方法实现
let handler = {
get(target, key, receiver) {
// 取值有必要的时候再递归
if(isObject(target[key])) {
return observe(target[key]);
}
let res = Reflect.get(target, key, receiver);
return res;
},
set(target, key, value, receiver) {
let hasOwnKey = hasOwn(target, key);
let oldValue = target[key];
let res = Reflect.set(target, key, value, receiver);
return res;
},
// 删除属性
deleteProperty(target, key){
let res = Reflect.deleteProperty(target, key);
return res;
}
}
let observed = new Proxy(data, handler);
toProxy.set(data, observed);
toRaw.set(observed, data);
return observed;
}
黄金盔甲已换上,从此高大上
上面只是数据拦截,那么不是还有数据变化了,关联变更么?
那就是依赖收集,数据变动了依赖触发
// 判断是否是object对象
function isObject(value) {
return typeof value === 'object' && value !== null;
}
// 判断当前是否包含当前属性
function hasOwn(target, key){
return target.hasOwnProperty(key);
}
let effectStack = []; // 副作用堆栈
const targetMap = new WeakMap();
let toProxy = new WeakMap(); // 弱引用映射表 es6 放置的是 原对象:代理过的对象
let toRaw = new WeakMap(); // 被代理过的对象:原对象 es6语法
/**
* 数据监听劫持数据
* @param {*} data
*/
function observe(data){
if(typeof data !== 'object' || data === null) {
return data;
}
let proxy = toProxy.get(data); // 如果已经代理过了 就将代理过的结果返回即可
if(proxy) {
return proxy;
}
// 如果将代理后的数据继续代理则返回当前的代理对象
if(toRaw.has(data)) {
return data;
}
// proxy handler方法实现
let handler = {
get(target, key, receiver) {
// 取值有必要的时候再递归
if(isObject(target[key])) {
return observe(target[key]);
}
let res = Reflect.get(target, key, receiver);
// 收集依赖
track(target,key);
return res;
},
set(target, key, value, receiver) {
updateView();
console.log(key,value);
let hasOwnKey = hasOwn(target, key);
let oldValue = target[key];
let res = Reflect.set(target, key, value, receiver);
if(!hasOwnKey) {
console.log('新增数据');
trigger(target,'add',key);
} else if(oldValue !== value) {
console.log('数据更新');
// 触发依赖
trigger(target,'set',key);
}
return res;
},
// 删除属性
deleteProperty(target, key){
console.log(`删除属性:[key:${key}, value:${target[key]}]`)
let res = Reflect.deleteProperty(target, key);
return res;
}
}
let observed = new Proxy(data, handler);
toProxy.set(data, observed);
toRaw.set(observed, data);
return observed;
}
// 响应式副作用
function effect(fn) {
let effectFn = reactiveEffect(fn); // 实现双向绑定方法
// 立即执行一次
effectFn();
}
// 响应式副作用
function reactiveEffect(fn){
let effect = function(){
run(effect,fn);
};
return effect;
}
/**
* 执行方法
* effect: 副作用
* fn 执行方法
*/
function run(effect,fn) {
try {
effectStack.push(effect);
fn(); // js 线程是单线程的
} finally {
effectStack.pop();
}
}
/**
* 收集依赖
* 数据格式如下:
* {
* target: {
* key: set[fn]
* }
* }
*/
function track(target,key) {
// 获取最新的一条副作用
let effect = effectStack[effectStack.length-1];
// 如果副作用存在,则需要保存进去
if(effect) {
let depTargetMap = targetMap.get(target);
// 先判断下依赖里面的target是否有,没有则初始化new Map()
if(!depTargetMap) {
targetMap.set(target, depTargetMap = new Map())
}
let depKeySet = depTargetMap.get(key);
// 先判断下依赖里面的target下面的key对象是否存在,不存在则创建 new Set()
if(!depKeySet) {
depTargetMap.set(key, depKeySet = new Set());
}
// 判断当前依赖里面是否有当前副作用, 不存在则add进去
if(!depKeySet.has(effect)) {
depKeySet.add(effect);
}
}
}
/**
* 触发副作用
*/
function trigger(target,method,key){
let depTargetMap = targetMap.get(target);
if(!depTargetMap) {
return;
}
// 依次遍历副作用依赖方法
let deps = depTargetMap.get(key);
if(deps) {
deps.forEach(fn=>{
fn();
})
}
}
// 数据来执行一把看看
let dataSource = {a:1,b:2};
let data = observe(dataSource);
effect(()=>{ // effect 会执行两次 ,默认先执行一次 之后依赖的数据变化了 会再次执行
console.log(`数据变更:${data.b}`);
})
总结:
vue3.0相对于vue2.0
- 2.0 默认会递归
- 数组改变length 是无效的
- 对象不存在的属性不能被拦截
Vue3.0的使用
const { value, computed, watch, onMounted, reactive } = Vue;
function usePosition(){
let position = Vue.reactive({
x:0,y:0
})
function updatePosition(e){
position.x = e.x;
position.y = e.y;
// console.log(`x:${position.x},y:${position.y}`)
}
Vue.onMounted(()=>{
window.addEventListener('mousemove',updatePosition);
})
Vue.onUnmounted(()=>{
window.removeEventListener('mousemove',updatePosition);
})
return position;
}
let app = {
setup(){
let state = Vue.reactive({name:'wp'});
let count = reactive({
value: 0,
});
const plusOne = computed(()=>count.value+1);
const increment = ()=>{ count.value++ };
watch(()=>count.value*2,val=>{
console.log('count * 2 is', val);
});
let position = usePosition();
function changeState(){
state.name = 'zhangsan';
}
Vue.effect(()=>{
console.log(`数据改变:${state.name}`)
})
return { // state数据,方法,computed计算属性 都可以在这里返回
state,
changeState,
position,
count,
plusOne,
increment
}
},
template: `<div @click="changeState">{{state.name}}</div>
<div>-count-{{ count.value }}-</div>
<div>-plusOne-{{ plusOne }}-</div>
<button @click="increment">新增</button>
`
}
Vue.createApp().mount(app,container);