一、ECMAScript(JS基础)
1.简介
1.1定义和作用
是一种运行在客户端(浏览器)的编程语言,实现人机交互
组成:ECMAScript(JavaScript语言基础)、Web APIs(DOM(页面文档对象模型)、BOM(浏览器对象模型))
作用:网页特效(监听用户的一些行为让网页作出对应的反馈)、表单验证(针对表单数据的合法性进行判断)、数据交互(获取后台的数据渲染到前端)、服务端编程(node.js)
1.2书写位置(同CSS)
1.2.1 内部JavaScript
直接写在html文件里,用script标签包住
<body>
<script>
alert('警告')
</script>
</body>
1.2.2 外部JavaScript
代码写在以.js结尾的文件里,通过script标签引入html页面中
<script src="./js/my.js">
//不写语句,不生效
</script>
1.2.3 内联JavaScript
代码写在标签内部,VUE使用这种模式
<body>
<button onclick="alert('逗你玩---')">点击我</button>
</body>
1.3注释与结束符
单行注释;//,ctrl+/
块注释:/**/,shift+alt+a
结束符:;可写可不写,浏览器自动推断语句结束位置,要么全写要么都不写
必须要加分号的情况
`立即执行函数`(function () { })();
`使用数组解构`
let a = 1
let b = 2
;[b, a] = [a, b]
1.4 输入与输出语法
输出语法1:向body内输出内容,输出内容写标签也会解析为网络元素
<script>
document.write('我是div标签')
document.write('<h1>我是标题</h1>')
</script>
输出语法2:页面弹出警示对话框,标签不会解析,可以通过反引号直接原格式写进括号内,输出就是什么样
/*框内只有确认按钮*/
alert('内容')
输出语法3:控制台打印
console.log('控制台打印')
输入语法:显示一个对话框,对话框中包含一条文字信息,用来提示用户输入文字
/*框内有确认和取消按钮*/
prompt('请输入您的年龄:')
⭐html文档流顺序执行JavaScript代码,alert()和prompt()会跳过页面渲染先被执行
1.5 字面量
字面量(literal)是在计算机中描述的事物,如数字字面量,字符串字面量,数组字面量,对象字面量
1.6 script传值html
//1.通过搜索类名
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
table{
width: 1000px;
height: 100px;
border: 1px solid #000;
border-collapse:collapse;
}
th,td {
border: 1px solid #000;
width: 200px;
height: 50px;
text-align: center;
}
</style>
</head>
<body>
<table>
<caption>
<h1>订单确认</h1>
</caption>
<tr>
<th>商品名称</th>
<th>商品价格</th>
<th>商品数量</th>
<th>总价</th>
<th>收货地址</th>
</tr>
<tr>
<td>小米手机青春PLUS</td>
<td class="Price"></td>
<td class="Number"></td>
<td class="Total"></td>
<td class="Address"></td>
</tr>
</table>
<script>
let priceData = prompt('请输入商品单价')
let numData = prompt('请输入商品数量')
let totalData = priceData * numData
let addressData = prompt('请输入收货地址')
document.querySelector('.Price').innerHTML = priceData+'元'
document.querySelector('.Number').innerHTML = numData
document.querySelector('.Total').innerHTML = totalData+'元'
document.querySelector('.Address').innerHTML = addressData
</script>
</body>
//2.直接写进script,可以解析标签
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
table {
width: 200px;
height: 100px;
border: 1px solid black;
border-collapse: collapse;
}
td,th,caption {
border: 1px solid black;
text-align: center;
width: 200px;
}
</style>
</head>
<body>
<script>
let amount = prompt("请输入银行卡余额:")
let ele = prompt("请输入本月电费:")
let water = prompt("请输入本月水费:")
let Inter = prompt("请输入本月网费:")
let total = Number(ele) + Number(water) + Number(Inter)
let monny = amount - total
document.write(`
<table>
<caption><h3>账单</h3></caption>
<tr>
<th>本月电费:</th>
<td>${ele}元</td>
</tr>
<tr>
<th>本月水费:</th>
<td>${water}元</td>
</tr>
<tr>
<th>本月网费:</th>
<td>${Inter}元</td>
</tr>
<tr>
<th>本月总消费:</th>
<td>${total}元</td>
</tr>
<tr>
<th>银行卡剩余:</th>
<td>${monny}元</td>
</tr>
</table>`)
</script>
//3.若想改变style值,可以使用CSS行内式
//生成柱状图
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
*{
margin: 0;
padding: 0;
}
.box{
width: 700px;
height: 300px;
border-left: 1px solid black;
border-bottom: 1px solid black;
display: flex;
margin:50px auto;
justify-content: space-around;
align-items: flex-end;
text-align: center;
}
.box>div{
width: 50px;
background-color: pink;
display: flex;
justify-content: space-between;
flex-direction: column;
}
.box div span{
margin-top: -20px;
}
.box div h4{
margin-bottom: -35px;
width: 70px;
margin-left: -10px;
}
</style>
</head>
<body>
<script>
let data = []
for(let i=0;i<4;i++){
data.push(prompt(`请输入第${i+1}个数据`))
}
document.write('<div class="box">')
for (let i=0;i<data.length;i++){
document.write(`
<div style="height: ${data[i]}px;">
<span>${data[i]}</span>
<h4>第${i+1}季度</h4>
</div>
`)
}
document.write('</div>')
</script>
</body>
</html>
2.变量与常量
变量:(1)定义:存储数据的’容器‘
(2)本质:是程序在内存中申请的一块用来存放数据的小空间
(3)命名规则与规范:规则必须遵守,规范为建议
规则:不能用关键字,如let,var,if,for;只能用下划线、字母、数字、$组成,数字不能开头;字母严格区分大小写
规范:起名有意义;小驼峰命名法(第一个单词首字母小写,后面首单词大写)
(4)基本使用:
//1.声明变量:let是关键字,即系统提供的专门用来声明变量的词语
let 变量名
//2.变量赋值:
变量名 = 值
//3.声明同时赋值
let 变量名 = 值
//4.变量更新:重新赋值,不能再次加let,重复声明了
let age=18
//5.声明多个变量:为了可读性,最好分开写
let age = 18,uname = 'Tom'
age=19
//6.使用数组:将一组数据存在单个变量名下
let 变量名 = []
console.log(变量名[下标])
⭐let和var的区别:旧JavaScript中使用var来声明,var可以先使用再声明,且可以重复声明,没有块级作用域,let解决var的这些问题
常量:某个值永远不会变时,使用const声明,如const G=9.8
常量不允许重新赋值,声明的时候必须赋值
复杂数据类型增删改查地址不变所以允许使用const,但是重新赋值其他数组改变对象会报错
常量值为对象时其属性和方法允许重新赋值。
3.关键字、标识符和保留字
| 术语 | 关键字 | 保留字 | 标识符 |
|---|---|---|---|
| 解释 | js中有特殊意义的词汇 | js中无意义,但将来可能会有特殊意义 | 变量名、函数名的另一种叫法 |
| 举例 | let,var,function,if,else,switch,case,break | int,short,long,char | 无 |
4.数据类型
4.1 基本数据类型/简单类型/值类型
在存储时变量中存储的是值本身,栈空间中由操作系统自动分配释放存取函数的参数值,局部变量等
⭐js是一种弱数据类型语言,赋值时才知道是什么类型,表单、prompt获取数据默认是字符串类型
⭐控制台数字和布尔类型是蓝色,字符串和underfined为灰色
⭐typeof关键字检测数据类型:typeof x(常用)或者typeof(x)
number数字型:常与算数运算符联合使用,优先级左往右;计算报错NAN(not a number),NaN是粘性的,任何对NAN的操作都会返回NaN
string字符串型:双引和单引无本质上的区别,推荐用单引号;
反引号``创建多行而无需用+号;
引号前加\反义符号可以输出引号,+可以数字相加\字符相连;
单双引号可以互相嵌套但是不可以自己嵌套自己;
模板字符串:反引号包含${};
有字符串的加法:如果其中一个操作数为字符串那么JS会将另一个操作数也转换为字符串,例如""+1="1";
有字符串的减法:JS会尝试将两个操作数都转换为数字,空字符串为0,如果有一个操作数无法转为数字那么结果将是NAN,例如""-2=-2
boolean布尔型:true/false,通过Boolean()转换布尔类型中’‘、0、undefined、null、false、NAN都是false,其余都为true
undefined未定义型:只有一个值underfined,只声明变量不赋值的情况下出现,工作中常用于通过检测变量是否为underfined,判断用户是否有数据传输过来
underfined+underfined=NAN,
underfined经数字转换为NAN,例如uderfined+3=NAN
null空类型:underfined表示没有赋值,null表示赋值了但内容为空,常作为尚未创建的对象
null经数字转换为0,例如null+3=3
4.2 引用数据类型/复杂类型
在存储变量中存储的仅仅是地址引用,堆栈空间中由程序员分配释放,若程序员不释放,由垃圾回收机制回收,栈中放地址,真正对象放堆中
object 对象
5.数据转换
5.1 隐式转换
某些运算符被执行时,系统内部自动将数据类型进行转换,这种转换称为隐式转换。
任何数据和字符串相加结果都是字符串,+作为正号解析则转为数字型,除了+以外的算数运算符都会转成数字类型
typeof '123'//string
typeof +'123'//number
null==undefined//true
null===undefined//false
5.2 显示转换
编写程序时过度依靠系统内部的隐式转换是不严禁的,因为隐式转换规律并不清晰,大多是靠经验总结的规律。为了避免因隐式转换带来的问题,通常根逻辑需要对数据进行显示转换。
5.2.1 Number数值类型
通过 Number 显示转换成数值类型,当转换失败时结果为 NaN(Not a Number)即不是一个数字。
<script>
let t = '12'
let f = 8
// 显式将字符串 12 转换成数值 12
t = Number(t)
// 结果为 20
console.log(t + f)
// 并不是所有的值都可以被转成数值类型
let str = 'hello'
// 将 hello 转成数值是不现实的,当无法转换成
// 数值时,得到的结果为 NaN (Not a Number)
console.log(Number(str))
</script>
parseInt只保留整数, parseFloat可以保留小数,toFixed指定保留小数位数
5.2.2 字符型
String(数据)
变量.toString(进制)
let myNumber = 11
let stringNunberOne = String(myNumber)
let stringNunberTwo = myNumber.toString(2)
6. 运算符
(1)赋值运算符:+=,-=,*=,/=,%=
(2)一元运算符:++num,num++ ;前置和后置单独使用时无区别,后置++用的比较多
(3)比较运算符:>,<,>=,<=,==,===(值和类型都等,开发推荐使用),!==;涉及到NAN都是false,NAN不等同于任何值包括自己;尽量不要比较小数,有精度问题;不同类型之间比较会发生隐式转换,最终为number
(4)逻辑运算符:&&,||,!
⭐运算符优先级(高到低):小括号(()),一元运算符(++,--,!),算术运算符(先*/%后+-),关系运算符(>,>=,<,<=),相等运算符(==,!=,===,!==),逻辑运算符(先&&后||),赋值运算符(=),逗号运算符(,)
⭐逻辑短路:&&左false短路,||左true短路
7. 语句
⭐表达式和语句
语句是一段可以执行的代码,不一定有值,比如alert(),for,break就不能被用于赋值
表达式可被求值,写在赋值语句右侧
⭐断点调试:学习时可以帮助更好的理解代码运行,工作时可以更快找到bug,在某句代码上加的标记就叫断点,当程序执行到这句有标记的代码时会暂停下来
浏览器打开调试界面
-
按F12打开开发者工具
-
点到源代码一栏 ( sources )
-
选择代码文件
7.1 分支语句
if分支语句(**重点):小括号内的结果若不是布尔类型时,会发生类型转换为布尔值,类似Boolean()
//单分支
if(条件表达式) {
// 满足条件要执行的语句
}
//双分支
if(条件表达式) {
// 满足条件要执行的语句
}else{
//不满足条件要执行的语句
}
//多分支
if(条件表达式) {
}else if(条件表达式) {
}else{
}
三元运算符:? 与 : 配合使用, 一些简单的双分支,可以使用 三元运算符(三元表达式),写起来比 if else双分支 更简单`
条件 ? 表达式1 : 表达式2
switch语句:适合于有多个条件的时候,也属于分支语句,大部分情况下和 if多分支语句 功能相同
//switch case语句一般用于等值判断, if适合于区间判断
//switch case一般需要配合break关键字使用 没有break会造成case穿透
//if 多分支语句开发要比switch更重要,使用也更多
//===不全等则执行default
switch (表达式) {
case 值1:
代码1
break
case 值2:
代码2
break
...
default:
代码n
}
7.2 循环语句
while循环
//循环三要素:1.初始值 (经常用变量)2.终止条件3.变量的变化量
//`break` 中止整个循环,一般用于结果已经得到, 后续的循环不需要的时候可以使用(提高效率)
//`continue` 中止本次循环,一般用于排除或者跳过某一个选项的时候
while (条件表达式) {
// 循环体
}
无限循环
/* 需求: 页面会一直弹窗询问你爱我吗?
(1). 如果用户输入的是 '爱',则退出弹窗
(2). 否则一直弹窗询问*/
//1.while(true) 来构造“无限”循环,需要使用break退出循环。(常用)
while (true) {
let love = prompt('你爱我吗?')
if (love === '爱') {
break
//2.for(;;) 也可以来构造“无限”循环,同样需要使用break退出循环。
for (; ;) {
let love = prompt('你爱我吗?')
if (love === '爱') {
break
}
}
for循环
for(变量起始值;终止条件;变量变化量){
//循环体
}
8 数组
数组:(Array)是一种可以按顺序保存数据的数据类型,属于对象类型
数据单元值类型:数组做为数据的集合,它的单元值可以是任意数据类型
使用场景:如果有多个数据可以用数组保存起来,然后放到一个变量中,管理非常方便
//1.声明语法:下标从0开始
let 数组名 = [数据1,数据2...]
let arr = new Array(数据1,数据2...)
//2.遍历数组:for循环
//3.增:
arr.push(新增内容)动态向数组的尾部添加一个单元
arr.unshift(新增内容)动态向数组头部添加一个单元
//4.删:
arr.pop()删除最后一个单元
arr.shift()删除第一个单元
arr.splice(操作的下标,删除的个数)动态删除任意单元
//5.改:重新赋值:数组名[下标]=值
//6.查:数组名[下标]
//7.排序:sort()方法
默认升序arr.sort(function(a,b){return a-b})
降序arr.sort(function(a,b){return b-a})
//8.冒泡排序
<script>
let arr = [1, 5, 3, 7, 9, 2, 4, 6, 8, 0]
for (let i = 0; i < arr.length - 1; i++) {
for (let j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
let temp = arr[j]
arr[j] = arr[j + 1]
arr[j + 1] = temp
}
}
}
console.log(arr)
</script>
8.1 map方法
map 可以遍历数组处理数据,并且返回新的数组
map 也称为映射。映射是个术语,指两个元素的集之间元素相互“对应”的关系。
map重点在于有返回值,forEach没有返回值(undefined)
<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)
</script>
</body>
8.2 join方法
join() 方法用于把数组中的所有元素转换一个字符串
// 小括号为空则逗号分割
console.log(newArr.join()) // red颜色,blue颜色,pink颜色
// 小括号是空字符串,则元素之间没有分隔符
console.log(newArr.join('')) //red颜色blue颜色pink颜色
console.log(newArr.join('|')) //red颜色|blue颜色|pink颜色
8.3 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) // 索引号
})
// console.log(result)
</script>
</body>
8.4 filter筛选数组
filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素
主要使用场景: 筛选数组符合条件的元素,并返回筛选之后元素的新数组
<body>
<script>
const arr = [10, 20, 30]
// const newArr = arr.filter(function (item, index) {
// // console.log(item)
// // console.log(index)
// return item >= 20
// })
// 返回的符合条件的新数组
const newArr = arr.filter(item => item >= 20)
console.log(newArr)
</script>
</body>
8.5 reduce数组累计
用于对数组中的元素进行累积处理,并最终返回一个单一的值。它广泛应用于数组的求和、求积、数据转换等场景。
arr.reduce(function(累计值, 当前元素){}, 起始值)
<script>
const arr = [1, 2, 3]
const re = arr.reduce((prev, item) => prev + item)
console.log(re)
</script>
9.函数
函数是被设计为执行特定任务的代码块
函数作用:函数可以把具有相同或相似逻辑的代码“包裹”起来,通过函数调用执行这些被“包裹”的代码逻辑,这么做的优势是有利于精简代码方便复用。
函数声明:声明(定义)一个完整函数包括关键字、函数名、形式参数、函数体、返回值5个部分
function 函数名(){
函数体
}
函数使用:声明(定义)的函数必须调用才会真正被执行,使用 () 调用函数。
规范:函数名的命名规则与变量是一致的,并且尽量保证函数名的语义,常用动词约定,前缀为动词,尽量小驼峰式命名法
| 动词 | 含义 |
|---|---|
| can | 判断是否可执行某个动作 |
| has | 判断是否含某个值 |
| is | 判断是否为某个值 |
| get | 获取某个值 |
| set | 设置某个值 |
| load | 加载某个值 |
//一次声明可多次调用,每次调用函数体里面的代码会重新执行一遍 //两个相同函数后面的会覆盖前面的 函数名()
函数传参:传入数据列表,声明这个函数需要传入几个数据,多个数据用逗号隔开,个数没有限制
形参:声明函数时写在函数名右边小括号里的叫形参(形式上的参数)
实参:调用函数时写在函数名右边小括号里的叫实参(实际上的参数)
形参可以理解为是在这个函数内声明的变量,实参可以理解为是给这个变量赋值,开发中尽量保持形参和实参个数一致,可以不一致,形参多会自动填上underfined,实参过多则被忽略
参数默认值:如果用户没有提供任何参数,函数会尝试计算undefined + undefined,这将导致结果成为NaN在修改后的函数定义中,x和y被赋予了默认值0。这意味着如果用户没有提供任何参数,x和y都将被设置为0,函数将返回0 + 0 = 0。如果用户提供了一个参数有值,第二个参数将使用默认值0,函数将返回5 + 0 = 5。
function 函数名(参数列表){
函数体
}
//结合数组
<script>
function getSum(arr){
let sum=0
for(let i=0;i<arr.length;i++){
sum+=arr[i]
}
return sum
}
document.write(getSum([1,2,3,4,5]))
</script>
函数返回值:prompt()、parseInt()有返回值,alert()无返回值
-
在函数体中使用return 关键字能将内部的执行结果交给函数外部使用
-
函数内部只能出现1 次 return,并且 return 下一行代码不会再被执行,所以return 后面的数据不要换行写
-
return会立即结束当前函数
-
函数可以没有return,这种情况默认返回值为 undefined
作用域:通常来说,一段程序代码中所用到的名字并不总是有效和可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。
作用域的使用提高了程序逻辑的局部性,增强了程序的可靠性,减少了名字冲突。
(1)全局作用域:作用于所有代码执行的环境(整个 script 标签内部)或者一个独立的 js 文件
处于全局作用域内的变量,称为全局变量
(2)局部作用域:作用于函数内的代码环境,就是局部作用域。 因为跟函数有关系,所以也称为函数作用域。
处于局部作用域内的变量称为局部变量
如果函数内部,变量没有声明,直接赋值,也当全局变量看,但是强烈不推荐
但是有一种情况,函数内部的形参可以看做是局部变量。
变量访问原则(就近原则):局部和全局都可访问情况下,同名先访局部再全局
匿名函数:函数可以分为具名函数和匿名函数
匿名函数:没有名字的函数,无法直接使用
function(){}
使用方式
//1.函数表达式:将匿名函数赋值给一个变量,并且通过变量名进行调用,我们将这个称为函数表达式
// 声明
let fn = function() {
console.log('函数表达式')
}
// 调用
fn()
//2.立即执行函数:不需要调用,立即执行,多个立即函数用;隔开,不然报错
作用:避免全局变量之间的污染
(function(){ xxx })();
(function(){xxxx}());
10.对象
对象是一种数据类型,无序的数据的集合,可以详细的描述某个事物
对象声明:
let 对象名 = {}
let 对象名 = new Object()
对象使用:属性(信息/特征名词)和方法(功能/行为名词)
-
属性都是成 对出现的,包括属性名和值,它们之间使用英文
:分隔 -
多个属性之间使用英文
,分隔,没有顺序 -
属性就是依附在对象上的变量
-
属性名可以使用
""或'',一般情况下省略,除非名称遇到特殊符号如空格、中横线等 -
方法是由方法名和函数两部分构成,它们之间使用 : 分隔
-
多个属性之间使用英文
,分隔 -
方法是依附在对象中的函数
-
方法名可以使用
""或'',一般情况下省略,除非名称遇到特殊符号如空格、中横线等
let 对象名 = {
属性名:属性值
方法名:函数
}
let person = {
name: '小明', // 描述人的姓名
age: 18, // 描述人的年龄
stature: 185, // 描述人的身高
gender: '男', // 描述人的性别
sayHi: function(){
document.write('hi~~')
}
};
console.log(person['stature'])
console.log(person.stature)
console.log(person.sayHi())
//1.增加:对象名.新属性名=值
//2.删除:delete 对象名.属性名
//3.修改:对象名.属性=值
//4.查询:对象名.属性或者对象名['属性']或者对象名["属性"],如果[]中不添加引号,默认会变成变量解析
注:无论是属性或是方法,同一个对象中出现名称一样的,后面的会覆盖前面的。
遍历对象:for in 不提倡遍历数组 因为 k 是 字符串
let obj = {
uname: 'pink'
}
for(let k in obj) {
// k 属性名 字符串 带引号 obj.'uname' k === 'uname'
// obj[k] 属性值 obj['uname'] obj[k]
}
内置对象:除了console.log()、document.write(),js还有其他内置的对象
(1)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,数字向下取整
// 舍弃小数部分,整数部分不变
//数组随机选中:let random = Math.floor(Math.random()*arr.length)
Math.floor(4.68)
-
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)
二、WEB APIs-DOM
1.获取元素
1.1 Web API基础认识
作用:使用JS去操作html和浏览器
分类:DOM和BOM
DOM(Document Object Model-文档对象类型):浏览器提供的一套专门用来操作网页内容的功能,用于开发页面内容特效和实现用户交互
DOM核心思想:把网页内容当作对象来处理
DOM树:将 HTML 文档以树状结构直观的表现出来,我们称之为文档树或 DOM 树,文档树直观的体现了标签与标签之间的关系,节点是文档树的组成部分,每一个节点都是一个 DOM 对象,主要分为元素节点、属性节点、文本节点等。
DOM对象:浏览器根据html标签生成的JS对象,所有标签属性都可以在这个对象上找到,修改这个对象的属性会自动映射到标签身上
document对象:document是DOM最大的对象,网页所有内容都在document里,他提供的属性和方法都是用来访问和操作网页的
1.2 获取DOM对象
1.2.1 通过CSS选择器获取DOM元素
<p id="nav">从整个 DOM 树中查找 DOM 节点是学习 DOM 的第一个步骤。</p>
<ul>
<li>元素</li>
<li>元素</li>
<li>元素</li>
<li>元素</li>
</ul>
//选择匹配的第一个元素,成功返回一个HTMLElement对象,没有则返回null,可直接修改:document.querySelector('css选择器')
<script>
const p = document.querySelector('p')
const nav = document.querySelector('nav')
const li = document.querySelector('ul li:first-child')
</script>
//选择匹配多个元素,成功返回NodeList对象合集,它是一个伪数组,不可以直接操作,其为数组没有pop(),push()等方法,只能遍历操作:document.querySelectorAll('选择器')
<script>
const lis = document.querySelectorAll('ul li')
for(let i = 0; i < lis.length; i++){
console.log(lis[i])
}
</script>
1.2.2 其他早期获取元素方式
//根据id获取一个元素
document.getElementById('')
//根据标签获取一类元素
document.getElementsByTagName('')
//根据类名获取元素
document.getElementsByClassName('')
1.3 操作元素内容
1.3.1 对象.innerText属性
将文本内容添加/更新到任意标签位置,文本中包含的标签不会被解析
<script>
// innerText 将文本内容添加/更新到任意标签位置
const intro = document.querySelector('.intro')
// intro.innerText = '嗨~ 我叫李雷!''
</script>
1.3.2 对象.innerHTML属性
将文本内容添加/更新到任意标签位置,文本中包含的标签会被解析
<script>
// innerHTML 将文本内容添加/更新到任意标签位置
const intro = document.querySelector('.intro')
intro.innerHTML = '嗨~ 我叫韩梅梅!'
intro.innerHTML = '<h4>嗨~ 我叫韩梅梅!</h4>'
</script>
注:此属性没办法得到表单内容,表单内容可通过input.value获取
1.4 操作元素属性
1.4.1 操作元素常用属性
直接能过属性名修改
对象.属性 = 值
<img src="./images/1.webp" alt="">
<script>
// 1. 获取 img 对应的 DOM 元素
const pic = document.querySelector('.pic')
// 2. 修改属性
pic.src = './images/lion.webp'
pic.width = 400;
pic.alt = '图片不见了...'
</script>
1.4.2 操作元素样式属性
对象.style.属性 = '值和单位'
//通过style属性操作CSS:多组单词采用小驼峰命名法
box.style.color = 'red'
box.style.width = '300px'
box.style.backgroundColor = 'pink'
元素.className = '新类名'
//操作类名(className)操作CSS:如果修改的样式比较多,直接通过style属性修改比较繁琐,我们可以通过借助于css类名的形式
注意:由于class是关键字, 所以使用className去代替;className是使用新值换旧值, 如果需要添加一个类,需要保留之前的类名
const div = document.querySelector('div')
div.className = 'nav box'
元素.classList.add(‘类名’):追加一个类
元素.classList.remove(‘类名’):删除一个类
元素.classList.toggle(‘类名’):切换一个类,有就删,没有就加
元素.classList.contains(‘类名’) 看看有没有包含某个类,如果有则返回true,么有则返回false
//通过classList操作类控制CSS:为了解决className 容易覆盖以前的类名,我们可以通过classList方式追加和删除类名
let box = document.querySelector('div')
box.classList.toggle('one')
1.4.3 操作表单元素属性
获取:DOM对象.属性名
设置:DOM对象.属性名= 新值
// 1. 获取元素
let input = document.querySelector('input')
const input = document.querySelector('[type=serch]')
// 2. 取值或者设置值 得到input里面的值可以用 value
// console.log(input.value)
input.value = '小米手机'
input.type = 'password'
// 2. 启用按钮
//disabled 是一个布尔属性,当设置在 <button> 元素上时,按钮会被禁用,即用户不能与它交互,它也不会触发任何事件(如点击事件)
let btn = document.querySelector('button')
btn.disabled = false
// 3. 勾选复选框:checked复选框在页面加载时就会自动被勾选
let checkbox = document.querySelector('.agree')
checkbox.checked = true
1.4.4 自定义属性
在html5中推出来了专门的data-自定义属性
在标签上一律以data-开头
在DOM对象上一律以dataset对象方式获取
<body>
<div data-id="1"> 自定义属性 </div>
<script>
// 1. 获取元素
let div = document.querySelector('div')
// 2. 获取自定义属性值
console.log(div.dataset.id)
</script>
</body>
1.5 定时器-间歇函数
setInterval 是 JavaScript 中内置的函数,它的作用是间隔固定的时间自动重复执行另一个函数,也叫定时器函数。
//1.开启定时器:单位ms
//setInterval(函数,间隔时间)
// //可以写匿名函数调用,如果写有名函数则不能加括号,这里是自动调用的,括号是调用写法这里只执行一次就结束了,所以直接写明定位到函数让他自动调用就可以了
//定时器是一个数字型,且会变,用let
<script>
function repeat() {
console.log('不知疲倦的执行下去....')
}
setInterval(repeat, 1000)
</script>
<script>
setInterval(function(){
console.log('不知疲倦的执行下去....')
},1000)
</script>
//2.关闭定时器
let 变量名 = setInterval(函数,间隔时间)
clearInterval(变量名)
2.事件基础
2.1 事件监听
事件:在编程时系统内发生的动作或者发生的事情
事件监听:让程序检测是否有事件产生,一旦有事件触发,就立即调用一个函数做出响应,也称为绑定事件或者注册事件
事件监听三元素:
事件源:那个dom元素被事件触发了,要获取dom元素
事件类型:用什么方式触发
事件调用的函数:要做什么事
事件监听版本:
DOM L0
事件源.on事件 = function(){}
DOM L2
事件源.addEventListener(事件,事件处理函数)
区别:on方式会被覆盖,addEventListener方式可绑定多次,拥有事件更多特性,推荐使用
元素对象.addEventListener(‘事件类型’,要执行的函数)
//`click` 译成中文是【点击】的意思,它的含义是监听(等着)用户鼠标的单击操作,除了【单击】还有【双击】`dblclick`
<script>
// 1. 获取 button 对应的 DOM 对象
const btn = document.querySelector('#btn')
// 2. 添加事件监听
btn.addEventListener('click', function () {
console.log('等待事件被触发...')
// 改变 p 标签的文字颜色
let text = document.getElementById('text')
text.style.color = 'red'
})
// 3. 只要用户点击了按钮,事件便触发了!!!
</script>
事件类型:鼠标事件、键盘事件、表单事件、焦点事件等
//1.鼠标事件:click鼠标点击,mouseenter鼠标经过,mouseleave鼠标离开
<script>
// 需要事件监听的 DOM 元素
const box = document.querySelector('.box');
// 监听鼠标是移入当前 DOM 元素
box.addEventListener('mouseenter', function () {
// 修改文本内容
this.innerText = '鼠标移入了...';
// 修改光标的风格
this.style.cursor = 'move';
})
</script>
//2.键盘事件:keydown键盘按下触发,keyup键盘抬起触发
//3.表单事件:input用户输入事件
给input注册 change 事件,值被修改并且失去焦点后触发
可用于表单验证、数据绑定、动态更新
const qtyInput=document.getElementById('quantity');
const totalElement=document.getElementById('total');
qtyInput.addEventListener('change', () => {
let total = qtyInput.value * productPrice;
totalElement.innerText = `Total: $${total}`;
});
//4.焦点事件:focus获得焦点,blur失去焦点
input.addEventListener('focus',function(){
ul.style.display = 'block'
input.classList.add('search')
})
imput.addEventListener('blur',function(){
ul.style.display = 'none'
input.classList.remove('search')
})
2.2事件对象
任意事件类型被触发时与事件相关的信息会被以对象的形式记录下来,我们称这个对象为事件对象。
获取事件对象:在事件绑定的回调函数的第一个参数就是事件对象,一般命名为event,ev,e
document.getElementById('myButton').addEventListener('click', function(event) { console.log(event); });
事件对象常用属性:
-
ev.type当前事件的类型 -
ev.clientX/Y光标相对浏览器窗口的位置 -
ev.offsetX/Y光标相于当前 DOM 元素的位置 -
ev.key用户按下的键盘键的值,如Enter
-
ev.target 返回事件的目标节点(触发该事件的节点),如生成事件的元素、文档或窗口
属性 描述 id 返回触发事件元素的id属性值 tagName 返回触发事件元素的标签名,例如"DIV"或"SPAN" nodeName 与tagName类似,返回触发事件元素的节点名 classList 返回触发事件元素的类名列表,以DOMTokenList对象形式返回 className 返回触发事件元素的类名,以字符串形式返回 innerHTML 返回触发事件元素的内部HTML内容 innerText 返回触发事件元素的内部文本内容
2.3 环境对象
环境对象指的是函数内部特殊的变量 this ,它代表着当前函数运行时所处的环境。
普通函数中this指向window
谁调用,this就是谁
this 本质上是一个变量,数据类型为对象
2.4 回调函数
如果将函数 A 做为参数传递给函数 B 时,我们称函数 A 为回调函数。
定时器,监听器里面有回调函数
3.事件进阶
3.1 事件流
事件流指的是事件完整执行过程中的流动路径
触发时总是经过两个阶段:捕获阶段(父到子)和冒泡阶段(子到父)
事件捕获
-
addEventListener第3个参数决定了事件是在捕获阶段触发还是在冒泡阶段触发 -
addEventListener第3个参数为true表示捕获阶段触发,false表示冒泡阶段触发,L2默认值为false -
L0事件监听只有冒泡阶段,没有捕获
事件冒泡
当一个元素触发事件后,会依次向上调用所有父级元素的同名事件
//当点击内部的<p>元素时,会按照从内到外的顺序依次触发点击事件:
<form onclick="alert('form')">
<div onclick="alert('div')">
<p onclick="alert('p')" ></p>
</div>
</form>
鼠标经过事件:
mouseover 和 mouseout 会有冒泡效果
mouseenter 和 mouseleave 没有冒泡效果 (推荐)
3.2 阻止冒泡
阻止冒泡是指阻断事件的流动,保证事件只在当前元素被执行,而不再去影响到其对应的祖先元素。
事件对象.stopPropagation()
注意:此方法可以阻止事件传播,不光在冒泡阶段有效,捕获阶段也有效
// 内层的盒子
child.addEventListener('click', function (ev) {
console.log('child...')
// 借助事件对象,阻止事件向上冒泡
ev.stopPropagation()
})
事件对象.preventDefault()
阻止元素默认行为的发生,如阻止链接的跳转,表单域跳转
const form = document.querySelector('form')
form.addEventListener('submit',function(e){
e.preventDefault()//阻止表单默认提交
})
3.3 解绑事件
L0中,on事件,直接用null覆盖就可以实现时间的解绑
同一个对象,后面注册的事件会覆盖前面注册的(同一个事件)
都是冒泡阶段执行
const btn = document.querySelector('button')
btn.onclick = function() {
alert('点击')
}
btn.onclick = null
L2中,addEventListener方式,必须使用removeEventListener(事件类型,事件处理函数,[获取或冒泡阶段])
后面注册的事件不会覆盖前面注册的(同一个事件)
可以通过第三个参数去确定是在冒泡或者捕获阶段执行
匿名函数无法被解绑
function fn(){
alert('点击了')
}
btn.addEventListener('click',fn)
btn.removeEventListener('click',fn)
3.4 事件委托
事件委托是利用事件流的特征解决一些现实开发需求的知识技巧,主要的作用是提升程序效率。
优点:减少注册次数,提高程序性能
原理:事件委托其实是利用事件冒泡的特点,给父元素注册事件,当触发子元素时,会冒泡到父元素身上,从而触发父元素事件
事件对象.target.tagName可以获取真正触发事件的元素
//子类没有捕获阶段,只有冒泡阶段
const ul = document.querySelector('ul')
ul.addEventListener('click',function(e){
if(e.target.tagName === 'LI'){
e.target.style.color = 'red'//选中的li变色
}
})
3.5 其他事件
3.5.1 页面加载事件
加载外部资源(如图片、外联CSS和JavaScript等)加载完毕时触发的事件,有些时候需要等页面资源全部处理完了做一些事情(script写在head中)
事件名:load
监听页面所有资源加载完毕:
window.addEventListener('load', function() {
// xxxxx
})
针对某个资源加载完毕
//等待图片加载完毕再执行
img.addEventListener('load', function() {
// xxxxx
})
初始HTML文档被完全加载和解析完成之后,无需等待样式表、图像等全加载,DOMContentLoaded被触发
document.addEventListener('DOMContentLoaded', function() {
// xxxxx
})
3.5.2 页面滚动事件
滚动条在滚动的时候持续触发的事件,事件名:scroll
监听整个页面滚动(元素也可以添加)
window.addEventListener('scroll', function() {
// xxxxx
})
获取位置:scrollLeft和scrollTop(属性)
获取被卷去的大小
获取元素内容往左、往上滚出去看不到的距离
这两个值是可读写的,值为数字型无单位
div.scrollTop//被卷去的头部 document.documentElement.scrollTop//页面卷去多少
/*让点击元素到视口顶端*/
for (let i = 0 ; i < li.length ; i++){
li[i].addEventListener('click',function(){
document.documentElement.scrollTop = boxLi[i].offsetTop
})
}
3.5.3 页面尺寸事件
会在窗口尺寸改变的时候触发事件:
window.addEventListener('resize', function() {
// xxxxx
})
检测屏幕尺寸:clientWidth和clientHeight
获取宽高:获取元素的可见部分宽高,不包括边框、margin、滚动条等
let w = document.documentElement.clientWidth
元素尺寸与位置:offsetWidth和offsetHeight
获取元素的自身宽高、包含元素自身设置的宽高、padding、border
只读属性
检测盒子位置,最近一级带有定位的祖先元素
页面滚动到某个元素就可以做某些事,通过JS的方式获取元素在页面中的位置
获取的是可视宽高, 如果盒子是隐藏的,获取的结果是0
3.6 日期对象
用来表示时间的对象,可以得到当前系统时间
实例化
获取当前时间const date = new Date()
指定时间const date1 = new Date(‘2022-5-1 08:30:00’)
时间对象方法
getFullYear 获取四位年份
getMonth 获取月份,取值为 0 ~ 11
getDate 获取月份中的每一天,不同月份取值也不相同
getDay 获取星期,取值为 0 ~ 6
getHours 获取小时,取值为 0 ~ 23
getMinutes 获取分钟,取值为 0 ~ 59
getSeconds 获取秒,取值为 0 ~ 59
const year = date.getFullYear(); // 四位年份
const month = date.getMonth(); // 0 ~ 11
时间戳
时间戳是指1970年01月01日00时00分00秒起至现在的总秒数或毫秒数,它是一种特殊的计量时间的方式。
注:ECMAScript 中时间戳是以毫秒计的。
获取时间戳的方法,分别为 getTime 和 Date.now 和 +new Date()
const date = new Date()
console.log(date.getTime())
console.log(+new Date())
// 无需实例化,只能得到当前时间戳,而前两种可以返回指定时间的时间戳
console.log(Date.now())
4.节点操作
4.1 插入节点
1.创造节点:document.createElement(‘标签名’)
2.追加节点:
插入到父元素中的最后一个子元素:父元素.appendChild(要插入的元素)
插入到父元素中的某个子元素的前面:父元素.insertBefore(要插入的元素,在哪个元素前面)
特殊情况下克隆节点:
元素.cloneNode(布尔值)
true会复制所有子节点,false克隆时不包括后代节点,默认false
4.2 删除节点
必须通过父元素删除:父元素.removeChild(要删的元素)
注:不存在父子关系则删除不成功
4.3 查找结点
DOM 树中的任意节点都不是孤立存在的,它们要么是父子关系,要么是兄弟关系,不仅如此,我们可以依据节点之间的关系查找节点。
父节点查找:子元素.parentNode
返回最近一级的父节点,找不到返回none
console.log(this.parentNode); // 父节点
console.log(this.parentNode.parentNode); // 爷爷节点
子节点查找:父元素.children
仅获得所有元素节点,返回一个伪数组
childNodes获得所有子节点
包括文本节点(空行、换格)、注释节点等,回车换行会被认为是空白文本节点
// 所有的子节点
console.log(ul.childNodes)
// 只包含元素子节点
console.log(ul.children)
下一个兄弟节点:nextElementSibling
上一个兄弟节点:previousElementSibling
5.M端事件
移动端常见事件
touch触摸事件:
touchstart手指触碰到一个DOM元素时触发
touchmove手指在一个DOM元素上滑动时触发
touchend手指从一个DOM元素上移开时触发
6.JS插件
css和js在本地文件package中
多个swiper同时使用的时候,类名需要注意区分
熟悉官网,了解这个插件可以完成什么需求 Swiper中文网-轮播图幻灯片js插件,H5页面前端开发
看在线演示,找到符合自己需求的demo Swiper演示 - Swiper中文网
查看基本使用流程 Swiper使用方法 - Swiper中文网
查看APi文档,去配置自己的插件 中文api - Swiper中文网
三、WEB APIs-BOM
1.window对象
1.1 BOM(浏览器对象模型)
BOM (Browser Object Model ) 是浏览器对象模型
-
window对象是一个全局对象,也可以说是JavaScript中的顶级对象
-
像document、alert()、console.log()这些都是window的属性,基本BOM的属性和方法都是window的
-
所有通过var定义在全局作用域中的变量、函数都会变成window对象的属性和方法
-
window对象下的属性和方法调用的时候可以省略window
1.2 定时器-延时函数
间歇函数 setInterval : 每隔一段时间就执行一次, , 平时省略window
延时函数setTimeout:JavaScript 内置的一个用来让代码延迟执行的函数,setTimeout 仅仅只执行一次,所以可以理解为就是把一段代码延迟执行, 平时省略window
setTimeout(回调函数, 延迟时间)
清除延时函数:
clearTimeout(timerId)
注意点
延时函数需要等待,所以后面的代码先执行,每一次调用延时器会产生一个新的延时器
返回值是一个正整数,表示定时器的编号
1.3 JS执行机制
单线程,同一时间只能做一件事,所以所有任务需要排队,如果JS执行时间过长,会造成页面渲染不连贯,导致页面渲染加载阻塞
HTML5允许js脚本创建多个线程,出现同步与异步
异步任务类型:普通事件、资源加载、定时器
执行机制:先执行执行栈中的同步任务,异步任务放入任务队列,待执行
事件循环:由于主线程不断的重复获取任务,执行任务,再获取任务,再执行,这种机制称为事件循环

1.4 location对象
location (地址) 它拆分并保存了 URL 地址的各个组成部分, 它是一个对象
| 属性/方法 | 说明 |
|---|---|
| href | 属性,获取完整的 URL 地址,赋值时用于地址的跳转 |
| search | 属性,获取地址中携带的参数,符号 ?后面部分 |
| hash | 属性,获取地址中的啥希值,符号 # 后面部分 |
| reload() | 方法,用来刷新当前页面,传入参数 true 时表示强制刷新 |
// 获取当前页面地址中的hash值
let hashValue = window.location.hash;
console.log('当前页面的hash值为:', hashValue);
1.5 navigator对象
navigator是对象,该对象下记录了浏览器自身的相关信息
常用属性和方法:
-
通过 userAgent 检测浏览器的版本及平台
// 验证是否为Android或iPhone const android = navigator.userAgent.match(/(Android);?[\s\/]+([\d.]+)?/) const iphone = navigator.userAgent.match(/(iPhone\sOS)\s([\d_]+)/)
1.6 history对象
history (历史)是对象,主要管理历史记录, 该对象与浏览器地址栏的操作相对应,如前进、后退等
常见方法:
| history对象方法 | 作用 |
|---|---|
| back() | 后退 |
| forward() | 前进 |
| go(参数) | 前进后退,参数1前进一个页面,参数-1后退一个页面 |
2.本地存储
将数据存储在本地浏览器中,常用于页面刷新数据不丢失
好处:
1、页面刷新或者关闭不丢失数据,实现数据持久化
2、容量较大,sessionStorage和 localStorage 约 5M 左右
2.1 localStorage
作用: 数据可以长期保留在本地浏览器中,刷新页面和关闭页面,数据也不会丢失
特性:以键值对的形式存储,多窗口共享数据,并且存储的是字符串, 省略了window
存储数据:localStorage.setItem(key,value)
读取数据:localStorage.getItem(key)
删除数据:localStorage.removeItem(key)
存储复杂数据类型:
问题:本地只能存储字符串,无法存储复杂数据类型.
解决:需要将复杂数据类型转换成 JSON字符串,在存储到本地
语法:JSON.stringify(复杂数据类型)
JSON字符串:
-
首先是1个字符串
-
属性名使用双引号引起来,不能单引号
-
属性值如果是字符串型也必须双引号
localStorage.setItem('goods', JSON.stringify(goods)) // 把JSON字符串转换为 对象 const str = localStorage.getItem('obj') // {"uname":"pink老师","age":18,"gender":"女"} console.log(JSON.parse(str))
浏览器查看本地数据:检查->Application->Storage->Local Storage->file->key\value
2.2 sessionStorage
特性:
-
用法跟localStorage基本相同
-
区别是:当页面浏览器被关闭时,存储在 sessionStorage 的数据会被清除
存储:sessionStorage.setItem(key,value)
获取:sessionStorage.getItem(key)
删除:sessionStorage.removeItem(key)
3.正则表达式
正则表达式(Regular Expression)是一种字符串匹配的模式(规则)
使用场景:
-
例如验证表单:手机号表单要求用户只能输入11位的数字 (匹配)
const input = document.getElementById('myInput'); input.addEventListener('input', function () { const value = this.value; const regex = /^\d{11}$/; if (!regex.test(value)) { // 如果不匹配,清除输入框内容(可根据需求调整为提示等其他操作) this.value = value.slice(0, -1); } }); -
过滤掉页面内容中的一些敏感词(替换),或从字符串中获取我们想要的特定部分(提取)等
// 定义敏感词列表 const sensitiveWords = ["badword1", "badword2", "badword3"]; // 生成正则表达式 const regex = new RegExp(sensitiveWords.join("|"), "gi"); // 实现替换函数 const replaceSensitiveWords = (text) => { return text.replace(regex, ""); }; // 示例使用 const inputText = "This is a badword1 and another badword2."; const cleanText = replaceSensitiveWords(inputText); console.log(cleanText); // 输出: This is a and another.
3.1 基本使用
-
定义规则
const reg = /表达式/
-
其中
/ /是正则表达式字面量 -
正则表达式也是
对象
-
-
使用正则
| 方法 | 描述 |
|---|---|
| test() | 测试字符串中是否存在匹配指定模式的子串,返回布尔值。 |
| exec() | 在字符串中执行匹配,返回匹配的结果数组,如果没有匹配则返回null。 |
| match() | 在字符串中执行匹配,返回匹配的结果数组,如果没有匹配则返回null。如果使用了全局匹配修饰符g,则返回所有匹配的结果数组。 |
| matchAll() | 在字符串中执行全局匹配,返回一个包含所有匹配结果的迭代器。 search() 在字符串中搜索匹配指定模式的子串,返回第一个匹配的位置索引,如果没有匹配则返回 -1。 |
| replace() | 在字符串中搜索匹配指定模式的子串,并用指定的字符串或函数进行替换。 |
| split() | 使用指定的模式作为分隔符来分割字符串,返回分割后的子串数组。 |
3.2 元字符
-
普通字符:
-
大多数的字符仅能够描述它们本身,这些字符称作普通字符,例如所有的字母和数字。
-
普通字符只能够匹配字符串中与它们相同的字符。
-
比如,规定用户只能输入英文26个英文字母,普通字符的话 /[abcdefghijklmnopqrstuvwxyz]/
-
元字符(特殊字符)
-
是一些具有特殊含义的字符,可以极大提高了灵活性和强大的匹配功能。
-
比如,规定用户只能输入英文26个英文字母,换成元字符写法: /[a-z]/
-
参考文档: MDN:正则表达式 - JavaScript | MDN 正则测试工具: 在线正则表达式测试
-
分类:
(1)边界符
正则表达式中的边界符(位置符)用来提示字符所处的位置,主要有两个字符
边界符 说明 ^ 表示匹配行首的文本(以谁开始) $ 表示匹配行尾的文本(以谁结束) 两个字符都在时为精确匹配
<body> <script> // 元字符之边界符 // 1. 匹配开头的位置 ^ const reg = /^web/ console.log(reg.test('web前端')) // true console.log(reg.test('前端web')) // false console.log(reg.test('前端web学习')) // false console.log(reg.test('we')) // false // 2. 匹配结束的位置 $ const reg1 = /web$/ console.log(reg1.test('web前端')) // false console.log(reg1.test('前端web')) // true console.log(reg1.test('前端web学习')) // false console.log(reg1.test('we')) // false // 3. 精确匹配 ^ $ const reg2 = /^web$/ console.log(reg2.test('web前端')) // false console.log(reg2.test('前端web')) // false console.log(reg2.test('前端web学习')) // false console.log(reg2.test('we')) // false console.log(reg2.test('web')) // true console.log(reg2.test('webweb')) // flase </script> </body>(2)量词
量词用来设定某个模式重复次数
量词 说明 * 重复零次或更多次 + 重复一次或更多次 ? 重复零次或一次 {n} 重复n次 {n,} 重复n次或更多次 {n,m} 重复n到m次 <body> <script> // 元字符之量词 // 1. * 重复次数 >= 0 次 const reg1 = /^w*$/ console.log(reg1.test('')) // true console.log(reg1.test('w')) // true console.log(reg1.test('ww')) // true console.log('-----------------------') // 2. + 重复次数 >= 1 次 const reg2 = /^w+$/ console.log(reg2.test('')) // false console.log(reg2.test('w')) // true console.log(reg2.test('ww')) // true console.log('-----------------------') // 3. ? 重复次数 0 || 1 const reg3 = /^w?$/ console.log(reg3.test('')) // true console.log(reg3.test('w')) // true console.log(reg3.test('ww')) // false console.log('-----------------------') // 4. {n} 重复 n 次 const reg4 = /^w{3}$/ console.log(reg4.test('')) // false console.log(reg4.test('w')) // flase console.log(reg4.test('ww')) // false console.log(reg4.test('www')) // true console.log(reg4.test('wwww')) // false console.log('-----------------------') // 5. {n,} 重复次数 >= n const reg5 = /^w{2,}$/ console.log(reg5.test('')) // false console.log(reg5.test('w')) // false console.log(reg5.test('ww')) // true console.log(reg5.test('www')) // true console.log('-----------------------') // 6. {n,m} n =< 重复次数 <= m const reg6 = /^w{2,4}$/ console.log(reg6.test('w')) // false console.log(reg6.test('ww')) // true console.log(reg6.test('www')) // true console.log(reg6.test('wwww')) // true console.log(reg6.test('wwwww')) // false // 7. 注意事项: 逗号两侧千万不要加空格否则会匹配失败 </script>(3)范围
范围 说明 [abc] 匹配包含的单个字符,理解为a||b||c单字符返回true [a-z] 连字符,来指定字符范围,表示a到z共26个英文字母 [^abc] 取反符匹配除了小写字符以外的字符 <body> <script> // 元字符之范围 [] // 1. [abc] 匹配包含的单个字符, 多选1 const reg1 = /^[abc]$/ console.log(reg1.test('a')) // true console.log(reg1.test('b')) // true console.log(reg1.test('c')) // true console.log(reg1.test('d')) // false console.log(reg1.test('ab')) // false // 2. [a-z] 连字符 单个 const reg2 = /^[a-z]$/ console.log(reg2.test('a')) // true console.log(reg2.test('p')) // true console.log(reg2.test('0')) // false console.log(reg2.test('A')) // false // 想要包含小写字母,大写字母 ,数字 const reg3 = /^[a-zA-Z0-9]$/ console.log(reg3.test('B')) // true console.log(reg3.test('b')) // true console.log(reg3.test(9)) // true console.log(reg3.test(',')) // flase // 用户名可以输入英文字母,数字,可以加下划线,要求 6~16位 const reg4 = /^[a-zA-Z0-9_]{6,16}$/ console.log(reg4.test('abcd1')) // false console.log(reg4.test('abcd12')) // true console.log(reg4.test('ABcd12')) // true console.log(reg4.test('ABcd12_')) // true // 3. [^a-z] 取反符 const reg5 = /^[^a-z]$/ console.log(reg5.test('a')) // false console.log(reg5.test('A')) // true console.log(reg5.test(8)) // true </script> </body>(4)字符类
字符 说明 \d 匹配0-9之间任一数字,相当于[0-9] \D 匹配所有0-9以外的字符,相当于[ ^0-9 ] \w 匹配任意的字母、数字、下划线,相当于[A-Za-z0-9_] \W 匹配除所有字母、数字、下划线以外的字符,相当于[ ^A-Za-z0-9_ ] \s 匹配空格(包括换行符、制表符、空格符),相当于[\t\r\n\v\f] \S 匹配非空格的字符,相当于[ ^\t\r\n\v\f ]
日期格式:/^\d{4}-\d{1,2}-\d{1,2}$/
3.3 替换和修饰符
字符替换:字符串.replace(/正则表达式/,’替换的文本‘)
注:只能替换一个
-
i 是单词 ignore 的缩写,正则匹配时字母不区分大小写
-
g 是单词 global 的缩写,匹配所有满足正则表达式的结果
<body> <script> // 替换和修饰符 const str = '欢迎大家学习前端,相信大家一定能学好前端,都成为前端大神' // 1. 替换 replace 需求:把前端替换为 web // 1.1 replace 返回值是替换完毕的字符串 // const strEnd = str.replace(/前端/, 'web') 只能替换一个 // 2. 修饰符 g 全部替换 const strEnd = str.replace(/前端/g, 'web') console.log(strEnd) </script> </body>
四、JS高级
1.作用域
作用域(scope)规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问,作用域分为全局作用域和局部作用域。
1.1 局部作用域
1.1.1 函数作用域
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。
注:
-
函数内部声明的变量,在函数外部无法被访问
-
函数的参数也是函数内部的局部变量
-
不同函数内部声明的变量无法互相访问
-
函数执行完毕后,函数内部的变量实际被清空了
1.1.2 块作用域
在 JavaScript 中使用 {} 包裹的代码称为代码块,代码块内部声明的变量外部将【有可能】无法被访问。
-
let声明的变量会产生块作用域,var不会产生块作用域 -
const声明的常量也会产生块作用域 -
不同代码块之间的变量无法互相访问
-
推荐使用
let或const
注:开发中 let 和 const 经常不加区分的使用,如果担心某个值会不小被修改时,则只能使用 const 声明成常量。
1.2 全局作用域
<script> 标签和 .js 文件的【最外层】就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。
<script>
// 此处是全局
function sayHi() {
// 此处为局部
}
// 此处为全局
</script>
-
为
window对象动态添加的属性默认也是全局的,不推荐! -
函数中未使用任何关键字声明的变量为全局变量,不推荐!!!
-
尽可能少的声明全局变量,防止全局变量被污染
1.3 作用域链
函数内部允许创建新的函数,作用域产生了嵌套的关系,父子关系的作用域关联在一起形成了链状的结构,作用域链的名字也由此而来。
作用域链本质上是底层的变量查找机制,在函数被执行时,会优先查找当前函数作用域中查找变量,如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域
-
嵌套关系的作用域串联起来形成了作用域链
-
相同作用域链中按着从小到大的规则查找变量
-
子作用域能够访问父作用域,父级作用域无法访问子级作用域
1.4 闭包
闭包是一种比较特殊和函数,使用闭包能够访问函数作用域中的变量。从代码形式上看闭包是一个做为返回值的函数
<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.5 变量提升
变量提升是 JavaScript 中比较“奇怪”的现象,它允许在变量声明之前即被访问
<script>
// 访问变量 str
console.log(str + 'world!');
// 声明变量 str
var str = 'hello ';
</script>
-
变量在未声明即被访问时会报语法错误
-
变量在声明之前即被访问,变量的值为
undefined -
let声明的变量不存在变量提升,推荐使用let -
变量提升出现在相同作用域当中
-
实际开发中推荐先声明再访问变量
2.函数进阶
2.1 函数提升
函数提升与变量提升比较类似,是指函数在声明之前即可被调用。
-
函数提升能够使函数的声明调用更灵活
-
函数表达式不存在提升的现象
-
函数提升出现在相同作用域当中
<script>
// 调用函数
foo()
// 声明函数
function foo() {
console.log('声明之前即被调用...')
}
// 不存在提升现象
bar() // 错误
var bar = function () {
console.log('函数表达式不存在提升现象...')
}
</script>
2.2 函数参数
2.2.1 默认值
<script>
// 设置参数默认值
function sayHi(name="小明", age=18) {
document.write(`<p>大家好,我叫${name},我今年${age}岁了。</p>`);
}
// 调用函数
sayHi();
sayHi('小红');
sayHi('小刚', 21);
</script>
-
声明函数时为形参赋值即为参数的默认值
-
如果参数未自定义默认值时,参数的默认值为
undefined -
调用函数时没有传入对应实参时,参数的默认值被当做实参传入
2.2.2 动态参数
arguments 是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参。
-
arguments是一个伪数组 -
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)// 两个参数
sum(1, 2, 4) // 两个参数
</script>
2.2.3 剩余参数
-
...是语法符号,置于最末函数形参之前,用于获取多余的实参 -
借助
...获取的剩余实参,是个真数组
<script>
function config(baseURL, ...other) {
console.log(baseURL) // 得到 'http://baidu.com'
console.log(other) // other 得到 ['get', 'json']
}
// 调用函数
config('http://baidu.com', 'get', 'json');
</script>
2.3 箭头函数
箭头函数是一种声明函数的简洁语法,它与普通函数并无本质的区别
-
箭头函数属于表达式函数,因此不存在函数提升
-
箭头函数只有一个参数时可以省略圆括号
() -
箭头函数函数体只有一行代码时可以省略花括号
{},并自动做为返回值被返回
<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>
2.3.1 箭头函数参数
箭头函数中没有 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>
2.3.2 箭头函数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>
3.解构赋值
解构赋值是一种快速为变量赋值的简洁语法,本质上仍然是为变量赋值,分为数组解构、对象解构两大类型。
3.1 数组解构
数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法
<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(b); // 2
console.log(c); // 3
</script>
-
赋值运算符
=左侧的[]用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量 -
变量的顺序对应数组单元值的位置依次进行赋值操作
-
变量的数量大于单元值数量时,多余的变量将被赋值为
undefined -
变量的数量小于单元值数量时,可以通过
...获取剩余单元值,但只能置于最末位 -
允许初始化变量的默认值,且只有单元值为
undefined时默认值才会生效
3.2 对象解构
对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法
-
赋值运算符
=左侧的{}用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量 -
对象属性的值将被赋值给与属性名相同的变量
-
对象中找不到与变量名一致的属性时变量值为
undefined -
允许初始化变量的默认值,属性不存在或单元值为
undefined时默认值才会生效
<script>
// 普通对象
const user = {
name: '小明',
age: 18
};
// 批量声明变量 name age
// 同时将数组单元值 小明 18 依次赋值给变量 name age
const {name, age} = user
console.log(name) // 小明
console.log(age) // 18
</script>
//多维解构赋值
<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
},
]
}
// 需求1: 请将以上msg对象 采用对象解构的方式 只选出 data 方面后面使用渲染页面
// const { data } = msg
// console.log(data)
// 需求2: 上面msg是后台传递过来的数据,我们需要把data选出当做参数传递给 函数
// const { data } = msg
// msg 虽然很多属性,但是我们利用解构只要 data值
function render({ data }) {
// const { data } = arr
// 我们只要 data 数据
// 内部处理
console.log(data)
}
render(msg)
// 需求3, 为了防止msg里面的data名字混淆,要求渲染函数里面的数据名改为 myData
function render({ data: myData }) {
// 要求将 获取过来的 data数据 更名为 myData
// 内部处理
console.log(myData)
}
render(msg)
</script>
4.深入对象
4.1 构造函数
构造函数是专门用于创建对象的函数,如果一个函数使用 new 关键字调用,那么这个函数就是构造函数。
<script>
// 定义函数
function foo() {
console.log('通过 new 也能调用函数...');
}
// 调用函数
new foo;
</script>
1. 使用 new 关键字调用函数的行为被称为实例化
-
实例化构造函数时没有参数时可以省略
() -
构造函数的返回值即为新创建的对象
-
构造函数内部的
return返回的值无效!
注:实践中为了从视觉上区分构造函数和普通函数,习惯将构造函数的首字母大写。
4.2 实例成员
通过构造函数创建的对象称为实例对象,实例对象中的属性和方法称为实例成员。
-
构造函数内部
this实际上就是实例对象,为其动态添加的属性和方法即为实例成员 -
为构造函数传入参数,动态创建结构相同但值不同的对象
注:构造函数创建的实例对象彼此独立互不影响。
<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>
4.3 静态成员
在 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指向构造函数本身
5.内置构造函数
在 JavaScript 内置了一些构造函数,绝大部的数据处理都是基于这些构造函数实现的,JavaScript 基础阶段学习的 Date 就是内置的构造函数
<script>
// 实例化
let date = new Date();
// date 即为实例对象
console.log(date);
</script>
5.1 Object
用于创建普通对象
-
推荐使用字面量方式声明对象,而不是
Object构造函数 -
Object.assign静态方法创建新的对象 -
Object.keys静态方法获取对象中所有属性 -
Object.values表态方法获取对象中所有属性值<script> // 通过构造函数创建普通对象 const user = new Object({name: '小明', age: 15}) // 这种方式声明的变量称为【字面量】 let student = {name: '杜子腾', age: 21} // 对象语法简写 let name = '小红'; let people = { // 相当于 name: name name, // 相当于 walk: function () {} walk () { console.log('人都要走路...'); } } //:object instanceof constructor,其中object是要检查的实例对象 console.log(student.constructor);//Object() console.log(user.constructor);//Object() console.log(student instanceof Object);//true </script>
5.2 Array
用于创建数组,数组赋值后,无论修改哪个变量另一个对象的数据值也会相当发生改变
<script>
// 构造函数创建数组
let arr = new Array(5, 7, 8);
// 字面量方式创建数组
let list = ['html', 'css', 'javascript']
</script>
总结:
-
推荐使用字面量方式声明数组,而不是
Array构造函数 -
实例方法
forEach用于遍历数组,替代for循环 (重点) -
实例方法
filter过滤数组单元值,生成新数组(重点) -
实例方法
map迭代原数组,生成新数组(重点) -
实例方法
join数组元素拼接为字符串,返回字符串(重点) -
实例方法
find查找元素, 返回符合测试条件的第一个数组元素值,如果没有符合条件的则返回 undefined(重点) -
实例方法
every检测数组所有元素是否都符合指定条件,如果所有元素都通过检测返回 true,否则返回 false(重点) -
实例方法
some检测数组中的元素是否满足指定条件 如果数组中有元素满足条件返回 true,否则返回 false -
实例方法
concat合并两个数组,返回生成新数组 -
实例方法
sort对原数组单元值排序 -
实例方法
splice删除或替换原数组单元 -
实例方法
reverse反转数组 -
实例方法
findIndex查找元素的索引值
5.3 包装类型
在 JavaScript 中的字符串、数值、布尔具有对象的使用特征
之所以具有对象特征的原因是字符串、数值、布尔类型数据是 JavaScript 底层使用 Object 构造函数“包装”来的,被称为包装类型
<script>
// 字符串类型
const str = 'hello world!'
// 统计字符的长度(字符数量)
console.log(str.length)
// 数值类型
const price = 12.345
// 保留两位小数
price.toFixed(2) // 12.34
</script>
5.3.1 String
用于创建字符串
<script>
// 使用构造函数创建字符串
let str = new String('hello world!');
// 字面量创建字符串
let str2 = '你好,世界!';
// 检测是否属于同一个构造函数
console.log(str.constructor === str2.constructor); // true
console.log(str instanceof String); // true
</script>
总结:
-
实例属性
length用来获取字符串的度长(重点) -
实例方法
split('分隔符')用来将字符串拆分成数组(重点) -
实例方法
substring(需要截取的第一个字符的索引[,结束的索引号])用于字符串截取(重点) -
实例方法
startsWith(检测字符串[, 检测位置索引号])检测是否以某字符开头(重点) -
实例方法
includes(搜索的字符串[, 检测位置索引号])判断一个字符串是否包含在另一个字符串中,根据情况返回 true 或 false(重点) -
实例方法
toUpperCase用于将字母转换成大写 -
实例方法
toLowerCase用于将就转换成小写 -
实例方法
indexOf检测是否包含某字符 -
实例方法
endsWith检测是否以某字符结尾 -
实例方法
replace用于替换字符串,支持正则匹配 -
实例方法
match用于查找字符串,支持正则匹配
注:String 也可以当做普通函数使用,这时它的作用是强制转换成字符串数据类型。
5.3.2 Number
用于创建数值
<script>
// 使用构造函数创建数值
let x = new Number('10')
let y = new Number(5)
// 字面量创建数值
let z = 20
</script>
总结:
-
推荐使用字面量方式声明数值,而不是
Number构造函数 -
实例方法
toFixed用于设置保留小数位的长度
6.编程思想
6.1 面向过程
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了。
6.2 面向对象
面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作。
在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工。
面向对象编程具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件项目。
面向对象的特性:
-
封装性
-
继承性
-
多态性
7.构造函数
<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面向对象可以通过构造函数实现的封装。
前面我们学过的构造函数方法很好用,但是 存在浪费内存的问题
8.原型对象
构造函数通过原型分配的函数是所有对象所共享的。
JavaScript 中对象的工作机制:当访问对象的属性或方法时,先在当前实例对象是查找,然后再去原型对象查找,并且原型对象被所有实例共享。
-
JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型对象
-
这个对象可以挂载函数,对象实例化不会多次创建原型上函数,节约内存
-
我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
-
构造函数和原型对象中的this 都指向实例化的对象
<script> function Person() { } // 每个函数都有 prototype 属性 console.log(Person.prototype) </script> //原型对象具体的作用 <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> //结合构造函数原型的特征,实际开发重往往会将封装的功能函数添加到原型对象中。 <script> function Person() { // 此处定义同名方法 sayHi this.sayHi = function () { console.log('嗨!' + this.name) } } // 为构造函数的原型对象添加方法 Person.prototype.sayHi = function () { console.log('Hi~' + this.name) } // 在构造函数的原型对象上添加属性 Person.prototype.name = '小明' let p1 = new Person() p1.sayHi(); // 输出结果为 嗨! let p2 = new Person() p2.sayHi() </script>
8.1 constructor属性
每个原型对象里面都有个constructor 属性(constructor 构造函数)
作用:该属性指向该原型对象的构造函数
使用场景:
如果有多个对象的方法,我们可以给原型对象采取对象形式赋值.
但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了
此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。
8.2 对象原型
对象都会有一个属性 proto 指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype
原型对象的属性和方法,就是因为对象有 proto 原型的存在。
注意:
-
proto 是JS非标准属性
-
[[prototype]]和proto意义相同
-
用来表明当前实例对象指向哪个原型对象prototype
-
proto对象原型里面也有一个 constructor属性,指向创建该实例对象的构造函数
8.3 原型继承
JavaScript 中大多是借助原型对象实现继承的特性
<body>
<script>
// 构造函数 new 出来的对象 结构一样,但是对象不一样
function Person() {
this.eyes = 2
this.head = 1
}
// console.log(new Person)
// 女人 构造函数 继承 Person
function Woman() {
}
// Woman 通过原型来继承 Person
// 父构造函数(父类) 子构造函数(子类)
// 子类的原型 = new 父类
Woman.prototype = new Person()
// 指回原来的构造函数
Woman.prototype.constructor = Woman
// 给女人添加一个方法 生孩子
Woman.prototype.baby = function () {
console.log('宝贝')
}
const red = new Woman()
console.log(red)
// 男人 构造函数 继承 Person
function Man() {
}
// 通过 原型继承 Person
Man.prototype = new Person()
Man.prototype.constructor = Man
const pink = new Man()
console.log(pink)
</script>
</body>
8.4 原型链
基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对象的链状结构关系称为原型链
<body>
<script>
// function Objetc() {}
console.log(Object.prototype)
console.log(Object.prototype.__proto__)
function Person() {
}
const ldh = new Person()
// console.log(ldh.__proto__ === Person.prototype)
// console.log(Person.prototype.__proto__ === Object.prototype)
console.log(ldh instanceof Person)
console.log(ldh instanceof Object)
console.log(ldh instanceof Array)
console.log([1, 2, 3] instanceof Array)
console.log(Array instanceof Object)
</script>
</body>
① 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
② 如果没有就查找它的原型(也就是 proto指向的 prototype 原型对象)
③ 如果还没有就查找原型对象的原型(Object的原型对象)
④ 依此类推一直找到 Object 为止(null)
⑤ proto对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线
⑥ 可以使用 instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
9.深浅拷贝
浅拷贝和深拷贝只针对引用类型
9.1 浅拷贝
如果是简单数据类型拷贝值,引用数据类型拷贝的是地址 (简单理解: 如果是单层对象,没问题,如果有多层就有问题)
浅拷贝:拷贝的是地址
常见方法:
-
拷贝对象:Object.assgin() 、展开运算符 {...obj} 、Object.create()
Object.assign()方法可以将一个或多个对象的属性复制到目标对象中,也可以用来创建对象的浅拷贝。扩展运算符...可以用来创建一个对象的浅拷贝Object.create()方法可以创建一个新对象,使用现有的对象作为新创建对象的原型const originalObject = { name: 'John', age: 30, hobbies: ['reading', 'running'] }; const copiedObject = Object.assign({}, originalObject); //const copiedObject = {...originalObject}; //const copiedObject = Object.create( originalObject); copiedObject.name = 'Jane'; console.log(originalObject.name); // 输出: John console.log(copiedObject.name); // 输出: Jane // 但是对于引用类型的属性(如数组)是浅拷贝 copiedObject.hobbies.push('swimming'); console.log(originalObject.hobbies); // 输出: ["reading", "running", "swimming"] console.log(copiedObject.hobbies); // 输出: ["reading", "running", "swimming"] -
拷贝数组:Array.from() 、 [...arr]、slice()、concat()
slice()方法返回一个新数组,包含原数组的浅拷贝。concat()方法用于合并数组,如果不传参数,会返回原数组的浅拷贝。 扩展运算符...可以将数组展开为单独的元素,用于创建新数组时实现浅拷贝。Array.from()方法可以从一个类数组或可迭代对象创建一个新数组,也可以用于浅拷贝。const originalArray = [1, 2, { a: 3 }, [4, 5]]; // 浅拷贝 const shallowCopy = originalArray.slice(); //const shallowCopy = originalArray.concat(); //const shallowCopy = {...originalArray}; //const shallowCopy = Array.from(originalArray) // 修改浅拷贝的第一层元素 shallowCopy[0] = 100; console.log(originalArray); // [1, 2, { a: 3 }, [4, 5]] (未受影响) console.log(shallowCopy); // [100, 2, { a: 3 }, [4, 5]] // 修改浅拷贝的嵌套对象 shallowCopy[2].a = 300; console.log(originalArray); // [1, 2, { a: 300 }, [4, 5]] (受影响) console.log(shallowCopy); // [1, 2, { a: 300 }, [4, 5]]
9.2 深拷贝
深拷贝:拷贝的是对象,不是地址
常见方法:
-
通过递归实现深拷贝
-
lodash/cloneDeep
-
通过JSON.stringify()实现
9.2.1 递归实现深拷贝
函数递归:
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数
-
简单理解:函数内部自己调用自己, 这个函数就是递归函数
-
递归函数的作用和循环效果类似
-
由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件 return
<body> <script> const obj = { uname: 'pink', age: 18, hobby: ['乒乓球', '足球'], family: { baby: '小pink' } } const o = {} // 拷贝函数 function deepCopy(newObj, oldObj) { debugger for (let k in oldObj) { // 处理数组的问题 一定先写数组 在写 对象 不能颠倒 if (oldObj[k] instanceof Array) { newObj[k] = [] // newObj[k] 接收 [] hobby // oldObj[k] ['乒乓球', '足球'] deepCopy(newObj[k], oldObj[k]) } else if (oldObj[k] instanceof Object) { newObj[k] = {} deepCopy(newObj[k], oldObj[k]) } else { // k 属性名 uname age oldObj[k] 属性值 18 // newObj[k] === o.uname 给新对象添加属性 newObj[k] = oldObj[k] } } } deepCopy(o, obj) // 函数调用 两个参数 o 新对象 obj 旧对象 console.log(o) o.age = 20 o.hobby[0] = '篮球' o.family.baby = '老pink' console.log(obj) console.log([1, 23] instanceof Object) </script> </body>
9.2.2 js库lodash里面cloneDeep内部实现了深拷贝
<body>
<!-- 先引用 -->
<script src="./lodash.min.js"></script>
<script>
const obj = {
uname: 'pink',
age: 18,
hobby: ['乒乓球', '足球'],
family: {
baby: '小pink'
}
}
const _ = require('lodash')
const o = _.cloneDeep(obj)
console.log(o)
o.family.baby = '老pink'
console.log(obj)
</script>
</body>
9.2.3 JSON序列化
<body>
<script>
// 定义一个包含嵌套对象的对象
let originalObj = {
name: 'John',
age: 30,
hobbies: ['reading', 'writing'],
address: {
city: 'New York',
street: '123 Main St'
}
};
// 使用JSON.parse(JSON.stringify())进行深拷贝
let copiedObj = JSON.parse(JSON.stringify(originalObj));
// 修改原始对象中的值
originalObj.name = 'Jane';
originalObj.hobbies.push('drawing');
originalObj.address.city = 'Los Angeles';
// 输出拷贝后的对象,验证是否为深拷贝
console.log(copiedObj);
</script>
</body>
10.异常处理
10.1 throw
异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行
总结:
-
throw 抛出异常信息,程序也会终止执行
-
throw 后面跟的是错误提示信息
-
Error 对象配合 throw 使用,能够设置更详细的错误信息
/*throw new Error()和throw error的主要区别在于,前者创建了一个新的错误对象,而后者只是抛出了一个已经存在的错误对象或者其他类型的值。使用throw new Error()可以提供更多的错误信息,例如错误的堆栈跟踪,这对于调试非常有帮助*/ <script> function counter(x, y) { if(!x || !y) { // throw '参数不能为空!'; throw new Error('参数不能为空!') } return x + y } counter() </script>
10.2 try...catch
-
try...catch用于捕获错误信息 -
将预估可能发生错误的代码写在
try代码段中 -
如果
try代码段中出现错误后,会执行catch代码段,并截获到错误信息<script> function foo() { try { // 查找 DOM 节点 const p = document.querySelector('.p') p.style.color = 'red' } catch (error) { // try 代码段中执行有错误时,会执行 catch 代码段 // 查看错误信息 console.log(error.message) // 终止代码继续执行 return } finally { alert('执行') } console.log('如果出现错误,我的语句不会执行') } foo() </script>
10.3 debugger
相当于断点调试
11.处理this
11.1 普通函数
普通函数的调用方式决定了 this 的值,即【谁调用 this 的值指向谁】
注: 普通函数没有明确调用者时 this 值为 window,严格模式下没有调用者时 this 的值为 undefined。
<script>
// 普通函数
function sayHi() {
console.log(this)
}
// 函数表达式
const sayHello = function () {
console.log(this)
}
// 函数的调用方式决定了 this 的值
sayHi() // window
window.sayHi()
// 普通对象
const user = {
name: '小明',
walk: function () {
console.log(this)
}
}
// 动态为 user 添加方法
user.sayHi = sayHi
uesr.sayHello = sayHello
// 函数调用方式,决定了 this 的值
user.sayHi()
user.sayHello()
</script>
11.2 箭头函数
箭头函数中的 this 与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在 this !箭头函数中访问的 this 不过是箭头函数所在作用域的 this 变量。
<script>
console.log(this) // 此处为 window
// 箭头函数
const sayHi = function() {
console.log(this) // 该箭头函数中的 this 为函数声明环境中 this 一致
}
// 普通对象
const user = {
name: '小明',
// 该箭头函数中的 this 为函数声明环境中 this 一致
walk: () => {
console.log(this)
},
sleep: function () {
let str = 'hello'
console.log(this)
let fn = () => {
console.log(str)
console.log(this) // 该箭头函数中的 this 与 sleep 中的 this 一致
}
// 调用箭头函数
fn();
}
}
// 动态添加方法
user.sayHi = sayHi
// 函数调用
user.sayHi()
user.sleep()
user.walk()
</script>
事件回调函数使用箭头函数时,this 为全局的 window,因此DOM事件回调函数不推荐使用箭头函数,如下代码所示:
<script>
// DOM 节点
const btn = document.querySelector('.btn')
// 箭头函数 此时 this 指向了 window
btn.addEventListener('click', () => {
console.log(this)
})
// 普通函数 此时 this 指向了 DOM 对象
btn.addEventListener('click', function () {
console.log(this)
})
</script>
基于原型的面向对象也不推荐采用箭头函数,如下代码所示:
<script>
function Person() {
}
// 原型对像上添加了箭头函数
Person.prototype.walk = () => {
console.log('人都要走路...')
console.log(this); // window
}
const p1 = new Person()
p1.walk()
</script>
11.3 改变this指向
11.3.1 call
使用 call 方法调用函数,同时指定函数中 this 的值
-
call方法能够在调用函数的同时指定this的值 -
使用
call方法调用函数时,第1个参数为this指定的值 -
call方法的其余参数会依次自动传入函数做为函数的参数
<script>
// 普通函数
function sayHi() {
console.log(this);
}
let user = {
name: '小明',
age: 18
}
let student = {
name: '小红',
age: 16
}
// 调用函数并指定 this 的值
sayHi.call(user); // this 值为 user
sayHi.call(student); // this 值为 student
// 求和函数
function counter(x, y) {
return x + y;
}
// 调用 counter 函数,并传入参数
let result = counter.call(null, 5, 10);
console.log(result);
</script>
11.3.2 apply
使用 call 方法调用函数,同时指定函数中 this 的值
-
apply方法能够在调用函数的同时指定this的值 -
使用
apply方法调用函数时,第1个参数为this指定的值 -
apply方法第2个参数为数组,数组的单元值依次自动传入函数做为函数的参数<script> // 普通函数 function sayHi() { console.log(this) } let user = { name: '小明', age: 18 } let student = { name: '小红', age: 16 } // 调用函数并指定 this 的值 sayHi.apply(user) // this 值为 user sayHi.apply(student) // this 值为 student // 求和函数 function counter(x, y) { return x + y } // 调用 counter 函数,并传入参数 let result = counter.apply(null, [5, 10]) console.log(result) </script>
11.3.3 bind
bind 方法并不会调用函数,而是创建一个指定了 this 值的新函数
注:bind 方法创建新的函数,与原函数的唯一的变化是改变了 this 的值。
<script>
// 普通函数
function sayHi() {
console.log(this)
}
let user = {
name: '小明',
age: 18
}
// 调用 bind 指定 this 的值
let sayHello = sayHi.bind(user);
// 调用使用 bind 创建的新函数
sayHello()
</script>
12.防抖节流
-
防抖(debounce) 所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间
function debounce(func, wait) { let timeout; return function () { let context = this; let args = arguments; clearTimeout(timeout); timeout = setTimeout(function () { func.apply(context, args); }, wait); }; } -
节流(throttle) 所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数
function throttle(func, delay) { let timer = null; return function () { if (!timer) { func.apply(this, arguments); timer = setTimeout(() => { timer = null; }, delay); } }; }

1457

被折叠的 条评论
为什么被折叠?



