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

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

新規登録して質問してみよう
ただいま回答率
85.48%
JavaScript

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

関数

関数(ファンクション・メソッド・サブルーチンとも呼ばれる)は、はプログラムのコードの一部であり、ある特定のタスクを処理するように設計されたものです。

Q&A

解決済

3回答

2070閲覧

何故callメソッドがあってundefinedではないのか

aaaaaaaa

総合スコア501

JavaScript

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

関数

関数(ファンクション・メソッド・サブルーチンとも呼ばれる)は、はプログラムのコードの一部であり、ある特定のタスクを処理するように設計されたものです。

1グッド

1クリップ

投稿2016/08/16 11:22

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
ikuwow👍を押しています

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

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

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

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

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

guest

回答3

0

Array.prototype.slice

list関数内にあるsliceメソッドのあとにチェーンされているcallはいったい何の意味があるのでしょうか。

Array.prototype.slicethis 値を配列に変換(ArraySpeciesCreate)してから処理します。
疑似配列となる argumentsFunction.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
think49

総合スコア18162

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

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

aaaaaaaa

2016/08/18 10:32

ご回答有難うございました。 array.prototype.sliceにthisを配列に変更するという機能があったのですね。所持している書籍には記載されていないのでとても勉強になります。
guest

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に変換するために使用しています。
上記のサンプルコードで、出力のlist2Array.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
gaogao_9

総合スコア103

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

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

aaaaaaaa

2016/08/18 10:28

ご回答有難うございました。 ご丁寧な回答をいただきまして、とても理解が深まりました。 申し訳ありませんが一つ質問させてください。 引数オブジェクト(arguments)は、配列のように、添え字的な意味合いを持つプロパティ名と値で構成されたものであって配列ではないので、array.prototypeがもつsliceメソッドが使えない。 そのために、callを使いthis参照に引数オブジェクトを与えることでarray.prototypeの持つsliceメソッドが使えるようになった。 別の回答者さんの回答で、array.prototype.sliceがthisを配列に変換することが分かりました。 ここで疑問なのですが、ここでいうthisは、array.prototype内のthisだと思うのですが、なぜarray.prototypeのthisに引数オブジェクトを与えることでsliceメソッドなどarray.prototypeがもつメンバを利用できるようになるのでしょうか。
think49

2016/08/21 12:44 編集

> なぜarray.prototypeのthisに引数オブジェクトを与えることでsliceメソッドなどarray.prototypeがもつメンバを利用できるようになるのでしょうか。 配列に変換する事で [[Prototype]] が Array.prototype になる為です。 プロトタイプチェーンの説明についてもう一度、目を通しておくことをお勧めします。
guest

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
hirohiro

総合スコア2068

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

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

hirohiro

2016/08/16 13:40 編集

私が書いた例はどうなってるかを確認するためのもので、逆に「それが出来たから何がうれしいのか?」はさっぱり分からないと思います。MDNのほうにはその部分が書かれていますが基本な事は分かってる前提で書かれているので、いきなり読むと理解を進めにくいこともありますね。ただここが読めるとJavascriptの世界が広がると思います。
aaaaaaaa

2016/08/18 10:54

ご回答有難うございました。 重ね重ね申し訳ありませんが質問させてください。 >>this !== window これは、this参照がグローバルオブジェクトでなければ・・・という式だと認識しました。aと{x:2}は、グローバルオブジェクトのプロパティですが、「// 2:3」と返ってくるということは、グローバルオブジェクトとグローバルオブジェクトのプロパティは、等価ということなのでしょうか。
hirohiro

2016/08/18 11:36 編集

a = {x:2}; これはwindowのプロパティ「a」が参照しているオブジェクトになります。 a={x:1,b:{x:2}}; もしこう書いたら、グローバル変数aに{x:1,b:{x:2}}このオブジェクトへの参照が代入され、a.bに{x:2}このオブジェクトへの参照が代入されます そしてこんな風に参照可能です。「console.log(a.b.x); //2」 hoge.call(a,3); // 2:3 これによって関数hoge内のthisはaですよと指定しています。 aは{x:2}を参照しておりwindowオブジェクトでは無いので、ifを通過しています。
aaaaaaaa

2016/08/19 07:28

ご回答有難うございました。 bindの第一引数に、nulやundefinedを指定するとグローバルオブジェクトを参照するようにcallも参照するからundefinedが第一引数だと「//false:37」になるわけですね。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問