JS

WEB API

DOM

DOM

文档对象模型:用来呈现以及与任意的HTML或XML文档交互的API

DOM树

将HTML中的内容以树状的形式直观的显示出来,直观的体现了标签与标签之间的关系

DOM对象

浏览器根据HTML标签生成的JS对象

标签的所有属性都可以在这个对象上找到

获取DOM对象

  1. 通过CSS选择器获取对应的标签

    document.querySelector获取符合标准的第一个元素

    1
    2
    document.querySelector('CSS选择器')
    // css选择器: div, #ID .Class

    document.querySelectorAll获取符合标准的所有元素

    1
    2
    document.querySelectorAll('CSS选择器')
    // css选择器: div, #ID .Class
  2. 其他方法

    document.getElementByID

    document.getElementByTagName

    document.getElementByClassName

操作元素

内容

innerText

只会显示纯文本,不会识别标签

innerHTML

可以识别html标签

属性

常用属性,直接获取元素然后修改集合

样式属性:修改样式时候一定要加上style

通过style属性操作css

对象.style.样式属性 =

单个属性直接写即可,如果属性有多个单词则需要进行小驼峰命名法

操作类名操作css

如果需要修改多种样式的话可以通过给特定元素附上属性类名来进行统一修改

const div = document.querySelector(‘div’)

div.className = “box”

calssName会覆盖原先的类名

通过classList控制类操作css

为了解决className会导致之前的类名被覆盖,所以引入classList来追加或删除类名

元素.classList.add(‘类名’)

元素.classList.remove(‘类名’)

切换一个类

元素.classList.toggle(‘类名’) 如果原先没有这个类名,则添加上,如果之前有这个类名则删除

元素.classList.contains(“类名”) 检测是否有这个类,有:true,没有False

表单属性

只能通过表单元素.value获取表单中的值,而不能通过innerHTML来获取表单中的内容

表单.value

表单.type = password text email

表单.checked = true or false 表单勾选

表单.disabled = true or false 按钮是否能点击

自定义属性

为了和自带属性区别,需要使用data-开头

获取自定义属性需要使用dataset获取属性集合,然后通过 .属性名来获取属性值

定时器-间歇函数

需要网页自动执行的代码,例如倒计时

开启定时器

返回值是一个定时器序号

1
setInterval(函数, 间隔时间) // 间隔时间以毫秒为单位

关闭定时器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let 变量名 = setInterval(函数, 间隔时间)
clearInterval(变量名)

<button disabled>我已经阅读用户协议(5)</button>
<script>
const but = document.querySelector('button')
let i = 5
let n = setInterval(function(){
i--
but.innerHTML = `我已经阅读用户协议(${i})`
if(i == 0){
but.disabled = false
but.innerHTML = "我同意该协议了"
clearInterval(n)
}
}, 1000)
</script>

setTimeout只执行一次,执行完自动关闭

1
setTimeout(回调函数, 等待的毫秒数)

清除延迟函数

1
2
let timer = setTimeout(回调函数, 等待的毫秒数)
clearTimeout(timer)

事件

事件监听

1
2
语法
元素对象.addEventListener("事件类型",要执行的函数)

三要素

  1. 事件源
  2. 事件类型:用什么方式触发,click mouseOver
  3. 被调用的函数

事件类型

鼠标事件

click

mouseenter

mouseleave

鼠标经过事件:

mouseover 和 mouseout:有冒泡效果

mouseenter 和 mouseleave:无冒泡效果

焦点事件:表单获得光标

focus: 获得焦点

input.addEventListener(“fcous”, function(){

})

blur: 失去焦点

键盘事件:键盘触发

Keydown : 键盘按下触发

Keyup: 键盘抬起触发

文本事件

input:用户输入事件

input中的change事件

change:只有当表单中的内容改变了才会触发

视频事件

ontimeupdate 事件在视频/音频 (audio/video)当前的播放位置发送改变时触发

onloadeddata 事件在当前帧的数据加载完成且还没有足够的数据播放视频/音频 (audio/video)的下一帧时触发

事件对象

第一个参数为事件对象,一般命名为 e event

1
元素.addEventListener("click", function(e){})

事件对象的属性:

type:获取当前事件的类型

clientX、clientY:获取光标相对于浏览器可见窗口左上角的位置

offsetX、offsetY:获取光标相对于当前DOM元素左上角的位置

key:用户按下键盘键的值

环境对象

函数内部特殊的变量this,代表当前函数运行时所处的环境

this表明调用该函数的对象,谁调用该函数,this就指向谁

回调函数

将函数作为参数对象传递的就是回调函数

事件流

事件流指的是事件完整执行过程中流动的路径

捕获

从大往小:从DOM的根元素开始执行对应的事件

冒泡

从小往大:当一个元素被触发的时候,同样的事件将会在元素的所有祖先元素的中依次触发,当一个元素被触发 的时候,会依次向上调用所有父级元素的同名事件

阻止冒泡

事件.stopPropagation()

解绑事件

L0 :on方法:事件.onclick=null

L2: 事件.addEventListener(“click”, fn) 解绑 事件.removeEventListener(“click”, fn) 匿名函数无法被解绑

鼠标经过事件:

mouseover 和 mouseout:有冒泡效果

mouseenter 和 mouseleave:无冒泡效果

事件委托

优点:减少注册次数,可以提高程序性能

原理:事件委托利用事件冒泡的特点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<p>
我不需要变色
</p>
</ul>
<script>
const ul = document.querySelector("ul")
// 可以通过给父元素绑定事件,当点击子元素的时候,通过冒泡到父元素位置,然后通过事件event中的target获取触发事件的子元素,并进行修改属性
ul.addEventListener("click", function(e){
// 只有当我们标签为LI的时候才会变红
if(e.target.tagName === "LI"){
e.target.styel.color = 'red'
}
})
</script>

