赞
踩
Vue响应式的原理,其实就是基于ES5的Object静态方法: Object.defineProperty() 对这个方法做劫持,还有说法是代理,劫持数据的setter和getter.然后结合发布订阅模式,在数据发生变化的时候,通知页面进行更新
注意:由于 ES5的Object.defineProperty()方法不支持IE8,所以我们的Vue不兼容IE8以下版本
MDN:方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
Object.defineProperty(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方法会监听到在取数据
要实现响应式源码,我们需要构造三个类,负责各自的功能:
1.Vue类 创建Vue的构造函数
2.Compile类 创建专职页面解析的构造函数
3.Watch类 发布订阅模式,监听数据的改变和页面的变化
返回指定对象中所有可枚举属性组成的数组
var data={name:'liuqiao',age:'27'};
var newObj=Object.keys(data)
console.log(newObj); //['name','age']
方法返回一个数组,成员是参数对象自身的所有可枚举属性的值(与Object.keys配套)
var data={name:'liuqiao',age:'27'};
var newObj=Object.values(data)
console.log(newObj); //['liuqiao','27']
支持响应式的核心源码,都依赖于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) } }); }); } }
实现了响应式,我们做一个页面解析的操作,比如将 {{}} 双花括号解析需要成输出的值
//创建一个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); } }); } }
//创建一个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); }); } } }
var vm = new Vue({
el: '#app',
data: {
name: 'liuqiao',
age: '27'
}
});
浏览器控制台,修改vm.name的值,会发现页面也跟着变化了,我们的Vue响应式源码就完成了,当然了,核心的内容思想是这样子的,只是比较简易版的响应式源码
面试中,经常会有面试官问到这个Vue的响应式原理,我自己总结了一下,可以这么回答:
Vue之所以具有响应式原理,是因为我们的Vue对象在创建的时候,使用了Object.defineProperty()这样的一个ES5的的Object静态方法.把data数据中的所有数据进行遍历,进行劫持,然后进行getter和setter的封装,当我们Vue实例上的data数据发生变化的时候,就会触发setter的操作,在setter的操作中,我们可以触发监听器去更新真实的dom.
这个阶段是发生在Vue生命周期的beforeCreate和created之间的.
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);
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。