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

响应式原理的实现

原理

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

*/

exportfunction observe(data)

{

//不是对象

if(!isObject(data)) return;

//说明已经被观测

if(data._ob_ instanceof Observer) return;

return new Observer(data);

}

注意

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

数组响应式处理

exportlet 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地址

github.com/yuxuewen/vu…

结语

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

以上是 Vue2.0源码学习1:开发环境的搭建和响应式原理的实现 的全部内容, 来源链接: utcz.com/a/29667.html

回到顶部