Vue响应式原理
一.简介
Vue响应式的原理,其实就是基于ES5的Object静态方法: Object.defineProperty() 对这个方法做劫持,还有说法是代理,劫持数据的setter和getter.然后结合发布订阅模式,在数据发生变化的时候,通知页面进行更新
注意:由于 ES5的Object.defineProperty()方法不支持IE8,所以我们的Vue不兼容IE8以下版本
二.实现响应式源码
1. Object.defineProperty()介绍
MDN:方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
Object.defineProperty(obj, property, descriptor)
- obj: 要在其定义属性的对象
- property 要定义或修改的属性的名字
- descriptor 将被定义或修改的属性描述符(对象)
descriptor描述符有好几个选项可以设置的,我们这里只讲对象属性访问器 Getter和Setter
<!--
浏览器控制台
obj.name = 'zhangssan' ====> 监听到对象在设置新值
obj.name ====> 监听到对象在取值
-->
<script>
var obj = {
name: 'liuqiao',
age: 27
}
//返回这个对象
var newObj = Object.defineProperty(obj, 'name', {
get: function () {
console.log('监听到对象在取值');
return name;
},
set: function (newValue) {
console.log('监听到对象在设置新值');
}
});
console.log(newObj);
</script>
上述代码:我们定义了一个对象,然后通过Object.defineProperty()做数据劫持 劫持get和set方法,当我们改变对象中的一个值的时候, set方法会监听到,数据的变化,当取值的时候,get方法会监听到在取数据
2.实现Vue响应式源码
要实现响应式源码,我们需要构造三个类,负责各自的功能:
1.Vue类 创建Vue的构造函数
2.Compile类 创建专职页面解析的构造函数
3.Watch类 发布订阅模式,监听数据的改变和页面的变化
扫描二维码关注公众号,回复: 11355705 查看本文章
2.1 Object.keys()
返回指定对象中所有可枚举属性组成的数组
var data={name:'liuqiao',age:'27'};
var newObj=Object.keys(data)
console.log(newObj); //['name','age']
2.2 Object.values()
方法返回一个数组,成员是参数对象自身的所有可枚举属性的值(与Object.keys配套)
var data={name:'liuqiao',age:'27'};
var newObj=Object.values(data)
console.log(newObj); //['liuqiao','27']
2.3 Vue类的创建(创建一个Vue的构造函数)
支持响应式的核心源码,都依赖于Object.defineProperty()方法
/**
*伪代码
* var vm=new Vue({
* el:"#app",
* data:{
* name:'liuqiao',
* age:27
* }
* });
*/
//创建一个Vue的构造函数或者类
class Vue {
//构造函数
constructor(optinons) {
// 实例化Vue时传的是对象
this.$el = document.querySelector(optinons.el);
this.$data = optinons.data;
//实现代理属性,使得data中的数据能被劫持到
this.observer(this.$data);
//生成一个Watch实例
this._ev = new Watch();
// 解析
new Compile(this.$el, this);
}
/**
* 将传递过来的对象中的属性直接绑定到实例对象上
*
* observer(data)
*/
observer(data) {
/*
* 我们需要遍历data这个对象 data:{name:'liuqiao',age:27}
* 可以使用Object.keys()
*/
Object.keys(data).forEach(key => {
// this 当前对象
// key 属性的名字
// {} 将被定义或修改的属性描述符(对象)
Object.defineProperty(this, key, {
// 使用getter和setter监听数据的变化,也就是数据劫持
get() {
console.log("监听:正在进行取值操作");
//返回正在操作的数据
return data[key];
},
set(value) {
console.log("监听:正在进行赋值操作");
data[key] = value;
// 数据更新了, 触发事件
this._ev.$emit(key)
}
});
});
}
}
2.4 Compile类创建(创建一个页面解析的构造函数)
实现了响应式,我们做一个页面解析的操作,比如将 {{}} 双花括号解析需要成输出的值
//创建一个Compile的构造函数(专职于页面解析工作)
class Compile {
/**
*
* @param {DocumentFragment} el DOM 对象
* @param {Vue} vm Vue 实例对象
*/
constructor(el, vm) {
// 将vm绑定到当前Compile的实例对象上
this.vm = vm;
this.compile(el);
}
compile(el) {
//遍历el这个Dom对象子节点
el.childNodes.forEach(node => {
//得到所有的文本节点,在Dom中,文本节点的nodeType==3
//并且处理节点的文本内容,内容类似 {{ name }} 正则处理
if (node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent)) {
console.log(node.textContent);
// 获取到匹配到的文本节点之后,直接将内容修改
// {{name}} ===>liuqiao
// {{ age}} ===>28
// 1.得到{{}}表达式
//RegExp.$1是RegExp的一个属性,指的是与正则表达式匹配的第一个 子匹配(以括号为标志)字符串
let exp = RegExp.$1.trim();
// 2.将对应的exp数据设置到当前节点的textContent上
node.textContent = this.vm[exp];
console.log(node.textContent);
console.log(this.vm);
// 3. 监听 this.vm === new Vue 的实例
this.vm._ev.$on(exp, () => {
node.textContent = this.vm[exp]
})
}
//递归遍历所有的子节点,找完为止
if (node.childNodes) {
this.compile(node);
}
});
}
}
2.5 Watch类创建 (创建一个监听数据数据变化通知页面的构造函数)
//创建一个Watch类 监听数据变化
class Watch {
constructor() {
//存储消息
this.dep = {};
}
//订阅消息
/**
* @param {eventName} 事件名字
* @param {callback} 回调函数
*/
$on(eventName, callback) {
if (!this.dep[eventName]) {
this.dep[eventName] = [callback];
}
else {
this.dep[eventName].push(callback);
}
}
//发布消息
/**
* @param {eventName} 事件名字
* @param {payload} 发布消息内容
*/
$emit(eventName, payload) {
if (this.dep[eventName]) {
this.dep[eventName].forEach(cb => {
cb(payload);
});
}
}
}
2.6 测试源码
var vm = new Vue({
el: '#app',
data: {
name: 'liuqiao',
age: '27'
}
});
浏览器控制台,修改vm.name的值,会发现页面也跟着变化了,我们的Vue响应式源码就完成了,当然了,核心的内容思想是这样子的,只是比较简易版的响应式源码
三.面试中的Vue响应式原理
面试中,经常会有面试官问到这个Vue的响应式原理,我自己总结了一下,可以这么回答:
Vue之所以具有响应式原理,是因为我们的Vue对象在创建的时候,使用了Object.defineProperty()这样的一个ES5的的Object静态方法.把data数据中的所有数据进行遍历,进行劫持,然后进行getter和setter的封装,当我们Vue实例上的data数据发生变化的时候,就会触发setter的操作,在setter的操作中,我们可以触发监听器去更新真实的dom.
这个阶段是发生在Vue生命周期的beforeCreate和created之间的.
四,扩展Vue3响应式写法
Vue3的响应式写法,是基于es6的proxy代理器,可以拦截作用
Proxy: 在目标对象的外层搭建了一层拦截,外界对目标对象的某些操作,必须通过这层拦截
var data = {
name: 'liuqiao',
age: 27
}
var newObj = new Proxy(data, {
// 重写数据以在中间创建一个代理
get: function (obj, key) {
console.log('监听到对象在取值');
return name;
},
set: function (obj, key, newVal) {
console.log('监听到对象在设置新值');
}
});
console.log(newObj);