Gutenberg Handbook 読書メモ (2) Block Tutorial

Gutenberg Handbook 読書メモ (1) の続き。

Block

https://wordpress.org/gutenberg/handbook/blocks/

新しいブロック型を作るためのチュートリアル。

このチュートリアルのコードは下記GitHubリポジトリから入手できる。
https://github.com/WordPress/gutenberg-examples

なお、本記事のサンプルコードはESNextとJSXを使用しているが、Gutenbergのブロック機能はES5でも利用できる。

Writing Your First Block Type

https://wordpress.org/gutenberg/handbook/blocks/writing-your-first-block-type/

はじめに、固定されたメッセージを表示するブロック型を作成する。静的なコンテンツを含むブロックはJavaScriptでregisterBlockType関数を使うだけで実装できる。この関数はブロックの青写真となり、エディタ上での表示、編集・保存時の動作等を定義する。

ブロックのスクリプトを追加(enque)する

ブロック型はJavaScriptで実装されるが、WordPressのenqueue_block_editor_assetsアクションを使ってスクリプトがエディタに読み込まれるようにする必要がある。このアクションはwp_enqueue_scriptsと似ているが、エディタのスクリプトとスタイルに限定される点に違いがある。

<?php

function gutenberg_boilerplate_enqueue_block_editor_assets() {
    wp_enqueue_script(
        'gutenberg-boilerplate-es5-step01',
        plugins_url( 'step-01/block.js', __FILE__ ),
        array( 'wp-blocks', 'wp-element' )
    );
}
add_action( 'enqueue_block_editor_assets', 'gutenberg_boilerplate_enqueue_block_editor_assets' );

特に注意が必要なのは以下のように設定していること。

  • wp-blocks は、ブロック型の登録と関連する関数の読み込みを行う
  • wp-element はWordPressのElement抽象化機能を使用する

ブロックの登録

スクリプトが登録されたので、次にブロックを実装する。

const { registerBlockType } = wp.blocks;
const blockStyle = { backgroundColor: '#900', color: '#fff', padding: '20px' };

registerBlockType( 'gutenberg-boilerplate-esnext/hello-world-step-01', {
    title: 'Hello World (Step 1)',

    icon: 'universal-access-alt',

    category: 'layout',

    edit() {
        return <p style={ blockStyle }>Hello editor.</p>;
    },

    save() {
        return <p style={ blockStyle }>Hello saved content.</p>;
    },
} );

ブロックを登録すると、エディタのinserterのダイアログで利用できるようになる。inserterではtitle, icon, categoryが利用される。アイコンは組み込みのアイコンから選ぶか、独自のsvg要素を指定する。

ブロック名は nemaspace/block-name という構造である必要がある。

editsave関数は、エディタ画面でのレンダリングと保存後のレンダリングを定義する。

Applying Styles From a Stylesheet

https://wordpress.org/gutenberg/handbook/blocks/applying-styles-with-stylesheets/

↑のサンプルではインラインでスタイルを定義しているが、外部のスタイルシートにスタイルを定義して、これを読み込む方法もある。

エディタは、それぞれのブロック型に対して、独自のクラス名を自動的に生成する。このクラス名には、editsave関数のオブジェクト引数からアクセスできる。

const { registerBlockType } = wp.blocks;

registerBlockType( 'gutenberg-boilerplate-esnext/hello-world-step-02', {
    title: 'Hello World (Step 2)',

    icon: 'universal-access-alt',

    category: 'layout',

    edit( { className } ) {
        return <p className={ className }>Hello editor.</p>;
    },

    save( { className } ) {
        return <p className={ className }>Hello saved content.</p>;
    },
} );

クラス名は、ブロック名を元に生成される。

/* 対応するブロックは gutenberg-boilerplate-esnext/hello-world-step-02 */
.wp-block-gutenberg-boilerplate-es5-hello-world-step-02 {
    color: green;
    background: #cfc;
    border: 2px solid #9c9;
    padding: 20px;
}

エディタでのみ使用するブロックのアセットを登録(enque)する

enqueue_block_editor_assetsアクションを使うことでエディタでのみ使用するスタイルを登録できる。

<?php

