手机版

深入分析JavaScript框架主干. js中的事件机制

时间:2021-09-17 来源:互联网 编辑:宝哥软件园 浏览:

事件模型及其原理主干。事件是事件实现的核心,它可以使对象具有事件能力。

Varevents=主干。事件={ 0.}对象通过listenTo listenTo其他对象,并通过触发器触发事件。您可以在没有主干MVC的自定义对象上使用事件。

var模型=_。扩展({},主干。事件);var view=_。扩展({},主干。事件);view.listenTo(模型,‘custom _ event’,函数(){ alert(‘catch the event’)});model . trigger(' custom _ event ');执行结果:

2016214153156830.jpg  (644434)

主干的模型和视图核心类继承自主干。事件.例子:骨干。型号:

var事件=主干。事件={ 0.}var模型=主干。模型=功能(属性、选项){ 0.};_.扩展(模型。原型,事件,{ 0.})原则上,事件是这样工作的:

被拦截的对象维护一个事件array _event,其他对象在调用listenTo时维护队列中的事件名称和回调:

2016214153253355.png  (367193)

一个事件名称可以对应多个回调。对于听者来说,他只知道回调的存在,但不知道哪个对象在听。当侦听器调用trigger(name)时,它将遍历_event,选择同名事件,并执行其下的所有回调一次。

应该注意的是,主干的listenTo实现不仅使侦听器能够维护对侦听器的引用,还使侦听器能够维护侦听器。这是因为听众可以在合适的时间单方面中断收听。因此,虽然它是一个循环引用,但使用主干的正确方法可以很好地维护,不会有任何问题,这将在后面的内存泄漏部分中看到。

此外,有时您只希望事件在绑定之后和回调发生时联系绑定。这在对公共模块的一些引用中很有用。ListenToOnce可以做到这一点。

默认情况下,与服务器同步数据的主干实现了一种机制,将模型与RESTful风格的服务器同步。这种机制不仅可以减少开发人员的工作量,还可以使模型更加健壮(在各种异常情况下仍然可以保持数据一致性)。然而,要真正发挥这一作用,匹配的服务器实现非常重要。为了说明这个问题,假设服务器有以下REST风格的接口:

GET /resources获取资源列表POST /resources创建一个资源并返回该资源的全部或部分字段GET /resources/{id}获取一个id的资源详细信息。返回资源的全部或部分字段DELETE /resources/{id}删除资源PUT /resources/{id}更新资源的全部字段PATCH /resources/{id}更新资源的部分字段,返回资源主干的全部或部分字段主要采用以下HTTP方式:

Model.save()逻辑上,根据当前模型是否有id,判断是使用POST还是PUT。如果模型没有id,则表示是新模型,模型的所有字段都将使用POST提交给/resources。如果模型有一个id,这意味着它是一个现有的模型,PUT将用于向/resources/{id}提交模型的所有字段。当传入选项包含patch:true时,Save将生成一个PATCH。Model.destroy()将生成DELETE,目标url为/resources/{id}。如果当前模型不包含id,则不会与服务器同步,因为此时骨干网认为服务器上还不存在该模型,所以不需要删除Model.fetch(),它将生成GET,目标url为/resources/{id},并将获得的属性更新为模型。Collection.fetch()将生成GET,目标url为/resources,并为返回数组中的每个对象自动实例化模型Collection.create()。事实上,将调用Model.saveoptions的参数存在于上述任何一种方法的参数列表中,主干和ajax请求的一些行为都可以通过选项进行修改。可用选项包括:

Wait:可以指定在更新模型之前是否等待服务器的返回结果。默认情况下,不要等待url:覆盖主干默认使用的url格式。attrs:可以指定将哪些字段保存到服务器。Patch:可与选件一起使用。生成一个补丁来部分更新模型。Patch:指定带有部分更新的REST接口data:将直接传递给jquery的ajax中的数据。其他:选项中的任何参数,只要能覆盖骨干网控制上传数据的所有动作,都会直接传递给jquery的ajax,作为其选项,骨干网会通过Model的urlRoot属性或者Collection的url属性知道具体的服务器接口地址,从而启动ajax。在模型的url默认实现中,除了urlRoot之外,第二个选择将是模型所在的集合的url,因此有时只需要在集合中写入url。