阻止默认行为

  1. preventDefault()
1
2
3
4
5
6
7
8
<a href="www.baidu.com">百度一下</a>
<script>
const a = document.querySelector("a")
a.addEventListener("click", function(e){
// 阻止点击a链接跳转
e.preventDefault()
})
</script>

其他事件

  1. 页面加载

    作用:等待页面全部加载完毕之后在进行js渲染,防止js写在html上方,js找不到指定元素

    window.addEventListener(‘load’, function(){}) 等待页面所有的资源全部加载完毕之后在进行函数中的代码

    1
    2
    3
    window.addEventListener("load", function(){
    // 等待页面加载完毕之后在进行一下js代码
    })

    DOMContentLoaded 等待文档中的HTML标签加载完毕就可以执行下列代码,不用等待页面全部元素加载完之后在进行js渲染

    1
    2
    3
    document.addEventListener("DOMContentLoaded", function(){
    // 需要执行的操作
    })
  2. 页面滚动

    事件名:scroll

    可以配合scrollLeft和scrollTop:计算被卷去的大小(像素),滑动的距离

    1
    2
    3
    4
    const div = document.querySelector("div")
    div.addEventListener("scroll", function(){
    div.scrollTop
    })

    获取整个页面滚动的距离

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    window.addEventListener("scroll", function(){
    // 获取页面的HTML滚动的距离,
    // 获取HTML标签:document.documentElement
    const n = document.documentElement.scrollTop
    if(n>100){
    //如果html向上滚动超过100px则进行以下写法
    }else{
    // 如果没有超过,则进行下面写法
    }
    })

    将页面打开的时候就在800px处打开

    1
    document.documentElement.stroll = 800 // 不带单位
  3. 页面尺寸

    clientWidth 和 clientHeight

    获取可见部分的宽高,包含padding不包含border

  4. 元素尺寸和位置

    获取宽高:

    offsetWidth和offsetHeight,包含padding和border

    获取位置:

    获取当前元素距离自己定位父级元素的左上距离

    offsetLeft和offsetTop

    获取相对于当前窗口的位置

    ntRect(), 返回元素大小及其相对于视口的位置

  5. 属性选择器

    1
    2
    3
    4
    5
    6
    7
    <input type = "text">
    <input type = "password">

    <!--只给第一个input颜色变为红色-->
    input[type = text] {
    color:red;
    }

日期对象的使用

实例化

1
2
3
4
5
// 获取当前时间
const date = new Date()

// 获取指定时间
const date1 = new Date("2024-1-3 10:45:00")

方法

方法 作用 说明
getFullYear() 获得年份 获取四位数年份
getMonth() 获取月份 取值为0-11
getDate() 获取月份中的每一天 不同月份取值不同
getDay() 获取星期 取值为0-6
getHours() 获取小时 取值为0-23
getMinutes() 获取分钟 取值为0-59
getSeconds() 获取秒 取值为0-59
1
2
3
4
5
6
7
8
9
10
function getMyDate(){
const date = new Date()
let h = date.getHours()
let m = date.getMinutes()
let s = date.getSeconds()
h = h > 10 ? '0'+h : h
m = m > 10 ? '0'+m : m
s = s > 10 ? '0'+s : s
return date.getFullYear() + date.getMonth() + date.getDate()+ h + m + s
}

时间戳

是指1970年01月01日00时00分00秒起止到现在的毫秒数

获取方法:

getTime(), 必须实例化date对象

1
2
const date = new Date()
date.getTime()

简写 +new Date()

1
console.log(+new Date())

使用Date.now()

1
console.log(Date.now())

节点操作

DOM节点

元素节点:div p 等标签

属性节点:比如class属性

文本节点:比如标签里面的文字

查找节点

1父节点

1
element.parentNode 

2子节点

element.children // 常用

仅获得所有元素节点

返回的是一个伪数组

element.childNode

获取所有子节点,包括文本节点,注释节点等

3兄弟节点

下一个兄弟节点:nextElementSibling属性

上一个兄弟节点:previousElementSibling属性

增加节点

  1. 创建节点

    创造一个新的网页元素,添加到网页内,一般先创建节点,再添加节点

    创建方法

    1
    document.creatElement('标签名')
  2. 追加节点

    插入到父元素最后一个子元素

    1
    父元素.appendChild(要插入元素)

    插入到父元素中某个子元素的前面

    1
    2
    父元素.insertBefore(要插入的元素, 在那个元素前面)
    ul.insertBefore(li, ul.children[0]) // 插入到第一元素之前
  3. 复制节点

    1
    元素.cloneNode(布尔值)

    true:代表复制时会包含后代节点一起复制

    false:代表不复制后代节点

    默认false

  4. 删除节点

    1
    父元素.removeChild(要删除元素)

M端事件

M端:移动端

触屏事件:touch(触摸事件)

触屏touch事件 说明
touchstart 手指触摸到一个dom元素的时候触发
touchmove 手指在dom元素上滑动的时候触发
touchend 手指从dom元素上移开的时候触发

swiper插件

官网:http://www.swiper.com.cn/

在线演示 https://www.swiper.com.cn/demo/index.html

基本使用流程https://www.swiper.com.cn/usage/index.html

查看APi文档,去配置自己的插件https://www.swiper.com.cn/api/index.html

Window对象

BOM

是浏览器对象模型

image-20240106091420434

window是全局对象,是js顶级对象

所有通过var定义的全局变量函数都会成为window对象的属性和方法

window对象下的属性和方法调用的时候可以省略window

定时器-延迟函数

setTimeout只执行一次,执行完自动关闭

1
setTimeout(回调函数, 等待的毫秒数)

清除延迟函数

1
2
let timer = setTimeout(回调函数, 等待的毫秒数)
clearTimeout(timer)

JS执行机制

同步任务

都在主线程上执行,形成一个执行栈

异步任务

