読者です 読者をやめる 読者になる 読者になる

abcdefGets

ゲッツ!

V8 の For In の話

V8 blogに話が出ていたが、V8のfor in構文が高速化したらしいので、
コードと共におっていこうと思う。

For In とは

for (const key in object) {

}

このような構文でObjectのキーをイテレーションする機能。
この構文を何故高速化したかというと、Facebookのコード実行の7% Wikipediaのコード実行の8%をForIn構文が占めていたらしく、
実行に負荷のかかる部分だったらしい。

一見この構文は単純に見えるが、処理系が行わなければならない仕事は以外に多く複雑だ。

まずはForIn構文の定義を見てみよう。上記のBlogにかかれているSpecを抜粋する。

EnumerateObjectProperties ( O ) 抽象命令であるEnumerateObjectPropertiesが引数Oを伴って呼び出される時、以下のステップをとる。
1. Type(O)がObjectか確認する。
2. Iteratorを返却する。IteratorのnextメソッドはOのenumerableな文字列のプロパティを全て列挙する。
IteratorオブジェクトはECMAScriptのコード中からアクセス不能である。プロパティの列挙順及びその方法は特に規定しないが、以下の規定に沿わなければならない。
A. Iteratorのthrowとreturnメソッドはnullであり、決して呼び出されない。
B. Iteratorのnextメソッドはオブジェクトのプロパティを処理して、次にiteratorの値として返すプロパティのキーを決める。
C. 戻り値のプロパティキーはSymbolを含まない
D. ターゲットのオブジェクトのプロパティは列挙中に削除されるかもしれない。
E. Iteratorのnextメソッドに処理される前に削除されたプロパティは無視される。もし、列挙中に新たなプロパティが追加された場合、新たに追加されたプロパティは現在の列挙中に処理される保証はない。
F. プロパティ名は列挙中に一度しかIteratorのnextメソッドによって返却されない。
G. 列挙するターゲットのオブジェクトのプロパティは、ターゲットのprototype、そのprototypeのprototypeも含むが、一度でも列挙されたプロパティのキーと同じキーを含んだprototypeのキーは列挙されない。(シャドウイングされたprototypeは列挙されない)
H. ptototypeのプロパティが既に処理されていた場合、[[Enumerable]]属性は考慮しない。
I. prototypeの列挙可能プロパティ名を取得する場合は、必ず、EnumerateObjectPropertiesをprototypeを引数に呼び出して取得しなければならない。
J. EnumerateObjectPropertiesはターゲットオブジェクトのプロパティを[[OwnPropertyKeys]]内部メソッドを呼び出して取得しなければならない。

かなり要件が複雑なのがわかる。

概要

Blogから抜粋

Map                               Descriptors              Enum Cache
--------------------------   ->   -------------------   -> -----------------  -> -------
|enumLength        |  3  |   |    | length    |   3 |   |  | Keys    | ptr | -|  | 'a' |
--------------------------   |    -------------------   |  -----------------     -------
|nofOwnDescriptors |  3  |   |    | EnumCache | ptr | --|  | indices | ptr |     | 'b' |
--------------------------   |    -------------------      -----------------     -------
|DescriptorArray   | ptr |---     | ...       | ... |                            | 'c' |
--------------------------        -------------------                            -------

MapのDescriptorが持っているEnumCacheからプロパティのキーリストを取得するようにしたので、高速になったらしい。

実装

さて実装はどうなっているか。

V8のForInはRuntime呼び出しで実装されている。
試しにd8で確認すると…

./d8 --print_code -e "for (const i in {__proto__: {a:1}}){}"

この行でForInEnumerateをRuntimeから呼び出しているのが確認できる。

0x1cb5dca0476e   398  b801000000     movl rax,0x1
0x1cb5dca04773   403  48bbc0ddc70001000000 REX.W movq rbx,0x100c7ddc0    ;; external reference (Runtime::ForInEnumerate)
0x1cb5dca0477d   413  e81efadfff     call 0x1cb5dc8041a0     ;; code: STUB, CEntryStub, minor: 8

ではRuntime::ForInEnumerateを確認しよう。

ちなみにMapとかV8の基礎情報は V8祭り を参照してほしい。

では確認ー

ForInEnumerateはsrc/runtime/runtime-forin.ccにある。

Runtime::ForInEnumerateを確認する前にこのファイルにある他のRuntimeも確認しておく。

このRuntimeに用意されているのは、

  • Runtime_ForInEnumerate
  • Runtime_ForInPrepare
  • Runtime_ForInHasProperty
  • Runtime_ForInFilter

の4つ。
ForInEnumerate以外はIgnitionインタープリタから呼び出されているようだが、今回はFullCodegenから呼び出されるコードをメインに見ていく。

RUNTIME_FUNCTION(Runtime_ForInEnumerate) {
  HandleScope scope(isolate);
  DCHECK_EQ(1, args.length());
  CONVERT_ARG_HANDLE_CHECKED(JSReceiver, receiver, 0);
  RETURN_RESULT_OR_FAILURE(isolate, Enumerate(receiver));
}

これがForInEnumerateの本体だ。
中ではさらにEnumerateをreceiverを引数に呼び出している。

次はEnumerate

// Returns either a FixedArray or, if the given {receiver} has an enum cache
// that contains all enumerable properties of the {receiver} and its prototypes
// have none, the map of the {receiver}. This is used to speed up the check for
// deletions during a for-in.
MaybeHandle<HeapObject> Enumerate(Handle<JSReceiver> receiver) {
  Isolate* const isolate = receiver->GetIsolate();
  JSObject::MakePrototypesFast(receiver, kStartAtReceiver, isolate);
  FastKeyAccumulator accumulator(isolate, receiver,
                                 KeyCollectionMode::kIncludePrototypes,
                                 ENUMERABLE_STRINGS);
  accumulator.set_is_for_in(true);
  // Test if we have an enum cache for {receiver}.
  if (!accumulator.is_receiver_simple_enum()) {
    Handle<FixedArray> keys;
    ASSIGN_RETURN_ON_EXCEPTION(
        isolate, keys, accumulator.GetKeys(GetKeysConversion::kKeepNumbers),
        HeapObject);
    // Test again, since cache may have been built by GetKeys() calls above.
    if (!accumulator.is_receiver_simple_enum()) return keys;
  }
  return handle(receiver->map(), isolate);
}

