JavaScriptにおけるシャローコピーとディープコピーについて
JavaScriptにおけるシャローコピー(shallow copy)とディープコピー(deep copy)について簡単にまとめました。 shallowは日本語で「浅い」という意味になります。
シャローコピー
シャローコピーはざっくり言うと参照元のオブジェクトとコピー先のオブジェクトどちらも同じメモリを参照していることをいいます。
簡単な例
const obj = { name: "hoge", age: 24 } const obj2 = obj obj.name = "fuga"; console.log(obj2.name) // fuga console.log(obj.name) // fuga
同じメモリを参照しているのでobj2
のname
を変更するobj
のname
も変更されてしまいます。
ディープコピー
ディープコピーとは、オブジェクトのみのコピーではなく、オブジェクトとメモリ上のデータの両方をコピーします。コピー元のプロパティを変更しても、コピー先のプロパティは変更されません。
スプレッド演算子を使ってディープコピーをしてみたいと思います。
const newObj = { ...obj } newObj.name = "taro" console.log(newObj.name) // taro console.log(obj.name) // hoge
ちゃんとできているように見えます。
では次のケースはどうでしょうか? familiesプロパティのオブジェクトを追加しました。
const obj = { name: "hoge", age: 24, families: { brothers: 3, sisters: 1 } } const newObj = { ...obj } newObj.families.brothers = 5 console.log(newObj.families.brothers) // 5 console.log(obj.families.brothers) // 5
コピー元とコピー先のプロパティどちらも変更されてしまっています。 スプレッド演算子はシャローコピーだということがわかります。
JSON.stringifyとJSON.parseを使う方法があるそうですが、undefinedや関数が定義されていると、プロパティがなくなってしまうのです。 下の例をみてください。
const person = { name: "二郎", age: 20, run: () => {console.log("すとすと")} } person2 = JSON.parse(JSON.stringify(person)) console.log(person2) // {name: "二郎", age: 20}
関数runがありません・・・
もっと簡単な方法はないかと思っていたところ、ありました。 lodashを使用する方法です。
const _ = require('lodash'); // 追加 const obj = { name: "hoge", age: 24, families: { brothers: 3, sisters: 1 }, run: () => {console.log("すとすと")} } const newObj = _.cloneDeep(obj) console.log(newObj); // { name: 'hoge', age: 24, family: { brother: 2, sister: 1}, run: [Function: run] } newObj.families.brothers = 10000; console.log(newObj.families.brothers); // 1000 console.log(obj.families.brothers); // 3
ちゃんとディープコピーができています。 これからもlodashとは仲良くしていきたいと思います。
簡単ではありますが、JavaScriptにおけるシャローコピー、ディープコピーについて紹介しました。
参考URL
Nodemailerを使ってgmailを送ってみる
タイトルにもあるように今回は、Nodemailerを使って手軽にgmailを送ってみます。railsには標準でActionMailerがあるのですが、nodeにも何か良いライブラリはないのかなーと調べたところありました。
Nodemailerについて
環境
node v8.11.1
はじめに
作業ディレクトリを作成します。
mkdir nodemailer_sample
パッケージをインストール
yarn add -D nodemailer
ファイルを作成
ルートディレクトリ直下にcontact.js
を作成してください。(ファイル名はなんでもいいです。)
const nodemailer = require('nodemailer');
まずは、nodemailer
を読み込みます。
その後、smtpの設定を行います。 今回はgmailを使用します。
const smtpConfig = nodemailer.createTransport({ service: 'gmail', //サービス名 port: 46, // ポート番号 secure: true, // 認証するかしないか auth: { user: 'メールアドレス', pass: 'パスワード' } });
ここでメールアドレスやパスワードを直に書くのはよくないので環境変数を使います。nodeには環境変数を手軽に使うことができるライブラリがあるのでインストールしましょう。
dotenvのインストール
dotenvについて
インストールします。
yarn add -D dotenv
ルートディレクトリ直下に.env
ファイルを作成します。
中身はこちら。
EMAIL_ADDRESS = "ご自身のメールアドレス" EMAIL_PASSWORD = "パスワード"
再度jsファイルを以下のように変更します。
const nodemailer = require('nodemailer'); require('dotenv').config(); // 追加 const smtpConfig = nodemailer.createTransport({ service: 'gmail', port: 46, secure: true, auth: { user: process.env.EMAIL_ADDRESS, // 追加 pass: process.env.EMAIL_PASSWORD // 追加 } });
require('dotenv').config()
で.env
を読み込みます。
secure
をtrue
に設定した場合、authにuser、passそれぞれに値が入っていないとエラーになるので設定してくださいね。
メッセージの設定
const message = { from: process.env.EMAIL_ADDRESS, to: process.env.EMAIL_ADDRESS, subject: 'テストです', text: 'サンプルメール送ってみたよ', }
今回は簡単なデモのため送信側も受信側も同じアドレスを使用しています。
メールの送信設定
smtpConfig.sendMail(message, (error, data) => { if(error) console.log(error) console.log(data); });
sendMailメソッドの第一引数に上で定義したメッセージの変数を、第二引数にはコールバック関数が入ります。
ターミナル上でログを確認するためにerror
とdata
を記述しました。
実際にメールを送信してみる
node ファイル名.js
gmialにメールが飛んできます!
まとめ
あっという間にメールを送信することができました! expressを使って入力画面、確認画面、サンクス画面を作ってメールを送信することも今後してみたいと思います。
JavaScriptにおけるカンマ演算子について
今回はJavaScriptにおける演算子の中でもカンマ演算子を取り上げたいと思います。
カンマ演算子は、演算対象を左から右へ評価し、最後に評価した値を返します。
簡単な例を示します。
let name = "john", age = 35, personalInfo; personalInfo = (name, age); console.log(personalInfo); // 35
personalInfoには最後に評価された値が代入されています。
もう一つ例を挙げます。
let name = "john", age = 35, personalInfo; personalInfo = (name, age); console.log(personalInfo); // 35 personalInfo = name, age console.log(personalInfo) // john
まず2行目でカンマ,
を使っているので最後に演算された値が代入されるはずです。ですが4行目では、最初に評価された値john
が代入されています。
このような結果になるのは、演算子の優先順位が関わっています。
カンマ演算子よりも代入演算子の方が演算子の優先順位が高いため、4行目では、nameが代入されています。括弧()
をつけることでカンマ演算子を先に演算するようにしています。
演算子の優先順位についてはこちらを参考にしてみてください。
簡単ではありますが以上です。
JavaScriptの真・偽とみなされる値について
今回はJavaScriptにおける真となる値、偽となる値について説明していきます。
偽とみなされる値
- null
- undefined
- flase
- 0
- NaN
- 空文字('')
上記の値以外は真となりますが、その中でも覚えておきたいものをピックアップしたいと思います。
真とみなされる値
- オブジェクト
- 配列
- 空白のみ
- 文字列の"false"
まず偽とみなされる値についてコードを書いて検証します。
null ? true : false // → false undefined ? true : false // → false false ? true : false // → false 0 ? true : false // → false NaN ? true : false // → false '' ? true : false // → false
次に真となる値について検証したいと思います。
const obj = {} obj ? true : false // → true const arr = []; arr ? true : false // → true const spaceString = ' ' spaceString ? true : false // → true const bool = "false" bool ? true : false // → true
空オブジェクト、空配列のときに偽としたいケースが多いと思います。 そのようなときは、
const obj = {}; console.log(Object.keys(obj).length ? true : false) // false const arr = []; console.log(arr.length ? true : false) // false
このように書くといいです。
ちょっとした備忘録でした。
Javascriptのイベントフェーズについて
今回はJavascriptのイベントフェーズについて説明していきたいと思います。
そもそもイベントにはどのようなものがあるのか?
イベントの種類
onClick
・・・ボタンやリンクをクリックした時
onSubmit
・・・フォームの送信ボタンをクリックした時
onKeyDown
・・・キーを押した時
onWheel
・・・要素のコンテンツをスクロールした時
他にも色々とあります。 気になる方は調べてみてください。
イベントフェーズ
次にイベントが始まって終わるまでのフェーズは全部で3段階あります。 順を追ってみてみましょう。
- キャプチャーフェーズ
- ターゲットフェーズ
- バブルフェーズ
1. キャプチャーフェーズ
ドキュメントルートツリーからイベントの発生する要素へ伝播するフェーズ
2. ターゲットフェーズ
イベントが発生した要素を特定するフェーズ
3. バブルフェーズ
2で発生したイベントがルートまで遡っていくフェーズ
このようにちゃんと順序があるんですね。
javascriptにおいてイベントの発火を検知するのに
addEventListener
を使うことが多いと思います。
addEventListenerはさまざまなイベント処理をハンドリングできるメソッドです。
ターゲットとなる要素.addEventListener(type, listener, useCapture);
type
:イベントの種類
listener
:主に関数
useCapture
:親要素などが同じイベントを持っている時に、キャプチャーフェーズでイベントを発生させたい時は、true
を設定します。そうでなければバブルフェーズで親のイベントが実行されます。デフォルトではfalse
となっています。
実際によくあるケースを例にだしてみます。
モーダルの例
お問い合わせのモーダルはあまり見ないですが、このようにモーダルの中にフォームなど記入するコンテンツがある場合です。
よくある仕様としては閉じるボタンを押すか、背景を押した時にモーダルが閉じるというものです。
実際にコードを書いてみます。※cssは省略します
<!doctype html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>イベントフェーズについて</title> <link rel="stylesheet" href="main.css"> </head> <body> <div class="modal" id="js-modal"> <div class="modal_content"> <form class="form" id="js-form"> <h2 class="form_title">お問い合わせフォーム</h2> <div class="form_content"> <input type="text" name="name" placeholder="名前を記入してください"> </div> <div class="form_content"> <input type="email" name="" placeholder="emailを入力してください"> </div> <div class="button"><button>送信</button></div> </form> </div> </div> <script src="main.js"></script> </body> </html>
const modal = document.getElementById("js-modal"); modal.addEventListener('click', () => { modal.classList.add("is-inactive") };
背景をクリックしてみてください。 モーダルが消えますよね?
次にフォームのコンテンツ部分をクリックしてください。
これもまたモーダルが消えます。
なぜこうなるかは先ほど説明したバブルフェーズが関わってきます。 モーダルのコンテンツの部分のクリックイベントがモーダルの背景の部分まで伝播してしまっているんですよね。
そのイベントの伝播を防いであげればいいですね。
コードの方をちょっと修正します。
const $modal = document.getElementById("js-modal"); const $modalContents = document.getElementById("js-form"); $modal.addEventListener('click', () => { $modal.classList.add("is-inactive"); }); $modalContents.addEventListener('click', (event) => { event.stopPropagation(); });
event.stopPropagation();
で親へのイベントの伝播を防いでいます。
実際にブラウザで検証した結果です。
モーダルのコンテンツの部分をクリックしてもモーダルが消えなくなりました。
まとめ
イベントの伝播の流れを知っておかないと、上のモーダルの件のように原因が突き止めずらくなると思います。イベントはどの場面でも必ず出てくるのでこの機会にちゃんと頭の中に叩き込んでおきます。
JavaScriptのプリミティブ型とオブジェクト型って?
最近写経をはじめました。 そこでの気づきや学びを備忘録としてここに残していきます。 おかしなところがあればご指摘ください。
写経する教材はこちらになります。
今回はJavaScriptのプリミティブ型とオブジェクト型についてです。
プリミティブ型とオブジェクト型
JavaScriptでの値はプリミティブ型かオブジェクト型のふたつになります。 プリミティブという言葉の意味は調べると 『原始的、素朴な、幼稚な』といった意味がでてきます。
具体的にプリミティブを表現するデータ型は6つあります。
- 数値(Number)
- 文字列(String)
- 論理値
- null
- undefined
- シンボル(Symbol)
※シンボルはES2015から新しく導入されました。
プリミティブ型の例
プリミティブ型を判別するためにtypeof演算子を使います。
typeof 123 // 'number' typeof "hoge" // 'string' typeof true // 'boolean' typeof null // 'object' typeof undefined // 'undefined'
null
の型がobjectとなってしまっています。null
はプリミティブ型です。
これは修正すべきだと提案はされているみたいなのですが、依存するコードが多く、いまだに言語仕様として残ってしまっているみたいです。
一方で以下がオブジェクト型の例です。
プリミティブ型は一つの値しか表現できず、不変です。一方でオブジェクトはプロパティやメソッドなどで値を変化させることができます。
オブジェクト型の例
typeof {} // "object'' typeof [] // "object" typeof new Date() // "object" typeof new RegExp() // "object" typeof new Map() // "object"
そこで自分はふと思いました。
プリミティブ型でもプロパティやメソッド使っているよな?と。
例えば文字列についてです。
const str = "あいうえお"; const strNum = str.length; console.log(strNum) // 5
あれ? プリミティブ型ってメソッドもプロパティもっているじゃん! この疑問を解消してくれるのがラッパーオブジェクトです。
プリミティブ型のデータの中で
- 数値
- 文字列
- 真偽値
などはそれぞれ対応するオブジェクトがあります。
// String const str = new String("あいうえお"); console.log(str.length); // 5 // Number const num = new Number(9); num.toString() console.log(num) // "9" // Boolean const bool = new Boolean(true) bool.hasOwnProperty() // false
このように初期化されたものは、プリミティブ型を内包したオブジェクトといえます。 このようなオブジェクトのことをラッパーオブジェクトと言います。
お気付きの方もいるかもしれませんが、上の例ではnew
をしていないのにも関わらずstr.length
といった形でlength
メソッドを使い文字数を出力しています。
結論を言うとラッパーオブジェクトへの変換は自動的にしてくれているようです。 プリミティブ型に対応するラッパーオブジェクトのプロパティやメソッドにアクセスすると一時的にラッパーオブジェクトに変換してくれるようです。
まとめ
普段なんとなく使っているプログラムもこのような仕組みを知ることで言語仕様への理解が少し深まりました。他の言語のRubyにはプリミティブ型はないのか?など比較をする基準ができるため今後も写経を続けていこうと思います。
reduxのcomposeが何をしているかちょっと覗いてみた
最近、業務でreactを使用しています。
その中でredux周りのライブラリを使うことが多いです。そんなreduxと仲良くしたいと思い今回はcompose
がどんなことをしているのか見ていきたいと思います。
そもそもcomposeって?
composeは任意の数の関数を引数に受け取りその関数を右から左の順番に実行していきます。
以下のコードではcomposeを使用してapplyMiddleware
でstoreを拡張する方法と、redux-devtoolsからいくつかの開発ツールを使用する例が挙げられています。
(公式サイトから引用)
import { createStore, applyMiddleware, compose } from 'redux' import thunk from 'redux-thunk' import DevTools from './containers/DevTools' import reducer from '../reducers' const store = createStore( reducer, compose( applyMiddleware(thunk), DevTools.instrument() ) )
実際のcomposeのコード
export default function compose(...funcs) { if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { return funcs[0] } return funcs.reduce((a, b) => (...args) => a(b(...args))) }
中身は10行程でとてもシンプルです。
そこで大事なのが最後の行の箇所です。
return funcs.reduce((a, b) => (...args) => a(b(...args)))
高階関数について
上記のコードがどのようになっているかを理解するためには高階関数を知る必要があります。実際に上記のコードと同じようなものを書いてみます。
const first = num => num + 1; const second = num => num * num; function compose(f1, f2){ return (value) => { return f1(f2(value)) } } const result = compose(first, second); console.log(result(3)); // 10
まず引数の値に1足した値を返す関数aと、引数の値を二乗した値を返す関数bを用意しました。 そしてcompose関数を作成しました。この関数の特徴としては以下が挙げられます。
- 引数に関数を受け取る
- 戻り値として関数を受け取る
returnしている箇所に注目してみます。
return (value) => { return f1(f2(value)) }
f1
関数が実行されるとf2
関数が実行されます。
f1
はf2
の戻り値を引数にもつので、ここでいうとvalueを2乗した結果が入ります。composeの仕組みとしては今説明したものになります。ですが今のままのコードですとcomposeのように任意の数の引数を受け取ることができず、また引数の数だけ関数を実行することができません。そのためコードを公式サイトと同じようにしていきます。
まず引数の数を固定にせず、呼び出すとき任意の数だけ受け取れるようにします。 上記のコードを修正します。
function compose(...funcs){ return (value) => { return f1(f2(value)) } }
こうすることでfuncsには引数に設定した関数の数だけ入った配列が入ってきます。 これで引数に任意の数だけ関数を渡せるようになりました。
次に引数の数だけ関数を実行できるようにします。
function compose(...funcs){ return funcs.reduce((before, after) => (...args) => before(after(...args))) }
ポイントはreduceを使うことでfuncsに入っている関数を順に取り出してきて、その中のコールバック関数で右にある関数から順番に実行されていきます。
const first = num => num + 1; const second = num => num * num; const third = num => num * num * num function compose(...funcs){ return funcs.reduce((before, after) => (...args) => before(after(...args))) } const result = compose(first, second, third)(3); console.log(result);
新たにthird関数を作成してみてcompose関数の引数に三つの関数を渡してみました。 このファイルを実行した結果をみてみます。
720
意図した通りに結果が出力されました。
まとめ
reduxのライブラリを読む上で高階関数やJsのスプレット演算子の使い方などを学ぶことができました。今度はcompose以外のredux周りのライブラリを解読していきたいと思います。