在上篇中说到,当执行流执行一个函数时,会创建对应的执行上下文(execution context)。每个执行上下文,都有三个重要属性:
每个执行环境(执行上下文)都有一个对应的变量对象(variable object),环境中(执行上下文中)定义的所有变量、函数都保存在变量对象中。
创建执行环境分为两个阶段:
VO包括:
1、根据函数的参数,创建并初始化arguments object
2、扫描该执行上下文中的函数声明(不包括函数表达式)
a) 找到所有的function 声明,将函数名当做属性创建,值为函数定义
b) 在扫描过程中如果存在重名的函数声明,那么后面的会覆盖前面的声明,函数声明与变量声明有冲突时,会忽略(以函数声明为主)
3、 扫描该执行上下文中的var变量声明
a) 找到所有的变量声明,将变量名当做属性创建,值初始为undefined
b) 在扫描过程中如果存在重名的变量声明以及重名的函数声明,会忽略;
如下代码:
- function foo(name) {
- var age = 20;
- function run() {}
- var say = function() {};
- age = 22;
- }
- foo('Joel');
进入函数执行上下文时变量对象(vo)如下:
- foo.EC.VO = {
- arguments: {
- 0: 'Joel',
- length: 1
- },
- name: 'Joel',//参数
- age: undefined,
- run: reference to function run(){},
- say: undefined
- }
在代码执行阶段,会顺序执行代码,根据代码,修改VO的值 如:
- foo.EC.VO = {
- arguments: {
- 0: 'Joel',
- length: 1
- },
- name: 'Joel',//形参
- age: 20,
- run: reference to function run(){},
- say: reference to FunctionExpression say
- }
变量提升的本质就是函数在创建执行环境时变量对象初始化下了参数、函数声明、变量;
- <script>
- function run() {
- console.log(name);
- console.log(say);
- var name = 'Joel';
- function say() {
- console.log('say');
- }
- }
- run();
- </script>
如上代码可以理解为这样:
- function run() {
- function say() { //Hoisting
- console.log('say');
- }
- var name = undefined; //Hoisting
- console.log(name);
- console.log(say);
- var name = 'Joel';
- }
- run();
- <script>
- function run() {
- console.log(a);
- a = 1;
- }
- run(); // ?
- function say() {
- a = 1;
- console.log(a);
- }
- say(); // ?
- </script>
第一段会报错:
。第二段会打印:
- Uncaught ReferenceError: a is not defined
。
- 1
- function foo(name) {
- console.log(run); // 输出run 函数定义
- console.log(say); //undefined
- function run() {}
- var say = function() {};
- }
- foo('Joel');
函数表达式会当做一个var 变量来处理
- console.log(run)
- function run() {
- console.log(a);
- a = 1;
- }
- var run=1;
输出: ƒ run() {
console.log(a);
a = 1;
}
当声明的变量与函数重名时,声明的变量会忽略;
如果在第6行代码后面添加 console.log(run),那么run 值会被重置为1,因为在上下文对象创建阶段发现已经存在run的函数声明,var 变量会被忽略,当代码在真正执行到6行时run的值被改变了;
- console.log(run)
- function run() {
- console.log(a);
- a = 1;
- }
- var run=1;
- console.log(run)
上篇中说到执行环境分为全局执行环境,函数执行环境,在本文开篇说到变量对象:
每个执行环境(执行上下文)都有一个对应的变量对象(variable object),环境中(执行上下文中)定义的所有变量、函数都保存在这个对象中,那么全局执行环境是不是可以理解为也存在一个变量对象。
我们先了解一个概念,什么叫叫全局对象。在 W3School 中也有介绍:
全局对象是预定义的对象,作为 JavaScript 的全局函数和全局属性的占位符。通过使用全局对象,可以访问所有其他所有预定义的对象、函数和属性。
在顶层 JavaScript 代码中,可以用关键字 this 引用全局对象。但通常不必用这种方式引用全局对象,因为全局对象是作用域链的头,这意味着所有非限定性的变量和函数名都会作为该对象的属性来查询。
例如,当JavaScript 代码引用 parseInt() 函数时,它引用的是全局对象的 parseInt 属性。全局对象是作用域链的头,还意味着在顶层 JavaScript 代码中声明的所有变量都将成为全局对象的属性。
1.可以通过 this 引用,在客户端 JavaScript 中,全局对象就是 Window 对象。
- console.log(this);
2.全局对象是由 Object 构造函数实例化的一个对象。
- console.log(this instanceof Object);
3.预定义了一大堆函数和属性。
- // 都生效
- console.log(Math.random());
- console.log(this.Math.random());
4.作为全局变量的宿主。
- var a = 1;
- console.log(this.a);
5.客户端 JavaScript 中,全局对象有 window 属性指向自身。
- var a = 1;
- console.log(window.a);
- this.window.b = 2;
- console.log(this.b);
写了这么多介绍全局对象,其实就是想说:全局上下文中的变量对象就是全局对象!
它们其实都是同一个对象,只是处于执行上下文的不同生命周期。未进入执行阶段之前,变量对象(VO)中的属性都不能访问,但是进入执行阶段之后,执行环境被压入执行环境栈变量对象(VO)转变为了活动对象(AO),里面的属性都能被访问了,然后开始进行执行阶段的操作。
来源: http://www.cnblogs.com/CandyManPing/p/7791309.html