Мутируем иммутабельное, переписываем имена

Многие, кто работает с JS, знают, что все есть объект в JS, а что не объект, то с этим можно работать как с объектом. Поэтому мы можем в JS вытворять такие штуки:

30..toString(16) // = 1e
"abc".length // = 3

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

let a = [1,2,3];
let s = "123";

a.length = 2;
a; // [1,2]

s.length = 2;
s; // TypeError: Cannot assign to read only property 'length' of string '123'

Хотя было бы логично же сделать поведение как и в массивах, да?

И вот одна из интересных задач, которую можно встретить не только на собеседовании, но и в жизни:

function foo(){}
var foo = function (){};
var foo = function bar(){};

Очень интересный, но уже стар как мир, вопрос: в чем различия?

Надо ли рассказывать в чем? Если надо раскрыть задачи такого плана— напишите в комментариях. Сейчас в 2х словах для тех, кто только изучает JS или пришел из других языков:

function foo(){}
// всплывет, доступна перед объявлением по foo(),
// function.name = 'foo',
// внутри тела доступна по foo() для рекурсии

var foo = function (){};
// всплывет var foo, доступна только после присвоения по foo(), 
// function.name = 'foo',
// внутри тела доступна по foo() для рекурсии

var foo = function bar(){};
// всплывет var foo, доступна только после присвоения по foo(), 
// function.name = 'bar',
// внутри тела функции доступна как по bar()
// так и по foo() для рекурсивного вызова

Очень кратко. Надо будет подробнее — распишу или ищите в документации. Так вот, к чему этот вопрос? Видите вот этот загадочный function.name ?

Для тех, кто не в курсе, имя функции можно получить через свойство name:

function foo(){}
console.log( foo.name ) // 'foo'

var foo = function(){}
console.log( foo.name ) // 'foo'

var foo = function bar(){}
console.log( foo.name ) // 'bar'

Ну и что? Зачем это нам и где это используется?

А это используется в том же console.log() , вы можете просто написать:

function foo(){}
console.log( foo ) // '[Function: foo]'

var foo = function(){}
console.log( foo ) // '[Function: foo]'

var foo = function bar(){}
console.log( foo ) // '[Function: bar]'

Вы обратили внимание что в нашем случае вроде бы анонимная функция всеравно имеет имя? Но что если:

function some(fn) {
  console.log(fn) // '[Function]'
  console.log(fn.name) // ''
}

some(function(){
  // some callback
});

И вот у вас весь стек трейс из таких вот анонимных [Function]. Что это? Откуда? И приходится гадать и распутывать. Что можно сделать, чтобы добавить имя? Все верно, присвоить в переменную или дать имя:

function some(fn) {
  console.log(fn) // '[Function: foo]'
  console.log(fn.name) // 'foo'
}

var foo;
some(foo = function(){
  // some callback
});

Это экзотика какая-то, кто так пишет, скажете вы. Ну да, лучше писать так:

some(function foo(){
  // some callback
});

И я полностью согласен. Подписывать (давать имена) функции — это полезно для отладки.

Задачка

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

const foo = function bar() {}
console.log(foo.name)

foo.name = 'lol';
console.log(foo.name) // ???

Что будет? Можем переименовать функцию?

Ответ:

  1. foo.name = 'bar'
  2. foo.name = 'lol' — TypeError: Cannot assign to read only property ‘name’ of function ‘function bar() {}’

Выходит что нельзя? А если прям ну очень надо?

Ну если “ну очень надо”, то можно. В мире нет ничего невозможного :)

const foo = function bar() {}
console.log(foo.name)

Reflect.defineProperty(foo,'name',{value:'lol'});

console.log(foo.name) // 'lol'
console.log(foo) // [Function: lol]

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

Если недоступен Reflect, то используйте Object:

Object.defineProperty(foo,'name',{value:'lol'});

Смысл тот же.

P.S.:

Я удивлен, как люди засирают NPM пакетами из 2–3 строк. Какие же это, матьево, пакеты? Я понимаю прелесть переиспользования логики, но если бы они хотя бы были сгруппированы в пакеты. Тот же leftpad, который вызвал пару лет назад бурю негодования сообщества и все кричали что мы разучились программировать, имеет хотя бы 47 строк кода. Но когда я наткнулся на этот “пакет”: https://github.com/sindresorhus/rename-fn

Я был в шоке… А какой следующий шаг? Экспортироватьть нативные функции? Этот модуль для переименования функции состоит из 1 строчки:

module.exports = (fn, name) => Object.defineProperty(fn, 'name', {value: name, configurable: true});

Друзья!.. Коллеги!.. Ребята! Не делайте так! Если хочется поделиться с миром — напишите статью, пост в твиттере, но не пульте в NPM такие вещи. Не надо вам в зависимости такой пакет. Вообще не нужна функция, используйте сразу вызов Reflect.defineProperty() с нужными параметрами на нужном объекте и не заворачивайте это все в функции. Зачем?