# js 的介绍
## JS是什么
- JS是一种运行在客户端的脚本语言,最早是在HTML(标准通用标记语言下的一个应用)网页上使用,用来给HTML网页增加动态功能
- 浏览器就是一种运行JS脚本语言的客户端,JS的解释器被称为JS引擎,为浏览器的一部分
## JS简史 ==(JS历史,了解为主,感兴趣的可以多看看)==
- 在WEB日益发展的同时,网页的大小和复杂性不断增加,受制于网速的限制,为完成简单的表单验证而频繁地与服务器交换数据只会加重用户的负担,当时走在技术革新最前沿的 Netscape(网景)公司,决定着手开发一种客户端语言,用来处理这种简单的验证
- `简单概括:网页的功能越来越多,当时的网速又不行,一些常用的功能用当时的技术处理起来极其复杂,所以 网景 公司需要一个处理这些常用功能对性能影响更低的新语言`
- 1995 年,就职于 Netscape 公司的布兰登·艾奇(Brendan Eich),开始着手为即将于 1996 年 2 月发布的 Netscape Navigator 2 浏览器开发一种名为 LiveScript 的脚本语言。为了尽快完成 LiveScript 的开发,Netscape 与 Sun 公司建立了一个开发联盟。在 Netscape Navigator 2 正式发布前夕,Netscape 为了搭上媒体热炒 Java 的顺风车,临时把 LiveScript 改名为 JavaScript。
- `简单概括:网景的布兰登·艾奇研发了一个新语言叫做 LiveScript (只用了10天)当时网景和 Sun 公司有合作关系,Sun 公司刚好新推出了一个叫做 Java 的语言很火,网景为了蹭热度就把 LiveScript 改名为了 JavaScript`
- `所以 Java 和 JavaScript 没有关系,完全是两门不同的语言,它们的区别就像:周杰与周杰伦的区别一样`
- 由于 JavaScript 1.0 获得的关注度越来越高,1996 年,微软就在其 Internet Explorer 3 中加入了名为 JScript 的 JavaScript 实现,这意味着有了两个不同的 JavaScript 版本,导致 JavaScript 没有一个标准化的语法和特性。
- `简单概括:JavaScript 火了后,微软在自家的 IE 浏览器中弄一个和 JavaScript 一样的 JScript,所以就有了两个不同的 JavaScript,导致没有一个标准化的语法和特性了`
- 1997 年,以 JavaScript 1.1 为蓝本的建议被提交给了欧洲计算机制造商协会(ECMA,European Computer Manufacturers Association)。该协会指定 39 号技术委员会(TC39,Technical Committee #39)负责“ 标准化一种通用、跨平台、供应商中立的脚本语言的语法和语义”。TC39 由来自 Netscape、Sun、微软、Borland 及其他关注脚本语言发展的公司的程序员组成,他们经过数月的努力完成了 ECMA-262 标准,定义一种名为 ECMAScript 的新脚本语言。
- `为了解决上述问题 欧洲计算机制造商协会 ECMA 制定了一个标准,定义了名为 ECMAScript 的新脚本语言,可以把 ECMAScript 是 JS 的一个规范`
### ECMAScript 发展过程
- 1998 年 6 月,ECMAScript 2.0 版发布。
- 1999 年 12 月,ECMAScript 3.0 版发布,成为 JavaScript 的通行标准,得到了广泛支持。
- 2007 年 10 月,ECMAScript 4.0 版草案发布,对 3.0 版做了大幅升级。草案发布后,由于 4.0 版的目标过于激进,各方对于是否通过这个标准,发生了严重分歧。以 Yahoo、Microsoft、Google 为首的大公司,反对 JavaScript 的大幅升级,主张小幅改动;以 JavaScript 创造者 Brendan Eich 为首的 Mozilla 公司,则坚持当前的草案。
- 2008 年 7 月,由于各方分歧太大,争论过于激进,ECMA 开会决定,中止 ECMAScript 4.0 的开发,将其中涉及现有功能改善的一小部分,发布为 ECMAScript 3.1,而将其他激进的设想扩大范围,放入以后的版本,由于会议的气氛,该版本的项目代号起名为Harmony(和谐)。会后不久,ECMAScript 3.1 就改名为 ECMAScript 5。
- 2009 年 12 月,ECMAScript 5.0 版正式发布。Harmony 项目则一分为二,一些较为可行的设想定名为 JavaScript.next 继续开发,后来演变成 ECMAScript 6;一些不是很成熟的设想,则被视为 JavaScript.next.next,在更远的将来再考虑推出。
- 2011 年 6 月,ECMAscript 5.1 版发布,并且成为ISO国际标准(ISO/IEC 16262:2011)。
- 2013 年 3 月,ECMAScript 6 草案冻结,不再添加新功能。新的功能设想将被放到 ECMAScript 7。
- 2013 年 12 月,ECMAScript 6 草案发布。然后是 12 个月的讨论期,听取各方反馈。
- 2015 年 6 月 17 日,ECMAScript 6 发布正式版本,即ECMAScript 2015。
- 从 2015 年以后,ECMA 开始每年发布一个小规模增量更新的升级版本,新版本将按照ECMAScript+年份的形式发布,截止目前,已经发布的版本包含 ECMAScript 2016、ECMAScript 2017、ECMAScript 2018、ECMAScript 2019、ECMAScript 2020、ECMAScript 2021、ECMAScript 2022,后续将继续发布新的版本。
## JS 的组成
- JavaScript 是由 ECMAScript,DOM 和 BOM 三者组成的。
0. ECMAScript: 定义了JavaScript 的语法规范,描述了语言的基本语法和数据类型
0. DOM (Document Object Model): 文档对象模型
- 有一套成熟的可以操作 ==页面元素的 API==,通过 DOM 可以操作页面中的元素。比如: 增加个 div,减少个 div,给 div 换个位置等
- API:
- `application programming interface` 的简写;
- 翻译:应用程序编程接口;大白话:`别人写好的代码,或者编译好的程序,提供给你使用,就叫作api`
0. BOM (Browser Object Model): 浏览器对象模型
- 有一套成熟的可以操作 ==浏览器的 API==,通过 BOM 可以操作浏览器。比如: 弹出框、浏览器跳转、获取分辨率等
## js 的三种书写方式
- 行内式
```
<input type="button" value="按钮" οnclick="alert('hello world')" />
```
- 内嵌式
```
<body>
<script>
alert('hello world')
</script>
</body>
```
- 外链式
```
<script src="main.js"></script>
```
## js 定义变量
### 什么是变量?
- 变量是计算机内存中存储数据的标识符,根据变量名称可以获取到内存中存储的数据;
- 翻译为大白话,我有一个东西需要暂时存储起来,比如说就是一个数字 100,我把它存放到了变量 a 中,我后续想要使用他的时候就可以直接通过变量 a 获取到数字 100
- 变量其实就相当于一个容器,内部可以存储任意类型的数据,使用变量时,用的是内部存储的数据。
### 为什么要定义变量?
使用变量可以方便的获取或者修改内存中的数据
### 如何定义变量
使用一个 var 的关键字进行定义,后面必须加一个空格,空格后面自定义变量名
```
var a;
var b;
var c;
```
### 变量赋值
- 变量定义之后,初始时没有进行赋值,内部有一个默认存储的值叫 undefined(未定义) 表示内部未赋值,但可以存储数据了
- 变量赋值的方式:通过等号 = 赋值,等号右边的值赋值给左边的变量 ==(等号在 JS 中叫做 赋值号;书写时,等号 = 两侧习惯书写一个空格)==
```
// 变量定义
var a;
// 变量赋值
a = 1;
// 变量定义并赋值
var b = 2;
```
### 使用变量
直接写变量名即可使用变量;变量在使用前,必须先有定义,如果没有定义,会出现引用错误
### 课堂案例
0. 交换变量, 需求: 交换两个变量所保存的值
### 变量命名规则(必须遵守,不遵守会报错)
- 由字母、数字、下划线、$符号组成,不能以数字开头
- 字母区分大小写
- 不能是关键字和保留字
- 关键字指的是js中有特殊功能的小词语,比如var、for等
- 保留字指的是现在没有特殊功能,但是将来新语法中有可能作为关键字使用
### 变量命名规范(建议遵守的,不遵守不会报错)
- 变量名必须有意义
- 遵守驼峰命名法
## js 数据类型
`JS中的值,无论是字面量还是变量,都有明确的类型`
- 数据类型分类(以基本数据类型为主)
- Number 数字类型
- 不区分整数、浮点数、特殊值,都是 Number 类型
- String 字符串类型
- 所有的字符串都是 String 类型
- undefined undefined类型
- ndefined本身就是一个数据,表示未定义,变量只声明不赋值的时候,值默认是 undefined
- Boolean 布尔类型
- Boolean 字面量:只有 true 和 false 两个字面量的值,必须是小写字母
- 计算机内部存储:true 为 1,false 为 0
- null null类型
- null 本身就是一个数据
- 从逻辑角度,null 值表示一个空对象指针
- 如果定义的变量准备在将来用于保存对象,最好该变量初始化为 null
- Object 对象类型(后续课程详细讲解)
### 数据类型检测
- 为什么要有数据类型检测?
- JS语言是一门动态类型的语言,变量并没有一个单独的数据类型,而是会随着内部存储数据的变化,数据类型也会发生变化
- 变量的数据类型,与内部存储数据有关
- 将来使用变量时,需要知道内部存储的数据是什么类型,避免程序出错
- 使用 typeof 的方法进行数据检测
- 检测方式:在 typeof 后面加小括号执行,将要检测的数据放在小括号内部
### 数据类型转换(转数值 / 转字符串 / 转布尔)
#### 转数值
0. Number(数据)方法
- 转型函数Number()可以用于任何数据类型,将其他数据类型转为数字
- 字符串:纯数字字符串转为对应数字,空字符串和空白字符串转为0,非空非纯数字字符串转为NaN
- 布尔值:true转为1,false转为0
- undefined:转为NaN
- null:转为0
0. parseInt()方法:字符串转整数方法
- 对浮点数进行取整操作
- 对数字取整直接舍弃小数部分,只保留整数
- 将字符串转为整数数字
- 将字符串转为整数数字,也包含取整功能
- 字符串中,必须是纯数字字符串或者数字字符开头的字符串,才能转换为正常数字,且只取整数部分
- 如果不是数字打头的字符串,会转换为NaN
0. parseFloat()方法:字符串转浮点数方法
- 将字符串转为浮点数数字
- 满足浮点数数字字符必须在字符串开始,如果不在开始返回值都是NaN
#### 转字符串
0. `变量.toString()`方法
0. `String(变量)`方法,有些值没有`toString()`,这个时候可以使用`String()`。比如`undefined`和`null`
0. + 号拼接字符串方式
- num + “” ,当 + 两边一个操作符是字符串类型,一个操作符是其他类型的时候,会先把其他类型转换成字符串在进行字符串拼接,返回字符串
#### 转布尔
0. Boolean(变量)方法
- 转型函数Boolean()可以用于任何数据类型,将其他数据类型转为布尔类型的值
- 转为false:NaN、0、“”空字符串、null、undefined
- 转为true:非0 非NaN数字、非空字符串
## js 运算符
- 什么是运算符?
- 也叫操作符,是 JS 中发起运算最简单的方式,例如: 5 + 6
- 表达式的组成包含操作数和操作符,表达式会得到一个结果,然后用结果参与程序
### JS 运算符分类
- 算数运算符( `+` / `-` / `*` / `/` / `%` )
- 运算顺序与数学中的运算顺序一致,先乘除取余,后加减,有小括号先算小括号
- 赋值运算符( `=` / `+=` / `-=` / `*=` / `/=` / `%=` )
- 比较运算符( `>` / `<` / `>=` / `<=` / `==` / `===` / `!=` / `!==` )
- 比较结果只会返回一个布尔类型值,true 或者 false
- 逻辑运算符( `&&` / `||` / `!` )
- 自增自减运算符( `++` / `--` )
# 什么是分支语句
- JS 代码是从上往下按照顺序依次执行的, 从第一行代码一直执行到最后一行
- 如果有一种情况是我有两段代码, 我只需要执行其中一个即可, 那么就可以用到分支语句
- 简单来说: 分支语句就是根据我们设定好的条件来决定要不要执行某些代码
## if 分支语句
- if 分支语句的基本书写
- 语法: `if (条件) { 想要执行的代码 }`
```
if (true) {
console.log('因为条件是 true 所以 我可以打印出来')
}
/**
* 语法分析:
if: 关键字,表明后续是一段if分支语句
(): 小括号内部填写条件, 用于决定是否执行后续内容
{}: 花括号内部填写要执行的分支代码, 如果条件为真, 则会执行
*/
```
- if ... else 语句的基本书写
- 语法: `if (条件) { 条件为真时 执行的代码 } else { 条件为假时 执行的代码 }`
```
if (true) {
console.log('因为条件是 true 所以 我可以打印出来')
} else {
console.log('因为条件是 true 所以 我不会被打印')
}
if (false) {
console.log('因为条件是 false 所以 我不会被打印')
} else {
console.log('因为条件是 false 所以 我可以打印出来')
}
/**
* 语法分析:
if: 关键字,表明后续是一段if分支语句
(): 小括号内部填写条件, 用于决定是否执行后续内容
{}: 花括号内部填写要执行的分支代码, 如果条件为真, 则会执行
else: 表明前边条件都为false时,执行后续的花括号内的代码
*/
```
- if 嵌套语句的基本书写
- 可以通过 `if` 和 `else if` 来设置多个条件进行判断
- 语法: `if(条件1){条件1为true时执行} else if (条件2){条件2为true时执行}`
- 会从头开始依次判断条件
- 如果第一个条件为 true 了, 那么就会执行后面的 {} 的内容
- 如果第一个条件为 false, 那么就会判断第二个条件
- 如果第二个条件也为 false, 那么会依次往后执行
- 注意
- 多个 {} 只会有一个被执行, 一旦有一个条件为 true 了, 后面的就不再判断了
- 如果所有的 条件都为 false, 那么会执行最后的 else {} 分支
- 如果所有的条件都为 false, 且没有最后的 else {} 分支, 那么当前 if 分支不会有任何代码段会被执行
- 尽可能多的使用案例帮助学员理解和使用
## switch 分支语句
- switch 也是 条件判断语句的一种, 是对于某一个变量的判断(全等的判断,数据类型不一致时会导致判断失败)
- switch 分支语句的基本书写
```
switch (要判断的变量) {
case 情况1:
情况1执行的代码
break;
case 情况2:
情况2执行的代码
break;
case 情况3:
情况3执行的代码
break;
default:
上述情况都不满足时执行的代码
}
```
- switch 通常是判断某一个变量等于某一个值的时候使用, 比如用户输入一个数字显示今天周几, 输入一个数字显示今天是几月
- switch 穿透语句的基本书写
- 从第一个满足条件的 case 开始
- 如果没有 break, 后面的条件不在判断, 直接执行代码
- 直到遇到一个 break 或者 switch 结束
```
switch (2) {
case 0:
console.log('星期日')
case 1:
console.log('星期一')
case 2:
console.log('星期二')
case 3:
console.log('星期三')
case 4:
console.log('星期四')
case 5:
console.log('星期五')
case 6:
console.log('星期六')
default:
console.log('请填写一个 0 ~ 6 的数字')
}
```
- 尽可能多的使用案例帮助学员理解和使用
## 三元表达式
- 三元表达式(三元运算/三目运算/三目)
- 语法
- `条件 ? 真-执行的代码 : 假-执行的代码`
- 意义
- 对 if else 语句的简写
- 注意
- 执行代码的位置只能写一句话
- 常用场景
- 利用三目执行代码(对if else 的优化)
- 利用三目给变量赋值
```
// 执行代码
var num = 5
num % 2 == 0 ? console.log('num 为偶数') : console.log('num 为奇数')
// 给变量赋值
var num = 1 // 我们暂且规定 num == 1 时代表为男性, num == 2 时代表为女性
var gender = num == 1 ? '男' : '女'
console.log(gender)
```
# 循环语句
- 什么是循环语句
- 根据某些给出的条件,重复执行同一段代码
- 循环必须要有某些固定的内容组成
- 初始化
- 条件判断
- 要执行的代码
- 自身改变
## while 循环语句
- while,中文叫 当…时,其实就是当条件满足时就执行代码,一旦不满足了就不执行了
- 语法 `while (条件) { 满足条件就会执行 }`
- 因为满足条件就执行, 所以我们写的时候一定要注意, 就是设定一个边界值, 不然就一直循环下去了, 简称: 死循环, 电脑卡死的时间根据电脑性能有所不同, 建议不要尝试
```
// 1. 初始化条件
var num = 0
// 条件判断
while (num < 10) {
// 3. 要执行的代码
console.log('当前 num 的值是: ', num)
// 4. 自身改变
num += 1 // 如果没有这行代码, 那么自身就没有改变, 条件将会一直满足, 代码也就会一直执行
}
```
## do...while 循环语句
- 是一个和 while 循环类似的循环
- while 会先进行条件判断, 满足就执行, 不满足就直接不执行了
- do...while 的循环是 先不管条件, 先执行一次, 然后再开始进行条件判断
- 语法: `do { 要执行的代码 } while (条件)`
```
// 1. 初始化
var num = 10;
do {
// 3. 要执行的代码
console.log('我执行了一次')
// 4. 自身改变
num += 1
// 2. 条件判断
} while (num < 10)
```
## for 循环语句
- 和 while do while 循环都不太一样的一种循环结构
- 但道理是和其他两种一样的, 都是循环执行代码的
- 语法: `for (var i = 0; i < 10; i++) { 要执行的代码 }`
```
// 把初始化, 条件判断, 自身改变, 都统一写在了一起
for (var i = 1; i <= 10; i++) {
console.log(i) // 会打印 1~10
}
// 初次使用时会觉得有一点繁琐与不习惯, 但是用起来比较好用
```
## 流程控制语句
- break 关键字的应用
- 在循环没有进行完毕的时候, 因为我设置的条件满足了, 就直接提前终止循环
- 举个例子: 我要吃五个包子, 我吃了三个了, 这时候吃饱了吃不下去了, 我就停止了继续吃包子这件事
- 那么此时要停止循环, 就可以直接使用 break 关键字
```
for (var i = 1; i <= 5; i++) {
// 每循环一次,就吃一个包子
console.log('我吃了一个包子')
// 当 i == 3 的时候, 条件为 true, 执行了 {} 里面的代码终止循环, 循环就不会继续向下执行了, 也就没有 4 和 5 了
if (i == 3) {
break
}
}
```
- continue 关键字的应用
- 在循环中, 把循环的本次跳过去, 继续执行后续的循环
- 举个例子: 还是吃五个包子, 要吃第三个的时候, 第三个掉地上了, 所以就不吃第三个了, 继续吃第四个和第五个
- 跳过本次循环, 就可以使用 continue 关键字
```
for (var i = 1; i <= 5; i++) {
// 当 i == 3 的时候, 执行 {} 里面的代码
// {} 里面有 continue, 那么本次循环后面的代码就都不执行了
// 自动算作 i 为 3 的这一次结束了, 去继续执行 i == 4 的那次循环
if (i == 3) {
console.log('这是第三个包子, 掉地下了, 我不吃了')
continue
}
console.log('我吃了一个包子')
}
```
## 循环嵌套的书写
- 循环嵌套其实就是一个循环内部又写了一个循环
```
for (var i = 0; i < 3; i++) { // 这个循环称为 外层循环
for (var k = 1; k < 4; k++) { // 这个循环称为 内层循环
/**
* 重点!!!
* 两个循环的循环变量不能相同, 会有冲突
*/
}
}
```
# 函数的概念
- 什么是函数?
- 首先明确一点,和我们数学中的函数是两个概念
- 在 JS 中,函数可以理解为将一段在程序中多次出现的代码封装起来的盒子,以便在多个地方调用执行
- 换句话说:函数就是一个内部封装了部分代码的盒子,可以在多个位置被调用
- 函数的使用
- 创建函数(定义函数)
- 调用函数
# 函数的定义
- 声明式定义
```
function fn() {
}
/**
* 分析:
* function:声明函数的关键字,代表接下来这段代码是一个函数
* fn:函数名,调用函数时需要使用,函数名自定义,符合命名规范和见名知意即可(!** 匿名函数时可以不写)
* ():小括号内存放函数的参数(后续讲解)
* {}:存放函数代码,调用函数时,想执行的代码都写在内部
*/
```
- 赋值式定义
```
var fn = function () {
}
```
# 函数的调用
```
function fn1() {
}
var fn2 = function () {
}
fn1()
fn2()
```
## 声明式与赋值式的区别
- 书写区别
- 调用区别
```
// 声明式
fn1() // 可以执行
function fn1(){
// 函数代码。。。。。。
}
fn1() // 可以执行
fn2() // 不可以执行(!** 声明时编程,其实就是相当于将一个函数赋值给一个变量,会有变量的声明提升,所以在变量声明前调用时,根据变量声明提升的规则,此时变量为 undefined ,所以不能被调用)
var fn2 = function () {
// 函数代码。。。。。。。。。
}
fn2() // 可以执行
```
# 函数的参数
- 参数是什么?
- 如果没有参数,那么函数的执行功能是固定的,写好函数后内部内容将不会变
- 比如:函数内部的代码为 1 + 1,那么始终执行时始终都是 1 + 1,如果此时想要计算 1 + 2 的值,需要重新封装一个 1+2 的函数
- 参数在哪里?如何使用
- 书写函数时有一个 () 内部就是书写参数的,函数分为两种,形参---实参
- 形参和实参的区别
- 形参:在函数声明时 function 后边的()内书写,每写一个参数,就相当于在函数内部创建一个变量,其值为函数调用时传递的值,只能在函数内部使用,不能在外部使用
- 实参:顾名思义,实际的参数,也就是函数在调用时传递的参数
```
function num () {
console.log(1 + 1)
}
num() // 打印值为 1+1
function num (a, b) { // 此处 a b 为形参
console.log(a + b)
}
num(1, 1) // 此处为 实参,分别传递给 a 和 b
num(1, 2) // 此处打印值为 1 + 2
```
- 注意
- 函数的形参与实参是按照从左到右的顺序一一对应的
```
// 少传参数
function num1(a, b, c, d) {
console.log(a,b,c,d)
}
num1(1, 2, 3, 4) // 打印1,2,3,4
num1(1, 2, 4) // 打印1,2,4,undefined
num1(4) // 打印4,undefined,undefined,undefined
// 多传参数
function num2 (a) {
console.log(a)
}
num2(1, 2) // 打印 1
num2(1) // 打印 1
```
# 函数的返回值
- 返回值是什么?有什么作用
- 函数内部默认有一个 return 他的值就是函数的返回值,如果函数内部不写 return 那么函数默认在函数体内部最底部返回一个 undefined
- 如果不想返回 undefined 需要手动在函数内部写上 return 并且跟上需要返回的值
- 可以中断函数(后续通过代码演示)
```
function num (a, b) {
a+b
}
var ab = num(1,2)
console.log(ab)
```
```
function num (a, b) {
// return a + b
console.log('函数内部执行的 a + b =',a+b)
}
var ab = num(1,2)
console.log('函数外部执行的 a + b =',ab)
```
# 函数的优点
- 函数其实就是将一段需要多次调用的代码抽离出来封装到一个盒子内部,方便在多个地方调用时简单化代码
- 抽离公共代码,项目代码整体更加简洁
- 方便(复用),在需要的地方直接 函数名 + 小括号 调用即可
# 函数的预解析
- 什么是预解析
- 在代码运行前,先全部分析一遍代码,这个行为叫做预解析(预解释)
- 预解析的内容
- 声明式函数定义
- var 声明变量
```
// 正常书写代码
fn()
console.log(a)
function fn() {
console.log(100)
}
var a = 100
// 预解析后可以理解为
function fn() {
console.log(100)
}
var a
fn()
console.log(a)
a = 100
```
# 作用域
- 什么是作用域?
- 就是变量可以起作用的范围
- 作用域内容
- 全局作用域(直接在 script 内书写的代码)
- 局部作用域(在 JS 中, 只有函数能够创建局部作用域)
```
function fn() {
var sum = '我是在函数 fn 内部创建的变量, 我是局部变量, 所以我只能在当前函数内使用'
var abc123 = '我是在 fn 函数内部创建的局部变量'
console.log(sum)
}
fn()
// console.log(sum) // 这里因为超出了这个变量的使用区间, 所以会 报错
var abc = '我是一个全局变量 abc' // 创建一个全局变量 abc
console.log(window)
```
# 作用域链
- 什么是作用域链?
- 作用域链就是在访问一个变量的时候, 如果当前作用域内没有会去自己的父级作用域, 也就是上一层作用域内查找, 如果找到就直接使用, 如果没有找到继续向上层查找直到查找到 最顶层的全局作用域, 如果找到了直接使用, 如果没找到 报错提示变量不存在(未定义)我们将这个一层一层向上查找的规律, 叫做作用域链
```
// var num = 999
// function fn1() {
// var num = 100
// function fn2() {
// console.log(num)
// /**
// * 打印的值 为 100
// * 1. 先在当前作用域内, 也就是 fn2 函数内部开始查找变量 num, 然后发现 当前作用域内 没有这个变量
// * 所以会去自己的父级的作用域内查找(也就是 fn1 这个函数内部)
// * 2. 来到了自己父级内部查找, 此时找到了一个变量 num 他的值 为 100, 然后直接使用这个变量 并停止查找
// */
// }
// fn2()
// }
// fn1()
// var num = 999
// function fn1() {
// function fn2() {
// console.log(num)
// /**
// * 打印的值 为 999
// * 1. 先在当前作用域内, 也就是 fn2 函数内部开始查找变量 num, 然后发现 当前作用域内 没有这个变量
// * 所以会去自己的父级的作用域内查找(也就是 fn1 这个函数内部)
// * 2. 来到了自己父级内部查找, 发现并没有一个叫做 num 的变量, 然后继续向上层查找, 也就是 全局作用域 内
// *
// * 3. 来到全局作用域内查找的时候 发现了一个叫做 num 的变量, 值为 999, 然后停止查找, 直接使用该变量
// */
// }
// fn2()
// }
// fn1()
// function fn1() {
// function fn2() {
// console.log(num)
// /**
// * num 找不到, 所以会报错
// *
// * 1. 先在当前作用域内查找, 也就是 fn2 内部, 发现没有, 去自己的父级查找, 也就是 fn1 内部
// * 2. 来到了 fn1 内部查找, 发现没有, 去自己的父级查找, 也就是 全局作用域
// * 3. 来到了全局作用域内查找, 发现还是没有, 然后停止查找, 返回一个 num 未定义的报错
// *
// * 4. 虽然 fn2 作用域内的子级作用域内(fn3函数内部) 有一个变量叫做 num 但是根据 作用域链的访问规则
// * 我们并不会去这个 作用域内查找, 因为 作用域只会逐层向上查找, 并不会向下查找
// */
// function fn3() {
// var num = 666
// }
// fn3()
// }
// fn2()
// }
// fn1()
```
### 作用域链的赋值规则
在给变量赋值的时候, 首先会去当前作用域查找, 如果有直接赋值, 并停止查找如果没有, 会去自己的父级查找, 在父级找到直接修改值然后停止查找, 如果没有继续向自己的父级查找, 直到找到全局作用域 在全局作用域内, 找到直接赋值修改他的值, 如果没有找到, 那么会在全局作用域创建一个变量, 并赋值
```
// function fn1() {
// function fn2() {
// num = 100
// }
// fn2()
// }
// fn1()
// console.log(num) // 100
// function fn1() {
// var num = 999
// function fn2() {
// num = 100
// /**
// * 在当前作用域内查找 num 发现没有, 会去自己的父级作用域内查找, 也就是 fn1 函数内部
// *
// * 在 fn1 函数内部发现一个变量 num 然后值为 999 我们会对这个变量做一个重新赋值的操作
// *
// * 也就是将他的值 重新修改为 100
// */
// }
// fn2()
// console.log(num) // 100
// }
// fn1()
// console.log(num) // 未定义
var num = 666
function fn1() {
var num = 999
function fn2() {
num = 100
/**
* 在当前作用域内查找 num 发现没有, 会去自己的父级作用域内查找, 也就是 fn1 函数内部
*
* 在 fn1 函数内部发现一个变量 num 然后值为 999 我们会对这个变量做一个重新赋值的操作
*
* 也就是将他的值 重新修改为 100
*/
}
fn2()
}
fn1()
console.log(num) // 666
var num = 666
function fn1() {
function fn2() {
num = 100
/**
* 在当前作用域内查找 num 发现没有, 会去自己的父级作用域内查找, 也就是 fn1 函数内部
*
* 在 fn1 函数内部, 发现没有 这个变量, 继续去自己的父级作用域查找, 也就是 全局作用域
*
* 在全局作用域发现了一个变量 叫做 num, 他的值是 666, 我们将这个变量重新赋值为 100
*/
}
fn2()
}
fn1()
console.log(num) // 100
```
# 递归函数
- 本质上还是一个函数
- 当一个函数在函数的内部, 调用了 自身, 那么就算是一个 所谓的 递归函数(只不过有点小缺陷)
```
// function fn(n) {
// /**
// * 计算 4 的阶乘
// *
// * 4 的阶乘: 4 * 3的阶乘
// */
// // return 4 * fn(3)
// return n * fn(n - 1)
// }
// var sum = fn(4)
// console.log(sum) // 此时打印的值 为 4 的阶乘
/**
* fn 函数 需要一个参数, 传入一个参数后 会得到这个参数的 阶乘结果
*
* fn(100) -> 100 的阶乘
* fn(99) -> 99 的阶乘
* ....
* fn(4) -> 4 的阶乘
*/
/**
* function fn(n) {
* return n * fn(n - 1)
* }
* fn(4)
*
*
* 第一次调用, 传递 参数 4
* 形参 n === 4
* 函数体 return n * fn(n - 1) -> return 4 * fn(3)
*
* 计算 fn(3) -> 计算 3 的阶乘
* 调用的时候 传参是 3
* 形参 n === 3
* 函数体 return n * fn(n - 1) -> return 3 * fn(2)
*
* 计算 fn(2) -> 计算 2 的阶乘
* 调用的时候 传参是 2
* 形参 n === 2
* 函数体 return n * fn(n - 1) -> return 2 * fn(1)
*
* 计算 fn(1) -> 计算 1 的阶乘
* 调用的时候 传参是 1
* 形参 n === 1
* 函数体 return n * fn(n - 1) -> return 1 * fn(0)
*
* 计算 fn(0) -> 计算 0 的阶乘
* 调用的时候 传参是 0
* 形参 n === 0
* 函数体 return n * fn(n - 1) -> return 0 * fn(-1)
*
* .... 永无止境, 相当于写了死循环
*
*
* 100 的阶乘 100 * 99 * 98 * 97 ..... * 3 * 2 * 1
*/
function fn(n) {
if (n === 1) {
// 说明此时想要计算 1 的阶乘, 那么我直接将 1 的阶乘的结果 return 出去
return 1
}
return n * fn(n - 1)
}
var sum = fn(4)
console.log(sum) // 24
var sum1 = fn(10)
console.log(sum1)
/**
* 第一次调用 传递的参数为 4
* 形参 n === 4
* 函数体 1. if 分支 2. return 递归调用
* 此时 分支语句 不会执行, 开始 执行 return 递归调用
* return n * fn(n - 1) return 4 * fn(3) == 24
*
* 计算 fn(3) === 6
* 传递的 形参 n === 3
* 此时分支语句不会执行, 开始执行 return 递归调用
* return n * fn(n - 1) return 3 * fn(2) === 6
*
* 计算 fn(2) === 2
* 传递的 形参 n === 2
* 此时分支语句不会执行, 开始执行 return 递归调用
* return n * fn(n - 1) return 2 * fn(1) === 2
*
* 计算 fn(1) === 1
* 传递的 形参 n === 1
* 此时 分支语句 判断成功, 所以直接 return 1 中断函数的递归
*/
```
# 对象
- 什么是对象
- JS 中的一种数据格式, 对象在 JS 中的数据类型数据为: 引用数据类型(也有喜欢叫 复杂数据类型)
- 如何向变量中 存储一个 叫做 对象的数据呢?
- 语法1: var obj = {键值对}
- 键值对 -> key: value 如果对象内部有多个 键值对, 那么需要使用 逗号 间隔
```
var obj = {
a: 100,
b: 200,
c: 300,
q: 666
}
console.log(obj)
/**
* 什么是键值对 (拿 对象 obj 为例子)
* 在这个对象中, a 为 key, 100 为对应 value
* 另外一种叫法: a 为 键, 100 为 对应的 值
* 另外一种叫法: a 为 属性名, 100 为 对应的 属性值
*
* b 为 key, 200 为对应的 value
* b 为 键, 200 为对应的 值
* b 为 属性名, 200 为对应的属性值
*/
```
# 创建对象
- 字面量的形式 (使用频率比较高)
- 语法: var obj = {键值对}
- 内置构造函数的创建
- 语法1: var obj1 = new Object() // 创建空对象
- 语法2: var obj1 = new Object({a: 1, b: 2}) // 创建一个具有属性或者说具有键值对的 对象
- 注意: new Object 的 O 是大写的, 不是小写
### JS 中创建对象的方式有哪些?(面试题)
- 目前暂时是 两种
- 1. 字面量的方式
- 2. 内置构造函数的方式
```
// 1. 字面量的方式
var obj = {
b: 2,
a: 1,
c: 0,
e: true,
d: 'abc',
q: undefined,
w: null,
r: function () {
console.log('我是 obj 对象 内 r 这个属性 的 属性值, 我是一个函数')
}
}
// console.log(obj)
// 2. 内置构造函数的方式
var obj1 = new Object({
a: 1,
q: 777,
w: 666,
e: 'qwer'
})
console.log(obj1)
```
# 对象的操作(增删改查)
## 换句话说, 就是对内部的属性的操作
- 分为两种方式
- 1. 点语法
- 2. 中括号语法(数组语法)
- 一般来说, 大部分场景使用点语法更简单, 有一些特殊场景只能使用 中括号语法
```
var obj = {
a: 1,
b: 'qwe',
c: true
}
console.log('原始对象: ', obj)
// 1. 点语法---查询: 获取到 对象内部 某一个属性对应的属性值
// console.log(obj.a) // 1
// console.log(obj.b) // 'qwe'
// 2. 点语法---新增: 向对象内部新增一个属性
// obj.q = 'QF001'
// obj.w = 'QF666'
// console.log('最新的对象: ', obj)
// 3. 点语法---修改: 修改对象内部某一个属性对应的属性值
// obj.c = false
// obj.b = 'QF001'
// console.log('修改属性后的对象: ', obj)
// 4. 点语法---删除: 删除对象内部的某一个属性
// delete obj.a
// console.log('删除属性后的 obj: ', obj)
// 5. 中括号语法---查询
// console.log(obj['a']) // 1
// console.log(obj['c']) // true
// 6. 中括号语法---新增
// obj['r = 123']
// obj['r'] = 123
// obj['w'] = 456
// console.log('新增后的 obj: ', obj)
// 7. 中括号语法---修改
// obj['b'] = 'QF001'
// obj['c'] = false
// console.log('修改后的 obj: ', obj)
// 8. 中括号语法的删除
// delete obj['a']
// console.log('删除属性后的 obj: ', obj)
```
### 一般大部分情况下, 点语法与中括号语法, 作用相同, 怎么选择都可以
- 特殊情况下我们需要使用中括号语法
- 1. 对象的属性名, 有纯数字或者特殊符号, 这个时候, 就只能使用中括号语法
- 2. 如果涉及变量相关的时候, 也需要使用中括号
```
// 特殊情况1
// var obj = {
// 100: '我的属性名是 纯数字 100',
// '!': '我的属性名是 特殊符号 !',
// '@': '我的属性名是 特殊符号 @'
// }
// // 此时不能使用点语法, 可以使用中括号语法
// // console.log(obj.100)
// // console.log(obj.!)
// // console.log(obj.@)
// console.log(obj[100])
// console.log(obj['100'])
// // console.log(obj[!]) // 有问题, 需要将 特殊符号用引号包裹
// console.log(obj['!'])
// console.log(obj['@'])
// 特殊情况2
var obj = {
a: 1,
b: 2,
name: 'QF001'
}
var myName = 'name'
// console.log(obj.myName)
/**
* obj.myName 我们猜想他这个 myName 是一个变量, 所以实际的应该是 obj.'name', 所以应该打印 obj 的name属性
*
* 但实际的输出结果是 undefined
*
*
* 原因:
* 因为 对象的 点语法, 会将 点 后边的 字符 当成一个 字符串去使用, 而不会当成变量
*
* 拿 obj.myName 举例
* 他会将 myName 当成一个 字符串 去 对象中查找, 有没有一个叫做 myName 的属性名
* 找完之后 发现对象中没有这个属性名, 所以打印的值 为 undefined
*/
console.log(obj[myName]) // 'QF001'
/**
* 中括号语法, 内部书写的字符, 如果不加引号, 会把它当成变量去使用, 所以找到实际的值之后, myName 这个变量对应的值为 'name'
*
* 所以 obj[myName] 相当于 写了 obj['name']
*
* 所以会去 对象 obj 中 找一个 叫做 name 的属性名, 找到之后 打印在页面
*/
```
- 对象关于 key 的要求 或者说 对于属性名的要求 或者说 对于 键值对的键 的要求
- 推荐使用符合变量命名规则与规范的名字
- 对象的 key 也可以使用 纯数字 来当作键名(属性名/key)
- 可以使用任何特殊符号 (但是需要使用引号包裹)
```
var obj = {
a: 1,
q: 'qwe',
t: true,
u: undefined
}
```
# for...in 循环遍历对象
- for...in: 一个循环语句
- 对象: 一种数据格式
- 遍历: 一般我们会说 '遍历对象' / '遍历数组'
- '遍历对象' 想办法拿到对象内部所有的 属性名与属性值
- 语法: for (var i in 要遍历的对象) { 循环要执行的代码 }
```
for (var i in obj) {
// console.log(1)
// console.log(i) // a q t u 这四个 是 对象 obj 的 所有 属性名 / 键名 / key
// 需求: 打印对象所有的属性值
// console.log(obj) // 此时打印的是完整的对象, 所以不合适
// console.log(obj.i) // undefined 点语法会将后边的字符当成字符串来使用, 而不是当成变量, 如果相当变量来使用, 那么应该使用中括号语法
console.log(obj[i]) // 该对象的所有属性值
}
```
# 数组
- 什么是数组?
- 数组 是一种数据类型, 他也是属于 引用数据类型(复杂数据类型)
- 根据字面意思来说, 存放数字的一个组合, 但这样说有点片面了
- 更完善的说法: 数组是存放一些数据的集合
- 换句话说: 我们把数据放在一个盒子中, 这个盒子就叫做数组, 注意! 数组内的数据是有顺序的
```
// 创建一个变量 并在内部存储一个 数组
var arr = [2, 1, 3, 'q', 'w', 'e', true, false, undefined]
console.log(arr)
```
## 创建数组
### 两种方法
- 1.字面量的方式
- 语法: var arr = [1, 2, 3, 'q', 'w', 'e']
- 2. 内置构造函数的方式
- 语法1: var arr = new Array() 创建一个空数组
- 语法2: var arr = new Array(5) 创建一个有长度的数组
- 语法3: var arr = new Array(1, 2, 3) 创建一个有内容的数组
```
// 1. 字面量的方式
var arr = [1, 2, 3, 'q', 'w', 'e']
console.log(arr)
// 2. 内置构造函数的方式
// 2.1 创建一个空数组
var arr1 = new Array()
console.log(arr1)
// 2.2 创建一个有长度的数组
var arr2 = new Array(5)
console.log(arr2)
// 2.3 创建一个有内容的数组
var arr3 = new Array(1, 2, 3, 'q', 'w', 'e')
console.log(arr3)
```
### 数组的 length 属性
- length 翻译过来就是 长度的意思
- 代表 这个数组内, 有多少个成员
- 语法: 数组名.length
```
var arr1 = [1, 2, 3, 4, 5]
var arr2 = ['q', 'w', 'e', 'r']
var arr3 = new Array(1, 2, 3, 4, 5, 6)
// console.log(arr1)
// console.log(arr2)
// console.log(arr1.length) // 5
// console.log(arr2.length) // 4
```
### 数组的索引
- 索引 也有人叫做 下标
- 就是指一个数据, 在这个数组内排列在第几个 位置上
- 注意: 在 JS 中, 索引(下标) 是从 0 开始计算的
- 如果想要获取到数组指定位置的值, 可以通过下标来获取
- 语法: 数组名[下标] -> 能够获取到这个数组中对应下标的成员具体的值
```
var arr = ['b', 'a', 'c', 1, 2, 3]
// 0 1 2 3 4 5
// console.log(arr)
console.log(arr[0]) // b
console.log(arr[3]) // 1
```
### 遍历数组
- 想办法 拿到 数组的每一个成员
- 想拿到数组的所有成员, 需要先想办法拿到数组的所有下标
- 规律: 所有数组的下标都是从 0 开始的, 然后到 数组.length - 1 结束
```
var arr = ['b', 'a', 'c', 1, 2, 3]
//下标 0 1 2 3 4 5
var arr1 = ['b', 'a', 'c', 1, 2]
//下标 0 1 2 3 4
// console.log(arr)
// console.log(arr1)
// 需求, 就是根据 arr 这个数组, 拿到他的所有下标
for (var i = 0; i < arr.length; i++) {
// console.log(i) // 0 1 2 3 4 5
// 需求: 拿到数组所有的值, 输出在控制台
console.log(arr[i])
}
```
### 案例
#### 1. 求数组内所有成员的和
```
// var arr = [9, 5, 6, 11, 8, 4, 3]
/**
* 1. 求数组内所有成员的和
*
* 1.1 先想办法拿到所有的成员
* 1.2 计算所有的和
*
* 思考:
* 如何拿到所有的成员?
* 用 循环 模拟数组的所有下标, 从而拿到所有的成员
*/
// var sum = 0
// for (var i = 0; i < arr.length; i++) {
// // console.log(i) // 输出的是我们模拟的数组下标
// // console.log(arr[i]) // 输出的是 数组的所有成员
// // sum = sum + arr[i]
// sum += arr[i]
// }
// console.log(sum) // 46
```
#### 2. 求数组内最大的数字
```
/**
* 2. 求数组内最大的数字
*
* 2.1 先想办法拿到数组所有的成员
* 2.2 然后去成员中 找出最大的值
*/
var arr = [9, 5, 6, 11, 8, 4, 3]
var max = arr[0] // 假设 数组下标为 0 的值, 是最大的, 然后存储在 变量 max 中
for (var i = 1; i < arr.length; i++) {
if (max < arr[i]) { // 如果我假设的最大值, 小于 数组中的某一项, 那么会执行这个分支语句的代码
max = arr[i] // 将数组中的较大的值, 重新赋值给 变量 max
}
}
console.log(max) // 11
/**
* 第一次执行 for 循环
* i === 0 max === 9
* 循环执行条件 i < arr.length i < 7 符合条件, 开始执行循环
*
* 循环内部的代码: if (max < arr[i])
* if ( 9 < 9 ) 所以分支语句不会执行
* i++
*
* 第二次执行 for 循环
* i === 1 max === 9
* 循环执行条件 i < arr.length i < 7 符合条件, 开始执行循环
*
* 循环内部的代码: if (max < arr[i])
* if (9 < 5) 所以分支语句不会执行
* i++
*
* 第三次执行 for 循环
* i === 2 max === 9
* 循环执行条件 i < arr.length i < 7 符合条件, 开始执行循环
*
* 循环内部的代码: if (max < arr[i])
* if (9 < 6) 所以分支语句不会执行
* i++
*
* 第四次执行 for 循环
* i === 3 max === 9
* 循环执行条件 i < arr.length i < 7 符合条件, 开始执行循环
*
* 循环内部的代码: if (max < arr[i])
* if (9 < 11) 此时条件成立, 执行分支语句的代码, 也就是 max = arr[i]
* i++
*
* 第五次执行 for 循环
* i === 4 max === 11
* .......
*/
```
## 冒泡排序方法
```
// var arr = [9, 6, 5, 2, 1, 4]
// // 需求: 将数组 下标0 与 下标1 交换位置
// // arr[0] = arr[1]
// // arr[1] = arr[0]
// var temp = arr[0] // 存储 arr[0] 原本的值
// arr[0] = arr[1] // 将 arr[0] 的值 重新赋值为 arr[1]
// arr[1] = temp // 将 原本 arr[0] 的值, 重新赋值给 arr[1]
// console.log(arr) // [6, 9, 5, 2, 1, 4]
/**
* 冒泡排序:
* 属于数组排序的算法之一
* 其实就是通过一种算法, 将 一个乱序的数组, 调整为指定顺序的数组(从大到小/从小到大)
*
* 什么是算法?
* 解决某一个问题最简单的方式 / 最高效的方式
*
* 从 1~10万, 这组数字中, 少了一个数字, 要求我们找出来
*
* 常规写法: 后一位 - 前一位 如果 差值 === 2 那么就找出来了
*
* 将 1~10万 分为两组 1~5万 5万零1~10万 然后去找这两组数字中 那一组的数量不够5万, 找到之后将这组再次一分为二
*/
// 准备一个乱序数组
// var arr = [9, 3, 6, 2, 4, 1, 8, 5, 7]
// console.log('原始数组: ', arr)
// 冒泡排序的核心: 对比数组前一项和后一项, 如果前一项的值较大, 那么就往后挪 (这个排序之后是按照从小到大的顺序)
// for (var i = 0; i < arr.length; i++) {
// if (arr[i] > arr[i + 1]) {
// var temp = arr[i]
// arr[i] = arr[i + 1]
// arr[i + 1] = temp
// }
// }
// console.log('1 轮冒泡排序后的数组: ', arr)
// 基础版
// for (var k = 0; k < arr.length; k++) { // 决定执行几次 所谓的 冒泡排序
// for (var i = 0; i < arr.length; i++) { // 拿到数组的前一项与后一项, 做一轮排序
// if (arr[i] > arr[i + 1]) {
// var temp = arr[i]
// arr[i] = arr[i + 1]
// arr[i + 1] = temp
// }
// }
// }
// console.log('冒泡排序后的数组: ', arr)
/**
* 基础版 有 3 个地方可以优化
*
* 自己优化时: 1. 效果不变 2. 有一个合适的理由
*/
// var arr = [9, 3, 6, 2, 4, 1, 8, 5, 7]
// // 0 1 2 3 4 5 6 7 8
// console.log('原始数组: ', arr)
// // 优化1
// for (var k = 0; k < arr.length; k++) {
// for (var i = 0; i < arr.length - 1; i++) {
// /**
// * i 的值为 0~7, 在最后一次循环的时候, i === 7
// *
// * 判断在执行的时候, 相当于是对比 arr[7] > arr[8]
// *
// * 判断次数少了一次 arr[8] > arr[9]
// */
// if (arr[i] > arr[i + 1]) {
// var temp = arr[i]
// arr[i] = arr[i + 1]
// arr[i + 1] = temp
// }
// }
// }
// console.log('冒泡排序后的数组: ', arr)
// var arr = [9, 8, 7, 6, 5, 4, 3, 2, 1]
// // 0 1 2 3 4 5 6 7 8
// console.log('原始数组: ', arr)
// /**
// * 优化2
// *
// * k === 0 第 1 次循环 确定了 [8] 的值
// * k === 1 第 2 次循环 确定了 [7][8] 的值
// * k === 2 第 3 次循环 确定了 [6][7][8] 的值
// * k === 3 第 4 次循环 确定了 [5][6][7][8] 的值
// * k === 4 第 5 次循环 确定了 [4][5][6][7][8] 的值
// * k === 5 第 6 次循环 确定了 [3][4][5][6][7][8] 的值
// * k === 6 第 7 次循环 确定了 [2][3][4][5][6][7][8] 的值
// * k === 7 第 8 次循环 确定了 [1][2][3][4][5][6][7][8] 的值
// * k === 8 第 9 次循环 确定了 [0][1][2][3][4][5][6][7][8] 的值
// *
// * 分析后发现, 外层循环的最后一次 是没有必要, 所以 我们可以将外层循环次数 - 1
// */
// for (var k = 0; k < arr.length - 1; k++) {
// for (var i = 0; i < arr.length - 1; i++) {
// if (arr[i] > arr[i + 1]) {
// var temp = arr[i]
// arr[i] = arr[i + 1]
// arr[i + 1] = temp
// }
// }
// }
// console.log('冒泡排序后的数组: ', arr)
var arr = [9, 8, 7, 6, 5, 4, 3, 2, 1]
// 0 1 2 3 4 5 6 7 8
console.log('原始数组: ', arr)
/**
* 优化3
*
* 在打印之后, 我们发现 每一轮 循环都有一些没有必要的判断
*
* 规律是: 每一轮判断的执行次数, 减少 k 次
*/
// for (var k = 0; k < arr.length - 1; k++) {
// console.log('这是第', k + 1, '轮循环, 此时 k === ', k)
// for (var i = 0; i < arr.length - 1 - k; i++) {
// console.log(arr[i], arr[i + 1])
// if (arr[i] > arr[i + 1]) {
// var temp = arr[i]
// arr[i] = arr[i + 1]
// arr[i + 1] = temp
// }
// }
// console.log(arr)
// }
// console.log('冒泡排序后的数组: ', arr)
// 完整版
for (var k = 0; k < arr.length - 1; k++) {
for (var i = 0; i < arr.length - 1 - k; i++) {
if (arr[i] > arr[i + 1]) {
var temp = arr[i]
arr[i] = arr[i + 1]
arr[i + 1] = temp
}
}
}
```
## 选择排序方法
```
/**
* 选择排序
*/
// var arr = [9, 3, 6, 2, 4, 1, 8, 5, 7]
// // 0 1 2 3 4 5 6 7 8
// console.log('原始数组: ', arr)
// 第一轮选择排序
// var minIndex = 0 // 假设当前最小值的下标 为 0
// for (var i = 1; i < arr.length; i++) {
// if (arr[minIndex] > arr[i]) { // 如果当前分支执行, 说明在数组中找到了一个 比假设的最小值要小的 元素
// minIndex = i
// }
// }
// /**
// * 上边的 for 循环执行完毕后, minIndex 记录的就是 真实的最小的值的下标
// *
// * 此时交换 真实最小值 与 我们假设的最小值
// */
// var temp = arr[0] // 存储 数组下标0的值
// arr[0] = arr[minIndex] // 将 下标0的值重新赋值 当前数组中最小的值
// arr[minIndex] = temp // 将 下标 minIndex 的值 重新赋值为 arr[0] 原本的值 此时就完成了 真实最小值与假设最小值位置的交换
// console.log('第 1 轮选择排序后的数组: ', arr)
// 第二轮选择排序
// var minIndex = 1 // 假设当前最小值的下标为 1
// for (var i = 2; i < arr.length; i++) {
// if (arr[minIndex] > arr[i]) {
// minIndex = i
// }
// }
// // for 循环执行完毕 minIndex 就是真实的最小值的下标, 此时交换 真实最小值与假设的最小值的位置即可
// var temp = arr[1]
// arr[1] = arr[minIndex]
// arr[minIndex] = temp
// console.log('第 2 轮选择排序后的数组: ', arr)
// 第三轮选择排序
// var minIndex = 2 // 假设当前最小值的下标为 2
// for (var i = 3; i < arr.length; i++) {
// if (arr[minIndex] > arr[i]) {
// minIndex = i
// }
// }
// // for 循环执行完毕 minIndex 就是真实的最小值的下标, 此时交换 真实最小值与假设的最小值的位置即可
// var temp = arr[2]
// arr[2] = arr[minIndex]
// arr[minIndex] = temp
// console.log('第 3 轮选择排序后的数组: ', arr)
// 将上述流程 简化为 for 循环
var arr = [9, 3, 6, 2, 4, 1, 8, 5, 7]
// 0 1 2 3 4 5 6 7 8
console.log('原始数组: ', arr)
/**
* 第几次循环 假设谁是最小值 和谁交换 内层循环从几开始
*
* k === 0 1 0 0 1
* k === 1 2 1 1 2
* k === 2 3 2 2 3
*/
for (var k = 0; k < arr.length; k++) {
var minIndex = k
for (var i = k + 1; i < arr.length; i++) {
if (arr[minIndex] > arr[i]) {
minIndex = i
}
}
var temp = arr[k]
arr[k] = arr[minIndex]
arr[minIndex] = temp
}
console.log('选择排序之后的 数组: ', arr)
```
# 数据类型之间的区别
- 数据类型分为两种
- 1. 基本数据类型(简单数据类型)
- 2. 引用数据类型(复杂数据类型)
### 1. 存储
```
* 1. 存储
* 变量的数据存储的地方是 内存中, 内存分为两个 栈内存, 堆内存
* * 基本数据类型存储在 栈内存中, 比如: string number undefined null boolean
* * 复杂数据类型, 将数据本体存放在堆内存中, 比如对象或者数组或者函数
* 然后将指向该内存的地址, 存放在数组名或者对象名或者函数名中
* 数组/对象/函数 名 存放在 栈内存中
*
* 面试官: 数据类型之间有什么区别?
* 基本数据类型有哪些, 然后他们存储的地方是 栈内存中引用数据类型有哪些,
然后他们数据本体存放的地方是 堆内存中, 然后变量名存储的位置是 栈内存中
*
* 基本数据类型内部存储的是值; 引用数据类型内部存储的是地址
```
### 2. 赋值
```
* 基本数据类型: 赋值以后, 两个变量之间没有任何关系, 相当于将我自己的某一个东西, 复制一份给你, 然后你的就是你的, 我的就是我的
* 例子: 我有一张考试卷, 然后我复制一份给你, 然后你在卷子上书写答案, 并不会影响到我自己原本的这张卷子
*
* 复杂数据类型: 因为变量内部存储的是指向堆内存的地址, 所以在赋值的时候, 其实是将 这个地址给到了另外一个变量
* 那么相当于这两个变量存储的是 同一个 钥匙, 所以操作其中一个变量的时候, 会影响另外一个变量
* 例子: 我房间有一个开门的钥匙, 我将我的钥匙复制一份, 给到你, 那么此时我们两个共同拥有了一个房间的钥匙
* 此时如果我对房间的布局做了修改, 那么你进入房间的时候你能看到布局的修改
* 此时如果你将房间的所有东西全都偷走, 那么我进入房间的时候 能看到房间所有东西都被偷走了
*
```
### 3. 比较
```
基本数据类型: 就是 值 的比较
引用数据类型: 比较的时候 比较的是 存储地址
```
### 4. 传参
```
基本数据类型: 将值拷贝一份传递给形参, 在函数内修改不会影响外界
引用数据类型: 将存储地址赋值给形参, 在函数内修改会影响外界
```
代码
```
// var num = 100
// var str = 'abc'
// var obj = {
// a: 1,
// b: 2
// }
// var arr = [1, 2, 3, 4]
// 2. 赋值
// var num1 = 100
// var num2 = num1 // num2 === 100
// num2 = 666
// console.log(num1) // 100
// console.log(num2) // 666
// var obj1 = {
// name: 'QF001',
// age: 18
// }
// // 这一步相当于将 变量 obj1 内部存储的 "钥匙", 给到了 变量 obj2, 那么此时 obj2 和 obj1 相当于操作的是一个内存空间
// var obj2 = obj1
// // console.log(obj2) // {name: 'QF001', age: 18}
// obj2.name = 'QF666'
// console.log(obj2) // {name: 'QF666', age: 18}
// console.log(obj1) // {name: 'QF666', age: 18}
/**
* 首先创建了一个 对象 obj1 数据本体为: {name: 'QF001', age: 18} 然后变量名存储的是指向堆内存的地址, 我们假设为 XF001
*
* 然后创建了一个对象 obj2 内部存储的是 和 obj1一样的地址
*
* 此时相当于 obj2 和 obj1 共同保管一个内存空间
*
* 换句话说: 操作obj2会影响 obj1 操作obj1也会影响obj2
*/
// var obj1 = {
// name: 'QF001',
// age: 18
// }
// var obj2 = {
// name: 'QF001',
// age: 18
// }
// obj2.name = 'QF666'
// console.log(obj1.name) // QF001
/**
* 1. 创建一个对象 obj1 内部存储的地址假设为 XF001
* 2. 创建一个对象 obj2 内部存储的地址假设为 XF002
*
* 注意此时两个对象除了长得一模一样之外 毫无关系
*
* 也就是说 操作 其中一个对象, 并不会影响另外一个
*/
// 3. 比较
// console.log(obj1 === obj2) // false
/**
* 引用数据类型在对比的时候, 对比的是 地址 而这两个对象的地址完全不同, 所以返回的结果就是 false
*/
// var num1 = 100
// var num2 = '100'
// console.log(num1 === num2) // false
// var arr1 = [1, 2, 3]
// var arr2 = arr1
// console.log(arr1 === arr2) // true
/**
* 引用数据类型在对比的时候, 对比的是 地址 因为 他们两个的地址完全相同, 所以返回的结果是 true
*/
// 4. 传参
function fn(num) {
num = 'QF001'
}
var str = 'abc'
fn(str)
// console.log(str) // 'abc'
function fn1(o) {
o.name = 'qwer'
}
var obj = {
name: 'ABC'
}
fn1(obj)
console.log(obj.name) // 'qwer'
```
面试题
```
var obj = {
name: 'Jack'
}
function fn() {
obj.name = 'Rose'
obj = {}
obj.name = 'Jerry'
console.log(obj.name) // ????
}
// console.log(obj.name) // Jack
fn()
console.log(obj.name) // ????
/**
* 代码从上往下开始执行
* 13行 创建一个变量 叫做 obj, 数据本体 {name: Jack} 假设内部存储的地址为 XF001
*
* 17 行 定义一个 fn 函数, 但是没调用, 所以直接向下走
*
* 23 行 调用函数, 开始执行函数内部代码, 函数的代码是 18~21 行, 执行顺序还是从上往下
*
* 开始执行函数内代码
* 18行 obj.name = 'Rose'
* 会先在当前作用域内查找有没有定义 obj 这个对象, 所以会向父级作用域查找
* 在父级作用域, 找到了 全局变量 obj
*
* 所以 18 行相当于 修改了 全局变量 obj 的 name 属性
*
* 19 行 obj = {}
* 会先在当前作用域内查找有没有定义 obj 这个对象, 所以会向父级作用域查找
* 在父级作用域, 找到了全局变量 obj
*
* 所以 19 行 相当于 修改 全局变量 obj 内部存储的地址, 此时这个变量为一个空对象了
* 然后内部存储的地址 更改了 XF002, 与原本的地址没有任何联系了
*
* 20 行 obj.name = 'Jerry'
* 会先在当前作用域内查找有没有定义 obj 这个对象, 因为没找到, 所以会向父级作用域查找
* 在父级作用域, 找到了全局变量 obj
*
* 然后给这个对象内部 添加一个 name 属性, 值 Jerry
*
* 21 行 console.log(obj.name)
* 根据分析我们此时可以得知, 我现在打印的对象应该是 全局对象 obj, 此时他的值已经被修改了
* 所以此时打印的值 应该是 Jerry
*
* 此时函数执行完毕, 代码继续向下执行
*
* 24 行 console.log(obj.name) 此时打印的是 当前作用域的变量 obj 因为现在 在全局作用域
*
* 所以会打印全局作用域的变量 obj, 因为在打印之前函数 fn执行时 已经修改过 obj 对象的内容
* 所以 此时 打印的时候 obj.name 应该是 Jerry
*/
// 扩展作业
var obj = {
name: 'Jack'
}
function fn() {
obj.name = 'Rose'
var obj = {}
obj.name = 'Jerry'
console.log(obj.name) // ????
}
fn()
console.log(obj.name) // ????
```
扩展作业
```
// var obj = {
// name: "Jack",
// };
// function fn() {
// obj.name = "Rose";
// var obj = {};
// obj.name = "Jerry";
// console.log(obj.name); // ????
// }
// fn();
// console.log(obj.name); // ????
// console.log(a) // 此时是在这个变量定义前, 去使用这个变量, 根据 变量提升的规则 此时的值 应该是 undefined
// console.log(a.name) // 首先可以明确 a 的值 目前 undefined, a.name -> undefined.name
// var a = {
// name: 'QF001'
// }
// console.log(a.name) // 是在 变量 定义以后 打印这个变量 QF001
/**
* 13 行 创建一个全局变量 obj 内部存储的是一个 指向堆内存的地址, 我们假设为 XF001
*
* 17 行 定义了一个函数 fn, 因为只是定义 没有调用, 所以直接略过, 向下继续执行
*
* 23 行 调用 函数 fn, 开始执行函数内部代码
*
* 函数内部代码开始执行:
* 18 行 obj.name = 'Rose'
* 此时是要先找到 obj 这个变量 并拿到它内部的 name 属性, 给它赋值, 所以会先在当前作用域 查找有没有定义 一个变量叫做 obj
* 此时 在当前作用域内 是有一个定义的 变量 叫做 obj, 但是是在 19行
* 也就是说, 我们此时是在变量定义前, 去使用这个变量
* 根据变量提升的规则, 在 JS 中 在定义变量前使用这个变量的话, 可以使用但是这个变量的值 是 undefined
*
* 所以 obj.name 相当于 undefined.name 这种写法 在 JS 中 是不被允许的, 也就是说浏览器会报错
*/
var obj = {
name: "Jack",
};
function fn() {
var obj = {};
obj.name = "Jerry";
console.log(obj.name); // Jerry
}
fn();
console.log(obj.name); // Jack
/**
* 53 行 创建一个全局变量 obj 内部存储地址假设 XF001
*
* 57 行定义函数fn, 直接略过
*
* 62 行 调用函数 fn, 开始执行函数内部代码
*
* 58 行: var obj = {}
* 定义一个 属于 fn 函数这个作用域的私有变量 obj 我们假设内部存储地址是 SY001
*
* 59 行: obj.name = 'Jerry'
* 找到 obj 这个对象的 name 属性, 给它赋值为 'Jerry', 寻找时会先在当前作用域内查找, 也就是我们的私有变量 obj
* 所以本行代码 相当于是 给 函数内的私有变量 obj 添加一个 name 属性, 属性值为 'Jerry'
*
* 60 行: console.log(obj.name)
* 此时会输出对象 obj 的 name 属性, 那么会现在当前作用域内查找, 所以会找到这个函数的私有变量 obj
* 也就是说 这里会打印 函数的私有变量 obj 的 name 属性
* 所以会输出 'Jerry'
*
* 到此函数执行完毕
*
* 63 行: console.log(obj.name)
* 此时会输出对象 obj 的 name 属性, 那么会先在当前作用域内查找, 也就是全局作用域内查找, 所以此时会找到全局变量 obj
* 所以会输出 'Jack'
*/
```
# 数组的常用方法
- 1. push
- * 语法: 数组.push(数据)
- * 作用: 向数组末尾添加数据
- * 返回值: 追加数据后, 数组最新的长度(length)
- 2. pop
- * 语法: 数组.pop()
- * 作用: 删除数组最后一条数据
- * 返回值: 被删除的数据
- 3. unshift
- * 语法: 数组.unshift(数据)
- * 作用: 向数组开头添加数据
-* 返回值: 添加数据后, 数组最新的长度(length)
- 4. shift
- * 语法: 数组.shift()
- * 作用: 删除数组第一条数据
- * 返回值: 被删除的数据
- 5. reverse
- * 语法: 数组.reverse()
- * 作用: 反转数组
- * 返回值: 反转后的数组
- 6. sort
- * 语法1: 数组.sort()
- * 作用: 会将数据转换为 字符串后, 一位一位的对比
- * 语法2: 数组.sort(function (a, b) {return a - b})
- * 作用: 会按照数字大小升序排列
- * 语法3: 数组.sort(function (a, b) {return b - a})
- * 作用: 会按照数字大小降序排列
- * 返回值: 排序后的数组
- 7. splice
- * 语法1: 数组.splice(开始索引, 多少个)
- * 作用: 截取数组部分内容
- * 语法2: 数组.splice(开始索引, 多少个, 插入的数据1, 插入的数据2, 插入的数据3...)
- * 作用: 截取数组部分内容, 并插入新的数据
- * 返回值: 截取出来的部分内容 组成的 数组
- 数组的方法 能够改变原数组的 就只有上边说的 7 个
- 8. slice
- * 语法: 数组.slice(开始索引, 结束索引)
- * 参数:
- + 包前不包后: 包含开始索引位置的数据, 不包含结束索引位置的数据
- + 不写开始索引, 默认是0; 不写 结束索引, 默认是 数组的length
- + 参数支持写负数, 表示倒数第几个, 其实就是 length + 负数
- * 作用: 截取数组部分内容
- * 返回值: 截取出来的部分内容组成的新数组
- 面试题: 数组中有两个方法, splice 与 slice, 你能描述一下他们两个的区别吗?
- 1. 参数含义不同, 然后介绍一下 参数哪里不同
- 2. splice 会改变原数组, 而 slice 不会
- 9. concat
- * 语法: 原始数组.concat(数组1, 数组2, ...., 数据1, 数据2, ....)
- * 作用: 进行数据拼接, 把数组...数据之类的小括号里的内容, 拼接在原始数组中
- * 返回值: 拼接好的数组
- 10. join
- * 语法: 数组.join('连接符')
- * 作用: 使用 "连接符", 把数组内的每一个数据连接成一个字符串 (不写连接符, 默认使用的是 逗号)
- * 返回值: 连接好的字符串
- 11. indexOf
- * 语法1: 数组.indexOf(要检查的数据)
- * 作用: 从前到后(从左到右) 检查该数据第一次在该数组内出现 索引
- * 语法2: 数组.indexOf(要检查的数据, 开始索引)
- * 作用: 在开始索引的位置, 按照从左到右的顺序, 检查该数据第一次在该数组内出现的 索引
- * 返回值: 找到数据的情况下, 会将该数据第一次出现的下标(索引)返回
- 没找到的情况下, 会直接返回一个 -1
- * 备注: 开始索引不写的时候 默认是0
- 12. lastIndexOf
- * 语法1: 数组.lastIndexOf(要检查的数据)
- * 作用: 从后向前(从右向左), 检查该数据第一次在该数组内出现的 索引
- * 语法2: 数组.lastIndexOf(要检查的数据, 开始索引)
- * 作用: 在开始索引的位置, 按照从右向左的顺序, 检查该数据第一次在该数组内出现的 索引
- * 返回值: 找到数据的情况下, 返回第一次出现的下标(索引)
- 没找到的情况下, 直接返回一个 -1
代码
```
// var arr = [1, 2, 3]
// console.log('原始数组: ', arr)
// 1. push
// var len = arr.push(500)
// console.log(len) // 4
// console.log(arr) // [1, 2, 3, 500]
// 2. pop
// var po = arr.pop()
// console.log(po) // 3
// console.log(arr) // [1, 2]
// 3. unshift
// var len = arr.unshift(666)
// console.log(len) // 4
// console.log(arr) // [666, 1, 2, 3]
// 4. shift
// var st = arr.shift()
// console.log(st) // 1
// console.log(arr) // [2, 3]
// 5. reverse
// var newArr = arr.reverse()
// console.log('newArr: ', newArr)
// console.log('arr: ', arr)
// 6. sort
// var arr = [100, 101, 200, 10, '999', 'qwe', '123abc']
// console.log('原始数组: ', arr)
// var newArr = arr.sort()
// console.log('newArr', newArr)
// console.log('arr', arr)
// var newArr = arr.sort(function (a, b) {return a - b})
// console.log('newArr', newArr)
// console.log('arr', arr)
// var newArr = arr.sort(function (a, b) { return b - a })
// console.log('newArr', newArr)
// console.log('arr', arr)
// 7. splice
// var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
// // 0 1 2 3 4 5 6 7 8
// console.log('原始数组: ', arr)
// 7.1
// var newArr = arr.splice(3, 4) // 从 下标3 开始截取, 截取 4 个 成员/数据
// console.log('newArr', newArr) // [4, 5, 6, 7]
// console.log('arr', arr) // [1, 2, 3, 8, 9]
// 7.2
// var newArr = arr.splice(2, 3, '数据1', '数据2', '数据3', '数据4', '数据5') // 不管插入多少个数据, 都是从下标2开始的
// console.log('newArr', newArr)
// console.log('arr', arr)
// 8. slice
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
// 0 1 2 3 4 5 6 7 8
console.log('原始数组: ', arr)
// var newArr = arr.slice(3, 4) // 从 下标3 开始截取, 截取到 下标4
// console.log('newArr', newArr) // [4]
// console.log('arr', arr)
var newArr = arr.slice(5) // 不写 结束索引, 相当于 写了数组.length, 所以这里相当于写了 slice(5, arr.length)
console.log(newArr) // [6, 7, 8, 9]
/**
* 此时 开始索引与结束索引都没有填写, 那么 开始索引相当于是写了一个 0, 结束索引相当于写了 数组.length
*
* arr.slice() 相当于写了 arr.slice(0, arr.length)
*/
// var newArr = arr.slice()
// console.log(newArr)
var newArr = arr.slice(3, -2)
console.log(newArr) // [4, 5, 6, 7]
/**
* arr.slice(3, -2)
* slice 这个方法 允许写 负数
*
* 写负数的时候 就相当于写了 length + 负数
*
* arr.slice(3, -2) -> arr.slice(3, arr.length + (-2)) -> arr.slice(3, arr.length - 2)
* -> arr.slice(3, 9 - 2)
* -> arr.slice(3, 7)
*/
var arr = [1, 1, 2, 2, 3, 3, 0, 4, 0]
// 0 1 2 3 4 5 6 7 8
console.log('原始数组: ', arr)
// 9. concat
// var newArr = arr.concat([4, 5, 6], [10, 20], ['a', 'b', 'c'], 'qwer')
// console.log('newArr', newArr) // [1, 2, 3, 4, 5, 6, 10, 20, 'a', 'b', 'c', 'qwer']
// console.log('arr', arr)
// 10. join
// var newArr = arr.join() // 不传递连接符, 默认使用的是 逗号连接
// var newArr = arr.join('!') // 使用 ! 将数组内的所有数据拼接成一个 字符串
// console.log(newArr)
// 11. indexOf
// var num = arr.indexOf(100) // 此时要检查的数据是 数字100, 但是数组中并没有出现过 数字 100, 所以返回值应该是 -1
// var num = arr.indexOf(0) // 此时要检查的数据是 数字0, 数字0按照从左到右的顺序, 第一次出现的下标为 6, 所以返回值应该是 6
// var num = arr.indexOf(1) // 此时要检查的数据是 数字1, 数字1按照从左到右的顺序, 第一次出现的下标为 0, 所以返回值应该是 0
// var num = arr.indexOf(1, 3) // 此时要检查的数据是 数字1, 但是是从下标3的位置开始按照从左往右的顺序查找, 因为后续并没有数字1, 所以此处应该返回 -1
// console.log(num)
// 12. lastIndexOf
// var num = arr.lastIndexOf(3) // 此时按照从右向左的顺序查找, 发现第一次出现的位置是 下标 5 的位置
var num = arr.lastIndexOf(3, 2) // 此时在下标2的位置按照从右向左的顺序查找, 但是此时在数组中后续的位置并没有出现数字3, 所以按照规则, 应该返回 -1
console.log(num)
```
# 数组遍历的常用方法
- 1. forEach
- * 语法: 数组.forEach(function (item, index, origin) {})
- * item: 数组的每一项 的值
- * index: 数组的每一项 对应的下标
- * origin: 原始数组 (了解即可, 一般没人用)
- * 作用: 遍历数组
- * 返回值: 该方法永远没有返回值 (undefined)
- 2. map
- * 语法: 数组.map(function (item, index, origin) {}) 三个参数的意义与 forEach 相同
- * 作用: 映射数组
- * 返回值: 返回一个和原数组长度相同的数组, 但是内部数据可以经过我们的映射加工
- * 映射加工: 就是在函数内 以 return 的形式书写
- 有一道面试题: 数组常用的遍历方法中, 有一个forEach 和 一个 map, 这两个方法有什么区别?
- 1. forEach 的作用是用来遍历数组, 而 map 的作用是用来映射数组
- 2. forEach 没有返回值, 而 map 是可以有返回值的
- 3. filter
- * 语法: 数组.filter(function (item, index, origin) {}) 三个参数的意义与 forEach 相同
- * 作用: 过滤数组
- * 返回值: 返回一个新数组, 内部存储的是原始数组过滤出来的部分内容
- * 过滤条件: 过滤条件以 return 的形式书写
- 4. find
- * 语法: 数组.find(function (item, index, origin) {}) 三个参数的意义 与 forEach 相同
- * 作用: 在数组内查找满足条件的第一项
- * 返回值: 找到的数据, 如果没找到返回的是 undefined
- * 查找条件以 return 的形式书写
- 5. findIndex
- * 语法: 数组.findIndex(function (item, index, origin) {}) 三个参数的意义 与 forEach 相同
- * 作用: 在数组内查找满足条件的第一项 的下标
- * 返回值: 找到的数据 的下标, 如果没找到返回的是 -1
- * 查找条件以 return 的形式书写
- 6. some
- * 语法: 数组.some(function (item, index, origin) {}) 三个参数的意义 与 forEach 相同
- * 作用: 判断数组内是否有一个满足条件
- * 返回值: 一个布尔值 true/false
- * 判断条件以 return 的形式书写
- 7. every
- * 语法: 数组.every(function (item, index, origin) {}) 三个参数的意义 与 forEach 相同
- * 作用: 判断数组内是否全都满足条件
- * 返回值: 一个布尔值 true/false
- * 判断条件以 return 的形式书写
- 8. reduce
- * 语法: 数组.reduce(function (prev, item, index, origin) {}, init)
- * prev: 表示初始值或者上一次的运算结果
- * item: 表示数组的每一项 的值
- * index: 表示数组的每一项 的下标(索引)
- * origin: 原始数组
- * 作用: 用来实现叠加效果
- * 返回值: 最终叠加的结果
- * 注意:
- + 叠加条件以 return 的形式书写
- + prev 第一次的值, 如果你传递了 init, 就是 init 的值, 如果没有传递 init, 那么就是 数组[0] 的值
- + 如果传递了 init, 循环执行 数组.length 次, 如果没有传递 init, 循环执行 数组.length - 1 次
代码
```
// var arr = [100, 200, 300, 400, 500, 600]
// console.log('原始数组: ', arr)
// 1. forEach
// arr.forEach(function (item, index, origin) {
// console.log(item, index)
// console.log(origin)
// })
// 2. map
// var newArr = arr.map(function (item, index, origin) {
// // console.log(item, index, origin)
// return item * 2
// })
// var newArr = arr.map(function (item) {
// return item * 2
// })
// console.log('映射出来的数组: ', newArr)
// 3. filter
// var newArr = arr.filter(function (item, index, origin) {
// return item > 350 // 过滤数组的内容, 只留下 item 大于 350 的成员
// })
// console.log(newArr)
// 4. find
// var newArr1 = arr.find(function (item, index, origin) {
// return item > 3500 // 在数组中查找第一个符合条件的成员
// })
// console.log(newArr1)
// 5. findIndex
// var newArr2 = arr.findIndex(function (item, index, origin) {
// return item > 3500
// })
// console.log(newArr2)
// var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
// console.log('原始数组: ', arr)
// 6. some
// var bo1 = arr.some(function (item, index, origin) {
// // return item % 2 === 0 // 判断数组内是否有 一个 满足条件的
// return item > 500 // 判断数组内是否有 一个 满足条件的
// })
// console.log(bo1) // true / false
// 7. every
// var bo2 = arr.every(function (item, index, origin) {
// // return item % 2 === 0 // 判断数组内 是否 全都 满足条件
// return item > 0 // 判断数组内 是否 全都 满足条件
// })
// console.log(bo2)
// var arr = [1, 2, 3, 4]
// console.log('原始数组: ', arr)
// // 8. reduce
// var str = arr.reduce(function (prev, item, index, origin) {
// return prev + item
// }, 0)
// console.log(str) // 10
/**
* 在 第 1 轮执行的时候
* prev === 0 (因为我使用 reduce 方法的时候传递了 第二个参数, 默认给的值为 0)
* item === 1 他的值就是数组第一项的值
* 在第一轮代码执行的时候, 运行了 return prev + item, 这个结果 会传递给 第二轮循环开始的时候的 prev
*
* 在 第 2 轮执行的时候
* prev === 1 (因为上一轮执行了 prev + item 的到的值是 1)
* item === 2 因为是第二轮循环, 所以他的值就是数组第二个元素的值
* 在第二轮代码执行的时候, 运行了 return prev + item, 这个结果会传递给 第三轮循环开始的时候的 prev
*
* 在 第 3 轮执行的时候
* prev === 3 (因为上一轮执行了 prev + item 得到的值是 3)
* item === 3 因为是第三轮循环, 所以他的值就是数组第三个元素的值
* 在第三轮代码执行的时候, 运行了 return prev + item, 这个结果会传递给 第四轮循环开始的时候的 prev
*
* 在 第 4 轮执行的时候
* prev === 6 (因为上一轮执行了 prev + item 得到的值是 6)
* item === 4 因为是第四轮循环, 所以他的值就是数组第四个元素的值
* 在第四轮代码执行的时候, 运行了 return prev + item, 这个结果会传递给 第五轮循环开始的时候的 prev
*
* 因为后续数组没有元素了, 所以循环到此结束, 然后将 最后一轮 prev 的值 return 出去, 外部可以使用一个变量去接收
*/
// var str = arr.reduce(function (prev, item, index, origin) {
// return prev + item
// })
// console.log(str) // 10
/**
* 第 1 轮
* prev === 1 (因为没传递 init, 所以 prev 第一次的值是数组下标0位置上的值)
* item === 2 (因为没传递 init, 所以 prev 是 下标[0] 的值, 然后 item 就拿到了 下标[1] 的值)
* 第 1 轮 运行代码 return prev + item 这个结果会传递给 下一轮循环开始时的 prev
*
* 第 2 轮
* prev === 3 (因为上一轮执行了 prev + item 得到的值 3)
* item === 3 (因为上一轮item的值是[1], 所以本轮的值是 [2])
* 第 2 轮 运行代码 return prev + item 这个结果会传递给 下一轮循环开始时的 prev
*
* 第 3 轮
* prev === 6 (因为上一轮执行了 prev + item 得到的值 6)
* item === 4 (因为上一轮item的值是[2], 所以本轮的值是 [3])
* 第 3 轮 运行代码 return prev + item 这个结果会传递给 下一轮循环开始是的 prev
*
* 因为后续数组没有元素了, 所以循环到此结束, 然后将最后一轮 prev 的值 return 出去, 外部可以使用一个变量去接收
*/
var arr = [100, 200, 150, 300]
// 0 1 2 3
var str = arr.reduce(function (prev, item, index, origin) {
return prev + item
})
console.log(str) // 750
/**
* 第 1 轮
* prev === 100 (因为没有传递 init, 所以 prev 第一次的值是数组 下标 0 的值)
* item === 200 (因为没有传递 init, 所以 prev 的值是 [0], 那么 item 的值就是 [1])
* 执行代码 return prev + item
*
* 第 2 轮
* prev === 300 (因为 上一轮执行了 prev + item 得到的值 300 )
* item === 150 (因为上一轮item的值是[1], 所以这一轮是[2])
* 执行代码 return prev + item
*
* 第 3 轮
* prev === 450 (因为上一轮执行了 prev + item 得到的值 450)
* item === 300 因为上一轮item的值是 [2], 所以这一轮是[3]
* 执行代码 return prev + item
*
* 后续数组没有元素了, 循环到此结束, 最终的 prev === 750, 然后将这个 750 return 出去
*/
```
### 课后作业
- 1. 计算数组的和 var arr = [1, 2, 3, 4, 5]
- 2. 将数组的值 放大 10 倍 并返回一个新数组
- 3. 过滤数组, 让数组内的值只有偶数 var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] -> 新数组: [2, 4, 6, 8, 10]
##### 代码
```
// 1. 计算数组的和 var arr = [1, 2, 3, 4, 5] reduce forEach map
// var arr = [1, 2, 3, 4, 5]
// var sum = 0
// arr.forEach(function (item) {
// sum += item
// })
// // arr.map(function (item) {
// // sum += item
// // })
// console.log(sum) // 15
// 2. 将数组的值 放大 10 倍 并返回一个新数组 map / forEach
// 2.1 forEach 版本
var arr = [1, 2, 3, 4, 5]
var newArr = []
arr.forEach(function (item) { // forEach 的功能遍历数组拿到数组的每一项(item就是数组每一项)
newArr[newArr.length] = item * 10
/**
* 第 1 轮 newArr.length === 0
* newArr[newArr.length] -> newArr[0] 所以第一轮循环是给 newArr 下标0的位置赋值
*
* 第 2 轮 newArr.length === 1
* newArr[newArr.length] -> newArr[1] 所以第二轮循环是给 newArr 下标1的位置赋值
*/
})
console.log(newArr) // [10, 20, 30, 40, 50]
// 2.2 map 版本
// var arr = [1, 2, 3, 4, 5]
// var newArr = arr.map(function (item) {
// return item * 10
// })
// console.log(newArr) // [10, 20, 30, 40, 50]
// 3. 过滤数组, 让数组内的值只有偶数 var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] -> 新数组: [2, 4, 6, 8, 10]
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] // filter forEach
// 3.1 filter
var newArr = arr.filter(function (item) {
return item % 2 === 0
})
console.log(newArr) // [2, 4, 6, 8, 10]
// 3.2 forEach
// var newArr = []
// arr.forEach(function (item) {
// if (item % 2 === 0) {
// newArr.push(item)
// }
// })
// console.log(newArr) // [2, 4, 6, 8, 10]
```
# 数组塌陷
```
// var arr = [0, 1, 2, 3, 4, 5]
// // 0 1 2 3 4 5
// console.log('原始数组: ', arr)
// 利用数组的方法, 将数组的每一项 删除 pop shift splice
// 1. 拿到数组的每一项, 暂时先不用数组的方法, 使用 for循环
// for (var i = 0; i < arr.length; i++) {
// arr.pop()
// /**
// * 第 1 轮
// * i === 0 arr.length === 6 条件成立, 开始执行内部代码
// * arr.pop() 将数组的最后一项删除, 删除完毕 数组.length === 5
// *
// * 第 2 轮
// * i === 1 arr.length === 5 条件成成立, 开始执行内部代码
// * arr.pop() 将数组的最后一项删除, 删除完毕 数组.length === 4
// *
// * 第 3 轮
// * i === 2 arr.length === 4 条件成立, 开始执行内部代码
// * arr.pop() 将数组的最后一项删除, 删除完毕 数组.length === 3
// *
// * 第 4 轮
// * i === 3 arr.length === 3 条件不成立, 循环到此结束
// */
// }
// var arr = [0, 1, 2, 3, 4, 5]
// // 0 1 2 3 4 5
// console.log('原始数组: ', arr)
// for (var i = arr.length - 1; i >= 0; i--) {
// // arr.pop()
// // arr.shift()
// arr.splice(i, 1)
// }
// for (var i = 0; i < arr.length; i++) {
// arr.splice(i, 1)
// /**
// * 第 1 轮
// * i === 0 arr.length === 6 符合条件
// * arr.splice(i, 1) (0, 1) 把 [0] 的值截取掉了
// * 现在的数组: arr === [1, 2, 3, 4, 5]
// * 对应的下标 0 1 2 3 4
// * 数组的length === 5
// *
// * 第 2 轮
// * i === 1 arr.length === 5 符合条件
// * arr.splice(i, 1) (1, 1) 把 [1] 的值截取掉了
// * 现在的数组: arr === [1, 3, 4, 5]
// * 对应的下标 0 1 2 3
// * 数组的length === 4
// *
// * 第 3 轮
// * i === 2 arr.length === 4 符合条件
// * arr.splice(i, 1) (2, 1) 把 [2] 的值截取掉了
// * 现在的数组: arr === [1, 3, 5]
// * 对应的下标 0 1 2
// * 数组的length === 3
// *
// * 第 4 轮
// * i === 3 arr.length === 3 条件不成立, 循环到此结束
// */
// }
var arr = [0, 1, 2, 3, 4, 5]
// 0 1 2 3 4 5
console.log('原始数组: ', arr)
for (var i = 0; i < arr.length; i++) {
arr.splice(i, 1)
// i--
// /**
// * 第 1 轮
// * i === 0 length === 6 符合条件
// * 执行内部代码
// * + arr.splice(i, 1) 此时相当于 把 [0] 的值截取掉了
// * + i-- 此时相当于把 i 的值更改为 -1
// * 循环内部代码执行完毕之后, 会执行一个 i++, 所以下一轮循环开始的时候 i 的值 === 0
// *
// * 第 2 轮
// * i === 0 length === 5 符合条件
// * 执行内部代码
// * + arr.splice(i, 1) 此时相当于 把 [0] 的值截取掉了
// * + i-- 此时相当于把 i 的值更改为 -1
// * 循环内部代码执行完毕之后, 会执行一个 i++, 所以下一轮循环开始的时候 i 的值 === 0
// *
// * 第 3 轮
// * i === 0 length === 4 符合条件
// * 执行内部代码
// * + arr.splice(i, 1) 此时相当于 把 [0] 的值截取掉了
// * + i-- 此时相当于把 i 的值更改为 -1
// * 循环内部代码执行完毕之后, 会执行一个 i++, 所以下一轮循环开始的时候 i 的值 === 0
// *
// * 第 4 轮
// * i === 0 length === 3 符合条件
// * 执行内部代码
// * + arr.splice(i, 1) 此时相当于 把 [0] 的值截取掉了
// * + i-- 此时相当于把 i 的值更改为 -1
// * 循环内部代码执行完毕之后, 会执行一个 i++, 所以下一轮循环开始的时候 i 的值 === 0
// *
// * 第 5 轮
// * i === 0 length === 2 符合条件
// * 执行内部代码
// * + arr.splice(i, 1) 此时相当于 把 [0] 的值截取掉了
// * + i-- 此时相当于把 i 的值更改为 -1
// * 循环内部代码执行完毕之后, 会执行一个 i++, 所以下一轮循环开始的时候 i 的值 === 0
// *
// * 第 6 轮
// * i === 0 length === 1 符合条件
// * 执行内部代码
// * + arr.splice(i, 1) 此时相当于 把 [0] 的值截取掉了
// * + i-- 此时相当于把 i 的值更改为 -1
// * 循环内部代码执行完毕之后, 会执行一个 i++, 所以下一轮循环开始的时候 i 的值 === 0
// *
// * 第 7 轮
// * i === 0 length === 0 不符合条件, 循环到此结束
// */
}
console.log('删除后的数组: ', arr)
```
# 数学的方法(Math)
### 在 JS 中 Math 对象给我们提供了操作数据的一些方法(数学的方法)
- 1. random
- * 语法: Math.random()
- * 作用: 得到一个随机数, 每次生成的数字都不一样, 但一定是0~1之间的, 包含0, 不包含1, 也就是说最大值可能是 0.99999....
- 2. round
- * 语法: Math.round(数字)
- * 作用: 将这个数字(小数), 按照四舍五入的形式变成整数
- 3. ceil
- * 语法: Math.ceil(数字)
- * 作用: 将这个数字(小数) 向上取整
- 4. floor
- * 语法: Math.floor(数字)
- * 作用: 将这个数字(小数) 向下取整
- 5. abs
- * 语法: Math.abs(数字)
- * 作用: 返回这个数字的绝对值
- 6. sqrt
- * 语法: Math.sqrt(数字)
- * 作用: 求 平方根
- 7. pow
- * 语法: Math.pow(基数, 幂)
- * 作用: 返回基数的几次幂
- 8. max
- * 语法: Math.max(数字1, 数字2, 数字3...)
- * 作用: 返回传入的数字中 最大的哪一个
- 9. min
- * 语法: Math.min(数字1, 数字2, 数字3...)
- * 作用: 返回传入的数字中 最小的哪一个
- 10. PI
- * 语法: Math.PI
- * 作用: 返回 π
代码
```
// 1. random
// var num = Math.random()
// console.log(num)
// 2. round
// var num1 = Math.round(4.499999)
// var num2 = Math.round(4.5)
// console.log(num1) // 4
// console.log(num2) // 5
// 3. ceil
// var num1 = Math.ceil(1.000001)
// var num2 = Math.ceil(1.999999)
// console.log(num1)
// console.log(num2)
// 4. floor
// var num1 = Math.floor(1.000001)
// var num2 = Math.floor(1.999999)
// console.log(num1)
// console.log(num2)
// 5. abs
// var num1 = Math.abs(1.23)
// var num2 = Math.abs(-1.23)
// console.log(num1)
// console.log(num2)
// 6. sqrt
// var num = Math.sqrt(36)
// console.log(num)
// 7. pow
// var num = Math.pow(3, 3)
// console.log(num)
// 8. max
// var num = Math.max(100, 5, 7, 99, 102, 96, 999)
// console.log(num)
// 9. min
// var num = Math.min(100, 5, 7, 99, 102, 96, 1)
// console.log(num)
// 10. PI
console.log(Math.PI)
```
#### 案例
- 1. 数组去重var arr = [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5]处理完毕后的 arr === [1, 2, 3, 4, 5]
- 2. 封装一个函数, 函数返回一个 0~10 之间的 随机整数 0 1 2 3 4 5 6 7 8 9 10
```
// 1. 数组去重
// var arr = [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5]
// // 1. 遍历数组, 拿到数组的每一项
// for (var i = 0; i < arr.length; i++) {
// // 2. 判断数组内, 除了当前项之外, 后续有没有与当前项相同的值
// if (arr.indexOf(arr[i], i + 1) !== -1) { // 如果当前分支条件成立, 那么代表数组后续一定有与当前重复的值
// arr.splice(i, 1) // 分支成立时, 将当前项删掉, 达到去重的目的
// i-- // 直接使用 splice 会出现 数组塌陷的情况, 会导致去重无法清除干净, 加上 i--, 解决数组塌陷
// }
// }
// console.log('去重后的数组: ', arr)
// 2. 封装一个函数, 函数返回一个 0~10 之间的
function fn() {
// 求 0~10之间的 随机整数
var fnNum1 = Math.random() // 得到了一个随机数, 但是区间为 0~1
var fnNum2 = fnNum1 * (10 + 1) // 得到了一个随机数, 区间为 0~10之间的
var fnNum3 = Math.floor(fnNum2) // 通过四舍五入, 将这个随机数改变为 随机整数
/**
* 当前 四舍五入的形式有问题, 我们现在要么改为向上取整, 要么改为向下取整
*
* 如果说 使用向上取整, 数字应该计算为: -0.999~9.999 向上取整后为 0~10
*
* 如果说 使用向下取整, 数字应该计算为: 0~10.999 向下取整后为 0~10
*
* 通过分析认为 向下取整比较好计算, 所以我们的 取整方式更改为 向下取整
* 计算公式 Math.floor(随机数 * (10 + 1))
*
* 目前的 随机数取证方式为 向下取整
* 0~0.999 0
* 1~1.999 1
* 2~2.999 2
* ...
* 9~9.999 9
* 10~10.999 10
*/
return fnNum3
}
/**
* 目前 随机数取整的方式为 四舍五入
*
* 0~0.499 -> 0
* 0.5~1.499 -> 1
* 1.5~2.499 -> 2
* ...
* 8.5~9.499 -> 9
* 9.5~9.999 -> 10
*/
var num = fn()
console.log('num 是一个随机数: ', num)
var obj = {}
for (var i = 0; i <= 100000; i++) {
var sum = fn() // 1. 循环开始 先拿到随机数的值
if (obj[sum] === undefined) {
obj[sum] = 1 // 代表 当前这个随机数 第一次出现
} else {
obj[sum]++ // 如果执行的是 else 分支, 表明 sum 不是第一次出现, 具体出现的次数我们不知道, 所以直接让他的属性值自增1 就行
}
}
console.log(obj)
```
# JS 的严格模式
- JS 是一个相对不是很严谨的语言, 在开发的时候一些代码也不是很严格
- 换句话说严格模式就是对开发的时候, 你写的代码做了一些要求
- 严格模式的规则
- 1. 声明变量必须要 var 关键字
- 2. 函数的形参不可以重复
- JS 中默认是没有开启严格模式, 如果想要开启严格模式, 需要手动在代码最开始的位置(script标签内第一行), 写一个字符串 'use strict'
- 现在的公司的项目中, 基本都是按照严格模式开发的
## 字符集(了解)
```
/**
* 字符集
*
* 计算机只能存储二进制数据 0101010
*
* 我们的 大写字母 小写字母 符号之类的内容 都是由 二进制数字组成
*
* 或者说我们在敲一个字符的时候, 都有一个对应的编号, 计算机存储的时候存储的是这些编号,
* 只不过我们看到的时候, 是通过这些编号解析成我们看到的内容
*
*
* 前身: ASCII as key (128) 只够 美国人用英语的使用
*
* 国内 推出了一个属于中国的 GBK 国标码 前128位 ASCII码, 后边从129位开始就是汉字
*
* unicode (万国码) 前 128 位还是 ASCII码, 后边开始是各个国家的文字码
*
* 八位十六进制编码 容量小, 但是占用内存也小 UTF-8
* 十六位的十六进制编码 容量大, 但是占用内存也大
*/
```