Chapter 06

JSによる処理をGoで書く

NoboNobo
NoboNobo
2020.10.21に更新

このチャプターの多くの部分はGoの標準のsyscall/jsパッケージの使い方になっています。
(一部にユーティリティ関数を使った例があります。)

オブジェクトのフィールド取り出し

JS:

let body = window.document.body;

Go:

body := js.Global().Get("docuemnt").Get("body")

オブジェクトのフィールドセット

JS:

window.UserValue = "hoge";

Go:

js.Global().Set("UserValue", "hoge")

メソッド・関数の呼び出し

JS:

let title = document.querySelector(".title");

Go:

title := js.Global().Get("docuemnt").Call("querySelector", ".title")

コーラブルオブジェクトの呼び出し

JS:

let querySelector = document.querySelector;
let title = querySelector(".title");

Go:

querySelector := js.Global().Get("docuemnt").Get("querySelector")
title := querySelector.Invoke(".title")

配列オブジェクトの長さとインデックスアクセス

JS:

len length = arrayTypeObj.length()
let item = arrayTypeObj[0];

Go:

length := arrayTypeObj.Length()
item := arrayTypeObj.Index(0)

async/await

JS:

async function hoge() {
  try {
    var res = await fetch("url");
    ...
  } catch {
    ...
  }
}

Go:

import "github.com/nobonobo/spago/jsutil"
func hoge() {
  go func() {
    res, err := jsutil.Await(js.Global().Call("fetch", "url"))
    if err != nil {
      ...
    }
    ...
  } ()
}

JSの値を取得したのちGoの型に変換

var jv js.Valueとします

型変換
bool jv.Bool()
int jv.Int()
float64 jv.Float()
string jv.String()

JS:

let width = dom.width

Go:

width := dom.Get("width").Int()

Goの型の値をJS側に渡す時

JSレイヤの関数やメソッドの引数に渡す場合は自動的にjs.ValueOfで処理されます。
その際の変換ルールは特定のプリミティブ型だけに適用されます。
js.ValueOf(any)の戻り値の型はjs.Value型です。

JSでのType
js.Value そのまま
js.Func function
nil null
bool boolean
integers and floats number
string string
[]interface{} array
map[string]interface{} object

その他のGo特有の型を渡すとpanicになります。

funcタイプをJSに渡す時

funcタイプだけはちょっと特殊で手間が必要です。

Go:

fnObj := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
  ...
})
promise.Call("then", fnObj)
fnObj.Release()

というようにjs.Funcタイプへの変換が必要です。また、利用が終了したらRelease()を呼んでリソースの解放が必要です。なお、FuncOfに渡せる関数はfunc(this js.Value, args []js.Value) interface{}という形でなければいけません。

これの難しい点はイベントハンドラに渡した場合、そのイベントが発火する可能性が残っている限りにおいてRelease()を呼んではいけません。

Release()が呼ばれていないfuncオブジェクトを握っているJSレイヤのリソースはそのままだとGCによる回収対象にはなりません。

その他のTips

  • spago.SetTitle(title string): タイトルを設定する
  • spago.AddMeta(name, content string): メタタグを追加する
  • spago.LoadScript(url string): JS スクリプトを読み込む
  • spago.LoadModule(names []string, url string): JS モジュールを読み込む
  • spago.AddStylesheet(url string): CSS を読み込む
  • jsutil.JS2Bytes(array) []byte: js の TypedArray や DataView などを Go のバイト列に変換
  • jsutil.Bytes2JS([]byte) js.Value: バイト列を Uint8Array に変換します
  • jsutil.JS2Go(jv js.Value) interface{}: JS のオブジェクトを再帰的に Go の型にマッピングし直す。map,slice,float64,string,bool 型しか変換しない。js.ValueOfの逆処理。

注意点

  • JSレイヤから値を取得し、それをそのままJSのレイヤに投げ込むならGoの型への変換は必要ありません。
  • 非同期処理の待ちが発生するような処理はgoroutineによるラップが必要になります。(awaitが必要な処理はasync修飾関数の中でなければならないのと同じ)
  • よくやりがちなのはイベントハンドラでtime.Sleep等を呼んでしまうパターン。
  • イベントハンドラではスタックをできるだけ早く開放すること(returnすること)が求められます。