函数中的this问题
在函数体的语句中,会出现this这个词,this就是函数的上下文
函数中this是谁,就说明函数的上下文是谁
函数中的this是谁,要看是如何调用的,因为this不是一成不变的
比如看下面的案例
<script> var obj = { a: 100, fun: function () { console.log(this.a); }, }; </script>
我们此时在obj对象中定义了一个fun函数,就是obj的属性
现在如果直接对象打点调用
obj.fun()
此时会输出100,说明上下文就是对象本身
如果此时把整个方法进行了一次赋值
<script> var obj = { a: 100, fun: function () { console.log(this.a); }, }; var f = obj.fun; f(); </script>
页面中会输出undefined,因为此时this的上下文不是obj了而是window了
如果在外部定义一个a,那么此时就睡输出外部a的值200
<script> var a = 200; var obj = { a: 100, fun: function () { console.log(this.a); }, }; var f = obj.fun; f(); </script>
1、直接圆括号执行,上下文是window对象
直接圆括号调用就是没有对象打点执行,不是方括号枚举执行,通常是从数组,对象中提取出来后单独执行的
<script> var a = 200; var obj = { a: 100, fun: function () { console.log(this.a); }, }; var f = obj.fun; f(); </script>
f()就是直接圆括号执行,因为这个f是从obj提取出来的
注意:
- 直接圆括号执行的this指向的是window对象
- 需要注意的是js中全局变量都是window对象的属性
- 还需要注意的是IIFE也属于直接圆括号调用的范畴,里面的this都是window对象
我们看IIFE
<script> var a = 200; var obj = { a: 100, fun: (function () { console.log(this.a); })() }; </script>
此时输出的就是200,IIFE的this指向的是window
小题目1:
<script> var xiaoming = { name: "小明", age: 25, sayHello: (function () { return this.age >= 18 ? "男生" : "未成年"; })(), }; console.log("大家好,我叫" + xiaoming.name + ",我是一个" + xiaoming.sayHello); </script>
答案是男生,这道题的重点是IIFE里面的this,上面我们说过了IIFE里面的this指向的是window,所以此时IIFE里面的this.age是undefined,由于undefined >= 18结果是false,三元表达式走后面的”未成年”
此时输出的说明this.age是小于18的
我们输出this.age看一下
<script> var xiaoming = { name: "小明", age: 25, sayHello: (function () { console.log(this.age) })(), }; xiaoming.sayHello() </script>
小题目2:
<script> var obj = { a: 100, fun: function () { var a = 300; console.log(this.a); }, }; var a = 200; var f = obj.fun; f(); </script>
输出的是200
切记this指向谁一定要看调用,此时我们发现,调用是圆括号直接执行的。所以我们就知道了,内部的this就是window,所以obj里面的所有的a都是障眼法。由于全局变量都是window对象的属性,
所以var a=200就是window.a = 200,此时弹出的结果就是200
2、从对象中调用或者数组中枚举执行的函数,上下文是这个对象或者数组
函数的实参是一个arguments对象
<script> function fun(a, b, c, d, e, f) { console.log(arguments); } fun(1, 2, 3, 4, 5); </script>
每一个函数都有一个属性是arguments,值是一个类数组对象
什么是类数组对象?
类数组对象和数组很像,本质是对象,拥有数组的length属性,有对应的下标索引值。函数的arguments或者document.get**获取DOM的时候返回对象类型都是类数组对象,因为这些对象虽然看似数组,但
是没有数组的能力,不能进行push等等操作
函数中this是上下文,需要看如何调用,如果想表达函数自己,用arguments.callee
<script> function fun() { console.log(arguments.callee == fun); } fun(); </script>
小题目3:
<script> function fun1(a, b, c) { arguments[0](); } function fun2(a, b, c, d, e) { consloe.log(this.length); } fun1(fun2, 1, 2, 3, 4, 5, 6); </script>
此时this是谁要看调用。fun1在调用的,fun1调用的时候函数的执行arguments[0]的执行,因为arguments是fun1的实参列表,所以第0项就是fun2函数,所以符合规则2;fun2函数中的this指的就是fun1函数的
arguments类数组对象,所以length就是7
小题目4:
此时我们把上面的题目升级
<script> function fun1(a, b, c) { arguments[0](1, 2, 3, 4, 5, 6); } function fun2(a, b, c, d, e) { console.log(this.length); console.log(arguments.length); console.log(arguments.callee.length); console.log(this.callee.length); } fun1(fun2, 1, 2, 3, 4, 5, 6); </script>
解析:通过分析知道了fun2中的this指的是fun1函数,所以此时this.length指的就是fun1的arguments类数组对象(因为是类数组枚举执行的符合规则2);
arguments本身是fun2函数自己的实参列表,所以长度是6(调用的时候传了1~6的参数);
arguments.callee是fun2函数自己,所以length就是形参列表为5;
this.callee.length指的就是fun1的形参列表为3
小题目5:
var m = 2; var obj = { fun1: function () { return this.fun2(); }, fun2: fun2, m: 4, }; function fun2() { return this.m; } console.log(obj.fun1());
小题目6:
var num = 1; var obj = { num: 2, fun: (function () { var num = 3; this.num += 4; return function () { this.num *= 5; num *= 6; console.log(num); }; })(), }; obj.fun(); obj.fun(); console.log(num); console.log(obj.num); var f1 = obj.fun; f1(); console.log(num); console.log(obj.num); var f2 = obj.fun; f2(); console.log(num);
小题目7:
var length = 1; var obj = { length: 10, b: [ { length: 20, fun: function () { console.log(this.length); }, }, ], }; var arr = [obj, obj.b, obj.b[0], obj.b[0].fun]; arr[0].b[0].fun(); arr[1][0].fun(); arr[2].fun(); arr[3]();
3、定时器直接调用,上下文是window对象
var a = 100; function fun() { console.log(this.a); } setInterval(fun, 1000);
需要注意的是定时器调用和定时器函数内部调用是有区别的
下面代码是定时器在调用obj.fun函数,所以调用者是定时器
var obj = { a: 300, fun: function () { console.log(this.a++); }, }; var a = 100; setInterval(obj.fun, 1000);
下面的代码本质是obj在调用函数,所以上下文是obj
var obj = { a: 300, fun: function () { console.log(this.a++); }, }; var a = 100; setInterval(function () { obj.fun(); }, 1000);
4、DOM事件中的this,指的是触发事件的这个DOM元素
var box1 = document.getElementById('box1'); var box2 = document.getElementById('box2'); var box3 = document.getElementById('box3'); var box4 = document.getElementById('box4'); function changeColor() { this.style.backgroundColor = 'purple' } box1.onclick = changeColor; box2.onclick = changeColor; box3.onclick = changeColor; box4.onclick = changeColor;
此时点击的元素变为紫色
5、call()和apply()可以设置函数的上下文
函数的上下文主要是看谁在调用,但是我们可以通过call()和apply()去设置函数的上下文。
call()和apply()本质就是调用函数的同时,指定上下文
比如我们有一个changeSex的函数,它的作用是修改sex的属性。
此时有一个xiaohong对象,sex为女
此时我们调用这个changeSex函数,强行将函数的上下文绑定为xiaohong
function changeSex() { if (this.sex == "男") { this.sex = "女"; } else { this.sex = "男"; } console.log(this); } var xiaohong = { name: "小红", sex: "女", }; changeSex.call(xiaohong); console.log(xiaohong.sex);
apply函数也有同样的功能
changeSex.apply(xiaohong)
需要注意的是call()和apply()的本质核心是有区别的:主要是语法上的区别,call是接受参数,apply是接受数组
call方法要求,所有的参数在上下文对象后面一一罗列
function person(name, age, sex) { this.name = name; this.age = age; this.sex = sex; console.log(this) } var xiaoming = { name: "小明", age: 3, sex: "男", }; person.call(xiaoming, "小红",5,"女");
此时我们换成apply,apply要求所有的参数必须规整到一个数组中
function person(name, age, sex) { this.name = name; this.age = age; this.sex = sex; console.log(this) } var xiaoming = { name: "小明", age: 3, sex: "男", }; person.apply(xiaoming, ["小红",15,"女"])
apply本质上只要求两个参数,第二个参数是一个数组集合
在使用结果上两种方式都是一样的
小题目8:
function fun1() { fun2.apply(obj, arguments); } function fun2(a, b, c) { console.log(obj); console.log(a); console.log(b); console.log(c); } var obj = { name: "小明", sex: "男", }; fun1("小红", "小刚", "小兰");
此时你会发现apply有一个功能是将第二个参数将数组进行结构,变成一个个的罗列参数,比如我们传进arguments是一个类数组对象,但是我们在fun2函数接受的a,b,c形参中给进行了解构,也就是分别变成
了小红、小刚、小兰
小题目9:
利用Math方法查找数组的最大值
Math.max.apply(null, [5432,123,5342,43,4,6,48,43,43,43,48,94,43,51,123,2])