Пишите ремонтую JS.

внешний интерфейс JavaScript браузер CSS
Пишите ремонтую JS.

0. Пишите впереди

Когда вы начинаете работать, вы не пишете код для себя, вы пишете код для тех, кто придет позже. — Николас С. Закас

Эта статья в основном представляет собой заметки для чтения «Написание поддерживаемого JS».Я расскажу о том, как написать простой в сопровождении JS, основываясь на собственном опыте работы. Когда автор писал эту книгу (примерно 2012-2013 гг.), ES6 еще не вышла, а учитывая, что в нынешнюю эпоху MV* почти все пишут ES6, то в этой статье будет специальное объяснение ES6 (оригинальное содержание книги для ES5). Автор оригинальной книги написал эту книгу, основываясь на собственном опыте работы (он начал работать в Yahoo 5 лет в 2006 г.), и автор подчеркивает в книге вещи, которые сейчас кажутся нам редкими и обычными (например: почему нам нужно его отключить?withа такжеevalЗачем всегда использовать===а также!==Для сравнения) я кратко коснусь этого содержания, предполагая, что вы уже знакомы с этими базовыми принципами здравого смысла.

Мы знаем, что у языка JS есть врожденные недостатки дизайна (он стал лучше только после ES6), и если вы намеренно не научитесь писать хороший и поддерживаемый код, вы можете легко написать плохой код (хотя он работает).

Есть четыре вещи, которые вам нужно знать о сопровождении кода:

  • 80% стоимости жизненного цикла программного обеспечения тратится на сопровождение.
  • Почти все специалисты по сопровождению программного обеспечения не являются его первоначальными авторами.
  • Соглашения о кодировании улучшают удобочитаемость программного обеспечения, позволяя инженерам быстро и полностью понимать новый код.
  • Если вы выпускаете исходный код как продукт, вам нужно убедиться, что он полностью упакован и чист, как и любой другой созданный вами продукт.

Да, код, который вы пишете, вероятно, не поддерживается вами. Потому что вы могли сменить компанию, вы можете работать над новым проектом или даже не помнить, что этот код был написан полгода назад. Поэтому не пишите код с позицией «Я здесь, чтобы двигать кирпичи, просто напишите, и если вы не сможете это изменить, вы ускользнете». Я считаю, что читатель сохранил код, написанный другие, и жаловались, что это было непонятно без каких-либо комментариев.Код, я не могу дождаться, чтобы вытащить человека, который написал код, и побить его. Так что, пожалуйста, не будь человеком, которого ты ненавидишь. Написание поддерживаемого кода — это и вопрос профессионализма, и отражение вашего профессионализма.

О том, как писать поддерживаемый JS, я начну сстиль программирования,Практика программированияа такжеОбъясняются три аспекта.

1. Стиль программирования

Программы пишутся для чтения людьми и лишь изредка выполняются компьютером. — Дональд Кнут

Мы часто сталкиваемся с этими двумя терминами: «стиль программирования» (рекомендации по стилю) и «соглашение о кодировании» (соглашение о коде). Стиль программирования — это тип соглашения о кодировании, используемый для указания планирования кода в одном файле. Руководства по написанию кода также включают передовой опыт программирования, планирование файлов и каталогов и комментарии. Эта статья посвящена соглашениям о написании кода для JS.

Зачем обсуждать стиль программирования? У каждого свои препрограммы, но больше времени мы работаем вместе как член команды, единый стиль очень важен, т.к. он будет способствовать высокому уровню членов команды (весь код выглядит очень похоже). Нет никаких сомнений в том, что крупные мировые компании выпустили документы по стилю программирования, такие как:Airbnb JavaScript Style Guide, Google JavaScript Style GuideПодождите, если вы внимательно прочитаете, то обнаружите, что многие из их спецификаций совпадают, но некоторые детали немного отличаются.

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

1.1 Форматирование

Об уровнях отступов: Я не хочу начинать дискуссию «Табуляция или пробел» против «2, или 4, или 6, или 8 пробелов», на эту тему можно спорить часами, отступы касаются даже программных значений. Вам просто нужно помнить следующие три момента:

  1. Код должен быть с отступом, держите его выровненным.
  2. Не смешивайте Tab и Space в одном проекте.
  3. Придерживайтесь стиля команды.

О завершающей точке с запятой: В зависимости от механизма автоматической вставки точки с запятой (ASI) анализатора код JS, опускающий точки с запятой, также может работать правильно. ASI автоматически ищет места в коде, где должна стоять точка с запятой, но ее нет, и вставляет ее. В большинстве случаев ASI будет правильно вставлять точки с запятой без ошибок, но правила вставки точек с запятой ASI очень сложны и трудны для запоминания, поэтому я рекомендую не опускать точки с запятой.Большинство руководств по стилю (кромеJavaScript Standard Style) рекомендуется не опускать точку с запятой.

о длине линии: Большинство языков и Руководство по стилю кодирования JS указывают строку из 80 символов, это значение исходит из одной строки отдельной строки текстового редактора, то есть одна строка в редакторе может отображать только 80 символов, более 80 Ряд символов либо свернут, либо скрыт, это все, что нам не нужно.Я также стараюсь ограничивать длину строки до 80 символов.

О разрывах строк: когда длина строки достигает максимального предела символов для одной строки, вам необходимо вручную разделить строку на две строки. Обычно мы переносим строку после оператора, а следующая строка добавляет два уровня отступа (лично я считаю, что один отступ — это хорошо, но определенно без отступа). Например:

callFunc(document, element, window, 'test', 100,
  true);

В этом примере запятая является оператором и должна стоять в конце строки в предыдущей строке. Эта позиция новой строки важна, поскольку механизм ASI в некоторых случаях вставляет точку с запятой в конце строки. Всегда ставьте оператор в конце строки, и ASI не будет вставлять точку с запятой самостоятельно, что позволит избежать ошибок. Из этого правила есть одно исключение: при присвоении значения переменной позиция второй строки должна совпадать с позицией оператора присваивания. Например:

var result = something + anotherThing + yetAnotherThing + somethingElse +
             anotherSomethingElse;

В этом коде переменнаяanotherSomethingElseи начало строкиsomethingСохраняйте выравнивание по левому краю, чтобы обеспечить удобочитаемость кода и контекст переноса текста с первого взгляда.

О пустых строках: пустые строки часто упускают из виду в соглашениях по программированию. В общем, код должен выглядеть как серия читаемых абзацев, а не как большой непрерывный кусок текста, смятый вместе. Иногда семантика фрагмента кода не связана с другим фрагментом кода, и вы должны разделить их пустыми строками, чтобы убедиться, что семантически связанный код отображается вместе. Как правило, рекомендуется добавлять пустые строки в следующих случаях:

  • между методами.
  • между локальной переменной в методе и первым оператором.
  • перед многострочным или однострочным комментарием.
  • Вставляйте пустые строки между логическими фрагментами внутри методов, чтобы улучшить читаемость.

1.2 Именование

Имена делятся на четыре категории: переменные, константы, функции и конструкторы: переменные и функции используют маленький регистр верблюда (первая буква строчная), конструкторы используют большой регистр верблюда (первая буква заглавная), а константы используют весь верхний регистр. и разделяйте слова символом подчеркивания.

let myAge; // 变量:小驼峰命名
const PAGE_SIZE; // 常量:全大写,用下划线分割单词

function getAge() {} // 普通函数:小驼峰命名
function Person() {} // 构造函数:大驼峰命名

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

let count = 10; // Good
let getCount = 10; // Bad, look like function

function getName() {} // Good
function theName() {} // Bad, look like variable

Иномание - это не только наука, это техника, но, как правило, разговоры, длина именования должна быть сохранена как можно короче, и к точке. Попробуйте отразить тип данных значения в имени переменной. Например, именованиеcount,lengthа такжеsizeУказывает, что тип данных является числовым, а именованныйname,titleа такжеmessageУказывает, что тип данных является строкой. Но переменные, названные одним символом, напримерi,jа такжеkОбычно используется в цикле. Используйте их, чтобы отразить имя типа данных, чтобы ваш код мог быть легко прочитан другими и вами.

Избегайте использования бессмысленных названий, таких как:foo,barа такжеtmp.对于函数和方法命名来说,第一个单词应该是动词,这里有一些使用动词常见的约定:

глагол имея в виду
can
has Функция возвращает логическое значение
is Функция возвращает логическое значение
get функция возвращает нелогическое значение
set функция для хранения значения

1.3 Прямое количество

JS содержит примитивные значения некоторых типов: строки, числа, логические значения,nullа такжеundefined. Также включает литералы объектов и литералы массивов. Из них только логическое значение не требует пояснений, остальные типы более или менее требуют продумывания того, как их можно представить более точно.

О строках: Строка также может быть заключена в двойные цитаты в одиночных котировок, рекомендуется различные разные спецификации,Но помните, что нельзя смешивать одинарные и двойные кавычки в одном проекте.

О цифровом: Запомните два совета: во-первых, во избежание двусмысленности не опускайте числа до и после десятичной точки; во-вторых, большинство разработчиков не знакомы с восьмеричным форматом и редко его используют, поэтому лучше всего использовать восьмеричные литералы в коде. .

// 不推荐的小数写法:没有小数部分
let price = 10.;

// 不推荐的小数写法:没有整数部分
let price = .1;

// 不推荐的写法:八进制写法已经被弃用了
let num = 010;

оnull:nullявляется особой величиной, но мы часто неправильно ее понимаем, сравнивая сundefinedсмущенный. следует использовать в следующих сценарияхnull.

  • Используется для инициализации переменной, которой может быть присвоен объект.
  • Используется для сравнения с уже инициализированной переменной, которая может быть или не быть объектом.
  • Когда ожидается, что параметр функции будет объектом, он передается как параметр.
  • Когда возвращаемое значение является желаемым объектом, как распространение возврата.

null.

  • Не используйтеnullОбнаружить, есть ли параметр.
  • Не используйте егоnullдля обнаружения неинициализированной переменной.

пониматьnullЛучше всего использовать его в качестве заполнителя для объекта. Это правило не упоминается во всех основных спецификациях программирования, но имеет решающее значение для глобальной ремонтопригодности.

оundefined:undefinedособое значение, мы часто сочетаем его сnullсмущенный. Одна из вещей, которая довольно сбивает с толку, заключается в том, чтоnull == undefinedРезультат ИСТИНА. Однако использование этих двух значений отличается. Те переменные, которые не инициализированы, имеют начальное значение, а именноundefined, Указывающий, что переменная ожидает присвоения. Например:

let person; // 不好的写法
console.log(person === undefined); // true

