参考资料

代码的执行所处的环境,也叫执行上下文,它确定了代码的作用域,作用域链,this属性,代码的生存期等等,让我们从解释器的角度看代码是如何执行的。

EC可以用如下的数据结构表达,它有很多属性,VO,[[scope]],this等等。

  1. EC={
  2. Variable Object:....,
  3. [[scope]]:...,
  4. this:...
  5. }

1 三种EC(代码执行环境)

  • global
  • function
  • eval

2 代码执行的过程

一段JS代码进入解释器到执行分为2步骤,这2步各自有各自的事要处理

  • 进入执行环境
  • 执行代码

3 Variable Object(VO)

我们声明的变量,声明的函数,传入的参数都放到哪里去了?引擎是在哪里寻找它们的?其实它们都放入到一个叫VO的对象里去了,可以说了解VO是很重要的。VO的数据结构可以如下表达

  1. VO={
  2. 声明的变量,
  3. 声明的函数,
  4. 参数(函数内部VO拥有)
  5. }

3.1 函数的声明与表达式

  • 函数声明式:function 函数名称 (参数:可选){ 函数体 }
  • 函数表达式:function 函数名称(可选)(参数:可选){ 函数体 }

ECMAScript是通过上下文来区分的,如果function foo(){}是作为赋值表达式的一部分的话,那它就是一个函数表达式,反之为函数声明式。

  1. function foo(){} // 声明
  2. var bar = function foo(){}; // 表达式,因为它是赋值表达式的一部分
  3. new function bar(){}; // 表达式,因为它是new表达式
  4. (function(){
  5. function bar(){} // 声明
  6. })();
  7. (function foo(){}); // 函数表达式:包含在分组操作符()内,而分组符里的表达式

注意:根据表达式的生成函数的函数名称是不会放入函数所处的EC的VO中的。

3.1.1 DEMO

  1. function f() {
  2. var bar = function foo() {};//这是表达式声明函数
  3. typeof bar;//function;
  4. typeof foo;//undefined;
  5. }
  6. f.VO.foo=undefined;//无论是在代码进入环境还是代码执行的时候

但是这个foo只在foo函数EC中有效,因为规范规定了标示符foo不能在外围的EC有效,而且是在foo的VO中存在,有些浏览器(chrome)是无法用debug访问到的,但是firefox是可以访问到的,但是IE6~IE8是在foo的外围可以访问到foo的,IE9已经修复了这个问题,可以用IE8执行如下代码。

  1. alert(typeof foo);//undefined
  2. var bar = function foo() {
  3. alert(typeof foo);//function
  4. function k() {
  5. }
  6. return function () {
  7. alert(typeof foo);//function
  8. alert(typeof k);//function
  9. }
  10. }
  11. bar()();

EC确定了VO的不同,所以按EC给VO分类。

3.2 全局环境的VO

所有在global声明的函数,变量都会在global的VO中存在。

  1. global.vo = {
  2. Math: <...>,
  3. String: <...>
  4. ...
  5. ...
  6. window: global //引用自身
  7. };

3.3 函数的VO

当进入执行上下文VO会有如下属性:

  • 函数的所有形参(如果我们是在函数执行上下文中)

    由名称和对应值组成的一个变量对象的属性被创建;没有传递对应参数的话,那么由名称和undefined值组成的一种变量对象的属性也将被创建。

    1. function f(a, b, a) {
    2. debugger;
    3. }
    4. f(1, 2, 3);

    执行的时候f的VO

    1. f.VO={
    2. a:3,
    3. b:2
    4. }

    因为形参名字重复,而VO的key是不可以重复的(VO是一个对象),所以在代码执行给VO赋值时根据先后顺序最后一个实参会覆盖第一个实参的值。

  • 所有函数声明(FunctionDeclaration, FD)

    由名称和对应值(函数对象(function-object))组成一个变量对象的属性被创建;如果变量对象已经存在相同名称的属性,则完全替换这个属性。

  • 所有变量声明(var, VariableDeclaration)

    由名称和对应值(undefined)组成一个变量对象的属性被创建;如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。

可以看到声明的函数优先级大于变量的声明

  1. alert(x); // function
  2. var x = 10;
  3. alert(x); // 10
  4. x = 20;
  5. function x() {};
  6. alert(x); // 20

3.3.1 DEMO

