前言
最近参与一次关于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
*/
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
网友评论