JavaScript基础语法(一)

46 阅读49分钟

1. JavaScript基础

1.1. JavaScript语言的介绍

JavaScript 入门易学性:

  • JavaScript 对初学者比较友好。可以使用任何文本编辑工具编写,只需要浏览器就可以执行程序
  • JavaScript 是有界面效果的(相比之下,C语言只有白底黑字)
  • JavaScript 的入门较简单(进阶不易)。比如,JS 是弱变量类型的语言,变量只需要用 var/let/const 来声明。而 Java 中变量的声明,要根据变量的类型来定义

Java 中需要这样定义变量:

int a;
float a;
double  a;
String a;
boolean;

而 JS 中,只需要用一种方式来定义:

// ES5 写法
var a

// ES6 写法
const b
let c

1.2. JavaScript 的组成

JavaScript 基础分为三个部分:

  • ECMAScript:JavaScript 的语法标准。包括变量、表达式、运算符、函数、if语句等
  • DOM:Document Object Model(文档对象模型),JS 操作页面上的元素(标签)的 API。比如让盒子移动、变色、改变大小、轮播图等等
  • BOM:Browser Object Model(浏览器对象模型),JS 操作浏览器部分功能的 API。通过 BOM 可以操作浏览器窗口,比如弹框、控制浏览器跳转、获取浏览器分辨率等等

通俗理解就是:ECMAScript 是 JS 的语法;DOM 和 BOM 是浏览器运行环境为 JS 提供的 API

1.3. 书写位置

JS 代码的书写位置有三种方式:(和 CSS 的引入方式类似)

  • 行内式:写在 HTML 标签内部
  • 内嵌式(内联式):写在 script 标签中
  • 外联式:引入外部 JS 文件

1.3.1. 行内式

<input type="button" value="点击我" onclick="alert('JavaScript行内式')">

解释:

  • 可以将单行或少量 JS 代码写在 HTML 标签的事件属性中(以 on 开头的属性),比如放在上面的 onlick 点击事件中
  • 这种书写方式,不推荐使用,原意是:可读性差,尤其是需要编写大量 JS 代码时,很难维护;引号多层嵌套时,也很容易出错
  • 关于代码中的引号,在 HTML 标签中,我们推荐使用双引号,JS 中推荐使用单引号

1.3.2. 内嵌式(内联式)

我们可以在 HTML 页面的 <body> 标签里放入 <script type="text/javascript"> 标签对,并在 <script> 里书写 JavaScript 代码

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script type="text/javascript">
      // 在这里写 js 代码
      alert('千古壹号 hello 方式2');
      console.log('qianguyihao hello 方式2');
    </script>
  </body>
</html>

解释:

  • type 属性中的 text 表示纯文本,因为 JavaScript 代码本身就是纯文本。当然,type 属性可以省略,因为 JavaScript 是所有现代浏览器以及 HTML5 中的默认脚本语言
  • 可以将多行 JS 代码写到 <script> 标签中
  • 内嵌式 JS 是学习时常用的书写方式

1.3.3. 外联式

引入独立的 JS 文件

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <!-- 外链式:引入外部的 js 文件:这个 utils.js 文件与当前的 html 文件,处于同一级目录 -->
    <script src="utils.js"></script>
  </body>
</html>

解释:

  • 上面这段代码,依然是放到 body 标签里,可以和内嵌的 JS 代码并列
  • 上方代码的 script 标签已经引入了外部 JS 文件,所以这个标签里面,不可以再写 JS 代码
  • 内嵌式 和 外联式 不能混用

总结:我们在实战开发中,基本都是采用外联式,因为将 HTML 文件和 JS 文件分开的方式,有利于解码的结构化和复用,复合高内聚、低耦合的思想。很少会有人把一大堆 JS 代码塞到单个 HTML 文件里

1.4. 注释

单行注释:

// 我是注释

多行注释:

/*
 多行注释1
 多行注释2
*/

// 或者

/**
 * 多行注释1
 * 多行注释2
 */

文档注释:

  • JSDoc 是 JavaScript 中用于文档注释的一种标准化格式。写完 JSDoc 的注释之后,再通过相关工具,开发者便可以为函数、方式、类和变量等添加注释,以便生成可读性强的 API 文档
/**
 * 这是一个示例函数,用于求两个数字的和。
 * @param {number} num1 - 第一个数字
 * @param {number} num2 - 第二个数字
 * @returns {number} - 两个数字的和
 */
function sum(num1, num2) {
  return num1 + num2;
}

补充:VS Code 中,单行注释的快捷键是 Ctrl + /,多行注释的默认快捷键是 Alt + Shift + A

当然,如果你觉得多行注释的默认快捷键不方便,还可以修改默认快捷键。操作如下:

  • VS Code ---> 首选项 ---> 键盘快捷方式 ---> 查找 "注释" 这两个字 ---> 将原来的快捷键修改为 其他的快捷键

1.5. JavaScript 输出语句

在下面的这个几个输出语句中,按照使用频率来排序的话,console.log() 用得最多,其次是 alert() 语句;其他语句用得较少,了解即可

1.5.1. alert()

alert:弹出警告框,它会在弹窗中显示一条信息,并等待用户按下 "OK"

  • 该方法只能传入一个参数
alert('弹出警告框')

上面的代码中,如果写了两个 alert()语句,则网页的效果是:弹出第一个警告框,点击确定后,继续弹出第二个警告框

1.5.2. console.log()

用于在浏览器的控制台中输出信息,里面可以传入一个或多个参数,常用于代码调试和测试,括号里面可以写字符串、数字、变量、表达式等

console.log('传入一个参数:' + arg1)
// 传入多个参数时,可以用逗号隔开
console.log('传入多个参数:', arg2, arg3)

控制台是程序员调试程序的地方。我们可以使用 console 语句打印日志,在控制台的 console 面板查看打印的内容,测试程序是否运行正常

console 语句可以设置不同的打印等级:

console.log('千古壹号1'); // 普通打印
console.warn('千古壹号2'); // 警告打印
console.error('千古壹号3'); // 错误打印

1.5.3. prompt()

就是专门弹出能够让用户输入内容的对话框。用得少,测试的时候偶然会用

var a = prompt('请随便输入点东西把')
console.log(a)

alert()prompt() 的区别:

  • alert 语句中可以输出数字和字符串,如果要输出字符串,则必须用引号括起来;prompt 语句中,用户不管输入什么内容,都是字符串
  • prompt 会返回用户输入的内容。我们可以用一个变量,来接收用户输入的内容

1.5.4. document.write()

该方法的作用是向网页中写入文本内容,可以是 HTML 代码。传入的参数可以是一个或者多个字符串

document.write('测试')

1.5.5. confirm()

页面上显示一个弹窗。弹窗上有 "确认" 和 "取消" 两个按钮,点击 "确认" 返回 true,点击 "取消" 返回 false

var result = confirm('你听说过JavaScript吗? ')
cconsole.log(result)

1.6. 变量和常量

1.6.1. 常量

常量也称之为 "字面量",是固定值,不可改变。看见什么,它就是什么

常量有下面这几种:

  • 数字常量(数值常量)
  • 字符串常量
  • 布尔常量
  • 自定义常量
1.6.1.1. 数字常量

数字常量非常简单,直接写数组就行,不需要任何其他的符号。既可以是整数,也可以是浮点数

// 不需要加引号
alert(9996)
alert(3.14)
1.6.1.2. 字符串常量

字符串常量就是用单引号或双引号括起来的内容。可以是单词、句子等,一定要加引号。在 JS 中,只要是单引号或双引号括起来的内容,都是字符串常量

console.log('9996')
console.log('你认识JavaScript吗')
1.6.1.3. 布尔常量

布尔常量就是表达真或者假,在 JS 中用 true 和 false 来表达

if(true) {
  console.log('这是真的')
}
1.6.1.4. 自定义常量:const

自定义常量是 ES6 中新增的语法

// const 常量名称 = 常量取值
const name = '赵云'

name = '李白' // 既然 name 是常量,所以这一行是错误的写法,因为 name 无法被修改为其他值

开发技巧:用变量定义常量

  • 我们一般不会直接使用常量,否则会导致代码冗余、不易维护。如果多个地方要用到同一个常量,那就建议事先定义一个变量,用来保存这个常量;然后在需要的地方去引用这个变量就行了。另外,当我们学习了 ES6 中的 const 之后,还可以使用自定义常量达到目的

1.6.2. 变量的概念

变量表示可以改变的数据,一个变量,就是一个用于存放这个数据的容器。我们通过 变量名 获取数据,甚至修改数据。变量还可以用来保存常量。

本质:变量是程序在内存中申请的一块用来存放数据的空间,比如:超市货架的储物格就是变量,在不同的时间段里,储物格中存储的数据可以不一样

实际开发中,变量使用得非常频繁,因为这些数据并非固定不变。比如:以下使用场景中的信息都可以用变量存储:

  • 商品信息:价格、库存数量、购买的客单价
  • 歌曲信息:时长、播放进度、歌词内容
  • 用户信息:用户名、年龄、性别、地址
  • 时间和日期
  • App系统设置的配置参数,用户的偏好设置:主题、语言等
  • 微博:用户关注的人、粉丝数量、发布的帖子数量、点击数

1.6.3. 变量的定义和赋值

定义变量是在告诉浏览器,我们需要一块内存空间,相当于生成超市里的储物格

给变量赋值就是往相当于往储物格里塞东西,可以今天上午塞的是面包,下午就换成了蛋糕

1.6.3.1. 变量定义/声明

在 ES6 语法之前,统一使用 var 关键字来声明一个变量

var name

在 ES6 语法以及之后的版本里,可以使用 const、let关键字来定义一个变量

// 如果你想定义一个常量,就用 const; 如果你想定义一个变量,就用 let
const name
let age
1.6.3.2. 变量的赋值

使用 = 这个符号即可给变量赋值,将等号右边的值,赋给左边的变量

综合起来:变量的定义、赋值、取值,距离如下:

// 定义:声明一个变量
var num
// 赋值:往变量中存储数据
num = 996
// 取值:从变量中获取存储的数据
console.log(num)

// 或者 定义 + 赋值的合并写法
// 定义了变量并赋值,我们称之为变量的初始化
var a = 100
let b = 'world'
1.6.3.3. 变量初始化

第一次给变量赋值,称之为 "变量的初始化",这个概念非常重要。第二次给这个变量赋值(也就是修改这个变量的值)就不叫初始化了

一个变量如果没有进行初始化(只声明,不赋值),那么这个变量中存储的值是 undefined,这个知识点必须知道

var num
console.log(num) // undefined
1.6.3.4. 修改变量的值

一个变量被重新赋值后,它原有的值就会被覆盖,变量值将以最后一次赋值为准

var a = 100
a = 110

console.log(a) // 110
1.6.3.5. 同时定义多个变量
  1. 同时定义多个变量时,只需要写一个 var,多个变量名之间用英文逗号隔开
var num1, num2

2. 定义多个变量的同时,分别进行初始化

var num1 = 10, num2 = 20

如果多个变量的初始化值都是一样的,则可以这样简写

var num1, num2
num1 = num2 = 10 // 重点在这一行

console.log(num1) // 10
console.log(num2) // 10

// 这种写法又有区别要注意
var num1, num2 = 10
console.log(num1); // undefined
console.log(num2); // 10
1.6.3.6. 变量之间可以相互赋值
var num1, num2
num1 = 10
num2 = num1 // 把 num1 的值拷贝一份,赋值给 num2
console.log(num2) // 10
1.6.3.7. 重复定义变量

在 ES5 中,如果用 var 定义了同名变量,那么,后定义的变量,会覆盖先定义的变量

var name = '赵云'
var name = '黄忠'
console.log(name) // 黄忠
1.6.3.8. 变量声明和赋值的几种情况

变量建议先声明,在使用;否则可能会产生意想不到的结果

写法1:先声明,再赋值(正常)

var a
a = 100
console.log(a) //100

写法2:只声明,不赋值(默认值为 undefined)

var a
console.log(a) // undefined

写法3:不声明,直接赋值(正常)

虽然不报错,但并不推荐这么写。变量 a 会被添加到 windoes 对象上。它跟写法1是有区别的,等以后学习了 变量提升 的概念就明白了。相比之下,更推荐用写法·1

a = 100
console.log(a) //100

写法4:不声明,不赋值,直接使用(会报错)

console.log(a) // 会报错
1.6.3.9. 变量的命令规则

JS 只大小敏感的语言。也就是说 A 和 a 是两个变量,并非同一个变量

var a = 250
var A = 888

必须遵守:

  • 只能由字母(A - Z、a - z)、数字(0~9)、下划线(_)、美元符($)组成
  • 不能以数字开头。变量名中不允许出现空格。尤其注意,变量名中不能出现中划线(-),很多人写了多年代码都不知道这一点
  • 严格区分大小写(JS 是区分大小写的语言)
  • 不能使用 JS 语言中保留的 关键字 和 保留字 作为变量名
  • 变量名长度不能超过 255 个字符
  • 汉语可以作为变量名。但是不建议使用,因为太low

建议遵守:

  • 命名要有可读性,方便顾名思义
  • 建议用驼峰命名法,比如:getElementById、getUserInfo
  • 赋值 = 符号的两边建议都加上空格

1.7. 标识符

在 JS 中所有的可以由我们自主命名的都可以称之为标识符。包括:变量名、函数名、属性名、参数名都是属于标识符

通俗来讲,标识符就是我们写代码时为某些东西起名字。类似于人出生的时候,起个任命

标识符的命名规则和变量的命名规则是一样的,标识符不能使用语言中保留的关键字及保留字

1.8. 关键字

被 JS 赋予了特殊含义的单词。也就是说,关键字是 JS 本身已经使用了的单词,我们不能用他们充当变量名、函数名等标识符。关键字在开发工具中会显示特殊的高亮颜色

JS 中的关键字如下:

ifelseswitchbreakcasedefaultforindowhilevarletconstvoidfunctioncontinuereturntrycatchfinallythrowdebuggerthistypeofinstanceofdeletewithexportnewclassextendssuperwithyieldimportstatictruefalsenullundefinedNaN

1.9. 保留字

实际上就是预留的 "关键字"。他们虽然现在还不是关键字,但是未来可能会称为关键字。同样不能用它们充当变量名、函数名等标识符

JS 中的保留字如下:

enum、await

abstractbooleanbytechardoublefinalfloat、goto、intlongnativeshortsynchronizedtransientvolatile、

arguments eval Infinity

# 以下关键字只在严格模式中被当成保留字,在ES6中是属于关键字
implements、interface、packageprivateprotectedpublic

2. 数据类型

JS 中一共有八种数据类型:

  • 基本数据类型(值类型):String 字符串、Boolean 布尔值、Number 数值、Undefined 未定义、Null 空对象、BigInt 大型数值、Symbol
  • 引用数据类型(引用类型):Object 对象

注意:内置对象 Function、Array、Date、RegEp、Error等都属于 Object 类型。也就是说,除了那七种基本数据类型之外,其他的,都称之为 Object 类型

数据类型之间最大的区别:

  • 基本数据类型:参数赋值的时候,传数值
  • 引用数据类型:参数赋值的时候,传地址

2.1. 栈内存和堆内存

我们首先记住一句话:JS 中,所有的变量都是保存在栈内存中的

基本数据类型:基本数据类型的值,直接保存在栈内存中。值与值之间是独立存在,修改一个变量不会影响其他的变量

引用类型:对象是保存到栈内存中的。没创建一个新的对象,就会在栈内存中开辟出一个新的空间;而变量保存了对象的内存地址(对象的引用),保存在栈内存当中。如果两个变量保存了同一个对象的引用,当一个通过一个变量修改属性时,另一个也会受到影响

2.2. String

2.2.1. 语法

如果在开发中需要使用文本,就可以用 String 字符串类来表示,比如姓名、商品价格等

其语法为:双引号"" 或者单引号 ''。引号中的内容都是文本

var a = 'acsfe'
var b = '12323'
var c = '哈哈哈哈哈'
var d = '' // 空字符串

console.log(typeof a) // string

引号的注意事项 :

  • 单引号和双引号不能混用,会报错
  • 同类引号不能嵌套:双引号里不能再放双引号,单引号里不能再放单引号
  • 单引号里可以嵌套双引号;双引号里可以嵌套单引号

2.2.2. 转义字符

假设现在有这样一个字符串—— 前端开发'就是"牛"

如果你想在 JS 中定义上述字符串的话,直接在代码中这样写是不行的,会报错,那要怎么办,这时候转义字符就派上用场了

