JavaScriptのconsole.logを作ってみた

# 概要
デバック情報などをブラウザ上に表示する　my_console.log()を作りました

# 使い方
```JavaScript
my_console.log("ログを表示する");
my_console.log("背景色を指定してログを表示する", "red");
my_console.log("りんごは\n赤いです", "yellow");

```
![55.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187537/a04eeeff-af79-066b-51e6-006ede4e2c1a.png)



# ソースコード
```JavaScript
var my_console = {

	ログの行数: 0,

	/***********************************************************************************************
		ログ_上限数は 五百行を基本とする。
		たった5000行で、物凄く重くなる。また、ほんの30回くらいUI更新する処理()したら、すぐ、ログ5000行なります。 
	/***********************************************************************************************/

	ログ_上限数: 500,/*これを超えると、古いログを半分削除する*/
	hash: "hash_2021mmddhhmmss",/*なんでもいい。とりあえず固有のIDにするためにコーディング時の日時をいれておく。例： "hash_201910111424" */

	modalWindow_elem: null,
	コンソールテキスト行_div_elem: null,
	isログの最後に現在日時を追加する: true,
	is永続的な新しいスタイルシートを設定した: false,
	ログの行数を表示する場所_span_elem: null,

	自動スクロールする_input_elem: null,

	isログ_上限数が多い事の警告を出した: false,

	createOverlay: function(){
		/* コーダーの為の安全装置 */
			if( this.hash === null ){
				my_alert('エラー。my_consoleを使いたいなら、プロパティのhashになんでもいいので文字列を入れて下さい');
				return false;
			}

		/*安全装置*/
			if(this.isログ_上限数が多い事の警告を出した !== true && this.ログ_上限数 > 500){
				my_alert('consoleのログ_上限数が 500行より多いです。この警告は初めに1度だけ行います。デバックが終了したら500に戻しましょう。');
				this.isログ_上限数が多い事の警告を出した = true;
			}

		/* ウインドウを生成 */
			/* 既に存在するかチェックし、"存在するなら何もしない */
			if( this.modalWindow_elem !== null){
				return true;
			}else{
				/* この中では、my_console私用NG。無限ループなる */
				// console.log('my_consoleのmodalwindowが無いのでこれから作ります');
			};

			/* body内の末尾に追加 */
			this.modalWindow_elem = document.body.appendChild(document.createElement('div'));
			this.modalWindow_elem.id = `my_console_${this.hash}`;

			var h2_elem 					= this.modalWindow_elem.appendChild(document.createElement('h2'));
				// h2_elem.innerHTML 			= 'console windows（クリックすると全面<=>後面）';

			var p_elem 						= this.modalWindow_elem.appendChild(document.createElement('p'));
				p_elem.innerHTML 			= `ログの上限数:${this.ログ_上限数}行&nbsp;現在の行数:<span id="log_rows_now">${this.ログの行数}</span>`;

			/*ちょっと力業だが、thisに入れておく*/
			this.ログの行数を表示する場所_span_elem = this.modalWindow_elem.querySelector('span#log_rows_now');

			var label_elem 					= this.modalWindow_elem.appendChild(document.createElement('label'));
				label_elem.innerHTML 		= `<input type="checkbox" name="is_auto_scroll_${this.hash}" checked="checked">自動スクロールする`;
			this.自動スクロールする_input_elem = this.modalWindow_elem.querySelector(`input[name=is_auto_scroll_${this.hash}]`);


			this.コンソールテキスト行_div_elem 	= this.modalWindow_elem.appendChild(document.createElement('div'));
			this.コンソールテキスト行_div_elem.id = `my_console_text_line_${this.hash}`;



			var that = this;
			this.modalWindow_elem.addEventListener('click', function(){

				/* UIが9999なので、10000にして前にだす */

				if(that.modalWindow_elem.style.zIndex === "10000"){
					that.modalWindow_elem.style.zIndex = "9998";
				}else{
					that.modalWindow_elem.style.zIndex = "10000";
				}

			});


			/*ここまでOK*/
			this.とあるelemに対してとあるcss_objを適用する(this.modalWindow_elem, {
				'width': '360px',
				'height': '80%',
				'margin-top': '10x',
				'padding': '10px',
				'font-size': '12px',
				'position': 'fixed', /* fixed: これは画面の決まった位置に要素を固定させたいときに使います。 */
				'border': '2px solid #D04255',
				'background-color': '#FFF',
				'background': 'rgba(255, 255, 255, 0.9)',/* 背景を半透明にする */
				/* 'display': 'none',  */
				'z-index': '9999',  /* 「オーバーレのHTML要素」よりもスタックレベルを高くする */
				'color': '#0000ff',
				'text-align': 'left',
				'right': 10 + 'px',
				'top': 10 + 'px',
				"padding": "4px",
				"padding-left": "20px",
				"padding-bottom": "50px",
			});

			this.とあるelemに対してとあるcss_objを適用する(h2_elem, {
				"font-size": "12px",
			});

			this.とあるelemに対してとあるcss_objを適用する(p_elem, {
				"font-size": "10px",
			});

			this.とあるelemに対してとあるcss_objを適用する(this.コンソールテキスト行_div_elem, {
				"height": "90%",
				"overflow": "scroll"
			});


			h2_elem = void 0;
			p_elem = void 0;
			label_elem = void 0;

	},

	コンソールウインドウを非表示にする: function(){
		this.modalWindow_elem.hidden = true;
	},
	コンソールウインドウを表示する: function(){
		this.modalWindow_elem.hidden = false;
	},
	isコンソールウインドウは表示されている: function(){
		if(this.modalWindow_elem.hidden === true){
			return false;
		}else{
			return true;
		}
	},
	/* 表示、非表示をトグルスイッチで切り替える */
	コンソールウインドウの表示と非表示を切り替えるトグルスイッチ: function(){
		this.createOverlay();

		if( this.isコンソールウインドウは表示されている() === true ){
			this.コンソールウインドウを非表示にする();
		}else{
			this.コンソールウインドウを表示する();
		}
	},

	log: function(text, 背景のcssカラーコード = 'transparent', isログの最後に現在日時を追加する = this.isログの最後に現在日時を追加する){

		this.createOverlay();

		/* 改行コードつきの文字を 人間に見やすくするために、置換してみる */
			text = text.replace(/\n/g, '<br>');

		/* てきとうにトリム */
			text = text.trim();

		/************************************************************
			追加するHTMLを作る。（軽量化の為、DOM操作は極力最小限で）
		/************************************************************/
			var HTML = '';
			/* 文章と日時の親 div */
			if(背景のcssカラーコード === 'transparent'){
				HTML += '<div class="p_no_oya">';
			}else{
				HTML += '<div class="p_no_oya" style="background:'+背景のcssカラーコード+'">';
			}
			/* 文章 */
			HTML += '<p class="my_console_text_'+ this.hash + '">' + text + '</p>\n';
			/* 日時 */
			if(isログの最後に現在日時を追加する === true){
				HTML += '<p class="my_console_date_'+ this.hash + '"><span>' + this.get現在日時() + '</span></p>\n';
			}
			/* 文章と日時の親 divの終わり */
			HTML += '</div>';

		/************************************************************
			追加する
		/************************************************************/
			this.コンソールテキスト行_div_elem.insertAdjacentHTML('beforeend', HTML);
			this.ログの行数++;

		/************************************************************
			永続的な新しいスタイルシートを１度だけ作成する */
		/************************************************************/
			if( this.is永続的な新しいスタイルシートを設定した !== true ){

				var style_elem = document.head.appendChild(document.createElement('style'));
					style_elem.id = `my_console_style_${this.hash}`;
					style_elem.type = 'text/css';
					style_elem.innerHTML = `
						div.p_no_oya{
							border-bottom: 1px solid #bbb;
						}

						p.my_console_text_${this.hash}{
							margin: 0px;
							color: #000000;
							background-color: transparent;
						}

						p.my_console_date_${this.hash}{
							margin: 0px;
							text-align: right;
							background-color: transparent;
						}

						p.my_console_date_${this.hash} span{
							text-align: right;
							color: #000000;
							background-color: #c4c4c4;
						}

					`;

				style_elem = void 0;
				this.is永続的な新しいスタイルシートを設定した = true;
			}

		/************************************************************
			ログの削除
		/************************************************************/
			if(this.ログの行数 > this.ログ_上限数){
				/*************************************************
					無限ループ注意
					本処理内でmy_console.log()内でmy_console.log()を呼ぶと、再帰的になり簡単に無限ループに突入します。
					辞めましょう。
				**************************************************/
				var 削除する行数 = Math.floor(this.ログ_上限数 / 2);
				var TEMP_削除した行数 = 0;
				var 削除対象_elem_nodeList = this.コンソールテキスト行_div_elem.querySelectorAll(`div.p_no_oya:nth-child(-n+${削除する行数})`);

				for(var i=0, len=削除対象_elem_nodeList.length; i<len; i++){
					var 削除対象_elem = 削除対象_elem_nodeList[i];
					削除対象_elem.parentNode.removeChild(削除対象_elem);
					削除対象_elem = void 0;
				}
				this.ログの行数 = this.ログの行数 - 削除対象_elem_nodeList.length;

				/*********************************************************************
					ログを削除した後なので無限ループにはならないとは思いますが、my_console.log()内でmy_console.log()を使う場合は注意すること
				*********************************************************************/
				my_console.log(`ログが上限LIMIT${this.ログ_上限数}行を超えたので、${削除対象_elem_nodeList.length}行を削除しました。`, 'yellow');

				削除対象_elem_nodeList = void 0;
			}

		/************************************************************
			スクロール
		/************************************************************/
			if(this.自動スクロールする_input_elem.checked  === true){
				this.下までスクロールする();
			}

		/************************************************************
			ログの行数を表示する
		/************************************************************/
			this.ログの行数を表示する場所_span_elem.innerText = this.ログの行数;


		/************************************************************
			new!
				ブラウザタイトルに、my_console.logの最終日時を出す
				（フリーズしたことを、タスクマネージャーからすぐ発見できるように）
		/************************************************************/
		var タイトルの先頭に追加する日時_String = this.getDateオブジェクトを日本語表記にして返す_title用(new Date());
		var 先頭に日時があるかもしれないtitle = document.title;
		/* 2週目以降にtitleにどんどん日時が追加されていくのを防ぐために、日時があるならば削除しておく*/
		var 先頭に日時の無いtitle = 先頭に日時があるかもしれないtitle.replace(/[0-9]{2}\/[0-9]{2} [0-9]{2}:[0-9]{2}/g, '');

		document.title = タイトルの先頭に追加する日時_String + " " + 先頭に日時の無いtitle;


		/************************************************************
			GC!!
		/************************************************************/
			HTML = void 0;
			style_elem = void 0;
			削除する行数 = void 0;
			TEMP_削除した行数 = void 0;
			削除対象_elem_nodeList = void 0;
			i = void 0;
			削除対象_elem = void 0;

	},


	getDateオブジェクトを日本語表記にして返す_title用: function( Date ){
		var date = Date;

		var year = date.getFullYear(); 
		var month = date.getMonth() + 1; /* monthは0始まりなので人間用にプラス1する */
		var day = date.getDate();

		var weeks = new Array('日','月','火','水','木','金','土');
		var week = weeks[ date.getDay() ];

		var hour = date.getHours();
		var min = date.getMinutes();
		var sec = date.getSeconds();

		if(month < 10) { month = "0" + month; }
		if(day < 10) { day = "0" + day; }
		if(hour < 10) { hour = "0" + hour; }
		if(min < 10) { min = "0" + min; }
		if(sec < 10) { sec = "0" + sec; }

		var string = `${month}/${day} ${hour}:${min}`;
		return string;
	},


	getゼロ埋め: function(num,length){
		return ('0000000000' + num).slice(-length);
	},

	get現在日時で使う_week_list: ['日','月','火','水','木','金','土'],
	get現在日時: function(){
		var now = new Date();

		// var year 			= now.getFullYear(); 
		var month 			= now.getMonth() + 1; /* monthは0始まりなので人間用にプラス1する */
		var day 			= now.getDate();
		var week 			= this.get現在日時で使う_week_list[ now.getDay() ];
		var hour 			= now.getHours();
		var min 			= now.getMinutes();
		var sec 			= now.getSeconds();
		var milliseconds	= now.getMilliseconds();

		month 			= this.getゼロ埋め(month, 2);
		day 			= this.getゼロ埋め(day, 2);
		hour 			= this.getゼロ埋め(hour, 2);
		min 			= this.getゼロ埋め(min, 2);
		sec 			= this.getゼロ埋め(sec, 2);
		milliseconds 	= this.getゼロ埋め(milliseconds, 3);

		now = void 0;

		return `${month}/${day}(${week}) ${hour}:${min} ${sec}秒${milliseconds} `;
	},

	下までスクロールする: function(){

		var topからの位置 = this.コンソールテキスト行_div_elem.scrollTop + 99999;

		this.コンソールテキスト行_div_elem.scrollTo({
			top: topからの位置,
			behavior: "instant"
		});

		topからの位置 = void 0;

	},

	とあるelemに対してとあるcss_objを適用する: function(elem, css_obj){
		for(var css名 in css_obj){
			elem.style[css名] = css_obj[css名];
		}

		elem = void 0;
		css_obj = void 0;

		return true;
	},

};/* end of my_console{} */

```


