VUE
把你了解的vue原理阐述一下
首先了解vue中的三个核心类
- Observer: 给对象的属性添加getter和setter,用于依赖收集和派发更新
- Dep: 用于收集当前响应式对象的依赖关系,每个响应式对象都有一个dep实例。dep.subs = watcher[]。当数据发生变更的时候,会通过dep.notify()通知🧍♂各个watcher
- Watcher: 观察者对象, render watcher,computed watcher, user watcher
- 依赖收集
- initState,对computed属性初始化时,会触发computed watcher依赖收集
- initState, 对监听属性初始化的时候,触发的user watcher依赖收集
- render,触发render watcher的收集
- 派发更新 Object.defineProperty
- 组件中响应的数据进行了修改,会触发setter逻辑
- dep.notify()
- 遍历所有的subs,调用每个watcher的update方法 总结原理 当创建vue实例时,vue会遍历data里的属性,object.defineProperty 为属性添加getter和setter对数据的读取进行劫持, getter: 依赖收集 setter: 派发更新 每个组建的实例都会有响应的watcher实例
计算属性的实现原理
computed watcher,计算属性的监听器 computed watcher 持有一个dep实例,通过dirty属性标记计算属性是否需要重新求值 当computed的依赖值改变后,就会通知订阅的watcher进行更新,对于computed watcher 会将dirty属性设置为true,并且进行计算属性的调用
- computed 所谓的缓存是指什么? 计算属性是基于他的响应式依赖进行缓存的,只有依赖发生改变的时候才会重新求值。
- 那computed缓存存在的意义是什么,或者你经常在什么时候使用 比如计算属性方法内部操作非常耗时,遍历一个极大的数组,计算一次可能需要耗时1s。
const largeArray = [
{...},
{...}
....
]//10w
data: {
id:1
}
computed: {
currentItem: function(){
return largeArray.find(item=>item.id===this.id)
}
stringId: function(){
return String(this.id)
}
}
- 以下情况,computed可以监听到数据的变化吗=====不能(只有data经过vue初始化监听,创建了observer的才会变化
template
{{storageMsg}}
computed: {
storageMsg: function(){
return sessionStorage.getItem('xxx')
},
time: function(){
return Date.now()
}
}
created(){
sessionStorage.setItem('xxx',111)
}
onClick(){
sessionStorage.setItem('xxx',Math.random())
}
watch和computed的区别
Vue.nextTick的原理
Vue.nextTick(()=>{
})
Vue是异步执行dom更新的,一旦观察到数据的变化,会把同一个event loop中观察数据变化的watcher推送紧这个队列。
在下一次事件循环时,Vue会清空异步队列,进行dom更新,所以是在下一次时间循环中更新的 优先级: Promise.then > MutationObserver => setImmediate => setTimeout vm.somData = 'new value ',
dom并不会马上更新,而是在异步队列被清除时才会更新dom 宏任务=> 微任务队列 =》 UI render 一般什么时候用到nextTick呢 在数据变化后要执行某个操作,而这个操作印尼的数据改变而改变的dom,这个操作应该放到nextTick回调中
手写一个简单的vue,实现响应式的更新
- 首先新建一个目录
- index.html主页面
- vue.js vue 主文件
- compiler.js 编译模版,解析指令,v-model,v-html
- observer.js 数据劫持
- watcher.js 观察者对象类
- index.html
<!DOCTYPE html>
<html lang="cn">
<head>
<title>My Vue</title>
</head>
<body>
<div id="app">
// 验证
<h1>模版表达式</h1>
<h3>{{msg}}</h3>
<br />
<h1>v-text测试</h1>
<h3 v-text="msg"></h3>
<br />
<h1>v-html测试</h1>
<div v-html="testHtml"></div>
<br />
<h1>v-model测试</h1>
<input type = "text" v-model="msg">
<input type = "text" v-model="count">
<button v-on:click="handler">按钮</button>
<br />
</div>
<script src="./index.js" type="module"></script>
</body>
</html>
- vue.js
/**
* 包括Vue的构造函数,接收各种配置参数等等
*/
import Observer from './observer.js'
import Compiler fom './compiler.js'
export default class Vue{
constructor(options={}){
this.$options = options
this.$data = options.data
this.$methods = options.methods
this.initRootElement(options)
// 利用Object.defineProperty将data里的属性注入到vue实例中
this._proxyData(this.$data)
// 实例化observer对象
new Observer(this.$data)
// 实例化Compiler对象,解析指令和模版表达式
new Compiler(this)
}
/**
* 获取根元素,并存储到vue实例,简单检查以下传入的el是否合规
*/
initRootElement(options){
if(typeof options.el === 'string'){
this.$el = document.querySelector(options.el)
}else if(options.el instanceof HTMLElement){
this.$el = options.el
}
if(!options.el){
throw new Error('传入的el不喝大,请传入css selector 或者htmlelement')
}
}
// data中的属性挂载到this实例上
_proxyData(data){
Object.keys(data).forEach(key=>{
Object.defineProperty(this,key,{
enumerable: true,
configurable: true,
get(){
return data[key]
}
set(newValue){
if(data[key] === newValue){
return
}
data[key] = newValue
}
})
})
}
}
- 验证一下,新建index.js
import Vue from './myvue/vue.js'
const vm = new Vue({
el: '#app',
data: {
msg: 'hello world',
testHtml: '<h2 style="color: red">html</h2>'
},
methods: {
handler(){
alert(111)
}
}
})
console.log(vm)
- vue里可以通过this来获取到data的属性
// data中的属性挂载到this实例上
_proxyData(data){
Object.keys(data).forEach(key=>{
Object.defineProperty(this,key,{
enumerable: true,
configurable: true,
get(){
return data[key]
}
set(newValue){
if(data[key] === newValue){
return
}
data[key] = newValue
}
})
})
}
- 接下来,先把几个核心类声明好,先忽略具体的实现
// dep.js
/**
* 发布订阅的模式
* 存储所有的观察者,watcher,
* 每个watcher都有一个update
* 通知subs里的每个watcher实例,触发update方法
*/
export default class Dep{
constructor(){
// 存储素有的观察者
this.subs = []
}
/** 添加观察者 */
addSub(watcher){
if(watcher && watcher.update) {
this.subs.push(watcher)
}
}
/** 发送通知 */
notify(){
this.subs.forEach(watcher=>{
watcher.update()
]})
}
// Dep在哪里实例化,哪里addsub
// Dep notify 在哪里调用
}
// observer.js
export default class Observer{
constructor(data){
this.traverse(data)
}
/** 递归遍历data里的所有属性 */
traverse(data){
if(!data || typeof data !== 'object'){
return
}
Object.keys(data).forEach(key=>{
this.defineReactive(data,key,data[key])
})
}
/** 给传入的数据设置 getter/setter */
defineReactive(obj, key, val){
this.traverse(val)
const dep = new Dep()
const that = this
Object.defineProperty(obj.key,{
configurable: true,
enumberable: true,
get(){
Dep.target && dep.addSub(Dep.target);
return value
},
set(newValue){
if(newValue === val){
return
}
val = newValue
that.traverse(newValue)
dep.notify()
}
})
}
}
// watcher.js
import Dep from './Dep.js'
export default class Watcher{
/**
* @param vm vue实例
* @param key data中的属性名
* @param cb 负责更新的回调函数
*/
constructor(vm,key,cb){
this.vm = vm
this.key = key
this.cb = cb
Dep.target = this
// 触发get方法,在get方法中会做一些操作?
this.oldValue = vm[key]
Dep.target = null
}
/** 当数据变化的时候,更新视图 */
update(){
let newValue = this.vm[this.key]
if(this.oldValue === newValue){
return
}
this.cb(newValue)
}
}
// watcher初始化获取oldValue的时候,会去做一些什么操作
// 通过vm[key]获取oldValue前,为什么要将实例挂载DEP上,获取之后为什么又要置为null
// update方法是什么时候执行的
// compiler.js 编译模版
import Watcher fom './watcher.js'
export default class Compiler{
constructor(vm){
this.el = vm.$el
this.vm = vm
this.methods = vm.$methods
this.compile(vm.$el)
}
/** 编译模版 */
compile(el){
const childNodes = el.childNodes
Array.from(childNodes).forEach(node=>{
if(this.isTextNode(node)){
this.compileText(node)
}else if(this.isElementNode(node)){
this.compileElement(node)
}
if(node.childNodes&& node.childNodes.legnth>0){
this.compile(node)
}
})
}
compileText(node){
//{{}}
const reg = /|{\{(.*)\}\}/g
const value = node.textContent
if(reg.test(value)){
const key = RegExp.$1.trim()
node.textContent = value.replace(reg,this.vm[key])
new Watcher(this.vm,key,(newValue)=>{
node.textContent = newValue
})
}
}
compileElement(node){
if(node.attributes.length){
Array.from(node.attributes).forEach(attr=>{
const attrName = attr.name
if(this.isDirective(attrName)){
let directiveName = attrName.inedexOf(':')>-1?attrName.substr(5):attrName.substr(2)
let key = attr.value
// 更新元素节点
this.update(node,key,directiveName)
}
})
}
}
update(node,key,directiveName){
// v-model,v-text,v-html,v-on:click
const updateFn = this[directiveName + 'Updater']
updateFn && updateFn.call(this,node, this.vm[key],key, directiveName)
}
textUpdater(node, value, key){
node.textContent = value
new Watcher(this.vm,key,(newValue)=>{
node.textContent = newValue
})
}
modelUpdater(node, value, key){
node.value = value
new Watcher(this.vm,key,(newValue)=>{
node.value = newValue
})
node.addEventListener('input',()=>{
this.vm[key] = node.value
})
}
htmlUpdater(node, value, key){
node.innerHTML = value
new Watcher(this.vm,key,(newValue)=>{
node.innerHTML = newValue
})
}
clickUpdater(node, value, key,directiveName){
node.addEventListener(directiveName,this.methods[key])
}
/**判断是否是文本节点*/
isTextNode(node){
return node.nodeType === 3
}
/**判断是否是元素节点*/
isElementNode(node){
return node.nodeType === 1
}
isDirective(attrName){
return attrName.startWith('v-')
}
}