文档章节

浅析javascript中的函数及执行环境

x
 xszl
发布于 2017/09/08 21:34
字数 1427
阅读 8
收藏 0

###函数简介 函数是javascript中一个主要的组成部分,像闭包、this、全局变量和局部变量都和函数息息相关,想要真正了解javascript是如何工作的,首先就得了解函数。

函数能访问内部声明的局部变量,也可以访问那些通过函数参数传递进来的变量,还能访问当前作用域外声明的全局变量,所有的这些都是基于函数的执行环境,一个提供变量可以被函数访问的环境。

要了解清楚函数被调用时所发生的一切细节会相对有些困难,下面所述部分只是技术说明的一个简化,想要了解详情的可以参考ECMA文档。

###执行环境的创建 当函数被调用时,会创建该函数的执行环境,下面来看看这个执行环境是如何构建的,你需要注意每一步的顺序

  1. 创建arguments属性,这是一个具有整数键的数组式对象(并非是数组),对象引用传入函数调用的值,同时该对象还包括length(函数传递进来的参数的数量)和callee(指向该函数的引用)属性
  2. 通过[[scope]]属性和执行环境创建函数的作用域。后面会详细说明
  3. 变量实例化,分为三步
    • 执行环境将argument对象中的值赋给函数签名,如果argument中没有,则赋值成undefined
    • 扫描函数体,检测是否有函数声明,如果有,将函数名作为环境变量的一个属性
    • 扫描函数体,检测是否有变量声明,如果有,将变量名作为函数变量的一个属性,属性值为undefined
  4. 创建this属性,这个需要依据函数如何被调用
    • 作为正常函数被调用,this指向全局对象
    • 作为对象的方法被调用,this指向该对象
    • 作为构造函数被调用,this指向那个构造函数生成的新对象
    • 使用call或者apply调用,this指向call或者apply的第一个参数
    • 使用setTimeout或者setInterval调用,this指向全局对象

####示例:

    function foo(a, b ,c) {
        function z() {
            alert('z!');
        }
        var d = 3;
    }
    foo('foo', 'bar');

调用foo('foo', 'bar'):

  • step1:创建arguments属性
ExecutionContext: {
    arguments: {
        0: 'foo',
        1: 'bar',
        length: 2,
        callee: function() //Points to foo function
    }
}
  • step3a: 变量实例化,arguments
ExecutionContext: {
    arguments: {
        0: 'foo',
        1: 'bar',
        length: 2,
        callee: function() //Points to foo function
    },
    a: 'foo',
    b: 'bar',
    c: undefined
}
  • step3b:变量实例化,function
ExecutionContext: {
    arguments: {
        0: 'foo',
        1: 'bar',
        length: 2,
        callee: function() //Points to foo function
    },
    a: 'foo',
    b: 'bar',
    c: undefined,
    z: function() //Created z() function
}

-step3c:变量实例化,variables

ExecutionContext: {
    arguments: {
        0: 'foo',
        1: 'bar',
        length: 2,
        callee: function() //Points to foo function
    },
    a: 'foo',
    b: 'bar',
    c: undefined,
    z: function(), //Created z() function
    d: undefined
}

-step4:this赋值

ExecutionContext: {
    arguments: {
        0: 'foo',
        1: 'bar',
        length: 2,
        callee: function() //Points to foo function
    },
    a: 'foo',
    b: 'bar',
    c: undefined,
    z: function(), //Created z() function
    d: undefined,
    this: window
}

###环境栈 函数的作用域创建完之后,函数开始执行代码,从第一行直到函数结束(最后一行或者return),这期间访问变量都是从上面ExecutionContext这个对象中读取。

每个函数都会有一个与之相关联的ExecutionContext,所有的声明都是在ExecutionContext中执行(在全局执行环境中有一个GlobalExecutionContext,和ExecutionContext类似,只是全局执行环境中没有arguments,所以没有第一步)

随着程序的执行,从一个函数进入另一个函数,那么就会创建一个环境栈。我们来看下述代码

    function a() {
        function b() {
            var c = {
                d: function() {
                    alert(1);
                }
            };
            c.d();
        }
        b.call();
    }
    a();

当javascript引擎将要执行aler()函数时,执行环境栈是:

  • d() Execution context
  • b() Execution context
  • a() Execution context
  • Global execution context

进入一个函数,就会将它的执行环境压入环境栈中,这个环境栈也被称作作用域链,需要注意的是作用域链和函数调用栈大是不同的。

在函数中查找一个变量,javascript引擎会先从作用域链的最前端开始搜索,如果没有找到,会向下一级的作用域链查找,直到作用域链的最底端,如果还没找到,就返回undefined 。

