Изучите общую архитектуру исходного кода lodash и создайте собственную библиотеку классов функционального программирования.

исходный код JavaScript

предисловие

Здравствуйте, яВакагава. Это学习源码整体架构系列Третья статья. Слово «общая архитектура» кажется немного большим, скажем так, это общая структура исходного кода. В этой статье рассказывается об упакованном и интегрированном коде, а не о разделенном коде на реальном складе.

学习源码整体架构系列Статья выглядит следующим образом:

1.Изучите общую архитектуру исходного кода jQuery и создайте собственную библиотеку js.
2.Изучите общую архитектуру исходного кода подчеркивания и создайте собственную библиотеку классов функционального программирования.
3.Изучите общую архитектуру исходного кода lodash и создайте собственную библиотеку классов функционального программирования.
4.Изучите общую архитектуру исходного кода sentry и создайте собственный SDK для мониторинга исключений переднего плана.
5.Изучите общую архитектуру исходного кода vuex и создайте собственную библиотеку управления состоянием.
6.Изучите общую архитектуру исходного кода axios и создайте собственную библиотеку запросов.
7.Изучите общую архитектуру исходного кода koa, проанализируйте принцип луковой модели koa и принцип совместной работы.
8.Изучите общую архитектуру исходного кода Redux и глубоко поймите принципы Redux и его промежуточного программного обеспечения.

Заинтересованные читатели могут нажать, чтобы прочитать.

underscoreСуществует множество статей по анализу исходного кода, иlodashСтатей по анализу исходного кода сравнительно немного. Одна из причин может заключаться вlodashСлишком много исходных строк. Комментарии составляют более 10 000 строк.

анализироватьlodashСтатей по общей структуре кода относительно немного, автор использует Google, Bing,githubЯ не могу найти его после поиска, может быть, я ищу его неправильно. Поэтому я решил написать один сам. Большинство людей будут использовать его в нормальном развитииlodash, и все знают более-менее,lodashСравниватьunderscoreХорошая производительность, основной причиной хорошей производительности является использование функции отложенной оценки.

узнал из этой статьиlodashВерсия:v4.17.15.unpkg.comАдрес https://unpkg.com/lodash@4.17.15/lodash.js

Длина статьи может быть относительно большой, ее можно сначала собрать, а потом прочитать, поэтому автор использует форму расширения и сокращения.

Гид:

Статья в основном учитrunInContext()экспорт_ lodashиспользование функцииbaseCreateметод прототипного наследованияLodashWrapperа такжеLazyWrapper,mixinметод монтирования наlodash.prototypeПосле объяснения в сочетании с примерамиlodash.prototype.value(wrapperValue)а такжеLazy.prototype.value(lazyValue)Исходный код реализации ленивых вычислений.

Анонимное выполнение функции

;(function() {

}.call(this));

разоблачение лодаша

var _ = runInContext();

функция runInContext

Упрощенная версия исходного кода здесь фокусируется только на записи функции и возвращаемом значении.

var runInContext = (function runInContext(context) {
 // 浏览器中处理context为window
 // ...
 function lodash(value) {}{
  // ...
  return new LodashWrapper(value);
 }
 // ...
 return lodash;
});

Видно, что декларацияrunInContextфункция. Eстьlodashфункция, окончательная обработка возвращает этоlodashфункция.

посмотри сноваlodashвозвращаемое значение в функцииnew LodashWrapper(value).

Функция LodashWrapper

function LodashWrapper(value, chainAll) {
 this.__wrapped__ = value;
 this.__actions__ = [];
 this.__chain__ = !!chainAll;
 this.__index__ = 0;
 this.__values__ = undefined;
}

Эти свойства устанавливаются:

__wrapped__: сохранить параметрыvalue.

__actions__: Сохраняет тело функции, которая должна быть выполнена.func, параметр функцииargs, функция выполняетсяthisнаправлениеthisArg.

__chain__,undefinedДважды инвертировать в логическое значениеfalse, связанные вызовы не поддерживаются. а такжеunderscoreАналогично, связанные вызовы по умолчанию не поддерживаются.

__index__: значение индекса по умолчанию равно 0.

__values__:основнойcloneпри использовании.

