追記: 10/11 ハテブでバズっているようで、色々指摘があったので追記
getElement*は動作が早いのでIDやクラス名が自明の場合はgetElement*を使う方がいいと言う意見もあり、また、ページの表示で大量に呼び出されるわけではないからボトルネックにはならないと言う意見もある。
getElement*で返されるオブジェクトは動的な変化に対応しており、querySelector*は動的な変化に対応していないため、場合によってはgetElement*を使うといい。このサイトで遊んでみよう。
https://ja.javascript.info/searching-elements-dom#ref-263
for await ... ofは非推奨なのでPromise.allを現代的な書き方にした
顧客先のブラウザが古い場合も考慮して、あえてレガシーな書き方もする場合があるらしい。現に、codeSandboxで動作確認をしていたとき、動かないものもあった(どの構文が動かないか忘れた)
ChatGPTの有料版に入っていても、JSのコードを出力させるといまだに古い書き方を出力される場合があるので、"現代的なJSの書き方で"のような文言を入れると現代的なJSの書き方で出力してくれる
元記事のタイトルは『"オジさん"と言われないための〜』だったがハテブコメで否定的な多く意見があったので『"レガシー"と言われないための〜』に変更。自虐と釣りタイトルを考慮してこの言葉を安直に使ってしまった。不快に思わせてしまいすみません。
ChatGPTには次のような文言を書けば似たようなのが出てきます。一回の出力で3,4個しか出力されないので、「もっと」 と打てばもっと出力してくれます
レガシーなJavaScriptと現代風に追加されたJavaScriptの機能を比較して記事を書いてみましょう。以下の条件に従ってください。*伝統的に使われている機能を使い続けていて最新のJavaScriptの機能を知らない人に向けた記事です。* 「レガシーなコード」と「現代風なコード」のサンプルコードを書いてください* 各機能の現代風のコードを書いた後、何が変わったのか、変わって便利になった点などを300文字程度でコメントを書いてください。ChatGPTにコードリーディングをしてもらっていると「これは伝統的な書き方ですが、もっと現代風の書き方がありますよ」といった旨を言われて悲しくなってしまった。要は新しい情報をキャッチアップできてないおじさんと言われたようなものだ。"現代風"のJavaScriptの書き方を調べるのは大変なのでChatGPTにいろいろリストアップしてもらったので共有したい。ちなみに
ここでは詳細なことには踏み込まず、流し読みしてもらうためにざっくり書いている。気になる箇所があればその都度ググって調べて欲しい。
getElement* メソッド):const elementById=document.getElementById('myId');const elementsByClass=document.getElementsByClassName('myClass');const elementsByTag=document.getElementsByTagName('div');querySelector とquerySelectorAll):const elementById=document.querySelector('#myId');const elementsByClass=document.querySelectorAll('.myClass');const elementsByTag=document.querySelectorAll('div');// こんな指定もできるconst elements1=document.querySelectorAll('div.parent > span#child');// parentというクラスめいのdiv子要素のうち、childというidを持ったspan要素const elements1=document.querySelectorAll("#parent li:nth-child(odd)");// parentというidを持った子孫の要素のうち、奇数番目のli要素querySelector とquerySelectorAll を使用することで、CSSセレクタの構文を使ってDOM要素を取得できるようになりました。これにより、要素の選択がより直感的で柔軟になり、一つのメソッドで複数の取得方法をカバーすることができるようになりました。getElement*を使う場合 (パフォーマンス編) :getElement*はquerySelector*に比べてかなり速い。呼び出し回数にも依存するが、数十倍-100倍程度さが開く。そう聞くとものすごい差に思えるが単位がミリ秒だから1万回呼び出すだけならボトルネックにはならない。しかし十万回以上呼び出す場合はquerySelectorは避けた方が吉。| 呼び出し回数 | getElementByClassName の時間 (ミリ秒) | querySelectorAll の時間 (ミリ秒) |
|---|---|---|
| 1,000 回 | 0(1m秒以下) | 48 |
| 10,000 回 | 3 | 359 |
| 100,000 回 | 12 | 3421 |
| 1,000,000 回 | 78 | 33991 |
以下に100万回呼び出すコードを書いておくので DevToolsを開いて動かしてみてほしい。
// 仮の要素とクラスを作成const div=document.createElement('div');div.className='test-class';document.body.appendChild(div);// getElementByClassName のパフォーマンス測定let start=performance.now();for(let i=0; i<1_000_000; i++){document.getElementsByClassName('test-class');}let end=performance.now();console.log(`getElementByClassName took${end- start} milliseconds.`);// querySelectorAll のパフォーマンス測定start=performance.now();for(let i=0; i<1_000_000; i++){document.querySelectorAll('.test-class');}end=performance.now();console.log(`querySelectorAll took${end- start} milliseconds.`);// 仮の要素を削除document.body.removeChild(div);getElement*を使う場合 (動的か静的か) :const arr=[1,2,3,4];const hasTwo= arr.indexOf(2)!==-1;// trueconst arr=[1,2,3,4];let result;if(arr.indexOf(2)!==-1){ result="2はある";}else{ result="2はない";}console.log(result);// 2はあるconst arr=[1,2,3,4];const hasTwo= arr.includes(2);// trueconst arr=[1,2,3,4];let result;if(arr.includes(2)){ result="2はある";}else{ result="2はない";}console.log(result);// 2はあるincludesメソッドを使用することで、配列内に特定の要素が存在するかどうかを簡単にチェックできるようになりました。これにより、コードの可読性が向上し、意図が明確になりました。const key="name";const age=20const gender="male"const obj={key: key,age: age,gender: gender};// { key: 'name', age: 20, gender: 'male' }const key="name";const age=20const gender="male"const obj={key: key,age: age,gender: gender};console.log(obj);// { key: 'name', age: 20, gender: 'male' }const key="name";const age=20const gender="male"const obj={ key, age, gender};// { key: 'name', age: 20, gender: 'male' }const key="name";const age=20const gender="male"const obj={ key, age, gender};console.log(obj);// { key: 'name', age: 20, gender: 'male' }const name="Alice";const greeting="Hello, "+ name+"!";// Hello, Alice!const name="Alice";const greeting=`Hello,${name}!`;// Hello, Alice!functionfetchData(callback){// データ取得後callback(data);}fetchData(function(data){console.log(data);});functionfetchData(){returnnewPromise((resolve, reject)=>{// データ取得後resolve(data);});}fetchData().then(data=>console.log(data));asyncfunctionfetchData(){// データ取得return data;}const data=awaitfetchData();console.log(data);配列操作: Manual Iteration vs. Array Methods
const numbers=[1,2,3,4];const doubled=[];for(let i=0; i< numbers.length; i++){ doubled.push(numbers[i]*2);}// [2, 4, 6, 8]const numbers=[1,2,3,4];const doubled= numbers.map(n=> n*2);// [2, 4, 6, 8]functiongreet(name){ name= name||"Guest";console.log("Hello, "+ name+"!");}functiongreet(name="Guest"){console.log(`Hello,${name}!`);}|| 演算子を使って条件を設定していました。このアプローチは、引数が0やfalseの場合などに問題を引き起こす可能性がありました。デフォルトパラメータを使用することで、この問題を回避し、関数定義がより直感的でエラーを防ぐことができます。if(user&& user.address&& user.address.street){console.log(user.address.street);}var user={address:{street:"123 Main St"}};if(user&& user.address&& user.address.street){console.log(user.address.street);// "123 Main St"}console.log(user?.address?.street);const user={address:{street:"456 Elm St"}};console.log(user?.address?.street);// "456 Elm St"console.log(user?.address?.postcode);// undefined昔のスタイル:
var arr=[1,2,3];var first= arr[0];// 1var second= arr[1];// 2現代風:
const arr=[1,2,3];const[first, second]= arr;// first は 1, secondは 2const arr=[1,2,3];const[first, second]= arr;console.log(first);// 1console.log(second);// 2const[ichi,...ni]= arr;console.log(ichi);// 1console.log(ni);// [ 2, 3 ]var orderDate="2023年1月1日"var orderDateNumbers= orderDate.match(/\d+/g);var year= orderDateNumbers[0];//2023var month= orderDateNumbers[1];// 1var day= orderDateNumbers[2];//1const orderDate = "2023年1月1日"const [year, month, day] = orderDate.match(/\d+/g); // yearには2023, monthには1, dayには1 が代入されている ```classUser{constructor(name, age){this.name= name;this._age= age;// アンダースコアを使用してプライベートを模倣}isAdult(){returnthis._age>20;}_isKanreki(){// アンダースコアを使用してプライベートメソッドを模倣returnthis._age>60;}}classUser{constructor(name, age){this.name= name;this._age= age;}isAdult(){returnthis._age>20;}_isKanreki(){returnthis._age>60;}publicIsKanreki(){returnthis._isKanreki();}}const tenno=newUser("hironomiya-tenno",63);console.log(tenno.name);// "hironomiya-tenno"console.log(tenno.isAdult());// trueconsole.log(tenno.publicIsKanreki());// trueconsole.log(tenno._age);// 63 ※外部からアクセスできてしまうconsole.log(tenno._isKanreki());// true ※外部からアクセスできてしまうclassUser{ name; #age;// 真のプライベートフィールドconstructor(name, age){this.name= name;this.#age= age;}isAdult(){returnthis.#age>20;}#isKanreki(){// 真のプライベートメソッドreturnthis.#age>60;}}classUser{ name; #age;constructor(name, age){this.name= name;this.#age= age;}isAdult(){returnthis.#age>20;}#isKanreki(){returnthis.#age>60;}publicIsKanreki(){returnthis.#age>60;}}const tenno=newUser("hironomiya-tenno",63);console.log(tenno.isAdult());// trueconsole.log(tenno.isKanreki);// undefined ※プライベートメソッドは外部からアクセスできないconsole.log(tenno.publicIsKanreki());// trueconsole.log(tenno.name);// hironomiya-tennoconsole.log(tenno.age);// undefined ※プライベートフィールドは外部からアクセスできない_を書いていました。しかしこれは単なる慣習なので、実際は外部からアクセスできてしまいます。現代的な方法として、プライベートフィールドおよびプライベートメソッドが導入されました。これらの機能は、フィールド名やメソッド名の前に # を付けることで使用できます。#をついたフィールドやメソッドは外部からアクセスできません。// 同時に複数のユーザを同期的に作りたいとするconstUSER_DATA_LIST=[{userId:1,userName:"User1"}{userId:2,userName:"User2"}{userId:3,userName:"User3"}{userId:4,userName:"User4"}{userId:5,userName:"User5"}]forawait(const userDataofUSER_DATA_LIST){awaitcreateUser(userData.userId, userData.userName)}// 同時に複数のユーザを同期的に作りたいとするconstUSER_DATA_LIST=[{userId:1,userName:"User1"}{userId:2,userName:"User2"}{userId:3,userName:"User3"}{userId:4,userName:"User4"}{userId:5,userName:"User5"}]Promise.all(USER_DATA_LIST.map(userData=>createUser(userData.userId, userData.userName)))サンプルコードはこちらの記事からお借りしました
https://zenn.dev/takeru0430/articles/9dcd9d70e4ec92
var arr1=[1,2,3];var arr2= arr1.concat([4,5,6]);var arr1=[1,2,3];var arr2= arr1.concat([4,5,6]);console.log(arr2);// [ 1, 2, 3, 4, 5, 6 ]const arr1=[1,2,3];const arr2=[...arr1,4,5,6];const arr1=[1,2,3];const arr2=[...arr1,4,5,6];console.log(arr2);// [ 1, 2, 3, 4, 5, 6 ]var key="name";var obj={};obj[key]="Taro";const key="name";const obj={[key]:"Taro"};const inputData=0;const value= inputData||"defaultの値";// "defaultの値"let inputData1;// 初期値を書いてないときは undefinedconst value1= inputData1||"あああ";console.log(value1);// "あああ"let inputData2=0;const value2= inputData2||"あああ";console.log(value2);// "あああ"const inputData=0;const value= inputData??"defaultの値";// 0let inputData1;// undefinedconst value1= inputData1??"あああ";console.log(value1);// "あああ"let inputData2=0;const value2= inputData2??"あああ";console.log(value2);// 0??) を使用することで、undefinedやnullの場合だけデフォルト値を設定できるようになりました。これにより、0やfalseなどのfalsyな値を誤ってデフォルト値で上書きする問題を防ぐことができます。const nested=[[1,2],[3,4]];const flattened=[].concat.apply([], nested);const nested=[[1,2],[3,4]];const flattened=[].concat.apply([], nested);console.log(flattened);// [ 1, 2, 3, 4 ]const nested=[[1,2],[3,4]];const flattened= nested.flat();const nested=[[1,2],[3,4]];const flattened= nested.flat();console.log(flattened);// [ 1, 2, 3, 4 ].flat()メソッドを使用することで、ネストされた配列を簡単に平坦化できるようになりました。これにより、コードが簡潔になり、配列の操作が直感的になりました。const billion=1000000000;// 1000000000const billion=1_000_000_000;// 1000000000_)を使用することで、大きな数字の可読性が向上しました。これにより、数字の桁を迅速に把握し、ミスを減少させることができます。try{// エラーがないときに動くコード}catch(error){console.error("An error occurred!");}try{// エラーがないときに動くコード}catch{console.error("An error occurred!");}catchを使用することが可能になりました。これにより、エラーオブジェクトが不要な場合のコードが簡潔になりました。const obj={a:1,b:2,c:3,d:4};const a= obj.aconst b= obj.b;const rest={c: obj.c,d: obj.d};const obj={a:1,b:2,c:3,d:4};const a= obj.aconst b= obj.b;const rest={c: obj.c,d: obj.d};console.log(a);// 1console.log(b);// 2console.log(rest);// { c: 3, d: 4 }const obj={a:1,b:2,c:3,d:4};const{ a, b,...rest}= obj;const obj={a:1,b:2,c:3,d:4};const{ a, b,...rest}= obj;console.log(a);// 1console.log(b);// 2console.log(rest);// { c: 3, d: 4 }Object.values() と Object.entries(): オブジェクトの値とエントリーの取得
const obj={a:1,b:2};const values=Object.keys(obj).map(key=> obj[key]);// [1, 2]const obj={a:1,b:2};const values=Object.values(obj);// [1, 2]const keys=Object.keys(obj);// ["a", "b"]Object.values()とObject.entries()を使用することで、オブジェクトの値やキーと値のペアを簡単に取得できるようになりました。これにより、オブジェクトのデータの操作が簡潔かつ効率的になりました。