函数的上下文

函数的上下文

概述

在函数体的语句中,会出现this这个词,this就是函数的上下文

函数中this是谁,就说明函数的上下文是谁

函数中的this是谁,要看是如何调用的,因为this不是一成不变的

比如我们看下面的例子

var obj = {
	a: 100,
	fun: function() {
		console.log(this.a);
	}
};

我们此时在obj对象中定义了一个fun函数,就是obj的属性

现在如果直接对象打点调用

obj.fun();

此时会弹出100,说明上下文就是对象本身

如果此时我们将整个方法进行一次赋值

var obj = {
	a: 100,
	fun: function() {
		console.log(this.a);
	}
};
var f = obj.fun;
f();

页面中就会弹出undefined,因为此时this的上下文不是obj了,而是window

规则1:直接圆括号执行,上下文是window对象

什么叫做直接圆括号调用,就是没有对象打点执行,不是方括号枚举执行,通常是从数组、对象中提取出来后单独执行的

var obj = {
	a: 100,
    fun: function() {
        alert(this.a);
    }
};
var f = obj.fun;
f();

f()就是直接圆括号执行,因为这个f是从obj提取出来的

  • 直接圆括号执行的this指向的是window对象
  • 需要注意的是js中全局变量都是window对象的属性
  • 还需要注意的是IIFE也属于直接圆括号调用的范畴,里面的this都是window对象
var a = 300;
var obj = {
    a: 100,
    b: (function() {
        console.log(this.a)
    })()
};

弹出的内容是300,一位IIFE的this指向的是window

小题目

企业面试题

var xiaohong = {
	name: '小红',
    age: 25,
    sayHello: (function() {
        console.log(this.age)
        return this.age >= 18 ? '女士' : '女生'
    })()
}
console.log(`大家好,我叫${xiaohong.name},我是一个${xiaohong.sayHello}`);

答案是女生,这道题的重点是IIFE里面的this,上面我们说过了IIFE里面的this指向的是window,所以此时IIFE里面的this.ageundefined,由于undefined >= 18结果是false,三元表达式走后面的“女生”

小题目

var obj = {
	a: 100,
	fun: function() {
		var a = 200;
        console.log(this.a);
	}
}
var a = 300;
var f = obj.fun;
f();

答案是300,切记this指向谁一定要看调用,此时我们发现,调用是圆括号直接执行的。所以我们就知道了,内部的this就是window,所以obj里面的所有的a都是障眼法。由于全局变量都是window对象的属性,所以var a = 300就是window.a = 300,此时弹出的结果就是300

规则2:从对象中调用或者数组中枚举执行的函数,上下文就是这个对象或者数组

先补充点函数知识

函数的length指的是函数的形参列表长度

function fun(a, b, c, d, e, f) {

}
console.log(fun.length);

函数的实参是一个arguments对象

function fun(a, b, c, d, e, f) {
    console.log(arguments)
}
fun(1, 2, 3, '你好', '哈哈');

每一个函数都有一个属性是arguments,值是一个类数组对象

什么是类数组对象?

类数组对象和数组很像,本质是对象,拥有数组的length属性,有对应的下标索引值。函数的arguments或者我们document.getXX获取DOM的时候返回对象类型都是类数组对象,因为这些对象虽然看似数组,但是没有数组的能力,不能进行push等等操作

我们知道函数中this是上下文,需要看如何调用,如果想表达函数自己,用arguments.callee

function fun() {
    console.log(arguments.callee == fun)
}
fun();

小题目

function fun1(a, b, c) {
    arguments[0]();
}

function fun2(a, b, c, d, e) {
	console.log(this.length);
}

fun1(fun2, 9, 2, 4, 2, 34, 234);

此时this是看谁调用的。fun1在调用时,fun1调用的时候执行了函数arguments[0],因为argumentsfun1的实参列表,所以第0项就是fun2函数,所以符合规则2;fun2函数中的this指的就是fun1函数的arguments类数组对象,所以length就是7

小题目

此时我们把上面的题目升级

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, 9, 2, 4, 2, 34, 234);

解析:通过分析知道了fun2中的this指的是fun1函数,所以此时this.length指的就是fun1arguments类数组对象(因为是类数组枚举执行的符合规则2)

arguments本身是fun2函数自己的实参列表,所以长度是6(调用的时候传了1~6的参数)