主干将根据对服务器执行的操作类型来决定是否在url后添加id。以下代码是Model的默认url实现:

url:函数(){ var base=_。结果(这个,‘urlRoot’)| | _。result(this.collection,' URL ')| | urlError();if (this.isNew())返回基数;返回base.replace(/([^\/])$/,' $ 1/')encodeuricomponent(this . id);},其中正则表达式/([\/])$/是一种巧妙的处理,最终解决了url是否包含“/”的不确定性。

这种常规匹配是行尾的non/字符,这样目标like /resources就会匹配s,然后在replace中使用分组号$1捕获s,用s/替换s,这样就会自动添加缺失的/;当/resources/的目标与结果不匹配时,没有必要替换它。模型和集合之间的关系是主干。即使类的模型实例确实在集合中,也不一定要使用集合类。但是,使用集合还有其他一些好处,包括:

url继承模型可以在集合属于集合之后继承集合的url属性。收集遵循下划线90%的收集和数组操作,使收集操作极其方便:

//我们希望在集合上实现的下划线方法。//主干集合90%的核心有用性实际上已经实现//right here : var methods=[' forEach ','每个',' map ',' collect ',' reduce ',' foldl ',' inject ',' find ',' detect ',' filter ',' select ',' reject ',' every ',' all ',' some ',' any ',' include ',' invoke ',' max ',' min ',' toArray ',' size ',' first ',' head ',' take ',' initial ',' rest ',' tail ',' drop '主干巧妙地使用以下代码将这些方法附加到集合中://mix在每个下划线方法中作为“集合#模型”的代理。_.每一个(方法,函数(方法){ collection . prototype[method]=function(){ var args=slice . call(arguments);//将参数数组转换为实数组args . unshift(this . models);//取Collection用来维护集合的数组作为return _[方法]的第一个参数。apply(_,args);//使用apply调用下划线}的方法;});自动拦截转发集合中的Model事件集合可以自动拦截转发集合中元素的事件,有些事件集合会进行相应的特殊处理,包括:

在监听到元素的销毁事件后,销毁将自动从集合中移除元素并引发移除事件。change:id在元素的id属性更改后,会自动更新模型的内部引用关系。自动模型构建可以利用collection的fetch加载服务器数据集合,同时自动创建相关的模型实例并调用构建方法。

元素重复判断集合会根据模型的idAttribute指定的唯一键来判断一个元素是否重复。默认情况下,唯一键是id,它可以被idAttribute覆盖。当一个元素被复制时,您可以选择是丢弃复制的元素还是合并两个元素。默认情况下是将其丢弃。

有时,从REST接口获得的数据不能完全满足接口的处理要求。在实例化主干对象之前,可以使用Model.parse或Collection.parse方法对数据进行预处理。一般来说,Model.parse用于处理单个返回对象的属性,Collection.parse用于处理返回的集合,通常会过滤掉不必要的数据。例如:

//只选择类型=1=backbone . collection . extend({ parse : function(models,options){ return _ })的bookvarbooks。过滤器(模型,函数(模型){返回模型。type==1;})} })//向Book对象添加url属性以呈现varbook=backbone . model . extend({ parse : function(model,options) {return _)。extend (model,{ URL 3360 '/books/' model . id });}})通过Collection的获取自动实例化模型,并调用其解析。

模型模型的默认值可以通过设置defaults属性来设置,这非常有用。因为获取数据是异步的,无论是模型还是集合,视图的呈现都可能在数据到达之前完成。如果没有默认值,一些使用模板引擎的视图可能会在渲染时出错。例如,由于使用with(){}语法,下划线自己的视图引擎将报告一个错误,因为对象缺少属性。

视图的elBackbone的视图对象非常短,对于开发人员来说,他们只关心一个el属性。El属性可以通过五种方式给出,优先级从高到低:

实例化视图时,在类中实例化视图时传递el声明el,在类中声明tagName时传递tagName,如何使用默认的‘div’取决于以下几点:

