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

panda2022-10-09 16:53:43前端阅读 JavaScript

稍安勿躁

经过前边两个氛围轻松的章节,接下来就是直接接触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.

数据类型

js有六种简单的数据类型,他们分别是:

  • Undefined
  • Null
  • Boolean
  • Number(包含BigInt)
  • String
  • Symbol

除此之外还有一种复杂类型,叫做object,它是一种无序的名值对的集合.严格意义上来讲的话,js里除了六种简单原始类型外,其余都可以归为object对象类型.

typeof 检测数据类型的方式之一

由于js的数据类型是一个动态的,那就需要有一种可以判断当前数据类型的手段,typeof 操作符就是其中之一,使用方式如下:

typeof variable

该操作符将返回以下类型:

  • undefined 未定义
  • boolean 布尔值
  • string 字符串
  • number 数值
  • object 对象
  • function 函数
  • symbol 符号

特别说明:你可能也发现了,返回值里为什么没有null呢?这是来自于js的一个远古bug.红宝书里也有解释到(但这其实是一个委婉的说法😀):这是因为特殊值 null 被认为是一个对空对象的引用。

Undefined

这个类型是一个特殊意义的值,只有一个值;如果你声明了一个变量,但是并没有赋值给它,那么它的值就是undefined.

书中还说到一种情况,就是如果你希望你的比那辆显式的声明一个undefined变量,使用形如下边的方式是不推荐的

let a=undefined;

其实我们一边编码中如果希望显式的声明,可以使用下边的方式,至少看上去不是那么别扭,而且更安全

let a=void 0;

Null

同样是一个特殊类型的值,也只有一个值.null可以用来表示一个空指针对象,所以如果你对它使用typeof操作符,得到的结果会是一个object类型.(其实是一个bug 0.0)

如果你确信将来这个变量会是一个对象类型的值时,你就可以将你的变量初始化为一个null值.

let a=null;
a={}

Boolean

这个没啥好说的吧,大家使用都很频繁.有true和false两个值.

Number

ECMAScript 中最有意思的数据类型或许就是 Number 了。

红宝书开篇就说了这句话,换句话说就是number类型可能是js里最古怪行为的类型了,哈哈哈

严格意义来说js中是不分浮点和整数类型的

浮点

要定义浮点类型,只需要数字中包含小数点就行

let a=1.1
let b=0.1
let c=.1 // 不推荐,应该添0 > 0.1

如果你只需要定义一个整数,那么就不要使用浮点类型,因为浮点类型占用的内存空间是是整数类型的两倍,js引擎一般来说也会尽量帮你把浮点类型抓换为整数类型,形如1.0这种会被直接转换为1的整数类型.对于非常大或非常小的数值,浮点数可以使用科学计数法来表示(貌似在我的职业生涯中还没有相关的实践 =.=).

浮点值得精确度最高可达17位小数,由于占用双倍内存,这里可以倒推整数类型的范围,应该同样是是17位.

Last Updated 2022-12-15 13:16:34