当前位置:文档之家› Backbone源码解析

Backbone源码解析

Backbone源码解析
Backbone源码解析

// 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:

// https://www.doczj.com/doc/21432933.html,

(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中部分方法依赖或继承自Underscore

var _ = 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对象, 我们将它重命名为MyBackbone

Backbone.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, 或空字符串时, 均会被转换为false

has : 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属性时, 也能正确访问到id

if (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是一个对象, 则认为是通过对象方式设置

// 此时第二个参数被认为是options

attrs = key;

options = value;

} else {

// 如果是通过key: value形式设置单个属性, 则直接设置attrs

attrs = {};

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;

// 服务器响应成功后执行success

options.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 事件并在触发时从集合中移除该模型

// 删除模型时, 模型中的数据并没有被清空, 但模型已经从集合中移除, 因此当没有任何地方引用该模型时, 会被自动从内存中释放

// 建议在删除模型时, 将模型对象的引用变量设置为null

var 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直接使用base

if (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获取上一次执行的状态, 如果上一次脚本没有执行完毕, 则值为true

var changing = this._changing;

// 开始执行标识, 执行过程中值始终为true, 执行完毕后this._changing被修改为false

this._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集合中的数据进行比较, 返回不一致的数据集合// 如果比较结果中没有差异, 则返回false

changedAttributes : 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事件中, 获取所有属性上一个状态的数据集合

// 该方法类似于previous()方法, 一般在change事件中调用, 用于数据比较或回滚

previousAttributes : function() {

// 将上一个状态的数据对象克隆为一个新对象并返回

return _.clone(this._previousAttributes);

},

// Check if the model is currently in a valid state. It's only possible to

// get into an *invalid* state if you're using silent changes.

// 验证当前模型中的数据是否能通过validate方法验证, 调用前请确保定义了validate方法

isValid : function() {

return !this.validate(this.attributes);

},

// 数据验证方法, 在调用set, save, add等数据更新方法时, 被自动执行

// 验证失败会触发模型对象的"error"事件, 如果在options中指定了error处理函数, 则只会执行options.error函数

// @param {Object} attrs 数据模型的attributes属性, 存储模型的对象化数据

// @param {Object} options 配置项

// @return {Boolean} 验证通过返回true, 不通过返回false

_validate : function(attrs, options) {

// 如果在调用set, save, add等数据更新方法时设置了options.silent属性, 则忽略验证

// 如果Model中没有添加validate方法, 则忽略验证

if (options.silent || !this.validate)

return true;

// 获取对象中所有的属性值, 并放入validate方法中进行验证

// validate方法包含2个参数, 分别为模型中的数据集合与配置对象, 如果验证通过则不返回任何数据(默认为undefined), 验证失败则返回带有错误信息数据

attrs = _.extend({}, this.attributes, attrs);

var error = this.validate(attrs, options);

// 验证通过

if (!error)

return true;

// 验证未通过

// 如果配置对象中设置了error错误处理方法, 则调用该方法并将错误数据和配置对象传递给该方法

if (options && options.error) {

options.error(this, error, options);

} else {

// 如果对模型绑定了error事件监听, 则触发绑定事件

this.trigger('error', this, error, options);

}

// 返回验证未通过标识

return false;

}

});

// Backbone.Collection 数据模型集合相关

// -------------------

// Collection集合存储一系列相同类的数据模型, 并提供相关方法对模型进行操作

var Collection = Backbone.Collection = function(models, options) {

// 配置对象

options || ( options = {});

// 在配置参数中设置集合的模型类

if (options.model)

this.model = options.model;

// 如果设置了comparator属性, 则集合中的数据将按照comparator方法中的排序算法进行排序(在add方法中会自动调用)

if (https://www.doczj.com/doc/21432933.html,parator)

https://www.doczj.com/doc/21432933.html,parator = https://www.doczj.com/doc/21432933.html,parator;

// 实例化时重置集合的内部状态(第一次调用时可理解为定义状态)

this._reset();

// 调用自定义初始化方法, 如果需要一般会重载initialize方法

this.initialize.apply(this, arguments);

// 如果指定了models数据, 则调用reset方法将数据添加到集合中

// 首次调用时设置了silent参数, 因此不会触发"reset"事件

if (models)

this.reset(models, {

silent : true,

parse : options.parse

});

};

// 通过extend方法定义集合类原型方法

_.extend(Collection.prototype, Events, {

// 定义集合的模型类, 模型类必须是一个Backbone.Model的子类

// 在使用集合相关方法(如add, create等)时, 允许传入数据对象, 集合方法会根据定义的模型类自动创建对应的实例

// 集合中存储的数据模型应该都是同一个模型类的实例

model : Model,

// 初始化方法, 该方法在集合实例被创建后自动调用

// 一般会在定义集合类时重载该方法

initialize : function() {

},

// 返回一个数组, 包含了集合中每个模型的数据对象

toJSON : function(options) {

// 通过Undersocre的map方法将集合中每一个模型的toJSON结果组成一个数组, 并返回

return this.map(function(model) {

// 依次调用每个模型对象的toJSON方法, 该方法默认将返回模型的数据对象(复制的副本)

// 如果需要返回字符串等其它形式, 可以重载toJSON方法

return model.toJSON(options);

});

},

// 向集合中添加一个或多个模型对象

// 默认会触发"add"事件, 如果在options中设置了silent属性, 可以关闭此次事件触发

// 传入的models可以是一个或一系列的模型对象(Model类的实例), 如果在集合中设置了model属性, 则允许直接传入数据对象(如 {name: 'test'}), 将自动将数据对象实例化为model指向的模型对象add : function(models, options) {

// 局部变量定义

var i, index, length, model, cid, id, cids = {}, ids = {}, dups = [];

options || ( options = {});

// models必须是一个数组, 如果只传入了一个模型, 则将其转换为数组

models = _.isArray(models) ? models.slice() : [models];

// 遍历需要添加的模型列表, 遍历过程中, 将执行以下操作:

// - 将数据对象转化模型对象

// - 建立模型与集合之间的引用

// - 记录无效和重复的模型, 并在后面进行过滤

for ( i = 0, length = models.length; i < length; i++) {

// 将数据对象转换为模型对象, 简历模型与集合的引用, 并存储到model(同时models中对应的模型已经被替换为模型对象)

if (!( model = models[i] = this._prepareModel(models[i], options))) {

throw new Error("Can't add an invalid model to a collection");

}

// 当前模型的cid和id

cid = model.cid;

id = model.id;

// dups数组中记录了无效或重复的模型索引(models数组中的索引), 并在下一步进行过滤删除

// 如果cids, ids变量中已经存在了该模型的索引, 则认为是同一个模型在传入的models数组中声明了多次

// 如果_byCid, _byId对象中已经存在了该模型的索引, 则认为同一个模型在当前集合中已经存在

// 对于上述两种情况, 将模型的索引记录到dups进行过滤删除

if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) {

dups.push(i);

continue;

}

// 将models中已经遍历过的模型记录下来, 用于在下一次循环时进行重复检查

cids[cid] = ids[id] = model;

}

// 从models中删除无效或重复的模型, 保留目前集合中真正需要添加的模型列表

i = dups.length;

while (i--) {

models.splice(dups[i], 1);

}

// 遍历需要添加的模型, 监听模型事件并记录_byCid, _byId列表, 用于在调用get和getByCid方法时作为索引

for ( i = 0, length = models.length; i < length; i++) {

// 监听模型中的所有事件, 并执行_onModelEvent方法

// _onModelEvent方法中会对模型抛出的add, remove, destroy和change事件进行处理, 以便模型与集合中的状态保持同步

( model = models[i]).on('all', this._onModelEvent, this);

// 将模型根据cid记录到_byCid对象, 便于根据cid进行查找

this._byCid[model.cid] = model;

// 将模型根据id记录到_byId对象, 便于根据id进行查找

if (model.id != null)

this._byId[model.id] = model;

}

// 改变集合的length属性, length属性记录了当前集合中模型的数量

this.length += length;

// 设置新模型列表插入到集合中的位置, 如果在options中设置了at参数, 则在集合的at位置插入

// 默认将插入到集合的末尾

// 如果设置了comparator自定义排序方法, 则设置at后还将按照comparator中的方法进行排序, 因此最终的顺序可能并非在at指定的位置

index = options.at != null ? options.at : this.models.length;

splice.apply(this.models, [index, 0].concat(models));

// 如果设置了comparator方法, 则将数据按照comparator中的算法进行排序

// 自动排序使用silent属性阻止触发reset事件

if (https://www.doczj.com/doc/21432933.html,parator)

this.sort({

silent : true

});

// 依次对每个模型对象触发"add"事件, 如果设置了silent属性, 则阻止事件触发

if (options.silent)

return this;

// 遍历新增加的模型列表

for ( i = 0, length = this.models.length; i < length; i++) {

if (!cids[( model = this.models[i]).cid])

continue;

options.index = i;

// 触发模型的"add"事件, 因为集合监听了模型的"all"事件, 因此在_onModelEvent方法中, 集合也将触发"add"事件

// 详细信息可参考Collection.prototype._onModelEvent方法

model.trigger('add', model, this, options);

}

return this;

},

// 从集合中移除模型对象(支持移除多个模型)

// 传入的models可以是需要移除的模型对象, 或模型的cid和模型的id

// 移除模型并不会调用模型的destroy方法

// 如果没有设置options.silent参数, 将触发模型的remove事件, 同时将触发集合的remove事件(集合通过_onModelEvent方法监听了模型的所有事件)

remove : function(models, options) {

var i, l, index, model;

// options默认为空对象

options || ( options = {});

// models必须是数组类型, 当只移除一个模型时, 将其放入一个数组

models = _.isArray(models) ? models.slice() : [models];

// 遍历需要移除的模型列表

for ( i = 0, l = models.length; i < l; i++) {

// 所传入的models列表中可以是需要移除的模型对象, 或模型的cid和模型的id

// (在getByCid和get方法中, 可通过cid, id来获取模型, 如果传入的是一个模型对象, 则返回模型本身)

model = this.getByCid(models[i]) || this.get(models[i]);

// 没有获取到模型

if (!model)

continue;

// 从_byId列表中移除模型的id引用

delete this._byId[model.id];

// 从_byCid列表中移除模型的cid引用

delete this._byCid[model.cid];

// indexOf是Underscore对象中的方法, 这里通过indexOf方法获取模型在集合中首次出现的位置

index = this.indexOf(model);

// 从集合列表中移除该模型

this.models.splice(index, 1);

// 重置当前集合的length属性(记录集合中模型的数量)

this.length--;

// 如果没有设置silent属性, 则触发模型的remove事件

if (!options.silent) {

// 将当前模型在集合中的位置添加到options对象并传递给remove监听事件, 以便在事件函数中可以使用

options.index = index;

model.trigger('remove', model, this, options);

}

// 解除模型与集合的关系, 包括集合中对模型的引用和事件监听

this._removeReference(model);

}

return this;

},

// 向集合的末尾添加模型对象

// 如果集合类中定义了comparator排序方法, 则通过push方法添加的模型将按照comparator定义的算法进行排序, 因此模型顺序可能会被改变

push : function(model, options) {

// 通过_prepareModel方法将model实例化为模型对象, 这句代码是多余的, 因为在下面调用的add 方法中还会通过_prepareModel获取一次模型

model = this._prepareModel(model, options);

// 调用add方法将模型添加到集合中(默认添加到集合末尾)

this.add(model, options);

return model;

},

// 移除集合中最后一个模型对象

pop : function(options) {

// 获取集合中最后一个模型

var model = this.at(this.length - 1);

// 通过remove方法移除该模型

this.remove(model, options);

return model;

},

// 向集合的第一个位置插入模型

// 如果集合类中定义了comparator排序方法, 则通过unshift方法添加的模型将按照comparator定义的算法进行排序, 因此模型顺序可能会被改变

unshift : function(model, options) {

// 通过_prepareModel方法将model实例化为模型对象

model = this._prepareModel(model, options);

// 调用add方法将模型插入到集合的第一个位置(设置at为0)

// 如果定义了comparator排序方法, 集合的顺序将被重排

this.add(model, _.extend({

Vue 源码解析:深入响应式原理(中)

Vue 源码解析:深入响应式原理(中) Directive Vue 指令类型很多,限于篇幅,我们不会把所有指令的解析过程都介绍一遍,这里结合前面的例子只介绍v-text 指令的解析过程,其他指令的解析过程也大同小异。 前面我们提到了Vue 实例创建的生命周期,在给data 添加Observer 之后,有一个过程是调用https://www.doczj.com/doc/21432933.html,pile 方法对模板进行编译。compile 方法的源码定义如下: Vue.prototype._compile = function (el) { var options = this.$options // transclude and init element // transclude can potentially replace original // so we need to keep reference; this step also injects // the template and caches the original attributes // on the container node and replacer node. var original = el el = transclude(el, options) this._initElement(el) // handle v-pre on root node (#2026) if (el.nodeType === 1 && getAttr(el, 'v-pre') !== null) { return

Android源代码结构分析

目录 一、源代码结构 (2) 第一层次目录 (2) bionic目录 (3) bootloader目录 (5) build目录 (7) dalvik目录 (9) development目录 (9) external目录 (13) frameworks目录 (19) Hardware (20) Out (22) Kernel (22) packages目录 (22) prebuilt目录 (27) SDK (28) system目录 (28) Vendor (32)

一、源代码结构 第一层次目录 Google提供的Android包含了原始Android的目标机代码,主机编译工具、仿真环境,代码包经过解压缩后,第一级别的目录和文件如下所示: . |-- Makefile (全局的Makefile) |-- bionic (Bionic含义为仿生,这里面是一些基础的库的源代码) |-- bootloader (引导加载器),我们的是bootable, |-- build (build目录中的内容不是目标所用的代码,而是编译和配置所需要的脚本和工具) |-- dalvik (JAVA虚拟机) |-- development (程序开发所需要的模板和工具) |-- external (目标机器使用的一些库) |-- frameworks (应用程序的框架层) |-- hardware (与硬件相关的库) |-- kernel (Linux2.6的源代码) |-- packages (Android的各种应用程序) |-- prebuilt (Android在各种平台下编译的预置脚本) |-- recovery (与目标的恢复功能相关) `-- system (Android的底层的一些库)

Mina2源码分析

Mina2.0框架源码剖析(一) 整个框架最核心的几个包是:org.apache.mina.core.service, org.apache.mina.core.session, org.apache.mina.core.polling以及 org.apache.mina.transport.socket。 这一篇先来看org.apache.mina.core.service。第一个要说的接口是IoService,它是所有IoAcceptor和IoConnector的基接口.对于一个IoService,有哪些信息需要我们关注呢?1)底层的元数据信息TransportMetadata,比如底层的网络服务提供者(NIO,ARP,RXTX等),2)通过这个服务创建一个新会话时,新会话的默认配置IoSessionConfig。3)此服务所管理的所有会话。4)与这个服务相关所产生的事件所对应的监听者(IoServiceListener)。5)处理这个服务所管理的所有连接的处理器(IoHandler)。6)每个会话都有一个过滤器链(IoFilterChain),每个过滤器链通过其对应的IoFilterChainBuilder来负责构建。7)由于此服务管理了一系列会话,因此可以通过广播的方式向所有会话发送消息,返回结果是一个WriteFuture集,后者是一种表示未来预期结果的数据结构。8)服务创建的会话(IoSession)相关的数据通过IoSessionDataStructureFactory来提供。9)发送消息时有一个写缓冲队列。10)服务的闲置状态有三种:读端空闲,写端空闲,双端空闲。11)还提供服务的一些统计信息,比如时间,数据量等。 IoService这个服务是对于服务器端的接受连接和客户端发起连接这两种行为的抽象。 再来从服务器看起,IoAcceptor是IoService 的子接口,它用于绑定到指定的ip和端口,从而接收来自客户端的连接请求,同时会fire相应的客户端连接成功接收/取消/失败等事件给自己的IoHandle去处理。当服务器端的Accpetor从早先绑定的ip和端口上取消绑定时,默认是所有的客户端会话会被关闭,这种情况一般出现在服务器挂掉了,则客户端收到连接关闭的提示。这个接口最重要的两个方法是bind()和unbind(),当这两个方法被调用时,服务端的连接接受线程就启动或关闭了。 再来看一看客户端的连接发起者接口IoConnector,它的功能和IoAcceptor基本对应的,它用于尝试连接到服务器指定的ip和端口,同时会fire相应的客户端连接事件给自己的IoHandle去处理。当connet方法被调用后用于连接服务器端的线程就启动了,而当所有的连接尝试都结束时线程就停止。尝试连接的超时时间可以自行设置。Connect方法返回的结果是ConnectFuture,这和前面说的WriteFuture类似,在后面会有一篇专门讲这个模式的应用。 前面的IoAcceptor和IoConnector就好比是两个负责握手的仆人,而真正代表会话的实际I/O操作的接口是IoProcessor,它对现有的Reactor模式架构的Java NIO框架继续做了一层封装。它的泛型参数指明了它能处理的会话类型。接口中最重要的几个方法,add用于将指定会话加入到此Processor中,让它负责处理与此会话相关的所有I/O操作。由于写操作会有一个写请求队列,flush就用于对指定会话的写请求队列进行强制刷数据。remove方法用于从此Processor中移除和关闭指定会话,

Pstree源码分析

Pstree源码分析 一.getopt_long获取参数,根据参数设置相应的变量。 二.read_proc ()主要函数,获取所有的process,生成tree. #define PROC_BASE "/proc" dir =opendir (PROC_BASE)) while ((de = readdir (dir)) != NULL) if ((pid = (pid_t) atoi (de->d_name)) != 0) //即读取/proc/number sprintf (path, "%s/%d/stat", PROC_BASE, pid); //path赋值为/proc/number/stat if ((file = fopen (path, "r")) != NULL) sprintf (path, "%s/%d", PROC_BASE, pid); //path赋值为/proc/number fread(readbuf, 1, BUFSIZ, file) ; //读取/proc/number/stat内容到readbuf if ((comm = strchr(readbuf, '('))&& (tmpptr = strrchr(comm, ')'))) //comm指向/proc/number/stat第一次出现’(‘的位置 //tmpptr指向/proc/number/stat最后一次出现’)‘的位置 ++comm; *tmpptr = 0; //即comm为/proc/number/stat中command内容 if (sscanf(tmpptr+2, "%*c %d", &ppid) == 1) //从tmpptr+2位置开始的第一个整数指向为ppid sprintf (taskpath, "%s/task", path); //taskpath赋值为/proc/number/task if ((taskdir=opendir(taskpath))!=0) sprintf(threadname,"{%s}",comm); // threadname赋值为command while ((dt = readdir(taskdir)) != NULL) if ((thread=atoi(dt->d_name)) !=0) //读取/proc/number/task中的number if (thread != pid) //即该number!=pid,也就是该process有子线程 add_proc(threadname, thread, pid, st.st_uid, NULL, 0); ……………….. …………………略 add_proc (comm, pid, ppid, st.st_uid, NULL, 0); add_proc()函数 add_proc (const char *comm, pid_t pid, pid_t ppid, uid_t uid, const char *args, int size) if (!(this = find_proc (pid))) this = new_proc (comm, pid, uid); //如果没有该process,则生成该pid对应的PROC结构else { strcpy (this->comm, comm); this->uid = uid;

JQUERY源码解析(架构与依赖模块)

jQuery设计理念 引用百科的介绍: jQuery是继prototype之后又一个优秀的Javascript框架。它是轻量级的js库,它兼容CSS3,还兼容各种浏览器(IE 6.0+,FF1.5+,Safari 2.0+,Opera9.0+),jQuery2.0及后续版本将不再支持IE6/7/8浏览器。jQuery使用户能更方便地处理HTML(标准通用标记语言下的一个应用)、events、实现动画效果,并且方便地为网站提供AJAX交互。jQuery还有一个比较大的优势是,它的文档说明很全,而且各种应用也说得很详细,同时还有许多成熟的插件可供选择。jQuery能够使用户的html页面保持代码和html内容分离,也就是说,不用再在html里面插入一堆js来调用命令了,只需定义id即可。 The Write Less,Do More(写更少,做更多),无疑就是jQuery的核心理念,简洁的API、优雅的链式、强大的查询与便捷的操作。从而把jQuery打造成前端世界的一把利剑,所向披靡! 简洁的API: 优雅的链式: 强大的选择器: 便捷的操作:

为什么要做jQuery源码解析? 虽然jQuery的文档很完善,潜意识降低了前端开发的入门的门槛,要实现一个动画随手拈来,只要简单的调用一个animate方法传递几个执行的参数即可,但如果要我们自己实现一个定制的动画呢?我们要考虑的问题太多太多了,浏览器兼容、各种属性的获取、逻辑流程、性能等等,这些才是前端开发的基础核心。 如果我们只知道使用jQuery,而不知道其原理,那就是“知其然,而不知其所以然”,说了这么多,那就赶快跟着慕课网进入“高大上”之旅吧,深入来探究jQuery的内部架构! jQuery整体架构 任何程序代码不是一开始就复杂的,成功也不是一躇而蹴的,早期jQuery的作者John Resig 在2005年提议改进Prototype的“Behaviour”库时,只是想让其使用更简单才发布新的jQuery 框架。起初John Resig估计也没料想jQuery会如此的火热。我们可以看到从发布的第一个1. 0开始到目前最新的2.1.1其代码膨胀到了9000多行,它兼容CSS3,还兼容各种浏览器,jQu ery使用户能更方便地处理DOM、事件、实现动画效果,并且方便地为网站提供AJAX交互。 1、最新jQuery2.1.1版本的结构: 代码请查看右侧代码编辑器(1-24行) 2、jQuery的模块依赖网: (单击图片可放大)

主成分分析源代码)

1.function y = pca(mixedsig) 2. 3.%程序说明:y = pca(mixedsig),程序中mixedsig为 n*T 阶混合数据矩阵, n为信号个数,T为采样点数 4.% y为 m*T 阶主分量矩阵。 5.% n是维数,T是样本数。 6. 7.if nargin == 0 8. error('You must supply the mixed data as input argument.'); 9.end 10.if length(size(mixedsig))>2 11. error('Input data can not have more than two dimensions. '); 12.end 13.if any(any(isnan(mixedsig))) 14. error('Input data contains NaN''s.'); 15.end 16. 17.%——————————————去均值———————————— 18.meanValue = mean(mixedsig')'; 19.[m,n] = size(mixedsig); 20.%mixedsig = mixedsig - meanValue*ones(1,size(meanValue)); %当数据本 身维数很大时容易出现Out of memory 21.for s = 1:m 22. for t = 1:n 23. mixedsig(s,t) = mixedsig(s,t) - meanValue(s); 24. end 25.end 26.[Dim,NumofSampl] = size(mixedsig); 27.oldDimension = Dim; 28.fprintf('Number of signals: %d\n',Dim); 29.fprintf('Number of samples: %d\n',NumofSampl); 30.fprintf('Calculate PCA...'); 31.firstEig = 1; https://www.doczj.com/doc/21432933.html,stEig = Dim; 33.covarianceMatrix = corrcoef(mixedsig'); %计算协方差矩阵 34.[E,D] = eig(covarianceMatrix); %计算协方差矩阵的特征值和特 征向量 35. 36.%———计算协方差矩阵的特征值大于阈值的个数lastEig——— 37.%rankTolerance = 1; 38.%maxLastEig = sum(diag(D) >= rankTolerance); 39.%lastEig = maxLastEig; https://www.doczj.com/doc/21432933.html,stEig = 10; 41.

Kettle源码分析_(详包)

PDI(Kettle)源码分析说明书 版本:Kettle v3.2 ************************有限公司 企业技术中心 2010-1-29

源码结构 src\目录下代码结构org.pentaho.di.cluster org.pentaho.di.core

org.pentaho.di.core.annotations org.pentaho.di.core.changed org.pentaho.di.core.config org.pentaho.di.core.gui

org.pentaho.di.core.listeners org.pentaho.di.core.playlist org.pentaho.di.core.plugins org.pentaho.di.core.reflection

org.pentaho.di.core.undo org.pentaho.di.job org.pentaho.di.job.entries org.pentaho.di.job.entries.abort

org.pentaho.di.job.entries.addresultfilenames org.pentaho.di.job.entries.columnsexist org.pentaho.di.job.entries.connectedtorepository org.pentaho.di.job.entries.copyfiles org.pentaho.di.job.entries.copymoveresultfilenames

传奇源码分析---框架

传奇源码分析---框架 标签:游戏源码框架 2013-10-31 14:09 2253人阅读评论(1) 收藏举报分类: 游戏源码(4) 版权声明:本文为博主原创文章,未经博主允许不得转载。 最近看游戏源码,对于大一点的源码,完全不知道怎么开始,太庞大了,网狐的源码都达到了1G多了,vc6.0打开直接卡死,不得不说vs2010还是很不错的。大的源码看不懂,最后去看最小的源码,传奇服务端源码。 1.找到winmain函数(GameSvr.cpp),InitApplication()函数注册窗口回调函数MainWndProc(MainWndProc.cpp). InitInstance()函数主要对窗口编程。 2.开启服务回调函数调用OnCommand(),创建了一个线程InitializingServer;在线程里面调用ConnectToServer()函数。ConnectToServer()里面将监听套接字(没看全局变量,推测的)注册到窗口回调函数里面,消息的ID为:_IDM_CLIENTSOCK_MSG然后,自己连接这个服务器,到此ConnectToServer()函数结束。 3.由于上一步,收到了_IDM_CLIENTSOCK_MSG,窗口函数调用OnClientSockMsg()。这个函数里面创建了ProcessLogin、ProcessUserHuman、ProcessMonster、ProcessNPC 线程,然后通过调用InitServerSocket创建CreateIOCPWorkerThread完成端口。继续调用InitThread创建AcceptThread线程,OK到此程序基本框架搭建起来了。CreateIOCPWorkerThread里面创建了完成端口工作者线程ServerWorkerThread。 到此服务器的基本架构搭建起来了。 直接看图吧,思路清晰一些。

如何看懂源代码--(分析源代码方法)

如何看懂源代码--(分析源代码方法) 4 推 荐 由于今日计划着要看Struts 开源框架的源代码 昨天看了一个小时稍微有点头绪,可是这个速度本人表示非常不满意,先去找了下资 料, 觉得不错... 摘自(繁体中文 Traditional Chinese):http://203.208.39.132/translate_c?hl=zh-CN&sl=en&tl=zh-CN&u=http://ww https://www.doczj.com/doc/21432933.html,/itadm/article.php%3Fc%3D47717&prev=hp&rurl=https://www.doczj.com/doc/21432933.html,&usg=AL kJrhh4NPO-l6S3OZZlc5hOcEQGQ0nwKA 下文为经过Google翻译过的简体中文版: 我们在写程式时,有不少时间都是在看别人的代码。 例如看小组的代码,看小组整合的守则,若一开始没规划怎么看,就会“噜看噜苦(台语)”不管是参考也好,从开源抓下来研究也好,为了了解箇中含意,在有限的时间下,不免会对庞大的源代码解读感到压力。网路上有一篇关于分析看代码的方法,做为程式设计师的您,不妨参考看看,换个角度来分析。也能更有效率的解读你想要的程式码片段。 六个章节: ( 1 )读懂程式码,使心法皆为我所用。( 2 )摸清架构,便可轻松掌握全貌。( 3 )优质工具在手,读懂程式非难事。( 4 )望文生义,进而推敲组件的作用。( 5 )找到程式入口,再由上而下抽丝剥茧。( 6 )阅读的乐趣,透过程式码认识作者。 程式码是别人写的,只有原作者才真的了解程式码的用途及涵义。许多程式人心里都有一种不自觉的恐惧感,深怕被迫去碰触其他人所写的程式码。但是,与其抗拒接收别人的程式码,不如彻底了解相关的语言和惯例,当成是培养自我实力的基石。 对大多数的程式人来说,撰写程式码或许是令人开心的一件事情,但我相信,有更多人视阅读他人所写成的程式码为畏途。许多人宁可自己重新写过一遍程式码,也不愿意接收别人的程式码,进而修正错误,维护它们,甚至加强功能。 这其中的关键究竟在何处呢?若是一语道破,其实也很简单,程式码是别人写的,只有原作者才真的了解程式码的用途及涵义。许多程式人心里都有一种不自觉的恐惧感,深怕被迫去碰触其他人所写的程式码。这是来自于人类内心深处对于陌生事物的原始恐惧。 读懂别人写的程式码,让你收获满满 不过,基于许多现实的原因,程式人时常受迫要去接收别人的程式码。例如,同事离职了,必须接手他遗留下来的工作,也有可能你是刚进部门的菜鸟,而同事经验值够了,升级了,风水轮流转,一代菜鸟换菜鸟。甚至,你的公司所承接的专案,必须接手或是整合客户前一个厂商所遗留下来的系统,你们手上只有那套系统的原始码(运气好时,还有数量不等的文件)。 诸如此类的故事,其实时常在程式人身边或身上持续上演着。许多程式人都将接手他人的程式码,当做一件悲惨的事情。每个人都不想接手别人所撰写的程式码,因为不想花时间去探索,宁可将生产力花在产生新的程式码,而不是耗费在了解这些程式码上。

Redis源代码分析

Redis源代码分析 一直有打算写篇关于redis源代码分析的文章,一直很忙,还好最近公司终于闲了一点,总算有点时间学习了,于是终于可以兑现承诺了,废话就到此吧,开始我们的源代码分析,在文章的开头我们把所有服务端文件列出来,并且标示出其作用: adlist.c //双向链表 ae.c //事件驱动 ae_epoll.c //epoll接口, linux用 ae_kqueue.c //kqueue接口, freebsd用 ae_select.c //select接口, windows用 anet.c //网络处理 aof.c //处理AOF文件 config.c //配置文件解析 db.c //DB处理 dict.c //hash表 intset.c //转换为数字类型数据 multi.c //事务,多条命令一起打包处理 networking.c //读取、解析和处理客户端命令 object.c //各种对像的创建与销毁,string、list、set、zset、hash rdb.c //redis数据文件处理 redis.c //程序主要文件 replication.c //数据同步master-slave sds.c //字符串处理 sort.c //用于list、set、zset排序 t_hash.c //hash类型处理 t_list.c //list类型处理 t_set.c //set类型处理 t_string.c //string类型处理 t_zset.c //zset类型处理 ziplist.c //节省内存方式的list处理 zipmap.c //节省内存方式的hash处理 zmalloc.c //内存管理 上面基本是redis最主要的处理文件,部分没有列出来,如VM之类的,就不在这里讲了。 首先我们来回顾一下redis的一些基本知识: 1、redis有N个DB(默认为16个DB),并且每个db有一个hash表负责存放key,同一个DB不能有相同的KEY,但是不同的DB可以相同的KEY;

VLC源码分析总结

VLC源码分析总结 1.概述 VLC属于Video LAN开源项目组织中的一款全开源的流媒体服务器和多媒体播放器。作为流媒体服务器,VLC跨平台,支持多操作系统和计算机体系结构;作为多媒体播放器,VLC 可以播放多种格式的媒体文件。主要包括有:WMV、ASF、MPG、MP、AVI、H.264等多种常见媒体格式。 VLC采用全模块化结构,在系统内部,通过动态的载入所需的模块,放入一个module_bank 的结构体中统一管理,连VLC的Main模块也是通过插件的方式动态载入的(通过module_InitBank函数在初始化建立module_bank时)。对于不支持动态载入插件的系统环境中,VLC也可以采用builtin的方式,在VLC启动的时候静态载入所需要的插件,并放入module_bank统一管理。 VLC的模块分成很多类别主要有:access、access_filter、access_output、audio_filter、audio_mixer、audio_output、codec、control、demux、gui、misc、mux、packetizer、stream_output、video_filter、video_output、interface、input、playlist 等(其中黑体为核心模块)。VLC无论是作为流媒体服务器还是多媒体播放器,它的实质思路就是一个“播放器”,之所以这么形象描述,是因为(The core gives a framework to do the media processing, from input (files, network streams) to output (audio or video, on ascreen or a network), going through various muxers, demuxers, decoders and filters. Even the interfaces are plugins for LibVLC. It is up to the developer to choose which module will be loaded. 摘于官网说明)它实质处理的是ES、PES、PS、TS等流间的转换、传输与显示。对于流媒体服务器,如果从文件作为输入即:PS->DEMUX->ES->MUX->TS;对于多媒体播放器如果采用UDP方式传输即:TS->DEMUX->ES。 2.插件管理框架 在VLC中每种类型的模块中都有一个抽象层/结构体,在抽象层或结构体中定义了若干操作的函数指针,通过这些函数指针就能实现模块的动态载入,赋值相关的函数指针的函数地址,最后通过调用函数指针能调用实际模块的操作。 对于VLC所有的模块中,有且仅有一个导出函数:vlc_entry__(MODULE_NAME)。(其中MODULE_NAME为宏定义,对于main模块,在\include\modules_inner.h中定义为main)动态载入模块的过程是:使用module_Need函数,在module_bank中根据各个插件的capability等相关属性,寻找第一个能满足要求并激活的模块。所谓激活是指,调用插件

jQuery源码解析

1jQuery源码总体结构 1.1兼容node.js 1.1.1简化jQuery源码 以jquery-1.11.3.js版本为例,将jQuery源码进行简化。 1、将function( global, factory ) {} 函数中的代码都去掉。 2、将function( window, noGlobal ) {} 函数中的代码都去掉。 那么简化后的jQuery代码为: ( function( global, factory ) { }(typeof window!=="undefined"?window:this,function(window,noGlobal){}) ); 我们会发现: 1、function( global, factory )函数是一个立即调用的匿名函数。 2、在调用时给function( global, factory )传了两个参数。 3、给参数global传的值是typeof window!=="undefined"?window:this 4、给参数factory传的值是function(window,noGlobal){} 下面对这两个参数详细解释: 1、形参global 是一个三目运算符typeof window !== "undefined" ? window : this 用于判断当前执行环境是否支持window类型,是的话返回window,否则返回this 2、形参factory则是一个函数,里面包含了一万多行的JQ功能函数function( window, noGlobal ) { ...... } 1.1.2匿名函数的代码 看JQ自带的英文注释我们可以大致知道它是为了兼容node.js、sea-JS等符合CommonJS规范或类CommonJS规范的js框架。 //实际上,global传入的window对象,factory传入的function( window, noGlobal ) function( global, factory ) { // module.export和module是node.js中用来创建模块的对象,所以这段代码的意思是://若此条件成立,则要执行下面语句来兼容node.js //利用factory做中间人,把JQ的各个功能模块用node.js创建模块的方法创建起来)if ( typeof module === "object" && typeof module.exports === "object" ) { //三目运算符,先判断当前环境是否支持window.document属性

uCOS-II源码详解

uC/OS-II源码分析(总体思路一) 首先从main函数开始,下面是uC/OS-II main函数的大致流程: main() { OSInit(); TaskCreate(...); OSStart(); } 首先是调用OSInit进行初始化,然后使用TaskCreate创建几个进程/Task,最后调用OSStart,操作系统就开始运行了。 OSInit 最先看看OSInit完成哪些初始化: void OSInit (void) { #if OS_VERSION >= 204 OSInitHookBegin(); #endif OS_InitMisc(); OS_InitRdyList(); OS_InitTCBList(); OS_InitEventList(); #if (OS_VERSION >= 251) && (OS_FLAG_EN > 0) && (OS_MAX_FLAGS > 0) OS_FlagInit(); #endif #if (OS_MEM_EN > 0) && (OS_MAX_MEM_PART > 0) OS_MemInit(); #endif #if (OS_Q_EN > 0) && (OS_MAX_QS > 0) OS_QInit(); #endif OS_InitTaskIdle(); #if OS_TASK_STAT_EN > 0 OS_InitTaskStat(); #endif #if OS_VERSION >= 204 OSInitHookEnd();

#endif #if OS_VERSION >= 270 && OS_DEBUG_EN > 0 OSDebugInit(); #endif } OS_InitMisc()完成的是一些其其他他的变量的初始化: OSIntNesting = 0; OSLockNesting = 0; OSTaskCtr = 0; OSRunning = FALSE; OSCtxSwCtr = 0; OSIdleCtr = 0L; 其中包括:中断嵌套标志OSIntNesting,调度锁定标志OSLockNesting,OS 标志OSRunning等。OSRunning在这里设置为FALSE,在后面OSStartHighRdy 中会被设置为TRUE表示OS开始工作。 OS_InitRdyList()初始化就绪Task列表: static void OS_InitRdyList (void) { INT8U i; INT8U *prdytbl; OSRdyGrp = 0x00; prdytbl = &OSRdyTbl[0]; for (i = 0; i < OS_RDY_TBL_SIZE; i++) { *prdytbl++ = 0x00; } OSPrioCur = 0; OSPrioHighRdy = 0; OSTCBHighRdy = (OS_TCB *)0; OSTCBCur = (OS_TCB *)0; } 首先将OSRdyTbl[]数组中全部初始化0,同时将OSPrioCur/OSTCBCur初始化为0,OSPrioHighRdy/OSTCBHighRdy也初始化为0,这几个变量将在第一个OSSchedule中被赋予正确的值。 OS_InitTCBList()这个函数看名称我们就知道是初始化TCB列表。 static void OS_InitTCBList (void)

Redis源码剖析(经典版)

Redis源码剖析(经典版) 为了熟悉Redis源码的执行流程,我们首先要熟悉它每个源码文件的作用,这样能起到事半功倍的效果,在文章的开头我们把所有服务端文件列出来,并且标示出其作用: adlist.c //双向链表 ae.c //事件驱动 ae_epoll.c //epoll接口, linux用 ae_kqueue.c //kqueue接口, freebsd用 ae_select.c //select接口, windows用 anet.c //网络处理 aof.c //处理AOF文件 config.c //配置文件解析 db.c //DB处理 dict.c //hash表 intset.c //转换为数字类型数据 multi.c //事务,多条命令一起打包处理 networking.c //读取、解析和处理客户端命令 object.c //各种对像的创建与销毁,string、list、set、zset、hash rdb.c //redis数据文件处理 redis.c //程序主要文件 replication.c //数据同步master-slave sds.c //字符串处理 sort.c //用于list、set、zset排序 t_hash.c //hash类型处理 t_list.c //list类型处理 t_set.c //set类型处理 t_string.c //string类型处理 t_zset.c //zset类型处理 ziplist.c //节省内存方式的list处理 zipmap.c //节省内存方式的hash处理 zmalloc.c //内存管理 上面基本是redis最主要的处理文件,部分没有列出来,如VM之类的,就不在这里讲了。 首先我们来回顾一下redis的一些基本知识: 1、redis有N个DB(默认为16个DB),并且每个db有一个hash表负责存放key,同一个DB不能有相同的KEY,但是不同的DB可以相同的KEY; 2、支持的几种数据类型:string、hash、list、set、zset; 3、redis可以使用aof来保存写操作日志(也可以使用快照方式保存数据文件)

struts2流程以及源码解析

1.1 Struts2请求处理 1. 一个请求在Struts2框架中的处理步骤: a) 客户端初始化一个指向Servlet容器的请求; b) 根据Web.xml配置,请求首先经过ActionContextCleanUp过滤器,其为可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助(SiteMesh Plugin),主要清理当前线程的ActionContext和Dispatcher; c) 请求经过插件过滤器,如:SiteMesh、etc等过滤器; d) 请求经过核心过滤器FilterDispatcher,执行doFilter方法,在该方法中,询问ActionMapper来决定这个请求是否需要调用某个Action; e) 如果ActionMapper决定需要调用某个Action,则ActionMapper会返回一个ActionMapping实例(存储Action的配置信息),并创建ActionProxy (Action代理)对象,将请求交给代理对象继续处理; f) ActionProxy对象根据ActionMapping和Configuration Manager询问框架的配置文件,找到需要调用的Action类; g) ActionProxy对象创建时,会同时创建一个ActionInvocation的实例; h) ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用;

i) 一旦Action执行完毕,ActionInvocation实例负责根据struts.xml中的配置创建并返回Result。Result通常是一个需要被表示的JSP或者FreeMarker 的模版,也可能是另外的一个Action链; j) 如果要在返回Result之前做些什么,可以实现PreResultListener接口,PreResultListener可以在Interceptor中实现,也可以在Action中实现; k) 根据Result对象信息,生成用户响应信息response,在生成响应过程中可以使用Struts2 框架中继承的标签,在此过程中仍会再次涉及到ActionMapper; 2. Struts2请求处理示意图: 1.2 Struts2请求处理源码分析

wireshark 源码分析

Wireshark源码剖析(2 struct _epan_dissect_t { tvbuff_t *tvb;//用来保存原始数据包 proto_tree *tree;//协议树结构 packet_info pi;// 包括各种关于数据包和协议显示的相关信息 }; typedef struct _proto_node { struct _proto_node *first_child;//协议树节点的第一个子节点指针struct _proto_node *last_child; //协议树节点的最后一个子节点指针struct _proto_node *next; //协议树节点的下一个节点指针 struct _proto_node *parent;//父节点指针 field_info *finfo;//保存当前协议要显示的地段 tree_data_t *tree_data;//协议树信息 } proto_node; packet_info typedef struct _packet_info { const char *current_proto; //当前正在解析的协议名称 column_info *cinfo; //wireshark显示的信息 frame_data *fd;//现在分析的原始数据指针 union wtap_pseudo_header *pseudo_header;//frame类型信息GSList *data_src; address dl_src; address dl_dst; address net_src; address net_dst; address src; address dst; guint32 ethertype; guint32 ipproto; guint32 ipxptype; guint32 mpls_label; circuit_type ctype; guint32 circuit_id; const char *noreassembly_reason; gboolean fragmented;

SPP基础库源码分析

SPP 基础库源码分析 一、原子操作基础库 在tbase文件夹下,包括以下源码文件,各文件的用途如下 atomic.h:通过__GNUC__和__WORDSIZE宏判断当前编译环境引入的具体原子操作头文件 atomic_asm.h:当编译环境为linux gcc版本小于4.0时被引入, atomic_gcc.h: 当编译环境为linux gcc版本大于4.0时被引入, atomic_asm8.h: 原用于32位系统时引入,现被注释了 atomic_gcc8.h: 当编译环境系统为64位时被引入 myatomic_gcc8.h: 当编译环境系统为64位时被引入 以上几个文件都是封装了glibc库中的cdefs.h的原子操作函数和相关的数据结构,以更简洁的名称提供给SPP各处代码使用。 注解:预定义__GNUC__宏 1 __GNUC__ 是linux平台,gcc编译器编译代码时预定义的一个宏。需要针对gcc编写代码时,可以使用该宏进行条件编译。 2 __GNUC__ 的值表示gcc的版本。需要针对gcc特定版本编写代码时,也可以使用该宏进行条件编译。 3 __GNUC__ 的类型是“int”,该宏被扩展后,得到的是整数字面值。可以通过仅预处理,查看宏扩展后的文本。 预定义__WORDSIZE宏,用于判断系统为32位还是64位 二、链表基础库 list.h:定义和实现链表的数据结构及相关操作 三、日志基础库 在tbase文件夹下,包括以下源码文件,各文件的用途如下 tlog.h:定义了日志功能所需的宏、枚举结构及日记类CTLog 详细源码分析: 首先保障多进程和多线程时的文件句柄安全性问题 定义了日志文件打印相关的多个宏 在tbase的名字空间下,定义tlog名字空间。 以下定义都在tbase::tlog名字空间下 定义枚举类型LOG_TYPE,LOG_LEVEL 定义钩子函数原型 定义日志类CTLog,CTLog的主要公共接口如下: //初始化日志 int log_open(int log_level, int log_type, const char* log_path, const char* name_prefix, int max_file_size, int max_file_no); //设置日志级别 int log_level(int level); //打印格式化日志 void log_i(int flag, int log_level, const char *fmt, ...);

相关主题
文本预览
相关文档 最新文档