在字符串中我们可以使用 作为转义字符。如果你想表示一些特殊符号,可以使用 继续转义:

  • ' 表示单引号
  • " 表示双引号
  • \ 表示 \
  • \r 表示回车
  • \n 表示换行
  • \t 表示缩进,tab 制表符
  • \b 表示空格
var str1 = '我说:"今天\t天气真不错!"'
// 我说:"今天	天气真不错!"

2.2.3. 获取字符串的长度

字符串是由若干个字符组成的,这些字符的数量就是字符串的长度。我们可以通过字符串的 length 属性获取整个字符串的长度

var str1 = '前端开发'
var str2 = 'Hello'

console.log(str1.length) // 4
console.log(str2.length) // 5

由此可见,字符串的 length 属性,在判断字符串的长度时,会认为:

  • 一个中文算一个字符,一个英文算一个字符
  • 一个标点符号(包括中文标点、英文标点)算一个字符
  • 一个空格算一个字符

2.2.4. 字符串拼接

多个字符串之间可以使用加号 + 进行拼接

语法:字符串 + 任意数据类型 = 拼接之后的新字符串

规则:拼接前,会把与字符串相加的这个数据类型转成字符串,然后再拼接成一个新的字符串

var str1 = '前端开发' + '永不止步'
var str2 = '前端开发' + 666

console.log(str1) // 前端开发永不止步
console.log(str2) // 前端开发666

2.2.5. 字符串的不可变性

字符串里面的值不可被改变,虽然看上去可以改变内容,但其实是地址变了,内存中新开辟了一个内存空间

var str = 'hello'
str = 'qianguhiao'

比如上面的代码,当重新给变量 str 赋值时,常量 hello 不会被改变,依然保存再内存中;str 会改为指向 qianguhiao

2.2.6. 模板字符串(模板字面量)

ES6 中引入了模板字符串,让我们上去了字符串拼接的烦恼

// 插入变量
// 注意:模板字符串使用的是反引号 ``
var name = '赵云'
var age = 3000

console.log('我是' + name + ', age:' + age) // 传统写法
console.log(`我是${name}, age: ${age}`) // ES6 写法


// 传入表达式
const a = 5
const b = 10

console.log(`this is ${a + b} and not ${2 * a + b}`)


// 换行
const result = {
    name: 'qianguyihao',
    age: 28,
    sex: '男',
};

// 模板字符串支持换行
const html = `<div>
	<span>${result.name}</span>
	<span>${result.age}</span>
	<span>${result.sex}</span>
</div>`;

console.log(html); // 打印结果也会换行



// 调用函数
function getName() {
    return 'qianguyihao';
}

console.log(`www.${getName()}.com`); // 打印结果:www.qianguyihao.com



// 支持嵌套使用
const nameList = ['千古壹号', '许嵩', '解忧少帅'];

function myTemplate() {
    // join('') 的意思是,把数组里的内容合并成一个字符串
    return `<ul>
	${nameList.map((item) => `<li>${item}</li>`).join('')}
	</ul>`;
}
document.body.innerHTML = myTemplate();

2.3. Boolean

布尔值有两个值:true 和 false。主要用来做逻辑判断:true 表示真,false 表示假。布尔值直接使用就可以了,千万不要加引号

var a = true
console.log(typeof a) // boolean

2.4. Number

在 JS 中所有的数值都是 Number 类型,包括整数和浮点数(小数)

var a = 100 // 定义一个变量 a,并且赋值整数 100
console.log(typeof a) // number

var b = 12.3
console.log(typeof b) // number

补充:在 JS 中,只要是数,就是 number 数值型的。无论整数、浮点数(即小数)、无论大小、无论正负,都是 number 类型的。包括接下来介绍的 Infinity、-Infinity、NaN 等特殊数值也是 number 类型的

2.4.1. 数值范围

ECMAScript 并不能表示世界上所有的数值。

  • 最大正数值:Number.MAX_VALUE,这个值为: 1.7976931348623157e+308
  • 最小正数值:Number.MIN_VALUE,这个值为: 5e-324 ,或者 2的负1074 次方。它是能够在浮点精度范围内表示的最小正数(不是最小负数)。小于这个数的正数会被转成0。

如果使用 Number 表示的变量超过了最大值,则会返回 Infinity。

  • Infinity:无穷大(正无穷)。比如 1/0 的值就是无穷大。
  • -Infinity:无穷小(负无穷)

注意:typeof Infinity的返回结果是 number。

2.4.2. NaN

NaN:是一个特殊的数字,表示 Not a Number,非数值。在进行数值运算时,如果得不到正常结果,就会返回 NaN

console.log('abc' / 18) // NaN

注意事项:

  • typeof NaN 的返回结果是 number
  • Undefined 和任何数值计算的结果都为 NaN。NaN 与任何值都不相等,包括 NaN 本身

2.4.3. 连字符和加号的区别

键盘上的 + 可能是连字符,也可能是数字的加号

console.log("我" + "爱" + "你");	// 连字符,把三个独立的汉字,连接在一起了
console.log("我+爱+你");			// 原样输出
console.log(1+2+3);				// 输出6

总结:如果加号两边都是 number 类型,此时是数字相加。否则,就是连字符(用于连接字符串)

2.4.4. 隐士转换

我们知道,"2" + 1 得到的结果其实是字符串,但是 "2" - 1 得到的结果确实数值1,这是因为计算机自动帮我们进行了 "隐士转换"

也就是说,-、*、/、% 这几个符号会自动进行隐士转换。例如:

var a = '4' + 3 - 6
console.log(37)

虽然程序可以对这个几个符号自动进行 "隐士转换";但作为程序员,我们最好自己完成转换,方便程序的可读性

关于隐士转换的详细知识,可以看后续的内容——和数据类型转换

2.5. Undefined 和 null

Undefined 类型的值只有一个,就是 undefined。比如:var a = nudefined

使用 typeof 检查一个 undefined 值时,会返回 undefined

undefined 的出现有以下几种情况:

2.5.1. 变量已声明未初始化

一个变量如果只声明了,但没有赋值,此时它的值就是 undefined

var name
console.log(name) // undefined
console.log(typeof name) // undefined

// 写法1
var name;
// 写法2。这种写法冗余了,不推荐。
var name = undefined;

注意事项:

  • 不要显示地将变量赋值为 undefined,不太规范。也就是说,上面的写法2是冗余的,增加了不必要的代码量,这种写法也也不太规范
  • 变量在定义时,尽量做一下初始化(赋值操作),而不是只声明一个变量。上的写法1就是属于只声明一个变量,也不太建议这种写法
  • 如果变量刚开始没有值,我们可以将其赋一个默认值(空字符串、false、0、null等值),这有利于代码书写的语义化。推荐的代码举例如下:
var a = ''; // 字符串类型的变量,如果刚开始没有值,则可以初始化为空字符串
var b = false; // 布尔类型的变量,如果刚开始没有值,则可以考虑默认值为 false
var c = 0;  // 字符串类型的变量,如果刚开始没有值,可以考虑默认值为 0
var d = null; // 空对象,可以初始化为 null

2.5.2. 变量未声明(未定义)

如果你从未声明一个变量,就去使用它,则会报错(这个大家都知道),此时,如果用 typeof 检查这个变量时,会返回 undefined

console.log(typeof a); // undefined
console.log(a); // 打印结果:Uncaught ReferenceError: a is not defined

2.5.3. 函数无返回值

如果一个函数没有返回值,那么,这个函数的返回值就是 undefined

或者,也可以这样理解:在定义一个函数时,如果末尾没有 return 语句,那么,其实就是 return undefined

function foo() {}

console.log(foo()) // undefined

2.5.4. 调用函数未传参

调用函数时,如果没有传递实参,那么,对应形参的值就是 undefined

function foo(name) {
  console.log(name)
}

foo() // undefined

实际开发中,如果调用函数时没有传参,我们可以根据需要给形参设置一个默认值:

function foo(name) {
  name = name || 'ceshi'
}

foo() // ceshi

等学习了 ES6 之后,上方代码也可以这样写:

function foo(name = 'ceshi') {}

foo()

2.5.5. null

Null 类型的值只有一个,就是 null。比如:var a = null

Null 专门用来定义一个空对象。例如:let a = null,又例如:Object.create(null)

如果你想定义一个变量用来保存引用类型(也就是对象),但是还不确定放什么内容,这个时候,可以在初始化时将其赋值给 null

从语义上讲,null 表示一个空对象,所以使用 typeof 检查一个 null 值时,会返回 object

var myobj = null
console.log(typeof myobj) // object

2.5.6. 其他区别

undefined 实际上是由 null 衍生出来的,所以 null === undefined 的结果为 true

但是null === undefined的结果是 false。它们虽然相似,但还是有区别的,其中一个区别是,和数字运算时:

  • 10 + null 结果为 10
  • 10 + undefined 结果为 NaN

规则总结:

  • 任何值和 null 运算,null 可看作 0 运算
  • 任何数据类型和 undefined 运算都是 NaN

2.6. 数据类型转换

类型转换分为两种:显式类型转换 和 隐士类型转换

2.6.1. 显式类型转换

显式类型转换:手动将某种数据类型,强制转换为另一种数据类型。也就是说,通过调用特定函数或运算符显式地将一个数据类型转换为另一个数据类型

常见的显式类型转换方法,有这几种:

  • toString()
  • String()
  • Number()
  • parseInt(string)
  • parseFloat(string)
  • Boolean()

2.6.2. 隐士类型转换

隐士类型转换:这是 JS 在运行时会 自动转换 的一种类型转换,不需要明确的代码指示。JS 在某些情况下会隐士地将一个数据类型转换为另一个数据类型,以完成某些操作或比较。

常见的隐士类型转换,包括下面这几种:

  • isNaN() 函数
  • 自增 / 自减运算符:++--
  • 运算符:正号 +a、负号 -a
  • 运算符:加号 +
  • 运算符:-*/%
  • 比较运算符:<><=>===等。比较运算符的运算结果都是布尔值:要么是 true,要么是 false
  • 逻辑运算符:&&||!。非布尔值进行与或运算时,会先将其转换为布尔值,然后再运算符。&&||的运算结果是原值,!的运算结果为布尔值

重点:隐士类型转换,内部调用的都是显示类型转换的方法

2.6.3. 转换为 String

2.6.3.1. toString() 方法
变量.toString()
常量.toString() // 这里的常量,不要直接写数字,但可以是其他常量

// 或者用一个新的变量接收转换结果
var result = 变量.toString()

该方法不会影响到原变量,它会将转换的结果返回。当然我们还可以直接写成 a = a.toString(),这样的话,就是直接修改原变量

当我们对一个字符串字面量调用 toString() 方法时,它实际上是调用了 String 构造函数,并将字符串字面量转换为一个 String 对象,然后调用该对象的toString() 方法。String 对象的 toString() 方法返回调用它的原始字符串值

// 基本数据类型
var a1 = 'qianguyihao'
var a2 = 29
var a3 = true

// 引用类型
var a4 = [1, 2, 3]
var a5 = { name: 'aianguyihao', age: 29 }

// undefined 和 null
var a6 = null
var a7 = undefined

// 打印结果都是字符串
console.log(a1.toString()); // "qianguyihao"
console.log(a2.toString()); // "29"
console.log(a3.toString()); // "true"
console.log(a4.toString()); // "1,2,3"
console.log(a5.toString()); // "object"

// 下面这两个,打印报错
console.log(a6.toString()); // 报错:Uncaught TypeError: Cannot read properties of undefined'
console.log(a7.toString()); // 报错:Uncaught TypeError: Cannot read properties of null

小技巧:在 chrome 浏览器的控制台中,Number 类型、Boolean 类型的打印结果是蓝色的,String 类型的打印结果是黑色的

注意事项:

  1. undefined 和 null 这两个值没有 toString() 方法,所以它们不能用 toString()。如果调用,会报错
  2. 多数情况下,toString() 不接收任何参数;当然也有例外:Number 类型的变量,在调用 toString() 时,可以在方法中传递一个整数作为参数。此时它会把数字转换为指定的进制,如果不指定则默认转换为10进制。例如:
var a = 255
// Number 数值在调用 toString() 时,可以在方法中传递一个整数作为参数
// 此时他将会把数字转换为指定的进制,如果不指定则默认转换为10进制
a = a.toString(2) // 转换为二进制

console.log(a); // "11111111"
console.log(typeof a); // string

3. 纯小数的小数点后面,如果紧跟连续6个或6个以上的 "0" 时,那么,将用 e 来表示这个小数。代码举例:

const num1 = 0.000001; // 小数点后面紧跟五个零
console.log(num1.toString()); // 打印结果:"0.000001"

const num2 = 0.0000001; // 小数点后面紧跟六个零
console.log(num2.toString()); // 【重点关注】打印结果:"1e-7"

const num3 = 1.0000001;
console.log(num3.toString()); // 打印结果:"1.0000001"

const num4 = 0.10000001;
console.log(num4.toString()); // 打印结果:"0.10000001"

4. 常量可以直接调用 toString() 方法,但这里的常量,不允许直接写数字。举例如下:

1.toString(); // 注意,会报错
1..toString(); // 合法。得到的结果是字符串"1"
1.2.toString(); // 合法。得到的结果是字符串"1.2"
(1).toString(); // 合法。得到的结果是字符串"1"
'1'.toString(); // 合法。得到的结果是字符串"1"

上方代码中,为何出现这样的打印结果?这是因为:

  • 第一行代码:JS 引擎认为 1.toString() 中的 . 是小数,是数字字面量的一部分,而不是方法调用的分隔符。小数点后面的字符是非法的
  • 第二行、第三行代码:JS 引擎认为第一个.是小数点,第二个.是属性访问的语法,所以能正常解释实行
  • 第四行代码:用()排除了.被视为小数点的语句解释,所以这种写法也能正常解释执行

小结:因为点号(.)被解释为数字字面量的一部分,而不是方法调用的分隔符。为了正确调用 toString 方法,可以使用括号或额外的点号

如果想让数字调用 toString() 方法,更推荐的做法是先把数字放到变量中存起来,然后通过变量调用 toString()。举例:

const a = 1
a.toString() // 合法,得到的结果是字符串 "1"

5. 既然常量没有方法,那它为什么可以调用 toString() 呢?这是因为,除了 undefined、null 之外,其他的常量都有对应的特殊的引用类型——基本包装类型,所以代码在解释执行的时候,会将常量转为基本包装类型,这样就可以调用相应的引用类型的方法。

我们在后续的内容《JavaScritpt基础/基本包装类型》中会专门讲到基本包装类型

2.6.3.2. String() 函数
String(常量 / 变量)

该方法不会影响到原数值,它会将转换的结果返回

使用 String() 函数做强制类型转换时:

  • 对于 Number、Boolean、String、Object 而言,本质上就是调用 toString() 方法,返回结果同 toString() 方法
  • 但是对于 null 和 undefined,则不会调用 toString() 方法。它会,将 undefined 直接转换为 "undefined",将 null 直接转换为 "null"
2.6.3.3. 隐士转换:字符串拼接

如果加号的两边有一个是字符串,则另一边会自动转换成字符串类型进行拼接

字符串拼接的格式:变量 + "" 或者 变量 + "abc"

var a = 123 // Number 类型
console.log(a + '') // "123"
console.log(a + 'haha') // "123haha"

上面的例子中,打印的结果,都是字符串类型的数据。实际上底层是调用的 String() 函数

2.6.3.4. prompt():用户输入

我们前面的有讲过,prompt()就是专门用来弹出能够让用户输入的对话框。重要的是:用户不管输入什么,都当字符串处理

2.6.4. 转换为 Number

2.6.4.1. Number() 函数
const result = Number(变量 / 常量)

使用 Number() 函数转为数字的规则如下:

原始值转换后的值
字符串1. 字符串去掉首尾空格后,剩余字符串的内容如果是纯数字,则直接将其转换为数字
  1. 字符串去掉首尾空格后,剩余的字符串里的内容只要包含了其他非数字的内容(小数点按数字来算),则转换为 NaN
  2. 如果字符串是一个空串或者是一个全是空格的字符串,则转换为0 | | 布尔值 | true 转成 1;false 转成 0 | | undefined | NaN | | null | 0 |
2.6.4.2. 隐士类型转换:+
  1. 字符串 + 其他数据类型 = 字符串

任何数据类型和字符串做加法运算,都会先自动将那个数据类型调用 String() 函数转换为字符串,然后在做拼串操作。最终的运算结果是字符串

result1 = 1 + 2 + '3' // '33'
result2 = '1' + 2 + 3 // '123'

2. Boolean + 数字 = 数字

Boolean 型和数字型相加时,true 按 1 来算,false 按 0 来算。这里其实是先调用 Number() 函数,将 Boolean 类型转为 Number 类型,然后在和数字相加

  1. null + 数字 = 数字

