XIANFESchool / FE-problem-collection

前端问题收集和知识经验总结
https://github.com/ShuyunXIANFESchool/FE-problem-collection/issues
63 stars 22 forks source link

JS event loop 与 AngularJS 的问题? #11

Open hjzheng opened 8 years ago

hjzheng commented 8 years ago

今天 Fix bug 遇到一个典型的问题, 先简单描述一下该问题:

<div ng-click="vm.toggle()"></div>
<div ng-show="vm.show">这是测试内容</div>
<div id="test">这是测试内容</div>
vm.toggle = function() {
     vm.show = !vm.show
     console.log($document[0].getElementById('test').offsetTop); 
};

参看上面的例子,其实我们是想得到中间 div 隐藏后,最后一个 div 的 offsetTop, 实际上得到的是中间 div 隐藏前的 offsetTop。

为什么呢,因为实际 toggle 中变量的变化,还没有引起 dom 变化,也就是脏检查并没有结束,Dom 也没有重新渲染。此时拿到的 offsetTop 肯定不是我们想要的。

可以参看此图:

qq20160707-1 2x

那么怎么办呢,大家都知道 js 是单线程的,存在一个 event loop 来处理异步任务,这些异步任务存放在一个队列里, 所以我们只需要将获取 offsetTop 操作放入这个队列里就 OK。 当 ng-click 中的函数,AngularJS的脏检查等如上图所示 在队列中执行完毕后,就轮到我们的获取 offsetTop 的异步任务了。

很简单

vm.toggle = function() {
     vm.show = !vm.show
     $timeout(function() {
          console.log($document[0].getElementById('test').offsetTop); 
     }, 0, false);
};

参考资料: https://docs.angularjs.org/guide/scope http://www.ruanyifeng.com/blog/2014/10/event-loop.html http://notes.iissnan.com/2014/waiting-for-dom-to-finish-rendering/

kuitos commented 8 years ago

$timeout 虽然是一个解决方案,但是它更像 hack 而不是 resolve,这里应该用 $scope.$$postDigest(fn) 来添加 dirty-check done hook,虽然是私有 api 但是方案上更 语义 & 贴近问题本质。 通常情况下在 ng 里要用到这个手段都是万不得已的情况,不建议当作合理场景,你可以从代码设计上再去考虑有没有更合理的方案。

hjzheng commented 8 years ago

@kuitos 👍 之后 AngularJS1.x 会添加方法$postDigestWatch https://github.com/angular/angular.js/issues/5828 去专门处理这种情况吧!