Javascript小学生都知道了javascript中的函数调用时会 隐性的接收两个附加的参数:this和arguments。参数this在javascript编程中占据中很是重要的地位,它的值取决于调用的模式。总的来讲Javascript中函数一共有4中调用模式:方法调用模式、普通函数调用模式、构造器调用模式、apply/call调用模式。这些模式在如何初始化关键参数this上存在差别。“可能还有小伙伴不知道它们之间的区别,那我就勉为其难撸一撸吧!”javascript
方法调用模式:函数是在某个明确的上下文对象中调用的,this绑定的是那个上下文对象。
html
普通函数调用模式:默认状况下,若是函数是被直接调用的,若是在严格模式下,就绑定到undefined,不然绑定到全局对象。java
构造器调用模式:函数经过new操做符调用,this绑定的是新建立的对象。web
apply/call调用模式:函数经过apply或者call调用,this绑定的是指定的对象,若是把null或者undefined做为this的绑定对象传入call/apply,在调用时会被忽略,实际应用的是默认绑定规则。ajax
下面举一个简单的综合例子:编程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
var
a=2;
function
foo(b) {
this
.b=b;
console.log(
this
.a);
}
var
obj={
a:4,
foo:foo
};
foo();
//普通函数调用,输出2
obj.foo();
//做为对象方法调用,输出4
foo.call(obj);
//call显示绑定,输出4
foo.call(
null
);
//输出2
var
bar=
new
foo(8);
//构造函数调用,输出了undefined(由console.log(a)打印)
console.log(bar.b)
//输出8
|
上面的例子在浏览器环境中已经测试经过了,在Node环境中在函数外面定义的变量不会成为全局对象的属性,理解这个例子的输出结果对于上面提到的四种调用方式大概就理解了。在大多数状况下,每次遇到函数调用(注意是每次,无论调用时这个函数位于哪里,只要遇到调用这个函数就要停下来肯定里面的this),只要仔细区分上面的四种调用模式,就能很快肯定函数中的this绑定的是哪一个对象。可是有一类状况很特殊,你不能一眼或者两眼就能看出函数调用的模式,那就是JavaScript中的异步函数调用。下面介绍几种实际开发过程当中经常使用的异步函数调用中this绑定的例子。浏览器
超时调用须要使用 window 对象的 setTimeout() 方法,它接受两个参数:要执行的代码和以毫秒表示的时间(即在执行代码前须要等待多少毫秒)。其中,第一个参数能够是一个包含JavaScript代码的字符串(就和在eval() 函数中使用的字符串同样),也能够是一个函数。setTimeout() 的第二个参数告诉 JavaScript 再过多长时间把当前任务添加到队列中。若是队列是空的,那么添加的代码会当即执行;若是队列不是空的,那么它就要等前面的代码执行完了之后再执行。
下面对setTimeout()的两次调用都会在一秒钟后显示一个警告框。
app
1
2
3
|
setTimeout(
function
() {
alert(
"Hello world!"
);
}, 1000);
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
var
a=5;
function
foo() {
this
.a++;
setTimeout(
function
(){
console.log(
this
.++a);
},1000);
}
var
obj={
a:2
};
foo.call(obj);
console.log(obj.a);
|
在浏览器环境测试,上述代码的输出结果是3 6,为何会是3 和6呢,首先咱们知道超时函数的回调函数是异步的,因此先输出的是最后一条语句执行的结果。foo.call(obj)语句经过call绑定obj,因此foo函数执行时内部的this绑定的是obj,因此this.a++使得obj的a属性增长了1.接下来经过超时函数设置回调的匿名函数一秒后加入到任务队列。因此在执行最后一条语句时,超时函数里的回调函数尚未执行,因此最后一条语句输出为3,接下来当任务队列里的回调函数被调用执行时,输出的是6,也就是全局变量a加1,所以超时调用的回调代码都是在全局做用域中执行的,函数中的this的值指向全局对象,这里补充说明一下在严格模式下this绑定的是undefined。异步
那么间歇调用setInterval方法是什么状况呢。稍微小改一下上面的代码:函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
var
a=5;
function
foo() {
this
.a++;
setInterval(
function
(){
console.log(++
this
.a);
},1000);
}
var
obj={
a:2
};
foo.call(obj);
console.log(obj.a);
|
上面的代码输出为3 6 7 8 9·····
也就是说间歇调用和超时调用的状况同样,回调函数也是在全局环境中执行的。
(1)HTML事件处理程序
1
2
|
<!-- 输出 "Click Me" -->
<
input
type
=
"button"
value
=
"Click Me"
onclick
=
"alert(this.value)"
>
|
因此你以为你懂这个东东了,《JS高程》红宝书中说,直接在HTML中添加事件处理会动态建立一个事件处理函数,执行这个函数时的this为目标元素。那咱们看个例子,瞧瞧本身是否是真的懂了。
点击一个div,让div里的文本从5每隔一秒递减一直到0
<!doctype html><html lang="en"><head> <meta charset="UTF-8"> <title></title><script type="text/javascript"> function test(){ this.innerHTML='5'; var timer=setInterval(function(){ if (this.innerHTML==1) { clearInterval(timer); } this.innerHTML--; },1000) }</script></head><body><div onclick="test()">沐浴星光</div> </body></html>
<!doctype html><html lang="en"><head> <meta charset="UTF-8"> <title></title><script type="text/javascript"> function test(target){ target.innerHTML='5'; var timer=setInterval(function(){ if (target.innerHTML==1) { clearInterval(timer); } target.innerHTML--; },1000) }</script></head><body><div onclick="test(event.target)">沐浴星光</div> </body></html>
(2)DOM0 级事件处理程序
使用DOM0级方法指定的事件处理程序被认为是元素的方法。所以,这时候的事件处理程序是在元素的做用域中运行;换句话说,程序中的 this 引用当前元素。来看一个例子。
1
2
3
4
|
var
btn = document.getElementById(
"myBtn"
);
btn.onclick =
function
(){
alert(
this
.id);
//"myBtn"
};
|
(3)DOM2 级事件处理程序
1
2
3
4
5
|
var
btn = document.getElementById(
"myBtn"
);
btn.addEventListener(
"click"
,
function
(){
alert(
this
.id);
//"myBtn"
},
false
);
|
1
2
3
4
|
var
btn = document.getElementById(
"myBtn"
);
btn.attachEvent(
"onclick"
,
function
(){
alert(
this
=== window);
//true
});
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
functionJSClass(){
this
.m_Text =
'division element'
;
this
.m_Element = document.createElement(
'div'
);
this
.m_Element.innerHTML =
this
.m_Text;
this
.m_Element.addEventListener(
'click'
,
this
.func);
// this.m_Element.onclick = this.func;
}
JSClass.prototype.Render=
function
(){
document.body.appendChild(
this
.m_Element);
}
JSClass.prototype.func =
function
(){
alert(
this
.m_Text);
};
var
jc =newJSClass();
jc.Render();
// add div
jc.func();
// 输出 division element
|
click添加的div元素division element会输出underfined,为何?
答案:division element undefined
解析:第一次输出很好理解,func()做为对象的方法调用,因此输出division element,点击添加的元素时,this其实已经指向this.m_Element,也就是事件的目标元素(事件对象的currentTarget属性值-或者说是注册事件处理程序的元素),由于是this.m_Element调用的addEventListener函数,因此内部的this全指向它了,而这个元素并无m_Text属性,因此输出undefined。
1
2
3
4
5
|
<ul id=
"myLinks"
>
<li id=
"goSomewhere"
>Go somewhere</li>
<li id=
"doSomething"
>Do something</li>
<li id=
"sayHi"
>Say hi</li>
</ul>
|
1
2
3
4
5
6
7
8
9
10
11
12
|
var
item1 = document.getElementById(
"goSomewhere"
);
var
item2 = document.getElementById(
"doSomething"
);
var
item3 = document.getElementById(
"sayHi"
);
item1.addEventListener(
"click"
,
function
(event){
alert(
this
.id);
//"goSomewhere"
});
item2.addEventListener(
"click"
,
function
(event){
alert(
this
.id);
//"oSomething"
});
item3.addEventListener(
"click"
,
function
(event){
alert(
this
.id);
//"sayHi"
});
|
1
2
3
4
|
var
list=document.getElementById(
'"myLinks'
);
list.addEventListener(
'click'
,
function
(event){
alert(
this
.id);
})
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<!DOCTYPE html>
<
html
>
<
head
>
<
meta
charset
=
"utf-8"
/>
<
title
></
title
>
</
head
>
<
body
>
<
ul
id
=
"myLinks"
>
<
li
id
=
"goSomewhere"
>Go somewhere</
li
>
<
li
id
=
"doSomething"
>Do something</
li
>
<
li
id
=
"sayHi"
>Say hi</
li
>
</
ul
>
</
body
>
<
script
type
=
"text/javascript"
>
var list=document.getElementById('myLinks');
list.addEventListener('click',function(event){
alert(this.id);
})
</
script
>
</
html
>
|
测试结果
也就是说不论点击哪个列表,弹出的是父元素的ID,那么该怎么改写才能实现预期的功能呢?咱们知道事件对象event有不少属性,其中包括两个属性currentTarget和target,在事件处理程序内部,对象this 始终等于currentTarget 的值(也就是添加事件处理程序的元素),而target则只包含事件的实际目标。若是直接将事件处理程序指定给了目标元素则 this、currentTarget 和target包含相同的值。若是事件处理程序是被委托代理的,那么这些值通常不一样。来看下面的例子。
1
2
3
4
5
6
|
var
list=document.getElementById(
'myLinks'
);
list.addEventListener(
'click'
,
function
(event){
alert(event.currentTarget===list)
//ture
alert(
this
===list)
//ture
});
|
这也解释了上面错误的事件委托为何一直弹出“myLinks”了。正确的事件委托程序是:
1
2
3
4
|
var
list=document.getElementById(
'myLinks'
);
list.addEventListener(
'click'
,
function
(event){
alert(event.target.id);
})
|
最后简要说明一下ajax请求中的this
1
2
3
4
5
6
7
8
9
10
11
12
|
var
xhr =
new
XMLHttpRequest();
xhr.onreadystatechange =
function
(){
if
(xhr.readyState == 4){
if
((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
}
else
{
alert(
"Request was unsuccessful: "
+ xhr.status);
}
}
};
xhr.open(
"get"
,
"example.txt"
,
true
);
xhr.send(
null
);
|
这个例子在onreadystatechange事件处理程序中使用了xhr对象,没有使用this对象,缘由是onreadystatechange事件处理程序的做用域问题。若是使用this对象,在有的浏览器中会致使函数执行失败,或者致使错误发生。所以,使用实际的XHR对象实例变量是较为可靠的一种方式。
参考:
《JavaScript高级程序设计》
《You Don't Konw JS:This&Object Prototypes》
《JavaScript语言精粹》