个人自学前端15-JS8-作用域和作用域链,var变量提升,闭包,自执行函数
作者:互联网
作用域和作用域链
一 作用域
什么是作用域?
作用域就是声明变量的'地点'。程序执行过程中,遇到函数调用,程序会被推入函数作用域中。
作用域的作用?
区分不同的变量,对数据进行保护。对变量具有存储和保护的作用。
1.1 作用域分类:
1:全局作用域 (大括号外面的区域)
2:局部作用域(大括号内部的区域)
其中,局部作用域又分为:
1:函数作用域 (函数{}内部的区域)
2:块级作用域 (非函数{}的其他{}的内部区域)
1.2 不同作用域内的变量:
全局变量:声明在全局作用域中的变量叫全局变量。
局部变量:声明在局部作用域中的变量叫局部变量。
其中:函数的形参属于局部变量
1.3 全局变量和局部变量的可见性:
1:全局变量在程序的任何地方都可以访问。
2:局部变量只能在局部作用域中可以访问。
扩展:
任何函数内都可以访问全局变量,全局作用域不能访问局部变量,局部作用域A不能访问局部作用域B中的变量
1.4 作用域的生命周期
1:全局作用域在程序关闭前一直存在,全局变量在程序关闭前一直都可以访问。全局变量一致都占据内存。
2:局部作用域在程序退出局部作用域时,局部作用域会被销毁,局部变量会随着局部作用域的销毁而销毁。
函数内的局部变量,会在函数结束调用之后被销毁。(被销毁后就无法访问了)
1.5:块级作用域
ES6新增的概念。
除了函数大括号之外的其他大括号内的区域就是块级作用域。
块级作用域内只有通过let和const声明的变量才会变成局部变量。
var 声明的变量不会变成块级作用域内的局部变量。(变量提升)
二 作用域链
若程序中出现多个同名变量,如何区分他们呢?使用作用域链机制进行变量查找可以区分。
如果局部作用域内又存在其他作用域,则这些作用域构成了一个 '作用域链'。
// 以下程序出现的作用域链是:add => fn => 块级作用域 => 全局作用域
{
function fn(){
function add(){
consle.log(x)
}
}
}
如果作用域链内出现了多个同名变量,则需要进行变量查找来区分他们。
// 以下程序出现的作用域链是:add => fn => 块级作用域 => 全局作用域
// 以下程序如果调用add打印x,打印的是10.不会打印20
{
let x = 20;
function fn(){
var x = 10;
function add(){
consle.log(x)
}
}
}
变量查找步骤:
1:写出访问变量代码所在的作用域链
2:沿着这个作用域链从里到外逐层查找变量声明,如果查找到,就停止查找(就近原则)
三 变量提升 (预解析)
var 声明的变量和函数声明在程序的解析阶段会被提升到本作用域的顶端
只提升声明部分!不提升赋值部分!
预解析完成之后,程序按照解析之后的js进行运行。
console.log(x);
var x = 10;
show();
function show(){
console.log(100)
}
// 以上代码预解析之后变成下面的的顺序,最后执行的是下面的代码顺序。
var x;
function show(){
console.log(100)
}
console.log(x);
x = 10;
show();
let 和 const 声明的变量不会有提升现象!
函数表达式只有声明的变量提升了!
var 在块级作用域中声明变量会提升到全局作用域中成为全局变量!
如果变量提升和函数提升冲突了,(标识符一致),以函数声明为准!
如果var变量和函数同名,则提升后,函数覆盖变量
循环中var和let:
循环用let声明i,则这个i是一个局部变量.
循环多少次,就有多少个块级作用域,每个作用域内有一个不同的i.
第一个i是0,第二个i是1,以此类推...
循环用var声明i,则这个i是一个全局变量.
不管循环多少次,修改的都是全局变量的i.
循环时操作元素,但凡在事件中使用了i,如果这个i是var声明的,则事件中访问到的i必定是循环结束之后的i.
循环结束之后的i,必定超出了数组的最大下标.
四 闭包
1.1 什么是闭包?
闭包有很多不同的描述。
1:闭包可以在函数外面访问闭包内部的变量。
2:闭包创建一个私有的不会被销毁的作用域,用于存储局部变量。
3:闭包就是函数套函数(最白痴的说法)
闭包表现形式:
// 这里的add作用域构成了一个闭包.
function add(){
let x = 10;
return function(){
console.log(x)
}
}
// 闭包返回子函数
let fn = add();
// 通过调用子函数来访问局部变量x
fn();
// ------------------------------------------------------------------------------
// 这里的add作用域构成了一个闭包。
function add(){
let x = 10;
oBtn.onclick = function(){
console.log(x)
}
}
// 通过点击oBtn来触发子函数,以此来访问局部变量x。
add();
oBtn.click()
1.2 闭包的作用
闭包有两个主要作用:1:存储。2:保护
闭包可以把数据存储到局部作用域中,在函数调用结束后局部变量也不会被销毁,可以一直访问。
闭包存储的变量本质上还是局部变量,在函数外部无法直接访问,但是可以通过闭包的子函数访问。
总结:闭包可以让变量同时具有全局变量任何时间都可以访问的特性,又具有局部变量不能在外部直接访问的特性
1.3 闭包的原理
闭包是如何让局部作用域在函数调用结束之后不会被销毁的?
这里需要先了解 js 的垃圾回收机制。
js 垃圾回收机制:如果一个变量无法再访问,则 js 会自动将其回收(销毁),以释放内存。
例如,函数调用结束,函数作用域被销毁,没有了函数作用域,则无法访问该作用域中的变量,
因此 js 垃圾回收机制会自动销毁局部变量。
只要让 js 认为局部变量还有可能继续得到访问,则 js 垃圾回收机制就不会销毁局部变量。
闭包通过调用子函数来访问局部变量,如果子函数可以任意时间调用,则子函数引用的局部变量就是可以在任何时间得到访问的,因此 js 垃圾回收机制就不会销毁局部变量。
只要闭包内的子函数可以在任意时间调用,则子函数内引入的变量就不会被 js 垃圾回收机制销毁.
1.4 闭包构成条件
1:父作用域套子函数作用域。
2:子函数引用父作用域内声明的局部变量。
3:子函数还可以在任意时间调用。
1.5 如何销毁闭包
销毁闭包的目的就是销毁闭包存储的变量。
知道闭包原理后,要让闭包失效,可以让子函数不再可以访问调用。
子函数不再可以调用,则子函数内部引入的变量就无法再访问,因此就会被垃圾回收机制回收以释放内存。
// 这里的add作用域构成了一个闭包。
function add(){
let x = 10;
oBtn.onclick = function(){
console.log(x)
}
}
// 通过点击oBtn来触发子函数,以此来访问局部变量x。
add();
// 这样,点击按钮不再触发子函数,达到销毁闭包目的。
oBtn.onclick = null;
五 自执行函数
自执行函数 => 匿名函数的调用,声明的同时,马上调用。
自执行函数 => 模块化开发时,需要通过自执行函数来进行模块化 => 快速创建一个函数作用域
语法:
1:(function(形参){})(实参)
2:(function(形参){}(实参))
自执行函数本质上是一个函数调用表达式.
它的返回值取决于return的值.
六 var变量提升
变量提升(预解析):
程序运行前,浏览器会把var声明和函数声明提升到本作用域顶端.提升之后代码再执行.
let 和 const 没有提升.
只提升声明部分,赋值部分不提升.
同名的变量和函数同时提升,以函数为准.
var x;
function x() {}
console.log(x);
x = 10;
console.log(x);
if (false) {
var x = 30;
}
标签:闭包,15,变量,作用域,局部变量,子函数,函数 来源: https://www.cnblogs.com/DarkCrow/p/15055377.html