最近准备开启Vue的源码学习,并且每一个Vue的重要知识点都会记录下来。我们知道Vue的核心理念是数据驱动视图,所有操作都只需要在数据层做处理,不必关心视图层的操作。这里先来学习Vue的响应式原理,Vue2.0的响应式原理是基于Object.defineProperty来实现的。Vue通过对传入的数据对象属性的getter/setter方法来监听数据的变化,通过getter进行依赖收集,setter方法通知观察者,在数据变更时更新视图。

1.使用rollup搭建开发环境

安装rollup环境

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

配置rollup

// rollup.config.js
import babel from "rollup-plugin-babel"
import serve from "rollup-plugin-serve" export default {
input: './src/index.js', // 打包入口
output: {
file: 'dist/umd/vue.js', //出口路径
name: 'Vue' , // 指定打包后全局变量的名字
format: 'umd' , // 统一模块规范
sourcemap: true, // es6->es5 开启源码调试,可以找到源代码报错位置
},
plugins:[ //使用的插件
babel({
exclude:'node_modules/**' //排除文件
}),
process.env.ENV==='development'?serve({
open:true,
openPage:'/public/index.html', //默认启动html的路径
port:3000,
contentBase: ''
}):null
]
}

项目搭建

这里搭建了一个Vue项目,主要代码都放在src下面

2.响应式原理探秘

1.Object.defineProperty

想要了解Vue2的响应式原理,我们得先来简单了解一下Object.defineProperty

Object.defineProperty()的作用就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性,默认情况下,使用 Object.defineProperty() 添加的属性值是不可修改(immutable)的。

Object.defineProperty(obj,prop,desc)
  • obj:需要定义属性的对象
  • prop:当前需要定义的对象属性
  • desc:属性描述符

该方法最低兼容到IE8,这也就是Vue最低兼容到IE8的原因。

2.Vue初始化过程

我们先来分析一下Vue的初始化都做了哪些事情,我们在使用Vue的时候一般都会这样写:

const vm = new Vue({
el:'#app',
data(){
return {
name: '南玖'
}
}
})

我们知道Vue只能通过new关键字初始化,所以Vue应该是一个构造函数,然后会调用this._init方法进行初始化过程,OK,我们自己可以来实现一下

import {initMixin} from "./init"
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)) {
warn('Vue is a constructor and should be called with the `new` keyword')
} // 开发环境下不通过new进行调用会告警
/*调用_init初始化,这个方法是挂在Vue原型上的*/
this._init(options)
// options就是new Vue是传入的参数,包括:el,data,computed,watch,methods...
} initMixin(Vue) // 给Vue原型上添加_init方法
export default Vue

我们接着来写这个init.js,这里主要是给Vue原型上挂上方法:_init,$mount,_render,$nextTick

import {initState} from "./state"

//initMixin就做了一件事,就是给Vue原型挂上_init方法
export function initMixin(Vue){
// 初始化流程
Vue.prototype._init = function (options){
// console.log(options)
const vm = this // vue中使用this.$options
vm.$options = options // /*初始化props、methods、data、computed与watch*/
initState(vm)
// 这里先看initState,后面还会有很多初始化事件:初始化生命周期、初始化事件、初始化render等等
}
}

初始化data,这里我们知道Vue支持传入的data可以是一个对象也可以是一个方法,所以我们需要判断一下传入的data的数据类型,是对象就直接传给observe,是方法就先执行再将返回值传给observe

function initData(vm) {
console.log('初始化数据',vm.$options.data)
// 数据初始化
let data = vm.$options.data;
data = vm._data = typeof data === 'function' ? data.call(this) : data
// 对象劫持,用户改变了数据 ==》 刷新页面
// MVVM模式 数据驱动视图 // Object.definePropety() 给属性增加get和set方法
observe(data) //响应式原理
}

3.响应式原理

将数据变成可观察的,我们都知道Vue2是通过Object.defineProperty来实现的。ok,这里我们就进入了这次的重点原理讲解:我们知道Object.defineProperty这个方法,只能劫持对象不能劫持数组,所以这里我们判断一下数据类型,数组需要单独处理,重写数组原型上的方法,在数组变更时在通知到订阅者

