文章目录
- 🥽 前言
- 🥽 JavaScript 简介
- 🌊 JavaScript 是什么
- 🌊 JavaScript 的作用
- 🌊 HTML/CSS/JS 的关系
- 🌊 浏览器执行 JS
- 🌊 JS 的组成
- 🥽 JavaScript 的书写位置
- 🌊 行内式 JS
- 🌊 内嵌JS
- 🌊 外部 JS文件
- 🥽 JavaScript 注释
- 🌊 单行注释
- 🌊 多行注释
- 🥽 JavaScript 输入输出语句
- 🌊 alert
- 🌊 console.log
- 🌊 prompt
- 🥽 变量
- 🌊 什么是变量
- 🌊 变量的使用
- 💦 声明变量
- 💦 赋值
- 💦 声明变量的同时初始化变量
- 💦 更新变量
- 💦 同时声明多个变量
- 💦 声明变量特殊情况
- 🌊 变量命名规范
- 💦 标识符、关键字、保留字
- 💦 小测试
- 🌊 练习 -- 交换两个变量的值
- 🥽 数据类型
- 🌊 数据类型简介
- 🌊 简单数据类型(基本数据类型)
- 💦 数字型 Number
- 💧 进制
- 💧 JavaScript中数值的最大和最小值
- 💧 数字型三个特殊值
- 💧 isNaN()
- 💦 字符串型 String
- 💧 字符串转义符
- 💧 字符串长度
- 💧 字符串拼接
- 💦 布尔型 Boolean
- 💦 Undefined
- 💦 Null
- 🌊 获取变量数据类型
- 🌊 字面量
- 🌊 数据类型转换
- 💦 转换为字符串
- 💦 转换为数字型
- 💦 计算年龄案例
- 💦 转换为布尔型
- 🥽 运算符
- 🌊 算数运算符
- 🌊 浮点数的精度问题
- 🌊 表达式和返回值
- 🌊 递增和递减运算符
- 🌊 比较运算符
- 💦 三种 = 运算符
- 🌊 逻辑运算符
- 💦 逻辑与 &&
- 💦 逻辑或 ||
- 💦 逻辑非 !
- 💦 短路运算(逻辑中断)
- 🌊 赋值运算符
- 🌊 运算符优先级
- 🥽 流程控制
- 🌊 流程控制
- 🌊 顺序流程控制
- 🌊 分支流程控制
- 💦 if 语句
- 💦 if else语句(双分支语句)
- 💦 if else if 语句(多分支语句)
- 💦 三元表达式
- 💦 switch 语句
- 💦 switch 语句和 if else if 语句的区别
- 🌊 循环
- 💦 for 循环
- 💧 for 循环重复相同的代码
- 💧 for 循环重复不相同的代码
- 💧 for 循环重复不相同的代码
- 💧 双重 for 循环
- 💦 while 循环
- 💦 do while 循环
- 💦 continue break
- 💧 continue 关键字
- 💧 break 关键字
- 🥽 对象
- 🌊 对象简介
- 🌊 对象的创建与对象属性的增删改查
- 💦 创建对象
- 💦 添加属性
- 💦 读取属性
- 💦 修改属性
- 💦 删除属性
- 🌊 对象的属性
- 🌊 对象字面量
- 🌊 枚举对象中的属性(for-in语句)
- 🌊 可变类型
- 🌊 对象的修改与变量的修改
- 🌊 方法(method)
- 🥽 函数
- 🌊 函数的创建
- 🌊 函数的调用
- 🌊 函数的创建方式
- 🌊 参数
- 💦 函数的参数
- 💦 函数参数的默认值
- 💦 对象类型数据作为函数实参
- 💦 函数作为参数
- 🌊 返回值
- 💦 函数的返回值
- 💦 箭头函数的返回值
- 🌊 作用域
- 💦 作用域简介
- 💦 作用域链
- 🌊 window对象
- 🥽 严格模式
- 🥽 面向对象
- 🌊 面向对象概述
- 🌊 类
- 🌊 属性
- 🌊 方法
- 🌊 构造函数
- 🌊 面向对象的三大特性
- 💦 封装
- 💦 多态
- 💦 继承
- 🌊 对象的结构
- 🌊 原型对象
- 💦 访问一个对象的原型对象
- 💦 原型链
- 💦 原型的作用
- 💦 修改原型
- 💦 instanceof
- 💦 判断对象是否具有指定属性
- 🌊 旧类(早期JS中定义类)
- 🌊 new运算符
- 🥽 数组(Array)
- 🌊 简介
- 🌊 数组的遍历
- 💦 for循环
- 💦 for-of语句
- 🥽 Map
- 🌊 创建Map
- 🌊 Map的属性和方法
- 💦 map.set(key, value) 向map中添加键值对
- 💦 map.get(key) 根据key获取值
- 💦 map.size 获取map中键值对的数量
- 💦 map.delete(key) 删除指定数据
- 💦 map.has(key) 检查map中是否包含指定键
- 💦 map.clear() 删除全部的键值对
- 🌊 map转换为数组
- 💦 Array.form()
- 💦 扩展转换
- 🌊 通过二维数组创建Map
- 🌊 遍历map
- 💦 for-of
- 💦 Map.forEach()
- 💦 map.keys() 获取map的所有的key
- 💦 map.values() 获取map的所有的value
- 💦 map.entries() 获取map的所有的键值对
- 🥽🌊💦💧
🥽 前言
到【流程控制】看的视频为黑马pink的教程
视频:JavaScript基础语法-dom-bom-js-es6新语法-jQuery-数据可视化echarts黑马pink老师前端入门基础视频教程
视频对应资源(在视频简介里面)
链接:【https://gitee.com/xiaoqiang001/java-script.git】
视频对应资源(百度网盘)
链接:【https://pan.baidu.com/s/1q952v5mnFGR9IFjHlyn7Wg】
提取码:1234
【对象】之后的视频更换为尚硅谷超哥的教程
视频:JavaScript核心基础_讲师(李立超)_JS教程
源码地址:
链接:https://pan.baidu.com/s/1spfVmWjorgV49HYgt9R_tQ?pwd=8c1x
提取码:8c1x
🥽 JavaScript 简介
🌊 JavaScript 是什么
JavaScript 是一种运行在客户端的脚本语言 (Script 是脚本的意思)
脚本语言:不需要编译,运行过程中由 js 解释器( js 引擎)逐行来进行解释并执行
js 现在也可以基于 Node.js 技术进行服务器端编程
🌊 JavaScript 的作用
- 表单动态校验(密码强度检测) ( JS 产生最初的目的 )
- 网页特效
- 服务端开发(Node.js)
- 桌面程序(Electron)
- App(Cordova)
- 控制硬件-物联网(Ruff)
- 游戏开发(cocos2d-js)
🌊 HTML/CSS/JS 的关系
HTML页面的结构(骨架),CSS页面的样式(外观),JavaScript页面的交互(行为)。
🌊 浏览器执行 JS
浏览器分成两部分:渲染引擎和 JS 引擎
- 渲染引擎:用来解析HTML与CSS,俗称内核,比如 chrome 浏览器的 blink ,老版本的webkit
- JS 引擎:也称为 JS 解释器。 用来读取网页中的JavaScript代码,对其处理后运行,比如 chrome 浏览器的 V8
浏览器本身并不会执行JS代码,而是通过内置 JavaScript 引擎(解释器) 来执行 JS 代码 。
JS 引擎执行代码时逐行解释每一句源码(转换为机器语言),然后由计算机去执行,所以 JavaScript 语言归为脚本语言,会逐行解释执行。
🌊 JS 的组成
ECMAScript 规定了JS的编程语法和基础核心知识,是所有浏览器厂商共同遵守的一套JS语法工业标准。
文档对象模型(Document Object Model,简称DOM),是W3C组织推荐的处理可扩展标记语言的标准编程接口。通过 DOM 提供的接口可以对页面上的各种元素进行操作(大小、位置、颜色等)。
BOM (Browser Object Model,简称BOM) 是指浏览器对象模型,它提供了独立于内容的、可以与浏览器窗口进行互动的对象结构。通过BOM可以操作浏览器窗口,比如弹出框、控制浏览器跳转、获取分辨率等。
🥽 JavaScript 的书写位置
注意:JavaScript 每行结束可以写分号也可以不写分号
JS 有3种书写位置,分别为行内、内嵌和外部。
🌊 行内式 JS
- 可以将单行或少量 JS 代码写在HTML标签的事件属性中(以 on 开头的属性),如:onclick
- 注意单双引号的使用:在HTML中我们推荐使用双引号, JS 中我们推荐使用单引号
- 可读性差, 在html中编写JS大量代码时,不方便阅读;
- 引号易错,引号多层嵌套匹配时,非常容易弄混;
- 特殊情况下使用
<body>
<!-- οnclick="alert('Hello JavaScript')" -->
<!-- 双引号中的代码为js代码 -->
<!-- 避免引号冲突,js代码使用单引号 -->
<input type="button" value="按钮" onclick="alert('Hello JavaScript')">
</body>
🌊 内嵌JS
可以将多行JS代码写到 script 标签中
<body>
<script>
alert('Hello JavaScript')
</script>
</body>
🌊 外部 JS文件
- 利于HTML页面代码结构化,把大段 JS代码独立到 HTML 页面之外,既美观,也方便文件级别的复用
- 引用外部 JS文件的 script 标签中间不可以写代码(写了不生效)
- 适合于JS 代码量比较大的情况
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./03-外部JS.js"></script>
</body>
</html>
alert('Hello JavaScript!!!!!')
<body>
<script src="./03-外部JS.js">
alert('123123')
</script>
</body>
script 标签中间代码不生效
🥽 JavaScript 注释
🌊 单行注释
快捷键:ctrl + /
<body>
<script>
// 单行注释
</script>
</body>
🌊 多行注释
快捷键: alt + shift + a
<body>
<script>
/* 多行注释
多行注释
多行注释 */
</script>
</body>
快捷键修改:
vscode => 左下角按钮 => 键盘快捷方式 => 查找 原来的快捷键 => 修改为新的快捷键 => 回车确认
🥽 JavaScript 输入输出语句
方法 | 说明 | 归属 |
---|---|---|
alert(‘弹出警示框要显示的内容’) | 浏览器弹出警示框 | 浏览器 |
console.log(‘要在控制台打印输出的信息’) | 浏览器控制台打印输出信息 | 浏览器 |
prompt(‘提示信息’) | 浏览器弹出输入框,用户可以输入 | 浏览器 |
alert() 主要用来显示消息给用户,console.log() 用来给程序员自己看运行时的消息。
🌊 alert
<body>
<script>
alert('弹出警示框要显示的内容')
</script>
</body>
🌊 console.log
<body>
<script>
console.log('控制台显示的内容')
</script>
</body>
打开浏览器的控制台:
- F12
- 鼠标右键 => 检查
- 笔记本电脑 (键盘左下角) Fn => F12
🌊 prompt
<body>
<script>
prompt('请输入信息')
</script>
</body>
🥽 变量
🌊 什么是变量
变量就是一个存放数据的容器,会在内存中开辟一块空间来存放数据,这个存放数据的空间就是变量。(好比,酒店就是计算机的内存空间,酒店的房间就是一个又一个的变量,住在酒店中的人就是一个又一个的数据)。
由上面的类比我们可以得到,每个变量都有自己的名字(酒店房间号,这样子才可以在内存找到变量中数据),变量中的数据可以更改(酒店房间内居住的人会变动)。
🌊 变量的使用
变量在使用时分为两步: 1. 声明变量 2. 赋值
💦 声明变量
使用关键字 var 声明一个变量
使用该关键字声明变量后,计算机会自动为变量分配内存空间
var age
💦 赋值
使用 = 将右边的值赋给左边的变量
age = 11
💦 声明变量的同时初始化变量
var age = 11
💦 更新变量
一个变量被重新复赋值后,它原有的值就会被覆盖,变量值将以最后一次赋的值为准。
<body>
<script>
var age = 11
age = 18
alert(age)
</script>
</body>
💦 同时声明多个变量
同时声明多个变量时,只需要写一个 var, 多个变量名之间使用英文逗号隔开。
<script>
var age = 12, username = 'zs'
console.log(age, username)
</script>
💦 声明变量特殊情况
只声明 不赋值:
var age
console.log(age)
没有赋值,变量未定义
不声明 不赋值 直接使用:
console.log(Password)
不声明 只赋值:
pwd = 11;
console.log(pwd)
可以不声明,赋值直接使用(不推荐)
🌊 变量命名规范
- 由字母(A-Za-z)、数字(0-9)、下划线(_)、美元符号( $ )组成,如:usrAge, num01, _name
- 严格区分大小写。var app; 和 var App; 是两个变量
- 不能 以数字开头。 18age 是错误的
- 不能 是关键字、保留字。例如:var、for、while
- 变量名必须有意义。 使用可以看变量名就知道其意义的,如 age
- 遵守驼峰命名法。首字母小写,后面单词的首字母需要大写。 myFirstName
💦 标识符、关键字、保留字
标识(zhi)符:
就是指开发人员为变量、属性、函数、参数取的名字。标识符不能是关键字或保留字。
关键字:
是指 JS本身已经使用了的字,不能再用它们充当变量名、方法名。
包括:
break、case、catch、continue、default、delete、do、else、finally、for、function、if、in、instanceof、new、return、switch、this、throw、try、typeof、var、void、while、with 等。
保留字:
实际上就是预留的“关键字”,意思是现在虽然还不是关键字,但是未来可能会成为关键字,同样不能使用它们当变量名或方法名。
包括:
boolean、byte、char、class、const、debugger、double、enum、export、extends、fimal、float、goto、implements、import、int、interface、long、mative、package、private、protected、public、short、static、super、synchronized、throws、transient、
volatile 等
注意:
如果将保留字用作变量名或函数名,那么除非将来的浏览器实现了该保留字,否则很可能收不到任何错误消息。当浏览器将其实现后,该单词将被看做关键字,如此将出现关键字错误。
var function = 123
var int = 123
console.log(int)
保留字目前可以使用,但是未来变为关键字会报错,不建议使用保留字为标识符。
💦 小测试
以下哪些是合法的变量名?
🌊 练习 – 交换两个变量的值
有两个变量 a b,要求将两个变量中的值进行交换。
交换两个变量的值就好比两户人家搬家搬到对方的家中,肯定要有其中一户人家暂时搬到酒店暂时居住,等另一户人家完成搬家,在搬到那户人家中,如此即可完成变量值的交换。
<script>
var a = 12
var b = 24
var t = a // 中间临时变量,暂存a的值
a = b // b的值赋给a
b = t // 原先暂存在t中的值赋给b,即完成变量值的交换
console.log(a)
console.log(b)
</script>
🥽 数据类型
🌊 数据类型简介
在计算机中,不同的数据所需占用的存储空间是不同的,为了便于把数据分成所需内存大小不同的数据,充分利用存储空间,于是定义了不同的数据类型。(比如,用合适的盒子装合适大小的物品,不浪费空间)
变量是用来存储值的所在处,它们有名字和数据类型。变量的数据类型决定了如何将代表这些值的位存储到计算机的内存中。
JavaScript 是一种弱类型或者说动态语言.
这意味着不用提前声明变量的类型,在程序运行过程中,类型会被自动确定 。在代码运行时,变量的数据类型是由 JS引擎 根据 = 右边变量值的数据类型来判断 的,运行完毕之后, 变量就确定了数据类型。
JavaScript 拥有动态类型,同时也意味着相同的变量可用作不同的类型,即变量的数据类型是可以更改的。 如,在程序的运行过程中变量的数据类型可以由原来的数字类型转变为字符串或其他类型的数据。
JS 把数据类型分为两类:
- 简单数据类型 :
Number – 数字型
String – 字符串型
Boolean – 布尔型
Undefined – 未定义
Null – 空 - 复杂数据类型:
object – 对象类型
🌊 简单数据类型(基本数据类型)
简单数据类型 | 说明 | 默认值 |
---|---|---|
Number | 数字型,包含整型值(12)和浮点型值(3.1415) | 0 |
Boolean | 布尔值类型,如true . false,等价于1和0 | false |
string | 字符串类型,如"张三”注意咱们js里面,字符串都带引号 | “” |
Undefined | var a;声明了变量a但是没有给值,此时a = undefined | undefined |
Null | var a = null;声明了变量a为空值 | null |
💦 数字型 Number
JavaScript 数字类型既可以用来保存整数值,也可以保存小数(浮点数)。
<script>
var intNumber = 12
var floatNumber = 3.1415926
console.log(intNumber)
console.log(floatNumber)
</script>
💧 进制
最常见的进制有二进制、八进制、十进制、十六进制。
在JS中八进制前面加0,十六进制前面加 0x。
八进制:
数字只有 0-7
// 八进制(逢8进1,好比十进制逢10进1)
// 八进制表示的数字 0 1 2 3 4 5 6 7 10 11 12 ...
// 对应的十进制数 0 1 2 3 4 5 6 7 8 9 10 ...
var eight = 07
console.log(eight)
eight = 012
console.log(eight)
十六进制:
逢16进1
数字:
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | a | b | c | d | e | f | 10 | 11 | … | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
对应十进制 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | … |
var sixteen = 0xf
console.log(sixteen)
sixteen = 0x10
console.log(sixteen)
sixteen = 0x12
console.log(sixteen)
💧 JavaScript中数值的最大和最小值
// 最大值
console.log(Number.MAX_VALUE)
// 最小值
console.log(Number.MIN_VALUE)
💧 数字型三个特殊值
// 无穷大
console.log(Infinity)
// 负无穷大
// 无穷小在数学中定义为无限趋近与0的数(极限为0)
console.log(Infinity)
// 不是一个数字
// NaN Not a Number
console.log(12 - 'hello') // 数字不能减去字符串,结果为非数字
💧 isNaN()
用来判断一个变量是否为非数字的类型,返回 true 或者 fals:
- 变量为数字返回的是 false
- 变量不是数字返回的是true
console.log(isNaN(12 - 'hello'))
console.log(isNaN(12))
💦 字符串型 String
字符串型可以是引号中的任意文本,其语法为 双引号 “” 和 单引号’ ’
因为 HTML 标签里面的属性使用的是双引号,JS 这里我们更推荐使用单引号。
JS 可以用单引号嵌套双引号 ,或者用双引号嵌套单引号 (外双内单,外单内双)。但是不能同种引号相互嵌套,如单引号内使用单引号。
var str1 = 'hello'
console.log(str1)
var str2 = "world"
console.log(str2)
var str3 = '张三说:"hello"'
console.log(str3)
var str4 = "张三说:'hello'"
console.log(str4)
// 会报错
// JS中引号采用就近原则成对匹配
// '张三说:' 单引号已经完成配对
// '' 后面的单引号只能与最后的进行匹配
// var str3 = '张三说:'hello''
💧 字符串转义符
类似HTML里面的特殊字符,字符串中也有特殊字符,我们称之为转义符。
转义符都是 \ 开头的,常用的转义符及其说明如下:
var str = 'hello world \n hello world \\ \' \" he\tllo world\b '
console.log(str)
💧 字符串长度
字符串是由若干字符组成的,这些字符的数量就是字符串的长度。通过字符串的 length 属性可以获取整个字符串的长度
var str4 = "张三说:'hello'"
console.log(str4.length)
💧 字符串拼接
多个字符串之间可以使用 + 进行拼接,其拼接方式为 字符串 + 任何类型 = 拼接之后的新字符串
拼接前会把与字符串相加的任何类型转成字符串,再拼接成一个新的字符串
console.log('hello'+12)
console.log(12+'hello')
console.log('hello'+'world')
console.log('hello'+true)
console.log('hello'+undefined)
console.log('hello'+null)
字符串和变量拼接,变量是不能添加引号的,因为加引号的变量会变成字符串,如果变量两侧都有字符串拼接,口诀“引引加加”
var age = 12
console.log("张三今年" + age + "岁")
💦 布尔型 Boolean
布尔类型有两个值:true 和 false ,其中 true 表示真(对),而 false 表示假(错)。
布尔型和数字型相加的时候, true 的值为 1 ,false 的值为 0。
console.log(false+12)
console.log(true+12)
💦 Undefined
一个声明后没有被赋值的变量会有一个默认值 undefined
var a
console.log(a)
console.log(a+1)
console.log(a+' hello')
未定义与数相加为非数字
💦 Null
一个声明变量给 null 值,里面存的值为空(即有一个盒子,但是里面没有东西)
var a = null
console.log(a)
// null 与数字相加 会转换为0
console.log(a+12)
console.log(a+' hello')
// null 和 false 都转换为0
console.log(a+false)
🌊 获取变量数据类型
typeof 可用来获取检测变量的数据类型
console.log(typeof 12)
console.log(typeof 'hello')
console.log(typeof true)
console.log(typeof undefined)
console.log(typeof null)
🌊 字面量
字面量是在源代码中一个固定值的表示法,字面量就是显示的值,看到啥就是啥。
在浏览器的控制台,数字显示为蓝色,字符串为黑色,null 和 undefined 为浅灰色(不同的浏览器可能会有差异)
console.log(12)
console.log('hello')
console.log(undefined)
console.log(null)
🌊 数据类型转换
把一种数据类型的变量转换成另外一种数据类型
通常会实现3种方式的转换:
- 转换为字符串类型
- 转换为数字型
- 转换为布尔型
💦 转换为字符串
<script>
var a = 123
// 方式1 调用toString() 变量名.toString()
console.log(a.toString())
console.log( typeof a.toString())
// 方式2 使用String()进行强制转换
console.log(String(a))
console.log( typeof String(a))
// 方式3 使用 + 与字符串进行拼接
// 拼接上长度为0的字符串
console.log((a + ''))
console.log( typeof (a + ''))
</script>
三种转换方式,我们更喜欢用第三种加号拼接字符串转换方式, 这一种方式也称之为隐式转换。
💦 转换为数字型
注意 parseInt 和 parseFloat 单词的大小写
var a = '123'
var b = '3.14'
var c = true
var d = null
var e = undefined
// 转为整数
// 小数转为整数会向下取整
console.log( parseInt(a),parseInt(b),parseInt(c),parseInt(d),parseInt(e) )
// console.log( parseInt(b) )
// console.log( parseInt(c) ) // 转换失败 非数字
// console.log( parseInt(d) ) // 转换失败 非数字
// console.log( parseInt(e) ) // 转换失败 非数字
// 转为小数
console.log( parseFloat(a),parseFloat(b),parseFloat(c),parseFloat(d),parseFloat(e) )
// console.log( parseFloat(b) )
// console.log( parseFloat(c) ) // 转换失败 非数字
// console.log( parseFloat(d) ) // 转换失败 非数字
// console.log( parseFloat(e) ) // 转换失败 非数字
// 强制转换
console.log( Number(a),Number(b),Number(c),Number(d),Number(e) )
// console.log( Number(b) )
// console.log( Number(c) ) // true 为 1,false 为 0
// console.log( Number(d) ) // null 为 0
// console.log( Number(e) ) // 转换失败 非数字
// 使用运算符号隐式转换
// 隐式转换是我们在进行算数运算的时候,JS 自动转换了数据类型
// 不能使用 + 与字符串进行隐式转换,会转换为字符串
console.log( a*1, b*1, c*1, d*1, e*1 )
// console.log( b * 1 )
// console.log( c * 1 ) // true 为 1,false 为 0
// console.log( d * 1 ) // null 为 0
// console.log( e * 1 ) // 转换失败 非数字
💦 计算年龄案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var year = prompt('请输入你的初始年份....') // 变量接收的值为字符串
console.log( typeof year )
var age = 2022 - year // year 隐式转换为数字型
alert('你的年龄为'+ age + '岁')
</script>
</body>
</html>
💦 转换为布尔型
代表空、否定的值会被转换为 false ,如 ‘’、0、NaN、null、undefined ,其余值都会被转换为 true。
// 代表空或否定的值转布尔值
console.log(Boolean(''))
console.log(Boolean(0))
console.log(Boolean(NaN))
console.log(Boolean(null))
console.log(Boolean(undefined))
// 非空、不为否定的值
console.log(Boolean('123'))
console.log(Boolean(1))
console.log(Boolean(-1))
console.log(Boolean(Infinity))
console.log(Boolean(-Infinity))
🥽 运算符
运算符(operator)也被称为操作符,是用于实现赋值、比较和执行算数运算等功能的符号。
JavaScript中常用的运算符有:
- 算数运算符
- 递增和递减运算符
- 比较运算符
- 逻辑运算符
- 赋值运算符
🌊 算数运算符
算术运算使用的符号,用于执行两个变量或值的算术运算。
console.log(1+1)
console.log(2-1)
console.log(3*2)
console.log(3/3)
console.log(3/2)
console.log(4%2) // 取模即求4/2的余数 可以整除,余数为0
console.log(3%2) // 不能整除 余数为1
判断 一个数能够被整除:
余数是0 就说明这个数能被整除, 这就是 % 取余运算符的主要用途
🌊 浮点数的精度问题
浮点数值的最高精度是 17 位小数,但在进行算术计算时其精确度远远不如整数。
在计算机中数的存储采用的是二进制,二进制无法精确的表示小数,所以会造成精度不准确的结果。
var result = 0.1 + 0.2; // 结果不是 0.3,而是:0.30000000000000004
console.log(0.07 * 100); // 结果不是 7, 而是:7.000000000000001
不要直接判断两个浮点数是否相等
🌊 表达式和返回值
表达式:是由数字、运算符、变量等以能求得数值的有意义排列方法所得的组合
表达式是由数字、运算符、变量等组成的式子
比如:
1+1
a+1
1*12
12/2
...
表达式最终都会有一个结果,返回给我们,我们成为返回值(表达式运算的结果)
🌊 递增和递减运算符
【视频链接】
如果需要反复给数字变量添加或减去1,可以使用递增(++)和递减( – )运算符来完成。
在 JavaScript 中,递增(++)和递减( – )既可以放在变量前面,也可以放在变量后面。放在变量前面时,我们可以称为前置递增(递减)运算符,放在变量后面时,我们可以称为后置递增(递减)运算符。
注意:递增和递减运算符必须和变量配合使用。
递增和递减运算符就是让变量加一或减一
前置递增递减和后置递增递减:
- 前置递增递减和后置递增递减运算符可以简化代码的编写,让变量的值 + 1 或 -1 比以前写法更简单
- 单独使用时,运行结果相同
- 前置递增递减和后置递增递减的区别:
主要在于变量+1或-1的返回值是在什么时候进行返回。
前置的返回值在变量加一或减一之后返回;而后置是在变量加一或减一之前返回返回值。
符号在前变量先+1或-1再返回值参与后面的运算,符号在后先返回变量的值参与后面的运算再该变量+1或-1。 - 后置:先原值运算,后自加(先人后己)
- 前置:先自加,后运算(先已后人)
- 开发时,大多使用后置递增/减,并且代码独占一行,例如:num++; 或者 num–;
var a = 10
++a // a = 11 a递增,a+1
var b = ++a + 2 // 前置,a先+1,再返回12参与运算 b = 12+2 = 14
console.log(b) // 14
var c = 10
c++ // c递增,c = 11
var d = c++ + 2 // 后置,先返回值参与运算,c再+1,d = 11+2,c = 11+1 = 12
console.log(d) // 13
var e = 10
// 等号右边从左向右计算
// 第一个e
// 后置 先返回值参与运算,e再+1
// f = 10 + ++e,e = 10+1 = 11
// 第二个e
// 前置,e先+1,再返回值参与运算
// e = 11+1 = 12
// f = 10 + 12 = 22
var f = e++ + ++e
console.log(f) // 22
🌊 比较运算符
比较运算符(关系运算符)是两个数据进行比较时所使用的运算符,比较运算后,会返回一个布尔值(true / false)作为比较运算的结果。
// 除了 === !== 其他比较运算符会进行隐式转换
console.log(2>'1') // t
console.log(2>1) // t
console.log(1<3) // t
console.log(4<'3') // f
console.log(3>='3') // t
console.log(3>=2) // t
console.log(4<=4) // t
console.log(4==4) // t
console.log(4=='4') // t
console.log(4!='4') // f
// 全等不会进行隐式转换
// 会比较数值和数据类型
console.log(4!=='4') // t
console.log(4==='4') // f
💦 三种 = 运算符
var num1 = 10;
var num2 = 100;
var res1 = num1 > num2; // f
var res2 = num1 == 11; // f
var res3 = num1 != num2; // t
🌊 逻辑运算符
逻辑运算符是用来进行布尔值运算的运算符,其返回值也是布尔值。后面开发中经常用于多个条件的判断
💦 逻辑与 &&
两边都是 true才返回 true,否则返回 false
一假都假 (只要其中一个为假就为假)
console.log(1>2 && 2>1)
console.log(1==1 && 2<1)
console.log(1==1 && 2>1)
💦 逻辑或 ||
两边都为 false 才返回 false,否则都为true
一真都真(只要其中一个为真就为真)
console.log(1>2 || 2>1)
console.log(1==1 || 2<1)
console.log(1!=1 || 2<1)
💦 逻辑非 !
逻辑非(!)也叫作取反符,用来取一个布尔值相反的值,如 true 的相反值是 false
console.log(!true)
console.log(!false)
console.log( !(4>'2') )
💦 短路运算(逻辑中断)
短路运算的原理:当有多个表达式(值)时,左边的表达式值可以确定结果时,就不再继续运算右边的表达式的值;
- 表达式1 && 表达式2
当表达式1为真时,会继续判断表达式的结果,因为&&要两个表达式都为真才为真,所以表达式1为真会继续判断第二个表达式,此时由于运行到了表达式2,所以该与运算的结果为表达式2的结果。
当表达式1为假时,不会判断表达式2,因为与运算一假都假,表达式1为假已经可以判断整个与运算,所以不会继续判断第二个表达式,最后与运算的结为表达式1的结果。- 如果第一个表达式的值为真,则返回表达式2
- 如果第一个表达式的值为假,则返回表达式1
- 表达式1 || 表达式2
当表达式1为假时,会继续判断表达式的结果,因为 || 要两个表达式都为假才为假,所以表达式1为假会继续判断第二个表达式,此时由于运行到了表达式2,所以该或运算的结果为表达式2的结果。
当表达式1为真时,不会判断表达式2,因为或运算一真都真,表达式1为真已经可以判断整个或运算,所以不会继续判断第二个表达式,最后或运算的结为表达式1的结果。- 如果第一个表达式的值为真,则返回表达式1
- 如果第一个表达式的值为假,则返回表达式2
// 1 为真(非0为真),继续向后判断
// 结果输出 12
console.log( 1 && 12 );
// 0 为假,不继续判断
// 输出 0
console.log( 0 && 213 );
console.log( 0 || 12 ); // 12
console.log( 0 || 0 ); // 0
console.log( 1 || 0 ); // 1
🌊 赋值运算符
用来把数据赋值给变量的运算符
var a = 100
a += 3 // 等价于 a = a+3
console.log( a )
a -= 3 // 等价于 a = a-3
console.log( a )
a *= 10 // 等价于 a = a*10
console.log( a )
a /= 10 // 等价于 a = a/10
console.log( a )
a %= 3 // 等价于 a = a%3
console.log( a )
🌊 运算符优先级
- 一元运算符里面的逻辑非优先级很高
- 逻辑与比逻辑或优先级高
🥽 流程控制
🌊 流程控制
流程控制就是来控制我们的代码按照什么结构顺序来执行
流程控制主要有三种结构,分别是顺序结构、分支结构和循环结构,这三种结构代表三种代码执行的顺序。
🌊 顺序流程控制
程序会按照代码的先后顺序,依次执行,代码从上到下执行
🌊 分支流程控制
分支结构:
由上到下执行代码的过程中,根据不同的条件,执行不同的路径代码(执行代码多选一的过程),从而得到不同的结果
JS 语言提供了两种分支结构语句
- if 语句
- switch 语句
💦 if 语句
语法:
// 条件成立执行代码,否则不执行
if (条件表达式) {
// 条件成立执行的代码语句
}
var age = prompt('请输入你的年龄')
if ( parseInt(age)>=18 ) {
alert('你成年了~')
}
💦 if else语句(双分支语句)
语法:
// 条件成立 执行 if 里面代码,否则执行else 里面的代码
if (条件表达式) {
// 条件成立执行的代码
} else {
// 条件不成立执行的代码
}
判断闰年:
算法:能被4整除且不能整除100的为闰年(如2004年就是闰年,1901年不是闰年)或者能够被 400 整除的就是闰年
var year = prompt('请输入年份')
var yearNum = parseInt(year)
if ((yearNum % 4 == 0 && yearNum % 100 != 0) || yearNum % 400 == 0) {
alert(year + '是闰年')
} else {
alert(year + '不是闰年')
}
💦 if else if 语句(多分支语句)
语法:
// 适合于检查多重条件。
if (条件表达式1) {
// 满足条件1执行
语句1;
} else if (条件表达式2) {
// 满足条件2执行
语句2;
} else if (条件表达式3) {
// 满足条件3执行
语句3;
....
} else {
// 上述条件都不成立执行此处代码
}
判断成绩级别:
- 90分(含)以上 ,输出:A
- 80分(含)~ 90 分(不含),输出:B
- 70分(含)~ 80 分(不含),输出:C
- 60分(含)~ 70 分(不含),输出:D
- 60分(不含) 以下,输出: E
var score = prompt('请输入你的成绩')
if ( score >= 90 ) {
alert('你的成绩等级为A')
} else if ( score >= 80 ) {
alert('你的成绩等级为B')
} else if ( score >= 70 ) {
alert('你的成绩等级为C')
} else if ( score >= 60 ) {
alert('你的成绩等级为D')
} else {
alert('你的成绩等级为E')
}
💦 三元表达式
有三元运算符组成的式子称为三元表达式.
语法:
表达式1 ? 表达式2 : 表达式3;
如果表达式1为 true ,则返回表达式2的值,如果表达式1为 false,则返回表达式3的值,类似于 if else (双分支) 的简写。
数字补0(范围0-99):
用户输入数字,如果数字小于10,则在前面补 0 ,比如01,09 ,如果数字大于10,则不需要补,比如 20。
var num = prompt('请输入一个0-99之间的数字')
// 大于等于10不用补0直接返回
// 小于10进行补0
var res = num>=10 ? num : '0'+num
alert(res)
💦 switch 语句
switch 语句也是多分支语句,它用于基于不同的条件来执行不同的代码。当要针对变量设置一系列的特定值的选项时,就可以使用 switch。
语法:
switch( 表达式 ){
case value1:
// 表达式 等于 value1 时要执行的代码
break;
case value2:
// 表达式 等于 value2 时要执行的代码
break;
default:
// 表达式 不等于任何一个 value 时要执行的代码
}
- 关键字 switch 后面括号内可以是表达式或值, 通常是一个变量
- 关键字 case , 后跟一个选项的表达式或值,后面跟一个冒号
- switch 表达式的值会与结构中的 case 的值做比较
- 如果存在匹配全等(===) ,则与该 case 关联的代码块会被执行,并在遇到 break 时停止,整个 switch 语句代码执行结束
- 如果所有的 case 的值都和表达式的值不匹配,则执行 default 里的代码
执行case 里面的语句时,如果没有break,则继续执行下一个case里面的语句。
var num = prompt('请输入一个整数数字(1-3)')
num = parseInt(num)
switch (num) {
case 1:
alert('你输入的是1')
break
case 2:
alert('你输入的是2')
break
case 3:
alert('你输入的是3')
break
default:
alert('你输入的数字大于3')
break
}
var num = prompt('请输入一个整数数字(1-3)')
num = parseInt(num)
switch (num) {
case 1:
alert('你输入的是1')
// break
case 2:
alert('你输入的是2')
// break
case 3:
alert('你输入的是3')
// break
default:
alert('你输入的数字大于3')
// break
}
💦 switch 语句和 if else if 语句的区别
- 一般情况下,它们两个语句可以相互替换
- switch…case 语句通常处理 case为比较确定值的情况, 而 if…else…语句更加灵活,常用于范围判断(大于、等于某个范围)
- switch 语句进行条件判断后直接执行到程序的条件语句,效率更高。而if…else 语句有几种条件,就得判断多少次。
- 当分支比较少时,if… else语句的执行效率比 switch语句高。
- 当分支比较多时,switch语句的执行效率比较高,而且结构更清晰。
🌊 循环
在实际问题中,有许多具有规律性的重复操作,在程序中要完成这类操作就需要重复执行某些语句。
在程序中,一组被重复执行的语句被称之为循环体,能否继续重复执行,取决于循环的终止条件。由循环体及循环的终止条件组成的语句,被称之为循环语句
JS 中的循环:
- for 循环
- while 循环
- do…while 循环
💦 for 循环
for 循环主要用于把某些代码循环若干次,通常跟计数有关系。
语法:
for(初始化变量; 条件表达式; 操作表达式 ){
//循环体
}
- 初始化变量:通常被用于初始化一个计数器,该表达式可以使用 var 关键字声明新的变量,这个变量帮我们来记录次数。
- 条件表达式:用于确定每一次循环是否能被执行。如果结果是 true 就继续循环,否则退出循环。
- 操作表达式:每次循环的最后都要执行的表达式。通常被用于更新或者递增计数器变量。当然,递减变量也是可以的。
for ( var i=0; i<10; i++ ) {
console.log(i)
}
执行过程:
💧 for 循环重复相同的代码
for( var i=0; i<10; i++ ) {
console.log('hello')
}
💧 for 循环重复不相同的代码
for ( var i=0; i<10; i++ ) {
console.log(i)
}
💧 for 循环重复不相同的代码
for( var i=0; i<15; i++ ) {
if ( i==0 ) {
console.log('当前i=0')
} else if ( i>0 && i<=10 ) {
console.log(i)
} else {
console.log('i大于10')
}
}
💧 双重 for 循环
循环嵌套是指在一个循环语句中再定义一个循环语句的语法结构,例如在for循环语句中,可以再嵌套一个for 循环,这样的 for 循环语句我们称之为双重for循环。
语法:
for (外循环的初始; 外循环的条件; 外循环的操作表达式) {
for (内循环的初始; 内循环的条件; 内循环的操作表达式) {
需执行的代码;
}
}
打印九九乘法表:
for( var i=1; i<=9; i++ ) {
var str = '' // 保存每行的结果
for ( var j=1; j<=i; j++ ) {
str += j+'X'+i+'='+i*j+'\t'
}
console.log(str)
}
💦 while 循环
while 语句可以在条件表达式为真的前提下,循环执行指定的一段代码,直到表达式不为真时结束循环。
while语句的语法结构如下:
while (条件表达式) {
// 循环体代码
}
执行思路:
- 先执行条件表达式,如果结果为 true,则执行循环体代码;如果为 false,则退出循环,执行后面代码
- 执行循环体代码
- 循环体代码执行完毕后,程序会继续判断执行条件表达式,如条件仍为true,则会继续执行循环体,直到循环条件为 false 时,整个循环过程才会结束
注意:
- 使用 while 循环时一定要注意,它必须要有退出条件,否则会成为死循环
- while 循环和 for 循环的不同之处在于 while 循环可以做较为复杂的条件判断,比如判断用户名和密码
计算 1 ~ 100 之间所有整数的和:
var sum = 0
var i=1
while (i<=100) {
sum += i
i++
}
console.log(sum)
💦 do while 循环
do… while 语句其实是 while 语句的一个变体。
该循环会先执行一次代码块,然后对条件表达式进行判断,如果条件为真,就会重复执行循环体,否则退出循环。
do… while 语句的语法结构如下:
do {
// 循环体代码 - 条件表达式为 true 时重复执行循环体代码
} while(条件表达式);
执行思路:
- 先执行一次循环体代码
- 再执行条件表达式,如果结果为 true,则继续执行循环体代码,如果为 false,则退出循环,继续执行后面代码
注意:先再执行循环体,再判断,我们会发现 do…while 循环语句至少会执行一次循环体代码
var i=0
do {
console.log(i)
i++
} while (i<0)
i = 0
while ( i<0 ) {
console.log(i)
i++
}
💦 continue break
💧 continue 关键字
continue 关键字用于立即跳出本次循环,继续下一次循环(本次循环体中 continue 之后的代码就会少执行一次)。
for ( var i=0; i<10; i++ ) {
if ( i==5 ) {
continue
}
console.log('当前i的值为:'+i);
}
💧 break 关键字
break 关键字用于立即跳出整个循环(循环结束)。
for (var i = 0; i < 10; i++) {
if (i == 5) {
break
}
console.log('当前i的值为:' + i)
}
🥽 对象
🌊 对象简介
- 为什么需要对象数据类型
- 原始值(数值 Number、大整数 BigInt、字符串 String、布尔值 Boolean等)只能用来表示一些简单的数据,原始值只能表示独立的数据,不能表示复杂数据。比如:需要在程序中表示一个人的信息。
- 对象:对象是JS中的一种复合数据类型,它相当于一个容器,在对象中可以存储各种不同类型数据
- 对象中可以存储多个各种类型的数据,对象中存储的数据,我们称为属性。
🌊 对象的创建与对象属性的增删改查
💦 创建对象
// 创建一个空对象
let obj = new Object()
let obj = Object() // 在使用Object创建对象时new关键字可以省略
console.log(obj)
💦 添加属性
语法:
对象.属性名 = 属性值
obj.name= "孙悟空"
obj.age = 18
obj.gender = "男"
console.log(obj)
💦 读取属性
语法:
对象.属性名
- 如果读取的是一个对象中没有的属性不会报错而是返回undefined
console.log(obj.name)
console.log(obj.hobby)
💦 修改属性
语法:
对象.属性名 = 新属性值
obj.name = "Tom"
console.log(obj.name)
console.log(obj)
💦 删除属性
语法:
delete 对象.属性名
deleteobj.name
console.log(obj.name)
console.log(obj)
🌊 对象的属性
- 属性名
- 通常属性名就是一个字符串,所以属性名可以是任何值,没有什么特殊要求。但是如果你的属性名太特殊了,不能直接使用,需要使用
[]
来设置,虽然如此,但是我们还是强烈建议属性名也按照标识符的规范命名let obj = Object() obj.name = "孙悟空" obj.if = "哈哈" // 关键字为属性名不建议 obj.let = "嘻嘻"// 不建议 obj["1231312@#@!#!#!"] = "呵呵"// 不建议 console.log(obj)
- 也可以使用符号(symbol)作为属性名,来添加属性。获取这种属性时,也必须使用symbol,且该symbol必须为之前添加属性时使用的symbol。使用symbol添加的属性,通常是那些不希望被外界访问的属性。
- 使用
[]
去操作属性时,可以使用变量。使用.
的形式添加属性时,不能使用变量。let mySymbol= Symbol() console.log(mySymbol) // 使用symbol作为属性名 obj[mySymbol] = "通过symbol添加的属性" console.log(obj) console.log(obj[mySymbol]) let newSymbol = Symbol() console.log(obj[newSymbol])
- 通常属性名就是一个字符串,所以属性名可以是任何值,没有什么特殊要求。但是如果你的属性名太特殊了,不能直接使用,需要使用
- 属性值
- 对象的属性值可以是任意的数据类型,也可以是一个对象
obj.a = 123 obj.b = 'hello' obj.c = true obj.d = 123n obj.f = Object() obj.f.name = "猪八戒" obj.f.age = 28 console.log(obj)
- 对象的属性值可以是任意的数据类型,也可以是一个对象
- 查看对象中是否具有某个属性
- in 运算符:用来检查对象中是否含有某个属性,语法:
属性名 in obj
,如果有返回true,没有返回false// obj对象中是否具有name属性 console.log("name" in obj) // obj对象中是否具有hobby属性 console.log("hobby" in obj)
- in 运算符:用来检查对象中是否含有某个属性,语法:
🌊 对象字面量
- 对象字面量其实就是创建对象的另一种方式
- 可以直接使用
{}
来创建对象 - 使用
{}
所创建的对象,可以直接向对象中添加属性 - 语法:
{ 属性名:属性值, [属性名]:属性值, // 用于添加属性名特殊的属性 ...... }
let mySymbol = Symbol()
let obj2 = {
name:"孙悟空",
age:18,
"hobby": 123, // 属性名其实是一个字符串
["gender"]:"男", // 属性名特殊的属性
[mySymbol]:"特殊的属性", // 属性名特殊的属性
hello:{ // 对象的属性值可以是一个对象
a:1,
b:true
}
}
console.log(obj2)
🌊 枚举对象中的属性(for-in语句)
- 枚举属性,指将对象中的所有的属性全部获取
- 枚举属性使用 for-in 语句
- 语法:
for(let propName in 对象){ 语句... }
- for-in 的循环体会执行多次,有几个属性就会执行几次,每次执行时,都会将一个属性名赋值给我们所定义的变量
- 语法:
- 注意:并不是所有的属性都可以枚举,比如:使用symbol(符号)添加的属性,符号添加的属性一般不希望被外界所访问,符号添加的属性不可枚举。
let obj = {
name:'孙悟空',
age:18,
gender:"男",
address:"花果山",
[Symbol()]:"测试的属性" // 符号添加的属性是不能枚举
}
for(let propName in obj){
console.log(propName, obj[propName])
}
🌊 可变类型
-
原始值都属于不可变类型,一旦创建就无法修改,在内存中不会创建重复的原始值
-
对象属于可变类型,对象创建完成后,可以任意的添加删除修改对象中的属性。
- 注意:
- 当对两个对象进行相等或全等比较时,比较的是对象的内存地址
- 如果有两个变量同时指向一个对象,通过一个变量修改对象时,对另外一个变量也会产生影响。当修改一个对象时,所有指向该对象的变量都会受到影响
🌊 对象的修改与变量的修改
- 修改对象:
- 修改对象时,如果有其他变量指向该对象, 则所有指向该对象的变量都会受到影响。如果我们修改存储对象的变量的指向(修改变量),即变量指向为不同的对象,此时通过变量修改对象不会影响其他变量所指向的对象。
- 修改变量:
- 修改变量时,只会影响当前的变量,不会影响其他变量。
- 在使用变量存储对象时,很容易因为改变变量指向的对象,提高代码的复杂度,所以通常情况下,声明存储对象的变量时会使用const。
- 注意:const只是禁止变量被重新赋值,对对象的修改没有任何影响。对于指向对象的变量来说,存储的为对象的地址,只要其指向没变(存储的地址没变)相当于该变量没有修改,修改对象的属性不会影响变量的指向。
🌊 方法(method)
当一个对象的属性指向一个函数,那么我们就称这个函数是该对象的方法,调用函数就称为调用对象的方法
let obj = {}
obj.name = "孙悟空"
obj.age = 18
// 函数也可以成为一个对象的属性,对象的方法
obj.sayHello = function(){
console.log("hello")
}
console.log(obj)
obj.sayHello() // 调用对象的方法 对象.方法()
🥽 函数
函数(Function):函数也是一个对象,它具有其他对象所有的功能,函数中可以存储代码,且可以在需要时调用这些代码
🌊 函数的创建
语法:
function 函数名(){
语句...
}
// 创建一个函数对象
function fn(){
console.log("你好!")
console.log("Hello!")
}
// 使用typeof检查函数对象时会返回function
console.log(typeof fn)
🌊 函数的调用
调用函数就是执行函数中存储的代码,语法:函数名()
。对于函数,调用几次,函数中的代码就会执行几次。
fn()
fn()
fn()
🌊 函数的创建方式
-
函数的创建方式主要有三种:
- 函数声明
function 函数名(){ 语句... }
- 函数表达式
const 变量 = function(){ 语句... }
- 箭头函数
const 变量 = () => { 语句... } // 如果箭头函数的函数体只有一句语句可以简写 const 变量 = () => 语句
function fn(){ console.log("函数声明所定义的函数~") } const fn2 = function(){ console.log("函数表达式") } const fn3 = () => { console.log("箭头函数") } const fn4 = () => console.log("箭头函数") console.log(typeof fn) console.log(typeof fn2) console.log(typeof fn3) console.log(typeof fn4)
- 函数声明
🌊 参数
💦 函数的参数
-
形式参数
- 在定义函数时,可以在函数中指定数量不等的形式参数(形参)
- 在函数中定义形参,就相当于在函数内部声明了对应的变量但是没有赋值。函数形参的赋值,发生在函数调用时实参的传递。
-
函数定义形参语法:
- 函数声明
function 函数名([参数]){ 语句... }
- 函数表达式
const 变量 = function([参数]){ 语句... }
- 箭头函数
const 变量 = ([参数]) => { 语句... }
- 函数声明
-
实际参数
- 在调用函数时,可以在函数的()传递数量不等的实参
- 实参会赋值给其对应的形参
- 实参:
- 如果实参和形参数量相同,则对应的实参赋值给对应的形参
- 如果实参多余形参,则多余的实参不会使用
- 如果形参多余实参,则多余的形参为undefined
- 实参的类型
- JS中不会检查参数的类型,可以传递任何类型的值作为参数
function fn(a, b){ console.log("a =", a, "\tb =", b) } fn(1, 2) fn(1, 2, 3) fn(1) fn(true, "hello") fn(null, 11n) fn({name:"孙悟空"},"hello")
-
箭头函数的参数
- 当箭头函数中只有一个参数时,可以省略()
const fn2 = a => { console.log("a =", a); }
- 当箭头函数中只有一个参数时,可以省略()
💦 函数参数的默认值
定义函数的参数时,可以为参数指定默认值。函数参数的默认值,会在形参没有对应实参传递时生效。
const fn3 = (a=10, b=20, c=30) => {
console.log("a =", a);
console.log("b =", b);
console.log("c =", c); // 由于在调用函数时形参c没有实参传递,所以形参c的值为默认值30
}
fn3(1, 2)
💦 对象类型数据作为函数实参
对象类型的数据作为函数的实参,实际上是将保存对象地址变量中的对象地址作为函数的实参传递给函数形参,所以函数的形参得到的为对象的地址,即对象类型的数据作为函数的实参,函数的形参也会指向该对象。
对象类型数据作为函数实参,在函数中对对象的属性进行修改会对对象的属性造成影响。
function f(a) {
console.log('f函数中输出: ', a)
a.name = 'Tom'
console.log('f函数中输出: ', a)
}
let obj = {
name: 'Mary'
}
f(obj)
console.log('f函数外输出: ', obj)
💦 函数作为参数
在JS中,函数也是一个对象(函数被称为一等函数或一等对象,别的对象能做的事情,函数也可以)
function fn(a) {
console.log(a)
if (typeof a === 'function') {
// 因为函数为对象,函数作为实参,a指向函数对象,a可以当前函数调用
a() // 如果传递的实参为函数对象则调用函数
}
}
function fn2(){
console.log("我是fn2")
}
fn(fn2)
fn(function(){
console.log("我是匿名函数~")
})
fn(()=>console.log("我是箭头函数"))
🌊 返回值
💦 函数的返回值
-
在函数中,可以通过return关键字来指定函数的返回值,返回值就是函数的执行结果,函数调用完毕返回值便会作为结果返回
function sum(a, b) { // console.log(a + b) // 计算完成后,将计算的结果返回而不是直接打印 return a + b } let numSum = sum(12, 13) console.log(numSum)
-
任何值都可以作为返回值使用(包括对象和函数之类),如果return后不跟任何值,则相当于返回undefined,如果不写return,那么函数的返回值依然是undefined
function fn1() { return 'hello' } function fn2() { return 123 } function fn3() { return {name: 'Tom'} } function fn4() { function fnInner() { console.log('fn4 fnInner') } return fnInner } function fn5() { return } function fn6() {} console.log('fn1', fn1()) // 打印调用函数后的返回值 console.log('fn2', fn2()) console.log('fn3', fn3()) console.log('fn4', fn4()) console.log('fn5', fn5()) console.log('fn6', fn6())
-
return一执行函数立即结束,return后面的代码不会继续执行
function fn(a, b) { let sum = a + b return sum console.log('hello world') console.log('hello world') console.log('hello world') console.log('hello world') } let sum = fn(11, 12) console.log(sum)
💦 箭头函数的返回值
对于箭头函数,如果箭头函数的函数体只有一句语句且返回值为该语句的计算值,则返回值可以直接写在箭头后,如果直接在箭头后设置对象字面量为返回值时,对象字面量必须使用()括起来,否则该对象字面量会被认为是函数体
let add = (a, b) => a + b
let fn = () => ({name:"孙悟空"})
let sum = add(12, 23)
let result = fn()
console.log(sum)
console.log(result)
🌊 作用域
💦 作用域简介
- 作用域指的是一个变量的可见区域
- 作用域有两种:
- 全局作用域
- 全局作用域在网页运行时创建,在网页关闭时销毁
- 所有直接编写到script标签中的代码都位于全局作用域中
- 全局作用域中的变量是全局变量,可以在任意位置访问
- 局部作用域
- 块作用域
- 块作用域是一种局部作用域
- 块作用域在代码块执行时创建,代码块执行完毕它就销毁
- 在块作用域中声明的变量是局部变量,只能在块内部访问,外部无法访问
- 函数作用域
- 函数作用域也是一种局部作用域
- 函数作用域在函数调用时产生,调用结束后销毁
- 函数每次调用都会产生一个全新的函数作用域
- 在函数中定义的变量是局部变量,只能在函数内部访问,外部无法访问
- 块作用域
- 全局作用域
💦 作用域链
当我们使用一个变量时,JS解释器会优先在当前作用域中寻找变量,如果找到了则直接使用;如果没找到,则去上一层作用域中寻找,找到了则使用
如果没找到,则继续去上一层寻找,以此类推;如果一直到全局作用域都没找到,则报错 xxx is not defined。
作用域链,就是就近原则,会寻找距离使用位置最近的作用域的变量。
let b = 33
function fn(){
function f1(){
let b = 55
console.log(b) // 当前作用域就有b,所有输出为当前作用域的b
}
f1()
console.log(b) // 当前作用域无b,会向外查找
}
fn()
🌊 window对象
🥽 严格模式
- JS运行代码的模式有两种:
- 正常模式
- 默认情况下代码都运行在正常模式中,在正常模式,语法检查并不严格
- 它的原则是:能不报错的地方尽量不报错
- 这种处理方式导致代码的运行性能较差
- 严格模式
- 在严格模式下,语法检查变得严格
- 禁止一些语法
- 更容易报错,有些正常模式下不报错的在严格模式下会报错,如
a = 10
即直接给变量a赋值没有声明,在正常模式下不报错,但是在严格模式下会报错 - 提升了性能
- 在严格模式下,语法检查变得严格
- 正常模式
- 在开发中,应该尽量使用严格模式,这样可以将一些隐藏的问题消灭在萌芽阶段,同时也能提升代码的运行性能
- 开启严格模式
- 全局严格模式,在js代码的开头(js代码第一行),写如下代码开启全局严格模式,
"use strict"
"use strict" // 全局的严格模式 a = 10
- 只在函数中开启严格模式
function fn(){ "use strict" // 函数的严格的模式 ... }
- 全局严格模式,在js代码的开头(js代码第一行),写如下代码开启全局严格模式,
🥽 面向对象
🌊 面向对象概述
- 面向对象编程(OOP)
- 程序是干嘛的?
- 程序就是对现实世界的抽象
- 对象是干嘛的?
- 一个事物抽象到程序中后就变成了对象
- 在程序的世界中,一切皆对象
- 面向对象的编程
- 面向对象的编程指,程序中的所有操作都是通过对象来完成
- 做任何事情之前都需要先找到它的对象,然后通过对象来完成各种操作
- 程序是干嘛的?
- 一个事物通常由两部分组成:数据和功能
- 一个对象由两部分组成:属性和方法
- 事物的数据到了对象中,体现为属性
- 事物的功能到了对象中,体现为方法
const five = {
// 添加属性
name:"王老五",
age:48,
height:180,
weight:100,
// 添加方法
sleep(){
console.log(this.name + "睡觉了~")
},
eat(){
console.log(this.name + "吃饭了~")
}
}
🌊 类
- 使用Object或对象字面量
{}
创建对象的问题:- 无法区分出不同类型的对象
- 不方便批量创建对象
- 在JS中可以通过类(class)来解决这个问题:
- 类是对象模板,可以将对象中的属性和方法直接定义在类中,定义后,就可以直接通过类来创建对象。类好比汽车制作的图纸,每个汽车为汽车这个类对应的汽车对象。
- 通过同一个类创建的对象,我们称为同类对象,可以使用
instanceof
来检查一个对象是否是由某个类创建,如果某个对象是由某个类所创建,则我们称该对象是这个类的实例。
- 类是创建对象的模板,要创建对象第一件事就是定义类,定义类的语法:
class 类名 {} // 类名要使用大驼峰命名 const 类名 = class {}
- 通过类创建对象
new 类()
// Person类专门用来创建人的对象
class Person{
}
// Dog类式专门用来创建狗的对象
class Dog{
}
// 使用类创建对象,便于批量创建对象
const p1 = new Person() // 调用类的构造函数创建对象
const p2 = new Person()
console.log(p1, p2) // 使用类创建对象,可以区分出不同类型的对象
const d1 = new Dog()
const d2 = new Dog()
console.log(d1, d2)
console.log(p1 instanceof Person) // true
console.log(d1 instanceof Person) // false
🌊 属性
类构造出来的对象,传统的属性添加方法
class Person {}
const p1 = new Person()
p1.name = 'Tom'
console.log(p1)
类的代码块,默认就是严格模式,类的代码块是用来设置对象的属性和方法的,不是什么代码都能写
class Person {
// Person的实例属性
// 在类中定义了类的实例属性,通过该类创建出来的对象都会具有一个属于自己的该属性
// 实例属性只能通过实例访问
// 调用实例属性 对象名.实例属性
name = "孙悟空" // Person的实例属性name,并附初始值,也可以不赋初始值,则默认初始值为undefined
age = 18
// 使用static声明的属性,是静态属性(类属性)
// 静态属性只能通过类去访问
// 调用静态属性 类名.静态属性
static hh = "静态属性"
}
const p1 = new Person()
p1.name = 'Tom' // 访问p1的实例属性name
console.log(p1)
console.log(Person.hh) // 访问Person的类属性
🌊 方法
class Person {
// 实例属性
name = "Tom"
// 方法
// 方法其实也是属性,只是方法的值为函数
// 添加方法的方式一
sayHi = function() { // 实例方法
console.log('hi world')
}
// 添加方法的方式二
// 添加方法可以直接写 `方法名+参数列表+方法体`
sayHello() { // 实例方法
console.log('hello world')
}
// 实例方法的调用 对象.方法()
// 实例方法中的this为调用方法的实例对象
printThis() {
console.log('实例方法的this: ', this)
}
// 静态方法(类方法)
// 通过类来调用 类.方法()
// 静态方法中this指向的是当前类
static printStaticThis() {
console.log('静态方法的this: ', this)
}
}
const p1 = new Person()
console.log(p1)
p1.sayHi()
p1.sayHello()
p1.printThis()
Person.printStaticThis()
🌊 构造函数
在类中可以添加一个特殊的方法constructor,该方法我们称为构造函数(构造方法),构造函数会在我们调用类创建对象时执行,我们可以在构造函数中,为实例属性进行赋值。
在构造函数中,this表示当前所创建的对象。
class Person {
constructor(name, age, gender) {
console.log("构造函数执行了~", name, age, gender)
// this指向当前创建出来的对象
// 向创建出来的对象中添加属性name,其值为通过参数传递过来的name
// 对象的属性可以动态添加
this.name = name
this.age = age
this.gender = gender
}
}
const p1 = new Person('孙悟空', 18, '男')
const p2 = new Person('猪八戒', 28, '男')
const p3 = new Person('沙和尚', 38, '男')
console.log(p1)
console.log(p2)
console.log(p3)
🌊 面向对象的三大特性
- 面向对象的三大特性:
- 封装 —— 安全性
- 继承 —— 扩展性
- 多态 —— 灵活性
💦 封装
- 对象就是一个用来存储不同属性的容器
- 对象不仅存储属性,还要负责数据的安全,即还要保证数据的合法性
- 直接添加到对象中的属性,并不安全,因为它们可以被任意的修改
- 如何确保数据的安全:
- 私有化数据
- 将需要保护的数据设置为私有,只能在类内部使用
- 实例属性使用
#
开头就变成了私有属性,私有属性只能在类内部访问
- 提供setter和getter方法来开放对数据的操作
- getter方法,用来读取属性
- setter方法,用来设置属性
- 属性设置私有,通过getter setter方法操作属性带来的好处
- 可以控制属性的读写权限
- 可以在方法中对属性的值进行验证
- 私有化数据
- 封装主要用来保证数据的安全
- 实现封装的方式:
- 属性私有化加
#
- 通过getter和setter方法来操作属性
// getter与setter使用下面的写法 // 调用属性和修改属性可以使用 对象.属性 // 但是在调用属性和修改属性时会自动调用相应的getter和setter方法 get 属性名(){ return this.#属性 } set 属性名(参数){ this.#属性 = 参数 }
- 属性私有化加
class Person {
// 实例使用#开头就变成了私有属性,私有属性只能在类内部访问
#name
#age
#gender
constructor(name, age, gender) {
this.#name = name
this.#age = age
this.#gender = gender
}
sayHello() {
console.log(this.#name)
}
// getter方法,用来读取属性
getName(){
return this.#name
}
// setter方法,用来设置属性
setName(name){
this.#name = name
}
getAge(){
return this.#age
}
setAge(age){
// 校验数据是否合法
if(age >= 0){
this.#age = age
}
}
// getter与setter使用下面的写法
// 调用属性和修改属性可以使用 对象.属性
// 但是在调用属性和修改属性时会自动调用相应的getter和setter方法
get gender(){
return this.#gender
}
set gender(gender){
this.#gender = gender
}
}
const p1 = new Person("孙悟空", 18, "男")
console.log(p1.getName())
// -11 修改的数据不合法,在setter方法中判断不合法不会继续修改,保证了数据的合法
p1.setAge(-11)
p1.gender = "女" // getter与setter使用 `get 属性` `set 属性` 写法的调用
console.log(p1.gender)
console.log(p1)
💦 多态
多态,在JS中不会检查参数的类型,所以这就意味着任何数据都可以作为参数传递,要调用某个函数,无需指定的类型,只要对象满足某些条件即可。即调用某个函数不关心函数参数的类型,关心函数的参数是否具有某些特点。
多态为我们提供了灵活性。
class Person{
constructor(name){
this.name = name
}
}
class Dog{
constructor(name){
this.name = name
}
}
class Test{
}
const dog = new Dog('旺财')
const person = new Person("孙悟空")
const test = new Test()
function sayHello(obj){
if(obj.hasOwnProperty('name')){ // 判断对象是否具有name属性
console.log("Hello,"+obj.name)
}
}
sayHello(dog)
sayHello(person)
sayHello(test)
💦 继承
- 可以通过extends关键来完成继承
- 当一个类继承另一个类时,就相当于将另一个类中的代码复制到了当前类中(简单理解)
class Animal{ constructor(name){ this.name = name } sayHello(){ console.log("动物在叫~") } } class Dog extends Animal{ } class Cat extends Animal{ } const dog = new Dog("旺财") const cat = new Cat("汤姆") dog.sayHello() cat.sayHello()
- 继承发生时,被继承的类称为 父类(超类),继承的类称为 子类
- 通过继承可以减少重复的代码,并且可以在不修改一个类的前提对其进行扩展
- 在子类中,可以通过创建同名方法来重写父类的方法
class Dog extends Animal{ // 在子类中,可以通过创建同名方法来重写父类的方法 sayHello(){ console.log("汪汪汪") } } const dog = new Dog("旺财") const cat = new Cat("汤姆") dog.sayHello() cat.sayHello()
- 重写构造函数时,构造函数的第一行代码必须为super(),super表示父类,super()调用父类的构造函数
class Cat extends Animal{ // 重写构造函数 constructor(name, age){ // 重写构造函数时,构造函数的第一行代码必须为super() super(name) // 调用父类的构造函数 this.age = age } sayHello(){ // 调用一下父类的sayHello super.sayHello() // 在方法中可以使用super来引用父类的方法 console.log("喵喵喵") } } const dog = new Dog("旺财") const cat = new Cat("汤姆", 3) dog.sayHello() cat.sayHello() console.log(dog) console.log(cat)
- 通过继承可以在不修改一个类的情况下对其进行扩展
- OCP 开闭原则,程序应该对修改关闭,对扩展开放
🌊 对象的结构
- 对象中存储属性的区域实际有两个:
- 对象自身
- 直接通过对象所添加的属性,位于对象自身中
- 在类中通过 x = y 的形式添加的属性,位于对象自身中
class Person { name = "孙悟空" // 在类中通过 x = y 的形式添加的属性 age = 18 // 在类中通过 x = y 的形式添加的属性 constructor(){ this.gender = "男" // 通过对象所添加的属性 } } const p = new Person() p.address = "花果山" // 通过对象所添加的属性
- 原型对象(prototype)
- 对象中还有一些内容,会存储到其他的对象里(原型对象)
- 在对象中会有一个属性用来存储原型对象,这个属性叫做__proto__
- 原型对象也负责为对象存储属性,当我们访问对象中的属性时,会优先访问对象自身的属性,对象自身不包含该属性时,才会去原型对象中寻找
- 会添加到原型对象中的情况:
- 在类中通过xxx(){}方式添加的方法,位于原型中
- 主动向原型中添加的属性或方法
- 对象自身
🌊 原型对象
💦 访问一个对象的原型对象
// 方式一
对象.__proto__ // 不推荐
// 方式二
Object.getPrototypeOf(对象)
class Person {
name = "孙悟空"
age = 18
sayHello() {
console.log("Hello,我是", this.name)
}
}
const p = new Person()
console.log(p)
// 一般以下划线开头的属性都是不希望直接进行访问的属性
console.log(p.__proto__)
const p = new Person()
console.log(p)
// 一般以下划线开头的属性都是不希望直接进行访问的属性
console.log(p.__proto__)
console.log(Object.getPrototypeOf(p)) // 获取某个对象的原型对象
由结果可以看出,原型对象中的数据包含:1. 对象中的数据(属性、方法等)2. constructor (对象的构造函数,其实就是对象对应的类)
console.log(p.__proto__.constructor)
console.log(p.constructor)
💦 原型链
- 原型对象也有原型,这样就构成了一条原型链,根据对象的复杂程度不同,原型链的长度也不同,p对象的原型链:p对象 --> 原型 --> 原型 --> null,obj对象的原型链:obj对象 --> 原型 --> null。
- 读取对象属性时,会优先对象自身属性,如果对象中有,则使用,没有则去对象的原型中寻找,如果原型中有,则使用,没有则去原型的原型中寻找,直到找到Object对象的原型(Object的原型没有原型(为null)),如果依然没有找到,则返回undefined
- 作用域链,是找变量的链,找不到会报错
- 原型链,是找属性的链,找不到会返回undefined
💦 原型的作用
所有的同类型对象它们的原型对象都是同一个,也就意味着,同类型对象的原型链是一样的。
class Person {
name = "孙悟空"
age = 18
sayHello() {
console.log("Hello,我是", this.name)
}
}
class Dog {}
const p = new Person()
const p2 = new Person()
const d = new Dog()
const d2 = new Dog()
console.log(p === p2)
console.log(p.__proto__ === p2.__proto__)
console.log(d.__proto__ === d2.__proto__)
console.log(p.__proto__ === d.__proto__)
原型就相当于是一个公共的区域,可以被所有该类实例访问,可以将该类实例中,所有的公共属性(方法)统一存储到原型中,这样我们只需要创建一个属性,即可被所有实例访问。
在对象中有些值是对象独有的,像属性(name,age,gender)每个对象都应该有自己值,对于这些放在对象上;但是有些值对于每个对象来说都是一样的,像各种方法,对于一样的值没必要重复的创建,所以放在对象的原型对象中
const p = new Person()
const p2 = new Person()
console.log(p.__proto__)
console.log(p2.__proto__)
console.log(p.sayHello === p2.sayHello)
JS中继承就是通过原型来实现的,当继承时,子类的原型就是一个父类的实例。
class Animal{}
class Cat extends Animal{}
const cat = new Cat()
// cat --> Animal实例 --> object --> Object原型 --> null
console.log(cat.__proto__)
console.log(cat.__proto__.__proto__)
console.log(cat.__proto__.__proto__.__proto__)
console.log(cat.__proto__.__proto__.__proto__.__proto__)
function fn() {}
console.log(fn.__proto__)
console.log(fn.__proto__.__proto__)
console.log(fn.__proto__.__proto__.__proto__)
💦 修改原型
- 大部分情况下,我们是不需要修改原型对象
- 注意:千万不要通过类的实例去修改原型
- 通过一个对象影响所有同类对象,这么做不合适
- 修改原型先得创建实例,麻烦
- 危险,如果将对象的原型对象进行整个替换,该操作很危险
- 除了通过__proto__能访问对象的原型外,还可以通过类的prototype属性,来访问实例的原型
class Person { name = "孙悟空" age = 18 sayHello() { console.log("Hello,我是", this.name) } } const p = new Person() console.log(p.__proto__) console.log(Person.prototype) console.log(Person.prototype === p.__proto__)
- 修改原型时,最好通过通过类去修改
- 好处:
- 一修改就是修改所有实例的原型
- 无需创建实例即可完成对类的修改
- 原则:
- 原型尽量不要手动改
- 要改也不要通过实例对象去改
- 通过 类.prototype 属性去修改,
类.prototype.属性 = 值
- 最好不要直接给prototype去赋值,即
类.prototype = 值
💦 instanceof
- instanceof 用来检查一个对象是否是一个类的实例
- instanceof检查的是对象的原型链上是否有该类实例,只要原型链上有该类实例,就会返回true
- Object是所有对象的原型,所以任何和对象和Object进行instanceof运算都会返回true
class Animal {}
class Dog extends Animal {}
const dog = new Dog()
// dog -> Animal的实例 -> Object实例 -> Object原型
console.log(dog instanceof Dog) // true
console.log(dog instanceof Animal) // true
console.log(dog instanceof Object) // true
💦 判断对象是否具有指定属性
- in
- 使用in运算符检查属性时,无论属性在对象自身还是在原型中,都会返回true
- 对象.hasOwnProperty(属性名) (不推荐使用)
- 用来检查一个对象的自身是否含有某个属性,该方法位于object原型中
- Object.hasOwn(对象, 属性名)
- 用来检查一个对象的自身是否含有某个属性
class Person {
name = "孙悟空"
age = 18
sayHello() {
console.log("Hello,我是", this.name)
}
}
const p = new Person()
cconsole.log("'name' in p", 'name' in p)
console.log("'address' in p", 'address' in p)
console.log('"sayHello" in p', "sayHello" in p)
console.log('-----------------------------------')
console.log('p.hasOwnProperty("name")', p.hasOwnProperty("name"))
console.log('p.hasOwnProperty("sayHello")', p.hasOwnProperty("sayHello"))
console.log('-----------------------------------')
console.log('Object.hasOwn(p, "name")', Object.hasOwn(p, "name"))
console.log('Object.hasOwn(p, "sayHello")', Object.hasOwn(p, "sayHello"))
🌊 旧类(早期JS中定义类)
早期JS中,直接通过函数来定义类,一个函数如果直接调用 xxx() 那么这个函数就是一个普通函数,一个函数如果通过new调用 new xxx() 那么这个函数就是一个构造函数
// 立即执行函数,防止类的代码还未完成书写就使用类进行对象的创建
var Person = (function () {
// 相当于class中的constructor构造函数
function Person(name, age) {
// 在构造函数中,this表示新建的对象
this.name = name
this.age = age
// 采用该方式添加方法会造成每个对象都有自己的方法,无法实现方法的共享
// this.sayHello = function(){
// console.log(this.name)
// }
}
// 向原型中添加属性(方法)
Person.prototype.sayHello = function () {
console.log(this.name)
}
// 类添加静态属性
Person.staticProperty = "xxx"
// 类添加静态方法
Person.staticMethod = function () {}
// 返回类
return Person
})()
const p = new Person("孙悟空", 18)
console.log(p)
var Animal = (function(){
function Animal(){
}
return Animal
})()
var Cat = (function(){
function Cat(){
}
// 继承Animal
// 继承之后,该类创建出来的对象的原型对象为父类的实例对象
Cat.prototype = new Animal()
return Cat
})()
var cat = new Cat()
console.log(cat)
🌊 new运算符
- new运算符是创建对象时要使用的运算符
- 使用new时,到底发生了哪些事情:【https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new】
- 当使用new去调用一个函数时,这个函数将会作为构造函数调用,类创建对象必须使用new关键字,使用new调用函数时,将会发生这些事:
- 创建一个普通的JS对象(Object对象 {}), 为了方便,称其为新对象
- 将构造函数的prototype属性设置为新对象的原型
- 使用实参来执行构造函数,并且将新对象设置为函数中的this
- 如果构造函数返回的是一个非原始值,则该值会作为new运算的返回值返回(千万不要这么做),如果构造函数的返回值是一个原始值或者没有指定返回值,则新的对象将会作为返回值返回,通常不会为构造函数指定返回值
function MyClass(){
// 1. 创建一个普通的JS对象(Object对象 {}), 为了方便,称其为新对象
var newInstance = {}
// 2. 将构造函数的prototype属性设置为新对象的原型
newInstance.__proto__ = MyClass.prototype
// 3. 使用实参来执行构造函数,并且将新对象设置为函数中的this
this = newInstance
// 4.
// 如果构造函数返回的是一个非原始值,则该值会作为new运算的返回值返回
// return {name: 'Tom'} // 则新创建的对象为{name: 'Tom'}
// 如果构造函数的返回值是一个原始值或者没有指定返回值,则新的对象将会作为返回值返回
return 1 // 新创建的对象为newInstance
}
var mc = new MyClass()
console.log(mc)
🥽 数组(Array)
🌊 简介
- 数组也是一种复合数据类型,在数组可以存储多个不同类型的数据
- 数组中存储的是有序的数据,数组中的每个数据都有一个唯一的索引,可以通过索引来操作获取数据,索引(index)是一组大于0的整数
- 数组中存储的数据叫做元素
- 创建数组
- 通过Array()来创建数组,也可以通过[]来创建数组
const arr = new Array() const arr2 = [1, 2, 3, 4, 5] // 数组字面量 console.log(arr) console.log(arr2)
- 通过Array()来创建数组,也可以通过[]来创建数组
- 向数组中添加元素
- 语法:
数组[索引] = 元素
const arr = new Array() console.log(arr) arr[0] = 10 // 向数组的第一个位置添加一个数据 arr[1] = 22 // 向数组的第二个位置添加一个数据 arr[2] = 44 // 向数组的第三个位置添加一个数据 arr[3] = 88 arr[4] = 99 console.log(arr)
- 语法:
- 读取数组中的元素
- 语法:
数组[索引]
- 如果读取了一个不存在的元素,不会报错而是返回undefined
const arr = [11, 22, 33, 44, 55] console.log(arr[0]) console.log(arr[1]) console.log(arr[10])
- 语法:
- length
- 获取数组的长度
- 获取的实际值就是数组的最大索引 + 1
- 向数组最后添加元素:
数组[数组.length] = 元素
- length是可以修改的
const arr = [11, 22, 33, 44, 55] console.log(arr) console.log(arr.length) // 获取数组的长度, 数组的最大索引 + 1 arr[arr.length] = 66 // 向数组最后添加元素 console.log(arr) console.log(arr.length) arr.length = 12 // 修改length console.log(arr) console.log(arr.length)
- 任何类型的值都可以成为数组中的元素
// 任何类型的值都可以成为数组中的元素 let arr = [1, "hello", true, null, { name: "孙悟空" }, () => {}] console.log(arr)
🌊 数组的遍历
遍历数组简单理解,就是获取到数组中的每一个元素
💦 for循环
//任何类型的值都可以成为数组中的元素
let arr = [1, "hello", true, null, { name: "孙悟空" }, () => {}]
console.log(arr)
// 遍历数组
for (let index = 0; index < arr.length; index++) {
const element = arr[index];
console.log('第' + (index+1) + '个数组元素: ', element)
}
💦 for-of语句
- for-of语句可以用来遍历可迭代对象
- 语法:
for(变量 of 可迭代的对象){ 语句... }
- 执行流程:for-of的循环体会执行多次,数组中有几个元素就会执行几次,每次执行时都会将一个元素赋值给变量
const arr = ["孙悟空", "猪八戒", "沙和尚", "唐僧"]
// 每次执行时都会将一个元素赋值给变量
for(let value of arr){
console.log(value)
}
// for-of语句可以用来遍历可迭代对象
for(let value of "hello"){
console.log(value)
}
🥽 Map
- Map用来存储键值对结构的数据(key-value)
- Object中存储的数据就可以认为是一种键值对结构
- Map和Object的主要区别:
- Object中的属性名只能是字符串或符号,如果传递了一个其他类型的属性名,JS解释器会自动将其转换为字符串
const obj = { "name":"孙悟空", 'age':18, [Symbol()]:"哈哈", [obj2]:"嘻嘻" // JS解释器会自动将其转换为字符串 object Object } console.log(obj)
- Map中任何类型的值都可以成为数据的key
- Object中的属性名只能是字符串或符号,如果传递了一个其他类型的属性名,JS解释器会自动将其转换为字符串
🌊 创建Map
语法:new Map()
// 创建一个Map
const map = new Map()
console.log(map)
// Map中任何类型的值都可以成为数据的key
// map.set(key, value) 向map中添加键值对
map.set("name", "孙悟空")
const obj2 = {}
map.set(obj2, "呵呵")
map.set(NaN, "哈哈哈")
console.log(map)
🌊 Map的属性和方法
💦 map.set(key, value) 向map中添加键值对
// 创建一个Map
const map = new Map()
console.log(map)
// Map中任何类型的值都可以成为数据的key
// map.set(key, value) 向map中添加键值对
map.set("name", "孙悟空")
const obj2 = {}
map.set(obj2, "呵呵")
map.set(NaN, "哈哈哈")
console.log(map)
💦 map.get(key) 根据key获取值
console.log(map.get("name"))
console.log(map.get(NaN))
console.log(map.get(obj2))
💦 map.size 获取map中键值对的数量
console.log(map)
console.log(map.size)
💦 map.delete(key) 删除指定数据
console.log(map)
console.log(map.size)
map.delete(NaN)
console.log(map)
console.log(map.size)
💦 map.has(key) 检查map中是否包含指定键
console.log(map.has("name"))
map.delete(NaN)
console.log(map.has(NaN))
💦 map.clear() 删除全部的键值对
console.log(map)
console.log(map.size)
map.clear()
console.log(map)
console.log(map.size)
🌊 map转换为数组
💦 Array.form()
const map = new Map()
map.set("name", "孙悟空")
map.set("age", 18)
// 将map转换为数组
// 转换的结果为二维数组
// Map的每个键值对的键和值组成一个数组作为转换结果数组的元素
const arr = Array.from(map) // [["name","孙悟空"],["age",18]]
console.log(map)
console.log(arr)
💦 扩展转换
const map = new Map()
map.set("name", "孙悟空")
map.set("age", 18)
// 将map转换为数组
// const arr = Array.from(map) // [["name","孙悟空"],["age",18]]
// 转换的结果为二维数组
// Map的每个键值对的键和值组成一个数组作为转换结果数组的元素
const arr = [...map]
console.log(map)
console.log(arr)
🌊 通过二维数组创建Map
const map2 = new Map([
["name", "猪八戒"],
["age", 18],
[{}, () => {}],
])
// 数组中的每个一维数组为一个键值对
// 每个一维数组的第一个元素为键,第二个元素为值
console.log(map2)
🌊 遍历map
💦 for-of
// 遍历map
for (const entry of map) {
// 获取的entry为map中的每个键值对
// entry是一个一维数组
// entry中第一个元素为键值对的键,第二个元素为键值对的值
console.log(entry)
const [key, value] = entry // 对entry解构赋值
console.log(key, value)
}
console.log('------------------------------------')
// 在获取到map的每个键值对时直接进行解构赋值
for (const [key, value] of map) {
console.log(key, value)
}
💦 Map.forEach()
// Map.forEach()遍历map
// 需要传递一个回调函数
// map中有几个键值对就会执行几次回调函数
// 回调函数的第一个参数为键值对的键,第二个参数为键值对的值
// 回调函数的第三个参数为map本身
map.forEach((key, value, map)=>{
console.log(key, value, map)
})
💦 map.keys() 获取map的所有的key
console.log(map.keys()) // 获取map的所有的key
for (const key of map.keys()) {
console.log(key)
}
💦 map.values() 获取map的所有的value
console.log(map.values()) // 获取map的所有的value
for (const value of map.values()) {
console.log(value)
}
💦 map.entries() 获取map的所有的键值对
console.log(map.entries()) // 获取map的所有的键值对
for (const entry of map.entries()) {
console.log(entry)
}