全面了解JavaScript的作用域链


Posted in Javascript onApril 03, 2019
JavaScript的作用域链

这是一个非常重要的知识点了,了解了JavaScript的作用域链的话,能帮助我们理解很多‘异常'问题。

下面我们来看一个小例子,前面我说过的声明提前的例子。

var name = 'Skylor.min';
 function echo() {
 alert(name);
 var name = 'mm';
 alert(name);
 alert(age);
 }

 echo();

 对于这个例子,没有接触过这方面的时候,第一反应是会纠结下,这第一个的name,到底调用全局变量的name,还是函数内部的name呢,如果调用全局的,可是函数内部也用定义和赋值啊, 如果调用函数内部的局部变量的话,那么他的值是mm吗?还是引用全局的'Skylor.min'呢?

于是这个小例子就会有这样的错误答案:

Skylor.min
mm
[脚本出错]
 

其实不然,知道函数内的提前说明,就知道这是不正确的。

    undefined
    mm
    [脚本出错]
 

应该是这样的,那到底为什么是这个答案呢,提前声明这又是什么呢?一切的一切,涉及到JavaScript的作用域链。

原理

首先来说说,JavaScript的作用域的原理:

在JavaScript权威指南中有一句很精辟的描述: JavaScript中的函数运行在它们被定义的作用域里,而不是它们被运行的作用域里。

另外在JavaScript中有个很重要的概念,那就是: 在JavaScript中,一切皆对象,函数也是。

在JS中,作用域的概念和其他语言差不多, 在每次调用一个函数的时候 ,就会进入一个函数内的作用域,当从函数返回以后,就返回调用前的作用域

JS的语法风格和C/C++类似, 但作用域的实现却和C/C++不同,并非用“堆栈”方式,而是使用列表,具体过程如下(ECMA262中所述):

  • 任何执行上下文时刻的作用域, 都是由作用域链(scope chain, 后面介绍)来实现
  • 在一个函数被定义的时候, 会将它定义时刻的scope chain链接到这个函数对象的[[scope]]属性
  • 在一个函数对象被调用的时候,会创建一个活动对象(也就是一个对象), 然后对于每一个函数的形参,都命名为该活动对象的命名属性, 然后将这个活动对象做为此时的作用域链(scope chain)最前端, 并将这个函数对象的[[scope]]加入到scope chain中.

看个例子吧:

var func = function(lps, rps){
        var name = 'Skylor.min';
        ........
    }
    func();
 

在执行func的定义语句的时候, 会创建一个这个函数对象的[[scope]]属性(内部属性,只有JS引擎可以访问, 但FireFox的几个引擎(SpiderMonkey和Rhino)提供了私有属性__parent__来访问它), 并将这个[[scope]]属性, 链接到定义它的作用域链上(后面会详细介绍), 此时因为func定义在全局环境, 所以此时的[[scope]]只是指向全局活动对象window active object.

在调用func的时候, 会创建一个活动对象(假设为aObj, 由JS引擎预编译时刻创建, 后面会介绍),并创建arguments属性, 然后会给这个对象添加俩个命名属性aObj.lps, aObj.rps; 对于每一个在这个函数中申明的局部变量和函数定义, 都作为该活动对象的同名命名属性.

然后将调用参数赋值给形参数,对于缺少的调用参数,赋值为undefined。

然后将这个活动对象做为scope chain的最前端, 并将func的[[scope]]属性所指向的,定义func时候的顶级活动对象, 加入到scope chain.

有了上面的作用域链, 在发生标识符解析的时候, 就会逆向查询当前scope chain列表的每一个活动对象的属性,如果找到同名的就返回。找不到,那就是这个标识符没有被定义。

注意到, 因为函数对象的[[scope]]属性是在定义一个函数的时候决定的, 而非调用的时候, 所以如下面的例子:

var name = 'Skylor.min';
 function echo() {
 alert(name);
 }

 function env() {
 var name = 'mm';
 echo();
 }

 env();

他的运行结果是:Skylor.min

结合上面的知识, 我们来看看下面这个例子,还记得那句JavaScript权威指南中的经典,JavaScript中的函数运行在它们被定义的作用域里,而不是它们被运行的作用域里。