// 把data中数据使用Object.defineProperty重新定义 es5
// Object.defineProperty 不能兼容IE8及以下,所以vue2无法兼容IE8版本
import {isObject,def} from "../util/index"
import {arrayMethods} from "./array.js" // 数组方法
export function observe (data) {
// console.log(data,'observe')
let isObj = isObject(data)
if(!isObj) return
return new Observer(data) // 观测数据
} class Observer {
constructor(v){
// 如果数据层次过多,需要递归去解析对象中的属性,依次增加set和get方法
def(v,'__ob__',this)
if(Array.isArray(v)) {
// 如果是数组的话并不会对索引进行监测,因为会导致性能问题
// 前端开发中很少去操作索引 push shift unshift
v.__proto__ = arrayMethods
// 如果数组里放的是对象,再进行监测
this.observerArray(v)
}else{
//对象则调用walk进行劫持
this.walk(v)
} }
observerArray(value) {
for(let i=0; i<value.length;i++) {
observe(value[i])
}
}
/* 遍历每一个对象并且为它们绑定getter与setter。该方法只有在数据类型为对象时才能被调用 */
walk(data) {
let keys = Object.keys(data); //获取对象key
keys.forEach(key => {
defineReactive(data,key,data[key]) // 定义响应式对象
})
}
} function defineReactive(data,key,value){
observe(value) // 递归实现深度监测,注意性能
Object.defineProperty(data,key,{
get(){
// 依赖收集,下期探讨
//获取值
return value
},
set(newV) {
//设置值
if(newV === value) return
observe(newV) //继续劫持newV,用户有可能设置的新值还是一个对象
value = newV
/*dep对象通知所有的观察者,下期探讨*/
//dep.notify()
console.log('值变化了',value)
}
})
}

4.数组方法重写


// 重写数组的7个方法: push,pop,shift,unshift,reverse,sort,splice会导致数组本身改变 let oldArrayMethods = Array.prototype
// value.__proto__ = arrayMethods
// arrayMethods.__proto__ = oldArrayMethods
export let arrayMethods = Object.create(oldArrayMethods) const methods = [
'push','pop','shift','unshift','reverse','sort','splice'
] methods.forEach(method=>{
arrayMethods[method] = function(...args) {
console.log('用户调用了:'+method,args)
const res = oldArrayMethods[method].apply(this, args) // 调用原生数组方法
// 添加的元素可能还是一个对象 let inserted = args //当前插入的元素
//数组新插入的元素需要重新进行observe才能响应式
let ob = this.__ob__
switch (method) {
case 'push':
case 'unshift':
inserted = args
break;
case 'splice':
inserted = args.slice(2)
break;
default:
break;
}
if(inserted) {
ob.observerArray(inserted) //将新增属性继续
} console.log('数组更新了:'+ JSON.stringify(inserted))
//通知所有注册的观察者进行响应式处理,这里下期再来探讨
// ob.dep.notify()
return res
}
})

OK,写到这里我们可以来测试一下我们的Vue了

let vm = new Vue({
el:'#app',
data(){
return{
a:1,
b:{name:'nanjiu'},
c:[{name:'front end'}]
}
},
computed:{}
})
vm._data.a = 2
vm._data.c.push({name:'sss'})

这里控制台应该会打印出如下内容:

这样Vue的数据响应式,我们就算实现了,但这里看着有点别扭,我们希望操作Vue的data里的数据可以直接通过this来获取,而不是通过this._data来获取,这个很简单,我们只需要再做一层代理就可以实现了。

5.代理

export function proxy (target,sourceKey,key) {
// target: 想要代理到的目标对象,sourceKey:想要代理的对象
const _that = this
Object.defineProperty(target, key, {
enumerable: true,
configurable: true,
get: function(){
return _that[sourceKey][key]
},
set: function(v){
_that[sourceKey][key] = v
}
})
}

然后再initData里面调用该方法

function initData(vm) {
console.log('初始化数据',vm.$options.data)
// 数据初始化
let data = vm.$options.data;
data = vm._data = typeof data === 'function' ? data.call(this) : data
// 对象劫持,用户改变了数据 ==》 刷新页面
// MVVM模式 数据驱动视图
Object.keys(data).forEach(i => {
proxy.call(vm,vm,'_data',i)
})
// Object.definePropety() 给属性增加get和set方法
observe(data) //响应式原理
}

然后我们就可以愉快的使用this直接去访问data里面的数据了~

3.总结

OK,Vue的响应式原理我们就算全都实现了一遍,Vue2的响应式原理主要是通过Object.defineProperty来实现的,但这个方法有缺陷,不能劫持数组,所以对数据需要单独处理,在Vue3中,底层把响应式处理改成了通过proxy来实现,这个方法对数组劫持也同样适用。这里我们只探讨了Vue是如何进行响应式处理,至于它如何收集依赖,以及如何通知视图更新我们下期再来一起学习吧~

觉得文章不错,可以点个赞呀_ 另外欢迎关注留言交流~

