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

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

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

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

Q&A

解決済

3回答

5489閲覧

JavaScriptのprototypeで、バインドした関数を利用するには?

nnahito

総合スコア2004

JavaScript

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

0グッド

0クリップ

投稿2017/09/05 16:28

概要

DIV要素をドラッグできるようにしたいと思い、コードを書いています。
そのコードをJavaScriptのprototypeを使っているのですが、うまく関数を実行できません。
その理由と解決策が知りたいです。

質問本文

DIV要素をドラッグできるようにしたいと思い、コードを書いています。
以下のコードをJavaScriptのprototypeを使っているのですが、どうも、孤立したメソッドのように扱われているようです。

prototypeの中でメソッドをバインドして利用することはできないのでしょうか?

一応、 Dragpos.prototype.testのような関数を定義し、this.test()のように呼び出すことは可能でした。
そのときにthis.panelconsole.logで表示させてもうまく表示ができます。

しかし、Dragpos.prototype.moveは、「TypeError: this.move is not a function」と出ます。
この理由がまだ勉強中なのでわかりません。
ご存知の方がいらっしゃいましたら、ご教授ください。
よろしくお願いいたします。

JavaScript

1var Dragpos = (function() { 2 // プロパティ定義エリア 3 var panel = ''; // ドラッグさせたいオブジェクトを保存するもの 4 var drag = false; // trueのとき、オブジェクトが移動するというフラグ 5 var mousemove = {}; 6 7 /** 8 * コンストラクタ 9 * @param {Object} element ドラッグさせたいオブジェクト 10 * @constructor 11 */ 12 function Dragpos(element) { 13 // 初期値で与えられた要素をプロパティとして持たせる 14 this.panel = document.getElementById(element); 15 16 // フラグを(一応)初期化しておく 17 this.drag = false; 18 19 // アクションをすべてバインド 20 document.addEventListener('mousedown', this.mouseDown, false); 21 document.addEventListener('mouseup', this.mouseUp, false); 22 document.addEventListener('mousemove', this.mouseMove, false); 23 24 } 25 26 27 // クリックされたとき 28 Dragpos.prototype.mouseDown = function(e){ 29 drag = true; 30 } 31 32 33 34 // クリックが離されたとき 35 Dragpos.prototype.mouseUp = function(e){ 36 drag = false; 37 } 38 39 40 41 // マウスが移動したとき 42 Dragpos.prototype.mouseMove = function(e){ 43 if (drag == true){ 44 //this.move(e); 45 console.log(this.panel); 46 } 47 } 48 49 50 51 // 要素の座標を移動させる処理 52 Dragpos.prototype.move = function (e) { 53 var offsetX; // スクロール位置(横) 54 var offsetY; // スクロール位置(縦) 55 var x; // x座標 56 var y; // y座標 57 var rect = {}; // 四角形の(x, y, w, h)が入る 58 59 // 四角形の(x, y, w, h) = (X座標, Y座標, 幅, 高さ)を取得 60 rect.x = this.panel.offsetLeft; 61 rect.y = this.panel.offsetTop; 62 rect.w = this.panel.clientWidth; 63 rect.h = this.panel.clientHeight; 64 65 offsetX = rect.w / 2; 66 offsetY = rect.h / 2; 67 68 if (e.pageX > rect.x && e.pageX < rect.x + rect.w) { 69 if (e.pageY > rect.y && e.pageY < rect.y + rect.h) { 70 x = e.pageX - offsetX; 71 y = e.pageY - offsetY; 72 this.panel.style.position = 'absolute'; 73 this.panel.style.top = y + 'px'; 74 this.panel.style.left = x + 'px'; 75 } 76 } 77 78 } 79 80 return Dragpos; 81})(); 82

html

1<!DOCTYPE html> 2<html lang="ja"> 3 4<head> 5 <!-- ページタイトル --> 6 <title>ドラッグテスト</title> 7 8 <!-- メタタグエリア --> 9 <meta charset="utf-8"> 10 11 <!-- JS読み込み --> 12 <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script> 13 <script src="dragpos.js"></script> 14 15 <!-- ページ内CSS --> 16 <style> 17 #foo { 18 width: 150px; 19 height: 150px; 20 margin: 20px; 21 color: #CCC; 22 background: #EFEFEF; 23 border: 3px dotted #DDD; 24 font-size: 2em; 25 font-weight: 900; 26 text-align: center; 27 line-height: 150px; 28 } 29 </style> 30 31</head> 32 33<body> 34 35 <div id="foo" draggable="true">Move</div> 36 37 38 <script> 39 $(document).ready(function(){ 40 // 要素を動かせるようにする 41 var dp = new Dragpos('foo'); 42 }); 43 </script> 44 45</body> 46 47</html> 48

参考

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

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

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

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

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

guest

回答3

0

ベストアンサー

問題は下記の部分です。

JavaScript

1 // アクションをすべてバインド 2 document.addEventListener('mousedown', this.mouseDown, false); 3 document.addEventListener('mouseup', this.mouseUp, false); 4 document.addEventListener('mousemove', this.mouseMove, false);

JavaScriptでコールバック関数を渡す場合、いわゆるthis問題がおきます。(アロー関数ではない)関数宣言や関数式で作られた関数自体にthisが何であるかという情報は含まれていません。そのため、呼び出す時の状況に応じてthisが変わります。よくあるメソッド呼び出しa.f()という形であれば、レシーバーathisとして設定されますが、x = a.fなどと関数だけ取り出してからx()としても、thisaにはなりません。つまり、コールバック関数のように、関数として渡してしまうと、メソッド呼び出しのようにレシーバーがthisになるとは限らなくなります。

方法はいくつかありますが、ES5でも使用できる方法としてbind()thisが何であるかを束縛することです。

JavaScript

1 // アクションをすべてバインド 2 document.addEventListener('mousedown', this.mouseDown.bind(this), false); 3 document.addEventListener('mouseup', this.mouseUp.bind(this), false); 4 document.addEventListener('mousemove', this.mouseMove.bind(this), false);

※ 将来的なバグを防ぐために、全てに.bind(this)を付けることを推奨します。

上記のようにbind()を使うと、コールバックとして呼び出される際に、どのような呼び出し方であったとしても、その関数内でのthisbind()で指定したオブジェクトに束縛されます。これで現在起こっている問題は解決すると思います。

なお、コールバック関数を受け取る関数の中には、何をthisとしてそのコールバック関数を呼び出すかを指定できる物があります(ArrayのforEach()等)。addEventListener()では使用できませんが、そのよう関数ではbind()の代わりにそのような方法を用いることもできます。さらに、ES2015以降はアロー関数が使えますので、手段がいくつか増えます。

とにかく、コールバック関数についてはthisについて常に意識してください。ただのメソッド呼び出しの形の()無しであるa.fのように指定した場合、thisが想定外のオブジェクトになって、うまくいくことは少ないです。他にも関数式を直接書いた場合もthis問題が起きますので、同じく注意が必要です。

投稿2017/09/05 23:04

raccy

総合スコア21733

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

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

nnahito

2017/09/05 23:56

ご回答ありがとうございます。 おっしゃるとおり、「 this.mouseDown.bind(this)」とするとうまく動きました。 これがJSのthis問題……始めて出会いました。 奥が深いですね…… もっと勉強させていただきます、ありがとうございます!!
guest

0

raccyさんの回答で正常に動作しそうなので、
私は他の軽微な不具合になりそうな箇所を補修します。

JavaScript

1var Person = (function() { 2 var name; 3 4 var Person = function(_name, _age){ 5 name = _name; 6 this.age = _age; 7 }; 8 9 Person.prototype.hello = function(){ 10 return "Hello, my name is " + name + ". age is " + this.age; 11 }; 12 13 return Person; 14})(); 15 16var miyabi = new Person("miyabi", 17); 17 18// ===== ここから検証開始 ===== 19 20console.log(miyabi); 21// Person {age: 17} <- nameが表示されない 22 23console.log(miyabi.hello()); 24// "Hello, my name is miyabi. age is 17" 25 26miyabi.name = "nnahito"; 27miyabi.age = 18; 28 29console.log(miyabi); 30// Person {age: 18, name: "nnahito"} 31 32console.log(miyabi.hello()); 33// "Hello, my name is miyabi. age is 18" <- nameが変わってない

これはよくあるクロージャーを利用したクラスの定義方法です。
この例の時、nameとageはそれぞれ以下のような扱いになります。

name: プライベート変数
即時実行関数の中で隠蔽され、インスタンスからはアクセスできない。

age: パブリック変数
外部から容易に書き換え可能なプロパティ

※このプライベート変数、パブリック変数というのはPHPの世界の呼び方であり、その認識で名付けた造語です。


なんでそんなことを説明するん?という話ですが…
このプライベート変数とパブリック変数は相互不干渉なので片方を書き換えても、もう片方の同名の変数は値が切り替わらない。

混同してるみたいだからちゃんと意識して使い分けてね!

JavaScript

1var Dragpos = (function() { 2 // これらはプライベート変数だね 3 var panel = ''; 4 var drag = false; 5 var mousemove = {}; 6 7 function Dragpos(element) { 8 // これらはthis.xxxだからパブリック変数を書き換えてるね 9 this.panel = document.getElementById(element); 10 this.drag = false; 11 } 12}

蛇足のおまけ

この方法のプライベート変数は片手落ちなので、
正しく理解して梱包出来なきゃ使わない方がいいっす。

JavaScript

1var Person = (function() { 2 var name; 3 var age; 4 5 var Person = function(_name, _age){ 6 name = _name; 7 age = _age; 8 }; 9 10 Person.prototype.hello = function(){ 11 return "Hello, my name is " + name + ". age is " + age; 12 }; 13 14 return Person; 15})(); 16 17var miyabi = new Person("miyabi", 17); 18var nnahito = new Person("nnahito", 18); 19 20console.log(miyabi.hello()); 21// "Hello, my name is nnahito. age is 18" <- miyabiどこいった 22 23console.log(nnahito.hello()); 24// "Hello, my name is nnahito. age is 18"

当然同じnameとage変数参照するんだからこうなるよね。
管理出来ないならおとなしくthis.xxxを書き換える設計にしたほうがいいと思う。

投稿2017/09/05 23:56

miyabi-sun

総合スコア21158

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

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

nnahito

2017/09/06 01:34

補足ありがとうございます。 先程、 var a = new Dragpos('hoge'); var b = new Dragpos('hoga'); とやると、「蛇足のおまけ」と同じ現象が生じました。。。 > 当然同じnameとage変数参照するんだから new でインスタンスが区切れていると思っていても、実際はそうでないんですね…なんでやねん。 JSは謎が多いです……
guest

0

下記スレッドを参考にしてみて下さい。

(2017/09/06 11:05追記)
サンプルコードを追記しました。

addEventListener + handleEvent

addEventListener は第二引数に handleEvent プロパティを持つオブジェクトを指定した場合、this 値を指定したオブジェクトに束縛することが出来ます。

JavaScript

1function Dragpos (element) { 2 // 中略 3 4 document.addEventListener('mousedown', this, false); 5 document.addEventListener('mouseup', this, false); 6 document.addEventListener('mousemove', this, false); 7} 8 9Dragpos.prototype.handleEvent = function handleEvent (event) { 10 switch (event.type) { 11 case 'mousedown': 12 return this.mouseDown.call(this, event); 13 case 'mouseup': 14 return this.mouseUp.call(this, event); 15 case 'mousemove': 16 return this.mouseMove.call(this, event); 17 } 18 19 throw new Error('unknown event type'); 20};

Function.prototype.bind

event.currentTarget に束縛された this 値を Function.prototype.bindnew Dragpos に束縛し直します。

JavaScript

1document.addEventListener('mousedown', this.mouseDown.bind(this), false); 2document.addEventListener('mouseup', this.mouseUp.bind(this), false); 3document.addEventListener('mousemove', this.mouseMove.bind(this), false);

jQuery.proxy

Function .prototype.bind と同様。

JavaScript

1document.addEventListener('mousedown', jQuery.proxy(this.mouseDown, this), false); 2document.addEventListener('mouseup', jQuery.proxy(this.mouseUp, this), false); 3document.addEventListener('mousemove', jQuery.proxy(this.mouseMove, this), false);

jQuery.prototype.on + event.data

this から取り出すのを諦めて、jQuery.prototype.onevent.data 経由で this 値を参照します。

JavaScript

1function Dragpos (element) { 2 // 中略 3 4 jQuery(document).on('mousemove', {thisArg: this}, this.mouseMove); 5} 6 7Dragpos.prototype.mouseMove = function mouseMove (event) { 8 var thisArg = event.data.thisArg; 9 10 if (thisArg.drag) { 11 console.log(thisArg.panel); 12 } 13};

Re: nnahito さん

投稿2017/09/05 16:36

編集2017/09/06 02:13
think49

総合スコア18156

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

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

nnahito

2017/09/05 16:49

ご回答ありがとうございます。 申し訳ないのですが、私の知識レベルが不足しており、どこを参考にすればよいかわかりませんでした… おそらく「(方法2) Function.prototype.bind を使う」かなと思い、 コンストラクタ部分に「this.move.bind(this);」を追記してみましたがだめでした……
think49

2017/09/06 02:14

親記事にサンプルコードを追記しました。 どの方法を選択するかは好みもありますが、個人的には handleEvent or bind の二択ですね。 本題ではないですが、jQuery#ready と addEventListener を併用しているのがやや気になりました。 ここまで書いたのであれば、jQuery は外してもいいのではと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問