Затем найдите исходный код,LodashWrapper, Вы найдете эти две строки кода.

LodashWrapper.prototype = baseCreate(baseLodash.prototype);
LodashWrapper.prototype.constructor = LodashWrapper;

Тогда посмотрите вверхbaseCreate、baseLodashэти две функции.

baseCreate прототипное наследование

//  立即执行匿名函数
// 返回一个函数,用于设置原型 可以理解为是 __proto__
var baseCreate = (function() {
 // 这句放在函数外,是为了不用每次调用baseCreate都重复申明 object
 // underscore 源码中,把这句放在开头就申明了一个空函数 `Ctor`
 function object() {}
 return function(proto) {
  // 如果传入的参数不是object也不是function 是null
  // 则返回空对象。
  if (!isObject(proto)) {
   return {};
  }
  // 如果支持Object.create方法,则返回 Object.create
  if (objectCreate) {
   // Object.create
   return objectCreate(proto);
  }
  // 如果不支持Object.create 用 ployfill new
  object.prototype = proto;
  var result = new object;
  // 还原 prototype
  object.prototype = undefined;
  return result;
 };
}());

// 空函数
function baseLodash() {
 // No operation performed.
}

// Ensure wrappers are instances of `baseLodash`.
lodash.prototype = baseLodash.prototype;
// 为什么会有这一句?因为上一句把lodash.prototype.construtor 设置为Object了。这一句修正constructor
lodash.prototype.constructor = lodash;

LodashWrapper.prototype = baseCreate(baseLodash.prototype);
LodashWrapper.prototype.constructor = LodashWrapper;

Автор нарисовал картину, чтобы показать эту связь.

lodash 原型关系图
схема прототипа lodash

Производная функция isObject

судитьtypeof valueне равноnull, и являетсяobjectилиfunction.

function isObject(value) {
 var type = typeof value;
 return value != null && (type == 'object' || type == 'function');
}

Пример использования Object.create()

Интервьюер спросил: Могу ли я смоделировать новый оператор, реализующий JS?Абзац написан в предыдущей статье, поэтому здесь он сокращен.

Нажмите, чтобы увидеть пример использования Object.create()

Я также упомянул об этом в статье, которую я составил ранее, вы можете прочитать ееВесь API-анализ объектов JavaScript

MDN Object.create()

Object.create(proto, [propertiesObject])метод создает новый объект, используя существующий объект для предоставления __proto__ вновь созданного объекта. Он принимает два параметра, но вторым необязательным параметром является дескриптор атрибута (обычно не используется, по умолчаниюundefined).

var anotherObject = {
    name: '若川'
};
var myObject = Object.create(anotherObject, {
    age: {
        value:18,
    },
});
// 获得它的原型
Object.getPrototypeOf(anotherObject) === Object.prototype; // true 说明anotherObject的原型是Object.prototype
Object.getPrototypeOf(myObject); // {name: "若川"} // 说明myObject的原型是{name: "若川"}
myObject.hasOwnProperty('name'); // false; 说明name是原型上的。
myObject.hasOwnProperty('age'); // true 说明age是自身的
myObject.name; // '若川'
myObject.age; // 18;

для не поддерживаемыхES5браузер,MDNпредоставляется наployfillстроить планы.

if (typeof Object.create !== "function") {
    Object.create = function (proto, propertiesObject) {
        if (typeof proto !== 'object' && typeof proto !== 'function') {
            throw new TypeError('Object prototype may only be an Object: ' + proto);
        } else if (proto === null) {
            throw new Error("This browser's implementation of Object.create is a shim and doesn't support 'null' as the first argument.");
        }

        if (typeof propertiesObject != 'undefined') throw new Error("This browser's implementation of Object.create is a shim and doesn't support a second argument.");

        function F() {}
        F.prototype = proto;
        return new F();
    };
}

lodashСуществует множество методов и свойств наlodash.prototypeЕсть также многоlodashтот же метод, что и выше. Конечно нетlodash.prototypeПерепишите его. но черезmixinустановлен.

mixin

конкретное использование миксина

_.mixin([object=lodash], source, [options={}])

