Vue工作机制
Vue.js的工作机制主要包括以下几个方面:
- 响应式数据绑定:Vue.js使用数据劫持和发布订阅模式来实现响应式数据绑定。当数据发生变化时,视图也会随之更新。
- 组件系统:Vue.js 2.0采用组件化的方式构建前端应用。组件是可重用的独立单位,可以包含模板、样式和逻辑。组件可以通过props和事件进行通信,使应用的结构更加清晰和可维护。
- 模板语法:Vue.js使用基于HTML的模板语法,方便开发者快速上手。模板中可以使用插值、指令和事件等语法,实现数据的动态展示和交互。
- 生命周期:Vue.js的生命周期指的是组件在创建、更新、销毁等不同阶段所执行的一系列方法。通过生命周期方法,我们可以对组件的状态进行控制和操作,实现复杂的业务逻辑。
- 指令:Vue.js提供了一些内置指令,如v-if、v-for、v-bind等,用于实现条件渲染、列表渲染、数据绑定等功能。指令可以接收参数,并且支持动态绑定。
- 插件系统:Vue.js 2.0的插件系统允许开发者扩展框架的功能。插件可以通过全局API或局部注册的方式使用,使应用的功能更加丰富和灵活。
- 组件通信:Vue.js提供了多种组件通信方式,如父子组件传递props、使用事件进行通信、使用Vuex进行状态管理等。这些通信方式使得组件之间的数据流动更加可控和可预测。
Vue响应式的原理defineProperty
Vue.js 的响应式原理主要是通过ES5的Object.defineProperty方法对数据进行劫持,并利用getter和setter方法进行数据的获取和设置。当读取data中的数据时,会自动调用getter方法;当修改数据时或者数据发生改变时,会自动调用setter方法去侦听检测数据的变化。同时,会通知观察者(watcher)自动重新触发渲染。
class KVue {
constructor(options) {
this._data = options.data;
this.observer(this._data);
}
observer(value) {
if (!value || typeof value !== "object") {
return;
}
Object.keys(value).forEach(key => {
this.defineReactive(value, key, value[key]);
});
}
defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
enumerable: true /* 属性可枚举 */,
configurable: true /* 属性可被修改或删除 */,
get() {
return val;
},
set(newVal) {
if (newVal === val) return;
this.cb(newVal);
}
});
}
cb(val) {
console.log("更新数据了", val);
}
}
let o = new KVue({
data: {
test: "I am test."
}
});
o._data.test = "hello,kaikeba";
依赖收集与追踪
Vue.js使用依赖收集和追踪系统来管理组件之间的依赖关系。该系统使用静态分析来查找模板中的依赖关系,并在编译期间收集依赖项。然后,这些依赖项被存储在Vue实例的__dep__数组中,用于在更新周期中追踪和触发依赖项的重新计算。
当一个依赖项发生变化时,Vue会使用__dep__数组来查找所有依赖于该依赖项的组件,并触发重新渲染。这使得Vue能够有效地更新视图,而不需要手动调用Vue实例的方法或属性。
此外,Vue还使用虚拟DOM(VNode)来比较新旧视图之间的差异,并仅更新需要更新的部分。这使得Vue在处理大量数据时能够保持高效的性能。
new Vue({
template:
`<div>
<span>{{text1}}</span>
<span>{{text2}}</span>
<div>`,
data: {
text1: 'name1'
},
created() {
this.text1 = "这是一段文本"
}
});
text1被修改,所以视图更新,但是text2视图没用到,所以不需要更新,如何实现呢,就需要我们的依赖收集。
// 依赖收集
class Dep {
constructor() {
// 存数所有的依赖
this.deps = []
}
// 在deps中添加一个监听器对象
addDep(dep) {
this.deps.push(dep)
}
// 通知所有监听器去更新视图
notify() {
this.deps.forEach((dep) => {
dep.update()
})
}
}
class Watcher {
constructor() {
// 在new一个监听器对象时将该对象赋值给Dep.target,在get中会用到
Dep.target = this
}
// 更新视图的方法
update() {
console.log("视图更新了!")
}
}
我们在增加了一个 Dep 类的对象,用来收集 Watcher 对象。读数据的时候,会触发 reactiveGetter 函数把当前的Watcher 对象(存放在 Dep.target 中)收集到 Dep 类中去。
写数据的时候,则会触发 reactiveSetter 方法,通知Dep 类调用 notify 来触发所有 watcher 对象的update 方法更新对应视图。
constructor(options) {
this._data = options.data
this.observer(this._data)
// 新建一个Watcher观察者对象,这时候Dep.target会指向这个Watcher对象
new Watcher();
// 在这里模拟render的过程,为了触发test属性的get函数
console.log('模拟render,触发test的getter', this._data.test);
}
defineReactive(obj, key, val) {
const dep = new Dep()
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
// 将Dep.target(即当前的Watcher对象存入Dep的deps中)
dep.addDep(Dep.target)
return val
},
set: function reactiveSetter(newVal) {
if (newVal === val) return
// 在set的时候触发dep的notify来通知所有的Watcher对象更新视图
dep.notify()
}
})
}
编译compile
核心逻辑 获取dom,遍历dom,获取{{}}、k-和@开头的 ,设置响应式。
目标功能
<body>
<div id="app">
<body>
<div id="app">
<p>{{name}}</p>
<p k-text="name"></p>
<p>{{age}}</p>
<p>
{{doubleAge}}
</p>
<input type="text" k-model="name">
<button @click="changeName">呵呵</button>
<div k-html="html"></div>
</div>
<script src='./compile.js'></script>
<script src='./kaikeba-vue.js'></script>
<script>
let kaikeba = new KVue({
el: '#app',
data: {
name: "I am test.",
age: 12,
html: '<button>这是一个按钮</button>'
},
created() {
console.log('开始啦')
setTimeout(() => {
this.name = '我是蜗牛'
}, 1500)
},
methods: {
changeName() {
this.name = '哈喽,开课吧'
this.age = 1
this.id = 'xx'
console.log(1, this)
}
}
})
</script>
</body>
<p>{{name}}</p>
<p k-text="name"></p>
<p>{{age}}</p>
<p>
{{doubleAge}}
</p>
<input type="text" k-model="name">
<button @click="changeName">呵呵</button>
<div k-html="html"></div>
</div>
<script src='./compile.js'></script>
<script src='./kaikeba-vue.js'></script>
<script>
let kaikeba = new KVue({
el: '#app',
data: {
name: "I am test.",
age: 12,
html: '<button>这是一个按钮</button>'
},
created() {
console.log('开始啦')
setTimeout(() => {
this.name = '我是蜗牛'
}, 1500)
},
methods: {
changeName() {
this.name = '哈喽,开课吧'
this.age = 1
this.id = 'xx'
console.log(1, this)
}
}
})
</script>
</body>
compile.js
class Compile {
constructor(el, vm) {
this.$vm = vm
this.$el = document.querySelector(el)
if (this.$el) {
this.$fragment = this.node2Fragment(this.$el)
this.compileElement(this.$fragment)
this.$el.appendChild(this.$fragment)
}
}
node2Fragment(el) {
// 新建文档碎片 dom接口
let fragment = document.createDocumentFragment()
let child
// 将原生节点拷贝到fragment
while (child = el.firstChild) {
fragment.appendChild(child)
}
return fragment
}
compileElement(el) {
let childNodes = el.childNodes
Array.from(childNodes).forEach((node) => {
let text = node.textContent
// 表达式文本
// 就是识别{{}}中的数据
let reg = /\{\{(.*)\}\}/
// 按元素节点方式编译
if (this.isElementNode(node)) {
this.compile(node)
} else if (this.isTextNode(node) && reg.test(text)) {
// 文本 并且有{{}}
this.compileText(node, RegExp.$1)
}
// 遍历编译子节点
if (node.childNodes && node.childNodes.length) {
this.compileElement(node)
}
})
}
compile(node) {
let nodeAttrs = node.attributes
Array.from(nodeAttrs).forEach((attr) => {
// 规定:指令以 v-xxx 命名
// 如 <span v-text="content"></span> 中指令为 v-text
let attrName = attr.name // v-text
let exp = attr.value // content
if (this.isDirective(attrName)) {
let dir = attrName.substring(2) // text
// 普通指令
this[dir] && this[dir](node, this.$vm, exp)
}
if (this.isEventDirective(attrName)) {
let dir = attrName.substring(1) // text
this.eventHandler(node, this.$vm, exp, dir)
}
})
}
compileText(node, exp) {
this.text(node, this.$vm, exp)
}
isDirective(attr) {
return attr.indexOf('k-') == 0
}
isEventDirective(dir) {
return dir.indexOf('@') === 0
}
isElementNode(node) {
return node.nodeType == 1
}
isTextNode(node) {
return node.nodeType == 3
}
text(node, vm, exp) {
this.update(node, vm, exp, 'text')
}
html(node, vm, exp) {
this.update(node, vm, exp, 'html')
}
model(node, vm, exp) {
this.update(node, vm, exp, 'model')
let val = vm.exp
node.addEventListener('input', (e) => {
let newValue = e.target.value
vm[exp] = newValue
val = newValue
})
}
update(node, vm, exp, dir) {
let updaterFn = this[dir + 'Updater']
updaterFn && updaterFn(node, vm[exp])
new Watcher(vm, exp, function (value) {
updaterFn && updaterFn(node, value)
})
}
// 事件处理
eventHandler(node, vm, exp, dir) {
let fn = vm.$options.methods && vm.$options.methods[exp]
if (dir && fn) {
node.addEventListener(dir, fn.bind(vm), false)
}
}
textUpdater(node, value) {
node.textContent = value
}
htmlUpdater(node, value) {
node.innerHTML = value
}
modelUpdater(node, value) {
node.value = value
}
}
入口文件
class KVue {
constructor(options) {
this.$data = options.data
this.$options = options
this.observer(this.$data)
// 新建一个Watcher观察者对象,这时候Dep.target会指向这个Watcher对象
// new Watcher()
// 在这里模拟render的过程,为了触发test属性的get函数
console.log('模拟render,触发test的getter', this.$data)
if (options.created) {
options.created.call(this)
}
this.$compile = new Compile(options.el, this)
}
observer(value) {
if (!value || (typeof value !== 'object')) {
return
}
Object.keys(value).forEach((key) => {
this.proxyData(key)
this.defineReactive(value, key, value[key])
})
}
defineReactive(obj, key, val) {
const dep = new Dep()
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
// 将Dep.target(即当前的Watcher对象存入Dep的deps中)
Dep.target && dep.addDep(Dep.target)
return val
},
set(newVal) {
if (newVal === val) return
val = newVal
// 在set的时候触发dep的notify来通知所有的Watcher对象更新视图
dep.notify()
}
})
}
proxyData(key) {
Object.defineProperty(this, key, {
configurable: false,
enumerable: true,
get() {
return this.$data[key]
},
set(newVal) {
this.$data[key] = newVal
}
})
}
}
依赖收集 Dep
class Dep {
constructor() {
// 存数所有的依赖
this.deps = []
}
// 在deps中添加一个监听器对象
addDep(dep) {
this.deps.push(dep)
}
depend() {
Dep.target.addDep(this)
}
// 通知所有监听器去更新视图
notify() {
this.deps.forEach((dep) => {
dep.update()
})
}
}
监听器
class Watcher {
constructor(vm, key, cb) {
// 在new一个监听器对象时将该对象赋值给Dep.target,在get中会用到
// 将 Dep.target 指向自己
// 然后触发属性的 getter 添加监听
// 最后将 Dep.target 置空
this.cb = cb
this.vm = vm
this.key = key
this.value = this.get()
}
get() {
Dep.target = this
let value = this.vm[this.key]
return value
}
// 更新视图的方法
update() {
this.value = this.get()
this.cb.call(this.vm, this.value)
}
}
总结
以上就是Vue.js的源码分析了,希望对大家有所帮助。