1.watch实现
1.1.测试
<body>
<div id="app" style="color:red;background:green">{{name}}{{obj}}{{age}}</div>
<script src="./dist/vue.js"></script>
<script>
const vm=new Vue({
el:'#app',
data:{
name:'zhangsan',
obj:{
a:'1'
},
age:18
},
watch:{
name(newVal,oldVal){
console.log(newVal,oldVal)
},
'obj.a'(newVal,oldVal){
console.log(newVal,oldVal)
},
age:[
function(newVal,oldVal){
console.log(newVal,oldVal)
},
function(newVal,oldVal){
console.log(newVal,oldVal)
}
]
}
})
vm.$watch('name',function(newVal,oldVal){
console.log(newVal,oldVal)
})
setTimeout(() => {
vm.name='lisi';
vm.age=2;
vm.obj.a='4';
}, 2000);
</script>
</body>
1.2.index.js
stateMixin(Vue);
1.3.state.js
- 初始化watch
- 函数形式和数组形式的watch合并
export function stateMixin(Vue) {
Vue.prototype.$watch = function (key, handler, options = {}) {
options.user = true;
new Watcher(this, key, handler, options);
}
}
export function initState(vm) {
if (opt.watch) {
initWatch(vm, opt.watch)
}
}
function initWatch(vm, watch) {
for (const key in watch) {
let handler = watch[key];
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i]);
}
} else {
createWatcher(vm, key, handler)
}
}
}
function createWatcher(vm, key, handler) {
return vm.$watch(key, handler)
}
1.4.observer/watcher.js
class Watcher {
constructor(vm, exprOrFn, cb, options) {
this.vm = vm;
if (typeof exprOrFn === 'string') {
this.exprOrFn = function () {
const splits = exprOrFn.split('.')
let obj = vm;
for (let i = 0; i < splits.length; i++) {
obj = obj[splits[i]]
}
return obj;
}
} else {
this.exprOrFn = exprOrFn;
}
this.cb = cb;
this.options = options;
this.user = !!options.user;
this.getter = this.exprOrFn;
this.value = this.get();
}
get() {
pushTarget(this);
const value = this.getter();
popTarget();
return value;
}
run() {
console.log('run')
const newValue = this.get();
const oldValue = this.value;
this.value = newValue;
if (this.user) {
this.cb.call(this.vm, newValue, oldValue);
}
}
}
2.computed实现
- 1.计算属性默认不会执行
- 2.多次取值如果依赖的变量没有变化,不会重新执行
- 3.依赖的值变化了,需要重新执行
- 4.dirty表示这个是否需要重新执行
2.1.测试
- 1.当在页面中直接写fullName是,fullName不会去收集渲染watcher,因为fullName没有dep,没有收集的功能,state的defineComputed中实现了此逻辑
- 2.firstName是在计算属性中依赖,所以他会收集属性watcher,和渲染watcher
<body>
<div id="app" style="color:red;background:green">{{fullName}}</div>
<script src="./dist/vue.js"></script>
<script>
const vm=new Vue({
el:'#app',
data:{
firstName:'zhang',
lastName:'san'
},
computed:{
fullName(){
console.log('get')
return this.firstName+this.lastName;
}
}
})
setTimeout(() => {
vm.firstName='li'
}, 2000);
</script>
</body>
2.2.state.js
export function stateMixin(Vue) {
Vue.prototype.$watch = function (key, handler, options = {}) {
options.user = true;
new Watcher(this, key, handler, options);
}
}
export function initState(vm) {
const opt = vm.$options;
if (opt.computed) {
initComputed(vm, opt.computed);
}
}
function createComputedGetter(key) {
return function () {
const watcher = this._computedWatchers[key];
if (watcher.dirty) {
watcher.evaluate();
}
if (Dep.target) {
watcher.depend();
}
return watcher.value;
}
}
function defineComputed(vm, key, useDef) {
let sharedProperty = {};
if (typeof useDef === 'function') {
sharedProperty.get = useDef;
} else {
sharedProperty.get = createComputedGetter(key);
sharedProperty.set = useDef.set;
}
Object.defineProperty(vm, key, sharedProperty);
}
function initComputed(vm, computed) {
const watchers = vm._computedWatchers = {};
for (let key in computed) {
const userDef = computed[key];
const getter = typeof userDef === 'function' ? userDef : userDef.get;
watchers[key] = new Watcher(vm, getter, () => {}, {
lazy: true
})
defineComputed(vm, key, userDef);
}
}
2.3.observer/watcher.js
class Watcher {
constructor(vm, exprOrFn, cb, options) {
this.lazy = !!options.lazy;
this.dirty = !!options.lazy;
this.value = this.lazy ? undefined : this.get();
}
update() {
if (this.dirty) {
this.dirty = true;
} else {
queueWatcher(this);
}
}
evaluate() {
this.dirty = false;
this.value = this.get();
}
depend() {
let i = this.deps.length;
while (i--) {
this.deps[i].depend();
}
}
}
2.4.observer/dep.js
Dep.target = null;
let stack = [];
export function pushTarget(watcher) {
Dep.target = watcher;
stack.push(watcher);
}
export function popTarget() {
stack.pop();
Dep.target = stack[stack.length - 1];
}