Первая статья из серии о подчеркивании, объясняющая организацию кода подчеркивания.
предисловие
существуетСерия тем по JavaScriptВ , мы написали множество функциональных функций, таких как защита от сотрясения, регулирование, дедупликация, оценка типов, плоский массив, глубокое и поверхностное копирование, поиск элементов массива, общий обход, каррирование, композиция функций, память функций, нарушение порядка, и т. д., как мы можем организовать эти функции, чтобы сформировать нашу собственную библиотеку функций инструмента? На данный момент мы должны узнать, как это делает подчеркивание.
внедрить это самостоятельно
Если бы мы сами организовали эти функции, как бы мы это сделали? Я думал, что сделаю это:
(function(){
var root = this;
var _ = {};
root._ = _;
// 在这里添加自己的方法
_.reverse = function(string){
return string.split('').reverse().join('');
}
})()
_.reverse('hello');
=> 'olleh'
Добавляем все методы в_
объект, а затем смонтировать объект на глобальный объект.
почему не напрямуюwindow._ = _
Это потому, что мы пишем библиотеку функций инструмента, которая требуется не только для работы на стороне браузера, но и в таких средах, как Node.
root
Однако подчеркивание написать не так просто, начнем сvar root = this
Начни говорить.
Причина, по которой написано это предложение, заключается в том, что мы хотим получить через него глобальный объект, а затем поместить_
объект, смонтируйте его.
Однако в строгом режиме это возвращает undefined вместо указания на Window.К счастью, подчеркивание не использует строгий режим, но даже в этом случае его нельзя избежать, потому что в модулях ES6 скрипты автоматически используют строгий режим, независимо от того, объявлены они или нет.use strict
.
Если это вернет undefined, код сообщит об ошибке, поэтому наша идея состоит в том, чтобы обнаружить среду и смонтировать ее на правильном объекте. Давайте изменим код:
var root = (typeof window == 'object' && window.window == window && window) ||
(typeof global == 'object' && global.global == global && global);
В этом коде мы оцениваем браузер и среду Node, но только ли эти две среды? Итак, давайте взглянем на Web Workers.
Web Worker
Web Worker относится к содержимому HTML5, цитируя слова из «Полного руководства по JavaScript»:
В стандарте Web Worker определено решение проблемы, заключающейся в том, что клиентский JavaScript не может быть многопоточным. «Рабочий» здесь определяется как параллельный процесс, выполняющий код. Однако Web Worker находится в автономной среде выполнения, не имеет доступа к объектам Window и объектам Document и взаимодействует с основным потоком только посредством асинхронной передачи сообщений.
Чтобы продемонстрировать эффект Web Worker, я написал демо,Посмотреть код.
В Web Worker объект Window недоступен, поэтомуtypeof window
а такжеtypeof global
Результатыundefined
, поэтому окончательное значение root равно false. Добавление свойств и методов к значению базового типа, например к объекту, естественным образом сообщит об ошибке.
так что нам делать?
Хотя объект Window недоступен в Web Worker, мы можем получить доступ к глобальному объекту в среде Worker через self. Мы просто ищем глобальные переменные для монтирования, чтобы мы могли смонтировать их в себе.
А в браузере, в дополнение к свойству окна, мы также можем напрямую обращаться к объекту Winow через свойство self.
console.log(window.window === window); // true
console.log(window.self === window); // true
Учитывая, что использование self может дополнительно поддерживать Web Worker, мы напрямую меняем код на self:
var root = (typeof self == 'object' && self.self == self && self) ||
(typeof global == 'object' && global.global == global && global);
node vm
На этом всё ещё нет конца.Что делает вас неожиданным, так это то, что в модуле vm узла, то есть модуле песочницы, в методе runInContext нет ни окна, ни глобальной переменной.Посмотреть код.
Но через это мы можем получить доступ к глобальному объекту, поэтому кто-то инициировал PR, и код был изменен на:
var root = (typeof self == 'object' && self.self == self && self) ||
(typeof global == 'object' && global.global == global && global) ||
this;
Апплет WeChat
На этом всё ещё не закончилось, настала очередь апплета WeChat.
Потому что в апплете WeChat и оконный, и глобальный undefined, и принудительно применяется строгий режим, если это undefined, то при монтировании будет ошибка, поэтому кто-то прислал еще один PR, и код стал таким:
var root = (typeof self == 'object' && self.self == self && self) ||
(typeof global == 'object' && global.global == global && global) ||
this ||
{};
Вот так сейчас выглядит v1.8.3.
Хотя автор может напрямую объяснить окончательный код, автор надеется показать вам, как этот, казалось бы, обычный код шаг за шагом превратился в этот, а также надеется сказать вам, что надежность кода не достигается за одну ночь, а представляет собой набор Опыт многих людей учёл множество неожиданных мест, что также является преимуществом проектов с открытым исходным кодом.
функциональный объект
Теперь давайте поговорим о втором предложенииvar _ = {};
.
Если только установитьДля пустого объекта, когда мы вызываем метод, мы можем использовать только `.reverse('hello')`, на самом деле, подчеркивание также поддерживает аналогичные объектно-ориентированные вызовы, а именно:
_('hello').reverse(); // 'olleh'
Давайте возьмем еще один пример для сравнения следующих двух методов вызова:
// 函数式风格
_.each([1, 2, 3], function(item){
console.log(item)
});
// 面向对象风格
_([1, 2, 3]).each(function(item){
console.log(item)
});
Но как?
Поскольку_([1, 2, 3])
может быть выполнен в виде_
Не буквальный объект, а функция!
К счастью, в JavaScript функции также являются объектами.Возьмем пример:
var _ = function() {};
_.value = 1;
_.log = function() { return this.value + 1 };
console.log(_.value); // 1
console.log(_.log()); // 2
Мы можем определить пользовательские функции в_
функция!
Текущее написано:
var root = (typeof self == 'object' && self.self == self && self) ||
(typeof global == 'object' && global.global == global && global) ||
this ||
{};
var _ = function() {}
root._ = _;
Как это сделать_([1, 2, 3]).each(...)
Шерстяная ткань? которыйФункция возвращает объект, этот объект, как вызвать зависаниеА как насчет методов в функциях?
Давайте посмотрим, как реализовано подчеркивание:
var _ = function(obj) {
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
};
_([1, 2, 3]);
Давайте проанализируем_([1, 2, 3])
Процесс выполнения:
- воплощать в жизнь
this instanceof _
, это указывает на окно,window instanceof _
ложно,!
Оператор инвертирован, поэтому выполнитеnew _(obj)
. -
new _(obj)
, это указывает на экземпляр объекта,this instanceof _
верно, после отрицания код продолжает выполняться - воплощать в жизнь
this._wrapped = obj
, выполнение функции заканчивается - Суммировать,
_([1, 2, 3])
возвращает объект как{_wrapped: [1, 2, 3]}
, прототип объекта указывает на _.prototype
Схематическая диаграмма выглядит следующим образом:
Затем возникает проблема, мы монтируем метод в_
В объекте функции он не связан с прототипом функции, поэтому возвращаемый экземпляр не может быть вызван._
методы объектов-функций!
Напишем пример:
(function(){
var root = (typeof self == 'object' && self.self == self && self) ||
(typeof global == 'object' && global.global == global && global) ||
this ||
{};
var _ = function(obj) {
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
}
root._ = _;
_.log = function(){
console.log(1)
}
})()
_().log(); // _(...).log is not a function
имеет эту проблему, поэтому нам также нужен способ_
Скопируйте вышеуказанный метод в_.prototype
выше этот метод_.mixin
.
_.functions
чтобы_
Скопируйте метод с прототипа в прототип, сначала мы должны получить_
Метод выше, поэтому давайте напишем_.functions
метод.
_.functions = function(obj) {
var names = [];
for (var key in obj) {
if (_.isFunction(obj[key])) names.push(key);
}
return names.sort();
};
Функция isFunction может относиться к«Типовая оценка тем JavaScript (часть 2)»
_.mixin
Теперь мы можем написать методы миксина.
var ArrayProto = Array.prototype;
var push = ArrayProto.push;
_.mixin = function(obj) {
_.each(_.functions(obj), function(name) {
var func = _[name] = obj[name];
_.prototype[name] = function() {
var args = [this._wrapped];
push.apply(args, arguments);
return func.apply(_, args);
};
});
return _;
};
_.mixin(_);
каждый метод может относиться к"JavaScript тема реализация общего метода обхода jQuery для каждого"
Стоит отметить, что поскольку_[name] = obj[name]
По этой причине мы можем расширить пользовательские методы для подчеркивания:
_.mixin({
addOne: function(num) {
return num + 1;
}
});
_(2).addOne(); // 3
На данный момент мы добились поддержки как объектно-ориентированного, так и функционального стилей.
экспорт
Наконец добрался до последнего шагаroot._ = _
, смотрим прямо на исходный код:
if (typeof exports != 'undefined' && !exports.nodeType) {
if (typeof module != 'undefined' && !module.nodeType && module.exports) {
exports = module.exports = _;
}
exports._ = _;
} else {
root._ = _;
}
Для поддержки модульности нам необходимо_
Экспортируется как модуль в соответствующую среду, но API модулей nodejs изменился, как и в более ранних версиях:
// add.js
exports.addOne = function(num) {
return num + 1
}
// index.js
var add = require('./add');
add.addOne(2);
В новой версии:
// add.js
module.exports = function(1){
return num + 1
}
// index.js
var addOne = require('./add.js')
addOne(2)
Итак, мы выбираем разные методы экспорта в зависимости от того, существуют ли экспорты и модули, так почему же в новой версии нам по-прежнему нужно использоватьexports = module.exports = _
Шерстяная ткань?
Это связано с тем, что в nodejs exports является ссылкой на module.exports.Когда вы используете module.exports = function(){}, вы фактически перезаписываете module.exports, но экспорты не изменились, чтобы избежать модификации в дальнейшем Экспорты не могут выводиться правильно, поэтому он написан таким образом, чтобы сохранить их унифицированными.
Напишите демо:
Первая демонстрация:
// exports 是 module.exports 的一个引用
module.exports.num = '1'
console.log(exports.num) // 1
exports.num = '2'
console.log(module.exports.num) // 2
Вторая демонстрация:
// addOne.js
module.exports = function(num){
return num + 1
}
exports.num = '3'
// result.js 中引入 addOne.js
var addOne = require('./addOne.js');
console.log(addOne(1)) // 2
console.log(addOne.num) // undefined
Третье демо:
// addOne.js
exports = module.exports = function(num){
return num + 1
}
exports.num = '3'
// result.js 中引入 addOne.js
var addOne = require('./addOne.js');
console.log(addOne(1)) // 2
console.log(addOne.num) // 3
Наконец, зачем вам нужно выносить суждение о export.nodeType? Это потому, что если вы добавите элемент с идентификатором экспорта на свою HTML-страницу, например
<div id="exports"></div>
Будет сгенерирована глобальная переменная window.exports, которую вы сможете распечатать прямо из командной строки браузера.
В этот момент в браузереtypeof exports != 'undefined'
Решение вступит в силу, и тогдаexports._ = _
, однако в браузере нам нужно_
Смонтируйте его в глобальную переменную, поэтому здесь нам также нужно решить, является ли он узлом DOM.
исходный код
Окончательный код выглядит следующим образом, с этой базовой структурой вы можете свободно добавлять функции, которые вам нужно использовать:
(function() {
var root = (typeof self == 'object' && self.self == self && self) ||
(typeof global == 'object' && global.global == global && global) ||
this || {};
var ArrayProto = Array.prototype;
var push = ArrayProto.push;
var _ = function(obj) {
if (obj instanceof _) return obj;
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
};
if (typeof exports != 'undefined' && !exports.nodeType) {
if (typeof module != 'undefined' && !module.nodeType && module.exports) {
exports = module.exports = _;
}
exports._ = _;
} else {
root._ = _;
}
_.VERSION = '0.1';
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
var isArrayLike = function(collection) {
var length = collection.length;
return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
};
_.each = function(obj, callback) {
var length, i = 0;
if (isArrayLike(obj)) {
length = obj.length;
for (; i < length; i++) {
if (callback.call(obj[i], obj[i], i) === false) {
break;
}
}
} else {
for (i in obj) {
if (callback.call(obj[i], obj[i], i) === false) {
break;
}
}
}
return obj;
}
_.isFunction = function(obj) {
return typeof obj == 'function' || false;
};
_.functions = function(obj) {
var names = [];
for (var key in obj) {
if (_.isFunction(obj[key])) names.push(key);
}
return names.sort();
};
/**
* 在 _.mixin(_) 前添加自己定义的方法
*/
_.reverse = function(string){
return string.split('').reverse().join('');
}
_.mixin = function(obj) {
_.each(_.functions(obj), function(name) {
var func = _[name] = obj[name];
_.prototype[name] = function() {
var args = [this._wrapped];
push.apply(args, arguments);
return func.apply(_, args);
};
});
return _;
};
_.mixin(_);
})()
Ссылки по теме
серия подчеркивания
Адрес каталога серий подчеркивания:GitHub.com/ в настоящее время имеет бриз….
Ожидается, что в серии подчеркиваний будет написано около восьми статей, посвященных структуре кода, цепным вызовам, внутренним функциям, механизмам шаблонов и т. д. в подчеркивании, с целью помочь вам прочитать исходный код и написать собственное подчеркивание.
Если есть какие-либо ошибки или неточности, пожалуйста, поправьте меня, большое спасибо. Если вам нравится или у вас есть вдохновение, добро пожаловать в звезду, что также является поощрением для автора.