需要消耗时间的任务

通过回调函数来实现

  1. 普通事件:click, resize
  2. 资源加载:load,error等
  3. 定时器:setInterval,setTimeout等

异步任务将添加到任务队列

JS执行机制

先顺序执行同步任务,遇到异步的任务的之后将其放到任务队列中,当同步任务执行完毕之后,在进行任务队列中的任务进行执行

由于主线程不断重复的获取任务,执行任务,在获取任务,在执行任务,这种机制被称为事件循环(event loop)

location对象

拆分并保存了URL地址的各个部分

常用的属性和方法:

href属性获取完整的URL地址,用于地址的转换

1
location.href = "http://www.baidu.com"

search获取地址中携带的参数,符号?后面的部分

1
location.search

hash获取地址中#后面的值

1
location.hash

reload刷新页面

1
2
location.reload() // f5 刷新
location.reload(true) // ctrl + f5 强制刷新

该对象记录下了浏览器自身的相关信息

1
2
3
4
5
6
7
8
9
10
11
// 检测浏览器信息
!(function() {
const userAgent = navigator.userAgent
// 验证是否为Android和iPhone
const android = userAgent.match(/(Android);?[\s\/]+([\d.]+)?/)
const iphone = userAgent.match(/(iPhone\sOS)\s([\d_]+)/)
// 如果是Android或者iPhone,则跳转到移动站点
if(android || iphone){
location.href="http://m.itcast.cn"
}
})();

history对象

history对象和方法 作用
back() 后退
forward() 前进
go(参数) 前进或后退
1表示前进一个页面
-1表示后退一个页面

本地存储

只能存储字符串类型

localStorage

1
2
3
4
5
6
7
// 存数据
localStorage.setItem(key,value)
// 取数据
localStorage.getItem(key)
// 删除数据
localStorage.removeItem(key)
localStorage.setltem(key,value)

存储复杂数据类型

需要将复杂数据类型转换成JSON字符串存储到本地

JSON对象属性和值都有引号,且是双引号

1
JSON.stringify(复杂数据类型)

将JSON字符串转换为对象

1
JSON.parse(JSON字符串)

map() 和 join()方法

map() 可以在不改变原数组的基础上制造一个与原数组一一对应的新数组,

1
2
3
4
const arr = ['red', 'blue', 'green']
const newArr = arr.map(function(ele, index){
return ele+'颜色'
})// newArr = ['red颜色', 'blue颜色', 'green颜色']

**join()**可以将数组转换成字符串

1
2
3
const arr = ['red', 'blue', 'green']
// join括号中的内容可以是分隔符,默认是,
console.log(arr.join()) // red.blue.green

正则表达式

语法

1
const 变量名 = /表达式/ 

test返回true或者false

1
2
3
4
5
变量.test("被检测的字符串")

// 例如
const re = /前端/
re.test("被检测的字符串前端")

exec返回数组

1
2
3
4
5
变量.exec("被检测的字符串")

// 例如
const re = /前端/
re.exec("被检测的字符串前端")

元字符

元字符即特殊字符,拥有强大的匹配能力的字符

例如 只能输入英文字母

可以使用 [a-z] [A-Z]

边界符

表示位置,必须以什么开头以什么结尾

^m: 表示以m为开头的字符

m$: 表示以m为结尾的字符

^m$:精确匹配,表示字符中有且只能有一个m

量词

表示字符串的长度

量词 说明
* 重复零次或更多次
+ 重复一次或更多次
? 重复零次或一次
{n} 重复n次
{n,} 重复n次或更多次
{n,m} 重复n到m次
1
2
3
4
const re = /^哈*$/
const re = /^哈+$/
const re = /^哈?$/
const re = /^哈{6}$/

字符类

  1. []匹配字符集合

    表示字符串中只要出现[]中的任意一个字符即可

    1
    const re = /[abc]/

    当时用精确匹配的时候,则[]中只能出现一个,出现多个则是错误

    1
    2
    3
    4
    5
    6
    7
    const re = /^[abc]$/ 
    re.test('a') // true
    re.test('a') // true
    re.test('a') // true
    re.test('ab') // false
    const re1 = /^[abc]{2}$/
    re1.test('ab') // true

    在[]中使用-表示一个范围

    1
    2
    3
    4
    5
    const re = /^$[a-z]{6}/
    // 只能输入英文字母和数字
    const re = /^$[a-zA-Z0-9]{6}/
    // 只能输入大于10000的数字
    const re = /^[1-9][0-9]{4}$/
  2. 在[]里面的^表示取反

    1
    2
    // 表示需要输入除了a-z之外的所有字符
    const re = /[^a-z]/
  3. 符号 . 表示匹配除了换行符之外的所有字符

  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]

修饰符

语法:/表达式/修饰符

修饰符:

  1. i :ignore,正则匹配不区分大小写
  2. g:global,匹配所有满足正则表达式的结果

替换:replace

字符串.replace(/正则表达式/, ‘替换文本’)

1
tx.value.replace(/aa|dd/, 'aa')

放大镜效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81

// 获取small middle large
const small = document.querySelector(".small")
const middle = document.querySelector(".middle")
const large = document.querySelector(".large")


// 小盒子部分
small.addEventListener('mouseover', function(e){
if(e.target.tagName === "IMG"){
// 删除上一个的active
small.querySelector(".small .active").classList.remove('active')
// 给当前的标签添加上active
e.target.parentNode.classList.add('active')
middle.firstElementChild.src = e.target.src
large.style.backgroundImage = `url(${e.target.src})`
}
})


//中盒子部分
let timeId

function enter(){
clearTimeout(timeId)
large.style.display = 'block'
};
function leave(){
timeId = setTimeout(function(){
large.style.display = 'none'
}, 200)
};

middle.addEventListener("mouseenter", enter)
middle.addEventListener("mouseleave", leave)


