[Перевод] 5 интересных JavaScript-находок, сделанных в исходном коде Vue

Чтение исходного кода известных фреймворков может хорошо помочь программисту в улучшении его профессиональных навыков. Автор статьи, перевод которой мы сегодня публикуем, недавно анализировал код vue2.x. Он нашёл в этом коде некоторые интересные JavaScript-идеи, которыми решил поделиться со всеми желающими.

rjid7bottz6bd-zfoz7cx83ljgy.jpeg

1. Определение точного типа любого объекта


Как все мы знаем, в JavaScript существует шесть примитивных типов данных (Boolean, Number, String, Null, Undefined, Symbol) и один объектный тип — Object. А вам известно о том, как различать типы различных объектных значений? Объект может быть массивом или функцией, он может представлять собой коллекцию значений Map или что-то ещё. Что нужно сделать для того чтобы узнать точный тип объекта?

Прежде чем искать ответ на этот вопрос — подумаем о разнице между Object.prototype.toString.call(arg) и String(arg).

Использование этих выражений направлено на преобразование переданного им параметра в строку. Но работают они по-разному.

При вызове String(arg) система попытается вызвать arg.toString() или arg.valueOf(). В результате, если в arg или в прототипе arg будут перезаписаны эти методы, вызовы Object.prototype.toString.call(arg) и String(arg) дадут разные результаты.

Рассмотрим пример.

const _toString = Object.prototype.toString
var obj = {}
obj.toString()  // [object Object]
_toString.call(obj) // [object Object]


Выполним этот код в консоли инструментов разработчика браузера.

1a224e4258505bc0e58d4da5b1c45db4.png


В данном случае вызовы obj.toString() и Object.prototype.toString.call(obj) приводят к одним и тем же результатам.

Вот ещё пример.

const _toString = Object.prototype.toString
var obj = {}
obj.toString = () => '111'
obj.toString()  // 111
_toString.call(obj) // [object Object]
/hello/.toString() // /hello/
_toString.call(/hello/) // [object RegExp]


Выполним код в консоли.

0dec7bf70203abe5660d79553ded248f.png


А теперь вызов метода объекта .toString() и использование конструкции Object.prototype.toString.call(obj) дают разные результаты.

Вот какие правила описывают в стандарте ECMAScript поведение метода Object.prototype.toString().

cd09f75037f1112044a2831a692141ba.png


Описание метода Object.prototype.toString() в стандарте ECMAScript

Взглянув на документацию, можно сделать вывод о том, что при вызове Object.prototype.toString() для разных объектов будут возвращаться различные результаты.

Исследуем эту идею в консоли.

533b423e98a59a7f2d9ee090dc6fa6fb.png


Кроме того, значение, возвращаемое Object.prototype.toString(), всегда представлено в следующем формате:

‘[object ’ + ‘tag’ +‘] ’


Если нам нужно извлечь из этой конструкции только часть tag, добраться до этой части можно, удалив ненужные символы из начала и конца строки с помощью регулярного выражения или метода String.prototype.slice().

function toRawType (value) {
    const _toString = Object.prototype.toString
    return _toString.call(value).slice(8, -1)
}
toRawType(null) // "Null"
toRawType(/sdfsd/) //"RegExp"


Исследуем эту функцию в консоли.

fc1a32de9755f29b2abf5818413eb172.png


Как видите, с помощью вышеприведённой функции можно узнать точный тип объектной переменной.

→ Здесь, в репозитории Vue, можно найти код подобной функции.

2. Кеширование результатов работы функции


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

function computed(str) {    
    console.log('2000s have passed')
    return 'a result'
}


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

Можно, например, написать функцию-обёртку для целевой функции. Такой функции можно дать имя cached. Эта функция принимает, в виде аргумента, целевую функцию, и возвращает новую функцию, оснащённую возможностями кеширования. В функции cached можно кешировать результаты предыдущих вызовов целевой функции, воспользовавшись сущностью Object или Map. Вот код этой функции:

function cached(fn){
  // Создаём объект для хранения результатов, возвращаемых целевой функцией после её выполнения
  const cache = Object.create(null);

  // Возвращаем целевую функцию в соответствующей обёртке
  return function cachedFn (str) {

    // Если в кеше нет подходящих результатов - функция будет выполнена
    if ( !cache[str] ) {
        let result = fn(str);

        // Сохраняем результат выполнения функции в кеше
        cache[str] = result;
    }

    return cache[str]
  }
}


Вот пример использования вышеописанной функции.

8879712102bbf72d9ba1a9df97ae8488.png


→ Вот код подобной функции, который имеется в кодовой базе Vue.

3. Преобразование строки вида hello-world к строке вида helloWorld


Когда над одним и тем же проектом совместно работает несколько программистов, им очень важно заботиться о единообразии стиля кода. Кто-то, например, может записывать некие составные идентификаторы в формате helloWorld, а кто-то — в формате hello-world. Для того чтобы навести в этой области порядок, можно создать функцию, которая преобразует строки вида hello-world к строкам вида helloWorld.

const camelizeRE = /-(\w)/g
const camelize = cached((str) => {
  return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
})
camelize('hello-world')
// "helloWorld"


→ Вот то место кода Vue, откуда взят этот пример.

4. Определение того, в каком именно окружении выполняется JavaScript-код


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

const inBrowser = typeof window !== 'undefined'
const inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform
const weexPlatform = inWeex && WXEnvironment.platform.toLowerCase()
const UA = inBrowser && window.navigator.userAgent.toLowerCase()
const isIE = UA && /msie|trident/.test(UA)
const isIE9 = UA && UA.indexOf('msie 9.0') > 0
const isEdge = UA && UA.indexOf('edge/') > 0
const isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android')
const isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios')
const isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge
const isPhantomJS = UA && /phantomjs/.test(UA)
const isFF = UA && UA.match(/firefox\/(\d+)/)


→ Вот где я нашёл этот код.

5. Различение встроенных и пользовательских функций


Известно, что в JavaScript существует два вида функций. Первый вид — это встроенные, или, как их ещё называют, «нативные» функции. Такие функции даёт нам среда, в которой выполняется код. Второй вид — это так называемые «пользовательские функции», то есть те, которые программисты пишут сами. Различить эти функции можно, учтя тот факт, что, при преобразовании их в строки, возвращаются различные результаты.

Array.isArray.toString() // "function isArray() { [native code] }"
function fn(){} 
fn.toString() // "function fn(){}"


Поэкспериментируем с этим кодом в консоли.

defe8f3457c598647b951562c96aeb0b.png


Метод toString() нативной функции всегда возвращает конструкцию следующего вида:

function fnName() { [native code] }


Зная это, можно написать функцию, позволяющую различать нативные и пользовательские функции:

function isNative (Ctor){
  return typeof Ctor === 'function' && /native code/.test(Ctor.toString())
}


→ Вот то место в кодовой базе Vue, где есть такая функция.

А вам удавалось находить что-то интересное, исследуя код в репозиториях известных JavaScript-проектов?

a_bsaactpbr8fltzymtkhqbw1d4.png

© Habrahabr.ru