Добавляет все перечисляемые свойства функции самого исходного объекта к целевому объекту. Если объект является функцией, метод функции будет добавлен в цепочку прототипов.

Примечание. Используйте _.runInContext для создания исходной функции lodash, чтобы избежать конфликтов, вызванных модификациями.

добавить версию

0.1.0

параметр

[object=lodash] (Функция|Объект): целевой объект.

источник (объект): исходный объект.

[options={}] (Object): Объект опций.

[options.chain=true] (логическое значение): следует ли включать цепочку операций.

вернуть

(*): Возвращаемый объект.

исходный код примеси

Нажмите здесь, чтобы развернуть исходный код миксина, а комментарии будут проанализированы позже.
function mixin(object, source, options) {
 var props = keys(source),
  methodNames = baseFunctions(source, props);

 if (options == null &&
  !(isObject(source) && (methodNames.length || !props.length))) {
  options = source;
  source = object;
  object = this;
  methodNames = baseFunctions(source, keys(source));
 }
 var chain = !(isObject(options) && 'chain' in options) || !!options.chain,
  isFunc = isFunction(object);

 arrayEach(methodNames, function(methodName) {
  var func = source[methodName];
  object[methodName] = func;
  if (isFunc) {
   object.prototype[methodName] = function() {
    var chainAll = this.__chain__;
    if (chain || chainAll) {
     var result = object(this.__wrapped__),
      actions = result.__actions__ = copyArray(this.__actions__);

     actions.push({ 'func': func, 'args': arguments, 'thisArg': object });
     result.__chain__ = chainAll;
     return result;
    }
    return func.apply(object, arrayPush([this.value()], arguments));
   };
  }
 });

 return object;
}
Далее, давайте посмотрим на производную функцию.

На самом деле, когда вы видите код функции конкретного определения, вы, вероятно, знаете назначение этой функции. Чтобы не затрагивать основную линию, длина статьи слишком велика. Конкретный исходный код здесь не раскрывается.

Заинтересованные читатели могут посмотреть исходный код других функций, производных от этих функций.

функциональные клавиши, полученные из миксина

существуетmixinВ функции последний вызов фактическиObject.keys

function keys(object) {
 return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object);
}

производные функции примеси baseFunctions

Возвращает коллекцию массивов функций

function baseFunctions(object, props) {
 return arrayFilter(props, function(key) {
  return isFunction(object[key]);
 });
}

функция isFunction, полученная из миксина

Определить, является ли аргумент функцией

function isFunction(value) {
 if (!isObject(value)) {
  return false;
 }
 // The use of `Object#toString` avoids issues with the `typeof` operator
 // in Safari 9 which returns 'object' for typed arrays and other constructors.
 var tag = baseGetTag(value);
 return tag == funcTag || tag == genTag || tag == asyncTag || tag == proxyTag;
}

производная функция миксина arrayEach

Похоже на [].forEach

function arrayEach(array, iteratee) {
 var index = -1,
  length = array == null ? 0 : array.length;

 while (++index < length) {
  if (iteratee(array[index], index, array) === false) {
   break;
  }
 }
 return array;
}

производная функция миксина arrayPush

Похоже на [].push

function arrayPush(array, values) {
 var index = -1,
  length = values.length,
  offset = array.length;

 while (++index < length) {
 array[offset + index] = values[index];
 }
 return array;
}

функция, производная от примеси, copyArray

копировать массив

function copyArray(source, array) {
 var index = -1,
  length = source.length;

 array || (array = Array(length));
 while (++index < length) {
  array[index] = source[index];
 }
 return array;
}

анализ исходного кода миксина

lodashДва вызова в исходном кодеmixin

// Add methods that return wrapped values in chain sequences.
lodash.after = after;
// code ... 等 153 个支持链式调用的方法

// Add methods to `lodash.prototype`.
// 把lodash上的静态方法赋值到 lodash.prototype 上
mixin(lodash, lodash);

// Add methods that return unwrapped values in chain sequences.
lodash.add = add;
// code ... 等 152 个不支持链式调用的方法