// 大盒子部分
large.addEventListener("mouseenter", enter)
large.addEventListener("mouseleave", leave)


// 鼠标移动部分
const layer = document.querySelector(".layer")
middle.addEventListener("mouseenter", function(){
layer.style.display = 'block'
})
middle.addEventListener("mouseleave", function(){
layer.style.display = 'none'
})
// 获取鼠标在盒子内部的距离
middle.addEventListener("mousemove", function(e){

let x = 0, y = 0
const middleParm = middle.getBoundingClientRect()
x = e.pageX - middleParm.left
y = e.pageY - middleParm.top - document.documentElement.scrollTop

let mx = 0, my = 0
if (x < 100){
mx = 0
}else if(x > 100 && x < 300){
mx = x-100
}else{
mx = 200
}

if (y < 100){
my = 0
}else if(y > 100 && y < 300){
my = y-100
}else{
my = 200
}

layer.style.left = mx + 'px'
layer.style.top = my + 'px'

large.style.backgroundPositionX = -mx*2 + 'px'
large.style.backgroundPositionY = -my*2 + 'px'
})

JS进阶

作用域

规定的变量能被访问的范围,如果出了这个范围就无法被访问

局部作用域

函数作用域

  1. 函数生成的变量只能让函数内部来访问,外部无法直接访问
  2. 函数的参数也是函数内部的局部变量
  3. 不同函数生命的变量无法互相访问
  4. 函数执行完毕之后,函数内部的变量实际被清空了

块作用域

  1. 只要使用{}包裹的代码称为代码块,代码块内部声明的变量外部有可能无法访问
  2. let和const声明的块作用域函数无法被外部访问,var声明的块作用域函数可以被外部访问

全局作用域

< script >和.js文件的最外层就是所谓的全局作用域,任何其他的作用域都可以被访问

作用域链

本质就是变量查找机制

当函数执行的时候会优先查找当前函数作用域的变量

如果当前作用域差找不到,则会逐级查找父级作用域,直到全集作用域

子作用域可以访问父作用域,但是父作用域无法访问子作用域

垃圾回收机制

生命周期

  1. 内存分配:当我们使用声明变量,函数,对象时,系统会自动分配内存
  2. 内存使用:读写内存
  3. 内存回收:使用完毕,由垃圾回收器自动回收不再使用的内存

全局变量不会回收(关闭页面回收)

局部变量不用会自动回收

**内存泄漏:**程序中分配的内存由于某种原因未释放或者无法释放

算法说明

堆:由操作系统自动分配,函数的参数值,局部变量,基本数据类型

栈:一般由程序自动分配释放,若程序员不释放,则有垃圾回收机制回收,复杂数据类型放到堆里面

引用计数法:

  1. 跟踪记录被引用的次数
  2. 如果被引用了一次,那么就记录次数1,多次引用会累加++
  3. 如果减少一个引用就减1 –
  4. 如果引用次数是0,则释放内存

**标记清除法:**找不到就删除

  1. 标记清除算法将“不再使用的对象”定义为“无法达到的对象”。
  2. 就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。凡是能从根部到达的对象,都是还需要使用的
  3. 那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。

闭包

概念:一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域

简单理解:闭包=内层函数外层函数的变量,里外层关系

只有通过函数才能访问某一变量,无法直接访问

1
2
3
4
5
6
7
8
function count(){
let i = 0
function fn(){
i++
console.log(i)
}
return fn
}

闭包可能会引起内存泄漏

变量提升

允许var定义的变量在声明前访问,只提升变量声明不提升变量赋值

  1. 变量在未声明即被访问时会报语法错误
  2. 变量在var声明之前即被访问,变量的值为undefinec
  3. let/const声明的变量不存在变量提升
  4. 变量提升出现在相同作用域当中
  5. 实际开发中推荐先声明再访问变量

函数进阶

函数提升

函数在声明前就可以被调用

函数参数

动态参数:arguments是函数内部内置的伪数组变量,包含了调用函数时传入的所有实参

  1. arguments是一个伪数组,只存在于函数中

  2. arguments的作用是动态获取函数的实参

  3. 可以通过for循环依次得到传递过来的实参

1
2
3
4
5
6
7
8
9
function sum(){
let sum = 0
for(let i = 0; i < arguments.length; i++){
sum += arguments[i]
}
consol.log(sum)
}
sum(1,2,3,4)
sum(2,2,3,4)

剩余参数

…参数名

获取多余的实参

真数组

1
2
3
4
5
6
7
8
9
function sum(a, b, ...arr){
let sum = 0
for(let i = 0; i < arr.length; i++){
sum += arr[i]
}
consol.log(sum)
}
sum(1,2,3,4)
sum(2,2,3,4)

展开运算符

使用…可以将一个数组进行展开,且不会改变原数组

求数组最大值

1
2
arr = [1,2,3]
Math.max(...arr)

合并数组

1
2
3
arr1 = [1,2,3]
arr2 = [1,2,3]
arr3 = [...arr1, ...arr]

箭头函数

目的:引入箭头函数的目的是更简短的函数写法并且不绑定this,箭头函数的语法比函数表达式更简洁

使用场景:箭头函数更适用于那些本来需要匿名函数的地方

基本语法

  1. 基本写法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 原本
    const fn = function() {
    console.log(123)
    }
    // 箭头函数
    const fn = () => {
    console.log(123)
    }
    // 参数箭头
    const fn = (x, y) => {
    console.log(x, y)
    }
    fn(1, 2)
  2. 当只有一个函数的时候可以省略小括号

    1
    2
    3
    4
    5
    // 当只有一个参数从时候可以省略小括号
    const fun = x => {
    console.log(x)
    }
    fun(2)
  3. 只有一行代码的时候可以省略大括号

    1
    const fn = x => console.log(x)
  4. 只有一行代码的时候可以省略return

    1
    2
    3
    4
    5
    6
    // 初始写法
    const fn = x => {
    return x + x
    }
    // 省略写法
    const fn = x => x+x
  5. 箭头函数直接返回对象

    1
    2
    3
    4
    5
    // {uname:uname},表示一个对象
    // 但是箭头函数后面如果直接跟{},则会将函数体与对象识别混淆
    // 所以使用小括号来包装一下对象
    const fn = (uname) => ({uname:uname})
    fn("张")

