LoginSignup
2
2

More than 5 years have passed since last update.

ざっくりECMAスクリプトを学ぼうとして少しつまづいたところ。

Last updated at Posted at 2018-08-26

[背景]
個人的なメモです。
Java,Ruby,Scalaなどのバックエンド系の言語は触ってきたのですがフロント系の言語は一度も触れたことがなかったので勉強してます。
まだまだ文法レベルを学んでいる超初心者なので、言語差異を吸収するために躓いたところをメモとして残しています。

ところどころ他の言語と比較していますが、あまり意味はないです。個人的な理解を深めるために残しています。
随時更新していこうと思ってます。

プロパティ系

単独記述

let firstName = "Hanko";
let lastName = "Yamada";
let age = 20;

// JavaScript的な書き方
let jsPerson = {
  firstName: firstName,
  lastName: lastName,
  age: age
};

// ECMAだと省略できる
let ecmaPerson = {
  firstName,
  lastName,
  age
}
console.log(jsPerson)
console.log(ecmaPerson)
{ firstName: 'Hanko', lastName: 'Yamada', age: 20 }
{ firstName: 'Hanko', lastName: 'Yamada', age: 20 }

プロパティの演算表示

let key = "lastName";

function getKey() {
  return "Address";
}

let person = {
  firstName: "Hanako",
  ["my" + getKey()]: "Saitama" // []でプロパティを定義できる
};
person[key] = "Yamada";
console.log(person);
{ firstName: 'Hanako', myAddress: 'Saitama', lastName: 'Yamada' }

Rubyだとこんな感じ。

key = "lastName"

def getKey()
  "Address"
end

person = {
  "firstName" => "Hanako",
  "my#{getKey}" => "Saitama"
}
person[key] = "Yamada"
p person

レスト演算子/スプレッド演算子

レスト演算子

...引数名で記述

function print(ele1, ele2, ...arry) {
  console.log(arry);
}
print(1, "a", true, {}, [1,2,3,4]);
[ true, {}, [ 1, 2, 3, 4 ] ]

Rubyだとたぶんこれにあたるのかな。

# Rubyの場合
def print(ele1, ele2, *arry)
  p arry
end
print(1, "a", true, {}, [1,2,3,4])

スプレッド演算子

引数に...配列名で指定するスプレッド演算子

// ECMA script
const message = 'Hello, World';
const chars = [...message];
console.log(chars)

const b1 = [1,2,3];
const b2 = [4,5,6];
const b3 = ["a", ...b2, true, {}, ...b1]
console.log(b3)
[ 'H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd' ]
[ 'a', 4, 5, 6, true, {}, 1, 2, 3 ]

クラス

言語で統一してくれって思う。

プライベートプロパティ,ゲッター・セッター,スタティックメソッド

// ECMA script
class Person {
  constructor(name, age) {
    // 慣習的にプライベートなプロパティには_をつける。プライベートであることの目印。
    // 外からアクセスはできてしまう。
    this._name = name;
    this._age = age;
  }

  // ゲッター
  get name() {
    return this._name;
  }

  // セッター
  set name(name) {
    this._name = name;
  }

  // インスタンスメソッド
  hello() {
    console.log("Hello, World");
  }

  // スタティックメソッド
  static eat() {
    console.log("I eat meat!");
  }
}

let person = new Person("Hanako", 32);
person.name = "Taro";
console.log(person)
person.hello()
Person.eat()
Person { _name: 'Taro', _age: 32 }
Hello, World
I eat meat!

Rubyの場合は以下。

# Ruby
class Person
  # これでゲッター、セッター定義
  attr_accessor :name, :age

  def initialize(name, age)
    @name = name
    @age = age
  end

  # インスタンスメソッド
  def hello
    "Hello, World"
  end

  # 特異メソッド
  def self.eat
    p "I eat meat!"
  end
end

person = Person.new("Hanako", 32)
person.name = "Taro"
p person
person.hello
Person.eat

イテラブル

言語で統一してくれって思った。

for-ofループ

arry = ["123", "456", "789"]

for (n of arry) {
  console.log(n)
}

for (n of arry.keys()) {
  console.log(n)
}

for (n of arry.entries()){
  console.log(n)
}
123
456
789
0
1
2
[ 0, '123' ]
[ 1, '456' ]
[ 2, '789' ]

Scalaで書くとこんな感じ。

