es5 作用域、变量提升
参考 变量与函数的提升(ES5)
js 在es5规范中,作用域只分为全局作用域和函数作用域
js 在es5规范中,var 定义的变量,和函数定义,有 ‘提升’属性;
变量提升和函数提升:
将所有变量声明和函数声明提升到所属作用域的顶端,即所谓的变量提升和函数提升。(如果声明不在任意函数内,则视为在全局作用域的顶部)
变量提升只提升变量声明,不提升赋值初始化:
// 最简单的‘变量提升’
console.log(a);
var a = 1;
// 相当于
var a; // 只提升变量声明
console.log(a); // undefined
a = 1;
函数声明提升
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重复声明一次函数f
function f() { console.log('I am inside!'); }
}
f();
}());
实际运行的代码如下
// ES5 环境
function f() { console.log('I am outside!'); }
(function () {
var f = undefined
if (false) {
// 重复声明一次函数f
function f() { console.log('I am inside!'); }
}
f();
}());
函数作用域里的‘变量提升’
var t = '全局'
function fx () {
console.log(t+'--1') // undefined,当前函数作用域用var定义的变量t的变量声明被提升到此作用域的顶层
if (true) {
var t = 'hello'
}
function fe () {
console.log(t+'--2') // undefined,当前函数作用域用var定义的变量t被提升到此作用域的顶层
if (true) {
var t = 'hello-2'
}
}
fe()
}
fx()
console.log(t) // '全局'
输出:
undefined--1
undefined--2
全局
for (var i = 0; i < 3; i++) {
}
console.log(i) // 3
for 循环中的用 var 定义的变量被提升到所属作用域(当前函数作用域或全局作用域)的顶端。
if语句同理
js:先解析最外层定义的所有变量,再执行。调用函数时也是先解析函数内定义的变量,再执行。再往内也是如此。
es5 作用域:全局作用域、函数作用域
es6 作用域:全局作用域、函数作用域、块级作用域
块级作用域:{ }内即为块级作用域且通过let 实现
作用域链:
- 存储执行期的上下文对象集合。js通过作用域链查找变量,沿作用域链从里往外找,当读取一个变量时,先从当前作用域查找,如果没有,则查找上一级,以此类推;
- js通过作用域链查找变量只能单向找,无法从外往里找(当前作用域无法访问当前作用域内其他作用域的内部变量),var 和 let 都遵循此规则。
作用域链执行机制:
let a = 1
funciton fx() {
let a =2
let b = 4
}
步骤1: 未执行fx()时,作用域链是这样的:scopes[0]在全局作用域中,a=1, function fx
步骤2: 当fx被调用,scopes[0] => a = 2,b=4
scopes[1] => 步骤1的移到这里来
- var 在全局作用域、函数作用域的各级作用域中定义的同名变量互不干扰
// es5 es6
function foo() {
var a = 1;
function fc() {
var a = '23'
console.log(a) // 22
}
fc()
console.log(a) //1
}
foo()
- let 在各级块级作用域(只要是{}括起来的)的各级作用域中定义的同名变量互不干扰
function foo() {
let b = 2;
{
let b = 'kk'
console.log(b); // kk
if(true){
let b = 'fdsf'
console.log(b); // fdsf
}
}
}
foo()
- var 声明变量会提升声明至当前所属作用域(全局作用域、函数作用域)顶部,即使是在块级作用域中,因为var 只认全局作用域、函数作用域(es5)。
function foo() {
console.log(a); // jj
{
if(false){
var a = 'jj'
}
}
}
foo()
为什么使用块级作用域?
目的:取得合理的变量,严谨、避免隐性问题,即当前作用域没有定义的变量,则向上一级找。当前作用域有定义的变量,则取当前作用域定义和初始化的值,当前作用域先读取后定义则报错。
并且let 不存在变量提升,不允许先读取后定义变量(不会读取到非预期的变量);不允许在同一作用域内重复声明同名变量,更合理严谨。
let t = 1
fuc {
读取 t = > 1 向上找,在全局找到
}
let t = 1
fuc {
let t = 2
读取 t = > 2 当前作用域有定义t变量,取当前。
}
let t = 1
fuc {
读取 t = > 2 报错!let 不存在变量提升, 当前作用域有定义t变量,但未初始化就读取,这在es6的let语法是不允许的,更合理严谨
let t = 2
}
解决var定义取得的不合理的值
例1:
var t = 1
fuc {
读取 t = > undefined // 当前作用域定义的t变量被提升到当前顶端
var t = 2
}
例2:
for (var i = 0; i < 3; i++) {
}
console.log(i) // 用于for循环的计数变量被暴露为全局
补充:
es5中,var重复声明不报错
var a =4; var a = 5;
es6 中 let 不可重复声明
let a =4; let a = 5
VM690:1 Uncaught SyntaxError: Identifier 'a' has already been declared
拓展阅读
同时支持变量提升和块级作用域:【ES6】JavaScript块级作用域的实现原理
【灵魂拷问】当面试官问你JavaScript预编译
【底层原理】图解JavaScript作用域链
闭包
由于js作用域链的只能从里往外查找变量的特性,为了在外部读取到A函数内部的变量,在A函数内再套一个闭包函数,这个闭包函数读取到A函数内部变量后返回到外部被引用,这时外部可访问。
注意:闭包有可能导致变量不再使用又不被回收(内存泄漏),比如赋值给外部作用域的变量,全局变量是不会被回收的(因为js不知道你何时会在用到)。
function outer() {
var a = '变量1'
var inner = function () {
console.info(a)
}
return inner // inner 就是一个闭包函数,因为他能够访问到outer函数的作用域,如:a
}
var inner = outer() // 获得inner闭包函数
inner() //"变量1"
// inner 变量不被回收
如果直接在外部作用域直接单独立即执行,不赋值给任何变量,则会被回收;或者赋值执行后置为null,也会被回收。
function outer() {
var a = '变量1'
var inner = function () {
console.info(a)
}
return inner
}
outer()() // 直接立即执行,执行后,outer的a变量会被回收
// 或
var inner = outer()
inner()
inner = null
js 内存泄漏
变量未被回收,又没用到就叫内存泄漏
变量回收原则:js认为变量你可能还有用,就不会回收。
1、全局变量不会被回收。
2、函数内部定义的变量在不被其他作用域引用的情况下,函数执行后,内部定义的变量和函数全部销毁回收。
3、变量被另一个作用域引用就不会回收,比如闭包。
4、- 循环引用
js 循环引用