###总结

  • this的值是在函数被调用的时候动态绑定,因此具备多重含义
  • arguments不是数组,而是一个带有数字作为属性的常规对象
  • 变量的定义实际是在步骤3c中定义,当代码执行到达初始化指令时,会发生初始化
  • 可以在函数声明之前调用函数
  • 不会执行的函数声明依旧会被创建(js作用域提升)
    function foo() {
        if (false) {
            function bar() {
                alert(1);
            }
        }
        bar();
    }

在执行代码进行if判断之前,bar() 已经在作用域中被创建,所以能正常运行

  • 变量覆盖,基于作用域链
  • 闭包:子函数会携带父函数的作用域,当我们访问一个变量时,如果当前执行环境中没有找到,但在下一级的作用域链中能找到
  • 全局变量在作用域链的最底端,访问全局变量需要遍历整个作用域链,效率很低。

javascript函数的核心其实就是执行环境和作用域链,合理利用这些进行设计,会让程序变得更简单自然,基于mixin的继承系统也很容易实现。很多javascript库,比如jquery,依靠执行环境加载模块,从而不会污染全局变量,尽可能深入的去了解执行环境和作用域链能发挥javascript的强大能力

© 著作权归作者所有

共有 人打赏支持
x
粉丝 0
博文 14
码字总数 17267
作品 0
浅析setTimeout与Promise

关于JavaScript异步编程,前文解析过了JavaScript并发模型,该并发模型基于事件循环。正巧又在Stackoverflow上回答了一个关于setTimeout与Promise执行顺序相关的问题,于是总结这一知识点,与...

熊建刚
08/13
0
0
javascript深入理解js闭包

一、变量的作用域 要理解闭包,首先必须理解Javascript特殊的变量作用域。 变量的作用域无非就是两种:全局变量和局部变量。 Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量...

Yamazaki
2012/06/15
0
0
深入浅出 JavaScript 中的 this

在 Java 等面向对象的语言中,this 关键字的含义是明确且具体的,即指代当前对象。一般在编译期确定下来,或称为编译期绑定。而在 JavaScript 中,this 是动态绑定,或称为运行期绑定的,这就...

idea_biu
2012/09/05
0
0
深入浅出 JavaScript 中的 this

JavaScript 是一种脚本语言,因此被很多人认为是简单易学的。然而情况恰恰相反,JavaScript 支持函数式编程、闭包、基于原型的继承等高级功能。本文仅采撷其中的一例:JavaScript 中的 this...

i33
2012/10/25
0
0
[JavaScript]-JavaScript的this原理.

一、问题的由来 学懂 JavaScript 语言,一个标志就是理解下面两种写法,可能有不一样的结果。 上面代码中,虽然obj.foo和foo指向同一个函数,但是执行结果可能不一样。请看下面的例子。 这种...

xiaoLoo
06/27
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Dubbo分析之Transport层

前言 上一篇文章Dubbo分析之Serialize层,介绍了最底层的序列化/反序列化层,本文继续分析Serialize层的上一层transport网络传输层,此层使用了现有的一些通讯开源框架(ex:netty,mina,grizzl...

ksfzhaohui
13分钟前
0
0
通告!Android 9 Pie未适配应用公示

8月7日,谷歌正式发布Android 9 Pie,至今已两月有余。近日,华为终端开放实验室对国内主流应用在Android 9 Pie的兼容性进行测试,结果显示:目前TOP3000应用兼容率已经超过95%,但仍有少量应...

安卓绿色联盟
15分钟前
0
0
Linux下多网卡绑定模式详解

在我们日常Linux使用中,一般对于生产网都会使用双网卡或多网卡接入,这样既能添加网络带宽,同时又能做相应的冗余,可谓好处多多。而一般我们都会使用Linux操作系统下自带的网卡绑定模式。这...

openthings
17分钟前
0
0
SylixOS中AARCH64跳转表实现原理

1. 跳转表存在的意义 1.1 内核模块反汇编 如下的程序清单,为一个内核模块的源码。 #define __SYLIXOS_KERNEL#include <SylixOS.h>#include <module.h> /* * SylixOS call module_i......

zhywxyy
18分钟前
1
0
聊一聊 Spring 中的线程安全性

本文摘自ImportNew公众号,摘录做学习资料,向大家推荐该公众号 Spring与线程安全 Spring作为一个IOC/DI容器,帮助我们管理了许许多多的“bean”。但其实,Spring并没有保证这些对象的线程安...

木子SMZ
19分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部