val arry = Vector("123", "456", "789")
for (n <- arry ) println(n)
for (n <- arry.indices) println(n)
for (i <- 0 to arry.length -1) println(i, arry(i))

イテレータ

  • イテレータは[Symbol.iteretaor]()で取得できる
  • イテレータはnext()をもつ
  • next()の戻り値は.value.doneプロパティを持つ
let message = "Hello";
let iterator = message[Symbol.iterator]();
iterator;
let a = iterator.next();

console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
{ value: 'H', done: false }
{ value: 'e', done: false }
{ value: 'l', done: false }
{ value: 'l', done: false }
{ value: 'o', done: false }
{ value: undefined, done: true }

カスタムイテレータ

// 自分で定義したオブジェクトにイテレータが使えない。
// let letters = {
//   a: "A",
//   b: "B",
//   c: "C",
//   d: "D"
// };
// let iter = letters[Symbol.iterator]();

// カスタムイテレータを以下のように定義。
let nums = {
    [Symbol.iterator]() {
    // イテレーションさせるデータを定義
    let data = {a: "A", b: "B", c: "C", d: "D"}

    let index = 0;
    let dataPropertyNames = Object.getOwnPropertyNames(data)

    return {
      // next()関数を返却する
      next() {
        // next()関数はvalueとdoneをプロパティにもつオブジェクトを返却させる
        return {
          value: data[dataPropertyNames[index]],
          done: index++ >= dataPropertyNames.length
        }
      }
    }
  }
}

let iter = nums[Symbol.iterator]();
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
{ value: 'A', done: false }
{ value: 'B', done: false }
{ value: 'C', done: false }
{ value: 'D', done: false }
{ value: undefined, done: true }

ジェネレータ

ジェネレータは、単にデータを取り出す以上のイテレータ機能を持たせたいようなときに使える。

基本

ジェネレータはイテレータを持っている。
ポイントは以下。

  • function* をつかって宣言する
  • なかでyieldをつかう(イテレーションするごとにyieldで止まる。)
function* myGenerator(){
  yield 1;
  yield 2;
}

let iter = myGenerator();
iter;
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
{ value: 1, done: false }
{ value: 2, done: false }
{ value: undefined, done: true }

上記のコードは、yieldで返す値を配列にしても同じになる。その場合はyield*とする。

function* myGenerator() {
  yield* [1, 2];
}

iter = myGenerator();
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());

 メソッドジェネレータ

メソッドにジェネレータの機能をもたせたい場合。

let obj = {
  * myGenerator(){
    yield 1;
    yield 2;
  }
}
let iter = obj.myGenerator();
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
{ value: 1, done: false }
{ value: 2, done: false }
{ value: undefined, done: true }

利用例

// ジェネレータの利用例
// フィボナッチ数列
function* fibonacci(){
  let n1 = 0;
  let n2 = 1;

  const calc = () => {
    sum = n1 + n2;
    [n2, n1] = [sum, n2];
    return sum;
  }

  // 無限ループ
  while(true){
    yield calc()
  }
}

let iter = fibonacci();
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: 5, done: false }
{ value: 8, done: false }
{ value: 13, done: false }
{ value: 21, done: false }
{ value: 34, done: false }
{ value: 55, done: false }
{ value: 89, done: false }

プロミス

従来のJavascript

同期型

var syncFunc1 = function() {
  console.log('Sync funciton1..');
}

var syncFunc2 = function() {
  console.log('Sync funciton2..');
}

var syncFunc3 = function() {
  console.log('Sync funciton3..');
}

syncFunc1();
syncFunc2();
syncFunc3();

非同期

上記の同期型の処理を非同期で書くと以下のようになる。
コールバックの中にコールバック関数を記述するため、見た目的にもわかりにくくなる。

var asyncFunc1 = function(callback) {
  console.log('async funciton1..');
  if (callback) {
    callback();
  }
}

var asyncFunc2 = function(callback) {
  console.log('async funciton2..');
  if (callback){
    callback();
  }
}

var asyncFunc3 = function(callback) {
  console.log('async funciton3..');
  if (callback){
    callback();
  }
}

// コールバックの中にコールバック関数を記述する
// 見た目的にもわかりにくくなる。
asyncFunc1(function(){
  asyncFunc2(function(){
    asyncFunc3();
  })
});

Promise