// 这里其实就是过滤 after 等支持链式调用的方法,获取到 lodash 上的 add 等 添加到lodash.prototype 上。
mixin(lodash, (function() {
 var source = {};
 // baseForOwn 这里其实就是遍历lodash上的静态方法,执行回调函数
 baseForOwn(lodash, function(func, methodName) {
  // 第一次 mixin 调用了所以赋值到了lodash.prototype
  // 所以这里用 Object.hasOwnProperty 排除不在lodash.prototype 上的方法。也就是 add 等 152 个不支持链式调用的方法。
  if (!hasOwnProperty.call(lodash.prototype, methodName)) {
   source[methodName] = func;
  }
 });
 return source;
// 最后一个参数options 特意注明不支持链式调用
}()), { 'chain': false });

объединить два звонкаmixinЗамените в анализе исходного кода следующим образом

Нажмите здесь, чтобы развернуть исходный код миксина и комментарии
function mixin(object, source, options) {
 // source 对象中可以枚举的属性
 var props = keys(source),
  // source 对象中的方法名称数组
  methodNames = baseFunctions(source, props);

 if (options == null &&
  !(isObject(source) && (methodNames.length || !props.length))) {
  // 如果 options 没传为 undefined  undefined == null 为true
  // 且 如果source 不为 对象或者不是函数
  // 且 source对象的函数函数长度 或者 source 对象的属性长度不为0
  // 把 options 赋值为 source
  options = source;
  // 把 source 赋值为 object
  source = object;
  // 把 object 赋值为 this 也就是 _ (lodash)
  object = this;
  // 获取到所有的方法名称数组
  methodNames = baseFunctions(source, keys(source));
 }
 // 是否支持 链式调用
 // options  不是对象或者不是函数,是null或者其他值
 // 判断options是否是对象或者函数,如果不是或者函数则不会执行 'chain' in options 也就不会报错
 //  且 chain 在 options的对象或者原型链中
 // 知识点 in [MDN in :  https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/in
 // 如果指定的属性在指定的对象或其原型链中,则in 运算符返回true。

 // 或者 options.chain 转布尔值
 var chain = !(isObject(options) && 'chain' in options) || !!options.chain,
  // object 是函数
  isFunc = isFunction(object);

 // 循环 方法名称数组
 arrayEach(methodNames, function(methodName) {
  // 函数本身
  var func = source[methodName];
  // object 通常是 lodash  也赋值这个函数。
  object[methodName] = func;
  if (isFunc) {
   // 如果object是函数 赋值到  object prototype  上,通常是lodash
   object.prototype[methodName] = function() {
    // 实例上的__chain__ 属性 是否支持链式调用
    // 这里的 this 是 new LodashWrapper 实例 类似如下
    /**
     {
     __actions__: [],
     __chain__: true
     __index__: 0
     __values__: undefined
     __wrapped__: []
     }
     **/

    var chainAll = this.__chain__;
    // options 中的 chain 属性 是否支持链式调用
    // 两者有一个符合链式调用  执行下面的代码
    if (chain || chainAll) {
     // 通常是 lodash
     var result = object(this.__wrapped__),
     // 复制 实例上的 __action__ 到 result.__action__ 和 action 上
     actions = result.__actions__ = copyArray(this.__actions__);

     // action 添加 函数 和 args 和 this 指向,延迟计算调用。
     actions.push({ 'func': func, 'args': arguments, 'thisArg': object });
     //实例上的__chain__ 属性  赋值给 result 的 属性 __chain__
     result.__chain__ = chainAll;
     // 最后返回这个实例
     return result;
    }

    // 都不支持链式调用。直接调用
    // 把当前实例的 value 和 arguments 对象 传递给 func 函数作为参数调用。返回调用结果。
    return func.apply(object, arrayPush([this.value()], arguments));
   };
  }
 });

 // 最后返回对象 object
 return object;
}

Резюме: Проще говоряlodashСтатический метод присваиванияlodash.prototypeначальство. Первый раз для поддержки связанных вызовов (lodash.afterЖдать153метод, поддерживающий цепочку вызовов), второй — метод, не поддерживающий цепочку вызовов (lodash.addЖдать152метод, который не поддерживает цепочку).

Сколько методов и свойств монтирует lodash в _ и _.prototype

