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

前言

最近参与一次关于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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353