前言
本人是android开发 公司前端业务比较多 所以也加入了vue的大军。闲暇之余去学习一下vue源码 加强理解Vue2.0版本的源码 学习优秀源码的思路(后续会加入 3.0 版本)
适用人群: 想深入理解vue 面试八股文同学
正文
废话我也不多说了,网上相关的文章太多了我就直接介绍vue 响应式实现思路。比如面试官问你的时候?
我说说我的个人理解啊,从整个技术设计思路来说核心用到了2点 Object.defineProperty+观察者模式 这样回答到了精髓,起码说明你是看过源码了解过得。然后按照以下几点阐述
-
数据劫持:Vue 2 使用 Object.defineProperty 方法来劫持(defineProperty)对象的属性,使之具有 getter 和 setter 方法。在 Vue 实例初始化时,会遍历数据对象的所有属性,将其转换为响应式属性。当访问这些属性时,Vue 会自动追踪依赖,并建立与视图的关联。
-
依赖追踪:Vue 2 中的依赖追踪是通过一个全局的 Dep 对象和每个属性对应的 Watcher 完成的。在数据劫持过程中,每个响应式属性都会被绑定一个 Dep 对象。而每个 Watcher 则代表一个依赖(如视图或计算属性)。当属性被访问时,会触发 getter 方法,该 getter 方法会将当前的 Watcher 添加到对应的 Dep 对象的依赖列表中。这样,在属性发生改变时,就可以通知对应的 Watcher 更新。
-
数据更新:当响应式属性发生变化时,会触发属性的 setter 方法。setter 方法会通知绑定的 Dep 对象,然后调用 Dep 对象的 notify 方法,进而触发所有依赖的 Watcher 的更新操作。在更新过程中,会重新渲染与该属性相关的视图或计算属性,保持视图的同步更新。
-
响应式侦测的局限性:Vue 2 的响应式侦测是在初始化阶段完成的,对于后续添加的属性或数组索引的变动无法进行响应式处理。为了解决这个问题,Vue 提供了一些特殊的方法(如 Vue.set 或 Array.prototype.$set)来实现对新增属性或索引的响应式支持。
上面这一套说出来 面试官基本也没啥问题了,如果变态的面试官需要你手写或上机操作呢,当然我们还是需要自己手动去敲代码实现的。
源码如下
// VueSrc.js
class Vue{
constructor(options){
this.options=options
this.data=options.data
this.el=options.el
//数据劫持 使用Object.defineProperty实现
Object.keys(this.data).forEach((key)=>{
this.defineReactive(this.data,key)
})
//将DOM转成 文档片段
const fragment= this.nodeToFragment(document.querySelector(this.el))
//将v-model指令编译加入node
console.log('frag',fragment)
this.compile(fragment)
document.body.appendChild(fragment)
}
nodeToFragment(node){
const fragment= document.createDocumentFragment()
let childNode
while((childNode=node.firstChild)){
console.log('root node',node.firstChild)
this.compileNode(childNode)
fragment.appendChild(childNode)
}
return fragment
}
compileNode(node){
if(node.nodeType===1){
const attrs=node.attributes;
for(let i=0;i<attrs.length;i++){
let attr=attrs[i]
if('v-model'===attr.name){
const key=attr.value
node.value=this.data[key]
node.addEventListener('input',(event)=>{
this.data[key]=event.target.value
})
}
}
}else if(node.nodeType===3){
// 处理文本节点
const reg = /\{\{(.*)\}\}/;
const value = node.textContent;
const match=value.match(reg)
if (match) {
const key = match[1].trim();
node.textContent = value.replace(reg, this.data[key]);
// 创建 watcher 监听数据变化
const watcher= new Watcher(this.data, key, (newValue) => {
node.textContent = newValue;
});
console.log('nodeType>>3',watcher)
}
}
}
compile(fragment){
let childs=fragment.childNodes;
console.log('childs',childs)
Array.from(childs).forEach(node=>{
if(node.nodeType===1){
this.compileNode(node)
this.compile(node)
}else if(3===node.nodeType){
this.compileNode(node)
}
})
}
// 劫持属性,实现响应式
defineReactive(obj, key) {
let value = obj[key];
const dep=new Dep()
Object.defineProperty(obj,key,{
get(){
console.log('get>>>',key,Dep.target)
if(Dep.target){
dep.addSub(Dep.target)
}
return value
},
set(newVal){
console.log('set>>>',key,newVal)
if(newVal!==value){
value=newVal
dep.notify()
}
}
})
}
}
class Dep{
constructor(){
this.subs=[]
}
addSub(sub){
this.subs.push(sub)
}
notify(){
this.subs.forEach((wachter)=>{
wachter.update()
})
}
}
class Watcher{
constructor(obj,key,callBack){
this.obj=obj;
this.key=key;
this.callBack=callBack
Dep.target=this;
this.value=obj[key]
Dep.target=null;
}
update() {
const newVal=this.obj[this.key]
if(newVal!==this.value){
this.value=newVal
this.callBack(newVal)
}
}
}
再写一个测试html代码
<body>
<div id="app"><input type="text" v-model="message" /><p>{{ message }}</p>
<input type="text" v-model="name" /><p>{{ name }}</p></div>
<script src="./VueSrc.js"></script>
<script >
let app = new Vue({ el: '#app', data: { message: 'hello vue' ,name:'curry'} })
</script>
</body>
小结
自己多写几遍 遇到不懂得函数多查查,哪怕手写 你写个大概出来面试也绝对是能通过的
最后如果觉得本文有帮助 记得点赞三连哦 十分感谢!