javascript深入理解--作用域,作用域链,闭包的面试题解
一、概要
作用域和作用域链是js中非常重要的特性,关系到理解整个js体系,闭包是对作用域的延伸,其他语言也有闭包的特性。
那什么是作用域?作用域指的是一个变量和函数的作用范围。
1、js中函数内声明的所有变量在函数体内始终是可见的;
2、在ES6中有全局作用域和局部作用域,但是没有没有块级作用域(catch只在其内部生效);
3、局部变量的优先级高于全局变量。
文章首发:https://www.mwcxs.top/page/574.html
二、作用域
我们来举几个栗子:
2.1变量提升
var scope="global";
function scopeTest(){
console.log(scope);
var scope="local"
}
scopeTest(); //undefined
上面的代码输出是undefined,这是因为局部变量scope变量提升了,等效于下面
var scope="global";
function scopeTest(){
var scope;
console.log(scope);
scope="local"
}
scopeTest(); //undefined
注意,如果在局部作用域中忘记var,那么变量就被声明为全局变量。
var scope="global";
function scopeTest(){
console.log(scope);
scope="local"
}
scopeTest(); //global
var scope="global";
function scopeTest(){
scope="local"
console.log(scope);
}
scopeTest(); //local
2.2没有块级作用域
和我们其他常用语言不同的是,js中没有块级作用域
var data = [];
for (var i = 0; i < 3; i++) {
data[i] = function () {
console.log(i);
};
}
data[0](); // 3
data[1](); // 3
data[2](); // 3
2.3作用域链
每个函数都有自己的执行上下文环境,当代码在这个环境中执行时候,会创建变量对象的作用域链,
那什么是作用域链?作用域链式是一个对象列表。
作用域链的作用?他保证了变量对象的有序访问。
作用域链开始的地方:当前代码执行环境的变量对象,常被称之为“活跃对象”(AO),变量的查找会从第一个链的对象开始,如果对象中包含变量属性,那么就停止查找,如果没有就会继续向上级作用域查找,直到找到全局对象中,如果找不到就会报ReferenceError。
2.4闭包
function createClosure(){
var name = "jack";
return {
setStr:function(){
name = "rose";
},
getStr:function(){
return name + ":hello";
}
}
}
var builder = new createClosure();
builder.setStr();
console.log(builder.getStr()); //rose:hello
上面在函数中反悔了两个闭包,这两个闭包都维持着对外部作用域的引用,因此不管在哪调用都是能够访问外部函数中的变量。在一个函数内部定义的函数,闭包中会将外部函数的自由对象添加到自己的作用域中,所以可以通过内部函数访问外部函数的属性,这就是js模拟私有变量的一种方式。

