搜档网
当前位置:搜档网 › Underscore源码解析

Underscore源码解析

Underscore源码解析
Underscore源码解析

// Underscore.js 1.3.3

// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.

// Underscore is freely distributable under the MIT license.

// Portions of Underscore are inspired or borrowed from Prototype,

// Oliver Steele's Functional, and John Resig's Micro-Templating.

// For all details and documentation:

// https://www.sodocs.net/doc/fb10890245.html,/underscore

(function() {

// 创建一个全局对象, 在浏览器中表示为window对象, 在Node.js中表示global对象

var root = this;

// 保存"_"(下划线变量)被覆盖之前的值

// 如果出现命名冲突或考虑到规范, 可通过_.noConflict()方法恢复"_"被Underscore占用之前的值, 并返回Underscore对象以便重新命名

var previousUnderscore = root._;

// 创建一个空的对象常量, 便于内部共享使用

var breaker = {};

// 将内置对象的原型链缓存在局部变量, 方便快速调用

var ArrayProto = Array.prototype, //

ObjProto = Object.prototype, //

FuncProto = Function.prototype;

// 将内置对象原型中的常用方法缓存在局部变量, 方便快速调用

var slice = ArrayProto.slice, //

unshift = ArrayProto.unshift, //

toString = ObjProto.toString, //

hasOwnProperty = ObjProto.hasOwnProperty;

// 这里定义了一些JavaScript 1.6提供的新方法

// 如果宿主环境中支持这些方法则优先调用, 如果宿主环境中没有提供, 则会由Underscore实现

var nativeForEach = ArrayProto.forEach, //

nativeMap = ArrayProto.map, //

nativeReduce = ArrayProto.reduce, //

nativeReduceRight = ArrayProto.reduceRight, //

nativeFilter = ArrayProto.filter, //

nativeEvery = ArrayProto.every, //

nativeSome = ArrayProto.some, //

nativeIndexOf = ArrayProto.indexOf, //

nativeLastIndexOf = https://www.sodocs.net/doc/fb10890245.html,stIndexOf, //

nativeIsArray = Array.isArray, //

nativeKeys = Object.keys, //

nativeBind = FuncProto.bind;

// 创建对象式的调用方式, 将返回一个Underscore包装器, 包装器对象的原型中包含Underscore所有方法(类似与将DOM对象包装为一个jQuery对象)

return new wrapper(obj);

};

// 针对不同的宿主环境, 将Undersocre的命名变量存放到不同的对象中

if ( typeof exports !== 'undefined') {// Node.js环境

if ( typeof module !== 'undefined'&& module.exports) {

exports = module.exports = _;

}

exports._ = _;

} else {// 浏览器环境中Underscore的命名变量被挂在window对象中

root['_'] = _;

}

// 版本声明

_.VERSION = '1.3.3';

// 集合相关的方法(数据和对象的通用处理方法)

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

// 迭代处理器, 对集合中每一个元素执行处理器方法

var each = _.each = _.forEach = function(obj, iterator, context) { // 不处理空值

if (obj == null)

return;

if (nativeForEach && obj.forEach === nativeForEach) {

// 如果宿主环境支持, 则优先调用JavaScript 1.6提供的forEach方法

obj.forEach(iterator, context);

} else if (obj.length === +obj.length) {

// 对<数组>中每一个元素执行处理器方法

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

if ( i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;

}

} else {

// 对<对象>中每一个元素执行处理器方法

for (var key in obj) {

if (_.has(obj, key)) {

if (iterator.call(context, obj[key], key, obj) === breaker)

return;

}

}

}

};

// 迭代处理器, 与each方法的差异在于map会存储每次迭代的返回值, 并作为一个新的数组返回_.map = _.collect = function(obj, iterator, context) {

// 用于存放返回值的数组

var results = [];

if (obj == null)

return results;

return obj.map(iterator, context);

// 迭代处理集合中的元素

each(obj, function(value, index, list) {

// 将每次迭代处理的返回值存储到results数组

results[results.length] = iterator.call(context, value, index, list);

});

// 返回处理结果

if (obj.length === +obj.length)

results.length = obj.length;

return results;

};

// 将集合中每个元素放入迭代处理器, 并将本次迭代的返回值作为"memo"传递到下一次迭代, 一般用于累计结果或连接数据

_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {

// 通过参数数量检查是否存在初始值

var initial = arguments.length > 2;

if (obj == null)

obj = [];

// 优先调用宿主环境提供的reduce方法

if (nativeReduce && obj.reduce === nativeReduce && false) {

if (context)

iterator = _.bind(iterator, context);

return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);

}

// 迭代处理集合中的元素

each(obj, function(value, index, list) {

if (!initial) {

// 如果没有初始值, 则将第一个元素作为初始值; 如果被处理的是对象集合, 则默认值为第一个属性的值

memo = value;

initial = true;

} else {

// 记录处理结果, 并将结果传递给下一次迭代

memo = iterator.call(context, memo, value, index, list);

}

});

if (!initial)

throw new TypeError('Reduce of empty array with no initial value');

return memo;

};

// 与reduce作用相似, 将逆向迭代集合中的元素(即从最后一个元素开始直到第一个元素)

