java中jquery的作用域,【JavaScript】(附面试题)深入理解作用域、作用域链和闭包...

引言

在JavaScript中有作用域、作用域链和闭包。我们最开始可能觉得知道这些的定义就算懂了(刚入门时的我也是这样),但是当深入了解的时候,发现自己知道的只是皮毛。所以,这篇文章将详细讲解作用域、作用域链和闭包。

我们先借助一道题,了解一下作用域、作用域链和闭包的形成过程~

let x = 1;

function A(y){

let x = 2;

function B(z){

console.log(x+y+z);

}

return B;

}

let C = A(2);

C(3);

1460000021841124

对于上面的这张解答图,有如下解释:

当创建一个函数时,会创建一个堆,同时初始化当前函数作用域,作用域([[Scope]])为所在上下文中的变量对象VO/AO。

当执行一个函数时,会创建新的执行上下文 -> 初始化this指向 -> 初始化作用域链([[ScopeChain]]) -> 创建AO变量对象存储变量

在EC(A)执行上下文中,堆被全局变量C所占用,不能出栈销毁,此时就形成了闭包。

图里面红线所代表的就是作用域链,在一个作用域中,它所需要的变量在当前作用域中没有,就会一层一层向上查找。

这样简单的一个题,引出了作用域、作用域链、闭包的概念,下面本篇文章将正式对它们进行讲解。

一、作用域

作用域([[Scope]])就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。

1.1 词法作用域与动态作用域

1.1.1 词法作用域

词法作用域也是静态作用域,在JavaScript中采用的就是词法作用域。词法作用域就是定义在词法阶段的作用域。换句话说,词法作用域是由你在写代码时将变量和块作用域写在哪里决定的。因此当词法分析器处理代码时会保持作用域不变(大部分情况是这样)

下面一个例子帮助理解:

var value = 1;

function foo() {

console.log(value);

}

function bar() {

var value = 2;

foo();

}

bar();

假如上述例子采用了词法作用域,那么它的执行过程就是:

首先执行bar()函数,在bar()函数中执行foo()函数,foo()函数中输出value的值。它首先会查找当前作用域中是否有value,如果没有,则会向外一层查找,则最后输出了1

1.1.2 动态作用域

动态作用域即是与词法作用域相反的。我们还是以上面的例子为例:

假如上述例子采用动态作用域:

它依然会像采用词法作用域的形式执行函数,唯一不一样的地方在于:在执行foo()函数时,他不会向外一层查找value,而是从调用的函数作用域中查找,所以最后的结果输出为2。

1.2 全局作用域

在代码任何地方都能访问到的对象拥有全局作用域,一般拥有全局作用域有以下三种情形

1.2.1 最外层函数和在最外层函数外面定义的变量

var globalValue = `global value`;

function checkGlobal() {

var localValue = `local value`;

console.log(localValue); // "local value"

console.log(globalValue); // "global value"

}

console.log(globalValue); // "global value"

console.log(checkGlobal); // "global value"

console.log(localValue); // "Uncaught ReferenceError: localValue is not defined"

在上面的例子中,globalValue就是一个全局变量,无论在哪都能访问,而localValue是一个局部变量,只能在函数内部访问

1.2.2 所有未定义直接赋值的变量

function checkGlobal() {

var localValue = 'local value';

globalValue = 'global value';

console.log(localValue, globalValue); // 'local value' 'globalValue'

}

console.log(globalValue); // 'globalValue'

console.log(localValue); // "Uncaught ReferenceError: localValue is not defined"

1.2.3 所有window对象的属性

1460000021841125

1.3 函数作用域

函数作用域,就是指声明在函数内部的变量,它正好和全局作用域相反。内层作用域可以访问到外层作用域,而外层作用域不能访问到内层作用域。

function check() {

var localValue = 'local value';

console.log(localValue); // 'local value'

}

console.log(localValue); // "Uncaught ReferenceError: localValue is not defined"

1.4 块级作用域

块级作用域可通过let和const声明,声明后的变量再指定块级作用域外无法被访问。

1.4.1 块级作用域被创建的情况

在一个函数内部

在一个代码块内部

1.4.2 块级作用域的特点

声明的变量不会提升到代码块顶部

let或者const声明的变量不会被提升到当前作用域顶部。

function check(bool) {

if(bool) {

let result = 1;

console.log(result);

}

console.log(result);

}

check(true); // 1 Uncaught ReferenceError: result is not defined

check(false); // Uncaught ReferenceError: result is not defined

