Posted at

Clasp+TypeScriptで、stringにメソッドを追加拡張してみる


背景

ググラビティの問題からか、あまり類似した記事を見つけることができなかったので、記載してみようと思う。

以前こういう記事を書いた。

https://qiita.com/yumechi/items/9b4ce2d90165780d3243

しかし、このメソッドの2引数のとり方は違和感がある。

function startsWith(target, pattern) {

return target.indexOf(pattern) === 0;
}

引き渡す順序を覚えていないといけないし、変数自身に聞けばいいような問題に思われる。可能ならこう呼びたいはず。

stringItem.startsWith(pattern);

もともとのプロジェクトを素のJavaScirptで書いていたのだが、TypeScript化するついでに、この問題を解決してみた。


調査: TypeString 自体に startsWith とかありそう?

なさそうだった。VSCodeで予測変換を噛ましてみたが、なさそうな感じだった。一応下記リンクとは一致していることを確認した。

TypeScript Strings - tutorialspoint

HandBookから探すのが難しい。どこを読むべきなんだろう。

microsoft/TypeScript-Handbook: The TypeScript Handbook is a comprehensive guide to the TypeScript language


Extension として書いたもの

参考にしたのは下記。


string.extension.ts

export {};

/**
* String Extender
*/

declare global {
interface String {
/**
* Return True if string starts with the prefix, otherwise return False.
* @param word
*/

startsWith(word: string): boolean;
/**
* Return True if string ends with the prefix, otherwise return False.
* @param word
*/

endsWith(word: string): boolean;
}
}

String.prototype['startsWith'] = function(word: string) {
return this.indexOf(word) === 0;
};

String.prototype['endsWith'] = function(word: string) {
return (
this.lastIndexOf(word) + word.length === this.length &&
word.length <= this.length
);
};


吐き出されたコードはこんな感じ。


string.extension.gs

// Compiled using ts2gas 1.6.2 (TypeScript 3.5.1)

var exports = exports || {};
var module = module || { exports: exports };
String.prototype['startsWith'] = function (word) {
return this.indexOf(word) === 0;
};
String.prototype['endsWith'] = function (word) {
return (this.lastIndexOf(word) + word.length === this.length &&
word.length <= this.length);
};

あ、メソッドの説明のところは Built-in Types — Python 3.7.3 documentation の startsWith メソッドの部分 を参考にしている。(私は Python の人であるため)

これを他のファイルでインポートしてやるといい感じに動いた。


ハマったこと

下記のように書くと、this が捉えるスコープが広くなりすぎて( Logger を使って print してやると、なんかズラッとすべてのメソッド名なり、変数名なりが取れているので GAS 全体のオブジェクトが取れてそうな感じだった)思ったように動作しなかった。

調べてみるとアロー関数 - JavaScript | MDNがヒットしたので、読んでみると、「this を束縛しない」の章で納得。レキシカルスコープの this 値を使っているということで、理解した。


string.extension.bad.ts

String.prototype.startsWith = (word: string): boolean => {

return this.indexOf(word) === 0;
};

吐き出されたコードがこれ。


string.extension.bad.gs

// Compiled using ts2gas 1.6.2 (TypeScript 3.5.1)

var exports = exports || {};
var module = module || { exports: exports };
var _this = this;
String.prototype['startsWith'] = function (word) {
return _this.indexOf(word) === 0;
};
String.prototype['endsWith'] = function (word) {
return (_this.lastIndexOf(word) + word.length === _this.length &&
word.length <= _this.length);
};

実行してみると 実行に失敗: TypeError: オブジェクト [object Object] で関数 lastIndexOf が見つかりません。(行 9、ファイル「string.extensions」)(合計ランタイム 0.382 秒)

つまりそうなる。はい。


感想

できるだろう…! と思ったが、実現するまでに時間がかかりすぎてしまった。 prototype に突っ込むとか、アロー関数と function の違いとか、ちょっと驚くこともあったが、実現できるということがわかったので学びがあってよかった。