更为简介的语法

1
2
3
4
5
6
7
8
9
10
// 初始写法
const a = document.querySelector("a")
a.addEventListener("click", function(e){
// 阻止点击a链接跳转
e.preventDefault()
})

// 箭头函数
const a = document.querySelector("a")
a.addEventListener("click", e => e.preventDefault())

箭头函数参数

  1. 普通函数有argument动态参数
  2. 箭头函数没有argument动态参数,但是有剩余参数…args
1
2
3
4
5
6
7
const getSum = (...args) => {
let sum = 0
for(let i = 0; i < args.length; i++){
sum += args[i]
}
return sum
}

箭头函数this

通常情况下this指向调用者

箭头函数不会创建自己的this,他只会从自己的作用域链的上一层沿用this

1
2
3
4
5
6
7
8
const user = {
name : '小明',
sleep:function(){
console.log(this) // 指向user,普通函数的this是调用者,即user
const fn = () => console.log(this) // 指向user, 因为箭头函数没有this,所以指向上一次的this,即user
fn()
}
}

解构赋值

数组解构

使用解构快速为变量赋值

数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法

传统写法

1
2
3
4
const arr = [100,60,80]
const max = arr[0]
const min = arr[1]
const avg = arr[2]

解构

1
const [max, min, avg] = [100,60,80]

交换两个变量的值

1
2
3
const a = 1
const b = 2; //此处必须加分号
[b, a] = [a, b]

必须加分号的两种情况

1
2
3
4
5
6
7
8
9
//立即执行函数
(function(){

})();

// 以数组开头的时候
let a = 1
let b = 2;
[a, b] = [b, a]

赋值的几种情况

  1. 变量多,单元值少

    1
    2
    const [a, b, c, d] = [1, 2, 3]
    // 其中 a = 1, b = 2, c = 3, d = undefined
  2. 变量少,单元值多

    1
    2
    const [a, b] = [1,2,3]
    // a = 1, b = 2
  3. 剩余参数,变量少,单元值多

    1
    const [a, b, ...args] = [1,2,3,4]
  4. 防止undefined传递

    1
    const[a = 0, b = 0] = []
  5. 按需导入赋值

    1
    2
    const [a, b, ,d] = [1, 2, 3, 4]
    // a = 1, b = 2, d = 4

对象解构

对象解构:将对象的属性和方法快速批量赋值给一系列变量的简洁语法

1
2
// const后面的参数值,一定要和对象中的属性名相同
const {uname, age} = {uname:"张三", uage:20}// 相当于:obj = {uname:'张三',uage:20} const uname = obj.uname

基本语法

  1. 赋值运算符=左侧的用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量
  2. 对象属性的值将被赋值给与属性名相同的变量
  3. 注意解构的变量名不要和外面的变量名冲突否则报错
  4. 对象中找不到与变量名一致的属性时变量值为undefined

解构对象改名

1
const {uname: name} = {uname:'张三'}

解构数组对象

1
2
3
4
5
6
7
const pig = [
{
uname:'啊啊',
age:18
}
]
const [{uname, age}] = p

多级对象解构

1
2
3
4
5
6
7
8
9
10
const pig = {
name: '佩奇',
family: {
mother: '猪妈妈',
father: '猪爸爸',
brother: '乔治'
},
age:6
}
const {name, family:{mother, father, brother}, age} = pig
1
2
3
4
5
6
7
8
9
10
11
12
const pig = [
{
name: '佩奇',
family: {
mother: '猪妈妈',
father: '猪爸爸',
brother: '乔治'
},
age:6
}
]
const [{name, family:{mother, father, brother}, age}] = pig

传参的时候解构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const pig = {
name: '佩奇',
family: {
mother: '猪妈妈',
father: '猪爸爸',
brother: '乔治'
},
age:6
}

function getFamily({family}){
console.log(family)
}
getFamily(pig)

forEach方法

语法

1
2
3
4
5
6
7
被遍历的数组.forEach(function(当前元素数组,当前元素索引号)){
函数体
}
//箭头函数版
被遍历的数组.forEach((当前元素数组,当前元素索引号) => ){
函数体
}

filter方法

1
2
3
const 数组 = 被遍历的数组.filter(function(当前元素数组,当前元素索引号)){
return 当前元素数组符合条件的
}

深入对象

创建对象的三种方式

  1. 字面量创建

    1
    2
    3
    const obj = {
    name:"张三"
    }
  2. 使用new Object创建对象

    1
    2
    3
    const obj = new Object({name:"张三"})
    const obj2 = new Object()
    obj2.name = "张三"
  3. 通过构造函数创建对象

构造函数

目标:利用构造函数来创建对象

可以通过构造函数来创建多个类似对象

1
2
3
4
5
6
7
function Pig(name, age, gender){
this.name = name
this.age = age
this.gender = gender
}
const dad = Pig('佩奇', 6, '女')
const dad = Pig('佩奇2', 16, '女')

约定:

  1. 命名以大写字母开头
  2. 他们只能由“new”操作符来执行

说明:

  1. 使用new关键字调用函数的行为被称为实例化
  2. 实例化构造函数时没有参数时可以省略()
  3. 构造函数内部无需写return,返回值即为新创建的对象
  4. 构造函数内部的return返回的值无效,所以不要写return
  5. new Object ( ) new Date ( )也是实例化构造函数