【Vue源码学习】响应式原理探秘的更多相关文章

  1. vue源码之响应式数据

    分析vue是如何实现数据响应的. 前记 现在回顾一下看数据响应的原因. 之前看了vuex和vue-i18n的源码, 他们都有自己内部的vm, 也就是vue实例. 使用的都是vue的响应式数据特性及$w ...

  2. Vue2源码解读 - 响应式原理及简单实现

    直接进入主题了,想必大家都知道实现vue响应式核心方法就是 Object.defineProperty,那就从它开始说 Object.defineProperty 缺点: 深度监听,需要递归到底,一次 ...

  3. 【Vue源码学习】依赖收集

    前面我们学习了vue的响应式原理,我们知道了vue2底层是通过Object.defineProperty来实现数据响应式的,但是单有这个还不够,我们在data中定义的数据可能没有用于模版渲染,修改这些 ...

  4. Vue源码学习1——Vue构造函数

    Vue源码学习1--Vue构造函数 这是我第一次正式阅读大型框架源码,刚开始的时候完全不知道该如何入手.Vue源码clone下来之后这么多文件夹,Vue的这么多方法和概念都在哪,完全没有头绪.现在也只 ...

  5. Dubbo源码学习--优雅停机原理及在SpringBoot中遇到的问题

    Dubbo源码学习--优雅停机原理及在SpringBoot中遇到的问题 相关文章: Dubbo源码学习文章目录 前言 主要是前一阵子换了工作,第一个任务就是解决目前团队在 Dubbo 停机时产生的问题 ...

  6. Vue源码学习三 ———— Vue构造函数包装

    Vue源码学习二 是对Vue的原型对象的包装,最后从Vue的出生文件导出了 Vue这个构造函数 来到 src/core/index.js 代码是: import Vue from './instanc ...

  7. Vue源码学习二 ———— Vue原型对象包装

    Vue原型对象的包装 在Vue官网直接通过 script 标签导入的 Vue包是 umd模块的形式.在使用前都通过 new Vue({}).记录一下 Vue构造函数的包装. 在 src/core/in ...

  8. 《Vue 进阶系列之响应式原理及实现》

    https://www.bilibili.com/video/av51444410/?p=5 https://github.com/amandakelake/blog/issues/63 https: ...

  9. 最新 Vue 源码学习笔记

    最新 Vue 源码学习笔记 v2.x.x & v3.x.x 框架架构 核心算法 设计模式 编码风格 项目结构 为什么出现 解决了什么问题 有哪些应用场景 v2.x.x & v3.x.x ...

随机推荐

  1. JAVA获取当前日期所在的周一到周日的所有日期集合

    /** * 获取当前日期所在的周一到周日的所有日期集合 * @return */ public static List<Date> getWeekDateList() { Calendar ...

  2. cmake配置MFC项目属性

    MFC的使用 使用下面的代码设置为: # 设置MFC的使用 SET(CMAKE_MFC_FLAG 2) 这里的 2 代表: 在共享 DLL 中使用 MFC, 1代表在静态库中使用 MFC 设置字符集 ...

  3. 【LeetCode】1135. Connecting Cities With Minimum Cost 解题报告 (C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 Kruskal算法 日期 题目地址:https://l ...

  4. 【LeetCode】203. Remove Linked List Elements 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 双指针 递归 日期 题目地址:https://lee ...

  5. Adversarial Training with Rectified Rejection

    目录 概 主要内容 rejection 实际使用 代码 Pang T., Zhang H., He D., Dong Y., Su H., Chen W., Zhu J., Liu T. Advers ...

  6. elementUI表单嵌套表格并对每行进行校验

    elementUI表单嵌套表格并对每行进行校验 elementUI 表单嵌套表格并进行校验. 目录 效果展示 代码链接 关键代码 完整代码 效果展示 先看看这是不是需要的效果^_^ ​ 如图,Elem ...

  7. Linux磁盘实用指令

    磁盘情况查询 df/du 查询磁盘整体占用情况 df 指令:df -h 查询目录磁盘占用情况 du 基本语法 指令:du [选项] 指定目录 常用选项 指定目录不填则默认当前目录 选项 功能 -s 指 ...

  8. Java初学者作业——编写Java程序,实现判断所输入字符的类型(数字、小写字母、大写字母或其他字符)

    返回本章节 返回作业目录 需求说明: 编写Java程序,实现判断所输入字符的类型(数字.小写字母.大写字母或其他字符) 实现思路: 声明变量c,用于存储用户输入的字符. 通过Scanner接收用户输入 ...

  9. Webpack有哪些常见的Loader?他们是解决什么问题的?

    先来了解一下Loader,webpack是属于模块化方案,他能让任意类型的文件都能运行在浏览器中,怎么做到呢?这时就有了loader 定义: loader 用于对模块的源代码进行转换.loader 可 ...

  10. x86-2-保护模式(protect mode)

    x86-2-保护模式(protect mode) 引入保护模式的原因: 操作系统负责计算机上的所有软件和硬件的管理,它可以百分百操作计算机的所有内容.但是,操作系统上编写的用户程序却应当有所限制,比如 ...