【JavaScript核心技术卷】全局代码与执行模型和对象模型

全局代码与执行模型和对象模型

一、常识回顾

对于JS编程,要考虑一下三个方面:

  1. 需要跨JS版本(ECMAScript3、ECMAScript5、ECMAScript6,版本的语法版本的API不同)
  2. 需要选择JS模式(非严格模式和严格模式
  3. 需要跨平台(Safari、Chrome、Opera、Firefox、IE),需要跨平台版本(IE6、IE8、IE10)的 DOMAPIBOMAPI

执行的JavaScript代码分三种类型

  1. Global Code,即全局的、不在任何函数里面的代码,例如一个js文件、嵌入在HTML页面中的js代码等。
  2. Function Code,即用户自定义函数中的函数体JS代码。
  3. Eval Code,即使用eval()函数动态执行的JS代码。

JS是通过函数名称来确定调用代码的,而其它语言是通过函数签名来确定调用代码的。首先要明白一个问题,JavaScript中的所谓函数是函数对象的简称,与其他语言中的函数/方法不是一个概念;其次,JavaScript中的函数名是函数对象数据结构的入口地址。所以,JavaScript中既没有函数签名的概念,也没有函数重载。函数名称仅仅是一个变量而已。

二、全局代码

(1)代码清单

GEC.html

<!DOCTYPE html>
<html>
<head>
    <title>Execution Context Example 1</title>
    <script type="text/javascript">
        
		//alert函数是宿主函数,JS没有输入输出,没有事件。
		alert("Color is now " + color);		//"Color is now undefined"
        var color = "blue";
        
		//声明第一个函数对象
		//函数声明完成两个步骤:
			//①声明函数名称变量,
				//	函数名称changeColor仅仅是一个变量而已,增加到变量对象中
				//	var changeColor; //初始化值为undefined,表示未知
			//②将新创建的函数对象赋予函数名称变量changeColor
        function changeColor(){
			alert("Color is now " + color);		//"Color is now blue "
        }

		//声明第二个函数对象
		//函数声明完成两个步骤:
			//①声明函数名称变量,
				//	函数名称changeColor仅仅是一个变量而已,增加到变量对象中
				//	var changeColor; //初始化值为undefined,表示未知
			//②将新创建的函数对象赋予函数名称变量changeColor
		//第一个函数对象将成为垃圾
        function changeColor(){
            if (color === "blue"){
                color = "red";
            } else {
                color = "blue";
            }
        }
        
        alert("Color is now " + color);		//"Color is now blue "
        changeColor();
        alert("Color is now " + color);		//"Color is now red"
      
  	    //输出函数定义的JS源代码
		//被解释器修改为alert(changeColor.toString());
		alert(changeColor);					
	  	//函数名称changeColor仅仅是一个变量而已,第二个函数对象将成为垃圾
  		var changeColor = "I want cover function named fn.";
		
		//变量changeColor
  	    //输出:"changeColor变量: I want cover function named fn."
		alert("changeColor变量:  " + changeColor);
		
		//访问的变量不存在,将产生异常  --- 【注意对比前面的color输出的结果是undefined】
			// window.prop这时prop是被当作属性来使用的,未赋值的属性是undefined,而直接输出prop的话会被当作变量对象,变量对象未定义未赋值直接使用的话会抛出ReferenceError
		//alert("otherChangeColor变量:  " + otherChangeColor);

		//window对象属性的changeColor
  	    //输出:"window.changeColor对象属性: I want cover function named fn."
	    alert("window.changeColor对象属性:  " + window.changeColor);

		//访问的属性不存在,将输出 undefined
		alert("window.otherChangeColor变量:  " + window.otherChangeColor);
	
		//window对象属性的changeColor
  		//输出:"window.window.changeColor对象属性: I want cover function named fn."
    	alert("window.window.changeColor对象属性:  " + window.window.changeColor);
      
		//window对象属性的changeColor
    	//输出:" this.changeColor对象属性:  I want cover function named fn."
	    alert("this.changeColor对象属性:  "  + this.changeColor);	//
      
		//window对象属性的changeColor
   		//输出:" this.window.changeColor对象属性:  I want cover function named fn."
	    alert("this.window.changeColor对象属性:  " + this.window.changeColor);
      
		//this与window一样是作为对象的引用,访问的属性不存在,将输出 undefined
		//输出:" this.this对象属性:  undefined "
  	    alert("this.this对象属性:  " + this.this);
	
		//访问空引用的对象,将产生异常
  	    //alert(this.this.changeColor);	
    </script>
</head>
<body>
  
</body>
</html>

(2)执行全局代码

1、创建全局执行环境(由引擎自动创建)-- Global EC

进程及其堆、线程及其执行环境栈、全局执行环境(包括栈桢ECO、执行作用域链 Scope Chain、全局变量对象 window对象/Global object对象)在浏览器启动时建立。在ECMAScript程序执行之前宿主中就已经存在了。

浏览器是单线程的, JavaScript引擎在初始化后, 在执行全局代码之前,会为全局代码执行创建全局代码执行环境,会自动创建执行模型和对象模型。

执行环境栈(ECS Execution Context Stack)存放的是执行环境对象,引擎在初始化后,将全局执行环境对象(Global Execution Context Object)压栈,引擎会创建好全局执行环境对象的执行作用域链Scope Chain。

执行环境/执行上下文(Execution Context) 包含:执行环境对象、执行作用域链、变量对象。

执行环境对象中的this变量所引用的对象统称为this环境对象。

在全局执行环境中,this引用window环境对象,在函数执行环境中,this引用函数的环境对象,function context is (what the this will point to)。

有三种执行环境:

  1. 全局执行环境GEC
  2. 函数执行环境FEC
  3. Eval执行环境EEC

JavaScript代码运行的地方都存在执行环境,它是一个概念,一种机制,用来完成JavaScript运行时作用域、生存期等方面的处理。执行环境包括环境对象的引用this和执行作用域链(/执行作用域)的(scope chain)引用、Scope Chain(Scope)(一般作用域Scope的概念是通过作用域链表来实现的)、Variable Object、Variable Instatiation等概念,在不同的场景/执行环境下,处理上存在一些差异。

1、GEC 全局执行环境(Global Execution Context) 内容

(1)栈桢内容:执行环境对象(ECO Execution Context Object)

成员: (scope chain), this

  • (scope chain):执行作用域(链)变量,引用执行作用域链
  • this:在GECO中,this变量(逻辑上可以看作隐式的全局形参变量),

引用window对象(window Context Object)。

注释:this是关键字,是只读的,不能赋值。

(2)执行作用域链:

Scope Chain(/Scope概念):执行作用域链/执行作用域是内部数据结构。可能是链表/列表结构。里面每个成员都是Variable object程序无法访问,只有JS引擎可以访问。变量对象(Variable Object VO):作用域链引用的对象统称为变量对象。

(3)全局变量对象:

在全局执行环境中,全局变量对象被叫Global Object对象 / window 对象(window的隐式属性[[Prototype]] —> Object.prototype )。

ECMA规范规定其运行环境都必须存在一个唯一的全局对象Global Object, Global Object一定是一个宿主对象,由宿主实现,ECMA规范对它没有额外要求。在Web中,为window对象。

JS的全局变量和JS的全局函数(例如:Math、String、Date、parseInt等JavaScript中内置的全局对象和函数),宿主的全局变量和宿主的全局函数均存放于window对象中。

2、FEC 函数执行环境(Function Execution Context) 内容

栈桢内容ECO:执行环境对象(Execution Context Object)

  • (scope chain) :执行作用域(链)变量,引用执行作用域链
  • this:隐式的函数形参变量
    1. 方法调用:this引用方法被调用的对象(Function Context Object,函数的环境对象)
    2. 函数调用
      ① 严格模式下的函数调用,this形参的值为 undefined
      ② 非严格模式下的函数调用,this形参引用window对象
    3. new 实例构造函数调用:this引用新创建的对象

注释:this是关键字,是只读的。

编程建议:

  • 如果用于函数调用,函数体内部不要使用this隐式形参
  • 如果用于方法调用或new新对象时,函数体内部可以使用this隐式形参。

执行作用域链:

Scope Chain:是内部数据结构。可能是链表/列表。程序无法访问,只有JS引擎可以访问。

局部变量对象:

在函数执行环境中,局部变量对象被叫做活动对象。是JS的内部数据结构。程序无法访问,只有JS引擎可以访问。函数的局部变量和函数的形参以及arguments(存放实参)变量存放于活动对象中。Arguments引用实参对象([[Prototype]]:Object.protorype)。

在这里插入图片描述

2、扫描全局代码,提升函数声明、变量声明

1、确定JS代码的执行模式

2、提升函数声明、变量声明,将它们放到源代码树的顶部,首先执行声明语句。

  • 如果是两个同名函数声明,出现在后面的函数声明可以覆盖前面的,前面声明的同名函数将成为垃圾。
  • 如果是两个同名变量声明,出现在后面的变量声明将被忽略。

函数声明会首先被提升,然后才是变量声明。

上面例子代码清单的执行顺序实际为(解析后的伪码):

<!DOCTYPE html>
<html>
<head>
    <title>Execution Context Example 1</title>
    <script type="text/javascript">
        
	//创建全局执行环境(由引擎自动创建)

	//第一遍扫描后,将声明放到源代码树的顶部
	//第二遍开始执行,先执行声明语句,将它们添加到执行环境中。

	//扫描全局代码,执行提升的函数声明、变量声明

	//声明第一个函数对象
	//函数声明完成两个步骤:
		//①声明函数名称变量,
			//	函数名称changeColor仅仅是一个变量而已,增加到变量对象中
			//	var changeColor; //初始化值为undefined,表示未知
		//②将新创建的函数对象赋予函数名称变量changeColor
		function changeColor(){
			alert("Color is now " + color);		//"Color is now blue "
        }
 
	//声明第二个函数对象
	//函数声明完成两个步骤:
		//①声明函数名称变量,
			//	函数名称changeColor仅仅是一个变量而已,增加到本地变量对象中
			//	var changeColor; //初始化值为undefined,表示未知
		//②将新创建的函数对象赋予函数名称变量changeColor
	//第一个函数对象将成为垃圾
        function changeColor(){
            if (color === "blue"){
                color = "red";
            } else {
                color = "blue";
            }
        }

    //变量声明时,
	//如果本地变量对象中未存在color变量,则将color变量增加到本地变量对象中。
    var color; //声明变量的值将被初始化值为undefined,表示未知

	//变量声明时,
	//如果本地变量对象中存在changeColor变量,则忽略该变量声明语句。
	var changeColor; //忽略该变量声明语句


		//执行全局代码
		//执行全局代码1
		//alert函数是宿主函数,JS没有输入输出,没有事件。
		alert("Color is now " + color);		//"Color is now undefined"
        color = "blue";
        
		//执行全局代码2
    	alert("Color is now " + color);		//"Color is now blue "
        changeColor();
        alert("Color is now " + color);		//"Color is now red"

		//执行全局代码3
	   	//被解释器修改为alert(changeColor.toString());
 		alert(changeColor);					//输出函数定义的JS代码
		//函数名称changeColor仅仅是一个变量而已,第二个函数对象将成为垃圾
	    changeColor = "I want cover function named fn.";
		
		//变量changeColor
  	    //输出:"changeColor变量: I want cover function named fn."
		alert("changeColor变量:  " + changeColor);
		
		//执行全局代码4
		//异常 访问的变量不存在,将产生异常
		//alert("otherChangeColor变量:  " + otherChangeColor);

		//window对象属性的changeColor
  	    //输出:"window.changeColor对象属性: I want cover function named fn."
	    alert("window.changeColor对象属性:  " + window.changeColor);

		//访问的属性不存在,将输出 undefined
		alert("window.otherChangeColor变量:  " + window.otherChangeColor);

		//window对象属性的changeColor
  		//输出:"window.window.changeColor对象属性: I want cover function named fn."
   		alert("window.window.changeColor对象属性:  " + window.window.changeColor);
      
		//window对象属性的changeColor
    	//输出:" this.changeColor对象属性:  I want cover function named fn."
	    alert("this.changeColor对象属性:  "  + this.changeColor);	//
      
		//window对象的属性changeColor
   		//输出:" this.window.changeColor对象属性:  I want cover function named fn."
	 	alert("this.window.changeColor对象属性:  " + this.window.changeColor);
      
		//this与window一样是作为对象引用,访问的属性不存在,将输出 undefined
		//输出:" this.this对象属性:  undefined "
  	    alert("this.this对象属性:  " + this.this);
	
		//访问undefined/null的对象属性,将产生异常
  	    //alert(this.this.changeColor);	      
    </script>
	</head>
	<body>
	</body>
</html>

3、执行提升的函数声明、变量声明

在这里插入图片描述

4、执行全局代码1

在这里插入图片描述

5、执行全局代码2

color的值变化
在这里插入图片描述

6、执行全局代码3

changeColor变为值。
在这里插入图片描述

7、执行全局代码4

上面全局代码4被注释了。

8、全局代码执行完毕

全局执行环境对象留在栈中,全局执行环境对象是不退栈的,等待下一个JavaScript脚本的执行。

9、产生的垃圾,等待垃圾回收器GC回收

当堆中的一个值失去引用之后,就会被标记为垃圾。

10、满足条件,GC启动,开始回收垃圾

① 回收垃圾并释放空间

② 然后进行碎片整理

这就是为什么没有GC的语言中,地址叫指针,指针是静态地址;而含有GC的语言中,由于GC将进行碎片整理,所以地址叫引用的原因所在,引用是动态地址。

11、在浏览器关闭时,全局执行环境对象出栈

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章