JavaScript中函数的上下文问题

体验函数的上下文

<script>
        var obj = {
            name: '小明',
            age: 21,
            a: 1,
            b: 2,
            addsum: function () {
                console.log(this.a + this.b);
                console.log(this === obj);
            }
        };
        // 如果此时让你回答上述的代码的最终结果为多少
        // 答案为不知道,因为不知道此对象的addsum方法的调用形式是什么,即不知道该函数的上下文

        // 调用函数方法1:
        obj.addsum();
        // 此时结果为3, 因为对象直接打点调用函数,该函数内的this就是obj,结果也为true

        // 调用函数方法2:
        var addsum = obj.addsum;
        // 将对象的方法单独再传给一个全局变量
        addsum();
        // 结果为nan,false
        // 因为此时this === windows,该函数的上下文为windows,全局变量都为windows的属性,但是没有a,b这两个全局变量输出为nan
    </script>

注意此时js语句内最后的两行代码。为什么将obj.addsum函数单拎出来赋值给addsum变量,在此调用结果就为NaN了呢?不应该还是3嘛?这就涉及到函数上下文的问题了。函数的上下文规则总结五种,下面进行归纳总结。

上下文规则一

 规则一:函数通过对象打点调用运行,函数的上下文就是此对象。即this === 打点的对象。

<script>
        // 定义一个函数,返回值为一个对象
        function outer() {
            var a = 11;
            var b = 22;
            return {
                a: 33,
                b: 66,
                fn: function () {
                    console.log(this.a + this.b);
                }
            };
        };

        // 调用函数fn
        outer().fn();               /* 结果为99 */

        // 分析
        // 此函数经过outer()调用后会返回一个对象,再进行打点调用fn()方法,此时fn函数的上下文为outer()函数返回的此对象,this === 此对象,结果为99

