Skip to content

S03-01 JS-基础-基础语法

[TOC]

概述

前端三大核心

前端开发最主要需要掌握的是三个知识点:HTMLCSSJavaScript

image-20250519091139198

计算机语言

计算机语言(Computer Language):指用于人与计算机之间通讯的语言,是人与计算机之间传递信息的介质。其概念比通用的编程语言要更广泛。例如,HTML是标记语言,也是计算机语言,但并不是编程语言。

前面我们已经学习了 HTML 和 CSS 很多相关的知识:

  • HTML:是一种标记语言
  • CSS:是一种样式语言;

他们本身都是属于计算机语言, 因为都在和计算机沟通交流;

  • 在生活中两个人想要沟通, 必然是通过某一种语言(中文/英语/粤语/东北话)
  • 计算机语言就是我们人和计算机进行交流要学习的语言;

网页的三大组成部分的另外一个核心就是 JavaScript:JavaScript 必然也是一种计算机语言;

image-20250519091522978

编程语言

概述

编程语言(Programming Language):用来定义计算机程序的形式语言。它是一种被标准化的交流技巧, 用来向计算机发出指令,一种能够让程序员准确地定义计算机所需要使用数据的计算机语言,并精确地定义在不同情况下应当采取的行动。

事实上, JavaScript 我们可以对其有更加精准的说法:一种编程语言。

编程语言的特点

  • 数据和数据结构
  • 指令及流程控制
  • 引用机制和重用机制
  • 设计哲学

这样的区分是否有意义呢?我们这里不讨论,我这里只把最专业的定义来告诉大家。

常见编程语言

image-20250519091550570

image-20250519091559592

编程语言历史

阶段一: 机器语言

  • 计算机的存储单元只有 0 和 1 两种状态,因此一串代码要让计算机“读懂”,这串代码只能由数字 0 和 1 组成。
  • 像这种由数字 0 和 1 按照一定的规律组成的代码就叫机器码,也叫二进制编码
  • 一定长度的机器码组成了机器指令,用这些机器指令所编写的程序就称为机器语言

image-20250519091626574

优点:

  • 无需编译解析:代码能被计算机直接识别,不需要经过编译解析;
  • 执行效率高:直接对硬件产生作用,程序的执行效率非常高;

缺点:

  • 可读性差:程序全是些 0 和 1 的指令代码,可读性差,还容易出错;
  • 不易编写:目前没有人这样开发;

阶段二: 汇编语言

  • 为了解决机器语言的缺陷,人们发明了另外一种语言——汇编语言
  • 这种语言用符号来代替冗长的、难以记忆的 0、1 代码。(mov/push 指令,经过汇编器,汇编代码再进一步转成 0101)

image-20250519091650449

优点:

  • 像机器语言一样,可以直接访问、控制计算机的各种硬件设备;
  • 占用内存少,执行速度快;

缺点:

  • 不兼容:不同的机器有不同的汇编语言语法和编译器,代码缺乏可移植性。一个程序只能在一种机器上运行,换到其他机器上可能就不能运行;
  • 不易编写/调试:符号非常多、难记。即使是完成简单的功能也需要大量的汇编语言代码,很容易产生 BUG,难于调试;

应用场景:操作系统内核、驱动程序、单片机程序;


阶段三: 高级语言

  • 最好的编程语言应该是什么? 自然语言;
  • 而高级语言, 就是接近自然语言, 更符合人类的思维方式
  • 跟和人交流的方式很相似, 但是大多数编程语言都是国外发明的, 因为都是接近于英文的交流方式

image-20250519091703877

image-20250519091716407

优点:

  • 易于理解:简单、易用、易于理解,语法和结构类似于普通英文;
  • 易于编写:远离对硬件的直接操作,使得一般人经过学习之后都可以编程,而不用熟悉硬件知识;
  • 兼容性好:一个程序还可以在不同的机器上运行,具有可移植性;

缺点:

  • 需要编译:程序不能直接被计算机识别,需要经编译器翻译成二进制指令后,才能运行到计算机上;
  • 种类繁多:JavaScript 、 C 语言、C++、C#、Java、Objective-C 、Python 等;

机器语言和高级语言

在前端,我们需要学好的只有一门高级语言:JavaScript。

image-20250519091740572

JavaScript

概述

JavaScript(JS):是一种高级的、解释型的编程语言。它是一门基于原型、头等函数的语言,是一门多范式的语言,它支持面向对象程序设计,指令式编程,以及函数式编程。

从上面的定义中, 我们会发现很多关键词:

  • 解释型语言? 原型? 头等函数? 多范式? 面向对象程序设计? 指令式编程? 函数式编程?
  • 这些改变往往会让人不知所云,需要我们完全掌握 JavaScript 再来回头看,每一个词语描述的都非常准确;

现在只需要知道,通俗的说法:

  • JavaScript 是一门高级编程语言, 是前端开发的重要组成部分!

HTML 和 CSS 也是前端开发的重要组成部分, 而 JavaScript 是前端开发的灵魂;

JS 历史

Javascript 诞生:1994 年 Netscape 公司出于实现与访问者互动的目的创造了 Javascript。

1994 年,网景公司(Netscape)发布了 Navigator 浏览器 0.9 版。

  • 这是历史上第一个比较成熟的网络浏览器,轰动一时。
  • 但是,这个版本的浏览器只能用来浏览,不具备与访问者互动的能力。
  • 网景公司急需一种网页脚本语言,使得浏览器可以与网页互动。

image-20250519091756955

image-20250519091803063

网景公司当时想要选择一种语言来嵌入到浏览器中:

  • 采用现有的语言,比如 Perl、Python、Tcl、Scheme 等等, 允许它们直接嵌入网页;
  • 1995 年网景公司招募了程序员Brendan Eich,希望将 Scheme 语言作为网页脚本语言的可能性;

就在这时,发生了另外一件大事:1995 年 Sun 公司将 Oak 语言改名为 Java,正式向市场推出;

  • Java 推出之后立马在市场上引起了轰动,Java 当初有一个口号:“write once run anywhere”;
  • 网景公司动了心,决定与 Sun 公司结成联盟,希望将 Java 嵌入到网页中来运行;
  • Brendan Eich 本人非常热衷于 Scheme,但是管理层那个时候有点倾向于 Java,希望可以简化 Java 来适应网页脚本的需求;

但是 Brendan Eich 对此并不感兴趣,他用 10 天时间设计出来了 JavaScript;

  • 最初这门语言的名字是 Mocha(摩卡);
  • 在 Navigator2.0 beta 版本更名为 LiveScript;
  • 在 Navigator2.0 beta 3 版本正式重命名为 JavaScript,当时是为了给这门语言搭上 Java 这个热词;

当然 10 天设计出来语言足够说明 Brendan Eich 是天才,但是这门语言当时更像是一个多种语言的大杂烩;

  • 借鉴 C 语言的基本语法;
  • 借鉴 Java 语言的数据类型和内存管理;
  • 借鉴 Scheme 语言,将函数提升到"第一等公民"(first class)的地位;
  • 借鉴 Self 语言,使用基于原型(prototype)的继承机制。

Brendan Eich 曾经这样描述过 JavaScript:

  • 与其说我爱 Javascript,不如说我恨它,它是 C 语言和 Self 语言一夜情的产物;
  • 十八世纪英国文学家约翰逊博士说得好:'它的优秀之处并非原创,它的原创之处并不优秀。’
  • (the part that is good is not original, and the part that is original is not good.)

微软的竞品 JScript

微软公司于 1995 年首次推出 Internet Explorer,从而引发了与 Netscape 的浏览器大战。

  • 微软对 Netscape Navigator 解释器进行了逆向工程,创建了 JScript,以与处于市场领导地位的网景产品同台竞争;
  • 这个时候对于开发者来说是一场噩耗,因为需要针对不同的浏览器进行不同的适配;

ECMAScript 标准规范 ECMA-262

1996 年 11 月,网景正式向 ECMA(欧洲计算机制造商协会)提交语言标准。

  • 1997 年 6 月,ECMA 以 JavaScript 语言为基础制定了 ECMAScript 标准规范ECMA-262;
  • ECMA-262 是一份标准,定义了 ECMAScript;
  • JavaScript 成为了 ECMAScript 最著名的实现之一;
  • 除此之外,ActionScript 和 JScript 也都是 ECMAScript 规范的实现语言;

ECMAScript 是一种规范,而 JavaScript 是这种规范的一种实现

JS 组成

ECMAScript 是 JavaScript 的标准,描述了该语言的语法和基本对象。

  • JavaScript 是ECMAScript的语言层面的实现;
  • 因为除了语言规范之外,JavaScript 还需要对页面和浏览器进行各种操作;
  • 除了基本实现之外,还包括DOM操作和BOM操作;

目前我们会针对性的学习 ECMAScript,也就是语言层面的内容,特别是 ES5 之前的语法。

