带你读红宝书-第三章-语言基础

164 阅读9分钟

稍安勿躁

经过前边两个氛围轻松的章节,接下来就是直接接触JavaScript的本身了;想想还有点小刺激呢~我会尽量以轻松的氛围把我读到的讲出来,避免你的枯燥.当然,也可以选择走马观花的过一遍啦

语法

对于经常写js的同学来说,可能觉得自己对js的语法早就了如指掌了,那么我们还是来看看有什么新的地方值得讨论的吧:

区分大小写

js世界里的一切都需要区分大小写,无论是变量,函数,属性还是操作符都是需要评估大小写的,废话不多说,上代码更能激起共鸣

const test='1'
const Test='2'
console.log(test) //1

这里的testTest是不同的变量~

标识符规则

标识符是指变量,函数,属性,函数参数的名称,它遵循下边的规则:

  • 第一个字符必须是字母,下划线,或者$
  • 剩下的字符可以是字母,下划线,或者$

标识符中的字母可以是扩展 ASCII(Extended ASCII)中的字母,也可以是Unicode的字母字符,如 À 和 Æ(但不推荐使用)。

上面这句话是红宝书第四版的原文.基于此,我还测试了使用我们的中文来定义变量,发现也是可以正常运行的.

const 哈哈 = 123;
console.log(哈哈)//123

哈哈,如果这样,我们能把保留的关键字都替换为中文,那...这,就是易语言!🤪

在我们的日常工作中,标识符的最佳实践为驼峰命名,形如firstSecond,doSomethingImportant;虽然你可能觉得这是共识,自然而然的,不过这并不是强制性的.

注释

这个就不用多说了吧,相信大家都已经熟练掌握了,上个代码就完事

// 单行注释
// console.log('hi')
// 多行注释
/* const run = () => {
  console.log('run run run~');
} */

严格模式

严格模式是es5新增的一种运行模式,它可以将之前es3一些不规范的地方进行警告或者抛出错误,现在很多情况下,我们的工程都会默认开启这个模式来规避一些隐患

这个模式可以使用两种方式启用,局部全局,下面展示一下用法

我们只需要在脚本顶部书写"use strict";,即可全局开启严格模式

// 全局
"use strict";

如果我们希望严格模式只在某个局部作用域内发挥作用,那么我们可以在函数体内的顶部书写"use strict";

// 局部
function run(){
  "use strict";
}

至于严格模式开启之后的一些限制,后续会介绍到的

语句

js中的语句通常是以';'结束的,在js的世界里其实是可以没有分号的,比如Vue作者尤雨溪,他就是习惯不加分号的,这在社区中一直是一个讨论的话题~

红宝书里是推荐加上分号的,原文:

即使语句末尾的分号不是必需的,也应该加上。记着加分号有助于防止省略造成的问题,比如可以避免输入内容不完整。此外,加分号也便于开发者通过删除空行来压缩代码(如果没有结尾的分号,只删除空行,则会导致语法错误)。加分号也有助于在某些情况下提升性能,因为解析器会尝试在合适的位置补上分号以纠正语法错误。

注意,这里的语句不等于表达式哈~

关键字与保留字

关键字是指具有特殊用途的字符,比如流程控制的if,while等;ECMA-262的第六版关键字如下:

break do in typeof 
case else instanceof var 
catch export new void 
class extends return while 
const finally super with 
continue for switch yield 
debugger function this 
default if throw 
delete import try

哪什么是保留字呢?保留字是未来关键字的预备军,和关键字一样不能作为标识符,这些保留字未来有可能会成为关键字,ECMA-262的第六版关键字如下:

始终保留: 
enum 
严格模式下保留: 
implements package public 
interface protected static 
let private 
模块代码中保留: 
await

这些词汇虽然不能用作标识符,但是却可以作为对象的属性名,但最好不要这样干,以确保你的代码能够兼容过去和未来的es版本.

变量

js是动态语言,这意味着你声明的变量可以用来保存任何类型的数据.红宝书里准确的把变量比作值的占位符.js发展到至今,我们有三种方式来声明我们的变量:var,const,let;而现在的js运行时几乎全部都支持了let|const,再加上前端工程化的推动,也可以很轻松实现向下兼容,综合来看var的方式可以成为过去式了,我们喜欢且应该使用let,const这两种方式来声明我们的变量.

var

虽然这种方式已经成为过去式,但是了解它更有助于我们了解js这门语言本身.

通常我们可以这样声明一个变量:

var a;

这样一个变量a就声明好了,如果声明时没有赋值,那么你它的值是undefined

由于动态语言的特性,我们可以这样用

var a=1
a="hello"

这样是完全合乎js语言本身的,但是不推荐这样做,变量的最佳实践之一就是其保存的数据类型从始至终不发生改变.

作用域

使用var时的最大诟病来了,来慢慢看吧

function f() {
  var a = 1
}
console.log(a); //a is not defined

var的作用域为函数作用域,函数执行完成退出时就会被销毁,很合理是吧,但是它存在一个变量提升的问题

function f() {
  console.log(a); //undefined
  var a = 1
}
f()

当你执行这段代码时,会发现一切正常,这就是因为js的变量提升,它会将使用var声明的变量提升到函数顶部,它可以被转换为下面等价代码:

function f() {
  var a;
  console.log(a); //undefined
  a = 1;
}
f()

var还存在一个可重复声明的bug?:

var a=1;
var a="a";
var a=!!0;
console.log(a) //false

wow~一切正常.正是由于var存在这一系列的问题,包括变量提升,可重复声明等一系列问题,所以我们急需要两位得力成员:let,const

let

let除了解决了上面var的问题,而且作用域变为了块级作用域!我们用一个典型的例子来说明它为我们带来的便利

for (var i = 0; i < 4; i++) {
  setTimeout(() => {
    console.log(i) // 3,3,3,3
  }, 1000)
}

在以前我们如果在一个for循环里延迟打印索引,你会发现索引打印出来永远时最大索引,这是因为我们异步打印的原因,setTimeout创建的宏任务会在我们主线代码执行完成之后再执行,等主线任务全部执行完延迟任务再去读取的时候,每次都是读取到的最大索引.那咋办?我们可以使用一个闭包来解决这个问题,我们把每次循环的索引保存进一个函数内,然后延迟函数引用函数内保存的变量,这样就可以解决这个问题了.

for (var i = 0; i < 4; i++) {
  ((i) => {
    setTimeout(() => {
      console.log(i) // 0,1,2,3
    }, 1000)
  })(i)
}

但是如果你使用let来解决这个问题,那这个巴适了,改动成本几乎为0

for (let i = 0; i < 4; i++) {
  setTimeout(() => {
    console.log(i) // 0,1,2,3
  }, 1000)
}

对了,还没说明什么是块级,可以这么理解:{}花括号内的作用域就是块级作用域

wow~这也太爽了!这是因为啥呢?因为let是块级作用域,每次循环执行时都会重新创建一个块.等价代码贴出来你可能会明白一切

for (let i = 0; i < 4; i++) {
  // let i;这是被隐藏在for()的作用域
  setTimeout(() => {
    console.log(i) // 0,1,2,3
  }, 1000)
}
// {let i = 0}
// {let i = 1}
// {let i = 2}
// {let i = 3}

暂时性死区问题

另外关于变量提升这个问题,其实let也会存在变量提升,js引擎在解析代码时也会留意let,但是我们为什么无法提前访问它呢?那是因为在声明之前,会有一个暂时性死区.在死区存在区间(也就是被声明之前),我们无法使用任何方式来访问它.

还有一个全局变量的问题

我们在全局作用域使用var声明变量时,变量会自动挂载到全局对象下作为一个属性,比如浏览器环境:

var a = 1
console.log(window.a); // 1

换let,它并不会自动成为全局对象的一个属性

let a = 1
console.log(window.a); // undefined

条件声明和无法重新声明

条件声明是什么意思呢?指在符合某一条件时才声明这个变量,通常适用于全局环境中声明变量,由于目前模块化的盛行,这个东西可以抛弃,而且红宝书里也提了这是一种反模式,原文:

注意 不能使用 let 进行条件式声明是件好事,因为条件声明是一种反模式,它让程序变得更难理解。如果你发现自己在使用这个模式,那一定有更好的替代方式。

let是无法重新声明变量的,以下两种方式都是不被允许的

var a = 1
let a = 1

let b = 1
var b = 1

const

由于大部分特性和let相同,我们这里只说一下区别吧.const声明的变量时必须赋予初值且后续无法重新赋值.看起来貌似const似乎声明后就无法改变,事实也是如此,但是如果你赋予的是一个引用类型,那么其內部的属性等是可以被改变的,而且也并没有范围const的原则.

那么如何区分let,const的使用场景呢?我的建议是能用const就用const,不行再用let.

数据类型

TODO