【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 属性即为你注册时传入的对象。