_.reduceRight = _.foldr = function(obj, iterator, memo, context) {

var initial = arguments.length > 2;

if (obj == null)

obj = [];

// 优先调用宿主环境提供的reduceRight方法

if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {

if (context)

}

// 逆转集合中的元素顺序

var reversed = _.toArray(obj).reverse();

if (context && !initial)

iterator = _.bind(iterator, context);

// 通过reduce方法处理数据

return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);

};

// 遍历集合中的元素, 返回第一个能够通过处理器验证的元素

_.find = _.detect = function(obj, iterator, context) {

// result存放第一个能够通过验证的元素

var result;

// 通过any方法遍历数据, 并记录通过验证的元素

// (如果是在迭代中检查处理器返回状态, 这里使用each方法会更合适)

any(obj, function(value, index, list) {

// 如果处理器返回的结果被转换为Boolean类型后值为true, 则当前记录并返回当前元素

if (iterator.call(context, value, index, list)) {

result = value;

return true;

}

});

return result;

};

// 与find方法作用类似, 但filter方法会记录下集合中所有通过验证的元素

_.filter = _.select = function(obj, iterator, context) {

// 用于存储通过验证的元素数组

var results = [];

if (obj == null)

return results;

// 优先调用宿主环境提供的filter方法

if (nativeFilter && obj.filter === nativeFilter)

return obj.filter(iterator, context);

// 迭代集合中的元素, 并将通过处理器验证的元素放到数组中并返回

each(obj, function(value, index, list) {

if (iterator.call(context, value, index, list))

results[results.length] = value;

});

return results;

};

// 与filter方法作用相反, 即返回没有通过处理器验证的元素列表

