JavaScript核心技术开发解密读书笔记(第四章)

2018-06-23  本文已影响42人  BeLLESS

第四章 变量对象

1. 创建过程

变量对象的创建,依次经历了一下几个过程。
1.在chrome浏览器中,变量对象会首先获得函数的参数变量及其值;在firefox浏览器中,是直接将参数对象arguments保存在变量对象中。
2.依次获取当前上下文中所有的函数声明,也就是使用function关键字生命的函数。在变量对象中会以函数名建立一个属性,属性值为指向该函数所在的内存地址引用。如果函数名的属性已经存在,那么该属性的值会被新的引用覆盖。
3.依次获取当前上下文中的变量声明,也就是使用var关键字声明的变量。每找到一个变量声明,就在变量对象中就以变量名建立一个属性,属性值为undefined。如果该变量名的属性已经存在,为了防止同名的函数被修改为undefined,则会直接跳过,原属性不会被修改。
了解了以上过程之后,结合《你不知道的JS(上)》提升一节,我们来看几个例子。

2. 几个例子
  1. 变量提升(先有鸡还是先有蛋)
a = 2;
var a;
console.log(a); // 2

很多人会认为是undefined,因为var a声明在a = 2之后,他们自然而然地认为变量被重新复制了,因此会赋予默认值undefined,但是真正的输出结果是2。我们再来看另外一个。

console.log(a); // undefined
var a = 2;

鉴于上一个代码片段所表现出来的某种非自上而下的行为特点,你可能会认为这个代码也会有同样的行为而输出2。还有人可能会认为,由于变量a在使用前没有先进行声明,因此会跑出ReferenceError异常。不幸的是两种猜测都是不对的,输出来的是undefined。

正确的思考思路是,包括变量和函数在内的所有声明都会在任何代码被执行前首先被处理。

当你看到var a = 2;时,可能会认为这是一个声明。但是JavaScript实际上会将其看成两个声明var a;a = 2;。第一个声明是在编译阶段进行的。第二个赋值声明会被留在原地等待执行阶段。
我们第一个代码片段会以如下形式进行处理。

var a; // 编译
a = 2; // 执行
console.log(a); // 2

类似地,我们第二个代码片段实际是按照以下流程处理的。

var a; // 编译
console.log(a); // undefined
a = 2; // 执行
  1. 函数相关提升
foo();
function foo () {
  console.log(a); // undefined
  var a = 2;
}

这里可以看到,foo函数的声明被提升了,因此第一行中的调用可以正常执行。

foo(); // TypeError
var foo = function () {
  // ...
}

这段程序中的foo函数被提升并分配给全局作用域,因此foo();不会导致ReferenceError。但是foo此时并没有赋值(如果它是一个函数声明而不是函数表达式,那么就会赋值)。foo()对于undefined值进行函数调用而导致非法操作,因此会跑出TypeError异常。

只有声明本身会被提升,而赋值或其他运行逻辑会留在原地。

同时也要记住,即使是具名的函数表达式,名称标识符在赋值之前也无法在所在作用域中使用。

foo(); // TypeError
bar(); // ReferenceError
var foo = function bar () {
  // ...
}

这个代码片段经过提升后,实际上会被理解为以下形式。

var foo;
foo(); // TypeError
bar(); // ReferenceError
foo = function () {
  var bar = ...self...
  // ...
}
  1. 函数优先

函数声明和变量声明都会被提升,但是一个值得注意的细节是函数会首先被提升,然后才是变量

foo(); // 1
var foo;
function foo () {
  console.log(1);
}
foo = function () {
  console.log(2);
}

这段代码会输出1而不是2,原因是这段代码会被JS引擎理解为如下形式:

function foo () {
  console.log(1);
}
foo(); // 1
foo = function () {
  console.log(2);
}

注意:var foo尽管出现在function foo () ...的声明之前,但它是重复的声明(因此会被忽略),因为函数声明会被提升到普通变量之前。
尽管重复的var声明会被忽略掉,但出现在后面的函数声明还是可以覆盖前面的。

foo(); // 3
function foo () {
  console.log(1);
}
var foo = function () {
  console.log(2);
}
function foo () {
  console.log(3);
}

最后附上书中一道例子,结合前面所讲的,留给各位作为思考题,知道答案的小伙伴请在在评论区留下你的答案。

test();
function test () {
  console.log(foo);
  console.log(bar);
  var foo = 'Hello';
  console.log(foo);
  var bar = function () {
    return 'world';
  }
  function foo () {
    return 'hello';
  }
}

以上是我对JavaScript核心技术开发解密第四章的读书笔记,码字不易,请尊重作者版权,转载注明出处。
By BeLLESS 2018.6.23 00:17

上一篇下一篇

猜你喜欢

热点阅读