我们知道arguments.calleefun2函数自己,所以length就是形参列表为5

this.callee.length指的就是fun1的形参列表为3

小题目

var m = 2;
var obj = {
	fun1: function() {
        return this.fun2();
    },
    fun2: fun2,
    m: 4
};
function fun2() {
    return this.m;
}
console.log(obj.fun1());

题目的核心就是上下文的传递

小题目

综合前面规则1和规则2出的面试题

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);

小题目

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元素

// 首先生成四个div
for (var i = 0; i < 4; i++) {
    var div = document.createElement('div');
    div.style.width = '100px';
    div.style.height = '100px';
    div.style.backgroundColor = 'white';
    div.style.display = 'inline-block';
    div.style.marginRight = '16px';
    div.style.border = '1px solid black';
    div.id = ('box' + i);
    document.body.appendChild(div);
}
// 点击div修改颜色
var box0 = document.getElementById('box0');
var box1 = document.getElementById('box1');
var box2 = document.getElementById('box2');
var box3 = document.getElementById('box3');

function changeColor() {
    this.style.backgroundColor = 'purple';
}
box0.onclick = changeColor;
box1.onclick = changeColor;
box2.onclick = changeColor;
box3.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是接收参数,apply是接收数组

call方法要求,所有的参数在上下文对象后面一一罗列

function person(name, age, height, weight) {
    this.name = name;
    this.age = age;
    this.height = height;
    this.weight = weight;
};
var xiaoming = {
    name: '小明',
    age: 3,
    height: 60,
    weight: 15
};
person.call(xiaoming, '小明', 23, 183, 65);

此时我们换成applyapply要求所有的参数必须规整到一个数组中

person.apply(xiaoming, ['小明', 30, 183, 75]);

apply本质上只要求两个参数,第二个参数是一个数组集合

在使用结果上两种方式都是一样的

小题目

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形参中进行了解构,也就是分别变成了 香蕉、葡萄、梨子

小题目

此时我们想根据apply的特点出一个思考题,利用Math方法进行数组的最大值查找

Math.max.apply(null, [789, 2342, 123, 2134, 2345, 22])

我们知道apply第二个参数是数组,但是apply有能力给解构,所以我们可以利用这个特点求数组的最大或最小值

上面的题目是企业中经常遇到的面试题,一定不要只写循环遍历求最大最小值

企业面试题

题目1

var a = 1;
function fn() {
    this.a++;
    a += 10;
    var a = 8;
}
fn()
console.log(a)

答:结果是2,因为fn执行的时候内部的this指的是window也就是全局的a=1,所以this.a++等于2;函数内部的a += 10本质上是undefined += 10结果为NaN,因为后面有个var a = 8;因为变量声明提升的原因造成的

题目2

var length = 5;
var arr = [fn1, fn2];
function fn1() {
    return this.length;
}
function fn2() {
    return this[0];
}
var a = arr[0]();
var b = arr[1]()();
console.log(a);		
console.log(b);		

因为a此时的this是数组枚举执行,符合规则2,也就是上下文是arr数组,所以length是2

b其实最后返回的就是一个函数,函数直接圆括号执行此时上下文是window所以是5

题目3

var number = 2;
var obj = {
	number: 3,
    fn1: (function() {
        this.number *= 2;
        number = number * 3;
        var number = 2;
        return function() {
            this.number *= 4;
            number *= 5;
            console.log(number);
        }
    })(),
    fn2: function() {
        this.number *= 2;
    }
}
var fn1 = obj.fn1;
console.log(number);
fn1();
obj.fn1();
obj.fn2();
console.log(window.number);
console.log(obj.number);

题目4

var length = 5;
function getLength() {
    return this.length;
}
function foo() {
    this.length = 1;
    return (function() {
        var length = 2;
        return {
            length: function(a, b, c) {
                return this.arr.length;
            },
            arr: [1, 2, 3, 4],
            info: function() {
                return getLength.call(this.length);
            }
        }
    })();
}
var result = foo().info();
console.log(result);

答案是3,因为result是一个对象,这个对象打点调用infoinfo函数中执行了getLength函数,并且将上下文一同绑定了,绑定的是对象的length属性,该属性值是一个函数,所以getLength函数在执行时候的this就是函数,this.length也就是该函数的length结果为3

posted @ 2024-10-06 15:34  hellozjf  阅读(19)  评论(0编辑  收藏  举报