美文网首页
Vue2.0源码学习1:开发环境的搭建和响应式原理的实现

Vue2.0源码学习1:开发环境的搭建和响应式原理的实现

作者: 泰然自若_750f | 来源:发表于2020-07-07 21:58 被阅读0次

    前言

    最近参与一次关于Vue2.0的集中学习。主要学习了以下内容。

    • 响应式原理的实现
    • vue的模板编译
    • 依赖收集和异步更新机制
    • Vue dom算法的实现

    现在对学习内容进行一次集中总结整理,方便以后的学习。

    开发环境的搭建

    rollup

    Rollup 是一个 JavaScript 模块打包器,可以将小块代码编译成大块复杂的代码, rollup.js更专注于Javascript类库打包。

    • 安装rollup

      npm install @babel/preset-env @babel/core rollup rollup-plugin-babel rollup-plugin-serve cross-env -D

    • 配置文件rollup.config.js

    /**
     * rollup 的配置文件
     */
    import babel from 'rollup-plugin-babel';
    import serve from 'rollup-plugin-serve';
    export default {
        //入口
        input: './src/index.js',
        output: {
            format: 'umd', // 模块化类型
            file: 'dist/vue.js', 
            name: 'Vue', // 打包后的全局变量的名字
            sourcemap: true //源码映射
        },
        plugins: [
            babel({
                exclude: 'node_modules/**' //忽略打包文件
            }),
            process.env.ENV === 'development'?serve({
                open: true,
                openPage: '/public/index.html',
                port: 8000,
                contentBase: ''
            }):null
        ]
    }
    
    • 脚本文件
      在package.json 中添加
     "scripts": {
        "build:dev": "rollup -c",//打包
        "serve": "rollup -c -w" //启动
      },
    
    • 启动
      在控制台执行npm run serve
    image

    响应式原理的实现

    原理

    Vue 2.0依赖Object.defineProperty 数据劫持来实现数据响应式的。

    var obj={
      a:1,
      b:2,
      c:3,
      d:[1],
    }
    Object.keys(obj).forEach(key=>{
      let value=obj[key];
      
      Object.defineProperty(obj,key,{
    
        get(){
           console.log(`获取obj${key}值${value}`); //获取obja值1
           return value;
        },
        set(newValue){
            console.log(`obj 中${key} 被赋值 ${newValue}`) //obj 中b 被赋值 3
    
        }
    })
    
    })
    obj.a;
    obj.b=3
    obj.d.push(2); //不能对push 方法进行数据劫持
    

    在上述实例 我们发现可以通过Object.defineProperty对数据进行劫持,进行响应式操作梳理,但也有如下问题。

    • 如果对象事多层嵌套,需要进行递归处理,否则不能监听到数据的更新。所以我们在开发中避免在 data中数据层级太深,影响性能。

    • 对数组中的方法不能进行数据劫持。push,pop,unshift,shift,splice,reverse,sort,需要进行特殊处理。

    实现

    Observer

    /**
     * 数据观测
     */
    import {isObject} from '../util/index.js'
    import {newArrayProto} from './array'
    import Dep from './dep.js'
     class Observer{
           constructor(data)
           {
                //将 this 挂载在data 上 可以使用ob 调用方法
                 Object.defineProperty(data,'_ob_',{
                       enumerable:false,
                       configurable:false,
                       value:this
                 })
                 if(data instanceof Array)
                 {
                     //数组是 [].__proto__=Array.prototype
                     //更改需要观测数组的原型链
                     data.__proto__=newArrayProto;
                     this.observeArray(data);
                 }
                 else{
                     //监测对象
                      this.walk(data);
                 }
                 
           }
           /**
            * 观测数组
            * @param {*} data 
            */
           observeArray(data){
    
              for(let i=0;i<data.length;i++)
              {
                  observe(data[i])
              }
    
           }
           /**
            * 遍历监测对象
            * @param {*} data 
            */
           walk(data){
              //  Object.keys 不可遍历不可枚举类型 所以 _ob_ 不会被遍历
             Object.keys(data).forEach(key=>{
                 defineReactive(data,key,data[key])
             })
           }
     }
     /**
      * 数据监测
      * @param {*} data 
      * @param {*} key 
      * @param {*} value 
      */
     function defineReactive(data,key,value){
          let dep=new Dep();
           observe(value);// value 还是对象,递归
           Object.defineProperty(data,key,{
               get(){
                   return value;
    
               },
               set(newValue){
                   if(newValue===value) return;
                   //对于赋值的如果是对象 进行响应式监测
                   observe(newValue);
                   value=newValue;
               }
           })
    
     }
     /**
      * 观测数据方法
      * @param {*} data 
      */
    
     export function observe(data)
     {
           //不是对象
           if(!isObject(data)) return;
           //说明已经被观测
           if(data._ob_ instanceof Observer) return;
           return new Observer(data);
     }
    
    
    

    注意

    • 如果观察对象还是一个对象,需要递归进行observe。
    • 观测后该对象加上ob标记,指向当前class Observer的实例。既可以方便调用实例中的方法,又可以标识当前对象已经被观测,避免重复观测。
    • 如果检测到数据类型是数组,修改被观测数组上的原型链,具体方法详看如下代码

    数组响应式处理

    export let newArrayProto=Object.create(Array.prototype);
     let oldMethods=Array.prototype;
     //需要重写数组的方法 
     let methods=[
           'push',
           'pop',
           'unshift',
           'shift',
           'splice',
           'sort',
           'reverce'
     ];
     methods.forEach((method)=>{
           newArrayProto[method]=function(...args){
                  //依然执行原数组原型上的方法
                  let result= oldMethods[method].call(this,...args);
                  let insered=null,//新增的元素
                      ob=this._ob_
                  switch(method){
                       case 'push':
                       case 'unshift':
                           insered=args;
                           break;
                        case 'splice':
                            insered=args.splice(2);
                            break;
                        default:
                            break;
                       
                  }
                  //对于新增元素进行观测
                  insered && ob.observeArray(insered);
                  return result;
           }
     })
    

    思路

    • 创建新的数组原型对象。

      let newArrayProto=Object.create(Array.prototype);

    • 重写原型对象的7个方法( push,pop,unshift,shift,splice,reverse,sort),这七个方法执行时还是要调用数组原型上的方法,同时
      也可以实现触发这七个方法,触发更新。需要注意的是push,unshift,splice三个方法会新增数组数据,因此也要对新增数据进行响应式观测。

    • 修改数组原型链

             if(data instanceof Array)
                 {
                     //数组是 [].__proto__=Array.prototype
                     //更改需要观测数组的原型链
                     data.__proto__=newArrayProto;
                     this.observeArray(data);
                 }
    

    调用 initState

    在是生命周期 beforeCreate 和 created之间调用,进行数据响应式处理,然后再进行模板编译和挂载($mount),下一节总结在Vue.prototype.mount 实现的功能。

     Vue.prototype._init=function(options){
                 const vm=this;
                 //将参数挂载到 vm 上
                 vm.$options = mergeOptions(vm.constructor.options,options);
                 callHook(vm,'beforeCreate');
                 initState(vm);
                 callHook(vm,'created');
                if(vm.$options.el)
                {
                     this.$mount(vm.$options.el);
                }
            }
    

    github地址

    https://github.com/yuxuewen/vue2.0_source.git

    结语

    以上是总结的vue2.0响应式原理的实现,熟悉源码并非是真正自己去实现一个Vue,而是通过作者的设计思路来拓展我们的视野,对平时开发有很大意义。本人前端小白,如果错误,请谅解,并欢迎批评指正。
    掘金地址:https://juejin.im/user/5efd45a1f265da22f511c7f3/posts

    相关文章

      网友评论

          本文标题:Vue2.0源码学习1:开发环境的搭建和响应式原理的实现

          本文链接:https://www.haomeiwen.com/subject/xahptktx.html