## 响应式原理
Vue内部使用了Object.defineProperty()来实现数据响应式,通过这个函数可以监听到set和get事件
~~~
var data = { name:'xxx'};
observe(data);
let name = data.name //get
data.name="yyy";
function observe(obj){
//判断类型
if(!obj || typeof obj !=='object'){
return;
}
Object.keys(obj).forEach((key)=>{
defineReactive(obj,key, obj[value]);
})
}
function defineReactive(obj, key, valu){
//递归子属性
observe(val);
Object,defineProperty(obj,key,{
//可枚举
enumerable: true,
//可配置
configurable: true,
//自定义函数
get:function reactiveGetter(){
console.log('get value');
return val;
},
set:function reactiveSetter(newVal){
console.log('change value');
val = newVal
}
})
}
~~~
代码实现了如何监听数据的set和get,仅仅如此是不够的,因为**自定义的函数一开始是不会执行的**。**只有先执行了依赖收集,才能在属性更新的时候派发更新**。
我们需要先触发依赖收集
~~~
<div>{{name}}</div>
~~~
在解析如上模板代码时,遇到{{name}}就会进行依赖收集。
实现一个**Dep类,用于解耦属性的依赖收集和派发更新操作**。
~~~
//通过Dep解耦属性的依赖和更新操作
class Dep{
constructor(){
this.subs = []; //所有的订阅列表(依赖列表)(订阅某属性的Watcher列表)
}
//添加依赖
addSub(sub){
this.subs.push(sub);
}
//更新
notify(){
this.subs.forEach((sub)=>{
sub.update();
})
}
}
//全局属性,通过该属性配置当前的Watcher(观察者)
Dep.target = null;
~~~
**当需要依赖收集的时候调用addSub,当需要派发更新的时候调用notify**。
Vue组件**挂载时添加响应式**的过程:
1. 组件挂载时,先对所有**需要的属性**调用Object.defineProperty()
2. 实例化Watcher(观察者),**传入组件更新的回调**。三种watcher(渲染watcher,计算watcher和侦听器watcher)
3. 实例化过程中,会对**模板中的属性进行求值,触发依赖收集**。
触发依赖收集的操作
~~~
class Watcher{
constructor(obj, key, cb){
//将Dep.target指向自己
//然后触发属性的getter添加监听
//最后将Dep.target置空
Dep.target = this;
this.cb = cb;
this.obj = obj;
this.key = key;
this.value = obj[key]
Dep.target = null;
}
update(){
//获得新值
this.value = this.obj[this.value];
//调用update方法更新dom
this.cb(this.value)
}
}
~~~
以上是Watcher的简单实现,在执行构造函数的时候,将Dep.target执行自身,从而使得**收集到了对应的Watcher**,**在派发更新的时候取出对应的Watcher然后执行update函数**。
接下来对defineReactive函数进行改造,在自定义函数中添加**依赖收集和派发更新**相关的代码
~~~
var data = { name:'xxx'};
observe(data);
let name = data.name //get
data.name="yyy";
function observe(obj){
//判断类型
if(!obj || typeof obj !=='object'){
return;
}
Object.keys(obj).forEach((key)=>{
defineReactive(obj,key, obj[value]);
})
}
function defineReactive(obj, key, valu){
//递归子属性
observe(val);
let dp = new Dep();
Object.defineProperty(obj,key,{
//可枚举
enumerable: true,
//可配置
configurable: true,
//自定义函数
get:function reactiveGetter(){
console.log('get value');
//将Watcher添加到订阅
if(Dep.target){
dp.addSub(Dep.target);
}
return val;
},
set:function reactiveSetter(newVal){
console.log('change value');
val = newVal;
//执行watcher的update方法
dp.notify();
}
})
}
~~~
简易的数据响应式,核心思路:**手动触发一次属性的getter来实现依赖收集**。
## Object.defineProperty的缺陷
如果通过**下标方式修改数组数据**或者**给对象新增属性**并不会触发组件的重新渲染。
因为**Object.defineproperty不会拦截到这些操作**,更确切的说,对于数组而言,**大部分操作都是拦截不到的**,只是Vue内部通过**重写函数的方式解决了这个问题**。
对于第一个问题,Vue提供了一个API解决vm.$set;
## 编译过程
大家在使用Vue开发的过程中,基本都是使用模板的方式,那么模板时怎么在浏览器中运行的吗?
模板时为了方便开发者进行开发,把模板丢到浏览器中肯定是不能运行的。
**Vue通过编译器将模板通过几个阶段最终编译为render函数,然后通过执行render函数生成vdom,最终映射为真是的DOM**
三个阶段
* 将模板解析为AST
* 优化AST
* 将AST转为render函数
第一阶段中,通过**各种各样的正则表达式去匹配模板中的内容**,然后将**内容提取出来做各种逻辑操作**,接下来生成一个**最基本的AST对象**。
~~~
{
// 类型
type: 1,
// 标签
tag,
// 属性列表
attrsList: attrs,
// 属性映射
attrsMap: makeAttrsMap(attrs),
// 父节点
parent,
// 子节点
children: []
}
~~~
然后会根据这个最基本的AST对象中的属性,进一步扩展AST
当然在这一阶段中,还会进行其他的一些判断逻辑。
优化AST的阶段:只是对节点进行了静态内容提取,也就是将**永远不会变的的节点提取出来,实现复用virtual dom,跳过对比算法的功能**。
通过AST生成render函数:主要目的是遍历真个AST,根据不同条件生成不同代码。
## NextTick原理分析
nextTick可以让我们在**下次DOM更新循环结束之后执行延迟回调**,用于**获取更新后的DOM**。
Vue2.4之前使用的microtasks,**但是microtasks优先级过高,在某些情况下可能出现比实际冒泡更快的情况**,但如果使**用macrotasks又可能会出现渲染的性能问题**。所有在新版本中会默认使用microtasks,但在特殊情况下会使用macrotasks。比如v-on。
对应实现**macrotasks,会先判断是否能使用setImmediate,不能的话降级为MessageChannel,以上都不行的话使用setTimeout**。
~~~
if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
macroTimerFunc = () => {
setImmediate(flushCallbacks)
}
} else if (
typeof MessageChannel !== 'undefined' &&
(isNative(MessageChannel) ||
// PhantomJS
MessageChannel.toString() === '[object MessageChannelConstructor]')
) {
const channel = new MessageChannel()
const port = channel.port2
channel.port1.onmessage = flushCallbacks
macroTimerFunc = () => {
port.postMessage(1)
}
} else {
macroTimerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
~~~
- 空白目录
- 双樾
- JS基础知识
- JS-WEB-API
- 开发环境
- 运行环境
- ES6
- 原型
- 异步
- 虚拟dom
- mvvm
- 组件化和React
- hybrid
- 其他
- 补充
- 技巧
- 快乐动起来呀
- css
- 掘金小册子
- js基础知识
- ES6知识点
- JS异步
- JS进阶知识
- 思考题
- DevTools Tips
- 浏览器基础知识
- 浏览器缓存机制0
- 浏览器渲染原理
- 安全防范知识点0
- 从V8中看JS性能优化0
- 性能优化琐碎事
- Webpack性能优化0
- 实现小型打包工具0
- React和Vue
- Vue生命周期
- vue基础知识点
- Vue响应式
- vue高级
- React基础
- Vue.js技术解密
- 准备工作
- 数据驱动
- new Vue()
- vue实例挂载
- 组件化
- 深入响应式原理
- 编译
- 扩展
- Vue Router
- Vuex