注意:由于闭包会额外的附带函数的作用域(内部匿名函数携带外部函数的作用域),因此,闭包会比其他函数多占用些内存空间,过度使用会导致内存占用增加。
三、闭包面试题解
由于作用域链机制的影响,闭包只能取得内部函数的最后一个值,这引起了一个副作用,如果内部函数在一个循环中,那么变量的值始终为最后一个值。
var data = [];
for (var i = 0; i < 3; i++) {
data[i] = function () {
console.log(i);
};
}
data[0](); // 3
data[1](); // 3
data[2](); // 3
如果想强制返回逾期结果,怎么整?
方法一:立即执行函数
for (var i = 0; i < 3; i++) {
(function(num) {
setTimeout(function() {
console.log(num);
}, 1000);
})(i);
}
// 0
// 1
// 2
方法二:返回一个匿名函数赋值
var data = [];
for (var i = 0; i < 3; i++) {
data[i] = (function (num) {
return function(){
console.log(num);
}
})(i);
}
data[0](); // 0
data[1](); // 1
data[2](); // 2
无论上是立即执行函数还是返回一个匿名函数赋值,原理上都是因为变量的按值传递,所以会将变量i的值赋值给实参num,在匿名函数的内部又创建了一个用于访问num的匿名函数,这样每一个函数都有一个num的副本,互不影响。
方法三:使用es6的let
var data = [];
for (let i = 0; i < 3; i++) {
data[i] = function () {
console.log(i);
};
}
data[0]();
data[1]();
data[2]();
解释一下原理:
var data = [];// 创建一个数组data;
// 进入第一次循环
{
let i = 0; // 注意:因为使用let使得for循环为块级作用域
// 此次 let i = 0 在这个块级作用域中,而不是在全局环境中
data[0] = function() {
console.log(i);
};
}
循环时,let声明了i,所以整个块是块级作用域,那么data[0]这个函数就成了一个闭包,这里用{}表述,只是希望通过它来说明let存在的时候,这个for循环块是块级作用域,而不是全局作用域。
上面的块级作用域,就像函数作用域一样,寒暑表执行完毕,其中的变量会被销毁,但是因为这个代码块中存在一个闭包,闭包的作用域链中引用着块级作用域,所以在闭包被调用之前,这个块级作用域内部的变量不会被销毁。
// 进入第二次循环
{
let i = 1; // 因为 let i = 1 和上面的 let i = 0
// 在不同的作用域中,所以不会相互影响
data[1] = function(){
console.log(i);
};
}
当执行data[1]()时,进入下面的执行环境。
{
let i = 1;
data[1] = function(){
console.log(i);
};
}
在上面这个执行环境中,它会首先寻找该执行环境中是否存在i,没有找到,就沿着作用域链继续向上找,在其所在的块级作用域执行环境中,找到i=1,于是输出1。
四、思考题
代码1:
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
checkscope()(); //local scope
代码2:
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
var foo = checkscope();
foo(); //local scope
四、参考
1、https://segmentfault.com/a/1190000000618597
2、https://www.cnblogs.com/zhuzhenwei918/p/6131345.html
javascript深入理解--作用域,作用域链,闭包的面试题解的更多相关文章
- JavaScript基础——理解变量作用域
一旦你开始在JavaScript应用程序中添加条件.函数和循环,就需要理解变量作用域.变量作用域规定了如何确定正在执行的代码行上的一个特定变量名的值. JavaScript允许你既定义全局版本又定义局 ...
- javascript深入理解-从作用域链理解闭包
一.概要 红宝书(P178)对于闭包的定义:闭包就是有权访问另外一个函数作用域中变量的函数. MDN,对于闭包的定义:闭包就是指能够访问自由变量的函数. 那么什么是自由变量?自由变量就是在函数中使用, ...
- 【学习笔记】深入理解js原型和闭包(14)——从【自由变量】到【作用域链】
先解释一下什么是“自由变量”. 在A作用域中使用的变量x,却没有在A作用域中声明(即在其他作用域中声明的),对于A作用域来说,x就是一个自由变量.如下图 如上程序中,在调用fn()函数时,函数体中第6 ...
- (转载)Javascript 进阶 作用域 作用域链
载请标明出处:http://blog.csdn.net/lmj623565791/article/details/25076713 一直觉得Js很强大,由于长期不写js代码,最近刚好温故温故. 1.J ...
- Javascript 进阶 作用域 作用域链
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/25076713 一直觉得Js很强大,由于长期不写js代码,最近刚好温故温故. 1. ...
- JavaScript 之 预编译 作用域,作用域链
第一次写博客,本来是学习jQuery遇到闭包问题,发现并没有理解闭包,发现闭包牵扯的知识点太多.复习了一遍(发现自己该记住的全忘了)写在博客里,自己也是小白,希望大神们指点迷津,必将感激不尽. 我们知 ...
- 【译】学习JavaScript中提升、作用域、闭包的终极指南
这似乎令人惊讶,但在我看来,理解JavaScript语言最重要和最基本的概念是理解执行上下文.通过正确学习它,你将很好地学习更多高级主题,如提升,作用域链和闭包.考虑到这一点,究竟什么是"执 ...
- js原型链闭包作用域链-Tom
1.原型相当于Java.C++里面的父类,由封装公有属性及方法而产生,子类可以继承. 原型继承实现(函数的原型属性指向原型函数一个实例对象,函数的原型的构造函数指向函数本身) 1)eg:原型链 fun ...
- 【学习笔记】深入理解js原型和闭包(12)——简介【作用域】
提到作用域,有一句话大家(有js开发经验者)可能比较熟悉:“javascript没有块级作用域”.所谓“块”,就是大括号“{}”中间的语句.例如if语句: 再比如for语句: 所以,我们在编写代码的时 ...
随机推荐
- 使用WSL连接Docker for Windows
在Windows下安装Docker for Windows Cotana搜索功能,打开Windows的Hype-v功能(注:会影响Virtualbox和Vmware的使用)并重启电脑. 从Docker ...
- 分析DuxCms之AdminUserModel
/** * 获取信息 * @param array $where 条件 * @return array 信息 */ public function getWhereInfo($where) { ret ...
- linux dialog详解(图形化shell)
liunx 下的dialog 工具是一个可以和shell脚本配合使用的文本界面下的创建对话框的工具.每个对话框提供的输出有两种形式: 1. 将所有输出到stderr 输出,不显示到屏幕. 2 ...
- select case when与IF的用法
case when概述 sql语句中的case语句与高级语言中的switch语句,是标准sql的语法,适用于一个条件判断有多种值的情况下分别执行不同的操作. case when示例 有一张表,里面有3 ...
- 第九章——运行tensorflow(Up and Running with TensorFlow)
本章简单介绍了TensorFlow的安装以及使用.一些细节需要在后续的应用中慢慢把握. TensorFlow并不仅仅局限于神经网络和机器学习,它甚至可以用于量子物理仿真. TensorFlow的优势: ...
- 思维导图软件比较-FREEMIND,XMIND,Mindjet Mindmanager
https://www.zhihu.com/question/22094277
- Spring Boot + Websocket + Thymeleaf + Lombok
https://github.com/guillermoherrero/websocket 验证错误消息文件名字:是默认名ValidationMessages.properties,编译后存放在cla ...
- Containerd 简介
我们可以把 docker 抽象为下图所示的结构(此图来自互联网): 从图中可以看出,docker 对容器的管理和操作基本都是通过 containerd 完成的. 那么,containerd 是什么呢? ...
- http2.0与http1.X的区别
此文只是方便重看,原文在:http://www.mamicode.com/info-detail-1199706.html 1.1 HTTP应用场景 http诞生之初主要是应用于web端内容获取,那时 ...
- js判断对象是否为空
/** *判断对象是否为空 * * @param e * @returns {Number} */function isEmptyObject(e) { var t; for (t in e) ret ...