image-20250519091841580

image-20250519091854661

JS 应用

image-20250519091415199

基础

编写方式

编写方式

位置一:HTML 代码行内(不推荐)

image-20250519100341555

位置二:Script 标签中

image-20250519100353437

位置三:外部的 Script 文件

需要通过 script 元素的src属性来引入 JavaScript 文件

image-20250519100405611

image-20250519100412770

注意事项

  1. script 元素不能写成单标签

    • 在外联式引用 js 文件时,script 标签中不可以写 JavaScript 代码,

      image-20250519100405611

    • script 标签不能写成单标签,即不能写成:<script src="index.js"/>

  2. 现代浏览器可以省略 type 属性

    • 在以前的代码中,<script>标签中会使用 type="text/javascript"
    • 现在可不写这个代码了,因为 JavaScript 是所有现代浏览器以及 HTML5 中的默认脚本语言;
  3. 加载顺序

    • 作为 HTML 文档内容的一部分,JavaScript默认遵循 HTML 文档的加载顺序(自上而下)
    • 推荐将 JavaScript 代码和编写位置放在 body 子元素的最后一行
  4. 严格区分大小写

    • HTML 元素和 CSS 属性不区分大小写,但是在 JavaScript 中严格区分大小写;

后续补充:script 元素还有 defer、async 属性,我们后续再详细讲解。

<noscript>

针对早期浏览器不支持 JavaScript 的问题,需要一个页面优雅降级的处理方案:<noscript>。

<noscript>:定义在浏览器不支持或禁用 JS 时显示的替代内容。

语法

  • 位置:可放置在 <head><body> 中。
  • 内容:可包含任何 HTML 元素(如文本、图片、链接等)。
html
<noscript>
  <!-- 替代内容 -->
</noscript>

示例

  1. <body> 中(常见用法)

    html
    <script>
      document.write('JavaScript 已启用!')
    </script>
    <noscript>
      <p>⚠️ 您的浏览器未启用 JavaScript,部分功能无法使用!</p>
    </noscript>
  2. <head> 中(特殊用途):仅用于加载 CSS 等资源

    html
    <head>
      <noscript>
        <link rel="stylesheet" href="no-js-styles.css" />
      </noscript>
    </head>

浏览器禁用 JS

路径:浏览器 - 设置 - 隐私与安全 - 网站设置 - JavaScript

image-20250613164149143

交互方式

交换方式:JavaScript 有如下和用户交互的手段:最常见的是通过 console.log, 目前大家掌握这个即可;

image-20250519100519283

Chrome 调试

Console

在前面我们利用 Chrome 的调试工具来调试了 HTML、CSS,它也可以帮助我们来调试 JavaScript。

当我们在 JavaScript 中通过console 函数显示一些内容时,也可以使用 Chrome 浏览器来查看:

image-20250519100606084

注意事项

  • 显示错误:如果在代码中出现了错误,那么可以在 console 中显示错误。
  • 命令行:console 中有个 > 标志,它表示控制台的命令行。
    • 单行命令:在命令行中我们可以直接编写 JavaScript 代码,按下enter会执行代码。
    • 多行命令:如果希望编写多行代码,可以按下shift+enter来进行换行编写。
  • 后续:debug:在后续我们还会学习如何通过 debug 方式来调试、查看代码的执行流程;

语句和分号

语句(Statements):是向浏览器发出的指令,通常表达一个操作或者行为(Action)。

比如我们前面编写的每一行代码都是一个语句,用于告知浏览器一条执行的命令;

image-20250519100623978

分号(Semicolon):通常每条语句的后面我们会添加一个分号,表示语句的结束

自动插入分号(an automatic semicolon,省略分号):当存在换行符(line break)时,在大多数情况下可以省略分号。JS 将换行符理解成“隐式”的分号;

推荐做法

  • 前期:在对 JavaScript 语法不熟悉的情况推荐添加分号
  • 后期:对 JavaScript 语法熟练的情况下,任意

注释

在 HTML、CSS 中我们都添加过注释,JavaScript 也可以添加注释。

JavaScript 的注释主要分为三种:

  • 单行注释// 单行注释

  • 多行注释/* 多行注释 */

  • 文档注释:VSCode 中需要在单独的 JavaScript 文件中编写才有效。

    image-20250519100656246

注意:JS 不支持注释的嵌套。

VSCode 插件

推荐两个 VSCode 插件

  • ES7+ React/Redux/React-Native snippets:这个插件是在 react 开发中会使用到的,但是我经常用到它里面的打印语句;

  • Bracket Pair Colorizer 2VSCode 已内置,配置方法:

    json
    "editor.bracketPairColorization.enabled": true,
    "editor.guides.bracketPairs":"active"

变量

概述

变量(Variable):用于存储数据的命名容器。程序通过变量名访问和操作内存中的数据。

程序中变量的数据

在我们平时开发中,使用最多的并不是固定的数据, 而是会变换的数据:

  • 比如购物车商品的数量、价格的计算等等;
  • 比如一首歌曲播放的时间、进度条、歌词的展示等等;
  • 比如微信聊天中消息条数、时间、语音的长度、头像、名称等等;
  • 比如游戏中技能的冷却时间、血量、蓝量、buff 时间、金币的数量等等;

image-20250519101154230

如果我们希望记录某一个之后会变量的数据,在 JavaScript 中我们可以定义一个 变量:

  • 一个变量,就是一个用于存放数值的容器;
  • 这个数值可能是一个用于计算的数字,或者是一个句子中的字符串,或者其他任意的数据;
  • 变量的独特之处在于它存放的数值是可以改变的;

我们可以把变量想象成一个盒子,盒子里面装着我们的数据,我们需要给盒子进行一个特性的名称。

  • 例如,变量 message 可以被想象成一个标有 “message” 的盒子,盒子里面的值为 “Hello!”;
  • 并且,这个盒子的值,我们想改变多少次,就可以改变多少次;

image-20250519101237822

image-20250519101232007

语法格式

语法格式:在 JavaScript 中如何命名一个变量呢?包含两部分:

变量的声明:在 JavaScript 中声明一个变量使用var关键字(variable 单词的缩写)(后续学习 ES6 还有 let、const 声明方式)

js
var message

变量的赋值:使用 = 给变量进行赋值;

js
var message = 'hello'

核心特性

  1. 存储的值可变:同一个变量在不同时刻可存储不同值。

    js
    var score = 100 // 初始值
    score = 95 // 更新值
  2. 声明和赋值分开操作

    js
    var message
    message = 'hello'
  3. 同时声明多个变量

    js
    // 写法一:
    var name, age, height
    name = 'tom'
    age = 18
    height = 1.88
    
    // 写法二:
    var name = 'tom',
      age = 18,
      height = 1.88
    
    // 写法三:(推荐)
    var name = 'tom'
    var age = 18
    var height = 1.88

命名规范

命名标识符规则必须遵守

  • 字母/下划线开头:包含字母、数字、下划线(如 user_age, _count, totalPrice)。
  • 区分大小写myVarmyvar
  • 不可用关键字:避免 if, for, function 等语言保留字。

命名标识符规范建议遵守

  • 驼峰写法:多个单词使用驼峰标识;
  • 空格:赋值 = 两边都加上一个空格;
  • 分号:一条语句结束后加上分号; 也有很多人的习惯是不加;
  • 见名知意:变量应该做到见名知意;

image-20250519101341287

练习

练习一:定义一些变量,保存自己的个人信息:

  • 比如姓名、年龄、身高、体重、爱好等等

练习二:定义一个变量 name,赋值成 coderwhy。定义一个变量 admin,将 name 赋值给 admin

练习三:定义变量,保存两个数字,并且对两个变量的数字进行交换

  • 方式一:使用临时变量
  • 方式二:不使用临时变量(了解即可)

练习四:让用户在浏览器中输入一个值,在 JavaScript 程序中使用变量接收

作业:你平时在使用一些应用程序时,哪些内容可以定义成变量?

  • 比如玩游戏、听歌、购物的应用程序中;

使用注意

  • 注意 1:变量未声明

    如果一个变量未声明(declaration)就直接使用,那么会报错

    image-20250519101432717

  • 注意 2:变量未赋值

    如果一个变量有声明,但是没有赋值,那么默认值是undefined

    image-20250519101453953

  • 注意 3:不用 var 声明变量

    如果没有使用 var 声明变量也可以,但是不推荐(事实上会被添加到 window 对象上)

    image-20250519101509116

数据类型

JS 的数据类型

JavaScript 中的值都具有特定的类型

  • 例如,字符串或数字。
  • 我们可以将值赋值给一个变量,那么这个变量就具备了特定的类型;

动态类型的编程语言(dynamically typed):允许一个变量可以在前一刻是个字符串,下一刻就存储一个数字的编程语言,如 JavaScript。