等价于:0 + 数字

  1. undefined + 数字 = NaN
  2. 任何值和 NaN 运算的结果都是 NaN
2.6.4.3. 隐士类型转换:-*/%

任何非 Number 类型的值做 -*/%运算时,会将这些值转换为 Number 然后再运算(内部调用的是 Number() 函数),运算结果都是 Number 类型

注意:任何数据和 NaN 进行运算,结果都是 NaN

var result1 = 100 - '1' // 99
var result2 = true + NaN // NaN
2.6.4.4. 隐士类型转换:正负号

注意:这里说的正号 / 负号,不是 加号 / 减号

任何值做 +a-a 运算时,底层调用的是 Number() 函数。不会改变原数值;得到的结果,会改变正负性

const a1 = '123'
console.log(+a1) // 123
console.log(-a1) // -123

const a2 = '123abc'
console.log(+a1) // NaN
console.log(-a1) // NaN

const a4 = false;
console.log(+a4); // 0
console.log(-a4); // -0

const a5 = null;
console.log(+a5); // 0
console.log(-a5); // -0

const a6 = undefined;
console.log(+a6); // NaN
console.log(-a6); // NaN
2.6.4.5. parseInt() 函数

将传入的数据当作字符串来处理,从左至右提取数值,一旦遇到非数值就立即停止;停止时如果还没有提取到数值,就返回 NaN

const result = parseInt(需要转换的字符串)

按照上面的规律,使用 parseInt() 函数转为数字的规则如下:

原始值转换后的值
字符串1. 只保留字符串最开头的数字,后面的中文自动消失
  1. 如果字符串不是以数字开头,则转换为 NaN
  2. 如果字符串是一个空串或者是一个全是空格的字符串,转换时会报错 | | 布尔值 | NaN | | undefined | NaN | | null | NaN |

Number() 函数 和 parseInt() 函数的区别:

  • Number():千方百计地想转换为数字,如果转换不了则返回 NaN
  • parseInt() / parseFloat():提取出最前面的数字部分(开头如果是空格,则自动忽略空格),没有提取出来,那就返回 NaN

parseInt() 函数具有以下特性:

  1. parseInt()、parseFloat() 会将传入的数据当作字符串来处理。也就是说,如果对于非字符串的使用,它会先将其转换为 String 然后再操作
var a = 168.23
var b = true
var c = null
var d = undefined
console.log(parseInt(a)) // 168
console.log(parseInt(b)) // NaN
console.log(parseInt(c)) // NaN
console.log(parseInt(d)) // NaN

2. 只保留字符串最开头的数字,后面的中文自动消失

console.log(parseInt('2017在公众号上写了6篇文章')); //打印结果:2017

console.log(parseInt('2017.01在公众号上写了6篇文章')); //打印结果仍是:2017   (说明只会取整数)

console.log(parseInt('aaa2017.01在公众号上写了6篇文章')); //打印结果:NaN (因为不是以数字开头)

3. 自动截取小数:取整,不四舍五入

var a = parseInt(5.8) + parseInt(4.7)
console.log(a) // 9

var a = parseInt(5.8 + 4.7);
console.log(a) // 10

4. 带两个参数,表示在转换时,包含了进制转换

var a = '110';

var num = parseInt(a, 16); // 【重要】将 a 当成 十六进制 来看待,转换成 十进制 的 num

console.log(num) // 272
2.6.4.6. parseFloat() 函数

将字符串转换为浮点数

parseFloat() 和 parseInt() 的作用类似,不同的是,parseFloat() 可以获取小数部分

var a = '123.456.789px'
console.log(parseFloat(a)) // 123.456

parseFloat() 的几个特性,可以参照 parseInt()

2.6.5. 转换为 Boolean

2.6.5.1. 转换结果举例

其他的数据类型都可以转换为 Boolean 类型。无论是隐士转换,还是显式转换,转换结果都是一样的。转换为 Boolean 类型的规则如下:

原始值转换后的值
字符串1. 空字符串的转换结果是 false,其余的都是 true
  1. 全是空格的字符串,转换结果也是 true
  2. 字符串 '0' 的转换结果也是 true | | 数字 | 0 和 NaN 的转换结果是 false,其余的都是 true。比如:Boolean(NaN)的结果是 false | | undefined | false | | null | false | | 对象 | 引用数据类型会转换为 true。注意:空数组[] 和空对象{},转换结果也是 true,这一点,很多人不知道 |

总结:空字符串、0、NaN、undefined、null 会转换为 false,其他值会转换为 true

2.6.5.2. 隐士类型转换:逻辑运算

当非 Boolean 类型的数值和 Boolean 类型的数值作比较时,会先把前者临时进行隐士转换为 Boolean 类型,然后在做比较;且不会改变前者的数据类型

const a = 1;

console.log(a == true); // 打印结果:true
console.log(typeof a); // 打印结果:number。可见,上面一行代码里,a 做了隐式类型转换,但是 a 的数据类型并没有发生变化,仍然是 Number 类型

console.log(0 == true); // 打印结果:false
2.6.5.3. !!

使用 !! 可以显示转换为 Boolean 类型

console.log(!!3) // true
2.6.5.4. Boolean() 函数
const result = Number(变量 / 常量)
2.6.5.5. 隐士类型转换:isNaN()
isNaN(参数)

解释:判断指定的参数是否不是数字(NaN,非数字类型),返回结果为 Boolean 类型。不是数字时返回 true,是数字时返回 false

在做判断时,会进行隐士类型转换。也就是说:任何不能被转换为数值的参数,都会让这个函数返回 true

执行过程:

  1. 先调用 Number(参数)函数
  2. 然后判断 Number(参数)的返回结果是否为数值。如果不为数字,则返回结果为 true;如果为数字,则返回结果为 false
console.log(isNaN('123')); // 返回结果:false。

console.log(isNaN(null)); // 返回结果:false

console.log(isNaN('abc')); // 返回结果:true。因为 Number('abc') 的返回结果是 NaN

console.log(isNaN(undefined)); // 返回结果:true

console.log(isNaN(NaN)); // 返回结果:true

3. 运算符

运算符和表达式形影不离:

  • 运算符:也叫操作符,是一种符号。通过运算符可以对一个或多个值进行运算,并获取运算结果
  • 表达式:数字、运算符、变量的组合(组成的式子)
    • 表达式最终都会有一个运算结果,我们将这个结果称为表达式的返回值
  • 运算元:参与运算的对象。这个对象一般是数值或者变量
    • 如果一个运算符(比如加法运算符)拥有两个运算元,那么它是二元运算符。一元运算符、三元运算符的概念同理

JS 中运算符,分类如下:

  • 算术运算符
  • 自增 / 自减运算符
  • 一元运算符
  • 三元运算符(条件运算符)
  • 逻辑运算符
  • 赋值运算符
  • 比较运算符

3.1. 算术运算符

算术运算符:用于执行两个变量或值的算术运算符

此外,算术运算符存在隐士类型转换的情况,前文 "数据类型转换"里面有讲过

常见的算术运算符有以下几种:

运算符描述
+加、字符串连接
-
*
/
%获取余数(取余 / 取模)
**幂运算,是 ES7 新增特性

3.1.1. 运算规则

  1. * / % 的优先级高于 + -
  2. 无论是 + - * / % 都是左结合性(从左至右计算)
  3. 小括号 ():能够影响计算顺序,且可以嵌套。没有中括号、没有大括号,只有小括号
var a = 1 + ((2 * 3) %4) / 3
// 原式:1 + 6 % 4 / 3 = 1 + 2 / 3 = 1.66666666666666

3.1.2. 取余(取模)运算

%符号在这里并不是用来做百分号的计算,和百分号计算没有关系

余数 = m % n

计算结果注意:

  • 取余运算结果的正负性,取决于 m,而不是 n。比如:10 % -3的运算结果是 1。-10 % 3的运算结果是 -1
  • 如果 n < 0,那么就先把 n 取绝对值后,再计算。等价于 m % (-n)
  • 如果 n 是 0,那么结果是 NaN
  • 在 n > 0 的情况下:
    • 如果 m >= n,那么就正常取余
    • 如果 m < n,那么结果就是m

3.1.3. 幂运算

**这个符号在 JS 中是幂运算符,是 ES7 中新增的特性。比如,2的3次方,可以表示为 2**3

除了**运算之外,JavaScript 还提供了Math.pow()方法,也可以进行幂运算

2**3
// 等价于
Math.pow(2, 3)

注意:**运算符的优先级高于乘法和除法运算符

3.1.4. 浮点数运算的精度问题

浮点数值的最高精度是17位小数,但在进行算术计算时,会丢失精度,导致计算不够准确。比如:

console.log(0.1 + 0.2); // 运算结果不是 0.3,而是 0.30000000000000004

console.log(0.07 * 100); // 运算结果不是 7,而是 7.000000000000001

因此,不要直接判断两个浮点数是否相等。前面的文档基本数据类型:Number 中有详细讲解

实际项目中,涉及数字计算的这部分,比较麻烦,且非常严谨;尤其是交易、金钱相关的业务,则一定不能出错

如果你直接把两个数字进行加减乘除,很容易丢失精度,导致计算不准确。实战中,往往需要把计算相关的代码封装成公共方法,提供给业务侧调用

我们也可以在开源网站找到一些已经封装好的工具类,比较知名的是 big.js

3.2. 赋值运算符

赋值:将等号右侧的值赋给符号左侧的变量

运算符运算规则举例
=直接赋值let a = 5
+=加后赋值a += 5 等价于 a = a + 5
-=减后赋值a -= 5 等价于 a = a - 5
*=乘后赋值a *= 5 等价于 a = a * 5
/=除后赋值a /= 5 等价于 a = a / 5
%=取余数后赋值a %= 5 等价于 a = a % a
**=幂运算后赋值a **= 5 等价于 a = a ** 5

3.2.1. 直接赋值

=运算符是直接赋值,很容易理解。比如:let a = 5。意思就是把 5 这个值,往 a 里面存储一份。简称:把 5 赋值给 a

我们要注意,a = 5 是有返回值的,它的返回值就等于5。因为赋值操作本身也是一个表达式,它会返回赋值后的值

let a = 5
console.log(a)  // 5

3.2.2. 链式赋值

const a = b = c = 2

解释:把 a、b、c 都赋值为2

注意:链式赋值的结合性是右结合性(从右至左的顺序进行计算)

const a, b
a = b = 3 // 先将 3 赋值给 b,再将 b 的值赋值给 a

3.3. 自增和自减运算符

3.3.1. ++

作用:可以快速对一个变量进行加1操作

注意事项:只能操作变量,不能操作常量或者表达式,否则会报错

let a1 = 1;
let a2 = 2;

a1++;
const result = a1++ + a2; // result的结果为4
// (a1+a2)++; // 报错,不支持表达式的写法,

const a3 = 3;
a3++; // 报错,因为常量无法再自加

自增分成两种:a++++a

  • 共同点:
    • 无论是a++还是++a,自增都会使原变量的值加1
    • 我们要注意的是:a是变量,而a++++a是表达式
    • 如果只想使用 a的值,不适用表达式的值,那么这两种写法都可以,因为 a 的值没有区别。一般是用 a++这种写法更多一些
  • 区别:
    • a++这个表达式的值等于原变量的值(a自增前的值)。可以这样理解:先把 a 的值赋值给表达式,然后 a 再自增
    • ++a这个表达式的值等于新值(a自增后的值)。可以这样理解:a 先自增,然后把自增的值赋值给表达式
let a1 = 3;
const result1 = 10 + a1++;

console.log('a1:', a1); // 打印结果:4
console.log('result1:', result1); // 打印结果:13

let a2 = 3;
const result2 = 10 + ++a2;

console.log('a2:', a2); // 打印结果:4
console.log('result2:', result2); // 打印结果:14

3.3.2. --

作用:可以快速对一个变量进行减1操作。原理同自增运算符

开发时,大多适用后置的自增 / 自减,并且代码独占一行。例如:num++或者num--

var n1 = 10;
var n2 = 20;

var result1 = n1++;

console.log(n1); // 11
console.log(result1); // 10

result = ++n1;
console.log(n1); //12
console.log(result); //12

var result2 = n2--;
console.log(n2); // 19
console.log(result2); // 20

result2 = --n2;
console.log(n2); // 18
console.log(result2); // 18

3.3.3. 隐士类型转换

自增和自减时,变量 a 包含了隐式类型转换的过程:

  • 先调用 Number(a) 函数
  • 然后将 Number(a) 的返回结果进行加 1 的操作,得到的结果赋值给 a
let a = '666'; // 这里不能用 const 定义,否则报错。
a++;

console.log(a); // 打印结果:667
console.log(typeof a); // 打印结果: number

let a = 'abc';
a++;

console.log(a); // 打印结果:NaN。因为 Number('abc')的结果为 NaN,再自增后,结果依然是 NaN
console.log(typeof a); // 打印结果:number

3.4. 一元运算符

一元运算符,只需要一个操作数,常见的一元运算符如下:

3.4.1. typeof

typeof 是典型的一元运算符,因为后面只跟一个操作数

因为 JS 是弱类型语言,是松散型语言,所以我们不需要显式指定数据的具体类型。但是很多时候,我们扔需要通过一种手段知道某个变量到底是哪一种数据类型,typeof 运算符应运而生

typeof()表示 "获取变量的数据类型",它是 JS 提供的一个操作符。返回的是小写,语法为:(以下两种写法都可以)

typeof 变量

typeof(变量)

注意:typrof 是一个运算符,或者说是一个操作符,所以说 typeof() 并不是一个函数,() 只是将括起来的内容当作一个整体而已

typeof 的语法返回结果
typeof 数字(含 typeof NaN)number
typeof 字符串string
typeof 布尔型boolean
typeof 对象object
typeof 方法function
typeof nullobject
typeof undefinedundefined
var a = '123';
console.log(typeof a); // 打印结果:string

console.log(typeof []); // 空数组的打印结果:object

console.log(typeof {}); // 空对象的打印结果:object

// typeof 无法区分数组,但 instanceof 可以

console.log([] instanceof Array); // 打印结果:true

console.log({} instanceof Array); // 打印结果:false

3.4.2. 正号 / 负号

注意:这里说的是正号 / 负号,不是加号 / 减号

  1. 不会改变原数值
  2. 正号不会对数字产生任何影响。比如说:2+2是一样的
  3. 我们可以对其他的数据类型适用+,来将其转换为 Number
  4. 负号可以对数字进行取反
var a = true;
a = +a; // 注意这行代码的一元运算符操作
console.log('a:' + a); // 1
console.log(typeof a); // number

console.log('-----------------');

var b = '18';
b = +b; // 注意这行代码的一元运算符操作
console.log('b:' + b); // 18
console.log(typeof b); // number

3.4.3. 隐式类型转换:正号 / 负号

任何值做+a-a运算时,内部调用的是 Number() 函数

const a = '666';
const b = +a; // 对 a 进行一元运算,b是运算结果

console.log(typeof a); // 打印结果:string。说明 a 的数据类型保持不变。
console.log(a); // 打印结果:"666"。不会改变原数值。

console.log(typeof b); // 打印结果:number。说明 b 的数据类型发生了变化。
console.log(b); // 打印结果:666

3.5. 三目运算符

三目运算符也叫三元运算符、条件运算符

条件表达式 ? 语句1 : 语句2

执行流程:三元运算符在执行时,首先对条件表达式进行求值:

  • 如果该值为 true,则执行语句1,并返回执行结果
  • 如果该值为 false,则执行语句2,并返回执行结果

如果条件表达式的求值结果是一个布尔值,会将其转换为布尔值然后再运算

3.6. 逻辑运算符

逻辑运算符有三个:

  • &&:与(且)。两个都为真,结果才为真。特点:一假则假
  • ||:或。只要有一个是真,结果就是真。特点:一真则真
  • !:非。对一个布尔值进行取反。特点:真边假,假边真

注意:能参与逻辑运算的,都是布尔值

console.log(3 < 2 && 2 < 4) // false

3.6.1. 非布尔值的与或运算

在实际开发中,我们经常用这种代码做容错处理或者兜底处理

非布尔值进行与或运算时,会通过隐式类型转换,先将其转换为布尔值,然后再运算,但返回结果为原值,比如说:

var result = 5 && 6 // 运算过程:true && true
console.log(result) // 6

上方代码可以看到,虽然运算过程为布尔值的运算,但返回结果是原值

