こんにちは、
JavaScriptのthisはややクセのある動作をするように思えるかもしれませんが、
- thisが何を指すかは関数の呼び出し方で決定する
- thisは関数スコープに存在する特殊な変数である
インスタンスとしてのthis
では、
function A(name){
this.name = name;
}
A.prototype.getThis=function(){
return this;
};
var a = new A('aaa');
console.log(a);
console.log(a === a.getThis()); // true
new演算子を使用すると、
ただし、
function AA(name){
return {name:'bbb'};
}
var aa = new AA('aaa');
console.log(aa);
console.log(aa instanceof AA); // false
このように、
thisが指すもの
さて、
- メソッドを変数に代入してから呼び出した場合
- イベントとして呼ばれた場合
- setTimeout、
setIntervalで呼ばれた場合
具体的には下記のようなケースです。
function A(name){
this.name = name;
}
A.prototype.logThis=function(){
console.log(this);
};
var a = new A('aaa');
var a_logThis = a.logThis;
a_logThis(); // Global
setTimeout(a.logThis, 0); // Global
実はJavaScriptにおいてthisは非常に簡単なルールで決定されます。そのルールとは、
つまり、
callとapply
さて、
function A(name){
this.name = name;
}
A.prototype.logThis=function(){
console.log(this);
};
var a = new A('aaa');
var a_logThis = a.logThis;
a_logThis.call(a); // {name:'aaa'}
callは第1引数にthisとなるオブジェクトを、
applyは第1引数にthisとなるオブジェクトを、
var log = function(){
console.log(this);
console.log(arguments);
};
log.call({id:1}, 2, 3);
// {id:1} , [2, 3]
log.apply({id:2}, [2, 3]);
// {id:2} , [2, 3]
callとapplyの違いは引数を個別に指定するか、
ただ、
thisとarguments
ところで、
クロージャについて説明した際にJavaScriptの変数は関数単位であると説明しましたが、
thisとイベント
さて、
span要素にdata-cbjs-tooltipというカスタム属性をつけておくと、
var addEvent = document.addEventListener ?
function (node, type, listener){
node.addEventListener(type, listener, false);
} :
function (node, type, listener){
node.attachEvent('on' + type, listener);
};
var TEXT = 'textContent' in document.documentElement ?
'textContent' : 'innerText';
function Tooltip(element, text){
this.element = element;
this.text = text;
// thisを変数に代入
var that = this;
addEvent(element, 'mouseover', function(evt){
//ここはfunctionの中なので、thisが変わっている
// clientXで表示領域内でのマウス位置を取得
var x = evt.clientX;
var y = evt.clientY;
// クロージャのthatからshowを呼び出す
that.show(x, y);
});
addEvent(element, 'mouseout', function(evt){
//ここはfunctionの中なので、thisが変わっている
// クロージャのthatからhideを呼び出す
that.hide();
});
}
Tooltip.prototype = {
show: function(x, y){
Tooltip.tip[TEXT] = this.text;
Tooltip.tip.style.left = x + 10 + 'px';
Tooltip.tip.style.top = y + 10 + 'px';
Tooltip.tip.style.display = 'block';
},
hide: function(){
Tooltip.tip.style.display = 'none';
}
};
addEvent(window, 'load', function(){
// 入れ物となる要素を作る
Tooltip.tip = document.createElement('div');
Tooltip.tip.id = 'cbjs-tooltip-element';
document.body.appendChild(Tooltip.tip);
var spans = document.getElementsByTagName('span');
for (var i = 0, len = spans.length;i < len; i++){
var span = spans[i];
// data-cbjs-tooltip属性の中身を取り出す
var tip = span.getAttribute('data-cbjs-tooltip');
if (tip) {
new Tooltip(span, tip);
}
}
});
ポイントはTooltipの中の次の部分です。
function Tooltip(element, text){
// thisを変数に代入
var that = this;
addEvent(element, 'mouseout', function(evt){
//ここはfunctionの中なので、thisが変わっている
// クロージャのthatからhideを呼び出す
that.hide();
});
}
addEventの引数に渡す関数の中ではthisもargumentsも違うものに置き換わっているので、
ここはthisとクロージャの両方が関わってきているので、
さて、
#cbjs-tooltip-element{
position:fixed;
display:none;
background:#fff;
color:#000;
border:1px solid #0080FF;
padding:10px;
margin:0px;
text-align:left;
line-height:1.2;
}
こちらもシンプルですね。ただ、
CSSとJavaScript両方が関係するので、
if の部分には簡単な条件を指定することができ、
<!--[if IE 6]>
<style>
#cbjs-tooltip-element{
position:absolute;
}
</style>
<script>
(function(proto){
var root = document.compatMode === 'BackCompat' ?
document.body : document.documentElement;
var show = proto.show;
proto.show = function(x, y){
x += root.scrollLeft;
y += root.scrollTop;
show.call(this, x, y);
};
})(Tooltip.prototype)
</script>
<![endif]-->
IE 6の場合はpositionをabsoluteにして、
なお、
なお、
- ツールチップ デモ: A JS
まとめ
3回に分けてプロトタイプベースの継承とthisについて学びました。継承はクロージャとも関わってくるため、
次回はアニメーションの基礎とCSSに関係した部分について解説します。