Хотя этот код работает нормально, я бы рекомендовал избегать его использования в вашем коде.undefined. Это значение часто путают с оператором typeof, который возвращает «undefined». На самом деле, поведение typeof также вызывает недоумение, потому что независимо от того, является ли значениеundefinedПеременная по-прежнему является необъявленной переменной, и результатом операции typeof является «undefined». Например:

// foo未被声明
let person;
console.log(typeof person); // "undefined"
console.log(typeof foo); // "undefined"

В этом коде как person, так и foo заставят typeof вернуть "undefined", даже несмотря на то, что person и foo ведут себя совершенно по-разному в других сценариях (использование foo в операторе вызовет ошибку, а использование person - нет).

Отключив использование специальных значенийundefined, эффективно гарантируя, что typeof вернет «undefined» только в одном случае: когда переменная объявлена. Если вы используете переменную, которая может (или не может) быть назначена объекту, назначьте ее какnull.

// 好的做法
let person = null;
console.log(person === null); // true

Присвойте переменной начальное значениеnullУказывает назначение этой переменной, которой, вероятно, в конечном итоге будет присвоен объект. операция оператора typeofnullвозвращает "объект", когда типundefinedразделены.

О литералах объектов и литералах массивовObjectа такжеArrayКонструктор для создания объектов и массивов.

1.4 ноты

Многим нравится ставить пробел после двойной косой черты, чтобы компенсировать текст комментария (что я настоятельно рекомендую). Есть три способа использования однострочных комментариев:

  • Однострочный комментарий, поясняющий следующую строку кода. Перед этой строкой комментария всегда есть пустая строка, а уровень отступа такой же, как и у следующей строки кода.
  • Комментарий в конце строки кода. Между концом кода и комментарием есть хотя бы один отступ. Комментарии (включая предыдущие разделы кода) не должны превышать максимальное количество символов, и если это так, поместите комментарий над текущей строкой кода.
  • Закомментированы большие куски кода (многие редакторы могут пакетно комментировать несколько строк кода).

Однострочные комментарии не должны отображаться как последовательные многострочные комментарии, если только вы не закомментировали большой блок кода. Используйте многострочные комментарии только тогда, когда вам нужно прокомментировать длинный фрагмент текста.

Хотя многострочные комментарии также можно использовать для комментирования одной строки, я рекомендую использовать многострочные комментарии только тогда, когда требуются многострочные комментарии. Многострочные комментарии обычно используются в следующих случаях:

  • Комментарии в начале модулей, классов, функций
  • нужно использовать многострочные комментарии

Я настоятельно рекомендую вам использовать многострочные комментарии в стиле Java, которые выглядят очень красиво, а многие редакторы поддерживают автоматическую генерацию, см. следующий пример:

/**
 * Java风格的注释,注意*和注释之间
 * 有一个空格,并且*左边也有一个空格。
 * 你甚至可以加上一些@参数来说明一些东西。
 * 例如:
 *
 * @author 作者
 * @param Object person
 */

Когда добавить комментарий - это тема, которую программисты часто спорят. Принцип прохождения прохода в том, чтоКомментарии добавляются, когда код не понятен и не должен быть добавлен, когда код понятен.Исходя из этого принципа, я рекомендую добавлять комментарии в следующих ситуациях:

  • Непонятный код:Кодекс обычно трудно понять, должен добавить комментарии. Использование кода, вы можете использовать однострочную комментарий, многострочные комментарии, аннотации или смешать два. Ключ заключается в том, чтобы другие было легче прочитать этот код.
  • Например этот кодwhile(el && (el = el.next)) {}.在团队开发中,总是会有一些好心的开发者在编辑代码时发现他人的代码错误,就立即将它修复。有时这段代码并不是错误的源头,所以“修复”这个错误往往会制造其他错误,因此本次修改应当是可追踪的。当你写的代码有可能会被别的开发者认为有错误时,则需要添加注释。
  • Любой, кто писал интерфейсные программы, знает, что иногда приходится писать неэффективный, неэлегантный и откровенно грязный код, чтобы старые браузеры работали.

1.5 операторы и выражения

овыравнивание фигурных скобок, существует два основных стиля выравнивания раскосов. Первый стиль заключается в размещении открывающей фигурной скобки в конце первой строки кода в операторе блока.Этот стиль унаследован от Java, второй стиль заключается в размещении открывающей фигурной скобки в строке после первой строки блока. Оператор блока Этот стиль Стиль стал популярным в C#, потому что Visual Studio применяет это выравнивание. В настоящее время нет основной спецификации программирования JS, рекомендующей этот стиль, а руководство по стилю Google JS явно запрещает это использование, чтобы избежать автоматической вставки неправильных точек с запятой. Лично я также рекомендую использовать первый формат выравнивания фигурных скобок.

// 第一种花括号对齐风格
if (condition) {

}

// 第二种花括号对齐风格
if (condition)
{

}

О интервале оператора блока: существуют следующие три стиля, второй стиль рекомендуется в большинстве спецификаций кода:

// 第一种风格
if(condition){
  doSomething();
}

// 第二种风格
if (condition) {
  doSomething();
}

// 第三种风格
if ( condition ) {
  doSomething();
}

Что касается оператора switch, во многих спецификациях кода JS об этом подробно не говорится, во-первых, вы обнаружите, что в реальной работе существует меньше сценариев использования. Поскольку вы можете использовать переключатель только тогда, когда есть много условных оценок (оператор if используется непосредственно для коротких условий), но опытные программисты обычно используют запросы к объектной таблице для решения этой проблемы перед лицом многих оценочных условий. См. рекомендуемый код стиля ниже:

switch (condition) {
  case 'cond1':
  case 'cond2':
    doCond1();
    break;
  case 'cond3':
    doCond3();
    break;
  default:
    doDefault();
}

Рекомендуется придерживаться следующего стиля:

  1. Условные скобки требуют пробела до и после переключателя;
  2. Оператор case должен иметь отступ на один уровень относительно оператора switch;
  3. Разрешить нескольким операторам case совместно использовать оператор обработки;
  4. Если нет кода выполнения по умолчанию, вы можете добавить

О с: JS-движки и инструменты минификации не могут оптимизировать код с помощью операторов with, потому что они не могут угадать правильное значение кода. В строгом режиме оператор with явно запрещен, и при его использовании сообщается о синтаксической ошибке. Это указывает на то, что комитет ECMAScript убежден в том, что with больше не следует использовать. Я также настоятельно рекомендую избегать оператора with.

О цикле for: Существует два типа циклов for, один из которых является традиционным циклом for, который унаследован от C и Java JS и в основном используется для обхода элементов массива, а другой — цикл for-in, который используется для обхода свойств объекта.

Для цикла for,Я рекомендую избегать continue, насколько это возможно, но нет причин полностью запрещать его, его использование должно основываться на удобочитаемости кода.

Цикл for-in используется для перебора свойств объекта. Без определения каких-либо условий управления цикл будет методично проходить по каждому свойству объекта и возвращать имя свойства вместо значения. Проблема с циклом for-in заключается в том, что он перебирает не только свойства экземпляра объекта, но и свойства, унаследованные от прототипа. При переборе свойств пользовательского объекта он обычно завершается с неожиданными результатами. По этой причине лучше всего использовать метод hasOwnProperty() для фильтрации свойств экземпляра для цикла for-in. Я также рекомендую это, если вы действительно не хотите пройти цепочку прототипов объекта, и в этом случае вы должны добавить комментарий.

// 包含对原型链的遍历
for (let prop in obj) {
  console.log(`key: ${prop}; value: ${obj[prop]}`);
}

for (let prop in obj) {
  if (obj.hasOwnProperty(prop)) {
    console.log(`key: ${prop}; value: ${obj[prop]}`);
  }
}

Еще одна вещь, которую следует отметить в отношении циклов for-in, заключается в том, что циклы for-in используются для перебора объектов. Распространенным неправильным использованием является использование цикла for-in для перебора элементов массива, результат может быть не таким, как вы хотите (вы получаете индекс массива), вы должны использовать цикл for-of ES6 для перебора массива.

let arr = ['a', 'b', 'c'];

for (let i in arr) {
  console.log(i); // 0, 1, 2
}

for (let v of arr) {
  console.log(v); // 'a', 'b', 'c'
}

1.6 Объявление переменной

我们知道JS中var声明的变量存在变量提升,对变量提升不熟悉的同学写代码的时候就会产生不可意料的Bug。 Например:

function func () {
  var result = 10 + result;
  var value = 10;
  return result; // return NaN
}

// 实际被解释成
function func () {
  var result;
  var value;

  result = 10 + result;
  value = 10;
  return result;
}

function func (arr) {
  for (var i = 0, len = arr.length; i < len; i += 1) {}
}

// 实际被解释成
function func (arr) {
  var i, len;
  for (i = 0, len = arr.length; i < len; i += 1) {}
}

Объявление переменной впереди означает:Определение переменной в любом месте внутри функции точно такое же, как определение переменной в верхней части функции.Поэтому популярным стилем является размещение всех ваших объявлений переменных в верхней части функции, а не разбросанных по углам. Короче говоря, логика кода, написанного в этом стиле, очень похожа на привычку JS-движка парсить этот код. Я также рекомендую всегда делать определение локальной переменной первым оператором внутри функции.

function func (arr) {
  var i, len;
  var value = 10;
  var result = value + 10;

  for (i = 0; len = arr.length; i < len; i += 1) {
    console.log(arr[i]);
  }
}

Конечно, если у вас есть возможность использовать ES6, то я настоятельно рекомендую вам полностью отказаться от var и использовать let и const для определения переменных. Поверьте мне, определенно стоит отказаться от var, let и const обеспечивают область видимости на уровне блоков, они безопаснее и ведут себя более предсказуемо, чем var.

1.7 Объявление функции и вызов

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

// 不好的写法
if (condition) {
  function func () {
    alert("Hi!");
  }
} else {
  function func () {
    alert("Yo!");
  }
}

Результаты выполнения этого кода в разных браузерах также отличаются. Большинство браузеров автоматически используют второе объявление независимо от оценки условия. Firefox, с другой стороны, выбирает подходящее объявление функции на основе результата вычисления условия. Этот сценарий является серой зоной для ECMAScript, и его следует избегать, насколько это возможно. Объявления функций следует использовать вне условных операторов. Этот шаблон также явно запрещен руководством по стилю JS от Google.

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

// 好的写法
callFunc(params);

// 不好的写法,看起来像一个块语句
callFunc (params);

// 用来做对比的块语句
while (condition) {}

1.8 Сразу вызываемые функции

IIFE (немедленно вызванное выражением функции), немедленно вызвать значение функции выражения, то есть в то же время объявляем функцию позвоните эту функцию немедленно. ES6 редко используется, потому что есть механизм модуля, а основная цель IIFE - это для моделирования объема изоляции модуля. Вот некоторые рекомендуемые формулировки IIFE:

// 不好的写法:会让人误以为将一个匿名函数赋值给了这个变量
var value = function () {
  return {
    msg: 'Hi'
  };
}();

// 为了让IIFE能够被一眼看出来,可以将函数用一对圆括号包裹起来
// 好的写法
var value = (function () {
  return {
    msg: 'Hi'
  };
}());

// 好的写法
var value = (function () {
  return {
    msg: 'Hi'
  };
})();

1.9 Строгий режим

Если вы пишете код ES5, рекомендуется всегда использовать строгий режим. Глобальный строгий режим не рекомендуется и может привести к ошибкам в старом коде. Рекомендуется использовать строгий режим на функциональном уровне или использовать строгий режим в IIFE.

1.10 Равно

Что касается механизма приведения типов в JS, мы должны признать, что он действительно сложен и его трудно запомнить (в основном лень). Поэтому я рекомендую вам в любом случае для сравнения равенства использовать===а также!==.

1.11 eval

Динамическое выполнение строк JS не является хорошей идеей, есть несколько случаев, когда может выполняться динамический JS, и я предлагаю вам избегать его, если вы не владеете JS и не знаете, что делаете.

eval("alert('bad')");
const func = new Function("alert bad('bad')");
setTimeout("alert('bad')", 1000);
setInterval("alert('bad')", 1000);

1.12 Тип оригинальной упаковки

JS бокс и распаковкаПоймите, что примитивные значения не имеют атрибутов и методов.Когда мы вызываем метод для строки, JS-движок автоматически упаковывает примитивное значение в объект, а затем вызывает метод этого объекта. Но это не означает, что вы должны использовать примитивные типы-оболочки для создания соответствующих примитивных значений, потому что умы разработчиков часто прыгают туда-сюда между объектами и примитивными значениями, что увеличивает вероятность ошибок и приводит разработчиков в замешательство. У вас также нет причин самостоятельно создавать эти объекты вручную.

// 自动装箱
const name = 'Nicholas';
console.log(name.toUpperCase());

// 好的写法
const name = 'Nicholas';
const author = true;
const count = 10;

// 不好的写法
const name = new String('Nicholas');
const author = new String(true);
const count = new Number(10);

1.13 Инструменты

В командной разработке для поддержания единого стиля очень важен инструмент Lint. Потому что хоть все и понимают необходимость придерживаться единого стиля программирования, но при написании кода невольно нарушаются правила руководства по стилю (ведь люди ошибаются). Здесь я рекомендую вам использоватьESLintИнструмент выполняет проверку стиля кода. Вам не нужно полностью переписывать правила конфигурации. Вы можете унаследовать существующие ведущие в отрасли стандарты кодирования JS, чтобы выполнить точную настройку для своей команды. Я рекомендую наследовать отсюдаAirbnb JavaScript Style Guide, Конечно, вы также можете унаследовать официально рекомендуемую конфигурацию или стиль кодирования Google JS.На самом деле, с точки зрения стиля кодирования, большинство правил из трех одинаковы, но некоторые детали несовместимы.

JavaScript Standard Style

Способ построения проектирования программного обеспечения двумя способами: один состоит в том, чтобы сделать очень простое программное обеспечение, которое, очевидно, не могло найти недостатки; другой - это очень сложно, что не может найти очевидные недостатки. - Kar Hoare, 1980 лауреат

Первая часть, которую мы в основном обсудим спецификацию в стиле кода JS, целью спецификации в стиле кода состоит в том, чтобы сделать консистенцию кода в сцене мультиплеера. Обсуждения о том, как решить общие проблемы не включены в спецификацию стилей, что является содержанием практики программирования.

Практика программирования является еще одним классом технических характеристик программирования. Спецификация стиля кода только заботится о презентации кода, а практики программирования заботятся о результатах кодирования. Вы можете подумать о программированной практике как «секрет) - они направляют разработчиков, чтобы каким-то образом написать код, и результат известен. Если вы использовали некоторые шаблоны дизайна, такие как узоры наблюдателей в MVC, вы были знакомы с практикой программирования. Схема дизайна является неотъемлемой частью практики программирования, посвященной разрешению и программным организациям, связанным с конкретными вопросами.

Эта часть практики программирования будет охватывать только очень небольшие вопросы. Некоторые из этих приемов связаны с шаблонами проектирования, другие — просто несколько простых советов по повышению общего качества вашего кода. В дополнение к проверкам стиля кода ESLint также включает некоторые предостережения относительно методов программирования. Настоятельно рекомендуется использовать этот инструмент в вашей работе по разработке JS, чтобы гарантировать, что эти, казалось бы, незаметные, но трудно обнаруживаемые ошибки не возникнут.

2.1 Слабая связь уровня пользовательского интерфейса

В веб-разработке пользовательский интерфейс определяется тремя слоями, которые изолированы, но взаимодействуют друг с другом.

  • HTML используется для определения данных и семантики страницы
  • CSS используется для оформления страниц и создания визуальных элементов.
  • JS используется для добавления поведения на страницы, чтобы сделать их более интерактивными.

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

Одно предостережение: компоненты, которые работают вместе, не могут достичь «отсутствия связи». Во всех системах всегда есть некоторая информация, совместно используемая компонентами для выполнения их работы. В этом есть смысл, наша цель — убедиться, что изменения в одном компоненте не будут регулярно влиять на другие части.

Если веб-интерфейс слабо связан, его легко отлаживать. Проблемы, связанные с текстом или структурой, можно обнаружить, ища HTML. Когда возникает проблема, связанная со стилем, вы знаете, что проблема в CSS. Наконец, для тех проблем, связанных с поведением, вы идете прямо к JS, чтобы найти проблему, способность быть основной частью ремонтопригодности веб-интерфейса.

<button onclick=handler>test</button>

«Эволюция модели веб-разработки»

React — один из наиболее типичных представителей, он предлагает использовать JSX для написания HTML, который напрямую объединяет структуру страницы и логику страницы. Если это относится к эпохе веб-страниц, то считается, что она считается типичным учебником по антишаблонам, но в эпоху веб-приложений она принимается и используется большинством людей. Включая CSS в JS, предложенный командой React, мы хотим писать CSS в JS, чтобы во фронтенд-разработке полностью преобладал JS, а компонентизация была более тщательной.Практический опыт проекта, поэтому сейчас я все еще поддерживаю выжидательная позиция и продолжать использовать предыдущие SASS и LESS для разработки CSS).

Хотя шаблоны разработки двух веб-эпох резко изменились, все же есть некоторые общие принципы, которых необходимо придерживаться в отношении трехуровневого слабосвязанного дизайна:

Извлечь JS из CSS.Ранний IE8 и более ранние браузеры позволяли писать JS на CSS (без написания примера, это антипаттерн, лучше если не помнить), что приведет к проблемам с производительностью и еще страшнее, что это сложно поддерживать позже. Но я считаю, что все вы здесь, вероятно, не сможете прикоснуться к такому коду, это нормально.

CSS отойдет от JS.Дело не в том, что вы не можете изменить CSS в JS, а в том, что вам не разрешено изменять стиль напрямую, но косвенно изменять стиль, изменяя класс. См. пример ниже:

// 不好的写法
element.style.color = 'red';
element.style.left = '10px';
element.style.top = '100px';
element.style.visibility = 'visible';

// 好的写法
.reveal {
  color: red;
  left: 10px;
  top: 100px;
  visibility: visible;
}

element.classList.add('.reveal');

Потому что className CSS может быть связующим звеном между CSS и JS. В течение жизненного цикла страницы JS может добавлять и удалять className элементов по желанию. Стиль, определенный className, находится в коде CSS. В любой момент стили в CSS можно изменить без обновления JS. JS не должен напрямую манипулировать стилями, чтобы поддерживать слабую связь с CSS.

Есть одна ситуация, когда допустимо использование атрибута стиля: когда вам нужно расположить элемент на странице так, чтобы он перепозиционировался относительно другого элемента или всей страницы. Этот расчет нельзя выполнить в CSS, поэтому можно использоватьstyle.top,style.left,style.bottomа такжеstyle.rghtчтобы правильно расположить элемент. Определите свойства по умолчанию для этого элемента в CSS и измените эти значения по умолчанию в Javascript.

Ввиду текущей ситуации, когда интерфейс уже написал HTML и JS вместе, я не буду рассказывать о практике разделения их в оригинальной книге. Однако я наговорил столько глупостей, пожалуйста, запомните одно:Предсказуемость приводит к более быстрому тестированию и разработке, а уверенность (а не догадки) в том, с чего начинать отладку ошибок, что приводит к более быстрому разрешению проблем и более высокому общему качеству кода.

2.2 Избегайте использования глобальных переменных

Основные проблемы, связанные с глобальными переменными, следующие: по мере роста объема кода слишком большое количество глобальных переменных затрудняет поддержку кода и делает его подверженным ошибкам. Одна или две глобальные переменные не имеют большого значения, у вас почти никогда не может быть нулевых глобальных переменных (если только ваш JS-код не взаимодействует ни с каким другим JS-кодом, а просто делает что-то самостоятельно, что очень редко, не означает, что нет нет ).

Если вы пишете код ES6, вам будет сложно создать глобальную переменную, если вы явно не напишетеwindow.globalVar = 'something'Механизм модуля ES6 автоматически помогает вам сделать хорошую сегмент домена задания, что делает ваш кодовой техническое обслуживание и безопасность становится выше (старый jser должен чувствовать, что современные интерфейсные разработчики действительно счастливы).

var

window.global = (function () {
  var exportVar = {}; // ES5没有let和const,故用var

  // add method and variable to exportVar

  return exportVar;
})();

Мы знаем, что при срабатывании события объект события (event object) будет передан в обработчик события как параметр обратного вызова, например:

// 不好的写法
function handleClick(event) {
  var pop = document.getElementById('popup');
  popup.style.left = event.clientX + 'px';
  popup.style.top = event.clientY + 'px';
  popup.className = 'reveal';
}

// 你应该明白addListener函数的意思
addListener(element, 'click', handleClick);

Этот код использует только два свойства объекта события: clientX и clientY. Используйте эти два свойства для позиционирования элемента перед его отображением на странице. Хотя этот код выглядит очень простым и без проблем, на самом деле его писать не стоит, потому что он имеет свои ограничения.

Правило 1: Изолируйте логику приложения

Первая проблема с примером кода выше состоит в том, что обработчик событий содержит логику приложения. Логика приложений является функциональным кодом, связанным с приложением, а не поведением пользователя. Логика, применяемая в приведенном выше примере, - отображение всплывающего окна в определенном месте. Хотя это взаимодействие должно произойти, когда пользователь нажимает на определенный элемент, это не всегда в этом случае.

