vue2和vue3的响应式简单实现 
响应式本质是数据与函数的关系, 数据驱动页面, 其实是数据驱动函数. 当数据改变时调用某些函数.
const title = document.getElementById('title')
const data = {
    title: 'Hello, world!'
}
title.textContent = data.title这是一个简单的操作, 实际上vue模板的就会编译成类似的东西, 只不过可能不是用id来索引, 不过最后都是要用textContent或者innerHTML的.
如果每次修改title后, 都要自己写一行title.textContent = data.title太麻烦了, 需要再封装一下.
我们希望每次set这个title后, 就执行一些操作, 那我们可以拦截set, 这些需要拦截set的操作就是依赖这个响应式变量的函数, deps; 我们还希望这个响应式变量可以仅在某些时候拦截, 其它情况不拦截, 因此我们可以拦截get, 只有需要响应式的时候才将依赖收集起来.
在这里可以利用一点: js是单线程的, 不可能有两个函数同时对一个变量同时写同时读, 因此我们可以准备一个全局变量ACTIVE_EFFECT和一个函数effect, 通过effect调用的函数就是需要收集依赖的.
let ACTIVE_EFFECT = null
function effect(fn) {
    ACTIVE_EFFECT = fn
    fn()
    ACTIVE_EFFECT = null
}接下来就是要想怎么拦截get/set了.
在js中, 提供了一些很有用的东西: Object.defineProperty和Proxy, 当然也可以自己写一个类, 大概这样子:
class ReactiveValue {
    get value() {}
    set value(newValue) {}
}vue2 
vue2为了考虑兼容性, 选择了Object.defineProperty. 因此, 在vue2中采用了选项式API, 并要求data返回一个Object.
const data = {
    title: 'Hello, world!'
}这里为了方便, 就不写成data(){}了, 直接写成一个对象.
在vue2中, 如果data不是一个函数, 而是一个普通的对象, 就会出现同个组件使用同个data的问题.
先以title为例, 然后改成通用的函数.
let titleValue = data.title // 在拦截get/set前需要保存原值, 如果get返回data.title或者set修改data.title就会无限调用
const deps = new Set()
Object.defineProperty(data, 'title', {
    get(){
        if (ACTIVE_EFFECT) {
            deps.add(ACTIVE_EFFECT)
        }
        return titleValue
    },
    set(newValue){
        titleValue = newValue
        deps.forEach(fn => fn())
    },
})这就是一个简单的响应式变量了, 不过只有title具有响应式. 整个对象的话, 其实就是遍历所有key-value
function makeReactive(obj) {
  if (typeof obj !== 'object' || obj === null) {
    return obj // 只对object进行响应式化
  }
  const depsMap = new Map() // 对于每一个对象, 都应该有一个depsMap. key是obj的key, value是对应的依赖集合
  Object.keys(obj).forEach((key) => {
    let value = obj[key] // 跟title一样, 要保存原值
    obj[key] = makeReactive(value) // 如果是object的话, 还要继续响应式化, 不是的话返回原值
    Object.defineProperty(obj, key, {
      get() {
        if (ACTIVE_EFFECT) {
          // 收集依赖
          let deps = depsMap.get(key)
          if (!deps) {
            deps = new Set()
            depsMap.set(key, deps)
          }
          deps.add(ACTIVE_EFFECT)
        }
        return value
      },
      set(newValue) {
        value = makeReactive(newValue) // 如果是对象, 那么新赋值的对象也要响应式化
        const deps = depsMap.get(key)
        if (deps) {
          deps.forEach(fn => fn())
        }
      },
    })
  })
}这里基本实现了vue2中响应式实现. 实际可能更加复杂. 这里是没有考虑如果传进来的obj已经是响应式变量该怎么办的, 以及watch需要的旧值没有处理.
vue2简单实现代码 
可以进一步封装.
function makeReactive(obj) {
  if (typeof obj !== 'object' || obj === null) {
    return obj // 只对object进行响应式化
  }
  const depsMap = new Map() // 对于每一个对象, 都应该有一个depsMap. key是obj的key, value是对应的依赖集合
  Object.keys(obj).forEach((key) => {
    let value = obj[key] // 跟title一样, 要保存原值
    obj[key] = makeReactive(value) // 如果是object的话, 还要继续响应式化, 不是的话返回原值
    const track = () => {
        if (ACTIVE_EFFECT) {
          // 收集依赖
          let deps = depsMap.get(key)
          if (!deps) {
            deps = new Set()
            depsMap.set(key, deps)
          }
          deps.add(ACTIVE_EFFECT)
        }
    }
    const trigger = () => {
        const deps = depsMap.get(key)
        if (deps) {
          deps.forEach(fn => fn())
        }
    }
    Object.defineProperty(obj, key, {
      get() {
        track()
        return value
      },
      set(newValue) {
        value = makeReactive(newValue) // 如果是对象, 那么新赋值的对象也要响应式化
        trigger()
      },
    })
  })
}vue3 
vue3的实现有两种, 一种是object的, 另一种是普通数据的. 也就是reactive和ref了. 不过我们在使用ref的时候会发现, ref就算传入一个object也可以正常使用, 其实背后是reactive在发力了.
ref(不含object) 
ref的实现比较简单, 毕竟不像对象有多个key-value, 像1, '1'这种就一个value
class RefImpl<T> {
  private _value: T
  private deps: Set<() => unknown>
  constructor(value) {
    this._value = value
  }
  private track() {
    if (ACTIVE_EFFECT) {
      if (!this.deps) {
        this.deps = new Set<() => unknown>()
      }
      this.deps.add(ACTIVE_EFFECT)
    }
  }
  private trigger() {
    if (this.deps) {
      this.deps.forEach(fn => fn())
    }
  }
  get value(): T {
    this.track()
    return this._value
  }
  set value(newValue: T) {
    this._value = newValue
    this.trigger()
  }
}
function ref<T>(value: T) {
  return new RefImpl<T>(value)
}同样要声明一下, 实际实现比这要复杂, 这里只是简单实现.
现在的ref就可以实现普通值的响应式了.
const count = ref(1)
effect(() => {
  console.log('a changed:', count.value)
})
setInterval(() => {
  count.value++
}, 1000)reactive 
reactive只允许传入一个对象, 使用Proxy来拦截get/set.
function reactive<T extends object>(obj: T): T {
    if (typeof obj !== 'object' || obj === null) {
        return obj
    }
    const depsMap = new Map<string | symbol, Set<()=>unknown>>()
    function track(key: string | symbol) {
        if (!ACTIVE_EFFECT) {
            return
        }
        let deps = depsMap.get(key)
        if (!deps) {
            deps = new Set()
            depsMap.set(key, deps)
        }
        deps.add(ACTIVE_EFFECT)
    }
    function trigger(key: string | symbol) {
        const deps = depsMap.get(key)
        if (deps) {
            deps.forEach(fn => fn())
        }
    }
    return new Proxy(obj, {
        get(target, key) {
            track(key)
            const result = Reflect.get(target, key)
            return (typeof result === 'object' && result !== null) ? reactive(result) : result
        },
        set(target, key, value) {
            const result = Reflect.set(target, key, value)
            trigger(key)
            return result
        }
    })
}末尾 
面试 
vue的响应式实现思路只有一点: 拦截get/set. 可能常见的面试题是这样(不一定全):
- vue2响应式实现和vue3的有什么不同
vue2使用Object.defineProperty, vue3对于基础类型使用简单的getter/setter对, 对于引用类型使用Proxy.
- vue2响应式实现有什么问题
- 效率低, 需要遍历对象的每一个key-value, 如果value是引用类型还要继续遍历.
- 响应式不充分, 对于原本不存在的key-value无法跟踪(可能有追问要跟踪怎么办, 使用Vue.set), 数组的push等方法也无法跟踪.
- vue3响应式实现有什么优点
- 效率高, 只需要执行一次, 如果遇到对象再生成一个Proxy.
- 响应式完全, 原本不存在的key-value也可以跟踪.
- ref和reactive有什么区别
- vue3为什么比vue2效率高
- ...
react? 
之前也写过react, react的响应式感觉没有vue的要好, 至少从理解角度来看, vue的要更好理解. 现在的话我虽然可能可以手写react的响应式, 不过估计会比vue要折磨很多.
react要求一个组件每次调用都应该有相同的响应式变量, 大概就是这样:
function TestComponent() {
    const [isLoading, setIsLoading] = useState(true)
    // ... 一些操作, 比如fetch
    if (isLoading) {
        return <div>loading...</div>
    }
    const [data, setData] = useState(...)
    return <div>
        {/* 展示data */}
    </div>
}这种就不行, 会警告还是报错有点忘了. 因为react依赖响应式变量的顺序, 如果其中一个组件突然少了个响应式变量, 那么后面的顺序全错了. react会执行两次组件, 并确保执行的结果相同.
个人理解, 我也不太清楚. 我从来没有觉得写react开心过, 离开上一家公司后根本不想碰react.