09-JavaScript作用域、作用域链、闭包、执行上下文

局部作用域 scope (函数作用域) 全局作用域

在js中,一段程序代码中所用到的名字并不总是有效可用的,而限定这个名字的可用性代码范围就是这个名字的作用域,作用域的使用提高了程序逻辑的局限性增强了程序的可靠性减少名字冲突
ES6 下一代标准,新增的有块级作用域 有 {} if() for{}
根据作用域的不同,名字可以分为两种:全局变量;局部变量
在全局作用域下声明的变量叫做全局变量(在函数外部定义的变量)
特点:
全局变量在代码中的任何位置都可以使用
特殊情况下,在函数内不使用var声明的变量也是全局变量(不建议使用)
只有在浏览器关闭时才会被销毁,因此比较占内存
在局部作用域下声明的变量叫做局部变量(在函数内部定义的变量)
当代码块运行结束后,就会被销毁,因此更节省内存空间
特点:
局部变量只能在该函数内部使用
函数的形参实际上就是局部变量
JavaScript 中,在函数体内 var 声明的变量是函数级作用域,是局部变量,在本函数体内可以访问,而且是在函数体内任意位置可以访问
function test() {
    console.log(val);
    var val = 'this is val';
    console.log(val);
 
    func();
    function func() {
        for (var i = 0; i < 5; i++) {
        }
        console.log('i: ', i);
        console.log('this is func');
    }
}
 
test();
上述代码结果是
undefined
this is val
i:  5
this is func
如下解析
function test() {
    // 变量提升, 缺省值是 undefined
    var val;
 
    // 函数声明提升
    function func() {
        // 变量提升
        var i;
 
        for (i = 0, i < 5, i++) {
        }
        console.log('i: ', i);
        console.log('this is func');
    }
 
    console.log(val);
    // 变量赋值
    val = 'this is val';
    console.log(val);
 
    func();
}

作用域链

作用域链包含了执行环境有权访问的所有变量和访问顺序
作为单线程语言的 JavaScript,初始化代码时会创建一个全局上下文,每一次函数调用都会创建一个执行上下文,执行上下文及包含关系:
  • 只要是代码,就至少有一个作用域
  • 写在函数内部的局部作用域
  • 如果函数中还有函数,那么在这个作用域中就又可以诞生一个作用域
根据在内部函数可以访问外部函数变量这种机制,用链式查找决定哪些数据能被内部函数访问,就称作作用域链.
ES6中的letconst
实现了块级所用域的变量声明方式 好处是 能有效避免由于变量提升导致的变量污染的问题;用 let 和 const 声明的变量作用域是代码块,这个设计比较符合大多数人的思维方式
const 声明了一个指向变量的指针,并不是说 const 声明的变量不可改变, 而是该指针指向的地址不可改变。

this

his 总是指向调用该函数的对象
预解析
JavaScript代码是由浏览器中的JavaScript解析器来执行的。JavaScript解析器在运行JavaScript代码的时候分为两步:预解析和代码执行
// 1问
// console.log(num);
 
// 2问
    console.log(num); //undefined   坑1
    var num = 10;
    // 相当于执行了以下代码
    // var num;
    // console.log(num);
    // var num = 10;
 
// 3问  (函数提升)
    fn();
    function fn() {
        console.log(11);
    }
 
// 4问
    fun(); //报错 坑2
    var fun = function() {
        console.log(22);
    }
    // 函数表达式 调用 必须写在函数表达式的下面
    // 相当于执行了以下代码
    // var fun;
    // fun();
    // fun = function() {
    //     console.log(22);
    // }
 
 
// 1.我们js引擎运行js分为两步: 预解析  代码执行
    // (1)预解析 js引擎会把js 里面所有的var 还有function 提升到当前作用域的最前面
    // (2)代码执行  按照代码书写的顺序从上往下执行
// 2.预解析分为 变量预解析(变量提升) 和 函数预解析(函数提升)
    // (1)变量提升 就是把所有的变量声明提升到当前的作用域最前面 不提升赋值操作
    // (2)函数提升 就是把所有的函数声明提升到当前作用域的最前面 不调用函数
预解析案例
// 案例1  undefined
var num = 10;
fun();
 
