【初心者向け 099】JS高級文法 (async&await,fetch,scope,Prototype,closure,This)
はじめに
いよいよJS文法を一回勉強することができました!!!!
コードの場合、copyrightがあるので、自分のコードでまとめました!
async&await
非同期処理を同期のように表現するkeywordです。
本来、call back hellの問題を解決するため、Promiseを作うことになりましたが、Promiseも結局、thenまみれのcall back hellになる場合が多いです。
まず、コードを理解する自体が難しいので、Promiseの短所を補うためにasync,awaitというキーワードを使うことになりました。
Promiseをリターンする関数の場合、必ずawaitを付けることが必修です!
まず、Promiseの復習!結局 ProducerとConsumerから分けることがとても大事でした。本当に難しいですが、なんとなく理解ができました!
Producer
const promise = new Promise(() => {
setTimeout(() => {
// resolve('user id is ...')
// reject(new Error('no network'));
}, 2000);
});
Promiseオブジェクトを生成し、Promise内で実行されるべきコードを定義します。
Promiseオブジェクトを生成する瞬間、executor関数がすぐに実行されますが、
生成と同時にPromise内に定義されたsetTimeoutが実行され、setTimeoutが正常に動作する場合、resolve関数が返され、エラーが発生する場合はreject関数が返されます。
Consumer
promise
.then(value => {
console.log(value);
}).catch(error => {
console.log(error);
}).finally(() => {
console.log('finally');
});
コンシューマープロセスは、then、catch、finallyに分けて説明できます。
Promiseオブジェクトが成功裏に実行される場合、thenでresolveの戻り値を受け取り、
Promiseオブジェクトがエラーを発生させる場合、catchでrejectの戻り値を受け取ります。
最後に、Promiseのresolveとrejectに関係なく、finallyが実行され、Promiseのコンシューマープロセスが完了します。
Promiseの処理
例えば、1秒後に配列をリターンするPromiseと3秒後に配列をリターンするPromiseがある場合、FIFOにより、TaskQueに入った順位によりPromiseは同期的に処理されます。
function countFour(){
countOne() //
.then((one) =>
countThree() //
.then((three) => [one, three])
);
}
//countFour()はProducerから生成したPromiseオブジェクトをもらいます。
function countFour()
.then((array) => console.log(array));
//4秒後
[one,three]
call back hellのようにとても複雑なコードになりました。
async, await
async function countFour(){
const one = await countOne();
const three = await countThree();
return [one, three];
}
function countFour()
.then((array) => console.log(array));
Promiseの並列処理
Promise.all()
: 並列的にすべてのPromiseを実行する関数です。
Promise.race()
: 並列的にすべてのPromiseを実行し、一番早く終わったPromiseの結果値をリターンします。
Promise.all([countOne(), countThree()]) //
.then((array) => console.log('all', array));
Promise.race([countOne(), countThree()]) //
.then((num) => console.log('firstfinish', num));
all [one,three]
firstfinish one
ポイントとして、Promiseを並列的に処理したので、allは1+3秒ではなく、3秒が、raceは1秒が掛かります!
fetch()
networkからデーターをもらう関数です。fetch()関数はPromiseをリターンし、PromiseはResponseというオブジェクトをリターンします。 Responseには .header, .bodyなどのネットワークrequestについての情報が含まれています。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
fetch(
'https://www.7timer.info/bin/astro.php?lon=113.2&lat=23.1&ac=0&unit=metric&output=json&tzshift=0'
)
.then((response) => response.json())
.then((data) => console.log(data.dataseries));
</script>
</body>
</html>
fetch()からPromiseのオブジェクトをもらったので、
.then()を読み出し、Promiseのリターン値である
responseを.json()というメソッドでと整えます。
.json()関数はresponseのbodyを(not JSON)JSのオブジェクト形の文字列にリターンしますが、またPromiseをリターンします。
そのため、もう一度.thenを利用し、Promiseが成功した場合、リターンするjavascriptのObjectの形で処理します。
data.dataseries
これにより、簡単にAPIの情報にアクセスすることができます。
Scope
lexical Environment
全ての変数はいわゆるscopeという寿命があります。Javaの場合、ブロック単位を徹底していますので、そこまで難しくはありませんでしたが、JSのlexical Environmentを勉強することで、Stackメモリの仕組みが理解できました。
const a = 1{
const a = 2{
const a = 3;
}
}
基本的にブロック内に同じ変数名がある場合はブロック内のlexical Environmentはブロック内の変数データーがある第二のスコープ (Environment Record) と外部のlexical Enviromentを参照するやつで構成されています。ブロック内で探す第二のスコープない場合は外部のlexical Enviromentを参照するScope chainにより、親ブロックのaの値を探しまわるパターンになります。
Scope chainがグロバール的につながっているからです。
しかし、探す周るプロセスが増えるので、変数はロカル変数を使用し、必要な時だけ使いましょう!
Hoisting
宣言文は基本的にJava Scriptの上にひっ張られます!
つまり、変数名、オブジェクト名のようなStack memoryに宣言した名前ですね!
しかし、関数の場合、変数名だけでなく、関数自体が上に上がりますので、宣言したブロックの上でも関数を呼び出すことができます!
yes();
function yes (){
console.log('yes');
}
yes
他のデータータイプの場合は一応宣言した変数が上がっているので、
undefinedでなく、not initializedというエラーが出ます!
ProtoType
簡単にいいますと、JavaのObjectのようなもので、親クラスもしくはInterfaceのことを意味します。JavaはInterfaceとクラスがしっかり分けられていますが、ProtoTypeはその中間みたいな気がします!
例えば、配列の場合、共通的にArrayが持っている関数を呼び出したり、オブジェクトの場合、Object(prototype)の関数を共通的に持っています。
PropertyDescriptor
JSのすべてのObjectのメタデータはPropertyDescritorというオブジェクトに保管されます。
const human = {name: 'john', job: 'actor'}
const descriptors = Object.getOwnPropertyDescriptors(human);
console.log(descriptors);
{
name: {
value: 'john',
writable: true,
enumerable: true,
configurable: true
},
job: {
value: 'actor',
writable: true,
enumerable: true,
configurable: true
}
}
writableは修正ができるかできないかを、enumerableはiterateな配列などに込めることができるかどうかを、configurableはpropertyを拡張したり、削除することができるかなどを意味し、基本的にはtrueです。
Object.defineProperty(human, 'name', {
value: 'hi',
writable: false,
enumerable: false,
configurable: false,
});
このように、falseになっている場合は、修正したり、Object.key()のように配列にも込めることができなくなります。
これがオブジェクトの整合性を維持します。
この場合、Object.freeze() で簡単にすべてをfalse()にするtこができます。
mixin
Prototypeはprototypeはclassではなくclass+interface みたいな感じだと記述しました。javascriptならではの特別な特徴があるからです。
まず、prototypeも関数も結局Object{}で、関数の場合多重継承ができます。
Javaのインターフェースのように、JSも関数の場合、一つ以上も継承することができます。
const jump = {
jump: function () {
console.log(`${this.name} がjump!`);
},
};
const breath = {
breath: function () {
console.log(`${this.name} が呼吸をしています。`);
},
};
function Kisatsudai(name) {
this.name = name;
}
Object.assign(Kisatsudai.prototype, jump, breath);
const tanjiro = new Kisatsudai('tanjiro');
console.log(tanjiro);
tanjiro.jump();
tanjiro.breath();
class Onigari {}
class Hasira extends Onigari {
constructor(name) {
super();
this.name = name;
}
}
Object.assign(Hasira.prototype, jump, breath);
const rengoku = new Hasira('rengoku');
rengoku.jump();
rengoku.breath();
tanjiro がjump!
tanjiro が呼吸をしています。
rengoku がjump!
rengoku が呼吸をしています。
Closure
先ほど、Scopeからブロック内には親を参照するscope chainがあり、外部lexical enviromentを参照できるからだと説明しました。
function outer() {
const a = 'inner point outer lexical enviroment';
function inner() ;
console.log(`inner : ${x}`);
}
return inner;
}
const func1 = outer();
func1();
outer()関数からinner関数の参照をリターンしました。そのため、func1にはi実質inner()と同じですが、外部lexical environmentも含まれており、const a の値も実質含まれております。 (どちらかというと参照だと思います)
inner : inner point outer lexical enviroment
要するにClosureは関数とその関数を囲めているlexical environmentの組合、function + lexical environment です。
内部の関数からlexical Environmentを参照することで外部関数のscope(変数を含めている空間)にアクセスできます。
これにより、innerのみリターンされ、outerのconst aは直接にはアクセスができないカプセル化効果がありますので、OOP的にデーターを隠すことができます。
This
global context
Javaの場合、thisはクラスのインスタンスを意味する静的なバインディングですが(compile以前に決める)、Javascriptのthisは動的バインディングができます。
node this
Node環境から、全域変数(global context)のthisはmoduleを意味します。
console.log(this)
{}
const a = 'wow'
module.exports.a = a;
console.log(this);
{a:'wow'}
node globalThis
Node環境から、全域変数(global context)のglobalThisはGlobal Object(api) を意味します。 node.jsにある基本オブジェクトを意味します。
console.log(globalThis);
<ref *1> Object [global] {
global: [Circular *1],
queueMicrotask: [Function: queueMicrotask],
clearImmediate: [Function: clearImmediate],
setImmediate: [Function: setImmediate] {
[Symbol(nodejs.util.promisify.custom)]: [Getter]
},
structuredClone: [Function: structuredClone],
clearInterval: [Function: clearInterval],
clearTimeout: [Function: clearTimeout],
setInterval: [Function: setInterval],
setTimeout: [Function: setTimeout] {
[Symbol(nodejs.util.promisify.custom)]: [Getter]
},
atob: [Function: atob],
btoa: [Function: btoa],
performance: Performance {
nodeTiming: PerformanceNodeTiming {
name: 'node',
entryType: 'node',
startTime: 0,
duration: 134.6279001235962,
nodeStart: 6.025200128555298,
v8Start: 31.82260012626648,
bootstrapComplete: 95.12580013275146,
environment: 50.91820001602173,
loopStart: -1,
loopExit: -1,
idleTime: 0
},
timeOrigin: 1696848027219.503
},
fetch: [AsyncFunction: fetch]
}
browser this,globalThis
Browser環境から、全域変数(global context)のthisはWindowというグ全域オブジェクト(global object)を意味します。
globalThisも同じです! fetch()もbrowerのAPIですね!
関数でのthis
関数内部でのthisはdefaultはglobalThisですが、
strict modeではundefinedです。
今はstrict modeを使うのが普通なので、ご注意ください!
function showMeTheThis() {
console.log(this);
}
showMeTheThis();
<ref *1> Object [global] {
global: [Circular *1],
queueMicrotask: [Function: queueMicrotask],
clearImmediate: [Function: clearImmediate],
setImmediate: [Function: setImmediate] {
[Symbol(nodejs.util.promisify.custom)]: [Getter]
},
.
.
Constructorでのthis
javascriptでは、オブジェクトを初期化する際にも動的バイイングになります。コード上にpropertyが宣言されていなくても、constructorのブロック内にthis.name =nameのように割り当てれば、オブジェクトを生成時に自動的に{name:??}が割り当てられる仕組みです。
しかし、動的バインディングにより、call backの際にundefindedをリターンする可能性があります。
そのため、bind()という関数を使う方法もあり、arrow functionで静的バインディングをすることができます。
function Human(name){
this.name= name;
this.printName = () => {
console.log(${this.name});
};
}
javascriptの関数は関数だけなく、オブジェクトを生成するconstructorとして定義することができます。これも動的バインディングが原因たと思いますが、これはメモリを重くすることになります。
しかし、アラー関数を活用すれば、コードを軽くすることができ、
classのメソッドを利用する方法もあります。
Babel
Javascriptの最新文法で作成したJSを昔のバージョンにダウングレードしてくれるサイトです。
基本的にはframeworkに内装されており、TypeScriptからも変換することもできますので、純粋JSのプロジェクトのみ使うかなと思いました!
Discussion