Comments (15)
核心是利用ES5的Object.defineProperty,这也是Vue.js为什么不能兼容IE8及以下浏览器的原因。
- Object.defineProperty方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。
Object.defineProperty(
obj, // 定义属性的对象
prop, // 要定义或修改的属性的名称
descriptor, // 将被定义或修改属性的描述符【核心】
- observe的功能就是用来监测数据的变化。实现方式是给非VNode的对象类型数据添加一个Observer,如果已经添加过则直接返回,否则在满足一定条件下去实例化一个Observer对象实例。
Observer是一个类,它的作用是给对象属性添加getter和setter,用于 依赖收集 和 派发更新
依赖收集getter(重点关注以下两点)
*const dep = new Dep() // 实例化一个Dep实例
*在get函数中通过dep.depend做依赖收集
Dep是一个Class,它定义了一些属性和方法,它有一个静态属性target,这是一个全局唯一Watcher【同一时间内只能有一个全局的Watcher被计算】。Dep实际上就是对Watcher的一种管理,Dep脱离Watcher单独存在是没有意义的。Watcher和Dep就是典型的观察者设计模式。
Watcher是一个Class,在它的构造函数中定义了一些和Dep相关的属性:
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
收集过程:当我们实例化一个渲染watcher的时候,首先进入watcher的构造函数逻辑,然后执行他的this.get()
方法,进入get函数把Dep.target赋值为当前渲染watcher并压栈(为了恢复用)。接着执行vm._render()
方法,生成渲染VNode,并且在这个过程对vm上的数据访问,这个时候就触发数据对象的getter(在此期间执行Dep.target.addDep(this)
方法,将watcher订阅到这个数据持有的dep的subs中,为后续数据变化时通知到哪些subs做准备)。然后递归遍历添加所有子项的getter。
Watcher在构造函数中初始化两个Dep实例数组。newDeps代表新添加的Dep实例数组,deps代表上一次添加的Dep实例数组。
依赖清空:在执行清空依赖(cleanupDeps)函数时,会首先遍历deps,移除对dep的订阅,然后把newDepsIds和depIds交换,newDeps和deps交换,并把newDepIds和newDeps清空。考虑场景,在条件渲染时,及时对不需要渲染数据的订阅移除,减少性能浪费。
考虑到Vue是数据驱动的,所以每次数据变化都会重写Render,那么vm._render()
方法会再次执行,并再次触发数据
收集依赖的目的是为了当这些响应式数据发生变化,触发它们的setter的时候,能知道应该通知哪些订阅者去做相应的逻辑处理【派发更新】
派发更新setter(重点关注以下两点)
*childOb = !shallow && observe(newVal) // 如果shallow为false的情况,会对新设置的值变成一个响应式对象
*dep.notify() // 通知所有订阅者
派发过程:当我们组件中对响应的数据做了修改,就会触发setter的逻辑,最后调用dep.notify()
方法,它是Dep的一个实例方法。具体做法是遍历依赖收集中建立的subs,也就是Watcher的实例数组【subs数组在依赖收集getter中被添加,期间通过一些逻辑处理判断保证同一数据不会被添加多次】,然后调用每一个watcher的update方法。
update函数中有个queueWatcher(this)
方法引入了队列的概念,是vue在做派发更新时优化的一个点,它并不会每次数据改变都会触发watcher回调,而是把这些watcher先添加到一个队列中,然后在nextTick后执行watcher的run函数
队列排序保证:
- 组件的更新由父到子。父组件创建早于子组件,watcher的创建也是
- 用户自定义watcher要早于渲染watcher执行,因为用户自定义watcher是在渲染watcher前创建的
- 如果一个组件在父组件watcher执行期间被销毁,那么它对应的watcher执行都可以被跳过,所以父组件的watcher应该先执行。
- 队列遍历:排序完成后,对队列进行遍历,拿到对应的watcher,执行
watcher.run()
。
run函数解析:先通过this.get()
得到它当前的值,然后做判断,如果满足新旧值不等、新值是对象类型、deep模式任何一个条件,则执行watcher的回调,注意回调函数执行的时候会把第一个参数和第二个参数传入新值value和旧值oldValue,这就是当我们自己添加watcher时候可以在参数中取到新旧值的来源。对应渲染watcher而言,在执行this.get()
方法求值的时候,会执行getter方法。因此在我们修改组件相关数据时候,会触发组件重新渲染,接着重新执行patch的过程
手写一个数据绑定:
<input id="input" type="text" />
<div id="text"></div>
let input = document.getElementById("input");
let text = document.getElementById("text");
let data = { value: "" };
Object.defineProperty(data, "value", {
set: function(val) {
text.innerHTML = val;
input.value = val;
},
get: function() {
return input.value;
}
});
input.onkeyup = function(e) {
data.value = e.target.value;
};
from daily-interview-question.
始终感觉这个问题有点问题,明明是单向绑定,只是 m -> v,在 vue 2.x 中 通过 defineProperty 实现的数据劫持,getter 收集依赖,setter 调用更新回调,这个过程是 vue 黑盒提供的,也就是说数据驱动视图,开发人员只需关注数据的变更即可;再说 v -> m,通过 v-model 的方式,指令添加是开发人员加的吧,如果一个组件有多个 v-model ,你要自己写 v-on 和 data 的修改吧。
from daily-interview-question.
Model 改变 View的过程: 依赖于ES5的object.defindeProperty,通过 defineProperty 实现的数据劫持,getter 收集依赖,setter 调用更新回调(不同于观察者模式,是发布订阅 + 中介)
View 改变 Model的过程: 依赖于 v-model ,该语法糖实现是在单向数据绑定的基础上,增加事件监听并赋值给对应的Model
附带补一个问题, 如果react也要双向绑定(虽然推荐是单向的),其实早期mixin【React.addons.LinkedStateMixin】可以实现, 也就是搞一些简单的setstate封装吧
from daily-interview-question.
底层就是defineProperty get是读取之前的旧数据,set中如果发现数据没改 直接return 原始值 ,如果改了就直接修改为NewValue
这个是一个简单的demo
<body>
<div id="app">
<input type="text" id="model"><br />
<div id="modelText"></div>
</div>
<script>
var model = document.querySelector("#model");
var modelText = document.querySelector("#modelText");
var defaultName = "defaultName";
var userInfo = {}
model.value = defaultName;
Object.defineProperty(userInfo, "name", {
get: function () {
return defaultName;
},
set: function (value) {
defaultName = value;
model.value = value;
console.log(value);
modelText.textContent = value;
}
})
userInfo.name = "new value";
var isEnd = true;
model.addEventListener("keyup", function () {
if (isEnd) {
userInfo.name = this.value;
}
}, false)
//加入监听中文输入事件
model.addEventListener("compositionstart", function () {
console.log("开始输入中文");
isEnd = false;
})
model.addEventListener("compositionend", function () {
isEnd = true;
console.log("结束输入中文");
})
</script>
</body>
这个图是 vue源码
他是最后通知 dep.notify() notify 调用 组件的watcher 中的update函数 大致就是这样
from daily-interview-question.
vue通过Object.defineProperty 劫持传进来的数据, 然后在数据getter的时候订阅重新编译模板的消息,然后通过js监听元素的事件,这里input事件也好,keyup事件也好,总是监听输入框值变化,将新的值重新赋值给被劫持的data,这样就会触发setter函数,再setter函数中就会去发布重新编译模板的消息;
from daily-interview-question.
一个比较直观的图,看了就明白了 https://seawind8888.github.io/2019/07/15/%E4%B8%80%E5%9B%BE%E7%9C%8B%E6%87%82Vue%E6%95%B0%E6%8D%AE%E7%BB%91%E5%AE%9A%E5%8E%9F%E7%90%86/
from daily-interview-question.
v-model是v-bind和v-on的语法糖。
v-bind即model=>view,当model数据发生变化,在setter中,去触发对应组件重新生成Vnode,对比新旧虚拟树,更新差异。
v-on即view=>model,view操作后,触发事件,调用回调函数,在回调函数中更新model
from daily-interview-question.
其实都是单向数据流,v-model也只是语法糖。主要**还是 数据驱动 视图,按照这个去开发就完事了。不要写的不伦不类
from daily-interview-question.
from daily-interview-question.
从页面渲染到加载数据,依赖于ES5的object.defindeProperty 此时收益数据依赖并当数据reactive,当修改数据的时候触发set 调用以来的notify并触发更新
from daily-interview-question.
<div id="content"> name: {{name}}<br/> <input type="text" v-model = 'name'> </div>
<script> const el = document.getElementById('content'); const template = el.innerHTML; const _data = { name: 'mark', } //new Proxy(target, handler); let changeName = new Proxy(_data, { set(obj, name, value){ obj[name] = value; render() } }) render(); function render(){ el.innerHTML = template.replace(/\{\{\w+\}\}/g, str=>{ str = str.substring(2, str.length-2); return _data[str]; }) Array.from(el.getElementsByTagName('input')).filter(ele => { return ele.getAttribute('v-model'); }).forEach(input=>{ let name = input.getAttribute('v-model'); input.value = changeName[name]; input.oninput = function(){ changeName[name] = this.value; } }) } </script>
input 中输入内容,焦点会消失
from daily-interview-question.
<div id="content"> name: {{name}}<br/> <input type="text" v-model = 'name'> </div>
<script> const el = document.getElementById('content'); const template = el.innerHTML; const _data = { name: 'mark', } //new Proxy(target, handler); let changeName = new Proxy(_data, { set(obj, name, value){ obj[name] = value; render() } }) render(); function render(){ el.innerHTML = template.replace(/\{\{\w+\}\}/g, str=>{ str = str.substring(2, str.length-2); return _data[str]; }) Array.from(el.getElementsByTagName('input')).filter(ele => { return ele.getAttribute('v-model'); }).forEach(input=>{ let name = input.getAttribute('v-model'); input.value = changeName[name]; input.oninput = function(){ changeName[name] = this.value; } }) } </script>
el.innerHTML = template.replace(/\{\{(\w+)\}\}/g, ($0,$1)=>{
return _data[$1];
})
这样会不会更简洁一些
from daily-interview-question.
想问一下Vue的双向绑定和vue的响应式原理有什么不同啊
from daily-interview-question.
双向数据绑定不应该指v-model吗?而Object.defineProperty那一坨流程是Vue响应式系统吧?
from daily-interview-question.
Vue 的双向数据绑定和响应式数据是两个不同的概念。
双向数据绑定
- Vue 中的双向数据绑定指的是当一个数据发生变化时,视图也会随之更新;而当用户在视图中修改了数据时,数据模型也会相应地更新
- 双向数据绑定是一种语法糖,它允许将表单元素(如input、textarea等)上的值与 Vue 实例中的数据属性进行双向绑定
这种双向绑定可以通过 v-model 指令来实现,例如:
<input type="text" v-model="message">
上面的代码表示将输入框与 Vue 实例中的 message
属性进行双向绑定,当用户在输入框中输入内容时,message
属性的值也会随之改变。
响应式数据
Vue 中的响应式数据指的是当一个组件的数据发生变化时,Vue 会自动检测到这些变化,并且重新渲染视图。
Vue 通过 Proxy
或 Object.defineProperty()
对数据对象进行代理或劫持,从而能够监听到数据的变化,并通知相关的组件进行重新渲染。例如:
const data = { message: 'Hello, Vue!' }
const vm = new Vue({
el: '#app',
data: data
})
上面的代码创建了一个 Vue 实例,并将 data
对象传入其中作为其数据模型。当 data.message
发生变化时,Vue 会自动检测到并通知相关的组件进行重新渲染。
因此:
- 双向数据绑定主要用于处理表单等交互操作,让用户能够方便地修改数据
- 响应式数据则是 Vue 实现数据驱动视图的核心机制,通过它使得视图能够自动地响应数据的变化
from daily-interview-question.
Related Issues (20)
- ``` javascript HOT 2
- 反转数字字符串 HOT 2
- es11
- 实现
- [...new Set(arr.toString().split(',').sort((a,b) => a-b ))].map(item => Number(item))
- let nums1 = [3,3,2] let nums2 = [1,2,3,3,2,2,4444] function res (nums1,nums2){ let a = [] for (const item of nums1) { for (const i in nums2) { if(item==nums2[i]){ a.push(nums2[i]) nums2.splice(i,1) break } } }; return a } console.log(res(nums1,nums2))
- splice插入
- 自己回答一波,若有错误请各位指出,互相学习!谢谢 ~
- 递归
- 第226题:TS 中逆变和协变如何理解
- objmap
- 通过协程来实现
- 这代码写的...
- 第 161 题:用最精炼的代码实现数组非零非负最小值 index HOT 2
- No description provided.
- > 就我的使用来说(Vue)key的作用是为了在数据变化时强制更新组件,以避免“原地复用”带来的副作用,官方文档也说明了**不带key性能更好**,见:[List Rendering - key](https://cn.vuejs.org/v2/guide/list.html#key)
- 解释原因
- an
- 同步/异步 > 微任务>宏任务
- Object.prototype,map
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from daily-interview-question.