Лучше всего изолировать логику приложения от всех обработчиков событий, поскольку в какой-то момент одна и та же логика может быть запущена в другом месте. Например, иногда вам нужно определить, показывать ли всплывающее окно, когда пользователь наводит указатель мыши на элемент, или выполнять ту же логику при нажатии клавиши на клавиатуре. Такие несколько обработчиков событий выполняют одну и ту же логику, и ваш код случайно дублируется.

Еще один недостаток размещения логики приложения в обработчиках событий связан с тестированием. При тестировании вам нужно запускать код функции напрямую, а не путем имитации клика по элементу. Если вы поместите логику вашего приложения в обработчик событий, единственный способ проверить это — запустить событие. Хотя некоторые среды тестирования могут имитировать триггерные события, на практике это не лучший способ тестирования. Лучший способ вызвать функциональный код — это вызов одной функции.

Всегда нужно разделять логику приложения и код обработки событий. Если бы вы рефакторили код из предыдущего примера, первым шагом было бы помещение кода, обрабатывающего логику всплывающих окон, в отдельную функцию, возможно, смонтированную на глобальном объекте, определенном для приложения. Обработчики событий всегда должны находиться в одном и том же глобальном объекте, отсюда и следующие два метода.

// 好的写法 - 拆分应用逻辑
var MyApplication = {
  handleClick: function (event) {
    this.showPopup(event);
  },

  showPopup: function (event) {
    var pop = document.getElementById('popup');
    popup.style.left = event.clientX + 'px';
    popup.style.top = event.clientY + 'px';
    popup.className = 'reveal';
  }
};

addListener(element, 'click', function (event) {
  MyApplication.handleClick(event);
});

Вся логика приложения, ранее содержавшаяся в обработчиках событий, теперь перемещена вMyApplication.showPopup()метод. СейчасMyApplication.handleClick()Метод делает только одно: вызываетMyApplication.showPopup().若应用逻辑被剥离出去,对同一段功能代码的调用可以在多点发生,则不需要一定依赖于某个特定事件的触发,这显然更加方便。但这只是拆解事件处理程序代码的第一步。

Правило 2: Не распространяйте объект события

eventMyApplication.handleClick()MyApplication.showPopup(). Как указано выше,eventОбъект содержит много дополнительной информации, связанной с событием, и этот код использует только две из них. Логика приложения не должна зависеть отeventОбъекты для завершения функции правильно, причины следующие:

  • В интерфейсе метода не указано, какие данные необходимы. Хороший API должен быть прозрачным для ожиданий и зависимостей. БудуeventОбъект как параметр не говорит вамeventКакие свойства полезны и для чего они используются?
  • Поэтому, если вы хотите протестировать этот метод, вы должны воссоздать объект события и передать его в качестве параметра. Итак, вам нужно точно знать, какая информация используется этим методом, чтобы вы могли правильно написать тестовый код.

Эти проблемы (имеется в виду непонятный формат интерфейса и самостоятельное построениеeventобъект для тестирования) нежелательны в больших веб-приложениях. Код, который недостаточно ясен, может привести к ошибкам.

Лучший способ - использовать обработчик событийevent对象来处理事件,然后拿到所有需要的数据传给应用逻辑。 Например,MyApplication.showPopup()Методу нужны только две части данных,xкоординаты иyкоординировать. Поэтому мы переписываем метод, чтобы он принимал эти два параметра.

// 好的写法
var MyApplication = {
  handleClick: function (event) {
    this.showPopup(event.clientX, event.clientY);
  },

  showPopup: function (x, y) {
    var pop = document.getElementById('popup');
    popup.style.left = x + 'px';
    popup.style.top = y + 'px';
    popup.className = 'reveal';
  }
};

addListener(element, 'click', function (event) {
  MyApplication.handleClick(event);
});

В этом недавно переписанном кодеMyApplication.handleClick()Будуxкоординаты иyКоординаты передаются вMyApplication.showPopup(), вместо ранее переданного объекта события. ясно видноMyApplication.showPopup()Ожидаемые параметры передаются, и эту логику можно легко вызвать напрямую из любого места теста или кода, например:

// 这样调用非常棒
MyApplication.showPopup(10, 10);

При обработке событий лучше всего позволить обработчику события вступить в контакт.eventevent对象执行任何必要的操作,包括阻止默认事件或阻止事件冒泡,都应当直接包含在事件处理程序中。 Например:

// 好的写法
var MyApplication = {
  handleClick: function (event) {
    // 假设事件支持DOM Level2
    event.preventDefault();
    event.stopPropagation();

    // 传入应用逻辑
    this.showPopup(event.clientX, event.clientY);
  },

  showPopup: function (x, y) {
    var pop = document.getElementById('popup');
    popup.style.left = x + 'px';
    popup.style.top = y + 'px';
    popup.className = 'reveal';
  }
};

addListener(element, 'click', function (event) {
  MyApplication.handleClick(event);
});

В этом коде,MyApplication.handleClick()Является обработчиком событий, поэтому он вызывается перед данными в логике приложения.event.preventDefault()а такжеevent.stopPropagation()event

2.4 Избегайте «нулевых сравнений»

В JS мы часто видим такой код: переменные сnullСравнение (которое проблематично использовать) для определения того, присвоено ли переменной разумное значение. Например:

var Controller = {
  process: function(items) {
    if (items !== null) {
      items.sort();
      items.forEach(function(item){});
    }
  }
};

В этом кодеprocess()Метод явно надеждаitemsэто массив, потому что мы видимitemsимеютsort()а такжеforEach(). Цель этого кода довольно очевидна: если параметрitemsне является массивом, остановите следующую операцию. Проблема с этим письмом заключается в том, что иnullСравнение на самом деле не позволяет избежать возникновения ошибок.itemsМожет быть 1, строка или даже произвольный объект. Эти значенияnullне равны, что, в свою очередь, приводит кprocess()После выполнения методаsort()возникает ошибка.

только сnullСравнение не дает достаточно информации, чтобы сказать, действительно ли выполнение последующего кода безопасно. К счастью, JS предоставляет нам несколько способов определить истинное значение переменной.

nullа такжеundefined.如果你希望一个值是字符串、数字、布尔值或者undefinedtypeofоператор.typeofОператор возвращает строку, представляющую тип значения.

  • Для струн,typeofвернуть"string".
  • Для чисел,typeofвернуть"number".
  • Для логических значенийtypeofвернуть"boolean".
  • дляundefined,typeofвернуть"undefined".

дляtypeofиспользования следующим образом:

// 推荐使用,这种用法让`typeof`看起来像运算符
typeof variable

// 不推荐使用,因为它让`typeof`看起来像函数调用
typeof(variable)

использоватьtypeofОбнаружение четырех вышеуказанных примитивных типов значений очень безопасно.

typeofundefinedпеременная черезtypeofвернусь"undefined".

последнее примитивное значение,null, обычно не используется для обнаружения инструкций. Как было сказано выше, просто иnullСравнения часто не содержат достаточно информации, чтобы определить, является ли тип значения допустимым. За одним исключением, если ожидаемое значение действительноnull, можно прямо иnullСравнивать. следует использовать===или!==прийти иnullсравнить, например:

// 如果你需要检测null,则使用这种方法
var element = document.getElementById('my-div');
if (element !== null) {
  element.className = 'found';
}

Если элемент DOM не существует, передатьdocument.getElementById()Полученное значение равноnull. Этот метод либо возвращает узел, либоnull. Потому что в это времяnullЭто предсказуемый результат, вы можете использовать!==Обнаруженные результаты возврата.

бегатьtypeof nullзатем вернуться"object", что является неэффективным суждениемnullМетоды. Если вам нужно обнаружитьnull, затем используйте оператор идентификации напрямую (===) или нетождественный оператор (!==).

2.4.2 Обнаружение эталонных значений

Справочные значения также называют объектами (object). В JS все значения, кроме примитивных значений, являются ссылками. Существует несколько встроенных типов ссылок:Object,Array,Dateа такжеErrorВ небольших количествах.typeofоператоры неэффективны при определении этих ссылочных типов, поскольку все объекты возвращают"object".

typeofДругое не рекомендуемое использование - при обнаруженииnullтипtypeofоператор дляnullвернет все"object".这看上去很怪异,被认为是标准规范的严重bug,因此在编程时要杜绝使用typeofобнаруживатьnullтип.

Лучший способ определить тип ссылочного значения — использоватьinstanceofоператор.instanceofОсновной синтаксис:value instanceof constructor.

instanceofОдна интересная особенность заключается в том, что она не только обнаруживает настроенный конструктор объектов, цепь прототипа также обнаружена. Цепь прототипов содержит много информации, в том числе используемой модели определения наследования. Например, по умолчанию каждый объект наследуется отObjectИтак, каждый предметvalue instanceof Objectвернусьtrue.因为这个原因,использоватьvalue instanceof ObjectОпределить принадлежность объекта к тому или иному типу практики не лучший вариант.

instanceof

function Person (name) {
  this.name = name;
}

var me = new Person('Nicholas');
console.log(me instanceof Object); // true
console.log(me instanceof Person); // true

При обнаружении пользовательских типов в JS лучше всего использоватьinstanceofОператоры, это тоже единственный способ. То же самое верно для встроенных типов JS (используйтеinstanceofОператор). Однако существует серьезное ограничение.

Предположим, кадр браузера (Framea) в объекте передается другому кадру (Frameb). Два рама определяются в конструкторе. Если объект является примером из кадра человека кадра A, следующие правила удерживаются.

frameAPersonInstance instanceof frameAPerson; // true
frameAPersonInstance instanceof frameBPerson; // false

Поскольку каждый фрейм (фрейм) содержит копию Person, он считается копией Person в этом фрейме (фрейме), хотя эти два определения могут быть абсолютно одинаковыми. Эта проблема возникает не только с пользовательскими типами, но и с двумя другими очень важными встроенными типами: функциями и массивами. Для этих двух типов, как правило, нет необходимости использоватьinstanceof.

2.4.3 Функция обнаружения

Технически функции в JS являются ссылочными типами, и то же самое существуетFunctionКонструктор, каждая функция является его экземпляром, например:

function myFunc () {}

// 不好的写法
console.log(myFunc instanceof Function); // true

// 好的写法
console.log(typeof myFunc === 'function'); // true

Однако этот метод также не может использоваться через кадры, поскольку каждый кадр имеет свой собственныйFunctionКонструктор. к счастьюtypeofОператоры также могут использоваться в функциях, возвращая"function".Лучший способ обнаружить функцию — использоватьtypeof, так как его можно использовать между кадрами.