_.reject = function(obj, iterator, context) {

var results = [];

if (obj == null)

return results;

each(obj, function(value, index, list) {

if (!iterator.call(context, value, index, list))

results[results.length] = value;

// 如果集合中所有元素均能通过处理器验证, 则返回true

_.every = _.all = function(obj, iterator, context) {

var result = true;

if (obj == null)

return result;

// 优先调用宿主环境提供的every方法

if (nativeEvery && obj.every === nativeEvery)

return obj.every(iterator, context);

// 迭代集合中的元素

each(obj, function(value, index, list) {

// 这里理解为 result = (result && iterator.call(context, value, index, list))

// 验证处理器的结果被转换为Boolean类型后是否为true值

if (!( result = result && iterator.call(context, value, index, list)))

return breaker;

});

return !!result;

};

// 检查集合中任何一个元素在被转换为Boolean类型时, 是否为true值?或者通过处理器处理后, 是否值为true?

var any = _.some = _.any = function(obj, iterator, context) {

// 如果没有指定处理器参数, 则默认的处理器函数会返回元素本身, 并在迭代时通过将元素转换为Boolean类型来判断是否为true值

iterator || ( iterator = _.identity);

var result = false;

if (obj == null)

return result;

// 优先调用宿主环境提供的some方法

if (nativeSome && obj.some === nativeSome)

return obj.some(iterator, context);

// 迭代集合中的元素

each(obj, function(value, index, list) {

if (result || ( result = iterator.call(context, value, index, list)))

return breaker;

});

return !!result;

};

// 检查集合中是否有值与目标参数完全匹配(同时将匹配数据类型)

_.include = _.contains = function(obj, target) {

var found = false;

if (obj == null)

return found;

// 优先调用宿主环境提供的Array.prototype.indexOf方法

if (nativeIndexOf && obj.indexOf === nativeIndexOf)

return obj.indexOf(target) != -1;

// 通过any方法迭代集合中的元素, 验证元素的值和类型与目标是否完全匹配

found = any(obj, function(value) {

return value === target;

// 依次调用集合中所有元素的同名方法, 从第3个参数开始, 将被以此传入到元素的调用方法中

// 返回一个数组, 存储了所有方法的处理结果

_.invoke = function(obj, method) {

// 调用同名方法时传递的参数(从第3个参数开始)

var args = slice.call(arguments, 2);

// 依次调用每个元素的方法, 并将结果放入数组中返回

return _.map(obj, function(value) {

return (_.isFunction(method) ? method || value : value[method]).apply(value, args);

});

};

// 遍历一个由对象列表组成的数组, 并返回每个对象中的指定属性的值列表

_.pluck = function(obj, key) {

// 如果某一个对象中不存在该属性, 则返回undefined

return _.map(obj, function(value) {

return value[key];

});

};

// 返回集合中的最大值, 如果不存在可比较的值, 则返回undefined

_.max = function(obj, iterator, context) {

// 如果集合是一个数组, 且没有使用处理器, 则使用Math.max获取最大值

// 一般会是在一个数组存储了一系列Number类型的数据

if (!iterator && _.isArray(obj) && obj[0] === +obj[0])

return Math.max.apply(Math, obj);

// 对于空值, 直接返回负无穷大

if (!iterator && _.isEmpty(obj))

return -Infinity;

// 一个临时的对象, computed用于在比较过程中存储最大值(临时的)

var result = {

computed : -Infinity

};

// 迭代集合中的元素

each(obj, function(value, index, list) {

// 如果指定了处理器参数, 则比较的数据为处理器返回的值, 否则直接使用each遍历时的默认值

var computed = iterator ? iterator.call(context, value, index, list) : value;

// 如果比较值相比上一个值要大, 则将当前值放入result.value

computed >= https://www.sodocs.net/doc/fb10890245.html,puted && ( result = {

value : value,

computed : computed

});

});

// 返回最大值

return result.value;

};

// 返回集合中的最小值, 处理过程与max方法一致

_.min = function(obj, iterator, context) {

if (!iterator && _.isArray(obj) && obj[0] === +obj[0])

return Math.min.apply(Math, obj);

var result = {

computed : Infinity

};

each(obj, function(value, index, list) {

var computed = iterator ? iterator.call(context, value, index, list) : value;

computed < https://www.sodocs.net/doc/fb10890245.html,puted && ( result = {

value : value,

computed : computed

});

});

return result.value;

};

// 通过随机数, 让数组无须排列

_.shuffle = function(obj) {

// shuffled变量存储处理过程及最终的结果数据

var shuffled = [], rand;

// 迭代集合中的元素

each(obj, function(value, index, list) {

// 生成一个随机数, 随机数在<0-当前已处理的数量>之间

rand = Math.floor(Math.random() * (index + 1));

// 将已经随机得到的元素放到shuffled数组末尾

shuffled[index] = shuffled[rand];

// 在前面得到的随机数的位置插入最新值

shuffled[rand] = value;

});

// 返回一个数组, 该数组中存储了经过随机混排的集合元素

return shuffled;

};

// 对集合中元素, 按照特定的字段或值进行排列

// 相比Array.prototype.sort方法, sortBy方法支持对对象排序

_.sortBy = function(obj, val, context) {

// val应该是对象的一个属性, 或一个处理器函数, 如果是一个处理器, 则应该返回需要进行比较的数据var iterator = _.isFunction(val) ? val : function(obj) {

return obj[val];

};

// 调用顺序: _.pluck(_.map().sort());

// 调用_.map()方法遍历集合, 并将集合中的元素放到value节点, 将元素中需要进行比较的数据放到criteria属性中

// 调用sort()方法将集合中的元素按照criteria属性中的数据进行顺序排序

// 调用pluck获取排序后的对象集合并返回

return _.pluck(_.map(obj, function(value, index, list) {

return {

value : value,

criteria : iterator.call(context, value, index, list)

};

}).sort(function(left, right) {

var a = left.criteria, b = right.criteria;

if (a ===

if (b ===

void 0)

return -1;

return a < b ? -1 : a > b ? 1 : 0;

}), 'value');

};

// 将集合中的元素, 按处理器返回的key分为多个数组

_.groupBy = function(obj, val) {

var result = {};

// val将被转换为进行分组的处理器函数, 如果val不是一个Function类型的数据, 则将被作为筛选元素时的key值

var iterator = _.isFunction(val) ? val : function(obj) {

return obj[val];

};

// 迭代集合中的元素

each(obj, function(value, index) {

// 将处理器的返回值作为key, 并将相同的key元素放到一个新的数组

var key = iterator(value, index);

(result[key] || (result[key] = [])).push(value);

});

// 返回已分组的数据

return result;

};

_.sortedIndex = function(array, obj, iterator) {

iterator || ( iterator = _.identity);

var low = 0, high = array.length;

while (low < high) {

var mid = (low + high) >> 1;

iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;

}

return low;

};

// 将一个集合转换一个数组并返回

// 一般用于将arguments转换为数组, 或将对象无序集合转换为数据形式的有序集合

_.toArray = function(obj) {

if (!obj)

return [];

if (_.isArray(obj))

return slice.call(obj);

// 将arguments转换为数组

if (_.isArguments(obj))

return slice.call(obj);

if (obj.toArray && _.isFunction(obj.toArray))

return obj.toArray();

// 将对象转换为数组, 数组中包含对象中所有属性的值列表(不包含对象原型链中的属性)

return _.values(obj);

};

// 计算集合中元素的数量

// 如果集合是一个对象, 则计算对象中的属性数量(不包含对象原型链中的属性)

return _.isArray(obj) ? obj.length : _.keys(obj).length;

};

// 数组相关的方法

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

// 返回一个数组的第一个或順序指定的n个元素

_.first = _.head = _.take = function(array, n, guard) {

// 如果没有指定参数n, 则返回第一个元素

// 如果指定了n, 则返回一个新的数组, 包含顺序指定数量n个元素

// guard参数用于确定只返回第一个元素, 当guard为true时, 指定数量n无效

return (n != null) && !guard ? slice.call(array, 0, n) : array[0];

};

// 返回一个新数组, 包含除第一个元素外的其它元素, 或排除从最后一个元素开始向前指定n个元素

// 与first方法不同在于, first确定需要的元素在数组之前的位置, initial确定能排除的元素在数组最后的位置

_.initial = function(array, n, guard) {

// 如果没有传递参数n, 则默认返回除最后一个元素外的其它元素

// 如果传递参数n, 则返回从最后一个元素开始向前的n个元素外的其它元素

// guard用于确定只返回一个元素, 当guard为true时, 指定数量n无效

return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));

};

// 返回数组的最后一个或倒序指定的n个元素

