javascriptのbindメソッドは、第一引数にレシーバオブジェクトのthis参照を指定でき、第二引数以降にそのレーシバオブジェクトに渡す引数を指定できるものと認識しております。
下記のソースは、モジラデペロッパネトワークというサイトでbindメソッドを解説し、且つbindの使いどころを指南されているときに出くわしたものです。
list関数に引数を与えると配列として返ってきて、それをlist1変数に代入しています。次に、this参照部分は、必要ないみたいなのでundefined、
list関数に渡したい37を配列の値として持つ返り値をleadingThirtysevenList変数に代入しています。そこに、さらに1と2と3を配列として扱う
list関数に渡し37、1、2、3という四つの値を持つ配列をlist3変数に代入していると読みました。
ここで二つ疑問が沸いたのですが、list関数内にあるsliceメソッドのあとにチェーンされているcallはいったい何の意味があるのでしょうか。
またlist3変数に代入するとき、list.bindを格納しているleadingThirtysevenListに1、2、3という三つの値を引数として受け取っていますが、
第一引数は、undefinedではないのでしょうか。leadingThirtysevenListは、list.bindを格納しているので第一引数は、this参照を指定できるのでは、と思ったのです。
javascript
1function list() { 2 return Array.prototype.slice.call(arguments); 3} 4 5var list1 = list(1, 2, 3); // [1, 2, 3] 6 7// 先頭の引数がプリセットされた関数をつくる 8var leadingThirtysevenList = list.bind(undefined, 37); 9 10var list2 = leadingThirtysevenList(); // [37] 11var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3] 12 13
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答3件
0
Array.prototype.slice
list関数内にあるsliceメソッドのあとにチェーンされているcallはいったい何の意味があるのでしょうか。
Array.prototype.slice
は this
値を配列に変換(ArraySpeciesCreate
)してから処理します。
疑似配列となる arguments
を Function.prototype.call
の第一引数に指定し、第二引数以降を未指定にすることで配列変換処理だけを利用しています。
Function.prototype.bind
またlist3変数に代入するとき、list.bindを格納しているleadingThirtysevenListに1、2、3という三つの値を引数として受け取っていますが、
第一引数は、undefinedではないのでしょうか。
第一引数は 37
です。
undefined
が指定されているのは this
値の方です。
JavaScript
1'use strict'; 2function list () { 3 console.log('this -> '+ this); 4 console.log('arguments -> ' + JSON.stringify(arguments)); 5 return Array.prototype.slice.call(arguments); 6} 7 8list(1, 2, 3); // this -> undefined & arguments -> {"0":1,"1":2,"2":3} 9list.call(null, 1, 2, 3); // this -> null & arguments -> {"0":1,"1":2,"2":3} 10list.bind('hoge')(1, 2, 3); // this -> "hoge" & arguments -> {"0":1,"1":2,"2":3} 11list.bind(0, 37)(1, 2, 3); // this -> 0 & arguments -> {"0":37,"1":1,"2":2,"3":3}
Strict Mode と 非Strict Mode(Sloppy Mode)
this
値の扱いにはStrict Modeと非Strict Mode(Sloppy Mode)で違いがあり、Strict Modeでは指定された値がそのまま使われますが、「非Strict Modeでは this
値に Object
型しか指定できない」という制約があります(ES3 仕様の名残です)。
その為、非Strict Modeでは次の仕組みで this
値が決定されます。
- this 値に Object 型が指定された場合
-> 指定された Object 型を this 値とする - this 値に Object, Undefined, Null 型以外が指定された場合
-> 指定された値を Object 型に変換し、this 値とする - this 値に Undefined, Null 型が指定された場合
-> 規定値であるグローバルオブジェクトを this 値とする(undefined, null は Object 型に変換できない)
Re: aaaaaaaa さん
投稿2016/08/16 13:26
編集2016/08/17 02:35総合スコア18162
0
ベストアンサー
まずとても重要なお話をします。
argumentsオブジェクトは配列ではありません。
これがとても重要なので、これを頭の片隅に置いた状態で以下をお読みください。
JavaScript
1(function(){ 2 console.log(Array.isArray(["a", "b", "c"])); // true 3 console.log(Array.isArray(arguments)); // false 4 5 // ちなみにargumentsは数字キーを添え字とした単なるオブジェクト(一般にarray likeと言います) 6 console.log(arguments); // {0: "a", 1: "b", 2: "c", length: 3} 7})("a", "b", "c");
そして、argumentsオブジェクトは配列ではないので、sliceメソッドをはじめとする、Arrayのprototypeにあるメソッド各種を直接呼び出すことが出来ません。
JavaScript
1(function(){ 2 var list1 = ["a", "b", "c"].slice(1, -1); // 配列の場合は、sliceメソッドを直接呼び出せる 3 console.log(list1); // ["b"] 4 5 // argumentsは配列ではないので、sliceメソッドを持っていない。(のでundefinedを返す) 6 console.log(typeof(arguments.slice)); // "undefined" 7})("a", "b", "c");
ところでこのargumentsオブジェクト。確かにArrayではないのですが、構造自体は配列に非常に似ていますよね。
argumentsオブジェクトと配列との差は、Arrayオブジェクトでないがゆえに、Arrayのsliceメソッドを持っていないというだけです。
であれば、**明示的にthisにargumentsを渡せば、配列とみなして振る舞ってくれるかも?**と思うかもしれません。
実際のところその通りで、Arrayとみなして振る舞います。
(細かい条件を言えば、数字キーを添え字として、length
プロパティに整数値を持つオブジェクトは、それ自体が配列でないとしても、配列として振る舞います。)
JavaScript
1(function(){ 2 var list1 = ["a", "b", "c"].slice(); // 配列の場合は、sliceメソッドを直接呼び出せる 3 console.log(list1); // ["a", "b", "c"] 4 5 // 因みに、[].sliceとArray.prototype.sliceは(ユーザ側で上書きしてなければ)同一です。 6 console.log([].slice === Array.prototype.slice); // true 7 8 var list2 = Array.prototype.slice.call(arguments); // thisにargumentsを指定してsliceメソッドを呼び出す 9 console.log(list2); // ["a", "b", "c"] 10 11 // 因みにsliceの仕様上、元々のthisが配列じゃないものであっても、戻り値は必ずArrayに変換される。 12 console.log(Array.isArray(list2)); // true 13})("a", "b", "c");
これが最初の疑問の回答です。
配列ではないものに対して、callを用いて明示的にthisを指定することにより、配列として振る舞わせてしまうことで、sliceを実現しています。
そしてなぜargumentsオブジェクトにこんな面倒臭い処理を施すかというと、sliceの仕様を利用して、array likeなargumentsオブジェクトを、ピュアなArrayに変換するために使用しています。
上記のサンプルコードで、出力のlist2
はArray.isArray
の結果がtrue
になったことが確認できるかと思います。
次にbindの話ですが、 aaaaaaaa さんは以下の様な挙動を期待していた、ということであってますよね?
JavaScript
1function f(){ 2 console.log(this); 3} 4 5var f1 = f.bind(undefined); 6 7f1(); // これはきっとundefinedだろう。 8f1("hoge"); // これはきっとthisが"hoge"に上書きされて「"hoge"」と表示されるだろう。
残念ながらbindの仕様はこうはなっていません。
以下に説明を加えますが、ここで1点注意があります。
それは、以降の挙動の説明はstrict mode
かどうかで結果が変わるということです。
(※strict modeとはなにか?の説明はここでは深く話しませんが、「先頭に"use strict";を付けると、挙動チェックが厳密になる」程度に思ってくれても差し支えありません。)
以下は、strict mode
でない場合について話します。
bindの第一引数にnullまたはundefinedを指定した場合は、その関数のthisは必ずグローバルオブジェクトを参照するようにします。
JavaScript
1function f(){ 2 console.log(this); 3} 4 5// thisにundefinedを指定したため、グローバルオブジェクトで束縛される。 6var f1 = f.bind(undefined); 7 8// これはグローバルオブジェクト(ブラウザ上ではwindowオブジェクト)を指す。 9f1(); // [object Window] 10 11// 一度bindによってthisが束縛されたものに対して、callで強制的にthisを上書きしようとしても、上書きすることが出来ない。 12f1.call("hoge"); // [object Window]
bindがこのようにthisを1度しか指定することが出来ない仕様なので、bindが返す関数は再度thisを指定するための引数を(必要が無いので)用意していません。
JavaScript
1function list() { 2 return Array.prototype.slice.call(arguments); 3} 4 5var list1 = list(1, 2, 3); // [1, 2, 3] 6 7// この時点でこれ以降のleadingThirtysevenListのthisは 8// グローバルオブジェクト以外になり得ないことが確定する 9var leadingThirtysevenList = list.bind(undefined, 37); 10 11// なので、以降の関数呼び出しにおいて、第一引数でthisを上書きする必要がなく、 12// 実際にそのような引数は提供されていない。(※thisと引数が束縛されただけの単なる関数を返す) 13var list2 = leadingThirtysevenList(); // [37] 14var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]
これが2個目の疑問の回答です。
投稿2016/08/16 15:21
編集2016/08/16 15:35総合スコア103
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2016/08/21 12:44 編集
0
###A1
list関数内にあるsliceメソッドのあとにチェーンされているcallはいったい何の意味があるのでしょうか。
callは関数を実行します。
hoge(); でも、hoge.call(); でも通常は結果は同じです。
ただcallはthisを指定して実行することが出来るので、普通に呼び出すのとちょっと違った使い方が出来ます。(chromeのconsoleで実験)
javascript
1function hoge(v){ 2 if(this !== window)console.log(this.x + ':' + v); 3 else console.log('false' + ':' + v); 4} 5a = {x:2}; 6hoge(3); // false:3 7hoge.call(a,3); // 2:3
Array.prototype.slice.call(arguments); つまりこれは
thisをarguments(関数に渡した引数を配列にしたもの)に固定してArrayのslice関数を呼び出す。ってことですね。(chromeのconsoleで実験)
javascript
1[37,1,2,3].slice(); //[37,1,2,3] 2[37,1,2,3].slice(0,2); //[37,1] 3Array.prototype.slice.call([37,1,2,3]); //[37,1,2,3] 4Array.prototype.slice.call([37,1,2,3],0,2); //[37,1]
###A2
第一引数は、undefinedではないのでしょうか。
bindもcallと同類(?)で、callは即時実行したのに対し、bindはthisを固定した関数オブジェクトを新たに生成しているようです。引数も固定でセットできるおまけつき。
第一引数をundefinedにするとthisが未設定状態になります。
(chromeのconsoleで実験)
javascript
1function hoge(v){ 2 if(this !== window)console.log(this.x + ':' + v); 3 else console.log('false' + ':' + v); 4} 5hoge(1); // false:1 6 7a={x:2}; 8hoge2 = hoge.bind(a,37); 9hoge2(); // 2:37 10 11hoge3 = hoge.bind(undefined,37); 12hoge3(); // false:37 13
投稿2016/08/16 13:22
編集2016/08/16 13:26総合スコア2068
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2016/08/16 13:40 編集
2016/08/18 10:54
2016/08/18 11:36 編集
2016/08/19 07:28
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2016/08/18 10:32