test1

  1. function f(a, b, c) {
  2. var a = a, g = 1;
  3. function g() {
  4. //function body
  5. }
  6. var k = 1;
  7. }
  8. f(1, 2, 3);

引擎进入执行环境时,把EC中的变量,形参,声明的函数放入VO,成为VO的属性

  1. f.VO={
  2. a:undefined,//这个是形参的a,优先级高于声明的变量a
  3. b:undefined,//这个是形参的b
  4. c:undefined,//这个是形参的c
  5. g:function,//函数的优先级最高,覆盖了变量g
  6. k:undefined//声明的k
  7. };

代码执行时给VO属性赋值,按代码执行过程

  1. f.VO={
  2. a:1,
  3. b:2,
  4. c:3,
  5. g:function,
  6. k:1
  7. };

test2

  1. function f(a) {
  2. a = a;
  3. b = a;
  4. }
  5. f(1);
  6. alert(a);//undefined
  7. alert(b);//1

test2很奇怪是吧,我们认为会alert(a)提示“1”,但是结果是undefined。在我们的脑海里总是有这个概念:没有声明的变量会变成全局变量,其实根本没这回事。事实是:给没有声明的变量赋值造成的现象是变量变为了global的属性(也就是window属性),而不是一个全局变量。让我们来看下代码的流程。

进入环境,把形参标示放入VO中,并赋值为undefined

  1. f.VO={
  2. a:undefined
  3. }

这里没有把b算入f的VO,因为b不是声明出来的

执行时

形参a根据实参1,被赋予1,代码第一行a=a,右边的a为1,解释给左边的a赋值,解释器从VO开始寻找a,发现VO中有a,就给其赋予形参a的值——1。解释器寻找b,从[[scope]]寻找一直到global的VO都没找到b,于是就给global添加一个属性——b,赋值于1。期间没有产生新的变量

test3

  1. function f() {
  2. var a = 1;
  3. return {
  4. set:function (b) {
  5. a = b;
  6. },
  7. get:function () {
  8. return a;
  9. }
  10. }
  11. }
  12. var o=f();
  13. o.set(2);
  14. o.get();//2

总结:

  • 所以我们没必要在函数体内又声明一个和形参相同的变量,直接访问形参变量便是。
  • 我们声明的变量可以放到一起声明,原因已经在前面阐述了,而代码执行过程只是根据你的赋值表达式往VO中属性赋值。

4 [[scope]]

[[scope]]是函数的内部属性,它指向一个数组对象(俗称作用域链对象),这个数组对象会包含父亲函数的VO一直到global的VO。

  1. [[scope]]-->VO+[[scope]]

这个对象在2种环境(进入执行环境,执行代码)有着不同状态。

eg

  1. function f() {
  2. var a = 1;
  3. }

针对这个函数来说。

  • 进入执行环境(执行代码前),函数的EC中的VO和[[scope]]

    1. f.VO={
    2. a:undefined
    3. }
    4. f.scope=[global.VO];//全局vo在进入f的执行环境前已经创建了。
  • 执行时,把f的VO推入[[scope]]指向的数据对象第一位。

    1. f.VO={
    2. a:1
    3. }
    4. f.scope=[f.VOglobal.VO];

4.1 catch,with可以改变[[scope]]指向的数组对象结构

catch,with关键词会在执行时把参数推入[[scope]]指向的数组对象第一位

  1. witch({a:1}){
  2. alert(a);//1
  3. var a=2;
  4. alert(a);//2
  5. }
  • 进入环境

    1. vo={
    2. a:undefined
    3. }
  • 执行

    1. scope=[{a:1},vo,global.vo]--->alert(a)//1
    2. var a=2;//执行到这里时a的值发生了改变并且影响到scope
    3. scope=[{a:2},vo,global.vo]--->alert(a)//2,

从本质上了解了作用域链,就很容易理解闭包了。

4.2 闭包

当函数内部定义了其他函数,就创建了闭包,闭包(子函数)有权访问父级函数的VO所有变量。

如果子函数[[scope]]持续引用了父函数的VO,就会使父函数的VO无法销毁掉。所以我们要妥善处理闭包的特性。

  1. function f() {
  2. var val = 1;
  3. return function () {
  4. return val;
  5. }
  6. }
  7. var temp=f();

