###前提・実現したいこと
fn.apply(ctx, [1, 2, 3]) //と Function.prototype.apply.call(fn, ctx, [1, 2, 3])
の違いを知りたいです。
ここの説明では
https://ponyfoo.com/articles/es6-reflection-in-depth
If we fear fn might shadow apply with a property of their own, we can rely on a safer but way more verbose alternative.
fnが独自のプロパティで適用シャドウかもしれないと恐れるならば、
安全に、より詳細な代替にすることができる
から
と説明されているようですが、
自分の英語の和訳不足で意味がわかりません。
どのような場合を恐れてFunction.prototype...から記述しているのでしょうか
ユースケースなど事例として教えていただけたら幸いです。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答3件
0
ベストアンサー
Function.prototype.apply.call
関数が Function.prototype
を継承していなくとも TypeError
を発生させません。
JavaScript
1function fn () { 2 console.log(arguments); 3} 4fn.__proto__ = null; 5 6Function.prototype.apply.call(fn, null, [1, 2, 3]); // [1, 2, 3] 7fn.apply(null, [1, 2, 3]); // TypeError: fn.apply is not a function
Function.prototype.apply の用途
用途によって呼び出し方も変わるものです。
JavaScript
1function fn () { 2 console.log(arguments); 3} 4 5fn.apply(null, [1, 2, 3]); // [1, 2, 3]
ユーザ定義関数をそのまま呼び出す場合は Function.prototype
を継承している事がコード制作者にとって自明なので問題ありません。
しかし、その関数の所在が明らかでない場合、例えば引数で受け取った関数の場合はどうでしょう。
JavaScript
1function hoge (fn) { 2 fn.apply(null, [1, 2, 3]); 3}
この場合、hoge()
が呼び出されるまで fn.apply(null, [1, 2, 3]);
が実行可能か判断することが出来ません。
それがどんな関数であっても呼び出せることを保証するなら Function.prototype.apply.call
で呼び出すのが確実です。
JavaScript
1function hoge (fn) { 2 Function.prototype.apply.call(fn, null, [1, 2, 3]); 3}
コールバック関数
前節では説明を省略しましたが、引数に何らかのアクションをする場合は引数値をテストして例外処理を設けるのが安全な設計です。
私はこの手の問題はESで規定されたビルトイン関数を参考にすることが多いのですが、ここでは Array.prototype.forEach
を事例にあげます。
JavaScript
1[1, 2, 3].forEach(null); // TypeError: null is not a function
以下、ES7 (ES2016)より引用します。
22.1.3.10 Array.prototype.forEach ( callbackfn [ , thisArg ] )
- Let O be ? ToObject(this value).
- Let len be ? ToLength(? Get(O, "length")).
- If IsCallable(callbackfn) is false, throw a TypeError exception.
7.2.3 IsCallable ( argument )
- If Type(argument) is not Object, return false.
- If argument has a [[Call]] internal method, return true.
- Return false.
第一引数 callbackfn
を Object 型に変換し、[[Call]]
を持たない場合に TypeError
例外を発生させます。
同様の処理をしたい場合は次のように書きます。
JavaScript
1function sample (callbackfn) { 2 callbackfn = Object(callbackfn); // Object 型に変換する 3 4 if (typeof callbackfn !== 'function') { // callbackfn が [[Call]] を持たないなら 5 throw new TypeError(callbackfn + ' is not a function'); 6 } 7}
次に Function.prototype.apply
を使うケースを想定します。
上記コードを拡張するなら、引数 callbackfn
が Function.prototype
を継承しているかを判定すればいいでしょう。
JavaScript
1function sample (callbackfn, _arguments) { 2 callbackfn = Object(callbackfn); // Object 型に変換する 3 4 if (typeof callbackfn !== 'function') { // callbackfn が [[Call]] を持たないなら 5 throw new TypeError(callbackfn + ' is not a function'); 6 } 7 8 if (!(callbackfn instanceof Function)) { 9 throw new Error(callbackfn ' must be an instance of Function'); 10 } 11 12 callbackfn(_arguments); 13}
ただし、iframe等、異なる所属のドキュメント上で生成された関数に対して上記コードは期待通りに動作しないので厳密性を上げるにはもう少し手を入れる必要があります。
JavaScript
1if (!(callbackfn instanceof Function) || Object.prototype.toString.call(callbackfn) !== '[object Function]' || ) { 2 throw new Error(callbackfn ' must be an instance of Function'); 3}
ここまで説明しておいて何ですが、実際にはここまで書くことはありません。
ECMAScript には [[Call]] を持たない値に対して関数呼び出ししようとした場合に TypeError 例外を返す仕様があるからです。
JavaScript
1function sample (callbackfn, _arguments) { 2 callbackfn = Object(callbackfn); // Object 型に変換する 3 4 callbackfn(_arguments); // [[Call]] を持つ値にたいして関数呼び出しを実行する 5} 6 7sample(null); // TypeError: callbackfn is not a function
そして、Function.prototype.apply
にも [[Call]]
が存在しない場合に TypeError
例外を発生させる規定があります。
19.2.3.1 Function.prototype.apply ( thisArg, argArray )
- If IsCallable(func) is false, throw a TypeError exception.
従って、Object 型への型変換処理を除けば次のように書けます。
JavaScript
1function sample (callbackfn, _arguments, thisArg) { 2 return Function.prototype.apply.call(callbackfn, thisArg, _arguments); 3} 4 5function callbackfn1 () { 6 console.log(arguments); 7} 8 9sample(callbackfn1, [1, 2, 3]); // [1, 2, 3] 10sample.__proto__ = null; 11sample(callbackfn1, [1, 2, 3]); // [1, 2, 3] ([[Prototype]] が Function.prototype でなくとも実行できる) 12sample({}); // TypeError: Function.prototype.apply was called on #<Object>, which is a object and not a function 13sample(null); // TypeError: Function.prototype.apply was called on null, which is a object and not a function
getElementById.apply()
jQuery に影響されてか次のようなショートカット関数をたまに見かけます。
JavaScript
1function $ (id) { 2 return document.getElementById(id); 3}
このコードには document
が現在のページの document
に固定されているという問題があります。
iframe要素、DOMParser
で生成、window.open
等、他の所属の document
も指定できたり、任意の要素ノードを指定できる方が汎用性が高まるでしょう。
JavaScript
1function $ (id, node) { 2 var getElementById = document.getElementById, 3 _arguments = Array.prototype.slice.call(arguments); 4 5 _arguments = [id].concat(_arguments.slice(2)); // 第二引数を取り除いた引数リストを生成する 6 7 if (Object(node) !== node || !('nodeType' in node) || typeof node.getElementById !== 'function') { // Object 型ではない、もしくは nodeType プロパティを持たない、もしくは getElementById プロパティが [[Call]] を持たないなら 8 node = document; 9 } 10 11 return getElementById.apply(node, _arguments); 12} 13 14console.log($('hoge')); 15console.log($('hoge', document)); // 任意の this 値を指定できる 16console.log($('hoge', document, 1, 2)); // document.getElementById('hoge', 1, 2) と等価 (将来的に getElementById が第二引数以降を持つようになっても対応できる) 17 18document.getElementById.__proto__ = null; // [[Prototype]] に Function.prototype がセットされていない状態にする 19console.log($('hoge')); // TypeError: getElementById.apply is not a function 20console.log($('hoge', document)); // TypeError: getElementById.apply is not a function 21console.log($('hoge', document, 1, 2)); // TypeError: getElementById.apply is not a function
getElementById
は DOM 規定のAPIですが、DOM がJavaScript 以外も考慮されたAPIという事もあってか [[Prototype]] に Function.prototype
が存在する事を保証していません。
多くの実装では document.getElemehtById.apply
が期待通りの動作するよう実装されていますが、仕様にない以上、次のように Function.prototype
を継承していない実装がある可能性があります。
JavaScript
1document.getElementById.__proto__ = null; // [[Prototype]] に Function.prototype がセットされていない 2document.getElementById.apply(document, ['hoge']); // TypeError: document.getElementById.apply is not a function
前節と同様、Function.prototype.apply
を直接呼び出せば安全に実装できます。
function $ (id, node) { var getElementById = document.getElementById, _arguments = Array.prototype.slice.call(arguments); _arguments = [id].concat(_arguments.slice(2)); // 第二引数を取り除いた引数リストを生成する if (Object(node) !== node || !('nodeType' in node) || typeof node.getElementById !== 'function') { // Object 型ではない、もしくは nodeType プロパティを持たない、もしくは getElementById プロパティが [[Call]] を持たないなら node = document; } return Function.prototype.apply.call(document.getElementById, node, _arguments); } document.getElementById.__proto__ = null; // [[Prototype]] に Function.prototype がセットされていない状態にする console.log($('hoge')); console.log($('hoge', document)); // 任意の this 値を指定できる console.log($('hoge', document, 1, 2)); // document.getElementById('hoge', 1, 2) と等価 (将来的に getElementById が第二引数以降を持つようになっても対応できる)
更新履歴
- 2016/11/06 12:00 コールバック関数、Function.prototype.apply, getElementById...etc の説明を追加
Re: kkkke さん
投稿2016/11/03 12:12
編集2016/11/06 03:00総合スコア18156
0
いろいろと私への不満が募っているようですが、私もあなたに伝えたいことが山ほどあります。
質問者に伝えたいこと
- 私にも生活リズムがあり、余裕の出来た空き時間に回答しているという事実を理解していますか
- 私が回答にどれぐらいの時間を割いていると思っていますか
- "多分この説明で「なるほどそうか!」と納得する方は多くないのではないでしょうか" と仰いますが、仕様書とはすぐに理解できるような簡単なものではありません。私は英文の仕様書なら1節を読み解くのに1日かけてもわからず、1週間ぐらいかけて読み解く場合がざらにあります。ですので、「最新仕様がいい」と気軽に言われてもそう簡単には読めません。勿論、最新仕様が一番いいのは分かります。ですが、それを読み解く労力を想像して下さい。)。
- 30分~1時間置きに返信を追加していますが、回答内容を読解する時間をどれだけかけていますか。
- 最新仕様(ES7)をお望みのようですが、私が掲示した 4.3 Terms and Definitions - ECMAScript® 2016 Language Specificationを読み解こうと努力をしましたか。読み解こうとしたならあなたはどの記述を読んでどんな解釈をしましたか。
- 初心者だからと甘えていませんか。初心者が分からないのは当然ですが、理解する為の自助努力を放棄して「他人に聞いた方が早い」と安易に考えていませんか。「理解するための手段が分からない」のなら分かりますが、理解する為の手段がありながら(仕様書のURLが分かっている)それをしないのは問題とは思いませんか。
- teratail をどのような場だと思っていますか。質問するときのヒント|teratail(テラテイル)を読みましたか。「まず、出来るだけ自分で調べてみる。」「調べてみてわからなかったら調べた内容を具体的に書き、自分の解釈を具体的に説明して意見を求める」teratailとはそんな場だと私は考えます。
簡単な質問/難しい質問
「上級者なら初心者の質問ぐらいは簡単に記憶から引っ張り出せるはず」
そんな風に考えているのかもしれませんが、上級者だって全てを記憶しているわけではなく、調べものぐらいします。
(私が上級者というわけではありません。物のたとえであって私よりも知識がある方はここにはたくさんいらっしゃいます。
今までに私の間違った回答をすれば指摘を頂いた事が何度もあります。)
簡単なように見えて難しい質問もあります。
特に仕様であるなら不正確な事はいいたくありませんので、該当部分を引用しながら仕様を読み直して説明します。
割と気軽に深い部分を質問する方はいるのですが、調べる作業を丸投げしているように感じます。
本来は質問者が出来るだけ調べてわからない点があれば該当部分を引用し、自分の解釈を述べ、分からない部分を具体化する質問であってしかるべきだと私は考えます。
Re: kkkke さん
投稿2016/11/06 03:06
総合スコア18156
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
[ES3-7] ビルトインオブジェクト(built-in object) とネイティブオブジェクト(Native Object)
ビルトインオブジェクトとは ECMAScript で規定されるオブジェクトの総称です。
- 4.3.7 組み込みオブジェクト (Built-in Object) - ECMAScript 3 日本語訳
- 4.3.7 built-in object - ECMA-262 Edition 5.1
- 4.3.9 built-in object - ECMAScript® 2016 Language Specification
ES7 ではビルトインオブジェクトから関数だけを抽出して**ビルトイン関数(built-in function)**が定義されています。
ES3, ES5.1 ではネイティブオブジェクト(Native Object)が定義されています。
(ES7 にはなく、別の用語が定義されています)
ECMAScript 5.1
以下、ECMAScript 5.1 仕様の記述を引用します。
4.3.7 built-in object
object supplied by an ECMAScript implementation, independent of the host environment, that is present at the start of the execution of an ECMAScript program
NOTE: Standard built-in objects are defined in this specification, and an ECMAScript implementation may specify and define others.
Every built-in object is a native object.
A built-in constructor is a built-in object that is also a constructor.
Every built-in object is a native object.
以下、『<a href="http://ecma262.info/">ECMA-262 Edition 5.1を読む</a>』より引用。
「4.3.7 組み込みオブジェクト(built-in object)
ホスト環境ではなく、ECMAScript実装によって提供されるオブジェクト。ECMAScriptプログラムを開始した時点で存在している。
NOTE: 標準の組み込みオブジェクトは本仕様で定義されている。ECMAScript実装では、他の組み込みオブジェクトを指定し、定義する事がある。組み込みオブジェクトは全てネイティブオブジェクトである。組み込みコンストラクタとはコンストラクタでもある組み込みオブジェクトの事である。」
4.3.6 native object
object in an ECMAScript implementation whose semantics are fully defined by this specification rather than by the host environment
NOTE: Standard native objects are defined in this specification. Some native objects are built-in; others may be constructed during the course of execution of an ECMAScript program.
以下、『<a href="http://ecma262.info/">ECMA-262 Edition 5.1を読む</a>』より引用。
「4.3.6 ネイティブオブジェクト
ホスト環境ではなく、本仕様によってセマンティクスが完全に定義されるECMAScript実装のオブジェクト。
NOTE: 標準のネイティブオブジェクトは本仕様で定義されている。ネイティブオブジェクトには組み込みオブジェクトとして提供されるものと、ECMAScriptプログラムの作成時に生成されるものがある。」
4.3.8 host object
object supplied by the host environment to complete the execution environment of ECMAScript
NOTE: Any object that is not native is a host object.
以下、『<a href="http://ecma262.info/">ECMA-262 Edition 5.1を読む</a>』より引用。
「4.3.8 ホストオブジェクト
ECMAScript の実行環境を補完する為にホスト環境によって提供されるオブジェクト。
NOTE: ネイティブでないオブジェクトは全てホストオブジェクトである。」
これらの説明はネイティブオブジェクトがビルトインオブジェクトより広義である事を表しています。
そして、ビルトインオブジェクトはECMAScriptプログラムを開始した時点で存在しているものですが、DOM既定の要素ノードオブジェクト等はECMAScfiptプログラムの外側で定義されているのでこれに該当しません。
では、ネイティブオブジェクトとホストオブジェクトの2択になるわけですが、ここでいうホスト環境とはおそらくOSやブラウザを表していると思われます。
DOMオブジェクトはOS/ブラウザ特有のオブジェクトではないので「ネイティブオブジェクト」であろうと判断できます。
更に補完するなら tyoeof
演算子では "object"
を返す値を「ネイティブかつ [[Call]]
を持たないもの」と定義しています。
JavaScript
1console.log(typeof document.createElement('p')); // "object"
従って、p要素ノードはネイティブオブジェクトと判断できます。
Re: kkkke さん
投稿2016/11/06 03:02
編集2016/11/06 23:15総合スコア18156
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2016/11/03 12:17 編集
2016/11/03 23:36 編集
2016/11/04 01:38
2016/11/04 12:08 編集
2016/11/04 12:26
2016/11/04 13:11 編集
2016/11/05 02:06 編集
2016/11/05 14:39 編集
2016/11/06 03:53
2016/11/06 06:51
2016/11/06 23:17