前言
大家好,我是HoMeTown,web领域有一本神书大家应该都有看过,这本书我看过两遍,但是每次看都是粗粗的略过一些重要的知识点,甚至一些面试过程中的问题,在这本书里都能找到答案。
工作这么多年,到现在为止对这本书都没有一个系统的知识点记录,这次想从头读一遍这一本JavaScript高级程序设计【第4版】,并把重要的知识点
记录下来,同时加上自己的见解,这也是我第一次在掘金上记录分享读书笔记,共勉之!
关注专栏,一起学习吧~
区分大小写
在JavaScript中,一切都区分大小写,test
和Test
是两个不同的变量
标识符
标识符可以由一个或多个字符组成,字符包含:
- 第一个字符必须是一个字母,下划线_ 或者美元符号$
- 其他的字符可以使字母、下划线、美元符号、数字
严格模式
strict mode
对ECMAScript3
的一些不规范写法会被处理,使用方法是 在脚本开头写上"use strict"
。
也可以指定一个函数下执行严格模式:
function func() { "use strict" } 复制代码
严格模式下,这些操作是不被允许的:
"use strict"; // 不能给未声明的变量赋值,则会导致抛出 ReferenceError function func1() { message = "hometown"; console.log(message); // Uncaught ReferenceError: message is not defined } // 不能定义名为 eval 和 arguments 的变量,否则会导致语法错误 function func2() { var eval = "1"; // Uncaught SyntaxError: Unexpected eval or arguments in strict mode } func2(); // 不允许使用八进制的数字字面量 const num = 070 ...持续更新... 复制代码
变量
var 声明变量
变量声明
var message = 'HoMeTown' // ---- 也可以这样 ---- message = "HoMeTown" // 合法但不推荐 复制代码
作用域
在一个函数内声明变量,改变了会成为函数内部的局部变量,调用函数时,声明变量并赋值,随着函数执行完毕,该变量会被立即销毁,无法再次访问:
function func() { var message = "hometown"; console.log(message); // hometown } func(); console.log(message); //var.html:16 Uncaught ReferenceError: message is not defined 复制代码
如果去掉前面的关键字var
,那么变量message
就会变成全局变量,外部也可以访问:
function func() { message = "hometown"; console.log(message); // hometown } func(); console.log(message); // hometown 复制代码
两者的区别其实就在于,后者会直接在全局对象window
上创建一个属性message
,但是前者不会。
前者:
console.log(window.message) // undefined 复制代码
后者:
console.log(window.message) // hometown 复制代码
后者的写法也就等价于window.message = "hometown"
。
虽然可以通过省略var 操作符
定义全局变量
,但不推荐这么做。在局部作用域中定
义的全局变量很难维护,也会造成困惑。这是因为不能一下子断定省略 var 是不是有意而
为之。在严格模式下,如果像这样给未声明的变量赋值,则会导致抛出 ReferenceError。
声明提升
一般情况下,直接使用一个未声明的变了,会抛出错误,像这样:
function func() { console.log(message); // var.html:30 Uncaught ReferenceError: message is not defined } func(); 复制代码
使用var
声明变量时,即使在变量使用之后才声明,也不会报错,像下面这段代码:
function func() { console.log(message); // undefined var message = "hometown"; } func(); 复制代码
这就是var声明变量
的提升(hoist)
。也就是先把所有var声明
全部都拉到顶部,然后再在当前位置赋值,例如上面的这段代码,可以理解为:
function func() { var message; console.log(message) message = "hometown" } func() 复制代码
这个东西前几年面试还问的挺多的。
let 声明变量
变量声明
let
声明变量与var
一样
let message = "hometown"; 复制代码
块级作用域
let
与var
有一个显著的区别就是,let
声明变量具有块级作用域
,而var
是函数作用域
,举一个例子:
if (true) { var message = "hometown"; console.log(message); // hometown } console.log(message); // hometown if (true) { let age = 18; console.log(age); // 18 } console.log(age); // Uncaught ReferenceError: age is not defined 复制代码
上面这个例子中,可以看到age
在if块之外使用时,抛出了异常,age 变量之所以不能在 if 块外部被引用,是因为它的作用域仅限于该块内部。块作用域
是函数作用域
的子集
,因此适用于 var 的作用域限制同样也适用于 let。
不能重复声明
同一个块级作用域中不能使用let
重复声明变量,只要这个变量之前声明过,就不行:
function func() { var message = "hometown"; var message = "hometown"; console.log(message); // hometown } func(); function func2() { // var age = 18; // Cannot redeclare block-scoped variable 'age'. let age = 18; // Cannot redeclare block-scoped variable 'age'. let age = 22; // Uncaught SyntaxError: Identifier 'age' has already been declared console.log(age); } func2(); 复制代码
如果不在同一块级作用域,是可以声明的:
function func() { let message = "hometown"; if (true) { let message = "inside"; console.log(message); // hometown } console.log(message); // inside } func(); 复制代码
其实这样也不行:
let age = 18; // Cannot redeclare block-scoped variable 'age' var age = 22; // Uncaught SyntaxError: Identifier 'age' has already been declared 复制代码
因为这样var
会hoist
,其实就等价于:
var age; let age = 18; age = 22; 复制代码
所以在使用let声明变量时,一定要确保当前块级作用域没有声明过该变量
!
暂时性死区
let
声明的变量不会像var
那样变量提升!也就意味着,只要在使用变量的时候,这个变量没有被声明过,那就会报错!即使你在后面声明了该变量:
console.log(message); // undefined var message = "hometown"; console.log(age); // let.html:60 Uncaught ReferenceError: Cannot access 'age' before initialization let age = 18; 复制代码
变量提升
反过来对应的就是暂时性死区
,我觉得变量提升
简直就是个设计缺陷,书中也讲到,尽量避免使用var
,多使用let
& const
,鬼知道使用var
时不时的会带来一些什么问题。
不会添加到window
使用var
在全局声明变量时,会直接在window
上添加属性,使用let
声明则不会:
var message = "hometown"; console.log(window.message); // hometown let age = 18; console.log(window.age); // undefined 复制代码
for循环中的let
在let
出现之前,for
循环中的迭代变量会渗透到循环外部:
for (var i = 0; i < 5; i++) {} console.log(i); // 5 复制代码
改为let
以后,这个问题就好了:
for(let i = 0; i < 5; i++) {} console.log(i); // Uncaught ReferenceError: i is not defined 复制代码
还有一个面试常见问题:
for (var i = 0; i < 5; i++) { setTimeout(() => { console.log(i); }, 0); } // 结果: 5 5 5 5 5 复制代码
这块其实有点涉及时间循环与宏任务的知识,for
循环作为一个同步任务开始执行,然后循环产生一个宏任务setTimeout
,循环完毕之后,此时用var
声明的全局的i
已经自增到了5
,我们可以打个debugger
,然后再Sources中看到,此时i
在这个地方:
值为下图:
当我们修改为let
声明后,我们可以看到:
每个 setTimeout 引用的都是不同的变量实例,所以 console.log 输出的是我们期望的值,也就是循环执行过程中每个迭代变量的值。
const声明变量
与let一样,不同点是,const声明的变量不能被修改。
变量声明的最佳实践
- 不使用var
- const优先、let次之
数据类型
Js中有6中数据类型简单数据类型:
- Number
- String
- Boolean
- Null
- Undefined
- Symbol
后面好像还加了一个BigInt
。
typeof
typeof null // object 复制代码
因为null在js中被认为是一个空对象的引用。
undefined
undefined只有一个特殊值undefined
,当声明了一个变量,未对其进行初始化时,默认为undefined
。
但是不能直接使用未声明的变量:
let message; console.log(message); // undefined console.log(age); // 报错 复制代码
有趣的是,typeof undefined === undefined
typeof 未定义的值 === undefined
:
let message; console.log(typeof message, typeof age) // undefined undefined 复制代码
所以,尽量在定义变量的时候进行初始化,这样进行typeof
操作时,只要返回undefined
就知道,这个变量一定是没有什么过,而不是声明了没有赋值。
null
null也只有一个特殊值null
,null表示一个空对象指针,这也是为什么typeof null === 'object'
的原因。
书中建议使用 null 初始化一个对象,比如:
let obj = null 复制代码
但是这个规则好像到typescript
中不太奏效。因为在TypeScript中完全校验值的类型,null 与 {} 是两种不同的类型。
undefined
是由null
派生的,所以undefined == null
返回true
, 但是undefined === null
返回false
书中推荐,永远不要给一个值赋值undefined
,这样我们一旦遇到undefined
就立马可以知道这个变量没有声明。可以给一个值赋值null
,证明他是一个假值,一个空对象指针。
Boolean
Boolean值的可选值:true、false。
在JavaScript中,true不全等与1,false不全等于1。
Number
JavaScript采用的是IEEE 754格式表示整数和浮点数(双精度)。
整数
最基本的即十进制表示:
let num = 100 复制代码
八进制字面量的第一个数字必须是0
:
let num = 070 // 八进制的56 复制代码
如果后面的值超出了数值0 ~ 7
,默认省去0,按照十进制处理:
let num = 079 // 按照十进制的79处理 复制代码
十六进制的字面量必须以0x
开头:
let num = 0xA // 十六进制的10 复制代码