深入理解JS—作用域和执行上下文

上次谈过原型链的概念,其中我们会有个疑问对于一个原型链而言,肯定会存在所取值的范围规定,那么我们能否跨过这些范围去取值?又该如何取?
在这里,就要谈到 作用域的问题。

作用域

简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。在JavaScript中,变量的作用域有全局作用域和局部作用域两种。

全局作用域

在代码中任何地方都能访问到的对象拥有全局作用域,一般来说以下几种情形拥有全局作用域:
(1)最外层函数和在最外层函数外面定义的变量拥有全局作用域,例如:
var authorName="芷若初荨";
function doSomething(){
    var blogName="Cecilia620";
    function innerSay(){
        alert(blogName);
    }
    innerSay();
}
alert(authorName); //芷若初荨
alert(blogName); //脚本错误
doSomething(); //Cecilia620
innerSay() //脚本错误
(2)所有末定义直接赋值的变量自动声明为拥有全局作用域,例如:
function doSomething(){
    var authorName="芷若初荨";
    blogName="Cecilia620";
    alert(authorName);
}
doSomething(); //Cecilia620
alert(blogName); //芷若初荨
alert(authorName); //脚本错误
(3)所有window对象的属性拥有全局作用域
一般情况下,window对象的内置属性都拥有全局作用域,例如window.name、window.location、window.top等等。

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

一般在函数内部,例如下列代码中的blogName和函数innerSay都只拥有局部作用域。
function doSomething(){
    var blogName="芷若初荨";
    function innerSay(){
        alert(blogName);
    }
    innerSay();
}
alert(blogName); //脚本错误
innerSay(); //脚本错误

作用域链

在JavaScript中,函数也是对象,实际上,JavaScript里一切都是对象。函数对象和其它对象一样,拥有可以通过代码访问的属性和一系列仅供JavaScript引擎访问的内部属性。其中一个内部属性是[[Scope]],由ECMA-262标准第三版定义,该内部属性包含了函数被创建的作用域中对象的集合,这个集合被称为函数的作用域链,它决定了哪些数据能被函数访问。
当一个函数创建后,它的作用域链会被创建此函数的作用域中可访问的数据对象填充。
例如定义下面这样一个函数:
function add(num1,num2) {
    var sum = num1 + num2;
    return sum;
}

执行此函数会创建一个“执行上下文”的内部对象,那么执行上下文是什么?

执行上下文

先来看个代码,如下:
image

分析:第一句报错,a没有定义,当然报错,第二句、第三句输出都是undefined,说明浏览器在执行console.log(a)时,已经知道了a是undefined,在第三句中,但却不知道a是10。
实际上,在一段js代码拿过来真正一句一句运行之前,浏览器已经做了一些“准备工作”,其中就包括对变量的声明,而不是赋值。变量赋值是在赋值语句执行的时候进行的。可用下图模拟:

image

这是第一种情况,让我们再来看一种,如下:

image
大家看到这句代码会发现这是直接对其this赋值,并没有声明;
还有第三种情况,如下:
image

在上述代码中,我们可以看到函数声明和函数表达式,实质上函数表达式就是属于变量声明,函数声明的过程属于赋值的过程,结合看来,这就是JS解释器根据一定的顺序在查找定义的变量和函数,那么这个顺序到底是什么呢?

在这里又要提到变量对象这个概念,

*变量对象(VO)
实际上简单来说,它就是知道数据存储在哪里以及该如何获取的一种机制,同时它也是执行上下文的属性。我们常见的声明新的变量和函数的过程其实就是在VO中创建新的和变量以及函数名对应的属性和属性值的过程。
VO按照以下顺序填充:
**函数参数(若未传入,初始化该参数值为undefined)
**函数声明(若发生命名冲突,会发生覆盖)
**变量声明(初始化变量值为undefined,若发生命名中途,会选择忽略)。
大家来看段代码:
console.log('EC0');
function funEC1(){
    console.log('Ec1');
    var funEC2=function (){
        console.log('EC2');
        var funEC3=function(){
            console.log('EC3');
        };
        funEC3();
    }
    funEC2();
}
funEC1();
//输出结果为:EC0 EC1 EC2 EC3

如下图所示:
这里写图片描述

看到这里,我们会想象到,javascript在执行一个代码段之前,都会进行这些“准备工作”来生成执行上下文。这个“代码段”其实分三种情况—— 全局代码,函数体,eval代码。
PS:以上如有错误请指正!(感觉思路还是有些混乱…………)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值