实例化执行过程

  1. 创建新的空对象
  2. 构造函数this指向新对象
  3. 执行构造函数代码,修改this,添加新属性
  4. 返回新对象

实例成员&静态成员

实例成员:通过构造函数创建的对象称为实例对象,实例对象中的方法称为实例成员

说明:

  1. 为构造函数传入参数,创建结构相同但值不同的对象
  2. 构造函数创建的实例对象彼此相互不影响

静态成员:构造函数的属性和方法称为静态成员

说明:

  1. 静态成员中只能构造函数来访问
  2. 静态方法中的this指向构造函数

内置构造函数

Object

  1. Object.keys(o) 获取所有属性的属性名

    1
    2
    const o = {name:'张三', age:12}
    console.log(Object.keys(o)) // name,age
  2. Object.values(o) 获取所有的属性值

    1
    2
    const o = {name:'张三', age:12}
    console.log(Object.values(o)) // 张三, 12
  3. Object.assign(new, old) 将old中的属性值和名拷贝到new对象中

    1
    2
    3
    4
    const o = {name:'张三', age:12}
    const oo = {}
    Object.assign(oo, o)
    console.log(oo) // {name:'张三', age:12}

Array

函数具体细节可查询MDNhttps://developer.mozilla.org/zh-CN/

  1. forEach遍历数组

  2. filter 过滤数组

  3. map 迭代数组

  4. reduce 累计器

    1
    2
    3
    4
    5
    arr.reduce(function(上一次值, 当前值){}, 起始值)
    const arr = [1,2,3]
    const total = arr.reduce(function(prev, current){
    return prev + current
    })

    如果有起始值,则将数组中元素相加完毕之后,再加上起始值,没有则不加

    执行过程:

    1. 如果没有起始值,则上一次值以数组的第一个数组元素的值
    2. 如果每一次循环,把返回值给作为下一次循环的上一次值
    3. 如果有起始值,则起始值作为上一次值

    当遇到对象数组进行累加的时候,一定要初始值为0

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    const arr = [
    {
    name:"张三",
    salary:10000
    },
    {
    name:"李四",
    salary:10000
    },
    {
    name:"王五",
    salary:10000
    },
    ]

    const total = arr.reduce((prve, current) => {
    return prve + current.salary
    }, 0)
  5. 实例方法 join:数组元素拼接位字符串,返回字符串

  6. 实例方法 find:查找元素,返回符合测试条件的第一个数组元素值,如果没有则返回undefined

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    const arr = [
    {
    name:'小米',
    price:1999
    },
    {
    name:'华为',
    price:9999
    },
    ]

    // 找到name为小米的对象并返回
    const mi = arr.find(function(item){
    return item.name==='小米'
    })

    //箭头函数
    const mi = arr.find(item=>item.name==='小米')
  7. 实例方法 every:检测数组元素是否都符合指定条件,如果所有元素都符合则返回true,否则返回false

    1
    2
    const arr1 = [10,20,30]
    const flag = arr1.every(itme=>item>=10)
  8. 实例方法 some:检测数组中的元素是否满足指定条件,如果存在满足条件的返回true,否则返回false

  9. 实例方法 concat:合并两个数组,返回新生成的数组

  10. 实例方法 sort:对原数组单元值排序

  11. 实例方法 splice:删除或替换原数组

  12. 实例方法 reverse:反转数组

  13. 实例方法 findIndex 查找元素的索引值

  14. 实例方法 from :将伪数组转换为真数组

String

  1. 实例属性 length:用来获取字符串的度长(重点)

  2. 实例方法 split(‘分隔符’):用来将字符串拆分成数组(重点)

    1
    2
    const str = '2024-8-9'
    const str1 = str.split("-") // ['2024', '8', 9]
  3. 实例方法 substring(需要截取的第一个字符的索引[,结束的索引号]): 用于字符串截取(重点)

    1
    2
    const str = '帅气的男人'
    const str1 = str.substring(1, 2)
  4. 实例方法 startswith(检测字符串[,检测位置索引号]):检测是否以某字符开头(重点)

  5. 实例方法 includes(搜索的字符串[,检测位置索引号]):判断一个字符串是否包含在另一个字符串中,根据情况返回true或 false(重点)

  6. 实例方法 toUpperCase:用于将字母转换成大写

  7. 实例方法 toLowercase:用于将就转换成小写

  8. 实例方法 indexof:检测是否包含某字符

  9. 实例方法 endswith:检测是否以某字符结尾

  10. 实例方法 replace:用于替换字符串,支持正则匹配

  11. 实例方法 match:用于查找字符串,支持正则匹配

Number

toFixed()设置保留小数位的长度

1
2
3
const price = 12.3654
// 保留两位小数
console.log(price.toFixed(2))

编程思想

面向过程

分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再依次调用即可

按照步骤划分

面向对象

把事务分解成一个个对象,然后对象之间相互合作

面向对象是以对象的功能来划分,而不是步骤

特性:

  1. 封装
  2. 继承
  3. 多态

构造函数

JS面向对象通过构造函数来实现封装性

构造函数可能会存在浪费内存的问题

1
2
3
4
5
6
7
function Star(uname, age){
this.uname = uname
this.age = age
this.sing = function(){
console.log('我会唱歌')
}
}

如果创建多个实例对象的话,则sing这个函数会存在多个

总结:

  1. 构造函数体现了面向对象封装的特性
  2. 构造函数实例创建的对象彼此相互独立互不影响

原型

原型

解决构造函数内存浪费的问题

  1. 构造函数通过原型分配的函数是所有对象所共享的
  2. JS规定,每一个对象都有一个prototype属性,指向另一个对象,所以我们也成为原型对象
  3. 这个对象可以挂载函数,对象实例化不会多次创建在原型上的函数,节约内存
  4. 可以将那些不变的方法,直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法
  5. 构造函数和原型对象中的this都指向实例化对象
