TypeScriptのプログラミングについて備忘としてメモ書きしています。
※目次をクリックすると目次の下部にコンテンツが表示されます。
- 1.NPMでTypeScriptインストール、コンパイル、動作確認
- 2.SystemJSを使ってブラウザでtypescriptをロードして実行
- 3.型注釈(型アノテーション)
- 4.列挙型(enum)
- 5.関数定義の注釈(アノテーション)
- 6.オーバーロード
- 7.lamda形式の関数
- 8.インタフェースの概要
- 9.クラスのコンストラクタの概要
- 10.クラスのアクセス修飾子
- 11.letを使ってブロックスコープ
- 12.変数の巻上げ(hoisting)とlet
- 13.クラスのプロパティとメソッド
- 14.クラスの継承、インタフェースの実装
- 15.スコープ、thisキーワードの注意点
- 16.インスタンスのチェック方法
- 17.モジュール
- 18.SystemJSでモジュールをエクスポート、インポート
- 19.ジェネリック
- 20.structural typing
- 21.型定義ファイルとDefinitelyTyped
- 22.カプセル化
- 23.コールバックの使用方法
- 24.observerパターン
- 25.イベントを使ったサンプル作成
1)Node.jsをインストール
下記サイトからダウンロードし、インストール
node.js
2)TypeScriptのツールインストール
npm install -g typescript
3)TypeScriptのサンプルコード作成(greeter.ts)
var name: string = "Jane";
var greeter: (name: string) => string;
greeter = function (name: string) {
return 'Hello ' + name;
};
document.body.innerHTML = greeter(name);
4)コンパイル
>tsc greeter.ts
→greeter.jsファイルが作成
(greeter.js)
var name = "Jane";
var greeter;
greeter = function (name) {
return 'Hello ' + name;
};
document.body.innerHTML = greeter(name);
5)HTMLファイル作成
<!DOCTYPE html> <html lang="ja"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>TypeScript動作確認</title> </head> <body> </body> <script src="js/greeter.js" type="application/javascript"></script> </html>
6)ブラウザで動作確認
SystemJSを使ってTypeScriptを動的に実行
●文字列の例
var name: string = ‘一郎’;
●数値の例
var score: number = 5.3;
●boolの例
var flg: boolean = false;
●配列の例
var names: string[] = [‘一郎’, ‘二郎’, ‘三郎’];
●関数の例
var sayHello: (name: string) => string;
sayHello = function (name: string) {
return 'こんにちは、' + name + 'さん!';
};
●オブジェクトの例
var member: { name: string; score: number; };
member = {
name: '一郎',
score: 3.9
};
●インタフェースを使った例
interface Member1 {
name: string;
score: number;
}
var member1: Member1 = {
name: '二郎',
score: 8.5
}
●undefined、null、void
・undefined
未割当ての変数の値。
・null
オブジェクトに意図せずに値が割当てられていない事を表す。たとえば、オブジェクトの配列検索するメソッドでマッチしなかった場合にnullを返す場合など。
・void
値をリターンしない関数などに使用。
・ある一定数の数の定数を表すのに使用。
・デフォルトでは0からインクリメントされる。
(サンプル)
enum Group {
group1,
group2,
group3
}
var mygrp = Group.group2;
console.log(mygrp); //1
var mygrpName = Group[mygrp];
console.log(mygrpName); //group2
●再定義して追加
下記のように同じ名前で再定義し、追加する事ができる。ただし、最初の要素に識別数値を指定する必要がある。
指定しないと下記エラーが発生
>tsc enum.ts
enum.ts(12,3): error TS2432: In an enum with multiple declarations, only one declaration can omit an initializer for its first enum element.
(サンプル)
enum Group {
group4 = 3,
group5
}
var mygrp = Group.group5;
console.log(mygrp); // 4
var mygrpName = Group[mygrp];
console.log(mygrpName); //group5
・括弧内で各パラメータの型を指定。
・括弧の後でリターン値の型を指定。リターン値がない場合はvoidを指定。
function getAvg(a: number, b: number, c: number): number {
//
}
●オプションのパラメータを指定する場合
・下記のようにオプションのパラメータにはクエスチョンマークを付与。
・オプションのパラメータは必須パラメータの後に指定する。
function getAvg(a: number, b: number, c?: number): number {
//
}
●パラメータにデフォルト値を指定
function getAvg(a: number, b: number, tax = 0.08): number {
//
}
●Restパラメータ
Restパラメータを使うと任意の数の引数を指定する事ができる。
下記仕様に従って指定する必要がある。
・パラメータの識別子に”…”のプレフィックスを付与。配列型を指定。
・Restパラメータは一つだけ指定できる。
・Restパラメータはパラメータリストの最後に指定する。
例)
function getAvg(...a: number[]): number {
var total = 0;
var count = 0;
for (var i = 0; i < a.length; i++) {
total += a[i];
count++;
}
var avg = total / count;
return avg;
}
var result = getAvg(2, 4, 6, 8, 10);
TypeScriptのオンラインマニュアルのサンプルコードを使って確認します。
Handbook - Welcome to TypeScript
●サンプルコードの仕様
pickCard関数は、引数の指定の仕方によって以下の二つの処理を切り替えています。
①引数にトランプのマークと数値のペアの配列を指定した場合
配列の中からランダムに選択して、選択したペアのインデックス番号をリターン。
②トランプの0~51の間の数値を引数に指定した場合
マークと数値(1~13)のペアのオブジェクトをリターン。
var suits = ["hearts", "spades", "clubs", "diamonds"];
function pickCard(x: {suit: string; card: number; }[]): number;
function pickCard(x: number): {suit: string; card: number; };
function pickCard(x): any {
if (typeof x == "object") {
var pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
else if (typeof x == "number") {
var pickedSuit = Math.floor(x / 13);
TypeScriptreturn { suit: suits[pickedSuit], card: x % 13 + 1 };
}
}
var myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
var selected = pickCard(myDeck);
var pickedCard1 = myDeck[selected];
alert(selected);
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
var pickedCard2 = pickCard(25);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
var addNum = (a: number, b: number) => a + b;
var addNum = (a: number, b: number) => {
return a + b;
}
2)オブジェクトをリターンする場合
下記のようにオブジェクトをリターンする場合は、括弧で囲みます。
var makeName = (f: string, l: string) => ({first: f, last: l});
3)thisキーワードを使用する場合
TypeScriptのオンラインマニュアルのサンプルコードを使って確認します。
Handbook - Welcome to TypeScript
①lamda形式を使用しない場合
・アラートボックスが実行されずエラーとなる。
createCardPicker()によって生成された関数内で使用される"this"は、"deck"オブジェクトではなく、windowオブジェクトを参照してしまう。
関数が呼び出されるときには"deck"オブジェクトのスコープが失われてしまっているため。
var deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function() {
return function() {
var pickedCard = Math.floor(Math.random() * 52);
var pickedSuit = Math.floor(pickedCard / 13);
return {suit: this.suits[pickedSuit], card: pickedCard % 13 + 1};
}
}
}
var cardPicker = deck.createCardPicker();
var pickedCard = cardPicker();
alert("card: " + pickedCard.card + " of " + pickedCard.suit);
②lamda形式を使用
・lamda形式を使用すると関数生成時に自動で"this"キーワードが使用できるようになる。
var deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function() {
return () => {
:
・具象クラス(concrete class)によって実装できる抽象型(abstract
type)として使用できる。
・TypeScriptで記述されていないフレームワークやサードパーティライブラリ内で利用できる操作を定義するコードを記述できる。
●インタフェースの宣言
・interfaceキーワードを使用し、属性や関数などのアノテーションを含む。
アノテーションは、属性や関数だけでなく、コンストラクタやインデクサ(indexer)も宣言できる。
・クラスのインタフェースを記述する場合は、コンストラクタやインデクサを定義する必要はない。
●サンプルコード
interface LabelledValue {
label: string;
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}
var myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
明示的に記述していない場合は、コンパイラが自動で追加する。
・他のクラスを継承していないクラスでは、自動で追加されるコンストラクタはパラメータがなく、任意のクラス属性を初期化する。
・他のクラスを継承するクラスでは、自動コンストラクタは、そのクラス自身の属性を初期化する前に、スーパークラスのシグネチャとマッチングし、引数をスーパークラスに渡す。
例)
class Animal {
constructor(private name: string) { }
move(meters: number) {
alert(this.name + " moved " + meters + "m.");
}
}
・コンストラクタのパラメータは、メンバー変数にマップしていない。コンストラクタのパラメータにprivateなどのアクセス修飾子を指定すると自動でマップされる。
・このコンストラクタのパラメータは、そのクラス上の属性として宣言されたかのように参照する事ができ、this.nameのようにAnimalクラスのインスタンスで使用することができる。
例)手動でコンストラクタパラメータをマップした例
class Animal {
private name: string
constructor(name: string) {
this.name = name;
}
move(meters: number) {
alert(this.name + " moved " + meters + "m.");
}
}
・属性とメソッドのデフォルトのアクセス権はpublicなので、明示的にpublicを指定する必要はない。
・コンストラクタのパラメーターを自動でpublic属性でマップさせたい場合は、コンストラクタのパラメータにpublicを指定する必要がある。
・アクセス権をクラス内のみに制限したい場合は、privateを指定する。
この場合、サブクラスからもそのメンバにアクセスできない。
(1)プロパティとメソッド
1)インスタンスプロパティ
・コンストラクタの前で宣言。
・宣言と同時に初期化する事もできる。
private members: Member[] = [];
・インスタンスプロパティはthisキーワードを使ってクラス内でアクセスする事ができる。
2)staticプロパティ
・staticプロパティは、クラス名を使ってアクセスする。
static maxCount: number = 20;
this.members.length >= Memberslist.maxCount
3)メソッド
・メソッド名にアクセス修飾子を指定する事もできる。デフォルトはpublic。
・メソッドのパラメータとリターン値にも型アノテーションを指定できる。
・インスタンスプロパティと同様にインスタンスメソッドもクラス内でthisを使ってアクセスでき、publicの場合は、クラスの外からインスタンス名を使ってアクセスできる。
4)staticメソッド
・staticメンバは、クラスのインスタンスを生成していなく、各staicメンバの一つのインスタンスがプログラム内に存在するだけで呼び出す事ができる。
・すべてのstaticメンバはインスタンス名ではなく、クラス名を介してアクセスされ、staticメンバは、staticではないプロパティやメソッドにはアクセスしない。
(サンプルコード)
class Member {
constructor(private name: string, private address: string) {}
}
class Memberslist {
private members: Member[] = [];
static maxCount: number = 20;
constructor(public listname: string) {}
addMember(member: Member) {
if (this.members.length >= Memberslist.maxCount) {
throw new Error('Memberslist is full');
}
this.members.push(member);
}
}
var memberslist = new Memberslist('Hobby Group');
var name = memberslist.listname;
memberslist.addMember(new Member('Tom', 'Tokyo'));
var maxMembers = Memberslist.maxMemberCount;
(2)getterとsetter
・TypeScriptは、setterとgetterのプロパティをサポートする。
・getまたはsetキーワードをプレフィックスとして指定する以外は、メソッドと同じ書式。
(サンプルコード)
var passcode = "secret passcode";
class Employee {
private _fullName: string;
get fullName(): string {
return this._fullName;
}
set fullName(newName: string) {
if (passcode && passcode == "secret passcode") {
this._fullName = newName;
}
else {
alert("Error: Unauthorized update of employee!");
}
}
}
var employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
alert(employee.fullName);
}
(1)インタフェースの実装
・implementsキーワードを使用。
・一つのクラスは複数のインタフェースを実装できる。
・インタフェースを実装する際、implementsの宣言はオプション。
・クラスで実装するメソッドは、インタフェースで指定したパラメータの数より少なくてもよい。メソッドを実行するのに必要がない引数は無視してもよい。
明示的に指定したパラメータはインタフェースのパラメータとマッチしなければならない。
例)
interface ClockInterface {
currentTime: Date;
setTime(d: Date);
}
class Clock implements ClockInterface {
currentTime: Date;
setTime(d: Date) {
this.currentTime = d;
}
constructor(h: number, m: number) { }
}
(2)クラスの継承
・extendsキーワードを使って他のクラスを継承。
・継承したベースクラスのすべての属性とメソッドを受け継ぐ。
・同じ名前のメンバーを追加する事によってベースクラスのpublicメンバをオーバーライドする事ができる。
・クラスは一つのスーパークラスしか継承できない。
・ベースクラスに対する"super"のコールは、サブクラスのコンストラクタの最初に記述しなければならない。
例)
class Animal {
name:string;
constructor(theName: string) { this.name = theName; }
move(meters: number = 0) {
alert(this.name + " moved " + meters + "m.");
}
}
class Snake extends Animal {
constructor(name: string) { super(name); }
move(meters = 5) {
alert("Slithering...");
super.move(meters);
}
}
例)問題のあるコード
btnClick()メソッドは、インスタンスで直接コールされる場合は期待通りに動作するが、イベントとして実行するとthisキーワードの指定が失われてしまってthis.countが未定義になってしまう。
class Count {
private count = 0;
btnClick() {
this.count++;
console.log(this.count);
}
}
var count = new Count();
document.getElementById('count').onclick = count.btnClick;
●回避方法1
・メソッドを属性に変更し、属性をlamda形式の関数で初期化。
class Count {
private count = 0;
btnClick = () => {
this.count++;
console.log(this.count);
}
}
var count = new Count();
document.getElementById('count').onclick = count.btnClick;
●回避方法2
・クラスを変更せず、closureを作成。
関数内にインスタンスメソッドへのコールをラップする。
var count = new Count();
document.getElementById('count').onclick = function () {
count.btnClick();
};
●回避方法3
・JavaScriptのbind関数を使用。
var count = new Count();
var handler = count.btnClick.bind(count);
document.getElementById('count2').onclick = handler;
形式
インスタンス名 instanceof クラス名
※クラスを継承している場合もチェックできる。
●指定した属性を持つかチェック
書式
'属性名' in インスタンス名
※クラス継承している場合もチェックできる。
※属性を定義していても値を割り当て初期化していなければfalseとなる。
例)
class Animal {
name: string = '';
}
class Snake extends Animal {
}
class Cherry {
name: string;
}
var animal = new Animal();
var snake = new Snake();
var cherry = new Cherry();
console.log(animal instanceof Animal); //true
console.log(snake instanceof Animal); //true
console.log(cherry instanceof Animal); //false
console.log('name' in animal); //true
console.log('name' in snake); //継承しているのでtrue
console.log('name' in cherry); //初期化していないのでfalse
(1)モジュールの概要
・ネームスペースを簡易にする仕組みを提供。
・実行時のモジュールロード方法としてCommonJS、AMD(Asynchronous Module Definition)などがある。
・内部モジュールは、そのスコープを制限する関数内にメンバーをエンクローズする。
内部モジュール名はグローバルスコープに追加され、エクスポートされたメンバーはグローバルにスコープされたモジュール識別子を介してアクセスできる。
・外部モジュールはグローバルスコープに追加されない。
外部モジュールのメンバーは、CommonJSを使用している場合はエイリアスを介して利用され、AMDを使用している場合はコールバック関数のスコープに制限される。
(2)内部モジュールの概要
・内部モジュールは、関連する機能をグループするのに使用できる。
・内部モジュールは、モジュールのスコープ内にエンクローズされたモジュールコンテンツを含むシングルトンのインスタンス。
・変数、関数、オブジェクト、クラス、インタフェースをモジュール内にグループ化する事によってグローバルスコープから隔離し、名前の重複を避ける事が出来る。
・一つのモジュールを複数のファイルに分割する事が出来、一つのファイルのサイズを小さくする事が出来る。
・クラスメンバはデフォルトでpublicだが、モジュール内のコンテンツはデフォルトでは隠蔽される。
・モジュール外からモジュール内のメンバを利用できるようにするには、exportキーワードを付与する。
例)
module ModA {
//ModA.IntAでアクセス
export interface IntA {
・・
}
//ModA.ClsA
export class ClsA implements IntA {
・・
}
// ModA内でのみ利用可能
var varA = 35;
class ClsB implements IntA {
・・
}
var varB = new ModA.ClsA();
・内部モジュール内でimportキーワードを指定すると他のモジュールやそのメンバのエイリアスとして使用できる。モジュールをネストして定義し、モジュールの指定が長くなる場合に便利。
例)
module modB {
import alias = ModA.intA;
・・
}
●内部モジュールのインクルード
・内部モジュールは、外部モジュールのように自動モジュールロードによるメリットはないが、ロードするスクリプトのメソッドを好きなように実装できる。
下記方法を使用できる。
①それぞれのファイルをWebページ内のscriptタグでインクルードする。
②プログラムを一つのファイルにコンパイルし、そのファイルをscriptタグでインクルードする。
③プログラムを一つのファイルにコンパイルし、縮小化し、scriptタグでインクルードする。
④外部モジュールに切り替え、モジュールローダーを使用する。
・ある種の統合開発環境では、モジュールは自動で認識されるが、依存するファイルを自動で探索して認識できない場合は、リファレンスコメントを使ってヒントを記述できる。
例)
///module modB { import alias = ModA.intA; ・・ }
※上記リファレンスコメントは、TypeScriptコンパイラで複数のファイルを一つのファイルにコンパイルする場合にも利用される。
(3)外部モジュールの概要
・大規模なアプリケーションを作成する際には外部モジュールとモジュールロードの仕組みは必須のツール。
・外部モジュールの名前は、拡張子を除くソースファイル名と同じ。
内部モジュールでは、下記のようにmoduleブロックを記述していたが、外部モジュールでは、ファイル名がモジュール名となるので記述する必要がない。
"module モジュール名 {"
・外部モジュールを実行時にロードして使用するには、下記例のようにrequire文とimport文でモジュールのパス(拡張子は除く)を指定する。
importで指定した名前をエイリアスとして外部モジュールを参照できる。
例)
import validation = require('./Validation');
var lettersRegexp = /^[A-Za-z]+$/;
export class LettersOnlyValidator implements validation.StringValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}
(4)モジュールロードの概要
・AMDとCommonJDの二つのモジュールロードの方法がある。
・TypeScriptのコンパイラにどちらのロード方法を使用するか指定する必要がある。
1)CommonJSを使ってロード
・NodeJS用。
・require関数がコールされるたびにモジュールをロード。
モジュールがロードされるとプログラムの実行が再開する。
例)
(sample.ts)
import m = require('mod');
export var t = m.something + 1;
(tsc --module commonjs sample.ts)
var m = require('mod');
exports.t = m.something + 1;
生成されたJavaScriptのコードでは、モジュールロードの後にコードが置かれる。
2)AMDを使ってロード
・モジュールロード中にプログラムの実行を停止せず、コードをコールバック関数に渡す。モジュールがロードされるとコールバック関数が実行される。
これによってその間に他のコードを実行する事が出来る。
(sample.ts)
import m = require('mod');
export var t = m.something + 1;
(tsc --amd commonjs sample.ts)
define(["require", "exports", 'mod'], function(require, exports, m) {
exports.t = m.something + 1;
});
生成されたJavaScriptのコードでは、コールバック関数内に置かれている事が分かる。
function identity<T>(arg: T): T {
return arg;
}
var output = identity<string>("myString");
var output = identity("myString");
●インターフェースの例
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
var myIdentity: GenericIdentityFn<number> = identity;
●クラスの例
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
var myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
●型の制約の例
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);// Now we know it has a .length property, so no more error
return arg;
}
loggingIdentity({length: 10, value: 3});
○structural typing
・同じ外部構造(インタフェース、特に暗黙的なもの)を持つ値を同じ型であるとするもの。
・明示的に名前を指定する必要がなく、構造がマッチする型であれば利用可能。
・同じ属性を有する同等の型を作成することが可能。
・既存のコードを変更せずに、同等の型を導入できる。
・外部クラスから継承せずに外部コードに渡す事が出来る。
・既存のコードを変更せずに新しいスーパータイプを作成できる。
・TypeScriptなど。
○nominative typing
・型宣言の構文からのみ型の同一性を判定する。型が同じ"名前"を持たなければならない。
・インタフェースを実装する場合、明示的にインタフェース名を指定して宣言しなければならない。
・偶然同じ属性を有する同等の型を作成することを防ぐ事が出来る。
・C#やJavaなど。
●TypeScriptはstructural typing
例)
interface Named {
name: string;
}
class Person {
name: string;
}
var p: Named;
// OK, because of structural typing
p = new Person();
(1)型定義ファイル
・ファイルの拡張子を".d.ts"とする。
・ファイル内のモジュール、変数、関数、クラス、enumをdeclareキーワードでプレフィックスして宣言する。
・型定義ファイルを使用する場合は、リファレンスコメントを使って指定する(/// <reference path="・・・/xxx.d.ts" />かimport文で指定する。
import文を使用する場合は、同じフォルダー内でJavaScriptファイルと同じファイル名にする。
(2)DefinitelyTypedとは?
・TypeScriptの型定義のリポジトリ。
・Angular, Backbone, Bootstrap, Breeze, D3, Ember, jQuery, Knockout, NodeJS, Underscoreなどの定義ファイルが含まれている。
・Jasmine, Mocha, qUnitなどのテストフレームワークの定義ファイルも含まれている。
・TypeScriptではカプセル化をフルにサポート。
・クラスインスタンスは、データとそのデータを操作するメソッドの両方を含む事ができ、そのデータをprivateのアクセス制限を指定してクラスインスタンス外部から隠蔽することが出来る。
●サンプルプログラム
class Totalizer {
private total = 0;
private taxRate = 0.08;
calc(amount: number) {
var tax = amount * this.taxRate;
var included = amount + tax;
this.total += included;
}
getTotal() {
return this.total;
}
}
var totalizer = new Totalizer();
totalizer.calc(150);
var total = totalizer.getTotal();
console.log(total);
※'use strict';でstrictモードにする。
'use strict';
{
var name_var = 'スコープのサンプル:var';
console.log('ブロック内: ' + name_var);
}
//varで定義しているのでブロック外でもアクセスできる
console.log('ブロック外: ' + name_var);
{
let name_let = 'スコープのサンプル:let';
console.log('ブロック内: ' + name_let);
}
//letで定義しているのでブロック外ではアクセスできない。
console.log('ブロック外: ' + name_let);
●コンパイル
letはECMAScript5ではサポートされていない。コンパイル時にes6を指定する。
tsc --watch -m amd -t es6 --emitDecoratorMetadata let.ts
・importの引数内でエクスポートしたクラス、関数、変数の名前などを指定。
・別名を使用したい場合は、"as"を使って指定。
例)
(exporter.js)
export function someMethod() {}
export var another = {};
(import.js)
import { someMethod, another as newName } from './exporter';
someMethod();
typeof newName == 'object';
●サンプルコード
(systemjs-sample-export.ts)
export class Greeter {
constructor(message) {
this.message = message;
}
greet() {
var element = document.querySelector('#message');
element.innerHTML = this.message;
}
};
(systemjs-sample1.ts)
import {Greeter as Sample} from './systemjs-sample-export';
var greeter = new Sample('Hello, world!');
greeter.greet();
・varキーワードで宣言した変数は、関数内のいかなる場所で宣言されたとしても、その関数の先頭で宣言されたのと同じように動作する。
例1)
関数の下の方で変数を宣言し値を割り当てたとしても、実際には(例1の実際の動作)にあるように関数の先頭で宣言が行われてしまい、値が割り当てられていない状態になってしまう。
var mytest = "global";
function testfunc() {
alert(mytest); //"undefind"と表示
var mytest = "local";
alert(mytest); //"local"と表示
}
testfunc();
(例1の実際の動作)
var mytest = "global";
function testfunc() {
var mytest;
alert(mytest); //"undefind"と表示
mytest = "local";
alert(mytest); //"local"と表示
}
testfunc();
●letを使う場合
・letキーワードを使用する場合にも変数の巻上げが適用され、この場合は関数の先頭ではなく、ブロックスコープの先頭に巻き上げられる。
例2)
var mytest = "global";
function testfunc1() {
alert(mytest); //"global"と表示
{
let mytest = "local";
alert(mytest); //"local"と表示
}
}
testfunc1();
・コールバック関数を引数として渡し、操作が完了したときにコールバック関数を実行する。
これによってメインスレッドがブロッキングされるのを防ぐ事ができる。
●コールバック関数の実行方法
①callメソッドを使用
コールバック関数内で使用する"this"キーワードのコンテキストをセットするのに使う値を指定する。
export function run1(callback: (arg: string) => void) {
callback.call(this, 'Sample #1');
}
function callbackFunction(arg: string) {
alert(arg);
}
run1(callbackFunction);
②applyメソッドを使用
コールバック関数内で使用する"this"キーワードのコンテキストをセットするのに使う値を配列で指定する。
export function run2(callback: (arg: string) => void) {
callback.apply(this, ['Snample #2']);
}
function callbackFunction(arg: string) {
alert(arg);
}
run2(callbackFunction);
③callbackに直接引数として指定
export function run3(callback: (arg: string) => void) {
callback('Sample #3');
}
function callbackFunction(arg: string) {
alert(arg);
}
run3(callbackFunction);
①subscriberをコレクションに追加。
②メッセージをpublish
③publisherがメッセージを受け取るとすべてのsubscriberに配布。
interface Subscriber {
() : any;
}
export class Publisher {
private subscribers: Subscriber[] = [];
addSubscriber(subscriber: Subscriber) {
this.subscribers.push(subscriber);
}
notify(message: string) {
for (var i = 0; i < this.subscribers.length; i++) {
this.subscribers[i].apply(message);
}
}
}
var publisher = new Publisher();
publisher.addSubscriber(function () {
console.log('Subscriber-A: ' + this);
});
publisher.addSubscriber(function () {
console.log('Subscriber-B: ' + this);
});
publisher.notify('Notify message');
①キャプチャフェーズ
DOMツリーをたどってルート要素から発生要素を探しに行く。
②ターゲットフェーズ
発生要素を検出する
③バブリングフェーズ
ルート要素まで遡る
●addEventListener
・構文
target.addEventListener(type, listener [, useCapture]);
"useCapture"はデフォルトはfalseでバブリングフェーズ、trueを指定するとキャプチャフェーズで実行する。
●サンプルコード
クリックイベントをリッスンし、イベントのフェーズと検知した要素をログ表示するサンプルを作成
export class ClickLogger {
constructor () {
document.addEventListener('click', this.eventListener, true);
}
eventListener(e: Event) {
var phase = e.eventPhase;
var tag = (e.target).tagName;
console.log('フェーズ:' + phase +
' 検知された要素:' + tag);
}
}
var clickLogger = new ClickLogger();