использоватьtypeofЕсть ограничение на функцию обнаружения. В IE8 и более ранних версиях IE используйтеtypeofдля обнаружения узлов DOM (например,document.getElementById()) в функции return"object"вместо"function". Например:

// IE 8及其更早版本的IE
console.log(typeof document.getElementById); // "object"
console.log(typeof document.createElement); // "object"
console.log(typeof document.getElementByTagName); // "object"

Эта странность возникает из-за различий в реализации DOM в браузерах. Короче говоря, эти более ранние версии IE не реализовывали DOM как встроенный метод JS, что приводило к встроеннымtypeofОператор распознает эти функции как объекты. Поскольку модель DOM четко определена, знание того, что член объекта существует, означает, что это метод, и разработчики часто пропускаютinоператор для обнаружения методов DOM, таких как:

// 检测DOM方法
if ("querySelectorAll" in document) {
  images = document.querySelectorAll("img");
}

Этот код проверяетquerySelectorAlldocument中,如果是,则使用这个方法。尽管不是最理想的方法,如果想在IE8及更早浏览器中检测DOM方法是否存在,这是最安全的做法。 Во всех остальных случаяхtypeofОператоры — лучший выбор для обнаружения функций JS.

2.4.4 Обнаружение массивов

Одной из старейших междоменных проблем в JS является передача массивов между фреймами. Разработчики быстро обнаружилиinstanceof ArrayВ этом сценарии не всегда возвращаются правильные результаты. Как упоминалось выше, каждый кадр имеет свой собственныйArrayконструктор, поэтому экземпляры в одном кадре не распознаются в другом. Дуглас Крокфорд впервые рекомендовал использовать «утиную типизацию» («утиная типизация» была концепцией, впервые предложенной автором Джеймсом Уиткомбом Райли, согласно которой «птица, которая ходит, плавает и крякает, как утка, является уткой», по существу фокусируясь на том, «что объект может делать», а не «что такое объект», для получения более подробной информации см. «Полное руководство по JS» (шестое издание) 9.5, 4 разделы), чтобы определить егоsort()метод существует.

// 采用鸭式辨型的方法检测数组
function isArray(value) {
  return typeof value.sort === "function";
}

Этот метод обнаружения основан на том факте, что массив является единственным, содержащимsort()Метод объекта. Конечно, если вы пройдете вisArray()Параметр содержитsort()объект метода, который также возвращаетtrue.

Было проведено много исследований о том, как определять типы массивов в JS, и, наконец, Юрий Зайцев (также известный как Kangax) придумал элегантное решение.

function isArray(value) {
  return Object.prototype.toString.call(value) === "[object Array]";
}

Kangax нашел встроенный вызов значенияtoString()Метод возвращает стандартный строковый результат во всех браузерах. Для массивов возвращаемая строка"[object Array]"

"[object JSON]".

С тех пор ECMAScript5 будетArray.isArray()Официально представлен JS. Единственная цель — точно определить, является ли значение массивом. Подобно функции Кангакса,Array.isArray()Также возможно обнаружить значения, переданные в рамки, так много библиотек JS в настоящее время реализуют этот метод аналогично.

2.4.5 Свойства обнаружения

Другой вариант использования null (и undefined) — это проверка существования свойства в объекте, например:

// 不好的写法:检测假值
if (object[propertyName]) {}

// 不好的写法:和null相比较
if (object[propertyName] != null) {}

// 不好的写法:和undefined比较
if (object[propertyName] != undefined) {}

Каждое суждение в приведенном выше коде фактически проверяет значение атрибута по заданному имени, а не оценивает, существует ли атрибут, на который указывает данное имя, потому что, когда значение атрибута ложно (ложное значение), возникают ошибки, такие как 0, "" (пустая строка), false, null и undefined. В конце концов, это все допустимые значения свойств. Например, если свойство записывает число, значение может быть равно нулю. В этом случае первое суждение в приведенном выше коде вызовет ошибку. И так далее, если значение свойства равноnullилиundefined, все три суждения приводят к ошибкам.

Лучший способ узнать, существует ли свойство, — это использоватьinоператор.inОператор просто проверяет, существует ли свойство, но не считывает значение свойства, что позволяет избежать двусмысленных утверждений, упомянутых ранее в этом подразделе.Если свойства экземпляра объекта существуют или унаследованы от прототипа объекта,inОператор вернетсяtrue. Например:

var object = {
  count: 0,
  related: null
};

// 好的写法
if ("count" in object) {
  // 这里的代码会执行
}

// 不好的写法:检测假值
if (object["count"]) {
  // 这里的代码不会执行
}

// 好的写法
if ("related" in object) {
  // 这里的代码会执行
}

// 好的写法
if (object["related"] != null) {
  // 这里的代码不会执行
}

Используйте, если вы хотите только проверить, существует ли экземпляр объекта экземпляра,hasOwnProperty()метод.所有继承自Objecttruefalse). Следует отметить, что в IE8 и более ранних версиях IE объекты DOM не наследуются отObject, поэтому этот метод также не включен. То есть вы вызываете объект DOMhasOwnProperty()Метод должен сначала проверить его существование (если вы уже знаете, что объект не является DOM, вы можете пропустить этот шаг).

// 对于所有非DOM对象来说,这是好的写法
if (object.hasOwnProperty("related")) {
  // 执行这里的代码
}

// 如果你不确定是否为DOM对象,则这样来写
if ("hasOwnProperty" in object && object.hasOwnProperty("related")) {
  // 执行这里的代码
}

Из-за ситуации с IE8 и более ранними версиями IE при оценке того, существуют ли свойства объекта экземпляра, я предпочитаю использоватьinоператор, который используется только тогда, когда вам нужно оценить свойства экземпляраhasOwnProperty(). Всякий раз, когда вам нужно обнаружить существование свойства, используйтеinоператор илиhasOwnProperty(). Это позволит избежать многих ошибок.

2.5 Отделение данных конфигурации от кода

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

Значения, которые жестко запрограммированы в приложении при настройке данных, например:

  • магическое число
  • URL
  • Строка, которую необходимо отобразить пользователю (может быть интернационализирована)
  • повторяющееся значение
  • настраивать
  • любое значение, которое может измениться

Мы всегда должны помнить, что данные конфигурации могут быть изменены, и вы не хотите, чтобы кто-то вдруг захотел изменить информацию, отображаемую на странице, заставив вас изменить исходный код JS.

Эти конфигурационные данные можно извлечь в константы, или смонтировать в объект, или записать в конфигурационные файлы (в JS рекомендуется JSON), и прочитать данные в конфигурационных файлах через программу, так что даже если данные изменены, Ваш программный код не будет иметь никаких изменений, что снизит вероятность ошибок.

Отладка очень сложна, если ошибка не выдается или не сообщается вам.如果所有的失败都是悄无声息的,首要的问题是那必将消耗你大量的时间才能发现它,更不要说单独隔离并修复它了。 так,Ошибка — друг разработчика, а не враг.

Ошибки часто появляются в нежелательных местах и ​​в неподходящее время, что создает проблемы. Что еще хуже, сообщения об ошибках по умолчанию часто слишком кратки, чтобы точно объяснить, что пошло не так. Сообщения об ошибках JS печально известны своей редкостью и расплывчатостью (особенно в старых версиях IE), что только усугубляет проблему. Представьте себе ошибку, которую можно было бы описать так: «Вызов функции не удался из-за этих условий». Тогда задача отладки сразу же упрощается, что как раз и является преимуществом выбрасывания собственных ошибок.

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

2.6.2 Выдача ошибок в JS

Нет никаких сомнений в том, что выбрасывать ошибки в JS более ценно, чем делать то же самое в любом другом языке, обвиняя в сложности отладки веб-сторону. можно использоватьthrowоператор, который выдает один из предоставленных объектов как ошибку. Однако любой тип объекта может быть выдан как ошибка.ErrorОбъекты используются чаще всего.

throw new Error('Something bad happened.');

ВстроенныйErrorТипы доступны во всех реализациях JS, его конструктор принимает один аргумент, ссылается на сообщение об ошибке (message). Когда ошибка выдается таким образом, если оператор try-catch не захватывает ее, браузер обычно отображает сообщение напрямую (строку сообщения). Большинство современных браузеров имеют консоль (консоль), когда возникает ошибка, где выводится сообщение об ошибке. Другими словами, любая ошибка, выброшенная и не выброшенная вами, обрабатывалась одинаково.

// 不好的写法
throw 'message';

throw { name: 'Nicholas' };
throw true;
throw 12345;
throw new Date();

Если вам нужно иметь в виду, что если вы не захватите оператор try-catch, вы будете выбрасывать любые значения, которые вызовут ошибку. Firefox, Opera и Chrome будут использовать значение этого брошенногоString()для завершения логики отображения сообщения об ошибке, но Safari и IE не такие. Для всех браузеров единственным безошибочным способом отображения пользовательского сообщения об ошибке является использование объекта Error.

2.6.3 Преимущества выбрасывания ошибок

Выброс собственной ошибки может использовать точный текст для отображения в браузере. В дополнение к номерам строк и столбцов вы можете включить любую информацию, необходимую для отладки проблемы. Я рекомендую всегда включать имя функции в сообщение об ошибке и причину сбоя функции. Рассмотрим следующую функцию:

function getDivs (element) {
  return element.getElementsByTagName('div');
}

Эта функция предназначена для получения элементов div во всех элементах-потомках под элементом element. Элемент DOM, переданный в функцию, может быть довольно распространенным явлением для работы с нулевым значением, но на самом деле требуется именно элемент DOM. Что произойдет, если вы передадите null этой функции? Вы увидите расплывчатое сообщение об ошибке, например, "ожидается объект". Затем вы должны посмотреть на стек выполнения и найти проблему в исходном файле. Отладка упрощается, выдавая ошибку:

function getDivs (element) {
  if (element && element.getElementsByTagName) {
    return element.getElementsByTagName('div');
  } else {
    throw new Error('getDivs(): Argument must be a DOM element.');
  }
}

дай сейчасgetDivs()Функция выдает ошибку, любой элемент времени не удовлетворяет условиям для продолжения выполнения, выдается ошибка, в которой явно указано, что произошло. Если ошибка отображается в консоли браузера, вы можете сразу начать отладку и знать, что наиболее вероятной причиной ошибки является то, что вызывающая функция пытается выполнить дальнейшие действия с элементом DOM со значением null.

Я склонен думать, что выдать ошибку — это все равно, что присвоить себе ярлык, говорящий себе, почему это не удалось..

2.6.4 Когда выдавать ошибку

