ECMAScript 6
深浅拷贝
深浅拷贝
直接赋值 vs 浅拷贝 vs 深拷贝
直接赋值:只要是对象,都会相互影响,因为直接拷贝对象栈里的地址 浅拷贝:若只是一层对象,不相互影响;若是多层对象,会互相影响
浅拷贝
拷贝的是 地址 常见方法 ① 拷贝对象:Object.assign()
或 展开运算符 {...obj}
② 拷贝数组:Array.prototype.concat()
或 [...arr]
const obj = {
uname : 'pink' ,
age : 18
}
const o = { }
Object. assign ( o, obj)
o. age = 20
console. log ( o)
console. log ( obj)
const o1 = { ... obj }
o1. age = 20
console. log ( o1)
console. log ( obj)
存在问题:若是简单数据类型拷贝值,引用数据类型拷贝的是地址 【即若是单层对象,则ok;若是多层对象,则有问题】 【拷贝对象后,里面的属性值若是简单数据类型,则直接拷贝值;若是引用数据类型,则拷贝地址】const obj = {
uname : 'pink' ,
age : 18 ,
family : {
baby : '小pink'
}
}
const o = { }
Object. assign ( o, obj)
o. age = 20
o. family. baby = '老pink'
console. log ( o)
console. log ( obj)
const o1 = { ... obj }
o1. age = 20
o1. family. baby = '老pink'
console. log ( o1)
console. log ( obj)
深拷贝
拷贝的是 对象【非地址】 常见方法 ① 递归【易出现“栈溢出(stack overflow),故必须加推出条件 return”】 ② lodash
或 cloneDeep
③ JSON.stringfy()
递归< div> < / div>
< script>
function getTime ( ) {
document. querySelector ( 'div' ) . innerHTML = new Date ( ) . toLocaleString ( )
setTimeout ( getTime, 1000 )
}
getTime ( )
< / script>
< div> < / div>
< script>
const obj = {
uname : 'pink' ,
age : 18 ,
hobby : [ '乒乓球' , '足球' ] ,
family : { baby : '小pink' }
}
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 {
newObj[ k] = oldObj[ k]
}
}
}
deepCopy ( o, obj)
console. log ( o)
o. age = 20
o. hobby[ 0 ] = '篮球'
o. family. baby = '老pinkb'
console. log ( obj)
< / script>
lodash
< script src= "./lodash.min.js" > < / script>
< script>
const obj = {
uname : 'pink' ,
age : 18 ,
hobby : [ '篮球' , '足球' ] ,
family : {
baby : '小baby'
}
}
const o = _. cloneDeep ( obj)
console. log ( o)
o. family. baby = '老pink'
console. log ( obj)
< / script>
JSON.stringfy()
const obj = {
uname : 'pink' ,
age : 18 ,
hobby : [ '篮球' , '足球' ] ,
family : {
baby : '小baby'
}
}
const o = JSON . parse ( JSON . stringify ( obj) )
console. log ( o)
o. family. baby = '老pink'
console. log ( obj)
异常处理
throw 抛异常
try / catch 捕获异常
debugger
示例const arr = [ 1 , 3 , 5 ]
const newArr = arr. map ( ( item, index ) => {
debugger
console. log ( item)
console. log ( index)
return item + 10
} )
console. log ( newArr)
处理this
this 指向
普通函数 this 指向
普通函数的调用方式决定了 this 的值【即谁调用,指向谁】 普通函数没有明确调用者时,this 值为 window;严格模式下没有调用者时,this 值为 undefined 示例< button> 点击< / button>
< script>
function sayHi ( ) {
console. log ( this )
}
const sayHello = function ( ) {
console. log ( this )
}
sayHi ( )
sayHello ( )
const user = {
name : '小明' ,
walk : function ( ) {
console. log ( this )
}
}
user. sayHi = sayHi
user. sayHello = sayHello
user. sayHi ( )
user. sayHello ( )
'use strict'
function fn ( ) {
console. log ( this )
}
fn ( )
document. querySelector ( 'button' ) . addEventListener ( 'click' , function ( ) {
console. log ( this )
} )
< / script>
箭头函数 this 指向
箭头函数中的 this 与普通函数完全不同,也不受调用方式的影响。事实上,箭头函数中并 不存在 this ① 箭头函数默认绑定外层 this 的值,故在箭头函数中 this 的值和外层的 this 是一样的 ② 箭头函数中的 this 引用的就是最近作用域中的 this ③ 向外层作用域中,一层一层查找 this,直达有 this 的定义 注意事项 ① DOM 事件回调函数如果里面需要 DOM 对象的 this,不推荐使用箭头函数 ② 基于原型的面向对象不推荐使用箭头函数 ③ 构造函数不推荐使用箭头函数 ④ 字面量对象中函数不推荐使用箭头函数 示例< button class = "btn" > 点击< / button>
< script>
console. log ( this )
console. log ( '---------------------------------' )
const sayHi = function ( ) {
console. log ( this )
}
sayHi ( )
const user = {
name : '小明' ,
walk : ( ) => {
console. log ( this )
}
}
user. sayHi = sayHi
user. sayHi ( )
console. log ( '---------------------------------' )
const btn = document. querySelector ( '.btn' )
btn. addEventListener ( 'click' , ( ) => {
console. log ( this )
} )
btn. addEventListener ( 'click' , function ( ) {
console. log ( this )
} )
console. log ( '---------------------------------' )
function Person ( ) {
}
Person . prototype. walk = ( ) => {
console. log ( '人都要走路...' )
console. log ( this )
}
const p1 = new Person ( )
p1. walk ( )
< / script>
改变 this
JS 中允许指定函数中 this 的指向,有 3 个方法可动态指定普通函数中 this 的指向
call()
使用 call
方法调用函数,同时指定被调用函数中 this 的值 fun.call (thisArg, arg1, arg2, ...)
① thisArg
:在 fun 函数运行时指定的 this 值 ② arg1, arg2
:传递的其他参数 ③ 返回值就是函数的返回值,因为它就是调用函数示例const obj = {
uname : 'pink'
}
function fn ( x, y ) {
console. log ( this )
console. log ( x + y)
}
fn . call ( obj, 1 , 2 )
apply()
使用 apply
方法调用函数,同时指定被调用函数中 this 的值 fun.apply (thisArg, [argsArray])
① thisArg
:在 fun 函数运行时指定的 this 值 ② argsArray
:传递的值,必须包含在 数组 里【apply
主要跟数组有关系,比如使用 Math.max()
求数组的最大值】 ③ 返回值就是函数的返回值,因为它就是调用函数 注:apply
不改变指向时,写 自己 或 null
,但不可省略示例function counter ( x, y ) {
return x + y
}
let result = counter . apply ( null , [ 5 , 10 ] )
console. log ( result)
const obj = {
age : 18
}
function fn ( x, y ) {
console. log ( this )
console. log ( x + y)
}
fn . apply ( obj, [ 1 , 2 ] )
const arr = [ 3 , 5 , 2 , 9 ]
console. log ( Math. max . apply ( null , arr) )
console. log ( Math. max . apply ( Math, arr) )
console. log ( Math. max ( ... arr) )
bind()
bind
方法不会调用函数,但能改变函数内部中 this 指向【如 改变定时器内部的 this 指向】fun.bind (thisArg, arg1, arg2, ...)
① thisArg
:在 fun 函数运行时指定的 this 值 ② arg1, arg2
:传递的其他参数 ③ 返回由指定的 this 值和初始化参数改造的 原函数拷贝(新函数) 示例function sayHi ( ) {
console. log ( this )
}
let user = {
name : '小明' ,
age : 18
}
let sayHello = sayHi . bind ( user)
sayHello ( )
< button> 发送短信< / button>
< script>
const obj = {
age : 18
}
function fn ( ) {
console. log ( this )
}
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 . disabled = false
} . bind ( btn) , 2000 )
} )
document. querySelector ( 'button' ) . addEventListener ( 'click' , function ( ) {
this . disabled = true
window. setTimeout ( function ( ) {
this . disabled = false
} . bind ( this ) , 2000 )
} )
< / script>
性能优化
防抖 debounce
单位时间内,频繁触发事件,只执行最后一次 使用场景: ① 搜索框搜索输入。只需用户最后一次输入完,再发送请求 ② 手机号、邮箱验证输入检测 案例:鼠标在盒子上移动,里面的数字就会变化 要求:鼠标在盒子上移动,鼠标停止 500ms 之后,里面的数字才会变化 +1 方案:① lodash 提供的防抖来处理 ② 手写一个防抖函数来处理< body>
< button class = "box" > 按钮< / button>
< script src= "./js/lodash.min.js" > < / script>
< script>
const box = document. querySelector ( '.box' )
let i = 1
function mouseMove ( ) {
box. innerHTML = i++
}
function debounce ( fn, t ) {
let timer
return function ( ) {
if ( timer)
clearTimeout ( timer)
timer = setTimeout ( function ( ) {
fn ( )
} , t)
}
}
box. addEventListener ( 'mousemove' , debounce ( mouseMove, 500 ) )
< / script>
< / body>
节流 throttle
单位时间内,频繁触发事件,只执行一次 使用场景: 高频事件:鼠标移动 mousemove、页面尺寸缩放 resize、滚动条滚动 scroll 等 案例:鼠标在盒子上移动,里面的数字就会变化 要求:鼠标在盒子上移动,不管移动多少次,每隔 500ms 才 +1 方案:① lodash 提供的节流函数来处理 ② 手写一个节流函数来处理< body>
< button class = "box" > 按钮< / button>
< script src= "./js/lodash.min.js" > < / script>
< script>
const box = document. querySelector ( '.box' )
let i = 1
function mouseMove ( ) {
box. innerHTML = i++
}
function throttle ( fn, t ) {
let timer = null
return function ( ) {
if ( ! timer) {
timer = setTimeout ( function ( ) {
fn ( )
timer = null
} , t)
}
}
}
box. addEventListener ( 'mousemove' , throttle ( mouseMove, 500 ) )
< / script>
< / body>
setTimeout 注意事项let timer = null
timer = setTimeout ( ( ) => {
clearTimeout ( timer)
console. log ( timer)
} , 1000 )
防抖 vs 节流
防抖
说明:单位时间内,频繁触发事件,只执行最后一次 【指触发事件后在 n 秒内函数只能执行一次。若在 n 秒内又触发了,则会重新计算函数执行时间】 使用场景:搜索框搜索输入、手机号、邮箱验证输入检测
节流
说明:单位时间内,频繁触发事件,只执行一次 【指连续触发事件,但在 n 秒内只执行一次函数】 使用场景:鼠标移动 mousemove、页面尺寸缩放 resize、滚动条滚动 scroll
综合案例
页面打开,可以记录上一次的视频播放位置 两事件: ① ontimeupdate
事件在视频/音频 (audio/video) 当前的播放位置发送改变时触发 ② onloadeddata
事件在当前帧的数据加载完成且还没有足够的数据播放视频/音频 (audio/video) 的下一帧时触发 案例分析 谁需要节流? ontimeupdate:触发频次太高了,我们可以设定 1 s 触发一次 代码
< 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" >
< meta name= "referer" content= "never" >
< title> 综合案例< / title>
< style>
* {
padding : 0 ;
margin : 0 ;
box- sizing: border- box;
}
. container {
width : 1200px;
margin : 0 auto;
}
. video video {
width : 100 % ;
padding : 20px 0 ;
}
. elevator {
position : fixed;
top : 280px;
right : 20px;
z- index: 999 ;
background : #fff;
border : 1px solid #e4e4e4;
width : 60px;
}
. elevator a {
display : block;
padding : 10px;
text- decoration: none;
text- align: center;
color : #999 ;
}
. elevator a. active {
color : #1286ff;
}
. outline {
padding- bottom: 300px;
}
< / style>
< / head>
< body>
< div class = "container" >
< div class = "header" >
< a href= "http://pip.itcast.cn" >
< img src= "https://pip.itcast.cn/img/logo_v3.29b9ba72.png" alt= "" >
< / a>
< / div>
< div class = "video" >
< video src= "https://v.itheima.net/LapADhV6.mp4" controls> < / video>
< / div>
< div class = "elevator" >
< a href= "javascript:;" data- ref= "video" > 视频介绍< / a>
< a href= "javascript:;" data- ref= "intro" > 课程简介< / a>
< a href= "javascript:;" data- ref= "outline" > 评论列表< / a>
< / div>
< / div>
< script src= "https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js" > < / script>
< script>
const video = document. querySelector ( 'video' )
video. ontimeupdate = _. throttle ( ( ) => {
localStorage. setItem ( 'currentTime' , video. currentTime)
} , 1000 )
video. onloadeddata = ( ) => {
video. currentTime = localStorage. getItem ( 'currentTime' )
}
< / script>
< / body>