- Developer at Alibaba TaoBao FED
- Member of W3C CSS Working Group
- Author of 《剖析 Vue.js 内部运行机制》
🤝🏻 Connect with Me
📖 Blog
🔧 Languages and Tools
:octocat:Vue.js 源码解析
Home Page: https://github.com/answershuto/learnVue
🤝🏻 Connect with Me
📖 Blog
🔧 Languages and Tools
defineReactive中的childOb我的理解好像并不是子元素,虽然这里调用会完成子元素的监听
childOb就是当前监听元素的Observer的实例引用 dep里面记录的和当前监听的元素应该一样 我觉得这里只是为了记录Object类型的元素的订阅(我还没发现有什么实际的作用)
const dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: ()=>{
if (Dep.target) {
dep.addSub(Dep.target);
}
},
set:newVal=> {
dep.notify();
}
})
**Dep.target 是全局的watcher, 假如options.data = {name:'demo', age:12}, 对它进行绑定的话,dep会两次把全局的Watcher push到自己的subs中,这样重复做有什么意义?或者理解有问题? **
learnVue/vue-src/core/instance/events.js
Line 54 in 3bb4002
function defineReactive (obj, key, val, cb) {
let val = val
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: ()=>{
/*....依赖收集等....*/
/*Github:https://github.com/answershuto*/
return val
},
set:newVal=> {
val = newVal;
cb();/*订阅者收到消息的回调*/
}
})
}
依赖什么时候会被回收呢?目前看依赖收集挺好的,期望能把依赖回收也讲讲。
function initData(vm: Component){}这里的 vm: Component是什么意思啊,不像是解构赋值啊
从源码角度再看数据绑定 中提到$set()及$remove()方法 可注明版本号
const key: ?string = vnode.key == null
开始依赖收集
class Vue { constructor(options) { this._data = options.data; observer(this._data, options.render); let watcher = new Watcher(this, ); } }
class Vue {
constructor(options) {
this._data = options.data;
observer(this._data, options.render);
let watcher = new Watcher(this, ); // 这括号里是没写完吗
}
}
按照代码习惯 data是函数的返回值这里可以理解,但是对象赋值尚不能理解,求大神解答,谢谢!
ob = new Observer(value);//创建ob观察者实例
var Observer = function Observer (value) {
this.value = value;
this.dep = new Dep();//创建发布者实例
this.vmCount = 0;
def(value, '__ob__', this);//将ob实例挂到__ob__属性上,这里会无限循环下去
if (Array.isArray(value)) {
var augment = hasProto
? protoAugment
: copyAugment;
augment(value, arrayMethods, arrayKeys);
this.observeArray(value);
} else {
this.walk(value);
}
};
请问您发现了这儿会无限循环吗,为什么要这样做呢
Hello, it seems to be a good tutorial about vue 2.
Are there any plans to translate it to english?
大佬好!在开始依赖收集章节顶部,新建Watcher的步骤被放在了Vue类的constructor里。然而在查阅源码后,我发现Watcher是由对应平台(Web或Weex)通过在最上层Vue实例的$mount
函数中调用mountComponent
而新建的。请问我的理解是否正确?谢谢!
看到依赖是Watcher.get 设置Dep.target, 通过value = this.getter.call(vm, vm)完成收集依赖的过程, 但仍然没有搞明白state的Watcher是如何初始化的;
(动态设置Dep.target参看了这篇文章https://segmentfault.com/a/1190000010014281)
响应式原理中 app._date.text 应该是app._data.text
updateChildren部分,倒数第二个图有错误,那3个新Vnode, 应该插到 oldStartIdx前面,不是放到后面
Dep.target = null
/依赖收集完需要将Dep.target设为null,防止后面重复添加依赖。/
这里应该只是定义吧,不是依赖收集完将Dep.target设为null?
比如const qnameCapture = '((?:' + ncname + '\:)?' + ncname + ')' const ncname = '[a‐zA‐Z_][\w\‐\.]*'; 这样的正则为什么就可以匹配到标签了呢 那一整章节后面都不大理解,痛苦
这里如果当前浏览器支持__proto__属性,则直接覆盖当前数组对象原型上的原生数组方法,如果不支持该属性,则直接覆盖数组对象的原型。
这里写反了吧
如果支持__proto__属性,直接覆盖数组对象的原型,不支持则一一覆盖数组对象上的方法
everlose指出《依赖收集》中有一处表述错误。
订阅者,当依赖收集的时候会addSub到sub中,在修改data中数据的时候会触发Watcher的notify,从而回调渲染函数。
Vue.prototype.$emit = function (event: string): Component {
const vm: Component = this
if (process.env.NODE_ENV !== 'production') {
const lowerCaseEvent = event.toLowerCase()
if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
tip(
`Event "${lowerCaseEvent}" is emitted in component ` +
`${formatComponentName(vm)} but the handler is registered for "${event}". ` +
`Note that HTML attributes are case-insensitive and you cannot use ` +
`v-on to listen to camelCase events when using in-DOM templates. ` +
`You should probably use "${hyphenate(event)}" instead of "${event}".`
)
}
}
let cbs = vm._events[event]
if (cbs) {
/*将类数组的对象转换成数组*/
cbs = cbs.length > 1 ? toArray(cbs) : cbs
const args = toArray(arguments, 1)
/*遍历执行*/
for (let i = 0, l = cbs.length; i < l; i++) {
cbs[i].apply(vm, args)
}
}
return vm
}
$emit的时候,为什么要将类数组换成数组?哪里有类数组?什么情况下会是类数组?我看就是$on的时候Vue自己建的数组啊
不知道Vue的源码是哪个版本?
learnVue/vue-src/core/instance/events.js
Line 80 in 9839fd9
在响应式原理当中,需要将 _proxy(options.data);/*构造函数中*/
写在构造函数当中,
function observer(value, cb) {
Object.keys(value).forEach(key => defineReactive(value, key, value[key], cb));
}
function defineReactive(obj, key, val, cb) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: () => {
/*....依赖收集等....*/
/*Github:https://github.com/answershuto*/
},
set: newVal => {
cb(); /*订阅者收到消息的回调*/
}
});
}
function _proxy(data) {
const that = this;
console.log(that);//====>在chrome下输出window?
Object.keys(data).forEach(key => {
Object.defineProperty(that, key, {
configurable: true,
enumerable: true,
get: function proxyGetter() {
return that._data[key];
},
set: function proxySetter(val) {
that._data[key] = val;
}
})
});
}
class Vue {
constructor(options) {
this._data = options.data;
console.log(this) //===> app
_proxy(options.data);/*构造函数中*/
observer(this._data, options.render);
}
}
let app = new Vue({
el: '#app',
data: {
text: 'text',
text2: 'text2'
},
render() {
console.log('render');
}
});
为啥在function _proxy
中输出的this
指向了window
?
_proxy
传入了指定的context
,输出才正确function observer(value, cb) {
Object.keys(value).forEach(key => defineReactive(value, key, value[key], cb));
}
function defineReactive(obj, key, val, cb) {
Object.defineProperty(obj, key, {
enumerable: true, // 当且仅当该属性的enumerable为true时,该属性才能够出现在对象的枚举属性中。
configurable: true, // 当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。
get: () => {
/*....依赖收集等....*/
/*Github:https://github.com/answershuto*/
},
set: newVal => {
cb(); /*订阅者收到消息的回调*/
}
});
}
function _proxy(data,context) {
const that = context;
console.log(that);//====>app
Object.keys(data).forEach(key => {
Object.defineProperty(that, key, {
configurable: true,
enumerable: true,
get: function proxyGetter() {
return that._data[key];
},
set: function proxySetter(val) {
that._data[key] = val;
}
})
});
}
class Vue {
constructor(options) {
this._data = options.data;
_proxy(options.data,this);/*构造函数中*/
observer(this._data, options.render);
}
}
let app = new Vue({
el: '#app',
data: {
text: 'text',
text2: 'text2'
},
render() {
console.log('render');
}
});
模版:
new Vue({
data: {
test: {a: 1}
},
el: "#app"
})
我通过浏览器断点逐行调试发现,在data被observe后,此时data下的__ob__.dep.subs集合为空数组,当执行Vue.prototype.$mount时new Watcher(),得到render watch调用watch.get()求值,这个get函数第一句pushTarget(watch)这一步后立马可以看到data下的__ob__.dep.subs下添加了这个render watch,而vuejs里watch是通过addSub添加到subs的,但此时并没有发现调用dep的addSub方法,关键的是这个时候没有到render那里去,所以依赖收集无从谈起,请教这个如何做到的?
您好
请问 src\core\vdom\create-element.js createElement函数里
if (Array.isArray(data) || isPrimitive(data)) {
debugger
normalizationType = children
children = data
data = undefined
}
我不懂为什么要这么做,还希望能够指点一下,万分感谢!
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: ()=>{
if (Dep.target) {
/*Watcher对象存在全局的Dep.target中*/
dep.addSub(Dep.target);
}
},
set:newVal=> {
/*只有之前addSub中的函数才会触发*/
dep.notify();
}
})
我这么理解的:关于大佬所说的例子中的如果text3改变了会不会触发视图更新问题关键点在于是不是触发get 方法
还有就是 这个watch中有一个问题如果是三个数据都会触发视图更新的话 会触发三次get方法也就是说这时Dep.target都会有值而在这里没有对subs判断去重会不会有重复的watcher实例存在?这些疑问还望大佬能帮我解惑答疑 @answershuto
这个可以用es6的 Proxy 实现
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.