function gutenberg_boilerplate_enqueue_block_editor_assets() {
    wp_enqueue_script(
        'gutenberg-boilerplate-es5-step02',
        plugins_url( 'step-02/block.js', __FILE__ ),
        array( 'wp-blocks', 'wp-element' )
    );
    wp_enqueue_style(
        'gutenberg-boilerplate-es5-step02-editor',
        plugins_url( 'step-02/editor.css', __FILE__ ),
        array( 'wp-edit-blocks' ),
        filemtime( plugin_dir_path( __FILE__ ) . 'step-02/editor.css' )
    );
}
add_action( 'enqueue_block_editor_assets', 'gutenberg_boilerplate_enqueue_block_editor_assets' );

エディタとフロントエンドで使用するアセットを登録(enque)する

ブロックのスクリプトはエディタでのみ必要なものだが、スタイルはサイトの表側とエディタの両方で読み込みたいこともある。

enqueue_block_editor_assetsアクションはエディタの読み込み時にしかトリガーされない。サイトの表側でも読み込まれるようにするには、enqueue_block_assetsアクションを使うのが良い。

<?php

function gutenberg_boilerplate_es5_enqueue_common_assets() {
    wp_enqueue_style(
        'gutenberg-boilerplate-es5-step02',
        plugins_url( 'step-02/style.css', __FILE__ ),
        array( 'wp-blocks' ),
        filemtime( plugin_dir_path( __FILE__ ) . 'step-02/style.css' )
    );
}
add_action( 'enqueue_block_assets', 'gutenberg_boilerplate_es5_enqueue_common_assets' );

JavaScriptをサイトの表側に限定したいなら、enqueue_block_assetsアクションを使った上で、! is_admin()の場合のみ読み込むようにすれば良い。

Introducing Attributes and Editable Fields

https://wordpress.org/gutenberg/handbook/blocks/introducing-attributes-and-editable-fields/

属性(attributes)

ブロックの出力は属性(attributes)の値によって決まる。

const { registerBlockType, Editable, source } = wp.blocks;

registerBlockType( 'gutenberg-boilerplate-esnext/hello-world-step-03', {
    title: 'Hello World (Step 3)',

    icon: 'universal-access-alt',

    category: 'layout',

    attributes: {
        content: {
            type: 'array',
            source: 'children',
            selector: 'p',
        },
    },

    edit( { attributes, className, focus, setAttributes, setFocus } ) {
        const { content } = attributes;

        function onChangeContent( newContent ) {
            setAttributes( { content: newContent } );
        }

        return (
            <Editable
                tagName="p"
                className={ className }
                onChange={ onChangeContent }
                value={ content }
                focus={ focus }
                onFocus={ setFocus }
            />
        );
    },

    save( { attributes, className } ) {
        const { content } = attributes;

        return <p className={ className }>{ content }</p>;
    },
} );

新しいブロック型を登録する際は、attributesプロパティによってeditsave関数で受け取りたいオブジェクトの形を指定できる。それぞれの値はsource function(???)であり、ブロックをマークアップするための値を取得する。

上記サンプルコードでは、contentの値を子供のp要素から取得している。

コンポーネントとEditableコンポーネント

ここまで、createElement関数(またはJSX)を使ってDOMノードを作成してきた。これらの振る舞いは、「コンポーネント」に閉じ込めることもできる。このような抽象化を行うことで、処理の共通化や複雑さの隠蔽といった恩恵を得ることができる。ブロックを作成する際に利用できるコンポーネントがいくつも用意されている。サンプルコードではEditableコンポーネントを使用している。

Editableコンポーネントはtextarea要素の機能強化版である。太字、イタリック体、ハイパーリンク等の機能を持っている(Editableコンポーネントは、現行のビジュアルエディタと同様、TinyMCEを使って実装されている)。

Block Controls: Toolbars and Inspector

https://wordpress.org/gutenberg/handbook/blocks/block-controls-toolbars-and-inspector/

ブロックのカスタマイズをシンプルにして、ユーザに一貫した体験を提供するため、エディタのプレビュー機能の提供を助ける組み込みのUIパターンが用意されている。wp.blocksグローバル変数は、エディタのインタフェースとして利用できる、いくつかの共通コンポーネントを含んでいる。

ツールバー

ユーザがブロックを選ぶと、その上にツールバーが現れ、その上にいくつものボタンが表示される。これらのブロック用コントロールは、選択された要素がEditableコンポーネントである場合などに自動的に表示される。