Понимание того, как выдать ошибку, — это только одна часть уравнения, другая часть — это понимание того, когда выдавать ошибку. Поскольку в JS нет проверки типов и параметров, большое количество разработчиков ошибочно полагают, что они должны сами реализовать проверку типов для каждой функции. Эта практика непрактична и влияет на общую производительность скрипта. Рассмотрим приведенную ниже функцию, которая пытается реализовать адекватную проверку типов.

// 不好的做法:检查了太多的错误
function addClass (element, className) {
  if (!element || typeof element.className !== 'string') {
    throw new Error('addClass(): First argument must be a DOM element.');
  }
  if (typeof className !== 'string') {
    throw new Error('addClass(): Second argument must be a string.');
  }
  element.className += '' + className;
}

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

В приведенном выше примере наиболее вероятной причиной ошибка передается функцию опорного значения, является NULL. Если второй параметр NULL или номер или логическое значение, не бросайте ошибку, потому что она будет отличать строку JS. Это означает, что результаты на дисплее элемента DOM не соответствует ожиданиям, но это не повышает серьезную ошибку в отношении степени. Итак, я проверю элементы DOM.

// 好的写法
function addClass (element, className) {
  if (!element || typeof element.className !== 'string') {
    throw new Error('addClass(): First argument must be a DOM element.');
  }
  element.className += '' + className;
}

Если функция вызывается только известной сущностью, проверка ошибок, вероятно, не нужна (в данном случае приватная функция); если нет возможности заранее определить все места, где будет вызываться функция, скорее всего, вы нужна проверка ошибок. Это, скорее всего, выиграет от собственной ошибки.Лучшее место для выдачи ошибки - это служебная функция, напримерaddClass()Функции, являющиеся частью общей среды сценариев, используются во многих местах, точнее в библиотеках JS.

Все библиотеки JS должны выдавать ошибки из своего общедоступного интерфейса для ошибок, вызванных известными условиями. С большими библиотеками, такими как jQuery, YUI и Dojo, невозможно предсказать, когда и где вы будете вызывать их функции. В их обязанности входит уведомлять вас, когда вы делаете что-то не так, потому что вы не можете зайти в код библиотеки, чтобы отладить причину ошибки. Стек вызовов функций должен завершаться при входе в интерфейс кода библиотеки, не глубже. Нет ничего хуже, чем видеть, как возникает ошибка при вызове функции кодом дюжины библиотек, и разработчик библиотеки должен взять на себя ответственность за предотвращение подобных ситуаций.

То же самое касается частных библиотек JS. Многие веб-приложения имеют свою собственную специализированную встроенную библиотеку JS или «берут» какую-нибудь известную библиотеку с открытым исходным кодом (например, jQuery). Библиотеки классов обеспечивают абстракцию над грязными деталями реализации, чтобы упростить их использование разработчиками. Генерация ошибок помогает безопасно скрыть эти грязные детали реализации от разработчика.

Вот несколько хороших практических правил для выбрасывания ошибок:

  • После того, как ремонт сложно отладить ошибку, попробуйте увеличить одну или две пользовательские ошибки. Когда появляется ошибка, которая поможет облегчить решить проблему.
  • Если вы пишете код, подумайте: «Надеюсь, [что-то] не произойдет, а если произойдет, мой код будет в беспорядке». В этот момент, если «что-то» происходит, возникает ошибка.
  • Если вы пишете код, что другие люди (не знаете, кто) используют его, подумайте о том, как они используют его, чтобы бросать ошибки в определенных ситуациях.

try {
  somethingThatMightCauseAnError();
} catch (ex) {
  // do nothing
}

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

2.6.6 Тип ошибки

Спецификация ECMA-262 определяет 7 типов ошибок. Все эти типы используются в движке JS при возникновении различных ошибок, но, конечно, мы также можем создавать их вручную.

  1. Ошибка: базовый тип для всех ошибок. На самом деле движок никогда не выдает такую ​​ошибку.
  2. EvalError: пройденоeval()Возникает при возникновении ошибки в коде выполнения функции.
  3. RangeError: Возникает, когда число превышает свои границы — например, при попытке создать массив длины -20 (new Array(-20);). Эта ошибка очень редка при обычном выполнении кода.
  4. ReferenceError: Выдает, что желаемый объект не существует — например, пытается вызвать функцию в нулевой ссылке на объект.
  5. SyntaxError: вызывается, когда в коде есть синтаксические ошибки.
  6. TypeError: 变量不是期望的类型时抛出。 Например,new 10или'prop' in true.
  7. URIError: даетencodeURI(),encodeURIComponent(),decodeURI()илиdecodeURIComponent()Генерируется, когда функции передается недопустимо отформатированная строка URI.

Понимание различных типов ошибок может помочь нам легче справляться с ними. Все типы ошибок наследуются от Error, поэтому используйтеinstanceof ErrorПроверка его типа не дает никакой полезной информации. Ошибки можно обрабатывать более надежно, проверяя определенные типы ошибок.

try {
  // 有些代码引发了错误
} catch (ex) {
  if (ex instanceof TypeError) {
    // 处理TypeError错误
  } else if (ex instanceof ReferenceError) {
    // 处理ReferenceError错误
  } else {
    // 其他处理
  }
}

Если вы выдаете свою собственную ошибку, и это тип данных, а не ошибка, вы можете очень легко отличить свою собственную ошибку от типа ошибки браузера. Однако генерация ошибок реальных типов имеет несколько преимуществ по сравнению с генерацией объектов других типов.

Во-первых, как обсуждалось выше, сообщения об ошибках отображаются в обычном механизме обработки ошибок браузера. Во-вторых, браузер прикрепляет дополнительную информацию к выброшенному объекту Error. Эта информация варьируется от браузера к браузеру, но они предоставляют контекстную информацию, такую ​​как номера строк и столбцов для ошибок, а в некоторых браузерах — информацию о стеке и исходном коде. Конечно, если вы используете конструктор Error, вы теряете способность отличать собственные сгенерированные ошибки от ошибок браузера.

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

function MyError (message) {
  this.message = message;
}
MyError.prototype = new Error();

Этот код имеет две важные детали: атрибуты сообщения, браузер должен знать строку сообщения об ошибке; настройкиprototype

throw new MyError('Hello World!');

Напоминаем, что этот метод не отображает сообщения об ошибках в IE8 и более ранних браузерах. Вместо этого вы увидите общее сообщение «Исключение создано, но не перехвачено». Самым большим преимуществом этого подхода является то, что пользовательские типы ошибок могут обнаруживать свои собственные ошибки.

try {
  // 有些代码引发了错误
} catch (ex) {
  if (ex instanceof MyError) {
    // 处理自己的错误
  } else {
    // 其他处理
  }
}

Если вы всегда сами ловите все ошибки, которые выбрасываете, то немного глупости в IE не имеет значения. Преимущества, получаемые от правильной системы обработки ошибок, огромны. Этот метод может давать все более и более гибкую информацию о том, как правильно обрабатывать ошибки.

2.7 Не двигайте объектами, которые не ваши

JS уникален тем, что ничего не накрестно. По умолчанию вы можете изменить любой объект, который вы можете коснуться. Это (анализатор) на самом деле не заботится, если эти объекты определяются разработчиком или частью среды выполнения по умолчанию - они могут быть изменены, если они доступны. В проекте, где разработчик работает самостоятельно, это не проблема, разработчик знает именно то, что изменяется, потому что он знает весь код, как задняя часть его руки. Однако в проекте по развитию многого человека случайная модификация объектов является большой проблемой.

2.7.1 Какова ваша цель

Вы владеете этими объектами, когда ваш код создает их. Код, который создает объекты, не обязательно должен быть написан вами, но пока вы несете ответственность за поддержку кода, вы владеете объектами. Например, команда YUI владеет объектом YUI, а команда Dojo владеет объектом dojo. Даже если первоначальный автор, написавший код для определения объекта, уходит, соответствующая команда остается владельцем объекта.

При использовании JS-библиотеки в своем проекте лично вы не станете автоматически владельцем этих объектов. При разработке многопользовательского проекта все предполагали, что они будут работать как обычные библиотечные объекты, как описано в их документации. Если вы используете YUI, измените объекты внутри них, это заставит вашу команду устроить ловушку. Это вызовет некоторые проблемы, в которые могут попасть некоторые люди.

Имейте в виду, если ваш код не создает эти объекты, не изменяйте их,включать:

  • Собственные объекты (объект, массив и т. д.)
  • DOM-объект (например, документ)
  • Объекты объектной модели браузера (BOM) (например, окно)
  • объект библиотеки классов

2.7.2 Принципы

  • Метод не новый
  • Не удалять методы

С такими изменениями легко справиться, когда вы единственный разработчик в проекте, потому что вы их знаете и ожидаете от них. При работе с командой над большим проектом подобные изменения могут вызвать путаницу и отнять много времени.

не переопределять методы

В JS наихудшей практикой является переопределение метода объекта, которым вы не владеете, в JS невероятно легко переопределить существующий метод. даже святойdocument.getElementById()Методы не являются исключением и могут быть легко переопределены. Возможно, вы видели паттерны, подобные следующему (эта практика также называется «перехватом функций»):

// 不好的写法
document._originalGetElementById = document.getElementById;
document.getElementById = function (id) {
  if (id === 'window') {
    return window;
  } else {
    return document._originalGetElementById(id);
  }
}

В приведенном выше примере поместите собственный методdocument.getElementById()«Указатель» хранится вdocument._originalGetElementByIdдля последующего использования. Потом,document.getElementById()Переопределено новым методом. Новый метод также иногда вызывает исходный метод, а в одном случае — нет.Этот паттерн «переопределение плюс надежная деградация» не менее плох, чем переопределение нативных методов, а может быть, даже хуже, потому чтоdocument.getElementById()Иногда оправдывает ожидания, иногда нет.В большом проекте одна такая проблема может привести к огромной трате времени и денег.

нет нового метода

Добавлять методы к существующим объектам в JS очень просто. Просто создайте функцию и назначьте ее свойству существующего объекта, превратив ее в метод. Эта практика может изменять все типы объектов.

// 不好的写法 - 在DOM对象上增加了方法
document.sayImAwesome = function () {
  alert("You're awesome.");
}
// 不好的写法 - 在原生对象上增加了方法
Array.prototype.reverseSort = function () {
  return this.sort().reverse();
}
// 不好的写法 - 在库对象上增加了方法
YUI.doSomething = function () {
  // 代码
}

Почти невозможно помешать вам добавлять методы к любому объекту (для этого в ES5 добавлено три новых метода, которые будут рассмотрены позже). Большая проблема с добавлением методов к объектам, которыми вы не владеете, может вызвать конфликты имен.Просто потому, что объект не имеет метода в данный момент, не означает, что у него не будет его в будущем.Что еще хуже, если собственный метод не будет вести себя так же, как ваш в будущем, вы окажетесь в кошмаре обслуживания кода.

