写在前面的话
接触前端到现在大概也有三个多月了,越深入学习越发现自己理解的学到的只是一知半解。于是翻阅了许多大神如简书中的这波能反杀的前端进阶系列博客,终于对JS有了更深的认识。本篇文章主要是对JS中的变量对象做一下总结,希望可以让看到这篇文章的各位对变量对象有更深的理解。此外,文章不足之处还望大家指出,共勉。
执行上下文的创建
在说变量对象这个东东之前,我们需要先明白JavaScript代码的整个执行过程大概是怎么样的。JavaScript代码的整个执行过程中,分为两个阶段,先开始代码编译阶段,然后开始代码执行阶段。在代码编译阶段,编译器会将JS代码翻译成可执行代码,在这个阶段中确定了作用域规则。接下来,开始代码执行阶段,由引擎完成,这个阶段中会调用各种函数,创建执行上下文,然后执行代码。
变量对象出现阶段
在调用一个函数时,会有一个执行上下文被创建。该执行上下文过程的生命周期分为两个阶段:
创建阶段
在此阶段,执行上下文开始创建变量对象,确定作用域链,this的指向等。
代码执行阶段
创建阶段完成后,开始执行代码,在这个时候,才开始变量的赋值,函数的引用,以及执行其他的代码。
简而言之,调用一个函数时,会生成一个执行上下文,这个执行上下文会先创建变量对象,确定作用域链,this指向等,然后执行上下文开始根据作用域链,先对变量对象中各变量赋值,再执行其他可执行代码。
变量对象生成阶段
1、 创建一个arguments对象,即存放该函数中的所有变量的对象。
2、 检查该函数体中的由function关键字声明的函数,若调用函数中有由function声明的函数,则以该函数的函数名为属性名在变量对象中创建一个属性,属性值为该函数内存地址的引用。若以该函数名创建的属性已经存在,该属性值会将原来的覆盖。
//demo01
function fn() {
function foo() {
return 1;
}
}
//在该例中,首先 foo 作为arguments对象属性名存入其中,此时为
// arguments = {foo: dcab****}
//此处的 "dcab****" 为 "ƒ foo() {return 1;}" 的内存地址
3、 检查该函数体中的由var关键字声明的变量,每检测到一个就将其变量名作为变量对象的属性名存入其中,属性值为该变量值。如果找到var声明变量,则以该变量名为属性在变量对象中创建属性,属性值为undefined。在变量对象中,若var声明的变量名已经存在,则不重复声明,跳过该var声明变量在变量对象中的属性创建。
综上所述:在函数调用创建变量对象过程中,首先检查function函数声明并在变量对象中以其函数名创建属性,属性值为该函数内存地址的引用,若该属性已存在,则属性值会将原来的覆盖。然后才是检查var声明变量,并以其变量名在变量对象中创建属性,属性值为该变量值,若该属性名已存在,属性值指向的是一个函数的内存的引用,则跳过对该var声明变量在变量对象中属性的创建。
//demo02
function fn() {
console.log(foo);
console.log(a);
var a = 4;
var foo = 5;
console.log(foo);
function foo() {
return 1;
}
}
fn();
//大家可以尝试写出该实例的输出结果
在demo02中,会有很多读者认为第一个console.log(foo)的输出结果是1或者undefined,但其实不然。按照变量对象生成顺序,首先检查实例中的function声明,并在变量对象中创建属性名为foo的属性,属性值为foo函数在内存中的引用地址。因为函数体中并没有其他的function声明,所以此时才开始检查var声明变量。首先检查到var声明的变量a,因此将 “ a : undefined, ”作为属性存入变量对象中。这里大家可能会有疑问,var a = 4 ,向变量对象中存储的属性应该是 “ a : 4, ” ,为什么会是 undefined 呢? 需要注意的是,现在我们讨论的是变量对象的创建阶段,并不是代码执行阶段,下面有该实例的实际运行顺序(这个仅是我自己所理解的,如发现不足指出请指出,谢谢)。该实例的变量对象现在还没有创建完成,还有一个var声明的foo变量,这个时候由于变量对象中已经有了foo这个属性,因此这个var声明的foo变量就不会再作为属性存放到变量对象中,即直接跳过foo属性的创建。
var声明的变量遇到同名属性时会跳过而不会覆盖,需要满足两个前提:
一、处于函数生命周期的创建对象阶段
二、遵循function声明优先
//demo02实际运行顺序
function fn() {
function foo() {
return 1;
}
var a = undefined;
//var foo 因为变量对象中已经存在foo属性,所以跳过该属性的创建
console.log(foo); // ƒ foo() {return 1;}
console.log(a); // undefined
a = 4; // 执行代码阶段才会将4赋值给a
foo = 5; // 重新将5赋值给foo覆盖掉原来的函数地址引用
console.log(foo); // 5
}
fn();
下面再借用波大神的一个demo:
//demo03
function test() {
console.log(foo);
console.log(bar);
var foo = "hello";
console.log(foo);
var bar = function(){
return "hello";
}
}
如果你理解透了demo2,那么demo3就不是问题,这里就不再赘述了。
变量提升
关于变量提升,先给大家举个例子:
相信大家都懂function声明的函数变量与var声明的函数变量之间的区别,即function声明的变量,在声明之前调用或者在声明之后调用该函数都可以,对输出结果无影响,而var声明的函数变量,若在声明之前调用,则会返回undefined,但很多朋友都是只知其然而不知其所以然。
其实这里的基本原理就是上述的变量对象的创建阶段,会首先检查function声明的变量,因此只要是function关键字声明的变量,位置在调用之前或者在调用之后都无所谓,因为它的优先级在创建变量对象阶段最高,当检测完了function声明的变量后,才开始检查var变量声明的变量,并且在创建阶段对var声明的变量赋值为undefined,如果var声明的变量有值,到达执行代码阶段才会将值赋给该变量。因此如果在var声明函数变量之前调用该变量,会报错undefined。
因此,无论function关键字声明的变量与var声明的变量处于何位置,在该执行上下文的变量创建阶段中,function声明变量总是作为变量对象的属性最先被创建,接着是var声明的变量作为变量对象的属性被创建,这就是变量提升。
JS变量声明的优先级
变量添加到作用域中的方法及优先级从高到低排序如下:
- 1 var声明变量并赋值
- 2 function声明
- 3 formal parameter(形参)
- 4 JS预定义变量
- 5 var声明但不赋值
- 6 函数的外部作用域的其他声明
function fnTest(arguments){
alert(arguments);//function arguments(){return "hello world"}
var arguments = 99;
function arguments(){return "hello world"};
alert(arguments);//99
}
fnTest(100);
首先,在变量对象创建阶段,function关键字声明变量要比var声明的关键字的优先级要高,文章前面已经说明。但是在同一执行上下文中若var声明变量并赋值,最终则总会覆盖与之同名的函数声明。
对这个优先级顺序不太理解的同学可以移步去寂寞先生的博客中一探究竟。下面我们来看一个实例:
var name = 'world';
(function () {
if(typeof name === 'undefined'){
var name = 'Jack';
console.log('Goodbye'+name); // GoodbyeJack
} else {
console.log('Hello'+name);
}
})();
这里首先要说一下沿着作用域链查找变量的方法:
- 在局部作用域中查找(若找到则返回该变量,否则继续向下找)
- 在上一级的作用域中查找,(······);
- 在全局作用域中查找(若找不到,则返回 typeError)
此实例中,虽然在函数体外定义了全局变量name,但是函数调用过程中,会优先考虑自身作用域内的变量,而该函数内部有var声明的变量name,因此不再考虑外部全局变量name。在该函数执行上下文中,首先创建变量对象,检查到有var声明的变量,因此将“ name:undefined ”作为属性值先存储在变量对象中。在if条件判断时,name的值其实还是undefined,因为按照执行上下文的顺序,这个时候代码并没有执行到 “ name = Jack ”,所以if条件成立,执行if语句中的代码。
小结
关于变量对象,到这里大概也就介绍完毕了。在实际工作中可能很少会用到这些知识,也就面试时候可能会有一些与之有关简短的代码题,但是个人觉得理解了变量对象对学习JavaScript还是有很大帮助的,会让我们更容易接受其他的一些知识,毕竟每个知识点都不是孤立的,他们之间都有“冥冥之中”的联系~
希望大家能通过本篇文章加深对变量对象的理解,有所收获。