目录
1.JS基础
1.1.序言
本笔记基于pink老师的JavaScript网课,仅针对对其他编程语言有一定了解,JS基础语法(包括JS的引入,条件判断,循环语句, 模版字符串等)已经迅速上手的同学食用。
笔记创作:在本笔记中代码,都是自己在学习中敲的,对一些知识点的解释和归纳是基于网课内容和自己的学习理解二次创作的(无copy内容);请要看pink老师 源代码
+ 笔记
的同学注意甄别。
笔记特点:因为是二次创作缘故,个人觉得简单的地方会写得比较简略,但对JavaScript进阶部分写得比较详细,而对自己发现的一些课堂内容小bug也会作以修正。如果在笔记中遇到一些错误,欢迎同学们指正。
1.2.函数
1.3.1.this
this代表当前函数运行时所处的环境
- 每个函数里都有一个this,普通函数的this指向window
function fn() {
console.log(this); // window
}
window.fn()
})
- this指向函数的调用者
const btn = document.querySelector('button')
btn.addEventListener('click', function () {
console.log(this)
// eg:
this.style.color = 'red'
})
1.2.2.回调函数
回调函数:将一个A函数作为另一个函数的参数,A为回调函数
1.2.3.间歇函数
间歇函数也属于回调函数,当达到时间后,回调执行函数(间歇地执行一个函数,执行多次)
<script>
fn = function () {
console.log(123)
}
// 打开计时器timer1
let timer1 = setInterval(fn, 5000)
// 打开计时器timer2
let timer2 = setInterval(function () {
console.log(111)
}, 5000)
// timer1返回定时器编号
console.log(timer1)
// 关闭定时器
clearInterval(timer1)
console.log(timer2)
</script>
1.2.4.定时器
一段时间后执行函数,只执行一次
<script>
// 三秒后隐藏
setTimeout(function () {
div.style.display = 'none'
}, 3000)
</script>
1.2.5.立即执行函数
(function(){xxx})();
(function(){xxx}());
无需调用,立即执行,其实本质已经调用了
多个立即执行函数之间用分号隔开
1.3.逻辑中断
逻辑或 :
||
=> 有真不看后,全真为前真 (在实际开发中,逻辑或的中断使用得较多)逻辑与:
&&
=> 有错不看后,全真为后真
从逻辑上来讲,逻辑或和数学中的
或
类似,逻辑与和数学中的且
类似。
第一个式子 第二个式子 或运算真值 且运算真值 0 0 0 0 0 1 1 0 1 0 1 0 1 1 1 1
或运算:
有真不看后
:或运算只要前面的式子正确,则整个式子真值为1
全真为前真
:要想整个式子真值为1,最大程度取决于第一个式子(因为代码是从左往右执行的啊!! )且运算:
有错不看后
:且运算只要前面的式子错误,则整个式子真值为0
全真为后真
:要想整个式子真值为1,最大程度取决于第二个式子(因为代码是从左往右执行的啊!!)
- 执行一个控制台输出两个数相加结果的函数,需要默认传参,否则会报错
function fn(x = 0, y = 0) {
console.log(x + y) // 0
}
fn()
- 用逻辑中断替代上面代码默认传参的效果
function fn1(x, y) {
// 如果x传值,则不执行'||'后面的代码
// 如果x没有传值则会发生逻辑中断,执行'||'后面的代码(赋与0),y亦是如此
x = x || 0
y = y || 0
console.log(x + y) // 0
}
fn1()
||
逻辑中断
let age = 18
console.log(false && age++) // false
&&
逻辑中断
console.log(false || 22) // 22
1.4.内置对象
1.4.1.Math对象
函数 | 功能 |
---|---|
Math.random() | 0 ~ 1 之间的随机数, 包含 0 不包含 1 |
Math.ceil() | 向上取整 |
Math.floor() | 向下取整 |
Math.round() | // 取整,四舍五入原则 |
Math.mix(),Math.max() | 最值 |
Math.pow(4, 2) // 4 的 2 次方 | 次方 |
Math.sqrt(16) | 平方根 |
应用:
- 封装一个生成范围随机数函数
creatRandom = function (start, end) {
while (1) {
num = Math.random() * (end + 1)
if (num > start) {
return Math.floor(num)
}
}
}
console.log(creatRandom(1, 100))
- 封装一个生成范围随机数函数
// Math.floor(Math.random() * (M - N) + N)
creatRandom1 = function (start, end) {
return Math.floor(Math.random() * (end - start + 1) + start)
}
console.log(creatRandom1(1, 100))
- 获取数组随机元素的下标
const random = Math.floor(Math.random() * lst.length)
1.4.2.日期对象
实例化
<script>
// 实例化一个日期对象data1,data1的时间是2024-4-3 13:00:00
const date1 = new Date('2024-4-3 13:00:00')
console.log(date1);// Mon Apr 15 2024 16:51:40 GMT+0800 (中国标准时间)
</script>
日期对象方法
函数 | 功能 |
---|---|
date1.getFullYear() | 年 |
date1.getMonth() + 1 | 月(0~11) |
date1.getDate() | 日 |
date1.getDay() | 星期几 (0~6) |
date1.getHours() | 时 |
date1.getMinutes() | 分 |
date1.getSeconds() | 秒 |
时间戳
- 定义:从1970年1月1日00时00分00秒到一个时间点的毫秒数
<script>
//获取时间戳有三种方法
// 1.getTime()
const now1 = new Date()
console.log(now1.getTime());
// 2.+new Date()
const now2 = +new Date()
console.log(now2);
// 3.Date.now()
console.log(Date.now());
</script>
2.API
2.1.BOM
2.1.1.元素操作
2.1.1.1.获取
建议采用css选择器方式进行获取
<input type="text" value="电脑" class="text">
<script>
// 输入框
const ipt = document.querySelector('.text')
</script>
2.1.1.2.修改
对于表单元素,修改属性:
<script>
// 修改表单属性
ipt.value = '123'
ipt.type = 'password'
// 修改按钮选择情况
checkbox.checked = true
// 修改按钮禁用情况
btn.disabled = true
</script>
修改类名
<script>
// 1.添加类名(但是会覆盖, 如果需要前类名,应该加上)
div.className = 'box'
// 2.不覆盖,classList
//追加一个类add()
div.classList.add('pushclass')
div.classList.add('div')
// 删除类remove()
div.classList.remove('div')
// 切换类 toggle() => 有就删掉,没有加上
div.classList.toggle('div')
</script>
2.1.2.节点操作
节点包括:元素节点、属性节点、文本节点、
2.1.2.1.查找节点
<script>
// 父节点
console.log(baby.parentNode);
console.log(baby.parentNode.parentNode);
// 子节点
// 1.children => 只选亲儿子,组成伪数组,
const ul = document.querySelector('ul')
console.log(ul.children);
// 2.childNodes => 包含了文本节点(一般不建议使用)
console.log(ul.childNodes);
// 兄弟节点
const li2 = document.querySelector('ul li:nth-child(2)')
// 下一个兄弟节点
console.log(li2.nextElementSibling);
// 上一个兄弟节点
console.log(li2.previousElementSibling);
</script>
2.1.2.2.增加节点
步骤:
- 创建节点
- 编辑节点
- 追加节点
<script>
// 1.创建节点
const create1 = document.createElement('section')
const create2 = document.createElement('section')
// 2.编辑节点
create1.classList.add('section')
create2.classList.add('section')
create1.innerHTML = 'create1'
create2.innerHTML = 'create2'
// 3.追加节点,在父元素最后一个子元素
ul.appendChild(create1)
// 追加节点,在某个子元素前面 => (插入的元素,插入哪个元素的前面)
// ul.insertBefore(create2, create1)
ul.insertBefore(create2, ul.children[0])
</script>
2.1.2.3.克隆节点
<script>
//克隆节点------------------------------------------
const nav = document.querySelector('nav')
// 克隆 => ture和false代表深克隆或是浅克隆
const clone1 = ul.children[0].cloneNode(true)
// 浅克隆 => 仅仅克隆标签
const clone2 = ul.children[1].cloneNode(true)
console.log(clone2);
// 深克隆 => 除了克隆标签还克隆标签里面的内容
// const clone2 = ul.children[1].cloneNode()
// console.log(clone2);
// 追加
nav.appendChild(clone1)
nav.insertBefore(clone2, nav.children[0])
nav.appendChild(ul.children[5].cloneNode(true))
// 删除节点------------------------------------------
// 需要经过父元素
// nav.removeChild(nav.children[2])
</script>
2.1.3.事件
2.1.3.1.事件流
事件流 => 冒泡(子到父) + 捕获(父到子)
2.1.3.2事件对象
事件监听里的函数的第一个参数默认为事件对象
<script>
// e为监听对象
btn.addEventListener('click', function (e) {}
</script>
2.1.3.2.1.事件对象常用属性
事件 | 含义 |
---|---|
e.clientX、e.clientY | 触发位置距离窗口左上角的距离 |
e.offsetX、e.offsetY | 触发位置距离DOM的距离 |
e.key | 键盘事件中,触发事件的按键名 |
2.1.3.2.2.e.key
<script>
input.addEventListener('keyup', function (e) {
if (e.key === 'Enter') {
console.log('回车') //输入enter打印'回车'
}
})
</script>
-
e.target :触发事件的元素
-
e.target.tagName:触发事件的元素的元素名
2.1.3.2.3.阻止元素默认行为
<script>
// 阻止表单提交
const form = document.querySelector('form')
form.addEventListener('submit', function (e) {
e.preventDefault()
})
// 阻止链接跳转
const a = document.querySelector('a')
a.addEventListener('click', function (e) {
e.preventDefault()
})
</script>
2.1.3.3.鼠标事件
事件 | 含义 |
---|---|
mouseover、mouseout | 有冒泡效果 |
mouseenter、mouseleave | 无冒泡效果(推荐) |
2.1.3.4.键盘事件
事件 | 含义 |
---|---|
keydown | 键盘按下 事件 |
keyup | 键盘弹起 事件 |
input | 输入文本 事件 |
2.1.3.5.页面尺寸事件
事件 | 含义 |
---|---|
resize | 浏览器窗口大小改变事件 |
offsetLeft 、offsetTop | 获取被卷取部分的宽高 => 包含自身宽高,padding,border |
element.getBoundingClientRect() | 相对于视图窗口的左上角距离 => 不包含自身宽高 |
备注: 如果父元素不加定位,offset为对html的距离;如果父元素有定位,offset为对父元素的距离。
2.1.3.6.页面加载、滚动事件
事件 | 含义 |
---|---|
load | 等资源加载完 |
scroll | 页面滚动 |
- load
<script>
window.addEventListener('load', function () {
// 等所有外部资源加载完
const btn = document.querySelector('button')
btn.addEventListener('click', function () {
console.log(123);
})
const img = document.querySelector('img')
img.addEventListener('load', function () {
console.log(111);
// 如果图片比较大,等图片加载完了再执行这段代码
})
</script>
2.1.3.7.移动端事件
事件 | 含义 |
---|---|
touchstart | 触摸 |
touchend | 离开 |
touchmove | 移动 |
2.1.3.8.事件委托
给父元素注册事件,当我们触发子元素时,会冒泡到父元素,触发父元素事件
<script>
ul.addEventListener('click', function (e) {
console.dir(e.target);// 返回触发事件的对象
// 要求:只有触发事件的对象是li,才会有效果
if (e.target.tagName === 'LI') {
e.target.style.backgroundColor = '#ccc'
}
})
</script>
2.2.DOM
2.2.1.事件循环
浏览器引擎分为:渲染引擎、js解析引擎
js为单线程,同步与异步
同步在主线程上执行,形成一个执行栈,异步通过回调函数实现。
异步分为三种类型:
-
普通事件:click, resize
-
资源加载:load, error
-
定时器: set系列
- 异步任务添加到相关 任务队列 里
执行栈 和 任务队列中的事件循环执行执行称为事件循环
2.2.2.DOM对象
2.2.2.1.location对象
控制台打印location:
console.log(window.location);
可以查看loaction对象的各种属性
- loaction对象常见的属性
属性 | 含义 |
---|---|
href | 跳转页面 |
search | 拿到表单相关信息 |
hash | 拿到#后面的值 |
reload | 刷新当前页面 |
- href
<script>
// href
// eg:跳转百度
location.href = 'http://www.baidu.com'
</script>
- search
<form action="">
<input type="text" name='username'>
<input type="password" name="password">
<button>提交</button>
</form>
<script>
console.log(location.search);
</script>
- hash
<script>
<a href="#/my">我的</a>
<a href="#/friend">关注</a>
<a href="#/download">下载</a>
const as = document.querySelectorAll('a')
for (let i = 0; i < as.length; i++) {
as[i].addEventListener('click', function () {
console.log(location.hash);
})
}
</script>
- reload
<script>
<button class="reload">刷新</button>
const reload = document.querySelector('.reload')
reload.addEventListener('click', function () {
// ture为强制刷新
location.reload()
})
</script>
2.2.2.2.history对象
- 前进,后退
功能 | 对应属性 | 推荐写法 |
---|---|---|
前进 | history.forward() | history.go(1) |
后退 | history.back() | history.go(-1) |
<script>
// 后退
back.addEventListener('click', function () {
// history.back()
history.go(-1)
})
// 前进
forward.addEventListener('click', function () {
// history.forward()
history.go(1)
})
</script>
2.2.3.本地存储
本地存储只有储存数据的改网页可以看见
// 2.获取数据--------------------
console.log(localStorage.getItem('uname'));
// 3.删除数据--------------------
localStorage.removeItem('uname')
console.log(localStorage.getItem('uname'));
2.2.3.1.JSON字符串
JSON : 不管是字符串还是值都有双引号(数字没有)
例如:{“uname”:“Jack”,“age”:18,“gender”:“男”}
- 存储复杂数据类型不能直接存储,必须转换成JSON字符串存储
- 复杂数据类型转JSON字符串
// 将obj对象转JSON字符串
JSON.stringify(obj)
- JSON字符串转复杂数据类型
// 将obj对象转JSON字符串,再将JSON字符串转成对象
JSON.parse(JSON.stringify(obj))
本地存储处理复杂数据类型
const obj = {
uname: 'Jack',
age: 18,
gender: '男'
}
// 存
localStorage.setItem('obj', JSON.stringify(obj))
// 取
console.log(localStorage.getItem('obj'));
// 对象存储为字符串
console.log(typeof localStorage.getItem('obj'));
// 把存储的JSON转化为对象使用
console.log(JSON.parse(localStorage.getItem('obj')));
2.2.4.正则表达式
定义规则
<script>
const str = '春眠不觉晓, 春暖花开'
// 1.定义规则
const reg = /春/
</script>
2.2.4.1.是否匹配 test()
<script>
// test => 返回boolen值
console.log(reg.test(str)); // /春/
// exec = >返回一个数组
console.log(reg.exec(str)); // ['春', index: 0, input: '春眠不觉晓, 春暖花开', groups: undefined]
</script>
2.2.4.2.元字符
2.2.4.2.1.边界符
表示位置,必须用什么开头,用什么结尾
<script>
// ^哈 => 必须以哈开头
console.log(/^哈/.test('哈哈哈')); // ture
// 哈$ => 必须以哈结尾
console.log(/哈$/.test('哈哈哈')); // ture
// ^哈$ => 精确匹配
console.log(/^哈哈哈$/.test('哈哈哈')); // ture
</script>
2.2.4.2.2.量词
表示重复次数
符号 | 含义 |
---|---|
* | 重复 0 次或更多次 |
+ | 重复 1 次或更多次() |
? | 重复 0 次或 1 次 |
{n} | 重复 n 次 |
{n,} | 重复 n 次或更多次 |
{n, m} | 重复 n 次到 m 次 |
- 代码示例
<script>
console.log(/^哈*$/.test('哈'));
console.log(/^哈+$/.test('哈')); // => 一个哈已经代表重复一次,没有哈才代表重复0次
console.log(/^哈?$/.test('哈'));
console.log(/^哈{3}$/.test('哈哈哈哈哈'));
console.log(/^哈{4,}$/.test('哈哈哈哈哈'));
console.log(/^哈{4,10}$/.test('哈哈哈哈'));
</script>
2.2.4.2.3.字符类[]
- 字符类用
[]
<script>
// 只要包含abc中的一个都可以,可以多个
console.log(/[abc]/.test('jia')); // ture
</script>
字符
[]
+ 精确匹配
<script>
// 只要包含abc中的一个就可以,不能多个
console.log(/^[abc]$/.test('jia,back')); // false,有两个a
// 只要英文字母都行,不能多个
console.log(/^[a-z]$/.test('j')); // ture
console.log(/^[A-Z]$/.test('J')); // ture
// 0-9
console.log(/^[0-9]$/.test('7')); // ture
// 复合写法
console.log(/^[0-9A-Za-z]$/.test('7')); // ture
</script>
^
表示取反
<script>
// 除了英文字母和数字为true
console.log(/^[^0-9A-Za-z]$/.test('-')); // ture
</script>
应用
<script>
// 腾讯QQ号(从10000开始)
// {4,}重复的是 [0-9]
console.log(/^[1-9][0-9]{4,}$/.test('3400316021')); // ture
</script>
2.2.4.3.预定义
对某些
正则表达式
常见模式的简写方式,可以等效替换对应正则表达式
-
常见预定义
预定义符 对应正则表达式 备注 \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] 匹配非空格字符
2.2.4.4.修饰符
修饰符 | 含义 |
---|---|
i | ignore (不区分大小写) |
g | global(全局查找) |
应用
- `` replace()`,字符串替换
- 加入
i
修饰,不区分大小写替换
<script>
const str = 'java是一门编程语言,JAVA.........'
// 不区分大小写替换
str.replace(/java/i, 'JS')
console.log(str);
// 替换完有返回值,需要变量接收
const new_str = str.replace(/java/i, 'JS')
console.log(new_str); // JS是一门编程语言,JAVA
</script>
加入g
修饰,可以替换多个
<script>
const new_str2 = str.replace(/java/ig, 'JS')
console.log(new_str2); //JS是一门编程语言,JS
</script>
也可以用
|
代替i
<script>
const new_str3 = str.replace(/java|JAVA/g, 'JS')
console.log(new_str3); // JS是一门编程语言,JS
</script>
3.JS进阶
3.1.函数进阶
3.1.1.作用域
- 作用域
**函数作用域:**在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。
**块作用域:**使用
{}
包裹的代码称为代码块,代码块内部声明的变量外部将【有可能】无法被访问。
全局作用域
3.1.2.闭包
闭包 = 内层函数 + 外层函数的变量
<script>
// 普通函数------------------------------
let i = 0
function fn() {
i++
console.log('--->');
console.log('函数1被调用了' + i + '次');
}
fn() // 函数1被调用了1次
fn() // 函数1被调用了2次
// 但是外部可以修改i,数据容易被别人篡改
i = 100
fn() // 函数100被调用了1次
</script>
<script>
// 闭包-------------------------------
// 应用:闭包可以实现数据的私有
function count() {
let j = 0
function fn2() {
j++
console.log('--->');
console.log('函数2被调用了' + j + '次');
}
return fn2
}
const use = count()
use() // 函数2被调用了1次
use() // 函数2被调用了2次
// 但是外部可以修改i,数据容易被别人篡改
j = 100
use() // 函数2被调用了3次
</script>
作用:
- 实现数据私有,外部也可以访问函数内部的变量
- 闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来
缺陷:
- 内存泄漏
3.1.3.箭头函数
作用:简化函数
<script>
// 普通函数
const fn1 = function () {
console.log(111);
}
// 箭头函数
const fn2 = () => {
console.log(222);
}
</script>
语法:
<script>
// 1.有参数
const fn3 = (x) => {
console.log(x);
}
fn3(333)
// 2.只有一个形参时,可省略小括号
const fn4 = x => {
console.log(x);
}
fn4(444)
// 3.只有一行代码时,可省略大括号
const fn5 = x => console.log(x);
fn5(555)
// 4.只有一行代码时,有返回值,不用写return
const fn6 = x => x + x;
console.log(fn6(333));
// 5.可以直接返回一个对象
const fn = (uname) => ({ uname: uname })
console.log(fn('555'));
</script>
应用:
<script>
// 利用箭头函数求和
const getSum = (...arr) => {
let sum = 0
for (let i = 0; i < arr.length; i++) {
sum += i
}
return sum
}
console.log(getSum(1, 2, 3, 4, 5));
</script>
3.1.4.箭头函数的this
- 在箭头函数之前,每一个函数的this取决于这个函数是如何被调用的
<script>
// 在上一层script中找到的this指向window
const fun1 = () => {
console.log(this);
}
fun1() // window
</script>
- 箭头函数不会创建自己的this,只会沿用作用域链的上一层this
<script>
// 函数ing在外层对象obj1中没有找到this(obj1是对象,函数里面才有this)
const obj1 = {
uname: 'Tom',
ing: () => {
console.log(this);
}
}
obj1.ing() // window
// 函数nei在他上一层,函数wai里面找到了this(函数里面才有this)
const obj2 = {
uname: 'Tom',
wai: function () {
let i = 1
console.log(this);
const nei = () => {
console.log(this);
}
ing()
}
}
obj2.wai() // obj
// 同样,如果想使用this,不推荐使用箭头函数
</script>
3.1.5.改变this指向
3.1.5.1.call()
- 作用
-
- 调用函数
- 改变this指向
- 第一个参数是需要指向的对象,后面的参数是函数形参依次对应的实参
const obj = {
name: 'Tom'
}
function fn(x, y) {
console.log(this); // {name: 'Tom'}
console.log(x + y); // 3
}
fn.call(obj, 1, 2)
3.1.5.2.apply()
-
apply()也可以调用函数和改变this指向
-
apply()与call()相比,传入的实参必须是以数组形式
const obj = {
name: 'Tom'
}
function fn(x, y) {
console.log(this); // {name: 'Tom'}
console.log(x + y); // 3
}
fn.apply(obj, [1, 2])
3.1.5.3.bind()
- bind()也可以改变this指向,但不会调用函数
- bind()返回值是this指向更改过的函数(原函数this指向不变)
let that
const obj = {
name: 'Tom'
}
function fn(x, y) {
that = this
}
fn()
// 原函数指向 window
console.log(that); // window
const newFunction = fn.bind(obj)
newFunction()
// 新函数指向 obj
console.log(that); // {name: 'Tom'}
fn()
// 原函数this指向不变,依旧指向 window
console.log(that); // window
3.2剩余参数
3.2.1动态参数
arguments => 内置的,包含由函数实参组成的
伪数组
<script>
function sum() {
let s = 0
for (let i = 0; i < arguments.length; i++) {
s += arguments[i]
}
console.log(s);
}
sum(1, 2, 3) // 6
</script>
3.2.2.剩余参数
<script>
function get1(...arr) {
console.log(arr); // 使用时不需要写...
}
get1(2, 3)
// 用户最少传3个参数
function get2(a, b, c, ...arr) {
console.log(arr);
}
get2(1, 2, 3, 4)
</script>
3.2.3.展开运算符
展开运算符其实是剩余参数不作为参数的应用,常用于对数组的操作。
所谓“展开”
其实是将数组的小括号展开
3.2.3.1应用
<script>
const arr = [1, 2, 3, 8, 2]
console.log(...arr); // 1 2 3 8 2
</script>
应用1:求数组最大值
<script>
const arr1 = [1, 2, 3]
console.log(Math.max(...arr1));
</script>
应用2:合并数组
<script>
const arr2 = [4, 5, 6]
console.log([...arr1, ...arr2]);
</script>
3.3.解构
3.3.1.数组解构
使用解构,可以快速批量赋值给一系列变量的简洁语法
<script>
const arr = [10, 20, 30]
a1 = arr[0]
b1 = arr[1]
c1 = arr[2]
// 等价于
const [a2, b2, c2] = arr
console.log(a2);
console.log(b2);
console.log(c2);
</script>
应用:交换值
- 注意:在数组结构前,要加
;
,与立即执行函数类似
<script>
let x = 1
let y = 2
;[x, y] = [y, x] // 注意:在数组结构前,要加;
console.log(x);
console.log(y);
</script>
与剩余参数结合
<script>
const [a1, b1, ...c1] = [1, 2, 3, 4]
console.log(a1);
console.log(b1);
console.log(c1); // [3,4] 真数组
</script>
为避免undefined,可以进行默认传参
<script>
const [a2 = 0, b2 = 0] = [1]
console.log(a2);
console.log(b2);
</script>
3.3.2.对象解构
如果不修改变量名,变量名要与属性名一样,且没有与其他变量变量名冲突
<script>
const { uname, age } = { uname: 'Tom', age: 18 }
console.log(uname); // Tom
console.log(age); // 18
</script>
对象结构的变量名可以修改
- 语法:
旧变量名:新变量名
<script>
const { uname: username, age: userage } = { uname: 'Tom', age: 18 }
console.log(username); // Tom
console.log(userage); // 18
</script>
解构数组对象
<script>
const user1 = [
{
uname1: 'Tom',
age1: 18
}
]
const [{ uname1, age1 }] = user1
console.log(uname1); // Tom
console.log(age1); // 18
</script>
多级对象解构
<script>
const user2 = {
uname2: 'Tom',
friends: {
friend1: 'Mary',
friend2: 'Jack',
friend3: 'Bob',
},
age2: 18,
}
const { uname2, friends: { friend1, friend2, friend3 }, age2 } = user2
console.log(uname2); // Tom
console.log(age); // 18
console.log(friend1); // Mary
console.log(friend2); // Jack
console.log(friend3); // Bob
</script>
3.4.构造函数
3.4.1.语法
一种特殊的函数,用于初始化对象
- 使用场景:快速创建多个类似的对象
- 构造函数名要
大写
- 只能由
new
操作符操作,使用new调用函数被成为实例化
,所创建的对象称为实例对象
- 构造函数没有参数可以省略
()
- 不用写
return
,自动返回创造的对象
<script>
// 构造函数
function Students(name, age) {
this.name = name
this.age = age
}
// 创建对象
const stu1 = new Students('Tom', 1)
</script>
3.4.2.实例化执行过程
实例化执行过程包含:
- 创建新对象
- this指向新对象
- 指向构造函数代码,修改this,添加新属性
- 返回新对象
this.name = name
中,
this.name是新创建对象的name,
而右边name是用new创造函数函数时传入的实参,例如上面代码中的 Tom
其实,this.name和name的关系更像的对象里
属性
和值
的关系
<script>
const stu_name = 'Tom'
const obj = {
name = stu_name
}
</script>
-
观察上面代码,你会发现构造函数中
this.name = name
就是参照我们平常创建函数过程中的name = stu_name
创建的;只不过我们在平常创建函数过程中,常常直接写成name = 'Tom'
罢了
3.4.3.实例成员和静态成员
实例对象中的属性和方法分别称为
实例属性
和实例方法
, 都称为实例成员
同理,构造函数中的属性和方法分别称为
静态属性
和静态方法
, 都称为静态成员
- 静态成员只能通过构造函数访问。(例如Math.random, Data.now())
- 静态方法中的this指向构造函数。
<script>
function Students() {
this.say = function(){
console.log(this) // Students
}
}
Students.say()
</script>
3.5.对象操作
3.5.1.values()
方法
<script>
const obj = { name: 'Tom', age: 18 }
console.log(Object.values(obj)); // 'Tom', 18
</script>
3.5.2.assign()
方法
<script>
const obj1 = {}
// 将obj拷贝给obj1
Object.assign(obj1, obj)
// 也可以用assign()方法进行对象合并
const obj2 = {scores: 90}
Object.assign(obj1, obj2) // {name: 'Tom', age: 18, scores: 90}
</script>
3.6.数字操作
3.6.1.toFixed()
数字型可以通过
toFixed()
方法以四舍五入的保留方法,保留指定小数(默认保留0位)
console.log(10.11.toFixed()); // 10
console.log(10.toFixed(2)); // 10.00
3.7.数组操作
3.7.1.forEach
forEach 只遍历,不返回值(加强版for循环)
<script>
// 适合于遍历数组对象
const arr = ['red', 'green', 'yellow']
arr.forEach(function (item, index) {
console.log(index); // 0, 1, 2
console.log(item); // red, green, blue
})
</script>
3.7.2.map
map
可以遍历处理数据,并且返回新的数组(forEach无返回)
const arr = ['red', 'green', 'blue', 'yellow']
const newArr = arr.map(function (item, index) {
console.log(index); // 0, 1, 2, 3
console.log(item); // red, green, blue, yellow
return `${index}:${item}`
})
console.log(newArr); // ["0:red","1:green","2:blue","3:yellow"]
3.7.3.jion
join
拼接数组元素为字符串
const str = arr.join('')
console.log(str); // redgreenblueyellow
// 自带‘ , ’
const str1 = arr.join()
console.log(str1); // red,green,blue,yellow
3.7.4.filter
与map相似,但是map可以根据添加
筛选
,返回新数组
const arr = [10, 20, 30]
const result = arr.filter(item => item >= 20)
console.log(result); // 20, 30
3.7.5.reduce
累加器,返回积累处理的结果,经常用于数组求和,其语法如下:
const arr = [1, 2, 3]
const sum1 = arr.reduce(function (prev, current) {
return prev + current
})
console.log(sum1); // 6
const sum2 = arr.reduce(function (prev, current) {
return prev + current
},4)
console.log(sum2); // 10
- 其中,
prev
和current
分别表示上一个值当前值和当前值 - 后面的4为起始值,初始值默认为零
如果没有起始值,执行情况如下:
prev | current | return | |
---|---|---|---|
第一次执行 | 1 | 2 | 3 |
第二次执行 | 3 | 3 | 6 |
如果有起始值4,
执行情况如下:
prev | current | return | |
---|---|---|---|
第一次执行 | 4 | 1 | 5 |
第二次执行 | 5 | 2 | 7 |
第三次执行 | 7 | 3 | 10 |
总结:
- 如果没有起始值,prev 和 current指向
arr[0]
和arr[1]
,相加后返回的值作为prev,而将 current的下一个值作为下一次指行中的current - 如果有起始值,则首次循环将prev 和 current指向
起始值
和arr[0]
,在依次执行
reduce还可以用于对象数组的属性累加
const someone = [
{
name: "Tom",
money: 10
},
{
name: "Jack",
money: 20
}
]
const total = someone.reduce((prev, current) => {
return prev.money + current.money
}, 10)
console.log(total); // 40
3.7.6.find
find
用于数组查找
- 找到值则不会继续向下查找
在数组查找中,找到则返回寻找的值,没有找到则返回
undefined
const arr = ['yellow', 'pink', 'green']
const ifPink = arr.find((item) => {
return item === "pink"
})
console.log(ifPink); // pink
find
还可以用于对象数组的属性查找(常常用于判断属性来渲染数组中的某个对象)
const someone = [
{
name: "Tom",
money: 10
},
{
name: "Jack",
money: 20
}
]
const ifJack = someone.find((item) => {
return item.name === "Jack"
})
console.log(ifJack); // {name: 'Jack', money: 20}
3.7.7every
,some
every
用于判断数组中是否每一个都符合条件,返回布尔值
arr = [1, 2, 3]
const ifEvery = arr.every(item => item >= 20)
console.log(ifEvery); // false
some
用于判断数组中是否有某一个值,与find类似,不同的是some返回的是布尔值
3.7.8.补充
方法 | 用途 |
---|---|
sort | 对原数组进行排序 |
concat | 合并两个数组,返回生成的新数组 |
splice | 删除数组某个指定值 |
reverse | 反转数组 |
findIndex | 查找元素索引 |
3.8.字符串操作
3.8.1.split
split
将字符串拆分成数组,与join(将数组拼接成字符串)相反
const str1 = '1,2,3'
const arr1 = str1.split(',') // 以','拆分字符串arr1成数组
console.log(arr1); // ['1', '2', '3']
3.8.2.substring
substring
将字符串截取
- 如果省略结束索引号,默认取到最后
- 截取范围左开右闭
const str2 = '春眠不觉晓'
console.log(str2.substring(1,2)); // 眠
console.log(str2.substring(3)); // 觉晓
console.log(str2.substring(4)); // 晓
3.8.3startWith
startWith
判断字符串是不是以某字符开头
- 索引号代表以那个索引开头向后查找
const str3 = '春眠不觉晓'
console.log(str3.startsWith('春')); // true
console.log(str3.startsWith('春眠')); // true
// 有索引号
console.log(str3.startsWith('觉',3)); // true
3.8.4includes
includes
判断一个字符串是否包含在另外一个字符串中(区分大小写)
const str4 = '春眠不觉晓'
console.log(str4.includes('春')); // true
console.log(str4.includes('夏')); // false
3.9.原型
3.9.1编程思想
- 面向过程:分析出解决问题所需要的
步骤
,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了。(分步) - 面向对象(oop):把事务分解成为一个个对象,然后由对象之间分工与合作。(分类)
-
- 特性:
-
- 封装性
- 承性
- 多态性
大体来看,面向过程的性能更好,面向对象的灵活性和复用性更好,但在前端中,
面向过程
编程更多
3.9.2原型对象
在JS中,构造函数体现了面向对象的封装特征
但是,在构造函数构造不同实例对象中创建了多个静态方法,存在内存浪费问题
// 构造函数
function Students(num) {
this.num = num
this.doing = () => {
console.log('learning');
}
}
// 实例化
const stu1 = new Students(1)
const stu2 = new Students(2)
// 造成内存浪费
console.log(stu1.doing === stu2.doing); // false
- 为解决上面问题,我们将共用的实例方法写进构造函数的
原型对象
中,然后通过构造函数创建的实例对象可以共用原型对象中的方法(类比与共享单车) - 构造函数和原型对象中的
this
都指向 实例化的对象
// 构造函数
function Students(num) {
this.num = num
}
// 原型对象
Students.prototype.doing = () => {
console.log('learning');
}
// 实例化
const stu1 = new Students(1)
const stu2 = new Students(2)
console.log(stu1.doing === stu2.doing); // true
原型对象中的this指向:
let temp
// 构造函数
function Students() { }
// 原型对象
Students.prototype.doing = function () {
temp = this
}
// 实例化
const stu1 = new Students()
stu1.doing()
console.log(temp == stu1); // true
3.9.3.constuctor属性
constuctor属性用于原型对象指向构造函数
- 前面我们用的使用原型对象都是以给原型对象追加方法的方式,这样给原型对象追加一种方法当然没有问题,但是当我们给原型追加对象追加多种方法时,就可以一起追加给原型对象,减少代码量,提高代码性能。
function Students() { }
console.log(Students.prototype); // 在控制台可以看到constructor:ƒ Students()
Students.prototype = {
sing: function () {
console.log('鸡你太美');
},
reading: function () {
console.log('春眠不觉晓');
}
}
console.log(Students.prototype); // 在控制台看不到constructor指向Students()
- 但是我们所说的一起追加,实则是将原型对象给重写(原型对象以前的一些配置代码被覆盖了),导致我们的原型对象指不回构造函数(因为指回构造函数的配置代码也被覆盖了),这时候,我们需要用
constuctor
属性手动重新指回构造函数。
function Students() { }
console.log(Students.prototype)
Students.prototype = {
constructor: Students, // 用constuctor属性手动重新指回构造函数
sing: function () {
console.log('鸡你太美');
},
reading: function () {
console.log('春眠不觉晓');
}
}
console.log(Students.prototype); // 在控制台又能看见constructor指向Students()
3.9.4.对象原型
每一个对象都有[[proto]]属性指向构造函数的原型对象,之所以构造函数构造的实例对象能使用原型对象的方法,也是因为它。
- [[proto]]对象原型中也有一个 constructor属性,指向该实例对象的构造函数
- 因为实例对象必须指向原型对象,所以[[proto]]对象原型是只读的属性,不能更改
// __proto__是JS非标准属性,[[prototype]]和__proto__意义相同
function Students() { }
const stu3 = new Students()
// 实例对象通过对象原型指向原型对象
console.log(stu3.__proto__ === Students.prototype); //constructor: ƒ Students()
3.9.5.原型继承
原型对象也可以继承
const Person = {
eyes: 2
}
function Woman() { }
Woman.prototype = Person // 覆盖
Woman.prototype.constructor = Woman // 手动指回
// 实例化
const Xiaohong = new Woman
console.log(Xiaoming.eyes); // 2
但是多个原型对象继承于一个对象时,问题出现了:
- 以上面代码举例,Woman的原型对象虽然继承与Person,但是他也应该有自己的私有原型对象(这些原型对象是其他继承于Person所没有的,例如做饭cook)
- 当我们给Woman添加私有的原型对象,由于Woman和Man都继承于Person,所以给Woman添加私有原型对象(cook),其实是对Person进行了修改,因此,Man也会通过继承而拥有Woman私有原型对象(cook)。
const Person = {}
function Woman() { }
Woman.prototype = Person // 覆盖
Woman.prototype.constructor = Woman // 手动指回
// 给 Woman 添加私有原型对象(cook)
Woman.prototype.cook = function () {
console.log('我会点外卖');
}
function Man() { }
Man.prototype = Person // 覆盖
Man.prototype.constructor = Man // 手动指回
// 实例化
const Xiaohong = new Woman
const Xiaoming = new Man
Xiaoming.cook() // 我会点外卖
// 打印Person对象,我们可以在Person对象上找到cook方法
// 说明我们给 Woman 添加私有原型对象(cook),实则是将原型添加到了Person上
console.log(Person);
这个问题我们可以通过将被继承的Person对象设置成
构造函数
解决
- 将Person设置成构造函数后,通过
new
关键字我们可以为Man和Woman分别创建一个对象,而Woman和Man再分别继承于这两个对象。- 由于封装性,创建的对象不同(这里的不同指是对象不同,内容是相同的,且两个对象互不影响,因为都是通过new创建而来)
- 这样他们就不会继承于一个对象,而是继承于一个构造函数创建的两个不同对象,就不会出现设置了私有原型,但是却出现了共用的问题了。
// 将Person设置成构造函数,而不是对象
// 再通过构造函数生成对象
function Person() { }
function Woman() { }
Woman.prototype = new Person()
Woman.prototype.constructor = Woman // 手动指回
Woman.prototype.cook = function () {
console.log('我会点外卖');
}
function Man() { }
Man.prototype = new Person() // 覆盖
Man.prototype.constructor = Man // 手动指回
// 实例化
const Xiaohong = new Woman
const Xiaoming = new Man
// 小红正常调用
Xiaohong.cook() // 我会点外卖
// 小明调用,会报错‘Xiaoming.cook is not a function’,
// 说明给小红设置的私有原型并没有和小米共用
Xiaoming.cook()
3.9.6.原型链
接上面所说,每一个对象都有[[proto]]属性指向构造函数的原型对象,那么原型对象也会有相应[[proto]]属性
console.log(Object.prototype);
function Person() { }
const Xiaozhuang = new Person()
console.log(Xiaozhuang.__proto__ === Person.prototype); // true
console.log(Xiaozhuang.__proto__.constructor === Person); // true
// Person原型对象的__proto__属性指向
console.log(Person.prototype.__proto__); // 输出发现,指向Object的原型对象
// 那么Object的原型对象也是对象,他的__proto__属性指向
console.log(Object.prototype.__proto__); // null
// 说明Object的原型对象的__proto__属性指向为空,Object是最大的对象
- 像这样以[[proto]]链接
原型对象
的链就叫做原型链
,原型继承也是沿着原型链向下的。 - 继承是沿着原型链向下的,查找是沿着原型链向上的。
- 当访问一个对象的属性或方法时,首先查找这个对象自身有没有该属性,如果没有就沿着原型链向上查找。
[[proto]]
对象原型的意义就在与将不同级的原型对象相连成链,方便了原型的继承和查找。
像我们上面讲的原型继承所举例的Person,Woman,Man也是如此,不过为了让他们互不影响,让他们继承于用Person构造函数创建的不同的实例对象而已,这里就不再过多赘述了。
3.9.7instanceof运算符
我们可以通过 instanceof 运算符来检测构造函数的原型对象属性是否出现在某个实例对象的原型链上(或者说检查一个构造函数是否属于另一个构造函数)
function Woman() { }
function Man() { }
const Xiaohong = new Woman()
const Xiaoming = new Man()
console.log(Xiaoming instanceof Woman); // false
console.log(Xiaohong instanceof Woman); // true
console.log(Xiaohong instanceof Object); // true
console.log(Xiaoming instanceof Object); // true
console.log([1, 2, 3] instanceof Array); // true
// 万物皆对象
console.log(Array instanceof Object); // true
3.10.补充
3.10.1.基础数据类型
-
JavaScript有两种数据类型,基础数据类型和引用数据类型
3.10.2.拷贝
-
拷贝分为深拷贝和浅拷贝
浅拷贝:只复制指向某个对象的指针,而不复制对象本身,新旧对象共享一块内存。
深拷贝:创建一模一样的对象,不共享内存,修改新对象,旧对象保持不变。
3.10.2.1.尝试拷贝
- 尝试拷贝:下面两种尝试拷贝方式其实都是以赋值方式模拟拷贝,并不属于拷贝(这里是方便分类,不是官方文档名词)。
3.10.2.1.1.直接赋值尝试拷贝
const obj = {
name: 'Tom',
age: 18
}
const newObj = obj
console.log(newObj.name); // Tom
obj.name = 'Jack'
console.log(obj.name); // Jack
// newObj会随着obj的改变而改变
console.log(newObj.name); // Jack
可以看出直接赋值,、新对象会随着原对象的改变而改变
3.10.2.1.2.展开运算符尝试拷贝
const obj = {
name: 'Tom',
age: 18
}
const newObj = { ...obj }
console.log(newObj.name); // Tom
obj.name = 'Jack'
console.log( obj.name); // Jack
// newObj不会随着obj的改变而改变
console.log(newObj.name) // Tom
-
但是展开运算符只能展开一层。
-
当进行对
嵌套对象
拷贝时,内层对象属性和方法会出现和直接赋值一样的问题。
const obj = {
name: 'Tom',
friend: {
name: 'Bob'
}
}
const newObj = { ...obj }
console.log(newObj.friend.name); // Bob
obj.friend.name = 'Jack'
console.log(obj.friend.name); // Jack
// 内层对象属性和方法依然会随着obj改变
console.log(newObj.friend.name); // Jack
3.10.2.2.浅拷贝
3.10.2.2.1.assign浅拷贝对象
const obj = {
name: 'Tom',
friend: {
name: 'Bob'
}
}
const newObj = {}
Object.assign(newObj, obj)
console.log(newObj.friend.name); // Bob
obj.friend.name = 'Jack'
console.log(obj.friend.name); // Jack
// 内层对象属性和方法依然会随着obj改变
console.log(newObj.friend.name); // Jack
3.10.2.2.2.concat浅拷贝数组
3.10.2.3.深拷贝
3.10.2.3.1.封装深拷贝函数
封装一个浅拷贝函数
const obj = {
name: 'Tom',
friend: {
name: 'Bob'
}
}
const newObj = {}
// 封装浅拷贝函数
function deepCopy(newObj, obj) {
for (let k in obj) {
// k是obj的属性名
// obj[k]是k对应是属性值
newObj[k] = obj[k]
}
}
deepCopy(newObj, obj)
console.log(newObj.friend); // Bob
obj.friend.name = 'Jack'
console.log(newObj.friend); // Jack
// 内层对象属性和方法依然会随着obj改变
console.log(obj.friend); // Jack
将浅拷贝函数升级成为深拷贝函数(利用递归)
const obj = {
name: 'Tom',
friend: {
name: 'Bob'
},
doing: ['sing', 'reading']
}
const newObj = {}
// 封装深拷贝函数
function deepCopy(newObj, obj) {
for (let k in obj) {
// 如果遍历到数组,进行递归
if (obj[k] instanceof Array) {
newObj[k] = [] // 创建新对象中的对应数组
deepCopy(newObj[k], obj[k])
}
// 如果遍历到对象,进行递归
else if (obj[k] instanceof Object) {
newObj[k] = {} // 创建新对象中的对应对象
deepCopy(newObj[k], obj[k])
}
// 简单数据类型直接拷贝
else {
newObj[k] = obj[k]
}
}
}
deepCopy(newObj, obj)
console.log(newObj.friend.name); // Bob
obj.friend.name = 'Jack'
console.log(newObj.friend.name); // Bob
// 内层数组不会随着obj改变
console.log(obj.friend.name); // Jack
console.log(newObj.doing[0]); // sing
obj.doing[0] = 'rap'
console.log(newObj.doing[0]); // sing
// 内层数组不会随着obj改变
console.log(obj.doing[0]); // rap
- 注意一定要先判断是否为数组,再判断是否为对象
- 因为数组包含于对象(万物皆对象)
当先判断是否为对象时,如果遍历到数组,也会通过是否为对象的判断,从而引发错误
3.10.2.3.2.lodash深拷贝
- lodash是一个一致性、模块化、高性能的JS使用工具库
<!-- 在使用时我已经将lodash的js文件下载到了本地 -->
<script src="../lodash.min.js"></script>
<script>
const obj = {
name: 'Tom',
friend: {
name: 'Bob'
},
doing: ['sing', 'reading']
}
const newObj = _.cloneDeep(obj)
// 测试
console.log(newObj.friend.name); // Bob
obj.friend.name = 'Jack'
console.log(newObj.friend.name); // Bob
// 内层数组不会随着obj改变
console.log(obj.friend.name); // Jack
console.log(newObj.doing[0]); // sing
obj.doing[0] = 'rap'
console.log(newObj.doing[0]); // sing
// 内层数组不会随着obj改变
console.log(obj.doing[0]); // rap
</script>
3.10.2.3.3.JSON深拷贝
利用JSON把对象转换为JSON字符串,再将字符串转化为对象
const obj = {
name: 'Tom',
friend: {
name: 'Bob'
},
doing: ['sing', 'reading']
}
// 把对象转换为JSON字符串,再将字符串转化为对象
const newObj = JSON.parse(JSON.stringify(obj))
console.log(newObj.friend.name); // Bob
obj.friend.name = 'Jack'
console.log(newObj.friend.name); // Bob
// 内层数组不会随着obj改变
console.log(obj.friend.name); // Jack
console.log(newObj.doing[0]); // sing
obj.doing[0] = 'rap'
console.log(newObj.doing[0]); // sing
// 内层数组不会随着obj改变
console.log(obj.doing[0]); // rap
3.11异常处理
3.11.1.throw抛出异常
- 抛出异常后,会中止程序
执行下面代码时,由于没有给函数传递参数,控制台打印 NaN
function fn(x, y) {
return x + y
}
console.log(fn()) // NaN
我们可以在没有传入参数时,给用户抛出异常
function fn(x, y) {
if (!x || !y) {
throw '没有参数传入' // Uncaught 没有参数传入
}
return x + y
}
console.log(fn());
给用户抛出异常,我们也可以把异常代码的行数一起抛出(推进使用)
function fn(x, y) {
if (!x || !y) {
throw new Error('没有参数传入')
}
return x + y
}
console.log(fn());
/*
异常捕获.html:14
Uncaught Error: 没有参数传入
at fn (异常捕获.html:14:15)
at 异常捕获.html:18:17
*/
3.11.2.try / catch捕获异常
- 当
try
里面的代码错误,catch
就会拦截浏览器提供的错误信息,不中断程序 - 我们要将预估会发生错误的代码写入
try
里面 catch
里的参数是浏览器提供的错误信息finally
里代码不管程序对不对,都会执行
function fn() {
try {
const p = document.querySelector('#p')
p.style.color = 'green'
} catch (err) {
console.log(err.message); // Cannot read properties of null (reading 'style')
}
// 不中断程序
console.log(111); // 111
}
fn()
- 我们也可以通过throw中断程序
function fn() {
try {
const p = document.querySelector('#p')
p.style.color = 'green'
} catch (err) {
console.log(err.message); // Cannot read properties of null (reading 'style')
throw new Error('选择器错误') // 中断程序,不再向下执行(finally里面要执行)
}
// 不执行
console.log(111);
}
fn()
-
加入
finally
function fn() { function fn() { try { const p = document.querySelector('#p') p.style.color = 'green' } catch (err) { console.log(err.message); throw new Error('选择器错误') // 中断程序,不再向下执行(finally里面要执行) } finally { console.log('执行完毕'); // 执行完毕 } // 不执行 console.log(111); } fn()
3.11.3.debugger
- 在断点调试时,如果代码非常多,我们可以在需要进行断点调试的地方写入debugger
- 当打开浏览器进行断点调试时,代码自动跳到debugger处(方便调试)
3.12.性能优化
3.12.1.防抖
单位时间内,频繁触发事件,只执行最后一次(从头开始执行)
- 执行下面代码,只要鼠标在盒子上移动时,盒子数字快速增加
<!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>
.box {
width: 200px;
height: 200px;
background-color: darkcyan;
}
</style>
</head>
<body>
<div class="box"></div>
<script>
const box = document.querySelector('.box')
let i = 1
function mouseMove() {
box.innerHTML = i++
}
box.addEventListener('mousemove', mouseMove)
</script>
</body>
</html>
- 但在更多时候,我们想让盒子从一处移动到另外一处,只会增加一次。
3.12.1.1.封装防抖函数
- 思路:
-
- 声明一个定时器变量
- 当鼠标每次滑动都判断是否有定时器,如果有定时器,先清除以前定时器
- 如果没有定时器,则开一个定时器
- 在定时器里面调用要执行的函数
- 实现:
``js
const box = document.querySelector(‘.box’)
let i = 1
function mouseMove() {
box.innerHTML = i++
}
function debounce(fn, t) {
// 1.声明一个定时器变量
let timer
return function () {
// 2.如果有定时器,先清除定时器
if (timer) clearTimeout(timer)
// 3.4.重新设置定时函数
timer = setTimeout(fn, t)
}
}
box.addEventListener(‘mousemove’, debounce(mouseMove, 200))
+ 这里比较难以理解的是为什么要return一个函数,其实这和我们要封装的 debounce()函数的用法有关
+ 平常我一般会写一个函数在`mousemove`后后面,例如:
```js
box.addEventListener('mousemove', function(){})
- 这样写我们其实是在这里定义了一个函数,而并没有调用这个函数,而是
mousemove
事件触发后再调用 - 但是我们要封装的 debounce()函数使用时是以调用的写法写入,在写入后,没有触发
mousemove
事件便会调用,而触发mousemove
事件却不会调用。 - 而我们加入return并返回函数后,return外的函数在解析完代码时就已经执行,触发
mousemove
事件后,调用的是return返回的函数。
上面的解释可能比较难懂,以下我来用代码演示以下执行过程吧(如果看不懂上面可直接跳到此处)
代码解析后会执行:
// 1.声明一个定时器变量
let timer
触发mousemove
事件后,执行return返回的函数:
box.addEventListener('mousemove', function () {
// 2.如果有定时器,先清除定时器
if (timer) clearTimeout(timer)
// 3.重新设置定时函数
timer = setTimeout(mouseMove, 200)
})
3.12.1.2.lodash防抖
- lodash防抖函数:
语法:
_.debounce(要执行的函数, 延后执行的时间)
<!-- 在使用时我已经将lodash的js文件下载到了本地 -->
<script src="../lodash.min.js"></script>
<script>
const box = document.querySelector('.box')
let i = 1
function mouseMove() {
box.innerHTML = i++
}
box.addEventListener('mousemove', _.debounce(mouseMove, 200))
</script>
- 将上面的代码运行,则当鼠标在盒子上移动时,盒子里的数字不会增加
- 当鼠标在盒子移动并悬停超过200毫秒时(或者鼠标移出盒子),盒子里的数字增加
3.12.2.节流
3.12.2.1.封装节流函数
- 思路:
-
- 声明一个定时器变量
- 当鼠标每次滑动都判断是否有定时器,如果有定时器已经开启,则不做操作
- 如果没有定时器,则开一个定时器
- 定时器到达时间,清空定时器
- 实现:
const box = document.querySelector('.box')
let i = 1
function mouseMove() {
box.innerHTML = i++
}
function debounce(fn, t) {
// 1.声明一个空定时器变量
let timer = null
return function () {
// 2.如果没有定时器,可以设置定时器
if (!timer) {
timer = setTimeout(function () {
fn()
// 4.冷却完,清空定时器
timer = null
}, t)
}
// 3.如果已经有定时器,不做操作
}
}
box.addEventListener('mousemove', debounce(mouseMove, 3000))
封装节流函数的思路与封装防抖函数思路相同,这里就不过多赘述。
代码改进:
- 值得一提的是,在测试这个代码时,我发现上面代码与lodash里面封装的节流函数有一定区别:我们上面封装的节流函数在第一次进入时盒子需要等3秒钟盒子数字才改变,而lodash封装函数第一次进入盒子,数字立马改变。
- 出现这个问题是,我们先调用函数,再定时;而在实际应用中,顺序恰恰应该相反。
- 其实还原lodash效果很简单,改一下if里的函数调用位置即可:
if (!timer) {
fn()
timer = setTimeout(function () {
// 4.冷却完,清空定时器
timer = null
}, t)
}
3.12.2.2.lodash节流
<!-- 在使用时我已经将lodash的js文件下载到了本地 -->
<script src="../lodash.min.js"></script>
<script>
const box = document.querySelector('.box')
let i = 1
function mouseMove() {
box.innerHTML = i++
}
box.addEventListener('mousemove', _.throttle(mouseMove, 3000))
</script>
- 将上面的代码运行,当鼠标在盒子上移动时,数字会增加
- 但3秒之内移动,数字不会再增加(3秒冷却)
3.12.2.3.区别
防抖是多次触发执行
最后一个
,节流是多次触发执行第一个
- 防抖 => 回城
- 节流 => 技能冷却