对于JS编程,要考虑一下三个方面:
执行的JavaScript代码分三种类型
JS是通过函数名称来确定调用代码的,而其它语言是通过函数签名来确定调用代码的。首先要明白一个问题,JavaScript中的所谓函数是函数对象的简称,与其他语言中的函数/方法不是一个概念;其次,JavaScript中的函数名是函数对象数据结构的入口地址。所以,JavaScript中既没有函数签名的概念,也没有函数重载。函数名称仅仅是一个变量而已。
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>
进程及其堆、线程及其执行环境栈、全局执行环境(包括栈桢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)。
有三种执行环境:
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
引用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)
注释:this是关键字,是只读的。
编程建议:
执行作用域链:
Scope Chain:是内部数据结构。可能是链表/列表。程序无法访问,只有JS引擎可以访问。
局部变量对象:
在函数执行环境中,局部变量对象被叫做活动对象。是JS的内部数据结构。程序无法访问,只有JS引擎可以访问。函数的局部变量和函数的形参以及arguments(存放实参)变量存放于活动对象中。Arguments引用实参对象([[Prototype]]:Object.protorype)。
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>
上面全局代码4被注释了。
全局执行环境对象留在栈中,全局执行环境对象是不退栈的,等待下一个JavaScript脚本的执行。
当堆中的一个值失去引用之后,就会被标记为垃圾。
① 回收垃圾并释放空间
② 然后进行碎片整理
这就是为什么没有GC的语言中,地址叫指针,指针是静态地址;而含有GC的语言中,由于GC将进行碎片整理,所以地址叫引用的原因所在,引用是动态地址。