26
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

TypeScript 入門:変数宣言

Posted at
1 / 42

以下の要約:
https://www.typescriptlang.org/docs/handbook/variable-declarations.html


TypeScript の変数宣言

  • 変数宣言の種類
    • var : 昔ながらの記法
    • let : 新しい記法、var の落とし穴を回避
    • const : let の再代入を不可にしたもの
  • var よりも letconst を使うべき

var 宣言


var 宣言

var a = 10;
  • 関数の中から外の変数にもアクセスできる
function f() {
    var a = 10;
    return function g() {
        var b = a + 1;
        return b;
    }
}

var g = f();
g(); // returns '11'

変数値が決まるタイミング

  • 関数 freturn した後に関数 g を宣言しても、ag の呼び出し時の値になる
function f() {
    var a = 1;

    a = 2;
    var b = g();
    a = 3;

    return b;

    function g() {
        return a;
    }
}

f(); // returns '2'

var の気持ち悪い変数スコープ

  • スコープはブロックに関係なく、function / module / namespace / グローバルいずれかの全体になる!
function f(shouldInitialize: boolean) {
    if (shouldInitialize) {
        var x = 10;
    }

    return x;
}

f(true);  // returns '10'
f(false); // returns 'undefined'

事態は悪化!

  • 同じ変数名を何度も宣言できてしまう…
  • i の値は何…?
function sumMatrix(matrix: number[][]) {
    var sum = 0;
    for (var i = 0; i < matrix.length; i++) {
        var currentRow = matrix[i];
        for (var i = 0; i < currentRow.length; i++) {
            sum += currentRow[i];
        }
    }

    return sum;
}

var 変数値の落とし穴

  • 次の実行結果はどうなるか?
for (var i = 0; i < 10; i++) {
    setTimeout(function() { console.log(i); }, 100 * i);
}
  • 答え
    • : 10 10 10 10 10 10 10 10 10 10
    • : 0 1 2 3 4 5 6 7 8 9

ワークアラウンド

  • IIFE (Immediately Invoked Function Expression) を使おう
for (var i = 0; i < 10; i++) {
    // capture the current state of 'i'
    // by invoking a function with its current value
    (function(i) {
        setTimeout(function() { console.log(i); }, 100 * i);
    })(i);
}

let 宣言


let 宣言

let hello = "Hello!";
  • var のこれまでの問題点を解決するために導入された

let の変数スコープ

  • let はレキシカルスコープ(=ブロックスコープ)
function f(input: boolean) {
    let a = 100;

    if (input) {
        // Still okay to reference 'a'
        let b = a + 1;
        return b;
    }

    // Error: 'b' doesn't exist here
    return b;
}

宣言の前には変数にアクセスできない

  • 何を当たり前のことを…
a++; // illegal to use 'a' before it's declared;
let a;
  • 実際には、変数はスコープ全体ですでに「存在」しているが、宣言までの部分は「一時的なデッドゾーン」として扱われる

…どういうこと?

  • 宣言前に、変数を「捕捉」することが可能
    • イリーガルなのは、「捕捉」した関数を呼び出すことだけ
    • ES2015 のモダンなランタイムならエラーを吐くが、TypeScript 自身はこれを許容する
function foo() {
    // okay to capture 'a'
    return a;
}

// illegal call 'foo' before 'a' is declared
// runtimes should throw an error here
foo();

let a;

再宣言とシャドーイング

  • let は再宣言を許可しない
let x = 10;
let x = 20; // error: can't re-declare 'x' in the same scope
  • こんな例もエラー
function f(x) {
    let x = 100; // error: interferes with parameter declaration
}

function g() {
    let x = 100;
    var x = 100; // error: can't have both declarations of 'x'
}

スコープを分ければ OK

function f(condition, x) {
    if (condition) {
        let x = 100;
        return x;
    }

    return x;
}

f(false, 0); // returns '0'
f(true, 0);  // returns '100'

シャドーイング

  • ネストしたスコープで同じ変数名を let 宣言すること
function sumMatrix(matrix: number[][]) {
    let sum = 0;
    for (let i = 0; i < matrix.length; i++) {
        var currentRow = matrix[i];
        for (let i = 0; i < currentRow.length; i++) {
            sum += currentRow[i];
        }
    }

    return sum;
}

ブロックスコープの変数捕捉

  • スコープを実行すると、変数の「環境」が作られる
  • スコープ終了後も、その環境は存在できる
function theCityThatAlwaysSleeps() {
    let getCity;

    if (true) {
        let city = "Seattle";
        getCity = function() {
            return city;
        }
    }

    return getCity();
}