数据类型分为两大类

  • 原始类型(Primitive):存储简单数据值,不可变(immutable),按值传递
  • 对象类型(Object):存储复杂数据结构,按引用传递,包含属性和方法。

JS 中有 8 种基本数据类型:7 种原始类型和 1 种引用类型

  • Number:任何类型的数字,整数或浮点数。
  • String:字符串。
  • Boolean:true 和 false。
  • Undefined:未定义的值,只有一个 undefined 值的独立类型。
  • Null:未知的值,只有一个 null 值的独立类型。
  • Object:更复杂的数据结构。
  • BigInt:任意长度的整数。
  • Symbol:唯一的标识符。

Number

Number:代表整数和浮点数

image-20250519102455704

常见操作:数字 number 可以有很多操作,比如,乘法 *、除法 /、加法 +、减法 - 等。

常见运算符:后续专门讲解。

image-20250519102508146

特殊数值(Special Numeric Values):除了常规的数字,还包括所谓的特殊数值也属于 Number 类型。

  • Infinity:代表数学概念中的无穷大 ,也可以表示-Infinity。如 1/0 == Infinity
  • NaN:代表一个计算错误,它是一个错误的操作所得到的结果;如'tom' * 100 == NaN

进制表示:在之前我们学习过进制的概念,数字类型也有其他的进制表示方法:

  • 十进制
  • 十六进制0x开头
  • 二进制0b开头
  • 八进制0o开头

image-20250519102539778

数字表示的范围

  • 最小正浮点数Number.MIN_VALUE,这个值为: 5e-324,小于这个的数字会被转化为 0。
  • 最大正浮点数Number.MAX_VALUE,这个值为: 1.7976931348623157e+308

相关方法

  • Number.isNaN()(num),用于判断是否不是一个数字。不是数字返回 true,是数字返回 false。

后续我们会对 Number 类型进行更加详细的学习;

String

String:用于表示和操作字符序列。

在开发中我们经常会有一些文本需要表示,这个时候我们会使用字符串 String:

  • 比如人的姓名:coderwhy。地址:广州市。简介:认真是一种可怕的力量;

字符串字面量:可以使用单引号'双引号"反引号指定。

image-20250519102559798

前后引号类型必须一致

  • 如果在字符串里面本身包括单引号,可以使用双引号;
  • 如果在字符串里面本身包括双引号,可以使用单引号;

image-20250519102608786

转义字符

转义字符:除了普通的可打印字符以外,一些有特殊功能的字符可以通过转义字符的形式放入字符串中:

image-20250519102617490

转义字符串开发中只有特殊场景才会用到,暂时掌握 \' \" \t \n四个的用法即可。

属性和方法

字符串还有很多细节和操作方法,在后续学习了面向对象后,我们再详细学习;

基本操作:这里我们先掌握几个基本的字符串使用操作:

  • 字符串拼接:通过+运算符(后续还会详细讲解)

    image-20250519102629631

  • 获取字符串长度

Boolean

Boolean布尔,用于表示真假

  • 比如是否毕业. 是否有身份证. 是否购买车票. 是否成年人;
  • 比如开发中,我们会判断一个账号是否登录、是否是管理员、是否具备某个权限、是否拥有某个英雄、皮肤等;

命令来源:布尔(英语:Boolean)是计算机科学中的逻辑数据类型,以发明布尔代数的数学家乔治·布尔为名。

Boolean 类型的值truefalse

image-20250519102645256

在后续 逻辑运算符 中我们还会详细学习和使用 Boolean 类型;

Undefined

Undefined未定义,表示变量已声明但尚未赋值,或对象属性/函数参数不存在。

Undefined 类型的值undefined

示例

  1. 未赋值的变量

    image-20250519102653625

  2. 显式赋值 undefined(不推荐)

    image-20250519102702198

注意事项

  1. 推荐变量定义时初始化:而不只是声明一个变量。
  2. 不要显式为变量赋值undefined:可以赋值0''null表示什么变量开始时都没有。

Null

Null:通常用来表示一个对象为空

Null 类型的值null

应用场景

  1. 初始化对象变量:给一个对象进行初始化时赋值为 null。

    js
    var obj = null // 稍后会被赋值
    obj = { name: 'tom', age: 18 }
  2. typeof 检测

    js
    typeof null // "object"

对比 undefined

对比项nullundefined
含义"无对象"的故意空值"未初始化"的系统默认值
类型typeof null → "object"(历史遗留错误)typeof undefined → "undefined"
赋值方开发者显式赋值JavaScript 引擎自动分配
相等判断null == undefined → truenull === undefined → false
典型用法表示有意的空对象引用表示未定义的初始状态

Object

Object引用类型,用于存储键值对集合。几乎所有复杂数据类型(如数组、函数)都是对象的特殊形式。

对象类型包括以下特殊类型

类型说明检测方式
Object{}new Object()typeof obj === "object"
Array[1, 2, 3]Array.isArray(arr)
Functionfunction() {}typeof func === "function"
Datenew Date()obj instanceof Date
RegExp/pattern/obj instanceof RegExp

对象的字面量语法

js
// 对象字面量语法
const person = {
  name: '张三', // 键(key): "name" → 值(value): "张三"
  age: 30, // 键值对 = 属性(property)
  isEmployed: true,
  sayHello() {
    // 方法(method)
    console.log(`你好,我是${this.name}`)
  }
}

相关操作

typeof

typeof:用于检测变量或表达式数据类型的一元操作符。它返回一个表示数据类型的字符串值。

语法

js
// 两种使用方式
typeof operand
typeof operand
  • operand:要检测的变量或表达式。接受任何 JS 数据类型作为参数。
  • 括号:可选,typeof xtypeof(x) 效果相同。typeof 是一个操作符,并非是一个函数,()只是将后续的内容当做一个整体而已。

返回值:始终返回表示数据类型的字符串

  • 特殊值:只有 Null/Array 类型或 Set/WeakSet/Map/WeakMap 类型的返回值不是自身类型的字符串描述,而是"object"
操作数类型返回值补充判断方法示例
Number"number"typeof 3.14 / typeof NaN
String"string"typeof "hello"
Boolean"boolean"typeof true
Undefined"undefined"typeof undefined
Object"object"typeof {}
BigInt"bigint"typeof 10n
Symbol"symbol"typeof Symbol()
Function"function"typeof alert
特殊值
Null"object" 🚨val === Nulltypeof null (历史遗留问题)
Array"object" 🚨Array.isArray()typeof []
Set"object" 🚨instanceof Settypeof new Set([1,2,3])
WeakSet"object" 🚨instanceof WeakSettypeof new WeakSet([{msg:'mr'},{age: 18}])
Map"object" 🚨instanceof Maptypeof new Map([[100,'aaa'],[{msg:'mr'},'bbb']])
WeakMap"object" 🚨instanceof WeakMaptypeof new WeakMap([[{msg:'mr'}, 'aaa']])

示例:类型检查

js
const typeCheck = (value) => {
  if (value === null) return 'null' // 区分null
  const baseType = typeof value
  // 区分数组和普通对象
  return baseType === 'object'
    ? Array.isArray(value)
      ? 'array'
      : 'object' // 区分array
    : baseType
}

// 测试
typeCheck(42) // 'number'
typeCheck(null) // 'null'
typeCheck([1, 2]) // 'array'
typeCheck({ a: 1 }) // 'object'
typeCheck(() => {}) // 'function'

数据类型转换

需求:在开发中,我们可能会在不同的数据类型之间进行某些操作

  • 比如把一个 String 类型的数字和另外一个 Number 类型的数字进行运算;
  • 比如把一个 String 类型的文本和另外一个 Number 类型的数字进行相加;
  • 比如把一个 String 类型或者 Number 类型的内容,当做一个 Boolean 类型来进行判断;
  • 等等

转换方式:也就是在开发中,我们会经常需要对数据类型进行转换:

  • 隐式转换:大多数情况下,运算符和函数会自动将赋予它们的值转换为正确的类型,这是一种隐式转换;
  • 显式转换:我们也可以,通过显式的方式来对数据类型进行转换;

接下来我们来看一下数据类型之间的转换:

  • String、Number、Boolean 类型;
String 转换

其他类型经常需要转换成字符串类型,比如和字符串拼接在一起或者使用字符串中的方法。

隐式转换

  • +操作:一个字符串和另一个字符串进行+操作;

    • 如果+运算符左右两边有一个是字符串,那么另一边会自动转换成字符串类型进行拼接;
  • 函数执行:某些函数的执行也会自动将参数转为字符串类型。如 console.log()函数;

显式转换

方法和函数的区别,我们后续在讲解面向对象时会讲到;

Number 转换

其他类型也可能会转换成数字类型

隐式转换

  • 算数运算:在算数运算中,通常会将其他类型转换成数字类型来进行运算。如"6" / "2"
  • +运算特殊:但是如果是+运算,并且其中一边有字符串,那么还是按照字符串来连接的;

