質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.49%
Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

3回答

5140閲覧

関数に渡すコールバック関数の中で、関数式の結果を代入する先のオブジェクトに参照出来るのは何故?

murabito

総合スコア108

Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

0グッド

0クリップ

投稿2018/04/11 07:39

編集2018/04/11 12:44

今日、コードを書いていて、ふと、JavaScriptの仕組みがよくわからなくなりました。(JavaScriptに限った話ではないのかもしれません)

以下はそのコードを質問用に簡略化したコードなのですが、関数式の代入先オブジェクトを関数に渡すコールバック関数の中で参照出来るのは、どういう仕組みからなのでしょうか?(質問1)

function makeNewObj(obj, callback) { // いろいろな処理がこの辺に //非同期コールバック callback(); // 新しいオブジェクトを返します。 return {new: true}; } function asyncCallback() { setTimeout(function() { console.log(newObj); }, 0) } const obj = {}; const newObj = makeNewObj(obj, asyncCallback); // 実行するとnewObjを参照出来ているのは、どういう仕組みなのか? // { new: true }

とりあえず僕的にはこういうことが可能らしいということはわかったのですが、直感的ではないと感じていて、これは良い作法なのかどうかも気になります。(質問2)

もし、これが良い作法ではない場合、このようにコールバックの中で代入先を参照したいような場合に、どのような方法が取れるのかを教えて頂けると嬉しいです。(質問3)

const newObj = makeNewObj(obj, function asyncCallback() { setTimeout(function() { console.log(newObj); }, 0) });

ちなみにこう書くと直感的度は増すものの、これが良い作法なのかどうかは、やはり疑問が僕的に残ります。

※駄文となってしまっため、疑問部分に(質問1、2、3)のように添えましたが、全てにご回答頂かなくてももちろん構いませんm(_ _)m

質問に含めているコードの超簡略化バージョン

function run() { console.log(test); } const test = 3; run();

少し時間を置いていたら、単純にこのレベルまで簡略化できる話なのだと思いました。

miyabi-sunさんのご指摘をコードに反映

質問に掲載しているコードがコールバックになっていないというご指摘を受けましたので、反映致しました。(※ 今回の質問用のコードを用意する上で、ご指摘を受けた部分は質問の趣旨からして不要と判断したたため、質問時に省いておりました)

function makeNewObj(obj, callback) { // いろいろな処理がこの辺に //非同期コールバック callback(null, 'value'); // 新しいオブジェクトを返します。 return {new: true}; } function asyncCallback(err, value) { setTimeout(function() { if (err) throw err; // newObjを参照して行う諸々の処理(書き換えはしない) console.log(newObj); //最終的な実行処理 console.log(value); }, 0) } const obj = {}; const newObj = makeNewObj(obj, asyncCallback);

解決後の報告

1. 質問1に対する疑問

これは僕のJavaScirptのhoistingに対する理解が不足している為でした。kei344さんから頂いた回答やコメントをきっかけに、信頼できそうな情報源を元に、JavaScriptがどのようにコンパイルフェーズと実行フェーズでどのような処理を行っているのかを調べまして、よく理解が出来ました。

2. 質問2と質問3に対する疑問

質問に掲載したコード(miyabi-sunさんの指摘を反映したもの)をthink49さんから頂いた改善案をもとにテコ入れした結果、以下のようになりました。

function makeNewObj(obj, callback) { // いろいろな処理がこの辺にあると仮定。以下は超簡略版。 const newObj = Object.assign({}, obj, {new: true}); //非同期コールバック callback(null, newObj); // 新しいオブジェクトを返します。 return newObj; } function asyncCallback(err, newObj) { setTimeout(function() { if (err) throw err; // newObjを参照して行う諸々の処理(書き換えはしない) console.log(newObj); //最終的な実行処理 console.log('done'); }, 0) } const obj = {id: 1}; const newObj = makeNewObj(obj, asyncCallback);

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

newObj がスコープ内にあるからでは。

【【JavaScript入門】変数のスコープを完全マスター! | 侍エンジニア塾ブログ | プログラミング入門者向け学習情報サイト】
https://www.sejuku.net/blog/23264

投稿2018/04/11 08:02

kei344

総合スコア69398

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

murabito

2018/04/11 08:33

ご回答ありがとうございます。質問に超簡略化バージョンの追記を加えたのですが、やはり、スコープ内にあるという説明が正解でしょうか?
kei344

2018/04/11 08:35

追記されたコードの console.log(newObj); についてであれば同じことです。
murabito

2018/04/11 08:37

``` function run() { console.log(test); } ``` こちらの部分になります。2度追記を行いました。
kei344

2018/04/11 08:38

同じです。
murabito

2018/04/11 08:42

ありがとうございます。
kei344

2018/04/11 08:46

function run( x ) { // 関数は全体のスコープ、x は関数runのローカルスコープ console.log(test); // ローカルスコープを探してtestが無いから全体のスコープのtestを返す var aaa = 2; // 関数runのローカルスコープ console.log(aaa); // 関数runのローカルスコープ、2が返る function bbb() { // 関数は関数runのローカルスコープ // 関数bbbのローカルスコープ } } var aaa = 3; // 全体のスコープ const test = 3; // 全体のスコープ run(); // 全体のスコープ
kei344

2018/04/11 08:48

スコープチェーンで調べてみてください。
murabito

2018/04/11 08:50

ありがとうございます。 function run( x ) { // 関数は全体のスコープ、x は関数runのローカルスコープ ____console.log(test); // ローカルスコープを探してtestが無いから全体のスコープのtestを返す ____var aaa = 2; // 関数runのローカルスコープ ____console.log(aaa); // 関数runのローカルスコープ、2が返る ____function bbb() { // 関数は関数runのローカルスコープ ____// 関数bbbのローカルスコープ ____} } console.log(test); // undefined!!!!!!!!!! var aaa = 3; // 全体のスコープ const test = 3; // 全体のスコープ run(); // 全体のスコープ
murabito

2018/04/11 08:52

関数の中で参照している変数が関数内にない場合は、グローバルスコープ全体(関数文より以下も含め)に同じ名前の変数がないかを探してくれるという訳ですね。一方、上のコメントに僕が貼ったように、console.log(test)をグローバルスコープで、const testが宣言代入される前に使うとundefinedになってしまうと。ややこしいですね。
kei344

2018/04/11 09:00

「巻き上げ」という挙動もあるのでもっと難解ですが、慣れると何とかなりますよ。 【var, let, constの使い分けについて|もっこりJavaScript|ANALOGIC(アナロジック)】 http://analogic.jp/var-let-const/#hoisting
murabito

2018/04/11 09:40

すみません、もう少しベストアンサーを保留にさせて下さいませ。一度つけたのに申し訳ございません。
murabito

2018/04/11 12:48

巻き上げについて調べまして、よく理解が出来ました。すみません。質問2と3への解を下さったthink49さんの方にベストアンサーをつけることになりましたm(_ _)m おつきあい頂いてありがとうございました。
guest

0

ベストアンサー

実行タイミングの問題

検証したかったのは、こういうことですか。

JavaScript

1function makeNewObj1() { 2 setTimeout(function () { 3 console.log(JSON.stringify(newObj1)); 4 }, 0); // 0秒後に console.log() 5 6 var startDate = new Date; 7 8 while (new Date - startDate < 1000); // 1秒待機 9 10 console.log('end1'); // "end1" -> {"new":true} 11 return {new: true}; 12} 13 14const newObj1 = makeNewObj1();

同期処理と非同期処理の順番性に疑問を感じておられるのでは。

  • const の処理を終える前に setTimeout が発動されるはずだ
  • ならば、ReferenceError になるべきではないのか

JavaScriptは原則、シングルスレッドであり、処理の連続性が保証されているので、同期処理が完了するまで非同期処理が待機させられる、という理解で良いと思います。

改善案

上位スコープに共有値を置く手法は、スコープが広い事から、グローバル変数と同じ悪手と考えます。
スコープは可能な限り狭く、が原則です。
基本的には、引数で渡すのがベターです。

JavaScript

1'use strict'; 2function makeNewObj2() { 3 var obj = {}; 4 5 setTimeout(handleTimeout, 0, obj); // 0秒後に console.log() 6 7 var startDate = new Date; 8 9 while (new Date - startDate < 1000); // 1秒待機 10 obj.new = true; 11 12 console.log('end2'); // "end2" -> {"new":true} 13 return obj; 14} 15 16function handleTimeout (obj) { 17 console.log(obj); 18} 19 20function main () { 21 const newObj2 = makeNewObj2(); 22} 23 24main();

これにより、newObj2 のスコープが別の関数に漏れる事はありません。
handleTimeout()obj のみをスコープに持ちます。

Re: handleTimeout さん

投稿2018/04/11 12:08

think49

総合スコア18162

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

murabito

2018/04/11 12:25

ご回答ありがとうございます。検証したかったのは(質問1の部分は)そういうことではなかったのですが、改善案の方は質問2と質問3に対して”まさに”といったものです。質問1の疑問は僕のJavaScriptのhoistingの理解が足りていなかったことが原因でした。 ``` function fn() { ____console.log(foo); } const foo = 3; fn(); ``` なぜ、fnの宣言が最初に来ていて、fooの代入が次にきているのに、最後にfn()と実行すると、fnの中でfooを読めるのかが理解できていなかったのですが、hoistingの仕組みを調べてよく理解できました。
think49

2018/04/11 14:32

const は hoisting せず、遡っての参照時に ReferenceError になる仕様です。 このコードが正常動作するのは、変数参照時に const 宣言が既に完了しているからです。
think49

2018/04/11 14:34

変数が実体化するのは、コードが構文解析された時ではありません。「コードが実行される時」です。
murabito

2018/04/11 21:41

> const は hoisting せず、遡っての参照時に ReferenceError になる仕様です。 ``` function fn1() { ____console.log(foo); ____var foo = 3; } function fn2() { ____console.log(foo); ____const foo = 3; } function fn3() { ____console.log(bar); ____const bar = 3; } //fn1() // undefined //fn2() // ReferenceError: foo is not defined //fn3() // ReferenceError: bar is not defined ``` こういう違いがあるということですよね?
think49

2018/04/12 03:51

fn3() は実行されないはずですが、3回に分けてコードを実行しているのなら、その通りです。 そのコードは分かりやすい例ですが、質問文や親記事のようにな「コードがかかれた順番」と「コードが実行される順番」が異なる順番があり、頭の中でシミュレーションする必要があります。
guest

0

以下はそのコードを質問用に簡略化したコードなのですが、関数式の代入先オブジェクトを関数に渡すコールバック関数の中で参照出来るのは、どういう仕組みからなのでしょうか?(質問1)

コールバック関数適用と戻り値は完全に異なるものです。
非同期処理でコールバック関数を渡したからといって、戻り値を返してはならないというルールは存在しません。

setTimeoutなんかはこの両者の違いを上手に使っている典型例の一つで、
一度コールバック関数で遅延させると同時に、IDが戻り値として返ってきます。
やっぱり実行を辞めたい場合はキャンセル関数に渡して上げる事で実行を取り下げる事が可能です。

JavaScript

1var id = setTimeout(() => console.log('test'), 10000); 2// 異常を感じたのでやっぱりキャンセルだ! 3clearTimeout(id); 4// test <- キャンセルされたので文字は表示されない

とりあえず僕的にはこういうことが可能らしいということはわかったのですが、直感的ではないと感じていて、これは良い作法なのかどうかも気になります。(質問2)

setTimeoutのように意味があれば良い作法ですし、
意味が無ければ辞めたほうが良いでしょう。

UNIX哲学に「一つのことを行い、またそれをうまくやるプログラムを書け。」とあります。
あまり色々な機能をゴテゴテと付け足した関数は汎用性が失われ使い勝手が悪くなります。

これらの事から、setTimeoutのようにキャンセルすることを見越してキー情報を返す。
…くらいの使い方以外はすることないんじゃないですかね?

もし、これが良い作法ではない場合、このようにコールバックの中で代入先を参照したいような場合に、どのような方法が取れるのかを教えて頂けると嬉しいです。(質問3)

???
ああ、なるほど…
やっぱり使い方分かってないじゃないですか。

まずコールバック引数というのは1つ以上の引数を必ず参照する作りにしてください。
中身でエラーが出てもtry~catchで取る事が出来ないので、
非同期処理を司るライブラリなんかは全て第一引数はかならずerrにするというルールがあるからです。

もし後続の処理を行われたらまずいという場合は、
new Error('エラー理由')でエラーを作成して第一引数に設定してください。

そして第二引数以降に、コールバック関数が使いたいと考えている変数を全て引数としてぶち込んで下さい。
これはnewObjをasyncCallbackで使えるようにした例です。

JavaScript

1function makeNewObj(obj, callback) { 2 // いろいろな処理がこの辺に 3 // 非同期コールバックを表現するためにsetTimeoutを利用 4 setTimeout(function() { 5 // コールバックに値を引き継ぎたいので関数適用時の引数として渡す 6 callback(null, obj, {new: true}); 7 }, 0); 8} 9 10// コールバック関数は必ず第一引数をerrにする 11// objとnewObjが使いたさそうでしたので両方要求する関数に 12function asyncCallback (err, obj, newObj) { 13 if (err) console.error(err); 14 // 本命の処理を行う 15 console.log(obj); 16 console.log(newObj); 17} 18 19// objもnewObjもここで宣言しない方が良いので削除 20makeNewObj({}, asyncCallback);

投稿2018/04/11 08:09

編集2018/04/11 08:59
miyabi-sun

総合スコア21158

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

murabito

2018/04/11 08:35 編集

ご回答ありがとうございます。ご指摘の部分は理解しているのですが今回の質問と関係ないと思ったため、質問コードの簡略化をする際に省いておりました。ご指摘部分を質問文に反映致します。
miyabi-sun

2018/04/11 09:02

よくよく検討し回答文を調整しました。 最終的に回答文の下のコードが今回のコードのあるべき姿になるでしょう。 外のスコープで宣言されたものを、中の関数が読むケースはあまりお行儀が良くないですがあります。 ただし、コールバック変数は何処で実行されるか分からないので、 スコープでの名前解決はあまり当てにせず、出来るだけ引数で受け取るようにしてください。
murabito

2018/04/11 09:09

ありがとうございます。「外のスコープで宣言されたものを、中の関数が読むケースはあまりお行儀が良くないですがあります。」の場合はやはり、今回の質問のように、「スコープでの名前解決はあまり当てにせず、出来るだけ引数で受け取るようにしてください。」というのは出来ないのですよね? `const newObj = makeNewObj(obj, asyncCallback);`の部分で、`asyncCallback`の第3引数には`newObj`は渡せませんし、miyabi-sunさんの編集前のコードでは、`makeNewObj`は何も返していなかったので、`newObj`には`undefined`が代入されてしまっていましたし。
miyabi-sun

2018/04/11 09:17

質問文のコードではなく、私の回答文の一番下にあるコードを使って下さい。 最後の実行結果がちゃんと下記のようになることも確認済みです。 {} { new: true }
murabito

2018/04/11 09:35 編集

それはコールバック内でのログ出力ですよね?今回の質問は「外のスコープで宣言されたものを、中の関数が読むケースはあまりお行儀が良くないですがあります。」とmiyabi-sunの言葉にもあるように、外のスコープで宣言されたものを中の関数が読むケースであるため、`var newObj = makeNewObj({}, asyncCallback);`の`newObj`に値が返っていることが必要です。そのnewObjがその行以降で使われることになるので。
miyabi-sun

2018/04/11 09:27

なるほど、既に巻き上げとかそういうジャンルの話になってて、コールバックから完全に話がそれていますね。 keiさんがリンクを張ってくださっているのでお任せします。
murabito

2018/04/11 09:34

わかりづらくて申し訳ございませんでした。最初からコールバックとして成立するコードを質問文に載せておくべきでしたね。(追記にあります通り、そこは重要ではなかったため省略していた次第です。)
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.49%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問