посмотри сноваlodashточно установлен на_Сколько статических методов и свойств находится в объекте функции и монтирует_.prototypeСколько существует методов и свойств.

использоватьfor inПросто попробуйте цикл. См. следующий код:

var staticMethods = [];
var staticProperty = [];
for(var name in _){
 if(typeof _[name] === 'function'){
  staticMethods.push(name);
 }
 else{
  staticProperty.push(name);
 }
}
console.log(staticProperty); // ["templateSettings", "VERSION"] 2个
console.log(staticMethods); // ["after", "ary", "assign", "assignIn", "assignInWith", ...] 305个

На самом деле упомянутое вышеlodash.afterЖдать153функция, которая поддерживает цепочку,lodash.addЖдать152Назначение из функции, которая не поддерживает цепочку.

var prototypeMethods = [];
var prototypeProperty = [];
for(var name in _.prototype){
 if(typeof _.prototype[name] === 'function'){
  prototypeMethods.push(name);
 }
 else{
  prototypeProperty.push(name);
 }
}
console.log(prototypeProperty); // []
console.log(prototypeMethods); // ["after", "all", "allKeys", "any", "assign", ...] 317个

в сравнении сlodashЕсть больше статических методов на12, что свидетельствует о том, что в дополнение кmixinКроме того, есть12другие формы поручения.

Метод, который поддерживает связанные вызовы, наконец, возвращает объект экземпляра, получает значение результата окончательной обработки и, наконец, должен вызватьvalueметод.

Автор нарисовалlodashДиаграмма отношения монтирования метода и свойства.

的方法和属性挂载关系
lodashОтношение монтирования метода и атрибута

Пожалуйста, приведите простые примеры во всем следующем

var result = _.chain([1, 2, 3, 4, 5])
.map(el => {
 console.log(el); // 1, 2, 3
 return el + 1;
})
.take(3)
.value();
// lodash中这里的`map`仅执行了`3`次。
// 具体功能也很简单 数组 1-5 加一,最后获取其中三个值。
console.log('result:', result);

То есть здесьlodashУмело знайте, сколько значений нужно в конце, и выполняйте несколько разmapДля циклов, для очень больших массивов очень полезно повысить производительность.
а такжеunderscoreвыполнить этот код, которыйmapВыполнено 5 раз. Если это нормально, добиться этой функции также просто.

var result = [1, 2, 3, 4, 5].map(el => el + 1).slice(0, 3);
console.log('result:', result);

в сравнении сlodashздесьmapказнен5Второсортный.

// 不使用 map、slice
var result = [];
var arr = [1, 2, 3, 4, 5];
for (var i = 0; i < 3; i++){
 result[i] = arr[i] + 1;
}
console.log(result, 'result');

просто скажи здесьmapметод, добавитьLazyWrapperПуть кlodash.prototypeхранится и, наконец, называетсяvalueпозвони снова. Подробности смотрите в реализации исходного кода ниже.

Добавить кLazyWrapperспособlodash.prototype

В основном следующие методы добавляются кlodash.prototypeна прототипе.