返回的函数[[scope]]持有f函数的VO,已至于f执行后无法释放VO等所占用的内存。

  1. var temp=null;//让GC去处理f的内存吧。

5 this

this是在代码进入执行环境时确认的,所以按代码进入执行环境时所在说明this更为清晰。

5.1 顶级执行环境global。

代码在global中执行,this永远都是global。

5.2 代码在函数执行环境

函数的EC中的this是由函数调用的方式来确定的。

5.2.1 使用apply,call方法调用函数

this指向这些函数的第一个参数

  1. var sth = "global";
  2. function f() {
  3. alert(this.sth);
  4. }
  5. f();//global
  6. var o = {sth:"o"};
  7. f.apply(o);//o
  8. f.call(o);//o

5.2.2 使用new调用函数

这是的函数叫构造函数

  • 先生成一个对象
  • this指向这个新对象~

5.2.3 单独使用()调用函数

执行时this的值取决于()左边的值所属的对象

5.2.3.1 当生成的函数对象有被引用

如果函数被引用,那么this指向这个引用函数的东东的所属环境,但是函数被函数的VO引用那么this指向null,再而转为global。

test1

  1. var sth = "global";
  2. function f() {
  3. alert(this.sth);
  4. }
  5. f();//global
  6. var o = {sth:"o"};
  7. o.f = f;
  8. o.f();//o

test2

  1. var a = 'global';
  2. function f() {
  3. alert(this);
  4. }
  5. f();//global
  6. f.prototype.constructor();//f.prototype

test3

这种情况下,f被k的vo引用,f的执行环境的this指向null,转为global。

  1. function k() {
  2. function f() {
  3. alert(this);
  4. }
  5. f();//window
  6. }
  7. k.vo.f=function;
  8. f.this=null==>global;

test4

  1. var foo = {
  2. bar: function () {
  3. alert(this);
  4. }
  5. };
  6. foo.bar(); // Reference, OK => foo ()左边的引用类型属于foo,this指向foo
  7. (foo.bar)(); // Reference, OK => foo “()”对foo.bar没有任何处理,返回仍是foo.bar。
  8. (foo.bar = foo.bar)(); // global 赋值操作符使返回值是foo.bar所指向的函数。返回的是一个没有东西引用的function
  9. (false || foo.bar)(); // global ||同上
  10. (foo.bar, foo.bar)(); // global 连续运算符仍是同上
5.2.3.2 当函数没有被引用

this指向null,但是浏览器不会让你这么干,它会把null变为global。

注:第5版的ECMAScript中,已经不强迫转换成全局变量了,而是赋值为undefined。

  1. (function (){
  2. alert(this);//window
  3. })();

5.3 eval执行环境中的this

  1. eval('alert(this)');//window

5.4 让我们回想下DOM事件

5.4.1 以节点对象的属性注册事件处理函数

以这个概念注册事件处理函数有2种实现方法,但是殊途同归——都是给节点对象的事件属性注册事件处理函数。

5.4.1.1 直接在html里写事件处理函数
  1. <div onclick="alert(this.innerHTML);">1</div>
  2. ==>'1'
5.4.1.2 用对象特性写事件处理函数
  1. <div id="J_Demo1">2</div>
  2. <script type="text/javascript">
  3. document.getElementById("J_Demo1").onclick = function () {
  4. alert(this.innerHTML);//2
  5. };
  6. </script>

5.4.2 addEventListener & attachEvent

其实说这2个方法对理解function的this有点跑偏,但是还是要标记下。

2这不同的是addEventListener绑定事件处理函数后函数的this指向这个节点对象,attachEvent指向window(attachEvent存在于<=IE8)。

6 Test

test1

有时候我们想这样做

  1. var $=document.getElementById;//引用这个方法
  2. $("J_Head");//Illegal invocation 非法调用

为什么会这样呢?因为我们调用getElementById的方式不对。$()执行是getElementById的中的this指向的是window,用document.getElementById方式调用getElementById,其this指向的是document,原因已经说过了。所以...

  1. $.apply(document,["login-container"]);//指定this指向对象就ok了
  2. (1,document.getElementById)();//Illegal invocation,连续运算符将getElementById已经从document中取出,执行时this指向null,进而指向global

