Math
- Math 给我们提供了操作数据的一些方法, 是 JS 内置的一个对象
数学方法
- random / round / ceil / floor / abs
- random
- 语法:
Math.random()
- 作用: 得到一个随机数, 每次生成的数字都不一样, 但一定是0~1之间的,包含0 不包含1
- 语法:
- round
- 语法:
Math.round(数字)
- 作用: 将一个小数 四舍五入 变成整数
- 语法:
- ceil
- 语法:
Math.ceil(数字)
- 作用: 将一个小数 向上取整
- 语法:
- floor
- 语法:
Math.floor(数字)
- 作用: 将一个小数, 向下取整
- 语法:
- abs
- 语法:
Math.abs(数字)
- 作用: 返回一个数字的绝对值
- 语法:
- random
- sqrt / pow / max / min / PI
- sqrt
- 语法:
Math.sqrt(数字)
- 作用: 求平方根
- 语法:
- pow
- 语法:
Math.pow(基数, 幂)
- 作用: 返回基数的几次幂
- 语法:
- max
- 语法:
Math.max(数字1, 数字2, 数字3, 数字4)
- 作用: 返回最大值
- 语法:
- min
- 语法:
Math.min(数字1, 数字2, 数字3, 数字4)
- 作用: 返回最小值
- 语法:
- PI
- 语法:
Math.PI()
- 作用: 得到 π
- 语法:
- sqrt
- 封装函数: 范围内的随机整数
简单了解对象
- 什么是对象
- 首先排除男女朋友, 我们这是正经编程
- 对象是一个 复杂数据类型, 也叫做 引用数据类型
- 虽然我们说是复杂类型, 但是也没有很复杂, 只不过是存储了一些基本数据类型的集合
var obj = {
num: 100,
str: 'hello',
boo: true
}
- 这里的 {} 和函数中的 {} 不一样, 函数内部书写代码, 对象里面是书写数据的
- 对象就是一个键值对的集合
- 什么是键值对?
- 对象 obj 中, num 是键, 100 是值
- 对象 obj 中, str 是键, 'hello' 是值
- 对象 obj 中, boo 是键, true 是值
- 其实就是我们准备一个房子, 把我们想要的数据放进去, 然后把房子的地址给到变量名, 当我们需要某一个数据的时候, 就可以根据变量名里面存储的地址找到对应的房子, 然后去房子里面找到对应的数据
- 什么是键值对?
创建对象
- 字面量方式创建对象
- 语法:
var obj = {} var obj1 = {键值对, 键值对}
- 语法:
- 内置构造函数创建
- 语法:
var obj = new Object()
- 语法:
- 对象内对于 键(key) 的要求
- 推荐使用符合变量命名规则和规范的名字
- 可以使用纯数字当作 键名
- 这种情况下该属性会排列在最前面
- 可以使用任何特殊符号
- 使用特殊符号的时候,在书写时需要被引号包裹
对象数据类型的操作(增删改查)两种语法
- 点语法
var obj = {} obj.name = 'qianfeng' // 增 obj.name = 'qianfeng123' // 改 console.log(obj.name) // 查 delete obj.name // 删
- 中括号语法(数组语法)
var obj = {} obj['name'] = 'qianfeng' // 增 obj['name'] = 'qianfeng123123' // 改 console.log(obj['name']) // 查 delete obj['name'] // 删
- 两者的差异
- 符合变量命名规范与规则的情况,两者并无差异
- 操作不符合变量命名规范与规则的名称时,比如纯数字或者带有特殊符号的,就只能用中括号语法
- 涉及变量相关的时候,只能使用中括号语法
for in 循环遍历对象
for (var k in obj) {
console.log('我执行了', k, obj[k])
}
数组数据类型
- 什么是数组
- 数组字面意思就是存放数字的一个组合, 但这么说太过于片面, 更完善的说法应该是
数组是存放一些数据的集合
- 简单来说我们把数据放到一个盒子中, 这个盒子就是数组, 注意数字内的数据是有顺序的
- 数组字面意思就是存放数字的一个组合, 但这么说太过于片面, 更完善的说法应该是
var arr = [1, 2, 3, 'qwe', 'abc', true, false, undefined, null]
// 我们把一堆数据存放到一个盒子中, 这就叫做数组 对应我们上述的 数组是存放一些数据的集合
创建数组数据类型
- 使用字面量的方式创建数组
// 创建一个空数组
var arr = [];
// 创建一个有内容的数组
var arr1 = [1, 2, 3]
- 内置构造函数创建数组
// 创建一个空数组
var arr = new Array()
// 创建一个有长度的数组
var arr1 = new Array(10)
// 创建一个有内容的数组
var arr2 = nee Array(1, 2, 3)
数组的 length 属性
length 就是长度的意思, 代表数组内有多少个成员(数据)
var arr = [1, 2, 3]
console.log(arr.length) // 3
var arr1 = [4, 5]
console.log(arr1.length) // 2
数组的 索引 概念
- 索引也叫做下标, 就是指一个数据在这个数组内排列在第几个位置上
- 注意: 在所有的语言中, 索引(下标)都是从0开始的
var arr = ['hello', 'world']
// 在这个数组中, 第一个数据(元素)是 'hello', 那么它的下标就是 0, 后续的 'world', 下标为1
// 想要获取数组中某一个位置上的数据(元素), 可以直接 数组名[下标]
console.log(arr[0], arr[1])
for 循环遍历数组
遍历数组就是想办法拿到数组的每一个元素, 通常我们可以通过下标获取到元素的某一项, 所以我们只需要想办法拿到数组的所有下标即可
var arr = [1, 2, 3, 'qwe', 'abc', true, false, undefined, null]
for (var i = 0; i < arr.length; i++) {
console.log(arr[i])
}
数组基本操作案例
- 冒泡排序
var arr = [9, 6, 3, 1, 4, 7, 8, 2, 5];
console.log('原始数组: ', arr)
for (var i = 0; i < arr.length - 1; i++) {
for (var j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
var temp = arr[j]
arr[j] = arr[j + 1]
arr[j + 1] = temp
}
}
}
console.log('交换后的数组: ', arr)
- 选择排序
var arr = [9, 6, 3, 1, 4, 7, 8, 2, 5];
console.log('原始数组: ', arr)
for (var j = 0; j < arr.length; j++) {
// 1. 假定数组中的第 0 个是最小数字的索引
var minIndex = j
// 2. 遍历数组找到数组中最小的哪一项的下标将其替换之前记录的索引
for (var i = j; i < arr.length; i++) {
if (arr[i] < arr[minIndex]) {
minIndex = i
}
}
// 3. 遍历结束, 找到最小的索引, 将两个位置的数据对换
var temp = arr[j]
arr[j] = arr[minIndex]
arr[minIndex] = temp
}
console.log('排序结束后: ', arr)
数组常用方法
- push; pop; unshift; shift;
- push
- 语法:
数组名.push(数据)
- 作用: 向数组末尾添加数据
- 返回值: 追加数据后, 数组最新的长度
- 语法:
- pop
- 语法:
数组名.pop()
- 作用: 删除数组最后一条数据
- 返回值: 被删除的数据
- 语法:
- unshift
- 语法:
数组名.unshift(数据)
- 作用: 向数组开头添加数据
- 返回值: 添加数据后, 数组最新的长度
- 语法:
- shift
- 语法:
数组名.shift()
- 作用: 删除数组第一条数据
- 返回值: 被删除的数据
- 语法:
- push
- reverse; sort; splice; slice; concat; join; indexOf; lastIndexOf
-
reverse
- 语法:
数组名.reverse()
- 作用: 反转数组
- 返回值: 反转后的数组
- 语法:
-
sort
- 语法:
数组名.sort(); 数组名.sort((a, b) => a - b); 数组名.sort((a, b) => b - a)
- 不传参数: 会将数据转换为字符串后, 一位一位的对比
- 回调函数内进行 a - b, 那么会按照数字大小升序排序
- 回调函数内进行 b - a, 那么会按照数字大小降序排序
- 作用: 根据参数对数组数据实现排序
- 返回值: 排序后的数组
- 语法:
-
splice
- 语法:
数组名.splice(开始索引, 多少个); 数组名.splice(开始索引, 多少个, 插入数据1, 插入数据2, ...)
- 作用: 截取数组部分内容, 并选择性插入内容
- 返回值: 截取出来的部分内容组成的新数组
- 语法:
-
slice
- 语法:
数组名.slice(开始索引, 结束索引)
- 参数:
- 包前不包后, 包含开始索引位置数据, 不包含结束索引位置数据
- 不写开始索引, 默认是 0, 结束索引不写, 默认是 length
- 可以填写负整数, 表示倒数第几个, 其实就是 length + 负整数
- 作用: 截取数组部分内容
- 返回值: 截取出来的部分内容组成的新数组
- 与 splice 的差异
- 参数含义不同
- splice 会改变原数组, slice 不会
- 语法:
-
concat
- 语法:
原始数组.concat(数组1, 数组2, 数据, ...)
- 作用: 进行数据拼接, 把参数的所有数组或数据, 拼接在原始数组身上
- 返回值: 拼接好的数组
- 语法:
-
join
- 语法:
数组名.join('连接符')
- 作用: 使用连接符, 把数组内的每一个数据连接成一个字符串(不写的话默认使用 逗号)
- 返回值: 连接好的字符串
- 语法:
-
indexOf
- 语法:
数组名.indexOf(要检查的数据); 数组名.indexOf(要检查的数据, 开始索引)
- 作用: 从前到后检查该数据第一次在该数组内出现的索引位置
- 返回值: 如果在数组内找到了该数据, 那么会返回该数据第一次出现的索引位置, 没找到返回 -1
- 语法:
-
lastIndexOf
- 语法:
数组名.lastIndexOf(要检查的数据); 数组名.lastIndexOf(要检查的数据, 开始索引)
- 作用: 从后向前检索该数据第一次在该数组内出现的位置
- 返回值: 如果在数组内找到了该数据, 那么会返回该数据第一次出现的索引位置, 没找到返回 -1
- 语法:
-
- forEach; map; filter; find; findIndex; every; some; reduce;
- forEach
- 语法:
数组.forEach(function (item, index, origin) {})
- item: 数组的每一项
- index: 数组的每一项对应的下标
- origin: 原始数组
- 作用: 遍历数组
- 返回值: 没有(undefined)
- 注意: 手写 return 也没用
- 语法:
- map
- 语法:
数组.map(function (item, index, origin) {})
- item: 数组的每一项
- index: 数组的每一项对应的下标
- origin: 原始数组
- 作用: 映射数组
- 返回值: 和数组长度一样的数组, 只不过内部数据经过映射加工
- 注意: 映射条件以 return 的形式书写
- 语法:
- filter
- 语法:
数组.filter(function (item, index, origin) {})
- item: 数组的每一项
- index: 数组的每一项对应的下标
- origin: 原始数组
- 作用: 过滤数组
- 返回值: 返回一个新数组, 内部存储的是原始数组内过滤出来的部分数据
- 注意: 过滤条件以 return 的形式书写
- 语法:
- find
- 语法:
数组.find(function (item, index, origin) {})
- item: 数组的每一项
- index: 数组的每一项对应的下标
- origin: 原始数组
- 作用: 在数组内查找满足条件的第一项
- 返回值: 找到的数据
- 注意: 查找条件以 return 的形式书写
- 语法:
- findIndex
- 语法:
数组.findIndex(function (item, index, origin) {})
- item: 数组的每一项
- index: 数组的每一项对应的下标
- origin: 原始数组
- 作用: 在数组内查找满足条件的第一项的下标
- 返回值: 找到的数据
- 注意: 查找条件以 return 的形式书写
- 语法:
- every
- 语法:
数组.every(function (item, index, origin) {})
- item: 数组的每一项
- index: 数组的每一项对应的下标
- origin: 原始数组
- 作用: 判断数组内是否每一个都满足条件
- 返回值: 一个布尔值
- 注意: 判断条件以 return 的形式书写
- 语法:
- some
- 语法:
数组.find(function (item, index, origin) {})
- item: 数组的每一项
- index: 数组的每一项对应的下标
- origin: 原始数组
- 作用: 判断数组内是否有一个符合条件的
- 返回值: 一个布尔值
- 注意: 判断条件以 return 的形式书写
- 语法:
- reduce
- 语法:
数组.reduce(function (prev, item, index, origin) {}, init)
- prev: 表示初始值或者上一次的运算结果
- item: 表示数组的每一项
- index: 表示数组的每一项索引
- origin: 原始数组
- 作用: 用来实现叠加效果
- 返回值: 最终叠加结果
- 注意:
- 叠加条件以 return 的形式书写
- prev 的值, 如果你传递了 init, 就是 init 的值, 如果没有传递 init, 那么就是
数组[0]
的值 - 如果传递了 init, 循环执行 length 次, 如果没有传递 init, 循环执行 length - 1 次
- 语法:
- forEach
数据类型之间的区别
存储的区别
- 基本数据类型存储在 栈内存中, 比如:
string; number; undefined; null; boolean;
- 复杂数据类型(引用数据类型)
- 将数据本体存放在 堆内存中, 比如对象或者数组, 然后将指向该堆内存的地址, 存放在数组名或者对象名中
- 数组名或者对象名存放在栈内存中
赋值的区别
- 基本数据类型:赋值以后,两个变量没有关系了, 相当于将我自己的某一个东西, 复制一份给到你, 然后你的是你的, 我的是我的
- 复杂数据类型:赋值以后,两个变量操作一个存储空间, 相当于我将我房间的钥匙复制给你一份, 你可以自由进出该房间或者对这个房间的布局做一些调整, 我也可以自由进出该房间并且也可以对这个房间的布局做调整
- 两种数据类型存储的区别
- 基础数据类型直接存储在 栈内存中
- 复杂数据类型会将数据本体存在堆内存中,变量名存储在堆内存中,变量名内部存储着指向堆内存的地址
比较的区别
- 基本数据类型是
值
的比较
var a = 1
var b = 2
console.log(a == b) // false, 因为两个变量的值是 1 和 2, 所以对比结果为 false
- 复杂数据类型是
存储地址
的比较
var obj1 = {a: 1}
var obj2 = obj1
var obj3 = {a: 1}
console.log(obj1 === obj2) // 两个变量的存储地址相同, 所以为true
console.log(obj1 === obj3) // 两个变量的存储地址不相同, 所以为false
传参的区别
- 基本数据类型: 将值拷贝一份传递给形参, 在函数内修改不会影响外界
- 复杂数据类型: 将存储地址赋值给形参, 在函数内修改会影响外界
函数的预解析
- 什么是预解析
- 在代码运行前,先全部分析一遍代码,这个行为叫做预解析(预解释)
- 预解析的内容
- 声明式函数定义
- 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
作用域
- 什么是作用域?
- 简答一句话概述:变量生效的范围
- 变量不是在所有地方都是可以使用的,而这个变量的可使用范围 就是作用域
全局作用域 && 局部作用域
-
全局作用域
- 最大的作用域
- 在全局作用域中定义的变量可以在任何地方使用
- 页面打开时浏览器给我们生成了一个全局作用域
window
这个作用域一直存在,直到浏览器页面关闭时才会销毁 var num = 100; var num2 = 200
这两行代码的变量就是存储在全局作用域下面,可以在任意地方去使用
-
局部作用域
- 局部作用域就是在全局作用域内的某一个地方,相对比较小的一些作用域
- 在局部作用域中定义的变量只能在当前的这个作用域内部使用,在全局作用域或者其他的局部作用域中不可以使用
- 在 JS 中,只有函数能生成一个局部作用域,别的都不行
- 简单来说,每一个函数内部都是一个局部作用域
var num = 100; // 全局作用域下声明的变量,在任何地方都可以使用 function fn() { var num2 = 200; // 在当前位置声明的变量都是 fn 函数局部作用域内的变量, 也就是说只能在当前 fn 函数内部使用 }
作用域链
- 作用域链是一个纯概念性的东西
- 当我们在某一个作用域内获取某一个变量时
- 会先在当前作用域查找,找到直接拿来用,没找到会向上层查找
- 如果上层作用域也没找到,那么会继续向上层作用域的上层作用域查找,一直到查找到全局作用域
- 这样一层一层向上查找构成一个链条(假设有,不是真的有链条),我们叫做作用域链
var num = 100;
function fn() {
var num2 = 200;
function fun() {
var num3 = 300;
console.log(num3); // 自己当前作用域就有(fun函数内部),拿过来直接用
console.log(num2); // 自己当前作用域没有(fun函数内部),去上一层查找,发现有(fn函数内部),拿过来用
console.log(num); // 自己当前作用域没有(fun函数内部),去上一层查找,发现没有(fn函数内部),继续向上一层作用域查找,发现有(全局作用域),直接用
console.log(a); // 自己没有,一级一级向上查找,到全局作用域还是没有,报错
}
}
变量使用规则
访问规则
- 变量的访问规则 也叫做 作用域的查找机制
- 作用域的查找机制只能向上查找,不能向下查找
function fn() {
var num = 100;
}
fn();
console.log(num); // 全局作用域没有,相当于已经查找到顶层,所以报错找不到,不会向 fn的局部作用域查找
赋值规则
- 当我们想要给某一个变量赋值的时候,就要先找到这个变量,然后再给它赋值
- 赋值规则
- 先在自己作用域内部查找,有就直接赋值
- 没有就去上一级作用域内部查找,有就直接赋值,如果没有继续向上一级作用域内部查找
- 如果一直找到全局作用域都没有,那么就把这个变量定义为全局变量,再给他赋值
function fn() {
num = 100;
}
fn();
/**
* fn 调用以后,要给 num 赋值
* 查找自己作用域没有后,向上层查找
* 上层就是全局作用域,发现还是没有
* 那么会把 num 定义为全局的变量,并给它赋值
* 所以 fn 函数调用后,全局就有了一个变量叫做 num 并且值是 100
*/
递归函数
- 什么是递归?
- 在编程世界中,递归就是一个自己调用自己的手段
- 递归函数:在一个函数内部,调用了自己,循环往复
/**
* 这就是一段简单的递归,在函数内部调用了自己,函数一执行,就调用自己一次,在调用在执行,循环往复没有尽头
*/
function fn() {
fn();
}
fn();
- 其实递归函数和循环很类似
- 需要有初始化,自增,执行代码,条件判断
- 如果没有就会是一个没有尽头的递归函数,我们通常叫这种为 死递归
写一个简单的递归
/**
* 求 1 至 5 的和
*
* 1 + 2 = 3
* 3 + 3 = 6
* 6 + 4 = 10
* 10 + 5 = 15
*/
// 写递归的第一步,先写结束条件(为了避免出现死递归)
function add(n) {
if (n == 5) {
return 5;
}
}
add(1);
// 第二步,写不满足结束条件时的递归操作
function add(n) {
if (n == 5) {
return 5;
} else {
return n + add(n + 1);
}
}
add(1);