[重读JS红宝书]二、真的弄清楚了var、let、const

716 阅读4分钟

重读JavaScript高级程序设计,反向碾压面试官?

第三章语言基础终于读完了,很多面试题都是来自这一章。但是会变成面试题的又似乎都是一些“坑”。我做了一些笔记,也选择性的忽略了一些,比如说类型转换。对于基础的语句和操作符就没有单独记下来了。

var、let、const

本身这不是一个问题,但是由于 var 的特殊性,ES6有了let和const之后,反而变成了一个面试问题。

本来想把这个坑给忽略掉,但是考虑到很多面试官都是从var时代来的 ̄□ ̄||

var 和 let

  1. var 声明的范围是函数作用域,let 声明的是块作用域。块作用域是函数作用域的子集。
if (true) {
	// 这里是块作用域
	let age = 26;
}
console.log(age); // ReferenceError: age没定义
function test() {
	// 这里是函数作用域
	var message = 'hi';
}
test();
console.log(message); // 报错

  1. var 声明提升,用 var 声明的变量会自动提升到函数作用域顶部。let 则不会。
function test() {
	console.log(age);
	var age = 26;
}
test(); // undefined

不会报错是因为ECMAScript运行时把它看成等价于如下代码:

function test() {
	var age;
	console.log(age);
	age = 26;
}
test(); // undefined
  1. 可以多次使用var声明同一个变量。let 则会报错。
  2. let 暂时性死区。原因是2,let声明变量不会在作用域中被提升。
console.log(name); // ReferenceError: name没定义
let name = 'Hello';
  1. 全局声明。使用 var 在全局作用域中声明的变量会成为你 window 对象的属性。let声明的则不会。

在 var 时代,如果不控制好作用域,经常会发生全局变量冲突。有了let之后就不用担心这个问题了,用let重复声明同一个变量会报错。同3。

  1. 不能使用 let 进行条件式声明。因为1、let 声明的是块作用域。
  2. for 循环中的 let 声明
for (var i = 0; i < 5; ++i) {
	setTimeout(() => console.log(i), 0)
}
// 5、5、5、5、5
for (var i = 0; i < 5; ++i) {
	setTimeout(() => console.log(i), 0)
}
// 0、1、2、3、4

const

和let行为基本相同。

  1. const 声明变量是必须初始化。
  2. const 声明的变量不允许修改。

如果,const变量引用的是一个对象,修改这个对象的内部属性并不违反2限制。

总结

有了ES6之后,基本不必再使用var了。上面大部分的内容都可以忽略掉。比如变量提升和全局声明成为window对象的属性这样的特性尤其不建议使用了。

数据类型

ECMAScript共有7中数据类型,其中 Symbol 是ES6新增的。

  • Undefined
  • Null
  • Boolean
  • Number
  • String
  • Symbol
  • Object

typeof 操作符

typeof 操作符会返回一个字符串,它可能是:

  • “undefined”
  • “boolean”
  • “string”
  • “number”
  • “object”
  • “function”
  • “symbol”

有什么不对?少了一个 Null。 因为 typeof null 返回的是“object” 多了一个 “function” 。 函数不代表一种数据类型,但是可以通过 typeof 辨别出来

浮点值的精度问题

0.1 + 0.2 == 0.3 // false

这是因为使用了IEEE 754数值,这个错误并非ECMAScript独有,其它使用相同格式的语言也有这个问题。

  • 整数和浮点值都是Number类型·
  • 浮点值的精确度最高可达17位小数,但还是远不如整数精确。因此,对于非整数的Number类型无法用 == 来比较
  • 我们可以使用最小精度值来实现上述比较:Number.EPSILON Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON

NaN == NaN // false

NaN 不等于包括它自己在内的任何值。可以通过 isNaN() 来进行判断。

字符串

ES6新增了模板字面量。使用反引号 标识

  • 保留换行符,可以跨行定义字符串。
  • 支持字符串插值,可以在 ${} 中放入JavaScript表达式

Symbol类型

符号是ES6新增的数据类型。它的作用是确保对象属性使用唯一标识,不会发生属性冲突。

  • Symbol 可以传入一个字符串作为描述,但是这个描述和符号的定义或标识无关。
  • Symbol 函数不能与new 关键字一起当做构造函数用
  • 全局符号注册表,使用Symbol.for()方法

数值转换

  • parseInt() 和 parseFloat() 可以将字符串转换为数值。
  • Number() 可以将任何数据类型转换为数值。

如果可以的话,不建议使用

操作符

相等和不相等 == 和 != ,它们在比较值钱会执行类型转换。

“55” == 55 // true

如果可以的话,不建议使用

全等和不全等 === 和 !==

用这个!

for - in

用于枚举对象中的非符号键属性

  • 符号键即Symbol类型的键
  • 属性包含原型链上的属性

可以通过 hasOwnProperty 方法来区分

for -of

用于遍历可迭代对象的元素。 什么是可迭代对象?

定义了Symbol.iterator 方法的对象! 我们给一个普通对象定义他的 Symbol.iterator 方法,就可以使用 for-of来遍历了。

let range = {
  from: 1,
  to: 5
};

// 1. for..of 调用首先会调用这个:
range[Symbol.iterator] = function() {
  // ……它返回迭代器对象(iterator object):
  // 2. 接下来,for..of 仅与此迭代器一起工作,要求它提供下一个值
  return {
    current: this.from,
    last: this.to,

    // 3. next() 在 for..of 的每一轮循环迭代中被调用
    next() {
      // 4. 它将会返回 {done:.., value :...} 格式的对象
      if (this.current <= this.last) {
        return { done: false, value: this.current++ };
      } else {
        return { done: true };
      }
    }
  };
};

// 现在它可以运行了!
for (let num of range) {
  console.log(num); // 1, 然后是 2, 3, 4, 5
}

总结

读完第三章,没有太多惊喜,认真学习了还没用过的Symbol类型。因为这章讲的是语言基础,基本都是在平常使用中遇到过的坑了~