ツールバーをカスタマイズして、特定のブロック専用のコントロールを読み込むこともできる。ブロック型のedit関数の戻り値がBlockControls要素を含んでいれば、これらのコントロールがブロックのツールバーに表示される。

const {
    registerBlockType,
    Editable,
    BlockControls,
    AlignmentToolbar,
    source
} = wp.blocks;

registerBlockType( 'gutenberg-boilerplate-esnext/hello-world-step-04', {
    title: 'Hello World (Step 4)',

    icon: 'universal-access-alt',

    category: 'layout',

    attributes: {
        content: {
            type: 'array',
            source: 'children',
            selector: 'p',
        },
    },

    edit( { attributes, className, focus, setAttributes, setFocus } ) {
        const { content, alignment } = attributes;

        function onChangeContent( newContent ) {
            setAttributes( { content: newContent } );
        }

        function onChangeAlignment( newAlignment ) {
            setAttributes( { alignment: newAlignment } );
        }

        return [
            !! focus && (
                <BlockControls key="controls">
                    <AlignmentToolbar
                        value={ alignment }
                        onChange={ onChangeAlignment }
                    />
                </BlockControls>
            ),
            <Editable
                key="editable"
                tagName="p"
                className={ className }
                style={ { textAlign: alignment } }
                onChange={ onChangeContent }
                value={ content }
                focus={ focus }
                onFocus={ setFocus }
            />
        ];
    },

    save( { attributes, className } ) {
        const { content } = attributes;

        return <p className={ className }>{ content }</p>;
    },
} );

ブロックが選択されている場合のみBlockControlsを読み込むよう注意が必要である。↑ではfocus変数の値を見ることで、BlockControlsの条件レンダリングを実現している。

Inspector

フォームのフィールドのためにより広い編集エリアがほしい場合、inspectorを使うことができる。InspectorControlsを読み込んで、edit関数で表示すればよい。

Gutenberg Handbook 読書メモ (1)

WordPressのエディタ画面のカスタマイズのため、新エディタ「Gutenberg」のドキュメントを読んでます。覚え書きを書き残しておきます。

Introduction

https://wordpress.org/gutenberg/handbook/

  • “Gutenberg”とは、新しいWordPressエディタのコードネーム
  • リッチなレイアウトを誰でも簡単に作れるようにするのが目的
  • “Block” という概念を導入することで、ショートコードやカスタムHTMLを不要にする

The Language of Gutenberg

https://wordpress.org/gutenberg/handbook/language/

  • Gutenbergの中心にあるのは「ブロック」の概念である
  • Gutenbergでは、記事(post)はブロックの集合からなる
  • ブロックは活版印刷における活字のようなもので、記事の編集時には必要だが、最終的に出力される記事には含まれない

ブロックはHTMLよりも高い次元にある

  • ブロックはHTMLを出力するための機能だが、ユーザが編集を助けるための機能も伴っている
  • ブロックは最終的なHTMLを出力するために必要な情報を全て保持している

2つのpost

  • Gutenbergの記事(post)はブロックのことを知っている(block-aware)
  • Gutenbergのpostは、post_contentそのものではない

ブロックのツリー

  • 実行時に、ブロックはメモリ上に保持される
  • GutenbergのpostはHTMLではなく、オブジェクトのツリーである
  • ルートノードは必要とせず、ノードのコレクションである
[
    {
        type: 'core/cover-image',
        attributes: {
            url: 'my-hero.jpg',
            align: 'full',
            hasParallax: false,
            hasBackgroundDim: true
        },
        children: [
            "Gutenberg posts aren't HTML"
        ]
    },
    {
        type: 'core/paragraph',
        children: [
            "Lately I've been hearing plen…"
        ]
    }
]

シリアライゼーションとHTMLコメントの目的

  • Gutenbergのデータモデルは、記事の編集中のメモリに保持されるが、レンダリング結果からは痕跡が消える
  • Gutenbergはデータモデルをpost_contentに保存できるようシリアライズする
  • シリアライズの過程では、ツリーをHTMLに変換する。その際、HTMLコメントをブロックの境界として用いる
  • 編集時には、HTMLコメントからツリーを再構築する
  • 仮に、Gutenbergに対応していないテーマを使用したとしても、最低限のコンテンツは描画されるようになっている(動的な要素は描画されない)

