关于Vue2源码的手写题,总结了几道比较有价值的,都是在面试中大佬问到的。
1,写一个双向绑定的基本实现?
const data = {
text: '',
};
let input = document.getElementById('input');
let text = document.getElementById('text');
defineReactive(data, 'text', '');
input.addEventListener('keyup', function(e) {
data.text = e.target.value
})
function defineReactive(data, key, value) {
Object.defineProperty(data, key, {
get() {
return value;
},
set(newVal) {
text.innerText = newVal;
}
});
}
2, props 在子组件 直接赋值,会报警告,如何实现?
function defineReactive$$1 (
obj,
key,
val,
customSetter,
shallow
) {
//...
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: () => {},
set: () => {
if (customSetter) {
customSetter();
}
}
})
}
// 在initProps时,调用defineReactive给每个prop,添加回调函数
// 在set时,判断是否有回调函数,有则调用,提示警告
function initProps (vm, propsOptions) {
var loop = function ( key ) {
defineReactive$$1(props, key, value, function () {
if (!isRoot && !isUpdatingChildComponent) {
warn(
"Avoid mutating a prop directly since the value will be " +
"overwritten whenever the parent component re-renders. " +
"Instead, use a data or computed property based on the prop's " +
"value. Prop being mutated: \"" + key + "\"",
vm
);
}
});
}
for (var key in propsOptions) loop( key );
}
3,computed属性 如何实现缓存功能?
// 每个计算属性也都是一个watcher,计算属性需要表示lazy:true,这样在初始化watcher时不会立即调用计算属性方法
var Watcher = function Watcher() {
this.dirty = this.lazy; // for lazy watchers
//...
this.value = this.lazy ? undefined : this.get(); // 调用get方法 会让渲染watcher执行
}
Watcher.prototype.update = function update () {
if (this.lazy) {
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
queueWatcher(this);
}
};
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/
Watcher.prototype.evaluate = function evaluate () {
this.value = this.get();
this.dirty = false;
};
function initComputed (vm, computed) {
var watchers = vm._computedWatchers = Object.create(null);
for (var key in computed) {
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
);
defineComputed(vm, key, computed[key]);
}
}
function defineComputed (target,key, userDef) {
sharedPropertyDefinition.get = createComputedGetter(key)
Object.defineProperty(target, key, sharedPropertyDefinition)
}
function createComputedGetter (key) {
// 创建缓存getter
return function computedGetter () {
var watcher = this._computedWatchers && this._computedWatchers[key];
if (watcher) {
// 默认watcher.dirty是false,当依赖变化时,会通知watcher调用update方法,dirty被置为true,
// 此时,才能执行watcher的evaluate(), dirty再次设置为false
if (watcher.dirty) {
watcher.evaluate();
}
if (Dep.target) {
watcher.depend();
}
// 如果依赖的值不发生变化,则返回上次计算的结果
return watcher.value
}
}
}
4,$destroy做了哪些事情?
Vue.prototype.$destroy = function () {
var vm = this;
callHook(vm, 'beforeDestroy');
// 1,将实例自身从parent从移除, splice()
var parent = vm.$parent;
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
remove(parent.$children, vm);
}
// 2,清除watchers, teardown()是通过把sub从数组中移除
if (vm._watcher) {fuc
vm._watcher.teardown();
}
var i = vm._watchers.length;
while (i--) {
vm._watchers[i].teardown();
}
//3,从data中移除__ob__引用
if (vm._data.__ob__) {
vm._data.__ob__.vmCount--;
}
// 触发它子组件的销毁钩子函数,这样一层层的递归调用
vm.__patch__(vm._vnode, null);
// fire destroyed hook
callHook(vm, 'destroyed');
//4,关闭所有实例的监听器
vm.$off();
// 5,移除 __vue__ 引用
if (vm.$el) {
vm.$el.__vue__ = null;
}
// release circular reference (#6759)
if (vm.$vnode) {
vm.$vnode.parent = null;
}
};
暂时总结这么多,后面逐渐积累,输出倒逼输入吧。