IIFE よさらば

  • IIFE とは結局、無理やり新しい変数環境を作るトリック
  • for ループで let を使うと、毎回新しいスコープが生成されている!
for (let i = 0; i < 10 ; i++) {
    setTimeout(function() { console.log(i); }, 100 * i);
}

const 宣言


const 宣言

const numLivesForCat = 9;
  • 一度値を割り当てたら、再割り当てできない
  • スコーピング規則は let と同じ

immutable ではない

const numLivesForCat = 9;
const kitty = {
    name: "Aurora",
    numLives: numLivesForCat,
}

// Error
kitty = {
    name: "Danielle",
    numLives: numLivesForCat
};

// all "okay"
kitty.name = "Rory";
kitty.name = "Kitty";
kitty.name = "Cat";
kitty.numLives--;

immutable にしたいときは?

  • readonly を使おう
interface Point {
    readonly x: number;
    readonly y: number;
}

letconst


letconst

  • 最小権限の原則に基づけば、可能な限り const を使うべき
    • 不用意な変数の書き換えを防げる
    • データフローを追いやすくなる
  • 一方で、letvar と同じくらい字数が少なくて書くのが簡単(え…?)

分割代入


分割代入(Destructuring)


配列

let input = [1, 2];
let [first, second] = input;
console.log(first); // outputs 1
console.log(second); // outputs 2

既存の変数や関数のパラメータにも使える

  • 変数
// swap variables
[first, second] = [second, first];
  • 関数パラメータ
function f([first, second]: [number, number]) {
    console.log(first);
    console.log(second);
}
f([1, 2]);

高度な使い方

  • ... 構文で残りをリストに格納
let [first, ...rest] = [1, 2, 3, 4];
console.log(first); // outputs 1
console.log(rest); // outputs [ 2, 3, 4 ]
  • 要らない要素は無視できる
let [first] = [1, 2, 3, 4];
console.log(first); // outputs 1
let [, second, , fourth] = [1, 2, 3, 4];

オブジェクト

let o = {
    a: "foo",
    b: 12,
    c: "bar"
};
let { a, b } = o;

変数宣言なしでも OK

({ a, b } = { a: "baz", b: 101 });
  • ( ... ); が必要なことに注意
    • JavaScript では { から始まる文は、ブロックとして解釈されてしまうため
  • (誰が使うの?)

... 構文も使える

  • 残りはオブジェクトとして格納される
let { a, ...passthrough } = o;
let total = passthrough.b + passthrough.c.length;

プロパティのリネーム

let { a: newName1, b: newName2 } = o;
  • 以下と同等
let newName1 = o.a;
let newName2 = o.b;
  • 型宣言したい場合
let { a, b }: { a: string, b: number } = o;

デフォルト値

function keepWholeObject(wholeObject: { a: string, b?: number }) {
    let { a, b = 1001 } = wholeObject;
}
  • b が未定義の場合 b = 1001 になる

関数宣言の分割代入

type C = { a: string, b?: number }
function f({ a, b }: C): void {
    // ...
}

デフォルト値の定義は難しい

  • オブジェクト自体にデフォルト値を定義
function f({ a, b } = { a: "", b: 0 }): void {
    // ...
}
f(); // ok, default to { a: "", b: 0 }
  • オプションの b にだけデフォルト値を定義
function f({ a, b = 0 } = { a: "" }): void {
    // ...
}
f({ a: "yes" }); // ok, default b = 0
f(); // ok, default to { a: "" }, which then defaults b = 0
f({}); // error, 'a' is required if you supply an argument
  • 分割代入の使用はシンプルかつ最小限に

スプレッド(Spread)

  • 分割代入の反対をやる演算子
let first = [1, 2];
let second = [3, 4];
let bothPlus = [0, ...first, ...second, 5];
// => [0, 1, 2, 3, 4, 5]

オブジェクトの場合

let defaults = { food: "spicy", price: "$$", ambiance: "noisy" };
let search = { ...defaults, food: "rich" };
// => { food: "rich", price: "$$", ambiance: "noisy" }
  • 後から出てきた値に上書きされる
let defaults = { food: "spicy", price: "$$", ambiance: "noisy" };
let search = { food: "rich", ...defaults };
// => { food: "spicy", price: "$$", ambiance: "noisy" }

オブジェクトスプレッドの制約

class C {
  p = 12;
  m() {
  }
}
let c = new C();
let clone = { ...c };
clone.p; // ok
clone.m(); // error!

End

26
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?