Мы собираемся извлечь уроки из истории библиотеки Prototype JS. Prototype известен модификацией различных JS-объектов. Он случайно добавляет методы в DOM и нативные объекты. На самом деле большая часть кода библиотеки предназначена для расширения существующего объекта, а не для создания самого объекта. Разработчики Prototype рассматривают эту библиотеку как дополнение к JS. В версиях ниже 1.6 Prototype реализовалdocument.getElementsByClassName()метод. Возможно, вы узнали этот метод, потому что он официально определен в HTML5, который стандартизирует использование Prototype.

Прототипdocument.getElementsByClassName()Метод возвращает массив, содержащий элементы с указанным именем класса CSS. Prototype также добавляет в массив метод,Array.prototype.each(), который итерации по поводу этого массива и выполняет функцию на каждом элементе. Это позволяет разработчикам написать код как:

document.getElementsByClassName('selected').each(doSomething);

С кодом все в порядке до тех пор, пока HTML5 не стандартизирует этот метод и браузеры не начнут реализовывать его нативно. Когда команда Prototype знает нативныйdocument.getElementsByClassName()Скоро, поэтому они добавили такой защитный код:

if (!document.getElementsByClassName) {
  document.getElementsByClassName = function (classes) {
    // 非原生实现
  };
}

Это только в прототипеdocument.getElementsByClassName()Определите его, когда не существует. Это кажется проблемой, но есть важный факт, что html5document.getElementsByClassName()Он не возвращает массив, поэтомуeach()Методов просто не существует. Нативный подход DOM использует специализированный тип коллекции, называемый NodeList.document.getElementsByClassName()Возвращает NodeList для сопоставления с другими вызовами методов DOM.

Если реализовано изначально в браузереdocument.getElementsByClassName()метод, то, поскольку NodeList не имеетeach()методы, либо родные, либо добавленные Prototypeeach()метод, вызовет ошибку JS при выполнении. Конечным результатом является то, что пользователям Prototype приходится как обновлять код библиотеки, так и изменять свой собственный код, что является кошмаром для обслуживания.

Прототип может учиться на ошибках, вы не можете точно прогнозировать, как JS изменится в будущем. Стандарты развивались, и они часто берут подсказки из библиотечного кода, такого как Prototype, для определения новых функций для следующего поколения стандартов. На самом деле, оригиналArray.prototype.forEach()Метод определен в ECMAScript5, он такой же, как и в Prototype.each()Поведение метода очень похоже. Проблема в том, что вы не знаете, чем официальная функция будет отличаться от родной, и даже небольшие отличия могут вызвать большие проблемы.

Большинство кодов библиотек JS имеют механизм плагинов, который позволяет безопасно добавлять некоторые функции в кодовую базу. Если вы хотите изменить его, лучший и наиболее удобный способ — создать плагин.

Метод не удаляется

// 不好的写法 - 删除了DOM方法
document.getElementById = null;

null, как бы он ни определялся раньше, его уже нельзя назвать. Если метод определен для экземпляра объекта (в отличие от прототипа объекта), вы также можете использоватьdeleteоператор для удаления.

var person = {
  name: 'Nicholas'
};

delete person.name;
console.log(person.name); // undefined

В приведенном выше примере атрибут name был удален из объекта person.deleteОператоры могут работать только со свойствами и методами экземпляра. если вprototypeиспользовать свойство или методdeleteне работает. Например:

// 不影响
delete document.getElementById;
console.log(document.getElementById('myelement')); // 仍然能工作

потому чтоdocument.getElementById()это метод на прототипе, используяdeleteне может быть удален. Тем не менее, вы все равно можете назначить его какnullспособ предотвратить вызов.

Излишне говорить, что метод удаления существующего объекта является плохой практикой. Разработчики, использующие этот метод, не только существуют, но и код, использующий этот метод, может уже существовать. Удаление используемого метода приведет к ошибке выполнения.Если ваша команда не должна использовать метод, пометьте его как «устаревший» либо в документации, либо с помощью статического анализатора кода. Удаление метода определенно должно быть последним средством.

И наоборот, на самом деле лучше не удалять методы объектов, которыми вы владеете. Очень сложно удалить методы из библиотечного кода или собственных объектов, потому что сторонний код полагается на эти функции. Во многих случаях код библиотеки и браузеры будут хранить ошибочные или неполные методы в течение длительного периода времени, потому что их удаление позже вызовет ошибки на бесчисленных сайтах.

2.7.3 Лучший подход

Изменение объектов, которыми вы не владеете, является хорошим решением некоторых проблем. В «чистом» состоянии этого обычно не происходит, это может произойти из-за того, что разработчик столкнулся с проблемой, а затем устранил ее, модифицировав объект. Тем не менее, у известной проблемы всегда есть более одного решения. Большая часть знаний в области информатики развивалась для решения сложных задач в средах со статическими типами языков, таких как Java. Могут существовать методы, так называемые шаблоны проектирования, которые не изменяют эти объекты напрямую, а расширяют их.

За пределами JS наиболее популярной формой расширения объектов является наследование. Если объект типа уже выполняет большую часть необходимой вам работы, наследуйте его и добавляйте некоторые функциональные возможности. В JS есть две основные формы: наследование на основе объектов и наследование на основе типов.

В JS наследование все еще имеет некоторые большие ограничения. Во-первых, вы не можете наследовать от объектов DOM или BOM. Во-вторых, из-за индексации массива иlengthСложная взаимосвязь между свойствами, унаследованная отArrayне работает должным образом.

наследование на основе объектов

При объектном наследовании, также часто называемом прототипным наследованием, объект наследует другой объект без вызова конструктора. ES5Object.create()Методы — это самый простой способ добиться этого наследования. Например:

var person = {
  name: 'Nicholas',
  sayName: function () {
    console.log(this.name);
  }
};

var myPerson = Object.create(person);
myPerson.sayName(); // "Nicholas"

В этом примере создается новый объект myPerson, унаследованный от человека. Это наследование похоже на набор прототипа myPerson для человека, из которого myPerson может получить доступ к свойствам и методам человека без необходимости снова переопределять переменные с тем же именем в новом объекте. Например, переопределилmyPerson.sayName()автоматически отключитсяperson.sayName()Доступ:

myPerson.sayName = function () {
  console.log('Anonymous');
};

myPerson.sayName(); // "Anonymous"
person.sayName(); // "Nicholas"

Object.create()方法可以指定第二个参数,该参数对象中的属性和方法将添加到新的对象中。 Например:

var myPerson = Object.create(person, {
  name: {
    value: 'Greg'
  }
});

myPerson.sayName(); // "Greg"
person.sayName(); // "Nicholas"

Объект myPerson, созданный в этом примере, имеет собственное значение свойства name, поэтому вызовитеsayName()Показывает «Грег» вместо «Николаса».

После того, как новый объект создан таким образом, новый объект полностью свободен для модификации. В конце концов, вы являетесь владельцем объекта, и вы можете произвольно добавлять методы в ваш проект, переопределять существующие методы или даже удалять методы (или блокировать доступ к ним).

thisценность . Например:

function Person (name) {
  this.name = name;
}

function Author (name) {
  Person.call(this, name); // 继承构造器
}

Author.prototype = new Person();

Person.call(this, name)thisВыполнение,this

По сравнению с наследованием на основе объектов наследование на основе типов более гибко при создании новых объектов. Определение типа позволяет создавать несколько объектов-экземпляров, каждый из которых наследуется от общего суперкласса. Новый тип должен четко определять используемые свойства и методы, которые должны полностью отличаться от свойств суперкласса.

узор фасада

Фасадный паттерн — это популярный паттерн проектирования, который создает новый интерфейс для существующего объекта. Фасад — это совершенно новый объект, за которым работает существующий объект. Фасады, иногда называемые обертками, оборачивают существующие объекты с разными интерфейсами. Если в вашем случае наследования недостаточно, логично в качестве следующего шага создать фасад.

Интерфейс DOM jQuery и yui использует фасад. Как упоминалось выше, вы не можете наследовать от объекта DOM, поэтому единственный выбор для новых функций может быть создан для фасада. Вот пример кода оболочки объекта DOM:

function DOMWrapper (element) {
  this.element = element;
}

DOMWrapper.prototype.addClass = function (className) {
  this.element.className += ' ' + className;
}

DOMWrapper.prototype.remove = function () {
  this.element.parentNode.removeChild(this.element);
}

// 用法
var wrapper = new DOMWrapper(document.getElementById('my-div'));
wrapper.addClass('selected');
wrapper.remove();

Тип DOMWrapper ожидает, что элемент DOM будет передан его конструктору. Элемент сохраняется для дальнейшего использования и определяет некоторые методы управления элементом.addClass()Способ для тех, кто еще не внедрил HTML5classListДобавлен элемент атрибутаclassNameпростой способ.remove()

.

Поскольку функции ES5 и HTML5 постепенно внедряются различными браузерами. Полифилы JS (также известные как прокладки) стали популярными. Полифилл — это имитация функции, которая полностью определена и изначально реализована в новой версии браузера. Например, ES5 добавляет к массивамforEach()функция.该方法在 ES3中有模拟实现,这样就可以在老版本浏览器中用上这个方法了。 polyfills的关键在于它们的模拟实现要与浏览器原生实现保持完全兼容。正是由于少部分浏览器原生实现这些功能,才需要尽可能的检测不同情况下它们这些功能的处理是否符合标准。

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

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

Для лучшей ремонтопригодности избегайте полифиллов и вместо этого создавайте фасады поверх существующей функциональности. Такой подход обеспечивает наибольшую гибкость, что особенно важно, когда в нативной реализации есть ошибки (избегая полифиллов). В этом случае вы не хотите использовать собственный API напрямую, иначе вы не сможете изолировать ошибки, связанные с собственной реализацией.

2.7.5 Модификация блока

ES5 представил несколько методов для предотвращения модификации объектов. Важно понимать эти возможности, чтобы теперь можно было делать такие вещи, как блокировка этих объектов, чтобы никто не мог преднамеренно или непреднамеренно изменить функциональность, которая им не нужна. Все текущие (2018 г.) браузеры поддерживают эти функции ES5 с тремя уровнями модификаций блокировки:

  • предотвратить расширение (Object.preventExtension()): запрещает «добавление» свойств и методов к объектам, но существующие свойства и методы можно изменять или удалять
  • тюлень(Object.seal()): аналогично «предотвращению расширения», но запрещает «удаление» существующих свойств и методов для объекта
  • Object.freeze()): Похожие «запечатанные», а свойства и методы запрещают «редактировать» уже существуют (все поля являются только для чтения)