1
2
3
4
5
6
7
//给数组定义自己的扩展方法
const arr = [1,2,3]
Array.prototype.sum = function(){
// 展开运算符
return Math.sum(...this)
}

constructor属性

每个原型对象里面都存在constructor属性

作用:该属性指向该原型对象的构造函数

1
2
3
4
5
6
7
8
9
10
11
function Star(){
}
// 使用下面方法会导致 原型中的constructor属性消失,所以需要重新指回原属性
Star.prototype = {
sing : function(){
console.log("唱歌")
},
dance : function(){
console.log("跳舞")
}
}

在里面从新指回Star

1
2
3
4
5
6
7
8
9
10
11
12
function Star(){
}
// 使用下面方法会导致 原型中的constructor属性消失,所以需要重新指回原属性
Star.prototype = {
constructor : Star,
sing : function(){
console.log("唱歌")
},
dance : function(){
console.log("跳舞")
}
}

对象原型

每个对象都有一个属性__ proto __ ,指向构造函数的prototype原型对象,使用构造函数创建的对象,可以使用prototype原型对象的属性和方法,是因为对象有 __ proto __原型的存在

1
2
3
function Star(){}
const ldh = new Star()
console.log(ldh.__proto__ === Star.prototype) // true

原型继承

继承是面向对象的一个重要特征,通过继承进一步提升封装代码的程度,JS中大多数是借助原型对象实现继承特性

1
2
3
4
5
6
7
8
function Woman(){
this.eays = 2
this.head = 1
}
function Man(){
this.eays = 2
this.head = 1
}

Woman 和 Man 都属于人类,所以可以提取出公共元素人,让后让Woman和Man继承Person

1
2
3
4
5
6
function Person(){
eays: 2,
head: 1
}
Woman.prototype = Person // 会覆盖掉原先的constructor,所以需要手动指回
Woman.prototype.constructor = Woman

但是这样有一个问题,如果想单独给女人增加一个生孩子的功能,可以尝试Woman.prototype.baby = function(){console.log(“生孩子”)}

如果使用上述方法的话,则男人的功能区也会出现baby的功能,因为男人女人都是用的一个原型

解决方法,使用new来创建继承

1
2
3
4
5
6
function Person(){
this.eays = 2,
this.head = 1
}
Woman.prototype = new Person() // 会覆盖掉原先的constructor,所以需要手动指回
Woman.prototype.constructor = Woman

原型链

基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联关系是一种链状结构,称为原型链

image-20240130203153329

原型链查找规则

  1. 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
  2. 如果没有就查找它的原型 (也就是 __ proto __ 指向的 prototype 原型对象)
  3. 如果还没有就查找原型对象的原型 (object的原型对象)
  4. 依次类推一直找到 object 为止 (null)
  5. __ proto __ 对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线
  6. 可以使用 instanceof运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上

深浅拷贝

深浅拷贝只针对引用类型

浅拷贝

拷贝的是地址

方法:

  1. 拷贝对象:Object.assgin() / 展开运算符{…obj}拷贝
  2. 拷贝数组:Array.prototype.concat() / 展开运算符 […arr]
1
2
3
4
5
6
7
8
9
const obj = {
uname : "pink",
age : 18
}
const o = {...obj}
// 修改o中的内容不会导致obj中的内容改变
o.age = 20
console.log(o)
console.log(obj)

问题:浅拷贝中的引用数据类型拷贝的是地址

1
2
3
4
5
6
7
8
9
10
11
const obj = {
uname : "pink",
age : 18
family : {
name: "小pink"
}
}
const o = {...obj}
o.family.name = "老pink"
console.log(o) // family中的name为老pink
console.log(obj) // family中的name也为老pink

深拷贝

拷贝的是对象,不是地址

方法:

  1. 通过递归实现深拷贝

    递归:函数自身调用自己

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    const obj = {
    uname : "pink",
    age : 18,
    hobby : ["乒乓球", "篮球"],
    family : {
    name: "小pink"
    }
    }
    // 深拷贝
    function deepCopy(newObj, oldObj){
    for(k in oldObj){
    // 如果类型是数组的话则需要递归拷贝
    if(oldObj[k] instanceof Array){
    newObj[k] = []
    deepCopy(newObj[k], oldObj[k])
    } else if(oldObj[k] instanceof Object) {// 该语句一定要写在Array后面,因为Array instanceof Object = true
    newObj[k] = {}
    deepCopy(newObj[k], oldObj[k])
    } else {
    newObj[k] = oldObj[k]
    }

    }
    }
  2. lodash/cloneDeep

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <!-- 先引用 -->
    <script src="./lodash.min.js"></script>
    <script>
    const obj = {
    uname : "pink",
    age : 18,
    hobby : ["乒乓球", "篮球"],
    family : {
    name: "小pink"
    }
    }
    //调用
    const o = _.cloneDeep(obj)
    console.log(o)
    </script>
  3. 通过JSON.stringify()实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const obj = {
    uname : "pink",
    age : 18,
    hobby : ["乒乓球", "篮球"],
    family : {
    name: "小pink"
    }
    }
    // 先通过JSON.stringify将对象转换成字符串,在使用JSON.parse将字符串转换成对象
    const o = JSON.parse(JSON.stringify(obj))

异常处理

throw抛异常

1
2
3
4
5
6
7
function fn(x, y){
if(!x || !y){
throw new Error("没有参数传递")
}
return x+y
}
console.log(fn())

try/catch捕获异常

1
2
3
4
5
6
7
8
9
10
11
12
function fn(x, y){
try{
// 可能错误的代码
return x/y
} catch(err){
// 拦截错误,但不中断程序
console.log(err.message)
} finally{
// 无论程序是否发生错误,都会执行finally中的代码
alert("好好好")
}
}

debugger

会在程序中添加一个断点,在执行的时候会暂停到这里

