[背景]
個人的なメモです。
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
を扱える -
async
とawait
による非同期処理-
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