Разбор Underscore
- понедельник, 20 октября 2014 г. в 03:11:00
(function() {
// сохраняет глобальный объект (window или exports) для дальнейшей работы
var root = this;
// сохраняет предыдущий underscore, если тот уже был подключен раньше.
var previousUnderscore = root._;
// крепит черточку к глобальному объекту
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = _;
}
exports._ = _;
} else {
root._ = _;
}
// поставит в глобал предыдущую черточку и вернет текущую
_.noConflict = function() {
root._ = previousUnderscore;
return this;
};
// просто, чтобы было меньше глюков при работе с модулем
if (typeof define === 'function' && define.amd) {
define('underscore', [], function() {
return _;
});
}
}.call(this)); // передает внутрь модуля глобальный объект
// то что будет использоваться как черточка - объект или как черточка - создатель объекта с кучей функций.
var _ = function(obj) {
if (obj instanceof _) return obj;
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
};
var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
// достаем функции из прототипов стандартных конструкторов
var
push = ArrayProto.push,
slice = ArrayProto.slice,
concat = ArrayProto.concat,
toString = ObjProto.toString,
hasOwnProperty = ObjProto.hasOwnProperty;
// достаем функции из самих объектов
var
nativeIsArray = Array.isArray,
nativeKeys = Object.keys,
nativeBind = FuncProto.bind;
_.VERSION = '1.7.0';
// при необходимости обертывает функцию для использования на конкретном контексте,
// то есть this внутри func будет ссылкой на context
var createCallback = function(func, context, argCount) {
if (context === void 0) return func; // нет контекста - не надо обертывать
switch (argCount == null ? 3 : argCount) { // передано ли колличество аргументов для func
case 1: return function(value) { // если один аргумент, то создадим новую ф-цию, которая вызовет func на конкретном контексте
return func.call(context, value); // и с этим аргументом
};
case 2: return function(value, other) {
return func.call(context, value, other);
};
case 3: return function(value, index, collection) {
return func.call(context, value, index, collection);
};
case 4: return function(accumulator, value, index, collection) {
return func.call(context, accumulator, value, index, collection);
};
}
return function() {
return func.apply(context, arguments); // создание самой медленной функции, которая вызовет func на нужном контексте,
}; // c любым колличеством аргументов, которые хранятся в массивоподобном объекте arguments
};
// создает функцию, которая
_.iteratee = function(value, context, argCount) {
if (value == null) return _.identity; // просто возвращает переданное значение
if (_.isFunction(value)) return createCallback(value, context, argCount);
if (_.isObject(value)) return _.matches(value); // проверяет наличие конкретного набора свойств
return _.property(value); // проверяет наличие одного названного свойства
};
_.each = _.forEach = function(obj, iteratee, context) { };
_.map = _.collect = function(obj, iteratee, context) { };
_.reduce = _.foldl = _.inject = function(obj, iteratee, memo, context) { };
_.reduceRight = _.foldr = function(obj, iteratee, memo, context) { };
_.find = _.detect = function(obj, predicate, context) { };
_.filter = _.select = function(obj, predicate, context) { };
_.reject = function(obj, predicate, context) { };
_.every = _.all = function(obj, predicate, context) { };
_.some = _.any = function(obj, predicate, context) { };
_.contains = _.include = function(obj, target) { };
_.invoke = function(obj, method) { };
_.pluck = function(obj, key) { };
_.where = function(obj, attrs) { };
_.findWhere = function(obj, attrs) { };
_.max = function(obj, iteratee, context) { };
_.min = function(obj, iteratee, context) { };
_.shuffle = function(obj) { };
_.sample = function(obj, n, guard) { };
_.sortBy = function(obj, iteratee, context) { };
// дальше похожие функции, которые делаются за счет добавки переданного уникального функционала к тому, который общий для всех
_.groupBy = group(function(result, value, key) { });
_.indexBy = group(function(result, value, key) { });
_.countBy = group(function(result, value, key) { });
_.sortedIndex = function(array, obj, iteratee, context) { };
_.toArray = function(obj) { };
_.size = function(obj) { };
_.partition = function(obj, predicate, context) { };
// типа коллекций, у которых, вместо названий свойств, номера
_.first = _.head = _.take = function(array, n, guard) { };
_.initial = function(array, n, guard) { };
_.last = function(array, n, guard) { };
_.rest = _.tail = _.drop = function(array, n, guard) { };
_.compact = function(array) { };
_.flatten = function(array, shallow) { };
_.without = function(array) { };
_.uniq = _.unique = function(array, isSorted, iteratee, context) { };
_.union = function() { };
_.intersection = function(array) { };
_.difference = function(array) { };
_.zip = function(array) { };
_.object = function(list, values) { };
_.indexOf = function(array, item, isSorted) { };
_.lastIndexOf = function(array, item, from) { };
_.range = function(start, stop, step) { };
// функции для обработки других функций
_.bind = function(func, context) { };
_.partial = function(func) { };
_.bindAll = function(obj) { };
_.memoize = function(func, hasher) { };
_.delay = function(func, wait) { };
_.defer = function(func) { };
_.throttle = function(func, wait, options) { };
_.debounce = function(func, wait, immediate) { };
_.wrap = function(func, wrapper) { };
_.negate = function(predicate) { };
_.compose = function() { };
_.after = function(times, func) { };
_.before = function(times, func) { };
_.once = _.partial(_.before, 2); // получается из before, у которого первым аргументом всегда будет 2
_.keys = function(obj) { };
_.values = function(obj) { };
_.pairs = function(obj) { };
_.invert = function(obj) {};
_.functions = _.methods = function(obj) {};
_.extend = function(obj) { };
_.pick = function(obj, iteratee, context) { };
_.omit = function(obj, iteratee, context) { };
_.defaults = function(obj) { };
_.clone = function(obj) { };
_.tap = function(obj, interceptor) { };
_.isEqual = function(a, b) { };
_.isEmpty = function(obj) { };
_.isElement = function(obj) { };
_.isArray = nativeIsArray || function(obj) { }; // если нет нативной функции - делается своя
_.isObject = function(obj) { };
_.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { }); // навешиваются функции сгенерированные из названий
if (!_.isArguments(arguments)) {
_.isArguments = function(obj) { }; // навешивается , если нет уже такой же нормально работающей
}
if (typeof /./ !== 'function') {
_.isFunction = function(obj) { };
}
_.isFinite = function(obj) {};
_.isNaN = function(obj) {};
_.isBoolean = function(obj) {};
_.isNull = function(obj) {};
_.isUndefined = function(obj) {};
_.has = function(obj, key) {};
_.identity = function(value) {};
_.constant = function(value) {};
_.noop = function(){};
_.property = function(key) { };
_.matches = function(attrs) { };
_.times = function(n, iteratee, context) { };
_.random = function(min, max) { };
_.now = Date.now || function() {};
_.escape = createEscaper(escapeMap);
_.unescape = createEscaper(unescapeMap);
_.result = function(object, property) { };
_.template = function(text, settings, oldSettings) { };
_.chain = function(obj) { };
// проверяет будут ли делать так: _(a).f1().f2().f3()
var result = function(obj) {
return this._chain ? _(obj).chain() : obj;
};
// делает так, чтобы
_.mixin = function(obj) {
_.each(_.functions(obj), function(name) {
var func = _[name] = obj[name];
_.prototype[name] = function() { // методы c _ перекочевали на _(х)
var args = [this._wrapped]; // будут вызываться методы _, с первым аргументом "х"
push.apply(args, arguments);
return result.call(this, func.apply(_, args)); // и, если надо, результат опять оборачивается в _();
};
});
};
// собственно, подмешиваются методы
_.mixin(_);
// тоже, что и миксом,
_.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
var method = ArrayProto[name];
_.prototype[name] = function() {
var obj = this._wrapped;
method.apply(obj, arguments); // только this-ом внутри метода будет изначальный объект без обертки.
if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0]; // и. если надо, удаляется первый элемент
return result.call(this, obj);
};
});
// то же
_.each(['concat', 'join', 'slice'], function(name) {
var method = ArrayProto[name]; // только метод свистнули из прототипа массива
_.prototype[name] = function() {
return result.call(this, method.apply(this._wrapped, arguments));
};
});
// этим достается сам объект x из вот этого _(х).f1().f2().f3()
_.prototype.value = function() {
return this._wrapped;
};
var root = this // сохранение текущего значения, потому что this штука переменчивая
var i, length = obj.length; // раннее создание переменных, которые будут использоваться в двух разных циклах
for (i = 0, length = keys.length; i < length; i++) {
iteratee(obj[keys[i]], keys[i], obj);
}
for (; i < length; i++) if (array[i] === item) return i; // i заранее присвоили значение
slice.call(array, Math.max(array.length - n, 0)); // копия части элементов массива
args.concat(slice.call(arguments)) // присоединение к аргсам копии аргументов
for (var key in obj) if (_.has(obj, key)) keys.push(key); // перебирает названия свойств объекта
obj[prop] = source[prop]; // добавление свойств одного объекта другому
(function() { }.call(this)); // моментальное выполнение, this внутри как снаружи
if (!(this instanceof _)) return new _(obj); // когда надо, чтобы функция что-то конструировала
var createCallback = function(func, context, argCount) {
return function(value) { // когда надо создать другую, внутри которой будет работать наша
return func.call(context, value);
};
};
_.each = _.forEach = function(obj, iteratee, context) {}; // создание функции и крепление ее на _
_.find = _.detect = function(obj, predicate, context) {
predicate = _.iteratee(predicate, context); // подмена пришедшего predicate на созданную из него функцию
_.some(); // вызов одной, навешенной на черточку функции, внутри другой
};
_.some(obj, function(value, index, list) { }); // создание и передача одной функции как аргумент в другую
// создает функцию,
var group = function(behavior) { // часть поведения которой передается в аргументах
return function(obj, iteratee, context) { // то есть, созданные функции работают одинаково
behavior(result, value, key); // только под конец - по разному
return result;
};
};
_.indexBy = group(function(result, value, key) { // сама генерация
result[key] = value;
});
// кусок кода, который выполняет часть себя, потом, если надо, запускает свою копию, ждет пока копия выполнится, потом выполняет оставшуюся часть себя
var flatten = function(input, shallow, strict, output) {
for (var i = 0, length = input.length; i < length; i++) {
flatten(value, shallow, strict, output); // подныривает во встреченные вложенные массивы
}
};
// обертывание одной функции вокруг другой
_.flatten = function(array, shallow) {
return flatten(array, shallow, false, []);
};
// аналог var x = func.bind(obj), означает, что каждый раз, когда делается так: x(),
// происходить будет что-то типа obj.func(), то есть this внутри func будет obj
nativeBind.apply(func, slice.call(arguments, 1))
// подмена методов объекта на их обертки, которые всегда вызывают оригиналы на этом объекте
// то есть нельзя будет вызвать метод объекта и передать ему в качестве this посторонний объект
obj[key] = _.bind(obj[key], obj);
// создание функции со своим
_.memoize = function(func, hasher) {
var memoize = function(key) {};
memoize.cache = {}; // личным объектом для запоминания
return memoize;
};
// задержка выполнения функции
setTimeout(function(){
return func.apply(null, args);
}, wait);
// функция, которая из названия делает другую функцию
_.property = function(key) {
return function(obj) {
return obj[key]; // которая выдергивает из переданного объекта значение названного свойства
};
};
// закидывает функцию
_.prototype[name] = function() { // с именем name в прототип черточки
var args = [this._wrapped];
push.apply(args, arguments);
return result.call(this, func.apply(_, args));
};
// после чего будет работать вот это: var x = new _; x[name]();
typeof exports !== 'undefined' // существования
if (length === +length) // является ли числом
var keys = obj.length !== +obj.length && _.keys(obj), // если проверка пройдена, тогда вызов _.keys
length = (keys || obj).length, // длина из кейса или, если нет, из самого объекта
results = Array(length),
currentKey;
memo = obj[keys ? keys[index++] : index++]; // достаем по названию, если нет, по номеру
if (!index) throw new TypeError(reduceError); // нет индекса - вылет с ошибкой
if (array == null) return void 0; // несколько проверок для поиска подходящего действия
if (n == null || guard) return array[0];
if (n < 0) return [];
return slice.call(array, 0, n);
if (!(this instanceof bound)) // сделан ли this с помощью new bound
_.isBoolean = function(obj) { // является ли объект булевым
return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
};
// проверка наличия родного бинда и его равенство тому, который навешен на func
if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
hasOwnProperty.call(source, prop) // является ли свойство своим (не прототипа)
if (key in obj) result[key] = obj[key]; // проверка наличия свойства в объекте
if (obj[prop] === void 0) obj[prop] = source[prop]; // проверка отсутствия свойства
// попытка создать функцию из кучи текста
try {
var render = new Function(settings.variable || 'obj', '_', source);
} catch (e) { // перехват ошибки
e.source = source;
throw e; // и бросание заново с добавкой
}
while (index--) { // быстрый цикл от конца к началу
currentKey = keys ? keys[index] : index;
memo = iteratee(memo, obj[currentKey], currentKey, obj);
}
// вызов отдельного метода или метода-свойства value, this - value, аргументы вытряхиваются из args
// типа func.apply(x, [1, 2, 3, 4]) похоже на x.func(1, 2, 3, 4), а func.call(x, [1, 2, 3, 4]) - на x.func([1, 2, 3, 4])
(isFunc ? method : value[method]).apply(value, args)
var mid = low + high >>> 1; // быстрое деление на 2
// создание и навешивание на _ кучи функций, внутрення реализация которых отличается только задействованным названием
_.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
_['is' + name] = function(obj) {
return toString.call(obj) === '[object ' + name + ']';
};
});
// применяет что-то (iteratee) на каждом элементе из obj, this-ом в этом чем-то будет context
// obj - коллекция или массив
function(obj, iteratee, context) {};
// собственно, что-то что будет применяться на каждом элементе из чего-то типа коллекции,
//где акумулятор - хранилище результата, value - элемент из коллекции, index - номер или название элемента
function(accumulator, value, index, collection) {};
memo = iteratee(memo, obj[currentKey], currentKey, obj);
// predicate вызывается для проверки переданного ему элемента, возвращает true, если проверка пройдена
function(obj, predicate, context) {};
// значение, номер/свойство в коллекции, коллекция/массив;
function(value, index, list) { }
// method либо отдельная функция, либо название ф-ции, которая есть на каждом элементе коллекции
function(obj, method) {};
// key - название, которое будет превращено в функцию, которая будет помогать проверять или выдергивать свойство из элемента.
function(obj, key) {};
// attrs - типа key, только функция будет проверять наявность всей пачки свойств в переданном элементе
function(obj, attrs) { };
// guard говорит функции, что ей дали лишний аргумент
function(obj, n, guard) {};
// похоже на предыдущее только первый объект явно массив
function(array, n, guard) {};
// скорее всего создает новую функцию, которая внутри использует переданную
function(func){};
// скорее всего проверка объекта на что-то или монипуляция со свойствами
function(obj){};