笔记配套课程:JS基础Day1-01-JavaScript核心与进阶课程安排_哔哩哔哩_bilibili
菜鸟教程参考:JavaScript 教程 | 菜鸟教程 (runoob.com)
一、JS基础
了解变量、数据类型、运算符等基础概念,能够实现数据类型的转换,结合四则运算体会如何编程。
- 体会现实世界中的事物与计算机的关系
- 理解什么是数据并知道数据的分类
- 理解变量存储数据的“容器”
- 掌握常见运算符的使用,了解优先级关系
- 知道 JavaScript 数据类型隐式转换的特征
介绍
掌握 JavaScript 的引入方式,初步认识 JavaScript 的作用
引入方式
JavaScript 程序不能独立运行,它需要被嵌入 HTML 中,然后浏览器才能执行 JavaScript 代码。通过 script 标签将 JavaScript 代码引入到 HTML 中,有两种方式:
内部方式
通过 script 标签包裹 JavaScript 代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 引入方式</title>
</head>
<body>
<!-- 内联形式:通过 script 标签包裹 JavaScript 代码 -->
<script>
alert('嗨,欢迎来传智播学习前端技术!')
</script>
</body>
</html>
外部形式
一般将 JavaScript 代码写在独立的以 .js 结尾的文件中,然后通过 script 标签的 src 属性引入
// demo.js
document.write('嗨,欢迎来传智播学习前端技术!')
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 引入方式</title>
</head>
<body>
<!-- 外部形式:通过 script 的 src 属性引入独立的 .js 文件 -->
<script src="demo.js"></script>
</body>
</html>
如果 script 标签使用 src 属性引入了某 .js 文件,那么 标签的代码会被忽略!!!如下代码所示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 引入方式</title>
</head>
<body>
<!-- 外部形式:通过 script 的 src 属性引入独立的 .js 文件 -->
<script src="demo.js">
// 此处的代码会被忽略掉!!!!
alert(666);
</script>
</body>
</html>
注释和结束符
通过注释可以屏蔽代码被执行或者添加备注信息,JavaScript 支持两种形式注释语法:
单行注释
使用 // 注释单行代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 注释</title>
</head>
<body>
<script>
// 这种是单行注释的语法
// 一次只能注释一行
// 可以重复注释
document.write('嗨,欢迎来传智播学习前端技术!');
</script>
</body>
</html>
多行注释
使用 /* */ 注释多行代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 注释</title>
</head>
<body>
<script>
/* 这种的是多行注释的语法 */
/*
更常见的多行注释是这种写法
在些可以任意换行
多少行都可以
*/
document.write('嗨,欢迎来传智播学习前端技术!')
</script>
</body>
</html>
注:编辑器中单行注释的快捷键为 ctrl + /
结束符
在 JavaScript 中 ; 代表一段代码的结束,多数情况下可以省略 ; 使用回车(enter)替代。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 结束符</title>
</head>
<body>
<script>
alert(1);
alert(2);
alert(1)
alert(2)
</script>
</body>
</html>
实际开发中有许多人主张书写 JavaScript 代码时省略结束符 ;
输入和输出
输出和输入也可理解为人和计算机的交互,用户通过键盘、鼠标等向计算机输入信息,计算机处理后再展示结果给用户,这便是一次输入和输出的过程。
举例说明:如按键盘上的方向键,向上/下键可以滚动页面,按向上/下键这个动作叫作输入,页面发生了滚动了这便叫输出。
输出
JavaScript 可以接收用户的输入,然后再将输入的结果输出:
alert()、document.wirte()
以数字为例,向 alert() 或 document.write()输入任意数字,他都会以弹窗形式展示(输出)给用户。
输入
向 prompt() 输入任意内容会以弹窗形式出现在浏览器中,一般提示用户输入一些内容。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 输入输出</title>
</head>
<body>
<script>
// 1. 输入的任意数字,都会以弹窗形式展示
document.write('要输出的内容')
alert('要输出的内容');
// 2. 以弹窗形式提示用户输入姓名,注意这里的文字使用英文的引号
prompt('请输入您的姓名:')
</script>
</body>
</html>
js组成
JavaScript的组成
-
ECMAScript:
- 规定了js基础语法核心知识。
- 比如:变量、分支语句、循环语句、对象等等
-
Web APIs :
- DOM 文档对象模型, 定义了一套操作HTML文档的API
- BOM 浏览器对象模型,定义了一套操作浏览器窗口的API
变量
理解变量是计算机存储数据的“容器”,掌握变量的声明方式
变量是计算机中用来存储数据的“容器”,它可以让计算机变得有记忆,通俗的理解变量就是使用【某个符号】来代表【某个具体的数值】(数据)
<script>
// x 符号代表了 5 这个数值
x = 5
// y 符号代表了 6 这个数值
y = 6
//举例: 在 JavaScript 中使用变量可以将某个数据(数值)记录下来!
// 将用户输入的内容保存在 num 这个变量(容器)中
num = prompt('请输入一数字!')
// 通过 num 变量(容器)将用户输入的内容输出出来
alert(num)
document.write(num)
</script>
声明
声明(定义)变量有两部分构成:声明关键字、变量名(标识)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 声明和赋值</title>
</head>
<body>
<script>
// let 变量名
// 声明(定义)变量有两部分构成:声明关键字、变量名(标识)
// let 即关键字,所谓关键字是系统提供的专门用来声明(定义)变量的词语
// age 即变量的名称,也叫标识符
let age
</script>
</body>
</html>
关键字是 JavaScript 中内置的一些英文词汇(单词或缩写),它们代表某些特定的含义,如 let 的含义是声明变量的,看到 let 后就可想到这行代码的意思是在声明变量,如 let age;
let 和 var 都是 JavaScript 中的声明变量的关键字,推荐使用 let 声明变量!!!
赋值
声明(定义)变量相当于创造了一个空的“容器”,通过赋值向这个容器中添加数据。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 声明和赋值</title>
</head>
<body>
<script>
// 声明(定义)变量有两部分构成:声明关键字、变量名(标识)
// let 即关键字,所谓关键字是系统提供的专门用来声明(定义)变量的词语
// age 即变量的名称,也叫标识符
let age
// 赋值,将 18 这个数据存入了 age 这个“容器”中
age = 18
// 这样 age 的值就成了 18
document.write(age)
// 也可以声明和赋值同时进行
let str = 'hello world!'
alert(str);
</script>
</body>
</html>
关键字
JavaScript 使用专门的关键字 let 和 var 来声明(定义)变量,在使用时需要注意一些细节:
以下是使用 let 时的注意事项:
- 允许声明和赋值同时进行
- 不允许重复声明
- 允许同时声明多个变量并赋值
- JavaScript 中内置的一些关键字不能被当做变量名
以下是使用 var 时的注意事项:
- 允许声明和赋值同时进行
- 允许重复声明
- 允许同时声明多个变量并赋值
大部分情况使用 let 和 var 区别不大,但是 let 相较 var 更严谨,因此推荐使用 let,后期会更进一步介绍二者间的区别。
变量名命名规则
关于变量的名称(标识符)有一系列的规则需要遵守:
- 只能是字母、数字、下划线、$,且不能能数字开头
- 字母区分大小写,如 Age 和 age 是不同的变量
- JavaScript 内部已占用于单词(关键字或保留字)不允许使用
- 尽量保证变量具有一定的语义,见字知义
注:所谓关键字是指 JavaScript 内部使用的词语,如 let 和var,保留字是指 JavaScript 内部目前没有使用的词语,但是将来可能会使用词语。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 变量名命名规则</title>
</head>
<body>
<script>
let age = 18 // 正确
let age1 = 18 // 正确
let _age = 18 // 正确
// let 1age = 18; // 错误,不可以数字开头
let $age = 18 // 正确
let Age = 24 // 正确,它与小写的 age 是不同的变量
// let let = 18; // 错误,let 是关键字
let int = 123 // 不推荐,int 是保留字
</script>
</body>
</html>
常量
概念:使用 const 声明的变量称为“常量”。
使用场景:当某个变量永远不会改变的时候,就可以使用 const 来声明,而不是let。
命名规范:和变量一致
const PI = 3.14
注意: 常量不允许重新赋值,声明的时候必须赋值(初始化)
数据类型
计算机世界中的万事成物都是数据。
计算机程序可以处理大量的数据,为了方便数据的管理,将数据分成了不同的类型:
注:通过 typeof 关键字检测数据类型
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 数据类型</title>
</head>
<body>
<script>
// 检测 1 是什么类型数据,结果为 number
document.write(typeof 1)
</script>
</body>
</html>
数值类型
即我们数学中学习到的数字,可以是整数、小数、正数、负数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 数据类型</title>
</head>
<body>
<script>
let score = 100 // 正整数
let price = 12.345 // 小数
let temperature = -40 // 负数
document.write(typeof score) // 结果为 number
document.write(typeof price) // 结果为 number
document.write(typeof temperature) // 结果为 number
</script>
</body>
</html>
JavaScript 中的数值类型与数学中的数字是一样的,分为正数、负数、小数等。
字符串类型
通过单引号( '') 、双引号( "")或反引号包裹的数据都叫字符串,单引号和双引号没有本质上的区别,推荐使用单引号。
注意事项:
- 无论单引号或是双引号必须成对使用
- 单引号/双引号可以互相嵌套,但是不以自已嵌套自已
- 必要时可以使用转义符
\,输出单引号或双引号
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 数据类型</title>
</head>
<body>
<script>
let user_name = '小明' // 使用单引号
let gender = "男" // 使用双引号
let str = '123' // 看上去是数字,但是用引号包裹了就成了字符串了
let str1 = '' // 这种情况叫空字符串
documeent.write(typeof user_name) // 结果为 string
documeent.write(typeof gender) // 结果为 string
documeent.write(typeof str) // 结果为 string
</script>
</body>
</html>
布尔类型
表示肯定或否定时在计算机中对应的是布尔类型数据,它有两个固定的值 true 和 false,表示肯定的数据用 true,表示否定的数据用 false。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 数据类型</title>
</head>
<body>
<script>
// pink老师帅不帅?回答 是 或 否
let isCool = true // 是的,摔死了!
isCool = false // 不,套马杆的汉子!
document.write(typeof isCool) // 结果为 boolean
</script>
</body>
</html>
undefined
未定义是比较特殊的类型,只有一个值 undefined,只声明变量,不赋值的情况下,变量的默认值为 undefined,一般很少【直接】为某个变量赋值为 undefined。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 数据类型</title>
</head>
<body>
<script>
// 只声明了变量,并末赋值
let tmp;
document.write(typeof tmp) // 结果为 undefined
</script>
</body>
</html>
注:JavaScript 中变量的值决定了变量的数据类型。
类型转换
理解弱类型语言的特征,掌握显式类型转换的方法
在 JavaScript 中数据被分成了不同的类型,如数值、字符串、布尔值、undefined,在实际编程的过程中,不同数据类型之间存在着转换的关系。
隐式转换
某些运算符被执行时,系统内部自动将数据类型进行转换,这种转换称为隐式转换。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 隐式转换</title>
</head>
<body>
<script>
let num = 13 // 数值
let num2 = '2' // 字符串
// 结果为 132
// 原因是将数值 num 转换成了字符串,相当于 '13'
// 然后 + 将两个字符串拼接到了一起
console.log(num + num2)
// 结果为 11
// 原因是将字符串 num2 转换成了数值,相当于 2
// 然后数值 13 减去 数值 2
console.log(num - num2)
let a = prompt('请输入一个数字')
let b = prompt('请再输入一个数字')
alert(a + b);
</script>
</body>
</html>
注:数据类型的隐式转换是 JavaScript 的特征,后续学习中还会遇到,目前先需要理解什么是隐式转换。
补充介绍模板字符串的拼接的使用
显式转换
编写程序时过度依靠系统内部的隐式转换是不严禁的,因为隐式转换规律并不清晰,大多是靠经验总结的规律。为了避免因隐式转换带来的问题,通常根逻辑需要对数据进行显示转换。
Number
通过 Number 显示转换成数值类型,当转换失败时结果为 NaN(Not a Number)即不是一个数字。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 隐式转换</title>
</head>
<body>
<script>
let t = '12'
let f = 8
// 显式将字符串 12 转换成数值 12
t = Number(t)
// 检测转换后的类型
// console.log(typeof t);
console.log(t + f) // 结果为 20
// 并不是所有的值都可以被转成数值类型
let str = 'hello'
// 将 hello 转成数值是不现实的,当无法转换成
// 数值时,得到的结果为 NaN (Not a Number)
console.log(Number(str))
</script>
</body>
</html>
理解什么是流程控制,知道条件控制的种类并掌握其对应的语法规则,具备利用循环编写简易ATM取款机程序能力
- 运算符
- 语句
- 综合案例
运算符
算术运算符
数字是用来计算的,比如:乘法 * 、除法 / 、加法 + 、减法 - 等等,所以经常和算术运算符一起。
算术运算符:也叫数学运算符,主要包括加、减、乘、除、取余(求模)等
| 运算符 | 作用 |
|---|---|
| + | 求和 |
| - | 求差 |
| * | 求积 |
| / | 求商 |
| % | 取模(取余数),开发中经常用于作为某个数字是否被整除 |
注意:在计算失败时,显示的结果是 NaN (not a number)
// 算术运算符
console.log(1 + 2 * 3 / 2) // 4
let num = 10
console.log(num + 10) // 20
console.log(num + num) // 20
// 1. 取模(取余数) 使用场景: 用来判断某个数是否能够被整除
console.log(4 % 2) // 0
console.log(6 % 3) // 0
console.log(5 % 3) // 2
console.log(3 % 5) // 3
// 2. 注意事项 : 如果我们计算失败,则返回的结果是 NaN (not a number)
console.log('pink老师' - 2)
console.log('pink老师' * 2)
console.log('pink老师' + 2) // pink老师2
赋值运算符
赋值运算符:对变量进行赋值的运算符
= 将等号右边的值赋予给左边, 要求左边必须是一个容器
| 运算符 | 作用 |
|---|---|
| += | 加法赋值 |
| -+ | 减法赋值 |
| *= | 乘法赋值 |
| /= | 除法赋值 |
| %= | 取余赋值 |
<script>
let num = 1
// num = num + 1
// 采取赋值运算符
// num += 1
num += 3
console.log(num)
</script>
自增/自减运算符
| 符号 | 作用 | 说明 |
|---|---|---|
| ++ | 自增 | 变量自身的值加1,例如: x++ |
| -- | 自减 | 变量自身的值减1,例如: x-- |
- ++在前和++在后在单独使用时二者并没有差别,而且一般开发中我们都是独立使用
- ++在后(后缀式)我们会使用更多
注意:
- 只有变量能够使用自增和自减运算符
- ++、-- 可以在变量前面也可以在变量后面,比如: x++ 或者 ++x
<script>
// let num = 10
// num = num + 1
// num += 1
// // 1. 前置自增
// let i = 1
// ++i
// console.log(i)
// let i = 1
// console.log(++i + 1)
// 2. 后置自增
// let i = 1
// i++
// console.log(i)
// let i = 1
// console.log(i++ + 1)
// 了解
let i = 1
console.log(i++ + ++i + i)
</script>
比较运算符
使用场景:比较两个数据大小、是否相等,根据比较结果返回一个布尔值(true / false)
| 运算符 | 作用 |
|---|---|
| >(大于) | 左边是否大于右边 |
| < | 左边是否小于右边 |
| >= | 左边是否大于或等于右边 |
| <= | 左边是否小于或等于右边 |
| === | 左右两边是否类型和值都相等(重点) |
| == | 左右两边值是否相等 |
| != | 左右值不相等 |
| !== | 左右两边是否不全等 |
<script>
console.log(3 > 5)
console.log(3 >= 3)
console.log(2 == 2)
// 比较运算符有隐式转换 把'2' 转换为 2 双等号 只判断值
console.log(2 == '2') // true
// console.log(undefined === null)
// === 全等 判断 值 和 数据类型都一样才行
// 以后判断是否相等 请用 ===
console.log(2 === '2')
console.log(NaN === NaN) // NaN 不等于任何人,包括他自己
console.log(2 !== '2') // true
console.log(2 != '2') // false
console.log('-------------------------')
console.log('a' < 'b') // true
console.log('aa' < 'ab') // true
console.log('aa' < 'aac') // true
console.log('-------------------------')
</script>
逻辑运算符
使用场景:可以把多个布尔值放到一起运算,最终返回一个布尔值
| 符号 | 名称 | 日常读法 | 特点 | 口诀 |
|---|---|---|---|---|
| && | 逻辑与 | 并且 | 符号两边有一个假的结果为假 | 一假则假 |
| || | 逻辑或 | 或者 | 符号两边有一个真的结果为真 | 一真则真 |
| ! | 逻辑非 | 取反 | true变false false变true | 真变假,假变真 |
| A | B | A && B | A || B | !A |
|---|---|---|---|---|
| false | false | false | false | true |
| false | true | false | true | true |
| true | false | false | true | false |
| true | true | true | true | false |
<script>
// 逻辑与 一假则假
console.log(true && true)
console.log(false && true)
console.log(3 < 5 && 3 > 2)
console.log(3 < 5 && 3 < 2)
console.log('-----------------')
// 逻辑或 一真则真
console.log(true || true)
console.log(false || true)
console.log(false || false)
console.log('-----------------')
// 逻辑非 取反
console.log(!true)
console.log(!false)
console.log('-----------------')
let num = 6
console.log(num > 5 && num < 10)
console.log('-----------------')
</script>
运算符优先级
逻辑运算符优先级: !> && > ||
二、语句
表达式和语句
if语句
分支语句
分支语句可以根据条件判定真假,来选择性的执行想要的代码
分支语句包含:
- if分支语句(重点)
- 三元运算符
- switch语句
if 分支语句
语法:
if(条件表达式) {
// 满足条件要执行的语句
}
小括号内的条件结果是布尔值,为 true 时,进入大括号里执行代码;为false,则不执行大括号里面代码
小括号内的结果若不是布尔类型时,会发生类型转换为布尔值,类似Boolean()
如果大括号只有一个语句,大括号可以省略,但是,俺们不提倡这么做~
<script>
// 单分支语句
// if (false) {
// console.log('执行语句')
// }
// if (3 > 5) {
// console.log('执行语句')
// }
// if (2 === '2') {
// console.log('执行语句')
// }
// 1. 除了0 所有的数字都为真
// if (0) {
// console.log('执行语句')
// }
// 2.除了 '' 所有的字符串都为真 true
// if ('pink老师') {
// console.log('执行语句')
// }
// if ('') {
// console.log('执行语句')
// }
// // if ('') console.log('执行语句')
// 1. 用户输入
let score = +prompt('请输入成绩')
// 2. 进行判断输出
if (score >= 700) {
alert('恭喜考入黑马程序员')
}
console.log('-----------------')
</script>
if双分支语句
如果有两个条件的时候,可以使用 if else 双分支语句
if (条件表达式){
// 满足条件要执行的语句
} else {
// 不满足条件要执行的语句
}
例如:
<script>
// 1. 用户输入
let uname = prompt('请输入用户名:')
let pwd = prompt('请输入密码:')
// 2. 判断输出
if (uname === 'pink' && pwd === '123456') {
alert('恭喜登录成功')
} else {
alert('用户名或者密码错误')
}
</script>
if 多分支语句
使用场景: 适合于有多个条件的时候
<script>
// 1. 用户输入
let score = +prompt('请输入成绩:')
// 2. 判断输出
if (score >= 90) {
alert('成绩优秀,宝贝,你是我的骄傲')
} else if (score >= 70) {
alert('成绩良好,宝贝,你要加油哦~~')
} else if (score >= 60) {
alert('成绩及格,宝贝,你很危险~')
} else {
alert('成绩不及格,宝贝,我不想和你说话,我只想用鞭子和你说话~')
}
</script>
三元运算符(三元表达式)
使用场景: 一些简单的双分支,可以使用 三元运算符(三元表达式),写起来比 if else双分支 更简单
符号:? 与 : 配合使用
语法:
条件 ? 表达式1 : 表达式2
例如:
// 三元运算符(三元表达式)
// 1. 语法格式
// 条件 ? 表达式1 : 表达式2
// 2. 执行过程
// 2.1 如果条件为真,则执行表达式1
// 2.2 如果条件为假,则执行表达式2
// 3. 验证
// 5 > 3 ? '真的' : '假的'
console.log(5 < 3 ? '真的' : '假的')
// let age = 18
// age = age + 1
// age++
// 1. 用户输入
let num = prompt('请您输入一个数字:')
// 2. 判断输出- 小于10才补0
// num = num < 10 ? 0 + num : num
num = num >= 10 ? num : 0 + num
alert(num)
switch语句(了解)
使用场景: 适合于有多个条件的时候,也属于分支语句,大部分情况下和 if多分支语句 功能相同
注意:
- switch case语句一般用于等值判断, if适合于区间判断
- switchcase一般需要配合break关键字使用 没有break会造成case穿透
- if 多分支语句开发要比switch更重要,使用也更多
例如:
// switch分支语句
// 1. 语法
// switch (表达式) {
// case 值1:
// 代码1
// break
// case 值2:
// 代码2
// break
// ...
// default:
// 代码n
// }
<script>
switch (2) {
case 1:
console.log('您选择的是1')
break // 退出switch
case 2:
console.log('您选择的是2')
break // 退出switch
case 3:
console.log('您选择的是3')
break // 退出switch
default:
console.log('没有符合条件的')
}
</script>
断点调试
**作用:**学习时可以帮助更好的理解代码运行,工作时可以更快找到bug
浏览器打开调试界面
- 按F12打开开发者工具
- 点到源代码一栏 ( sources )
- 选择代码文件
**断点:**在某句代码上加的标记就叫断点,当程序执行到这句有标记的代码时会暂停下来
循环语句
使用场景:重复执行 指定的一段代码,比如我们想要输出10次 '我学的很棒'
学习路径:
1.while循环
2.for 循环(重点)
while循环
while : 在…. 期间, 所以 while循环 就是在满足条件期间,重复执行某些代码。
语法:
while (条件表达式) {
// 循环体
}
例如:
// while循环: 重复执行代码
// 1. 需求: 利用循环重复打印3次 '月薪过万不是梦,毕业时候见英雄'
let i = 1
while (i <= 3) {
document.write('月薪过万不是梦,毕业时候见英雄~<br>')
i++ // 这里千万不要忘了变量自增否则造成死循环
}
循环三要素:
1.初始值 (经常用变量)
2.终止条件
3.变量的变化量
例如:
<script>
// // 1. 变量的起始值
// let i = 1
// // 2. 终止条件
// while (i <= 3) {
// document.write('我要循环三次 <br>')
// // 3. 变量的变化量
// i++
// }
// 1. 变量的起始值
let end = +prompt('请输入次数:')
let i = 1
// 2. 终止条件
while (i <= end) {
document.write('我要循环三次 <br>')
// 3. 变量的变化量
i++
}
</script>
中止循环
break 中止整个循环,一般用于结果已经得到, 后续的循环不需要的时候可以使用(提高效率)
continue 中止本次循环,一般用于排除或者跳过某一个选项的时候
<script>
// let i = 1
// while (i <= 5) {
// console.log(i)
// if (i === 3) {
// break // 退出循环
// }
// i++
// }
let i = 1
while (i <= 5) {
if (i === 3) {
i++
continue
}
console.log(i)
i++
}
</script>
无限循环
1.while(true) 来构造“无限”循环,需要使用break退出循环。(常用)
2.for(;;) 也可以来构造“无限”循环,同样需要使用break退出循环。
// 无限循环
// 需求: 页面会一直弹窗询问你爱我吗?
// (1). 如果用户输入的是 '爱',则退出弹窗
// (2). 否则一直弹窗询问
// 1. while(true) 无限循环
// while (true) {
// let love = prompt('你爱我吗?')
// if (love === '爱') {
// break
// }
// }
// 2. for(;;) 无限循环
for (; ;) {
let love = prompt('你爱我吗?')
if (love === '爱') {
break
}
}
综合案例-ATM存取款机
分析:
①:提示输入框写到循环里面(无限循环)
②:用户输入4则退出循环 break
③:提前准备一个金额预先存储一个数额 money
④:根据输入不同的值,做不同的操作
(1) 取钱则是减法操作, 存钱则是加法操作,查看余额则是直接显示金额
(2) 可以使用 if else if 多分支 来执行不同的操作
完整代码:
<script>
// 1. 开始循环 输入框写到 循环里面
// 3. 准备一个总的金额
let money = 100
while (true) {
let re = +prompt(`
请您选择操作:
1.存钱
2.取钱
3.查看余额
4.退出
`)
// 2. 如果用户输入的 4 则退出循环, break 写到if 里面,没有写到switch里面, 因为4需要break退出循环
if (re === 4) {
break
}
// 4. 根据输入做操作
switch (re) {
case 1:
// 存钱
let cun = +prompt('请输入存款金额')
money = money + cun
break
case 2:
// 存钱
let qu = +prompt('请输入取款金额')
money = money - qu
break
case 3:
// 存钱
alert(`您的银行卡余额是${money}`)
break
}
}
</script>
if 多分支语句和 switch的区别:
-
共同点
- 都能实现多分支选择, 多选1
- 大部分情况下可以互换
-
区别:
- switch…case语句通常处理case为比较确定值的情况,而if…else…语句更加灵活,通常用于范围判断(大于,等于某个范围)。
- switch 语句进行判断后直接执行到程序的语句,效率更高,而if…else语句有几种判断条件,就得判断多少次
- switch 一定要注意 必须是 === 全等,一定注意 数据类型,同时注意break否则会有穿透效果
- 结论:
- 当分支比较少时,if…else语句执行效率高。
- 当分支比较多时,switch语句执行效率高,而且结构更清晰。
for 语句
掌握 for 循环语句,让程序具备重复执行能力
for 是 JavaScript 提供的另一种循环控制的话句,它和 while 只是语法上存在差异。
for语句的基本使用
- 实现循环的 3 要素
<script>
// 1. 语法格式
// for(起始值; 终止条件; 变化量) {
// // 要重复执行的代码
// }
// 2. 示例:在网页中输入标题标签
// 起始值为 1
// 变化量 i++
// 终止条件 i <= 6
for(let i = 1; i <= 6; i++) {
document.write(`<h${i}>循环控制,即重复执行<h${i}>`)
}
</script>
-
变化量和死循环,
for循环和while一样,如果不合理设置增量和终止条件,便会产生死循环。 -
跳出和终止循环
<script>
// 1. continue
for (let i = 1; i <= 5; i++) {
if (i === 3) {
continue // 结束本次循环,继续下一次循环
}
console.log(i)
}
// 2. break
for (let i = 1; i <= 5; i++) {
if (i === 3) {
break // 退出结束整个循环
}
console.log(i)
}
</script>
结论:
JavaScript提供了多种语句来实现循环控制,但无论使用哪种语句都离不开循环的3个特征,即起始值、变化量、终止条件,做为初学者应着重体会这3个特征,不必过多纠结三种语句的区别。- 起始值、变化量、终止条件,由开发者根据逻辑需要进行设计,规避死循环的发生。
- 当如果明确了循环的次数的时候推荐使用
for循环,当不明确循环的次数的时候推荐使用while循环
注意:
for的语法结构更简洁,故for循环的使用频次会更多。
循环嵌套
利用循环的知识来对比一个简单的天文知识,我们知道地球在自转的同时也在围绕太阳公转,如果把自转和公转都看成是循环的话,就相当于是循环中又嵌套了另一个循环。
实际上 JavaScript 中任何一种循环语句都支持循环的嵌套,如下代码所示:
// 1. 外面的循环 记录第n天
for (let i = 1; i < 4; i++) {
document.write(`第${i}天 <br>`)
// 2. 里层的循环记录 几个单词
for (let j = 1; j < 6; j++) {
document.write(`记住第${j}个单词<br>`)
}
}
记住,外层循环循环一次,里层循环循环全部
倒三角
// 外层打印几行
for (let i = 1; i <= 5; i++) {
// 里层打印几个星星
for (let j = 1; j <= i; j++) {
document.write('★')
}
document.write('<br>')
}
九九乘法表
样式css
span {
display: inline-block;
width: 100px;
padding: 5px 10px;
border: 1px solid pink;
margin: 2px;
border-radius: 5px;
box-shadow: 2px 2px 2px rgba(255, 192, 203, .4);
background-color: rgba(255, 192, 203, .1);
text-align: center;
color: hotpink;
}
javascript
// 外层打印几行
for (let i = 1; i <= 9; i++) {
// 里层打印几个星星
for (let j = 1; j <= i; j++) {
// 只需要吧 ★ 换成 1 x 1 = 1
document.write(`
<div> ${j} x ${i} = ${j * i} </div>
`)
}
document.write('<br>')
}
三、数组
知道什么是数组及其应用的场景,掌握数组声明及访问的语法。
数组定义
数组:(Array)是一种可以按顺序保存数据的数据类型
**使用场景:**如果有多个数据可以用数组保存起来,然后放到一个变量中,管理非常方便
数组的基本使用
定义数组和数组单元
<script>
// 1. 语法,使用 [] 来定义一个空数组
// 定义一个空数组,然后赋值给变量 classes
// let classes = [];
// 2. 定义非空数组
let classes = ['小明', '小刚', '小红', '小丽', '小米']
</script>
通过 [] 定义数组,数据中可以存放真正的数据,如小明、小刚、小红等这些都是数组中的数据,我们这些数据称为数组单元,数组单元之间使用英文逗号分隔。
访问数组和数组索引
使用数组存放数据并不是最终目的,关键是能够随时的访问到数组中的数据(单元)。其实 JavaScript 为数组中的每一个数据单元都编了号,通过数据单元在数组中的编号便可以轻松访问到数组中的数据单元了。
我们将数据单元在数组中的编号称为索引值,也有人称其为下标。
索引值实际是按着数据单元在数组中的位置依次排列的,注意是从 0 开始的,如下图所示:
观察上图可以数据单元【小明】对应的索引值为【0】,数据单元【小红】对应的索引值为【2】
<script>
let classes = ['小明', '小刚', '小红', '小丽', '小米']
// 1. 访问数组,语法格式为:变量名[索引值]
document.write(classes[0]) // 结果为:小明
document.write(classes[1]) // 结果为:小刚
document.write(classes[4]) // 结果为:小米
// 2. 通过索引值还可以为数组单重新赋值
document.write(classes[3]) // 结果为:小丽
// 重新为索引值为 3 的单元赋值
classes[3] = '小小丽'
document.wirte(classes[3]); // 结果为: 小小丽
</script>
数据单元值类型
数组做为数据的集合,它的单元值可以是任意数据类型
<script>
// 6. 数组单值类型可以是任意数据类型
// a) 数组单元值的类型为字符类型
let list = ['HTML', 'CSS', 'JavaScript']
// b) 数组单元值的类型为数值类型
let scores = [78, 84, 70, 62, 75]
// c) 混合多种类型
let mixin = [true, 1, false, 'hello']
</script>
数组长度属性
重申一次,数组在 JavaScript 中并不是新的数据类型,它属于对象类型。
<script>
// 定义一个数组
let arr = ['html', 'css', 'javascript']
// 数组对应着一个 length 属性,它的含义是获取数组的长度
console.log(arr.length) // 3
</script>
操作数组
数组做为对象数据类型,不但有 length 属性可以使用,还提供了许多方法:
- push 动态向数组的尾部添加一个单元
- unshit 动态向数组头部添加一个单元
- pop 删除最后一个单元
- shift 删除第一个单元
- splice 动态删除任意单元
unshit、shift、push、pop(头尾的添加删除)
使用以上4个方法时,都是直接在原数组上进行操作,即成功调任何一个方法,原数组都跟着发生相应的改变。并且在添加或删除单元时 length 并不会发生错乱。
<script>
// 定义一个数组
let arr = ['html', 'css', 'javascript']
// 1. push 动态向数组的尾部添加一个单元
arr.push('Nodejs')
console.log(arr)
arr.push('Vue')
// 2. unshit 动态向数组头部添加一个单元
arr.unshift('VS Code')
console.log(arr)
// 3. splice 动态删除任意单元
arr.splice(2, 1) // 从索引值为2的位置开始删除1个单元
console.log(arr)
// 4. pop 删除最后一个单元
arr.pop()
console.log(arr)
// 5. shift 删除第一个单元
arr.shift()
console.log(arr)
</script>
forEach遍历数组
forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数
注意:
1.forEach 主要是遍历数组
2.参数当前数组元素是必须要写的, 索引号可选。
<body>
<script>
// forEach 就是遍历 加强版的for循环 适合于遍历数组对象
const arr = ['red', 'green', 'pink']
const result = arr.forEach(function (item, index) {
console.log(item) // 数组元素 red green pink
console.log(index) // 索引号 0 1 2
})
// console.log(result)
</script>
</body>
map遍历数组
使用场景:
map 可以遍历数组处理数据,并且返回新的数组
语法:
<body>
<script>
const arr = ['red', 'blue', 'pink']
// 1. 数组 map方法 处理数据并且 返回一个数组
const newArr = arr.map(function (ele, index) {
// console.log(ele) // 数组元素
// console.log(index) // 索引号
return ele + index;
})
console.log(newArr) // ['red0', 'blue1', 'pink2']
</script>
</body>
map 也称为映射。映射是个术语,指两个元素的集之间元素相互“对应”的关系。
map重点在于有返回值,forEach没有返回值(undefined)
filter筛选数组
filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素
主要使用场景: 筛选数组符合条件的元素,并返回筛选之后元素的新数组
<body>
<script>
const arr = [10, 20, 30]
// 返回过滤出符合条件的新数组
// const newArr = arr.filter(function (item, index) {
// // console.log(item) // 10 20 30
// // console.log(index) // 0 1 2
// return item >= 20
// })
// 简写形式
const newArr = arr.filter(item => item >= 20)
console.log(newArr) // 20 30
</script>
</body>
join转字符串
**作用:**join() 方法用于把数组中的所有元素转换为一个字符串
语法:
<body>
<script>
const arr = ['red', 'blue', 'pink']
// 1. 数组 map方法 处理数据并且 返回一个数组
const newArr = arr.map(function (ele, index) {
// console.log(ele) // 数组元素
// console.log(index) // 索引号
return ele + '颜色'
})
console.log(newArr)
// 2. 数组join方法 把数组转换为字符串
// 小括号为空则逗号分割
console.log(newArr.join()) // red颜色,blue颜色,pink颜色
// 小括号是空字符串,则元素之间没有分隔符
console.log(newArr.join('')) //red颜色blue颜色pink颜色
console.log(newArr.join('|')) //red颜色|blue颜色|pink颜色
</script>
</body>
reduce数组求和
reduce 返回累积处理的结果,经常用于求和等
<body>
<script>
// arr.reduce(function(上一次值,当前值){}, 初始值)
const arr = [1,5,8]
// 没有初始值
const sum = arr.reduce(function(pre, current){
return pre + current
})
console.log(sum) // 14
// 上一次值 当前值 返回值(第一次循环)
// 1 5 6 (第二次循环)
// 6 8 14
// 有初始值
const sum2 = arr.reduce(function(pre, current){
return pre + current
}, 10)
console.log(sum2) // 24
// 上一次值 当前值 返回值(第一次循环)
// 10 1 11 (第二次循环)
// 11 5 16 (第三次循环)
// 16 8 24
// 箭头函数写法
const sum3 = arr.reduce((pre, current) => pre + current, 5)
console.log(sum3) // 19
// 如果是对象数组,则初始值为0不可省略
let arr2 = [
{name: '张三', salary: 10000},
{name: '李四', salary: 10000},
{name: '王五', salary: 10000}
]
const sums = arr2.reduce(function(pre, current){
return pre + current.salary
}, 0)
console.log(sums) // 30000
</script>
</body>
原理:
- 如果没有起始值,则上一次值为数组的第一个元素值
- 每一次新循环,上一次值为上次循环的返回值
- 如果有起始值,则起始值为上一次值
find查找数组
find查找元素,返回符合测试条件的 第一个 数组元素值,如果没有则返回 undefined
<body>
<script>
const arr = ['red', 'blue', 'green']
const findArr = arr.find(function (item){
return item === 'blue'
})
console.log(findArr) // blue
const arr2 = [
{name: '小米', price: 1999},
{name: '华为', price: 2888}
]
// 从众多数据里找到小米这个对象
const mi = arr2.find(function(item){
return item.name === '小米'
})
console.log(mi) // {name: '小米', price: 1999}
// 箭头函数写法
const mi2 = arr2.find(item => item.name === '华为')
console.log(mi2) // {name: '华为', price: 2888}
</script>
</body>
更多API请查看官方文档:
developer.mozilla.org/zh-CN/docs/…
常用字符串函数
<body>
<script>
// split():将字符串分割为数组,与 join() 相反
const str = 'pink,red'
console.log(str.split(',')) // ['pink','red']
const str2 = '2023-07-28'
console.log(str2.split('-')) // ['2023','07','28']
// substring(开始,结束):字符串的截取
const str3 = '今天又要做核酸了'
// 只有开始索引则默认取到最后
console.log(str3.substring(5)) // 核酸了
// 不包含结束索引
console.log(str3.substring(5,7)) // 核酸
// startsWith():判断当前字符串是否以另外一个给定的子字符串开头,结果返回布尔值
const a = 'pink老师上课中'
console.log(a.startsWith('ink')) // false
// includes():判断一个数组是否包含指定值(区分大小写),结果返回布尔值
const b = 'pink老师上课中'
console.log(b.includes('ink')) // true
</script>
</body>
练习:
<body>
<script>
// 将字符串渲染成下图这种形式
const str = '50g茶叶,清洗球'
// const a = str.split(',').map(function (item) {
// return `<span>【赠品】 ${item}</span><br>`
// }).join('')
// document.querySelector('div').innerHTML = a
document.querySelector('div').innerHTML = str.split(',').map(item => `<span>【赠品】 ${item}</span><br>`).join('')
</script>
</body>
四、函数
理解封装的意义,能够通过函数的声明实现逻辑的封装,知道对象数据类型的特征,结合数学对象实现简单计算功能。
- 理解函数的封装的特征
- 掌握函数声明的语法
- 理解什么是函数的返回值
- 知道并能使用常见的内置函数
声明和调用
函数可以把具有相同或相似逻辑的代码“包裹”起来,通过函数调用执行这些被“包裹”的代码逻辑,这么做的优势是有利于精简代码方便复用。
声明(定义)
声明(定义)一个完整函数包括关键字、函数名、形式参数、函数体、返回值5个部分
调用
声明(定义)的函数必须调用才会真正被执行,使用 () 调用函数。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 声明和调用</title>
</head>
<body>
<script>
// 声明(定义)了最简单的函数,既没有形式参数,也没有返回值
function sayHi() {
console.log('嗨~')
}
// 函数调用,这些函数体内的代码逻辑会被执行
// 函数名()
sayHi()
// 可以重复被调用,多少次都可以
sayHi()
</script>
</body>
</html>
注:函数名的命名规则与变量是一致的,并且尽量保证函数名的语义。
小案例: 小星星
<script>
// 函数声明
function sayHi() {
// document.write('hai~')
document.write(`*<br>`)
document.write(`**<br>`)
document.write(`***<br>`)
document.write(`****<br>`)
document.write(`*****<br>`)
document.write(`******<br>`)
document.write(`*******<br>`)
document.write(`********<br>`)
document.write(`*********<br>`)
}
// 函数调用
sayHi()
sayHi()
sayHi()
sayHi()
sayHi()
</script>
参数
通过向函数传递参数,可以让函数更加灵活多变,参数可以理解成是一个变量。
声明(定义)一个功能为打招呼的函数
- 传入数据列表
- 声明这个函数需要传入几个数据
- 多个数据用逗号隔开
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 函数参数</title>
</head>
<body>
<script>
// 声明(定义)一个功能为打招呼的函数
// function sayHi() {
// console.log('嗨~')
// }
// 调用函数
// sayHi()
// 这个函数似乎没有什么价值,除非能够向不同的人打招呼
// 这就需要借助参数来实现了
function sayHi(name) {
// 参数 name 可以被理解成是一个变量
console.log(name)
console.log('嗨~' + name)
}
// 调用 sayHi 函数,括号中多了 '小明'
// 这时相当于为参数 name 赋值了
sayHi('小明')// 结果为 小明
// 再次调用 sayHi 函数,括号中多了 '小红'
// 这时相当于为参数 name 赋值了
sayHi('小红') // 结果为 小红
</script>
</body>
</html>
总结:
- 声明(定义)函数时的形参没有数量限制,当有多个形参时使用
,分隔 - 调用函数传递的实参要与形参的顺序一致
默认值
<script>
// 设置参数默认值
function sayHi(name="小明", age=18) {
document.write(`<p>大家好,我叫${name},我今年${age}岁了。</p>`);
}
// 调用函数
sayHi();
sayHi('小红');
sayHi('小刚', 21);
</script>
总结:
- 声明函数时为形参赋值即为参数的默认值
- 如果参数未自定义默认值时,参数的默认值为
undefined - 调用函数时没有传入对应实参时,参数的默认值被当做实参传入
形参和实参
形参:声明函数时写在函数名右边小括号里的叫形参(形式上的参数)
实参:调用函数时写在函数名右边小括号里的叫实参(实际上的参数)
形参可以理解为是在这个函数内声明的变量(比如 num1 = 10)实参可以理解为是给这个变量赋值
开发中尽量保持形参和实参个数一致
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 函数参数</title>
</head>
<body>
<script>
// 声明(定义)一个计算任意两数字和的函数
// 形参 x 和 y 分别表示任意两个数字,它们是两个变量
function count(x, y) {
console.log(x + y);
}
// 调用函数,传入两个具体的数字做为实参
// 此时 10 赋值给了形参 x
// 此时 5 赋值给了形参 y
count(10, 5); // 结果为 15
</script>
</body>
</html>
动态参数
arguments 是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参。
<script>
// 求和函数,计算所有参数的和
function sum() {
// console.log(arguments)
let s = 0
for(let i = 0; i < arguments.length; i++) {
s += arguments[i]
}
console.log(s)
}
// 调用求和函数
sum(5, 10)// 两个参数:15
sum(1, 2, 4) // 三个参数:7
</script>
总结:
arguments是一个伪数组arguments的作用是动态获取函数的实参
剩余参数
<script>
function config(baseURL, ...other) {
console.log(baseURL) // 得到 'http://baidu.com'
console.log(other) // other 得到 ['get', 'json']
}
// 调用函数
config('http://baidu.com', 'get', 'json');
</script>
总结:
...是语法符号,置于最末函数形参之前,用于获取多余的实参- 借助
...获取的剩余实参,是个真数组
返回值
函数的本质是封装(包裹),函数体内的逻辑执行完毕后,函数外部如何获得函数内部的执行结果呢?要想获得函数内部逻辑的执行结果,需要通过 return 这个关键字,将内部执行结果传递到函数外部,这个被传递到外部的结果就是返回值。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 函数返回值</title>
</head>
<body>
<script>
// 定义求和函数
function count(a, b) {
let s = a + b
// s 即为 a + b 的结果
// 通过 return 将 s 传递到外部
return s
}
// 调用函数,如果一个函数有返回值
// 那么可将这个返回值赋值给外部的任意变量
let total = count(5, 12)
</script>
</body>
</html>
总结:
- 在函数体中使用return 关键字能将内部的执行结果交给函数外部使用
- 函数内部只能出现1 次 return,并且 return 下一行代码不会再被执行,所以return 后面的数据不要换行写
- return会立即结束当前函数
- 函数可以没有return,这种情况默认返回值为 undefined
作用域
作用域(scope)规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问,作用域分为全局作用域和局部作用域。
全局作用域
<script> 标签和 .js 文件的【最外层】就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。
处于全局作用域内的变量,称为全局变量
<script>
// 此处是全局
function sayHi() {
// 此处为局部
}
// 此处为全局
</script>
全局作用域中声明的变量,任何其它作用域都可以被访问,如下代码所示:
<script>
// 全局变量 name
const name = '小明'
// 函数作用域中访问全局
function sayHi() {
// 此处为局部
console.log('你好' + name)
}
// 全局变量 flag 和 x
const flag = true
let x = 10
// 块作用域中访问全局
if(flag) {
let y = 5
console.log(x + y) // x 是全局的
}
</script>
总结:
- 为
window对象动态添加的属性默认也是全局的,不推荐! - 函数中未使用任何关键字声明的变量为全局变量,不推荐!!!
- 尽可能少的声明全局变量,防止全局变量被污染
JavaScript 中的作用域是程序被执行时的底层机制,了解这一机制有助于规范代码书写习惯,避免因作用域导致的语法错误。
局部作用域
作用于函数内的代码环境,就是局部作用域。 因为跟函数有关系,所以也称为函数作用域。
处于局部作用域内的变量称为局部变量
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。
不同函数内部声明的变量无法互相访问
函数执行完毕后,函数内部的变量实际被清空了
<script>
// 声明 counter 函数
function counter(x, y) {
// 函数内部声明的变量
const s = x + y
console.log(s) // 18
}
// 设用 counter 函数
counter(10, 8)
// 访问变量 s
console.log(s)// 报错
</script>
块级作用域
在 JavaScript 中使用 {} 包裹的代码称为代码块,代码块内部声明的变量外部将【有可能】无法被访问。
<script>
{
// age 只能在该代码块中被访问
let age = 18;
console.log(age); // 正常
}
// 超出了 age 的作用域
console.log(age) // 报错
let flag = true;
if(flag) {
// str 只能在该代码块中被访问
let str = 'hello world!'
console.log(str); // 正常
}
// 超出了 age 的作用域
console.log(str); // 报错
for(let t = 1; t <= 6; t++) {
// t 只能在该代码块中被访问
console.log(t); // 正常
}
// 超出了 t 的作用域
console.log(t); // 报错
</script>
JavaScript 中除了变量外还有常量,常量与变量本质的区别是【常量必须要有值且不允许被重新赋值】,常量值为对象时其属性和方法允许重新赋值。
<script>
// 必须要有值
const version = '1.0.0';
// 不能重新赋值
// version = '1.0.1';
// 常量值为对象类型
const user = {
name: '小明',
age: 18
}
// 不能重新赋值
user = {};
// 属性和方法允许被修改
user.name = '小小明';
user.gender = '男';
</script>
总结:
let声明的变量会产生块作用域,var不会产生块作用域const声明的常量也会产生块作用域- 不同代码块之间的变量无法互相访问
- 推荐使用
let或const
注:开发中 let 和 const 经常不加区分的使用,如果担心某个值会不小被修改时,则只能使用 const 声明成常量。
作用域链
在解释什么是作用域链前先来看一段代码:
<script>
// 全局作用域
let a = 1
let b = 2
// 局部作用域
function f() {
let c
// 局部作用域
function g() {
let d = 'yo'
}
}
</script>
函数内部允许创建新的函数,f 函数内部创建的新函数 g,会产生新的函数作用域,由此可知作用域产生了嵌套的关系。
如下图所示,父子关系的作用域关联在一起形成了链状的结构,作用域链的名字也由此而来。
作用域链本质上是底层的变量查找机制,在函数被执行时,会优先查找当前函数作用域中查找变量,如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域,如下代码所示:
<script>
// 全局作用域
let a = 1
let b = 2
// 局部作用域
function f() {
let c
// let a = 10;
console.log(a) // 1 或 10
console.log(d) // 报错
// 局部作用域
function g() {
let d = 'yo'
// let b = 20;
console.log(b) // 2 或 20
}
// 调用 g 函数
g()
}
console.log(c) // 报错
console.log(d) // 报错
f();
</script>
总结:
- 嵌套关系的作用域串联起来形成了作用域链
- 相同作用域链中按着从小到大的规则查找变量
- 子作用域能够访问父作用域,父级作用域无法访问子级作用域
匿名函数
函数可以分为具名函数和匿名函数
匿名函数:没有名字的函数,无法直接使用。
函数表达式
// 声明
let fn = function() {
console.log('函数表达式')
}
// 调用
fn()
立即执行函数
(function(){ xxx })();
(function(){xxxx}());
无需调用,立即执行,其实本质已经调用了
多个立即执行函数之间用分号隔开
闭包
闭包是一种比较特殊的函数,使用闭包能够访问函数作用域中的变量。从代码形式上看闭包是一个做为返回值的函数,如下代码所示:
<body>
<script>
// 1. 闭包 : 内层函数 + 外层函数变量
// function outer() {
// const a = 1
// function f() {
// console.log(a)
// }
// f()
// }
// outer()
// 2. 闭包的应用: 实现数据的私有。统计函数的调用次数
// let count = 1
// function fn() {
// count++
// console.log(`函数被调用${count}次`)
// }
// 3. 闭包的写法 统计函数的调用次数
function outer() {
let count = 1
function fn() {
count++
console.log(`函数被调用${count}次`)
}
return fn
}
const re = outer()
// const re = function fn() {
// count++
// console.log(`函数被调用${count}次`)
// }
re()
re()
// const fn = function() { } 函数表达式
// 4. 闭包存在的问题: 可能会造成内存泄漏
</script>
</body>
总结:
1.怎么理解闭包?
- 闭包 = 内层函数 + 外层函数的变量
2.闭包的作用?
- 封闭数据,实现数据私有,外部也可以访问函数内部的变量
- 闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来
3.闭包可能引起的问题?
- 内存泄漏
变量提升
变量提升是 JavaScript 中比较“奇怪”的现象,它允许在变量声明之前即被访问,
<script>
// 访问变量 str
console.log(str); // undefined
// 声明变量 str
var str = 'hello ';
</script>
总结:
- 变量在未声明即被访问时会报语法错误
- 变量在声明之前即被访问,变量的值为
undefined let声明的变量不存在变量提升,推荐使用let- 变量提升出现在相同作用域当中
- 实际开发中推荐先声明再访问变量
注:关于变量提升的原理分析会涉及较为复杂的词法分析等知识,而开发中使用 let 可以轻松规避变量的提升,因此在此不做过多的探讨,有兴趣可查阅资料。
函数提升
函数提升与变量提升比较类似,是指函数在声明之前即可被调用。
<script>
// 调用函数
foo()
// 声明函数
function foo() {
console.log('声明之前即被调用...')
}
// 不存在提升现象
bar() // 错误: bar is not a function
var bar = function () {
console.log('函数表达式不存在提升现象...')
}
</script>
总结:
- 函数提升能够使函数的声明调用更灵活
- 函数表达式不存在提升的现象
- 函数提升出现在相同作用域当中
剩余函数or展开运算符
箭头函数
箭头函数是一种声明函数的简洁语法,它与普通函数并无本质的区别,差异性更多体现在语法格式上。
<body>
<script>
// const fn = function () {
// console.log(123)
// }
// 1. 箭头函数 基本语法
// const fn = () => {
// console.log(123)
// }
// fn()
// const fn = (x) => {
// console.log(x)
// }
// fn(1)
// 2. 只有一个形参的时候,可以省略小括号
// const fn = x => {
// console.log(x)
// }
// fn(1)
// // 3. 只有一行代码的时候,我们可以省略大括号
// const fn = x => console.log(x)
// fn(1)
// 4. 只有一行代码的时候,可以省略return
// const fn = x => x + x
// console.log(fn(1))
// 5. 箭头函数可以直接返回一个对象
// const fn = (uname) => ({ uname: uname })
// console.log(fn('刘德华'))
</script>
</body>
总结:
- 箭头函数属于表达式函数,因此不存在函数提升
- 箭头函数只有一个参数时可以省略圆括号
() - 箭头函数函数体只有一行代码时可以省略花括号
{},并自动做为返回值被返回
箭头函数参数
箭头函数中没有 arguments,只能使用 ... 动态获取实参
<body>
<script>
// 1. 利用箭头函数来求和
const getSum = (...arr) => {
let sum = 0
for (let i = 0; i < arr.length; i++) {
sum += arr[i]
}
return sum
}
const result = getSum(2, 3, 4)
console.log(result) // 9
</script>
箭头函数 this
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this。
<script>
// 以前this的指向: 谁调用的这个函数,this 就指向谁
// console.log(this) // window
// // 普通函数
// function fn() {
// console.log(this) // window
// }
// window.fn()
// // 对象方法里面的this
// const obj = {
// name: 'andy',
// sayHi: function () {
// console.log(this) // obj
// }
// }
// obj.sayHi()
// 2. 箭头函数的this 是上一层作用域的this 指向
// const fn = () => {
// console.log(this) // window
// }
// fn()
// 对象方法箭头函数 this
// const obj = {
// uname: 'pink老师',
// sayHi: () => {
// console.log(this) // this 指向谁? window
// }
// }
// obj.sayHi()
const obj = {
uname: 'pink老师',
sayHi: function () {
console.log(this) // obj
let i = 10
const count = () => {
console.log(this) // obj
}
count()
}
}
obj.sayHi()
</script>
解构赋值
解构赋值是一种快速为变量赋值的简洁语法,本质上仍然是为变量赋值,分为数组解构、对象解构两大类型。
数组解构
数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法,如下代码所示:
<script>
// 普通的数组
let arr = [1, 2, 3]
// 批量声明变量 a b c
// 同时将数组单元值 1 2 3 依次赋值给变量 a b c
let [a, b, c] = arr
console.log(a); // 1
console.log(a); // 2
console.log(a); // 3
// 多余的变量将被赋值为 undefined
let [A,B,C,D] = arr
console.log(A,B,C,D); // 1,2,3,undefind
// 可以通过 `...` 获取剩余单元值,但只能置于最末位
let [x,...y] = arr
console.log(x,y) // 1,[2,3]
// 多维数组解构
const arr2 = [1,2,[3,4]]
const [a,b,c] = arr2
console.log(a) // 1
console.log(b) // 2
console.log(c) // [3,4]
const [a,b,[c,d]] = arr2
console.log(c) // 3
console.log(d) // 4
// 数组对象解构
const person = [
{
name: '佩奇',
family: {
mother: '猪妈',
father: '猪爸',
sister: '乔治'
},
age: 6
}
]
const [{name, family: {mother, father, sister }}] = person
console.log(name) // 佩奇
console.log(mother) // 猪妈
console.log(father) // 猪爸
console.log(sister) // 乔治
</script>
总结:
- 赋值运算符
=左侧的[]用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量 - 变量的顺序对应数组单元值的位置依次进行赋值操作
- 变量的数量大于单元值数量时,多余的变量将被赋值为
undefined - 变量的数量小于单元值数量时,可以通过
...获取剩余单元值,但只能置于最末位 - 允许初始化变量的默认值,且只有单元值为
undefined时默认值才会生效 - 立即执行函数 (function(){})() 跟 数组解构 都需要在前面加上分号,避免跟上一行没加分号代码粘连
- 支持多维解构赋值,比较复杂后续有应用需求时再进一步分析
例:
对象解构
对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法,如下代码所示:
<script>
// 普通对象
const user = {
name: '小明',
age: 18
};
// 批量声明变量 name age
// 同时将数组单元值 小明 18 依次赋值给变量 name age
const {name, age} = user
console.log(name) // 小明
console.log(age) // 18
</script>
总结:
- 赋值运算符
=左侧的{}用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量 - 对象属性的值将被赋值给与属性名相同的变量名
- 对象中找不到与变量名一致的属性时变量值为
undefined - 允许初始化变量的默认值,属性不存在或单元值为
undefined时默认值才会生效 - 支持多维对象解构赋值,如下所示
<body>
<script>
// 1. 这是后台传递过来的数据
const msg = {
"code": 200,
"msg": "获取新闻列表成功",
"data": [
{
"id": 1,
"title": "5G商用自己,三大运用商收入下降",
"count": 58
},
{
"id": 2,
"title": "国际媒体头条速览",
"count": 56
},
{
"id": 3,
"title": "乌克兰和俄罗斯持续冲突",
"count": 1669
},
]
}
// 直接解构: 请将以上msg对象 采用对象解构的方式 只选出 data 方面后面使用渲染页面
// const { data } = msg
// console.log(data)
// 通过函数传参解构: 上面msg是后台传递过来的数据,我们需要把data选出当做参数传递给 函数
// const { data } = msg
// msg 虽然很多属性,但是我们利用解构只要 data值
function render({ data }) {
// const { data } = arr
// 我们只要 data 数据
// 内部处理
console.log(data)
}
render(msg)
// 起别名来解构:为了防止msg里面的data名字混淆,要求渲染函数里面的数据名改为 myData
// const { data: myData } = msg
// console.log(myData)
function render({ data: myData }) {
// 要求将 获取过来的 data数据 更名为 myData
// 内部处理
console.log(myData)
}
render(msg)
</script>
五、对象
对象是 JavaScript 数据类型的一种,之前已经学习了数值类型、字符串类型、布尔类型、undefined。对象数据类型可以被理解成是一种数据集合。它由属性和方法两部分构成。本节需知道对象数据类型的特征,能够利用数组对象渲染页面
- 理解什么是对象,掌握定义对象的语法
- 掌握数学对象的使用
语法
声明一个对象类型的变量与之前声明一个数值或字符串类型的变量没有本质上的区别。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 对象语法</title>
</head>
<body>
<script>
// 声明字符串类型变量
let str = 'hello world!'
// 声明数值类型变量
let num = 199
// 声明对象类型变量,使用一对花括号
// user 便是一个对象了,目前它是一个空对象
let user = {}
</script>
</body>
</html>
属性和访问
数据描述性的信息称为属性,如人的姓名、身高、年龄、性别等,一般是名词性的。
- 属性都是成 对出现的,包括属性名和值,它们之间使用英文
:分隔 - 多个属性之间使用英文
,分隔 - 属性就是依附在对象上的变量
- 属性名可以使用
""或'',一般情况下省略,除非名称遇到特殊符号如空格、中横线等
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 对象语法</title>
</head>
<body>
<script>
// 通过对象描述一个人的数据信息
// person 是一个对象,它包含了一个属性 name
// 属性都是成对出现的,属性名 和 值,它们之间使用英文 : 分隔
let person = {
name: '小明', // 描述人的姓名
age: 18, // 描述人的年龄
stature: 185, // 描述人的身高
gender: '男', // 描述人的性别
}
</script>
</body>
</html>
声明对象,并添加了若干属性后,可以使用 . 或 [] 获得对象中属性对应的值,我称之为属性访问。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 对象语法</title>
</head>
<body>
<script>
// 通过对象描述一个人的数据信息
// person 是一个对象,它包含了一个属性 name
// 属性都是成对出现的,属性名 和 值,它们之间使用英文 : 分隔
let person = {
name: '小明', // 描述人的姓名
age: 18, // 描述人的年龄
stature: 185, // 描述人的身高
gender: '男', // 描述人的性别
};
// 访问人的名字
console.log(person.name) // 结果为 小明
// 访问人性别
console.log(person.gender) // 结果为 男
// 访问人的身高
console.log(person['stature']) // 结果为 185
// 或者
console.log(person.stature) // 结果同为 185
</script>
</body>
</html>
扩展:也可以动态为对象添加属性,动态添加与直接定义是一样的,只是语法上更灵活。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 对象语法</title>
</head>
<body>
<script>
// 声明一个空的对象(没有任何属性)
let user = {}
// 动态追加属性
user.name = '小明'
user['age'] = 18
// 动态添加与直接定义是一样的,只是语法上更灵活
</script>
</body>
</html>
方法和调用
数据行为性的信息称为方法,如跑步、唱歌等,一般是动词性的,其本质是函数。
- 方法是由方法名和函数两部分构成,它们之间使用 : 分隔
- 多个属性之间使用英文
,分隔 - 方法是依附在对象中的函数
- 方法名可以使用
""或'',一般情况下省略,除非名称遇到特殊符号如空格、中横线等
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 对象方法</title>
</head>
<body>
<script>
// 方法是依附在对象上的函数
let person = {
name: '小红',
age: 18,
// 方法是由方法名和函数两部分构成,它们之间使用 : 分隔
singing: function () {
console.log('两只老虎,两只老虎,跑的快,跑的快...')
},
run: function () {
console.log('我跑的非常快...')
}
}
</script>
</body>
</html>
声明对象,并添加了若干方法后,可以使用 . 或 [] 调用对象中函数,我称之为方法调用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 对象方法</title>
</head>
<body>
<script>
// 方法是依附在对象上的函数
let person = {
name: '小红',
age: 18,
// 方法是由方法名和函数两部分构成,它们之间使用 : 分隔
singing: function () {
console.log('两只老虎,两只老虎,跑的快,跑的快...')
},
run: function () {
console.log('我跑的非常快...')
}
}
// 调用对象中 singing 方法
person.singing()
// 调用对象中的 run 方法
person.run()
</script>
</body>
</html>
扩展:也可以动态为对象添加方法,动态添加与直接定义是一样的,只是语法上更灵活。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 对象方法</title>
</head>
<body>
<script>
// 声明一个空的对象(没有任何属性,也没有任何方法)
let user = {}
// 动态追加属性
user.name = '小明'
user.['age'] = 18
// 动态添加方法
user.move = function () {
console.log('移动一点距离...')
}
</script>
</body>
</html>
注:无论是属性或是方法,同一个对象中出现名称一样的,后面的会覆盖前面的。
null
null 也是 JavaScript 中数据类型的一种,通常只用它来表示不存在的对象。使用 typeof 检测类型它的类型时,结果为 object。
遍历对象
let obj = {
uname: 'pink'
}
for(let k in obj) {
// k 属性名 字符串 带引号 obj.'uname' k === 'uname'
// obj[k] 属性值 obj['uname'] obj[k]
}
for in 不提倡遍历数组 因为 k 是 字符串
构造函数
构造函数是专门用于创建对象的函数,如果一个函数使用 new 关键字调用,那么这个函数就是构造函数。
<script>
// 定义函数
function foo() {
console.log('通过 new 也能调用函数...');
}
// 调用函数
new foo;
</script>
总结:
- 使用
new关键字调用函数的行为被称为实例化,可用来快速创建多个类似对象 - 实例化构造函数时没有参数时可以省略
() - 构造函数的返回值即为新创建的对象
- 构造函数内部的
return返回的值无效!
注:实践中为了从视觉上区分构造函数和普通函数,习惯将构造函数的首字母大写。
实例成员
通过构造函数创建的对象称为实例对象,实例对象中的属性和方法称为实例成员。
<script>
// 构造函数
function Person() {
// 构造函数内部的 this 就是实例对象
// 实例对象中动态添加属性
this.name = '小明'
// 实例对象动态添加方法
this.sayHi = function () {
console.log('大家好~')
}
}
// 实例化,p1 是实例对象
// p1 实际就是 构造函数内部的 this
const p1 = new Person()
console.log(p1)
console.log(p1.name) // 访问实例属性
p1.sayHi() // 调用实例方法
</script>
总结:
- 构造函数内部
this实际上就是实例对象,为其动态添加的属性和方法即为实例成员 - 为构造函数传入参数,动态创建结构相同但值不同的对象
注:构造函数创建的实例对象彼此独立互不影响。
静态成员
在 JavaScript 中底层函数本质上也是对象类型,因此允许直接为函数动态添加属性或方法,构造函数的属性和方法被称为静态成员。
<script>
// 构造函数
function Person(name, age) {
// 省略实例成员
}
// 静态属性
Person.eyes = 2
Person.arms = 2
// 静态方法
Person.walk = function () {
console.log('^_^人都会走路...')
// this 指向 Person
console.log(this.eyes)
}
</script>
总结:
- 静态成员指的是添加到构造函数本身的属性和方法
- 一般公共特征的属性或方法静态成员设置为静态成员
- 静态成员方法中的
this指向构造函数本身
内置对象
回想一下我们曾经使用过的 console.log,console其实就是 JavaScript 中内置的对象,该对象中存在一个方法叫 log,然后调用 log 这个方法,即 console.log()。
除了 console 对象外,JavaScritp 还有其它的内置的对象
包装类型
在 JavaScript 中的字符串、数值、布尔具有对象的使用特征,如具有属性和方法,如下代码举例:
<script>
// 字符串类型
const str = 'hello world!'
// 统计字符的长度(字符数量)
console.log(str.length)
// 数值类型
const price = 12.345
// 保留两位小数
price.toFixed(2) // 12.34
</script>
之所以具有对象特征的原因是字符串、数值、布尔类型数据是 JavaScript 底层使用 Object 构造函数“包装”来的,被称为包装类型。
String
String 是内置的构造函数,用于创建字符串。
<script>
// 使用构造函数创建字符串
let str = new String('hello world!');
// 字面量创建字符串
let str2 = '你好,世界!';
// 检测是否属于同一个构造函数
console.log(str.constructor === str2.constructor); // true
console.log(str instanceof String); // false
</script>
总结:
- 实例属性
length用来获取字符串的度长(重点) - 实例方法
split('分隔符')用来将字符串拆分成数组(重点) - 实例方法
substring(需要截取的第一个字符的索引[,结束的索引号])用于字符串截取(重点) - 实例方法
startsWith(检测字符串[, 检测位置索引号])检测是否以某字符开头(重点) - 实例方法
includes(搜索的字符串[, 检测位置索引号])判断一个字符串是否包含在另一个字符串中,根据情况返回 true 或 false(重点) - 实例方法
toUpperCase用于将字母转换成大写 - 实例方法
toLowerCase用于将就转换成小写 - 实例方法
indexOf检测是否包含某字符 - 实例方法
endsWith检测是否以某字符结尾 - 实例方法
replace用于替换字符串,支持正则匹配 - 实例方法
match用于查找字符串,支持正则匹配
注:String 也可以当做普通函数使用,这时它的作用是强制转换成字符串数据类型。
Number
Number 是内置的构造函数,用于创建数值。
<script>
// 使用构造函数创建数值
let x = new Number('10')
let y = new Number(5)
// 字面量创建数值
let z = 20
const num = 10.923
console.log(num.toFixed(1)) // 10.9
const num2 = 10
console.log(num2.toFixed(2)) // 10.00
</script>
总结:
- 推荐使用字面量方式声明数值,而不是
Number构造函数 - 实例方法
toFixed用于设置保留小数位的长度
Math
Math 是 JavaScript 中内置的对象,称为数学对象,这个对象下即包含了属性,也包含了许多的方法。
属性
- Math.PI,获取圆周率
// 圆周率
console.log(Math.PI);
方法
- Math.random,生成 0 到 1 间的随机数
// 0 ~ 1 之间的随机数, 包含 0 不包含 1
Math.random()
// 生成 N~M 之间的随机数
Math.floor(Math.random() * (M - N + 1)) + N
- Math.ceil,数字向上取整
// 舍弃小数部分,整数部分加1
Math.ceil(3.4)
- Math.floor,数字向下取整
// 舍弃小数部分,整数部分不变
Math.floor(4.68)
// 找到数组中随机的一个字符
let arr = ['red', 'green', 'blue']
Math.floor(Math.random() * arr.length)
- Math.round,四舍五入取整
// 取整,四舍五入原则
Math.round(5.46539)
Math.round(4.849)
- Math.max,在一组数中找出最大的
// 找出最大值
Math.max(10, 21, 7, 24, 13)
- Math.min,在一组数中找出最小的
// 找出最小值
Math.min(24, 18, 6, 19, 21)
- Math.pow,幂方法
// 求某个数的多少次方
Math.pow(4, 2) // 求 4 的 2 次方
Math.pow(2, 3) // 求 2 的 3 次方
- Math.sqrt,平方根
// 求某数的平方根
Math.sqrt(16)
小例子:随机点名系统
let arr = ['赵云','马超', '张飞', '黄忠', '关羽']
let random = Math.floor(Math.random() * arr.length)
document.write(arr[random])
数学对象提供了比较多的方法,这里不要求强记,通过演示数学对象的使用,加深对对象的理解。
日期对象
掌握 Date 日期对象的使用,动态获取当前计算机的时间。
ECMAScript 中内置了获取系统时间的对象 Date,使用 Date 时与之前学习的内置对象 console 和 Math 不同,它需要借助 new 关键字才能使用。
实例化
// 1. 实例化
// const date = new Date(); // 系统默认时间
const date = new Date('2020-05-01') // 指定时间
// date 变量即所谓的时间对象
console.log(typeof date)
方法
// 1. 实例化
const date = new Date();
// 2. 调用时间对象方法
// 通过方法分别获取年、月、日,时、分、秒
const year = date.getFullYear(); // 四位年份
const month = date.getMonth(); // 0 ~ 11
getFullYear 获取四位年份
getMonth 获取月份,取值为 0 ~ 11
getDate 获取月份中的每一天,不同月份取值也不相同
getDay 获取星期,取值为 0 ~ 6
getHours 获取小时,取值为 0 ~ 23
getMinutes 获取分钟,取值为 0 ~ 59
getSeconds 获取秒,取值为 0 ~ 59
时间戳
时间戳是指1970年01月01日00时00分00秒起至现在的总秒数或毫秒数,它是一种特殊的计量时间的方式。
注:ECMAScript 中时间戳是以毫秒计的。
// 1. 实例化
const date = new Date()
// 2. 获取时间戳
console.log(date.getTime())
// 还有一种获取时间戳的方法
console.log(+new Date())
// 还有一种获取时间戳的方法
console.log(Date.now())
获取时间戳的方法,分别为 getTime 和 Date.now 和 +new Date()
window对象
BOM (Browser Object Model ) 是浏览器对象模型
- window对象是一个全局对象,也可以说是JavaScript中的顶级对象
- 像document、alert()、console.log()这些都是window的属性,基本BOM的属性和方法都是window的
- 所有通过var定义在全局作用域中的变量、函数都会变成window对象的属性和方法
- window对象下的属性和方法调用的时候可以省略window
定时器
setTimeout() 执行一次
语法规范:window.setTimeout(调用函数,延时时间)
-
setTimeout() 在调用时,window、延时时间都可以省略,延时时间单位为毫秒,省略的话默认为0
-
如果有很多定时器的话,可以给定时器加标示符 let timer = setTimeout(function(){},1000)
-
setTimeout() 也称为回调函数 callback,需要等到时间到了才去调用这个函数
(回调:回头调用,指上一件事干完在回头调用这个函数)
以前讲的 element.onclick = function(){} 或 element.addEventListener('click', fn) 里面的函数也是回调函数
setInterval() 重复执行
语法规范:window.setInterval(回调函数,间隔时间),注意事项跟 setTimeout 一样
<script>
// setTimeout()
// 书写方式一:匿名函数
setTimeout(function(){
console.log('时间到了')
},2000)
// 书写方式二:有具体函数名
function callback(){
console.log('时间到了')
}
// 加了标示符的回调函数,方便后面关闭定时器用
let timer = setTimeout(callback, 2000)
// 关闭定时器
clearTimout(timer)
// setInterval()
let timerId = setInterval(function(){
console.log('持续输出')
},2000)
// 清除定时器
clearInterval(timerId)
</script>
location对象
location (地址) 它拆分并保存了 URL 地址的各个组成部分, 它是一个对象
| 属性/方法 | 说明 |
|---|---|
| href | 属性,获取完整的 URL 地址,赋值时用于地址的跳转 |
| search | 属性,获取地址中携带的参数,符号 ?后面部分 |
| hash | 属性,获取地址中的啥希值,符号 # 后面部分 |
| reload() | 方法,用来刷新当前页面,传入参数 true 时表示强制刷新 |
<body>
<form>
<input type="text" name="search"> <button>搜索</button>
</form>
<a href="#/music">音乐</a>
<a href="#/download">下载</a>
<button class="reload">刷新页面</button>
<script>
// location 对象
// 1. href属性 (重点) 得到完整地址,赋值则是跳转到新地址
console.log(location.href)
// location.href = 'http://www.itcast.cn'
// 2. search属性 得到 ? 后面的地址
console.log(location.search) // ?search=笔记本
// 3. hash属性 得到 # 后面的地址
console.log(location.hash)
// 4. reload 方法 刷新页面
const btn = document.querySelector('.reload')
btn.addEventListener('click', function () {
// location.reload() // 页面刷新
location.reload(true) // 强制页面刷新 ctrl+f5
})
</script>
</body>
navigator对象
navigator是对象,该对象下记录了浏览器自身的相关信息
常用属性和方法:
- 通过 userAgent 检测浏览器的版本及平台(F12,切换成移动手机模式)
// 检测 userAgent(浏览器信息)
(function () {
const userAgent = navigator.userAgent
// 验证是否为Android或iPhone
const android = userAgent.match(/(Android);?[\s\/]+([\d.]+)?/)
const iphone = userAgent.match(/(iPhone\sOS)\s([\d_]+)/)
// 如果是Android或iPhone,则跳转至移动站点
if (android || iphone) {
location.href = 'http://m.itcast.cn'
}})();
histroy对象
history (历史)是对象,主要管理历史记录, 该对象与浏览器地址栏的操作相对应,如前进、后退等
使用场景
history对象一般在实际开发中比较少用,但是会在一些OA 办公系统中见到。
常见方法:
<body>
<button class="back">←后退</button>
<button class="forward">前进→</button>
<script>
// histroy对象
// 1.前进
const forward = document.querySelector('.forward')
forward.addEventListener('click', function () {
// history.forward()
history.go(1)
})
// 2.后退
const back = document.querySelector('.back')
back.addEventListener('click', function () {
// history.back()
history.go(-1)
})
</script>
</body>
本地存储(今日重点)
本地存储:将数据存储在本地浏览器中
常见的使用场景:
todomvc.com/examples/va… 页面刷新数据不丢失
好处:
1、页面刷新或者关闭不丢失数据,实现数据持久化
2、容量较大,sessionStorage和 localStorage 约 5M 左右
localStorage(重点)
作用: 数据可以长期保留在本地浏览器中,刷新页面和关闭页面,数据也不会丢失
**特性:**以键值对的形式存储,并且存储的是字符串, 省略了window,多窗口(页面)共享(同一浏览器可以共享)
<!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>本地存储-localstorage</title>
</head>
<body>
<script>
// 本地存储 - localstorage 存储的是字符串
// 1. 存储
localStorage.setItem('age', 18) // 点击F12,应用程序,存储,本地存储,就能看到本地存储的字符串
// 2. 获取
console.log(typeof localStorage.getItem('age')) // string
// 3. 删除
localStorage.removeItem('age')
</script>
</body>
</html>
sessionStorage(了解)
特性:
- 用法跟localStorage基本相同,在同一窗口(页面)下数据可以共享,以键值对形式存储使用
- 区别是:当页面浏览器被关闭时,存储在 sessionStorage 的数据会被清除(声明周期为关闭浏览器)
存储:sessionStorage.setItem(key,value)
获取:sessionStorage.getItem(key)
删除:sessionStorage.removeItem(key)
删除所有数据:sessionStorage.clear()
localStorage 存储复杂数据类型
**问题:**本地只能存储字符串,无法存储复杂数据类型.
**解决:**需要将复杂数据类型转换成 JSON字符串,在存储到本地
**语法:**JSON.stringify(复杂数据类型)
JSON字符串:
- 首先是1个字符串
- 属性名使用双引号引起来,不能单引号
- 属性值如果是字符串型也必须双引号
<body>
<script>
// 本地存储复杂数据类型
const goods = {
name: '小米',
price: 1999
}
localStorage.setItem('goods', goods)
console.log(localStorage.getItem('goods')) [object Object]
// 1. 把对象转换为JSON字符串 JSON.stringify
localStorage.setItem('goods', JSON.stringify(goods))
console.log(localStorage.getItem('goods')) // {"name":"小米","price":999}
console.log(typeof localStorage.getItem('goods')) // string
</script>
</body>
问题: 因为本地存储里面取出来的是字符串,不是对象,无法直接使用
解决: 把取出来的字符串转换为对象
语法: JSON.parse(JSON字符串)
<body>
<script>
// 本地存储复杂数据类型
const goods = {
name: '小米',
price: 1999
}
// localStorage.setItem('goods', goods)
// console.log(localStorage.getItem('goods'))
// 1. 把对象转换为JSON字符串 JSON.stringify
localStorage.setItem('goods', JSON.stringify(goods))
// console.log(typeof localStorage.getItem('goods')) // string
// 2. 把JSON字符串转换为对象 JSON.parse
console.log(JSON.parse(localStorage.getItem('goods'))) //{name: '小米', price: 999}
console.log(typeof JSON.parse(localStorage.getItem('goods'))) // object
</script>
</body>
编程思想
学习 JavaScript 中基于原型的面向对象编程序的语法实现,理解面向对象编程的特征。
面向过程
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次
调用就可以了。
面向对象
面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作。
在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工。
面向对象编程具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件项目。
面向对象的特性:
-
封装性
-
继承性
-
多态性
构造函数
对比以下通过面向对象的构造函数实现的封装:
<script>
function Person() {
this.name = '佚名'
// 设置名字
this.setName = function (name) {
this.name = name
}
// 读取名字
this.getName = () => {
console.log(this.name)
}
}
// 实例对像,获得了构造函数中封装的所有逻辑
let p1 = new Person()
p1.setName('小明')
console.log(p1.name)
// 实例对象
let p2 = new Person()
console.log(p2.name)
</script>
封装是面向对象思想中比较重要的一部分,js面向对象可以通过构造函数实现的封装。
同样的将变量和函数组合到了一起并能通过 this 实现数据的共享,所不同的是借助构造函数创建出来的实例对象之
间是彼此不影响的
总结:
- 构造函数体现了面向对象的封装特性
- 构造函数实例创建的对象彼此独立、互不影响
封装是面向对象思想中比较重要的一部分,js面向对象可以通过构造函数实现的封装。
前面我们学过的构造函数方法很好用,但是 存在浪费内存的问题
原型对象
构造函数通过原型分配的函数是所有对象所 共享的。
- JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型对象
- 这个对象可以挂载函数,对象实例化不会多次创建原型上函数,节约内存
- 我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
- 构造函数和原型对象中的this 都指向 实例化的对象
<script>
function Person() {
}
// 每个函数都有 prototype 属性
console.log(Person.prototype)
</script>
了解了 JavaScript 中构造函数与原型对象的关系后,再来看原型对象具体的作用,如下代码所示:
<script>
function Person() {
// 此处未定义任何方法
}
// 为构造函数的原型对象添加方法
Person.prototype.sayHi = function () {
console.log('Hi~');
}
// 实例化
let p1 = new Person();
p1.sayHi(); // 输出结果为 Hi~
</script>
构造函数 Person 中未定义任何方法,这时实例对象调用了原型对象中的方法 sayHi,接下来改动一下代码:
<script>
function Person() {
// 此处定义同名方法 sayHi
this.sayHi = function () {
console.log('嗨!');
}
}
// 为构造函数的原型对象添加方法
Person.prototype.sayHi = function () {
console.log('Hi~');
}
let p1 = new Person();
p1.sayHi(); // 输出结果为 嗨!
</script>
构造函数 Person 中定义与原型对象中相同名称的方法,这时实例对象调用则是构造函中的方法 sayHi。
通过以上两个简单示例不难发现 JavaScript 中对象的工作机制:当访问对象的属性或方法时,先在当前实例对象是查找,然后再去原型对象查找,并且原型对象被所有实例共享。
<script>
// 1。我们定义的这个方法,任何一个数组实例对象都可以使用
// 2. 自定义的方法写到 数组.prototype 身上
// 1.最大值
const arr = [1, 2, 3]
Array.prototype.max = function () {
// 展开运算符
return Math.max(...this )
// 原型函数里面的this 指向谁? 实例对象 arr
}
console.log(arr.max()) // 3
console.log([2,5,9].max()) // 9
// 2.最小值
Array.prototype.min = function () {
// 展开运算符
return Math.min(...this)
// 原型函数里面的this 指向谁? 实例对象 arr
}
console.log(arr.min()) // 1
// const arr = new Array(1,2)
// console.log(arr) // [1, 2]
</script>
总结:结合构造函数原型的特征,实际开发重往往会将封装的功能函数添加到原型对象中。
constructor 属性
在哪里? 每个原型对象里面都有个constructor 属性(constructor 构造函数)
作用:该属性指向该原型对象的构造函数, 简单理解,就是指向我的爸爸,我是有爸爸的孩子
使用场景:
如果有多个对象的方法,我们可以给原型对象采取对象形式赋值.
但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了
此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。
<script>
function Star() {}
// 给构造函数的原型对象添加单个对象的方法,并不会覆盖原来的内容
// Star.prototype.sing = function() {
// console.log('唱歌')
// }
// Star.prototype.dance = function() {
// console.log('跳舞')
// }
console.log(Star.prototype) // {constructor: f}
// 如果有多个对象的方法,则给构造函数的原型对象采取对象形式赋值,这样会覆盖原来的内容
Star.prototype = {
sing: function() {
console.log('唱歌')
}
dance: function() {
console.log('跳舞')
}
}
console.log(Star.prototype) // {sing: f, dance: f},这里被覆盖了后就没有了constructor
console.log(Star.prototype.constructor) // f Object(){[native code]} 这里看出没有constructor了
// 将constructor指向该原型对象的构造函数Star
Star.prototype = {
// 重新指回创造这个原型对象的 构造函数
constructor: Star,
sing: function() {
console.log('唱歌')
}
dance: function() {
console.log('跳舞')
}
}
console.log(Star.prototype) // {constructor: f, sing: f, dance: f} [[Prototype]]: Object
</script>
对象原型
对象都会有一个属性 proto 指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype
原型对象的属性和方法,就是因为对象有 proto 原型的存在。
注意:
- proto 是JS非标准属性
- [[prototype]]和__proto__意义相同
- 用来表明当前实例对象指向哪个原型对象prototype
- __proto__对象原型里面也有一个 constructor属性,指向创建该实例对象的构造函数
原型继承
继承是面向对象编程的另一个特征,通过继承进一步提升代码封装的程度,JavaScript 中大多是借助原型对象实现继承
的特性。
龙生龙、凤生凤、老鼠的儿子会打洞描述的正是继承的含义。
<body>
<script>
// 继续抽取 公共的部分放到原型上
// const Person1 = {
// eyes: 2,
// head: 1
// }
// const Person2 = {
// eyes: 2,
// head: 1
// }
// 构造函数 new 出来的对象 结构一样,但是对象不一样
function Person() {
this.eyes = 2
this.head = 1
}
// console.log(new Person)
// 女人 构造函数 继承 想要 继承 Person
function Woman() {
}
// Woman 通过原型来继承 Person
// 父构造函数(父类) 子构造函数(子类)
// 子类的原型 = new 父类
Woman.prototype = new Person() // {eyes: 2, head: 1}
// 指回原来的构造函数
Woman.prototype.constructor = Woman
// 给女人添加一个方法 生孩子
Woman.prototype.baby = function () {
console.log('宝贝')
}
const red = new Woman()
console.log(red) // Woman |> [[prototype]]:Person |> baby、constructor、eyes:2、head:1
// console.log(Woman.prototype)
// 男人 构造函数 继承 想要 继承 Person
function Man() {
}
// 通过 原型继承 Person
Man.prototype = new Person()
Man.prototype.constructor = Man
const pink = new Man()
console.log(pink) // Man |> [[prototype]]:Person |> constructor、eyes:2、head:1
// 如果写成
// const Person = {
// eyes: 2,
// head: 1
// }
// 则即使没有给男人添加孩子这个方法,但是还是能得到生孩子这个方法,因为:
// 男人和女人都同时使用了同一个对象 Person
// Woman.prototype = Person
// Man.prototype = Person
// 他们指向同一个对象,修改一个则都会被影响
// 解决方案是:
// 利用构造函数,new 出来的对象 结构一样,但是对象不一样,就能避免这样的问题,写法如下:
// function Person() {
// this.eyes = 2
// this.head = 1
// }
// Woman.prototype = new Person()
// Man.prototype = new Person()
</script>
</body>
原型链
基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对
象的链状结构关系称为原型链
<body>
<script>
// function Objetc() {}
console.log(Object.prototype) // {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
console.log(Object.prototype.__proto__) // null
function Person() {
}
const ldh = new Person()
// console.log(ldh.__proto__ === Person.prototype)
// console.log(Person.prototype.__proto__ === Object.prototype)
console.log(ldh instanceof Person) // true
console.log(ldh instanceof Object) // true
console.log(ldh instanceof Array) // false
console.log([1, 2, 3] instanceof Array) // true
console.log(Array instanceof Object) // true
</script>
</body>
① 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
② 如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)
③ 如果还没有就查找原型对象的原型(Object的原型对象)
④ 依此类推一直找到 Object 为止(null)
⑤ __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线
⑥ 可以使用 instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
未完待续...