1
2
3
4
5
6
7
8
9
10
11
12
13
function fn(x, y){
try{
debugger
// 可能错误的代码
return x/y
} catch(err){
// 拦截错误,但不中断程序
console.log(err.message)
} finally{
// 无论程序是否发生错误,都会执行finally中的代码
alert("好好好")
}
}

this问题

普通函数的this指向

谁调用普通函数,this就指向谁

当没有调用者的时候this指向window,严格模式下没有调用者时,this值为undefined

箭头函数的this指向

箭头函数中的 this 与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在 this!

  1. 箭头函数会默认帮我们绑定外层 this 的值,所以在箭头函数中 this 的值和外层的 this 是一样的
  2. 箭头函数中的this引用的就是最近作用域中的this
  3. 向外层作用域中,一层一层查找this,直到有this的定义

注意

  1. 在开发中[使用箭头函数前需要考虑函数中 this 的值],事件回调函数使用箭头函数时,this 为全局的 window
    因此DOM事件回调函数如果里面需要DOM对象的this,则不推荐使用箭头函数
  2. 同样由于箭头函数 this的原因,基于原型的面对象也不推荐采用箭头函数

改变this

  1. call()

    call调用函数,同时指定被调用函数中this的值

    语法:fun.call(thisArg, arg1, arg2)

    • thisArg: 在 fun 函数运行时指定的 this 值
    • arg1,arg2: 传递的其他参数
    • 返回值就是函数的返回值,因为它就是调用函数
    1
    2
    3
    4
    5
    6
    7
    8
    const obj = {
    name:"张三"
    }
    function fn(x, y){
    return x+y
    }
    // 让函数fn中的this指向obj
    fn.call(obj,1,2)
  2. apply()

    与call的区别是传递参数的方式不用

    语法 fun.apply(thisArg, [argsArrays])

    • thisArg:在fun函数运行时指定的 this 值
    • argsArray:传递的值,必须包含在数组里面
    • 返回值就是函数的返回值,因为它就是调用函数
    • 因此apply 主要跟数组有关系,比如使用Math.max()求数组的最大值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const obj = {
    name:"张三"
    }
    function fn(x, y){
    return x+y
    }
    // 让函数fn中的this指向obj
    fn.call(obj,[1,2])
    // 求最大值
    const max = Math.max.apply(Math,[1,2,3])
  3. bind()

    bind方法不会调用函数,但是能改变函数内部this的指向

    语法:fun.bind(thisArg, arg1, arg2)

    • thisArg:在 fun 函数运行时指定的 this 值
    • arg1,arg2:传递的其他参数
    • 返回由指定的 this 值和初始化参数改造的 原函数拷贝 (新函数)
    • 因此当我们只是想改变 this 指向,并且不想调用这个函数的时候,可以使用 bind,比如改变定时器内部的
      this指向

防抖

单位时间内,频繁触发的事件,只执行最后一次

没有实现防抖时:鼠标每滑动1px,box中的内容就加一

1
2
3
4
5
6
7
8
<div class="box"></div>
<script>
const box = document.querySelector(".box")
let i = 1
box.addEventListener("mousemove", function(){
box.innerText = i++
})
</script>

实现方式

  1. lodash(咯大师)提供的库 debounce

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <div class="box"></div>
    <script src="./lodash.min.js"></script>
    <script>
    const box = document.querySelector(".box")
    let i = 1
    function mouseMove() {
    box.innerHTML = i++
    }
    // box.addEventListener("mousemove", mouseMove)
    // _.debounce(func, [wait=0], [options=])延迟 wait 毫秒后调用 func 方法
    box.addEventListener("mousemove", _.debounce(mouseMove, 500))
    </script>
  2. 手写debounce函数

    防抖的核心就是利用定时器(setTimeout)来实现

    1. 声明一个定时器变量
    2. 当鼠标每次滑动都先判断是否有定时器了,如果有定时器先清除以前的定时器
    3. 如果没有定时器则开启定时器,记得存到变量里面
    4. 定时器里面调用要执行的函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function debounce(fn, t){
    let timer
    // 因为debounce需要传递参数过来,所以是立即调用,所以需要使用返回函数来进行业务
    return function(){
    if(timer) clearTimeout(timer)
    timer = setTimeout(function(){
    fn()
    }, t)
    }
    }
    box.addEventListener("mousemove", debounce(mouseMove, 500))

节流

单位时间内,频繁触发事件,只执行一次

使用场景:

高频事件:鼠标移动 mousemove,页面尺寸缩放 resize,滚动条滚动 scroll

实现方式

  1. lodashi的节流函数

    1
    2
    //_.throttle(func, [wait=0], [options=]) 在 wait 秒内最多执行 func 一次的函数
    box.addEventListener("mousemove", _.throttle(mouseMove, 500))
  2. 手写节流

    节流的核心就是利用定时器(setTimeout)来实现

    1. 声明一个定时器变量
    2. 当鼠标每次滑动都先判断是否有定时器了,如果有定时器则不开启新定时器
    3. 如果没有定时器开启定时器,记得存到变量里面
      • 定时器里面调用执行的函数
      • 定时器里面要把定时器清空
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function throttle(fn, t){
    let timer = null
    return function(){
    // 没有定时器则创建
    if(!timer){
    timer = setTimeout(function(){
    fn()
    // 清空定时器
    timer = null
    }, t)
    }
    }
    }

ontimeupdate 事件在视频/音频 (audio/video)当前的播放位置发送改变时触发

onloadeddata 事件在当前帧的数据加载完成且还没有足够的数据播放视频/音频 (audio/video)的下一帧时触发

由于ontimeupdate 触发频率特别高,所以可以采用节流

对视频操作

1
2
3
4
const video = document.querySelector("video")
video.ontimeupdate = _.throttle(()=>{
localStorage.setItem("currentTime",video.currentTime)
}, 1000)