問題一覧
1
連結リストはどのように拡張可能か?
next に加えて prev プロパティも付け加えることで、前の要素を参照するために簡単に戻れるようにすることができます。 リストの最後の要素を参照する tail と呼ばれる変数を追加することもできます(末尾から要素を追加/削除するときに更新します)。 …データ構造はニーズに応じて異なります。
2
レキシカル環境とはなにか?
JavaScript では、実行中のすべて関数、コードブロック {...} およびスクリプト全体には、レキシカル環境 と呼ばれる内部の(隠れた)関連オブジェクトがあります。 つまり、スコープが存在する範囲でレキシカル環境が存在している。 レキシカル環境オブジェクトは2つの部分から構成されます: 環境レコード(Environment Record) 。プロパティとしてすべてのローカル変数をもつオブジェクトです(this の値など、他の情報もいくらか持っています)。 外部のレキシカル環境 への参照。通常、直近の外部のレキシカルなコードに関連付けられています(現在の波括弧の外側)。
3
前問の function delay(f, ms) { // setTimeout の中で、ラッパーから this と 引数を渡すための変数を追加 return function(...args) { // ここのthisは呼び出しの際の実行コンテキストが入る let savedThis = this; setTimeout(function() { f.apply(savedThis, args); }, ms); }; } についてもっと短く書く方法がある。 どうするか?
アロー関数をsetTimeoutの引数に指定する。 アロー関数内部では、this、argumentsもたず、アロー関数外部と同様の値となる。 つまり、return function()のthis,argumentsをそのまま 採用するので、setTimeoutで実行される際に関数実行となってしまうような考慮が不要となる。 function delay(f, ms) { return function() { setTimeout(() => f.apply(this, arguments), ms); }; };
4
連結リストとはどのようなデータ構造か?
まとめ 連結リストとは、参照を使用したチェーン構造によってデータを保持する構造である。 配列のようにインデックスでN個目の要素を取得というようなことはできない。 →先頭から辿る必要あり しかし、先頭への要素追加などは、配列よりもコストが低い arr.shift()などの操作時、配列要素の番号の再割当てを必要としない構造となっている。 本当に速い挿入/削除が必要な場合に利用する この連結リストは以下のような再帰的な構造をもっている。 next要素に次要素のオブジェクトが設定されている。 let list = { value: 1, next: { value: 2, next: { value: 3, next: { value: 4, next: null } } } }; 例えば、新しい値を先頭に追加するには、リストの先頭を更新します。: let list = { value: 1 }; list.next = { value: 2 }; list.next.next = { value: 3 }; list.next.next.next = { value: 4 }; // 新しい値をリストの前に追加する list = { value: "new item", next: list }; 間から値を取り除くには、その前の next を変更します: list.next = list.next.next; 値 2 は今やチェーンからは除外されています。もしもそれがどこにも保持されていない場合、自動的にメモリから削除されます。 配列とは違って、大量の番号の再割り当てはなく、要素を簡単に組み替えることができます。 主な欠点は、番号では簡単に要素にアクセスできないことです。 配列では簡単です( arr[n] で直接参照します)が、リストではアイテムの最初から始めてN個目の要素を取得するために、N 回 next を行う必要があります でも、常にこのような操作が必要とは限りません。例えば、キュー(queue)や デック(deque) が必要なときです。これらは両端から要素を非常に高速に追加/削除できる順序付けられた構造です。
5
関数に対して、明示的にthisを指定することができる。組み込みの関数メソッドはなにか? 二つ挙げよ
1 call関数を利用する。 以下構文 func.call(context, arg1, arg2, ...) 例 function say(phrase) { alert(this.name + ': ' + phrase); } let user = { name: "John" }; // user は this になり, "Hello" は最初の引数になります say.call( user, "Hello" ); // John: Hello 2 apply関数を利用する 以下構文 func.apply(context, args)
6
Jsonは多重構造のオブジェクトも変換できるが、変換できない構造はなにか?
重要な制限: 循環参照があってはいけません。 例: let room = { number: 23 }; let meetup = { title: "Conference", participants: ["john", "ann"] }; meetup.place = room; // meetup は room を参照 room.occupiedBy = meetup; // room は meetup を参照 JSON.stringify(meetup); // Error: Converting circular structure to JSON ここでは、循環参照(room.occupiedBy が meetup を参照し、meetup.place が room を参照している)のため変換が失敗します。
7
グローバルオブジェクトとはなにか?
グローバルオブジェクトはどこでも利用可能な変数と関数を提供します。デフォルトで言語や環境に組み込まれています。 ブラウザでは window、Node.js では global、他の環境では別の名前になっているかもしれません。 最近では、すべての環境でサポートされるべきグローバルオブジェクトの標準的な名前として globalThis が言語に追加されました。すべての主要なブラウザでサポートされています。
8
関数呼び出しが終わったら、その関数のレキシカル環境はどうなるか?
通常、関数呼び出しが終わった後、すべての変数のとともに、レキシカル環境はメモリから削除されます。 これはそこへの参照が存在しないためです。他のJavaScriptオブジェクトと同様に、到達可能な間だけメモリに保持されます。 しかし、以下のクロージャのような関数を返す場合は、到達可能性があるため消されない。 function f() { let value = 123; return function() { alert(value); } } let g = f(); // g.[[Environment]] は、対応する f() 呼び出しのレキシカル環境 // への参照を保持します
9
クロージャとはなにか?
クロージャ(closure) は外部変数を記憶し、それらにアクセスできる関数です。 つまり: それらは隠された [[Environment]]プロパティを使ってどこに作成されたのかを自動的に覚えていて、すべてが外部変数にアクセスできます。 JavaScriptにおいてはすべての関数がクロージャです。
10
下のコードで、スケジュールされた setTimeout 呼び出しがあります。その後、完了までに 100ms 以上かかる重い計算が実行されます。 スケジュールされた関数はいつ実行されるでしょう? ループの後 ループの前 ループの最初 alert は何を表示するでしょう? let i = 0; setTimeout(() => alert(i), 100); // ? // この関数を実行する時間は 100ms より多いと仮定する for(let j = 0; j < 100000000; j++) { i++; }
どの setTimeout も現在のコードが完了した後にだけ実行されます。 i は最後のもの 100000000 になるでしょう。 つまりループのあと
11
sum(a)(b) = a+b のように動作する関数 sum を書いてください。 正確にこの通り2つの括弧で指定します(タイプミスではありません)。 例: sum(1)(2) = 3 sum(5)(-1) = 4
2つ目の括弧が動作するために、1つ目は関数を返さなければなりません。 このようになります: function sum(a) { return function(b) { return a + b; // 外部のレキシカル環境から "a" を取る }; } alert( sum(1)(2) ); // 3 alert( sum(5)(-1) ); // 4
12
Jsonがサポートしない型はどんなものか?
関数プロパティ(メソッド) シンボルキーと値 undefined を格納しているプロパティ JSONはデータのみのマルチ言語仕様(他の言語でも使える)なので、JavaScript固有のオブジェクトプロパティの一部は JSON.stringify ではスキップされます。 letuser={ sayHi() { // 無視される alert("Hello"); }, [Symbol("id")]: 123, // 無視される something: undefined // 無視される }; alert( JSON.stringify(user) ); // {} (空オブジェクト)
13
以下のコードの問題点を改善せよ // worker.slow のキャッシングを作成する let worker = { someMethod() { return 1; }, slow(x) { // 実際には、CPUの重いタスクがここにあるとします alert("Called with " + x); return x * this.someMethod(); // (*) } }; // 前と同じコード function cachingDecorator(func) { let cache = new Map(); return function(x) { if (cache.has(x)) { return cache.get(x); } let result = func(x); // (**) cache.set(x, result); return result; }; } alert( worker.slow(1) ); // オリジナルメソッドは動きます worker.slow = cachingDecorator(worker.slow); // キャッシングする alert( worker.slow(2) ); // Whoops! Error: Cannot read property 'someMethod' of undefined 行 (*) で、this.someMethod にアクセスしようとして失敗してエラーが起きます。 これを改善するにはどうすればよいか
まず以下の通りデコレーションが行われ、 worker.slow = cachingDecorator(worker.slow); // キャッシングする 以下のように実行するとエラーがおきる。 alert( worker.slow(2) ) 上記ではオブジェクト実行をしているが、 デコレーションされたメソッドは関数実行している。 以下のコードにあるfunc(x)によって関数実行されているため、ここをfunc.callに書き換える。 第一引数は、前述した通り、オブジェクトを実行コンテキストとして実行はされているので、thisでいい。 つまり、コメント部記載のようになる! // 前と同じコード function cachingDecorator(func) { let cache = new Map(); return function(x) { if (cache.has(x)) { return cache.get(x); } let result = func(x); // func.call(this,x); cache.set(x, result); return result; }; }
14
レキシカル環境が作成されるタイミングは?
関数実行時 画像の例でいうと、 counter() が呼ばれたとき、その呼び出しに対する新たなレキシカル環境が作成され、その外部レキシカル環境の参照は 関数生成時につくられる counter.[[Environment]] から取得されます: 変数は、その変数が存在するレキシカル環境で更新されます
15
関数オブジェクトの名前を出力するには?
関数オブジェクトのnameプロパティが利用できる。 let sayHi = function() { alert("Hi"); } alert(sayHi.name); // sayHi しかし、正しい名前を把握する方法がない場合があります。そのようなとき、name プロパティは次ように空になります。: // 配列の中で作られた関数 let arr = [function() {}]; alert( arr[0].name ); // <empty string> // エンジンには正しい名前を設定する術がないので名前はありません
16
関数実行時に関するレキシカル環境の振る舞いを説明せよ
関数が実行されると、呼び出しの先頭では新しいレキシカル環境が自動的に作られます。 また、ローカル変数と呼び出しパラメータを格納します。 さらに外部のレキシカル環境への参照をもちます。
17
前問の通り、 以下のようなthisが失われてしまう状況に対する改善策のベストプラクティスを示せ また、遅延実行時、userへ値の再代入がおこなわれてしまっても大丈夫となる根拠をしめせ let user = { firstName: "John", sayHi() { alert(`Hello, ${this.firstName}!`); } }; setTimeout(user.sayHi, 1000); // Hello, undefined!
bindメソッドを使用する。 func.bind(context) は特別な関数ライクな オブジェクトを作成し、かえってくる。これは関数として呼ぶことができる。 また、func に context を渡すことでthisを固定化することができる。 やってることはapplyやcallと似てるが、 bindはthisを固定化した関数が返ってくるので、オブジェクトメソッドが相手だと分かりきってる場合は、使いやすい。 また遅延実行前にuserオブジェクトが書き換えられても、すでにbind済みであれば書き換え前のuserが対象となる let user = { firstName: "John", sayHi() { alert(`Hello, ${this.firstName}!`); } }; let sayHi = user.sayHi.bind(user); // (*) // オブジェクトなしで実行可能 sayHi(); // Hello, John! setTimeout(sayHi, 1000); // Hello, John! // 1秒以内に user の値が変わったとしても // sayHi は古い user オブジェクトを参照しているバインド前の値を使用します user = { sayHi() { alert("Another user in setTimeout!"); } };
18
関数オブジェクトを動的に作成する方法は?
"new Function" 構文を利用する 文字列から関数オブジェクトが作れる let func = new Function ([arg1[, arg2[, ...argN]],] functionBody) let sum = new Function('a', 'b', 'return a + b'); alert( sum(1, 2) ); // 3 歴史的な理由から、引数はカンマ区切りのリストで与えられます。 これらの3つの意味は同じです: new Function('a', 'b', ' return a + b; '); // 基本構文 new Function('a,b', ' return a + b; '); // カンマ区切り new Function('a , b', ' return a + b; '); // スペースありのカンマ区切り
19
カスタムプロパティとはなにか?
オブジェクトに対して独自のプロパティを追加できる機能 関数で、返却対象の関数に対してカスタムプロパティを外側から付与し、内部で参照できるので、クロージャみたいな使い方ができる。 function sayHi() { alert("Hi"); // 何度実行したかカウントしましょう sayHi.counter++; } sayHi.counter = 0; // 初期値 sayHi(); // Hi sayHi(); // Hi alert( `Called ${sayHi.counter} times` ); // 2度呼ばれました
20
JavaScriptエンジンによって、デバックが意図しない動作となる例をあげよ
クロージャなどで使用する外部変数が、使用されてないようであれば、自動的にガベージコレクションの対象となってしまう。 なので以下のようなクロージャで デバックタイミングで Valueを参照しても、すでに消されてる可能性がある。 使用されていれば問題ない。 function f() { let value = Math.random(); function g() { debugger; // in console: type alert( value ); No such variable! } return g; } let g = f(); g(); V8 のこの機能は知っておくと良いです。もしも Chrome/Edge/Opera でデバッグしている場合、遅かれ早かれこれに遭遇するでしょう。 これはデバッガのバグではなく、V8の特別な機能です
21
名前を利用しないケース ではどのような潜在的な問題があるか? let sayHi = function(who) { if (who) { alert(`Hello, ${who}`); } else { //外部のレキシカル環境から実行可能 sayHi("Guest"); } };
sayHiに対して再代入が行われた時や、 関数に外側から名前がつけられなかった場合に、 エラーとなってしまう。 let sayHi = function(who) { if (who) { alert(`Hello, ${who}`); } else { sayHi("Guest"); // Error: sayHi is not a function } }; let welcome = sayHi; sayHi = null; welcome(); // Error, 入れ子の sayHi 呼び出しはこれ以上動作しません!
22
再帰はループの代わりに利用される用途の 他にどのような用途で利用されるか?
再帰のもう1つの優れた用途は、再帰的な探索です。
23
スプレッド演算子の対象はなにか?
イテラブルである必要がある。 let str = "Hello"; alert( [...str] ); // H,e,l,l,o スプレッド演算子は内部的にはイテレータを使用して要素を集めます。これは for..of と同じ方法です。 従って、文字列の場合、 for..of は文字を返すので ...str は "H","e","l","l","o" になります。文字のリストは配列初期化子 [...str] に渡されます。
24
再帰処理をする際の考慮事項は?
1 最大の再帰の深さは JavaScript エンジンによって制限されています。 10,000 は確実で、エンジンによってはより多くの値が可能ですが、 100,000 は恐らく大多数の制限を超えます。 これを緩和する自動最適もあります(“末尾再帰”)が、どこでもサポートされているわけではなく、単純なケースでのみ機能します。 2、 メモリ要件に注意してください。コンテキストはメモリを必要とします
25
関数宣言で、名前付き関数式のように自関数を呼び出したい場合は?
関数宣言なら宣言した時の名前のままでよい。
26
スクリプト実行中 let phrase; が実行されたとき、内部ではどうなるか?
今は初期値がないので、 undefined が格納されます。 この時点からこの変数を使用することができます。
27
変数宣言と関数宣言の違いは?
関数も変数のように値です。 違いは、関数宣言は即座に完全に初期化されることです つまり、レキシカル環境が作成されたタイミングで、変数の場合はletなど宣言しないと初期化されず使えない状態だが、関数はすぐ使えるようになる。 そのため、宣言自体の前でも関数宣言として宣言された関数を呼び出すことができます。 当然、この動作は関数宣言にのみ適用され、let say = function(name)... のように変数に関数を割り当てる関数式にはあてはまりません。
28
arguments変数の制限事項を説明せよ
デメリットは arguments は配列ではなく、配列ライクで反復可能という点です。 従って、配列メソッドをサポートしないので、 arguments.map(...) 呼び出しをすることはできません。
29
このコードの結果はどうなるでしょう? let x = 1; function func() { console.log(x); // ? let x = 2; } func();
ReferenceError: Cannot access 'x' before initialization エラーになる!! 変数は実行がコードブロック(または関数)に入った時点から、“初期化されていない” 状態 で始まります。 そして対応する let 文まで初期化されません。 コードブロックにある時点で外部の同名の変数は参照されない! 変数が一時的に利用できないゾーン(コードブロックの先頭から let まで)は “デッドゾーン” と呼ばれることがあります。
30
callとapply関数の違いは?
call と apply の構文の唯一の違いは、call は引数のリストを期待し、apply はそれらの配列ライクなオブジェクトを期待している点です。 そのため、これら2つの呼び出しはほぼ同等です: func.call(context, ...args); func.apply(context, args); callは…スプレッド演算子を利用することで つまりイテラブルな配列を引数とすることができ、applyは配列ライクならそのままいける イテラブルで配列ライクなら、applyの方が高速らしい let wrapper = function() { return func.apply(this, arguments); };
31
遅延ゼロのスケジュールに関するブラウザの考慮事項は?
遅延ゼロは実際にはゼロではありません(ブラウザにおいて) ブラウザでは、ネストされたタイマーを実行できる頻度に制限があります。 HTML5 標準 では次のように書かれています: “5つのネストされたタイマーの後には…間隔は少なくとも4ミリ秒に強制されます。” つまり、5回タイマー使うまでは遅延ゼロで即実行されるけど、6回以上になると、遅延ゼロが4ミリになってしまう let start = Date.now(); let times = []; setTimeout(function run() { times.push(Date.now() - start); // 前の呼び出しからの遅延を覚える if (start + 100 < Date.now()) alert(times); // 100ms 後に遅延を表示 else setTimeout(run, 0); // もしくは再スケジュール }, 0); // 出力例: // 1,1,1,1,9,15,20,24,30,35,40,45,50,55,59,64,70,75,80,85,90,95,100 最初のタイマーはすぐに実行され(仕様に書いてある通り)、次に遅延が発生し、9, 15, 20, 24... となっています。呼び出し間で4ミリ秒以上の必須の遅延が発生します。 同様のことが、setTimeout の代わりに setInterval を使用する場合にも起きます: setInterval(f) は数回ゼロ遅延で f を実行し、その後 4ミリ秒以上の遅延で実行します。 従って、この概念はブラウザ固有のものです。
32
スクリプト開始時、コーディングで記載した変数はどのような状態か?
レキシカル環境の環境レコードのプロパティとして、用意だけされる。 ↑初期化もされてない状態で、letで参照されるまでは参照できない。
33
JSON.stringify 呼び出し時など、Jsonへの変換が期待されるタイミングで呼び出されるメソッドはなにか
文字列変換用の toString のように、オブジェクトはJSONへの変換用メソッド toJSON を提供しています。JSON.stringify は利用可能であればそれを自動で呼び出します。 組み込みオブジェクトなども toJsonを用いて変換などをしている。 例 "date":"2017-01-01T00:00:00.000Z" let room = { number: 23 }; ↓これが "room": {"number":23} let room = { number: 23, toJSON() { return this.number; } }; ↓toJsonメソッドによりこうなる "room": 23 つまり、オブジェクトを数値そのものとして振る舞うことができる。
34
プロパティと変数の違いを説明せよ
プロパティと変数は全く別物 sayHi.counter = 0 のような関数に割り当てられたプロパティは、関数の中でローカル変数 counter として定義 されません 私たちは、関数をオブジェクトとして扱うことができ、その中にプロパティを格納することが出来ます。しかしそれはその実行には影響を与えません
35
以下のコードの結果を解説せよ function makeCounter() { let count = 0; return function() { return count++; }; } let counter = makeCounter(); let counter2 = makeCounter(); alert( counter() ); // 0 alert( counter() ); // 1 alert( counter2() ); // ? alert( counter2() ); // ?
0,1が出力される。 関数実行時にレキシカル環境が生成されるため。 また、 返却される関数オブジェクトは別となる
36
全ての関数オブジェクトはどのような隠しプロパティをもっているか? また、どのタイミングでその隠しプロパティは生成されるか?
すべての関数オブジェクトは [[Environment]] という名前の隠しプロパティをもち、その関数が作成されたレキシカル環境への参照を保持します。 なので、counter.[[Environment]] には {count: 0} レキシカル環境への参照があります。 このようにして、関数はどこで呼ばれても、どこで作成されたかを覚えているのです。 [[Environment]] への参照は,関数オブジェクトの生成時に一度だけ,そして永遠に設定されます.
37
カスタムプロパティの主な利用用途は?
Jqueryの$関数のようにライブラリでよく利用されている。 多分こんな感じで$関数に対してプロパティを設定してそう $関数を返す関数でプロパティとかを設定して返している。 →外部からもアクセス可能 function makeCounter() { let count = 0; function counter() { return count++; } counter.set = value => count = value; counter.decrease = () => count--; return counter; } 関数を返す場合に、プロパティを付与しておきたい時に使えばいいと思う
38
let objCopy = Object.assign({}, obj) 、 あるいは let arrCopy = Object.assign([], arr) を短く記載するにはどうするか?
let arrCopy = [...arr]; // 配列をパラメータのリストに展開します // その後、結果を新しい配列に格納します let objCopy = { ...obj }; // オブジェクトをパラメータのリストに展開します // その後、結果を新しいオブジェクトに結果を返します
39
前問題で言及したようなデコレーターの方法だと、ある問題点がある。なにか?
オブジェクトのメソッドをラッピングすると、ラッピング関数でそのメソッドを引数として受け取って、呼び出してしまうため、thisの参照先である実行コンテキストがオブジェクトではなく、undefinedになってしまう。 // worker.slow のキャッシングを作成する let worker = { someMethod() { return 1; }, slow(x) { // 実際には、CPUの重いタスクがここにあるとします alert("Called with " + x); return x * this.someMethod(); // (*) } }; // 前と同じコード function cachingDecorator(func) { let cache = new Map(); return function(x) { if (cache.has(x)) { return cache.get(x); } let result = func(x); // (**) cache.set(x, result); return result; }; } alert( worker.slow(1) ); // オリジナルメソッドは動きます worker.slow = cachingDecorator(worker.slow); // キャッシングする alert( worker.slow(2) ); // Whoops! Error: Cannot read property 'someMethod' of undefined 行 (*) で、this.someMethod にアクセスしようとして失敗してエラーが起きます。なぜかわかりますか? 理由は、ラッパーは行 (**) でオリジナル関数を func(x) として呼び出すためです。また、このように呼び出した場合、関数は this = undefined となります。
40
関数でカスタムプロパティを利用するのと、クロージャを利用するのとどっちがよいか
関数でカスタムプロパティを利用すると、プロパティなので、外部からアクセスや変更可能である。 対してクロージャの場合は、内側からしかアクセスできない。 なので、どちらを使うかは利用目的次第といえる function makeCounter() { // 次の代わり: // let count = 0 function counter() { return counter.count++; }; counter.count = 0; return counter; } let counter = makeCounter(); alert( counter() ); // 0 alert( counter() ); // 1 //プロパティの場合はここからでも確認できる alert(counter.count); // 1
41
関数で、全ての(残りの)引数をまとめて受け取るにはどのように記述するか?
...配列名で残りをまとめて受け取れる。 function sumAll(...args) { // args は配列の名前です let sum = 0; for (let arg of args) sum += arg; return sum; } alert( sumAll(1) ); // 1 alert( sumAll(1, 2) ); // 3 alert( sumAll(1, 2, 3) ); // 6 ちなみに以下のように、最後以外だとエラーとなる。 残りのパラメータは最後である必要があります 残りのパラメータはすべての残っている引数をまとめるため、下記は意味をなさず、エラーとなります。: function f(arg1, ...rest, arg2) { // arg2 after ...rest ?! // error } ...rest は常に最後です。
42
これはコンストラクタ関数の助けを借りて作られたカウンタオブジェクトです。 これは動作するでしょうか?何が表示されるでしょう? function Counter() { let count = 0; this.up = function() { return ++count; }; this.down = function() { return --count; }; } let counter = new Counter(); alert( counter.count ); // ? alert( counter.up() ); // ? alert( counter.up() ); // ? alert( counter.down() ); // ?
コンストラクタ関数はthisを作り thisにプロパティを設定して返す機能を持っている。 なので、counter.countは参照できない。 count変数はインスタンスのプロパティではなく、関数内のプロパティである。 コンストラクタ関数実行時にレキシカル環境が作成され、そこにcount変数が設定される。 これらは外部変数として、内部関数から参照可能となる。 なので答えは、1,2,1である。
43
JSON.stringify の第二引数に配列でなく関数を渡すとどうなるか?
関数はすべての (key,value) ペアで呼ばれ、“置換された” 値を返す必要があります。 JSONに出力するには、 “そのままの” value を返せばOKです。逆に出力しないためには、undefied を返します。 以下の例では、 とある項目の場合はundefinedを返すようにしています。、 let room = { number: 23 }; let meetup = { title: "Conference", participants: [{name: "John"}, {name: "Alice"}], place: room // meetup は room を参照 }; room.occupiedBy = meetup; // room は meetup を参照 alert( JSON.stringify(meetup, function replacer(key, value) { alert(`${key}: ${value}`); return (key == 'occupiedBy') ? undefined : value; })); /* key:value pairs that come to replacer: : [object Object] title: Conference participants: [object Object],[object Object] 0: [object Object] name: John 1: [object Object] name: Alice place: [object Object] number: 23 occupiedBy: [object Object] */ 最初の呼び出しだけ特別です。これは特別な “ラッパーオブジェクト” ({"": meetup}) を使って作られます。 言い換えると、最初の (key,value) ペアは空のキーを持ち、値はターゲットのオブジェクト全体です。そういう訳で、上の例の最初の行は ":[object Object]" となっています。
44
前問のシンプルな解決策の問題点は?
遅延を与えた後、user.sayHiメソッドを呼び出すので、その間にuserに対して値の再代入が行われると意図した動きにならない。 →脆弱性となりそう let user = { firstName: "John", sayHi() { alert(`Hello, ${this.firstName}!`); } }; setTimeout(() => user.sayHi(), 1000); // ...1秒以内に次が行われると user = { sayHi() { alert("Another user in setTimeout!"); } }; // Another user in setTimeout?!?
45
[...str] とArray.fromの違いは? let str = "Hello"; // Array.from は iterable を配列に変換します alert( Array.from(str) ); // H,e,l,l,o
結果は [...str] と同じです。 しかし Array.from(obj) と [...obj] には微妙な違いがあります: Array.fromは配列ライクと iterables の両方で動作します。 スプレッド演算子は iterables でのみ動作します。 従って、何かを配列に変換するタスクにおいては、Array.from がより適しています。
46
lengthプロパティのつかいみちとして どのようなものがあるか?
関数をパラメータとして受け取る際、 その関数の引数の数(length)によって、べつの動作を定義できる。 以下の例ではhandlersをうけとって、 関数オブジェクトのlengthによって処理分岐を行っている。 function ask(question, ...handlers) { let isYes = confirm(question); for(let handler of handlers) { if (handler.length == 0) { if (isYes) handler(); } else { handler(isYes); } } } // 肯定的な解答では、両方のハンドラが呼ばれます // 否定的な解答では、2つ目だけが呼ばれます ask("Question?", () => alert('You said yes'), result => alert(result));
47
以下のコードは、関数に対して定義されたパラメータ以上に渡しているが、動作するか? function sum(a, b) { return a + b; } alert( sum(1, 2, 3, 4, 5) );
使われるのは 最初の二つだけだが、動作する
48
スコープについて、 変数がコードブロックの中で、宣言された場合は、どのようなスコープとなるか
そのコードブロックの中までのスコープ。 { // メッセージを表示 let message = "Hello"; alert(message); } { // 別のメッセージを表示 let message = "Goodbye"; alert(message); }
49
組み込み関数がその実行環境でサポートするかテストしたい場合はどうするか?
例としてPromiseがある。 グローバルオブジェクトのもちものとして存在チェックができる。 if (!window.Promise) { window.Promise = ... // 新しい言語機能のカスタム実装 } ↑上記のようにしてポリフィルが実装できる。
50
アロー関数内でarguments変数を利用するとどうなるか? function f() { let showArg = () => alert(arguments[0]); showArg(); }
アロー関数は関数内コンテキストを保持しないため、外部のコンテキストのarguments変数を参照する。 なので、f関数のコンテキストとなる f(1); // 1
51
デコレーターとはなにか?
関数をパラメーターとし、振る舞いを与える関数のこと。 デコレーター関数に対して、引数の関数を与えると、その関数が別の関数でラッピングされるようなイメージ
52
スプレッド演算子とはなにか?
配列を展開して渡すことができる オブジェクトもいける 通常の値とスプレッド演算子を組み合わせることもできます。: let arr1 = [1, -2, 3, 4]; let arr2 = [8, 3, -8, 1]; alert( Math.max(1, ...arr1, 2, ...arr2, 25) ); // 25
53
Dateオブジェクトで、setMonth(2)を指定すると何月となるか?
3月になる 月はゼロからカウントされます(なので、1月は ゼロです)。
54
以下のコードについて、salaryの値を、会社や部門や社員ごとに足し合わせる再帰的なメソッドをあげよ let company = { sales: [{ name: 'John', salary: 1000 }, { name: 'Alice', salary: 600 }], development: { sites: [{ name: 'Peter', salary: 2000 }, { name: 'Alex', salary: 1800 }], internals: [{ name: 'Jack', salary: 1300 }] } };
パラメータのオブジェクトから探索していく。 パラメータのオブジェクトが以下であるか確認する。 配列である場合は、要素のオブジェクトに対してreduce関数を通してsalaryを集計する。集計した値をかえす。 オブジェクトである場合はオブジェクト内のオブジェクトをパラメーターとして自メソッドを呼び出し、足し合わせていく。 function sumSalaries(department) { if (Array.isArray(department)) { // case (1) return department.reduce((prev, current) => prev + current.salary, 0); // 配列の合計 } else { // case (2) let sum = 0; for (let subdep of Object.values(department)) { sum += sumSalaries(subdep); // サブ部署への再帰呼び出し、結果を合計する } return sum; } } alert(sumSalaries(company)); // 6700
55
通常のループ処理について、再帰とループどっち使うべき?
どんな再帰もループで書き直すことができます。通常、ループのバリアントは、より効率的にすることができます。 しかし、関数が条件によって異なる再帰サブコールを使用してその結果をマージするときや、分岐がより複雑な場合には書き直しは簡単ではないことがあります。 また、最適化は不要であり、努力に値しないものである可能性があります。 再帰はより短いコードで書くことを可能とし、理解や保守をし易くします。 最適化はあらゆる場所で必要とされるわけではなく、大半は良いコードが必要です。そのために再帰は使用されています。 理解や保守性でかんがえる!
56
new Functionで作られた関数オブジェクトの考慮事項を説明せよ
通常、関数は特別なプロパティ [[Environment]] でどこで生成されたかを覚えています。それは作成された場所からレキシカル環境を参照します new Function を使用して作られた関数の場合、その [[Environment]] は現在のレキシカル環境ではなく、グローバルのレキシカル環境を参照します。 そのため、このような関数は外部変数へのアクセスは持たず、グローバル変数のみとなります。 function getFunc() { let value = "test"; let func = new Function('alert(value)'); return func; } getFunc()(); // error: value は未定義
57
Json.parseの制限事項は?
JSONはコメントをサポートしていません。JSONへコメントを追加すると無効になります。
58
指定時間経過後、一度だけ関数を実行するにはどうするか?
let timerId = setTimeout(func|code, delay[, arg1, arg2...]) パラメータ: func|code 関数もしくは実行するコードの文字列。 通常は関数です。歴史的な理由で、コードの文字列も渡すことができますが、推奨されません。 delay 実行前の遅延時間で、ミリ秒単位です (1000 ms = 1 秒). arg1, arg2… 関数の引数です(IE9-ではサポートされていません) function sayHi(phrase, who) { alert( phrase + ', ' + who ); } setTimeout(sayHi, 1000, "Hello", "John"); // Hello, John もし最初の引数が文字列の場合、JavaScript はそれから関数を作ります。 従って、これも動作します: setTimeout("alert('Hello')", 1000); しかし、文字列を使うことは推奨されていません。次のように、それらの代わりに関数を使ってください。: setTimeout(() => alert('Hello'), 1000);
59
現在では使われない 変数のvar宣言について説明せよ
1 var で宣言された変数は、関数スコープかグローバルスコープしか適用されない。 つまり、var はコードブロックを無視する! if (true) { var test = true; // "let" の代わりに "var" を使う } alert(test); // true, if の後も変数は生きています 2 再宣言も可能である var user = "Pete"; var user = "John"; // "var" は何もしません (宣言済み) // ...エラーは発生しません alert(user); // John 3 var 宣言は、関数の開始時(またはグローバルのスクリプト開始時)に処理されます。 言い換えると、var 変数は関数の最初で定義され、定義される場所は関係ありません(定義がネストされた関数ではないと言う仮定で)。 なので、以下のコードでも問題なく動く function sayHi() { phrase = "Hello"; alert(phrase); var phrase; } sayHi();
60
定期的に何かを実行するのに setIntervalを利用しない方法でなにがあるか? またその利点は?
setTimeoutをチェーン実行する。 名前付き関数式を利用することにも注目 利点は以下の通り 1 再帰的な setTimeout は setInterval よりも柔軟です。この方法は、現在の呼び出しの結果に応じて、次の呼び出しのスケジュールが異なる場合があります。 例えば、5秒毎にデータを確認するためにサーバへリクエストを送るサービスを書く必要があるとします。しかし、サーバが高負荷である場合には、間隔を 10, 20, 40 秒… と言ったように増やす必用があります。 これは、その疑似コードです: let delay = 5000; let timerId = setTimeout(function request() { ...send request... if (request failed due to server overload) { // 次の実行のためにインターバルを増加させる delay *= 2; } timerId = setTimeout(request, delay); }, delay); 2 setIntervalの場合は、内部の処理も含めてdelay時間をカウント ネストされたsetTimeoutの場合は処理の最後でチェーン化すれば、処理のおわりからのdelay時間のカウントにできる
61
グローバルオブジェクトのプロパティとして暗黙的に宣言するにはどうすればよいか?
ブラウザでは、var(let/constではありません!)で宣言されたグローバル関数や変数は、グローバルオブジェクトのプロパティになります: var gVar = 5; alert(window.gVar); // 5 (became a property of the global object) 関数宣言(関数式ではなく、メインコードフローの中で function キーワードをもつ文)も同じ効果があります。 最近のスクリプトは JavaScript モジュール を利用します。するとこのようなことは起こりません。
62
setInterval/setTimeoutに渡された関数に関するガベージコレクションの考慮事項を説明せよ
関数が setInterval/setTimeout に渡されたとき、内部参照がそこに作られスケジューラに保存されます。 この場合、たとえその関数への参照が他にない場合でも、関数はガベージコレクションの対象にはなりません。 // 関数はスケジューラが呼び出すまでメモリ内に留まります setTimeout(function() {...}, 100); setInterval では cancelInterval が呼ばれるまで、関数はメモリ上に存在し続けます。 そこには副作用があります。 関数は外部のレキシカル環境を参照するので、それが生きている間は外部の変数も生き続けます。 それらは関数自身よりもはるかに多くのメモリを必要とする場合があります。 従って、スケジュールされた機能がもう必要ないときは、たとえそれが非常に小さいとしても、それをキャンセルする方がいいです。
63
スプレッド演算子を利用すると配列に対してどのような操作ができるか?
配列をマージできる。 let arr = [3, 5, 1]; let arr2 = [8, 9, 15]; let merged = [0, ...arr, 2, ...arr2]; alert(merged); // 0,3,5,1,2,8,9,15 (0, then arr, then 2, then arr2)
64
キャッシュする機能をデコレーターを利用して実装せよ
デコレーター関数はクロージャを利用し引数に任意の関数をとる 返却値はキャッシュ機能を与える関数であり、引数の任意の関数をラップする つまり、ラップ関数に任意の関数を与えると、ラップされた関数が戻ってくるのでそれを利用する function slow(x) { // CPUを大量に消費するジョブがここにある可能性があります alert(`Called with ${x}`); return x; } function cachingDecorator(func) { let cache = new Map(); return function(x) { if (cache.has(x)) { // 結果が map にあれば return cache.get(x); // それを返します } let result = func(x); // なければ func を呼び cache.set(x, result); // 結果をキャッシュ(覚える)します return result; }; } slow = cachingDecorator(slow); alert( slow(1) ); // slow(1) はキャッシュされました alert( "Again: " + slow(1) ); // 同じ alert( slow(2) ); // slow(2) はキャッシュされました alert( "Again: " + slow(2) ); // 前の行と同じ
65
グローバルオブジェクトのプロパティとして明示的に宣言するにはどうするか?
その値が重要で、グローバルで見えるようにしたい場合にはプロパティとして直接記述します: // すべてのスクリプトがアクセスできるよう、現在のユーザ情報をグローバルに作成 window.currentUser = { name: "John" }; // コードのどこかで alert(currentUser.name); // John // あるいは、ローカル変数に currentUser がある場合には、 // 明示的に window から取得することも可能です alert(window.currentUser.name); // John とはいえ、グローバル変数は一般的には推奨されません。できるだけ少なくするべきです。関数が “入力” 変数を得て、明確な “結果” を出力するというコードデザインは、外部やグローバル変数を使用する場合よりも明確でエラーも少なく、テストもしやすいです。
66
関数がネスト呼び出しを受けた時の実行コンテキストの動作はどうなる?
再帰呼び出しでは、関数に紐づく実行コンテキストが、実行コンテキストスタックという構造で管理される。 スタックされおわり、最後の関数が処理を終えると、その実行コンテキストは不要となり、メモリから削除される。 そして。スタックから前の実行コンテキストを取り戻し、状況が復元され処理を続ける こんな感じでスタックする pow(2,3)の場合 Context: { x: 2, n: 1, at line 1 } pow(2, 1) Context: { x: 2, n: 2, at line 5 } pow(2, 2) Context: { x: 2, n: 3, at line 5 } pow(2, 3)
67
名前付き関数式とはなにか?
名前がついてる関数式で、 関数の内側から自関数を参照することができるようになり、 関数の外側からはみえないというもの。 ※関数宣言ではできないので注意! ↓これは名前なし let sayHi = function(who) { alert(`Hello, ${who}`); }; ↓これは名前あり let sayHi = function func(who) { if (who) { alert(`Hello, ${who}`); } else { func("Guest"); // 自身を再度呼ぶために func を使用 } }; sayHi(); // Hello, Guest // しかしこれは動作しません: func(); // Error, func は未定義(関数の外からは見えません)
68
前問からある以下デコレーター関数においてはキャッシュの条件となる引数がxのみをキーとして受け取っていた。 キャッシュの条件として複数の引数に対応するにはどうすればよいか? function cachingDecorator(func) { let cache = new Map(); return function(x) { if (cache.has(x)) { return cache.get(x); } let result = func.call(this, x); // "this" は正しいものが渡されます cache.set(x, result); return result; }; } 今回は 複数引数の組み合わせ (例:min,max) で結果を覚えたい。どうすればよいか? キャッシュ実装に関する案と デコレーション対象となる関数への引数の渡し方に関する案の二つ考えよ
キャッシュ実装に関する案 案1 複数キーを許可するマップライクなデータ構造を実装する 案2 入れ子のマップを使う。 xでマップがとれてきて、そのマップに対してyで取得みたいなかんじ。 案3 複数キーを結合して通常のマップを利用する→複数キーでハッシュ値をもとめてそれをキーとすることもできる。単純結合でもいい 案3が一番楽! デコレーション対象の関数への複数引数を 渡す方法 デコレーター関数の戻り値となる関数の引数をxのみから、複数引数に対応させる必要がある。 よって、argumentsで受け取るか、…argsで受け取るのが最善か。 以下例はargumentsで実装する ハッシュ算出に使う関数は受け取っておく function cachingDecorator(func, hash) { let cache = new Map(); // 引数なしの関数にする 引数はargumentsで管理する return function() { let key = hash(arguments); // ハッシュ化してキー管理する if (cache.has(key)) { return cache.get(key); } // 関数呼び出しの際、argumentsを展開して渡す let result = func.call(this, ...arguments); cache.set(key, result); return result; }; } //ハッシュ値を求める関数はこれ 問題点あり 後問題にて後述 function hash(args) { return args[0] + ',' + args[1]; }
69
JSON.stringify の第二引数、第三引数について説明せよ
let json = JSON.stringify(value[, replacer, space]) replacer エンコードするプロパティの配列、またはマッピング関数 function(key, value) です。 第2引数にプロパティの配列を渡した場合、それらのプロパティだけがエンコードされます。 space フォーマットで使うスペースの数です。 let room = { number: 23 }; let meetup = { title: "Conference", participants: [{name: "John"}, {name: "Alice"}], place: room // meetup は room を参照 }; room.occupiedBy = meetup; // room は meetup を参照 alert( JSON.stringify(meetup, ['title', 'participants', 'place', 'name', 'number']) ); /* { "title":"Conference", "participants":[{"name":"John"},{"name":"Alice"}], "place":{"number":23} } */
70
遅延なしのsetTimeoutにはどのような意味があるか? setTimeout(() => alert("World"), 0); alert("Hello");
非同期で現在のコードがおわったらすぐ後に実行される。 例えば、上記の場合は “Hello” を出力し、その後すぐに “World” を表示します。: なぜこうなるか? 最初の行は “0ms 後のカレンダーに呼び出しを置いています”。 しかし、スケジューラは現在のコードが完了した後に “カレンダーのチェック” をします。 そのため、 "Hello" が最初で、"World" が後になります。 まあ起動処理に時間がかかってるってことか
71
任意の処理1と処理2にかんして、ベンチマークを行うにはどのような設計を行うと良いか?
1 処理1と処理2を複数回実行する 例:処理1を10000回テスト 処理2を10000回テスト 2 上記のテストについて、それぞれData.nowメソッド同士での差分によって実行時間を取得する。 3 CPUリソースが少ないタイミングがあるので、 上記の処理を交互に繰り返すようにし、 処理1、2についてそれぞれの実行時間を加算していく 例: 処理1を10000回テスト 処理2を10000回テスト 処理1を10000回テスト 処理2を10000回テスト ・・・ 4何度も実行されるホットコードに対して、javascriptエンジンが最適化を行うようになっているので、 ヒートアップ(メインの実行の前の助走)を追加する。 例: 処理1を10000回テスト 計測はしない 処理2を10000回テスト 計測はしない 処理1を10000回テスト 計測する 処理2を10000回テスト 計測する 処理1を10000回テスト 計測する 処理2を10000回テスト 計測する ・・・
72
以下のコードはどのように動作するか? function sayHi() { alert(phrase); var phrase = "Hello"; } sayHi();
varの動作は変数宣言は関数実行の開始タイミング しかし、代入はコード実行時となるので、 undefined となる
73
arguments変数について説明せよ
インデックスによってすべての引数を含む 特別な配列ライクなオブジェクト。 arguments は引数の数に関係なく、関数に指定されたすべての引数を取得する function showName() { alert( arguments.length ); alert( arguments[0] ); alert( arguments[1] ); // 反復可能(iterable) です。 // for(let arg of arguments) alert(arg); } // 表示: 2, Julius, Caesar showName("Julius", "Caesar"); // 表示: 1, Ilya, undefined (2つ目の引数がないので) showName("Ilya");
74
デコレーターでスロットリングを実装せよ
“スロットリング” デコレータ throttle(f, ms) を作ります – これはラッパーを返し、ms ミリ秒毎に最大一度 f の呼び出しを渡します。“クールダウン” 期間に入る呼び出しは無視されます。 もし無視された呼び出しがクールダウン中の最後のものであれば、遅延の終わりにそれを実行します。 function throttle(func, ms) { let isThrottled = false, savedArgs, savedThis; function wrapper() { if (isThrottled) { // 呼び出してまもないならスキップする。その際前回の値をセーブしておく。 savedArgs = arguments; savedThis = this; return; } func.apply(this, arguments); // ここで呼び出す。 isThrottled = true; setTimeout(function() { isThrottled = false; // クールタイムがおわったら、次回の実行を許可する また、セーブされてた場合は即時ラッパー関数を起動する。 if (savedArgs) { wrapper.apply(savedThis, savedArgs); savedArgs = savedThis = null; } }, ms); } return wrapper; }
75
関数をデコレーションする際の考慮事項は?、
もしオリジナルの関数に func.calledCount といったようなカスタムプロパティが含まれている場合、デコレートされた関数はラッパーであるためそれらは提供しません。 したがって、それらを利用する際には注意が必要です。
76
Json.parseの第二引数について説明せよ
完全な構文は以下の通り let value = JSON.parse(str[, reviver]); str パースする JSON文字列です。 reviver 各 (key,value) ペアで呼ばれ、値を変形することができるオプションの関数(function(key,value))です。 つまり、第二引数は、Json文字列に誤りなどがあったり、取り込む時に別の型への変換が必要な場合に用いることができる。 let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}'; let meetup = JSON.parse(str); alert( meetup.date.getDate() ); // Error!
77
setTimeout とは異なり、関数を1回ではなく定期的に与えられた時間間隔で実行するには?
メソッド setIntervalを利用する let timerId = setInterval(func|code, delay[, arg1, arg2...]) これ以上の呼び出しを止めるためには、clearInterval(timerId) を呼ぶ必要があります。 次の例は、2秒毎にメッセージを表示し、5秒後に表示は停止されます。: // 2秒のインターバルで繰り返し let timerId = setInterval(() => alert('tick'), 2000); // 5秒後に停止 setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000);
78
コードが変数へアクセスしたい時、どのような動作となるか? レキシカル環境を用いて説明せよ
コードが変数にアクセスしたいとき、最初に内部のレキシカル環境を探します。その次に外側を探し、チェーンの最後になるまで繰り返します。 もし変数がどこにもない場合、strict モードではエラーになります。use strict がなければ、未定義変数への代入は下位互換性のために新しいグローバル変数を作成します。
79
前問にある function hash(args) { return args[0] + ',' + args[1]; } だとキーが二つの組み合わせまでしか対応できない。これを改善して以下のようにした。 function hash(args) { return args.join(); } どのような問題点があるか? 解決方法は?
残念なことに、これは動作しません。なぜなら私たちが呼んでいる hash(arguments) と arguments オブジェクトは両方とも 反復可能であり配列ライクではありますが、本当の配列ではありません。 なので join の呼び出しは失敗します: 以下のように改善する function hash() { alert( [].join.call(arguments) ); // 1,2 } hash(1, 2);
80
このように動作する関数 sum を書いてください。: alert( sum(1)(2) ); // 3 alert( sum(5)(-1)(2) ); // 6 alert( sum(6)(-1)(-2)(-3) ); // 0 alert( sum(0)(1)(2)(3)(4)(5) ); // 15
最初の実行(sum(n))では関数を返す必要がある 次の実行では、上記で返された関数で足し算処理を行う必要があり、再度実行可能であるため、同じ関数を返す必要がある。 アラートで数値を出力するために 関数オブジェクトに対してtoStringの実装が必要である。 実装しないとデフォのやつがでる function sum(a) { let currentSum = a; function f(b) { currentSum += b; return f; } f.toString = function() { return currentSum; }; return f; }
81
その calls プロパティにすべての関数呼び出しを保存するラッパーを返すデコレータ spy(func) を作成してください。 すべての呼び出しは引数の配列として格納されます。 For instance: function work(a, b) { alert( a + b ); // work は任意の関数またはメソッド } work = spy(work); work(1, 2); // 3 work(4, 5); // 9 for (let args of work.calls) { alert( 'call:' + args.join() ); // "call:1,2", "call:4,5" }
ここでは、ログにすべての引数を格納するために calls.push(args) を使い、呼び出しをフォワードするために、f.apply(this, args) を使う事ができます。 正直、実行する関数がオブジェクトメソッドでないので、applyメソッドを利用する必要はないが、汎用性のためそのように実装する。 実際、thisの中身はグローバルオブジェクト がくるとおもう function spy(func) { function wrapper(...args) { wrapper.calls.push(args); return func.apply(this, arguments); } wrapper.calls = []; return wrapper; } 関数には呼び出し履歴を、外部から取得可能とするためクロージャではなくカスタムプロパティを利用する。
82
文字列からDateオブジェクトに変換するには? フォーマットは?
Date.parse(str) で文字列からタイムスタンプを取得し、それからDateオブジェクトを生成する。 変換時、フォーマットが正しくない場合には NaN を返却する 文字列のフォーマットは YYYY-MM-DDTHH:mm:ss.sssZ でなければなりません。: YYYY-MM-DD は日付です。年-月-日 "T" の文字はデリミタとして使用されます。 HH:mm:ss.sss は時間です(時、分、秒とミリ秒)。 オプションの 'Z' の部分は、フォーマット +-hh:mm のタイムゾーンを示します。UTC+0 を意味する単一の文字 Z です。 より短い表記も可能です。 YYYY-MM-DD や YYYY-MM 、または YYYY です。 let date = new Date( Date.parse('2012-01-26T13:51:50.417-07:00') ); alert(date);
83
関数の実行に関する情報の格納先はどこか
実行コンテキストに格納されている。 実行コンテキスト(execution context) は関数の実行に関する詳細を含む内部のデータ構造です。: 今はどの制御フローであるか、現在の変数、this の値(ここでは使いませんが)や、その他いくつかの内部データを持ちます。 1つの関数呼び出しには、それに関連付けられた実行コンテキストが1つだけあります。
84
以下のような、空の配列のメソッドを利用するようなテクニックをなんというか function hash() { alert( [].join.call(arguments) ); // 1,2 } hash(1, 2);
メソッドの借用 なぜ動くかというと、関数が配列ライクに対応しているから
85
関数オブジェクトでパラメータの数を調べるには?
lengthプロパティを利用する function f1(a) {} function f2(a, b) {} function many(a, b, ...more) {} alert(f1.length); // 1 alert(f2.length); // 2 alert(many.length); // 2 ここで、残りのパラメータはカウントされないことが分かります。
86
以下のようなオブジェクトメソッドでthisが失しなわれてしまうケースに対する改善策で最もシンプルな改善策は? let user = { firstName: "John", sayHi() { alert(`Hello, ${this.firstName}!`); } }; setTimeout(user.sayHi, 1000); // Hello, undefined!
そのままオブジェクトメソッドを渡すのではなく、関数をつくってラップする。 そうすると関数の中で、オブジェクトメソッドとして実行するのでthisは失われない。 let user = { firstName: "John", sayHi() { alert(`Hello, ${this.firstName}!`); } }; setTimeout(function() { //userオブジェクトは外部レキシカル環境から取得可能 user.sayHi(); // Hello, John! }, 1000);
87
再帰のプログラムパターンとはなにか
関数が自分自身を呼ぶこと 例 x の n 乗をする関数 pow(x, n)の場合 nが3ならxを3回ループして掛け合わせていく x *= x ←これを三回 上の処理を再帰的に表現すると、 3回目の処理である場合はxをそのままかえす(再帰処理の打ち止め) 1-2回目の処理である場合は、自分の関数をカウントアップ(ダウン)して呼び出す 的な感じになる。 ただ、ループ処理とは動作的に異なる点がいくつかある function pow(x, n) { if (n == 1) { return x; } else { return x * pow(x, n - 1); } } alert( pow(2, 3) ); // 8
88
new Function構文はどのような時に活用するか?
文字列で関数の命令部を記述できるので、 これは 複雑なWebアプリケーションでサーバからコードを受け取ったり、テンプレートから動的に関数をコンパイルするような、非常に特定のケースで使用される。
89
ソートするオブジェクトの配列を持っているとします。: let users = [ { name: "John", age: 20, surname: "Johnson" }, { name: "Pete", age: 18, surname: "Peterson" }, { name: "Ann", age: 19, surname: "Hathaway" } ]; それをするための通常の方法はこのようになります: // by name (Ann, John, Pete) users.sort((a, b) => a.name > b.name ? 1 : -1); // by age (Pete, Ann, John) users.sort((a, b) => a.age > b.age ? 1 : -1); 私たちはこのように冗長さを無くすことはできますか?
フィールド名を引数とした関数を返却する関数を作ればよい let users = [ { name: "John", age: 20, surname: "Johnson" }, { name: "Pete", age: 18, surname: "Peterson" }, { name: "Ann", age: 19, surname: "Hathaway" } ]; function byField(field) { return (a, b) => a[field] > b[field] ? 1 : -1; } users.sort(byField('name')); users.forEach(user => alert(user.name)); // Ann, John, Pete users.sort(byField('age')); users.forEach(user => alert(user.name)); // Pete, Ann, John
90
オブジェクトをJson形式に変換するには? 逆に、json形式をオブジェクトに変換するには?
JSON.stringify : オブジェクトをJSONに変換します。(文字列) JSON.parse : JSONをオブジェクトに変換します。 let student = { name: 'John', age: 30, isAdmin: false, courses: ['html', 'css', 'js'], wife: null }; let json = JSON.stringify(student); alert(typeof json); // string です! alert(json); /* JSON-encoded object: { "name": "John", "age": 30, "isAdmin": false, "courses": ["html", "css", "js"], "wife": null } */
91
再帰を利用して、n番目のフィボナッチ数を求めるにはどうやるか? フィボナッチ数の例: 1, 1, 2, 3, 5, 8, 13, 21....
フィボナッチは定義上、再帰的なツリー構造により求められる。 例えば6番目のフィボナッチを求めたい場合 5番目のフィボナッチと4番目のフィボナッチを足し合わせた数となる。 また、末尾処理は fib(1) = 1かfib(0)=0とする。 fib(2)の場合は fib(1)とfib(0)で1となる。 同様に fib(n)の場合は、fib(n-1)+fib(n-2)で定義できる。 function fib(n) { return n <= 1 ? n : fib(n - 1) + fib(n - 2); } ただ、一回の関数で再帰処理を2回もはじめるので、遅い! alert( fib(3) ); // 2 alert( fib(7) ); // 13 // fib(77); // とても遅いでしょう!
92
以下のコードについて、最深の要素から値出力をしていきたい。 どうやるか? 再帰とループの二つ考えよ let list = { value: 1, next: { value: 2, next: { value: 3, next: { value: 4, next: null } } } };
再帰でやる場合、 最深まで辿ったあと値出力を行いながら戻っていく。 function printReverseList(list) { if (list.next) { printReverseList(list.next); } alert(list.value); } ループでやるには、連結リストは最後の要素を簡単にとれないので、要素を配列に変換し、ループでアラートしていく。 function printReverseList(list) { let arr = []; let tmp = list; while (tmp) { arr.push(tmp.value); tmp = tmp.next; } for (let i = arr.length - 1; i >= 0; i--) { alert( arr[i] ); } }
93
コンストラクタ関数でthisに対するプロパティ設定ではない、通常の変数設定(let count = 0)はどのような場合に利用されるか? function Counter() { let count = 0; } let counter = new Counter();
上記の例だと空のオブジェクトが返ってくるだけで意味ないが、以下のように、クロージャの外部変数として活用できる。 外部からアクセスできないので、プライベート的なかんじかな? function Counter() { let count = 0; this.up = function() { return ++count; }; this.down = function() { return --count; }; } let counter = new Counter();
94
f の各呼び出し毎に ms ミリ秒遅延をするデコレータ delay(f, ms) を作成してください。 例: function f(x) { alert(x); } // ラッパーを作成 let f1000 = delay(f, 1000); let f1500 = delay(f, 1500); f1000("test"); // "test" は 1000ms 後に表示 f1500("test"); // "test" は 1500ms 後に表示 つまり、delay(f, ms) "ms 遅延した" f のバリアントを返します。 上のコードにおいて、f は単一引数の関数ですが、あなたの解答はすべての引数とコンテキスト this を渡すようにしてください。
オブジェクトメソッドにも対応する必要があるということで、 f.apply(this, args);を利用する必要があるが、今回は遅延ありの非同期実行となり、 setTineoutにわたす関数では、常にグローバル実行となってしまうため、thisでオブジェクトメソッドに対応できない。 よって、thisをあからじめ変数に保存しておく必要がある。 function delay(f, ms) { // setTimeout の中で、ラッパーから this と 引数を渡すための変数を追加 return function(...args) { // ここのthisは呼び出しの際の実行コンテキストが入る let savedThis = this; setTimeout(function() { f.apply(savedThis, args); }, ms); }; }
95
デコレーターでデバウンスを実装せよ
function debounce(func, wait) { let timeout; return function() { clearTimeout(timeout); timeout = setTimeout(() => { func.apply(this, arguments); }, wait); }; } デバウンスされたメソッドは、 連続で呼び出されるとセットされたタイムアウトがクリアされ、再設定される。