🦔

【初心者向け 099】JS高級文法 (async&await,fetch,scope,Prototype,closure,This) 

2023/10/09に公開

はじめに

いよいよJS文法を一回勉強することができました!!!!

https://academy.dream-coding.com/courses/javascript

コードの場合、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

https://babeljs.io/

Javascriptの最新文法で作成したJSを昔のバージョンにダウングレードしてくれるサイトです。
基本的にはframeworkに内装されており、TypeScriptからも変換することもできますので、純粋JSのプロジェクトのみ使うかなと思いました!

Discussion