关于Vue2源码的几道手写题

331 阅读1分钟

关于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;
  }
};

暂时总结这么多,后面逐渐积累,输出倒逼输入吧。