一般来说,如果模块是公共模块,那么el不是在类中提供的,而是在实例化的时候从外部导入的,这样就可以保持公共View的独立性,并且tagName不依赖于现有的DOM元素。一般来说,它对于自包含视图很有用,例如,表中的一行tr,ul中的一行li,以及一些DOM事件必须在html存在的情况下成功绑定,例如模糊。对于此视图,只能选择现有的html视图类,并且可以导出几个属性。

//要合并为属性的视图选项列表. var viewOptions=['model ',' collection ',' el ',' id ',' attributes ',' className ',' tagName ',' events '];内存泄漏事件机制可以给代码维护带来便利,但由于事件绑定,对象之间的引用变得复杂无序,容易造成内存泄漏。以下写入将导致内存泄漏:

var Task=主干。Model.extend({})var TaskView=主干。View.extend({ tagName: 'tr ',template: _)。模板(' TD %=id %/tdtd %=summary %/tdtd %=description %/TD '),initialize : function(){ this . listento(this . model,' change ',this . render);},render:函数(){ this。$ El . html(this . template(this . model . to JSON()));归还这个;}})var TaskCollection=主干。collection . extend({ URL : ' http://API . test . clipper RM.com/API/test tasks ',model: Task,comparator : ' summary ' })var TaskCollectionView=主干。view . extend({ initialize : function(){ this . listento(this . collection,' add ',this . addone);this.listenTo(this.collection,' reset ',this . render);},addOne:函数(任务){ var view=new task view({ model : task });这个。$el.append(view.render()。$ El);},render:函数(){ var _ this=this//简单粗暴地清空DOM当调用排序事件触发的渲染调用时,之前实例化的TaskView对象会泄漏这一点。$ El . empty();this.collection .每个(函数(模型){ _this.addOne(模型);})返回此;}})使用以下测试代码并结合Chrome的堆内存快照来证明:

var tasks=nullvar tasklist=null$(function () { //body.$('#start ')。单击(function(){ tasks=new TaskCollection();task list=new TaskCollectionView({ collection : tasks,El : ' # task list ' })task list . render();tasks . fetch();}) $('#refresh ')。单击(function(){ tasks . fetch({ reset : true });}) $ ('# sort ')。点击(function(){//将监听排序放在这里,避免第一次加载数据后自动排序,触发排序事件,可能会混淆task list . listen ence(tasks,‘sort’,task list . render);tasks . sort();})})点击开始,使用Chrome“Profile”下的“拍摄堆快照”功能,检查当前堆内存情况,使用子类型过滤器,可以看到主干对象(1 1 4 4)有10个实例:

2016214153540966.jpg  (929520)

之所以用child来过滤,是因为我们的类继承了主干的类型,继承使用了重写原型的方法。当主干继承时,变量名是子变量。最后,返回子对象并排序后,再次拍摄快照,可以看到实例数变成了14个。这是因为在渲染过程中,创建了四个新的任务视图,但是之前的四个任务视图没有发布(原因是:

2016214153605533.jpg  (940588)

再次点击排序,再次抓取快照,实例数量增加4到18个!

2016214153626639.jpg  (934614)

那么,为什么每次排序后都不能释放之前的TaskView呢?因为TaskView的所有实例都是听模型的,这就导致了对新创建的TaskView实例的引用的存在,旧的TaskView无法删除,而创建了一个新的,这就导致了内存的不断增加。而且由于引用存在于变更事件的回调队列中,模型每次触发变更时都会通知旧的TaskView实例,导致很多无用代码的执行。那么如何改善呢?

要修改任务集合视图:

var TaskCollectionView=主干。view . extend({ initialize : function(){ this . listento(this . collection,' add ',this . addone);this.listenTo(this.collection,' reset ',this . render);//初始化视图数组以跟踪创建的视图this。view=[]},addone:函数(任务){ var view=new task view({ model : task });这个。$el.append(view.render()。$ El);//保存新创建的视图this.views.push(视图);},render:函数(){ var _ this=this//遍历视图数组,并调用remove _。每个(这个。视图,函数(视图){视图。移除()。每个视图主干的off();})//清空视图数组,旧视图将成为没有任何引用的不可达对象。//垃圾收集器会这样回收它们,view=[];这个。$ El . empty();this.collection .每个(函数(模型){ _this.addOne(模型);})返回此;}})主干的视图有一个remove方法,它不仅删除了与视图相关联的DOM对象,还阻止了事件拦截。它通过在listenTo到方法时记录那些被拦截的对象,使被拦截的对象删除它们的引用(在上面的事件原理中提到过)。使用移除内事件基类的停止列表来完成此操作。上面的代码使用了一个View数组来跟踪新创建的TaskView对象,在渲染时,依次调用这些view对象的remove,然后清空数组,这样就可以释放这些TaskView对象了。除了调用remove,它还调用off,这将视图对象与外部拦截断开。

事件驱动模块自定义事件:自定义事件更适合多人协同开发,因为我们知道如果函数名相同,那么后面的函数会覆盖前面的,而事件绑定了就不会被覆盖。

脚本类型=' text/JavaScript '//custom event varmod=backbone . model . extend({ default s 3360 { name 3360 ' trig kit 4 };},initialization : function(){ //初始化构造函数this.on('change '),function(){//绑定change事件,并在数据发生变化时执行此回调函数alert(123);});} });var模型=新Modmodel.set('name ',' backbone ');//将默认名称属性值修改为主干,当数据发生变化时,弹出123/脚本事件绑定。此外,我们还可以自定义要绑定的已更改数据类型:

object.on(事件、回调、[上下文])绑定一个回调函数到一个对象上,当事件触发时执行回调函数:

脚本类型='text/javascript' //自定义事件var Mod=主干模型。extend({默认值: {名称: ' trig kit 4 ',年龄: 21},初始化:函数(){ //初始化构造函数this.on('change:age ',function(){ //绑定变化事件,当数据改变时执行此回调函数警报(123);});} });定义变量模型=新Modmodel.set('name ',' backbone ');//修改默认的名字属性值为脊梁,此时数据被改变,弹出123/script listen script type=' text/JavaScript ' $(function(){ var Mod=Backbone .模特。extend({ defaults : { name : ' trig kit 4 ' });var V=主干视图。extend({ initialize : function(){ this。listento(这个。模型,‘改变’,这个。显示);//listenTo比在多了个参数},显示:函数(模型){ $(“body”).追加(“div”模型。get(' name ')'/div ');} });var m=新Modvar v=新V({型号:m });//模型指定创建的模型对象m,即前面的路由,哈希值的对应m.set('name ',' hello ');//对模型进行就改时,触发事件,页面也就更新了});/script

istenTo

脚本类型=' text/JAVAScript ' $(function(){ var Mod=主干模型。extend({ defaults : { name : ' trig kit 4 ' });var V=主干视图。extend({ initialize : function(){ this。listento(这个。模型,‘改变’,这个。显示);//listenTo比在多了个参数},显示:函数(模型){ $(“body”).追加(“div”模型。get(' name ')'/div ');} });var m=新Modvar v=新V({型号:m });//模型指定创建的模型对象m,即前面的路由,哈希值的对应m.set('name ',' hello ');//对模型进行就改时,触发事件,页面也就更新了});/script

模型集合器骨气。募捐集合是模型的有序组合,我们可以在集合上绑定"改变"事件,从而当集合中的模型发生变化时获得通知,集合也可以监听添加和"移除"事件,从服务器更新,并能使用下划线。射流研究…提供的方法

路由与历史管理

脚本类型=' text/JavaScript ' var Workspace=主干路由器。extend({ routes : { ' help ' : ' help ',' search/:query': 'search ',' search/: query/p : page ' : ' search ' },help : function(){ alert(123);},搜索:功能(查询、页面){ alert(345);} });var w=新工作区;主干。历史。start();//主干通过混杂值找到对应的回调函数/script事件委托脚本类型=' text/JavaScript ' $(function(){ var V=主干View.extend({ el : $('body '),//对事件进行集体操作events : { ' click input ' : ' hello ',' mouseover li' : 'world' },hello : function(){ alert(1234);},世界:函数(){ alert(123)} });var视图=新五;});/scriptbody输入类型=' button '值=' hwx '/ul Li 1234/Li Li 1234/Li Li 1234/Li Li 1234/Li Li 1234/Li Li 1234/Li Li/ul/body事件委托格式:事件空格由谁来触发:对应的回调函数

版权声明:深入分析JavaScript框架主干. js中的事件机制是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。