JS
JS重修
JavaScript介绍
JavaScript是什么
-
是一种运行在浏览器上的编程语言,实现人机交互效果
-
作用
- 网页特效
- 表单验证
- 数据交互
- 服务端编程(node.js)
-
组成
- ECMAScript
- 规定了js基础语法核心知识
- WebApis
- DOM:页面文档对象模型
- 对页面元素进行移动,大小,添加删除等操作
- BOM:浏览器对象模型
- 页面弹窗,检测窗口宽度,存储数据到浏览器
- DOM:页面文档对象模型
- ECMAScript
-
案例
<style> .pink{ background-color: pink; outline: none; } </style> </head> <body> <button class="pink">按钮1</button> <button>按钮2</button> <button>按钮3</button> <button>按钮4</button> <script> let buts = document.querySelectorAll('button'); for(let i=0;i<buts.length;i++){ buts[i].addEventListener('click',function(){ document.querySelector('.pink').className=''; this.className='pink'; }) } </script> </body>
JavaScript书写位置
-
书写位置
-
行内(在vue中常用)
<button onclick="alert('123')"> 点我变成富豪 </button> -
内部
- 通常将js写在所有html的最后位置
-
外部
<script src="./js/xx.js"> 中间写的js被忽略 </script>
-
JavaScript的注释
- 单行注释:ctrl+/
- 多行注释:shift+alt+a
JavaScript的结束符
- 写js的时候就约定不写结束符分号
- 按照团队约定,要么都写,要么都不写
输入和输出语法
- 输出
- document.write('123')或者document.write('
你好
') - alert('123')
- console.log('123')
- document.write('123')或者document.write('
- 输入
- prompt('提示文字')
字面量
- 在计算机科学中,字面量(literal)是在计算机中描述 事/物
- 比如
- 我们的工资是1000,此时1000就是数字字面量
- '黑马程序员'字符串字面量
- []数组字面量,{}对象字面量等等
变量
变量是什么
- 变量不是数据本身,他们仅仅是一个用于存储数值的容器,可以理解为时一个个用来装东西的纸箱子
变量基本使用
-
let即关键字(let:允许,许可,让,要),是系统提供的专门用来定义变量的关键字
-
变量的赋值
- 使用let来定义变量,不使用var
<script> //变量的初始化 let a = 10 let b = 12, m = 22 //变量的更新 a = 11 </script>-
注意点
<script> //使用var是可以这样进行变量更新的,但是如果换成let就报错 var a = 10 var a = 11 </script>
变量的本质
- 变量的本质是内存中数据的引用
变量命名规则与规范
- js严格区分大小写
- 其他变量命名规范基本和java语言相同
变量关键字let和var的区别
- var声明的缺点
- 可以先使用,然后再声明,是不合理的
- 可以重复声明变量,不合理
- 变量提升,全局变量,没有块级作用域等
数组
-
声明
<script> let arr = [] </script> -
数组中,数据的编号被叫做索引或者下标
-
数组可以存储任意类型的数据
常量
- 使用const声明的变量称之为“常量”
- 当某个变量永远不会改变时候,就可以使用const来声明,而不是let
- 常量不允许重新赋值,声明的时候必须赋值(初始化)
- 不需要重新赋值的数据使用const来声明
数据类型
- 数据类型分类可以更加充分和高效的利用内存
- 也更加方便使用数据
js中分为基本数据类型和引用数据类型
-
基本数据类型
-
number
-
可以是整数,小数,负数等
-
NaN代表一个计算错误,他是一个不正确或者一个未定义的数学操作所得到的结果
-
NaN是粘性的,任何对NaN的操作都返回NaN
-
console.log(NaN+2)
-
-
-
string
- 通过单引号,双引号,反引号包裹的数据都叫做字符串,单引号和双引号没有本质区别,推荐使用单引号
- 注意事项
- 单引号和双引号可以互相嵌套(外双内单,或者外单内双)
- 必要时候可以使用\来进行转义,输出单引号或者双引号
- 字符串的拼接
- 使用+拼接:document.write('我的名字叫'+name)
- 使用模板字符串:document.write(
我叫${name})
-
boolean
-
undefined
- 声明一个变量不赋值就是undefined
- 在实际开发中,我们可以通过检测这个变量是不是undefined,就可以判断用户是否有数据传递过来
-
null
- 在js中null仅仅是一个代表‘无’,‘空’或者‘值未知’的特殊值
- null和undefined的区别
- null表示赋值了,但是内容为空
- undefined表示没有赋值
- null在实际开发中的使用场景
- 将来有个变量里面存放的是一个对象,但是对象还没创建好,可以先给个null
-
-
引用数据类型
- Object
- 函数
- 数组
-
检测数据类型
- 通过typeof关键字来检测数据类型
- 作为运算符:typeof x(常用的写法)
- 作为函数:typeof(x)
- 不管是作为运算符还是函数结果都是一样的,所以建议使用运算符的写法
- 通过typeof关键字来检测数据类型
-
js是弱数据类型,变量到底属于那种类型,只有赋值之后,我们才能确认
数据类型转换(隐式转换和显示转换)
为什么需要类型转换
- 使用表单,prompt获取过来的数据默认是字符串类型的,此时就不能直接简单的进行运算
隐式转换
-
系统内部自动将数据类型进行转换,这种转换称为隐式转换
-
规则
- +号两边只要有一个是字符串,都会把另外一个转换成字符串
- 除了+号以外的算数运算符,比如- * /等都会把数据转换为数字类型
-
缺点
- 转换类型不明确,靠经验才能总结
-
小技巧
- +号作为正号解析可以转换成数字型
- 任何数据和字符串相加结果都是字符串
显示转换
-
编写程序的时候多度依赖系统内部的隐式转换是不严谨的,因为隐式转换规律并不清晰,大多是靠经验总结的规律。所以为了避免因为隐式转换带来的问题,通常需要对数据进行显示转换
-
字符串转换为数字型
-
Number('123')(显示转换)
-
+'123'(隐式转换)
-
parseInt('12px'):只解析字符串中的数字结果为12(显示转换)
-
parseInt('12px')结果为12 parseInt('12.34px')结果为12 parseInt('a12.3px')结果为NaN
-
-
parseFloat('12.34px')(显示转换)
-
parseFloat('12.34px')结果12.34 parseFloat('12px')结果12 parseFloat('a12.9px')结果NaN
-
-
比较运算符
- =是赋值
- ==是判断值是否相等
- ===是判断值和数据类型是否相等
- 开发中强烈使用===
- NaN不等于任何值,包括自己

表达式和语句
-
表达式
- 表达式是可以被求值的代码,JavaScript引擎会将其计算出一个结果
-
语句
- 语句是一段可以被执行的代码
-
表达式和语句的区别
- 表达式可以被求值
- 语句不一定有值
分支语句
-
判断条件的注意事项
- true或者false
- 数字处了0其他都为true
- 字符串处了空字符串其他都为true
-
分支语句包含
- if分支语句
- 三元运算符
- switch语句
三元运算符数字补零案例
let str = prompt('请输入一个数')
alert(str<10 ? 0+str : str)
switch
- switch是用===来判断值是否相等的
- switch一般用于等值判断,不适合区间判断
循环
- 断点调试
- 在浏览器的sources一栏可以断点调试
数组
-
数组的声明
let arr1 = [] let arr2 = new Array('1','2') -
数组的操作(CRUD)
-
添加
-
push():将一个或者多个元素添加到数组的末尾,并返回新数组的长度(重点学习)
-
unshift():将一个或者多个元素添加到数组的开头,并返回新数组的长度
-
案例
-
数组的筛选
let arr1 = [10,23,4,5,624,66] let arr2 = [] for(let i=0; i<arr1.length; i++){ arr1[i]>20?arr2.push(arr1[i]):null }
-
-
-
删除
- pop():将数组的最后一个元素删除,并返回元素的值
- shift():将数组的开头一个元素删除,并返回元素的值
- splice(index,number):删除从index位置以后的number个元素,如果number没有指定,则删除index后面的所有元素
-
-
数组的其他方法
-
map()方法:map可以遍历数组处理数据,并且返回新的数组
const arr = ['red','green','blue'] const newarr = arr.map(function(ele,index){ //ele代表数组元素 console.log(ele) //index代表索引号 console.log(index) return ele+'颜色' })- map也称为映射,映射指两个元素集之间元素相互“对应”的关系
- map重点在于有返回值,forEach没有返回值
-
join(参数)方法:join()方法用于把数组中的所有元素转换一个字符串
const arr = ['red','green','blue'] //默认使用,作为分隔符 const str = arr.join('')- 数组元素是通过参数中指定的分隔符进行分隔的,空字符串(''),则表示所有元素之间都没有任何字符
-
...展开运算符
const arr = [1,2,3,4] console.log(...arr) //结果为1,2,3,4-
作用:求数组的最大值和最小值和合并数组
const arr1 = [1,2,3,4] console.log(Math.max(...arr1)) const arr2 = [1,23,1] const arrall = [...arr1,...arr2] console.log(arrall)
-
-
forEach方法:负责遍历数组,没有返回值
- 回调函数中的参数,数组元素是必写的,索引号可以自选
- forEach适合于遍历对象数组
const arr = [1, 2, 3, 4, 5, 6] arr.forEach(function (ele, index) { console.log(ele) }) -
filter()方法
- filter方法是创建一个新的数组,新数组是经过指定条件过滤出来的元素
- 逻辑处理只能是比较符,如果需要改变元素,需要通过map来
- 数组元素是必写的,索引号可以自选
const arr = [1, 2, 3, 4, 5, 6] const newarr = arr.filter(function (ele, index) { return ele>3 })
-
函数
函数声明
function 函数名(){
函数体
}
函数调用
函数名()
给函数参数设置默认值
function add(x=0,y=0){
console.log(x+y)
}
//如果调用函数时候没有赋值,采用默认值
add(1,2)
有返回值的函数
function test(str){
return str
}
函数返回值的细节
- return函数可以没有return,这种情况函数默认返回值为undefined
作用域
-
如果函数内部,变量没有声明,直接赋值,具有全局变量的特性,但是强烈不推荐
-
如
function add(){ num=10 } add() console.log(num)
变量的访问原则
- 先局部后全局
匿名函数之函数表达式
- 函数表达式
- 将匿名函数赋值给一个变量,并且通过变量名称进行调用
let fn = function(){
}
- 注意点
- 具名函数在任何位置都可以调用
- 函数表达式必须先写表达式后调用
匿名函数之立即执行函数
- 注意点
- 多个立即执行函数必须要加分号结束
- 作用
- 防止变量污染
//写法一
(function(){})();
//写法二
(function(){}())
//写法三,前面加上!或则算数运算符也可以,就可以将function(){}看做一个整体
!function(){}()
转换时间案例
- 计算时分秒的公式
- 小时:h=parseInt(总秒数/60/60%24)
- 分钟:m=parseInt(总秒数/60%60)
- 秒数:s=parseInt(总秒数%60)
let as = +prompt('请输入总秒数:')
function getTime(as) {
let s = parseInt(as % 60)
let m = parseInt(as / 60 % 60)
let h = parseInt(as / 60 / 60 % 24)
s = s < 10 ? '0' + s : s + ''
m = m < 10 ? '0' + m : m + ''
h = h < 10 ? '0' + h : h + ''
return `${h}时 ${m}分 ${s}秒`
}
let timeWhile = setInterval(function () {
console.log(getTime(as))
as--
as < 0 ? clearInterval(timeWhile) : null
}, 1000)
逻辑中断
- 利用逻辑中断实现函数中没有给定参数出现undefined的情况
function test(x, y) {
x = x || 0
y = y || 0
return x + y
}
转换为Boolean类型
显示转换
Boolean(undefined)
- '',0,undefined,null,false,NaN转换为布尔值后都是false,其余都为true
隐式转换
- 有字符串的加法都会自动转换为字符串拼接
- 减法只能用于数字,他会是空字符串转换为0
- null经过数字转换之后会变为0
- undefined经过数字转换后会变成NaN
- null==undefined 为true
- null===undefined 为false
对象
- 对象的声明
let 对象名 = {}
let 对象名 = new Object({uname:'clt'})
//使用自定义的构造函数创建对象
function Person(name, age, sex) {
this.name = name
this.age = age
this.sex = sex
}
const mother = new Person('d',21,'f')
-
属性
- 属性都是成对出现的,包括属性名和值,他们之间使用:分开
- 多个属性之间使用,分开
- 属性就是依附在对象上的变量
- 属性名可以使用"",''括起来,一般情况下省略,除非名称遇到特殊符号如空格,中横线等
-
对象的CRUD
let goods = {
name: '小米',
num: '1',
weight: 20,
address: '九江职业大学'
}
//查
console.log(goods.name)
console.log(goods['name'])
//增
goods.price = '2000元'
//改
goods.name = '华为'
//删
delete goods.price
- 对象的遍历
let goods = {
name: '小米',
num: '1',
weight: 20,
address: '九江职业大学',
get: function(receive){
console.log(`${receive}收`)
}
}
//使用for in遍历对象
//k是对象中的属性名,字符串类型
for(let k in goods){
console.log(goods[k])
}
-
注意点
- 一般不用for in遍历数组,因为k是数组中数组的下标,但是是字符串类型的
-
对象渲染数据案例
<table border="1">
<th colspan="4">
学生信息表
</th>
<tr>
<td>学号</td>
<td>姓名</td>
<td>性别</td>
<td>年龄</td>
</tr>
<script>
let strudents = [
{ id: 1, name: '小明', sex: '男', age: 21 },
{ id: 2, name: '小红', sex: '男', age: 21 },
{ id: 3, name: '小抗', sex: '男', age: 21 },
{ id: 4, name: '小即', sex: '男', age: 21 }
]
for (let i = 0; i < strudents.length; i++) {
document.write(`
<tr>
<td>${strudents[i].id}</td>
<td>${strudents[i].name}</td>
<td>${strudents[i].sex}</td>
<td>${strudents[i].age}</td>
</tr>
`)
}
</script>
</table>
内置对象
数学内置对象
-
Math对象包含的方法有
-
random():生成0-1之间的随机数,前闭后开
- 如何生成N-M之间的随机数,全是闭区间
Math.floor(Math.random()*(M-N+1)+N) -
ceil(number):向上取整
-
floor(number):向下取整
-
max(1,2,3):找出最大值
-
min():找出最小值
-
pow()幂运算
-
abs():绝对值
-
PI:圆周率
-
web API
DOM-获取元素
使用css选择器获取dom元素
const li = document.querySelectorAll('li')
const li = document.querySelector('ul li')
- 使用querySelectorAll得到的是一个伪数组
- 有长度有索引号
- 但是没有pop和push等数组方法
修改dom元素的内容
const li = document.querySelector('li')
li.innerText='123'
li.innerHtml='<strong>123</strong>'
- innerText属性是纯文本
- innerHTML属性是可以解析标签的
修改dom元素的样式
- 通过style对象来修改样式
const li = document.querySelector('li')
li.style.backgroundColor='red'
- 通过className属性修改样式
- className是使用新值换旧值,会覆盖掉元素上原来的类名
const li = document.querySelector('li')
li.className='box'
通过classList改变元素样式
const box = document.querySelector('.box')
//为元素添加类名,不覆盖原来的类名
box.classList.add('pink')
//移除指定的类名
box.classList.remove('pink')
//如果元素有指定的类名,则移除,如果没有,则添加
box.classList.toggle('pink')
操作表单中的属性
//获取input框中的值
input.value
//多选框中的设置选中状态
input.checked=true
//多选框中的设置不选中
button.disabled=true
自定义属性
- 在html5中推出了专门的data-自定义属性
- 在标签上一律以data-开头
- 在DOM对象上一律以dataset对象方式获取
<div class="box" data-name="陈浪涛" data-age="21"></div>
<script>
const box = document.querySelector('.box')
console.log(box.dataset.name);
</script>
定时函数-间歇函数
- setInterval(函数名,ms)
- setInterval()函数会返回一个数字
//写法一
setInterval(function(){},1000)
//写法二
function fn(){}
setInterval(fn,1000)
//写法三
function fn(){}
setInterval('fn()',1000)
轮播图案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="./iconfont.css">
<style>
@font-face {
font-family: 'iconfont';
src: url('iconfont.woff2?t=1684157672836') format('woff2'),
url('iconfont.woff?t=1684157672836') format('woff'),
url('iconfont.ttf?t=1684157672836') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 10px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.banner {
width: 400px;
height: 225px;
border: 1px solid black;
background-image: url(./images/01.jpg);
background-size: contain;
background-repeat: no-repeat;
}
ul {
margin: 0;
padding: 0;
margin: 0 auto;
margin-top: 15%;
transform: translateX(40%);
}
ul span {
cursor: pointer;
width: 10px;
height: 10px;
}
.next {
position: relative;
right: -160px;
top: 16px;
font-size: 25px;
display: block;
color: white;
font-weight: 900;
width: 10px;
height: 10px;
margin-top: 100px;
cursor: pointer;
transform: rotateZ(-90deg);
margin-left: 200px;
}
.last {
position: relative;
top: -5px;
left: 10px;
font-size: 25px;
display: block;
color: white;
font-weight: 900;
width: 10px;
height: 10px;
cursor: pointer;
margin-left: 10px;
transform: rotateZ(90deg);
}
</style>
</head>
<body>
<div class="banner">
<div class="nextandlast">
<span class="iconfont next"></span>
<span class="iconfont last"></span>
</div>
<ul>
<span class="iconfont" data-id="0"></span>
<span class="iconfont" data-id="1"></span>
<span class="iconfont" data-id="2"></span>
<span class="iconfont" data-id="3"></span>
</ul>
</div>
<script>
let data = [
{ url: 'url(./images/01.jpg)' },
{ url: 'url(./images/02.png)' },
{ url: 'url(./images/03.png)' },
{ url: 'url(./images/04.png)' },
]
const span = document.querySelectorAll('ul span')
const img = document.querySelector('.banner')
const nextandlast = document.querySelectorAll('.nextandlast span')
let a
let zuobiao
//悬浮选中图片
function button() {
for (let i = 0; i < span.length; i++) {
span[i].addEventListener('mouseenter', function (event) {
event.target.innerHTML = ''
for (let j = 0; j < span.length; j++) {
if (j !== +(event.target.dataset.id)) {
span[j].innerHTML = ''
}
}
img.style.backgroundImage = data[event.target.dataset.id].url
//清除浮动
clearInterval(a)
})
span[i].addEventListener('mouseleave', function (event) {
//开启自动播放
zuobiao = event.target.dataset.id
zidong(zuobiao)
})
}
}
button()
//自动播放
function zidong(index) {
a = setInterval(function () {
img.style.backgroundImage = data[index].url
span[index].innerHTML = ''
zuobiao = index
for (let j = 0; j < span.length; j++) {
if (j !== +(span[index].dataset.id)) {
span[j].innerHTML = ''
}
}
index++
if (index === data.length) {
index = 0
}
}, 1500)
}
zidong(0)
//设置nextandlast
function next() {
nextandlast[0].addEventListener('click', function () {
clearInterval(a)
if (zuobiao === 3) {
zuobiao = 0
img.style.backgroundImage = data[zuobiao].url
span[zuobiao].innerHTML = ''
for (let j = 0; j < span.length; j++) {
if (j !== zuobiao) {
span[j].innerHTML = ''
}
}
} else {
img.style.backgroundImage = data[++zuobiao].url
span[zuobiao].innerHTML = ''
for (let j = 0; j < span.length; j++) {
if (j !== zuobiao) {
span[j].innerHTML = ''
}
}
}
zidong(zuobiao)
})
}
next()
function last() {
nextandlast[1].addEventListener('click', function () {
clearInterval(a)
if (zuobiao === 0) {
zuobiao = 3
img.style.backgroundImage = data[zuobiao].url
span[zuobiao].innerHTML = ''
for (let j = 0; j < span.length; j++) {
if (j !== zuobiao) {
span[j].innerHTML = ''
}
}
} else {
img.style.backgroundImage = data[--zuobiao].url
span[zuobiao].innerHTML = ''
for (let j = 0; j < span.length; j++) {
if (j !== zuobiao) {
span[j].innerHTML = ''
}
}
}
zidong(zuobiao)
})
}
last()
</script>
</body>
</html>
DOM-事件基础
事件监听三要素
- 事件源:与函数绑定的那个对象
- 事件类型:click等
- 事件调用的函数:
绑定事件
const button = document.querySelector('button')
//方式一
button.addEventListener('click',function(){})
//方式二
button.onclick=function(){}
- addEventListener和on事件的区别
- on方式设置的事件会被覆盖掉,addEventListener方式不可以被覆盖,推荐使用
事件类型
- 鼠标事件
- click
- mouseenter
- mouseleave
- mouseover
- mousemove
- 焦点事件
- focus
- blur
- 键盘事件
- keydown
- keyup
- 文本事件
- input:当文本框有文本输入时候触发,一个字符触发一次
- change:只有当文本内容改变且失去焦点时候触发
使用js自动调用事件源上的已经绑定的事件
const button = document.querySelector('button')
button.addEventListener('click',function(){
console.log(123)
})
//使用js自动调用点击事件,不用手动点击调用
button.click()
使用trim和键盘事件制作评论区
const input = document.querySelector('[type=text]')
input.addEventListener('keyup', function (e) {
if (e.key === 'Enter') {
if (input.value.trim()) {
console.log(input.value)
}
input.value = ''
}
})
事件对象
- 就是事件回调函数的第一个参数,常常写为event,e,ev
- 部分常用的属性
- type
- 获取当前的事件类型
- clientX/clientY
- 获取光标相对于浏览器可见窗口左上角的位置
- offsetX/offsetY
- 获取光标相对于当前DOM元素左上角的位置
- key
- 用户按下的键盘键的值
- 现在不提倡使用keyCode
- type
环境对象
-
指的是函数内部特殊的变量this,它代表着当前函数运行时所处的环境
-
作用
- 弄清除this的指向,可以让代码更简洁
-
函数的调用方式不同,this指向的对象也不同
- 谁调用,this就指向谁是判断this指向的粗略规则
回调函数
- 把函数当做另外一个函数的参数传递,这个函数就叫回调函数
- 回调函数本质还是函数,只不过把他当成参数使用
- 使用匿名函数作为回调函数比较常见
tab栏案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="./iconfont.css">
<style>
.content {
width: 600px;
height: 400px;
border: 1px solid gray;
margin: 0 auto;
}
.nav {
width: 580px;
height: 30px;
border: 1px solid gray;
margin: 0 auto;
margin-top: 10px;
}
.nav ul {
margin: 0px;
padding: 0px;
display: flex;
justify-content: space-around;
}
.nav ul li {
height: 30px;
line-height: 30px;
list-style: none;
/* background-color: gray; */
cursor: pointer;
}
.active {
border-bottom: 2px solid red;
color: red;
}
.navcontent {
width: 580px;
height: 330px;
border: 1px solid gray;
margin: 0 auto;
margin-top: 20px;
}
.navcontent div {
width: 580px;
height: 330px;
background-color: gray;
position: absolute;
display: none;
}
.divactive{
display: block !important;
}
</style>
</head>
<body>
<div class="content">
<div class="nav">
<ul>
<li class="active">主页</li>
<li>美妆</li>
<li>汽车</li>
<li>生活</li>
</ul>
</div>
<div class="navcontent">
<div class="divactive">主页</div>
<div>美妆</div>
<div>汽车</div>
<div>生活</div>
</div>
</div>
<script>
const navs = document.querySelectorAll('.nav ul li')
const divs = document.querySelectorAll('.navcontent div')
//添加事件
for (let i = 0; i < navs.length; i++) {
navs[i].addEventListener('mouseenter', function () {
//排他思想
const active = document.querySelector('.nav ul .active')
active.classList.remove('active')
this.classList.add('active')
const divactive = document.querySelector('.navcontent .divactive')
divactive.classList.remove('divactive')
divs[i].classList.add('divactive')
})
}
</script>
</body>
</html>
全选案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.ck input:checked {}
</style>
</head>
<body>
全选<input class="ckall" type="checkbox"><br>
<div class="ck">
选项1<input type="checkbox"><br>
选项2<input type="checkbox"><br>
选项3<input type="checkbox">
</div>
<script>
const ckall = document.querySelector('.ckall')
const cks = document.querySelectorAll('.ck input')
ckall.addEventListener('click', function () {
for (let i = 0; i < cks.length; i++) {
cks[i].checked = this.checked
}
})
for (let i = 0; i < cks.length; i++) {
cks[i].addEventListener('click', function () {
ckall.checked = document.querySelectorAll('.ck input:checked').length === cks.length
})
}
</script>
</body>
</html>
DOM-事件进阶
事件流
- 事件流指的是事件完整执行过程中的流动路径
- 事件流中有两个阶段,分别为捕获阶段,冒泡阶段
- 捕获阶段是从父到子,冒泡阶段是从子到父
事件捕获
- 从DOM的根元素开始去执行对应的事件(从外到里)
- 说明:
- addEventListener第三个参数传入true代表开启事件捕获
- 如果传入false代表开启冒泡阶段,默认为false
- 如果使用on事件来添加事件,则只有冒泡阶段,没有捕获
事件冒泡
- 当一个元素的事件被触发的时候,同样的事件将会在该元素的所有祖先元素中依次被触发
- 当一个元素触发事件后,会依次向上调用所有父级元素的同名事件
- 阻止事件冒泡
- 使用事件对象的stopPropagation()方法
son.addEventListener('click',function(e){
alert('儿子')
e.stopPropagation()
})
解绑事件
-
on事件方式,直接使用null覆盖就可以实现事件的解绑
const button = document.querySelector('button') button.onclick=function(){} button.onclick = null -
addEventListener方法,使用removeEventListener方法解绑事件
let fn = function (e) { alert('儿子') son.removeEventListener('click',fn) e.stopPropagation() } son.addEventListener('click',fn)
鼠标经过事件的区别
mouseover和mouseout
- 该事件会有冒泡效果
mouseenter和mouseleave
- 该事件没有冒泡效果
两种注册事件的区别
- 传统的on注册
- 同一个对象,后面注册的事件会覆盖前面注册的事件(针对于同一类型的事件)
- 直接使用null覆盖就可以实现事件的解绑
- 只有冒泡阶段
- addEventListener
- 语法:addEventListener(事件类型,事件处理回调函数,是否使用捕获)
- 后面注册的事件不会覆盖前面注册的事件(针对于同一个类型的事件)
- 可以通过第三个参数去确定开启冒泡还是捕获,默认是冒泡
- 必须使用removeEventListener(事件类型,事件回调函数,获取捕获或者冒泡阶段)
- 匿名函数无法被解绑
事件委托
-
事件委托是利用事件流的特征解决一些开发需求的技巧
-
优点:减少事件注册次数,可以提高程序性能
-
原理:事件委托其实是利用事件冒泡的特点
- 给父元素注册事件,当我们触发子元素的时候,会冒泡到父元素身上,从而触发父元素的事件
-
实现:事件对象.target.tagName可以获得真正触发事件的元素
-
事件委托案例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> ul { /* width: 400px; */ padding: 0; margin: 0; border: 1px solid gray; } ul li { list-style: none; } </style> </head> <body> <ul> <li>我是1个li</li> <li>我是2个li</li> <li>我是3个li</li> <li>我是4个li</li> <li>我是5个li</li> <p>我是p标签</p> </ul> <script> const ul = document.querySelector('ul') ul.addEventListener('click', function (e) { if (e.target.tagName === 'LI') { e.target.style.color = 'red' } }) </script> </body> </html>
阻止事件的默认行为
- 使用事件对象的prementDefault()方法来阻止表单的默认提交行为
<body>
<div class="content">
<form action="http://www.baidu.com">
<input id="but" type="submit" value="点我阻止表单默认行为"/>
</form>
</div>
<script>
const form = document.querySelector('form')
form.addEventListener('submit',function(e){
e.preventDefault()
})
</script>
</body>
其他事件
页面加载事件
load事件
-
加载css(图片,外联的javascript,css)加载完毕后触发的事件
-
作用
- 有时候需要在页面资源全部加载完毕后做一些事情
- 老代码喜欢把script代码写在head标签中,这个时候是找不到dom元素的
-
给window对象添加load事件
const button = document.querySelector('button') window.addEventListener('load',function(){ //等待页面全部加载完毕后才开始回调js的代码,给button添加点击事件 button.addEventListener('click',function(){}) }) -
也可以针对于某个资源加载完毕后才开始执行相对应的代码
const img = document.querySelector('img') img.addEventListener('load',function(){ console.log('图片资源加载完毕') })
DOMContentLoaded事件
-
当初始的HTML文档被完全加载和解析完成之后,DOMContentLoaded事件被触发,而无需等待样式表,图像的资源的完全加载
-
给页面的DOM元素添加事件
document.addEventListener('DOMContentLoaded',function(){})
页面加载事件总结
- load事件
- 监听整个页面资源给window加
- DOMContentLoaded事件
- 给document加
- 无需等待样式表,图像等完全加载
页面滚动事件
scroll事件
-
滚动条在滚动的时候持续触发的事件
-
作用
- 很多页面需要检测用户把页面滚动到某个区域后做一些处理,比如固定导航栏,返回顶部
-
事件名scroll
window.addEventListener('scroll',function(){}) -
给window或者document添加scroll事件
-
监听某个元素的内部滚动直接给某个元素添加即可
-
使用document.documentElement可以获取html标签,document.body可以获取body
-
数字型的不带单位的数字
scrollTo(x,y)方法
-
该方法可以将页面滚动到指定坐标位置
window.scrollTo(0,1000)
页面尺寸事件
resize
-
当页面尺寸改变的时候就会触发该事件
window.addEventListener('resize',function(){})
获取元素的宽高
- clientWidth和clientHegiht属性
- 这两个属性是获取元素的可见部分的宽高(不包含边框,margin,滚动条等)
获取元素的宽高
- offsetWidth和offsetHeight
- 这两个属性获取的宽高包含元素自身设置的宽高,padding,border
- 需要注意的是获取的是可视宽高,如果盒子是不可见的,获取的结果是0
获取元素的位置
- offsetLeft和offsetTop
- 获取元素距离自己定位父级元素的左上距离
- 这两个属性是只可读属性
页面尺寸和位置总结
- offsetWidth和offsetHeight的宽度和高度包括内容+padding+border
- offsetTop和offsetLeft得到的位置是相对于带有定位的父级元素的位置
- 如果都没有则以文档左上角为准
导航栏固定案例
<body>
<div class="header">
</div>
<div class="nav">
</div>
<script>
const nav = document.querySelector('.nav')
const header = document.querySelector('.header')
window.addEventListener('scroll',function(){
const sc = document.documentElement.scrollTop
const n = nav.offsetTop
if(sc>=n){
header.style.top=0
}else{
header.style.top='-60px'
}
})
</script>
</body>
实现bibil的tab栏的底部的滑块移动案例
- 步骤
- 用到事件委托
- 点击链接得到当前元素的offsetLeft值
- 修改底部的线条盒子的transform值为点击链接的offsetLeft值
- 添加过渡效果即可
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.tab{
width: 400px;
height: 60px;
/* border: 1px solid gray; */
margin: 0 auto;
margin-top: 100px;
}
.tab ul li{
list-style: none;
float: left;
margin-right: 20px;
cursor: pointer;
}
.tab ul{
border: 1px solid white;
padding: 0;
margin: 0;
/* 在这里一定要对ul进行定位,让其成为li的offsetLeft的父级 */
position: relative;
}
.line{
width: 30px;
height: 2px;
background-color: pink;
margin-top: 20px;
transition: all 0.5s;
}
</style>
</head>
<body>
<div class="tab">
<ul>
<li>美食</li>
<li>音乐</li>
<li>文化</li>
<li>游戏</li>
<div class="line"></div>
</ul>
</div>
<script>
const line = document.querySelector('.line')
const lis = document.querySelectorAll('.tab ul li')
const tabul = document.querySelector('.tab ul')
tabul.addEventListener('click',function(e){
if(e.target.tagName==='LI'){
//获取被点击li的坐标
const offsetleft = e.target.offsetLeft
//改变线条的坐标
line.style.transform=`translateX(${offsetleft}px)`
}
})
</script>
</body>
</html>
获取位置
- element.getBoundingClientRect()
- 方法返回元素的大小及其相对于视口的位置
总结
- scrollLeft和scrollTop
- 作用:获取被卷去头部和左侧的长度,配合页面滚动来使用,可以读写
- clientWidth和clientHeight
- 作用:获取元素的宽度和高度,不包含padding,margin,滚动条,只读
- offsetWidth和offsetHeight
- 作用:获取元素的宽度和高度,和clientWidth相反
- offsetLeft和offsetTop
- 作用:获取元素距离自己具有定位的父元素的左和上的距离,只读。
M端事件
- 移动端也有自己独特的地方,比如触屏事件touch(也称触摸事件)
- touch对象代表一个触摸点,触摸点可能是手指,触摸笔,触屏事件可响应用户手指对屏幕的操作
- 常见的touch事件
- touchstart:手指触摸到DOM元素时候触发
- touchmove:手指在DOM元素上滑动时候触发
- touchend:手指从一个DOM元素上移开时候触发
插件
轮播图插件swiper
日期对象
日期对象的实例化
const data = new Data()
//获取指定的日期对象
const data = new Date('2022-2-2 8:00;0:00')
日期中常用的方法
- getFullYear():获取四位年份
- getMonth():获取月份(0-11)
- getDate():获取月份中的每一天,不同月份取值不同
- getdDay():获取星期 值为0-6
- getMinutes():获取分钟:值为0-59
- getHours():获取小时值为0-23
- getSeconds():获取秒,值为0-59
- toLoaclString()方法:得到的日期格式是yyyy/MM/dd HH:MM:ss
- toLocalDateString
- toLOcalTimeString
时间戳的使用
-
使用场景
- 计算倒计时效果
-
三种获取时间戳的方法
-
//使用getTime()方法 const date = new Date() date.getTime() // const date = +new Date() //now() const date = Date.now() -
需要注意的是前面两种方式可以得到指定时间的时间戳,但是第三种只能得到当前时间的
-
-
案例计算倒计时
<script>
const last = +new Date('2023-5-19 08:00:00')
const now = Date.now()
let cha = last-now
let s = parseInt(cha/1000%60)
let h = parseInt(cha/1000/60/60%60)
let m = parseInt(cha/1000/60%60)
console.log(`${h}时${m}分${s}秒`)
</script>
DOM-节点操作
- DOM树里的每一个内容都称为节点
- 节点类型
- 元素节点
- 所有的标签,比如body,div,html是根节点
- 属性节点
- 所有的属性 比如href
- 文本节点
- 所有的文本
- 其他
- 元素节点
查找节点
-
主要是通过节点之间的关系来查找
-
查找父节点
-
子元素.parentNode -
返回的是对象,如果没有找到,返回null
-
案例关闭
<body> <div class="parent"> <div class="child">x</div> </div> <script> const child = document.querySelector('.child') child.addEventListener('click',function(){ child.parentNode.style.display='none' }) </script> </body>
-
-
查找子节点
- childNodes
- 获取所有子节点,包括文本节点(空格,换行),注释节点等
- children(重点)
- 仅获得所有元素节点
- 返回的是一个伪数组
- childNodes
-
查找兄弟节点
- nextElementSibling查找下一个兄弟
- previousElementSibling查找上一个兄弟
-
新增节点
-
创建节点:使用document.createElement('元素名称')
-
追加节点:使用父元素.appendChild(新增节点)
const div = document.createElement('div') const body = document.querySelector('body')、 //方式一,追加到父元素的最后 body.appendChild(div) //方式二,追加到父元素下的子元素的前面 body.insertBefore(新建的元素,某个子元素)
-
-
克隆节点
- 元素.cloneNode(布尔值)
- 为true,代表克隆时会包含后代节点一起克隆
- 为false,代表克隆时不包含后代节点
- 默认为false
- 元素.cloneNode(布尔值)
-
删除节点
- 在js中,原生DOM操作中,要删除元素必须通过父元素删除
- 父元素.removeChild(要删除的元素)
- 注意点
- 如果不存在父子关系则删除不成功
- 删除节点和隐藏节点有区别的:隐藏节点还是存在的,但是删除,直接不存在
综合案例
- 学生信息表案例
- 思路:尽量减少对dom的操作,加大对数据的操作
- 声明一个空的数组
- 点击录入,生成对象添加到数组里面
- 根据数组数据渲染到页面上
- 点击删除,删除数组中对应的数据
- 再次根据数组对页面渲染数据
BOM-操作浏览器
- window对象是一个全局对象,也可以说是js中的顶级对象
- 像document,alert(),console.log()这些都是window的属性,基于BOM的属性和方法都是window的
- 所有通过var定义在全局作用域中的变量,函数都会变成window对象的属性和方法
- window对象下的属性和方法调用的时候可以省略window
定时器
-
是js中用来让代码延迟执行的函数
-
setTimeout(回调函数,ms) -
清除延迟函数
-
let timer = setTimeout(function(){},1000) clearTimeout(timer) -
经典面试题
-
console.log(11) setTimeout(function(){ console.log(22) },1000) console.log(33) //输出11 33 22 -
console.log(11) setTimeout(function(){ console.log(22) },0) console.log(33) //输出11 33 22 -
上面的两段代码有什么区别
-
JS执行机制
-
js是单线程的
-
为了解决单线程所带来的问题,利用多核cpu的计算能力,HTML5提出web worker标椎,允许js脚本创建多个线程,于是,js出现了同步和异步
-
同步任务
- 同步任务都是在主线程上执行的,形成一个执行栈
-
异步任务
- js的异步是通过回调函数实现的
- 一般而言,异步任务有以下三种类型
- 普通事件,如click,resize等
- 资源加载,如load,error等
- 定时器,包括setInterval,setTimeout
- 异步任务相关添加到任务队列中(消息队列)
-
执行机制
- 先执行执行栈中的同步任务
- 异步任务放入任务队列中
- 一旦执行栈中所有的同步任务执行完毕,系统就会按次序读取任务队列中的异步任务
![]()
-
由于主线程不断的重复获得任务,执行任务,在获得任务,在执行,所以这种机制被称为事件循环
location对象
-
location的数据类型是对象,他拆分并保存URL地址的各个组成部分
-
常见的属性
- href属性是获取完整的URL地址,对其赋值时自动跳转到指定地址
- search属性获取地址中携带的参数,也就是?后面的部分
- hash属性获取地址中的哈希值,符号#后面部分
-
常见的方法
- reload():表示刷新当前页面,参数为true表示强制刷新当前页面
- 强制刷新:表示不从本地浏览器获取数据,直接全部从服务器获取数据重新渲染页面
- reload():表示刷新当前页面,参数为true表示强制刷新当前页面
-
案例
<body> <a href="http://www.baidu.com"><span>5</span>秒后自动跳转页面</a> <script> const a = document.querySelector('a') let i = 5 let timer = setInterval(function () { i-- a.innerHTML = `<span>${i}</span>秒后自动跳转页面` if (i === 0) { clearInterval(timer) location.href = 'http://www.baidu.com' } }, 1000) </script> </body>
navigator对象
-
navigator的数据类型是对象,该对象记录了浏览器自身的相关信息
-
常用的属性
-
userAgent检测浏览器的版本和平台
-
通过检测当前运行的平台是否是移动端,如果是则切换移动端页面
-
!(function () { const userAgent = navigator.userAgent //验证是androd还是iphone const android = userAgent.match(/(Android);?[\sc\/]+([\d.]+)?/) const iphone = userAgent.match(/(iPhone\sOS)\s([\d_]+)/) if (android || iphone) { //如果是移动端则跳转到百度页面 location.href='http://www.baidu.com' } })()
-
-
-
history对象
- history的数据类型是对象,主要管理历史记录,该对象与浏览器地址栏的操作相对应,如前进,后退,历史记录等
- 常用的方法
- back():后退
- forward():前进
- go(参数):参数为1,前进1个页面,参数为-1,后退一个页面
正则表达式
介绍
- 正则表达式是用于匹配字符串中字符组合的模式。在js中,正则表达式也是对象
- 通常用来查找,替换那些符合正则表达式的文本
- 在js中的使用场景
- 用户名表单只能输入英文字母,数字或者下划线,昵称输入框中可以输入中文(匹配)
- 过滤掉页面内容中的一些敏感词(替换),或从字符串中获取我们想要的特定的部分(提取)等
语法
-
字面量定义//
-
test()方法
-
//待检测字符串 const str = '你好前端' //定义正则规则,待检测的字符串中是否有前端两个字 const reg = /前端/ console.log(reg.test(str))
-
-
exec()方法
- 在一个指定字符串中执行一个搜索匹配
- 如果匹配成功,返回一个数组,否则返回null
-
总结
- test方法用于判断是否有符合规则的字符串,返回的是布尔值
- exec方法用于检索符合规则的字符串,找到返回数组,否则null
元字符
-
是一些具有特殊含义的字符,可以极大提高了灵活性和强大的匹配功能
- 如规定用户只能输入英文26个英文字母,普通字符的话abc...
- 使用元字符为 [a-z]
-
边界符(表示位置,开头和结尾,必须使用什么开头,用什么结尾)
-
用来提示字符所处的位置
-
^:表示匹配行首的文本(以谁开始)
-
$:表示匹配行尾的文本(以谁结尾)
-
如果是^和$两个一起使用表示精确匹配
-
//表示以哈开头即可 true console.log(/^哈/.test('哈哈哈')) //表示以哈结尾即可 false console.log(/哈哈$/.test('哈')) //表示精确匹配 //true console.log(/^哈哈$/.test('哈哈')) //false console.log(/^哈哈$/.test('哈'))
-
-
-
量词(表示重复的次数)
-
用来设定某个模式出现的次数
-
*:重复零次或者更多次
-
+:重复一次或者更多次
-
?:重复零次或则一次
-
{n}:重复n次
-
{n,}:重复n次或者更多次
-
{n,m}:重复n到m次
-
//表示h出现[2,3]次 console.log(/^h{2,3}$/)
-
-
-
字符类(比如\d 表示0-9)
-
[]匹配字符集合
-
后面的字符串只要包含[a,b]中的任意一个字符,都返回true
-
console.log(/[abc]/.test('d'))//false console.log(/[abc]/.test('a'))//true console.log(/[abc]/.test('bc'))//true //精确匹配只能匹配其中的一个 console.log(/^[abc]$/.test('bc'))//false //精确匹配加了量词就可以匹配两个 console.log(/^[abc]{2}$/.test('bc'))//true -
配合连字符使用表示一个范围
-
[a-z]:表示a到z26个小写英文字母
-
[a-zA-Z]:表示26个小写和大写英文字母
-
[0-9]:表示0到9数字
-
如qq号的匹配:
-
const qq = /^[1-9][0-9]{4,}$/ //表示开头只能大于1,且位数大于5 //注意:{4,}表示[0-9]出现4次或则多次 const reg = /^[a-zA-Z0-9]$/ //表示匹配字符只能是a-z小写字母和A-Z大写字母,0-9数字中的一个 const reg = /^[a-zA-Z0-9]{2,}$/ //表示匹配字符只能是a-z小写字母和A-Z大写字母,0-9数字中的两个或则多个
-
-
-
配合^使用表示取反
-
如
-
const reg = /[^a-z]/ //表示匹配除了a-z小写字母以外的字符
-
-
-
配合.使用
- 表示匹配除了换行符之外的任何单个字符
-
案例
<style> .error { color: red; } .right { color: green; } </style> </head> <body> <input type="text" placeholder="要求10-12位的数字,字母,下划线组成"><span></span> <script> const input = document.querySelector('input') const span = document.querySelector('span') input.addEventListener('blur', function () { const reg = /^[a-zA-Z0-9-_]{10,12}$/ const flag = reg.test(this.value.trim()) if(flag){ span.innerText= '用户名正确' span.classList.add('right') }else{ span.innerText= '用户名格式错误' span.classList.add('error') } }) </script> </body>
-
-
预定义:指的是某些常见模式的简写方式
-
\d:相当于[0-9]
-
\D:相当于[^0-9]
-
\w:相当于[A-Za-z0-9_]
-
\W:相当于[^A-Za-z0-9_]
-
\s:相当于[\t\r\n\v\f],匹配空格(包括换行符,制表符,空格符等)
-
\S:相当于[^\t\r\n\v\f]
-
比如日期格式
const reg = /^[\d]{4}-[\d]{1,2}-[\d]{1,2}$/
-
-
修饰符
-
修饰符是用来约束正则执行的某些细节行为,如是否区分大小写,是否支持多行匹配等
-
const reg = /表达式/修饰符-
i:是单词ignore缩写,表示匹配时候是否区分大小写
-
g:是global的缩写,表示匹配所有满足表达式的结果
-
如
//将str中的前端替换为Java const reg = /前端/ig const str = '要认真学习前端啊' const newstr = str.replace(reg,'Java') console.log(newstr)
-
-
案例过滤敏感词
//就是用表达式做替换 <textarea name="" id="" cols="30" rows="10"></textarea> <button>发布</button> <div></div> const area = document.querySelector('textarea') const button = area.nextElementSibling const div = button.nextElementSibling // | 在正则表达式中表示或 const reg = /激情|基情/g button.addEventListener('click', function () { const newstr = area.value.replace(reg, '**') div.innerHTML = newstr }) -
正则表达式登录案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
@font-face {
font-family: 'iconfont';
src: url('./download(1)/font_4078231_5vyoc003ad4/iconfont.woff2?t=1684586774623') format('woff2'),
url('./download(1)/font_4078231_5vyoc003ad4/iconfont.woff?t=1684586774623') format('woff'),
url('./download(1)/font_4078231_5vyoc003ad4/iconfont.ttf?t=1684586774623') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 35px;
text-align: center;
line-height: 40px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: white;
}
.login {
width: 350px;
height: 400px;
background-color: black;
position: relative;
margin: 25vh auto;
}
.nav {
position: relative;
width: 350px;
height: 60px;
border-bottom: 1px solid white;
}
.nav a {
color: white;
display: inline-block;
height: 50px;
width: 80px;
line-height: 65px;
text-decoration: none;
/* border-bottom: 1px solid red; */
text-align: center;
}
.nav a:nth-child(1) {
margin-left: 40px;
}
.nav a:nth-child(1) {
margin-right: 90px;
}
.line {
width: 80px;
transform: translateX(40px);
border: 1px solid white;
transition: all .3s;
}
.content {
width: 348px;
height: 337px;
border: 1px solid white;
}
.l1,
.l2 {
width: 348px;
height: 337px;
border: 1px solid red;
}
.l2 {
background-color: black;
border: 0px solid;
display: none;
}
.l1>div {
width: 250px;
height: 40px;
margin: 0 auto;
margin-top: 30px;
/* border: 1px solid red; */
}
.l1>div:nth-child(1)>div {
width: 40px;
height: 38px;
display: inline-block;
float: left;
/* background-color: red; */
border: 1px solid white;
}
.l1>div:nth-child(2)>div {
width: 40px;
height: 38px;
display: inline-block;
float: left;
border: 1px solid white;
/* background-color: red; */
}
.l1 input {
width: 205px;
height: 38px;
margin: 0;
padding: 0;
border: 1px solid white;
outline: none;
background-color: black;
font-size: 16px;
color: white;
}
.check>span {
color: white;
font-size: 12px;
line-height: 38px;
margin-left: 5px;
display: inline-block;
height: 40px;
}
.check>input {
width: 15px;
float: left;
}
.submit>input {
width: 250px;
cursor: pointer;
}
.error {
font-size: 12px;
color: red;
}
.right {
font-size: 12px;
color: green;
}
</style>
</head>
<body>
<!--autocomplete表示是否关闭输入框的记忆功能-->
<form action="" autocomplete="off">
<div class="login">
<div class="nav">
<a href="javascript:">登录</a>
<a href="javascript:">扫码登录</a>
<div class="line"></div>
</div>
<div class="content">
<div class="l1">
<div>
<div>
<span class="iconfont"></span>
</div>
<input type="text" name="username" placeholder="请输入用户名">
<span>用户名不符合格式</span>
</div>
<div>
<div>
<span class="iconfont"></span>
</div>
<input type="text" name="password" placeholder="请输入密码">
<span>用户名不符合格式</span>
</div>
<div class="check">
<input type="checkbox">
<span>请勾选协议</span>
</div>
<div class="submit">
<input type="submit" value="登录">
</div>
</div>
<div class="l2" style="color: white;">
二维码
</div>
</div>
</div>
</form>
<script>
const nav = document.querySelector('.nav')
nav.addEventListener('click', function (e) {
if (e.target.tagName === 'A') {
const line = document.querySelector('.line')
const l1 = document.querySelector('.l1')
const l2 = document.querySelector('.l2')
line.style.transform = `translateX(${e.target.offsetLeft}px)`
if (e.target.innerText === '登录') {
l1.style.display = 'block'
} else {
l1.style.display = 'none'
}
if (e.target.innerText === '扫码登录') {
l2.style.display = 'block'
} else {
l2.style.display = 'none'
}
}
})
//验证数据
const username = document.querySelector('input[name=username]')
const password = document.querySelector('input[name=password]')
const check = document.querySelector('input[type=checkbox]')
const form = document.querySelector('form')
let usernameflag = false
let passwordflag = false
username.addEventListener('change', function () {
const reg = /^[\w]{6,8}$/
const flag = reg.test(this.value)
if (!flag) {
this.nextElementSibling.innerText = '用户名不符个规范'
this.nextElementSibling.classList.remove('right')
this.nextElementSibling.classList.add('error')
return
}
this.nextElementSibling.innerText = '验证通过'
this.nextElementSibling.classList.remove('error')
this.nextElementSibling.classList.add('right')
usernameflag = true
})
password.addEventListener('change', function () {
const reg = /^[\w]{6,8}$/
const flag = reg.test(this.value)
if (!flag) {
this.nextElementSibling.innerText = '密码不符个规范'
this.nextElementSibling.classList.remove('right')
this.nextElementSibling.classList.add('error')
return
}
this.nextElementSibling.innerText = '验证通过'
this.nextElementSibling.classList.remove('error')
this.nextElementSibling.classList.add('right')
passwordflag = true
})
form.addEventListener('submit',function(e){
if(!(usernameflag&&passwordflag&&check.checked)){
alert('请按格式填写信息')
e.preventDefault()
}
})
</script>
</body>
</html>
本地存储
本地存储介绍
- 数据存储在用户浏览器中
- 设置,读取方便,甚至页面刷新不丢失数据
- 容量较大,sessionStorage和localStorage约5M左右
本地存储分类
localStorage
-
作用:可以将数据永久存储在本地,除非手动删除,否则关闭页面也会存在
-
特性:
- 可以多窗口(同一个网站页面,不涉及跨域)共享(同一个浏览器的数据)
- 以键值对的形式存储使用
- 本地存储存储的是字符串类型
-
语法
-
//增,改 localStorage.setItem(key,value) //读 localStorage.getItem(key) //删 localStorage.removeItem(key) //全删 localStorage.clear()
-
sessionStorage
- 特性
- 生命周期为关闭浏览器窗口
- 在同一个窗口(页面)下可以共享数据
- 以键值对的形式存储
- 用法跟localStorage基本相同
存储复杂数据类型
- 因为本地存储只能存储字符串类型
- 所以在存储像对象这样的复杂数据类型时候要转换为JSON字符串存储
const person = {
name: '陈浪涛',
age: 21
}
localStorage.setItem('info',JSON.stringify(person))
console.log(JSON.parse(localStorage.getItem('info')))
- 综合案例实现数据的CRUD,持久化
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta content="width=device-width, initial-scale=1.0">
<title>综合案例</title>
<style>
table {
padding: 5px;
border-collapse: collapse;
margin: 0 auto;
margin-top: 10px;
}
form {
margin: 0 auto;
}
</style>
</head>
<body>
<form action="javascript:;">
姓名
<input type="text" name="name">
年龄
<input type="text" name="age">
地址
<input type="text" name="address">
性别
<select name="sex" id="">
<option value="男">男</option>
<option value="女">女</option>
</select>
<button>添加</button>
</form>
<table border="1">
<thead>
<th colspan="7" style="background-color: rgb(234, 231, 231);">学生信息表</th>
<tr>
<td style="text-align: center;background-color: rgb(234, 231, 231);">学号</td>
<td style="text-align: center;background-color: rgb(234, 231, 231);">姓名</td>
<td style="text-align: center;background-color: rgb(234, 231, 231);">年龄</td>
<td style="text-align: center;background-color: rgb(234, 231, 231);">地址</td>
<td style="text-align: center;background-color: rgb(234, 231, 231);">性别</td>
<td style="text-align: center;background-color: rgb(234, 231, 231);">录入时间</td>
<td style="text-align: center;background-color: rgb(234, 231, 231);">操作</td>
</tr>
</thead>
<tbody>
</tbody>
</table>
<script>
//数据对象数组
let arr = []
const form = document.querySelector('form')
//利用立即执行函数将数据恢复
!function () {
const localarr = JSON.parse(localStorage.getItem('arr'))
console.log(typeof localarr);
if (localarr !== null) {
arr = localarr
resolve()
}
}()
//获取表单数据并添加到数组中
function add() {
const name = form.children[0].value
const age = form.children[1].value
const address = form.children[2].value
const sex = form.children[3].value
const id = arr.length + 1
//验证数据
const info = document.querySelectorAll('[name]')
for (let i = 0; i < info.length; i++) {
if (info[i].value.trim() === '') {
return alert('数据不能为空')
}
}
//数据添加到数组中
arr.push({
id,
name,
age,
address,
sex
})
}
//事件委托给form提交事件
form.addEventListener('submit', function (e) {
add()
//重置数据
this.reset()
//渲染数据
resolve()
//利用本地存储持久化
long(arr)
})
//渲染数据
function resolve() {
const tbody = document.querySelector('tbody')
//清除数据
tbody.innerHTML = ''
//使用数组方法map和join重新渲染数据
const newarr = arr.map(function(ele,index){
return `
<tr>
<td>${ele.id}</td>
<td>${ele.name}</td>
<td>${ele.age}</td>
<td>${ele.address}</td>
<td>${ele.sex}</td>
<td>${new Date().toLocaleString()}</td>
<td><a href="javascript:">删除</a></td>
</tr>
`
})
tbody.innerHTML = newarr.join('')
}
//删除数据,事件委托
const table = document.querySelector('table')
table.addEventListener('click', function (e) {
if (e.target.tagName === 'A') {
let flag = confirm('你确定要删除嘛?')
if (flag) {
arr.splice(e.target.dataset.id, 1)
}
//重新持久化
long(arr)
//渲染数据
resolve()
}
})
//持久化数据
function long(arr) {
localStorage.setItem('arr', JSON.stringify(arr))
}
</script>
</body>
</html>
JavaScript进阶
作用域和作用域链
局部作用域
- 局部作用域分为函数作用域和块作用域
- 函数作用域:在函数内部生命的变量只能在函数内部被访问,外部无法直接访问
- 不同函数内部声明的变量无法相互访问
- 函数执行完毕后,函数内部的变量实际被清空了
- 块作用域:在javascript中使用{}包裹的代码称为代码块,代码块内部声明的变量外部无法被访问
- let和const声明的变量会产生块作用域,var不会
- 函数作用域:在函数内部生命的变量只能在函数内部被访问,外部无法直接访问
全局作用域
- 在script和.js文件的js变量就是全局作用域
- 为window对象动态添加的属性默认也是全局的,不推荐
- 函数中未使用任何关键字的变量为全局作用域,不推荐
- 要尽可能少的声明全局变量,防止全局变量被污染
作用域链
- 作用域链本质上是底层的变量查找机制
- 在函数执行的时候,会优先查找当前函数作用域中查找变量
- 如果当前作用域查不到则会依次逐级查找父级作用域直到全局作用域
JS垃圾回收机制
- 垃圾回收机制简称GC
- js中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收
内存的生命周期
- js环境中分配内存,一般有如下生命周期
- 内存分配
- 内存使用
- 内存回收
- 说明
- 全局变量一般不会回收(除非关闭页面)
- 内存泄漏
- 程序中分配的内存由于某种原因程序未释放或无法释放叫做内存泄漏
拓展
- 堆栈空间分配区别
- 栈:由操作系统自动分配释放函数的参数值,局部变量等,基本数据类型放在栈里面
- 堆:一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收,复杂数据类型放到堆里面
垃圾回收机制的算法
-
引用计数:IE采用引用计数算法,定义“内存不在使用”,就是看一个对象是否有指向他的引用,没有引用就回收对象
-
算法说明
- 跟踪记录被引用的次数
- 如果被引用了一次,那么就记录次数1,多次引用就会累加++
- 如果减少一个引用就减1 --
- 如果引用次数是0,则释放内存
-
缺点:嵌套引用不会回收
-
function(){ let o1 = {} let o2 = {} o1.a = o2 o2.a = o1 } -
因为他们的引用次数永远不会为0,相互引用大量存在会导致大量的内存泄漏
-
-
-
标记清除法:现代浏览器通用的大多是基于标记清除算法地某些改进算法
- 核心
- 标记清除算法将不在使用的对象定义为无法达到的对象
- 就是从根部(全局对象)出发定时扫描内存中的对象,凡是能从根部到达的对象,都是还需要使用的
- 那些无法由根部触及到的对象被标记为不再使用,将会被回收
- 核心
闭包
-
概念:一个函数对周围状态的引用捆绑在一起,内层函数中访问到外层函数的作用域
-
闭包=内层函数+外层函数的变量
-
如
function outer(){ const a = 1 function inner(){ console.log(a) } inner() } outer()
闭包作用
-
封装数据,提供操作,外部也可以使用函数内部的变量
//闭包的基本格式 function outer() { let a = 10 function inner() { return a } return inner } const fun = outer() fun() -
闭包应用
-
实现变量私有
-
如实现一个函数调用次数打印
//传统写法 //缺点是count是可以被外部篡改的 let count = 0 function test() { console.log(`函数被调用${count}次`) } //使用闭包实现变量私有化 function outer() { let count = 0 function test() { console.log(`函数被调用${count}次`) } return test }
-
-
变量提升
-
仅限于var声明的变量有变量提升
-
num = 10 console.log(num) var num //var变量声明的变量在程序执行时候,程序会将var声明的所有变量的声明提升到当前作用域的最前面 //只提升变量,不提升赋值 -
了解即可
函数进阶
函数提升
- 程序会将所有的函数声明提升到当前作用域的最前面
- 只提升函数声明,不提升函数调用
- 总结
- 函数表达式不存在函数提升
函数参数
动态参数
-
arguments是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参
function add() { let sum = 0 for (let i = 0; i < arguments.length; i++) { sum += arguments[i] } return `计算和为${sum}` } add(1,2,3,4)
剩余参数
-
参数使用...args来表是可以将参数封装到一个数组中
-
...是语法符号,且获取的是一个真数组
-
开发中提倡使用剩余参数,箭头函数中没有arguments
function add2(...args) { let sum = 0 for (let i = 0; i < args.length; i++) { sum += args[i] } return `计算和为${sum}` }
展开运算符
-
形式是...,可以将数组进行展开
-
不会修改原数组
const arr = [1,2,3,4] console.log(...arr) //结果为1,2,3,4
箭头函数
箭头函数的基本使用
-
引入箭头函数的目的是更简短的函数写法并且不绑定this
-
箭头函数更适合用于那些本来需要匿名函数的地方
-
语法
//匿名函数 let fn = function(){} //箭头函数替代 let fn = (x,y) => {} let fn = x => console.log(1) //省去return let fn = (x,y) => x+y //返回一个对象,省去匿名函数大括号后,返回对象需要使用括号抱起来 let fn = uname => ({uname:uname}) -
注意点
- 没有arguments可变长参数,但是有剩余参数
-
使用箭头函数案例
let getSum = (...args) => { let sum = 0 for(let i=0;i<args.length;i++){ sum += args[i] } return sum }
箭头函数的this指向
- 箭头函数不会创建自己的this,他只会从自己的作用域链的上一层沿用this
//this指向window
let fn = ()=>{console.log(this)}
//this指向window
let obj = {
uname: 'clt',
sayHi: ()=>{console.log(this)}
}
//this指向obj
let obj = {
uname: 'clt',
sayHi: function(){
let i = 0
let fn = ()=>{
console.log(this)
}
}
}
//this指向but对象
but.addEventListener('click',function(){console.log(this)})
//this指向window
but.addEventListener('click',()=>{console.log(this)})
解构赋值
数组解构
基本语法
-
数组解构是将数组的单元值快速批量赋值给一系列变量的简介语法
-
基本语法
const arr = [1,2,3] const [a,b,c] = arr console.log(a) console.log(b) console.log(c)-
应用:交换两个数据
let a = 1 let b = 2; //需要注意的是这里需要加上分号 [b,a]=[a,b] //同样如果上面有代码回影响下面的执行,需要加分号隔开 ;[1,2,3].map(function(ele,index){ console.log(ele) }) //立即执行函数也需要加上分号
-
使用
//变量多元素少
//a=1,b=2
const [a,b] = [1,2,3]
//变量少元素多
//a=1,b=2,c=3
const [a,b,c] = [1,2,3,4]
//使用剩余参数接收
//a=1,b-2,args为真数组
const [a,b,...args] = [1,2,3,4]
//防止出现undefined,为变量赋值默认值
const [a=0,b=0] = [1,2]
const [a=0,b=0] = []
//可以忽略
const [a,,c] = [1,2,3]
console.log(a)//1
console.log(c)//3
//多维数组结构
const [a,b,[c,d]] = [1,2,[3,4]]
对象解构
基本语法
-
对象解构是将对象的属性和方法快速批量赋值给一系列变量的简介语法
-
基本语法
- 对象属性的值会被赋值给与属性名相同的变量
const person = { uname: 'clt', sayHi: function(){ console.log('hello') } } const {uname,sayHi} = person console.log(uname) sayHi() //如果遇到外部也有相同的变量时候,需要改变解构的变量 let uname = '123' const person = {uname:'clt',password:'123'} const {uname:username,password} = person console.log(username) console.log(password)
使用
//对象的解构
const person = {uname:'clt',password:'123'}
const {uname,password} = person
//多级对象的解构,多级对象解构时候需要将名字写出来
const person = {uname:'clt',password:'123',family:{mother:'12',father:'34',sister:'123'}}
const {uname,password,family:{mother,father,sister}} = person
//对象数组
const personarr = [{uname:'clt',password:'123'}]
const [{uname,password}] = personarr
- 结构后台传过来的数据
const msg = {
code: 200,
msg: '数据获取成功',
data: [
{
id:1,
name:'clt',
password:'123'
}
]
}
//渲染数据data
function render({data}) {
console.log(data)
}
render(msg)
深入对象
构造函数
-
是一种特殊的函数,主要用来初始化对象
-
常规的{...}语法允许创建一个对象。此时就可以通过构造函数来快速创建多个类似的对象
-
语法
//构造函数 function Person(name, age, sex) { this.name = name this.age = age this.sex = sex } const mother = new Person('clt',21,'女') const father = new Person('clt1',21,'女') const son = new Person('clt1',21,'女') -
注意点
- 构造函数在技术上是常规的函数
- 约定:
- 命名以大写字母开头
- 只能由new关键字来执行
-
说明
- 使用new关键字调用函数的行为被称为实例化
- 构造函数内部无需写return,返回值为新创建的对象
- 构造函数内部的return返回的值无效
实例化的过程
- 创建新对象
- 构造函数this指向新对象
- 执行构造函数代码,修改this,添加新属性
- 返回新对象
实例成员和静态成员
-
实例成员:
-
通过构造函数创建的对象称为实例对象,其中的方法和属性称为实例成员
-
说明:
- 为构造函数传入参数,创建结构相同但值不同的对象
- 构造函数创建的实例对象彼此独立互不影响
-
例子
function Person (name,age,set){ //静态成员 this.name = name this.age = age this.set = set }
-
-
静态成员
-
构造函数的属性和方法被称为静态成员
-
说明
- 静态成员只能构造函数来访问
- 如Date.now(),Math.PI,Math.random()
- 静态方法中this指向构造函数
- 静态成员只能构造函数来访问
-
例子
function Person(){ //省略实例成员 } //添加静态成员 Person.name = 'clt' Person.sayHi = ()=>{ //this指向的是构造函数 console.log(this) }
-
-
总结
//实例变量 function Person(name,age){ this.name = name this.age = age } //静态变量 Person.sayHi = ()=>{ console.log(this); } Person.static = '静态变量' const person1 = new Person('clt',12) //输出undefined //实例对象无法访问静态变量 console.log(person1.static) //静态变量只能通过构造函数本身访问 console.log(Person.static)
内置的构造函数(包装类型)
- 在js中最主要的数据类型有6种
- String,Number,undefined,null,Boolean
- 对象等引用类型
- 其中基本数据类型也有专门的构造函数,称为包装类型
- Js中几乎所有的数据都可以基于构造函数创建
Object
-
常用的几种静态方法
-
keys(obj)方法:可以获取指定实例对象中的属性
-
const person = new Object({name:'clt',age:21}) console.log(Object.keys(person)) //遍历对象 for(let k in person){ console.log(person[k]) }
-
-
values(obj)方法:可以获取指定实例对象中的属性值
-
const person = new Object({name:'clt',age:21}) console.log(Object.values(person))
-
-
assign()方法:用于对象拷贝
-
常常用于给对象添加属性
const person = {name:'clt',age:21} Object.assign(person,{sex:'男'}) console.log(person) //结果为{name:'clt',age:21,sex:'男'}
-
-
Array
-
常用的实例方法
-
forEach()
-
filter()
-
map()
-
reduce():用于将数组中的数累加并返回结果,如果有初始值则会累加结果继续加上初始值并返回
const arr = [1, 2, 3] //pre代表上一次两个数累加的结果,cur代表待累加的第一个数 const total = arr.reduce(function (pre, cur) { console.log(pre) console.log(cur) return pre + cur }) console.log(total) //可以有初始值,初始值在数组累加之后的结果上加上初始值 const total1 = arr.reduce((pre,cur)=>{ return pre + cur },10) console.log(total1);-
执行流程说明
- 如果没有初始值,则上一次的值就是数组的第一个元素
- 每一次循环,把返回值给作为下一次循环的上一次值
- 如果有初始值,则初始值作为上一次的值
-
案例:如果是针对于对象数组中的属性累加,需要给定初始值为0
const salary = [ { name: 'clt1', salary: 1000 }, { name: 'clt2', salary: 2000 }, { name: 'clt3', salary: 3000 } ] const allsalary = salary.reduce((pre, next) => { return pre + next.salary }, 0) console.log(allsalary)
-
-
-
数组其他方法
-
join:数组元素拼接为字符串
-
使用Object.values()和join将对象的值转换为字符串
const person2 = { name: 'clt', age: '21' } console.log(Object.values(person2).join('*'));
-
-
find:查找符合条件的元素
-
使用find查找指定符合条件的元素,且只返回第一条符合条件的数据
//使用find查找指定的元素 const huawei = newarr.find(function(ele){ return ele.name === '小米' }) console.log(huawei) //使用filter返回所有的符合条件的元素 const xiaomi = newarr.filter(function(ele){ return ele.name === '小米' }) console.log(xiaomi)
-
-
every:检测数组所有元素是否都符合指定条件,是返回true,否则false
-
使用every检测数组中是否全是小米手机
const flag = newarr.every(function(ele,index,arr){ //ele是数组元素 console.log(ele) //index是索引 console.log(index) //arr是需要判断的数组 console.log(arr) //返回布尔值 return ele.name === '小米' }) console.log(flag)
-
-
some:检测数组中元素是否满足指定条件,如果有满足的元素返回true,否则返回false
-
concat:合并两个数组,返回生成的新数组
-
sort:对元素组进行排序
-
splice:删除或者替换源数组单元
-
reverse:反转数组
-
findIndex:查找指定元素的索引值
-
Array.from():将伪数组变成真数组,静态方法
const lis = document.querySelectorAll('ul li') const liss = Array.from(lis) console.log(liss)
-
String
-
常用的实例方法
-
length
-
split(分隔符):将字符串以指定分隔符转换为数组
-
substring(startindex,endindex):用于截取从指定索引到指定索引的字符串
-
startWith(待检测的字符串,startindex):检测指定字符串是否在指定索引号出现,返回布尔值
-
inclueds(待搜索的字符串,startindex):检测待搜索的字符串从指定位置开始是否被包含在原字符串中,返回布尔值
-
toUpperCase():
-
toLowerCase():
-
indexOf(字符):检测是否包含某个字符
-
endsWith('字符'):检测是否以某个字符结尾
-
replace()用于替换指定字符串中的字符串,支持正则匹配,返回新的字符串
-
const str = 'Hello,World' console.log(str.replace(/H/gi,'C'))
-
-
match():用于查找字符串,支持正则匹配,返回数组
-
const str = 'Hello,World' console.log(str.match(/He|Wo/ig))
-
-
Number
- 常见的实例方法
- toFixed(2):四色五入将数字保留两位小数
- toString():将数字转换为字符串
编程思想
面向过程编程
- 优点:性能比面向对象高,适合跟硬件联系很紧密的东西,例如单片机就采用面向过程编程
- 缺点:没有面向对象易维护,易复用,易扩展
面向对象编程
- 优点:易维护,易复用,易扩展,由于面向对象有封装,继承,多态,可以设计出低耦合的系统
- 缺点:性能比面向过程低
js中是通过构造函数来实现封装的
-
同样的将变量和函数组合到了一起并能通过this实现数据的共享,所不同的是借助构造函数创建出来的实例对象之间是彼此不影响的
-
总结:
- 构造函数体现了面向对象的封装性
- 构造函数实例创建的对象彼此独立,互不影响
-
例子
function Person(){
this.name = name
this.age = age
this.sing = function(){
console.log('唱歌')
}
}
const clt1 = new Person('clt1',21)
const clt2 = new Person('clt2',22)
//问题存在内存浪费问题
原型
- 可以解决构造函数创建对象浪费内存的问题
基本介绍
- 构造函数通过原型分配的函数是所有对象所共享的
- js规定,每一个构造函数都有一个prototype属性,指向另一个对象,所以也可以称为原型对象
- 这个对象可以挂载函数,对象实例化不会多次创建原型上的函数,节约内存
- 可以将不变的方法,直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法
- 构造函数和原型对象中的this都指向实例化的对象
利用原型对象给数组增加一个求和函数
Array.prototype.sum = function () {
return this.reduce((pre, cur) => pre + cur)
}
const arr = [1, 2, 3]
console.log(arr.sum());
constructor属性和应用
- 每一个构造函数的原型对象中都有一个constructor属性,指向构造函数
function Person (){}
Person.prototype.sing = function(){
console.log('唱歌')
}
console.log(Person.prototype.constructor);
- 如果对原型对象进行了从新赋值,而不是追加属性或者方法,需要手动定义constructor指向构造函数
function Person(){}
Person.prototype = {
constructor: Person,
sing: function(){
console.log(12)
},
dance: function(){
console.log(23)
}
}
- 作用
- 使原型对象知道自己的构造函数是谁
对象原型
-
对象都有一个属性_proto_指向构造函数的prototype原型对象
-
注意点
- __proto__是js非标准属性
- [[prototype]]和__proto__意义相同
- 对象原型里面也有一个constructor属性,指向创建该实例对象的构造函数
- 是只读,不可修改
![]()
原型继承
- 继承是面向对象编程的另一个特征,通过继承进一步提高代码封装的程度,js中大多是借助原型对象实现继承的特性
使用原型实现继承
const Person = {
eye: 2,
head: 1
}
//Man构造函数利用原型对象实现继承Person,但是如果有多个对象继承时候,想要添加自己的内容,每一个对象都会有,这个是问题
function Man(){}
Man.prototype = Person
Man.prototype.constructor = Man
//Person改为构造函数
function Person() {
eye:2,
head:1
}
function Man() {}
Man.prototype = new Person()
function Woman() {}
Woman.prototype = new Person()
Woman.prototype.sayHi = function() {}
//使用构造函数就可以解决Woman可以添加自己的方法而不在Man上出现
原型链
- 基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对象的链状结构关系称为原型链

-
原型链的引用
- 原型链的查找规则
- 当访问一个对象的属性或者方法的时候,首先查找这个对象自身有没有
- 如果没有就查找他的对象原型,也就是构造函数的原型对象
- 如果还没有就查找构造函数的原型对象中的对象原型,也就是Object的原型对象
- 如果还没有,就查找Object的原型对象的对象原型,也就是null
- __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线
- 可以使用instanceof运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上
Object.prototype.sayHi = function() { console.log(123) } function Person(){} const man = new Person() man.sayHi() - 原型链的查找规则
使用js面向对象制作模态框
<body>
<button class="delete">删除</button>
<button class="login">登录</button>
<script>
function Model(title = '', msg = '') {
this.model = document.createElement('div')
this.model.classList.add('model')
this.model.innerHTML=`
<div class="title">${title} <i>X</i></div>
<div>${msg}</div>
`
}
Model.prototype.open = function(){
//使用逻辑中断来实现不重复创建
document.querySelector('.model') && document.querySelector('.model').remove()
document.documentElement.appendChild(this.model)
//绑定关闭事件
this.model.querySelector('i').addEventListener('click',()=>{
this.close()
})
}
Model.prototype.close = function(){
this.model.remove()
}
const del = document.querySelector('.delete')
const login = document.querySelector('.login')
del.addEventListener('click',function(){
new Model('温馨提示','你没有删除权限').open()
})
login.addEventListener('click',function(){
new Model('温馨提示','你没有登录权限').open()
})
</script>
</body>
深浅拷贝
浅拷贝
- 使用展开运算符实现浅拷贝
const person = {
name: 'clt',
age: 21
}
const o = {...person}
o.age = 22
console.log(o)
console.log(person)
- 使用Object的assign方法
const person = {
name: 'clt',
age: 21
}
const o = {}
Object.assign(o,person)
- 问题
- 对于对象里面还有对象,浅拷贝不能将深层次的对象拷贝
深拷贝
实现深拷贝的方法
- 使用递归函数实现深拷贝
- 使用第三方库lodash/cloneDeep
- 通过JSON.stringify实现
使用递归来实现对象数组深拷贝
function get(obj) {
let o
if (typeof obj === 'object') {
o = {}
} else if (typeof obj === 'array') {
o = []
}
for (let k in obj) {
if (typeof obj[k] === 'object') {
o[k] = get(obj[k])
}else if (typeof obj[k] === 'array') {
o[k] = get(obj[k])
} else {
o[k] = obj[k]
}
}
//实现深拷贝
return o
}
- 问题,如果是内部的对象再引用内部的对象,可能会出现问题
js中的异常
抛异常
throw new Error('参数不能为空')
捕获异常
try{
}catch(err){
console.log(err.msg)
}finally{
}
使用debugger关键字
if(true){
//在调试的时候会从这里开始,不需要在浏览器中手动打断点
debugger
}
this指向详解
- 普通函数没有明确调用者时候值为window,严格模式下没有调用者时候this的值为undefined
'use strict'
//使用该字符串在script的首行,开启严格模式
对于普通函数
- 谁调用,就指向谁
- 严格模式下,指向undefined
对于箭头函数
-
本身没有this
-
向外查找最近的this,就指向它
-
注意点
- 原型对象,构造函数中的函数尽量不使用箭头函数
- 事件的回调函数尽量不使用箭头函数
- 字面量对象中的函数
改变this指向
-
使用call方法改变
-
const person = { name: '123', age:21 } function fn(){ console.log(this) } //call(this指向,其他参数) fn.call(person)
-
-
使用apply方法改变
-
const person = { name: '123', age:21 } function fn(x,y){ //其中x就是参数1,y就是参数2 console.log(x,y) console.log(this) } //apply(this指向,[参数1,参数2]) fn.call(person) -
和数组结合使用
const arr = [1,2,3,4] //以这种方法同样也能求出最大值 Math.max.apply(null,arr)
-
-
使用bind改变
-
const person = { name: 'clt', age:21 } function fn() { console.log(this) } //bind(this指向,args1,args2) const fun = fn.bind(person) fun() -
注意点
- bind只会改变this指向,并不会调用函数
- 返回值是经过改造的新函数,也就是this改变后的原函数
-
-
改变this指向实现按钮两秒后恢复
const but = document.querySelector('button') but.addEventListener('click',function(){ this.disabled = true setTimeout(function(){ this.disabled = false }.bind(this),2000) })
防抖
- 单位时间内,频繁触发事件,只会执行最后一次
实现方式
-
使用lodash库中的debounce实现即可
-
语法
let i = 0 function add(){ i++ } const but = document.querySelector('button') //语法bounce(回调函数,时间) but.addEventListenor('mouseover',_bounce(add,200)) -
使用setTimerout实现防抖函数
-
let debounch = function (call, time) { let timer return function () { if (timer) { clearTimeout(timer) } timer = setTimeout(function () { call() }, time) } }
-
节流
- 单位时间内,频繁触发一件事情,只会执行一次
- 使用lodash中的throttle方法或者自定义函数
核心思想
- 声明一个定时器变量
- 当事件触发时候,先判断是否有定时器变量是否为空,不为空则不开启定时器
- 如果没有开启则开启定时器
let debounch = function (call, time) {
let timer
return function () {
if (!timer) {
timer = setTimeout(function () {
call()
}, time)
}
}
}
防抖和节流的总结
防抖
- 概念
- 单位时间内,频繁触发一件事,只会触发最后一次
- 使用场景
- 搜索框搜索输入,手机号,邮箱验证输入检测等
节流
- 概念
- 单位时间内,频繁触发意见事情,只会触发一次
- 使用场景
- 高频事件:鼠标移动mousemove等
节流中和案例(实现记忆播放)
两个关于视频或者音频播放的事件
- ontimeupdate:事件在视频/音频当前的播放位置发生改变时候触发
- onloadeddata:事件在当前贞的数据加载完成且还没有足够的数据播放视频或者音频的下一贞时候触发
思路
- 在ontimeupdate事件触发的时候,每个1秒钟,就记录当前时间到本地存储
- 下一次打开页面时候,onloadeddata事件触发,就可以从本地存储取出时间,让视频从取出的时间播放,如果没有就默认为0s
- video.currentTime可以获取当前的时间
实现
<video src="./images/队长-哪里都是你.mp4" autoplay controls></video>
<script>
const video = document.querySelector('video')
video.onloadeddata = function() {
this.currentTime = localStorage.getItem('currentTime')
}
//这里需要使用节流,使用lodash中的throttle
video.ontimeupdate = function () {
localStorage.setItem('currentTime',this.currentTime)
}
</script>


浙公网安备 33010602011771号