Promiseはコールバックに置き換わるもので非同期処理のために用意されている機能

Promise.new

  • returnでPromiseクラスのオブジェクトを返すこと
  • resolveメソッドの引数をthenで受け取れること
function getName(){
  // Promiseクラスのオブジェクトを返すようにする。
  // resolveはメソッド
  return new Promise(resolve => {
    setTimeout(()=> {
      // resolveでthenに渡すデータを設定している。
      resolve("Makio");
    },2000)
  })
}

// thenのnameにはMakioが渡る。
getName().then(name => {
  console.log(name);
})

ここで、以下の部分にreturnで値を渡すと、

getName().then(name => {
  console.log(name);
})

次のthenに値を渡すようなこともできる。

getName()
.then(name => {
  console.log(name);
  return 20; // 次のthenのageに渡す。
})
.then(age => {
  console.log(`${age} old years`);
})

関数をチェーンするような場合は、以下のような書き方。

function getName() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve("Makio");
    }, 2000);
  })
}

function getAge() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(20);
    }, 4000);
  })
}


getName()
.then(name => {
  console.log(name);
})
.then(getAge)
.then(age => {
  console.log(age);
})

Promise.all

thenのチェーンがうるさいような場合はこっちのほうがいい気がした。

// Promiseを返す関数を配列で渡す。
Promise.all([
  getName(), 
  getAge()
])
.then(([name, age]) => { // thenで結果を配列で受け取る
  console.log(name);
  console.log(age);
});

Promiseのエラーの扱い

rejectを使ってtry~catch~finally`的に例外を扱う。

function getName() {
  // エラーを扱いたい場合は、rejectも渡す。
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // rejectメソッドを呼び出し
      reject("Error Occured!")
    }, 2000);
  })
}

function getAge() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(20);
    }, 4000);
  })
}

getName().then(name => {
  console.log(name);
})
.then(getAge)
.then(age => {
  console.log(`${age} years old.`)
})
.catch(err => { // catchされるのはrejectだけでなく、例外がthrowされたときなどでもcatchされる。
  console.log(err);
  return "Hogehoge Finally"
})
.then((hoge) => { // ここのthenはfinally相当。
  console.log(hoge);
})

Promise.resolve

シンプルに非同期でデータを送りたいときのための静的メソッドがPromise.resolve
Promiseオブジェクトのクラスを作らなくてもよくなる。

function getName() {
  return Promise.resolve("hanako");
}

Promise.reject

Promise.resolveの対として、Promise.rejectも静的メソッドとして用意されている。

function getName() {
  return Promise.reject("Error occured!!")
}

非同期関数

非同期関数の特徴

  • thenメソッドを使わずに、Promiseを扱える
  • asyncawaitによる非同期処理
    • async: 非同期
    • await: 期待しながら待つ

基本形

let myPromise = (name) => new Promise(resolve => {
  resolve(`my name is ${name}.`);
})

// functionの前にasyncをつける
// mainは非同期関数
async function main() {
  // Promiseクラスのオブジェクトの前にawaitをつける
  let name1 = await myPromise('hanako')
  let name2 = await myPromise('kojiro')
  console.log(name1)
  console.log(name2)
}

// 非同期関数の実行
main()

エラーハンドリング

let getHoge = () => {
  // 意図的にErrorを発生させる
  return Promise.reject("Error Occured!");
}

async function main() {
  // 非同期関数内では、try - catchで例外としてキャッチする
  try {
    let hoge = await getHoge()
  }catch(err) {
    console.log(err); 
  }
}

main() // ​​​​​Error Occured!​​​​​

Promise.allと組み合わせて使う

Promise.allと組み合わせて使うと、awaitの重複をなくせる。

let myPromise = (name, sec) => new Promise(resolve => {
  setTimeout(() => {
    console.log(name);
  },sec);
  resolve(`this is ${name}.`);
})

// awaitの重複をなくしてみる
async function main() {
  // resolveの値を受け取る
  let [msg1, msg2, msg3, msg4] = await Promise.all([
    myPromise('tochigi', 4000), 
    myPromise('tokyo', 1000),
    myPromise('ibaraki', 2000),
    myPromise('saitama', 3000)
  ]);
}

main();

これを実行すると非同期なので、セットしたタイマ順に出力される。

tokyo
ibaraki
saitama
tochigi

コレクション

// TODO

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2