さて、このEnumerateで注目したいのは、FastKeyAccumulatorである。このFastKeyAccumulatorがオブジェクトのprototypeの情報、プロパティの情報を取得し、
最適な処理に振り分けている。

void FastKeyAccumulator::Prepare() {
  DisallowHeapAllocation no_gc;
  // Directly go for the fast path for OWN_ONLY keys.
  if (mode_ == KeyCollectionMode::kOwnOnly) return;
  // Fully walk the prototype chain and find the last prototype with keys.
  is_receiver_simple_enum_ = false;
  has_empty_prototype_ = true;
  JSReceiver* last_prototype = nullptr;
  for (PrototypeIterator iter(isolate_, *receiver_); !iter.IsAtEnd();
       iter.Advance()) {
    JSReceiver* current = iter.GetCurrent<JSReceiver>();
    bool has_no_properties = CheckAndInitalizeEmptyEnumCache(current);
    if (has_no_properties) continue;
    last_prototype = current;
    has_empty_prototype_ = false;
  }
  if (has_empty_prototype_) {
    is_receiver_simple_enum_ =
        receiver_->map()->EnumLength() != kInvalidEnumCacheSentinel &&
        !JSObject::cast(*receiver_)->HasEnumerableElements();
  } else if (last_prototype != nullptr) {
    last_non_empty_prototype_ = handle(last_prototype, isolate_);
  }
}

これがFastKeyAccumulatorの初期化処理。やっていることは

  • Receiverオブジェクトのprototypeを辿る
  • prototypeにプロパティがあれば、has_empty_prototype_falseにする。
  • そして、全てのprototypeを辿り終わったら、has_empty_prototype_ == trueの場合は、receiverのMapオブジェクトがEnumを持っていて、Enumerableなプロパティを持っていないければ、is_receiver_simple_enum_ = trueになる。
  • それ以外の場合はlast_non_empty_prototype_に最後のprototypeを渡す。

さて、runtime-forinのEnumerate関数に戻ると、

