Underscore.js — известная библиотека инструментов, я часто использую ее для работы с объектами, массивами и т. д. В этой статье мы подробно проанализируем структуру исходного кода Underscore, изучим основные моменты его исходного кода вместе с вами, а затем подражаем ему в напишите простую полку, чтобы углубить понимание. Прочитав его исходный код, я думаю, что его основные моменты заключаются в следующем:
- Нет необходимости в новом конструкторе
- Поддерживает как вызовы статических методов, так и вызовы методов экземпляра.
- Поддержка связанных вызовов
Примеры в этой статье были загружены на GitHub, и все мои сообщения в блоге и примеры находятся в том же репозитории.
Внешний слой представляет собой функцию самоисполнения
Внешний экстерьер представляет собой функцию самозаполнения, которая будет в самоиполненной функции._
монтируется на окно. Это процедура, используемая многими сторонними библиотеками. Если вы не знаете, с чего начать просмотр исходного кода, не знаете, где находится вход, или не понимаете его внешней структуры, см.Легко читать исходный код фреймворка из архитектуры: возьмите в качестве примеров jQuery, Zepto, Vue и lodash-es., в этой статье подробно объясняется, как начать просмотр исходного кода. В этой статье в основном объясняются основные моменты архитектуры исходного кода Underscore, и я не буду вдаваться в подробности о том, как начать работу.
Конструктор без новых
Когда мы используем сторонние библиотеки, нам часто нужно сначала получить их экземпляр.Некоторые библиотеки нужно явно вызывать с помощью new, например, родной Promise, а некоторые библиотеки могут получать экземпляры объектов без new, например jQuery. Возврат экземпляра без new определенно не поддерживается нативным JS.Библиотеки с такими функциями инкапсулируются сами по себе. Разные библиотеки также имеют разные идеи при их инкапсуляции, давайте поговорим о двух из них.
схема jQuery
Ранее я писал в другой статьеЛегко читать исходный код фреймворка из архитектуры: возьмите в качестве примеров jQuery, Zepto, Vue и lodash-es.В нем подробно объясняется, как jQuery реализует возврат экземпляра без нового. Кроме того, я также реализовал собственную библиотеку инструментов, имитируя схему jQuery:Примените то, что вы узнали: научите, как создать библиотеку инструментов, упаковать и опубликовать ее, а также решить проблему неточного десятичного вычисления JS, кстати.. Вот фрагмент кода из моей статьи о библиотеке инструментов для краткого обзора этого решения:
// 首先创建一个fc的函数,我们最终要返回的其实就是一个fc的实例
// 但是我们又不想让用户new,那么麻烦
// 所以我们要在构造函数里面给他new好这个实例,直接返回
function FractionCalculator(numStr, denominator) {
// 我们new的其实是fc.fn.init
return new FractionCalculator.fn.init(numStr, denominator);
}
// fc.fn其实就是fc的原型,算是个简写,所有实例都会拥有这上面的方法
FractionCalculator.fn = FractionCalculator.prototype = {};
// 这个其实才是真正的构造函数,这个构造函数也很简单,就是将传入的参数转化为分数
// 然后将转化的分数挂载到this上,这里的this其实就是返回的实例
FractionCalculator.fn.init = function(numStr, denominator) {
this.fraction = FractionCalculator.getFraction(numStr, denominator);
};
// 前面new的是init,其实返回的是init的实例
// 为了让返回的实例能够访问到fc的方法,将init的原型指向fc的原型
FractionCalculator.fn.init.prototype = FractionCalculator.fn;
// 调用的时候就不用new了,直接调用就行
FractionCalculator();
План подчеркивания
Решение jQuery состоит в том, чтобы создать другой объект в конструкторе, а затем указать на прототип этого объекта к прототипу jQuery, чтобы возвращенный экземпляр мог получить доступ к способу экземпляра jQuery. Цель достижима, но решение довольно длительное. Раствор подчеркивания намного проще:
function _(){
if(!(this instanceof _)) {
return new _();
}
}
// 调用的时候直接_()就可以拿到实例对象
const instance = _();
console.log(instance);
Вывод приведенного выше кода:
Вы можете видеть, что конструктор указывает на_()
, указывая, что это действительно экземпляр _, давайте проанализируем поток выполнения кода:
- передача
_()
, это внутри указывает на внешнюю область видимости, здесь мы окно, потому что окно не является экземпляром _, оно войдет внутрь, если.Об этом моменте, если вы все еще не понимаете, пожалуйста, прочитайте эту статью.- внутри если позвонит
new _()
, который берет экземпляр объекта и преобразует этот объектreturn
выйди.new _()
также позвонит_()
метод, но поскольку используется новый вызов, this внутри указывает на экземпляр из нового, поэтому, если не удается войти, выполнение завершается.
Underscore ловко использует указание this, чтобы определить, звоните ли вы по новому или обычному вызову, определяя указание this.Если это обычный вызов, он поможет вам с новым, а затем вернется.
Поддерживаются как статические, так и экземплярные методы.
Друзья, которые использовали Underscore, должны были заметить, что для одного и того же метода Underscore поддерживает как вызов статического метода, так и вызов метода экземпляра.Ниже приведен официальный пример:
_.map([1, 2, 3], function(n){ return n * 2; }); // map作为静态方法调用
_([1, 2, 3]).map(function(n){ return n * 2; }); // map作为实例方法调用
Когда мы вызываем метод как статический метод, данные для обработки являются первым параметром; когда мы вызываем его как метод экземпляра, данные для обработки передаются в качестве параметра конструктору. Поговорим о том, как это достигается.
На самом деле, самый простой способ — написать две функции: одну — статический метод, а другую — метод экземпляра. Но если мы это сделаем, то логика внутренней обработки двух функций на самом деле очень похожа, может быть, просто параметры немного отличаются. Это определенно не то, что должен делать элегантный программист. Метод, предложенный Underscore, состоит в том, чтобы сначала написать все методы как статические, а затем использовать унифицированную функцию для монтирования всех статических методов в прототипе, что делает его методом экземпляра. Попробуем сделать это поэтапно.
Сначала напишите статический метод
Давайте сначала напишем простой метод карты и смонтируем его на _ как статический метод:
_.map = function(array, callback) {
var result = [];
var length = array.length;
for(var i = 0; i< length; i++) {
var res = callback(array[i]);
result[i] = res;
}
return result;
}
После написания этого метода его можно использовать напрямую.Приведенный выше пример вызывается следующим образом:
Сопоставляется с методами экземпляра
Подчеркивание, которое используется вmixin
метод для сопоставления статических методов с прототипами,mixin
Метод принимает объект в качестве параметра, а затем копирует все методы этого объекта в прототип. Конкретный процесс выглядит следующим образом:
- Выньте атрибут функции в параметре и поместите его в массив
- Перебрать этот массив и установить для каждого элемента в нем прототип
- При настройке прототипа обратите внимание на параметры экземплярного метода и статического метода
Давайте посмотрим на код:
_.mixin = function(obj) {
// 遍历obj里面的函数属性
_.each(_.functions(obj), function(item){
// 取出每个函数
var func = obj[item];
// 在原型上设置一个同名函数
_.prototype[item] = function() {
// 注意这里,实例方法待处理数据是构造函数接收的参数,改造构造函数的代码在后面
// 这里将数据取出来作为静态方法的第一个参数
var value = this._wrapped;
var args = [value];
// 将数据和其他参数放到一个数组里面,作为静态方法的参数
Array.prototype.push.apply(args, arguments);
// 用处理好的参数来调用静态方法
var res = func.apply(this, args);
// 将结果返回
return res;
}
});
}
// 上面的mixin写好后不要忘了调用一下,将_自己作为参数传进去
_.mixin(_);
// 构造函数需要接收处理的数据
// 并将它挂载到this上,这里的this是实例对象
function _(value){
if(!(this instanceof _)) {
return new _(value);
}
this._wrapped = value;
}
над_.mixin(_);
После вызова_
Статические методы во всех сопоставлениях с прототипом, так что_()
Возвращенный экземпляр также имеет все статические методы, что позволяет_
Поддерживаются два метода вызова. Некоторые друзья, возможно, заметили, что наш код выше также имеетeach
а такжеfunctions
Два вспомогательных метода, мы также реализуем следующие два метода:
// functions就是取出对象上所有函数的名字,塞到一个数组里面返回
_.functions = function(obj){
var result = [];
for(var key in obj) {
if(typeof obj[key] === 'function'){
result.push(key);
}
}
return result;
}
// each就是对一个数组进行遍历,每个都执行下callback
_.each = function(array, callback){
var length = array.length;
for(var i = 0; i < length; i++) {
callback(array[i]);
}
}
миксин кстати поддерживает плагины
Подчеркиватьmixin
Это не только позволяет ему поддерживать статические методы и методы экземпляра, но и потому, что он также_
Статический метод , мы также можем использовать его. Официальная поддержка пользовательских плагинов использует этот метод. Ниже приведен официальный пример:
_.mixin({
capitalize: function(string) {
return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase();
}
});
_("fabio").capitalize(); // Fabio
На самом деле тот, который мы написали ранееmixin
Метод уже поддерживает использование пользовательских методов в качестве методов экземпляра, но он все еще немного коротковат, и есть еще статический метод, поэтому добавляем строчку кода и присваиваем полученные параметры_
Просто делать:
_.mixin = function(obj) {
_.each(_.functions(obj), function(item){
var func = obj[item];
// 注意这里,我们同时将这个方法赋值给_作为静态方法,这下就完全支持自定义插件了
_[item] = func;
_.prototype[item] = function() {
var value = this.value;
var args = [value];
Array.prototype.push.apply(args, arguments);
var res = func.apply(this, args);
return res;
}
});
}
Поддержка связанных вызовов
Также распространены связанные вызовы, такие как точки jQuery, о которых я писал в другой статье.Примените то, что вы узнали: научите, как создать библиотеку инструментов, упаковать и опубликовать ее, а также решить проблему неточного десятичного вычисления JS, кстати.Я подробно объяснил, как реализован цепной вызов этого метода экземпляра, ключ в том, что каждый метод экземпляра возвращает текущий экземпляр после завершения вычисления.Для методов экземпляра текущий экземпляр - это. Этот метод также применим к Underscore, но цепочка вызовов Underscore должна поддерживать больше сценариев из-за его собственных требований и структуры API:
- Метод экземпляра Underscore также поддерживает прямой вызов для возврата результата вместо простого возврата экземпляра.
- Статические методы Underscore также поддерживают связанные вызовы.
Методы экземпляра поддерживают цепочку
Давайте пойдем шаг за шагом, сначала для решения проблемы, что методы экземпляра поддерживают вызовы цепочки.Мы уже реализовали сопоставление статических методов с методами экземпляра.Возвращаемое значение метода экземпляра, реализованного ранее, является возвращаемым значением статического метода. Чтобы реализовать связанные вызовы, нам также нужен метод экземпляра, чтобы возвращать текущий экземпляр (то есть this) после вычисления, поэтому нам нужна основа, чтобы решить, должен ли быть возвращен результат вычисления или текущий экземпляр. Эта основа задается пользователем в Underscore, то есть явным вызовомchain
метод. Согласно нашему анализу,chain
Это должно быть очень просто, чтобы дать основу для суждения о том, что должен возвращать метод экземпляра, то есть установить флаг для текущего экземпляра:
_.chain = function() {
this._chain = true;
return this;
}
chain
Вот так просто, две строчки кода, а дальше наш метод экземпляра определяет, возвращается ли результат вычисления или текущий экземпляр по _chain:
_.mixin = function(obj) {
_.each(_.functions(obj), function(item){
var func = obj[item];
_[item] = func;
_.prototype[item] = function() {
var value = this._wrapped;
var args = [value];
Array.prototype.push.apply(args, arguments);
var res = func.apply(this, args);
// 检查链式调用标记,如果是链式调用
// 将数据挂载到实例上,返回实例
var isChain = this._chain;
if(isChain) {
// 注意如果方法是chain本身,不要更新_wrapped,不然_wrapped会被改为chain的返回值,也就是一个实例
// 这里有点丑,后面优化
if(item !== 'chain') {
this._wrapped = res;
}
return this;
}
return res;
}
});
}
Давайте напишем сноваunique
Метод для проверки вызова нижнего цепочка:
_.unique = function(array){
var result = [];
var length = array.length;
for(var i = 0; i < length; i++) {
if(result.indexOf(array[i]) === -1){
result.push(array[i]);
}
}
return result;
}
Попробуйте привязать звонки:
Мы обнаружили, что результат правильный, но на выходе экземпляр, а не то, что мы хотим, поэтому нам также нужен метод для вывода реального результата расчета.Этот метод может быть повешен только на прототип и не может быть написан как статический метод , иначе он будет Подход к нашему миксину вернет экземпляр:
_.prototype.value = function() {
return this._wrapped;
}
Давай еще раз попробуем:
Статические методы поддерживают цепочку
Статические методы также поддерживают цепные вызовы, мы должны сделать их возвращаемые значения доступными для методов экземпляра. Как правило, возвращаемое значение статического метода не может возвращать экземпляр, но теперь у нас естьchain
метод, мы напрямую позволяем этому методу построить_
Достаточно вернуть экземпляр, вышеприведенный метод экземпляра поддерживает цепочку вызовов с использованием готового экземпляра и возвращает это, но еслиchain
Возврат нового экземпляра также совместим с вышеизложенным, поэтомуchain
Измените его на:
_.chain = function(obj) {
var instance = _(obj);
instance._chain = true;
return instance;
}
Таким образом, наш статический методchain
Его также можно вызывать в цепочке, а данные передаются как параметр тому же, что и другим статическим методам.chain
:
оптимизировать код
На этом наша функция в основном реализована, ноmixin
Функцию еще нужно оптимизировать:
-
var res = func.apply(this, args);
Здесь это указывает на текущий экземпляр, но когда метод вызывается как статический метод, например_.map()
, this внутри метода указывает на_
, поэтому его следует изменить на_
. Это было передано здесь раньше, потому чтоchain
Операция внутри вот такая, и теперь она изменена на новый экземпляр, так что нет необходимости передавать это, поэтому она изменена на правильную_
. -
правильно
item
Особое суждение, причина, по которой это делается, потому чтоchain
Операция внутри такова, поэтому она фактически была установлена в приложенииthis._chain
Это правда, поэтому он перейдет в если. Теперь создается новый экземпляр. Когда дело доходит до применения, настройка фактическиres._chain
, поэтому он не войдет в if, когда вы захотите вызвать метод следующего экземпляра,this._chain
будет правдой, так что это можно удалить напрямую._.mixin = function(obj) { _.each(_.functions(obj), function(item){ var func = obj[item]; _[item] = func; _.prototype[item] = function() { var value = this._wrapped; var args = [value]; Array.prototype.push.apply(args, arguments); var res = func.apply(_, args); var isChain = this._chain; if(isChain) { // if(item !== 'chain') { this._wrapped = res; // } return this; } return res; } }); }
-
Подчеркивание также будет
isChain
В качестве метода было предложено суждение о вышесказанном, здесь я этого не делал, более интуитивно понятно смотреть на это вместе.
Суммировать
Эта статья в основном объясняет архитектуру исходного кода Underscore и реализует простую полку сама по себе Конкретная реализация некоторых имен переменных и методов может отличаться, но принцип тот же. Соорудив эту простую полку, мы на самом деле узнали:
- Построение экземпляра объекта без нового
-
mixin
Как расширить статический метод до прототипа - путем явного вызова
chain
для поддержки цепочки статических и экземплярных методов
В конце статьи спасибо, что потратили свое драгоценное время на чтение этой статьи. Если эта статья немного поможет вам или вдохновит, пожалуйста, не скупитесь на лайки и звезды GitHub. Ваша поддержка является движущей силой для автор продолжать творить.
Добро пожаловать, чтобы обратить внимание на мой общедоступный номербольшой фронт атакиПолучите высококачественные оригиналы впервые~
Цикл статей "Передовые передовые знания":nuggets.capable/post/684490…
Адрес GitHub с исходным кодом из серии статей «Advanced Front-end Knowledge»:GitHub.com/Денис — см....