如果想要访问到result,需要自己手动将变量提升到当前作用域顶部。像这样

function check(bool) {

let result = null;

if(bool){

result = 1

}

console.log(result);

}

....

禁止重复声明

在同层级的作用域内,已经声明过的变量,不可以再次声明。

// 1.同层级作用域

var bool = true;

let bool = false; // Uncaught SyntaxError: Identifier 'bool' has already been declared

// 不同层级作用域

var bool = true;

function check() {

let bool = false; // 这里不会报错

// .....

}

在循环中的使用

在for循环中声明的变量仅在循环内部使用。

for(let i = 0; i < 1; i++) {

console.log(i); // 0

}

console.log(i); // Uncaught ReferenceError: i is not defined

但是循环内部又是一个单独的作用域

for(let i = 0; i < 2; i++) {

let i = 'hello';

console.log(i); // 'hello' 'hello'

}

二、作用域链

2.1 作用域链的定义及形成

当所需要的变量在所在的作用域中查找不到的时候,它会一层一层向上查找,直到找到全局作用域还没有找到的时候,就会放弃查找。这种一层一层的关系,就是作用域链。

var a = 1;

function check() {

return function() {

console.log(a); // 当前作用域内找不到a,会向上一层一层查找,最后找到了全局下的a,输出结果为1

console.log(b); // 同理,所以输出"Uncaught ReferenceError: b is not defined"

}

}

var func = check(); // 此时返回匿名函数

func(); // 执行匿名函数

三、闭包

3.1 闭包的定义

当函数可以记住并访问所在的词法作用域时,就产生了闭包。即使函数是在当前词法作用域之外执行。

3.2 闭包的形成

我们来看一段代码

function foo() {

var a = 1;

return function() {

console.log(a);

}

}

var bar = foo();

bar();

foo()函数的执行结果返回给bar,而此时由于变量a还在使用,因而没有被销毁,然后执行bar()函数。这样,我们就能在外部作用域访问到函数内部作用域的变量。这个就是闭包。

闭包的形成条件:

函数嵌套

内部函数引用外部函数的局部变量

3.3 闭包的作用

可以读取函数内部的变量

可以使变量的值长期保存在内存中,生命周期比较长。

可用来实现JS模块(JQuery库等)

JS模块是具有特定功能的JS文件,将所有的数据和功能都封装在一个函数内部(私有的),只向外暴露一个包含多个方法的对象或函数,模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能。

(function() {

var a = 1;

function test() {

return a;

}

window.module = {a, test}; // 向外暴露

})()

Document

console.log(module.a); // 1

console.log(module.test()); // 1

3.4 闭包的特性

每个函数都是闭包,函数能够记住自己定义时所处的作用域,函数走到了哪,定义时的作用域就到了哪。

内存泄漏

内存泄漏就是一个对象在你不需要它的时候仍然存在。所以不能滥用闭包。当我们使用完闭包后,应该将引用变量置为null。

function outer(){

var num = 0;

return function add(){

num++;

console.log(num);

};

}

var func1 = outer();

func1(); // 1

func1(); // 2 [没有被释放,一直被占用]

var func2 = outer();

func2(); // 1 [重新引用函数时,闭包是新的]

func2(); // 2

3.5 闭包的应用

现在要求实现点击第几个button就输出几

先写一个html

1

2

再来写JS

let buttons = document.getElementsByTagName('button');

for(var i = 0; i < buttons.length; i++) {

buttons[i].onclick = function() {

console.log(i + 1);

}

}

第一次我们可能会写出这样的代码,但是我们会发现,这个代码存在问题,无论我点第几个按钮,都会输出3。这是因为此时的i是全局变量,当执行点击事件时,i已经变成了3

我们可以用两种方式解决这个问题

let声明

将上述代码中的var改为let

闭包

for(var i = 0; i < buttons.length; i++) {

(function(k){

buttons[k].onclick = function() {

console.log(k + 1);

}

})(i)

}

1460000021841127

面试题

let x = 5;

function fn(x) {

return function(y) {

console.log(y + (++x));

}

}

let f = fn(6);

f(7);

console.log(x);

1460000021841126

总结

本篇文章主要讲解了关于作用域、作用域链和闭包的知识,如果觉得对你有帮助,可以给本篇文章点个赞呀~如果有哪里不对的地方,还请大家指出来,我们共同学习、共同进步~

最后,分享一下我的公众号「web前端日记」,欢迎大家前来关注~

1460000021841128

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值