-
Notifications
You must be signed in to change notification settings - Fork 383
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
vue早期源码学习系列之一:如何监听一个对象的变化 #84
Comments
怎么去用chrome浏览器的控制台查看以及修改js数据啊,好神奇,还有这个动画是怎么做的 |
gif录制软件licecap @physihan |
你好。我想问一下如何查看一个开源项目的commit记录,之前一直以为在github上面就有。结果找半天没有找到 |
@zhangguixu 点击此处 |
thk~ 看到了。好蠢。。这么明显都没有看到。﹌○﹋ @caiyongmin |
你好,在vue里提供了vm.$set()和Vue.set()两个方法去在更新对象属性后监听到改变,但是在单文件组件的开发方式下如何使用呢?貌似在控制台打印出this没有$set()方法,Vue根本没有 |
我想用你的方法checkout react最初的版本 发现commit 有8000多 这怎么找到最初的那个版本额 要是一页页翻简直不可能完成啊 求解~~~ |
@zhouxiaoyan 然而,现在就不容易了。因为github改版了,现在的commits分页页面是这样的https://github.com/facebook/react/commits/master?after=Y3Vyc29yOstm9cRAV8BHxA0tkrQuPluXwVcZKzM0 且这个after参数并非commit的id,这就很烦人了。 ================分割线=============== 缺点:由于是公共api,所以会有调用频次的限制。(可以通过注册应用来解决,不过很麻烦) 然而,当你去查看这个commit的时候,你会发现有点懵逼。。因为即便是第一个commit,那代码也至少有成千上万行。究其原因,是因为react一开始就是facebook的内部产物,人家在内部开发得差不多了,才开源到github上,所以,之前的commit都丢失了,无法追溯。 另外,要提一点,那就是分支的选择。因为commit是可以通过rebase来重置合并的,比如像vue现在的master分支,你追溯master分支最早的commit,会发现是2016年的,这显然是合并过的。要想找到最最最初的版本,有时候你还得合理地选择分支,比如选择branch 0.11,这是vue的早期版本,其commit并没有被合并,所以可以通过公共api查询到。 不过,这些方法并不完善,请灵活使用。 总的来说,自github改版之后,我就没有找到非常快捷又方便的方法。如果你找到了,欢迎交流。 |
haonan |
关于遗留问题2,提出一个方案: |
还是没懂 |
@Larixs 我也是这样去修复的,不知道这样有没有什么弊端呢???希望楼主可以解答一下,谢谢 |
@RebornL 之前做练习也是这样写的,大概是重新new一个对象会导致所有数据重置 |
如何监控一个对象,我已经看懂 |
大佬为什么set的那个方法中最后为什么val = newVal? 只要你这样app.data.name="oo"; val就会被更改成newVal啊 这一步算不算多余的? |
意思是getter或setter和writable不能同时设置。 so,没有设置 |
有一点不明白,set函数中val只是一个形参,它是怎么影响到属性值的? |
和楼上一样的问题,想了半天也没想通,val确实不是引用类型。 |
@fishelren,不知道你有没有得到答案?起先我以为val = newVal会调用get方法,导致属性值改变,但是调试发现并没有进get方法。 |
@FisNew ,我后来的理解是这样的val不是改变了吗?get方法不是也要返回val吗,下次返回的值就是val的新值了,不过这么想多少都觉得有点自欺欺人。。。。。。 |
@fishelren 但是单步调试会发现,他就是在赋值语句val = newVal里面改变的属性值。。。 |
这是个好问题,在理解这个问题之前,请务必先阅读阮一峰老师关于闭包的这篇文章,其中重点是第四小节:闭包的用途。 我的看法如下:
如果还不明白,让我们对比着阮一峰老师的例子看看。 // 这个函数 f1 像极我们的 convert 函数
function f1() {
var n = 999; // 类似于我们定义的 val
nAdd = function () { // 类似于我们的 setter,只不过这里固定地加1而已
n += 1
}
function f2() { // 类似于我们的 getter
alert(n);
}
return f2;
}
var result = f1(); // 类似于调用 convert 方法,对 data 的 key 进行定义,data.key 初始值为 999
result(); // 999 // 类似于执行 data.key ,调用 getter,得到 n 的值,为 999
nAdd(); // 类似于执行 data.key = data.key + 1,调用 setter,使得 n++。此时,变量 n 的值为 1000
result(); // 1000 // 再次访问 data.key,返回当前 n 的值,也就是 1000
// 神奇的地方来了,现在我们需要定义 key2 了
var result2 = f1();
result2(); // 999
// 请注意,这里输出的 n 是999,并非是上面已经加 1 的 1000 了;
// 因为此处的 n 和 上面的 n 本质上是同时存在的两个不同变量!! 不知道我有没有说明白? @fishelren @FisNew |
@youngwind,感谢解答!明白了。以前看过阮一峰老师这篇文章,但是没有理解到,现在算是真正的理解闭包了。 |
@youngwind 非常感谢老师的指点!很喜欢老师的源码解析系列文章~ |
有一个疑惑,getter函数为什么要返回val值(return val)?从代码流程上来看这里只是单纯的返回一个对应的值,可是为什么要这么做?将该语句注释后运行data.user.name,浏览器直接报错,FF显示"TypeError: data.user is undefined",chrome显示"Uncaught TypeError: Cannot set property 'name' of undefined",可是name属性确实存在啊,希望老师解惑。 |
@wangshengkun ECMAScript中指定了两种属性:数据属性和访问器属性。 |
@youngwind 老师,我觉得你举的例子好像有点问题,我改了下了一下代码,这跟你写的p.convert函数一样,变量值使用的是形参,而不是闭包搜索n的值,但结果是作为形参传进去的值n执行完操作是不会修改原n值的。你写的p.convert是通过形参传递的参数,而不是通过闭包,所以val是怎么赋上的新值我还不太明白。就算是通过闭包,你在闭包作用域内定义的let val = obj[key],obj[key]是基本类型,通过函数修改闭包作用域内的值val,也不会改变obj[key]的值吧?不知道是不是我理解错了,有点混乱。
|
好赞 这个读源码的思路,准备把你写的系列vue源码分析文章看完 |
@Junx123 |
@Junx123 你的函数在内部执行nAdd(n)时,n(1000)不是闭包对象。 |
@youngwind 找到库作者的第一次提交,对于一些比较流行的库,对于打了tag,可以通过最早打的tag来查看第一次commit , 就算不能看到最真实的第一次提交,从学习的角度,基本能满足需求了 |
嗯,有早期 tag 的话,会更加方便的。tag 所处状态,功能往往会比较完整,文档也会相对完善,易于学习。 @hitao123 |
@Junx123 这个是作用域链的问题,推荐一篇文章: http://bubkoo.com/2014/06/01/ecma-262-3-in-detail-chapter-4-scope-chain/ |
感谢博主的文章,受益匪浅。 // Observer构造函数
// Observer构造函数为参数对象设置访问器属性,以实现数据观察
function Observer(data) {
this.data = data
this.walk(data)
}
const proto = Observer.prototype
proto.walk = function (data) {
let val
for (const key in data) {
if (data.hasOwnProperty(key)) {
// 以对象属性的初始值设置val
val = data[key]
if (typeof val === 'object') {
// 如果val仍是对象,以val为参数调用Observer构造函数
// 该构造函数调用为val对象中的属性设置getter和setter
new Observer(val)
}
else {
// 调用convert函数,将对对象数据属性的访问转换为对访问器属性的访问
this.convert(val, key)
}
}
}
}
proto.convert = function (val, key) {
// 访问器实际上访问和修改的值是闭包val,仅仅用对象数据属性进行初始化
Object.defineProperty(this.data, key, {
configurable: true,
enumerable: true,
get: function () {
console.log('你访问了:' + key)
return val
},
set: function (v) {
console.log('你设置了:' + key)
console.log(key + ' = ' + v)
if (v !== val) {
val = v
}
}
})
} |
请问
这里 this.data = data 的含义。 |
把 data 缓存起来
在 2018-08-19 17:53:02,"niudiewei" <notifications@github.com> 写道:
请问
function Observer(data) {
this.data = data;
this.walk(data);
}
这里 this.data = data 的含义。
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub, or mute the thread.
|
@Junx123 这里确实是闭包的原因导致修改val会影响对象的值,这里的闭包应该是set和get 函数,虽然没有明显的return这两个函数到convert之外,但是JavaScript将这两个函数暴露出去了,当你访问或者赋值app对象的时候,就会调用get或者set函数,而这两个函数都访问了val这个值,所以set会影响get。 |
@youngwind 您好,在下对 convert这个函数有一些小疑问,在set函数中 为什么 不给 key赋值,而给val赋值,我的意思是 为什么 不用 key = newval, 我尝试后发现这样是行不通的 求解答? |
@HuoXiaoYe |
@muzea 非常感谢您的回答,我自己对Object.defineProperty()方法存在了误解,现在大致明白了。再次感谢您的指导。 |
对 convert 的函数 写成这样是不是好理解些 |
前言
我们都知道,要想精通前端领域,研究分析成熟的框架是必不可少的一步。很多人可能都有这样的体会:“很努力地去阅读一些热门框架的源码,但是发现难度太高,花了很多时间却得不到什么,最终不得不放弃。”
我也一直被这个问题困扰,直到我想到了这样的一个方法。
从成熟框架的早期源码开始看起,从作者的第一个commit开始看起,然后逐个的往前翻。这样一开始的代码量不多,多看几遍还是可以理解的。而且在这个过程中,就像电影回放一样,我们可以看到作者先写什么,后写什么,在哪些地方进行了什么样的改良,其中又不小心引入了什么bug,等等。
这真的是一个很好的办法。所以,我就用这个方法来研究vue的源码。
目标
我checkout到的版本是这个位置,在这个版本中,我们可以发现:代码中主要是observer和emitter这些东西,这些是以后实现数据绑定以及$watch的关键基础。所以我们把本篇的学习目标定位为:“如何监听一个对象的变化” 具体要实现效果如下图所示。
注:这个版本build出来的vue代码运行是会报错了,根据错误提示,很容易定位到错误原因是src/internal/init.js文件头部少引了observer和util,添加上就好了。
思路
我们有两个难点需要解决。
第一:当对象的某个属性变化的时候,如何触发自定义的回调函数?
答案:ES5中新添加了一个方法:Object.defineProperty,通过这个方法,可以自定义getter和setter函数,从而在获取对象属性和设置对象属性的时候能够执行自定义的回调函数。
第二:对象往往是一个深层次的结构,对象的某个属性可能仍然是一个对象,这种情况怎么处理?
比如说
答案:递归算法,也就是下面代码中的walk函数。如果对象的属性仍然是一个对象的话,那么继续new一个Observer,直到到达最底层的属性位置。
下面是实现的具体代码。
代码
遗留问题
上面实现的代码还有很多问题。
比如:
参考资料:
The text was updated successfully, but these errors were encountered: