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

# 背景

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

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

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

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

```typescript
function startsWith(target, pattern) {
  return target.indexOf(pattern) === 0; 
}
```

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

```typescript
stringItem.startsWith(pattern);
```

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

# 調査： TypeString 自体に startsWith とかありそう？

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

[TypeScript Strings - tutorialspoint](https://www.tutorialspoint.com/typescript/typescript_strings.htm)

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

[microsoft/TypeScript-Handbook： The TypeScript Handbook is a comprehensive guide to the TypeScript language](https://github.com/microsoft/TypeScript-Handbook)

# Extension として書いたもの

参考にしたのは下記。

* [TypeScript で動的にメソッドを追加する - ひびのログ](https://www.tee-talog.com/entry/2018/01/30/224052) 
* [【TypeScript】拡張メソッドの実装(基本型) - 開発覚書はてな版](https://kakkoyakakko2.hatenablog.com/entry/2018/06/12/003000)
* [How to extend String Prototype and use it next, in Typescript? - Stack Overflow](https://stackoverflow.com/questions/39877156/how-to-extend-string-prototype-and-use-it-next-in-typescript)

```typescript: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
  );
};
```

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

```javascript: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 メソッドの部分](https://docs.python.org/3/library/stdtypes.html?highlight=startswith#str.startswith) を参考にしている。（私は Python の人であるため）

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

# ハマったこと

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

調べてみると[アロー関数 - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/Arrow_functions#No_separate_this)がヒットしたので、読んでみると、「this を束縛しない」の章で納得。レキシカルスコープの this 値を使っているということで、理解した。


```typescript:string.extension.bad.ts
String.prototype.startsWith = (word: string): boolean => {
  return this.indexOf(word) === 0;
};
```

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

```javascript: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 の違いとか、ちょっと驚くこともあったが、実現できるということがわかったので学びがあってよかった。