JSのクラス周りのまとめ
##実行順
classの定義前にnewしようとしても見つからない。(関数の定義のように巻き上げたりしない)
new A() //error!
class A{}
new A() // new 可能
クラスが定義される前に、コンストラクタ関数を呼び出すコードが書かれていても、実行されるのがクラスの定義後だと実行できる。
function test(){
return new A() //クラス定義前
}
function test2(a){
return a instanceof A //クラス定義前
}
class A {} //ここでクラス定義
test() // A //動く
test2(test()) //true
##クラスの定義とコンストラクタ
class Hello{}
classを定義すると、コンストラクタ関数が作られている。
class Hello{} //classを定義すると、コンストラクタ関数が定義されている。
typeof Hello //この時点でHelloというワードが何か確認できる、"function"と返ってくる・
new Hello() //生成
new Hello //これでも生成される。
let a = new Hello() //もういちど生成
a.constructor //classから出来たオブジェクトにはconstructorというプロパティでコンストラクタにアクセス出来る。この場面ではコンソールには、class Helloと表示される。
constructor()という関数を定義すると、コンストラクタの挙動を定義できる。
class Hello{
constructor(value){
this.value = value
}
}
new Hello("world")
##プロパティ
this.
でclassに値を設定できる(プロパティ)
class Name{
constructor(value){
this.value = value
}
}
const name = new Name("hello")
name.value // "hello"
##メソッド
class内に関数名(){}
と書くと、classに備え付けられた関数(メソッド)になる。
class Test{
run(){} //`function`といった単語は必要ない。
}
使う側
const test = new Test()
test.run()
同じclass内のメソッドを使う場合は、thisと付ける必要がある。
class Test {
run(){
this.test() //thisがないと、この関数はどこだと言われる
}
test(){
console.log("hello")
}
}
##静的メソッド
staticと付けると静的メソッドになる。
class Hoge{
static calc(a,b){
return a + b
}
}
使う側
class Hoge{
static calc(a,b){
return a + b
}
}
Hoge.calc(5,5) //newをしていない。直接classの関数を使っている
参考Math
##getter、setter
関数にgetというキーワードを付けるとgetter関数に、setを付けるとsetter関数になる。
class Hoge{
constructor(){
this.value = 0;
}
get prop(){
return this.value ;
}
set prop(x){
this.value = 2 * x; // この時`this.setter名 =`などしてしまうと、ループしてしまうので怒られる
}
}
使う側
get
let hoge = new Hoge()
hoge.prop // 0
set
hoge.prop = 10
hoge.prop // 20
##継承
extendsキーワードを使うと、classを継承出来る。
継承すると親のメソッドやプロパティを持つようになる。
class A {
constructor(a){
this.a = a
}
}
class B extends A {
constructor(a,b){
super(a) //親のコンストラクタを呼び出す。
this.b = b //何かをextendsしたクラスでは、コンストラクタ内でまずsuper()がないと、コンストラクタ内でthisが使えない。
}
}
使う側
const b = new B(1,2)
b.a //1
b.b //2
親のメソッドを呼ぶには、super.メソッド名()
とする。
class A {
hoge(){}
}
class B extends A {
hoge(){
super.hoge()
}
}
複数のクラスを継承することは出来ない。複数を継承する時は、1つずつ継承する。
class A {}
class B extends A {}
class C extends B {}
あとMix-inという書き方があるらしいのだけどよくわからない。
クラス - JavaScript | MDN
##デフォルト引数、可変長引数、計算プロパティ名
####デフォルト引数
関数の引数に初期値を設定できる。
引数に値が入らなかった場合それらが使われる。
class Hoge{
constructor(a,b = 2,c = b){
this.value = a*b*c
}
}
new Hoge(5).value //20
####Rest parameters
関数の引数を可変長の引数にできる。
引数はArrayで受け取ることになる。
class Hoge{
constructor(a,b,...args){
console.log(a,b,args)
}
}
new Hoge(1,2,3,4,5)//1,2,[3,4,5]
参考:Rest parameters - JavaScript | MDN
####Computed property names
変数などを使ってプロパティ名を作り設定することが出来る。
class定義中では、メソッド名で使うことが出来る。Symbolを使った特別な関数を定義する時にも使う。
const name = "test"
class Test {
constructor(value){
this.value = value;
//this.[value] = value //プロパティでも使えるかなと思ったが動かない
}
["my" + "Func"](){ //Test.myFuncの定義になる。
return true
}
[name](){ //外部の変数を使っている。
return true
}
[this.value](){ //これはundefined()になる。コンストラクタの値は使えない。
}
}
new Test().myFunc() // true
new Test("a").test() // true
参考:オブジェクト初期化子 - JavaScript | MDN
##Symbolを使ったメソッド
用意されたSymbolを使って関数を定義すると、特定の場面の挙動を制御できる。
###イテレータ
イテレータという関数がclassにあると、イテレート可能(iterable)になる。
iterableになると、forOf文などで値を取り出しながら処理を行ったりといったことが出来るようになる。
class HasIterator {
constructor(){
this.a = 1;
this.b = 2;
this.length = 2;
}
* [Symbol.iterator](){
for(let i = 0 ; i < this.length ; i++){
if(i === 0){yield this.a}
if(i === 1){yield this.b}
}
}
}
[Symbol.iterator]
という部分が、for of文などの時に呼ばれる。ここでイテレータ関数を返す必要があるので、function* (){}
というジェネレータを作る構文でジェネレータ(イテレータ)を返している。
参考:JavaScript の イテレータ を極める! - Qiita
参考:js-primer/source/basic/loop at master · asciidwango/js-primer
使う側
let iterable = new HasIterator()
for(value of iterable){ //for of
console.log(value) //1 2
}
[...iterable] //展開される。[1,2]
let [a_,b_] = iterable //展開される。[分割代入 - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment)
a_ // 1
オブジェクトのプロパティをすべて返すイテレータぐらいなら、わざわざSymbol.iteratorを実装する必要はなくて、イテレータを作って返してくれるObject.keysなどを使うと良いと思う。
クラスに逐次的な処理や、ストリーム処理、遅延実行(ジェネレータ)が必要な時に有効。
###Symbol.species
Arrayのmap関数などは、使われたクラスのデフォルトコンストラクタを使うらしいが、
static get [Symbol.species]()
で、その時使うコンストラクタを指定することができる。
class MyArray extends Array {
static get [Symbol.species]() { return Array; }
}
let a = new MyArray(1,2,3)
let b = a.map((x)=>x)
b instanceof MyArray // false
b instanceof Array // true
Arrayなどの基本の組み込みオブジェクトを拡張する時に便利らしい。
static get [Symbol.species]()
がない場合
class MyArray extends Array{
}
let a = new MyArray(1,2,3)
let b = a.map((x)=>x)
b instanceof MyArray // true
b instanceof Array // true
###Symbol.toPrimitive
Symbol.toPrimitiveで定義された関数は、数字化、文字列化、される場面に呼び出される。
class Age{
constructor(value){
this.value = value
}
[Symbol.toPrimitive](hint) {
if (hint == "number") {
return this.value;
}
if (hint == "string") {
return this.value + "";
}
console.log(hint) //確認用
return this.value;
}
}
+(new Age(1)) // 1
new Age(1) | 0 // 1
`${new Age(1)}` // "1" テンプレートリテラルでの文字列化。
new Age(1) + new Age(1) // default default 2
new Age(1) + "" // default "1"
演算子オーバーライドみたいなことまでできるかな?、と思ったが、相手のオブジェクトがわからないので、(hint変数でわかるのは、"number"、"string"、"default" のみ)そこまでは出来ないと思われる。
##classをexportする
Node.jsのrequire構文でclassをexportするには、
module.exports = class A {}
使う側
const A = require("./A.js")
new A()
複数のクラスをexportするには、
class A {}
class B {}
module.exports.A = A
module.exports.B = B
使う側
const A = require("./A.js")
new A.A()
new A.B()
##判断する
###継承しているか判断する
class A {}
class B extends A {}
let b = new B()
b instanceof A //true
b instanceof B //true
###コンストラクタを判断する
let a = new A()
a.constructor === A //true
a.constructor.name === "A" //true
###プロパティを持っているか判断する
a.hasOwnProperty("hoge") //false
###メソッドを持っているか判断する
typeof a.hoge === "function" //false
##Maybe
Maybeを書いてみた。参考
class Maybe {
constructor(value) {
this.value = value;
}
bind(f) {
if (this instanceof Just) {
return f(this.value)
} else if (this instanceof Nothing) {
return this
}
}
};
class Just extends Maybe {
constructor(a) {
super(a)
}
}
class Nothing extends Maybe {
constructor() {
super(null)
}
}
module.exports.Just = Just;
module.exports.Nothing = Nothing;