显式转换

  • 使用Number()函数来进行显式的转换;

类型转换规则

image-20250519102758610

Boolean 转换

布尔(boolean)类型转换是最简单的。

隐式转换:发生在逻辑运算中。

显式转换:调用 Boolean(value) 显式地进行转换。

转换规则

  • false:直观上为“空”的值(如 0、空字符串、null、undefined 和 NaN)将变为 false。
  • true:其他值变成 true。
  • 特殊值"0":包含 0 的字符串 "0" 是 true
    • 一些编程语言(比如 PHP)视 "0" 为 false。但在 JavaScript 中,非空的字符串总是 true。

image-20250519102813453

JS 真值和假值

在 JavaScript 中:

  • 假值(Falsy)false, 0, -0, 0n, "", null, undefined, NaN
  • 真值(Truthy):除假值外的所有值(包括 {}, [], "0", "false" 等)。

运算符

概述

运算符

在小学的时候我们就学习了各种运算符,比如加号 +、乘号 *、减号 - 、除号/

几乎所有的编程语言都有各种各样的运算符(Operators,操作符)

  • 初次接触这些运算符, 你会感觉种类繁多, 难以记忆.
  • 但是并不需要特别担心, 因为很多的运算符我们在以后的开发中, 每天都会使用;
  • 多练习, 不需要刻意去记忆;
  • 而且常见的高级语言运算符都是相似的,学了 JavaScript 运算符很容易掌握 C/C++/OC/Python 等语言的运算符;

计算机最基本的操作就是执行运算,执行运算时就需要使用运算符来操作:

  • 比如 console.log(20 + 30); +号就是一个运算符.
  • 比如 console.log(20 _ 30); _号也是一个运算符.

运算符分类:JavaScript 按照使用场景的不同将运算符分成了很多种类型:

  • 算术运算符+-*/%**++--
  • 赋值运算符=+=-=*=/=%=**=??=
  • 比较运算符=====!=!==><>=<=?
  • 逻辑运算符&&||!??
  • 位运算符&|^~<<>>>>>
  • 三元运算符?:
  • 类型运算符typeofinstanceofindelete
  • 其他运算符,voidnew...()

运算符优先级:从高到低排列(部分关键优先级):

| 优先级 | 运算符类型 | 运算符示例 | | | | :----- | :--------------- | :---------------------------------- | --- | --- | --- | --- | | 21 | 分组 | () | | | | 20 | 成员访问 | . [] | | | | 19 | new (带参数列表) | new Date() | | | | 18 | 函数调用 | fn() | | | | 17 | 后置递增/减 | ++ -- | | | | 16 | 逻辑非/位非 | ! ~ | | | | 15 | 乘除/取模 | * / % | | | | 14 | 加减 | + - | | | | 13 | 位移 | << >> >>> | | | | 12 | 关系 | < > <= >= in instanceof | | | | 10 | 相等 | == != === !== | | | | 6 | 逻辑与 | && | | | | 5 | 逻辑或 | | | | | | | 4 | 三元运算符 | ?: | | | | 3 | 赋值 | = += -= 等 | | | | 2 | yield | yield | | | | 1 | 逗号 | , | | |

运算元

在正式开始运算之前,我们先学习一下常见的术语:

运算元(Operand,参数):是指被运算符操作的值或变量(如5 * 2有 2 个运算元52)。

运算符:对运算元执行操作的符号(如 +, -, *, / 等)。它分为:

  • 一元运算符:一个运算符对应的只有一个运算元(如-3)。

  • 二元运算符:一个运算符拥有两个运算元(如2 + 3)。

表达式:由运算元和运算符组成的计算单元。

算术运算符

算术运算符:用于数学计算,返回数字结果(除 + 可能返回字符串)。

运算符名称示例结果特殊说明
+加法/字符串连接5 + 27字符串连接:"a" + "b" → "ab"
-减法5 - 23
*乘法5 * 210
/除法10 / 255 / 0 → Infinity
%取余10 % 31求余数
**指数(幂)2 ** 38ES2016 新增
++自增let x=5; x++x=6前增量:++x 先增后用
--自减let x=5; x--x=4后增量:x-- 先用后减

示例

js
'5' - '2' // 3 (自动转数字)
'5' + 2 // "52" (字符串连接)

取余 %

取余(%):返回左侧操作数除以右侧操作数的余数

示例

js
console.log(13 % 5) // 3
console.log(-13 % 5) // -3
console.log(4 % 2) // 0
console.log(-4 % 2) // -0

幂 **