function factory() {
 var name = 'Skylor.min';
 var intro = function(){
  alert('I am ' + name);
 }
 return intro;
 }

 function app(para){
 var name = para;
 var func = factory();
 func();
 }

 app('mm');

当调用app的时候, scope chain是由: {window活动对象(全局)}->{app的活动对象} 组成.

在刚进入app函数体时, app的活动对象有一个arguments属性, 其他俩个值为undefined的属性: name和func. 和一个值为'mm'的属性para;

此时的scope chain如下:

[[scope chain]] = [
 {
  para : 'mm',
  name : undefined,
  func : undefined,
  arguments : []
 }, {
  window call object
 }
 ]

 当调用进入factory的函数体的时候, 此时的factory的scope chain为:

[[scope chain]] = [
 {
  name : undefined,
  intor : undefined
 }, {
  window call object
 }
 ]

注意到, 此时的作用域链中, 并不包含app的活动对象.

在定义intro函数的时候, intro函数的[[scope]]为:

[[scope chain]] = [
 {
  name : 'Skylor.min',
  intor : undefined
 }, {
  window call object
 }
 ]

从factory函数返回以后,在app体内调用intor的时候, 发生了标识符解析, 而此时的sope chain是:

[[scope chain]] = [
 {
  intro call object
 }, {
  name : 'Skylor.min',
  intor : undefined
 }, {
  window call object
 }
 ]

 因为scope chain中,并不包含factory活动对象. 所以, name标识符解析的结果应该是factory活动对象中的name属性, 也就是'Skylor.min'.

所以运行结果是: I am Skylor.min

至此,完整的一个运行流程,很清晰的能读懂“JavaScript中的函数运行在它们被定义的作用域里,而不是它们被运行的作用域里。”这句话讲的是什么了。

为了解释上面的一些问题,还得说说JavaScript的预编译。

JavaScriptの预编译

预编译,学过C等的我们都知道,可是问题来了,JavaScript是脚本语言,JavaScript的执行过程是一种翻译执行的过程,那在JavaScript的执行中,有没有类似编译的过程呢?

如果不是很确定,先通过一个例子:

alert(typeof fun); //function
    function fun() {
        alert('I am Skylor.min');
    };
 

这时候弹出来的是?-----我去,是“I am Skylor.min”然而这时为什么呢,为啥不是undefined呢。

恩, 对, 在JS中, 是有预编译的过程的, JS在执行每一段JS代码之前, 都会首先处理var关键字和function定义式(函数定义式和函数表达式).

如上文所说, 在调用函数执行之前, 会首先创建一个活动对象, 然后搜寻这个函数中的局部变量定义,和函数定义, 将变量名和函数名都做为这个活动对象的同名属性, 对于局部变量定义,变量的值会在真正执行的时候才计算, 此时只是简单的赋为undefined.

而对于函数的定义,是一个要注意的地方:

alert(typeof fun); //结果:function
 alert(typeof fn); //结果:undefined
 function fun() { //函数定义式
 alert('I am Skylor.min');
 };
 var fn = function() { //函数表达式
 }
 alert(typeof fn); //结果:function

这就是函数定义式和函数表达式的不同, 对于函数定义式, 会将函数定义提前. 而函数表达式, 会在执行过程中才计算.

说到这里, 顺便说一个问题 :

    var name = 'Skylor.min';
    age = 25;
 

我们都知道不使用var关键字定义的变量, 相当于是全局变量, 联系到我们刚才的知识:

在对age做标识符解析的时候, 因为是写操作, 所以当找到到全局的window活动对象的时候都没有找到这个标识符的时候, 会在window活动对象的基础上, 返回一个值为undefined的age属性.

也就是说, age会被定义在顶级作用域中.

现在, 也许你注意到了我刚才说的: JS在执行每一段JS代码之前, 都会首先处理var关键字和function定义式(函数定义式和函数表达式).

对, 让我们看看下面的例子:

<script >
 alert(typeof mm); //结果:undefined
 </script >
 <script >
 function mm() {
  alert('I am Skylor.min');
 }
 </script >

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
采用CSS和JS,刚好我最近有个站点要用到下拉菜单!
Jun 26 Javascript
js资料prototype 属性
Mar 13 Javascript
jquery文字上下滚动的实现方法
Mar 22 Javascript
jquery form表单序列化为对象的示例代码
Mar 05 Javascript
jquery实现效果比较好的table选中行颜色
Mar 25 Javascript
jQuery之DOM对象和jQuery对象的转换与区别分析
Jan 08 Javascript
纯JavaScript代码实现文本比较工具
Feb 17 Javascript
如何用Node写页面爬虫的工具集
Oct 26 Javascript
Node.js fs模块(文件模块)创建、删除目录(文件)读取写入文件流的方法
Sep 03 Javascript
javascript 函数的暂停和恢复实例详解
Apr 25 Javascript
jQuery+ajax实现用户登录验证
Sep 13 jQuery
SpringBoot在yml配置文件中配置druid的操作
Nov 16 Javascript
从理论角度讨论JavaScript闭包
Apr 03 #Javascript
Node.js+Express+Mysql 实现增删改查
Apr 03 #Javascript
微信小程序配置服务器提示验证token失败的解决方法
Apr 03 #Javascript
js前端面试之同步与异步问题详解
Apr 03 #Javascript
详解JavaScript 为什么要有 Symbol 类型?
Apr 03 #Javascript
es6 filter() 数组过滤方法总结
Apr 03 #Javascript
基于Vue 实现一个中规中矩loading组件
Apr 03 #Javascript
You might like
如何去掉文章里的 html 语法
2006/10/09 PHP
php实现图片文件与下载文件防盗链的方法
2014/11/03 PHP
PHP中的一些常用函数收集
2015/05/26 PHP
YII Framework框架教程之安全方案详解
2016/03/14 PHP
thinkPHP自动验证机制详解
2016/12/05 PHP
详解thinkphp实现excel数据的导入导出(附完整案例)
2016/12/29 PHP
PHP实现上传多文件示例代码
2017/02/20 PHP
tp5 sum某个字段相加得到总数的例子
2019/10/18 PHP
用js实现下载远程文件并保存在本地的脚本
2008/05/06 Javascript
jQuery 使用手册(七)
2009/09/23 Javascript
javascript 图片上一张下一张链接效果代码
2010/03/12 Javascript
理解Javascript_06_理解对象的创建过程
2010/10/15 Javascript
JS+CSS实现仿支付宝菜单选中效果代码
2015/09/25 Javascript
js图片轮播效果实现代码
2020/04/18 Javascript
JS 学习总结之正则表达式的懒惰性和贪婪性
2017/07/03 Javascript
vue与bootstrap实现时间选择器的示例代码
2017/08/26 Javascript
JavaScript面向对象精要(下部)
2017/09/12 Javascript
使用flow来规范javascript的变量类型
2019/09/12 Javascript
JS实现移动端可折叠导航菜单(现代都市风)
2020/07/07 Javascript
Python中装饰器的一个妙用
2015/02/08 Python
基于python log取对数详解
2018/06/08 Python
pandas DataFrame 删除重复的行的实现方法
2019/01/29 Python
Python、 Pycharm、Django安装详细教程(图文)
2019/04/12 Python
python实现简单井字棋游戏
2020/03/04 Python
Jogun Shop中文官网:韩国知名时尚男装网站
2016/10/12 全球购物
美国流行背包品牌:JanSport(杰斯伯)
2018/03/02 全球购物
英国时尚优质的女装:Hope Fashion
2018/08/14 全球购物
英国家用电器折扣网站:Electrical Discount UK
2018/09/17 全球购物
车工岗位职责
2013/11/26 职场文书
小组口号大全
2014/06/09 职场文书
领导班子对照检查材料
2014/09/22 职场文书
正风肃纪剖析材料
2014/09/30 职场文书
公安机关党的群众路线教育实践活动剖析材料
2014/10/10 职场文书
选调生挂职锻炼工作总结
2015/10/23 职场文书
小学记事作文之200字
2019/08/06 职场文书
python用海龟绘图写贪吃蛇游戏
2021/06/18 Python