デリミタと式文法の構文解析

  • HTMLコメントでは、二重のハイフン(–)を除いて、文法上の制約は無い
  • ブロックの属性はJSONリテラルとしてコメントに埋め込まれる

シリアライズされたブロックの中身

ブロックがwp_contentに保存されると、その属性はコメント内に保存される

<!-- wp:image -->
<figure class="wp-block-image">
    <img src="source.jpg" alt="" />
</figure>
<!-- /wp:image -->

サーバサイドでレンダリングされる動的なブロックは以下のようになる:

<!-- wp:latest-posts {"postsToShow":4,"displayPostDate":true} /-->

Gutenbergのライフサイクル

要約すると、Gutenbergの記事を編集する手順は、文書を保存してツリーを生成するところから始まる。最終的に、ブロックはpost_contentに保存される。編集中、全ての操作はブロックのツリーに変更を加える。

Extensibility

https://wordpress.org/gutenberg/handbook/extensibility/

ブロックの作成

ブロックAPIを使うことで、静的なブロック、サーバサイドでレンダリングされる動的ブロック、post_metaにデータを保存するブロックなどを作成できる。

※メモ:クライアントサイドでの動的なレンダリングはできない???

静的ブロックは以下のように作成できる。

var el = wp.element.createElement;

wp.blocks.registerBlockType( 'mytheme/red-block', {
    title: 'Red Block',
    icon: 'universal-access-alt',
    category: 'layout',
    edit: function() {
        return el( 'div', { style: { backgroundColor: '#900', color: '#fff', padding: '20px' } }, 'I am a red block.' );
    },
    save: function() {
        return el( 'div', { style: { backgroundColor: '#900', color: '#fff', padding: '20px' } }, 'I am a red block.' );
    }
} );

ブロックの削除

ブラックリストの使用

以下のようなJavaScriptコードを書き、

// myplugin.js

wp.blocks.unregisterBlockType( 'core/verse' );

エディタ内で読み込むようにすればよい。

<?php
// myplugin.php

function myplugin_blacklist_blocks() {
    wp_enqueue_script(
        'myplugin-blacklist-blocks',
        plugins_url( 'myplugin.js', __FILE__ ),
        array( 'wp-blocks' )
    );
}
add_action( 'enqueue_block_editor_assets', 'myplugin_blacklist_blocks' );

ホワイトリストの使用

特定のブロック以外を無効化したい場合、以下のように書く。

// myplugin.js
var allowedBlocks = [
    'core/paragraph',
    'core/image',
    'core/html',
    'core/freeform'
];

wp.blocks.getBlockTypes().forEach( function( blockType ) {
    if ( allowedBlocks.indexOf( blockType.name ) === -1 ) {
        wp.blocks.unregisterBlockType( blockType.name );
    }
} );

inserterからブロックを隠す

inserterに表示するブロックはサーバサイドでフィルタリングできる。

// 許可するブロック型の名前のリスト
add_filter( 'allowed_block_types', function() {
    return [ 'core/paragraph' ];
} );

ブロックの編集(実験的)

既存のブロックの挙動を変更するため、Gutenbergはフィルターを提供している:

  • registerBlockType: ブロック設定のフィルタリング
  • getSaveContent.extraProps: save関数でWP elementを返す全てのブロックに適用されるフィルタ。ブロックにpropsを追加する場合に使用する
  • BlockEdit: ブロックのedit関数で受け取ったWP elementを編集するのに使う

全てのブロックにデフォルトで背景を設定している。

// Our filter function
function addBackgroundProp( props ) {
    return Object.assign( props, { backgroundColor: 'red' } );
}

// Adding the filter
wp.blocks.addFilter(
    'getSaveContent.extraProps',
    'myplugin\add-background',
    addBackgroundProp
);

エディターUIの拡張(SlotとFill)

Coming soon. とのこと(2017/12/04現在)

Block API

https://wordpress.org/gutenberg/handbook/block-api/

プラグインやテーマは、エディタに対する独自の機能や追加の機能をブロックとして登録できる

ブロック型の登録

全てのブロックは、ブロック型定義の登録を行う必要がある。これには、registerBlockType関数を使用する。

ブロック名