// "constructor"
["drop", "dropRight", "take", "takeRight", "filter", "map", "takeWhile", "head", "last", "initial", "tail", "compact", "find", "findLast", "invokeMap", "reject", "slice", "takeRightWhile", "toArray", "clone", "reverse", "value"]
Нажмите здесь, чтобы развернуть конкретный исходный код и комментарии
// Add `LazyWrapper` methods to `lodash.prototype`.
// baseForOwn 这里其实就是遍历LazyWrapper.prototype上的方法,执行回调函数
baseForOwn(LazyWrapper.prototype, function(func, methodName) {
 // 检测函数名称是否是迭代器也就是循环
 var checkIteratee = /^(?:filter|find|map|reject)|While$/.test(methodName),
  // 检测函数名称是否head和last
  // 顺便提一下 ()这个是捕获分组 而加上 ?:  则是非捕获分组 也就是说不用于其他操作
  isTaker = /^(?:head|last)$/.test(methodName),
  // lodashFunc 是 根据 isTaker 组合 takeRight take methodName
  lodashFunc = lodash[isTaker ? ('take' + (methodName == 'last' ? 'Right' : '')) : methodName],
  // 根据isTaker 和 是 find 判断结果是否 包装
  retUnwrapped = isTaker || /^find/.test(methodName);

 // 如果不存在这个函数,就不往下执行
 if (!lodashFunc) {
  return;
 }
 // 把 lodash.prototype 方法赋值到lodash.prototype
 lodash.prototype[methodName] = function() {
  // 取实例中的__wrapped__ 值 例子中则是 [1,2,3,4,5]
  var value = this.__wrapped__,
   // 如果是head和last 方法 isTaker 返回 [1], 否则是arguments对象
   args = isTaker ? [1] : arguments,
   // 如果value 是LayeWrapper的实例
   isLazy = value instanceof LazyWrapper,
   // 迭代器 循环
   iteratee = args[0],
   // 使用useLazy isLazy value或者是数组
   useLazy = isLazy || isArray(value);

  var interceptor = function(value) {
   // 函数执行 value args 组合成数组参数
   var result = lodashFunc.apply(lodash, arrayPush([value], args));
   // 如果是 head 和 last (isTaker) 支持链式调用 返回结果的第一个参数 否则 返回result
   return (isTaker && chainAll) ? result[0] : result;
  };

  // useLazy true 并且 函数checkIteratee 且迭代器是函数,且迭代器参数个数不等于1
  if (useLazy && checkIteratee && typeof iteratee == 'function' && iteratee.length != 1) {
   // Avoid lazy use if the iteratee has a "length" value other than `1`.
   // useLazy 赋值为 false
   // isLazy 赋值为 false
   isLazy = useLazy = false;
  }
  // 取实例上的 __chain__
  var chainAll = this.__chain__,
   // 存储的待执行的函数 __actions__ 二次取反是布尔值 也就是等于0或者大于0两种结果
   isHybrid = !!this.__actions__.length,
   // 是否不包装 用结果是否不包装 且 不支持链式调用
   isUnwrapped = retUnwrapped && !chainAll,
   // 是否仅Lazy 用isLazy 和 存储的函数
   onlyLazy = isLazy && !isHybrid;

  // 结果不包装 且 useLazy 为 true
  if (!retUnwrapped && useLazy) {
   // 实例 new LazyWrapper 这里的this 是 new LodashWrapper()
   value = onlyLazy ? value : new LazyWrapper(this);
   // result 执行函数结果
   var result = func.apply(value, args);

   /*
   *
   // _.thru(value, interceptor)
   // 这个方法类似 _.tap, 除了它返回 interceptor 的返回结果。该方法的目的是"传递" 值到一个方法链序列以取代中间结果。
   _([1, 2, 3])
   .tap(function(array) {
    // 改变传入的数组
    array.pop();
   })
   .reverse()
   .value();
   // => [2, 1]
   */

   // thisArg 指向undefined 或者null 非严格模式下是指向window,严格模式是undefined 或者nll
   result.__actions__.push({ 'func': thru, 'args': [interceptor], 'thisArg': undefined });
   // 返回实例 lodashWrapper
   return new LodashWrapper(result, chainAll);
  }
  // 不包装 且 onlyLazy 为 true
  if (isUnwrapped && onlyLazy) {
   // 执行函数
   return func.apply(this, args);
  }
  // 上面都没有执行,执行到这里了
  // 执行 thru 函数,回调函数 是 interceptor
  result = this.thru(interceptor);
  return isUnwrapped ? (isTaker ? result.value()[0] : result.value()) : result;
 };
});

Подводя итог, я написал так много заметок, проще говоря: на самом деле это используетсяLazyWrapper.prototypeпереписать оригиналlodash.prototypeФункция, которая определяет, должна ли функция использовать ленивое вычисление, а затем вызывает ее при необходимости.

Читатели могут выполнять отладку с помощью точек останова, эффективно использовать точки останова для входа в функции и просматривать комментарии, которые могут быть более понятными.

Нажмите, чтобы просмотреть скриншоты отладки точки останова.
例子的chain和map执行后的debugger截图
Скриншот отладчика после выполнения цепочки и карты примера
例子的chain和map执行后的结果截图
Скриншот результата после выполнения цепочки и карты примера