# コードを改良して頂きました。爆速になりました

改良前と改良後のコードを比較するために、
ログを1万行出力するテストを行いました

▼結果
改良前の実行時間: 60227 msec(約60秒)
改良後の実行時間: 00093 msec<font color="DeepPink">(約0.1秒)</font>

▼下記のテストコードを実行しました
```JavaScript
var count = 0;
var start_Date = new Date();

for(let i = 0; i<10000; i++){
  count++;
  my_console.log(count + " 行目のログです", "yellow");
}

var end_Date = new Date();

my_console.log("▼改良後");
my_console.log(`実行時間: ${end_Date.getTime() - start_Date.getTime()} msec`);
```

## 改良前のコードの実行結果
![改良前.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187537/a543b1a1-6796-51d8-6731-7540d46114df.png)


## @ss8826 さんから頂いた改良されたコードの実行結果
![改良後.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187537/9e7092ac-f46c-7fa0-4305-b47868e25062.png)

## 改良して頂いた事
- my_console.log()を実行する度に毎回 一時的な変数<code>var HTML = ''</code> を作っていましたが、それをオブジェクトプロパティとして作っておき、毎回再利用する
- DOMにHTMLを追加するinsertAdjacentHTML()処理は、setTimeout()を使って非同期で行う。

# 感想
びっくりしました


# 次やる事
- 改良したコードをqiitaに投稿する
- まさか人様に見て頂けるなんて夢にも思っていなかったので、my_console.log()に関係のない自作関数を消したり、自分しか分からないメモやコメントを書きなおす

# おまけ
改良して頂いたコードで、ログを９万行出力という恐ろしい処理を恐る恐る実行してみると、
たったの<font color="DeepPink">913msec</font>で処理が終わりました。
![99.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187537/44f5be63-2866-68a5-3b5c-53b774468210.png)


私のコードで9万行出力テストをしたところ、5分待っても処理が終わらなかったため、ブラウザを再起動しました。