那么,返回结果是哪个原值呢?

  1. 两个非布尔值,做逻辑运算
    1. 与运算的返回结果
      1. 如果第一个值为 false,则只执行第一条语句,并直接返回第一个值;不会再往后执行
      2. 如果第一个值为 true,则继续执行第二条语句,并返回第二个值(无论第二个值结果如何)
    1. 或运算的返回结果
      1. 如果第一个值为 true,则只执行第一条语句,并直接返回第一个值;不会再往后执行
      2. 如果一个值为 false,则继续执行第二条语句,并返回第二个值(无论第二个值的结果如何)
  1. 三个及以上的非布尔值,做逻辑运算
    1. 与运算的返回结果(value1 && value2 && value3)
      1. 从左到右依次计算操作数,知道第一个为 false 的值为止
      2. 如果所有的值都为 true,则返回最后一个值
    1. 或运算的返回结果(value1 || value2 || value3)
      1. 从左到右依次计算操作数,找到第一个为 true 的值为止
      2. 如果所有的值都为 false,则返回最后一个值

3.6.2. 非布尔值的 ! 运算

非布尔值进行非运算时,会先将其转换为布尔值,然后再运算,返回结果是 布尔值

let a = 10
a = !a

console.log(a) // false
console.log(typeof a) // boolean

3.6.3. 短路运算的妙用

下方举例中的写法技巧,在实际开发中,经常用到。这种写法,是一种很好的日容错、容灾、降级方案,需要多看几遍

  1. JS 中的 && 属于短路的与:
    1. 如果第一个值为 false,则不会执行后面的内容
    2. 如果第一个值为 true,则继续执行第二条语句,并返回第二个值
const a1 = 'qianguyihao';
// 第一个值为true,会继续执行后面的内容
a1 && alert('看 a1 出不出来'); // 可以弹出 alert 框

const a2 = undefined;
// 第一个值为false,不会继续执行后面的内容
a2 && alert('看 a2 出不出来'); // 不会弹出 alert 框

2. JS 中的 || 属于短路的或:

    1. 如果第一个值为 true,则不会执行后面的内容
    2. 如果第一个值为 false,则继续执行第二条语句,并返回第二个值
const result; // 请求接口时,后台返回的内容
let errorMsg = ''; // 前端的文案提示
if (result & result.retCode == 0) {
  errorMsg = '恭喜你中奖啦~'
}

if (result && result.retCode != 0) {
	// 接口返回异常码时
	errorMsg = result.msg || '活动太火爆,请稍后再试'; // 文案提示信息,优先用 接口返回的msg字段,其次用 '活动太火爆,请稍后再试' 这个文案兜底。
}

if (!result) {
	// 接口挂掉时
	errorMsg = '网络异常,请稍后再试';
}

3.7. 比较运算符

比较运算符可以比较两个值之间的大小关系,如果关系成立它会返回 true,如果关系不成立立即返回 false

比较运算符有很多种,比如:

  • >:大于号
  • <:小于号
  • >=:大于或等于
  • <=:小于或等于
  • ==:等于
  • ===:全等于
  • !=:不等于
  • !==:不全等于
const result = 5 > 10 // false

3.7.1. 非数值的比较

  1. 对于非数值进行比较时,会将其转换为数值类型(内部是调用 Number() 方法),再进行比较
console.log(1 > true) // false
console.log(1 >= true) // true
console.log(1 > '0'); //true

2. 特殊情况:如果参与比较的都是字符串,则不会将其转换为数字进行比较,比较的是字符串的 Unicide编码【非常重要,这里是一个大坑,很容易踩到】,比较字符编码时,是一位一位进行比较,顺序从左到右。如果大一样,则继续比较下一位。比如说,当你尝试去比较"123""56"这两个字符串时,不会发现,字符串"56"竟然比字符串"123"要打(因为 5 比 1 大)

// 比较两个字符串时,比较的是字符串的字符编码,所以可能会得到不可预期的结果
console.log('56' > '123') // true

因此:当我们想比较两个字符串的数字时,一定一定要先转型在比较大小,比如:parseInt()

  1. 任何值和 NaN 做任何比较都是 false

3.7.2. ==

==这个符号,它是判断是否等于,而不是赋值。注意事项如下:

  1. ==这个符号,还可以验证字符串是否相同
console.log('我爱你中国' == '我爱你中国') // true

2. ==这个符号并不严谨,会做隐式转换,将不同的数据类型,转为相同类型进行比较

console.log('6' == 6) // true
console.log(true == '1') // true
console.log(0 == -0) // true
console.log(null == 0) // false

3. undefined 衍生自 null,所以这两个值做相等判断时,会返回 ture

console.log(undefined == null) // true

4. NaN 不和任何值相等,包括它本身

console.log(NaN == NaN); //false
console.log(NaN === NaN); //false

例如:我们想判断 b 这个变量的值是否等于 NaN,可以使用 isNaN() 函数来判断一个值是否是 NaN

console.log(isNaN(b)) // 如果 b 为 NaN,则返回 true,否则返回 false

3.7.3. ===

全等在比较时,不会做类型转换。如果要保证完全等于(即:不仅要判断取值相等,要判断数据类型相同),我们就要用三个等号===。例如:

console.log('6' === 6); //false
console.log(6 === 6); //true

上述内容分析出:

  • ==两个等号,不严谨,"6"6是 true
  • ===三个等号,严谨,"6"6是 false

另外还有:==的反面是!====的反面是!==

console.log(3 != 8); // true
console.log(3 != '3'); // false,因为3=="3"true,所以反过来就是false。
console.log(3 !== '3'); // true,应为3==="3"false,所以反过来是true

3.8. 运算符优先级

运算的优先级如下:(优先级从高到低)

  • .[]new
  • ()
  • ++--
  • !~+(单目)、-(单目)、typeofvoiddelete
  • */%
  • +(双目)、-(双目)
  • <<>>>>>
  • 比较运算符:<<=>>=
  • 比较运算符:==!=====!==
  • &
  • ^
  • |
  • 逻辑运算符:&& (注意:逻辑与 && 比逻辑或 || 的优先级更高)
  • 逻辑运算符:||
  • ?:
  • =+=-=*=/=%=<<=>>=>>>=&=^=|=
  • ,

备注:在实际写代码的时候,如果你不清楚哪个优先级更高,可以先尝试把括号用上

4. 流程控制语句

在一个程序执行的过程中,各条语句的执行顺序对程序的结果是有直接影响的。所以,我们必须清楚每条语句的执行流程。而且,很多时候我们要通过控制语句的执行顺序来实现我们想要的业务逻辑和功能

流程控制语句分类:

  • 顺序结构
  • 选择结构:if 语句、switch 语句
  • 循环结构:while 语句、for 语句

4.1. 代码块

{}包围起来的代码,就是代码块

在 ES5 语法中,代码块,只具有分组的作用,没有其他的用途。代码块中的内容,在外部是完全可见的。举例:

{
  var a = 2
  alert('qianguyihao')
  console.log('千古壹号')
}

console.log('a=' + a)

// 打印结果:可以看出,虽然变量a是定义在代码块中的,但是在外部依然可以访问
千古壹号
a=2

4.2. if 语句

if 语句有以下三种形式

4.2.1. if 语句的三种形式

形式1:条件成立才执行,如果条件不成立,那就什么都不做

// 条件表达式对于非布尔类型的数据,会先转换成布尔类型再判断
if(条件表达式) {
  // 条件为真时,做的事情
}

形式2:if...else...

if(条件表达式) {
  // 条件为真时,做的事情
} else {
  // 条件为假时,做的事情
}

形式3:多分支的 if 语句

if(条件表达式1) {
  // 条件1为真时,做的事情
} else if(条件表达式2) {
  // 条件1不满足,条件2满足,做的事情
} else if(条件表达式3) {
  // 条件1、2不满足,条件3满足时,做的事情
} else {
  // 条件1、2、3都不满足时,做的事情
}

注意:以上所有的语句体中,只执行其中一个

4.2.2. if 语句的嵌套

我们通过下面这个例子来引出 if 语句的嵌套

//第一步,输入
const bianhao = parseInt(prompt('您想加什么油?填写92或者97'));
const sheng = parseFloat(prompt('您想加多少升?'));

//第二步,判断
if (bianhao == 92) {
    //编号是92的时候做的事情
    if (sheng >= 20) {
        const price = sheng * 5.9;
    } else {
        const price = sheng * 6;
    }
} else if (bianhao == 97) {
    //编号是97的时候做的事情
    if (sheng >= 30) {
        const price = sheng * 6.95;
    } else {
        const price = sheng * 7;
    }
} else {
    alert('不好意思,没有这个编号的汽油!');
}

alert('价格是' + price);

4.3. switch 语句

switch 语句也叫条件分支语句

switch(表达式) {
	case1// 语句体1;
		break;

	case2// 语句体2;
		break;

	...
	...

	default// 语句体 n+1;
		break;
}

4.3.1. 执行流程

执行流程如下:

  1. 首先,计算出表达式的值,和各个 case 依次比较,一旦有对应的值,就会执行相应的语句,在执行的过程中,遇到 break 就会结束
  2. 然后,如果所有的 case 都和表达式的值不匹配,就会执行 default 语句体部分

4.3.2. 结束条件

  • 情况 a:遇到 break 就结束(而不是遇到 default 就结束)。因为 break 在此处的作用是,立即结束并退出整个 switch 语句
  • 情况 b:执行到程序的末尾就结束

4.3.3. 注意点

  1. switch 后面的括号里可以是变量、常量、表达式,通常是一个变量(一般做法是:先把表达式存放到变量中)
  2. case 后面的值可以是变量、常量、表达式
  3. case 的判断逻辑是===,不是==。因此,字符串'6'和数字6是不一样的
  4. defalut 不一定要写在最后面。switch 中的 default 无论放到什么位置,都会等到所有 case 都不匹配在执行。default 也可以省略不写

4.3.4. case 穿透

switch 语句中的 break 可以省略,但一般不建议(对于新手而言)。否则结果可能不是你想要的,会出现一个现象:case 穿透

当然,如果你能利用好 case 穿透,会让代码写得十分优雅

const num = 4;

