开发中我们经常需要复制一个对象或数组,如果直接使用赋值,当改变复制之后的对象或数组时,原对向也会改变,拷贝时我们需要改变复制之后的对象或数组的值,但不改变原对象或数组的值。
浅拷贝和深拷贝只针对引用类型
浅拷贝:拷贝的是地址
常见方法:
1.拷贝对象:Object.assign()/展开运算符{...obj}拷贝对象
2.拷贝数组:Array.prototype.concat()或者[...arr]
如果简单数据类型拷贝值,引用数据类型拷贝的是地址(简单理解,如果是单层对象,没问题,如果是多层对象就有问题)
- const obj={
- uname:'pink',
- age:18,
- family:{
- baby:'小pink'
- }
- }
- // const o=obj
- // console.log(o)
- // o.age=20
- // console.log(o)
- // console.log(obj)
- // 浅拷贝
- // const o={...obj}
- // console.log(o)
- // o.age=20
- // console.log(o)
- // console.log(obj)
- const o={}
- Object.assign(o,obj)
- o.age=20
- o.family.baby='老pink'
- console.log(o)
- console.log(obj)//obj里的family里的baby也改变了
深拷贝拷贝的是对象,不是地址
函数递归:
如果一个函数内部可以调用其本身,那么这个函数就是递归函数
- let i=1
- function fn(){
- console.log(`这是第${i}次`)
- if(i>=6){
- return
- }
- i++
- fn()
- }
- fn()
需求:
- function getTime(){
- document.querySelector('div').innerHTML=new Date().toLocaleString()
- setTimeout(getTime,1000)//1s之后执行该函数
- }
- getTime()
- const obj={
- uname:'pink',
- age:18,
- hobby:['乒乓球','足球']
- }
- const o={}
- //拷贝函数
- 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]===o.uname
- newObj[k]=oldObj[k]//不可使用点 点为添加属性,而k是一个变量
- }
-
- }
- }
- deepCopy(o,obj)//函数调用 两个参数o 新对象 obj 旧对象
- console.log(o)
- o.age=20
- o.hobby[0]='篮球'
- console.log(obj)
jsloadsh里面cloneDeep内部实现了深拷贝
- const obj={
- uname:'pink',
- age:18,
- hobby:['乒乓球','足球'],
- family:{
- baby:'小pink'
- }
- }
- const o=_.cloneDeep(obj)
- console.log(o)
- o.family.baby='老pink'
- console.log(obj)
- const obj={
- uname:'pink',
- age:18,
- hobby:['乒乓球','足球'],
- family:{
- baby:'小pink'
- }
- }
- //把对象转换为JSON字符串
- console.log(JSON.stringify(obj))
- const o=JSON.parse(JSON.stringify(obj))//先转换为字符串,后再转为对象,前后就变成了新的对象,就不会改变原来的值
- console.log(o)
- o.family.baby='123'
- console.log(obj)
异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致争个程序无法继续运行
总结:
- function fn(x,y){
- if(!x||!y){
- throw '没有参数传递进来'
- throw new Error('没有参数传递进来')
- }
- return x+y
- }
- console.log(fn())
我们可以通过try/catch捕获错误信息(浏览器提供的错误信息)try试试 catch拦住 finally 最后
总结:
- function fn(){
- try{
- // 可能发送错误的代码 要写到try里面
- const p=document.querySelector('.p')
- p.style.color='red'
- }catch(err){
- // 拦截错误 提示浏览器提供的错误信息 但是不中断程序的执行
- console.log(err.message)
- throw new Error('你看看,选择器错误了吧')
- // 需要加return中断程序
- // return
- }
- finally{
- //不管程序对不对,一定会执行的代码
- alert('弹出对话框')
- }
- }
在代码中间添加debugger,运行时会自动进入调试阶段
普通函数的调用方式决定了this的值,即谁调用this的值指向谁
普通函数没有明确调用者时this值为window,严格模式下没有调用者时this地值为undefined
- // 普通函数谁调用就指向谁
- console.log(this)//window
- function fn(){
- console.log(this)//window
- }
- window.fn()
- widow.setTimeout(function(){
- console.log(this)//window
- },1000)
- document.querySelector('button').addEventListener('click',function(){
- console.log(this)//指向button
- })
- const obj={
- sayHi:function(){
- console.log(this)//指向obj
- }
-
- }
- obj.sayHi()
箭头函数中的this与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在this
注意1:在开发中【使用箭头函数前要考虑函数中this的值】,事件回调函数使用箭头函数时,this为全局的window,因此DOM事件回调函数如果需要DOM对象的this,则不推荐使用箭头函数
注意2:同样由于箭头函数this的原因,基于原型的面向对象也不推荐采用箭头函数
总结:
- 函数内不存在this,沿用上一级的
- 不适用:构造函数,原型函数,字面量对象中函数,dom事件函数等等
- 适用:需要使用上层this的地方
JavaScript中还允许指定函数中this的指向,有3个方法可以动态指定普通函数中this的指向
使用call方法调用函数,同时指定被调用函数中this的值
语法:
fun.call(thisArg,arg1,arg2,...)
- thisArg:在fun函数运行时指定的this值
- arg1,arg2:传递的其他参数
- 返回值就是函数的返回值,因为他就是调用函数
- const obj={
- uname:'pink'
- }
- function fn(x,y){
- console.log(this)//window
- console.log(x+y)
- }
- // 1.调用函数
- // 2.改变this指向
- fn.call(obj,1,2)//将fn this指向obj,并向fn中传入实参1,2
使用apply方法调用函数,同时指定被调用函数中this的值
语法:
fun.apply(thisArg,[argsArray])
- thisArg:在fun函数运行时指定的this值
- argsArray:传递的值,必须包含在数组里面,传入的实参必须是数组
- 返回值就是函数的返回值,因为它就是调用函数
- 因此apply主要跟数组有关系,比如使用Math,max()求数组的最大值
- const obj={
- age:18
- }
- function fn(x,y){
- console.log(this)//{age:18}
- console.log(x+y)
- }
- // 1.调用函数
- // 2.改变this指向
- fn.apply(obj,[1,2])//改变this指向obj并且传入一个数组,但是要用参数为数组的长度来接收
- // 3.返回值 本身就是在调用函数 所以返回值就是函数的返回值
- // 使用场景:求数组的最大值
- const arr=[100,44,77]
- const max=Math.max.apply(Math,arr)
- const min=Math.min.apply(Math,arr)
- console.log(max,min)
- console.log(Math.max(...arr))
- //求数组最大值的方法 遍历for循环 使用apply 使用展开运算符
call和apply的区别
- 都是调用函数,都能改变this的指向
- 参数不一样,apply传递的必须是数组
bind()方法不会调用函数,但是能改变函数内部this指向
语法:
fun.bind(thisArg,arg1,arg2,...)
- thisArg:在fun函数运行时指定的this值
- arg1,arg2:传递的其他参数
- 返回由指定的this值和初始化参数改造的原函数拷贝(新函数)
- 因此当我们只是想改变this指向,并且不想调用这个函数的时候,可以使用bind,比如改变定时器内部的this指向
- 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()
- // 需求 有一个按钮 点击里面就禁用 2秒钟之后开启
- const btn=document.querySelector('button')
- btn.addEventListener('click',function(){
- // 禁用按钮
- this.disabled=true
- window.setTimeout(function(){//匿名函数this指向window
- // 在这个函数里面 我们要this由原来的window改为btn
- this.disabled=false
- }.bind(this),2000)
- })
相同点:都可以改变函数内部的this指向
区别点:
主要应用场景:
防抖:单位时间内,频繁触发事件,只执行最后一次
使用场景:
要求:鼠标在盒子上移动,鼠标停止500ms之后,里面的数字才会变化+1
实现方式:
1.loadsh提供的防抖来处理
- 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>Documenttitle>
- <style>
- .box {
- width: 500px;
- height: 500px;
- background-color: #ccc;
- color: #fff;
- text-align: center;
- font-size: 100px;
- }
- style>
- head>
-
- <body>
- <div class="box">div>
- <script src="../js进阶知识点/lodash.min.js">script>
- <script>
- // 利用防抖实现性能优化
- // 需求:鼠标在盒子上移动,里面的数字就会变化
- const box=document.querySelector('.box')
- let i=1
- function mouseMove(){
- box.innerHTML=i++
- }
- // 添加事件
- // box.addEventListener('mousemove',mouseMove)
- // 利用Lodash库实现防抖-500ms之后采取+1
- // 语法:_.deboumce(fun,时间)
- box.addEventListener('mousemove',_.debounce(mouseMove,500))
-
- script>
- body>
-
- html>
2.手写一个防抖函数处理
核心思路:防抖的核心思路就是利用定时器(setTimeout)l来实现
- // 利用防抖实现性能优化
- // 需求:鼠标在盒子上移动,里面的数字就会变化
- const box=document.querySelector('.box')
- let i=1
- function mouseMove(){
- box.innerHTML=i++
- }
-
- function debounce(fn,t){
- let timer
- return function(){
- //2.3.4
- if(timer)clearTimeout(timer)
- timer=setTimeout(function(){
- fn()//加小括号调用fn函数
- },t)
- }
- }
- box.addEventListener('mousemove',debounce(mouseMove,500))
节流:单位时间内,频繁触发事件,只执行一次
使用场景:高频事件:鼠标移动 mousemove、页面尺寸缩放 resize、滚动条滚动scroll等
要求:鼠标在盒子上移动,不管移动多少次,每隔500ms才+1
实现方式:
- // 利用lodash库实现节流 500ms之后采取+1
- // 语法:_.throttle(fun,时间)
- box.addEventListener('mousemove',_.throttle(mouseMove,2000))//2s之内最多执行一次函数,不管滑动多少次
鼠标在盒子上移动,不管移动多少次,每隔500ms才+1
核心思路:
节流的核心就是利用定时器(setTimeout)来实现
- // 利用防抖实现性能优化
- // 需求:鼠标在盒子上移动,里面的数字就会变化
- const box=document.querySelector('.box')
- let i=1
- function mouseMove(){
- box.innerHTML=i++
- }
- // 添加事件
- // box.addEventListener('mousemove',mouseMove)
- // 利用Lodash库实现防抖-500ms之后采取+1
- // 语法:_.deboumce(fun,时间)
- // box.addEventListener('mousemove',_.debounce(mouseMove,500))
- // 手写防抖函数
- // 核心是利用setTimeout定时器来实现
- // function debounce(fn,t){
- // let timer
- // return function(){
- // //2.3.4
- // if(timer)clearTimeout(timer)
- // timer=setTimeout(function(){
- // fn()//加小括号调用fn函数
- // },t)
- // }
- // }
- // box.addEventListener('mousemove',debounce(mouseMove,500))
- ///
- // 利用lodash库实现节流 500ms之后采取+1
- // 语法:_.throttle(fun,时间)
- // box.addEventListener('mousemove',_.throttle(mouseMove,2000))//2s之内最多执行一次函数,不管滑动多少次
- function throttle(fn,t){
- let timer=null
- return function(){
- if(!timer){
- timer=setTimeout(function(){
- fn()
- // 在定时器里面清除定时器
- //清空定时器当函数执行完毕之后再清除定时器
- timer=null
- // 在setTimeout中是无法删除定时器的,因为定时器还在运作,所以使用timer=null
- },t)
- }
- }
- }
- box.addEventListener('mousemove',throttle(mouseMove,2000))
| 性能优化 | 说明 | 使用场景 |
|---|---|---|
| 防抖 | 单位时间内,频繁触发事件,只执行最后一次 | 搜索框搜索输入,邮箱验证输入检测 |
| 节流 | 单位时间内,频繁触发事件,只执行一次 | 高频事件:鼠标移动mousemove,页面尺寸缩放resize、滚动条滚动scroll等等 |
页面打开,可以记录上一次的视频播放位置
分析:
两个事件:
思路:
- // 1.获取元素 要对视频进行操作
- const video=document.querySelector('video')
- // 当前播放位置发生改变时触发
- // video.ontimeupdate=function(){
- // console.log(11)
- // }
- video.ontimeupdate=_.throttle(()=>{
- console.log(video.currentTime)//获得当前的时间
- localStorage.setItem('currentTime',video.currentTime)//把当前的时间存储到本地存储中
- },1000)//1s之后调用一次函数
- // 打开页面触发事件,就从本地存储里面取出记录的时间,赋值给vodeo.currentTime
- video.onloadeddata=()=>{
- // console.log(111)
- video.currentTime=localStorage.getItem('currentTime')||0//前面为假就执行后面的代码
- }
以上笔记均为学习哔站pink老师视频所得!