Цепной вызов, наконец, возвращает объект экземпляра.Фактическая функция обработки данных не вызывается, но сохраняется и сохраняется, и, наконец, вызываетсяvalueспособ выполнения этих функций.

lodash.prototype.value — это wrapperValue

function baseWrapperValue(value, actions) {
 var result = value;
 // 如果是lazyWrapper的实例,则调用LazyWrapper.prototype.value 方法,也就是 lazyValue 方法
 if (result instanceof LazyWrapper) {
  result = result.value();
 }
 // 类似 [].reduce(),把上一个函数返回结果作为参数传递给下一个函数
 return arrayReduce(actions, function(result, action) {
  return action.func.apply(action.thisArg, arrayPush([result], action.args));
 }, result);
}
function wrapperValue() {
 return baseWrapperValue(this.__wrapped__, this.__actions__);
}
lodash.prototype.toJSON = lodash.prototype.valueOf = lodash.prototype.value = wrapperValue;

В случае ленивой оценки вызовLazyWrapper.prototype.valueкоторыйlazyValue.

LazyWrapper.prototype.value — это ленивая оценка lazyValue.

Нажмите здесь, чтобы развернуть исходный код lazyValue и комментарии
function LazyWrapper(value) {
 // 参数 value
 this.__wrapped__ = value;
 // 执行的函数
 this.__actions__ = [];
 this.__dir__ = 1;
 // 过滤
 this.__filtered__ = false;
 // 存储迭代器函数
 this.__iteratees__ = [];
 // 默认最大取值个数
 this.__takeCount__ = MAX_ARRAY_LENGTH;
 // 具体取值多少个,存储函数和类型
 this.__views__ = [];
}
/**
* Extracts the unwrapped value from its lazy wrapper.
*
* @private
* @name value
* @memberOf LazyWrapper
* @returns {*} Returns the unwrapped value.
*/
function lazyValue() {
 // this.__wrapped__ 是 new LodashWrapper 实例 所以执行.value 获取原始值
 var array = this.__wrapped__.value(),
  //
  dir = this.__dir__,
  // 是否是函数
  isArr = isArray(array),
  // 是否从右边开始
  isRight = dir < 0,
  // 数组的长度。如果不是数组,则是0
  arrLength = isArr ? array.length : 0,
  // 获取 take(3) 上述例子中 则是 start: 0,end: 3
  view = getView(0, arrLength, this.__views__),
  start = view.start,
  end = view.end,
  // 长度 3
  length = end - start,
  // 如果是是从右开始
  index = isRight ? end : (start - 1),
  // 存储的迭代器数组
  iteratees = this.__iteratees__,
  // 迭代器数组长度
  iterLength = iteratees.length,
  // 结果resIndex
  resIndex = 0,
  // 最后获取几个值,也就是 3
  takeCount = nativeMin(length, this.__takeCount__);

 // 如果不是数组,或者 不是从右开始 并且 参数数组长度等于take的长度 takeCount等于长度
 // 则直接调用 baseWrapperValue 不需要
 if (!isArr || (!isRight && arrLength == length && takeCount == length)) {
  return baseWrapperValue(array, this.__actions__);
 }
 var result = [];

 // 标签语句 label
 // MDN label 链接
 // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/label
 // 标记语句可以和 break 或 continue 语句一起使用。标记就是在一条语句前面加个可以引用的标识符(identifier)。
 outer:
 while (length-- && resIndex < takeCount) {
  index += dir;

  var iterIndex = -1,
   // 数组第一项
   value = array[index];

  while (++iterIndex < iterLength) {
   // 迭代器数组 {iteratee: function{}, typy: 2}
   var data = iteratees[iterIndex],
    iteratee = data.iteratee,
    type = data.type,
    // 结果 迭代器执行结果
    computed = iteratee(value);

   if (type == LAZY_MAP_FLAG) {
    // 如果 type 是 map 类型,结果 computed 赋值给value
    value = computed;
   } else if (!computed) {
    if (type == LAZY_FILTER_FLAG) {
     // 退出当前这次循环,进行下一次循环
     continue outer;
    } else {
     // 退出整个循环
     break outer;
    }
   }
  }
  // 最终数组
  result[resIndex++] = value;
 }
 // 返回数组 例子中则是 [2, 3, 4]
 return result;
}
// Ensure `LazyWrapper` is an instance of `baseLodash`.
LazyWrapper.prototype = baseCreate(baseLodash.prototype);
LazyWrapper.prototype.constructor = LazyWrapper;

