【JavaScript】访问外部作用域的同名变量或 this 的几种方法

我们之前讨论过 python 和 js 在实现闭包时,使用变量的作用域差异

https://blog.csdn.net/qq_16181837/article/details/104805151

今天又遇到了相关的问题

js 如何访问外部作用域的同名变量?
在 python 中,我们有 nolocal 、 global 关键字,可以方便地声明某变量的作用域:

def outer():
	outer_var = 'outer'
	def inner():
		# 闭包
		nolocal outer_var
		outer_var = 'inner'
		return outer_var
	print(inner())
	
outer()

输出:

inner


但是据我所知,js 是没有这样的关键字的。
查了很多资料

我这里提供几个方法:

法一、访问外部包装的对象

function outer() {
    var variable = 'outer';
    var obj = {
        outer_var: variable
    };
    function inner() {
        var variable = 'inner';
        console.log(obj.outer_var);
    }
    inner();
}
outer()

输出:

inner

法二、通过函数访问外部变量

function outer() {
    var variable = 'outer';

    function get_outer() {
        return variable;
    }
    function inner() {
        var variable = 'inner';
        console.log(get_outer());
    }
    inner();
}
outer()

输出:

inner


可能你会问了,我们为什么非要让内部的变量与外部同名呢?
的确可以,一些情况下我们可以在内部声明一个例如 local_variable 之类的变量以示区分,但在某些情况下是不可以的

看这个例子:

class test {
    constructor(btn) {
        // 给传进来的 btn 绑定一个回调事件
        // 调用对象的 method 方法

        $(btn).on('click', function(outer_this) {
            // 但是这里的 this 是调用回调函数的对象,即 btn 而不是指向对象的 this
            // 如何访问外部作用域的 this?
            return function() {
                this.method();
            }
        }(this));
    }

    method() {
        console.log('Calling method.');
    }
}

var obj = new test($('.btn-1')[0])

报错:
在这里插入图片描述
我们看一下此时的 this 指向谁:
在这里插入图片描述
注册事件时,函数被作为一个属性绑定在 .btn-1 这个对象上。
那么自然地,回调时 this 就会指向这个对象。

我们都说 js 万物皆对象,所以我为什么说

一些情况下我们可以在内部声明一个例如 local_variable 之类的变量以示区分,但在某些情况下是不可以的

怎么办呢?
上面的法一是可以用的,即访问外部包装的对象
但是法二是不行的,因为 get_outer 函数中的 this 指向的是其调用者,即全局对象或者是 undefined。
对于这种情况,我要介绍三种方法:

法一、改变函数 this 指向

这里我们用到函数的一个方法 bind,为 bind 方法提供一个对象实参,其返回一个绑定到该对象上的函数拷贝。
即 fun.bind(obj) 会返回一个 obj.fun 供以后调用。

class test {
    constructor(btn) {
        btn.addEventListener('click', function() {
            this.method();
        }.bind(this));
    }
    method() {
        console.log('Calling method.');
    }
}
var obj = new test($('.btn-1')[0]);

输出:

Calling method.

这里拓展两个方法:

fun.call(obj[, arg1[, arg2[, arg3…]]])
fun.apply(obj, arg1, arg2, arg3…)

这两个方法将会立即执行函数,并改变其运行时的 this 指向为第一个参数 obj,并传入指定参数。
不同点在于 apply 方法的第二个参数为一个数组,而 call 为一个列表。

法二、传参

说来惭愧,一直在想如何能够像 python 一样直接访问到外部的变量,忽略了最简单的方法
不过这里面存在一个问题,原生 webapi 的 addEventListener 方法并没有提供传入回调函数的参数的接口,所以这里需要用闭包来实现:

原生 webapi:

 class test {
     constructor(btn) {
         btn.addEventListener('click', function(outer_this) {
             return function() {
                 outer_this.method();
             }
         }(this));
     }
     method() {
         console.log('Calling method.');
     }
 }

 var obj = new test($('.btn-1')[0]);

jQuery:

相比来说,jquery 就方便多了,因为他提供了传入参数的接口:

 class test {
     constructor(btn) {
         $(btn).on('click', {
             outer_this: this
         }, function(jq_event) {
             jq_event.data.outer_this.method();
         });
     }
     method() {
         console.log('Calling method.');
     }
 }

 var obj = new test($('.btn-1')[0]);

法三、

还有更简单的,这本来应该是最自然的思路,然而我给想复杂了

 class test {
     constructor(btn) {
     	var that = this;
         $(btn).on('click', function(jq_event) {
             that.method();
         });
     }
     method() {
         console.log('Calling method.');
     }
 }

 var obj = new test($('.btn-1')[0]);

jq 的 on 允许你传入一个对象,当函数被回调时,jq 会调用该函数,并传入一个 jq 的事件对象,其内的 data 属性即为你注册时传入的对象。

至此为止,问题基本解决。但实际上这都是补救的方法,js 缺少像 python 那样直接声明变量作用域的优雅的关键字,很遗憾啊。

posted @ 2020-03-20 00:06  高厉害  阅读(1548)  评论(0编辑  收藏  举报