Backbone源码解析
- 格式:doc
- 大小:758.64 KB
- 文档页数:44
前端框架Backbone的使用技巧与实践前言随着互联网的发展,前端技术也越来越成熟。
像Angular、React等前端框架层出不穷,但是今天我要和大家分享的是一款前端框架——Backbone的使用技巧和实践。
一、Backbone概述Backbone是一个轻量级的前端框架,它提供了一组可靠的结构、数据、事件等功能,帮助开发者更好地管理前端应用的复杂性。
它是以MVC为基础的,主要用于Web应用程序的开发,使得应用程序更加可维护、可扩展、易于测试。
二、Backbone结构Backbone框架整体结构分为三个部分:1. Models(模型):负责存储和管理数据,并与其他组件相互关联。
2. Views(视图):负责呈现数据和接收用户操作,并将用户的操作反馈给其他组件。
3. Controllers(控制器):负责协调Models和Views,将所有的组件互相关联起来。
三、Backbone实践接下来,我将分享一些Backbone的实践技巧,希望对大家有所帮助。
1. 使用Backbone的Models进行数据管理在使用Backbone的Models时,我们可以通过设置默认属性和事件响应机制来方便地进行数据管理。
//定义一个数据模型var Student = Backbone.Model.extend({defaults: {name: '',age: 0,sex: ''},initialize: function() {console.log('A new student has been created');},logStatus: function() {console.log('Name: ' + this.get('name') + ', Age: ' + this.get('age') + ', Sex: ' + this.get('sex'));}});//实例化一条数据模型var student = new Student();//设置模型属性student.set({name: 'Tom',age: 18,sex: 'Male'});//获取模型属性console.log('Name: ' + student.get('name') + ', Age: ' + student.get('age') + ', Sex: ' + student.get('sex'));//触发模型事件student.on('change', function() {console.log('The student model has been changed');});2. 使用Backbone的Views进行数据呈现在使用Backbone的Views时,我们可以通过模板、事件响应机制和DOM操作来方便地进行数据呈现。
Dubbo的负载均衡算法源码分析Dubbo提供了四种负载均衡:RandomLoadBalance,RoundRobinLoadBalance,LeastActiveLoadBalance,ConsistentHashLoadBalance。
这⾥顺便说下Dubbo的负载均衡是针对单个客户端的,不是全局的。
以下代码基于2.7.2-SNAPSHOT版本。
LoadBalanceLoadBalance接⼝只提供了⼀个对外暴露的⽅法:<T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;AbstractLoadBalanceAbstractLoadBalance使⽤模板设计模式,具体负载均衡算法由⼦类的doSelect实现public abstract class AbstractLoadBalance implements LoadBalance {//预热权重计算,provider刚启动权重在预热时间内随启动时间逐渐增加,最⼩为1static int calculateWarmupWeight(int uptime, int warmup, int weight) {int ww = (int) ((float) uptime / ((float) warmup / (float) weight));return ww < 1 ? 1 : (ww > weight ? weight : ww);}//模板⽅法,参数判断public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) {if (CollectionUtils.isEmpty(invokers)) {return null;}if (invokers.size() == 1) {return invokers.get(0);}return doSelect(invokers, url, invocation);}//真正执⾏负载均衡的⽅法protected abstract <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation);//计算权重protected int getWeight(Invoker<?> invoker, Invocation invocation) {int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.WEIGHT_KEY, Constants.DEFAULT_WEIGHT);if (weight > 0) {long timestamp = invoker.getUrl().getParameter(Constants.REMOTE_TIMESTAMP_KEY, 0L);if (timestamp > 0L) {int uptime = (int) (System.currentTimeMillis() - timestamp);int warmup = invoker.getUrl().getParameter(Constants.WARMUP_KEY, Constants.DEFAULT_WARMUP);//预热时间,默认为10分钟if (uptime > 0 && uptime < warmup) {//预热weight = calculateWarmupWeight(uptime, warmup, weight);}}}return weight >= 0 ? weight : 0;}}RandomLoadBalancepublic class RandomLoadBalance extends AbstractLoadBalance {public static final String NAME = "random";@Overrideprotected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {int length = invokers.size();//invoker个数boolean sameWeight = true;//每个invoker都有相同权重int[] weights = new int[length];//权重数组int firstWeight = getWeight(invokers.get(0), invocation);//第⼀个权重weights[0] = firstWeight;int totalWeight = firstWeight;//总权重//计算总权重和判断权重是否相同for (int i = 1; i < length; i++) {int weight = getWeight(invokers.get(i), invocation);weights[i] = weight;totalWeight += weight;if (sameWeight && weight != firstWeight) {sameWeight = false;}}//权重不相同if (totalWeight > 0 && !sameWeight) {//得到⼀个在[0,totalWeight)的偏移量,然后这个偏移量所在的invokerint offset = ThreadLocalRandom.current().nextInt(totalWeight);for (int i = 0; i < length; i++) {offset -= weights[i];if (offset < 0) {return invokers.get(i);}}}//权重相同,直接随机[0,length)return invokers.get(ThreadLocalRandom.current().nextInt(length));}}RoundRobinLoadBalancepublic class RoundRobinLoadBalance extends AbstractLoadBalance {public static final String NAME = "roundrobin";//循环周期,如果在这个周期内invoker没有被客户端获取,那么该invoker对应的轮询记录将被删除。
推荐系统算法的代码实现原理解析随着互联网的发展,推荐系统(Recommendation System)在各个领域中变得越来越重要,无论是电商平台、音乐推荐、视频推荐,还是新闻推荐,都需要一个高效的推荐系统来为用户提供有价值的个性化推荐。
推荐系统的本质是通过分析用户的历史行为和兴趣,将最相关的物品或信息推荐给用户。
在推荐系统中,有多种算法可以实现推荐功能,常见的包括基于内容的推荐、协同过滤、矩阵分解和深度学习等。
下面将逐一分析这些算法的代码实现原理。
1.基于内容的推荐算法(Content-Based Recommendation Algorithm):这种算法通过分析物品的内容特征来推荐相似的物品给用户。
其实现原理是通过使用自然语言处理(NLP)和文本挖掘技术来提取物品的关键词或特征,并根据用户的历史行为匹配相似的物品。
2.协同过滤算法(Collaborative Filtering Algorithm):这种算法通过分析用户和物品的历史行为来进行推荐,其实现原理可以分为两种类型:基于用户的协同过滤和基于物品的协同过滤。
基于用户的协同过滤是通过计算用户与其他用户之间的相似度,然后根据相似用户的行为来为目标用户进行推荐。
基于物品的协同过滤是通过计算物品与其他物品之间的相似度,然后根据用户对相似物品的行为来进行推荐。
3.矩阵分解算法(Matrix Factorization Algorithm):这种算法将用户和物品的历史行为构建成一个矩阵,然后通过矩阵分解的方法,将矩阵分解为两个低维矩阵的乘积,从而得到用户和物品的特征向量。
通过计算用户和物品的特征向量之间的相似度,可以为用户进行推荐。
4.深度学习算法(Deep Learning Algorithm):这种算法是近年来推荐系统中的热门算法之一,它通过使用多层神经网络来学习用户和物品的隐藏表示。
深度学习算法广泛应用于图像识别、自然语言处理等领域,并且在推荐系统中已取得了很好的效果。
经典backbone——VGG16VGG16简介VGG是由Simonyan 和Zisserman在⽂献《》中提出卷积神经⽹络模型,其名称来源于作者所在的⽜津⼤学视觉⼏何组(Visual Geometry Group)的缩写。
该模型参加2014年的 ImageNet图像分类与定位挑战赛,取得了优异成绩:在分类任务上排名第⼆,在定位任务上排名第⼀。
结构VGG中根据卷积核⼤⼩和卷积层数⽬的不同,可分为A,A-LRN,B,C,D,E共6个配置(ConvNet Configuration),其中以D,E两种配置较为常⽤,分别称为VGG16和VGG19。
下图给出了VGG的六种结构配置:上图中,每⼀列对应⼀种结构配置。
例如,图中绿⾊部分即指明了VGG16所采⽤的结构。
我们针对VGG16进⾏具体分析发现,VGG16共包含:13个卷积层(Convolutional Layer),分别⽤conv3-XXX表⽰3个全连接层(Fully connected Layer),分别⽤FC-XXXX表⽰5个池化层(Pool layer),分别⽤maxpool表⽰其中,卷积层和全连接层具有权重系数,因此也被称为权重层,总数⽬为13+3=16,这即是VGG16中16的来源。
(池化层不涉及权重,因此不属于权重层,不被计数)。
特点VGG16的突出特点是简单,体现在:卷积层均采⽤相同的卷积核参数卷积层均表⽰为conv3-XXX,其中conv3说明该卷积层采⽤的卷积核的尺⼨(kernel size)是3,即宽(width)和⾼(height)均为3,3*3是很⼩的卷积核尺⼨,结合其它参数(步幅stride=1,填充⽅式padding=same),这样就能够使得每⼀个卷积层(张量)与前⼀层(张量)保持相同的宽和⾼。
XXX代表卷积层的通道数。
池化层均采⽤相同的池化核参数池化层的参数均为2×模型是由若⼲卷积层和池化层堆叠(stack)的⽅式构成,⽐较容易形成较深的⽹络结构(在2014年,16层已经被认为很深了)。
backtrader源码解析Backtrader是一款Python开源的量化交易框架,具有易于使用、可扩展性强、技术支持好等特点。
它内置了丰富的技术指标库,支持动态回测、数据中心化管理等功能。
以下将为您带来backtrader源码解析。
第一步:导入模块和数据预处理首先,导入模块需要使用backtrader、pandas、numpy。
在数据预处理分别使用pandas和numpy,先将数据进行读取和预处理。
该过程中,需要设置好数据范围,选择从哪里开始到哪里结束。
第二步:策略实现和回测结果展示在该步骤中,需要先定义一个策略类,该类需要继承于backtrader.strategies.Strategy。
在该类中,需要设置好初始化函数、参数、数据、指标、交易逻辑等。
其中,针对每种交易逻辑,需要自定义一个策略类,在该类中再实现买入和卖出两个函数。
在最后,需要将策略类传递给backtrader.Cerebro对象,使它去执行整个策略。
在回测结束后,可以通过api查看回测的结果和细节,例如年化率、夏普比等。
第三步:风险控制和投资管理在实际交易中,风险控制和投资管理是非常重要的。
使用Backtrader,可以实现以下风险控制措施:(1)投资组合分散化:通过分散持仓,减少单一仓位风险;(2)止损订单:限制可能的损失,最大限度保护资金;(3)利润保护:在盈利时,使用止盈订单锁定一部分利润;(4)单个交易金额限制:这个限制将帮助控制获利或稍微减少损失所需的资金数量;(5)投资组合大小限制:一定的限制,以确保投资组合的总价值不超过可用资金。
总结:Backtrader作为一款优秀的量化交易框架,内置大量的技术指标和功能,可以帮助投资者快速开发自己的交易策略。
该框架具有不错的可扩展性和易于使用的特点。
在使用该框架时,建议注意风险控制和投资管理,以保护资金并确保高线性的回报。
百大框架源码解析1.SpringFramework:介绍SpringFramework的基础概念、核心组件和特性,包括IoC、AOP、Bean生命周期、Spring MVC等。
2. Hibernate:详细介绍Hibernate框架的核心概念、工作原理、实体映射、查询语言等。
3. Struts2:介绍Struts2的MVC架构、拦截器机制、表单验证、国际化等特性。
4. Django:从模型、视图、控制器、URL分发、中间件等方面介绍Django框架的基础知识。
5. Flask:介绍Flask框架的路由、模板、表单、数据库等方面的内容。
6. Ruby on Rails:介绍Ruby on Rails框架的MVC架构、路由、模板、ORM等方面的内容。
7. Express.js:介绍Express.js框架的路由、中间件、模板引擎、数据库等方面的内容。
8. AngularJS:介绍AngularJS框架的指令、数据绑定、服务、控制器等方面的内容。
9. React:介绍React框架的虚拟DOM、组件、状态管理、生命周期等方面的内容。
10. Vue.js:介绍Vue.js框架的响应式数据绑定、指令、组件、路由等方面的内容。
11. Bootstrap:介绍Bootstrap框架的基础样式、组件、网格系统、响应式设计等方面的内容。
12. jQuery:介绍jQuery框架的DOM操作、事件处理、动画效果、Ajax等方面的内容。
13. Ember.js:介绍Ember.js框架的路由、控制器、模板、组件等方面的内容。
14. Backbone.js:介绍Backbone.js框架的模型、视图、集合、路由等方面的内容。
15. Meteor:介绍Meteor框架的实时数据通信、模板、集合、路由等方面的内容。
16. Knockout.js:介绍Knockout.js框架的可观察对象、数据绑定、模板、组件等方面的内容。
backbone用法Backbone是一个轻量级的JavaScript框架,用于构建单页应用程序和管理前端数据。
它提供了一组简单而强大的工具,帮助开发人员组织和维护大规模的前端代码。
下面将介绍Backbone的一些常用用法。
1. 模型(Model):Backbone的核心是模型,它用于管理应用程序中的数据。
通过定义模型类,可以创建数据对象,并为其添加属性和方法。
模型还可以绑定事件,以便在数据变化时触发相应的回调函数。
2. 视图(View):视图负责将模型中的数据展示给用户,并响应用户的操作。
可以通过视图来创建HTML元素并将其渲染到DOM中。
视图也可以监听模型的变化,并在数据更新时自动更新对应的视图。
3. 集合(Collection):集合是一组模型的容器,用于管理多个模型对象。
它提供了丰富的方法,用于增加、删除、遍历和过滤模型。
集合还能和后端服务器进行数据交互,通过RESTful接口获取和保存数据。
4. 路由(Router):路由用于管理应用程序的URL,并根据URL的变化加载不同的视图。
通过定义路由规则,可以将不同的URL映射到对应的视图和操作。
当URL发生变化时,路由会自动触发相应的回调函数。
5. 事件(Events):Backbone提供了强大的事件系统,用于模块之间的通信和解耦。
可以在模型、视图和集合上绑定自定义事件,并触发相应的回调函数。
事件可以传递参数,以便在不同模块之间共享数据。
总结:Backbone是一个简洁、灵活的框架,可以帮助开发人员更好地组织和管理前端代码。
它的模型、视图、集合和路由等组件提供了丰富的功能,使得构建复杂的单页应用程序变得更加容易。
通过合理运用Backbone的各种用法,可以提高开发效率并改善代码质量。
比特币源码分析比特币是一种基于区块链技术的加密货币,其源码被广大开发者研究分析,用于了解其工作原理以及发现其中的安全漏洞。
本文将从比特币核心原理、交易处理、区块链维护等方面,对比特币源码进行详细分析。
一、比特币核心原理比特币的核心原理是基于区块链的分布式账本技术。
源码中的`block.cpp`文件定义了区块链的数据结构,并提供了与区块链相关的函数和方法。
其中,一个区块(block)包含了多个交易(transaction),形成交易的有向无环图(DAG)结构。
每个交易都包含了输入(input)和输出(output),用于记录比特币的转移过程。
源码中的`transaction.cpp`文件定义了交易的数据结构,并提供了生成交易的函数和验证交易的方法。
此外,比特币代码还实现了梅克尔树(Merkle Tree)算法,用于高效验证交易的完整性。
二、交易处理交易处理是比特币系统的核心功能之一,负责处理用户发送的交易并将其打包到区块中。
源码中的`txmempool.cpp`文件定义了交易内存池(Transaction Memory Pool,简称TxMempool)的数据结构和相关方法。
交易内存池维护了所有待处理的交易,其中的交易按照一定的规则进行排序,以便在后续的打包过程中选择最优的交易。
源码中的`miner.cpp`文件实现了交易的打包逻辑,包括选择交易、计算工作量证明等步骤。
交易处理模块的源码给出了比特币系统如何高效处理用户交易的方法和策略。
三、区块链维护区块链的维护是比特币系统的另一个核心功能,负责保证区块链的安全性和一致性。
源码中的`chain.cpp`文件定义了区块链的数据结构和相关方法。
其中,一个区块链由多个区块组成,每个区块包含了前一个区块的哈希值,这一特性保证了区块链的不可篡改性。
源码中的`validation.cpp`文件实现了区块链的验证逻辑,包括验证区块、验证交易等步骤。
此外,比特币代码还实现了共识算法(Consensus Algorithm),通过分布式共识的方式保证了区块链的一致性。
mmsegmentation backbone解析
mmsegmentation backbone解析
mmsegmentation 的后端 backbone 是一个用于实现 semantic segmentation 的强大框架,可以用于解析和分析图像的细节信息。
mmsegmentation 大致分为三部分:特征提取、特征聚合、分割。
特征提取使用的是一种多尺度特征融合算法,使用不同尺度的特征来包括物体的全部信息,特征聚合的算法根据多尺度特征的输出,将特征图融合到一起,最后分割从融合的特征图中进行图像语义分割。
mmsegmentation 的特征提取往往是基于 CNN 卷积神经网络的,提取特征的基本过程就是在多尺度上提取特征,从而得到更多的特征。
先用多尺度获取更多的特征,然后将这些特征融合到一起,最后进行语义分割。
mmsegmentation 的特征聚合通常使用 CNN 的卷积神经网络,从不同的特征图中获取多尺度的特征,然后融合到一起,再利用卷积神经网络对特征图进行聚合,使聚合后的特征图保留尽可能多的有用信息,最后再进行语义分割。
mmsegmentation 的分割部分,使用去细节化的技术。
这种技术
能够将轮廓信息和细节信息进行分离,从而得到更清晰的分割结果。
使用该分割技术,可以分割出图像中的不同物体,并使得图像具有更好的细节表现。
总的来说,mmsegmentation 是一个功能强大的框架,可用于实
现图像的语义分割任务,尤其是在多尺度特征融合方面具有优势。
det3d代码解析-回复关于det3d代码的解析。
det3d是一个基于PyTorch的开源3D目标检测工具库,用于3D目标检测和分割任务。
本文将逐步回答关于det3d代码的解析,深入研究其实现细节,帮助读者更好地理解该代码库。
第一步:安装det3ddet3d的安装非常简单,只需要在命令行中运行以下命令:pip install det3d这将自动安装所需的所有依赖项,并准备好代码库以供使用。
第二步:查看代码结构在安装det3d之后,我们可以查看其源代码结构。
代码库的基本结构如下:- det3d- core- anchor- bbox- box_np_ops- calibration- ...- datasets- models- tools- utils- ...其中,`core`目录包含了一些基本的核心功能,如锚框(anchor),边界框(bbox),点云操作(box_np_ops),标定(calibration)等等。
`datasets`目录用于处理输入数据集,`models`目录包含了各种3D目标检测模型的实现,而`tools`目录则提供了一些用于训练和测试模型的工具。
`utils`目录包含了一些辅助函数和工具。
第三步:深入研究代码细节接下来,我们可以深入研究代码的实现细节,例如模型的实现、数据集的处理,以及训练和测试过程。
对于模型的实现,det3d提供了多种3D目标检测模型的实现,如PointPillars、SECOND、PointRCNN等。
这些模型都继承自基类`Detectors3D`,并实现了`forward`方法来进行推理。
这些模型通常包含了一些主要组件,如骨干网络(backbone)、特征提取器(neck)、目标头部(head)和后处理(post-processing)等。
在数据集的处理方面,det3d支持多种数据集格式,如KITTI、Waymo 和nuScenes等。
可以通过简单配置来指定所要使用的数据集,在准备数据集时,det3d会进行数据的加载、预处理和增强等操作。
// Backbone.js 0.9.2// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.// Backbone may be freely distributed under the MIT license.// For all details and documentation:// (function() {// 创建一个全局对象, 在浏览器中表示为window对象, 在Node.js中表示global对象var root = this;// 保存"Backbone"变量被覆盖之前的值// 如果出现命名冲突或考虑到规范, 可通过Backbone.noConflict()方法恢复该变量被Backbone占用之前的值, 并返回Backbone对象以便重新命名var previousBackbone = root.Backbone;// 将Array.prototype中的slice和splice方法缓存到局部变量以供调用var slice = Array.prototype.slice;var splice = Array.prototype.splice;var Backbone;if ( typeof exports !== 'undefined') {Backbone = exports;} else {Backbone = root.Backbone = {};}// 定义Backbone版本Backbone.VERSION = '0.9.2';// 在服务器环境下自动导入Underscore, 在Backbone中部分方法依赖或继承自Underscorevar _ = root._;if (!_ && ( typeof require !== 'undefined'))_ = require('underscore');// 定义第三方库为统一的变量"$", 用于在视图(View), 事件处理和与服务器数据同步(sync)时调用库中的方法// 支持的库包括jQuery, Zepto等, 它们语法相同, 但Zepto更适用移动开发, 它主要针对Webkit内核浏览器// 也可以通过自定义一个与jQuery语法相似的自定义库, 供Backbone使用(有时我们可能需要一个比jQuery, Zepto更轻巧的自定义版本)// 这里定义的"$"是局部变量, 因此不会影响在Backbone框架之外第三方库的正常使用var $ = root.jQuery || root.Zepto || root.ender;// 手动设置第三方库// 如果在导入了Backbone之前并没有导入第三方库, 可以通过setDomLibrary方法设置"$"局部变量// setDomLibrary方法也常用于在Backbone中动态导入自定义库Backbone.setDomLibrary = function(lib) {$ = lib;};// 放弃以"Backbone"命名框架, 并返回Backbone对象, 一般用于避免命名冲突或规范命名方式// 例如:// var bk = Backbone.noConflict(); // 取消"Backbone"命名, 并将Backbone对象存放于bk变量中// console.log(Backbone); // 该变量已经无法再访问Backbone对象, 而恢复为Backbone定义前的值// var MyBackbone = bk; // 而bk存储了Backbone对象, 我们将它重命名为MyBackboneBackbone.noConflict = function() {root.Backbone = previousBackbone;return this;};// 对于不支持REST方式的浏览器, 可以设置Backbone.emulateHTTP = true// 与服务器请求将以POST方式发送, 并在数据中加入_method参数标识操作名称, 同时也将发送X-HTTP-Method-Override头信息Backbone.emulateHTTP = false;// 对于不支持application/json编码的浏览器, 可以设置Backbone.emulateJSON = true;// 将请求类型设置为application/x-www-form-urlencoded, 并将数据放置在model参数中实现兼容Backbone.emulateJSON = false;// Backbone.Events 自定义事件相关// -----------------// eventSplitter指定处理多个事件时, 事件名称的解析规则var eventSplitter = /\s+/;// 自定义事件管理器// 通过在对象中绑定Events相关方法, 允许向对象添加, 删除和触发自定义事件var Events = Backbone.Events = {// 将自定义事件(events)和回调函数(callback)绑定到当前对象// 回调函数中的上下文对象为指定的context, 如果没有设置context则上下文对象默认为当前绑定事件的对象// 该方法类似与DOM Level2中的addEventListener方法// events允许指定多个事件名称, 通过空白字符进行分隔(如空格, 制表符等)// 当事件名称为"all"时, 在调用trigger方法触发任何事件时, 均会调用"all"事件中绑定的所有回调函数on : function(events, callback, context) {// 定义一些函数中使用到的局部变量var calls, event, node, tail, list;// 必须设置callback回调函数if (!callback)return this;// 通过eventSplitter对事件名称进行解析, 使用split将多个事件名拆分为一个数组// 一般使用空白字符指定多个事件名称events = events.split(eventSplitter);// calls记录了当前对象中已绑定的事件与回调函数列表calls = this._callbacks || (this._callbacks = {});// 循环事件名列表, 从头至尾依次将事件名存放至event变量while ( event = events.shift()) {// 获取已经绑定event事件的回调函数// list存储单个事件名中绑定的callback回调函数列表// 函数列表并没有通过数组方式存储, 而是通过多个对象的next属性进行依次关联/** 数据格式如:* {* tail: {Object},* next: {* callback: {Function},* context: {Object},* next: {* callback: {Function},* context: {Object},* next: {Object}* }* }* }*/// 列表每一层next对象存储了一次回调事件相关信息(函数体, 上下文和下一次回调事件)// 事件列表最顶层存储了一个tail对象, 它存储了最后一次绑定回调事件的标识(与最后一次回调事件的next指向同一个对象)// 通过tail标识, 可以在遍历回调列表时得知已经到达最后一个回调函数list = calls[event];// node变量用于记录本次回调函数的相关信息// tail只存储最后一次绑定回调函数的标识// 因此如果之前已经绑定过回调函数, 则将之前的tail指定给node作为一个对象使用, 然后创建一个新的对象标识给tail// 这里之所以要将本次回调事件添加到上一次回调的tail对象, 是为了让回调函数列表的对象层次关系按照绑定顺序排列(最新绑定的事件将被放到最底层)node = list ? list.tail : {};node.next = tail = {};// 记录本次回调的函数体及上下文信息node.context = context;node.callback = callback;// 重新组装当前事件的回调列表, 列表中已经加入了本次回调事件calls[event] = {tail : tail,next : list ? list.next : node};}// 返回当前对象, 方便进行方法链调用return this;},// 移除对象中已绑定的事件或回调函数, 可以通过events, callback和context对需要删除的事件或回调函数进行过滤// - 如果context为空, 则移除所有的callback指定的函数// - 如果callback为空, 则移除事件中所有的回调函数// - 如果events为空, 但指定了callback或context, 则移除callback或context指定的回调函数(不区分事件名称)// - 如果没有传递任何参数, 则移除对象中绑定的所有事件和回调函数off : function(events, callback, context) {var event, calls, node, tail, cb, ctx;// No events, or removing *all* events.// 当前对象没有绑定任何事件if (!( calls = this._callbacks))return;// 如果没有指定任何参数, 则移除所有事件和回调函数(删除_callbacks属性)if (!(events || callback || context)) {delete this._callbacks;return this;}// 解析需要移除的事件列表// - 如果指定了events, 则按照eventSplitter对事件名进行解析// - 如果没有指定events, 则解析已绑定所有事件的名称列表events = events ? events.split(eventSplitter) : _.keys(calls);// 循环事件名列表while ( event = events.shift()) {// 将当前事件对象从列表中移除, 并缓存到node变量中node = calls[event];delete calls[event];// 如果不存在当前事件对象(或没有指定移除过滤条件, 则认为将移除当前事件及所有回调函数), 则终止此次操作(事件对象在上一步已经移除)if (!node || !(callback || context))continue;// Create a new list, omitting the indicated callbacks.// 根据回调函数或上下文过滤条件, 组装一个新的事件对象并重新绑定tail = node.tail;// 遍历事件中的所有回调对象while (( node = node.next) !== tail) {cb = node.callback;ctx = node.context;// 根据参数中的回调函数和上下文, 对回调函数进行过滤, 将不符合过滤条件的回调函数重新绑定到事件中(因为事件中的所有回调函数在上面已经被移除)if ((callback && cb !== callback) || (context && ctx !== context)) {this.on(event, cb, ctx);}}}return this;},// 触发已经定义的一个或多个事件, 依次执行绑定的回调函数列表trigger : function(events) {var event, node, calls, tail, args, all, rest;// 当前对象没有绑定任何事件if (!( calls = this._callbacks))return this;// 获取回调函数列表中绑定的"all"事件列表all = calls.all;// 将需要触发的事件名称, 按照eventSplitter规则解析为一个数组events = events.split(eventSplitter);// 将trigger从第2个之后的参数, 记录到rest变量, 将依次传递给回调函数rest = slice.call(arguments, 1);// 循环需要触发的事件列表while ( event = events.shift()) {// 此处的node变量记录了当前事件的所有回调函数列表if ( node = calls[event]) {// tail变量记录最后一次绑定事件的对象标识tail = node.tail;// node变量的值, 按照事件的绑定顺序, 被依次赋值为绑定的单个回调事件对象// 最后一次绑定的事件next属性, 与tail引用同一个对象, 以此作为是否到达列表末尾的判断依据while (( node = node.next) !== tail) {// 执行所有绑定的事件, 并将调用trigger时的参数传递给回调函数node.callback.apply(node.context || this, rest);}}// 变量all记录了绑定时的"all"事件, 即在调用任何事件时, "all"事件中的回调函数均会被执行// - "all"事件中的回调函数无论绑定顺序如何, 都会在当前事件的回调函数列表全部执行完毕后再依次执行// - "all"事件应该在触发普通事件时被自动调用, 如果强制触发"all"事件, 事件中的回调函数将被执行两次if ( node = all) {tail = node.tail;// 与调用普通事件的回调函数不同之处在于, all事件会将当前调用的事件名作为第一个参数传递给回调函数args = [event].concat(rest);// 遍历并执行"all"事件中的回调函数列表while (( node = node.next) !== tail) {node.callback.apply(node.context || this, args);}}}return this;}};// 绑定事件与释放事件的别名, 也为了同时兼容Backbone以前的版本Events.bind = Events.on;Events.unbind = Events.off;// Backbone.Model 数据对象模型// --------------// Model是Backbone中所有数据对象模型的基类, 用于创建一个数据模型// @param {Object} attributes 指定创建模型时的初始化数据// @param {Object} options* @format options* {* parse: {Boolean},* collection: {Collection}* }*/var Model = Backbone.Model = function(attributes, options) {// defaults变量用于存储模型的默认数据var defaults;// 如果没有指定attributes参数, 则设置attributes为空对象attributes || ( attributes = {});// 设置attributes默认数据的解析方法, 例如默认数据是从服务器获取(或原始数据是XML格式), 为了兼容set方法所需的数据格式, 可使用parse方法进行解析if (options && options.parse)attributes = this.parse(attributes);if ( defaults = getValue(this, 'defaults')) {// 如果Model在定义时设置了defaults默认数据, 则初始化数据使用defaults与attributes参数合并后的数据(attributes中的数据会覆盖defaults中的同名数据)attributes = _.extend({}, defaults, attributes);}// 显式指定模型所属的Collection对象(在调用Collection的add, push等将模型添加到集合中的方法时, 会自动设置模型所属的Collection对象)if (options && options.collection)this.collection = options.collection;// attributes属性存储了当前模型的JSON对象化数据, 创建模型时默认为空this.attributes = {};// 定义_escapedAttributes缓存对象, 它将缓存通过escape方法处理过的数据this._escapedAttributes = {};// 为每一个模型配置一个唯一标识this.cid = _.uniqueId('c');// 定义一系列用于记录数据状态的对象, 具体含义请参考对象定义时的注释this.changed = {};this._silent = {};this._pending = {};// 创建实例时设置初始化数据, 首次设置使用silent参数, 不会触发change事件this.set(attributes, {silent : true});// 上面已经设置了初始化数据, changed, _silent, _pending对象的状态可能已经发生变化, 这里重新进行初始化this.changed = {};this._silent = {};this._pending = {};// _previousAttributes变量存储模型数据的一个副本// 用于在change事件中获取模型数据被改变之前的状态, 可通过previous或previousAttributes方法获取上一个状态的数据this._previousAttributes = _.clone(this.attributes);// 调用initialize初始化方法this.initialize.apply(this, arguments);// 使用extend方法为Model原型定义一系列属性和方法_.extend(Model.prototype, Events, {// changed属性记录了每次调用set方法时, 被改变数据的key集合changed : null,// // 当指定silent属性时, 不会触发change事件, 被改变的数据会记录下来, 直到下一次触发change事件// _silent属性用来记录使用silent时的被改变的数据_silent : null,_pending : null,// 每个模型的唯一标识属性(默认为"id", 通过修改idAttribute可自定义id属性名)// 如果在设置数据时包含了id属性, 则id将会覆盖模型的id// id用于在Collection集合中查找和标识模型, 与后台接口通信时也会以id作为一条记录的标识idAttribute : 'id',// 模型初始化方法, 在模型被构造结束后自动调用initialize : function() {},// 返回当前模型中数据的一个副本(JSON对象格式)toJSON : function(options) {return _.clone(this.attributes);},// 根据attr属性名, 获取模型中的数据值get : function(attr) {return this.attributes[attr];},// 根据attr属性名, 获取模型中的数据值, 数据值包含的HTML特殊字符将被转换为HTML实体, 包含 & < > " ' \// 通过 _.escape方法实现escape : function(attr) {var html;// 从_escapedAttributes缓存对象中查找数据, 如果数据已经被缓存则直接返回if ( html = this._escapedAttributes[attr])return html;// _escapedAttributes缓存对象中没有找到数据// 则先从模型中获取数据var val = this.get(attr);// 将数据中的HTML使用 _.escape方法转换为实体, 并缓存到_escapedAttributes对象, 便于下次直接获取return this._escapedAttributes[attr] = _.escape(val == null ? '' : ''+ val);},// 检查模型中是否存在某个属性, 当该属性的值被转换为Boolean类型后值为false, 则认为不存在// 如果值为false, null, undefined, 0, NaN, 或空字符串时, 均会被转换为falsehas : function(attr) {return this.get(attr) != null;},// 设置模型中的数据, 如果key值不存在, 则作为新的属性添加到模型, 如果key值已经存在, 则修改为新的值set : function(key, value, options) {// attrs变量中记录需要设置的数据对象var attrs, attr, val;// 参数形式允许key-value对象形式, 或通过key, value两个参数进行单独设置// 如果key是一个对象, 则认定为使用对象形式设置, 第二个参数将被视为options参数if (_.isObject(key) || key == null) {attrs = key;options = value;} else {// 通过key, value两个参数单独设置, 将数据放到attrs对象中方便统一处理attrs = {};attrs[key] = value;}// options配置项必须是一个对象, 如果没有设置options则默认值为一个空对象options || ( options = {});// 没有设置参数时不执行任何动作if (!attrs)return this;// 如果被设置的数据对象属于Model类的一个实例, 则将Model对象的attributes数据对象赋给attrs// 一般在复制一个Model对象的数据到另一个Model对象时, 会执行该动作if ( attrs instanceof Model)attrs = attrs.attributes;// 如果options配置对象中设置了unset属性, 则将attrs数据对象中的所有属性重置为undefined// 一般在复制一个Model对象的数据到另一个Model对象时, 但仅仅需要复制Model中的数据而不需要复制值时执行该操作if (options.unset)for (attr in attrs)attrs[attr] =void 0;// 对当前数据进行验证, 如果验证未通过则停止执行if (!this._validate(attrs, options))return false;// 如果设置的id属性名被包含在数据集合中, 则将id覆盖到模型的id属性// 这是为了确保在自定义id属性名后, 访问模型的id属性时, 也能正确访问到idif (this.idAttribute in attrs)this.id = attrs[this.idAttribute];var changes = options.changes = {};// now记录当前模型中的数据对象var now = this.attributes;// escaped记录当前模型中通过escape缓存过的数据var escaped = this._escapedAttributes;// prev记录模型中数据被改变之前的值var prev = this._previousAttributes || {};// 遍历需要设置的数据对象for (attr in attrs) {// attr存储当前属性名称, val存储当前属性的值val = attrs[attr];// 如果当前数据在模型中不存在, 或已经发生变化, 或在options中指定了unset属性删除, 则删除该数据被换存在_escapedAttributes中的数据if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {// 仅删除通过escape缓存过的数据, 这是为了保证缓存中的数据与模型中的真实数据保持同步delete escaped[attr];// 如果指定了silent属性, 则此次set方法调用不会触发change事件, 因此将被改变的数据记录到_silent属性中, 便于下一次触发change事件时, 通知事件监听函数此数据已经改变// 如果没有指定silent属性, 则直接设置changes属性中当前数据为已改变状态(options.silent ? this._silent : changes)[attr] = true;}// 如果在options中设置了unset, 则从模型中删除该数据(包括key)// 如果没有指定unset属性, 则认为将新增或修改数据, 向模型的数据对象中加入新的数据options.unset ?delete now[attr] : now[attr] = val;// 如果模型中的数据与新的数据不一致, 则表示该数据已发生变化if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) { // 在changed属性中记录当前属性已经发生变化的状态this.changed[attr] = val;if (!options.silent)this._pending[attr] = true;} else {// 如果数据没有发生变化, 则从changed属性中移除已变化状态delete this.changed[attr];delete this._pending[attr];}}// 调用change方法, 将触发change事件绑定的函数if (!options.silent)this.change(options);return this;},// 从当前模型中删除指定的数据(属性也将被同时删除)unset : function(attr, options) {(options || ( options = {})).unset = true;// 通过options.unset配置项告知set方法进行删除操作return this.set(attr, null, options);},// 清除当前模型中的所有数据和属性clear : function(options) {(options || ( options = {})).unset = true;// 克隆一个当前模型的属性副本, 并通过options.unset配置项告知set方法执行删除操作return this.set(_.clone(this.attributes), options);},// 从服务器获取默认的模型数据, 获取数据后使用set方法将数据填充到模型, 因此如果获取到的数据与当前模型中的数据不一致, 将会触发change事件fetch : function(options) {// 确保options是一个新的对象, 随后将改变options中的属性options = options ? _.clone(options) : {};var model = this;// 在options中可以指定获取数据成功后的自定义回调函数var success = options.success;// 当获取数据成功后填充数据并调用自定义成功回调函数options.success = function(resp, status, xhr) {// 通过parse方法将服务器返回的数据进行转换// 通过set方法将转换后的数据填充到模型中, 因此可能会触发change事件(当数据发生变化时)// 如果填充数据时验证失败, 则不会调用自定义success回调函数if (!model.set(model.parse(resp, xhr), options))return false;// 调用自定义的success回调函数if (success)success(model, resp);};// 请求发生错误时通过wrapError处理error事件options.error = Backbone.wrapError(options.error, model, options);// 调用sync方法从服务器获取数据return (this.sync || Backbone.sync).call(this, 'read', this, options);},// 保存模型中的数据到服务器save : function(key, value, options) {// attrs存储需要保存到服务器的数据对象var attrs, current;// 支持设置单个属性的方式 key: value// 支持对象形式的批量设置方式 {key: value}if (_.isObject(key) || key == null) {// 如果key是一个对象, 则认为是通过对象方式设置// 此时第二个参数被认为是optionsattrs = key;options = value;} else {// 如果是通过key: value形式设置单个属性, 则直接设置attrsattrs = {};attrs[key] = value;}// 配置对象必须是一个新的对象options = options ? _.clone(options) : {};// 如果在options中设置了wait选项, 则被改变的数据将会被提前验证, 且服务器没有响应新数据(或响应失败)时, 本地数据会被还原为修改前的状态// 如果没有设置wait选项, 则无论服务器是否设置成功, 本地数据均会被修改为最新状态if (options.wait) {// 对需要保存的数据提前进行验证if (!this._validate(attrs, options))return false;// 记录当前模型中的数据, 用于在将数据发送到服务器后, 将数据进行还原// 如果服务器响应失败或没有返回数据, 则可以保持修改前的状态current = _.clone(this.attributes);}// silentOptions在options对象中加入了silent(不对数据进行验证)// 当使用wait参数时使用silentOptions配置项, 因为在上面已经对数据进行过验证// 如果没有设置wait参数, 则仍然使用原始的options配置项var silentOptions = _.extend({}, options, {silent : true});// 将修改过最新的数据保存到模型中, 便于在sync方法中获取模型数据保存到服务器if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) {return false;}var model = this;// 在options中可以指定保存数据成功后的自定义回调函数var success = options.success;// 服务器响应成功后执行successoptions.success = function(resp, status, xhr) {// 获取服务器响应最新状态的数据var serverAttrs = model.parse(resp, xhr);// 如果使用了wait参数, 则优先将修改后的数据状态直接设置到模型if (options.wait) {delete options.wait;serverAttrs = _.extend(attrs || {}, serverAttrs);}// 将最新的数据状态设置到模型中// 如果调用set方法时验证失败, 则不会调用自定义的success回调函数if (!model.set(serverAttrs, options))return false;if (success) {// 调用响应成功后自定义的success回调函数success(model, resp);} else {// 如果没有指定自定义回调, 则默认触发sync事件model.trigger('sync', model, resp, options);}};// 请求发生错误时通过wrapError处理error事件options.error = Backbone.wrapError(options.error, model, options);// 将模型中的数据保存到服务器// 如果当前模型是一个新建的模型(没有id), 则使用create方法(新增), 否则认为是update方法(修改)var method = this.isNew() ? 'create' : 'update';var xhr = (this.sync || Backbone.sync).call(this, method, this, options);// 如果设置了options.wait, 则将数据还原为修改前的状态// 此时保存的请求还没有得到响应, 因此如果响应失败, 模型中将保持修改前的状态, 如果服务器响应成功, 则会在success中设置模型中的数据为最新状态if (options.wait)this.set(current, silentOptions);return xhr;},// 删除模型, 模型将同时从所属的Collection集合中被删除// 如果模型是在客户端新建的, 则直接从客户端删除// 如果模型数据同时存在服务器, 则同时会删除服务器端的数据destroy : function(options) {// 配置项必须是一个新的对象options = options ? _.clone(options) : {};var model = this;// 在options中可以指定删除数据成功后的自定义回调函数var success = options.success;// 删除数据成功调用, 触发destroy事件, 如果模型存在于Collection集合中, 集合将监听destroy 事件并在触发时从集合中移除该模型// 删除模型时, 模型中的数据并没有被清空, 但模型已经从集合中移除, 因此当没有任何地方引用该模型时, 会被自动从内存中释放// 建议在删除模型时, 将模型对象的引用变量设置为nullvar triggerDestroy = function() {model.trigger('destroy', model, model.collection, options);};// 如果该模型是一个客户端新建的模型, 则直接调用triggerDestroy从集合中将模型移除if (this.isNew()) {triggerDestroy();return false;}// 当从服务器删除数据成功时options.success = function(resp) {// 如果在options对象中配置wait项, 则表示本地内存中的模型数据, 会在服务器数据被删除成功后再删除// 如果服务器响应失败, 则本地数据不会被删除if (options.wait)triggerDestroy();if (success) {// 调用自定义的成功回调函数success(model, resp);} else {// 如果没有自定义回调, 则默认触发sync事件model.trigger('sync', model, resp, options);}};// 请求发生错误时通过wrapError处理error事件options.error = Backbone.wrapError(options.error, model, options);// 通过sync方法发送删除数据的请求var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options);// 如果没有在options对象中配置wait项, 则会先删除本地数据, 再发送请求删除服务器数据// 此时无论服务器删除是否成功, 本地模型数据已被删除if (!options.wait)triggerDestroy();return xhr;},// 获取模型在服务器接口中对应的url, 在调用save, fetch, destroy等与服务器交互的方法时, 将使用该方法获取url// 生成的url类似于"PATHINFO"模式, 服务器对模型的操作只有一个url, 对于修改和删除操作会在url后追加模型id便于标识// 如果在模型中定义了urlRoot, 服务器接口应为[urlRoot/id]形式// 如果模型所属的Collection集合定义了url方法或属性, 则使用集合中的url形式: [collection.url/id]// 在访问服务器url时会在url后面追加上模型的id, 便于服务器标识一条记录, 因此模型中的id需要与服务器记录对应// 如果无法获取模型或集合的url, 将调用urlError方法抛出一个异常// 如果服务器接口并没有按照"PATHINFO"方式进行组织, 可以通过重载url方法实现与服务器的无缝交互url : function() {// 定义服务器对应的url路径var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();// 如果当前模型是客户端新建的模型, 则不存在id属性, 服务器url直接使用baseif (this.isNew())return base;// 如果当前模型具有id属性, 可能是调用了save或destroy方法, 将在base后面追加模型的id// 下面将判断base最后一个字符是否是"/", 生成的url格式为[base/id]return base + (base.charAt(base.length - 1) == '/'? '': '/') + encodeURIComponent(this.id);},// parse方法用于解析从服务器获取的数据, 返回一个能够被set方法解析的模型数据// 一般parse方法会根据服务器返回的数据进行重载, 以便构建与服务器的无缝连接// 当服务器返回的数据结构与set方法所需的数据结构不一致(例如服务器返回XML格式数据时), 可使用parse方法进行转换parse : function(resp, xhr) {return resp;},// 创建一个新的模型, 它具有和当前模型相同的数据clone : function() {return new this.constructor(this.attributes);},// 检查当前模型是否是客户端创建的新模型// 检查方式是根据模型是否存在id标识, 客户端创建的新模型没有id标识// 因此服务器响应的模型数据中必须包含id标识, 标识的属性名默认为"id", 也可以通过修改idAttribute属性自定义标识isNew : function() {return this.id == null;},// 数据被更新时触发change事件绑定的函数// 当set方法被调用, 会自动调用change方法, 如果在set方法被调用时指定了silent配置, 则需要手动调用change方法change : function(options) {// options必须是一个对象options || ( options = {});// this._changing相关的逻辑有些问题// this._changing在方法最后被设置为false, 因此方法上面changing变量的值始终为false(第一次为undefined)// 作者的初衷应该是想用该变量标示change方法是否执行完毕, 对于浏览器端单线程的脚本来说没有意义, 因为该方法被执行时会阻塞其它脚本// changing获取上一次执行的状态, 如果上一次脚本没有执行完毕, 则值为truevar changing = this._changing;// 开始执行标识, 执行过程中值始终为true, 执行完毕后this._changing被修改为falsethis._changing = true;// 将非本次改变的数据状态添加到_pending对象中for (var attr in this._silent)this._pending[attr] = true;// changes对象包含了当前数据上一次执行change事件至今, 已被改变的所有数据// 如果之前使用silent未触发change事件, 则本次会被放到changes对象中var changes = _.extend({}, options.changes, this._silent);// 重置_silent对象this._silent = {};// 遍历changes对象, 分别针对每一个属性触发单独的change事件for (var attr in changes) {// 将Model对象, 属性值, 配置项作为参数以此传递给事件的监听函数this.trigger('change:'+ attr, this, this.get(attr), options);}// 如果方法处于执行中, 则停止执行if (changing)return this;// 触发change事件, 任意数据被改变后, 都会依次触发"change:属性"事件和"change"事件while (!_.isEmpty(this._pending)) {this._pending = {};// 触发change事件, 并将Model实例和配置项作为参数传递给监听函数this.trigger('change', this, options);// 遍历changed对象中的数据, 并依次将已改变数据的状态从changed中移除// 在此之后如果调用hasChanged检查数据状态, 将得到false(未改变)for (var attr in this.changed) {if (this._pending[attr] || this._silent[attr])continue;// 移除changed中数据的状态delete this.changed[attr];}// change事件执行完毕, _previousAttributes属性将记录当前模型最新的数据副本// 因此如果需要获取数据的上一个状态, 一般只通过在触发的change事件中通过previous或previousAttributes方法获取this._previousAttributes = _.clone(this.attributes);}// 执行完毕标识this._changing = false;return this;},// 检查某个数据是否在上一次执行change事件后被改变过/*** 一般在change事件中配合previous或previousAttributes方法使用, 如:* if(model.hasChanged('attr')) {* var attrPrev = model.previous('attr');* }*/hasChanged : function(attr) {if (!arguments.length)return !_.isEmpty(this.changed);return _.has(this.changed, attr);},// 获取当前模型中的数据与上一次数据中已经发生变化的数据集合// (一般在使用silent属性时没有调用change方法, 因此数据会被临时抱存在changed属性中, 上一次的数据可通过previousAttributes方法获取)// 如果传递了diff集合, 将使用上一次模型数据与diff集合中的数据进行比较, 返回不一致的数据集合// 如果比较结果中没有差异, 则返回falsechangedAttributes : function(diff) {// 如果没有指定diff, 将返回当前模型较上一次状态已改变的数据集合, 这些数据已经被存在changed属性中, 因此返回changed集合的一个副本if (!diff)return this.hasChanged() ? _.clone(this.changed) : false;// 指定了需要进行比较的diff集合, 将返回上一次的数据与diff集合的比较结果// old变量存储了上一个状态的模型数据var val, changed = false, old = this._previousAttributes;// 遍历diff集合, 并将每一项与上一个状态的集合进行比较for (var attr in diff) {// 将比较结果不一致的数据临时存储到changed变量if (_.isEqual(old[attr], ( val = diff[attr])))continue;(changed || (changed = {}))[attr] = val;}// 返回比较结果return changed;},// 在模型触发的change事件中, 获取某个属性被改变前上一个状态的数据, 一般用于进行数据比较或回滚// 该方法一般在change事件中调用, change事件被触发后, _previousAttributes属性存放最新的数据previous : function(attr) {// attr指定需要获取上一个状态的属性名称if (!arguments.length || !this._previousAttributes)return null;return this._previousAttributes[attr];},// 在模型触发change事件中, 获取所有属性上一个状态的数据集合。