新しいブロック型の登録にはregisterBlockType関数を使用する。この関数は、ブロック名と設定オブジェクトを引数に取る。

ブロック名は namespace/block-name という構造になっていなければならない。namespaceはプラグインやテーマの名前である。

// Registering my block with a unique name
registerBlockType( 'my-plugin/book', {} );

注意:ブロック名には小文字のアルファベットと数字、ダッシュ(-)記号のみを含み、最初の1文字目はアルファベットでなければならない

ブロック設定オブジェクトの代表的な設定は下記の通り

  • title(必須): ブロックのinserter上に表示する名前
  • category(必須): inserter上の分類。common formatting layout widgets embedのいずれか
  • icon: ブロックのアイコン WordPressのDashiconsの名前を指定するか、独自のsvg要素を指定する
  • keywords: 検索時のキーワード
  • attributes: ブロックが使用する構造化されたデータ
  • transforms: 説明なし。WIPらしい
  • useOnce: 記事ごとに1個だけしか使えない場合はtrueにする
  • supports: サポート機能を拡張する設定
    • anchor(default: false): ページ内リンクできるようにする
    • customClassName(default: true): ブロックのラッパー要素にブロック独自のクラス名をつける
    • className(default: true): .wp-block-bour-block-name という形式のクラスをつけるか
  • supportHTML(default: true): HTMLモードで編集可能か

Edit and Save

https://wordpress.org/gutenberg/handbook/block-edit-save/

editsave関数によって、ブロックがどのようにレンダリングされるか定義できる。
※以下のサンプルコードではESNextとJSXを使用している。

Edit

edit 関数はエディタを使用している際のブロックの構造を定義する。

// Defining the edit interface
edit() {
    return <hr />;
}

この関数は、引数のオブジェクトから以下のプロパティを受け取ることができる。

attributes

利用可能な属性と、それに対応した値。以下のサンプルでは、contentという属性を定義して、ブロックのコンテンツとして使用している。

edit( { attributes } ) {
    return <div>{ attributes.content }</div>;
}

className

ラッパー要素のクラス名。これらはsave関数によって自動的に追加されるが、editの段階では自動的には追加されない。クラス名を明示的に受け取るにはclassNameプロパティを使用する。

edit( { attributes, className } ) {
    return <div className={ className }>{ attributes.content }</div>;
}

focus

ブロックがフォーカスされている状態か否かを判定する。

edit( { attributes, className, focus } ) {
    return (
        <div className={ className }>
            { attributes.content }
            { focus &&
                <span>Shows only when the block is focused.</span>
            }
        </div>
    );
}

setAttributes

ユーザの操作に応じて属性の値を更新できる。

edit( { attributes, className, focus } ) {
    const { content, mySetting } = attributes;

    // ユーザがボタンをクリックしたら設定を切り替え
    const toggleSetting = () => setAttributes( { mySetting: ! mySetting } );
    return (
        <div className={ className }>
            { content }
            { focus &&
                <button onClick={ toggleSetting }>Toggle setting</button>
            }
        </div>
    );
}

setFocus

ToDoらしい

Save

save関数は、最終的なマークアップを決定する。これはGutenbergによってシリアライズされて、post_contentに保存される。

save() {
    return <hr />;
}

save関数はnullを返すこともできる。この場合、属性のみがシリアライズされ、サーバサイドでHTMLの描画処理が行われる(これを動的(dynamic)ブロックと呼ぶらしい)。

save関数もプロパティを受け取ることができる。

attributes

editと同様の使い方。保存時にコンテンツが確定し、サーバサイドでの描画が必要ない静的ブロックでは、attributesの値をsave関数でマークアップに埋め込めば良い。

save( { attributes } ) {
    return <div>{ attributes.content }</div>;
}

Learning Vue.js 2 を読んだ

Learning Vue.js 2

Vue.jsは公式ドキュメントが充実していて、かつ、完璧な日本語訳もされているので、Vue.jsの入門書はそれほど必要ではありません。また、vue-cliが生成するアプリケーションの構造を見ると、ライブラリの組み合わせ方や設定方法などもわかります。

しかし、実際のアプリケーションを作りながらステップバイステップで作るチュートリアルは公式では提供されていません。ブログなどに書かれた簡単なチュートリアルは見つかりますが、それなりの規模のアプリケーションをテストまで含めて書いていく、というものになると、数は少ないでしょう。

本書『Learning Vue.js 2』はそのようなユースケースにぴったりな本です。本書の基本的な構成は、Vue.jsを触り始めるところから始めて、vue-cliによるスキャッフォルディング、ディレクティブの解説、コンポーネント、プラグイン、テスト、デプロイと、Vue.jsアプリケーションを作るのに必要な事項が一通り網羅されています。これらの事項を、「ショッピングリスト」と「ポモドーロタイマー」の2つのアプリケーションを作る過程で学ぶことができます。

英語で書かれた本ではありますが、使われている語彙や文法はとても易しいです。著者がドイツ人で、おそらく英語ネイティブではない、というのが良い具合に作用していそうです。Vue.jsの入門書としてだけでなく、英語で書かれた技術書にチャレンジしてみたい方にもおすすめです。

Secrets of the JavaScript Ninja 2nd 読書メモ 第5章 達人のための関数:クロージャとスコープ

Secrets of the Javascript Ninja

  • クロージャによる開発の単純化
  • 実行コンテキストでJavaScriptプログラムの実行を追跡する
  • レキシカル環境で変数スコープを追跡する
  • 変数の型を理解する
  • クロージャの動作を調べる

クロージャを理解する

JavaScriptを書く上で、クロージャは欠かせないものである。クロージャを使うと、クロージャを定義した時点において、同一スコープに属するクロージャの外部の変数を操作することができる。

最も基本的なクロージャは以下のようになる。下記サンプルで、outerFunction関数は、outerValueにアクセスできる。

var outerValue = 'ninja';
function outerFunction() {
  console.log(outerValue);
}
outerFunction();

クロージャの働きがわかりやすい例は以下のようになる。

var outerValue = 'samurai';
var later;

function outerFunction() {
  var innerValue = 'ninja';

  function innerFunction() {
    console.log(outerValue);
    console.log(innerValue);
  }

  later = innerFunction;
}

outerFunction();
later();

outerFunctionの呼び出しによって値がセットされたinnerValueは、呼び出しの終了によってメモリ上から消えてしまい、最後のlater()によるinnerFunctionの呼び出しの時点ではundefinedになっているようにも思える。
しかし、実際にlater()を実行すると、innerValueには’ninja’が入っている。

実は、innerFunctionを定義するとき、関数宣言を定義するだけでなく、関数定義の時点のスコープ内にある全ての変数を包含するクロージャが作成されているのだ。ここで重要なのは、クロージャ経由で外部の変数にアクセスする場合、その変数はクロージャが破棄されるまでメモリ上に残り続けるという点である。

クロージャを動かす

プライベート変数(のようなもの)

JavaScriptにはプライベート変数はないが、クロージャを使うことで似たようなものを作り出すことができる。

function Ninja() {
  var feints = 0;
  this.getFeints() = function() {
    return feints;
  };
  this.feint = function() {
    feints++;
  }
}

var ninja1 = new Ninja();
ninja1.feint();
console.log(ninja1.feints); // undefined
console.log(ninja1.getFeints()); // 1

var ninja2 = new Ninja();
console.log(ninja2.getFeints()); // 0

クロージャをコールバックとして使う

setInterval(function() {
  // doSomething
}, 100);

実行コンテキストによるコード実行の追跡

JavaScriptにおいて、実行の基本的な単位は関数である。JavaScriptのコードは、関数が関数を呼び、そして呼び出し元に戻っていくような流れで実行される。このとき、JavaScriptエンジンは関数呼び出しのスタックを記憶している。

コードがJavaScriptエンジンによって実行されるとき、それぞれの文は一定の実行コンテキストによって実行される。実行コンテキストには、グローバルコンテキストと関数実行コンテキストの2種類がある。

  • グローバルコンテキストは1つだけ存在する
  • 関数実行コンテキストは関数呼び出しのたびに作成される

関数呼び出しのたびに実行コンテキストが作成されるが、その関数の処理が終わると元の場所に復帰する必要がある。そのため、関数内でさらに関数が呼ばれるような場合には、関数の実行コンテキストがスタックとしてメモリ上に保持される。

function skulk(ninja) {
  report(ninja + " skulking");
}
function report(message) {
  console.log(message);
}
skulk("Kuma"); // グローバルコンテキストからの関数呼び出し