_.last = function(array, n, guard) {

if ((n != null) && !guard) {

// 计算并指定获取的元素位置n, 直到数组末尾, 作为一个新的数组返回

return slice.call(array, Math.max(array.length - n, 0));

} else {

// 如果没有指定数量, 或guard为true时, 只返回最后一个元素

return array[array.length - 1];

}

};

// 获取除了第一个或指定前n个元素外的其它元素

_.rest = _.tail = function(array, index, guard) {

// 计算slice的第二个位置参数, 直到数组末尾

// 如果没有指定index, 或guard值为true, 则返回除第一个元素外的其它元素

// (index == null)值为true时, 作为参数传递给slice函数将被自动转换为1

return slice.call(array, (index == null) || guard ? 1 : index);

};

// 返回数组中所有值能被转换为true的元素, 返回一个新的数组

// 不能被转换的值包括 false, 0, '', null, undefined, NaN, 这些值将被转换为false

_.compact = function(array) {

return _.filter(array, function(value) {

return !!value;

});

};

// 将一个多维数组合成为一维数组, 支持深层合并

// shallow参数用于控制合并深度, 当shallow为true时, 只合并第一层, 默认进行深层合并

return _.reduce(array, function(memo, value) {

// 如果元素依然是一个数组, 进行以下判断:

// - 如果不进行深层合并, 则使用Array.prototype.concat将当前数组和之前的数据进行连接

// - 如果支持深层合并, 则迭代调用flatten方法, 直到底层元素不再是数组类型

if (_.isArray(value))

return memo.concat( shallow ? value : _.flatten(value));

// 数据(value)已经处于底层, 不再是数组类型, 则将数据合并到memo中并返回

memo[memo.length] = value;

return memo;

}, []);

};

// 筛选并返回当前数组中与指定数据不相等的差异数据(可参考difference方法注释)

_.without = function(array) {

return _.difference(array, slice.call(arguments, 1));

};

// 对数组中的数据进行去重(使用===进行比较)

// 当isSorted参数不为false时, 将依次对数组中的元素调用include方法, 检查相同元素是否已经被添加到返回值(数组)中

// 如果调用之前确保数组中数据按顺序排列, 则可以将isSorted设为true, 它将通过与最后一个元素进行对比来排除相同值, 使用isSorted效率会高于默认的include方式

// uniq方法默认将以数组中的数据进行对比, 如果声明iterator处理器, 则会根据处理器创建一个对比数组, 比较时以该数组中的数据为准, 但最终返回的唯一数据仍然是原始数组

