liam61 / blog

Lawler's Blog 不定期分享一些前端技术
156 stars 28 forks source link

【面经】字节跳动 二面9:实现简易版 MVVM #5

Open liam61 opened 5 years ago

liam61 commented 5 years ago

实现简易版 MVVM

关键词:Object.defineProperty

class Observer {
  constructor(data) { this.data = data; }

  observe(prop, cbObj) { // 调用了 observe 才会对属性 prop 进行观察
    if (!prop || typeof prop !== 'string') return;
    this.defineReactive(this.data, prop, this.data[prop], cbObj);

    // 由于 new Observer() 出的对象没有 data.a 属性,必须通过 data.data.a 调用,熟悉 vue 的应该知道这一点
    // 为了方便我们直接调用 data.a,把 this.data 的属性代理到 this 上
    this.proxyData(prop, this.data);
  }

  defineReactive(obj, key, value, { getCallback, setCallback }) { // 抽离的对属性进行监控的方法
    Object.defineProperty(obj, key, {
      get() {
        getCallback(value);
        return value;
      },
      set(newValue) {
        if (newValue !== value) {
          setCallback(newValue, value);
          value = newValue;
        }
      }
    });
  }

  proxyData(key, data) {
    Object.defineProperty(this, key, {
      get() { return data[key]; }, // 对调用 data.a 进行劫持,返回内部的 data[key]
      set(newValue) { data[key] = newValue; } // 对 data.a = 2; 进行劫持,实际上的在操作内部的 data[key] = 2;
    });
  }
}

// 测试
const data = new Observer({ a: 1, b: 2 });

const callbackObj = {
  getCallback(value) { console.log(`get: value: ${value}`); },
  setCallback(newValue, oldValue) { console.log(`set: newValue: ${newValue}, oldValue: ${oldValue}`); }
}
data.observe("a", callbackObj);
data.a; // console: 1
data.a = 2; // console: 2 1
data.b; // undefined 因为没有进行 data.observe('b', ...)
anwenyao commented 5 years ago

冒昧的说一句哈 ,看了下题目,如果按照原题目的话,感觉这个您这个有一点点小问题 就是 const data = new Observer({ a: 1, b: 2 }); 这句之后,直接访问data.adata.b应该是能够得到值的。 题目:

const data = new Observer({ a: 1 });
console.log(data.a); // 1 <---这里

但是您这个在构造函数中没有把{ a: 1, b: 2 }的属性赋值给实例 直接访问data.a/b就是undefined。 参照了您的思路,我自己也写了一个,感觉也有点问题。有什么不对的地方欢迎指出。

class Observer {
    constructor(data){
        this.data = data;
        let keys = Object.keys(data);
        this.observer = {};//调用了$on 的对象的属性
        for(let key of keys){
            Object.defineProperty(this,key,{
                set(val){
                    this.$emit(key,this.data[key],val);
                    this.data[key] = val;
                },
                get(){
                    return this.data[key];
                }
            })
        }
    }

    $on(name,callBack) {
        if(!this.observer[name]){
            this.observer[name] = callBack;
        }

    }

    $emit(name,...args){
        if(this.observer[name]){
            this.observer[name](...args);
            return;
        }
        return;
    }

  }

  let data = new Observer({a:1,b:2});
  console.log(data.a);//1
  data.$on('a',(newVal,oldVal)=>{
      console.log(`newValue:${newVal},oldValue:${oldVal}`);
  });
  data.a = 2;//newValue:1,oldValue2
  data.b = 4;
  console.log(data.b);//4
  data.$on('b',(newVal,oldVal)=>{
    console.log(`第一次:${newVal},第二次:${oldVal}`);
});
  data.b = 5;//第一次:4,第二次:5

最后,非常感谢您的面经!

liam61 commented 5 years ago

@anwenyao 你这样理解是对的,当时面试时我也是这个思路。写面经时想尽量简单些,就直接以 显示监听属性后才数据劫持 为基准,如 data.observe("a", callbackObj); data.b; // undefined