// 1. グローバルコンテキストが作成される
// 2. skulk関数の実行コンテキストがスタックに積まれる
// 3. report関数の実行コンテキストがスタックに積まれる
// 4. report関数の実行コンテキストが破棄され、skulk関数に復帰する
// 5. skulk関数の実行コンテキストが破棄され、グローバルコンテキストに復帰する

関数の実行スタック(Call Stack)は、Chrome DevTools等を活用することで簡単に確認できる。

レキシカル環境(lexical environments)で識別子を追いかける

レキシカル環境(lexical environments)とは、JavaScriptエンジンの内部の構成要素で、識別子と変数のマッピングを追いかけるのに使われる。

var ninja = "Hattori";
console.log(ninja);

上記コードで、レキシカル環境は、console.log文の際にninja変数について尋ねられる。レキシカル環境は、一般にはスコープと呼ばれる。

通常、レキシカル環境はJavaScriptコードの特定の構造と結びついている。すなわち、関数、コードのブロック、try-catch文のcatch部分である。これらの構造は独自の識別子マッピングを持つことができる。

コードのネスト

レキシカル環境は、多くの場合、コードのネストに基づいている。

const ninja = 'Muneyoshi';

// skulk関数はグローバル
function skulk() {
  const action = 'skulking';

  // report関数はskulk関数の内側
  function report() {
    const reportNum = 3;

    // forループはreport関数の内側
    for (let i = 0; i < reportNum; i++) {
      console.log(ninja + ' ' + action + ' ' + i);
    }
  }
}

skulk関数を呼び出すたびに、新しいレキシカル環境が作成される。

重要なのは、内側のコードは外側でアクセスされた変数にアクセス可能なことである。

コードのネストとレキシカル環境

ローカル変数、関数宣言、関数パラメーターの追跡に加えて、それぞれのレキシカル環境は外部のレキシカル環境も追跡しなければならない。JavaScriptでは、関数をファーストクラスオブジェクトにすることで、外部の環境の追跡を可能にしている。

関数が作成されると、関数が作成されたレキシカル環境への参照が、 [[Environment]] という内部プロパティとして保持される。二重の角カッコは、JavaScriptエンジンが内部で使用するプロパティの印であり、プログラムから直接触ることはできない。

関数が実行されるたびに、新しい関数の実行コンテキストが作成され、スタックに積まれる。加えて、新しく関連するレキシカル環境が作成される。新しく作成されるレキシカル環境の外部環境のために、JavaScriptエンジンは関数の内部の [[Environment]] プロパティから環境を取り出す。これによって、新しく実行される関数のための環境が用意される。

JavaScript変数の種類を理解する

JavaScriptでは、3種類のキーワードを使って変数を定義できる。var, let, constである。これらは (1) 変更可能性(mutability) (2) レキシカル環境との関係 という2点で異なっている。

変数の変更可能性(mutability)

変数宣言のキーワードを変更可能性で分類するなら、constは変更不可、varletは変更可、となる。constで宣言された変数は変更不可(immutable)となり、値の再代入ができない。一方、varまたはletによって宣言された変数は何度でも値を再代入することができる。

const変数

const 変数には、宣言と同時に初期値を設定する必要がある。const 変数は以下のような目的で使用される。

  • 再代入すべきでない変数を明示する
  • 固定された値を定義する

const 変数には再代入ができないため、意図しない変更から保護されている。また、JavaScriptエンジンによる最適化も可能になる。再代入の不要な変数(多くは不要なはず)に対しては、constを使って宣言することが推奨される。

なお、constで宣言した変数は再代入ができないだけで、オブジェクトのプロパティを変更したり、配列の要素を追加・削除したりすることは可能である。オブジェクトや配列の変更を禁止したい場合はObject.freezeメソッドを使用する。

// strictモードでないと、freezeしたオブジェクトへの代入はエラーにはならず、無視される
'use strict';

const job = 'samurai';

try {
  job = 'ninja';
  console.log(job);    
} catch (e) {
  console.log('error');
}
// => error

const ninja = {};

try {
  ninja.weapon = 'katana';
  console.log(ninja.weapon);
} catch (e) {
  console.log('error');
}
// => katana

Object.freeze(ninja);

