【Vue源码学习】响应式原理探秘
最近准备开启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源码学习】响应式原理探秘的更多相关文章
- vue源码之响应式数据
分析vue是如何实现数据响应的. 前记 现在回顾一下看数据响应的原因. 之前看了vuex和vue-i18n的源码, 他们都有自己内部的vm, 也就是vue实例. 使用的都是vue的响应式数据特性及$w ...
- Vue2源码解读 - 响应式原理及简单实现
直接进入主题了,想必大家都知道实现vue响应式核心方法就是 Object.defineProperty,那就从它开始说 Object.defineProperty 缺点: 深度监听,需要递归到底,一次 ...
- 【Vue源码学习】依赖收集
前面我们学习了vue的响应式原理,我们知道了vue2底层是通过Object.defineProperty来实现数据响应式的,但是单有这个还不够,我们在data中定义的数据可能没有用于模版渲染,修改这些 ...
- Vue源码学习1——Vue构造函数
Vue源码学习1--Vue构造函数 这是我第一次正式阅读大型框架源码,刚开始的时候完全不知道该如何入手.Vue源码clone下来之后这么多文件夹,Vue的这么多方法和概念都在哪,完全没有头绪.现在也只 ...
- Dubbo源码学习--优雅停机原理及在SpringBoot中遇到的问题
Dubbo源码学习--优雅停机原理及在SpringBoot中遇到的问题 相关文章: Dubbo源码学习文章目录 前言 主要是前一阵子换了工作,第一个任务就是解决目前团队在 Dubbo 停机时产生的问题 ...
- Vue源码学习三 ———— Vue构造函数包装
Vue源码学习二 是对Vue的原型对象的包装,最后从Vue的出生文件导出了 Vue这个构造函数 来到 src/core/index.js 代码是: import Vue from './instanc ...
- Vue源码学习二 ———— Vue原型对象包装
Vue原型对象的包装 在Vue官网直接通过 script 标签导入的 Vue包是 umd模块的形式.在使用前都通过 new Vue({}).记录一下 Vue构造函数的包装. 在 src/core/in ...
- 《Vue 进阶系列之响应式原理及实现》
https://www.bilibili.com/video/av51444410/?p=5 https://github.com/amandakelake/blog/issues/63 https: ...
- 最新 Vue 源码学习笔记
最新 Vue 源码学习笔记 v2.x.x & v3.x.x 框架架构 核心算法 设计模式 编码风格 项目结构 为什么出现 解决了什么问题 有哪些应用场景 v2.x.x & v3.x.x ...
随机推荐
- JAVA获取当前日期所在的周一到周日的所有日期集合
/** * 获取当前日期所在的周一到周日的所有日期集合 * @return */ public static List<Date> getWeekDateList() { Calendar ...
- cmake配置MFC项目属性
MFC的使用 使用下面的代码设置为: # 设置MFC的使用 SET(CMAKE_MFC_FLAG 2) 这里的 2 代表: 在共享 DLL 中使用 MFC, 1代表在静态库中使用 MFC 设置字符集 ...
- 【LeetCode】1135. Connecting Cities With Minimum Cost 解题报告 (C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 Kruskal算法 日期 题目地址:https://l ...
- 【LeetCode】203. Remove Linked List Elements 解题报告(Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 双指针 递归 日期 题目地址:https://lee ...
- Adversarial Training with Rectified Rejection
目录 概 主要内容 rejection 实际使用 代码 Pang T., Zhang H., He D., Dong Y., Su H., Chen W., Zhu J., Liu T. Advers ...
- elementUI表单嵌套表格并对每行进行校验
elementUI表单嵌套表格并对每行进行校验 elementUI 表单嵌套表格并进行校验. 目录 效果展示 代码链接 关键代码 完整代码 效果展示 先看看这是不是需要的效果^_^ 如图,Elem ...
- Linux磁盘实用指令
磁盘情况查询 df/du 查询磁盘整体占用情况 df 指令:df -h 查询目录磁盘占用情况 du 基本语法 指令:du [选项] 指定目录 常用选项 指定目录不填则默认当前目录 选项 功能 -s 指 ...
- Java初学者作业——编写Java程序,实现判断所输入字符的类型(数字、小写字母、大写字母或其他字符)
返回本章节 返回作业目录 需求说明: 编写Java程序,实现判断所输入字符的类型(数字.小写字母.大写字母或其他字符) 实现思路: 声明变量c,用于存储用户输入的字符. 通过Scanner接收用户输入 ...
- Webpack有哪些常见的Loader?他们是解决什么问题的?
先来了解一下Loader,webpack是属于模块化方案,他能让任意类型的文件都能运行在浏览器中,怎么做到呢?这时就有了loader 定义: loader 用于对模块的源代码进行转换.loader 可 ...
- x86-2-保护模式(protect mode)
x86-2-保护模式(protect mode) 引入保护模式的原因: 操作系统负责计算机上的所有软件和硬件的管理,它可以百分百操作计算机的所有内容.但是,操作系统上编写的用户程序却应当有所限制,比如 ...