Execution Context(EC) in ECMAScript的更多相关文章

  1. 你不知道的JavaScript--Item19 执行上下文(execution context)

    在这篇文章里,我将深入研究JavaScript中最基本的部分--执行上下文(execution context).读完本文后,你应该清楚了解释器做了什么,为什么函数和变量能在声明前使用以及他们的值是如 ...

  2. Javascript 的执行环境(execution context)和作用域(scope)及垃圾回收

    执行环境有全局执行环境和函数执行环境之分,每次进入一个新执行环境,都会创建一个搜索变量和函数的作用域链.函数的局部环境不仅有权访问函数作用于中的变量,而且可以访问其外部环境,直到全局环境.全局执行环境 ...

  3. HttpClient(4.3.5) - HTTP Execution Context

    Originally HTTP has been designed as a stateless, response-request oriented protocol. However, real ...

  4. Client API Object Model - Execution Context

    1. executionContext. executionContext定义代码在其中执行的上下文. 并且适用在再form或者grid中的event handler. 比如formContext 或 ...

  5. 创建dynamics CRM client-side (三) - Execution Context

    Execution Context 在代码执行的时候定义了event  context. 当form或者grid发生event时候传递了execution context. 可以在event hand ...

  6. JavaScript内部原理系列-执行上下文(Execution Context)

    概要 本文将向大家介绍ECMAScript的执行上下文以及相关的可执行代码类型. 定义 每当控制器到达ECMAScript可执行代码的时候,控制器就进入了一个执行上下文.执行上下文(简称:EC)是个抽 ...

  7. 理解Javascript之执行上下文(Execution Context)

    1>什么是执行上下文 Javascript中代码的运行环境分为以下三种: 全局级别的代码 - 这个是默认的代码运行环境,一旦代码被载入,引擎最先进入的就是这个环境. 函数级别的代码 - 当执行一 ...

  8. SQL Server安全(6/11):执行上下文与代码签名(Execution Context and Code Signing)

    在保密你的服务器和数据,防备当前复杂的攻击,SQL Server有你需要的一切.但在你能有效使用这些安全功能前,你需要理解你面对的威胁和一些基本的安全概念.这篇文章提供了基础,因此你可以对SQL Se ...

  9. 深入理解javascript执行上下文(Execution Context)

    本文转自:http://blogread.cn/it/article/6178 在这篇文章中,将比较深入地阐述下执行上下文 - Javascript中最基础也是最重要的一个概念.相信读完这篇文章后,你 ...

随机推荐

  1. RabbitMQ集群安装配置+HAproxy+Keepalived高可用

    RabbitMQ集群安装配置+HAproxy+Keepalived高可用 转自:https://www.linuxidc.com/Linux/2016-10/136492.htm rabbitmq 集 ...

  2. HBase概念及表格设计

    HBase概念及表格设计 1. 概述(扯淡~) HBase是一帮家伙看了Google发布的一片名为“BigTable”的论文以后,犹如醍醐灌顶,进而“山寨”出来的一套系统. 由此可见: 1. 几乎所有 ...

  3. python之开篇---hello world!

    (1)前沿 (2)python 简介 (3)python hello world 实现 (4) -------------qq:1327706646 ------------------------- ...

  4. 【BZOJ4542】[Hnoi2016]大数 莫队

    [BZOJ4542][Hnoi2016]大数 Description 小 B 有一个很大的数 S,长度达到了 N 位:这个数可以看成是一个串,它可能有前导 0,例如00009312345.小B还有一个 ...

  5. 只需两步删除 node_modules

    peng@PENG-PC /E/_My_File_____/home/learn/web_qianduan/mithril-demo/demo2/mithril -demo $ npm install ...

  6. Coreos 安装及配置

    Coreos 安装及配置 本文由Vikings(http://www.cnblogs.com/vikings-blog/) 原创,转载请标明.谢谢! 目前国内使用coreos的场景还不多,搜索core ...

  7. java面向对象入门之带参方法创建

    /* Name :创建带参的方法 Power by :Stuart Date:2015.4.25 */ //创建Way类 class Way{ //Way类成员的基本变量 int add1=123; ...

  8. Kindeditor API

    根据map规则删除range中的element或attribute. cmd.remove({ span : '*', div : 'class,border' });   commonNode(ma ...

  9. rails常用函数

    1.rails g controller Users rails g model User 2.user.reload.email reload 使用数据库中的数据重新加载对象

  10. JQuery 双击动态编辑

             $(this).append(input);            $("input#temp").focus();            $("inp ...