浅析JavaScript 中的作用域及作用域链

执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为,每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。

某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁(全局执行环境知道应用程序退出时才会被销毁)。

——《JavaScript高级程序设计(第3版)》

作用域:全局作用域、私有作用域、块级作用域(ES6)

全局作用域

在Web浏览器中,全局作用域被认为是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。

当打开web页面的时候,会提供一个供js代码执行的环境(全局作用域),会默认提供一个最大的window对象。

全局变量

在全局作用域中声明的变量。

全局变量和window对象的关系

在window对象上的方法等,可以直接去除前面的window,直接通过属性名使用,比如window.alert("1");可直接写为alert("1");

在全局作用域下声明的变量,相当于给window对象添加了一个属性,属性名就是变量名,属性值就是变量值。(针对varfunction直接写一个变量名。ES6阻断了与window的关系,故而letconst声明的变量不会添加到window对象中。)

判断一个对象有没有某个属性

属性名 in 对象
返回值是布尔数据类型,true代表包含,false代表不包含

var obj = {name : "lili"};
"name" in obj;//返回值是布尔数据类型
带var和不带var的全局变量的异同
相同点

在全局作用域下声明都是给window添加了一个属性和属性值。

不同点
  • 带var的全局变量有变量提升;不带var的全局变量没有变量提升
  • 带var的全局变量通过delete window.属性名删不掉;不带var的全局变量可以删掉
var a=3;
b=6;
delete window.a;//false
delete window.b;//true

私有作用域

每个函数都有自己的执行环境。

函数执行的时候形成的作用域就是私有的,保护里面的变量不受外界干扰。

私有变量
  • 在私有作用域声明的变量
  • 形参
    // 形参也是私有变量
    function fn(x) {
        // 私有变量
        var m = 3;
        console.log(m);//3
    }
    console.log(window.fn());//undefined
注意

函数未被调用时,函数体以字符串的形式存储在堆内存中。

function fn(b){
    var x = 3;
    n = 3;
}
// 函数未执行,没有为window对象添加属性n及其值3
console.log(n);// n is not defined

块级作用域

为了解决块级作用域的问题,ES6引入了新的关键字let,用let替代var可以申明一个块级作用域的变量。

在块级作用域下,所有的变量都在定义的花括号内,从定义开始到花括号结束这个范围内可以使用,出了这个范围就无法访问。

// 块级作用域
{
    let s=3;
    console.log(s);//3
}
console.log(s);//s is not defined

练习

test 1

在私有作用域中只有以下两种情况是私有变量:

  • 形参
  • 声明过的变量(var、function、let 等)

剩下的都不是私有变量,需要基于作用域链向上查找

/* 
全局作用域
变量提升:var a,b,c;function fn=aaafff111
代码自上而下执行
*/
var a=12,b=13,c=14;
function fn(a){
    /* 
    私有作用域
    形参赋值:a=12
    变量提升:var b
    代码自上而下执行
    */
    console.log(a,b,c);//12 undefined 14
    var b=c=a=20;
    console.log(a,b,c);//20 20 20
}
fn(a);
console.log(a,b,c);//12 13 20
test 2

如果函数传进来的参数是引用数据类型,此时形参拿到的是地址值,虽然形参在函数里面是私有变量,但是真正更改的还是那个引用地址中存储的内容,所以全局下的变量也会发生变化。

/* 
全局作用域
变量提升:var ary;function fn=aaafff111;
代码自上而下执行
*/
var ary=[12,13];//赋值 ary=aaafff222
function fn(ary){
    /* 
    私有作用域
    形参赋值:ary=aaafff222
    变量提升:无
    */
   console.log(ary);//[12,13]
   ary[0]=100;//[100,13]
   ary=[100];//赋值 ary=aaafff333
   ary[0]=0;//[0]
   console.log(ary);//[0]
}
fn(ary);
console.log(ary);//[100,13]

作用域链

当代码在一个环境中执行时,会创建变量对象的一个作用域链(保证对执行环境有权访问的所有变量和函数的有序访问)。

当某个函数被调用时,会创建一个执行环境及相应的作用域链。然后,使用arguments 和其他命名参数的值来初始化函数的活动对象。在函数执行过程中,为读取和写入变量的值,就需要在作用域链中查找变量。

函数执行过程中查找变量的时候,先看自己私有作用域中有没有(首先查找的都是当前执行代码所在环境的变量对象),如果有就使用自己私有的变量,如果没有就向上一级作用域继续查找,如果上一级作用域也没有,就一直向上找,直到找到window(全局作用域)为止,如果window也没有,根据所执行操作不同会得到不同的结果:

1、获取操作 报错:variable is not defined

function fn(b){
    var x=3;
    console.log(n);//n is not defined
}
fn(1);

2、赋值操作 相当于给全局window对象添加了一个属性名和属性值

function fn(b){
    var x=3;
    n=3;
    console.log(n);//3
}
fn(1);
console.log(n);//3
上级作用域

当前函数执行会形成一个私有作用域A,这个私有作用域A的上级作用域是谁,跟函数在哪执行无关,跟函数在哪定义(创建)有关系,在哪定义,私有作用域A的上级作用域就是谁。

简而言之,上级作用域是谁,跟函数在哪执行没有关系,跟它在哪定义有关系。

案例 1
//C的上级作用域就是B,B的上一级作用域就是A,A的上级作用域就是window
function A(){
    function B(){
        function C(){

        }
    }
}
案例 2
var n=3;
function fn(){
    console.log(n);
}
function sum(){
    var n=6;
    fn();//fn()的上一级作用域是全局作用域
}
fn();//3
sum();//3
案例 3
/* 
var n
function fn=aaafff111 上级作用域为全局作用域
var x
*/
var n=10;
function fn(){
    /* 
    var n
    function f=aaafff222 上级作用域为fn()
    */
    var n=20;
    function f(){
        n++;
        console.log(n);
    }
    f();
    return f;
}
//fn()执行结束后将结果赋值给x
var x=fn();//x=aaafff222即x=f //21
x();//f() //22
x();//f() //23
console.log(n);//10

案例 4
//函数每次执行都会开辟一个新的栈内存
function fn(x){
    return function fn1(y){
        console.log(x,y);
    }
}
var f=fn(1);
f(2);
fn(1)(2);//fn1(2)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值