LazyWrapper.prototype.value = lazyValue;

Автор нарисовалlodashа такжеLazyWrapperпредставлена ​​диаграммой отношений.

和的关系图
lodashа такжеLazyWrapperдиаграмма отношений

резюме:lazyValueПроще говоря, реализованная функция состоит в том, чтобы несколько раз выполнить ранее записанную функцию и несколько раз выполнить функцию, хранящуюся в записи. То есть в следующем примереmapфункция будет выполняться только3Второсортный. Если ленивые вычисления не используются, тоmapфункция будет выполняться5Второсортный.

var result = _.chain([1, 2, 3, 4, 5])
.map(el => el + 1)
.take(3)
.value();

Суммировать

К этому моменту текст в основном подходит к концу и, наконец, подводится итог.

Статья в основном учитrunInContext()экспорт_ lodashиспользование функцииbaseCreateметод прототипного наследованияLodashWrapperа такжеLazyWrapper,mixinметод монтирования наlodash.prototype, объяснено позже с комбинацией примеровlodash.prototype.value(wrapperValue)а такжеLazy.prototype.value(lazyValue)Исходный код реализации ленивых вычислений.

Поделитесь методом, который знает только имя функции, чтобы найти местоположение объявления функции местоположения исходного кода.VSCodeНавык:Ctrl + p. войти@functionNameфункция позиционированияfunctionNameКонкретное место в исходном файле. Если вы знаете место вызова, просто нажмитеalt+鼠标左键Вы можете перейти к месту объявления функции.

Если читатель обнаружит что-то неправильное или что-то, что можно улучшить, или где это неясно написано, пожалуйста, прокомментируйте и укажите. Кроме того, я думаю, что написание хорошее, и это немного полезно для вас.Вы можете ставить лайки, комментировать, пересылать и делиться, что также является своего рода поддержкой для автора. Большое спасибо.

Рекомендуемое чтение

репозиторий lodash | официальная документация lodash | lodash китайская документация
Создайте библиотеку интерфейсных инструментов, похожую на lodash.
Ленивая оценка - интерпретация исходного кода lodash
luobo tang: анализ реализации ленивой оценки lazy.js
репозиторий lazy.js на гитхабе
узнал из этой статьиlodashверсияv4.17.15 unpkg.comСвязь

Предыдущие статьи автора

Интервьюер спросил: наследование JS
Интервьюер спросил: этот пункт JS
Интервьюер спросил: Могу ли я смоделировать вызов и применить методы JS?
Интервьюер спросил: Могу ли я смоделировать метод привязки, реализующий JS?
Интервьюер спросил: Могу ли я смоделировать новый оператор, реализующий JS?
Внешний интерфейс использует сканер puppeteer для создания PDF-файла «React.js Book» и его слияния.

о

Автор: Чанг ИВакагаваНазвание смешано в реках и озерах. По дороге на фронт | Энтузиасты РРТ | Знаю очень мало, только хорошо учусь.
Личный блог-Вакагава,использоватьvuepressРефакторинг, опыт чтения может быть лучше
Колонка самородков, добро пожаловать, обратите внимание~
segmentfaultПередняя колонка обзора, добро пожаловать, обратите внимание~
Знайте переднюю колонку видения, добро пожаловать, обратите внимание~
github blog, соответствующий исходный код и ресурсы размещены здесь, попросите одинstar^_^~

Добро пожаловать, чтобы добавить общедоступную учетную запись WeChat для общения в WeChat

Может быть более интересным общедоступный аккаунт WeChat, нажмите и удерживайте, чтобы отсканировать код, чтобы следовать. Добро пожаловать, чтобы добавить автора WeChatruochuan12(Укажите источник, в основном никто не будет отклонен), пригласите вас в [Группу обмена видением переднего плана] для долгосрочного обмена и обучения ~

若川视野
Видение Вакагава

В этой статье используетсяmdniceнабор текста