Object.preventExtension()а такжеObject.isExtensible()Можно использовать две функции. ты сможешьMDNСм. использование связанных методов выше, поэтому я не буду здесь вдаваться в подробности.

Использование этих методов в ES5 — отличный способ гарантировать, что ваш проект не будет заблокирован для внесения изменений с вашего согласия. Если вы являетесь автором кодовой базы, вы, вероятно, захотите заблокировать части основной библиотеки, чтобы предотвратить их случайное изменение, или чтобы принудительно сохранить расширения, которым разрешено выживать. Если вы разработчик приложения, заблокируйте все части приложения, которые вы не хотите изменять. В обоих случаях описанный выше метод блокировки можно использовать только после определения всех функций этих объектов. Когда объект заблокирован, его нельзя разблокировать.

2.8 Браузер нюхает

Браузерный сниффинг всегда был горячей темой в области веб-разработки.Пишете ли вы JS, CSS или HTML, вы всегда будете сталкиваться с кроссбраузерной совместимостью (хотя текущая ситуация намного лучше, чем раньше, но перед лицом новых Использование интерфейса API все еще существует в случае прослушивания браузера). Далее представлена ​​история обнаружения на основе UA, чтобы объяснить, почему обнаружение UA является необоснованным.

2.8.1 Обнаружение UA

Самым ранним обнюхиванием браузера было обнаружение пользовательского агента, когда сервер (а позже и клиент) использовал строку пользовательского агента для определения типа браузера. В течение этого периода сервер будет блокировать определенные браузеры от просмотра содержимого веб-сайта полностью на основе строки пользовательского агента. Одним из браузеров, получивших наибольшую выгоду, был Netscape. Нельзя отрицать, что Netscape был самым мощным браузером (в то время), настолько, что многие веб-сайты считали, что только Netscape сможет правильно отображать их страницы. Строка пользовательского агента для браузеров Netscape:Mozilla/2.0 (Win95; I). Когда IE был впервые выпущен, он фактически был вынужден использовать большую часть строки пользовательского агента браузера Netscape, чтобы гарантировать, что сервер сможет обслуживать новый браузер. Поскольку большая часть процесса обнаружения пользовательского агента заключается в поиске строки «Mozilla» и номера версии после косой черты, строка пользовательского агента браузера IE устанавливается наMozilla/2.0 (compatible; MSIE 3.0; Windows 95), Вы чувствуете себя очень курица вор. IE принимает такую ​​строку пользовательского агента, что означает, что каждое обнаружение типа браузера также идентифицирует новый браузер как браузер Netscape Navigator. Это также приводит к тому, что новые браузеры частично копируют существующие строки пользовательского агента браузера. Строка пользовательского агента дистрибутива Chrome содержит часть строки пользовательского агента Safari, которая, в свою очередь, содержит часть строки пользовательского агента Firefox, которая, в свою очередь, содержит часть строки пользовательского агента Netscape.

Обнаружение на основе UA крайне ненадежно и сложно в обслуживании по следующим причинам:

  • UA может быть подделан, браузер, объявленный как Chrome, может быть другим браузером
  • Каждый раз, когда появляется новый браузер или обновляется существующая версия браузера, исходный код, основанный на обнаружении UA, должен обновляться, а стоимость обслуживания и вероятность ошибки чрезвычайно высоки.

Поэтому я рекомендую вам максимально избегать обнаружения UA, даже если это необходимо.

2.8.2 Обнаружение функций

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

// 不好的写法
if (navigator.userAgent.indexOf("MSIE 7") > -1) { }

// 好的写法
if (document.getElementById) {}

Поскольку обнаружение функции не зависит от используемого браузера, а только от наличия или отсутствия функции, поддержка нового браузера не обязательно требуется. Например, в первые дни DOM не все браузеры поддерживалиdocument.getElementById(), поэтому код для получения элемента на основе его идентификатора выглядит избыточным.

// 好的写法
// 仅为举例说明特性检测,现代浏览器都支持getElementById
function getById (id) {
  var el = null;

  if (document.getElementById) { // DOM
    el = document.getElementById(id);
  } else if (document.all) { // IE
    el = document.all[id];
  } else if (document.layers) { // Netscape <= 4
    el = document.layers[id];
  }

  return el;
}

Этот подход в равной степени применим к обнаружению новейших функций браузеров на сегодняшний день, которые браузеры реализовали экспериментально, и спецификация все еще находится в стадии доработки. Распространенным Polyfill является приложение для обнаружения функций, например:

if (!Array.isArray) {
  Array.isArray = function (arr) {
    return Object.prototype.toString.call(arr) === '[object Array]'
  }
}

2.8.3 избегать выводных свойств

Где один из видов неправильного использования функции обнаружения "функция вывода" (Feature Inference). Выведенные характеристики пытаются использовать несколько свойств, но проверяется только одно из них. Наличие характерного вывода Существует еще одна особенность. Вопрос в том, если предположить, что вывод неверен и может вызвать проблемы с обслуживанием. Например, ниже приведены некоторые характеристики, полученные с использованием старого кода:

// 不好的写法 - 使用特性推断
function getById (id) {
  var el = null;

  if (document.getElementsByTagName) { // DOM
    el = document.getElementById(id);
  } else if (window.ActiveXObject) { // IE
    el = document.all[id];
  } else { // Netscape <= 4
    el = document.layers[id];
  }

  return el;
}

Эта функция является наихудшим выводом признаков, когда делается несколько выводов:

  • еслиdocument.getElementsByTagName()Существует,document.getElementByIdтакже существует. На самом деле предполагается, что все методы существуют из-за существования метода DOM.
  • еслиwindow.ActiveXObjectСуществует,document.allтакже существует. Этот вывод в основном заключает, чтоwindow.ActiveXObjectОн существует только в IE, иdocument.allТакже существует только в IE, поэтому, если вы судите, что кто-то существует, другие также должны существовать. На самом деле, некоторые версии Opera также поддерживаютdocument.all.
  • Если эти выводы неверны, это должен быть Netscape Navigator 4 или более ранние версии. Это кажется правильным, но не строгим.

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

2.8.4 Избегайте выводов браузера

В какой-то момент обнаружение пользовательского агента и обнаружение функций сбивает с толку многих веб-разработчиков. Таким образом, написанный код становится таким:

// 不好的写法
if (document.all) {
  id = document.uniqueID;
} else {
  id = Math.random();
}

Проблема с этим кодом заключается в том, что при обнаруженииdocument.all, косвенно определить, является ли браузер IE. После того, как вы определили, что браузером является IE, предположим, что безопасно использовать специфичные для IE браузеры.document.uniqueID. Тем не менее, все, что вы делаете, это просто иллюстрации к документу зонда. Все, если он существует, и не может быть использован для определения того, является ли браузер IE. потому чтоdocument.allсуществование не означаетdocument.uniqueIDтакже доступен, так что это ложный неявный вывод, который может привести к неправильной работе кода.

Чтобы выразить проблему более четко, код был изменен следующим образом:

var isIE = navigator.userAgent.indexOf("MSIE") > -1;

Изменено следующим образом:

// 不好的写法
var isIE = !!document.all;

document.all

var isIE = !!document.all && document.uniqueID;

2.8.5 Как выбрать

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

До сих пор я бы не сказал никогда не использовать обнаружение пользовательского агента, потому что я верю, что есть законные варианты использования, но в то же время я не верю, что их много. Если вы хотите использовать сниффинг пользовательского агента, имейте это в виду:Единственный безопасный способ сделать это — настроить таргетинг на более старые или определенные версии браузеров. И никогда не следует ориентироваться на последнюю версию или будущие браузеры.

Моя личная рекомендация — по возможности использовать обнаружение признаков. Если вы не можете этого сделать, вы можете отступить и рассмотреть возможность использования обнаружения пользовательского агента. Никогда не используйте вывод браузера, потому что вы застрянете с плохо поддерживаемым кодом, подобным этому, и вам нужно будет постоянно обновлять его по мере выхода новых браузеров..

3. Инжиниринг

Я был бы более чем счастлив потратить целый день на программную автоматизацию задачи, если только это не займет всего 10 секунд, чтобы сделать это вручную. — Дуглас Адамс, «Последний шанс увидеть»

Фронтенд-инжиниринг постепенно привлекает внимание фронтенд-инженеров, поскольку масштаб веб-интерфейсных проектов продолжает увеличиваться.Фронтенд-инжиниринг следует рассматривать с четырех аспектов: модульность, компонентизация, стандартизация и автоматизация. Здесь я сосредоточусь на работе по автоматизации.Современные интерфейсные проекты (эра веб-приложений, представленная SPA, которая отличается от традиционной эры веб-страниц) обычно включают в себя много работы, которую необходимо автоматизировать, например:

  • Транскодирование: код ES6 конвертируется в ES5 через Babel, TS в ES5, LESS, SASS в CSS
  • Сжатие: в основном сжатие JS и CSS, включая статический ресурс (в основном изображения) сжатие
  • Слияние файлов: объединяйте несколько файлов JS или CSS, чтобы уменьшить количество HTTP-запросов.
  • Среда: процесс автоматизации среды разработки, тестовой среды и производственной среды отличается
  • Развертывание: статические ресурсы автоматически загружаются в CDN, автоматический выпуск и т. д.

Это всего лишь список некоторых работ, которые необходимо автоматизировать.В реальных ситуациях разные проекты будут иметь разные требования к настройке. Я также считаю, что в наши дни все делают это вручную, обычно с помощью инструмента сборки, такого как webpack. Для написания ремонтопригодного JS (здесь это должен быть front-end проект в более широком смысле, а не только JS), автоматизированных процессов, подобных приведенным выше (подумайте, есть ли в вашем текущем проекте какая-либо работа, требующая от вас выполнения ее вручную каждый раз , подумайте, как это автоматизировать) следует автоматизировать с помощью кода и избегать ручного вмешательства (люди допускают ошибки, а лень не является добродетелью программиста).

Фронтенд-инжиниринг — очень широкая тема, достаточно, чтобы написать еще один пост в блоге, чтобы представить, заинтересованные студенты, я рекомендую книгуФронтенд-инжиниринг: системный дизайн и практика, Эта книга была опубликована в январе 2018 года, и ее содержание также соответствует духу времени, и ее стоит внимательно смаковать. У Zhihu также есть информация о фронтенд-инжиниринге.ОбсуждатьПосмотрите на точку зрения большого кофе.

Статья впервые опубликована вмой блог, Эта бумагаМеждународная лицензия Creative Commons Attribution 4.0Лицензия.