try {
  ninja.weapon = 'kunai';
  console.log(ninja.weapon);    
} catch (e) {
  console.log('error');
}
// => error

const weapons = [];
weapons.push('kunai');

// 配列のfreezeにもObject.freezeを使う
Object.freeze(weapons);

try {
  weapons.push('shuriken');
  console.log(weapons);    
} catch (e) {
  console.log('error');
}
// => error

変数定義キーワードとレキシカル環境

レキシカル環境との関係(スコープ)の観点で分類するなら、varlet, constの間で線が引かれる。

varキーワードの使用

varキーワードを使用するとき、変数は最寄りの関数かグローバルなレキシカル環境に保存される(ブロックのレキシカル環境は無視されることに注意!)。

for (var i = 0; i < 3; i++) {
  console.log(i);
}
console.log(i); // iにはforループの外でもアクセスできる

let, constを使ってブロックスコープ変数を定義する

let, const を使って定義した変数は最寄りのレキシカル環境に保存される。もし最寄りがブロックのレキシカル環境であれば、そこに保存される。

for (let i = 0; i < 3; i++) {
  const message = 'messge:' + i;
  console.log(message);
}
console.log(typeof i, typeof message);
// => undefined, undefined

再代入可能な変数には let を使うことで、よりスコープの狭い変数を定義することができる。後方互換性以外の理由でvarを使う理由はない。

レキシカル環境に識別子を登録する

識別子の登録過程

JavaScriptコードの実行は2段階に分けて行われる。

最初のフェーズは新しいレキシカル環境が作成されるたびに実行される。このフェーズでは、コードは実行されず、JavaScriptエンジンは変数と関数の宣言を探して現在のレキシカル環境に登録する。2番目のフェーズでは、コードが実行される。このフェーズは以下のように詳細化できる。

  1. 関数環境を作成する場合、arguments識別子を作成する
  2. 関数またはグローバル環境を作成する場合、コードを走査して関数宣言を探し、識別子を登録する
  3. 変数宣言のための走査が行われ、varで宣言された変数は最寄りの関数の、letまたはconstで宣言された変数は最寄りのブロックのレキシカル環境に登録される

関数宣言より前に関数を実行する

  • 関数宣言を使用して定義された関数には宣言より前でもアクセスできる
  • 関数式やアロー関数はできない

関数の上書き

関数宣言を使用すると、その関数を参照する変数はコードの実行よりも先に定義される。

console.log(typeof fun); // function
var fun = 3;
console.log(typeof fun); // number
function fun(){}
console.log(typeof fun); // number

まとめ

  • クロージャによって、その関数が定義されたスコープ内にある全ての変数にアクセス可能になる
  • クロージャは疑似プライベート変数やコールバック関数に利用できる
  • JavaScriptエンジンは実行スタックによって関数の実行を追跡する
  • JaaScriptエンジンはレキシカル環境(スコープ)によって識別子を追跡する
  • JavaScriptでは、グローバル・関数・ブロックのいずれかのスコープの変数が定義できる
  • 変数の定義には var, let, const キーワードが使用できる
  • クロージャはJavaScriptのスコープのルールの副作用である

『ポートとソケットがわかればインターネットがわかる』を読んだ

ポートとソケットがわかればインターネットがわかる――TCP/IP・ネットワーク技術を学びたいあなたのために (Software Design plus)

Geekなページ のあきみち氏による、TCP/IPの入門書。情報処理技術者試験対策本のような、無味乾燥な知識をただ覚えるための本ではなく、ネットワークの初歩的な知識と、それを実際に何かに使うことの橋渡しをしようとする意図が感じられる。

具体的には、この手の入門書には珍しく、本書にはソケットを利用したプログラムのサンプルが載っている(※)。その他、ネットワークコマンドの使用例も記載されていて、手を動かしながら学ぶことができる。

挿絵も的確で、本文だけではわかりづらいところでも理解の助けになっている。

ネットワークを初めて学ぶ人だけでなく、自分のように、ネットワークに苦手意識のあるソフトウェアエンジニアが読んでも得るものは多いと思う。

※ただし、C言語で書かれているので、厳しいという人もいそう。Working With TCP SocketsというRubyを使ってソケットプログラミングを行う入門書もあるけど、こっちはこっちで英語なのがつらいという人が多いかも。