if (!accumulator.is_receiver_simple_enum()) {

FastKeyAccumulator::is_receiver_simple_enum()で処理を分けている。
つまり、自身にEnumerableなプロパティを持たず、prototypeにも持たず、MapにEnumを持っているオブジェクトはこのファストパスに入る。
このパスではFastKeyAccumulator::GetKeys()でObjectのプロパティキーを取得する。

MaybeHandle<FixedArray> FastKeyAccumulator::GetKeys(
    GetKeysConversion keys_conversion) {
  if (filter_ == ENUMERABLE_STRINGS) {
    Handle<FixedArray> keys;
    if (GetKeysFast(keys_conversion).ToHandle(&keys)) {
      return keys;
    }
    if (isolate_->has_pending_exception()) return MaybeHandle<FixedArray>();
  }

  return GetKeysSlow(keys_conversion);
}

GetKeysの定義は結構簡単で、filter_プロパティは今回はENUMERABLE_STRINGSなのでifに入る。
そしたらFastKeyAccumulator::GetKeysFastを呼び出してHandle<FixedArray>に変換する。

MaybeHandle<FixedArray> FastKeyAccumulator::GetKeysFast(
    GetKeysConversion keys_conversion) {
  bool own_only = has_empty_prototype_ || mode_ == KeyCollectionMode::kOwnOnly;
  Map* map = receiver_->map();
  if (!own_only || !OnlyHasSimpleProperties(map)) {
    return MaybeHandle<FixedArray>();
  }

  // From this point on we are certiain to only collect own keys.
  DCHECK(receiver_->IsJSObject());
  Handle<JSObject> object = Handle<JSObject>::cast(receiver_);

  // Do not try to use the enum-cache for dict-mode objects.
  if (map->is_dictionary_map()) {
    return GetOwnKeysWithElements<false>(isolate_, object, keys_conversion);
  }
  int enum_length = receiver_->map()->EnumLength();
  if (enum_length == kInvalidEnumCacheSentinel) {
    Handle<FixedArray> keys;
    // Try initializing the enum cache and return own properties.
    if (GetOwnKeysWithUninitializedEnumCache().ToHandle(&keys)) {
      if (FLAG_trace_for_in_enumerate) {
        PrintF("| strings=%d symbols=0 elements=0 || prototypes>=1 ||\n",
               keys->length());
      }
      is_receiver_simple_enum_ =
          object->map()->EnumLength() != kInvalidEnumCacheSentinel;
      return keys;
    }
  }
  // The properties-only case failed because there were probably elements on the
  // receiver.
  return GetOwnKeysWithElements<true>(isolate_, object, keys_conversion);
}

FastKeyAccumulator::GetKeysFastの定義がこちら。
いろいろコードがあるのだが、重要なのは、is_dictionary_mapのチェックである。
is_dictionary_mapは拡張されるObjectの場合trueになっている。

現在の所、

  • グローバルオブジェクトのMap、
  • Object.create(null)の戻り値のMap

dictionary_mapという扱いになっている。

このオブジェクトの場合はそもそもTransitionするMapを持っていない、EnumCacheも持っていないので、
GetOwnKeysWithElementsのtemplate引数をfalseで呼び出す。
それ以外のオブジェクトの場合は、enum_cacheの有無をチェックし、
存在しなければGetOwnKeysWithUninitializedEnumCacheを呼び出し、その場で作成する。 GetOwnKeysWithUninitializedEnumCacheは省略するが、enum_cacheをdescriptorから作成し、プロパティ一覧を返す。 enum_cacheを既に持っているオブジェクトの場合、GetOwnKeysWithElementsがtemplate引数にtrueを渡して呼び出される。

template <bool fast_properties>
MaybeHandle<FixedArray> GetOwnKeysWithElements(Isolate* isolate,
                                               Handle<JSObject> object,
                                               GetKeysConversion convert) {
  Handle<FixedArray> keys;
  ElementsAccessor* accessor = object->GetElementsAccessor();
  if (fast_properties) {
    keys = GetFastEnumPropertyKeys(isolate, object);
  } else {
    // TODO(cbruni): preallocate big enough array to also hold elements.
    keys = KeyAccumulator::GetOwnEnumPropertyKeys(isolate, object);
  }
  MaybeHandle<FixedArray> result =
      accessor->PrependElementIndices(object, keys, convert, ONLY_ENUMERABLE);

  if (FLAG_trace_for_in_enumerate) {
    PrintF("| strings=%d symbols=0 elements=%u || prototypes>=1 ||\n",
           keys->length(), result.ToHandleChecked()->length() - keys->length());
  }
  return result;
}

これがGetOwnKeysWithElementsの実装。
template引数がtrueの場合はGetFastEnumPropertyKeysを呼び出す。

// Initializes and directly returns the enume cache. Users of this function
// have to make sure to never directly leak the enum cache.
Handle<FixedArray> GetFastEnumPropertyKeys(Isolate* isolate,
                                           Handle<JSObject> object) {
  Handle<Map> map(object->map());
  bool cache_enum_length = map->OnlyHasSimpleProperties();

  Handle<DescriptorArray> descs =
      Handle<DescriptorArray>(map->instance_descriptors(), isolate);
  int own_property_count = map->EnumLength();
  // If the enum length of the given map is set to kInvalidEnumCache, this
  // means that the map itself has never used the present enum cache. The
  // first step to using the cache is to set the enum length of the map by
  // counting the number of own descriptors that are ENUMERABLE_STRINGS.
  if (own_property_count == kInvalidEnumCacheSentinel) {
    own_property_count =
        map->NumberOfDescribedProperties(OWN_DESCRIPTORS, ENUMERABLE_STRINGS);
  } else {
    DCHECK(
        own_property_count ==
        map->NumberOfDescribedProperties(OWN_DESCRIPTORS, ENUMERABLE_STRINGS));
  }

  if (descs->HasEnumCache()) {
    Handle<FixedArray> keys(descs->GetEnumCache(), isolate);
    // In case the number of properties required in the enum are actually
    // present, we can reuse the enum cache. Otherwise, this means that the
    // enum cache was generated for a previous (smaller) version of the
    // Descriptor Array. In that case we regenerate the enum cache.
    if (own_property_count <= keys->length()) {
      isolate->counters()->enum_cache_hits()->Increment();
      if (cache_enum_length) map->SetEnumLength(own_property_count);
      return ReduceFixedArrayTo(isolate, keys, own_property_count);
    }
  }

  if (descs->IsEmpty()) {
    isolate->counters()->enum_cache_hits()->Increment();
    if (cache_enum_length) map->SetEnumLength(0);
    return isolate->factory()->empty_fixed_array();
  }

  isolate->counters()->enum_cache_misses()->Increment();

  Handle<FixedArray> storage =
      isolate->factory()->NewFixedArray(own_property_count);
  Handle<FixedArray> indices =
      isolate->factory()->NewFixedArray(own_property_count);

  int size = map->NumberOfOwnDescriptors();
  int index = 0;

  for (int i = 0; i < size; i++) {
    PropertyDetails details = descs->GetDetails(i);
    if (details.IsDontEnum()) continue;
    Object* key = descs->GetKey(i);
    if (key->IsSymbol()) continue;
    storage->set(index, key);
    if (!indices.is_null()) {
      if (details.location() == kField) {
        DCHECK_EQ(kData, details.kind());
        FieldIndex field_index = FieldIndex::ForDescriptor(*map, i);
        int load_by_field_index = field_index.GetLoadByFieldIndex();
        indices->set(index, Smi::FromInt(load_by_field_index));
      } else {
        indices = Handle<FixedArray>();
      }
    }
    index++;
  }
  DCHECK(index == storage->length());

  DescriptorArray::SetEnumCache(descs, isolate, storage, indices);
  if (cache_enum_length) {
    map->SetEnumLength(own_property_count);
  }
  return storage;
}

GetFastEnumPropertyKeysがこれ。
実はGetFastEnumPropertyKeysは先程省略した、GetOwnKeysWithUninitializedEnumCacheからも呼び出される。
とても長いコードだが、やっていることはdescriptorがenum_cacheを持っていれば、それを返すし、なければ作成して返す。
これだけ。

さて、これがForInでキーを列挙する最速のパスなのだが、遅いパターンはどうだろうか。 GetOwnKeysWithElementsのに戻るが、

keys = KeyAccumulator::GetOwnEnumPropertyKeys(isolate, object);

と、KeyAccumulator::GetOwnEnumPropertyKeysを呼び出すらしい。

Handle<FixedArray> KeyAccumulator::GetOwnEnumPropertyKeys(
    Isolate* isolate, Handle<JSObject> object) {
  if (object->HasFastProperties()) {
    return GetFastEnumPropertyKeys(isolate, object);
  } else if (object->IsJSGlobalObject()) {
    return GetOwnEnumPropertyDictionaryKeys(
        isolate, KeyCollectionMode::kOwnOnly, nullptr, object,
        object->global_dictionary());
  } else {
    return GetOwnEnumPropertyDictionaryKeys(
        isolate, KeyCollectionMode::kOwnOnly, nullptr, object,
        object->property_dictionary());
  }
}

これが実装
object->HasFastPropertiesであれば、GetFastEnumPropertyKeysに戻れるらしい。
HasFastPropertiesである条件は、

  • mapがHashTableでなく、StringTableでも無いこと。

その条件の場合はやはり、enum_cacheから取得される。
それ以外のグローバルオブジェクトの場合、GetOwnEnumPropertyDictionaryKeysのパスに入る。
GetOwnEnumPropertyDictionaryKeysでは
各dictionaryのCopyEnumKeysToが呼び出されプロパティのコピーが行われる。

template <typename Derived, typename Shape, typename Key>
void Dictionary<Derived, Shape, Key>::CopyEnumKeysTo(
    Handle<Dictionary<Derived, Shape, Key>> dictionary,
    Handle<FixedArray> storage, KeyCollectionMode mode,
    KeyAccumulator* accumulator) {
  DCHECK_IMPLIES(mode != KeyCollectionMode::kOwnOnly, accumulator != nullptr);
  Isolate* isolate = dictionary->GetIsolate();
  int length = storage->length();
  int capacity = dictionary->Capacity();
  int properties = 0;
  for (int i = 0; i < capacity; i++) {
    Object* key = dictionary->KeyAt(i);
    bool is_shadowing_key = false;
    if (!dictionary->IsKey(isolate, key)) continue;
    if (key->IsSymbol()) continue;
    PropertyDetails details = dictionary->DetailsAt(i);
    if (details.IsDontEnum()) {
      if (mode == KeyCollectionMode::kIncludePrototypes) {
        is_shadowing_key = true;
      } else {
        continue;
      }
    }
    if (dictionary->IsDeleted(i)) continue;
    if (is_shadowing_key) {
      accumulator->AddShadowingKey(key);
      continue;
    } else {
      storage->set(properties, Smi::FromInt(i));
    }
    properties++;
    if (mode == KeyCollectionMode::kOwnOnly && properties == length) break;
  }

  CHECK_EQ(length, properties);
  DisallowHeapAllocation no_gc;
  Dictionary<Derived, Shape, Key>* raw_dictionary = *dictionary;
  FixedArray* raw_storage = *storage;
  EnumIndexComparator<Derived> cmp(static_cast<Derived*>(*dictionary));
  Smi** start = reinterpret_cast<Smi**>(storage->GetFirstElementAddress());
  std::sort(start, start + length, cmp);
  for (int i = 0; i < length; i++) {
    int index = Smi::cast(raw_storage->get(i))->value();
    raw_storage->set(i, raw_dictionary->KeyAt(index));
  }
}

でこれが、CopyEnumKeysToの実装。
forでループを回してプロパティをコピーしている。
まあこれが早いわけないよねということで、ForInの高速化の実装を確かめた。

まとめ

ForInのRuntime呼び出しでは、レシーバオブジェクトがFastPropertiesさえ持っていれば、
EnumCacheから値を取得するので高速。 IgnitionがForInを処理するパスについてはまたそのうち。

「Angular 4 の最新動向と、2017年再注目のDart、そしてAngular Dart」に行ってきた

AngularとDartの勉強会でした。

以下メモ

Angular4がやってくる!?新機能ダイジェスト

Asai Masahiko

Angular 4がやってくる!? 新機能ダイジェスト.pdf - Google ドライブ

  • Semantic Versioningの導入
  • 非推奨ポリシーの導入(2つのメジャーリリースを挟む)

変更点ダイジェスト

  • ViewEngineの改善
  • templateタグが非推奨に
  • @angular/animationsの独立
  • typescript2.1へのアップデート
  • metaタグの追加・更新・削除が可能に
  • Email Validator・EqualTo Validatorの追加

Angular2で作った社内向けツールを4に移行してみた

ハーモニーお菓子管理 お菓子の在庫が見れる

14 Components 4 Service 5 Route

およそ半日くらいで移行完了

まとめ

SemanticVersioningが導入後発のMajor Release 思っていたより小さなアップデート ViewEngineの改善はでかい

15分で分かった気になるDart

docs.google.com

小林達(さとし)

Dartを取り巻く状況

当初はjsを置き換える予定だった
AdSense/AdWordsがangular-dartにリプレイス
The new Google AdSense user interface: built with AngularDart
The new AdWords UI uses Dart — we asked why

Dart言語

発表から6年だが、まだ活発
DartPadで試せる
(DartPad)

すべてオブジェクト
intなどは初期化しないとnull
boolはtrue以外はすべてfalse
dart:collectionパッケージには(java)のような感じでコレクションがある。

  • Map
  • Iterable(List,Set)
  • 高階関数もつかえる
  • 型推論はもうちょい
  • Future・async・await
  • ドット2つでビルダー化する(thisを返す様になる)
  • 充実した標準ライブラリ
  • 公式ドキュメントが充実

Sound Dart

Stroing Mode

AngularDartで快適SPA

docs.google.com

AngularDartの歴史

TypeScript版と分離した

ビジネスに使える

AngularDartをさっと見る

Streamが言語自体に組み込まれている

サーバサイドDartを試してみる

docs.google.com

All-in-oneならAqueduct

シンプルなら純正のShelfがいいかも

初心者にはとっつきづらい

中間なのはRedStone
Shelfを利用したWrapper

まとめ

とりあえず、Dartの開発がまだ続いているのに驚いた(結構活発に)
けど、ES2015 + Typescriptの躍進があったので、他のオプショナル静的型の言語がフロントエンドで使われるには、
なかなか厳しい環境だろうなと思う。
ただ、Dartはランタイムがかなり充実している印象なので、それはうらやましい。
AngularDartがAngularと全く別のラインで開発されてるのも知らんかった。
が、まあ使わないだろう。

Typescript 2.2.0 の Mixin を使ってDIしてみる

表題の通り。
今まではMixinがなかったので、泥臭く型チェックができない方法(文字列とか)でDIしていたが、
typescript 2.2.0からMixinに対応したので、DIを考察する。

Dependency Injectionとは

Dependency Injectionとはその名の通り依存性を外から注入する仕組み。
なにが嬉しいかというと、クラス内部で依存しているはずのクラスを外部からコントロールできるようにすることで、
クラスの内部実装と外部への依存を疎結合にし、テストし易いクラスを作ることができる。

猿でも分かる! Dependency Injection: 依存性の注入 - Qiita
Inversion of Control コンテナと Dependency Injection パターン

DI for Typescript

さて、Typescriptで上述のDependency Injectionを実現するにはどういうパターンがあるか、検証してみたい。

public Dependency パターン

名前は勝手につけました。
その名の通りpublicプロパティに依存性を設定する。

実装

// deps1.ts

export interface Deps1 {
  hello(): string;
}

export class Deps1Impl implements Deps1 {
  public hello() {return 'hello'}
}


// deps2.ts

export interface Deps2 {
  world(): string;
}

export class Deps2Impl implements Deps2 {
  public world() {return 'world'}
}


// deps1+deps2-module.ts

import {
  Deps1
} from './deps1';
import {
  Deps2
} from './deps2';

export interface Deps1Deps2Module {
  deps1: Deps1;
  deps2: Deps2;
}


// deps1+deps2-module-impl.ts

import {
  Deps1Deps2Module
} from './deps1+deps2-module';
import {
  Deps1Impl
} from './deps1';
import {
  Deps2Impl
} from './deps2';

type Constructor<T> = new(...args: any[]) => T;

export function Deps1Deps2ModuleImpl<T extends Constructor<Deps1Deps2Module>>(Base: T) {
  return class extends Base {
    constructor(...a) {
      super(...a);
      this.deps1 = new Deps1Impl();
      this.deps2 = new Deps2Impl();
    }
  };
}


// inject-target.ts
import {
  Deps1
} from './deps1';
import {
  Deps2
} from './deps2';
import {
  Deps1Deps2Module
} from './deps1+deps2-module';


export class InjectTarget implements Deps1Deps2Module {
  public deps1: Deps1;
  public deps2: Deps2;

  public greet() {return `${this.deps1.hello()} ${this.deps2.world()}`}
}


// injected.ts

import {
  InjectTarget
} from './inject-target'
import {
  Deps1Deps2ModuleImpl
} from './deps1+deps2-module-impl';


export class Injected extends Deps1Deps2ModuleImpl(InjectTarget) {
}


// main.ts

import {
  Injected
} from './injected';


const injected = new Injected();
console.log(injected.greet());

deps1.ts deps2.ts

これらは単純な依存クラス

deps1+deps2-module.ts

こちらはdeps1とdeps2を注入される側の規約を定義したinterfaceクラス
依存性を注入される側はこのインターフェースを実装する。

deps1+deps2-module-impl.ts

依存性注入を実行するMixin関数。
ここで実際に依存性の注入を行う。

inject-target.ts

依存性を注入されるクラス。
interface Deps1Deps2Moduleをimplementsしている以外は、通常のクラスと変わりない。

injected.ts

ここでDeps1Deps2ModuleImpl関数にInjectTargetクラスを渡したクラスを継承することで、
依存性が注入されるクラスを生成する。

main.ts

エントリーポイント

問題点

publicプロパティに実装するしか無いので、カプセル化に問題がある。
防御的なクラスが作りづらい。

method injection パターン

こちらは上記の問題を踏まえ、publicメソッドを使ってinjectionの実現をする。

実装

// deps1+deps2-module.ts

import {
  Deps1
} from './deps1';
import {
  Deps2
} from './deps2';

export interface Dependencies {
  deps1: Deps1;
  deps2: Deps2;
}

export interface Deps1Deps2Module {
  __setDependencies(deps: Dependencies): void;
}


// deps1+deps2-module-impl.ts

import {
  Deps1Deps2Module
} from './deps1+deps2-module';
import {
  Deps1Impl
} from './deps1';
import {
  Deps2Impl
} from './deps2';

type Constructor<T> = new(...args: any[]) => T;

export function Deps1Deps2ModuleImpl<T extends Constructor<Deps1Deps2Module>>(Base: T) {
  return class extends Base {
    constructor(...a) {
      super(...a);
      this.__setDependencies({deps1: new Deps1Impl(), deps2: new Deps2Impl()});
    }
  };
}


// inject-target.ts

import {
  Deps1
} from './deps1';
import {
  Deps2
} from './deps2';
import {
  Deps1Deps2Module
} from './deps1+deps2-module';


export class InjectTarget implements Deps1Deps2Module {
  private deps1: Deps1;
  private deps2: Deps2;

  public greet() {return `${this.deps1.hello()} ${this.deps2.world()}`}

  public __setDependencies({deps1, deps2}) {
    this.deps1 = deps1;
    this.deps2 = deps2;
  }
}

変更点

deps1+deps2-module.ts

__setDependenciesというpublicなセッタメソッドを用意して、
外部から直接interfaceを触れられないようにした。

deps1+deps2-module-impl.ts

__setDependeciesに依存関係を渡すように修正

inject-target.ts

__setDependenciesを実装

問題点

__setDependenciesがpublicメソッドなのは変わらず。

考察

ほんとはconstructor injectionが型チェック付きでできれば良いのだが…
今回のMixinでは不可能なので、method injectionでやるのが一番良いのかなと思う。

元々はScalaのCakeパターンとかあのへんの感じでやりたかった(願望)
ただ、typescriptの貧弱な言語機能でもここまで頑張れるのがわかったので良かった。

とりあえず、文字列ベースのDIコンテナを使うよりはいいかもしれないので、一旦プロダクトで使ってみようと思う。
可能性を伸ばしていきたい。
伸びしろですねぇ!

今回の実装はこちらにありまぁーす

GitHub - brn/typescript-mixin-di-sample: Sample Implementation for typescript dependency injection by class mixin pattern.

jspmでtypescriptの開発をする

jspmからtypescriptの開発をした時の備忘録を書く

typescriptコンパイラ + jspm

まずはjspmでtypescriptをランタイムコンパイルできるようにするため、
plugin-typescriptをインストールする。

cli

jspm install ts

jspm.config.js

SystemJS.config({
  ...
  packages: {
    "/src": {
      "defaultExtension": "ts",
      "meta": {
        "*.ts": {
          "loader": "ts"
        }
      }
    }
  }
});

これだけでtypescriptをランタイムで使う準備ができた。

typescriptのバージョンを変える

typescriptのバージョンを開発環境に合わせて変える。

まず、好みのバージョンのtypescriptをインストールする。

cli

jspm install typescript@2.2.1

インストールしたら、plugin-typescriptのtypescriptを更新する。

jspm.config.js

SystemJS.config({
  ...
  map: {
    "github:frankwallis/plugin-typescript@5.3.1": {
      "map": {
        "typescript": "npm:typescript@<バージョン>"
      }
    },
  }
}

<バージョン>のところに先程インストールしたtypescriptのバージョンを入れる。

typescriptのオプション

これもjspm.config.jsで設定する。

SystemJS.config({
  typescriptOptions: {
    "tsconfig": true, // tsconfigを使う場合はtrueにする
    "typeCheck": false // 型チェックするかどうか
  },
});

tsconfigプロパティがtrueの場合は、ページのルートからtsconfig.jsonを探すので、
ファイルの置き場所に注意。
typeCheckはtrue・false以外に'strict'も指定できる。

tsconfigプロパティがfalseの場合はここに全てのオプションを設定する必要がある。

typescriptの設定

tsconfig.json

module: system

を設定しておけば、import構文が全てSystemJS化するので、 jspm経由でロードできる。

以上

Firebase SDK for javascript

Firebase SDKの使い方・注意点等について書く。

インストール

scriptタグで直接CDNから読み込むのが一番かんたん。

<script src="https://www.gstatic.com/firebasejs/live/3.0/firebase.js"></script>

バンドルしてしまう場合は

npm install --save firebase

でnpmからインストール

準備

Firebase Consoleから新規プロジェクトを作成

f:id:brn_take:20170227181124p:plain f:id:brn_take:20170227181340p:plain

コンソールから設定を取得

f:id:brn_take:20170227181559p:plain

f:id:brn_take:20170227181629p:plain

npmの人は// Initialize Firebase以下をコピー

ここまでで、こんな感じになります。

index.html

<!doctype html>
<html>
    <head>
       <script src="https://www.gstatic.com/firebasejs/3.6.10/firebase.js"></script>
       <script>
           // Initialize Firebase
           var config = {
                 apiKey: "AIzaSyD8BQ8oDDcXEmZgEWNz1KYLhBWIZDCR8aw",
                 authDomain: "test-proj-509cf.firebaseapp.com",
                 databaseURL: "https://test-proj-509cf.firebaseio.com",
                 storageBucket: "test-proj-509cf.appspot.com",
                 messagingSenderId: "107396821085"
           };
           firebase.initializeApp(config);
       </script>
   </head>
    <body>
    </body>
</html>

使う

firebaseは主にfirebaseオブジェクトのメソッドから操作する。

データベースを参照する

const databaseRef = firebase.database().ref('production/messages');

refの引数は参照する階層になる。

データベースに値が追加されたら通知する。

databaseRef.on('child_added', snapshot => {
  const value = snapshot.val();
  const key = snapshot.key();
});

child_addedキーでref以下の階層に値が追加された場合に通知が飛ぶ。
通知の引数になるsnapshotから値を取得する場合はval()メソッドで値を取得する。
もし自身のパスが取得したければ、key()で取得できる。

データベースから値が削除されたら通知する。 

databaseRef.on('child_removed', snapshot => {
  const value = snapshot.val();
  const key = snapshot.key();
});

データベースの値が変更されたら通知する。 

databaseRef.on('child_changed', snapshot => {
  const value = snapshot.val();
  const key = snapshot.key();
});

データベースの値が移動したら通知する。 

databaseRef.on('child_moved', snapshot => {
  const value = snapshot.val();
  const key = snapshot.key();
});

最初に接続した時にDBに値があれば、child_addedかvalueに通知が来る。

全てのイベントを通知する。

databaseRef.on('value', snapshot => {...});

値を追加する

基本

const newRef = databaseRef.push();
newRef.set({foo: 'bar'});

pushで新たなエントリーを生成し、setで値を設定する。

push(value: any, onComplete: () => void)

ユニークキーを生成して、そのキーで新たなエントリーを生成する。
valueを指定した場合は後述するsetも同時に行われる。
キー生成の詳細については The 2120 Ways to Ensure Unique Identifiers

set(value: any, onComplete: () => void)

現在のロケーションに値をセットする。

フィルターをかける

フィルター系で注意したいのは、firebaseは複数のフィルターを組み合わせられないという点
なので、データ構造は十分考慮する必要があるし、場合によってはjavascript側で並べ替えることも考慮した方がいい。

順序を変える。 

sqlorder by的なもの

databaseRef.orderByChild('createdAt').on(...);
databaseRef.orderByKey().on(...);
databaseRef.orderByValue().on(...);

orderByChild(propertyName: string)

指定された子要素のプロパティ値で順序を決める。
多分一番使う気がする。デフォルトで昇順になる。

orderByKey

キーでソートする。
キーに順序をもたせた場合はこれを呼ぶだけでいい。

orderByValue

値でソートする。
プリミティブが直接子要素になっている場合以外に使い道がない気がするが。

値を指定する。

子要素の値を指定してフィルターする。
このメソッドはorderByに影響を受け、orderByの指定があった場合はそのプロパティを比較に使う。
もしorderByの指定がなければ、第二引数で比較する値を指定する。

databaseRef.startAt(Date.now(), 'createdAt');
databaseRef.orderByChild('createdAt').startAt(Date.now());

databaseRef.endAt(Date.now(), 'createdAt');
databaseRef.orderByChild('createdAt').endAt(Date.now());

databaseRef.equalTo(Date.now(), 'createdAt');
databaseRef.orderByChild('createdAt').equalTo(Date.now());

startAt(value: any, propertyName?: string)

値が一致する要素から開始する。
一致しない要素は全てスキップされる。

endAt(value: any, propertyName?: string)

値が一致したら終了する。
一致するまでは全ての要素が流れる。

equalTo(value: any, propertyName?: string)

値が一致した要素のみを流す。

件数を制限する

databaseRef.limitToFirst(1000).on(...)
databaseRef.limitToLast(1000).on(...);

limitToFirst(value: number)

指定した件数を先頭から取得する。

limitToLast(value: number)

指定した件数を最後尾から取得する。

注意点・Tips等

Firebaseのイベントで気をつけなければいけないのは、初回に繋いだ瞬間全ての値が流れてくること。

キャッシュ等をしていて、差分からの値が欲しければ、最後の値をstartAt()に渡してイベントをスキップするような実装にする必要がある。
また、valueとchild_addedを一緒に指定すると、valueとchild_addedの両方に値が流れるので注意。

基本的にはvalueは使わず、child_added/moved/changed/removedをちゃんと使い分けたほうがよい。

HTML5とか勉強会「Webパフォーマンス」に参加してLTしてきた

LTしてきた。

とりあえず、早口すぎたなと思った。
時間を気にしすぎてしまったかも。

以下勉強会メモ

先入観とバイアスを考慮したWebサイトパフォーマンス改善

t.co

竹洞さん

日本の現状

世界基準

  • 表示開始時間 0.5秒以内
    • Webブラウザ上に最初の1ピクセル目が表示された時間
  • 表示完了時間 2秒以内
    • DOM Complete
    • DOM処理が終わったタイミング

アメリカのEコマース

(アメリカのEコマース)https://www.dynatrace.com/en/benchmarks/united-states/retail/ns

日本

(日本のWebサイトパフォーマンスランキング)https://www.dynatrace.com/en/benchmarks/japan/retail/ns

どのプロジェクトでも2秒切れば直帰率は50%減る。
CMOがパフォーマンスを確認する
パフォーマンスが売上に直結している。

パフォーマンスは売上を引きずる要員であり、上げる要員ではない

先入観とバイアスだらけ

最低保証パフォーマンスを規定する

寒いマンション

マンションのほうが死亡率が高い。
寒いから。寒さ・暑さ対策といえば断熱材
ほんとに?

遮熱材が入っていない。
コンクリート自体が冷たさを溜め込む 冷輻射

我々WEB業界は建築業界を責められない

この辺はたとえの話だったのだが、結構マンション・不動産の詳細な
話に踏み込んでいたので、ちょっと戸惑った。

知らないという事を認識しよう

計測してみるまでは、実際のことはわからない

Don’t guess, measure!

ボトルネックを特定せよ
早まったコンポーネントの最適化

要は何が起きてるかを理解するまでは、最適化や修正をすべきではないってこと。
憶測だけでは状況は悪化する。

非科学的手法は削除する

以下のツールは使うな!

  • Google PageSpeed Insights
    • このツールだけでは高速化できない。このツールはベストプラクティスに合致しているかどうかを判定するツール。業務では使うべきではない
  • Chromeの開発者ツール。あたりを付けるぶんには問題ない。でもこれは真の値ではない。ユーザーが何秒で見ているかの比率を見るべき
    • カバー率の問題。自分のISPがどれほどの範囲をカバーしているのか
    • カバー率(時間)の問題。自分が見たタイミングがほんとに正しいか。
    • インターネットが変わらないという先入観
  • Webpage Test
    • この計測環境の仕様を知っているか。PCのスペック

ここで使えないと言われているサービスの問題点は、パフォーマンスをある一点からしか計測していないからである。
大事なのはユーザーの環境で実際に何秒かかったのかという一点。

統計学を学ぼう!

統計的な知識でグラフを見ることでほんとに重要なことが読み取れる。

法律での瑕疵担保責任の変化

瑕疵とは?
請負側に品質保証責任があることが明記される。
品質が達成されるまでは無料で働かなければならない。

仕様の実現から目的の実現へ

今まで
ソフトウェアはPL法の対象にならない。 ハードウェアは対象

これから
ソフトウェアの請負も品質保証書が必要になる。

つまりクライアントの目的(パフォーマンスも含む)が達成されない場合、損害賠償の可能性がある。

まとめ

  • 高速化するときには先入観で見ない。
  • お金をかけてでも、製造業のような品質保証をする。

まあまあ最近のパフォーマンスAPIなど

yakura @myasaka

最近みたクライアントサイドの話

jsの比重が大きくなっている。48%ほど

パフォーマンスまわりの仕様

  • 計測関連のAPI
  • 遅くさせないための仕組み
  • スケジューリング

計測関連

User Timing

Safariにそろそろ来そう…?

Performance Observer

(使い方)https://developers.google.com/web/updates/2016/06/performance-observer

エントリのタイプもこれから増えそう

Long Tasks

長くかかったタスクを検出(50ms以上とか)

First Paint

適切なFirst Paintがどこなのか

  • FirstContentPaint
  • FirstMeaningfulPaint

人間の体感的なメトリクス

遅くさせないための仕様

Intersection Observer

scroll と getBoundingClientRectの組合せはおそい!

そこでIntersectionObserver。表示範囲との交差を検出して領域判定を必要としない。
LazyLoadingとかにも使える

CSSからのアプローチ

position:sticky

ある程度までスクロールすると、要素がfixedになる

CSS Contained

大きさが変わらない要素を宣言して高速化する

.widget {
  contain: layout style paint;
}

スケジューリング

HTTP Linkヘッダ

  • のHTTPヘッダ版
  • HTMLのパースを待たずにDL開始

Link<style.css>:rel=stylesheet

preloadヘッダ

WebフォントやCSS

requestTimingIdleCallback

アイドル時間に実行させる優先度の低いタスクを渡す。

大量の要素を高速に表示する婆バーチャルレンダリング入門

久保田 @anatoo

バーチャルレンダリングとは

speakerdeck.com

  • パフォーマンスのためのテクニック
  • ウェブページに大量の要素を高速に表示するためのテクニック

普段から使っている(react-infinite)https://github.com/seatgeek/react-infiniteとかのことだったので、
あんまメモってない。
iOSのUITableViewとかもそうだったはず。

こっからLT

WebWorker & Atomics

自分

www.slideshare.net

組織にパフォーマンスを根付かせる

speakerdeck.com

始めた経緯

どんな事を勉強していけばいいかわからなくて悩んでます。

半年くらい前に、こんな質問を新人さんにされたことがきっかけ。

パフォーマンスの勉強をやってもらうのがいいかも

ServiceWorkerのnavigation-preloadについて

ちょっとごたごたしてあまり聞けなかった。。。

まとめ

ソフトバンク本社凄くきれい。
LT5分てやっぱきついな

Typescript 2.2 変更点

Typescript 2.2がでた。

変更点が少々あるので確認しておきたい。

Mixinのサポート

遂にMixinがサポートされた。やり方が少々直感的ではないけども。

公式のサンプルコードで確認する。

class Point {
    constructor(public x: number, public y: number) {}
}

class Person {
    constructor(public name: string) {}
}

type Constructor<T> = new(...args: any[]) => T;

function Tagged<T extends Constructor<{}>>(Base: T) {
    // 今まではここでエラーになっていた。
    return class extends Base {
        _tag: string;
        constructor(...args: any[]) {
            super(...args);
            this._tag = "";
        }
    }
}

const TaggedPoint = Tagged(Point);

let point = new TaggedPoint(10, 20);
point._tag = "hello";

class Customer extends Tagged(Person) {
    accountBalance: number;
}

let customer = new Customer("Joe");
customer._tag = "test";
customer.accountBalance = 0;

上のコードのnew(...args: any[]) => TがMixinのコンストラクタとして認識され、
Tagged関数内で無名クラスでnew(...args: any[]) => Tを継承することが許されるようになった。

type Constructor<T> = new(...args: any[]) => T;

function Loggable<T extends Constructor<{}>>(Target: T) {
    return class extends Target {
        protected log(content: string) {console.log(content)}
    }
}

class Base {}

class Derived extends Loggable(Base) {
  public doSomething() {
    this.log('...');
  }
}

みたいにTrait的な感じで使える。

object型のサポート

今までのtypescriptはnumber | string | boolean | symbol | null | undefined以外にPrimitive型を持っていなかったが、
今回からobject型が追加された。object型は上記のPrimitive型以外の全てに対応する。

これも公式のサンプルだが、

declare function create(o: object | null): void;

create({ prop: 0 }); // OK
create(null); // OK

create(42); // Error
create("string"); // Error
create(false); // Error
create(undefined); // Error

のような感じ、要はPrimitive以外を受け付けるanyのような感じか。

new.targetのサポート

new.targetがtypescriptでもサポートされた。
あまり使う機会は無いが、例ではErrorの継承があげられている。

class CustomError extends Error {
    constructor(message?: string) {
        super(message); // 'Error' breaks prototype chain here
        Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain
    }
}

ErrorだけでなくArray等の、
constructorを単純に呼び出すだけではうまく継承できないビルトインクラスを継承する場合に使う。

null/undefinedチェックの強化

式中のnull/undefinedのチェックが強化された。
以下の場合はエラーになる。

  • もし、+オペレーターのオペランドがnullableで、オペランドがstringでもanyでも無いとき。
  • もし、-, *, **, /, %, <<, >>, >>>, &, |, ^オペランドがnullableのとき。
  • もし、<, >, <=, >=, inオペランドがnullableのとき。
  • もし、instanceof の右辺がnullableのとき。
  • もし、+, -, ~, ++, -- の単項がnullableのとき。

stringをキーにしたオブジェクトにドットでアクセスできるようになった

以下のような型

interface StringMap<T> {
    [x: string]: T;
}

にたいしてドットでプロパティアクセスしてもOKになった。

var stringMap: StringMap;
stringMap.foobar; // ok
stringMpa['foobar']; // ok

jsxの子要素に対するspreadオペレーターがサポートされた。

これも公式の例を拝借

function Todo(prop: { key: number, todo: string }) {
    return <div>{prop.key.toString() + prop.todo}</div>;
}

function TodoList({ todos }: TodoListProps) {
    return <div>
        {...todos.map(todo => <Todo key={todo.id} todo={todo.todo} />)} //ここ!
    </div>;
}

let x: TodoListProps;

<TodoList {...x} />

のように、直接子要素を展開する構文がサポートされた。

jsx: react-nativeがサポートされた

これはそのまま。
react-nativeでコンパイルすると、jsx構文がそのまま残った上でjsの拡張子になる。

まとめ

Mixinが嬉しい!