_.uniq = _.unique = function(array, isSorted, iterator) {

// 如果使用了iterator处理器, 则先将当前数组中的数据会先经过按迭代器处理, 并返回一个处理后的新数组

// 新数组用于作为比较的基准

var initial = iterator ? _.map(array, iterator) : array;

// 用于记录处理结果的临时数组

var results = [];

// 如果数组中只有2个值, 则不需要使用include方法进行比较, 将isSorted设置为true能提高运行效率if (array.length < 3)

isSorted = true;

// 使用reduce方法迭代并累加处理结果

// initial变量是需要进行比较的基准数据, 它可能是原始数组, 也可能是处理器的结果集合(如果设置过iterator)

_.reduce(initial, function(memo, value, index) {

// 如果isSorted参数为true, 则直接使用===比较记录中的最后一个数据

// 如果isSorted参数为false, 则使用include方法与集合中的每一个数据进行对比

if ( isSorted ? _.last(memo) !== value || !memo.length : !_.include(memo, value)) { // memo记录了已经比较过的无重复数据

// 根据iterator参数的状态, memo中记录的数据可能是原始数据, 也可能是处理器处理后的数据

memo.push(value);

// 处理结果数组中保存的始终为原始数组中的数据

results.push(array[index]);

}

return memo;

}, []);

// 返回处理结果, 它只包含数组中无重复的数据

// union方法与uniq方法作用一致, 不同之处在于union允许在参数中传入多个数组

_.union = function() {

// union对参数中的多个数组进行浅层合并为一个数组对象传递给uniq方法进行处理

return _.uniq(_.flatten(arguments, true));

};

// 获取当前数组与其它一个或多个数组的交集元素

// 从第二个参数开始为需要进行比较的一个或多个数组

_.intersection = _.intersect = function(array) {

// rest变量记录需要进行比较的其它数组对象

var rest = slice.call(arguments, 1);

// 使用uniq方法去除当前数组中的重复数据, 避免重复计算

// 对当前数组的数据通过处理器进行过滤, 并返回符合条件(比较相同元素)的数据

return _.filter(_.uniq(array), function(item) {

// 使用every方法验证每一个数组中都包含了需要对比的数据

// 如果所有数组中均包含对比数据, 则全部返回true, 如果任意一个数组没有包含该元素, 则返回false

return _.every(rest, function(other) {

// other参数存储了每一个需要进行对比的数组

// item存储了当前数组中需要进行对比的数据

// 使用indexOf方法搜索数组中是否存在该元素(可参考indexOf方法注释)

return _.indexOf(other, item) >= 0;

});

});

};

// 筛选并返回当前数组中与指定数据不相等的差异数据

// 该函数一般用于删除数组中指定的数据, 并得到删除后的新数组

// 该方法的作用与without相等, without方法参数形式上不允许数据被包含在数组中, 而difference方法参数形式上建议是数组(也可以和without使用相同形式的参数)

_.difference = function(array) {

// 对第2个参数开始的所有参数, 作为一个数组进行合并(仅合并第一层, 而并非深层合并)

// rest变量存储验证数据, 在本方法中用于与原数据对比

var rest = _.flatten(slice.call(arguments, 1), true);

// 对合并后的数组数据进行过滤, 过滤条件是当前数组中不包含参数指定的验证数据的内容

// 将符合过滤条件的数据组合为一个新的数组并返回

return _.filter(array, function(value) {

return !_.include(rest, value);

});

};

// 将每个数组的相同位置的数据作为一个新的二维数组返回, 返回的数组长度以传入参数中最大的数组长度为准, 其它数组的空白位置使用undefined填充

// zip方法应该包含多个参数, 且每个参数应该均为数组

_.zip = function() {

// 将参数转换为数组, 此时args是一个二维数组

var args = slice.call(arguments);

// 计算每一个数组的长度, 并返回其中最大长度值

var length = _.max(_.pluck(args, 'length'));

// 依照最大长度值创建一个新的空数组, 该数组用于存储处理结果

var results = new Array(length);

for (var i = 0; i < length; i++)

results[i] = _.pluck(args, ""+ i);

// 返回的结果是一个二维数组

return results;

};

// 搜索一个元素在数组中首次出现的位置, 如果元素不存在则返回 -1

// 搜索时使用 === 对元素进行匹配

_.indexOf = function(array, item, isSorted) {

if (array == null)

return -1;

var i, l;

if (isSorted) {

i = _.sortedIndex(array, item);

return array[i] === item ? i : -1;

}

// 优先调用宿主环境提供的indexOf方法

if (nativeIndexOf && array.indexOf === nativeIndexOf)

return array.indexOf(item);

// 循环并返回元素首次出现的位置

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

if ( i in array && array[i] === item)

return i;

// 没有找到元素, 返回-1

return -1;

};

// 返回一个元素在数组中最后一次出现的位置, 如果元素不存在则返回 -1

// 搜索时使用 === 对元素进行匹配

_.lastIndexOf = function(array, item) {

if (array == null)

return -1;

// 优先调用宿主环境提供的lastIndexOf方法

if (nativeLastIndexOf && https://www.sodocs.net/doc/fb10890245.html,stIndexOf === nativeLastIndexOf) return https://www.sodocs.net/doc/fb10890245.html,stIndexOf(item);

var i = array.length;

// 循环并返回元素最后出现的位置

while (i--)

if ( i in array && array[i] === item)

return i;

// 没有找到元素, 返回-1

return -1;

};

// 根据区间和步长, 生成一系列整数, 并作为数组返回

// start参数表示最小数

// stop参数表示最大数

// step参数表示生成多个数值之间的步长值

_.range = function(start, stop, step) {

// 参数控制

if (arguments.length <= 1) {

stop = start || 0;

start = 0;

}

// 生成整数的步长值, 默认为1

step = arguments[2] || 1;

// 根据区间和步长计算将生成的最大值

var len = Math.max(Math.ceil((stop - start) / step), 0);

var idx = 0;

var range = new Array(len);

// 生成整数列表, 并存储到range数组

while (idx < len) {

range[idx++] = start;

start += step;

}

// 返回列表结果

return range;

};

// 函数相关方法

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

// 创建一个用于设置prototype的公共函数对象

var ctor = function() {

};

// 为一个函数绑定执行上下文, 任何情况下调用该函数, 函数中的this均指向context对象// 绑定函数时, 可以同时给函数传递调用形参

_.bind = function bind(func, context) {

var bound, args;

// 优先调用宿主环境提供的bind方法

if (func.bind === nativeBind && nativeBind)

return nativeBind.apply(func, slice.call(arguments, 1));

// func参数必须是一个函数(Function)类型

if (!_.isFunction(func))

throw new TypeError;

// args变量存储了bind方法第三个开始的参数列表, 每次调用时都将传递给func函数args = slice.call(arguments, 2);

return bound = function() {

if (!(this instanceof bound))

return func.apply(context, sargs.concat(slice.call(arguments)));

ctor.prototype = func.prototype;

var self = new ctor;

var result = func.apply(self, args.concat(slice.call(arguments)));

if (Object(result) === result)

return result;

return self;

};

指向对象本身

// 该方法一般在处理对象事件时使用, 例如:

// _(obj).bindAll(); // 或_(obj).bindAll('handlerClick');

// document.addEventListener('click', obj.handlerClick);

// 在handlerClick方法中, 上下文依然是obj对象

_.bindAll = function(obj) {

// 第二个参数开始表示需要绑定的函数名称

var funcs = slice.call(arguments, 1);

// 如果没有指定特定的函数名称, 则默认绑定对象本身所有类型为Function的属性

if (funcs.length == 0)

funcs = _.functions(obj);

// 循环并将所有的函数上下本设置为obj对象本身

// each方法本身不会遍历对象原型链中的方法, 但此处的funcs列表是通过_.functions方法获取的, 它已经包含了原型链中的方法

each(funcs, function(f) {

obj[f] = _.bind(obj[f], obj);

});

return obj;

};

// memoize方法将返回一个函数, 该函数集成了缓存功能, 将经过计算的值缓存到局部变量并在下次调用时直接返回

// 如果计算结果是一个庞大的对象或数据, 使用时应该考虑内存占用情况

_.memoize = function(func, hasher) {

// 用于存储缓存结果的memo对象

var memo = {};

// hasher参数应该是一个function, 它用于返回一个key, 该key作为读取缓存的标识

// 如果没有指定key, 则默认使用函数的第一个参数作为key, 如果函数的第一个参数是复合数据类型, 可能会返回类似[Object object]的key, 这个key可能会造成后续计算的数据不正确

hasher || ( hasher = _.identity);

// 返回一个函数, 该函数首先通过检查缓存, 再对没有缓存过的数据进行调用

return function() {

var key = hasher.apply(this, arguments);

return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));

};

};

// 延时执行一个函数

// wait单位为ms, 第3个参数开始将被依次传递给执行函数

_.delay = function(func, wait) {

var args = slice.call(arguments, 2);

return setTimeout(function() {

return func.apply(null, args);

}, wait);

};

// 延迟执行函数

// JavaScript中的setTimeout会被放到一个单独的函数堆栈中执行, 执行时间是在当前堆栈中调用的函数都被执行完毕之后

// defer设置函数在1ms后执行, 目的是将func函数放到单独的堆栈中, 等待当前函数执行完成后再执行// defer方法一般用于处理DOM操作的优先级, 实现正确的逻辑流程和更流畅的交互体验

};

