以下代码是使用rollup打包成Vue.js的
./dist/Vue.js
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.VueReactivity = {}));
}(this, (function (exports) { 'use strict';
function isObject(val) {
return typeof val == 'object' && val !== null;
}
function hasChanged(oldValue, newValue) {
return oldValue !== newValue;
}
let isArray = Array.isArray;
const extend = Object.assign;
//判断key是否是数字,key肯定是字符串形式的,arr[key]中key也是数字型的字符串
const isIntegerKey = (key) => {
return parseInt(key) + '' === key;
};
//判断该对象上是否有该属性
const hasOwn = (target, key) => Object.prototype.hasOwnProperty.call(target, key);
function effect(fn, options = {}) {
const effect = createReactiveEffect(fn, options);
if (!options.lazy) {
effect();
}
return effect; //返回响应式的effect
}
let activeEffect;
const effectStack = [];
let id = 0;
//当用户取值的时候需要将activeEffect 和属性做关联
//当用户更改的时候 要通过属性找到effect重新执行
function createReactiveEffect(fn, options) {
const effect = function reactiveEffect() {
try {
effectStack.push(effect);
activeEffect = effect;
return fn(); //会取值
}
finally {
effectStack.pop();
activeEffect = effectStack[effectStack.length - 1];
}
};
effect.id = id++; //构建的是一个id
effect.__isEffect = true;
effect.options = options;
effect.deps = []; //effect用来收集依赖了哪些属性
return effect;
}
//依赖收集,目的是为了让effect和属性关联起来
const targetMap = new WeakMap;
function track(target, type, key) {
if (activeEffect == undefined) {
return; //用户只是取了值,而且这个值不是在effect中使用的,什么都不用收集
}
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
}
//console.log(targetMap)
}
function trigger(target, type, key, newValue, oldValue) {
//去映射表里找到属性对应的 effect, 让它重新执行
const depsMap = targetMap.get(target);
if (!depsMap)
return;
const effectsSet = new Set();
const add = (effectsAdd) => {
if (effectsAdd) { //有可能只是改了属性,然而这个属性之前没有在effect中使用,所以没有将该属性和effect关联起来
effectsAdd.forEach(effect => effectsSet.add(effect));
}
};
//1.如果更改的数组长度 小于依赖收集的长度 要触发重新渲染
//2.如果调用了push方法 或者其它新增数组的方法(必须能改变长度的方法),也要触发更新
if (key === 'length' && isArray(target)) { //如果是数组,你改了length
depsMap.forEach((dep, key) => {
console.log(dep, key); //我对index为2的这一项收集了effect
if (key >= newValue || key === 'length') {
add(dep); //更改的数组长度 小于等于收集到的属性的值
}
});
}
else {
add(depsMap.get(key));
// add(depsMap.get(key));
// add(depsMap.get(key));
switch (type) {
case 'add':
if (isArray(target) && isIntegerKey(key)) {
add(depsMap.get('length')); //增加属性 需要触发length的依赖收集
}
}
}
effectsSet.forEach((effect) => effect());
console.log('me');
}
function createGetter(isReadonly = false, shallow = false) {
/**
* target 是原对象
* key 是取什么属性
* receiver 代理对象
*/
return function get(target, key, receiver) {
//return target[key]
//return receiver[key]这样取值会造成无限递归
//Refelct 就是要后续慢慢替换掉Object对象,一般使用proxy 会配合Reflect
const res = Reflect.get(target, key, receiver); //Reflect.ownKey Reflect.definedProperty
//Reflect.ownKey Reflect.definedProperty(Object中的很多方法移植到了Relfect)
//更新视图的方法是在set里做的,所以对于Readonly来说不需要收集依赖
if (!isReadonly) {
//console.log('收集当前属性,如果这个属性变化了,稍后可能要更新视图',key);
track(target, 'get', key);
}
//如果是浅的,浅的不需要递归代理
if (shallow) {
return res;
}
//懒递归 当我们取值的时候才去做递归代理 这样做的原因是proxy只代理第一层
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res);
}
return res;
};
}
//readonly不能set
function createSetter(shallow = false) {
//针对数组而言 如果调用push方法,就会产生两次触发
//1.给数组新增了一项,同时也更改了长度
//2.因为更改了长度再次触发set (第二次的触发是无意义的)
return function set(target, key, value, receiver) {
const oldValue = target[key]; //获取旧值
//target[key]=value;//如果设置失败 没有返回值
//console.log('用户设置值了,我要去更新了')
//假设有一个属性不能被修改 target[key]=value,不会报错;但是通过Reflect.set 会返回false
//设置属性,可能以前有,还有可能以前没有 (新增和修改)
//如何判断数组是新增还是修改
//这里有五种情况:obj.新增属性 obj.存在属性 arr.push(x)=>数组添加数据,然后又改变了长度 arr[i]=x
let hadKey = isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target, key);
//这个不能放在上面 如果放在上面 通过Reflect.set后,值肯定是有的,hasOwn(target,key)一定为true
const res = Reflect.set(target, key, value, receiver);
if (!hadKey) {
//console.log('新增')
trigger(target, 'add', key, value);
}
else if (hasChanged(oldValue, value)) {
//console.log('修改')
trigger(target, 'set', key, value);
}
return res;
};
}
const get = createGetter(); //不是只读的也不是浅的
const shallowGet = createGetter(false, true);
const readonlyGet = createGetter(true);
const shallowReadonlyGet = createGetter(true, true);
const set = createSetter();
const shallowSet = createSetter(true);
const mutableHandlers = {
get,
set
};
const shallowreactiveHandlers = {
get: shallowGet,
set: shallowSet
};
let readonlySet = {
set(target, key) {
console.warn(`cannot set ${JSON.stringify(target)} on key ${key} falied`);
}
};
//readonly没有set
const readonlyHandlers = extend({
get: readonlyGet
}, readonlySet);
const shallowReadonlyHandlers = extend({
get: shallowReadonlyGet
}, readonlySet);
//是否是浅的,默认是深度
//是否是只读的 默认不是只读的
function reactive(target) {
return createReactiveObject(target, false, mutableHandlers);
}
function shallowReactive(target) {
return createReactiveObject(target, false, shallowreactiveHandlers);
}
function readonly(target) {
return createReactiveObject(target, true, readonlyHandlers);
}
function shallowReadonly(target) {
return createReactiveObject(target, true, shallowReadonlyHandlers);
}
/**
*
* @param target 创建代理的目标
* @param isReadonly 当前是不是只读的
* @param baseHandler 针对不同的方式创建不同的代理对象
*/
//weakMap(key只能是对象) map(key可以是其他类型)
const reactiveMap = new WeakMap(); //目的是添加缓存
const readonlyMap = new WeakMap();
function createReactiveObject(target, isReadonly, baseHandler) {
if (!isObject(target)) {
return target;
}
const proxyMap = isReadonly ? readonlyMap : reactiveMap;
const existProxy = proxyMap.get(target);
if (existProxy) {
return existProxy; //如果已经代理过了,那就直接把上次的代理返回就可以的
}
//如果是对象 就做一个代理 new proxy
const proxy = new Proxy(target, baseHandler);
proxyMap.set(target, proxy);
return proxy;
}
//数组,对象是如何劫持 effect的实现 ref的实现。。。
exports.effect = effect;
exports.reactive = reactive;
exports.readonly = readonly;
exports.shallowReactive = shallowReactive;
exports.shallowReadonly = shallowReadonly;
Object.defineProperty(exports, '__esModule', { value: true });
})));
./index.html
<!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>
<div id="app"></div>
<script src="./dist/Vue.js"></script>
<script>
let {reactive,shallowReactive,readonly,shallowReadonly,effect}=VueReactivity;
let school={name:'zf',age:12,address:{num:517},arr:[1,2,3]}
let proxy=reactive(school)
let proxy2=reactive({name:'zf'})
// effect(()=>{
// //默认这个函数会执行一次,执行的时候应该把用到的属性和这个effect关联起来
// console.log(proxy.name)
// effect(()=>{
// console.log(proxy.age)
// })
// })
// effect(()=>{
// console.log(proxy.name)
// // console.log(proxy2.name)
// })
effect(()=>{
//当你调用stringfy的时候 会访问数组中每一个属性,包括length;
// console.log(JSON.stringify(proxy.arr))
console.log(proxy.arr[2])
})
//下次更新属性的时候,会再次执行这个effect
// proxy.arr.push(100)
proxy.arr.length=2
//1.通过索引更新数组
//2.可以通过length来修改
//3.去length,新增
// proxy.arr[1]=5
// setTimeout(() => {
// proxy.name='zf1'
// proxy.name='zf2'
// proxy.name='zf3'
// proxy.name='zf4'
// proxy.name='zf5'
// proxy.name='zf6'
// proxy.name='zf7'
// }, 1000);
</script>
</body>
</html>