function fun() {
    console.log(num);
    var num = 20;
}
// 相当于执行了以下操作
// var num;
// function fun() {
//     var num;
//     console.log(num);
//     num = 20;
// }
// num = 10;
// fun();
// 案例2 
// 案例2
f1();
console.log(c);
console.log(b);
console.log(a);
 
function f1() {
    var a = b = c = 9;
    console.log(a);
    console.log(b);
    console.log(c);
}
 
// 相当于以下代码
function f1() {
    var a;
    a = b = c = 9;
    // var a = b = c = 9;
    // 相当于 var a = 9; b = 9; c = 9;   b和c直接赋值 没有var声明 当全局变量看
    // 集体声明 var a = 9, b = 9, c = 9;
    console.log(a); //9
    console.log(b); //9
    console.log(c); //9
}
f1();
console.log(c); //9
console.log(b); //9
console.log(a); //报错
// 案例3   undefined,20
var num = 10;
 
function fn() {
    console.log(num);
    var num = 20;
    console.log(num);
}
fn();
// 相当于以下代码
// var num;
// function fn() {
//     var num;
//     console.log(num);
//     num = 20;
//     console.log(num);
// }
// num = 10;
// fn();

令人迷惑的闭包

说起闭包 那离不开函数这个大项。在js中,函数是一等公民,那就意味着函数的使用时非常灵活的,比如,可以作为另外一个函数的参数,也可以作为另外一个函数的返回值来使用;
分成两个:在计算机科学中和JavaScript中。
计算机科学中对闭包的定义:
闭包又称词法闭包或函数闭包,是在支持头等函数的编程语言中,实现词法绑定的一种技术。在实现上是一个结构体,存储了一个函数和一个关联的环境。闭包和函数最大的区别在于,当捕捉闭包的时候,他的自由变量会在捕捉时被确定,这样即使脱离了捕捉时的上下文,也能照常运行。
闭包的概念出现于60年代,最早实现闭包的程序是 Scheme,javascript中有大量的设计是来源于Scheme,
定义:一个函数和对其周围状态的引用捆绑在一起,这样的组合就是闭包。换句话说,就是一个普通函数可以访问到其外层函数的作用域;每当创建一个函数,闭包就会在函数创建的同时被创建出来;
function a(count){
	return function (b){
	return count + b
}
}
var c = a(10)
console.log(c(5))
//此时是形成了闭包的
简单理解:一个作用域可以访问另一个函数内部的局部变量
作用
  • 延伸了变量的作用范围
  • 使变量的值始终保持在内存中且不会污染全局作用域
闭包的内存泄漏
如果后续我们不再使用c函数了,那么该函数对象应该要被销毁,并且其引用着的父作用域AO也应该被销毁掉;
但是目前因为在全局作用域下c变量有引用,会造成内存都是无法被释放的;那么怎么解决这个问题呢?值为null 就可以切断引用,垃圾回收机制在下一次检测时,他们就会被销毁掉。
执行上下文
指当前执行环境中的变量、函数声明,参数(arguments),作用域链,this等信息。分为全局执行上下文、函数执行上下文,其区别在于全局执行上下文只有一个,函数执行上下文在每次调用函数时候会创建一个新的函数执行上下文。
const ExecutionContextObj = {
    VO: window, // 变量对象
    ScopeChain: {}, // 作用域链
    this: window
};
执行上下文生命周期
创建阶段
生成变量对象
  • 创建arguments
  • 扫描函数声明
  • 扫描变量声明
建立作用域链
确定this的指向
执行阶段
  • 变量赋值
  • 函数的引用
  • 执行其他代码
执行上下文栈
执行上下文栈的作用是用来跟踪代码的,由于JS是单线程的,每次只能做一件事情,其他的事情会放在指定的上下文栈中排队等待执行。
JS解释器在初始化代码的时候,首先会创建一个新的全局执行上下文到执行上下文栈顶中,然后随着每次函数的调用都会创建一个新的执行上下文放入到栈顶中,随着函数执行完毕后被执行上下文栈顶弹出,直到回到全局的执行上下文中。(栈:一种数据结构,遵循后进先出的原则)
function getName() {
    const year = getYear();
 
    const name = 'Lynn';
    console.log(`${name} ${year} years old this year`);
}
 
function getYear() {
    return 18;
}
 
getName();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值