本系列文章根据《爱前端邵山欢老师深入浅出的js面向对象》视频整理归纳
函数上下文
在 JavaScript 中,函数的上下文是有规律可循的,基本可以分为以下几项:
规律一:函数用圆括号调用,函数上下文是 window 对象。
如下,函数 function fun(){} 的上下文是什么,即 this 的指向是谁, 不是根据它如何定义,而是根据如何调用得出的,在这里fun() 是通过函数名加圆括号直接调用的,所以此时函数的上下文,即 this 指向就是 window 对象。
function fun(){
var a = 1;
console.log(this.a);
}
var a = 2;
fun(); // 输出为 2
而我们知道,所有的全局变量都是 window 对象的属性(函数里面的局部变量,不是 window 的属性,不是任何东西的属性,就是一个变量。),所以在这里最后输出 2。
规律二:函数如果作为一个对象的方法,对象使用点方法进行调用,那么函数的上下文就是这个对象。
如下,定义一个函数 fun,并将其作为新建对象 obj 的一个属性 c,通过 对象.函数() 的方法进行调用:
function fun(){
var a = 1;
console.log(this.a);
}
var obj = {
'a' = 2,
'b' = 3,
'c' = fun
};
obj.c(); // 输出为 2
此时函数的上下文为 obj 对象,即函数里的 this 指向这个对象 obj,所以输出为 2。
规律三:函数是事件处理函数,那么函数的上下文就是触发这个事件的对象。
如下,我们创建三个 div,给其设定背景色,定义一个函数 fun,将其作为三个 DOM 元素的事件处理函数,使点击不同的 div 时,相应 div 背景色发生变化。
HTML 部分
<div id="box1"></div>
<div id="box2"></div>
<div id="box3"></div>
CSS 部分
div {
width: 50px;
height: 50px;
background-color: red;
margin: 10px 0;
}
JavaScript 部分
function fun(){
this.style.backgroundColor = 'blue';
}
document.getElementById('box1').onclick = fun;
document.getElementById('box2').onclick = fun;
document.getElementById('box3').onclick = fun;
在这里函数不会执行,直到我们点击了某一个 div 标签,此时点击谁,函数上下文就是谁,this 就指向谁。
规律四:函数被定时器调用时,上下文是 window 对象。
如下,定义函数 fun,在定时器 setInterval 中调用:
funcyion fun(){
console.log(this.a);
}
var a = 1;
setInterval(fun,1000); // 每隔一秒输出一次 1
因为函数被定时器调用,上下文是 window 对象,所以输出为 1。
我们经常会因为对于被定时器调用的函数 this 指向理解有误,而犯如下的错误:
点击 div,使之2秒后变为蓝色:
var box = getElementById('box');
box.onclick = function(){
setTimeout(function(){
this.style.backgroundColor = 'blue';
},2000);
}
上述代码在点击 div 2秒后会报错,因为我们理所当然的认为 this 指向触发事件处理函数的对象,但其实在这里,函数是在定时器内调用的,所以函数的上下文是 window 对象,即这里的 this 指向 window 对象,而 window 对象没有背景颜色这个属性,所以会报错。
而我们要实现最初想达成的效果,可以通过在定时器外面的事件处理函数中,把 this 存为局部变量 that 来备份上下文,因为在这里 this 就是 box 这个元素。
var box = document.getElementById('box');
box.onclick = function (){
var that = this;
setTimeout(function(){
that.style.backgroundColor = 'blue';
},2000)
}
规律五:数组中存放的函数,被数组索引调用,函数上下文就是这个数组。
如下,定义一个函数 fun,使之成为数组的一项:
function fun(){
console.log(this.length);
}
var arr = [fun,1,2];
arr[0](); // 3
在这里函数是从数组中索引并加圆括号进行调用的,所以最终调用者可以认为是这个数组,所以上下文就是这个数组,即 this 的指向也是这个数组。
综合练习:
function fun1(c,d,e,f,g){
console.log(this.length);
}
function fun2(a,b){
arguments[0](4,5,6,7,8);
}
fun2(fun1,1,2,3);
输出为 4
解析:
涉及知识点一: arguments.callee
在函数内部,我们无法通过 this 得到函数自身,必须使用arguments.callee。
function fun(){
console.log(arguments.callee === fun);
}
fun(); // 输出 true
涉及知识点二:函数的长度是什么
函数的长度是形参列表的长度(即函数定义时写在 fun 圆括号内的字母个数),与实参长度无关(即函数调用时圆括号内的参数个数),
即 arguments.callee.length = 形参个数
而 arguments.length = 实参个数
function fun(a,b,c,d,e){ // 此处的 a,b,c,d,e 是形参
console.log(arguments.callee.length); // 5
console.log(arguments.length); // 6
}
fun(1,2,3,4,5,6); // 输出 5 6
// 此处的 1,2,3,4,5,6 为实参
此时再回头看我们的练习题, fun1 作为 fun2 的实参进行调用,而 fun2 中的 arguments[0] 就是 fun2,所以函数的最终调用,是 arguments 对象进行方括号索引得到 fun1 ,然后加圆括号执行调用,而 arguments 是一个对象,可以参照我们总结的规律五得出,fun1 函数中的 this 指向 arguments 对象,那么最终我们所要输出的就是 arguments.length,它的值则为fun2(fun1,1,2,3)的实参个数,所以最终输出为 4
如果我们将 fun1 函数变成:
function fun1(){
console.log(this.callee.length);
}
那么结果将输出 arguments.callee.length,即 fun2 的形参个数 2