//switch判断语句
switch (num) {
    case 1:
        console.log('星期一');
        break;
    case 2:
        console.log('星期二');
        break;
    case 3:
        console.log('星期三');
        break;
    case 4:
        console.log('星期四');
    //break;
    case 5:
        console.log('星期五');
    //break;
    case 6:
        console.log('星期六');
        break;
    case 7:
        console.log('星期日');
        break;
    default:
        console.log('你输入的数据有误');
        break;


// 输出结果
星期四
星期五
星期六

上方代码的解释:因为在 case4 和case5 中都没有 break,那语句走到 case6 的break 才会停止

4.4. if 和 switch 如何选择

记住一句话:如果是对区间进行判断,则建议用 if。如果是对几个固定的值进行判断,那么,数量少的话用 if,数量多的话用 switch

4.5. for 循环

for(初始化表达式; 条件表达式; 更新表达式){
  // 语句
}

执行流程:

  1. 执行初始化表达式,初始化变量(初始化表达式只会执行依次)
  2. 执行条件表达式,判断是否执行循环
    1. 如果为 true,则执行循环
    2. 如果为 false,则终止循环
  1. 执行更新表达式,更新表达式执行完毕继续重复 第2点
for(let i = 1; i <= 100; i++) {
  console.log(i)
}

上方代码的解释:i 是循环变量,1 是初始化值,i <= 100是执行条件,i++ 是步长

4.6. while 循环

while(条件表达式) {
  // 语句
}

执行流程:

  • while 语句在执行时,先对条件表达式进行求值判断:
    • 如果值为 true,则执行循环体
      • 循环体执行完毕后,继续对表达式进行判断
      • 如果为 true,则继续执行循环体,依次类推
    • 如果值为 false,则终止循环

如果有必须的话,我们可以使用 break 来终止循环

4.7. do...while 循环

do {
  // 语句
} while(条件表达式)

执行流程:

  • do...while 语句在执行时,会先执行循环体:
    • 循环体执行完毕以后,在对 while 后的条件表达式进行判断
      • 如果结果为 true,则继续执行循环体,执行完毕继续判断,以此类推
      • 如果结果为 false,则终止循环

while 循环和 do...while 循环的区别:

  • 这两个语句功能类似,不同的是
    • while:先判断后执行。只有条件表达式为真,才会执行循环体
    • do...while:先执行后判断。无论条件表达式是否为真,循环体至少会被执行一次

4.8. break 和 continue

4.8.1. break

  • break 可以用来推出 switch 语句或者退出整个循环语句(循环语句包括for 循环、while循环。不包括 if。单独的 if 语句不能使用 break 和 continue,否则会报错)
  • break 会立即终止离它最近的那个循环语句
  • 可有为循环语句创建一个 label,来标识当前的循环(格式:label:循环语句)。使用 break 语句时,可以在 break 后跟着一个 label,这样 break 将会结束指定的循环,而不是最近的

举例1:通过 beak 终止循环语句

for (let i = 0; i < 5; i++) {
    console.log('i的值:' + i);
    if (i == 2) {
        break; // 注意,虽然在 if 里 使用了 break,但这里的 break 是服务于外面的 for 循环。
    }
}

// 输出
i的值:0
i的值:1
i的值:2

举例2:label 的使用

outer: for (let i = 0; i < 5; i++) {
    console.log('外层循环 i 的值:' + i);
    for (let j = 0; j < 5; j++) {
        break outer; // 直接跳出outer所在的外层循环(这个outer是我自定义的label)
        console.log('内层循环 j 的值:' + j);
    }
}

// 输出
外层循环 i 的值:0

4.8.2. continue

  • continue 只能用于循环语句(包括 for循环、while循环,不包括 if。单独的 if 语句里不能用 break 和 continue,否则会报错)。可以用来跳过当此循环,继续下一次循环
  • 同样,continue 默认只会离开它最近的循环起作用
  • 同样,如果需要跳过执行的当次循环,可以使用 label 标签
for (let i = 0; i < 10; i++) {
    if (i % 2 == 0) {
        continue;
    }
    console.log('i的值:' + i);
}

// 输出
i的值:1
i的值:3
i的值:5
i的值:7
i的值:9

5. 内置对象

JavaScript 中的对象分为三种:自定义对象、内置对象、浏览器对象。前面两种对象是 JS 的基础内容,属于 ECMAScript;第三个浏览器对象,属于 JS 独有,即 JS 内置的API

内置对象:就是指这个语句自带的一些对象,供开发者使用,这些对象提供了一些常用或者基本而必要的功能(属性和方法)

内置对象最大的优点就是帮助我们快速开发

JavaScript 的内置对象:

内置对象对象说明
Arguments函数参数集合
Array数组
Boolean布尔对象
Math数学对象
Date日期时间
Error异常对象
Function函数构造器
Number数值对象
Obejct基础对象
RegExp正则表达式对象
String字符串对象

5.1. String

在日常开发中,String 对象(字符串对象)的使用频率是非常高的。所以有必要详细介绍

需要注意的是:字符串的所有方法,都不会改变原字符串(字符串的不可变性),操作万后会返回一个新的值

5.1.1. 查找字符串

5.1.1.1. indexOf() / lastIndexOf()

这个方法,是使用频率最高的一个方法

作用:获取字符串中指定内容的索引

语法1:

索引值 = str.indexOf(想要查询的字符串)

备注:indexOf()是从前向后查找字符串的位置。同理,lastIndexOf()是从后向前寻找

解释:可以检索一个字符串是否含有指定内容。如果字符串中含有该内容,则会返回其第一次出现的索引;如果没有找到指定的内容,则返回 -1

因此可以得出一个重要技巧:

  • 如果获取的索引值为 0,说明字符串是以查询的参数为开头的
  • 如果获取的索引位置为 -1,说明这个字符串中没有指定的内容

举例1:查找单个字符

const str = 'abcdea';

//给字符查索引(索引值为0,说明字符串以查询的参数为开头)
console.log(str.indexOf('c')); // 2
console.log(str.lastIndexOf('c')); // 2

console.log(str.indexOf('a')); // 0
console.log(str.lastIndexOf('a')); // 5

举例2:查找字符串

const name = 'qianguyihao'
console.log(name.indexOf('yi')) // 6

语法2:这个方法可以指定第二个参数,用来指定查找的起始位置

索引值 = str.indexOf(想要查询的字符串, [起始位置])

举例3:两个参数时,需要特别注意

var str = 'qianguyihao'
result = str.indexOf('a', 3) // 从下标为3的位置开始查找 'a' 这个字符

console.log(result) // 9

实用案例:查找字符串 'qianguyihao' 中,所有a出现的位置以及次数

思路:

  1. 先查找出第一个 a 出现的位置
  2. 只要 indexOf 返回的结果不是 -1,就继续往后查找
  3. 因为 indeOf 只能查找第一个,所以后面的查找,可以利用第二个参数,在当前索引加1,从而继续查找
var ste = 'qianguyihao'
var index = str.indexOf('a')
var num = 0

while(index !== -1) {
  console.log(index)
  num++ // 没打印一次,就计数一次
  index = str.indexOf('a', index + 1)
}

console.log('a出现的次数是:' + num)
5.1.1.2. search()

作用:获取字符串中指定内容的索引(参数里一般是正则)

备注:search()方法里的参数,即可以传字符串,也可以传正则表达式

索引值 = str.search(想要查找的字符串)
索引值 = str.search(正则表达式)

解释:可以检索一个字符串中是否含有指定内容。如果字符串中含有该内容,则会返回其第一次的索引;如果没有找到指定的内容,则返回 -1

举例:

const name = 'qianguyihao';

console.log(name.search('yi')); // 打印结果:6
console.log(name.search(/yi/i)); // 打印结果:6

备注:上方的/yi/i采用的是正则表达式的写法,意思是,让 name 去匹配字符 yi,忽略大小写。我们在后面会专门介绍正则表达式

5.1.1.3. includes()

作用:字符串中时候包含指定的内容

布尔值 = str.includes(想要查找的字符串, [position])

解释:判断一个字符串中是否含有指定内容。如果字符串中含有该内容,则会返回 true,否则返回 false

参数中的position:如果不指定,则默认为0;如果指定,则规定了检索的起始位置

const name = 'qianguyihao';

console.log(name.includes('yi')); // 打印结果:true
console.log(name.includes('haha')); // 打印结果:false

console.log(name.includes('yi',7)); // 打印结果:false
5.1.1.4. startsWith()

作用:字符串是否以指定的内容开头

布尔值 = str.startsWith(想要查找的内容, [position])

解释:判断一个字符串是否以指定的子字符串开头。如果是,则返回 true;否则返回 false

参数中的 position:

  • 如果不指定,则默认为0
  • 如果指定,则规定了检索的起始位置。检索的范围包括:这个指定位置开始,直到字符串的末尾
const name = 'abcdefg';

console.log(name.startsWith('a')); // 打印结果:true
console.log(name.startsWith('b')); // 打印结果:false

// 因为指定了起始位置为3,所以是在 defg 这个字符串中检索。
console.log(name.startsWith('d',3)); // 打印结果:true
console.log(name.startsWith('c',3)); // 打印结果:false
5.1.1.5. endsWith()

作用:字符串是否以指定的内容结尾

布尔值 = str.endsWith(想要查找的内容, [position])

解释:判断一个字符串是否以指定的子字符串结尾。如果是,则返回 true;否则返回 false

参数中的 position:

  • 如果不指定,则默认为 str.length
  • 如果指定,则规定了检索的结束位置。检索的范围包括:从第一个字符串开始,直到这个指定的位置

注意:startsWith() 和 endsWith() 这两个方法,它们的 position 的含义是不同的

const name = 'abcdefg';

console.log(name.endsWith('g')); // 打印结果:true
console.log(name.endsWith('f')); // 打印结果:false

// 因为指定了截止位置为3,所以是在 abc 这个长度为3字符串中检索
console.log(name.endsWith('c', 3)); // 打印结果:true
console.log(name.endsWith('d', 3)); // 打印结果:false

5.1.2. 获取指定位置的字符

5.1.2.1. charAt()
字符 = str.charAt(index)

解释:返回字符串指定位置的字符。这里的str.charAt(index)str[index]的效果是一样的

注意:字符串中第一个字符的下标是 0。如果参数 index 在[0, string.length]之间,该方法将返回一个空字符串

var str = new String('smyhvae');

for (var i = 0; i < str.length; i++) {
    console.log(str.charAt(i));
}

// 输出
s
m
y
h
v
a
e

上面这个例子一般不用。一般打印数组和 json 的时候用索引,打印 String 不建议用索引

5.1.2.2. str[]

str.charAt(index)str[index]的效果是一样的,不再赘述。

两者的区别在于:str[index]是 H5 标准里新增的特性

5.1.2.3. charCodeAt()
字符 = str.charCodeAt(index)

解释:返回字符串指定位置的字符的 Unicode 编码。不会修改原字符串

在实际应用中,通过这个方法,我们可以判断用户按下了哪个按键

5.1.3. 字符串截取

5.1.3.1. slice()

slice() 方法用的最多

新字符串 = str.slice(开始索引,结束索引) // 两个参数都是索引值,包左不包右

解释:从字符串中截取指定的内容。不会修改原字符串,而是将截取到的内容返回

注意:上面的参数,包左不包右。参数举例如下:

  • (2, 5)截取时,包左不包右
  • (2)表示从指定的索引位置开始,截取到最后
  • (-3)表示从倒数第三个开始,截取到最后
  • (1, -1)表示从第一个截取到倒数第一个
  • (5, 2)表示前面的大,后面的小,返回值为空
5.1.3.2. substring()
新字符串 = str.substring(开始索引,结束索引) // 两个参数都是索引值,包左不包右

解释:从字符串中截取指定的内容。和 slice()类似

substring()slice()是类似的,但不同之处在于:

  • substring()不能接收负值作为参数。如果传递了一个赋值,则默认使用0
  • substring()还会自动调整参数的位置,如果第二个参数小于第一个,则自动交换。比如说:substring(1, 0)相当于截取的是第一个字符
5.1.3.3. substr()
新字符串 = str.substr(开始索引, 截取的长度)

解释:从字符串中截取指定的内容,不会修改原字符串,而是将截取到的的内容返回

注意:这个方法的第二个参数截取的长度,不是结束索引

参数举例:

  • (2, 4)从索引值为 2 的字符开始,截取 4个字符
  • (1)从指定位置开始,截取到最后
  • (-3)从倒数第几个开始,截取到最后

备注:ECMAScript 没有对 substr() 方法进行标准化,因此不建议使用它

5.1.4. String.fromCharCode()

作用:根据字符的 Unicode 编码获取字符

var result1 = String.fromCharCode(72)
var result2 = String.fromCharCode(20013)

console.log(result1) // H
console.log(result2) // 中

5.1.5. concat()

新字符串 = str1.concat(str2) // 连接两个字符串

解释:字符串的连接

这种方法基本不用,直接把两个字符串相加就好

是的,你会发现,数组中也有 concat()方法,用于数组的连接。这个方法在数组中用得挺多的

var str1 = 'qiangu';
var str2 = 'yihao';

var result = str1.concat(str2);
console.log(result); // 打印结果:qianguyihao

5.1.6. split()

作用:字符串转换为数组

新的数组 = str.solit(分隔符)

解释:通过指定的分隔符,将一个字符串拆分成一个数组。不会改变原字符串

备注:split()这个方法在实际开发中用得非常多。一般来说,从接口拿到的 JSON 数据中,经常会接收到类似于"q, i, a, n"这样的字符串,前端需要将这个字符串拆成为['q', 'i', 'a', 'n']数组,这个时候solit()方法就排上用场了

var str = 'qian, gu, yi, hao'; // 用逗号隔开的字符串
var array = str.split(','); // 将字符串 str 拆分成数组,通过逗号来拆分

console.log(array); // 打印结果是数组:["qian", " gu", " yi", " hao"]

//split()方法:字符串变数组
var str3 = '千古壹号|qianguyihao|许嵩';

console.log('结果1:' +str3.split()); // 无参数,表示:把整个字符串作为一个元素添加到数组中。

console.log(str3.split('')); // 参数为空字符串,则表示:分隔字符串中每一个字符,分别添加到数组中

console.log(str3.split('|')); // 参数为指定字符,表示:用 '|' 分隔字符串。此分隔符将不会出现在数组的任意一个元素中

console.log(str3.split('许')); // 同上

5.1.7. replace()

新的字符串 = str.replace(被替换的子串,新的子串)

解释:将字符串中的指定内容,替换为新的内容并返回。不会修改原字符串

注意:这个方法,默认只会替换第一个被匹配到的字符。如果要全局替换,需要使用正则

//replace()方法:替换
var str2 = 'Today is fine day,today is fine day !';
console.log(str2);

console.log(str2.replace('today', 'tomorrow')); //只能替换第一个today
console.log(str2.replace(/today/gi, 'tomorrow')); //这里用到了正则,才能替换所有的today

5.1.8. repeat()

newStr = str.repeat(重复的次数)

解释:将字符串重复指定的次数。会返回新的值,不会修改原字符串

举例1:

const name = 'qianguyihao';

console.log(name.repeat(2)); // 打印内容:qianguyihaoqianguyihao

举例2:模糊字符串的后四位

const telephone = '13088889999';
const mix_telephone = telephone.slice(0, -4) + '*'.repeat(4); // 模糊电话号码的后四位

console.log(telephone); // 打印结果:13088889999
console.log(mix_telephone); // 打印结果:1308888****

5.1.9. trim()

作用:去除字符串前后的空白

//去除字符串前后的空格,trim();
let str = '   a   b   c   ';
console.log(str);
console.log(str.length); // 15

console.log(str.trim());
console.log(str.trim().length); // 9

5.1.10. 大小写转换

var str = 'abcdEEEP'

// 转换成小写
console.log(str.toLowerCase()) // abcdeeep

// 转换成大写
console.log(str.toUpperCase()) // ABCDEEEP

5.1.11. html 方法

  • anchor() 创建 a 链接
  • big()
  • sub()
  • sup()
  • link()
  • bold()

注意,str.link() 返回值是字符串

var str = '你好';

console.log(str.anchor());
console.log(str.big());
console.log(str.sub());
console.log(str.sup());
console.log(str.link('http://www.baidu.com'));
console.log(str.bold());

5.2. Number 和 Math

5.2.1. Number

5.2.1.1. Number.isInteger()

作用:判断是否为整数

布尔值 = Number.isInteger(数字)
5.2.1.2. toFixed()

作用:小数点后面保留多少位

字符串 = myNum = toFixed(num)

解释:将数字 myNum 的小数点后面保留 num 位小数(四舍五入),并返回。不会改变原数字。注意:返回结果是字符串

参数 num:指定了小数点后面的位数

let num = 3.456;
let num2 = num.toFixed(2);

console.log(num); // 打印结果:3.456
console.log(num2); // 打印结果:3.46

console.log(typeof num); // number
console.log(typeof num2); // string

另外需要注意的是:数字常量不能直接调 toFixed() 方法。比如:1.toFixed(2)在 JS 中会引发语法错误。因为点号被解释为数字字面量的一部分,而不是方法调用的分隔符。为了正确调用 toFixed() 方法,可以使用括号或额外的点号

toFixed() 在这一点上,跟前面讲的 toString() 是类似的,推荐的做法是先把数字放到变量中存起来,然后通过变量调用 toFixed()

5.2.2. Math

5.2.2.1. Math.abs()

作用:获取绝对值

注意:参数中可以接收字符串类型的数字,此时会将字符串做隐式类型转换,然后再调用 Math.abs() 方法

console.log(Math.abs(2)); // 2
console.log(Math.abs(-2)); // 2

// 先做隐式类型转换,将 '-2'转换为数字类型 -2,然后再调用 Math.abs()
console.log(Math.abs('-2'));

console.log(Math.abs('hello')); // NaN
5.2.2.2. Math.random()

作用:生成随机数

举例如下:

  • 生成 [0, x] 之间的随机浮点数Math.round(Math.randim()*x)
  • 生成 [x, y] 之间的随机浮点数Math.round(Math.random()*(y - x) + x)

举例1:生成[ x, y] 之间的随机整数

也就是说:生成两个整数之间的随机整数,并且要包含这个两个整数

这个功能很常用,我们可以封装成一个方法,代码实现如下:

function getRandom(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min
}

console.log(getRandom(1, 10));

举例2:随便点名

function getRandom(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

const arr = ['许嵩', '邓紫棋', '毛不易', '解忧邵帅'];
const index = getRandom(0, arr.length - 1); // 生成随机的index
console.log(arr[index]); // 随机点名
5.2.2.3. pow()

如果想计算 a 的 b 次方,可以使用如下函数:

Math.pow(a, b)

举例1:

var a = Math.pow(3, Math.pow(2, 2));
console.log(a);

举例2:

var a = Math.pow(Math.pow(3, 2), 4);
console.log(a);
5.2.2.4. sqrt()

如果想要计算数值 a 的开二次方,可以使用如下函数:

Math.sqrt(a)

5.3. Date

Date 对象再实际开发中,使用得很频繁,且容易再细节地方出错,需要引起重视

内置对象 Date 用来处理日期和时间

需要注意的是:与 Math 对象不同,Date 对象是一个构造函数,需要先实例化后才能使用

5.3.1. 创建 Date 对象

创建 Date 对象有两种写法:

  • 写法一:如果 Date() 不写参数,就返回当前时间对象
  • 写法二:如果 Date() 里面写参数,就返回括号里输入的时间对象

写法一:不传递参数时,则获取系统的当前时间对象

var date1 = new Date()
console.log(date1) // Mon Nov 10 2025 09:31:07 GMT+0800 (中国标准时间)
console.log(typeof date1) // object

代码解释:不传递参数时,表示的是获取系统的当前时间对象。也可以理解成是:获取当前代码执行的时间

写法二:传递参数

传递参数时,表示获取指定时间的时间对象。参数中即可以传递字符串,也可以传递数字,也可以传递时间戳

通过传参的这种写法,我们可以把时间字符串 / 时间数字 / 时间戳,按照指定的格式,转换为时间对象

举例1:参数是字符串

const date11 = new Date('2020/02/17 21:00:00');
console.log(date11); // Mon Feb 17 2020 21:00:00 GMT+0800 (中国标准时间)

const date12 = new Date('2020/04/19'); // 返回的就是四月
console.log(date12); // Sun Apr 19 2020 00:00:00 GMT+0800 (中国标准时间)

const date13 = new Date('2020-05-20');
console.log(date13); // Wed May 20 2020 08:00:00 GMT+0800 (中国标准时间)

const date14 = new Date('Wed Jan 27 2017 12:00:00 GMT+0800 (中国标准时间)');
console.log(date14); // Fri Jan 27 2017 12:00:00 GMT+0800 (中国标准时间)

举例2:参数是多个数字

const date21 = new Date(2020, 2, 18); // 注意,第二个参数返回的是三月,不是二月
console.log(date21); // Wed Mar 18 2020 00:00:00 GMT+0800 (中国标准时间)

const date22 = new Date(2020, 3, 18, 22, 59, 58);
console.log(date22); // Sat Apr 18 2020 22:59:58 GMT+0800 (中国标准时间)

const params = [2020, 06, 12, 16, 20, 59];
const date23 = new Date(...params);
console.log(date23); // Sun Jul 12 2020 16:20:59 GMT+0800 (中国标准时间)

举例3:参数是时间戳

const date31 = new Date(1591950413388);
console.log(date31); // Fri Jun 12 2020 16:26:53 GMT+0800 (中国标准时间)

// 先把时间对象转换成时间戳,然后把时间戳转换成时间对象
const timestamp = new Date().getTime();
const date32 = new Date(timestamp);
console.log(date32); // Fri Jun 12 2020 16:28:21 GMT+0800 (中国标准时间)

5.3.2. 日期格式化

上一段内容里,我们获取到了 Date 对象,但这个对象,打印出来的结果并不是特别直观。如果我们需要获取日期的指定部分,就需要用到 Date 对象自带的方法

获取了日期指定的部分之后,我们就可以让日期按照指定的格式,进行展示(即日期的格式化)。比如说,我们期望能以2025-11-10 09:37:20这种格式进行展示

Date 对象有如下方法,可以获取日期和时间的指定部分:

方法名含义备注
getFullYear()获取年份
getMonth()获取月:0~110 代表一月
getDate()获取日:1~31获取的是几号
getDay()获取星期:0~60 代表周日,1 代表周一
getHours()获取小时:0~23
getMinutes()获取分钟:0~59
getSeconds()获取秒:0~59
getMillisecondes()获取毫秒1s = 1000ms
// 方法:日期格式化
// 格式要求:今年是 2020年02月02日 08:57:09 星期日
function formatDate() {
  var date = new Date()

  var year = date.getFullYear() // 年
  var month = date.getMonth() + 1 // 月
  var day = date.getDate() // 日

  var week = date.getDay() // 星期几
  var weekArr = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']

  var hour = date.getHours() // 时
  hour = hour < 10 ? '0' + hour : hour // 如果只有一位,则前面补零

  var minute = date.getMinutes() // 分
  minute = minute < 10 ? '0' + minute : minute // 如果只有一位,则前面补零

  var second = date.getSeconds() // 秒
  second = second < 10 ? '0' + second : second // 如果只有一位,则前面补零

  var result = `今天是:${year}${month}${day}${hour}:${minute}:${second} ${weekArr[week]}`

  return result
}

5.3.3. 获取时间戳

时间戳:指的是从格林威治标准时间的1979年1月1日, 0时0分0秒到当前日期所花费的毫秒(1秒 = 1000毫秒)

计算机底层在保存时间时,使用的都是时间戳。时间戳的存在,就是为了统一时间的单位

我们经常会利用时间戳来计算时间,因为它更精确。而且,在实战开发中,接口返回给前端的日期数据,都是以时间戳的形式

var myDate = new Date('1970/01/01 0:0:0')

console.log(myDate.getTime()) // 获取时间戳

//输出:-28800000

解释:为啥打印结果是-28800000,而不是 0 呢?这是因为,我们的当前代码,是在中文环境下运行的,与英文时间会存在8个小时的时差(中文时间比英文时间早了八个小时)。如果代码是在英文环境下运行,打印结果就是 0

获取 Date 对象的时间戳

// 方式一:最常用的写法
const timestamp1 = +new Date()
console.log(timestamp1) // 输出举例:1589448165370

// 方式二:较为常用的写法
const timestamp2 = new Date().getTime()
console.log(timestamp2) // 输出举例:1589448165370

// 方式三:valueOf()
const timestamp3 = new Date().valueOf()
console.log(timestamp3) // 输出举例:1589448165370

// 方式四:
const timestamp4 = new Date() * 1
console.log(timestamp4) // 输出举例:1589448165370

// 方式五:
const timestamp5 = Number(new Date())
console.log(timestamp5) // 输出举例:1589448165370

// 方式六:获取当前时间的时间戳
console.log(Date.now()) // 输出举例:1589448165370

上面这种方式六,用得也很多。只不过,Date.now()是 H5 标准中新增的特性,如果你的项目需要兼容低版本的 IE 浏览器,就不要用了

5.3.4. format()

作用:将时间对象转换为指定格式

参考链接:www.cnblogs.com/tugenhua070…

5.3.5. Moment.js

Moment.js 是一个轻量级的 JavaScript 时间库,我们可以利用它很方便地进行时间操作,提升开发效率

使用举例:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
    </head>
    <body>
        <script src="https://cdn.bootcdn.net/ajax/libs/moment.js/2.26.0/moment.min.js"></script>
        <script>
            // 按照指定的格式,格式化当前时间
            console.log(moment().format('YYYY-MM-DD HH:mm:ss')); // 打印结果举例:2020-06-12 16:38:38
            console.log(typeof moment().format('YYYY-MM-DD HH:mm:ss')); // 打印结果:string

            // 按照指定的格式,格式化指定的时间
            console.log(moment('2020/06/12 18:01:59').format('YYYY-MM-DD HH:mm:ss')); // 打印结果:2020-06-12 18:01:59

            // 按照指定的格式,获取七天后的时间
            console.log(moment().add(7, 'days').format('YYYY-MM-DD hh:mm:ss')); // 打印结果举例:2020-06-19 04:43:56
        </script>
    </body>
</html>

6. 数组

数组(Array)是属于内置对象,数组和普通对象的功能类似,都可以用来存储一些值。不同的是:

  • 普通对象是使用字符串所为属性名,而数组是使用数字作为索引来操作元素
  • 索引:从 0 开始的整数就是索引

数组的存储性能比普通的对象要好。在实际开发中我们经常使用数组存储一些数据(尤其是列表数据),使用频率非常高

比如说,上面的这个页面的列表数据,它的数据结构就是一个数组

数组中的元素可以是任意的数据类型,可以是对象,可以是函数,也可以是数组。数组的元素中,如果存放的是数组,我们就称这种数组为二维数组

6.1. 创建数组对象

方式一:使用字面量创建数组(最简单,使用的也最多)

let arr1= [] // 创建一个空数组
let arr2 = [1, 2, 3] // 创建带初始值的数组

方式二:使用构造函数创建数组

let arr = new Array(参数)

如果参数为空,表示创建一个空数组;如果参数是一个数值,表示数组的长度;如果有多个参数,表示数组中的元素内容

举个例子:

// 方式一
let arr1 = [11, 12, 13];

// 方式二
let arr2 = new Array(); // 参数为空:创建空数组
let arr3 = new Array(4); // 参数为 size
let arr4 = new Array(15, 16, 17); // 参数为多个数值:创建一个带数据的数组

console.log(typeof arr1); // 打印结果:object

console.log('arr1 = ' + JSON.stringify(arr1)); // arr1 = [11, 12, 13]
console.log('arr2 = ' + JSON.stringify(arr2)); // arr2 = []
console.log('arr3 = ' + JSON.stringify(arr3)); // arr3 = [null, null, null, null]
console.log('arr4 = ' + JSON.stringify(arr4)); // arr4 = [15, 16, 17]

6.2. 数组的基本操作

6.2.1. 数组的索引

索引(下标):用来访问数组元素的序号,代表的是数组的元素在数组中的位置(下标从 0 开始算起)

数组可以通过索引来访问、修改对应的数组元素

6.2.2. 添加元素

数组[索引] = 值
const arr = []

// 像数组中添加元素
arr[0] = 10
arr[1] = 20
arr[2] = 30

console.log(JSON.stringify(arr)) // [10, 20, 30]

6.2.3. 获取元素

数组[索引]

如果读取不存在的索引(比如元素没那么多),系统不会报错,而是返回 undefined

const arr = [21, 22, 23];

console.log(arr[0]); // 打印结果:21
console.log(arr[5]); // 打印结果:undefined

6.2.4. 获取数组长度

可以使用length属性来获取数组的长度(即:元素的个数)

数组的长度是元素个数,不要跟索引号混淆

数组的长度 = 数组名.length
const arr = [21, 22, 23];

console.log(arr.length); // 打印结果:3

补充:对于连续的数组,使用 length 可以获取到数组的长度(元素个数);对于非连续的数组(即:稀疏的数组,本文稍后会介绍),length 的值会大于元素的个数。因此,尽量不要创建非连续的数组

6.2.5. 修改数组长度

可以通过修改 length 属性修改数组的长度

  • 如果修改的 length 大于原长度,则多出部分会空出来,值为 null
  • 如果修改的 length 小于原长度,则多出的元素会被删除,数组将从后面删除元素
  • 特例:伪数组 arguments 的长度可以修改,但是不能修改里面的元素,以后会单独介绍
const arr1 = [11, 12, 13];
const arr2 = [21, 22, 23];

// 修改数组 arr1 的 length
arr1.length = 1;
console.log(JSON.stringify(arr1)); // [11]

// 修改数组 arr2 的 length
arr2.length = 5;
console.log(JSON.stringify(arr2)); // [21, 22, 23, null, null]

6.2.6. 遍历数组

遍历:就是把数组中的每个元素从头到尾都访问一次

最简单的做法是通过 for 循环,遍历数组中的每一项

const arr = [10, 20, 30, 40, 50];

for (let i = 0; i < arr.length; i++) {
    console.log(arr[i]); // 打印出数组中的每一项
}

下一个篇文章,会介绍数组的各种方法,到时候,会有更多的做法去遍历数组

6.2.7. 数组的注意点

和其他编程语言相比,JS 语言中的数组比较灵活,有许多与众不同的地方

  1. 如果访问数组中不存在的索引时,不会报错,会返回 undefined
  2. 当数组的存储空间不够时,数组会自动扩容。其他编程语言中数组的大小是固定的,不会自动扩容
  3. 数组可以存储不同的类型数据,其它编程语言中数组只能存储相同类型数据
  4. 数组分配的存储空间不一定是连续的。其他语言数组分类的存储空间是连续的

JS 中的数组采用 "哈希映射" 的方式分配存储空间,我们可以通过索引找到对应空间。各大浏览器也对数组分配的存储空间进行了优化:如果存储的都是相同类型的数据,则会尽量分配连续的存储空间;如果存储的不是相同的数据类型,则不会分配连续的存储空间

6.2.8. 解构赋值

解构赋值是 ES6 中新增的一种赋值方式

ES5中,如果想把数组中的元素赋值给它们变量,是这样做的:

const arr = [1, 2, [3,4]];
let a = arr[0]; // 1
let b = arr[1]; // 2
let c = arr[2]; // [3, 4]

上面这种写法比较啰嗦。通过 ES6 中的结构复制,我们可以像下面这样做:

  1. 数组解构赋值
let [a, b, c] = [1, 2, [3, 4]]
console.log(a) // 1
console.log(b) // 2
console.log(c) // [3, 4]

注意点:

  • 等号左边的个数和格式,必须和右边的一摸一样,才能完全解构
  • 当然,左边的个数和右边的个数,可以不一样
  1. 默认值。在赋值之前,我们可以给左边的变量指定默认值
let [a, b = 3, c = 4] = [1, 2];
console.log(a); // 1
console.log(b); // 2。默认值被覆盖。
console.log(c); // 4。继续保持默认值。

3. 我们可以使用 ES6 中新增的扩展运算符打包剩余的数据。如果使用了扩展运算符,那么扩展运算符只能写在最后

let [a, ...b] = [1, 2, 3];
console.log(a); // 1
console.log(b); // [2, 3]

6.2.9. 实用案例

6.2.9.1. 翻转数组
const arr = [10, 20, 30, 40, 50]; // 原始数组
const newArr = []; // 翻转后的数组
for (let i = 0; i < arr.length; i++) {
    newArr[i] = arr[arr.length - i - 1];
}
console.log(JSON.stringify(newArr)); // [50,40,30,20,10]
6.2.9.2. 冒泡排序
const arr = [20, 10, 50, 30, 40];
for (let i = 0; i < arr.length - 1; i++) {
    for (let j = 0; j < arr.length - i - 1; j++) {
        if (arr[j] > arr[j + 1]) {
            let temp = arr[j];
            arr[j] = arr[j + 1];
            arr[j + 1] = temp;
        }
    }
}
console.log(JSON.stringify(arr)); // [10,20,30,40,50]

6.3. 数组的常见方法

6.3.1. isArray()

作用:用来判断是否为数组

布尔值 = Array.isArray(被检测的数组)

以前,我们通过 A instanceof B来判断 A 是否属于 B 类型。但是在数组中,这里 instanceof 方法已经用的不多了,因为有 isArray() 方法

6.3.2. 数组转为字符串

方式一:toString()

解释:把数组转换成字符串,每一项用英文逗号,分割

备注:大多数的数据类型都可以使用.toString()方法,将其转换为字符串

// 语法
字符串 = 数组.toString()

// 举例
const result = [1, 3, 5].toString() // 转换结果为 '1, 3, 5'                                               

方式二:String()

// 语法
字符串 = String(数组)

// 举例
const result = String([1, 3, 5]) // 转换结果为:'1, 3, 5'

方式三:join()

作用:将数组转换为字符串,返回结果为转换后的字符串(不会改变原来的数组)

补充:join() 方法可以指定一个字符串作为参数,这个参数是元素之间的连接符;如果不指定连接符,则默认使用英文逗号,作为连接符,此时和toString()的效果是一致的

// 语法
新的字符串 = 原数组.join(参数) // 参数选填

const arr = ['a', 'b', 'c'];

const result1 = arr.join(); // 这里没有指定连接符,所以默认使用 , 作为连接符

const result2 = arr.join('-'); // 使用指定的字符串作为连接符

console.log(typeof arr); // 打印结果:object
console.log(typeof result1); // 打印结果:string

console.log('arr =' + JSON.stringify(arr)); // arr =["a","b","c"]
console.log('result1:' + result1); // result1:a,b,c
console.log('result2:' + result2); // result2:a-b-c

6.3.3. split()

查看 5.1.6 split() 方法的介绍

6.3.4. Array.from()

作用:将伪数组或可遍历对象转换为真数组

array = Array.from(arrayLike)
const name = 'qianguyihao'
console.log(Array.from(name)); 
// 打印结果是数组:["q","i","a","n","g","u","y","i","h","a","o"]

伪数组与真数组的区别:

  • 伪数组:包含 length 属性的对象或可迭代的对象

另外,伪数组的原型链中没有 Array.prototype,而真数组的原型链中有 Array.prototype。因此伪数组没有数组的一般方法,比如 pop()、join() 等方法

6.3.5. Array.of()

作用:根据参数里的内容,创建数组

// 语法
Array.of(value1, value2, value3)

// 举例
const arr = Array.of(1, 'abc', true)
console.log(arr) // [1, 'abc', true]

补充:new Array()Array.of()的区别在于,当参数只有一个时,前者表示数组的长度,后者表示数组中的内容

6.3.6. 元素的添加和删除

6.3.6.1. push()

作用:向数组的最后面插入一个或多个元素,返回结果为新数组的长度。会改变原数组,因为原数组变成了新数组

var arr = ['王一', '王二', '王三']
// 语法
新数组的长度 = 数组.push(元素)
新数组的长度 = 数组.push(元素1, 元素2...)

var result1 = arr.push('王四') // 末尾插入一个元素
var result2 = arr.push('王五', '王六') // 末尾插入多个元素

console.log(JSON.stringify(arr)) // ["王一", "王二", "王三", "王四", "王五", "王六"]
console.log(result1) // 4
console.log(result2) // 6
6.3.6.2. pop()

作用:删除数组中的最后一个元素,返回结果为被删除的元素

// 语法:被删除的元素 = 数组.pop()

var arr = ['王一', '王二', '王三']
var result = arr.pop()

console.log(JSON.stringify(arr) // ["王一", "王二]
console.log(result) // 王三
6.3.6.3. unshift()

作用:在数组最前面插入一个或多个元素,返回结果为新数组的长度。会改变原数组,将原数组变成了新数组。插入元素后,其他元素的索引会依次调整

// 语法
新数组的长度 = 数组.unshift(元素)
新数组的长度 = 数组.unshift(元素1, 元素2...)

// 举例
var arr = ['王一', '王二', '王三'];

var result1 = arr.unshift('王四'); // 最前面插入一个元素
var result2 = arr.unshift('王五', '王六'); // 最前面插入多个元素

console.log(JSON.stringify(arr)); // ["王五","王六","王四","王一","王二","王三"]
console.log(result1); // 4
console.log(result2); // 6
6.3.6.4. shift()

作用:删除数组中的第一个元素,返回结果为被删除的元素

// 语法:被删除的元素 = 数组.shift()

var arr = ['王一', '王二', '王三']
var result = arr.shift()

console.log(JSON.stringify(arr)) // ["王二", "王三"]
console.log(result) // 王一
6.3.6.5. splice()

作用:从数组中删除指定的一个或多个元素,返回结果为被删除元素组成的新数组(会改变原来的数组)

备注:该方法会改变原数组,会将指定元素从原数组中删除;被删除的元素会封装到一个新的数组中返回

新数组 = 原数组.splice(起始索引index)

新数组 = 原数组.splice(起始索引index, 需要删除的个数)

新数组 = 原数组.splice(起始索引index, 需要删除的个数, 新的元素1, 新的元素2...)

上方语法中,第三个及之后的参数,表示:删除元素之后,向原数组中添加新的元素,这些元素将会自动插入到起始位置索引的前面。也可以理解成:删除了哪些元素,就在哪些元素的所在位置补充新的内容

注意:slice() 方法和 splice() 方法很容易搞混,请一定要注意区分

// 举例1
var arr1 = ['a', 'b', 'c', 'd', 'e', 'f']
var result1 = arr1.splice(1) // 从第index为1的位置开始,删除元素

console.log(JSON.stringify(arr1)) // ["b", "c", "d", "e", "f"]
console.log(JSON.stringify(result1)) // ["a"]

// 举例2
var arr2 = ['a', 'b', 'c', 'd', 'e', 'f']
var result2 = arr2.splice(-2) // 删除最后两个元素

console.log(JSON.stringify(arr2)) // ["a", "b", "c", "d"]
console.log(JSON.stringify(result3)) // ["e", "f"]

// 举例3
var arr3 = ['a', 'b', 'c', 'd', 'e', 'f'];
var result3 = arr3.splice(1, 3); //从第index为1的位置开始删除元素,一共删除三个元素

console.log(JSON.stringify(arr3)) // ["a","e","f"]
console.log(JSON.stringify(result3)) // ["b","c","d"]

// 举例4:删除指定元素
const arr4 = ['a', 'b', 'c', 'd'];
arr4.splice(arr4.indexOf('c'), 1); // 删除数组中的'c'这个元素

console.log(JSON.stringify(arr4)) // ["a", "b", "d"]

// 举例5:第三个参数的用法
var arr5 = ['a', 'b', 'c', 'd', 'e', 'f']
var result5 = arr5.splice(1, 3, '千古壹号', 'vae')

console.log(JSON.stringify(arr5)) // ["a","千古壹号","vae","e","f"]
console.log(JSON.stringify(result5)) // ["b","c","d"]
6.3.6.6. concat()

作用:连接两个或多个数组,返回结果为新的数组。不会改变原数组。简称:数组合并

// 语法
新数组 = 数组1.concat(数组2, 数组3...)

// 举例
const arr1 = [1, 2, 3]
const arr2 = ['a', 'b', 'c']
const arr3 = ['千古壹号', 'vue']

const result1 = arr1.concat(arr2);

const result2 = arr2.concat(arr1, arr3);

console.log('arr1 =' + JSON.stringify(arr1));
console.log('arr2 =' + JSON.stringify(arr2));
console.log('arr3 =' + JSON.stringify(arr3));

console.log('result1 =' + JSON.stringify(result1));
// result1 = [1, 2, 3, 'a', 'b', 'c']
console.log('result2 =' + JSON.stringify(result2));
// result2 = ['a', 'b', 'c', 1, 2, 3, '千古壹号', 'vae']

数组合并的另一种方式:我们可以使用...这种扩展运算符,将两个数组进行合并。举例如下:

const arr1 = [1, 2, 3]
const result = ['a', 'b', 'c', ...arr1]
console.log(JSON.stringify(result)) // ["a", "b", "c", 1, 2, 3]

备注:数组不能使用加号进行拼接。如果使用加号进行拼接会先转成字符串在拼接

6.3.6.7. slice()

作用:从数组中提取指定的一个或者多个元素,返回结果为新的数组(不会改变原来数组)

备注:该方法不会改变原数组,而是将截取到的元素封装到一个新数组中返回

// 语法
新数组 = 原数组.slice(开始位置的索引)
新数组 = 原数组.slice(开始位置的索引, 结束位置的索引)
// 注意:提取的元素中,包含开始位置,不包含结束位置

// 举例
const arr = ['a', 'b', 'c', 'd', 'e', 'f'];

const result1 = arr.slice(); // 不加参数时,则获取所有的元素。相当于数组的整体赋值
const result2 = arr.slice(2); // 从第二个值开始提取,直到末尾
const result3 = arr.slice(-2); // 提取最后两个元素
const result4 = arr.slice(2, 4); // 提取从第二个到第四个之间的元素(不包括第四个元素)
const result5 = arr.slice(4, 2); // 空

console.log('arr:' + JSON.stringify(arr));
// arr: ['a', 'b', 'c', 'd', 'e', 'f'];
console.log('result1:' + JSON.stringify(result1));
// result1: ['a', 'b', 'c', 'd', 'e', 'f'];
console.log('result2:' + JSON.stringify(result2));
// result2: ['c', 'd', 'e', 'f'];
console.log('result3:' + JSON.stringify(result3));
// result3: ['e', 'f'];
console.log('result4:' + JSON.stringify(result4));
// result4: ['c', 'd'];
console.log('result5:' + JSON.stringify(result5));
// result5: [];
6.3.6.8. fill()

作用:用一个固定的值填充数组,返回结果为新的数组。会改变原数组

// 用一个固定值填充数组。数组里的每个元素都会被这个固定值填充
新数组 = 数组.fill(固定值);

// 从 startIndex 开始的数组元素,用固定值填充
新数组 = 数组.fill(固定值, startIndex);

// 从 startIndex 到 endIndex 之间的元素(包左不包右),用固定值填充
新数组 = 数组.fill(固定值, startIndex, endIndex);
// 举例1
// 创建一个长度为4的空数组,然后用 'f' 来填充这个空数组
console.log(Array(4).fill('f')); // ['f', 'f', 'f,' 'f']

// 将现有数组的每一个元素都进行填充
console.log(['a', 'b', 'c', 'd'].fill('f')); // ['f', 'f', 'f,' 'f']


// 举例2
let arr1 = ['a', 'b', 'c', 'd'];
let arr2 = arr1.fill('f', 1, 3);

console.log(arr1); // ['a', 'f', 'f,' 'd']
console.log(arr2); // ['a', 'f', 'f,' 'd']

6.3.7. 反转数组

reverse():反转数组,返回结果为反转后的数组(会改变原来的数组)

// 语法
反转后的数组 = 数组.reverse()

// 举例
var arr = [1, 2, 3, 4, 5]
var result = arr.reverse() // 将数组 arr 进行反转
console.log(JSON.strigify(arr)) // [1, 2, 3, 4, 5]
console.log(JSON.stringify(result)) // [5, 4, 3, 2, 1]

6.3.8. 数组排序

sort():对数组的元素进行从小到大来排序(会改变原来的数组)

6.3.8.1. 无参时

如果在使用 sort() 方法时不带参,则默认按照元素的 Unicode 编码,从小到大进行排序

举例1:当数组中的元素为字符串时

let arr1 = ['e', 'b', 'd', 'a', 'f', 'c']
let result = arr1.sort() // 将数组 arr1 进行排序

console.log(JSON.stringify(arr1)) // ["a","b","c","d","e","f"]
console.log(JSON.stringify(result)) // ["a","b","c","d","e","f"]

从上方的打印结果中,我们可以看到,sort 方法会改变原数组,而且方法的返回值也是同样的结果

举例2:当数组中的元素为数字时

let arr2 = [5, 2, 11, 3, 4, 1];
let result = arr2.sort(); // 将数组 arr2 进行排序

console.log(JSON.stringify(arr2)) // [1, 11, 2, 3, 4, 5]
console.log(JSON.stringify(result)) // [1, 11, 2, 3, 4, 5]

上方的打印结果中,你会发现,使用 sort() 排序后,数字 11 竟然在数字 2 的前面,这是因为 sort() 方式是按照 Unicode 编码进行排序的

6.3.8.2. 带参时

如果在 sort() 方法中带参,我们就可以自定义排序规则。具体做法如下:

我们可以在 sort() 的参数中添加一个回调函数,来指定排序规则。回调函数中需要定义两个形参,JS 将会分别使用数组中的元素作为实参去调用回调函数

JS 根据回调函数的返回值来决定元素的排序:

  • 如果返回一个大于 0 的值,则元素会交换位置
  • 如果返回一个小于 0 的值,则不交换位置
  • 如果返回一个等于 0 的值,则认为两个元素相等,则不交换位置

举例1:将数组中的数字按照从小到大的排序

// 写法1
var arr = [5, 2, 11, 3, 4, 1]
var result = arr.sort(function(a, b) {
  if(a > b) {
    // 如果 a 大于 b,则交换 a 和 b 的位置
    return 1
  } else if(a < b) {
    // 如果 a 小于 b,则位置不变
    return -1
  } else {
    // 如果 a 等于 b,则位置不变
    return 0
  }
})
console.log(JSON.stringify(arr)) // [1, 2, 3, 4, 5, 11]
console.log(JSON.stringify(result)) // [1, 2, 3, 4, 5, 11]

// 写法2:ES5写法
let arr = [5, 2, 11, 3, 4, 1]
let result = arr.sort(function(a, b) {
  return a - b // 升序排列
  //return b -a // 降序排列
})
console.log(JSON.stringify(arr)) // [1, 2, 3, 4, 5, 11]
console.log(JSON.stringify(result)) // [1, 2, 3, 4, 5, 11]

// 写法3:ES6写法,箭头函数
let arr = [5, 2, 11, 3, 4, 1]
let result = arr.sort((a, b) => {
  return a - b // 升序排列
  //return b -a // 降序排列
})
console.log(JSON.stringify(arr)) // [1, 2, 3, 4, 5, 11]
console.log(JSON.stringify(result)) // [1, 2, 3, 4, 5, 11]

// 写法4:推荐写法
// 写法3因为函数体内只有一句话,所以可以去掉 return 语句,继续简化如下写法
let arr = [5, 2, 11, 3, 4, 1]
let result = arr.sort((a, b) => a - b)
console.log(JSON.stringify(arr)) // [1, 2, 3, 4, 5, 11]
console.log(JSON.stringify(result)) // [1, 2, 3, 4, 5, 11]

举例2:实际开发中将数组从小到大排序

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
    </head>
    <body>
        <script>
            let dataList = [
                {
                    title: '品牌鞋子,高品质低价入手',
                    publishTime: 200,
                },
                {
                    title: '不是很贵,但是很暖',
                    publishTime: 100,
                },
                {
                    title: '无法拒绝的美食,跟我一起吃',
                    publishTime: 300,
                },
            ];

            console.log('qianguyihao 排序前的数组:' + JSON.stringify(dataList));

            // 将dataList 数组,按照 publishTime 字段,从小到大排序。(会改变原数组)
            dataList.sort((a, b) => parseInt(a.publishTime) - parseInt(b.publishTime));

            console.log('qianguyihao 排序后的数组:' + JSON.stringify(dataList));
        </script>
    </body>
</html>

// 输出
qianguyihao 排序前的数组:[
    {"title":"品牌鞋子,高品质低价入手","publishTime":200},
    {"title":"不是很贵,但是很暖","publishTime":100},
    {"title":"无法拒绝的美食,跟我一起吃","publishTime":300}
]

qianguyihao 排序后的数组:[
    {"title":"不是很贵,但是很暖","publishTime":100},
    {"title":"品牌鞋子,高品质低价入手","publishTime":200},
    {"title":"无法拒绝的美食,跟我一起吃","publishTime":300}
]

上方代码中,肯定有人会问:publishTime 字段已经是 int 类型了,为啥在排序前还要做一次 parseInt() 转换。这是因为,这种数据,一般是后台接口返回给前端的,数据可能是 int 类型、也可能是字符串类型,所以前端还是统一做一下 partInt() 比较保险。这是一种良好的工作习惯和风险意识

6.3.9. 查找数组的元素

6.3.9.1. indexOf() / lastIndexOf()

作用:获取元素的索引

语法1:

元素的索引 = 数组.indexOf(想要查询的元素)
元素的索引 = 数组.lastIndexOf(想要查询的元素)

解释:可以检索一个数组中是否含有指定的元素。如果数组中含有该元素,则会返回其第一次出现的索引,并立即停止查找;如果没有指定的内容,则返回 -1

备注:indexOf()是从左往右查找元素的位置。同理,lastIndexOf()是从右往左寻找

这个方法的作用:

  • 如果找到了指定的元素,就返回元素对应的位置
  • 如果没有找到执行的元素,就会返回 -1
const arr = ['a', 'b', 'c', 'd', 'e', 'd', 'c']
console.log(arr.indexOf('c')) // 2
console.log(arr.lastIndexOf('d')) // 5
console.log(arr.indexOf('k')) // -1

语法2:这个方法还可以指定第二个参数,用来指定查找的起始位置

索引值 = 数组.indexOf(想要查询的元素, [查找的起始位置])

let arr = ['q', 'i', 'a', 'n', 'g', 'u', 'y', 'i', 'h', 'a', 'o']
result = str.indexOf('a', 3) // 从小标为3的位置开始查找, 'a'这个元素
console.log(result) // 9
6.3.9.2. includes()
布尔值 = arr.includes(想要查找的元素, [position])

解释:判断一个数组中是否包含指定的元素。如果是,则会返回 true,否则返回 false

参数中的 position:如果不指定,则默认为0;如果指定,则规定了索引的起始位置

const arr = [11, 12, 13, 14, 15]
console.log(arr.includes(12)) // true
console.log(arr.includes(20)) // false
console.log(arr.includes(11, 1)) // false
6.3.9.3. find()

作用:找出第一个满足指定条件返回 true 的元素,并立即停止查找;如果没找到,则返回 undefined

备注:一旦找到符合条件的第一个元素,将不再继续往下遍历

let arr = [2, 3, 2, 5, 7, 6]
let result = arr.find((item, index) => {
  // return item > 4  // 遍历数组arr, 一旦发现有第一个元素大于4,就把这个元素返回
  // 上面这行代码是简写方式,完整写法如下
  if(item > 4) {
    return true
  }
})
console.log(result)

注意:如果改变了 itemResult 内部的值,则 arr 里的对应元素,它的值也会被改变

6.3.9.4. findIndex()

作用:找出第一个满足指定条件返回true的元素索引,并立即停止遍历;如果没直到,则返回 -1

let arr = [2, 3, 2, 5, 7, 6];

let result = arr.findIndex((item, index) => {
    return item > 4; //遍历数组arr,一旦发现有第一个元素大于4,就把这个元素的index返回
});

console.log(result); // 3
6.3.9.5. every() / some()

every():对数组中每一项运行回调函数,如果都返回 true,every 就返回 true;如果有一项返回 false,则停止遍历,此方法返回 false

注意:every() 方法的返回值是 Boolean 值,参数是回调函数

some():对数组中每一项运行回调函数,只要有一个元素返回 true,则停止遍历,此方法返回 true

注意:some() 方法的返回值是 Boolean 值

every() 和 some() 的使用场景:

  • every():全部真,才为真。让你需要让数组中的每一个元素都满足指定条件时,那就使用 every()
  • some():一个真,则为真,点到位置。数组中只要有一个元素满足指定条件时,就停止遍历。那就使用 some()
var arr1 = ['千古', '宿敌', '南山忆', '素颜'];
var bool1 = arr1.every(function (item, index, array) {
    if (item.length > 2) {
        return false;
    }
    return true;
});
console.log(bool1); //输出结果:false。只要有一个元素的长度是超过两个字符的,就返回false

var arr2 = ['千古', '宿敌', '南山', '素颜'];
var bool2 = arr2.every(function (item, index, array) {
    if (item.length > 2) {
        return false;
    }
    return true;
});
console.log(bool2); //输出结果:true。因为每个元素的长度都是两个字符。

6.3.10. 遍历数组

6.3.10.1. for 循环遍历
const arr = ['千古一好', '许嵩', 'vue']
for(let i = 0; i < arr.length; i++) {
  console.log(arr[i]) 
}

// 输出
千古一好
许嵩
vue
6.3.10.2. forEach()

forEach() 这种遍历方法只支持 IE8 以上的浏览器。IE8 及以下的浏览器均不支持该方法。所以如果需要兼容 IE8,则不要使用 forEach(),改为使用 for 循环来遍历即可

// ES5语法
arr.forEach(function (currentItem, currentIndex, currentArray) {
	console.log(currentValue);
});

// ES6语法
arr.forEach((currentItem, currentIndex, currentArray) => {
	console.log(currentValue);
});

forEach() 方法需要一个函数作为参数。这种函数,是由我们创建但是不由我们调用的,我们称为回调函数

数组中有几个元素,该回调函数就会执行几次

回调函数中传递三个参数:

  • 参数1:当前正在遍历的元素
  • 参数2:当前正在遍历的元素的索引
  • 参数3:正在遍历的数组

注意,forEach() 没有返回值。也可以理解成:forEach() 的返回值是 undefined。如果你尝试newArray = currentArray.forEach()这种方式来接收,是达不到效果的

let myArr = ['王一', '王二', '王三'];

myArr.forEach((currentItem, currentIndex, currentArray) => {
    console.log('item:' + currentItem);
    console.log('index:' + currentIndex);
    console.log('arr:' + JSON.stringify(currentArray));
    console.log('----------');
});

// 输出
item:王一
index:0
arr:["王一","王二","王三"]
----------
item:王二
index:1
arr:["王一","王二","王三"]
----------
item:王三
index:2
arr:["王一","王二","王三"]
----------
6.3.10.3. for of

ES6 语法推出了 for of,可以用于循环遍历数组

for in 是专门用于遍历对象的。对象的属性是无序的(而数组是元素是有顺序的),for in 循环就是专门用于遍历无序的对象。所以,不要使用 for in 遍历数组

for(let key in obj) {
  console.log(key)
  console.log(obj.key)
}
6.3.10.4. map()

作用:对数组中的每一项进行加工

// ES5语法
const newArr =  arr.map(function (currentItem, currentIndex, currentArray) {
    return newItem;
});

// ES6语法
const newArr = arr.map((currentItem, currentIndex, currentArray) => {
    return newItem;
});

解释:对数组中每一项运行回调函数,返回该函数的结果,组成的新数组(返回的是加工后的新数组)。不会改变原数组

举例1:拷贝过程中改变数组元素的值

有一个已知的数组 arr1,要求让 arr1 中的每个元素的值都加 10,这里就可以用到 map() 方法

const arr1 = [1, 3, 6, 2, 5, 6]
const arr2 = arr1.map(item => {
  return item + 10 // 让 arr1 中的每个元素加10
})

console.log(arr2) // [11, 13, 16, 12, 15, 16]

举例2:实际开发中经常用到

将 A 数组中某个属性的值,存储到 B 数组中

const arr1 = [
  {name: '千古壹号', age: '28'},
  {name: '许嵩', age: '32'}
]

// 将数组 arr1 中的name 属性,存储到数组 arr2 中
const arr2 = arr1.map(item => item.name)

// 上面的代码是简写的方式,完整写法如下
const _arr1 = arr1.map(item => {
  return item.name
})

// 将数组 arr1 中的 name、age 这两个属性,改一下键的名字,存储到 arr3 中
const arr3 = arr1.map(item => ({
  myName: item.name,
  myAge: item.age
}))

console.log('arr1:' + JSON.stringify(arr1))
console.log('arr2:' + JSON.stringify(arr2))
console.log('arr3:' + JSON.stringify(arr3))


// 输出
arr1:[{"name":"千古壹号","age":"28"},{"name":"许嵩","age":"32"}]
arr2:["千古壹号","许嵩"]
arr3:[{"myName":"千古壹号","myAge":"28"},{"myName":"许嵩","myAge":"32"}]
6.3.10.5. filter()
const newArr = arr.filter((currentItem, currentIndex, currentArray) => {
  return true
})

解释:对数组中的每一项运行回调函数,该函数返回结果是 true 的项,将组成新的数组(返回值就是这个新数组)。不会改变原数组

作用:对数组进行过滤

举例1:找出数组 arr1 中大于 4 的元素,返回一个新的数组

let arr1 = [1, 3, 6, 2, 5, 6]
let arr2 = arr1.filter(item => {
  if(item > 4) {
    return true // 将 arr1 中大于 4 的元素返回,组成新的数组
  }
  return false
})

console.log(JSON.stringify(arr1)) // [1, 3, 6, 2, 5, 6]
console.log(JSON.stringify(arr2)) // [6, 5, 6]

上方代码更简洁的写法如下:

let arr1 = [1, 3, 6, 2, 5, 6];

let arr2 = arr1.filter(item => item > 4); // 将arr1中大于4的元素返回,组成新的数组

console.log(JSON.stringify(arr1)); // 打印结果:[1,3,6,2,5,6]
console.log(JSON.stringify(arr2)); // 打印结果:[6,5,6]

举例2:获取对象数组 arr1 中指定类型的对象,放到数组 arr2 中

const arr1 = [
  { name: '许嵩', type: '一线' },
  { name: '周杰伦', type: '退居二线'},
  { name: '邓紫棋', type: '一线' }
]

const arr2 = arr1.filter(item => item.type == '一线') // 筛选出一线歌手
console.log(JSON.stringify(arr2))

// 输出
[
    { name: '许嵩', type: '一线' },
    { name: '邓紫棋', type: '一线' },
];
6.3.10.6. redues()

reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。返回值是回调函数累计处理的结果

arr.reduce((previousvalue, currentValue, currentIndex, arr){
  
}, initialValue) 

参数解释:

  • previousValue:必填,上一次调用回调函数时的返回值
  • currentValue:必填,当前正在处理的数组元素
  • currentIndex:选填,当前真正处理的数组元素下标
  • arr:选填,调用 reduce() 方法的数组
  • initialValue:选填,可选的初始值(作为第一次调用回调函数时传给 previousValue 的值)

在以往的数组方法中,匿名的回调函数里是传三个参数:item、index、arr。但是在 reduce() 方法中,前面多传了一个参数 previousValue,这个参数的意思上一次调用回调函数时的返回值。第一次执行回调函数时,previousValue 没有值怎么办?initialValue 参数传给它

备注:绝大多数人在一开始接触 reduce() 的时候会很懵逼,但是没关系,有事没事多看几遍,自然就掌握了。如果能熟练使用 reduce() 的用法,将能代替很多其他的数组方法,并逐渐走上进阶之路,领先于他人

// 举例1:计算数组中所有元素项的总和
const arr = [2, 0, 1, 9, 6]
const total = arr.reduce((prev, item) => {
  return prev + item
})
console.log(total) // 18

// 举例2:统计某个元素出现的次数
function repeatCount(arr, value) {
  if(!arr || arr.length == 0) return 0

  return arr.reduce((totalCount, item) => {
    totalCount += item == value ? 1 : 0
    return totalCount
  })
}
let arr1 = [1, 2, 6, 5, 6, 1, 6]
console.log(totalCount(arr1, 6)) // 3

// 举例3:求元素的最大值
const arr = [2, 0, 1, 9, 6]
const maxValue = arr.reduce((prev, item) => {
  return prev > item ? prev : item
})
console.log(maxValue) // 9

参考链接:juejin.cn/post/684490…

7. 函数

函数:就是一些功能或语句的封装。在需要的时候,通过调用的形式,执行这些语句

补充:

  • 函数也是一个对象
  • 使用 typrof 检查一个函数时,会返回 function

函数的作用:

  • 一次定义,多次调用。将大量重复的语句抽取出来,写在函数里,以后需要这些语句时,可以直接调用函数,避免重复劳动
  • 简化代码,可读性更强,让编程模块化。高内聚、低耦合
console.log("你好");
sayHello();	// 调用函数
sayHello();	// 再调用一次函数



// 定义函数
function sayHello(){
	console.log("欢迎");
	console.log("welcome");

7.1. 函数的定义 / 声明

方式一:函数声明(命名函数)

// 语法
function 函数名([形参1, 形参2,....形参N]) { // 备注:语法中的括号,表示可选
  // 函数体语句
}

function sum(a, b) {
  return a + b
}

解释如下:

  • 函数名:命名规定和变量的命名规定一样,必须符合 JS 标识符的命名规则。只能是字母、数字、下划线、美元符号,不能以数字开头
  • 圆括号里,是形参列表,可选。即使没有形参,也必须书写圆括号
  • 大括号里,是函数体语句

方式二:函数表达式(匿名函数)

// 语法
const 变量名 = function([形参1, 形参2...形参N]) {
  // 语句
}

const fun2 = function() {
  console.log("我是匿名函数中封装的代码 ")
}

解释如下:

  • 上面的 fun2 是变量名,不是函数名
  • 函数表达式的声明方式跟声明变量类似,只不过变量里存的是值,而函数表达式里存的是函数
  • 函数表达式也可以传递参数

从方式二的举例中可以看出:所为的 "函数表达式",其实就是将匿名函数赋值给一个变量。因为,一个匿名函数终究还是要给它一个接收对象,进而方便地调用这个函数

方式三:使用构造函数 new Function()

const 变量名/函数名 = new Function('形参1', '形参2', '函数体')

注意,Function 里面的参数都必须是字符串格式。也就说,形参也必须放在字符串里;函数体也是放在字符串里包裹起来,放在 Function 的最后一个参数的位置

const fun3 = new Function('a', 'b', 'console.log('我是函数内部的内容');console.log(a + b)')
fun3(1, 2) 

// 输出
我是函数内部的内容
3

方式三的写法很少用,原因如下:

  • 不方便书写:写法过于啰嗦和麻烦
  • 执行效率较低:首先需要把字符串转换为 JS 代码,然后再执行

7.2. 函数的调用

调用函数即:执行函数体中的语句。函数必须要等到被调用时才执行

方式一:普通函数的调用

// 语法
函数名() // 最常用
函数名.call()

// 举例
function fn1() {
  console.log('我是函数体里面的内容')
}

fn1()
fn1.call()

方式二:通过对象的方法来调用

var obj = {
  a: 'qinguyihao',
  fn2: function() {
    console.log('千古壹号,永不止步!')
  }
}

obj.fn2() // 调用函数

如果一个函数是作为一个对象的属性保存,那么,我们称这个函数是这个对象的方法

方式三:立即执行函数

立即执行函数在定义后,会自动调用

(function() {
  console.log('我是立即执行函数')
})();

方式四:通过构造函数来调用

function Fun3() {
  console.log('千古壹号,永不止步!')
}

new Fun3()

方式五:绑定事件函数

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
    </head>
    <body>
        <div id="btn">我是按钮,请点击我</div>

        <script>
            var btn = document.getElementById('btn');
            //2.绑定事件
            btn.onclick = function() {
                console.log('点击按钮后,要做的事情');
            };
        </script>
    </body>
</html>

方式六:定时器函数

// 举例:每间隔一秒,将数字加 1
let num = 1
setInterval(function() {
  num++
  console.log(num)
}, 1000)

7.3. 函数的参数:形参和实参

函数的参数包括形参和实参。形参是函数内的一些待定值。再调用函数时,需传入这些参数的具体值(即实参)

可以再函数的()中指定一个或多个参数,也可以不指定参数。多个参数之间用英文逗号隔开

// a, b 是形参,表示待定值
function add(a, b) {
  const sum = a + b
  console.log(sum)
}

// 1, 2 是实参,表示传入的具体值。调用函数时,传入实参
add(1, 2)

形参:

  • 概念:形式上的参数。定义函数时传递的待定值(此时并不知道是什么值)
  • 声明形参相当于在函数内部声明了变量,但并不赋值。也可以说,形参的默认值是 undefined

实参:

  • 概念:实际的参数。调用函数时传递的具体值。实参将传递给函数中对应的形参
  • 函数的实参可以是任意数据类型。调用函数时,解析器不会检查实参类型,所以要注意,是否有可能会接收到非法的参数,如果有可能则需要对参数进行检查

实参和形参的个数,可以不同。调用函数时,解析器不会检查实参的数量

  • 如果实参个数 > 形参个数,则末尾的实参是多余的,不会被赋值,因为没有形参能接收它
  • 如果实参个数 < 形参个数,则末尾的形参是多余的,值是 undefined,因为它没有接收到实参(undefined 参与运算时,表达式的运算结果为 NaN)
function sum(a, b) {
  console.log(a + b)
} 

sum(1, 2) // 3
sum(1, 2, 3) // 3
sum(1) // NaN

7.4. 函数的返回值

函数体可以没有返回值,也可以根据需要加返回值。语法格式:return 函数的返回值

function sum(a, b) {
  return a + b
}

console.log(sum(3, 4)) // 7

return 关键字的作用既可以是终止函数,也可以给函数添加返回值

解释:

  1. return 后的返回值将会作为函数的执行结果返回,可以定义一个变量,来接收该返回值
  2. 在函数中,return 后的语句都不会执行。也就是说,函数在执行完 return 语句之后,会立即推出函数
  3. 如果 return 语句后不跟任何值,就相当于返回一个 undefined
  4. 如果函数中不写 return,则也会返回 undefined
  5. 返回值可以是任意的数据类型,可以是对象,也可以是函数
  6. return 只能返回一个值。如果用逗号隔开多个值,则以最后一个为准

7.5. 类数组对象 arguments

在调用函数时,浏览器每次都会传递两个隐含的参数:

  • 函数的上下文对象 this
  • 封装实参的对象 arguments
function foo() {
  console.log(arguments)
  console.log(typeof arguments)
}

foo(a, b)

函数内的 arguments 是一个类数组对象,里面存储的是它接收到的实参列表。所有函数都内置了一个 arguments 对象,有个讲究的地方是:只有函数才有 arguments

具体来说,在调用函数时,我们所传递的实参都会在 arguments 中保存。arguments 代表的是所有实参

arguments 的展示形式是一个伪数组。意思是,它和数组有点像,但它并不是数组。它具有以下特点:

  • 可以进行遍历;具有数组的 length 属性,可以获取长度
  • 可以通过索引(从0开始计数)存储数据、获取和操作数据。比如,我们可以通过索引访问某个实参
  • 不能调用数组的方法。比如 push()、pop() 等方法都没有

7.5.1. 返回函数实参的个数

arguments.length可以用来获取实参的个数

fn(2, 4);
fn(2, 4, 6);
fn(2, 4, 6, 8);

function fn(a, b) {
    console.log(arguments);
    console.log(fn.length); //获取形参的个数
    console.log(arguments.length); //获取实参的个数

    console.log('----------------');
}

此外,即使我们不定义形参,也可以通过 arguments 来获取实参:arguments[0] 表示第一个参数、arguments[1] 表示第二个实参,以此类推

举例:将传入的实参进行求和,如论实参的个数有多少

function foo() {
  let sum  = 0
  for(let i = 0; i < arguments.length; i++) {
    sum += arguments[i]
  }
  return sum
}

const result = foo(1, 2)
console.log(result)

7.5.2. 返回增在执行的函数

arguments 里边有一个属性叫做 callee,这个属性对应一个函数对象,就是当前正在指向的函数对象

function fun() {
  console.log(arguments.callee =  fun) // true
}

fun('hello')

在使用函数递归调用时,推荐使用 arguments.callee 代替函数名本身

7.5.3. 修改元素

arguments 还可以修改元素,但不能改变数组的长度

function fn(a, b) {
    arguments[0] = 99; // 将实参的第一个数改为99
    arguments.push(8); // 此方法不通过,因为无法增加元素
}

fn(2, 4);
fn(2, 4, 6);
fn(2, 4, 6, 8);

7.6. 递归函数

概念:如果一个函数在内部调用这个函数自身,这个函数就是递归函数。递归在数据解构和算法中经常用到,可以将很多复杂的数据模型拆解为简单问题尽心求解,一定要掌握

递归的要素:

  • 递归模式:把大问题拆解为小问题进行分析。也成为递归体
  • 边界条件:需要确定递归到何时结束。也称为递归出口

举例:求一个正整数的阶乘

普通写法:

function factorial(n) {
  let result = 1
  for(let i = 1; i <= n; i++) {
    result *= i
  }
  return result
}

console.log(factorial(5)) // 120

递归写法:

function factorial(n) {
  // 递归出口:如果计算1的阶乘,就不用递归了
  if(n == 1) return 1

  // 开始递归:如果当前这个 n 不是 1,就返回 n * (n - 1)
  return n * factorial(n - 1)
}

console.log(factorial(5)) // 120

7.8 立即执行函数

概念:函数定义完,就立即被调用,这种函数叫做立即执行函数

(function() {
    // 函数体
})();

// 或者
// 立即执行函数也可以传递参数
(function() {
    // 函数体
})(a, b);


// 举例
(function(a, b) {
    console.log("a=" + a)
    console.log("b=" + b)
})(123, 456)

7.9 作用域、变量提升

7.9.1 作用域概念和分类

概念:作用域是一个变量或函数的作用范围。作用域在函数定义时,就已经确定了

目的:为了提高程序的可靠性,同时减少命名冲突

在 JS 中,一共有两种作用域:(ES5中)

  • 全局作用域:作用于整个 script 标签内部,或者作用于一个独立的 JS 文件
  • 函数作用域(局部作用域):作用于函数内的代码环境

全局作用域 和 window对象:直接编写在 script 标签中的 JS 代码,都在全局作用域。全局作用域在页面打开时创建,在页面关闭时销毁

在全局作用域中有一个全局对象 window,它代表的是浏览器的窗口,由浏览器创建,我们可以直接使用:

  • 创建的变量都会作为 window 对象的属性保存。比如在全局作用域内写const a = 100,这里的 a 等价于 window.a
  • 创建的函数都会作为 window 对象的方法保存

7.9.2 作用域的访问关系

在内部作用域中可以访问外部作用域的变量,在外部作用域中无法访问到内部作用域的变量

const a = 'aaa'
function foo() {
    const b = 'bbb'
    console.log(a) // aaa。说明内层作用域可以访问外层作用域里的变量
}

foo()
console.log(b) // 报错。说明外层作用域无法访问内层作用域里的变量

7.9.3 变量的作用域

根据作用域的不同,变量可以分为两类:全局变量、局部变量

全局变量:

  • 在全局作用域下声明的变量,叫全局变量。在全局作用域的任何一个地方,都可以访问这个变量
  • 在全局作用域下声明的变量是全局变量

局部变量:

  • 定义在函数作用域的变量,叫局部变量。仅限函数内部访问这个变量
  • 函数的形参也是属于局部变量

从执行效率来:

  • 全局变量:只有浏览器关闭时才会被销毁,比较占内存
  • 局部变量:当其所在的代码块运行结束后,就会被销毁,比较节约内存

特殊情况:

  1. 无论是在函数外还是函数内,变量如果未经声明就赋值(意思是,如果不加 var / let /const),这个变量就是全局变量
// 声明变量时如果不加var/let/const,这个变量是全局变量。且可以被修改。
function fn() {
  a = 1;
}
fn(); // 这行代码必须要写,否则下一行代码执行时会报错:Uncaught ReferenceError: a is not defined
console.log(a); // 打印结果:1
  1. 如果局部变量和全局变量重名,则在函数内部,变量是以局部变量为准

7.9.4 作用域上下关系

当在函数作用域操作一个变量时,它会先在自身作用域中寻找,如果有就直接使用(就近原则)。如果没有则向上一级作用域中寻找,直到找到全局作用域;如果全局作用域中依然没有找到,则会报错 ReferenceError

在函数中要访问全局变量可以使用 window 对象(比如说,全局作用域和函数作用域都定义了变量 a,如果想访问全局变量,可以使用 window.a