幂(**等价于 Math.pow()ES2016,返回第一个操作数取第二个操作数的的结果。

示例

js
console.log(3 ** 4) // 81
console.log(10 ** -2) // 0.01
console.log(2 ** (3 ** 2)) // 512
console.log((2 ** 3) ** 2) // 64

自增/自减

自增(++)/自减(--)一元运算符,用于对变量进行加 1 或减 1 操作。

分类:它们有前缀形式后缀形式,行为有重要区别。

  • 前缀形式++x/--x,先执行增/减操作,后返回增减后的值。

    js
    let x = 5
    let y = ++x // 1. x 变为 6  2. y 被赋值为 6
    console.log(x, y) // 6, 6
  • 后缀形式x++/x--,先返回当前值,后执行增/减操作。

    js
    let a = 5
    let b = a-- // 1. b 被赋值为 5  2. a 变为 4
    console.log(a, b) // 4, 5

核心特性

  1. 自增/自减只能应用于变量:应用于数值(如 5++)会报错。

    js
    // x++ 返回的是值而非变量引用,不能再次应用 ++
    let x = 5;
    x++++; // SyntaxError: Invalid left-hand side expression

赋值运算符

赋值运算符:用于将值赋给变量

js
let x = 123 // 将值 123 写入 x 然后返回 x

分类

  • 基础赋值运算符=
  • 算术赋值运算符+=-=*=/=%=**=
  • 位运算赋值运算符&=|=^=<<=>>=>>>=
  • 逻辑赋值运算符&&=||=??=

基础赋值运算符

基础赋值

基础赋值:将右侧的值赋给左侧的变量,并返回被赋的值(可链式赋值)。

语法

js
variable = value

示例

js
// 基本赋值
let x = 10

// 链式赋值
let a, b, c
a = b = c = 5 // 所有变量值为5

// 解构赋值
const [first, second] = [1, 2] // first=1, second=2
const { name, age } = { name: 'Alice', age: 30 }
链式赋值

链式赋值(Chained Assignment):允许在单个表达式中将同一个值连续赋给多个变量

语法

js
variable1 = variable2 = ... = value;

执行顺序

js
let a, b, c
a = b = c = 10

// 执行顺序:
// 1. c = 10 → 将10赋值给c
// 2. b = c → 将c的值(10)赋值给b
// 3. a = b → 将b的值(10)赋值给a

核心特性

  1. 从右向左执行:赋值操作从最右侧开始
  2. 共享值:所有变量获得相同的值
  3. 返回最终值:整个表达式返回最左侧变量的值
  4. 变量必须先声明(使用 let/var/const)

注意事项

  1. 变量要先声明

    js
    // ❌ 错误:未声明的变量
    a = b = 10 // 在严格模式下报错,非严格模式创建全局变量
    
    // ✅ 正确:先声明
    let a, b
    a = b = 10
  2. 共享同一个数组/对象引用

    js
    const arr1 = (arr2 = []) // 共享同一个数组引用
    
    arr1.push(1)
    console.log(arr2) // [1] - 两个变量指向同一数组
    console.log(arr1 === arr2) // true
  3. 更现代的替代方案:解构赋值(更强大)

    js
    // 更现代的替代方案
    const [x, y, z] = Array(3).fill(0)
    
    // 对象属性
    const config = { width: 100, height: 100, depth: 100 }

算术赋值运算符

原地修改

原地修改 (In-place Modification):是指直接在原始数据结构上进行更改,而不创建新的副本。这种操作方式直接改变内存中的原始数据,而不是返回一个新的修改后的版本。

核心特性

  1. 内存效率:不创建新对象,节省内存
  2. 副作用:操作会改变原始数据
  3. 性能优势:避免创建副本的开销
  4. 引用保留:所有指向该数据的引用都会看到变化

示例:算术复合赋值

我们经常需要对一个变量做运算,并将新的结果存储在同一个变量中。

js
// 原地修改
var n = 10
n = n + 5
n = n * 2

// 使用 += *= 缩写
n += 5
n *= 2
算术赋值

算术赋值:将算术运算与赋值结合,简化表达式。

所有算术运算符都有简短的“修改并赋值”运算符:/=-= 等。

运算符示例等价于说明
+=x += 5x = x + 5加法赋值
-=x -= 3x = x - 3减法赋值
*=x *= 2x = x * 2乘法赋值
/=x /= 4x = x / 4除法赋值
%=x %= 3x = x % 3取模赋值
**=x **= 2x = x ** 2指数赋值 (ES2016)

示例:特殊行为

js
// 字符串操作
let str = 'Hello'
str += ' World!' // "Hello World!"

// 类型转换
let num = '5'
num *= 2 // 10 (自动转数字)

// 浮点数问题
let price = 19.99
price *= 100 // 1998.9999999999998 (精度问题)

位运算赋值运算符@

位运算赋值运算符(位运算复合赋值运算符):用于底层二进制操作。

运算符示例等价于说明
&=x &= 5x = x & 5按位与赋值
|=x |= 3x = x | 3按位或赋值
^=x ^= 1x = x ^ 1按位异或赋值
<<=x <<= 2x = x << 2左移赋值
>>=x >>= 1x = x >> 1带符号右移赋值
>>>=x >>>= 1x = x >>> 1无符号右移赋值

示例:应用场景

js
// 权限系统
let permissions = 0
const READ = 1
const WRITE = 2

// 添加写权限
permissions |= WRITE

// 检查读权限
const canRead = (permissions & READ) === READ

逻辑赋值运算符

逻辑赋值运算符(逻辑复合赋值运算符):针对逻辑运算的增强赋值。

运算符示例等价于说明
&&=x &&= yx = x && y逻辑与赋值。仅在 x真值时为其赋值(很少用
||=x ||= yx = x || y逻辑或赋值。仅在 x假值时为其赋值
??=x ??= yx = x ?? y逻辑空赋值。仅在 x空值(null 或 undefined) 时为其赋值

示例

  1. 基本应用:设置默认值

    js
    // ||= 设置默认值(null/undefined/0/false/''都会触发默认值)
    function foo(msg) {
      msg ||= '默认值' // 等价于:msg = msg || '默认值'
    }
    
    // ??= 设置默认值(更严谨,只有null或undefined触发默认值)
    function foo(msg) {
      msg ??= '默认值' // 等价于:msg = msg ?? '默认值'
    }
  2. ??= 的应用场景

    判断 obj 是否有值,如果有值将 obj.name 赋值给 obj

    js
    let obj = { name: 'tom' }
    obj &&= obj.name // 等价于 obj = obj && obj.name
    console.log(obj) // 'tom'
  3. 特殊行为

    js
    // ||= 和 ??= 的区别
    let count = 0
    
    count ||= 10 // 0 是 false → count=10
    count ??= 20 // 0 不是 null/undefined → 保持0
    
    // 实际应用:配置默认值
    const config = {}
    config.timeout ??= 5000 // undefined:仅当未定义时设置

解构赋值@

解构赋值(Destructuring Assignment)ES2015,从数组或对象中提取数据,并直接赋值给变量。它分为数组解构对象解构

核心特性

  • 解包:从数组或对象中提取值。
  • 赋值:将提取的值赋给相应的变量。
  • 模式匹配:根据特定模式匹配数据结构。
数组解构

数组解构(Array Destructuring)ES2015,从数组中提取值,并将这些值直接赋给变量。

语法

js
const colors = ['red', 'green', 'blue']

// 基础解构
const [firstColor, secondColor, thirdColor] = colors

console.log(firstColor) // 'red'
console.log(secondColor) // 'green'
console.log(thirdColor) // 'blue'

核心特性

  1. 顺序问题:数组解构有严格的顺序问题,参数一一对应数组中的元素

    js
    const numbers = [1, 2, 3, 4, 5]
    const [first, second, third] = numbers // 1 2 3
  2. 跳过不需要的元素

    js
    const numbers = [1, 2, 3, 4, 5]
    
    // 跳过不需要的元素
    const [first, , third, , fifth] = numbers
    
    console.log(first) // 1
    console.log(third) // 3
    console.log(fifth) // 5
  3. 设置默认值:当解构的值可能为 undefined 时,可以设置默认值

    js
    const fruits = ['apple', 'banana']
    
    // 设置默认值
    const [fruit1, fruit2, fruit3 = 'orange'] = fruits
    
    console.log(fruit1) // 'apple'
    console.log(fruit2) // 'banana'
    console.log(fruit3) // 'orange' (使用默认值)
  4. 剩余参数:使用剩余参数 (...) 捕获数组剩余部分

    js
    const letters = ['a', 'b', 'c', 'd', 'e']
    
    // 获取剩余元素
    const [first, second, ...rest] = letters
    
    console.log(first) // 'a'
    console.log(second) // 'b'
    console.log(rest) // ['c', 'd', 'e']
对象解构

对象解构ES2015,从对象中提取值,并将这些值直接赋给变量。

语法

js
const user = {
  id: 1,
  username: 'js_dev',
  email: 'dev@example.com'
}

// 解构基本属性
const { username, email } = user

console.log(username) // 'js_dev'
console.log(email) // 'dev@example.com'

核心特性

  1. 顺序问题:对象解构没有顺序问题,是根据 key 来解构的。

    js
    const obj = { name: 'tom', age: 18, heigth: 1.88 }
    const { height, age, name } = obj
    console.log(name, age, height) // 'tom' 18 1.88
  2. 重命名变量

    js
    const book = {
      title: 'JavaScript: The Good Parts',
      author: 'Douglas Crockford'
    }
    
    // 重命名解构出的变量
    const { title: bookTitle, author: bookAuthor } = book
    
    console.log(bookTitle) // 'JavaScript: The Good Parts'
    console.log(bookAuthor) // 'Douglas Crockford'
  3. 设置默认值:当属性不存在时使用默认值

    js
    const settings = {
      theme: 'dark',
      fontSize: 16
    }
    
    // 设置默认值(当属性不存在时使用)
    const { theme, fontSize, notifications = true } = settings
    
    console.log(notifications) // true(使用默认值)
  4. 嵌套解构

    js
    const employee = {
      id: 101,
      name: 'Alice',
      department: {
        name: 'Engineering',
        location: 'Building A'
      }
    }
    
    // 嵌套解构
    const {
      name,
      department: { location }
    } = employee
  5. 结合重命名和默认值

    js
    const product = {
      name: 'Laptop',
      price: 999
    }
    
    // 组合使用重命名和默认值
    const { name: productName, category = 'Electronics', stock: availableStock = 0 } = product
  6. 剩余参数

    js
    const student = {
      name: 'Bob',
      age: 20,
      major: 'Computer Science',
      gpa: 3.8
    }
    
    // 获取剩余属性
    const { name, ...academicInfo } = student
    
    console.log(name) // 'Bob'
    console.log(academicInfo) // { age: 20, major: 'Computer Science', gpa: 3.8 }
  7. 动态属性名解构

    js
    const config = {
      apiUrl: 'https://api.example.com',
      timeout: 5000
    }
    
    const property = 'apiUrl'
    const { [property]: apiEndpoint } = config
    
    console.log(apiEndpoint) // 'https://api.example.com'
  8. 不修改原始对象

    js
    const original = { a: 1, b: 2 }
    
    // 解构不会修改原始对象
    const { a } = original
    console.log(original) // { a: 1, b: 2 } (未改变)

应用场景

  1. 获取坐标

    js
    function getPosition({ x, y }) {
      console.log(x, y)
    }
    
    getPosition({ x: 10, y: 20 })

比较运算符

比较运算符:用于比较两个值,返回布尔值(truefalse)表示比较结果。

分类

  • 相等性比较运算符
  • 关系比较运算符
  • 特殊比较运算符

相等性比较

运算符名称示例结果说明
==宽松相等'5' == 5true执行类型转换后比较值
===严格相等'5' === 5false不执行类型转换,要求类型和值都相同
!=宽松不等'5' != 5false执行类型转换,检查不相等
!==严格不等'5' !== 5true不执行类型转换,检查不相等

特殊值 NaN:唯一不等于自身的值

js
// 唯一不等于自身的值
NaN == NaN // false
NaN === NaN // false

问题:任何值和 NaN 比较,永远为false

js
// 错误
value === NaN // 永远false

解决:使用 Number.isNaN()Object.is()进行比较

js
// 正确
Number.isNaN(value)
Object.is(value, NaN)

对比 === 和 ==

问题==存在一个问题,它不能区分出 0 和 false,或者空字符串和 false,因为它会进行隐式类型转换,false 或空字符串都会被转换为数字 0。

  • 类型转换===:不进行类型转换;==:隐式进行类型转换。
  • 比较方式===:比较值和类型;==:比较转换后的值。
  • 性能===:更快;==:稍慢。
  • 可预测性===:高;==:低。
  • 推荐使用===

关系比较

运算符名称示例结果
>大于10 > 2true
<小于10 < 2false
>=大于等于5 >= 5true
<=小于等于5 >= 5true

比较规则

  • 数字:直接比较数值

    js
    10 > 5 // true
    3 < 2 // false
  • 字符串:字典序比较

    js
    'b' > 'a' // true
    '2' > '10' // true ('2'字符码50 > '1'字符码49)
  • 混合类型:转换为数字比较

    js
    '10' > 5 // true (字符串'10'转数字10)
    true > 0.5 // true (true转1)

特殊比较

运算符名称示例结果
in检查对象是否包含指定属性'name' in {name: 'tom'}true
instanceof检查对象是否为特定构造函数的实例[] instanceof Arraytrue

逻辑运算符

逻辑运算符:用于处理布尔逻辑。它们不仅能操作布尔值,还能处理各种数据类型,并利用短路求值特性实现高效的条件判断。

运算符名称语法规则
&&逻辑与expr1 && expr2如果 expr1 可转换为 false,返回 expr1;否则返回 expr2
||逻辑或expr1 || expr2如果 expr1 可转换为 true,返回 expr1;否则返回 expr2
!逻辑非!expr将操作数转换为布尔值后取反
??(ES2020)逻辑空expr1 ?? expr2expr1nullundefined 时返回 expr2;否则返回 expr1

逻辑与 &&

逻辑与(Logical AND)&&,用于检查两个或多个表达式是否同时为真

  • 如果所有操作数都为真值,返回最后一个真值
  • 如果任一操作数为假值,返回第一个假值

语法

js
result = expression1 && expression2

示例

  1. 真值/假值处理

    js
    console.log(5 && 3) // 3 (两个真值)
    console.log(0 && 2) // 0 (第一个假值)
    console.log('' && 'test') // "" (第一个假值)
    console.log(null && 10) // null (第一个假值)

短路求值(Short-Circuiting):逻辑与运算符具有短路特性:

  • 如果第一个操作数为假值,不计算第二个操作数
  • 只有第一个操作数为真值时,才计算第二个操作数。
js
function logMessage() {
  console.log('函数被执行')
  return true
}

false && logMessage() // 无输出(函数未执行)
true && logMessage() // "函数被执行"

运算符优先级&& 优先级高于 ||

js
// && 优先级高于 ||
const result = true || (false && false) // true (相当于 true || (false && false))

// 使用括号明确意图
const safeResult = (true || false) && false // false

逻辑或 ||

逻辑或(Logical OR)||,用于检查两个或多个表达式中是否至少有一个为真

  • 如果任一操作数为真值,返回第一个真值
  • 如果所有操作数都为假值,返回最后一个假值

语法

js
result = expression1 || expression2

示例

  1. 真值/假值处理

    js
    console.log(5 || 3) // 5 (第一个真值)
    console.log(0 || 2) // 2 (第二个真值)
    console.log('' || 'test') // "test" (第二个真值)
    console.log(null || 10) // 10 (第二个真值)
    console.log(undefined || null || 0) // 0 (所有操作数都是假值,返回最后一个)
  2. 设置默认值(最常见用法)

    js
    // 配置默认端口
    const port = process.env.PORT || 8080
    
    // 默认用户设置
    const userSettings = user.customSettings || defaultSettings
    
    // 默认消息
    const message = inputMessage || '没有可用消息'

短路求值(Short-Circuiting):逻辑或运算符具有短路特性:

  • 如果第一个操作数为真值,不计算第二个操作数
  • 只有第一个操作数为假值时,才计算第二个操作数。
js
function logMessage() {
  console.log('函数被执行')
  return true
}

true || logMessage() // 无输出(函数未执行)
false || logMessage() // "函数被执行"

避免假值被覆盖

  • 问题0"" 是有效值但被覆盖

    js
    // 问题:0 是有效值但被忽略
    const quantity = 0
    const display = quantity || '未知' // "未知"
  • 解决方案

    1. 方案一:使用空值合并运算符 (??)

      js
      // 解决方案:使用空值合并运算符 (??)
      const correctDisplay = quantity ?? '未知' // 0
    2. 方案二:明确检查

      js
      // 解决方案:明确检查
      const finalMessage = message === '' ? '' : message || '默认消息'

逻辑非 !

逻辑非(Logical NOT)!,对其操作数的布尔值进行取反

  • 如果操作数为 真值(truthy),返回 false
  • 如果操作数为 假值(falsy),返回 true

语法

js
const result = !expression

示例

  1. 真值/假值转换

    js
    console.log(!0) // true (0 是假值)
    console.log(!1) // false (1 是真值)
    console.log(!'') // true (空字符串是假值)
    console.log(!'hello') // false (非空字符串是真值)
    console.log(!null) // true
    console.log(!undefined) // true
    console.log(![]) // false (数组是真值)
    console.log(!{}) // false (对象是真值)

双重非!!,快速将任何值转换为布尔类型。

js
console.log(!!0) // false
console.log(!!1) // true
console.log(!!'') // false
console.log(!!'hello') // true
console.log(!!null) // false
console.log(!!undefined) // false
console.log(!![]) // true
console.log(!!{}) // true

性能考量:逻辑非是 JavaScript 中最快的操作之一,几乎不影响性能

js
const iterations = 10000000

console.time('Logical NOT')
for (let i = 0; i < iterations; i++) {
  const result = !i
}
console.timeEnd('Logical NOT')

// 典型结果:10-50ms(取决于运行环境)

逻辑空 ??

逻辑空(Nullish Coalescing Operator,空值合并运算符)??ES2020,是一个逻辑运算符:

  • 当左侧操作数为 nullundefined 时,返回右侧操作数。
  • 当左侧操作数不是 nullundefined 时,返回左侧操作数。

语法

js
const result = valueToCheck ?? defaultValue

示例

  1. 安全的默认值设置

    js
    // 用户配置处理
    function loadUserSettings(settings) {
      const theme = settings.theme ?? 'light'
      const fontSize = settings.fontSize ?? 16
      const notifications = settings.notifications ?? true
    
      console.log(`主题: ${theme}, 字体大小: ${fontSize}, 通知: ${notifications}`)
    }
    
    loadUserSettings({ theme: 'dark' })
    // 主题: dark, 字体大小: 16, 通知: true
  2. 结合可选链 ?.

    js
    const user = {
      profile: {
        name: 'Alice',
        address: null
      }
    }
    
    // 安全访问嵌套属性
    const street = user?.profile?.address?.street ?? '街道未知'
    const city = user?.profile?.address?.city ?? '城市未知'
    
    console.log(street) // "街道未知"
    console.log(city) // "城市未知"

短路求值(Short-Circuiting):与逻辑运算符类似,?? 具有短路行为:

  • 如果左侧不是 null/undefined不计算右侧表达式
  • 只有左侧是 null/undefined 时,才计算右侧。
js
function logMessage() {
  console.log('函数被执行')
  return '默认值'
}

const value1 = '有效值' ?? logMessage() // 无输出(函数未执行)
const value2 = null ?? logMessage() // "函数被执行"

对比逻辑或||

  • 逻辑空:只判断左侧是否为null/undefined
  • 逻辑或:会判断左侧是否为null/undefined以及""0falseNaN等假值。
js
const nullValue = null
const undefinedValue = undefined
const zero = 0
const emptyString = ''
const falseValue = false

console.log(nullValue ?? '默认值') // "默认值"
console.log(undefinedValue ?? '默认值') // "默认值"

console.log(zero ?? 42) // 0
console.log(emptyString ?? '未知') // ""
console.log(falseValue ?? true) // false

运算符优先级&& > || > ??,不能直接与 &&|| 混用。

js
// 问题:?? 优先级低于 && 和 ||
const value = null || undefined ?? "默认"; // SyntaxError

// 解决方案:使用括号
const value = (null || undefined) ?? "默认"; // "默认"

分支语句

概述

分支结构

分支结构:是编程中的基本控制结构,允许程序根据特定条件判断选择不同的执行路径。几乎所有的编程语言都有分支结构。

程序是生活的一种抽象, 只是我们用代码表示了出来

  • 在开发中, 我们经常需要根据一定的条件, 来决定代码的执行方向
  • 如果 条件满足,才能做某件事情
  • 如果 条件不满足,就做另外一件事情

JS 中常见的分支结构

  • if 分支结构
  • switch 分支结构

程序执行顺序

程序执行顺序:在程序开发中,程序有三种不同的执行顺序

  • 顺序:从上向下,顺序执行代码。
  • 分支:根据条件判断,决定执行代码的分支
  • 循环:让 特定代码 重复 执行

image-20250619075520155

代码块

代码块(Code Block):是 JavaScript 中由花括号 {} 包围的一组语句,它定义了一个独立的执行单元作用域边界

语法

js
{
  // 语句1
  // 语句2
  // ...
}

作用域管理

  • 块级作用域:使用 letconst 声明的变量仅在代码块内部可见。
  • 词法环境:每个代码块创建新的词法环境

语句分组:在开发中,一行代码很难完成某一个特定的功能,我们就会将这些代码放到一个代码块中。

image-20250624154443061

应用场景

  • 控制流语句(if/for/while):在 JS 中,我们可以通过流程控制语句来决定如何执行一个代码块:
    • 通常会通过一些关键字来告知 JS 引擎代码要如何被执行;
    • 比如分支语句、循环语句对应的关键字等;
  • 函数定义
  • 独立作用域
  • 模块模式(立即执行函数)

生活中的条件判断

现实生活中有很多情况, 我们要根据条件来做一些决定:

  • 小明妈妈说: 如果小明考试了 100 分, 就去游乐场(判断分数等于 100 分)
  • 网吧禁止未成年人入内(判断年龄大于等于 18 岁,是否带身份证,是否带钱)
  • 开发中,登录成功:账号和密码正确 或 扫描二维码成功

image-20250519103157638

if 语句

if 分支结构有三种:

  • if

  • if...else

  • if...else if...else

if

if:是 JavaScript 中最基础且最重要的条件控制语句,用于根据特定条件的真假执行不同的代码路径

语法

js
if (condition) {
  // 条件为真时执行的代码
}

核心特性

  1. 省略{}:如果代码块中只有一行代码,那么{}可以省略。
  2. 条件表达式:必须返回布尔值或可转换为布尔值的值。
  3. JS 中的假值false, 0, "", null, undefined, NaN。其他值都为真值。

image-20250519103224196

案例一:如果小明考试超过 90 分, 就去游乐场

  • “如果”相当于 JavaScript 中的关键字 if
  • 分数超过 90 分是一个条件(可以使用 > 符号)

案例二:单位 5 元/斤的苹果,如果购买超过 5 斤,那么立减 8 元

  • 注意:这里我们让用户输入购买的重量,计算出最后的价格并且弹出结果

if...else

if...elseelse 可选,如果条件为假时,执行 else 分支中的代码。

语法

js
if (condition) {
  // 条件为真时执行
} else {
  // 条件为假时执行
}

image-20250519103248736

案例一:如果分数超过 90 分去游乐场,否则去上补习班

  • 满足条件时,去游乐场。
  • 不满足(else),去上补习班。

案例二:m=20,n=30,比较两个数字的大小,获取较大的那个数字

js
if (m > n) {
  return m
} else {
  return n
}

if...else if...else

if...else if...elseelse if 可多个,如果需要判断多个条件,可以使用 else if。

语法

js
if (condition1) {
  // 条件1为真时执行
} else if (condition2) {
  // 条件2为真时执行
} else {
  // 所有条件为假时执行
}

image-20250519103311384

案例: 分数评级

  • 考试分数大于 90:优秀
  • 大于 80 小于等于 90:良好
  • 大于 60 小于等于 80:合格
  • 小于 60 分:不及格
js
if (score > 90) console.log('优秀')
else if (score > 80) console.log('良好')
else if (score > 60) console.log('及格')
else console.log('不及格')

三元运算符

三元运算符:是 JS 中一种简洁的条件表达式,它提供了一种在单行中实现简单 if-else 逻辑的方法。作为 JS 中唯一需要三个操作数的运算符,它因其结构而被称为"三元运算符"。

语法

js
条件 ? 表达式1 : 表达式2
  • 条件:计算结果为布尔值的表达式。
  • 表达式 1:当条件为 true 时执行的表达式。
  • 表达式 2:当条件为 false 时执行的表达式。

工作原理:三元运算符按以下步骤执行:

  1. 计算条件表达式。
  2. 如果条件为 true,计算并返回表达式 1的值。
  3. 如果条件为 false,计算并返回表达式 2的值。

示例:

  1. 变量赋值

    js
    const isRaining = true
    const activity = isRaining ? '室内看电影' : '公园散步'
    console.log(activity) // "室内看电影"
  2. 嵌套三元运算符:可读性差,不推荐

    js
    const score = 85
    const grade = score >= 90 ? 'A' : score >= 80 ? 'B' : score >= 70 ? 'C' : score >= 60 ? 'D' : 'F'
    
    console.log(grade) // "B"

对比 if...else:相同功能的不同实现,三元运算符在简单条件时更加简洁

js
// 使用 if-else
let message
if (user.isLoggedIn) {
  message = '欢迎回来!'
} else {
  message = '请登录'
}

// 使用三元运算符
const message = user.isLoggedIn ? '欢迎回来!' : '请登录'

避免在表达式中修改状态

js
// 不推荐:包含副作用
let counter = 0
const value = condition ? counter++ : counter--

// 推荐:避免在表达式中修改状态
let counter = 0
if (condition) {
  counter++
} else {
  counter--
}

switch 语句

基本语法

switch:是 JS 中的多分支选择结构,用于根据表达式的不同值执行不同 case 分支的代码块

语法

js
switch (expression) {
  case value1:
    // expression 等于 value1 时执行
    break

  case value2:
    // expression 等于 value2 时执行
    break

  // 更多 case...

  default:
  // 没有匹配的 case 时执行
}
  • switch 关键字:声明 switch 语句的开始,后跟括号 () 包含要计算的表达式。
  • case 子句:指定要匹配的值,使用冒号 : 结束,值可以是常量、变量或表达式。
  • default 子句:可选的默认分支,当没有 case 匹配时执行,通常放在最后(但不是必须)。
  • break 语句:终止当前 case 的执行,防止"贯穿"(fall-through)到下一个 case。

严格比较( ===:与 if 语句不同,switch 语句只能做值的严格比较判断。

js
const num = '5'

switch (num) {
  case 5: // '5' === 5
    console.log('数字5') // 不会执行(类型不同)
    break
  case '5': // '5' === 5
    console.log('字符串"5"') // 执行
    break
}

贯穿行为

贯穿行为(Fall-through):贯穿行为发生在:

  • 当某个 case 分支执行完毕后,
  • 没有遇到中断语句(如 breakreturnthrow),
  • 程序继续执行下一个 case 分支的代码,
  • 无论下一个 case 的条件是否匹配。
js
const day = 1

switch (day) {
  case 1:
    console.log('星期一')
  // 这里没有 break!
  case 2:
    console.log('星期二')
    break
  case 3:
    console.log('星期三')
    break
}

// 输出:
// 星期一
// 星期二

阻止贯穿行为:以下语句可以阻止贯穿行为:

  • break:立即退出 switch 语句。
  • return:退出当前函数。
  • throw:抛出异常。
  • continue:在循环中跳过当前迭代(在循环内的 switch 中有效)。

最佳实践

  1. 明确注释贯穿意图
  2. 使用代码风格规范:使用空行和注释区分

如何避免意外贯穿

  1. 使用代码检查工具:配置 ESLint 规则:

    js
    {
      "rules": {
        "no-fallthrough": "error"
      }
    }
  2. 防御性编程:在 default 分支抛出错误

    js
    function handleStatus(status) {
      switch (status) {
        case 'PENDING':
          return processPending()
        case 'ACTIVE':
          return processActive()
        case 'COMPLETED':
          return processCompleted()
        default:
          throw new Error(`未知状态: ${status}`)
      }
    }
  3. 代码审查:在团队协作中:

    • 特别注意没有 breakcase
    • 审查贯穿逻辑是否合理
    • 要求对贯穿行为添加明确注释

循环语句

认识循环

在开发中我们经常需要做各种各样的循环操作:

  • 比如把一个列表中的商品、歌曲、视频依次输出进行展示;
  • 比如对一个列表进行累加计算;
  • 比如运行相同的代码将数字 1 到 10 逐个输出;

循环(Loop):是编程中的基础控制结构,用于重复执行代码块,直到满足特定条件为止。

如果是对某一个列表进行循环操作,我们通常也会称之为 遍历(traversal)或者迭代(iteration);

循环分类:在 JavaScript 中支持三种循环方式:

  • while
  • do...while
  • for
  • for...in
  • for...of

while

while 循环:用于在指定条件为真时重复执行代码块。它提供了一种在未知迭代次数的情况下执行重复操作的方法。

语法

js
while (condition) {
  // 循环体 - 条件为真时重复执行的代码
}
  • while 关键字:声明循环开始。
  • 条件表达式:每次迭代前评估,返回布尔值。
  • 循环体:条件为真时执行的代码块。
  • 迭代器:通常在循环体内修改条件变量。

执行流程

  1. 条件评估:检查 condition 是否为真。
  2. 执行循环体:如果条件为真,执行循环体内代码。
  3. 重复评估:执行完循环体后返回步骤 1。
  4. 退出循环:当条件为假时,跳过循环体继续执行后续代码。

image-20250519103511486

示例

  1. 简单计数器

    js
    let count = 1
    
    while (count <= 5) {
      console.log(`计数: ${count}`)
      count++ // 关键:修改循环变量
    }
    
    // 输出:
    // 计数: 1
    // 计数: 2
    // 计数: 3
    // 计数: 4
    // 计数: 5

对比 for:选择指南:

  • 使用 while:当迭代次数未知(如等待用户输入、处理动态数据)
  • 使用 for:当迭代次数已知(如遍历数组、固定次数操作)

问题:死循环

  • 问题:如果缺少迭代器更新,会造成条件一直成立(为 true),那么会产生死循环。

    js
    // 危险:缺少迭代器更新
    let i = 0
    while (i < 5) {
      console.log(i)
      // 忘记 i++ → 无限循环!
    }
    
    // 解决方案:确保条件变量被修改
  • 解决方案

    1. 关闭页面停止死循环。
    2. 确保条件变量被修改。

问题:作用域问题

  • 问题:在循环外访问内部变量,会报ReferenceError

    js
    // 错误:在循环外访问内部变量
    while (condition) {
      let temp = computeValue()
    }
    console.log(temp) // ReferenceError
  • 解决方案:在外部声明变量

    js
    // 正确:在外部声明变量
    let result
    while (condition) {
      result = computeValue()
    }
    console.log(result)

do...while

do...while 循环:是 JS 中的一种后测试循环结构,它首先执行循环体中的代码,然后再检查循环条件。与 while 循环不同,do...while 循环保证循环体至少执行一次,无论初始条件是否满足。

语法

js
do {
  // 循环体 - 至少执行一次
} while (condition)
  • do 关键字:标记循环体开始。
  • 循环体:包含要重复执行的代码。
  • while 关键字:声明条件检查。
  • 条件表达式:决定是否继续循环。
  • 分号while(condition) 后必须加分号。

执行流程

  1. 首次执行:无条件执行循环体。
  2. 条件评估:执行完循环体后检查条件。
  3. 重复执行:如果条件为真,返回步骤 1。
  4. 退出循环:如果条件为假,结束循环。

image-20250519103540146

示例

  1. 简单计数器

    js
    let count = 1
    
    do {
      console.log(`计数: ${count}`)
      count++
    } while (count <= 5)
    
    // 输出:
    // 计数: 1
    // 计数: 2
    // 计数: 3
    // 计数: 4
    // 计数: 5
  2. 游戏菜单系统

    js
    let choice
    
    do {
      console.log('1. 开始游戏')
      console.log('2. 加载存档')
      console.log('3. 设置')
      console.log('4. 退出')
    
      choice = prompt('请选择:')
    
      switch (choice) {
        case '1':
          startGame()
          break
        case '2':
          loadGame()
          break
        case '3':
          openSettings()
          break
        case '4':
          console.log('再见!')
          break
        default:
          console.log('无效选择!')
      }
    } while (choice !== '4')

对比 while推荐使用 while

特性do...while 循环while 循环
执行顺序先执行后检查先检查后执行
最少执行次数至少 1 次可能 0 次
适用场景需要至少执行一次的操作条件可能初始为假的情况
语法需要结尾分号不需要分号
可读性条件在循环后,有时不够直观条件在开头,结构清晰

问题:缺少分号

  • 问题:在 do...while 后遗漏分号

    js
    // 错误:在do...while后遗漏分号
    do {
      // ...
    } while (condition) // 缺少分号 → 语法错误
  • 解决方案:始终添加分号

    js
    do {
      // ...
    } while (condition)

问题:死循环类似 while,如果缺少迭代器更新,会造成条件一直成立(为 true),那么会产生死循环。

问题:作用域问题类似 while

for

for 循环:是 JS 中最常用的迭代控制结构,用于在已知迭代次数需要遍历集合时重复执行代码块。它提供了一种简洁的方式将初始化条件检查迭代更新组合在单行语句中。

语法

js
for (initialization; condition; finalExpression) {
  // 循环体 - 重复执行的代码
}
  • 初始化 (initialization):循环开始前执行一次,通常用于声明计数器。
  • 条件 (condition):每次迭代前检查,为真则执行循环体。
  • 迭代更新 (finalExpression):每次迭代后执行,通常更新计数器。
  • 循环体:重复执行的代码块。

执行流程

  1. 初始化:执行初始化表达式(仅一次)。
  2. 条件检查:评估条件表达式。
    • 如果 true → 执行循环体。
    • 如果 false → 退出循环。
  3. 执行循环体:运行循环体内的代码。
  4. 迭代更新:执行迭代表达式。
  5. 重复:返回步骤 2。

示例

  1. 标准计数器(已知迭代次数)

    js
    for (let i = 0; i < 5; i++) {
      console.log(`迭代次数: ${i}`)
    }
    
    // 输出:
    // 迭代次数: 0
    // 迭代次数: 1
    // 迭代次数: 2
    // 迭代次数: 3
    // 迭代次数: 4
  2. 遍历数组(遍历集合)

    js
    const fruits = ['苹果', '香蕉', '橙子']
    
    for (let i = 0; i < fruits.length; i++) {
      console.log(`水果 ${i + 1}: ${fruits[i]}`)
    }
    
    // 输出:
    // 水果 1: 苹果
    // 水果 2: 香蕉
    // 水果 3: 橙子

递减循环

js
for (let count = 10; count > 0; count--) {
  console.log(count)
}
console.log('发射!')

// 输出: 10,9,8,7,6,5,4,3,2,1, 发射!

带标签的 for 循环:用于嵌套循环中精确控制流程

js
// 标签
outerLoop: for (let i = 0; i < 3; i++) {
  innerLoop: for (let j = 0; j < 3; j++) {
    if (i === 1 && j === 1) {
      break outerLoop // 跳出外层循环
    }
    console.log(`i=${i}, j=${j}`)
  }
}

// 输出:
// i=0, j=0
// i=0, j=1
// i=0, j=2
// i=1, j=0

性能优化

  1. 缓存长度:保存集合长度到变量

    js
    // 缓存长度
    for (let i = 0, len = items.length; i < len; i++) {
      // 处理items[i]
    }
  2. 减少 DOM 访问:保存获取的 DOM 元素到变量

    js
    // 1. 减少DOM访问
    const container = document.getElementById('container')
    const items = container.children
  3. 倒序循环避免比较

    js
    // 2. 倒序循环避免比较
    for (let i = items.length - 1; i >= 0; i--) {
      // 处理items[i]
    }
  4. 循环展开

    js
    // 3. 循环展开
    for (let i = 0; i < 100; i += 4) {
      process(i)
      process(i + 1)
      process(i + 2)
      process(i + 3)
    }

问题:死循环类似 while

问题:作用域问题类似 while

循环嵌套

循环嵌套(Nested Loops):是指:

  • 一个循环(外层循环)内部包含另一个循环(内层循环),
  • 内层循环在外层循环的每次迭代中都会完整执行
  • 嵌套层数理论上无限制,但通常不超过 3 层。

语法

  1. 双重嵌套(最常见)

    js
    for (let i = 0; i < 3; i++) {
      // 外层循环
      console.log(`外层循环 i=${i}`)
    
      for (let j = 0; j < 2; j++) {
        // 内层循环
        console.log(`  内层循环 j=${j}`)
      }
    }
  2. 混合类型嵌套

    js
    let row = 1
    while (row <= 3) {
      // while 外层循环
      console.log(`第${row}行:`)
    
      for (let col = 1; col <= 2; col++) {
        // for 内层循环
        console.log(`  列${col}`)
      }
    
      row++
    }

时间复杂度:嵌套层数增加会指数级增长执行时间。

  • 单层循环:O(n)
  • 双层嵌套:O(n × m)
  • 三层嵌套:O(n × m × p)

示例

  1. 遍历二维数组

    js
    const matrix = [
      [1, 2, 3],
      [4, 5, 6],
      [7, 8, 9]
    ]
    
    // 遍历二维数组
    for (let i = 0; i < matrix.length; i++) {
      for (let j = 0; j < matrix[i].length; j++) {
        console.log(`matrix[${i}][${j}] = ${matrix[i][j]}`)
      }
    }

问题:性能问题

  • 问题:嵌套循环导致指数级时间复杂度增长
  • 解决方案
    • 优化算法复杂度(如使用哈希表替代嵌套搜索)
    • 减少循环层数
    • 使用并行处理(Web Workers)

循环控制

循环的跳转(控制):

  • 在执行循环过程中, 遇到某一个条件时, 我们可能想要做一些事情;
  • 比如循环体不再执行(即使没有执行完), 跳出循环;
  • 比如本次循环体不再执行, 执行下一次的循环体;

循环的跳转控制

break: 直接跳出循环, 循环结束

  • break 某一条件满足时,退出循环,不再执行后续重复的代码

continue: 跳过本次循环次, 执行下一次循环体

  • continue 指令是 break 的“轻量版”。
  • continue 某一条件满足时,不执行后续重复的代码

练习:猜数字游戏:游戏规则:

  • 电脑随机生成一个 0~99 之间的数字;
  • 玩家有 7 次猜测的机会;
  • 玩家猜测一个数字, 输入到程序中;
  • 电脑根据输入的数字打印: 猜大了/猜小了/猜对了的情况;
  • 猜对了, 那么游戏结束,显示恭喜您;
  • 7 次机会用完打印: 您的次数用完了, 您失败了;