// 函数节流方法, throttle方法主要用于控制函数的执行频率, 在被控制的时间间隔内, 频繁调用函数不会被多次执行

// 在时间间隔内如果多次调用了函数, 时间隔截止时会自动调用一次, 不需要等到时间截止后再手动调用(自动调用时不会有返回值)

// throttle函数一般用于处理复杂和调用频繁的函数, 通过节流控制函数的调用频率, 节省处理资源

// 例如window.onresize绑定的事件函数, 或element.onmousemove绑定的事件函数, 可以用throttle进行包装

// throttle方法返回一个函数, 该函数会自动调用func并进行节流控制

_.throttle = function(func, wait) {

var context, args, timeout, throttling, more, result;

// whenDone变量调用了debounce方法, 因此在多次连续调用函数时, 最后一次调用会覆盖之前调用的定时器, 清除状态函数也仅会被执行一次

// whenDone函数在最后一次函数执行的时间间隔截止时调用, 清除节流和调用过程中记录的一些状态

var whenDone = _.debounce(function() {

more = throttling = false;

}, wait);

// 返回一个函数, 并在函数内进行节流控制

return function() {

// 保存函数的执行上下文和参数

context = this;

args = arguments;

// later函数在上一次函数调用时间间隔截止时执行

var later = function() {

// 清除timeout句柄, 方便下一次函数调用

timeout = null;

// more记录了在上一次调用至时间间隔截止之间, 是否重复调用了函数

// 如果重复调用了函数, 在时间间隔截止时将自动再次调用函数

if (more)

func.apply(context, args);

// 调用whenDone, 用于在时间间隔后清除节流状态

whenDone();

};

// timeout记录了上一次函数执行的时间间隔句柄

// timeout时间间隔截止时调用later函数, later中将清除timeout, 并检查是否需要再次调用函数

if (!timeout)

timeout = setTimeout(later, wait);

// throttling变量记录上次调用的时间间隔是否已经结束, 即是否处于节流过程中

// throttling在每次函数调用时设为true, 表示需要进行节流, 在时间间隔截止时设置为false(在whenDone函数中实现)

if (throttling) {

// 节流过程中进行了多次调用, 在more中记录一个状态, 表示在时间间隔截止时需要再次自动调用函数

more = true;

} else {

// 没有处于节流过程, 可能是第一次调用函数, 或已经超过上一次调用的间隔, 可以直接调用函数

result = func.apply(context, args);

whenDone();

// throttling变量记录函数调用时的节流状态

throttling = true;

// 返回调用结果

return result;

};

};

// debounce与throttle方法类似, 用于函数节流, 它们的不同之处在于:

// -- throttle关注函数的执行频率, 在指定频率内函数只会被执行一次;

// -- debounce函数更关注函数执行的间隔, 即函数两次的调用时间不能小于指定时间;

// 如果两次函数的执行间隔小于wait, 定时器会被清除并重新创建, 这意味着连续频繁地调用函数, 函数一直不会被执行, 直到某一次调用与上一次调用的时间不小于wait毫秒

// debounce函数一般用于控制需要一段时间之后才能执行的操作, 例如在用户输入完毕200ms后提示用户, 可以使用debounce包装一个函数, 绑定到onkeyup事件

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

// @param {Function} func 表示被执行的函数

// @param {Number} wait 表示允许的时间间隔, 在该时间范围内重复调用会被重新推迟wait毫秒

// @param {Boolean} immediate 表示函数调用后是否立即执行, true为立即调用, false为在时间截止时调用

// debounce方法返回一个函数, 该函数会自动调用func并进行节流控制

_.debounce = function(func, wait, immediate) {

// timeout用于记录函数上一次调用的执行状态(定时器句柄)

// 当timeout为null时, 表示上一次调用已经结束

var timeout;

// 返回一个函数, 并在函数内进行节流控制

return function() {

// 保持函数的上下文对象和参数

var context = this, args = arguments;

var later = function() {

// 设置timeout为null

// later函数会在允许的时间截止时被调用

// 调用该函数时, 表明上一次函数执行时间已经超过了约定的时间间隔, 此时之后再进行调用都是被允许的

timeout = null;

if (!immediate)

func.apply(context, args);

};

// 如果函数被设定为立即执行, 且上一次调用的时间间隔已经过去, 则立即调用函数

if (immediate && !timeout)

func.apply(context, args);

// 创建一个定时器用于检查和设置函数的调用状态

// 创建定时器之前先清空上一次setTimeout句柄, 无论上一次绑定的函数是否已经被执行

// 如果本次函数在调用时, 上一次函数执行还没有开始(一般是immediate设置为false时), 则函数的执行时间会被推迟, 因此timeout句柄会被重新创建

clearTimeout(timeout);

// 在允许的时间截止时调用later函数

timeout = setTimeout(later, wait);

};

// 该函数用于获取和计算固定数据的逻辑, 如获取用户所用的浏览器类型

_.once = function(func) {

// ran记录函数是否被执行过

// memo记录函数最后一次执行的结果

var ran = false, memo;

return function() {

// 如果函数已被执行过, 则直接返回第一次执行的结果

if (ran)

return memo;

ran = true;

return memo = func.apply(this, arguments);

};

};

// 返回一个函数, 该函数会将当前函数作为参数传递给一个包裹函数

// 在包裹函数中可以通过第一个参数调用当前函数, 并返回结果

// 一般用于多个流程处理函数的低耦合组合调用

_.wrap = function(func, wrapper) {

return function() {

// 将当前函数作为第一个参数, 传递给wrapper函数

var args = [func].concat(slice.call(arguments, 0));

// 返回wrapper函数的处理结果

return wrapper.apply(this, args);

};

};

// 将多个函数组合到一起, 按照参数传递的顺序, 后一个函数的返回值会被一次作为参数传递给前一个函数作为参数继续处理

// _.compose(A, B, C); 等同于 A(B(C()));

// 该方法的缺点在于被关联的函数处理的参数数量只能有一个, 如果需要传递多个参数, 可以通过Array或Object复合数据类型进行组装

_.compose = function() {

// 获取函数列表, 所有参数需均为Function类型

var funcs = arguments;

// 返回一个供调用的函数句柄

return function() {

// 从后向前依次执行函数, 并将记录的返回值作为参数传递给前一个函数继续处理

var args = arguments;

for (var i = funcs.length - 1; i >= 0; i--) {

args = [funcs[i].apply(this, args)];

}

// 返回最后一次调用函数的返回值

return args[0];

};

};

// 返回一个函数, 该函数作为调用计数器, 当该函数被调用times次(或超过times次)后, func函数将被执行// after方法一般用作异步的计数器, 例如在多个AJAX请求全部完成后需要执行一个函数, 则可以使用after在每个AJAX请求完成后调用

_.after = function(times, func) {

// 如果没有指定或指定无效次数, 则func被直接调用

// 返回一个计数器函数

return function() {

// 每次调用计数器函数times减1, 调用times次之后执行func函数并返回func函数的返回值if (--times < 1) {

return func.apply(this, arguments);

}

};

};

// 对象相关方法

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

// 获取一个对象的属性名列表(不包含原型链中的属性)

_.keys = nativeKeys ||

function(obj) {

if (obj !== Object(obj))

throw new TypeError('Invalid object');

var keys = [];

// 记录并返回对象的所有属性名

for (var key in obj)

if (_.has(obj, key))

keys[keys.length] = key;

return keys;

};

// 返回一个对象中所有属性的值列表(不包含原型链中的属性)

_.values = function(obj) {

return _.map(obj, _.identity);

};

// 获取一个对象中所有属性值为Function类型的key列表, 并按key名进行排序(包含原型链中的属性) _.functions = _.methods = function(obj) {

var names = [];

for (var key in obj) {

if (_.isFunction(obj[key]))

names.push(key);

}

return names.sort();

};

