《JavaScript高级程序设计》(第4版)阅读笔记(二十一)

这一篇继续分享《高程四》第四章的内容。

 

4.2.2 变量声明
 

 

ES6之后,JavaScript的变量声明经历了翻天覆地的变化。直到ECMAScript 5.1, var 都是声明变量的唯一关键字。ES6不仅增加了let 和 const 两个关键字,而且还让这两个关键字压倒性地超越var 成为首选。
 

1. 使用 var 的函数作用域声明
 

在使用 var 声明变量时,变量会被自动添加到最接近的上下文。在函数中,最接近的上下文就是函数的局部上下文。在with 语句中,最接近的上下文也是函数上下文。如果变量未经声明就被初始化了,那么它就会自动被添加到全局上下文,如下面的例子所示:

function add(num1, num2) {
  var sum = num1 + num2;
  return sum;
} 
let result = add(10, 20); // 30
console.log(sum); // 报错:sum在这里不是有效变量

这里,函数 add() 定义了一个局部变量 sum ,保存加法操作的结果。这个值作为函数的值被返回,但变量 sum 在函数外部是访问不到的如果省略上面例子中的关键字 var ,那么 sum在 add() 被调用之后就变成可以访问的了,如下所示:

function add(num1, num2) {
  sum = num1 + num2;
  return sum;
} 
let result = add(10, 20); // 30
console.log(sum); // 30

这一次,变量 sum 被用加法操作的结果初始化时并没有使用var 声明。在调用 add() 之后, sum 被添加到了全局上下文,在函数退出之后依然存在,从而在后面可以访问到。

注意 未经声明而初始化变量是JavaScript编程中一个非常常见的错误,会导致很多问题。为此,读者在初始化变量之前一定要先声明变量。在严格模式下,未经声明就初始化变量会报错。

var 声明会被拿到函数或全局作用域的顶部,位于作用域中所有代码之前。这个现象叫作“提升”(hoisting)。提升让同一作用域中的代码不必考虑变量是否已经声明就可以直接使用。可是在实践中,提升也会导致合法却奇怪的现象,即在变量声明之前使用变量。下面的例子展示了在全局作用域中两段等价的代码:

var name = "Jake";
// 等价于:
name = 'Jake';
var name;

下面是两个等价的函数:

function fn1() {
  var name = 'Jake';
} 
// 等价于:
function fn2() {
  var name;
  name = 'Jake';
}

通过在声明之前打印变量,可以验证变量会被提升。声明的提升意味着会输出 undefined 而不是 Reference Error :

 

2. 使用 let 的块级作用域声明
 

ES6新增的 let 关键字跟 var 很相似,但它的作用域是块级的,这也是JavaScript中的新概念。块级作用域由最近的一对包含花括号 {} 界定。换句话说, if 块、 while 块、 function
块,甚至连单独的块也是 let 声明变量的作用域。

if (true) {
  let a;
}
console.log(a); // ReferenceError: a没有定义
while (true) {
  let b;
}
console.log(b); // ReferenceError: b没有定义
function foo() {
  let c;
}
console.log(c); // ReferenceError: c没有定义
// 这没什么可奇怪的
// var声明也会导致报错
// 这不是对象字面量,而是一个独立的块
// JavaScript解释器会根据其中内容识别出它来
{
  let d;
}
console.log(d); // ReferenceError: d没有定义

let 与 var 的另一个不同之处是在同一作用域内不能声明两次。重复的 var 声明会被忽略,而重复的 let 声明会抛出SyntaxError

var a;
var a;
// 不会报错
{
  let b;
  let b;
}// SyntaxError: 标识符b已经声明过了

let 的行为非常适合在循环中声明迭代变量。使用 var 声明的迭代变量会泄漏到循环外部,这种情况应该避免。来看下面两个例子:

for (var i = 0; i < 10; ++i) {}
console.log(i); // 10
for (let j = 0; j < 10; ++j) {}
console.log(j); // ReferenceError: j没有定义

严格来讲, let 在JavaScript运行时中也会被提升,但由于“暂时性死区”(temporal dead zone)的缘故,实际上不能在声明之前使用 let 变量。因此,从写JavaScript代码的角度说, let 的提升跟 var 是不一样的。

 

3. 使用 const 的常量声明
 

除了 let ,ES6同时还增加了 const 关键字。使用 const 声明的变量必须同时初始化为某个值。一经声明,在其生命周期的任何时候都不能再重新赋予新值。

const a; // SyntaxError: 常量声明时没有初始化
const b = 3;
console.log(b); // 3
b = 4; // TypeError: 给常量赋值

const 除了要遵循以上规则,其他方面与 let 声明是一样的:(也是属于块级作用域的) 

if (true) {
  const a = 0;
}
console.log(a); // ReferenceError: a没有定义
while (true) {
  const b = 1;
}
console.log(b); // ReferenceError: b没有定义
function foo() {
  const c = 2;
}
console.log(c); // ReferenceError: c没有定义
{
  const d = 3;
}
console.log(d); // ReferenceError: d没有定义

const 声明只应用到顶级原语或者对象。换句话说,赋值为对象的 const 变量不能再被重新赋值为其他引用值,但对象的键则不受限制。(也就是说,const定义的值并不是完全不可改变,仅仅是const定义的那个“变量”保存的值不能改变——如果是值类型,保存的就是值,所以值不能改变;如果是引用类型,那么保存的是对象的地址,所以地址不能改变,不能让它指到别的对象,但是这个对象可以有什么属性,却是不受约束的)

const o1 = {};
o1 = {}; // TypeError: 给常量赋值
const o2 = {};
o2.name = 'Jake';
console.log(o2.name); // 'Jake'

如果想让整个对象都不能修改,可以使用Object.freeze() (这个方法的作用是,把这个对象里的所有属性的特性中的“可配置性”改为了false(不可以修改和删除属性),并且不允许它新增属性),这样再给属性赋值时虽然不会报错,但会静默失败

const o3 = Object.freeze({});
o3.name = 'Jake';
console.log(o3.name); // undefined

由于 const 声明暗示变量的值是单一类型且不可修改,JavaScript运行时编译器可以将其所有实例都替换成实际的值,而不会通过查询表进行变量查找。谷歌的V8引擎就执行这种优化。(也就是说,由于这种优化,const声明比let声明多了一点性能优势)

注意 开发实践表明,如果开发流程并不会因此而受很大影响,就应该尽可能地多使用 const 声明,除非确实需要一个将来会重新赋值的变量。这样可以从根本上保证提前发现重新
赋值导致的bug。(也就是说,如果值不改变,就用const,如果值需要改变,那就let就好了。优先使用const)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值