文章目录
JS高阶技巧
- 深入this学习,直到如何判断this指向和改变this指向
- 直到在JS中如何处理异常,学习深浅拷贝,理解递归
深浅拷贝
直接赋值,复制的地址,会影响原来的对象。所以有了深浅拷贝
首先浅拷贝和深拷贝只针对引用类型
浅拷贝
浅拷贝拷贝的是地址,浅浅地拷贝了一层
总结:
直接赋值和浅拷贝有什么区别?
- 直接赋值的方法,只要是对象,都会相互影响,因为是直接拷贝对象栈里面的地址
- 浅拷贝如果是一层对象,不相互影响,如果出现多层对象拷贝还会相互影响
浅拷贝怎么理解?
- 拷贝对象之后,里面的属性值是简单数据类型直接拷贝值
- 如果属性值是引用数据类型则拷贝的是地址
深拷贝
深拷贝拷贝的是对象,不是地址
常见方法(深拷贝三种实现方式):
- 通过递归实现深拷贝
- lodash/cloneDeep
- 通过JSON.stringfy()实现
通过递归实现深拷贝
利用递归函数实现setTimeout模拟setInterval效果
需求:
页面每隔1s输出当前时间
输出当前时间可以使用:new Date().toLocaleString()
<!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>
</head>
<body>
<div></div>
<script>
function getTime(){
document.querySelector('div').innerHTML=new Date().toLocaleString()
setTimeout(getTime,1000)
}
getTime()
</script>
</body>
</html>
简易深拷贝的函数 (对象,数组,二维数组,但如果对象数组之间相互引用,这个代码无法实现)
//拷贝函数 简易的深拷贝(对象,数组,二维数组,但如果对象数组之间相互引用,这个代码无法实现)
function deepCopy(newObj, oldObj) {
for (let k in oldObj) {
//处理数组的问题 把数组再次遍历 //递归思想
//一定要把数组判断放在前面,不能颠倒。因为数组也属于对象,万物皆对象
if (oldObj[k] instanceof Array) {
newObj[k]=[]
deepCopy(newObj[k], oldObj[k])
}
//处理对象的问题
else if (oldObj[k] instanceof Object) {
newObj[k]={}
deepCopy(newObj[k], oldObj[k])
}
else {
//k 属性名 oldObj[k] 属性值
newObj[k] = oldObj[k] //到这儿只是浅拷贝,还需要判断数组这种复杂类型,所以加了一个if else
}
}
}
JS库lodash里面cloneDeep内部实现了深拷贝
lodash是一个一致性,模块化,高性能的JavaScript实用工具库。
直接引入,使用
<script src="../js/lodash.min.js"></script>
<script>
const obj = {
uname: 'pink',
age: 18,
hobby: ['羽毛球', '滑板',['111','111']],
family:{
baby:'小pink'
}
}
const o= _.cloneDeep(obj)
console.log(o);
o.family.baby="老pink"
console.log(obj);
</script>
通过JSON.stringfy()实现深拷贝
<script>
const obj = {
uname: 'pink',
age: 18,
hobby: ['羽毛球', '滑板',['111','111']],
family:{
baby:'小pink'
}
}
// JSON.stringify()//把对象转化为JSON 字符串, 打印出来的是一堆字符串
// JSON.parse()//在把字符串转化为对象
const o=JSON.parse(JSON.stringify(obj))
console.log(o);
o.family.baby="老pink"
console.log(obj);
</script>
异常处理
异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行
1.throw抛异常
function fn(x,y){
if(!x||!y){
//throw '没有参数传递进来'
throw new Error('没有参数传递过来')
}
return x+y
}
console.log(fn());
总结:
- throw抛出异常信息,程序也会终止执行
- throw后面跟着的是错误提示信息
- Error对象配合throw使用,能够设置更详细的错误信息
2.try/catch捕获异常
我们可以通过try/catch捕获错误信息(浏览器提供的错误信息) try 试试 catch 拦住 finally 最后
<p>123</p>
<script>
function fn() {
try {
//可能发生错误的代码 要写到try里面
const p = document.querySelector('.p')
p.style.color = 'red'
} catch (err) {
//拦截错误,提示浏览器提供的错误信息,但是不中断程序的进程
console.log(err.message);
throw new Error ('你看看,选择器错误了吧') //如果觉得浏览器提示的错误不够明显,可以搭配throw使用
//想要中断程序,加上return
// return
}
finally{
//不管程序对不对,一定会执行的代码
alert('我无条件的执行')
}
console.log(11);
}
fn()
</script>
总结:
- try/catch用于捕获错误信息
- 将预估可能发生错误的代码写在try代码段中
- 如果try代码段中出现错误后,会执行catch代码段,并截获到错误信息
- finally不管是否有错误,都会执行
3.debugger
与断点一样,不过会直接跳到断点处
处理this
this是JavaScript最具“魅惑”的知识点,不同的应用场合this的取值可能会有意向不到的结果。
this指向
普通函数this指向
普通函数的调用方式决定了this的值,即**【谁调用this的值指向谁】**
普通函数没有明确调用者时this值为window,严格模式(‘use strict’)下没有调用者时this的值为undefined。
<button>点击</button>
<script>
//普通函数: 谁调用我,this就指向谁
console.log(this); //window
function fn(){
console.log(this); //window
}
window.fn()
window.setTimeout(function(){console.log(this);},1000)
document.querySelector('button').addEventListener('click',function(){
console.log(this);//指向button
})
const obj ={
sayHi:function(){
console.log(this);//指向obj
}
}
obj.sayHi()
</script>
箭头函数this指向
箭头函数中的this与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在this
像原型和构造函数中尽量不要使用箭头函数,因为原型和构造函数中要用到大量的this指向实例对象
- 箭头函数会默认帮我们绑定外层this的值,所以在箭头函数中this的值和外层的this是一样的
- 箭头函数中的this引用的就是最近作用域中的this
- 向外层作用域中,一层一层查找this,直到有this的定义
总结:
- 函数内不存在this,沿用上一级的。向外层作用域中,一层一层查找this,直到有this的定义
- 不适用场景: 构造函数,原型函数,DOM事件函数等等
- 适用场景: 需要使用上层this的地方
改变this
JavaScript中还允许指定函数中this的指向,有3个方法可以动态指定普通函数中this的指向
1.call() (了解即可,使用场景较少)
使用call方法调用函数,同时指定被调用函数中this的值
const obj={
uname:"pink"
}
function fn(x,y){
console.log(this);
}
//1.调用函数
//2.改变this指向
//fn()
fn.call(obj,1,2)
2.apply()
使用apply方法调用函数,同时指定被调用函数中this的值
使用场景:求数组最大值
const obj={
age:18
}
function fn(x,y){
console.log(this);
console.log(x+y);
}
//1.调用函数
//2.改变this指向
// fn.apply(this指向谁,数组参数)
fn.apply(obj,[1,2])
//3.返回值 本身就是在调用函数,所以返回值就是函数的返回值
//使用场景: 求数组最大值
// const max=Math.max(1,2,3)
// console.log(max); //之前的写法
const arr=[100,2,22]
const max=Math.max.apply(Math,arr)
const min=Math.min.apply(Math,arr)
console.log(max,min);
**小结:**求数组最大值(已有至少三种):
- 遍历数组求最大值
- 扩展运算符
- apply方法
3.bind()
bind方法不会调用函数,但是能改变函数内部的this指向
因此当我们只想改变this指向,并且不想调用这个函数的时候,可以使用bind,比如改变定时器内部的this指向
<button>点击</button>
<script>
const obj={
age:18
}
function fn(){
console.log(this);
}
//1.bind 不会调用函数
//2.能改变this指向
//3.返回值是个函数, 但是这个函数里面的this是更改过的obj
const fun=fn.bind(obj)
console.log(fun);
fun()
const btn=document.querySelector('button')
btn.addEventListener('click',function(){
//禁用按钮
this.disabled=true
window.setTimeout(function(){
//在这个普通函数里面,我们要this由原来的的window改为btn
this.disabled=false
}.bind(btn),2000) //这里写this是调用上一级setTimeout的window 用bind解决问题
})
//需求:有一个按钮,点击里面就禁用,2秒之后开启
</script>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P7YKmk7T-1681567960123)(C:\Users\86184\Desktop\4.3-4.14\call,apply,bind.png)]
性能优化
防抖(debounce)
单位时间内,频繁触发事件,只执行最后一次
使用场景:
搜索框搜索使用。只需用户最后一次输入完,再发送请求。
手机号,邮箱验证输入检测
做个小案例,不加防抖,鼠标滑动,盒子内的数加1。
<!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>
.box{
width: 200px;
height: 200px;
background-color: pink;
}
</style>
</head>
<body>
<div class="box"></div>
<script>
//需求:鼠标在盒子上移动,里面的数字就会变化+1
const box=document.querySelector('.box')
let i=1
function mouseMove(){
box.innerHTML=i++
//如果里面存在大量消耗性能的代码,就会使性能大大降低
}
//添加事件
box.addEventListener('mousemove',mouseMove)
</script>
</body>
</html>
加上防抖:鼠标在盒子上移动,鼠标停止500ms之后,里面的数字才会变化+1
实现方式(两种):
1.lodash提供的防抖处理
2.手写一个防抖函数来处理(核心思路:利用定时器setTimeout来实现)
<!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>
.box{
width: 200px;
height: 200px;
background-color: pink;
}
</style>
</head>
<body>
<div class="box"></div>
<script src="../js/lodash.min.js"></script>
<script>
//利用防抖实现性能优化
//需求:鼠标在盒子上移动,里面的数字就会变化+1
const box=document.querySelector('.box')
let i=1
function mouseMove(){
box.innerHTML=i++
//如果里面存在大量消耗性能的代码,就会使性能大大降低
}
// //添加事件
// box.addEventListener('mousemove',mouseMove)
//方法一.利用Lodash库实现防抖 -500ms之后采取+1
//语法:_.debounce(fun,时间)
// box.addEventListener('mousemove',_.debounce(mouseMove,500))
//方法二.手写一个防抖函数来处理(核心思路:利用定时器setTimeout来实现)
//1.声明定时器变量
//2.每次鼠标移动(事件触发)的时候都要先判断是否有定时器,如果有先清除之前的定时器
//3.如果没有定时器,则开启定时器,存入到定时器变量中
//4.定时器里面写函数调用
function debounce(fn,t){
let timer
//return 返回一个匿名函数
return function(){
if(timer) clearTimeout(timer)
timer =setTimeout(function(){
fn() //加小括号调用fn函数
},t)
}
}
box.addEventListener('mousemove',debounce(mouseMove,500))
</script>
</body>
</html>
节流(throttle)
单位时间内,频繁触发事件,只执行一次
使用场景:
高频事件:鼠标移动mousemove、页面尺寸缩放resize、滚动条滚动scroll等等
加上节流:鼠标在盒子上移动,不管移动多少次,每隔500ms里面的数字才会变化+1
实现方式(两种):
1.lodash提供的节流处理
2.手写一个节流函数来处理(核心思路:利用定时器setTimeout来实现)
<!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>
.box {
width: 200px;
height: 200px;
background-color: pink;
}
</style>
</head>
<body>
<div class="box"></div>
<script src="../js/lodash.min.js"></script>
<script>
//利用节流实现性能优化
//需求:鼠标在盒子上移动,不管移动多少次,每隔500ms里面的数字才会变化+1
const box = document.querySelector('.box')
let i = 1
function mouseMove() {
box.innerHTML = i++
//如果里面存在大量消耗性能的代码,就会使性能大大降低
}
// //添加事件
// box.addEventListener('mousemove',mouseMove)
//方法一.利用Lodash库实现防抖 -500ms之后采取+1
//语法:_.throttle(fun,时间)
// box.addEventListener('mousemove',_.throttle(mouseMove,3000))
// 方法二.手写一个节流函数来处理(核心思路:利用定时器setTimeout来实现)
// 1.声明定时器变量
// 2.每次鼠标移动(事件触发)的时候都要先判断是否有定时器,如果有先清除之前的定时器
// 3.如果没有定时器,则开启定时器,存入到定时器变量中
// 4.定时器里面写函数调用
function throttle(fn, t) {
let timer = null
//return 返回一个匿名函数
return function () {
if (!timer) {
timer = setTimeout(function () {
fn() //加小括号调用fn函数
//清空定时器
timer=null
}, t)
}
}
}
box.addEventListener('mousemove', throttle(mouseMove, 3000))
</script>
</body>
</html>
清楚定时器问题
let timer =null
timer =setTimeout(()=>{
clearTimeout(timer)
console.log(timer);//结果为1
},1000)
//在setTimeout中是无法删除定时器的,因为定时器还在运作,所以使用timer=null 而不是clearTimeout(timer)
执行上下文栈
JavaScript引擎执行是顺序执行的,但是它并不是一行一行地分析和执行代码,而是一段一段地分析和执行的。当执行每一段代码之前,他会先做一个“准备工作”。
更专业的说法就是创建“执行上下文(execution context)”
例如:变量声明的提升,函数声明的整体提升等
**问题:**我们写的函数很多,JavaScript引擎如何创建和管理这么多执行上下文呢?
JavaScript引擎通过创建执行上下文栈(Execution context stack,ECS)来管理执行上下文。
当JavaScript开始解释执行代码的时候,最先遇到的就是全局代码,所以它会向执行上下文栈压入一个全局执行上下文,我们用globalContext表示,并且只有当整个程序运行结束时,ECStack才会被清空,所以程序结束之前,ECStack最底部永远有个globalContext。
当JavaScript执行一个函数时,它就会创建一个执行上下文,并且压入执行上下文栈。当函数执行完毕时,就会将函数的执行上下文从栈里弹出。
进程与线程
进程(process)
程序的一次执行,它占有一片独立的内存空间
可以通过Windows任务管理器查看进程
线程(thread)
是进程内一个独立执行单元
是程序执行的一个完整流程
是CPU的最小调度单元
图解
相关知识
- 应用程序必须运行在某个进程的某个线程上
- 一个进程中至少有一个运行的线程:主线程,进程启动后自动创建
- 一个进程中也可以同时运行多个线程,我们会说程序是多线程运行的
- 一个进程内的数据可以供其中的多个线程直接共享
- 多个线程之间的数据不能直接共享
- 线程池(thread pool)保存多个线程对象的容器,实现线程对象的反复利用
问题:
1.何为多进程与多线程?
多进程运行:一应用程序可以同时启动个实例运行
多线程:在一个进程内,同时有多个线程运行
2.比较单线程与多线程
多线程
优点:能够有效提升CPU的利用率
缺点:创建多线程开销、线程间切换开销、死锁与状态同步问题
单线程
优点:顺序编程简单易懂
缺点:效率低
3.JS是单线程运行的,但使用H5中的Web Worker是可以多线程运行
4.浏览器运行是多线程的
浏览器有单进程(firefox、老版IE)
也有多进程(Chrome 、新版IE)