// 将一个或多个对象的属性(包含原型链中的属性), 复制到obj对象, 如果存在同名属性则覆盖

_.extend = function(obj) {

// each循环参数中的一个或多个对象

each(slice.call(arguments, 1), function(source) {

// 将对象中的全部属性复制或覆盖到obj对象

for (var prop in source) {

obj[prop] = source[prop];

}

});

return obj;

};

_.pick = function(obj) {

// 创建一个对象, 存放复制的指定属性

var result = {};

// 从第二个参数开始合并为一个存放属性名列表的数组

each(_.flatten(slice.call(arguments, 1)), function(key) {

// 循环属性名列表, 如果obj中存在该属性, 则将其复制到result对象

if ( key in obj)

result[key] = obj[key];

});

// 返回复制结果

return result;

};

// 将obj中不存在或转换为Boolean类型后值为false的属性, 从参数中指定的一个或多个对象中复制到obj // 一般用于给对象指定默认值

_.defaults = function(obj) {

// 从第二个参数开始可指定多个对象, 这些对象中的属性将被依次复制到obj对象中(如果obj对象中不存在该属性的话)

each(slice.call(arguments, 1), function(source) {

// 遍历每个对象中的所有属性

for (var prop in source) {

// 如果obj中不存在或属性值转换为Boolean类型后值为false, 则将属性复制到obj中

if (obj[prop] == null)

obj[prop] = source[prop];

}

});

return obj;

};