该js语句内先定义了一个函数outer(),函数会返回一个对象,对象内还有一个函数fn。当outer().fn(),调用后,该函数先执行outer()函数,会立刻返回一个

 {

                a: 33,

                b: 66,

                fn: function () {

                    console.log(this.a + this.b);

                }对象。然后此对象fn(),也就是对象打点调用函数,此时函数fn的上下文就是该对象,此时this===该返回的对象,接着进行函数执行语句是console.log(this.a + this.b),也就是按照规则一,进行33 + 66 的运算,最后输出99

上下文规则二

规则二:函数通过圆括号“()”调用运行,函数的上下文就是window对象。即this === window。

<script>
        // 定义全局函数
        function fn() {
            return this.a + this.b;
        }

        // 定义全局变量
        var a = 11;
        var b = 22;

        // 定义对象
        var obj = {
            a: 33,
            b: fn(),
            c: fn
        };

        // 调用函数
        var result = obj.c();
        console.log(result);

        // 结果为66
        // 分析
        // 当obj.c()时,obj就是函数fn的上下文,this===obj.执行后为33+fn(),此时fn的调用方法为加圆括号调用,函数的上下文为window,此时全局变量为window的属性,11+22=33,所有obj.b=33,最后结果为66
    </script>

该示例先定义了一个全局函数fn,函数内是返回两个值的和,但此时不知道函数fn的上下文是谁,结果为“不知道”。接着定义了两个全局变量a,b,这里需要了解的是全局变量都是window的属性。接着定义了对象obj,对象obj.b的值是函数fn的运行值,对象obj.c的值是fn函数,在内存中指向全局函数fn的地址,即两者公用一块地址。接着通过obj.c()来调用函数fn,此时函数fn的上下文就是objthis === obj。所以在函数fn内“this.a” = 33,“this.b” = fn()fn()明显是一个函数的执行结果。注意此时fn是通过圆括号“()”调用的,那么他的上下文就是windowthis === window。他访问的是全局变量a,b,再次进行相加得到b = 33,那么回到最初的结果33 + 33 = 66。再将66赋值给result,最后输出。

上下文规则三

规则三:函数通过数组下标调用运行,函数的上下文就是该数组。即this === 数组

<script>
        // 定义一个函数
        function fn() {
            arguments[3]();
        };

        // 调用函数
        fn(1, 2, 3, function () {
            console.log(this[1]);
        });

        // 结果为2
        
        // 分析
        // 函数fn的功能:调用执行传入函数fn的下标为3的实参,此时fn内的四个参数为一组类数组,所以this===该组数组,那么就会输出2
    </script>

该示例先定义了一个全局函数fn,该函数的功能是实现传入的函数fn()实参数组下标为3的语句功能。接着调用函数fn()。此时传入的函数fn()实参数组为[

1, 2, 3, function () {

            console.log(this[1]);

        }

]其中下标为3的实参为一个函数,那么显然是要实现该函数的功能,但是是通过数组来调用的,那么此时该函数的上下文就是该实参数组,this = 实参数组,而数组下标为1的值是2,所以最后结果为2。

上下文规则四

规则四:IIFE函数,也就是立即执行函数,这种函数的上下文为windo,this===window。

<script>
        // 定义全局变量
        var a = 33;

        // 定义对象
        var obj = {
            a: 66,
            fn: (function () {
                var a = this.a;
                return function () {
                    console.log(a + this.a);
                }
            })()
        };

        // 调用函数
        obj.fn();

        // 结果为99

        // 分析
        // fn的属性值是一个IIFE函数,也就是立即执行函数,这种函数的上下文为windo,this===window,此时a = 33,接着会返回一个函数给fn,而该函数会因为闭包的性质记住a,接着通过obj打点调用fn,obj成为该函数的上下文,此时this === obj,所以this.a = 66,最后结果为99
    </script>

该示例先定义了一个全局变量a。接着定义了对象obj,此对象的obj.fn值是一个IIFE函数,会立即执行,且IIFE函数执行值是返回一个函数,也就是obj.fn的值其实就是该返回的函数,此函数是实现两个值的和。接着obj.fn()打点调用了函数fnfnIIFE函数会立即执行,此时fnIIFE函数的上下文是window,即this === window, 会找全局变量。执行第一条语句“var a = this.a;”,由于this === window,所以this.a = 33,接着返回一个

function () {

                    console.log(a + this.a);

                }函数,此函数内的a因为闭包性质,会记录a的值。那么this.a的值为什么?this等于什么?因为是对象obj打点调用的函数,所以此函数的上下文就是对象obj,this === obj,那么this.a=66。最后相加输出99

上下文规则五

规则五:函数通过定时器/延时器来调用,函数的上下文为window对象,this === window。

<script>
        // 定义对象
        var obj = {
            a: 33,
            b: 66,
            fn: function () {
                console.log(this.a + this.b);
            }
        };

        // 定义全局变量
        var a = 1;
        var b = 2;

        // 调用函数
        setTimeout(obj.fn, 1000);
        // 结果为3

        setTimeout(function () {
            obj.fn();
        }, 1000);
        // 结果为99

        // 分析
        // 第一种调用:因为函数是由定时器来直接调用的,所以函数的上下文为window,this === window,接着寻找全局变量,从而输出结果为3
        // 第二种调用:因为函数是由定时器来间接调用的,实际上定时器是调用的外部的包裹obj.fn()的函数,此时fn的上下文为obj,this===obj,结果为99
    </script>

该示例首先定义了一个对象obj,其中obj.fn是一个函数,此函数的功能是打印两个数的和。接着定义了两个全局变量a,b。接着通过延时器obj.fn来直接调用函数,此时函数的上下文就是window对象,this === window所以会寻找全局变量a,b,所以最后会输出3。注意我们再次用延时器调用此函数,但是在延时器内再定义一个函数,而函数内才调用函数fn,此时得到的结果不为3。这是为什么呢?这是因为对于obj.fn()他是在函数体内的,而延时器是间接来通过他调用函数fn的,而obj.fn()的形式符合规则一对象打点调用,函数的上下文就是对象obj,所以会输出99

上下文规则六

规则六:DOM事件处理函数,函数的上下文就是绑定的DOM元素。

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .box {
            width: 200px;
            height: 200px;
            float: left;
            margin-right: 10px;
            border: 1px solid black;
        }
    </style>
</head>

<body>
    <div class="box" id="box1"></div>
    <div class="box" id="box2"></div>
    <div class="box" id="box3"></div>
    <script>
        // 获取元素
        var box1 = document.getElementById('box1');
        var box2 = document.getElementById('box2');
        var box3 = document.getElementById('box3');

        // 定义事件处理函数
        function colortored() {
            // 备份上下文
            var self = this;
            setTimeout(function () {
                self.style.backgroundColor = 'red';
            }, 2000);
        };

        // 调用函数
        box1.onclick = colortored;
        box2.onclick = colortored;
        box3.onclick = colortored;

        // 结果是点哪个盒子哪个盒子两秒后变红

        // 分析
        // 当通过打点事件监听调用函数,谁打点,谁就是this,此时定义一个self变量来保存当前的上下文,也是为了解决由于延时器调用函数,从而改变上下文的问题。
    </script>
</body>

</html>

此示例是在网页上点击哪个盒子,哪个盒子两秒后变红,而且要使用同一个事件监听函数。为了实现使用同一个事件监听函数,我们需要先定义一个函数用于封装,函数内设置一个延时器,延时器内再设置事件监听函数,函数的作用是使盒子背景变红。但此时你就要注意到该事件监听函数不是直接通过延时器来调用的,所以直接写this.style.backgroundColor = 'red'是不对的。最后给每个盒子绑定监听来调用函数。那么此时的函数的上下文就是绑定的盒子,为了实现this.style.backgroundColor = 'red',而不受规则五的影响,我们要添加“var self = this;”语句,来备份一下此时的上下文,再在事件监听函数内书写self.style.backgroundColor = 'red';同样实现了规则五的作用,此时的this === self === 绑定的盒子。

指定函数的上下文

指定函数的上下文需要用到call()方法或apply()方法。

<script>
        // 定义对象
        var xiaoming = {
            c: 100,
            m: 100,
            e: 100
        }

        // 定义函数
        function fn() {
            console.log(this.c + this.m + this.e);
        }

        // 调用函数  
        fn.apply(xiaoming); /* fn打点调用apply是将xiaoming指定为该函数的上下文 */
        fn.call(xiaoming);  /* fn打点调用call是将xiaoming指定为该函数的上下文 */

        // 但是两者有区别
        // 当还有函数还有要传入的参数时,call是在,小明后以逗号隔开的方式写入;apply是以数组的形式写入其中
    </script>

此示例定义了一个对象xiaoming,定义了函数fn。通过函数打点调用apply/call方法的同时,会将call/apply()方法内的参数传入到函数内,该参数就是指定函数的上下文,本示例中传入了参数“xiaoming”,即让对象xiaoming成为函数fn的上下文,函数的this === xiaoming,接着再进行输出,不管是apply()方法还是call()方法都是输出一样的结果,但是两者还是有区别的。

call方法与apply方法的不同

两者的不同就在于传入方法内的参数形式不同。

对于call()方法,当需要传给函数的参数除了上下文后,如果还有其他的参数,需要以逗号","隔开的形式写入。

对于apply()方法,当需要传给函数的参数除了上下文后,如果还有其他的参数,需要以数组的形式写入。

<script>
        // 定义函数
        function fun1(a, b) {
            console.log(a + b);
        };

        // 定义函数
        function fun2() {
            fun1.apply(this, arguments);
        }
        // fun2函数的作用是调用函数fun1

        // 调用函数
        fun2(33, 66);
        // 当函数fun2被调用后,此时会将函数fun2的上下文window指定给函数fun1,并且因为传入的还有arguments,类数组对象,也就是一个数组,所以在这里必须用到apply方法,33,66以数组的形式进入到函数fun1中,得出结果99
    </script>

该示例先定义了一个函数fun1,此功能是输出两个参数的和。接着定义了函数fun2,此函数的功能是调用函数fun1,并将给函数fun1添加上下文,此时还不知道this等于什么?第二个参数是一个类数组对象,也就是传入的参数以数组的形式传入。接着用圆括号的形式调用函数fun2,此时符合规则二,此时this === window,将window对象作为上下文,传给函数fun1,同时传入的还有一个arguments类数组对象,伪数组,也是数组。那么就需要用到apply()方法,进而得到99的值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值