Обход подводных камней Angular и экономия времени

Автор оригинала: Alexander Poshtaruk
  • Перевод
С помощью Angular можно сделать всё что угодно. Или почти всё. Но иногда это коварное «почти» приводит к тому, что разработчик губит время, создавая обходные решения, или пытаясь понять, почему что-то происходит, или почему что-то не работает так, как ожидается.



Автор статьи, перевод которой мы сегодня публикуем, говорит, что хочет поделиться советами, которые помогут Angular-разработчикам сэкономить немного времени. Он собирается рассказать о подводных камнях Angular, с которыми ему (и не только ему) довелось встретиться.

№1. Пользовательская директива, которую вы применили, не работает


Итак, вы обнаружили симпатичную директиву Angular сторонней разработки и решили использовать её со стандартными элементами в шаблоне Angular. Замечательно! Попробуем это сделать:

<span awesomeTooltip="'Tooltip text'"> </span>

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

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

<span [awesomeTooltip]="'Tooltip text'"> </span>

После этого, потеряв немного времени, мы видим в консоли следующее.


Вот в чём дело: мы просто забыли импортировать модуль с директивой

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

Поэкспериментировать с директивами можно здесь.

№2. ViewChild возвращает undefined


Предположим, вы создали ссылку на элемент для ввода текста, описанный в шаблоне Angular.

<input type="text" name="fname" #inputTag>

Вы собираетесь, с помощью функции RxJS fromEvent, создать поток, в который будет попадать то, что вводится в поле. Для этого вам понадобится ссылка на поле ввода, которую можно получить с помощью декоратора Angular ViewChild:

class SomeComponent implements AfterViewInit {

    @ViewChild('inputTag') inputTag: ElementRef;

    ngAfterViewInit(){
        const input$ = fromEvent(this.inputTag.nativeElement, 'keyUp')
    }
...    
}

Здесь мы, с помощью функции RxJS fromEvent, создаём поток, в который будут попадать данные, вводимые в поле.
Испытаем этот код.


Ошибка

Что случилось?

Собственно говоря, здесь применимо следующее правило: если ViewChild возвращает undefined — поищите в шаблоне *ngIf.

<div *ngIf="someCondition">
    <input type="text" name="fname" #inputTag>
</div>

Вот он — виновник проблемы.

Кроме того, проверьте шаблон на наличие в нём других структурных директив или ng-template выше проблемного элемента.
Рассмотрим возможные варианты решения этой проблемы.

▍Вариант решения проблемы №1


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

<div [hidden]="!someCondition">
    <input type="text" name="fname" #inputTag>
</div>

▍Вариант решения проблемы №2


Ещё один способ решения этой проблемы заключается в использовании сеттеров.

class SomeComponent {
  
  @ViewChild('inputTag') set inputTag(input: ElementRef|null) {
    if(!input) return;
    
    this.doSomething(input);
  }

  doSomething(input) {
    const input$ = keysfromEvent(input.nativeElement, 'keyup');
    ...
  }
}

Тут, как только Angular назначает свойству inputTag определённое значение, мы создаём поток из данных, введённых в поле ввода.

Вот пара полезных ресурсов, имеющих отношение к этой проблеме:

  • Здесь можно почитать о том, что результаты работы ViewChild в Angular 8 могут быть статическими и динамическими.
  • Если вы испытываете сложности при работе с RxJs — взгляните на этот видеокурс.

№3. Выполнение кода при обновлении списка, сгенерированного с помощью *ngFor (после того, как элементы появились в DOM)


Предположим, у вас имеется какая-нибудь интересная пользовательская директива для организации прокручиваемых списков. Вы собираетесь применить её к списку, который создан с помощью директивы Angular *ngFor.

<div *ngFor="let item of itemsList; let i = index;"
     [customScroll]
     >
  <p *ngFor="let item of items" class="list-item">{{item}}</p>
    
</div>

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

Может показаться, что это можно сделать с помощью хука ngOnChanges:

class SomeComponent implements OnChanges {
  @Input() itemsList = [];
  
  @ViewChild(CustomScrollDirective) scroll: CustomScrollDirective;

  ngOnChanges(changes) {
    if (changes.itemsList) {
      this.scroll.update();
    }
  }
...
}

Правда, тут мы встречаемся с проблемой. Хук вызывается до вывода обновлённого списка браузером. В результате пересчёт параметров директивы для организации прокрутки списка выполняется неправильно.

Как выполнить вызов сразу после того, как *ngFor завершит работу?

Сделать это можно, выполнив следующие 3 простых шага:

▍Шаг №1


Поместим ссылки на элементы туда, где применяется *ngFor (#listItems).

<div [customScroll]>
    <p *ngFor="let item of items" #listItems>{{item}}</p>
</div>

▍Шаг №2


Получим список этих элементов с помощью декоратора Angular ViewChildren. Он возвращает сущность типа QueryList.

▍Шаг №3


Класс QueryList имеет свойство changes, предназначенное только для чтения, которое выдаёт события каждый раз, когда меняется список.

class SomeComponent implements AfterViewInit {
  @Input() itemsList = [];
  
  @ViewChild(CustomScrollDirective) scroll: CustomScrollDirective;
  @ViewChildren('listItems') listItems: QueryList<any>;
  private sub: Subscription;
  
  ngAfterViewInit() {
    this.sub = this.listItems.changes.subscribe(() => this.scroll.update())
  }
...
}

Теперь проблема решена. Здесь можно поэкспериментировать с соответствующим примером.

№4. Проблемы с ActivatedRoute.queryParam, возникающие в том случае, когда запросы можно выполнять без параметров


Понять суть этой проблемы нам поможет следующий код.

// app-routing.module.ts
const routes: Routes = [
    {path: '', redirectTo: '/home', pathMatch: 'full'},
    {path: 'home', component: HomeComponent},
  ];
  
  @NgModule({
    imports: [RouterModule.forRoot(routes)], // Фрагмент #1
    exports: [RouterModule]
  })
  export class AppRoutingModule { }
  
  
  //app.module.ts
  @NgModule({
    ...
    bootstrap: [AppComponent] // Фрагмент #2
  })
  export class AppModule { }
  
  // app.component.html
  <router-outlet></router-outlet> // Фрагмент #3
  
  
  // app.component.ts
  export class AppComponent implements OnInit {
    title = 'QueryTest';
  
    constructor(private route: ActivatedRoute) { }
  
    ngOnInit() {
      this.route.queryParams
          .subscribe(params => {
            console.log('saveToken', params); // Фрагмент #4
          });
    }
  }

К некоторым фрагментам этого кода сделаны комментарии вида Фрагмент #x. Рассмотрим их:

  1. В главном модуле приложения мы определили маршруты и добавили туда RouterModule. Маршруты настроены так, что если в URL не предоставлен маршрут, мы перенаправляем пользователя на страницу /home.
  2. В качестве компонента для загрузки мы указываем в главном модуле AppComponent.
  3. AppComponent использует <router-outlet> для вывода соответствующих компонентов маршрута.
  4. Теперь — самое важное. Нам нужно получить queryParams для маршрута из URL

Предположим, что нам достался такой URL:

https://localhost:4400/home?accessToken=someTokenSequence

В таком случае queryParams будет выглядеть так:

{accessToken: ‘someTokenSequence’}

Посмотрим на работу всего этого в браузере.


Тестирование приложения, в котором реализована система маршрутизации

Тут у вас может появиться вопрос о сути проблемы. Параметры мы получили, всё работает как ожидается…

Присмотритесь к приведённой выше копии экрана браузера, и к тому, что выводится в консоль. Тут можно заметить, что объект queryParams выдаётся дважды. Первый объект оказывается пустым, он выдаётся в ходе процесса инициализации маршрутизатора Angular. Только после этого мы получаем объект, в котором содержатся параметры запроса (в нашем случае — {accessToken: ‘someTokenSequence’}).

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


Второй объект при выполнении запроса без параметров не выдаётся

В результате оказывается, что если код ожидает второго объекта, из которого он может получить данные запроса, то он не будет запущен в том случае, если в URL не было параметров запроса.

Как решить эту проблему? Здесь нам может помочь RxJs. Мы создадим на основе ActivatedRoute.queryParams два наблюдаемых объекта. Как обычно — рассмотрим пошаговое решение проблемы.

▍Шаг №1


Первый наблюдаемый объект, paramsInUrl$, будет выдавать данные в том случае, если значение queryParams не является пустым:

export class AppComponent implements OnInit {

    constructor(private route: ActivatedRoute,
                private locationService: Location) {
    }

    ngOnInit() {
      
        // Отфильтровываем первое пустое значение
        // И повторно выдаём значение с параметрами
        const paramsInUrl$ = this.route.queryParams.pipe(
            filter(params => Object.keys(params).length > 0)
        );

     ...

    }
}

▍Шаг №2


Второй наблюдаемый объект, noParamsInUrl$, будет выдавать пустое значение только в том случае, если в URL не было обнаружено параметров запроса:

export class AppComponent implements OnInit {
    title = 'QueryTest';

    constructor(private route: ActivatedRoute,
                private locationService: Location) {
    }

    ngOnInit() {
      
            ...

        // Выдаёт пустой объект, но только в том случае, если в URL
        // не было обнаружено параметров запроса
        const noParamsInUrl$ = this.route.queryParams.pipe(
            filter(() => !this.locationService.path().includes('?')),
            map(() => ({}))
        );

            ...
    }
}

▍Шаг №3


Теперь скомбинируем наблюдаемые объекты с помощью функции RxJS merge:

export class AppComponent implements OnInit {
    title = 'QueryTest';

    constructor(private route: ActivatedRoute,
                private locationService: Location) {
    }

    ngOnInit() {
      
        // Отфильтровываем первое пустое значение
        // И повторно выдаём значение с параметрами
        const paramsInUrl$ = this.route.queryParams.pipe(
            filter(params => Object.keys(params).length > 0)
        );

        // Выдаёт пустой объект, но только в том случае, если в URL
        // не было обнаружено параметров запроса
        const noParamsInUrl$ = this.route.queryParams.pipe(
            filter(() => !this.locationService.path().includes('?')),
            map(() => ({}))
        );

        const params$ = merge(paramsInUrl$, noParamsInUrl$);

        params$.subscribe(params => {
            console.log('saveToken', params);
        });

    }
}

Теперь наблюдаемый объект param$ выдаёт значение лишь один раз — независимо от того, содержится ли что-нибудь в queryParams (выдаётся объект с параметрами запроса) или нет (выдаётся пустой объект).

Поэкспериментировать с этим кодом можно здесь.

№5. Медленная работа страниц


Предположим, у вас имеется компонент, который выводит некие отформатированные данные:

// home.component.html
<div class="wrapper" (mousemove)="mouseCoordinates = {x: $event.x, y: $event.y}">

  <div *ngFor="let item of items">
  
    <span>{{formatItem(item)}}</span>

  </div>

</div>

{{mouseCoordinates | json}}



// home.component.ts
export class HomeComponent {

    items = [1, 2, 3, 4, 5, 6];

    mouseCoordinates = {};

    formatItem(item) {
       
        // Имитация тяжёлых вычислений
        const t = Array.apply(null, Array(5)).map(() => 1); 
      
        console.log('formatItem');
        return item + '%';
    }

}

Этот компонент решает две задачи:

  1. Он выводит массив элементов (предполагается, что эта операция выполняется однократно). Кроме того, он форматирует то, что выводится на экран, вызывая метод formatItem.
  2. Он выводит координаты мыши (это значение, очевидно, будет обновляться очень часто).

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


Много вызовов formatItem и довольно большая нагрузка на процессор

В чём же дело? А дело в том, что когда Angular перерисовывает шаблон, он вызывает и все функции из шаблона (в нашем случае — функцию formatItem). В результате, если в функциях шаблона выполняются какие-нибудь тяжёлые вычисления, это создаёт нагрузку на процессор и влияет на то, как пользователи будут воспринимать соответствующую страницу.

Как это исправить? Достаточно выполнить вычисления, выполняемые в formatItem, заранее, и вывести на страницу уже готовые данные.

// home.component.html
<div class="wrapper" (mousemove)="mouseCoordinates = {x: $event.x, y: $event.y}">
  <div *ngFor="let item of displayedItems">
    <span>{{item}}</span>
  </div>
</div>

{{mouseCoordinates | json}}


// home.component.ts
@Component({
    selector: 'app-home',
    templateUrl: './home.component.html',
    styleUrls: ['./home.component.sass']
})
export class HomeComponent implements OnInit {

    items = [1, 2, 3, 4, 5, 6];
    displayedItems = [];

    mouseCoordinates = {};

    ngOnInit() {
        this.displayedItems = this.items.map((item) => this.formatItem(item));
    }

    formatItem(item) {
        console.log('formatItem');
        const t = Array.apply(null, Array(5)).map(() => 1);
        return item + '%';
    }
}

Теперь тест производительности выглядит гораздо приличнее.


Всего 6 вызовов formatItem и низкая нагрузка на процессор

Теперь приложение работает гораздо лучше. Но у применённого здесь решения есть некоторые особенности, не всегда приятные:

  • Так как мы выводим координаты мыши в шаблоне — возникновение события mousemove всё ещё приводит к запуску проверки изменений. Но, так как нам нужны координаты мыши, избавиться от этого мы не можем.
  • Если же в обработчике события mousemove должны лишь выполняться некие вычисления (которые не влияют на то, что выводится на странице), тогда, чтобы ускорить приложение, можно поступить следующим образом:

    1. Можно, внутри функции-обработчика события, использовать NgZone.runOutsideOfAngular. Это позволяет предотвратить запуск проверки изменений при возникновении события mousemove (это повлияет исключительно на данный обработчик).
    2. Можно предотвратить zone.js-патч для некоторых событий, использовав следующую строку кода в polyfills.ts. Это подействует на всё Angular-приложение.

* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // отключить патч для указанных событий

Если вас интересуют вопросы повышения производительности Angular-приложений — вот, вот, вот и вот — полезные материалы об этом.

Итоги


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

Уважаемые читатели! Знаете ли вы о чём-то таком, что, при разработке Angular-приложений, помогает экономить время?

RUVDS.com
909,75
RUVDS – хостинг VDS/VPS серверов
Поделиться публикацией

Комментарии 14

    +3
    Я как-то не очень согласен с названием статьи, т.к. с каких пор подводными камнями фреймворка считается глупость и невнимательность лиц, использующих его

    п1. Ты сам забыл импортировать то, что собираешься использовать (https://angular.io/api/core/NgModule#imports)

    п2. Ты запрашиваешь из вьюхи элемент, которого нет на странице (https://angular.io/api/core/ViewChild#example-3)

    п3. Ты или не додумался сделать сеттер на Input и трекать там изменения, если они приходят из родителя, или сделать это по завершению http запроса и т.п.

    п4. Выдавать за проблемы то, что вообще вроде-бы как и не проблема

    п5. Ты сам на каждый вызов функции делаешь 100500 раз одинаковые вычисления, не почитав, как работает change detection

    facepalm.jpg
      0

      Вот не надо кривой переусложнённый дизайн фреймворка прикрывать "глупостью" пользователей. Вот, смотрите, как те же проблемы выглядят, например, в $mol:


      1) Пользовательская директива, которую вы применили, не работает

      В $mol нет директив. Есть только композиция компонент. Соответственно "забыть" директиву невозможно. А даже если бы директивы и были, то забыть импортировать их тоже было бы невозможно, так как вручную в $mol ничего не импортируется. Написал директиву — её код включился в бандл автоматически, благодаря MAM. Не написал — не включился. Всё просто, надёжно и не требует от разработчика помнить об этой рутине.


      2) ViewChild возвращает undefined

      Так как все вложенные элементы в $mol компонентах объявляются как ленивые фабрики типа:


      @ $mol_mem
      Input() {
          return $mol_string.make({
              // overrides
          })
      }

      То получить экземпляр элемента можно в любой момент, даже если приложение ещё не запущено или инпут не рендерится:


      this.Input() // компонент инпута
      this.Input().dom_node() // дом-элемент инпута

      3) Выполнение кода при обновлении списка, сгенерированного с помощью *ngFor

      В $mol благодаря ОРП зависимости трекаются автоматически, так что никакая ручная синхронизация состояний не требуется.


      4) Проблемы с ActivatedRoute.queryParam, возникающие в том случае, когда запросы можно выполнять без параметров

      В $mol все зависимые состояния объявляются как инварианты от своих зависимостей. Система реактивности же гарантирует, что вычисленное значение будет ровно одно и ровно в ожидаемом виде. Выглядит это примерно так:


      token() {
          return this.$.$mol_state_arg.value( 'accessToken' )
      }

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


      5) Медленная работа страниц

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


      image

        0
        Написал директиву — её код включился в бандл автоматически

        Что происходит, когда есть несколько директив с одним именем?


        То получить экземпляр элемента можно в любой момент, даже если приложение ещё не запущено или инпут не рендерится:

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


        В $mol благодаря ОРП зависимости трекаются автоматически, так что никакая ручная синхронизация состояний не требуется.

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


        В $mol все зависимые состояния объявляются как инварианты от своих зависимостей. Система реактивности же гарантирует, что вычисленное значение будет ровно одно и ровно в ожидаемом виде.

        Вы даже не поняли, в чем проблема. queryParams — это observable, так что он точно так же реактивно эмитит значения.


        В $mol ререндер одного элемента не приводит к ререндеру другого.

        Представьте себе — в ангуляре тоже. В приведенном примере нет никаких проблем с ререндером. На самом деле, можно вообще в mousemove заменить значения на константы и рендериться в принципе ничгео не будет. А тормозить — будет.

          +1
          Что происходит, когда есть несколько директив с одним именем?

          Вы это просто не сможете сделать, так как все имена глобально уникальны.


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

          Можете совершать с ним любые действия.
          О какого рода ошибках идёт речь?


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

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


          Вы даже не поняли, в чем проблема. queryParams — это observable, так что он точно так же реактивно эмитит значения.

          А вы даже не поняли, что в $mol нет ни обсерваблов, ни эмитов значеий, ни этой проблемы.


          Представьте себе — в ангуляре тоже. В приведенном примере нет никаких проблем с ререндером.

          А давайте не спорить о терминах?


          На самом деле, можно вообще в mousemove заменить значения на константы и рендериться в принципе ничгео не будет. А тормозить — будет.

          А в $mol тормозить не будет.

            0
            Вы это просто не сможете сделать, так как все имена глобально уникальны.

            В смысле не могу? Могу. Вот просто взял и объявил две директивы с одним именем. Что произойдет, как ваш фреймворк реагирует на такую ситуацию?


            Можете совершать с ним любые действия.

            Какие любые действия я могу с ним совершать, если элемента нет? С чем я буду их совершать?


            О какого рода ошибках идёт речь?

            Строка кода выполнена, но действие над элементом не совершено, т.к. элемента еще в тот момент не было.


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

            Нет, это она как раз отследить могла. Она не могла отследить, что список перерендерился.


            А вы даже не поняли, что в $mol нет ни обсерваблов, ни эмитов значеий, ни этой проблемы.

            Как вы можете утверждать что такой проблемы нет, если вы не поняли, в чем вообще была проблема?


            А давайте не спорить о терминах?

            Речь не о терминах а о смысле.


            А в $mol тормозить не будет.

            Будет что-то другое.

      +1
      В смысле не могу? Могу. Вот просто взял и объявил две директивы с одним именем. Что произойдет, как ваш фреймворк реагирует на такую ситуацию?

      Тайпскрипт упадёт с ошибкой.


      Какие любые действия я могу с ним совершать, если элемента нет? С чем я буду их совершать?

      Он создаётся в момент обращения.


      Строка кода выполнена, но действие над элементом не совершено, т.к. элемента еще в тот момент не было.

      Боюсь это не возможно.


      Будет что-то другое.

      И что же?

        0
        Тайпскрипт упадёт с ошибкой.

        1. А если тайпскрипта нет?
        2. Даже если тайпскрипт — он с ошибкой в таких случаях не падает
        3. А если бы падал — это кто-то в какой-то зависимости зависимости объявил директиву и теперь мне в своем проекте надо свою переименовывать, т.к. иначе — "падает"? Очень удобно.

        Он создаётся в момент обращения.

        Нет, не создает, т.к. в момент обращения еще неизвестно, что там за объект.


        Боюсь это не возможно.

        Ну либо ошибка либо действие не выполнено. Других вариантов нет.


        И что же?

        Это вы мне скажите.

          +1
          А если тайпскрипта нет?

          В $mol его не может не быть.


          Даже если тайпскрипт — он с ошибкой в таких случаях не падает

          При объявлении одноимённых классов? Ещё как падает.


          А если бы падал — это кто-то в какой-то зависимости зависимости объявил директиву и теперь мне в своем проекте надо свою переименовывать, т.к. иначе — "падает"? Очень удобно.

          Свои сущности именуются в своём неймспейсе.


          Нет, не создает, т.к. в момент обращения еще неизвестно, что там за объект.

          Известно.


          Ну либо ошибка либо действие не выполнено. Других вариантов нет.

          О какого рода действиях идёт речь?


          Это вы мне скажите.

          Как только найду — я вам сообщу.

            0
            При объявлении одноимённых классов? Ещё как падает.

            В разных модулях? Не падает.


            Известно.

            Откуда? Не известно ничего. Какая дом-нода там будет, может зависеть, например, от фазы луны (в смысле натурально от фазы луны, без преувеличений).


            О какого рода действиях идёт речь?

            Действиях с дом-нодой, которой нет.


            Как только найду — я вам сообщу.

            То есть, вы не знаете граничных кейсов, на которых ваш фреймворк дает плохую производительность? Печально.

              0
              В разных модулях? Не падает.

              В $mol не используются js-modules. Там весь код объявляется примерно так:


              namespace $ {
                  export class $my_component {}
              }

              Откуда?

              Благодаря pull семантике с мемоизацией. Я же писал в самом начале как объявляются свойства: https://habr.com/ru/company/ruvds/blog/459304/#comment_20382138
              Дом-элемент точно так же создаётся при первом обращении и запоминается.


              То есть, вы не знаете граничных кейсов, на которых ваш фреймворк дает плохую производительность?

              А что такое "плохая производительность"?

                +1
                Благодаря pull семантике с мемоизацией.

                Но информации об объекте-то нет. Ее в принципе нет и быть не может, т.к. до непосредственно исполнение неизвестно, в какой фазе луны запущен скрипт.


                Дом-элемент точно так же создаётся при первом обращении и запоминается.

                Какой элемент? Мы же до того, как элемент будет создан, этого не знаем и узнать не можем.

                  0

                  О каких ещё фазах луны идёт речь? Тут всё предельно детерминировано.


                  Элемент создаётся в момент обращения к нему. Даже не знаю, как это объяснить ещёпонятней.

                    0
                    О каких ещё фазах луны идёт речь?

                    Об обычных. В зависимости от фазы там будет либо элемент Х либо элемент Y либо вообще не будет никакого элемента.
                    Какой именно элемент вы тогда собираетесь "детерменировано" создавать во время "обращения"?

                      0

                      Вы какие-то странные вещи говорите. Не может там быть разных элементов. Архитектура такая.

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

      Самое читаемое