手写Vue2.0源码-面试官到底想知道啥

84 阅读3分钟

前言

本人是android开发 公司前端业务比较多 所以也加入了vue的大军。闲暇之余去学习一下vue源码 加强理解Vue2.0版本的源码 学习优秀源码的思路(后续会加入 3.0 版本)

适用人群: 想深入理解vue 面试八股文同学

正文

废话我也不多说了,网上相关的文章太多了我就直接介绍vue 响应式实现思路。比如面试官问你的时候?

我说说我的个人理解啊,从整个技术设计思路来说核心用到了2点 Object.defineProperty+观察者模式 这样回答到了精髓,起码说明你是看过源码了解过得。然后按照以下几点阐述

  1. 数据劫持:Vue 2 使用 Object.defineProperty 方法来劫持(defineProperty)对象的属性,使之具有 getter 和 setter 方法。在 Vue 实例初始化时,会遍历数据对象的所有属性,将其转换为响应式属性。当访问这些属性时,Vue 会自动追踪依赖,并建立与视图的关联。

  2. 依赖追踪:Vue 2 中的依赖追踪是通过一个全局的 Dep 对象和每个属性对应的 Watcher 完成的。在数据劫持过程中,每个响应式属性都会被绑定一个 Dep 对象。而每个 Watcher 则代表一个依赖(如视图或计算属性)。当属性被访问时,会触发 getter 方法,该 getter 方法会将当前的 Watcher 添加到对应的 Dep 对象的依赖列表中。这样,在属性发生改变时,就可以通知对应的 Watcher 更新。

  3. 数据更新:当响应式属性发生变化时,会触发属性的 setter 方法。setter 方法会通知绑定的 Dep 对象,然后调用 Dep 对象的 notify 方法,进而触发所有依赖的 Watcher 的更新操作。在更新过程中,会重新渲染与该属性相关的视图或计算属性,保持视图的同步更新。

  4. 响应式侦测的局限性:Vue 2 的响应式侦测是在初始化阶段完成的,对于后续添加的属性或数组索引的变动无法进行响应式处理。为了解决这个问题,Vue 提供了一些特殊的方法(如 Vue.set 或 Array.prototype.$set)来实现对新增属性或索引的响应式支持。

上面这一套说出来 面试官基本也没啥问题了,如果变态的面试官需要你手写或上机操作呢,当然我们还是需要自己手动去敲代码实现的。

源码如下

// VueSrc.js


class Vue{
    constructor(options){
        this.options=options
        this.data=options.data
        this.el=options.el
        //数据劫持 使用Object.defineProperty实现
        Object.keys(this.data).forEach((key)=>{
            this.defineReactive(this.data,key)
        })
        //将DOM转成 文档片段
      const fragment= this.nodeToFragment(document.querySelector(this.el))
       //将v-model指令编译加入node
       console.log('frag',fragment)
       this.compile(fragment)
       document.body.appendChild(fragment)
    }

    nodeToFragment(node){
       const fragment= document.createDocumentFragment()
       let childNode
       while((childNode=node.firstChild)){
            console.log('root node',node.firstChild)
            this.compileNode(childNode)
            fragment.appendChild(childNode)
       }
       return fragment
    }

    

    compileNode(node){
        if(node.nodeType===1){
            const attrs=node.attributes;
            for(let i=0;i<attrs.length;i++){
                let attr=attrs[i]
                if('v-model'===attr.name){
                    const key=attr.value
                   node.value=this.data[key]
                   node.addEventListener('input',(event)=>{
                   this.data[key]=event.target.value
                   })
                }
            }
        }else if(node.nodeType===3){
            // 处理文本节点
        const reg = /\{\{(.*)\}\}/;
        const value = node.textContent;
         const match=value.match(reg)
        if (match) {
          const key = match[1].trim();
          node.textContent = value.replace(reg, this.data[key]);
  
          // 创建 watcher 监听数据变化
         const watcher= new Watcher(this.data, key, (newValue) => {
           
            node.textContent = newValue;
          });
          console.log('nodeType>>3',watcher)
        }
            

        }
    }

    compile(fragment){
        let childs=fragment.childNodes;
        console.log('childs',childs)
        Array.from(childs).forEach(node=>{
            if(node.nodeType===1){
                this.compileNode(node)
                this.compile(node)
            }else if(3===node.nodeType){
                this.compileNode(node)
            }
        })
    }


     // 劫持属性,实现响应式
    defineReactive(obj, key) {
        let value = obj[key];
        const dep=new Dep()
        Object.defineProperty(obj,key,{
            get(){
                console.log('get>>>',key,Dep.target)
                if(Dep.target){
                    dep.addSub(Dep.target)
                }
                return value
            },
            set(newVal){
                console.log('set>>>',key,newVal)
                if(newVal!==value){
                    value=newVal
                    dep.notify()
                }
            }
        })
    }
   

}
class Dep{

    constructor(){
        this.subs=[]
    }

    addSub(sub){
        this.subs.push(sub)
    }
    notify(){
        this.subs.forEach((wachter)=>{
            wachter.update()
        })
    }
}

class Watcher{
    constructor(obj,key,callBack){
        this.obj=obj;
        this.key=key;
        this.callBack=callBack

        Dep.target=this;
        this.value=obj[key]
        Dep.target=null;
    }

    update() {
        const newVal=this.obj[this.key]
        if(newVal!==this.value){
            this.value=newVal
            this.callBack(newVal)
        }
    }
}

再写一个测试html代码


 <body>
    <div id="app"><input type="text" v-model="message" /><p>{{ message }}</p>
    <input type="text" v-model="name" /><p>{{ name }}</p></div>
    <script  src="./VueSrc.js"></script>
    <script >
      let app = new Vue({ el: '#app', data: { message: 'hello vue' ,name:'curry'} })
    </script>
  </body>

小结

自己多写几遍 遇到不懂得函数多查查,哪怕手写 你写个大概出来面试也绝对是能通过的

最后如果觉得本文有帮助 记得点赞三连哦 十分感谢!