// 创建一个obj的副本, 返回一个新的对象, 该对象包含obj中的所有属性和值的状态

// clone函数不支持深层复制, 例如obj中的某个属性存放着一个对象, 则该对象不会被复制

// 如果obj是一个数组, 则会创建一个相同的数组对象

_.clone = function(obj) {

// 不支持非数组和对象类型的数据

if (!_.isObject(obj))

return obj;

// 复制并返回数组或对象

return _.isArray(obj) ? obj.slice() : _.extend({}, obj);

};

// 执行一个函数, 并将obj作为参数传递给该函数, 函数执行完毕后最终返回obj对象

// 一般在创建一个方法链的时候会使用tap方法, 例如:

// _(obj).chain().tap(click).tap(mouseover).tap(mouseout);

_.tap = function(obj, interceptor) {

interceptor(obj);

return obj;

};

// eq函数只在isEqual方法中调用, 用于比较两个数据的值是否相等

// 与 === 不同在于, eq更关注数据的值

// 如果进行比较的是两个复合数据类型, 不仅仅比较是否来自同一个引用, 且会进行深层比较(对两个对象的结构和数据进行比较)

// 对于复合数据类型, 如果它们来自同一个引用, 则认为其相等

// 如果被比较的值其中包含0, 则检查另一个值是否为-0, 因为 0 === -0 是成立的

// 而 1 / 0 == 1 / -0 是不成立的(1 / 0值为Infinity, 1 / -0值为-Infinity, 而Infinity不等于-Infinity)

if (a === b)

return a !== 0 || 1 / a == 1 / b;

// 将数据转换为布尔类型后如果值为false, 将判断两个值的数据类型是否相等(因为null与undefined, false, 0, 空字符串, 在非严格比较下值是相等的)

if (a == null || b == null)

return a === b;

// 如果进行比较的数据是一个Underscore封装的对象(具有_chain属性的对象被认为是Underscore对象) // 则将对象解封后获取本身的数据(通过_wrapped访问), 然后再对本身的数据进行比较

// 它们的关系类似与一个jQuery封装的DOM对象, 和浏览器本身创建的DOM对象

if (a._chain)

a = a._wrapped;

if (b._chain)

b = b._wrapped;

// 如果对象提供了自定义的isEqual方法(此处的isEqual方法并非Undersocre对象的isEqual方法, 因为在上一步已经对Undersocre对象进行了解封)

// 则使用对象自定义的isEqual方法与另一个对象进行比较

if (a.isEqual && _.isFunction(a.isEqual))

return a.isEqual(b);

if (b.isEqual && _.isFunction(b.isEqual))

return b.isEqual(a);

// 对两个数据的数据类型进行验证

// 获取对象a的数据类型(通过Object.prototype.toString方法)

var className = toString.call(a);

// 如果对象a的数据类型与对象b不匹配, 则认为两个数据值也不匹配

if (className != toString.call(b))

return false;

// 执行到此处, 可以确保需要比较的两个数据均为复合数据类型, 且数据类型相等

// 通过switch检查数据的数据类型, 针对不同数据类型进行不同的比较

// (此处不包括对数组和对象类型, 因为它们可能包含更深层次的数据, 将在后面进行深层比较)

switch (className) {

case '[object String]':

// 如果被比较的是字符串类型(其中a的是通过new String()创建的字符串)

// 则将B转换为String对象后进行匹配(这里匹配并非进行严格的数据类型检查, 因为它们并非来自同一个对象的引用)

// 在调用 == 进行比较时, 会自动调用对象的toString()方法, 返回两个简单数据类型的字符串

return a == String(b);

case '[object Number]':

// 通过+a将a转成一个Number, 如果a被转换之前与转换之后不相等, 则认为a是一个NaN类型

// 因为NaN与NaN是不相等的, 因此当a值为NaN时, 无法简单地使用a == b进行匹配, 而是用相同的方法检查b是否为NaN(即 b != +b)

// 当a值是一个非NaN的数据时, 则检查a是否为0, 因为当b为-0时, 0 === -0是成立的(实际上它们在逻辑上属于两个不同的数据)

return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);

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

Vue 源码解析:深入响应式原理(中) Directive Vue 指令类型很多,限于篇幅,我们不会把所有指令的解析过程都介绍一遍,这里结合前面的例子只介绍v-text 指令的解析过程,其他指令的解析过程也大同小异。 前面我们提到了Vue 实例创建的生命周期,在给data 添加Observer 之后,有一个过程是调用https://www.sodocs.net/doc/fb10890245.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.sodocs.net/doc/fb10890245.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.sodocs.net/doc/fb10890245.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.sodocs.net/doc/fb10890245.html,/itadm/article.php%3Fc%3D47717&prev=hp&rurl=https://www.sodocs.net/doc/fb10890245.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, ...);

相关主题