angular之scope.$watch
某“大神”挖了个陨石坑,我于是乎似懂非懂的接手,玩了一个月angular。现在项目告一段落,暂别了繁重的重复性工作,可以开始回顾、认真的折腾下之前犹抱琵琶的angular。
angular吸引人的特性之一就是双向绑定,model有变化view自动更新。一说到自动执行,首先浮到脑海的必须是监听和回调函数。angular也确实是这样做的,scope.$watch就是此行为的接口。一如所有的类库或框架,使用起来很简单,实现却并不容易。
我不是一个执念于从零开始的人,喜欢站在巨人的肩上,这篇随笔取材于此:创建你自己的AngularJS。
首先,先搭建一个单元测试环境,如果嫌麻烦当然可以不用,如果不会可以在node环境下输入以下命令行:
npm install -g grunt //安装grunt
npm install -g bower //安装bower
npm install -g yo //安装yeoman
npm install -g generator-angular //安装angular生成器
yo angular //生成angular项目文件
npm install //安装项目依赖包
bower install //安装前端依赖库
grunt test //执行单元测试
以上命令来自记忆,或有遗漏,总之如果执行成功表示环境搭建完毕。
接下来开始编写自己的scope,我们先整理下测试环境,在angular/test/spec下建立两个文件夹,命名随意,我是define和unit。
修改angular/test/karma.conf.js中的files字段:
files: [
"test/spec/define/*.js",
"test/spec/unit/*.js"
]
然后可以新建Scope类了,在spec/define中新建scope.js:
'use strict';
function Scope() {
}
是的,只是一个简单的构造函数。
然后在spec/unit中新建test.js,编写单元测试语句:
'use strict';
describe("Scope", function() {
it("can be constructed and used as an object", function() {
var scope = new Scope();
scope.prop = 1;
expect(scope.prop).toBe(1);
})
});
如无意外,打印出来的结果是这样的:
PhantomJS 1.9. (Windows ): Executed of SUCCESS ( secs / 0.001 secs)
其中PhantomJS是一个不展示用户界面的浏览器,测试的代码就是在这里面跑的。
模型已经搭建完成,现在我们要实现的是监听scope内部prop属性的变动。要实现监听,首先内部要有监听器的队列,其次要有添加监听器的函数,最后还要有轮询检测的函数,分别在scope.js中添加这三个内容:
'use strict';
function Scope() {
this.$$watchers = [];
}
Scope.prototype.$watch = function() {
var watcher = {};
this.$$watchers.unshift(watcher);
}
Scope.prototype.$digest = function() {
}
一般类库框架,对于添加监听器的函数都支持两个参数,字符串和回调函数,比如jquery中的on。对于我们来说,字符串应该是要监听的属性。修改$watch函数如下:
Scope.prototype.$watch = function(prop, callback) {
var watcher = {
prop: prop,
callback: callback
};
this.$$watchers.unshift(watcher);
}
现在轮到$digest函数,轮询函数的任务在于遍历所有的监听器,比较当前属性和上一个属性是否不同,不同则执行监听器中的callback。等等,上一个属性怎么获取的,看来添加监听器的时候应该保存一个初始值,于是两个函数都要修改:
Scope.prototype.$watch = function(prop, callback) {
var watcher = {
prop: prop,
callback: callback,
last: this[prop]
};
this.$$watchers.unshift(watcher);
};
Scope.prototype.$digest = function() {
var scope = this;
scope.$$watchers.forEach(function(watcher) {
if(scope[watcher.prop] !== watcher.last) {
watcher.last = scope[watcher.prop];
watcher.callback();
}
});
};
现在感觉功能已经实现了,但实际上有没有用呢,要写个单元测试验证一下,修改test.js:
'use strict';
describe("Scope", function() {
it("can be constructed and used as an object", function() {
var scope = new Scope(),
callback = jasmine.createSpy(); //创建一个可检测是否被调用的回调函数
scope.prop = 1; //初始化值
scope.$watch('prop', callback); //添加监听函数
scope.prop = 2; //重新赋值
scope.$digest(); //轮询一遍
expect(callback).toHaveBeenCalled(); //检测回调函数是否被调用
});
});
执行grunt test,得到的结果如下:
PhantomJS 1.9.8 (Windows 8): Executed 1 of 1 SUCCESS (0.004 secs / 0.003 secs)
可见是正常可用的,但是不是哪里写错了导致怎么执行都正常呢,我们注释掉重新赋值的这行:
//scope.prop = 2;
得到的测试结果如下:
PhantomJS 1.9.8 (Windows 8): Executed 1 of 1 (1 FAILED) ERROR (0.01 secs / 0.002 secs)
严格测试下,添加两个监听器:
'use strict';
describe("Scope", function() {
it("can be constructed and used as an object", function() {
var scope = new Scope(),
callback1 = jasmine.createSpy(),
callback2 = jasmine.createSpy();
scope.prop1 = 1;
scope.prop2 = 1;
scope.$watch('prop1', callback1);
scope.$watch('prop2', callback2);
scope.prop1 = 2;
scope.prop2 = 2;
scope.$digest();
expect(callback1).toHaveBeenCalled();
expect(callback2).toHaveBeenCalled();
});
});
结果也是没有问题的:
PhantomJS 1.9.8 (Windows 8): Executed 1 of 1 SUCCESS (0.004 secs / 0.002 secs)
基本功能是没问题了,但还需要优化。
首先,代码不够“优雅”。监听器中的prop属性唯一的用处就是在$digest中供取值,而且为此$digest需要将this重新付给scope变量导致$digest中代码偏乱。把scope.js的代码整理一下:
'use strict';
function Scope() {
this.$$watchers = [];
}
Scope.prototype.$watch = function(prop, callback) {
var scope = this,
watcher = {
get: function() {
return scope[prop];
},
callback: callback,
last: scope[prop]
};
scope.$$watchers.unshift(watcher);
};
Scope.prototype.$digest = function() {
this.$$watchers.forEach(function(watcher) {
if(watcher.get() !== watcher.last) {
watcher.last = watcher.get();
watcher.callback();
}
});
};
这样是不是整洁多了?
还有,回调函数现在是不传入参数的,而按照习惯来说,应该传入新值和旧值吧。所以要对$digest和test.js作出修改:
Scope.prototype.$digest = function() {
this.$$watchers.forEach(function(watcher) {
if(watcher.get() !== watcher.last) {
watcher.callback(watcher.get(), watcher.last);
watcher.last = watcher.get();
}
});
};
'use strict';
describe("Scope", function() {
it("can be constructed and used as an object", function() {
var scope = new Scope(),
newValue, oldValue,
callback = function(newV, oldV) {
newValue = newV;
oldValue = oldV;
};
scope.prop = 1;
scope.$watch('prop', callback);
scope.prop = 2;
scope.$digest();
expect(newValue).toBe(2);
expect(oldValue).toBe(1);
});
});
测试的结果是ok的。
最后,当我们在回调函数中对设置了监听器的属性进行赋值时,会出现问题,比如修改test.js:
'use strict';
describe("Scope", function() {
it("can be constructed and used as an object", function() {
var scope = new Scope(),
callback1 = function(newV, oldV) {
scope.prop2 = 2;
scope.counter++;
},
callback2 = function(newV, oldV) {
scope.prop1 = 2;
scope.counter++;
};
scope.prop1 = 1;
scope.prop2 = 1;
scope.counter = 0;
scope.$watch('prop1', callback1);
scope.$watch('prop2', callback2);
scope.prop1 = 2; //失败
//scope.prop2 = 2; //成功
scope.$digest();
expect(scope.counter).toBe(2);
});
});
注意注释的部分,为什么会产生这种偏差呢?因为在$watch中,我们是使用unshift插入监听器的,当在callback1中设置prop2的时候,prop2的监听器已经被轮询过了,所以不再会调用。
那怎么解决这一问题呢?这里就要使用到dirty-checking了。在$digest中不停的对监听器进行轮询,但最少轮询一次,也就是do...while...。当有回调函数被调用时,则置整个轮询的dirty为true,需要进行下一次轮询;当一次轮询中没用任何回调函数被调用,则终止轮询。修改$digest如下:
Scope.prototype.$digest = function() {
var dirty;
do {
dirty = false;
this.$$watchers.forEach(function(watcher) {
if(watcher.get() !== watcher.last) {
dirty = true;
watcher.callback(watcher.get(), watcher.last);
watcher.last = watcher.get();
}
});
} while(dirty)
};
但这样做有一个致命的问题就是,当回调函数这样的时候:
callback1 = function(newV, oldV) {
scope.prop2 += 1;
scope.counter++;
},
callback2 = function(newV, oldV) {
scope.prop1 += 1;
scope.counter++;
};
PhantomJS表示,你根本停不下来:
WARN [PhantomJS 1.9. (Windows )]: Disconnected ( times), because no message in ms. Warning: Task "karma:unit" failed. Use --force to continue.
这里就要设置一个while循环的上限值TTL(Time To Live):
Scope.prototype.$digest = function() {
var dirty, ttl = 10;
do {
dirty = false;
ttl--;
this.$$watchers.forEach(function(watcher) {
if(watcher.get() !== watcher.last) {
dirty = true;
watcher.callback(watcher.get(), watcher.last);
watcher.last = watcher.get();
}
});
} while(dirty && ttl > 0)
};
好了,到这里就结束了。现在去看angular中scope的source code,一定能看到很多眼熟的东西。
angular之scope.$watch的更多相关文章
- angular的$scope
angularJS是一个MVVM的前端js框架. $scope的作用是angular向视图(html)传递数据的通道,它不负责处理和操作数据.也就是说要想向视图传递数据的话,必须定义$scope变量. ...
- angular directive scope
angular directive scope 1.当directive 中不指定scope属性,则该directive 直接使用 app 的scope: 2.当directive 中指定scope属 ...
- Angular - - $rootScope.Scope
这里讲的是一些scope的操作,如创建/注销/各种监听及scope间的通信等等. $rootScope.Scope 可以使用$injector通过$rootScope关键字检索的一个根作用域. 可以通 ...
- angular的$scope的使用
1. 可以在scope中直接使用 // 监听日期变化 $scope.$watch('vaFilter.startEffectiveDate', function(newDate, oldDate, s ...
- angular.js_$scope
Scope(作用域) 是应用在 HTML (视图) 和 JavaScript (控制器)之间的纽带. Scope 是一个对象,有可用的方法和属性. Scope 可应用在视图和控制器上. Angular ...
- angular中的scope
angular.element($0).scope() 什么是scope? scope是一个refer to the application model的object.它是一个expression的执 ...
- Angular作用域的层级概念(scope)
首先引入 angular 的根作用域:$rootScope ng-app:定义了angualr的作用域 ng-controller:定义了控制器 $scope定义了视图与控制器之间的纽带,而scope ...
- angular 指令作用域 scope
转载自:https://segmentfault.com/a/1190000002773689 下面我们就来详细分析一下指令的作用域. 在这之前希望你对AngularJS的Directive有一定的了 ...
- angular controller as syntax vs scope
今天要和大家分享的是angular从1.2版本开始带来了新语法Controller as.再次之前我们对于angular在view上的绑定都必须使用直接的scope对象,对于controller来说我 ...
随机推荐
- ddos攻击和cc攻击的区别和防护!!
什么是DDoS攻击? DDoS攻击就是分布式的拒绝服务攻击,DDoS攻击手段是在传统的DoS攻击基础之上产生的一类攻击方式.单一的DoS攻击一般是采用一对一方式的,随着计算机与网络技术的发展,DoS攻 ...
- JS实现添加至购物车功能
效果图展示: 当将书拖拽至购物车一览时: 首先将页面的基本结构写出来: <!DOCTYPE html> <html lang="en"> <head& ...
- CentOS编译PHP过程中常见错误信息的解决方法
原文链接:http://www.linuxidc.com/Linux/2014-05/102327.htm ********************************************** ...
- java ArrayList 踩坑记录
做编程的一个常识:不要在循环过程中删除元素本身(至少是我个人的原则).否则将发生不可预料的问题. 而最近,看到一个以前的同学写的一段代码就是在循环过程中删除元素,我很是纳闷啊.然后后来决定给他改掉.然 ...
- [STM32F429-DISCO-HAL]4.Uart 的使用
今天来学习一下最常用的外设之一USART. 首先是硬件的连接,我们需要至少三根线,GND,TX,RX.参阅datasheet.默认的USART1_TX和USART1_RX的引脚如下图 关于HAL dr ...
- [算法题] Remove Duplicates from Sorted Array ii
题目内容 本题来源LeetCode Follow up for "Remove Duplicates": What if duplicates are allowed at mos ...
- angular学习(一)-- Expression
1.1 表达式:Expression 在AngularJS中,表达式是一种类似于模板引擎的语法, 可以在书写的位置 "输出" 数据. 基本使用 表达式写在双大括号内:{{ expr ...
- ECC椭圆曲线详解(有具体实例)
前言 ECC英文全称"Ellipse Curve Cryptography" 与传统的基于大质数因子分解困难性的加密方法不同,ECC通过椭圆曲线方程式的性质产生密钥 ECC164位 ...
- 【javascript】函数中的this的四种绑定形式
目录 this的默认绑定 this的隐式绑定 隐式绑定下,作为对象属性的函数,对于对象来说是独立的 在一串对象属性链中,this绑定的是最内层的对象 this的显式绑定:(call和bind方法) n ...
- 如何在GitHub上生成ssh公钥并用NetBeans克隆项目
一.生成ssh公钥. 1.首先判断本机是否创建了公有密钥: $ ls ~/.ssh 这个命令用于检查是否已经存在 id_rsa.pub 或 id_dsa.pub 文件,如果文件已经存在,下面步骤可省略 ...