赞
踩
目标:了解作用域对程序执行的影响及作用域链的查找机制,使用闭包函数创建隔离作用域避免全局变量污染。
- 作用域(scope)规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问,
- 作用域分为:
局部作用域
全局作用
局部作用域分为函数作用域和块作用域。
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。
总结:
- 函数内部声明的变量,在函数外部无法被访问
- 函数的参数也是函数内部的局部变量
- 不同函数内部声明的变量无法互相访问
- 函数执行完毕后,函数内部的变量实际被清空了
在 JavaScript 中使用 { } 包裹的代码称为代码块,代码块内部声明的变量外部将**【有可能】**无法被访问。
总结:
- let 声明的变量会产生块作用域,var 不会产生块作用域
- const 声明的常量也会产生块作用域
- 不同代码块之间的变量无法互相访问
- 推荐使用 let 或 const
总结
- 局部作用域分为哪两种?
函数作用域 函数内部
块级作用域 {}- 局部作用域声明的变量外部能使用吗?
不能
<script> 标签 和 .js 文件 的**【最外层】**就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。
全局作用域中声明的变量,任何其它作用域都可以被访问
注意:
- 为 window 对象动态添加的属性默认也是全局的,不推荐!
- 函数中未使用任何关键字声明的变量为全局变量,不推荐!!!
- 尽可能少的声明全局变量,防止全局变量被污染
总结
- 全局作用域有哪些?
<script> 标签内部
.js 文件- 全局作用域声明的变量其他作用域能使用吗?
相当能
JavaScript 中的作用域是程序被执行时的底层机制,了解这一机制有
助于规范代码书写习惯,避免因作用域导致的语法错误。
作用域链本质上是底层的变量查找机制。
在函数被执行时,会优先查找当前函数作用域中查找变量
如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域
总结:
- 嵌套关系的作用域串联起来形成了作用域链
- 相同作用域链中按着从小到大的规则查找变量
- 子作用域能够访问父作用域,父级作用域无法访问子级作用域
总结
- 作用域链本质是什么?
作用域链本质上是底层的变量查找机制- 作用域链查找的规则是什么?
会优先查找当前函数作用域中查找变量
查找不到则会依次逐级查找父级作用域直到全局作用域
目标: 了解JS垃圾回收机制的执行过程
学习目的: 为了闭包做铺垫
学习路径:
- 什么是垃圾回收机制
- 内存的声明周期
- 垃圾回收的算法说明
垃圾回收机制(Garbage Collection) 简称 GC
JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收。
正因为垃圾回收器的存在,许多人认为JS不用太关心内存管理的问题
但如果不了解JS的内存管理机制,我们同样非常容易成内存泄漏(内存无法被回收)的情况
不再用到的内存,没有及时释放,就叫做内存泄漏
JS环境中分配的内存, 一般有如下生命周期:
- 内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存
- 内存使用:即读写内存,也就是使用变量、函数等
- 内存回收:使用完毕,由垃圾回收器自动回收不再使用的内存
- 说明:
全局变量一般不会回收(关闭页面回收);
一般情况下局部变量的值, 不用了, 会被自动回收掉- 内存泄漏:程序中分配的内存由于某种原因程序未释放或无法释放叫做内存泄漏
总结
- 什么是垃圾回收机制?
简称 GC
JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收- 什么是内存泄漏?
不再用到的内存,没有及时释放,就叫做内存泄漏- 内存的生命周期是什么样的?
内存分配、内存使用、内存回收
全局变量一般不会回收; 一般情况下局部变量的值, 不用了, 会被自动回收掉
堆栈空间分配区别:
- 栈(操作系统): 由操作系统自动分配释放函数的参数值、局部变量等,基本数据类型放到栈里面。
- 堆(操作系统): 一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收。复杂数据类型放到堆里面。
下面介绍两种常见的浏览器垃圾回收算法: 引用计数法 和 标记清除法
引用计数法:
IE采用的引用计数算法, 定义“内存不再使用”,就是看一个对象是否有指向它的引用,没有引用了就回收对象
算法:
- 跟踪记录被引用的次数
- 如果被引用了一次,那么就记录次数1,多次引用会累加 ++
- 如果减少一个引用就减1 –
- 如果引用次数是0 ,则释放内存
由上面可以看出,引用计数算法是个简单有效的算法。
但它却存在一个致命的问题:嵌套引用(循环引用)
如果两个对象相互引用,尽管他们已不再使用,垃圾回收器不会进行回收,导致内存泄露
因为他们的引用次数永远不会是0。这样的相互引用如果说很大量的存在就会导致大量的内存泄露
标记清除法:
现代的浏览器已经不再使用引用计数算法了。
现代浏览器通用的大多是基于标记清除算法的某些改进算法,总体思想都是一致的。
核心:
- 标记清除算法将**“不再使用的对象”定义为“无法达到的对象”**。
- 就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。 凡是能从根部到达的对象,都是还需要使用的。
- 那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。
总结
- 标记清除法核心思路是什么?
从根部扫描对象,能查找到的就是使用的,查找不到的就要回收
目标: 能说出什么是闭包,闭包的作用以及注意事项
概念:一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域
简单理解:闭包 = 内层函数 + 外层函数的变量 先看个简单的代码
Closure闭包的意思
闭包作用:封闭数据,提供操作,外部也可以访问函数内部的变量
闭包的基本格式:
闭包应用:实现数据的私有
比如,我们要做个统计函数调用次数,函数调用一次,就++
总结
- 怎么理解闭包?
闭包 = 内层函数 + 外层函数的变量- 闭包的作用?
封闭数据,实现数据私有,外部也可以访问函数内部的变量
闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来- 闭包可能引起的问题?
内存泄露
变量提升是 JavaScript 中比较“奇怪”的现象,它允许在变量声明之前即被访问(仅存在于var声明变量)
注意:
- 变量在未声明即被访问时会报语法错误
- 变量在var声明之前即被访问,变量的值为 undefined
<script>
console.log(num + '件'); //undefined件
var num = 10
</script>
原因:js在执行代码的时候会把所有var声明的变量提升到当前作用域的最前面,但是,只提升声明,不提升赋值。
3. let/const 声明的变量不存在变量提升
4. 变量提升出现在相同作用域当中
5. 实际开发中推荐先声明再访问变量
说明:
JS初学者经常花很多时间才能习惯变量提升,还经常出现一些意想不到的bug,正因为如此,ES6 引入了块级作用域,
用let 或者 const声明变量,让代码写法更加规范和人性化。
总结
- 用哪个关键字声明变量会有变量提升?
var- 变量提升是什么流程?
先把var 变量提升到当前作用域于最前面
只提升变量声明, 不提升变量赋值
然后依次执行代码
我们不建议使用var声明变量
知道函数参数默认值、动态参数、剩余参数的使用细节,提升函数应用的灵活度,知道箭头函数的语法及与普通函数的差异。
函数提升与变量提升比较类似,是指函数在声明之前即可被调用。
<script> //1.会把所有的函数声明提升到当前作用域的最前面 //2.只提升函数声明,不提升函数调用 fn() function fn(){ console.log('函数提升'); } //函数表达式必须先声明和赋值然后再调用 fun() //fun is not a function //函数表达式 var fun = function(){ console.log('函数表达式'); } </script>
总结:
- 函数提升能够使函数的声明调用更灵活
- 函数表达式不存在提升的现象
- 函数提升出现在相同作用域当中
arguments 是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参
总结:
- arguments 是一个伪数组,只存在于函数中
- arguments 的作用是动态获取函数的实参
- 可以通过for循环依次得到传递过来的实参
总结
- 当不确定传递多少个实参的时候,我们怎么办?
arguments 动态参数- arguments是什么?
伪数组
它只存在函数
目标: 能够使用剩余参数
剩余参数允许我们将一个不定数量的参数表示为一个数组
动态参数和剩余参数区别
- … 是语法符号,置于最末函数形参之前,用于获取多余的实参
- 借助 … 获取的剩余实参,是个真数组
开发中,还是提倡多使用剩余参数。
总结
- 剩余参数主要的使用场景是?
用于获取多余的实参- 剩余参数和动态参数区别是什么?开发中提倡使用哪一个?
动态参数是伪数组
剩余参数是真数组
开发中使用剩余参数想必也是极好的
目标:能够使用展开运算符并说出常用的使用场景
**展开运算符(…),**将一个数组进行展开
说明:
<script>
const arr1 = [1, 2, 3]
// 展开运算符 可以展开数组
// console.log(...arr)
// console.log(Math.max(1, 2, 3))
// ...arr1 === 1,2,3
// 1 求数组最大值
console.log(Math.max(...arr1)) // 3
console.log(Math.min(...arr1)) // 1
// 2. 合并数组
const arr2 = [3, 4, 5]
const arr = [...arr1, ...arr2]
console.log(arr)
</script>
总结
- 展开运算符主要的作用是?
可以把数组展开,可以利用求数组最大值以及合并数组等操作- 展开运算符和剩余参数有什么区别?
展开运算符主要是 数组展开
剩余参数 在函数内部使用
目标: 能够熟悉箭头函数不同写法
目的:引入箭头函数的目的是更简短的函数写法并且不绑定this,箭头函数的语法比函数表达式更简洁
使用场景:箭头函数更适用于那些本来需要匿名函数的地方
学习路径:
- 基本语法
- 箭头函数参数
- 箭头函数this
语法1:基本写法
语法2:只有一个参数可以省略小括号
语法3:如果函数体只有一行代码,可以写到一行上,并且无需写 return 直接返回值
语法4:加括号的函数体返回对象字面量表达式
<script> //平常函数写法 function fn(){ console.log('函数'); } //函数表达式 const fun = function(){ console.log('函数表达式'); } //箭头函数 const func = ()=>{ console.log('箭头函数'); } //调用箭头函数 func() //有参数箭头函数 //()里面是形参 const funct = (x)=>{ console.log(x); } //调用箭头函数 //括号里面是实参 funct(1) //箭头函数语法2:只有一个参数可以省略小括号 //()里面是形参 const functi = x=>{ console.log(x); } //调用箭头函数 //括号里面是实参 functi(1) //语法3:如果函数体只有一行代码,可以写到一行上,并且无需写 return 直接返回值 const functio = x=>console.log(x); functio(2) // const functio1 = x=>x+x console.log(functio1(2)); </script>
总结
- 箭头函数属于表达式函数,因此不存在函数提升
- 箭头函数只有一个参数时可以省略圆括号 ()
- 箭头函数函数体只有一行代码时可以省略花括号 {},并自动做为返回值被返回
- 加括号的函数体返回对象字面量表达式
1. 普通函数有arguments 动态参数
2. 箭头函数没有 arguments 动态参数,但是有剩余参数 …arg
<script>
const getSum = (...args) =>{
let sum = 0
for(let i = 0;i<args.length;i++){
sum = sum + args[i]
}
return sum
}
console.log(getSum(1,2,3,4,5,6));
</script>
总结
- 箭头函数里面有arguments动态参数吗?可以使用什么参数?
没有arguments动态参数
可以使用剩余参数
在箭头函数出现之前,每一个新函数根据它是被如何调用的来定义这个函数的this值, 非常令人讨厌。
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this。
在开发中**【使用箭头函数前需要考虑函数中 this 的值】,事件回调函数使用箭头函数时,this 为全局的 window,因此DOM事件回调函数为了简便,还是不太推荐使用箭头函数**
总结
- 箭头函数里面有this吗?
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this- DOM事件回调函数推荐使用箭头函数吗?
不太推荐,特别是需要用到this的时候
事件回调函数使用箭头函数时,this 为全局的 window
解构赋值是一种快速为变量赋值的简洁语法,本质上仍然是为变量赋值。
数组解构是将数组的单元值(即数组元素)快速批量赋值给一系列变量的简洁语法。
基本语法:
- 赋值运算符 = 左侧的 [ ] 用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量
- 变量的顺序对应数组单元值的位置依次进行赋值操作
基本语法:典型应用交互2个变量
<script> // 1. 立即执行函数要加 // (function () { })(); // (function () { })(); // 2. 使用数组的时候 // const arr = [1, 2, 3] const str = 'pink'; //不加分号会被理解成 /* const str = 'pink' [1, 2, 3].map(function (item) { console.log(item) }) */ [1, 2, 3].map(function (item) { console.log(item) }) let a = 1 let b = 2 ;[b, a] = [a, b] console.log(a, b) </script>
总结
- 数组解构赋值的作用是什么?
是将数组的单元值快速批量赋值给一系列变量的简洁语法- Js 前面有两哪种情况需要加分号的?
立即执行函数
数组解构
练习 • 独立完成数组解构赋值
需求①: 有个数组: const pc = [‘海尔’, ‘联想’, ‘小米’, ‘方正’]
解构为变量: hr lx mi fz
需求②:请将最大值和最小值函数返回值解构 max 和min 两个变
数组解构时,变量和单元值数量不等的情况
1.数组解构时:变量多 单元值少的情况:
变量的数量大于单元值数量时,多余的变量将被赋值为 undefine
2.数组解构时:变量少 单元值多的情况:
3. 利用剩余参数解决变量少 单元值多的情况:
剩余参数返回的还是一个数组
4. 防止有undefined传递单元值的情况,可以设置默认值:
允许初始化变量的默认值,且只有单元值为 undefined
5. 按需导入,忽略某些返回值:
6. 支持多维数组的结构:
<script> const pc = ['海尔', '联想', '小米', '方正'] const [hr, lx, xm, fz] = ['海尔', '联想', '小米', '方正'] console.log(hr); console.log(lx); console.log(xm); console.log(fz); // 请将最大值和最小值函数返回值解构 max 和min 两个变量 function getValue() { return [100, 60] } const [max, min] = getValue() console.log(max) console.log(min) // 1. 变量多, 单元值少 , undefined // const [a, b, c, d] = [1, 2, 3] // console.log(a) // 1 // console.log(b) // 2 // console.log(c) // 3 // console.log(d) // undefined // 2. 变量少, 单元值多 // const [a, b] = [1, 2, 3] // console.log(a) // 1 // console.log(b) // 2 // 3. 剩余参数 变量少, 单元值多 // const [a, b, ...c] = [1, 2, 3, 4] // console.log(a) // 1 // console.log(b) // 2 // console.log(c) // [3, 4] 真数组 // 4. 防止 undefined 传递 // const [a = 0, b = 0] = [1, 2] // const [a = 0, b = 0] = [] // console.log(a) // 1 // console.log(b) // 2 // 5. 按需导入赋值 // const [a, b, , d] = [1, 2, 3, 4] // console.log(a) // 1 // console.log(b) // 2 // console.log(d) // 4 // const arr = [1, 2, [3, 4]] // console.log(arr[0]) // 1 // console.log(arr[1]) // 2 // console.log(arr[2]) // [3,4] // console.log(arr[2][0]) // 3 // 多维数组解构 // const arr = [1, 2, [3, 4]] // const [a, b, c] = [1, 2, [3, 4]] // console.log(a) // 1 // console.log(b) // 2 // console.log(c) // [3,4] // const [a, b, [c, d]] = [1, 2, [3, 4]] // console.log(a) // 1 // console.log(b) // 2 // console.log(c) // 3 // console.log(d) // 4 </script>
总结
- 变量的数量大于单元值数量时,多余的变量将被赋值为?
undefined- 变量的数量小于单元值数量时,可以通过什么剩余获取所有的值?
剩余参数… 获取剩余单元值,但只能置于最末位
对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法
1. 基本语法:
- 赋值运算符 = 左侧的 { } 用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量
- 对象属性的值将被赋值给与属性名相同的变量
- 注意解构的变量名不要和外面的变量名冲突否则报错
4.对象中找不到与变量名一致的属性时变量值为 undefined
2.给新的变量名赋值:
可以从一个对象中提取变量并同时修改新的变量名
冒号表示“什么值:赋值给谁”
3. 数组对象解构
练习 • 独立完成对象解构赋值
需求①: 有个对象: const pig = { name: ‘佩奇’,age: 6 }
结构为变量: 完成对象解构,并以此打印出值
需求②:请将pig对象中的name,通过对象解构的形式改为 uname,并打印输出
需求③:请将 数组对象, 完成 商品名和价格的解构
4. 多级对象解构:
<script> const pig = { name:'佩奇', family:{ mother:'猪妈妈', father:'猪爸爸', sister:'乔治' }, age:6 } //多级对象解构 //多级对象解构,前面要加上对象的名字 const {name,family:{mother,father,sister},age} = pig console.log(name); console.log(mother); console.log(father); console.log(sister); console.log(age); </script>
练习 • 独立完成对象解构赋值
请将刚才数据完成3个需求
<script> // 1. 这是后台传递过来的数据 const msg = { "code": 200, "msg": "获取新闻列表成功", "data": [ { "id": 1, "title": "5G商用自己,三大运用商收入下降", "count": 58 }, { "id": 2, "title": "国际媒体头条速览", "count": 56 }, { "id": 3, "title": "乌克兰和俄罗斯持续冲突", "count": 1669 }, ] } // 需求1: 请将以上msg对象 采用对象解构的方式 只选出 data 方面后面使用渲染页面 // const {data} = msg // console.log(data); // 需求2: 上面msg是后台传递过来的数据,我们需要把data选出当做参数传递给 函数 // function render(arr) { // // 我们只要 data 数据 // const {data} = arr // // 内部处理 // console.log(data); // } // render(msg) // function render({data}) { // // 我们只要 data 数据 // //const {data} = arr // // 内部处理 // console.log(data); // } // render(msg) // 需求3, 为了防止msg里面的data名字混淆,要求渲染函数里面的数据名改为 myData function render(arr) { // 要求将 获取过来的 data数据 更名为 myData const {data:myData} = arr // 内部处理 console.log(myData); } render(msg) function render({data:myData}) { // 要求将 获取过来的 data数据 更名为 myData //const {data:myData} = arr // 内部处理 console.log(myData); } render(msg) </script>
案例 渲染商品列表案例(es6)
请根据数据渲染以下效果
**核心思路:**有多少条数据,就渲染多少模块,然后 生成对应的 html结构标签, 赋值给 list标签即可
①:利用forEach 遍历数据里面的 数据
②:拿到数据,利用字符串拼接生成结构添加到页面中
③:注意:传递参数的时候,可以使用对象解构
<body> <div class="list"> <!-- 看成一个字符串 --> <!-- <div class="item"> <img src="" alt=""> <p class="name"></p> <p class="price"></p> </div> --> </div> <script> const goodsList = [ { id: '4001172', name: '称心如意手摇咖啡磨豆机咖啡豆研磨机', price: '289.00', picture: 'https://yanxuan-item.nosdn.127.net/84a59ff9c58a77032564e61f716846d6.jpg', }, { id: '4001594', name: '日式黑陶功夫茶组双侧把茶具礼盒装', price: '288.00', picture: 'https://yanxuan-item.nosdn.127.net/3346b7b92f9563c7a7e24c7ead883f18.jpg', }, { id: '4001009', name: '竹制干泡茶盘正方形沥水茶台品茶盘', price: '109.00', picture: 'https://yanxuan-item.nosdn.127.net/2d942d6bc94f1e230763e1a5a3b379e1.png', }, { id: '4001874', name: '古法温酒汝瓷酒具套装白酒杯莲花温酒器', price: '488.00', picture: 'https://yanxuan-item.nosdn.127.net/44e51622800e4fceb6bee8e616da85fd.png', }, { id: '4001649', name: '大师监制龙泉青瓷茶叶罐', price: '139.00', picture: 'https://yanxuan-item.nosdn.127.net/4356c9fc150753775fe56b465314f1eb.png', }, { id: '3997185', name: '与众不同的口感汝瓷白酒杯套组1壶4杯', price: '108.00', picture: 'https://yanxuan-item.nosdn.127.net/8e21c794dfd3a4e8573273ddae50bce2.jpg', }, { id: '3997403', name: '手工吹制更厚实白酒杯壶套装6壶6杯', price: '99.00', picture: 'https://yanxuan-item.nosdn.127.net/af2371a65f60bce152a61fc22745ff3f.jpg', }, { id: '3998274', name: '德国百年工艺高端水晶玻璃红酒杯2支装', price: '139.00', picture: 'https://yanxuan-item.nosdn.127.net/8896b897b3ec6639bbd1134d66b9715c.jpg', }, ] // 1. 声明一个字符串变量 let str = '' // 2. 遍历数据 goodsList.forEach(item => { // console.log(item) // 可以得到每一个数组元素 对象 {id: '4001172'} // const {id} = item 对象解构 const { name, price, picture } = item str += ` <div class="item"> <img src=${picture} alt=""> <p class="name">${name}</p> <p class="price">${price}</p> </div> ` }) // 3.生成的 字符串 添加给 list document.querySelector('.list').innerHTML = str </script> </body>
- forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数
主要使用场景: 遍历数组的每个元素
语法:
例如:
注意:
1. forEach 主要是遍历数组
2. 参数当前数组元素是必须要写的, 索引号可选。
<script>
// forEach 就是遍历 加强版的for循环 适合于遍历数组对象
const arr = ['red', 'green', 'pink']
//2. 参数当前数组元素是必须要写的, 索引号可选
const result = arr.forEach(function (item) {
console.log(item) // 数组元素 red green pink
//console.log(index) // 索引号
})
// console.log(result)
</script>
filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素
主要使用场景: 筛选数组符合条件的元素,并返回筛选之后元素的新数组
filter() 筛选数组
返回值:返回数组,包含了符合条件的所有元素。如果没有符合条件的元素则返回空数组
参数:currentValue 必须写, index 可选
因为返回新数组,所以不会影响原数组
<script>
const arr = [10, 20, 30]
// const newArr = arr.filter(function (item, index) {
// // console.log(item)
// // console.log(index)
// return item >= 20
// })
// 返回的符合条件的新数组
const newArr = arr.filter(item => item >= 20)
console.log(newArr)
</script>
业务分析:
①: 页面初始渲染
②: 点击不同需求显示不同
分析:
①:渲染页面 利用forEach 遍历数据里面的 数据,并渲染数据列表
②:根据 filter 选择不同条件显示不同商品
步骤:
①:渲染页面模块
(1) 初始化需要渲染页面,同时,点击不同的需求,还会重新渲染页面,所以渲染做成一个函数
(2) 做法基本跟前面案例雷同,就是封装到了一个函数里面
②:点击不同需求,显示不同页面内容
(1) 点击采取事件委托方式 .filter
(2) 利用过滤函数 filter 筛选出符合条件的数据,因为生成的是一个数组,传递给渲染函数即可
(3) 筛选条件是根据点击的 data-index 来判断
(4) 可以使用对象解构,把 事件对象 解构
(5) 因为 全部区间不需要筛选,直接 把goodList渲染即可
<body> <div class="filter"> <a data-index="1" href="javascript:;">0-100元</a> <a data-index="2" href="javascript:;">100-300元</a> <a data-index="3" href="javascript:;">300元以上</a> <a href="javascript:;">全部区间</a> </div> <div class="list"> <!-- <div class="item"> <img src="" alt=""> <p class="name"></p> <p class="price"></p> </div> --> </div> <script> // 2. 初始化数据 const goodsList = [ { id: '4001172', name: '称心如意手摇咖啡磨豆机咖啡豆研磨机', price: '289.00', picture: 'https://yanxuan-item.nosdn.127.net/84a59ff9c58a77032564e61f716846d6.jpg', }, { id: '4001594', name: '日式黑陶功夫茶组双侧把茶具礼盒装', price: '288.00', picture: 'https://yanxuan-item.nosdn.127.net/3346b7b92f9563c7a7e24c7ead883f18.jpg', }, { id: '4001009', name: '竹制干泡茶盘正方形沥水茶台品茶盘', price: '109.00', picture: 'https://yanxuan-item.nosdn.127.net/2d942d6bc94f1e230763e1a5a3b379e1.png', }, { id: '4001874', name: '古法温酒汝瓷酒具套装白酒杯莲花温酒器', price: '488.00', picture: 'https://yanxuan-item.nosdn.127.net/44e51622800e4fceb6bee8e616da85fd.png', }, { id: '4001649', name: '大师监制龙泉青瓷茶叶罐', price: '139.00', picture: 'https://yanxuan-item.nosdn.127.net/4356c9fc150753775fe56b465314f1eb.png', }, { id: '3997185', name: '与众不同的口感汝瓷白酒杯套组1壶4杯', price: '108.00', picture: 'https://yanxuan-item.nosdn.127.net/8e21c794dfd3a4e8573273ddae50bce2.jpg', }, { id: '3997403', name: '手工吹制更厚实白酒杯壶套装6壶6杯', price: '99.00', picture: 'https://yanxuan-item.nosdn.127.net/af2371a65f60bce152a61fc22745ff3f.jpg', }, { id: '3998274', name: '德国百年工艺高端水晶玻璃红酒杯2支装', price: '139.00', picture: 'https://yanxuan-item.nosdn.127.net/8896b897b3ec6639bbd1134d66b9715c.jpg', }, ] //①:渲染页面 利用forEach 遍历数据里面的 数据,并渲染数据列表 //②:根据 filter 选择不同条件显示不同商品 //1.渲染函数 封装 function render(arr) { //声明空字符串 let str = '' //遍历数组 arr.forEach(element => { //解构 const { name, price, picture } = element str += ` <div class="item"> <img src=${picture} alt=""> <p class="name">${name}</p> <p class="price">${price}</p> </div> ` }); //追加给list document.querySelector('.list').innerHTML = str } render(goodsList) //(1) 点击采取事件委托方式 .filter //(2) 利用过滤函数 filter 筛选出符合条件的数据,因为生成的是一个数组,传递给渲染函数即可 document.querySelector('.filter').addEventListener('click', e => { //需要用到这两个属性,解构出来 e.target.dataset.id e.target.tagName const { tagName, dataset } = e.target //判断点击了a if (tagName === 'A') { //初始化arr的数组内容为 goodsList 因为全部区间不需要筛选,直接拿goodsList的值 // arr接收返回的新数组 let arr = goodsList if (dataset.index === '1') { //点了1后 //arr更新为1-100的数据集合 arr = goodsList.filter(item => item.price >= 0 && item.price <= 100 // function (item) { // return item.price >= 0 && item.price <= 100 // } ) } else if (dataset.index === '2') { arr = goodsList.filter(item => item.price >= 100 && item.price <= 300) } else if (dataset.index === '3') { arr = goodsList.filter(item => item.price >= 300) } //只要点了a就会渲染,所以不用判断所有价格区间 //重新渲染符合条件的 render(arr) } }) //(3) 筛选条件是根据点击的 data-index 来判断 //(4) 可以使用对象解构,把 事件对象 解构 //(5) 因为 全部区间不需要筛选,直接 把goodList渲染即可 </script> </body>
目标:能够利用构造函数创建对象
构造函数 :是一种特殊的函数,主要用来初始化对象
使用场景:常规的 {…} 语法允许创建一个对象。比如我们创建了佩奇的对象,继续创建乔治的对象还需要重新写一遍,此时可以通过构造函数来快速创建多个类似的对象。
构造函数在技术上是常规函数。
不过有两个约定:
- 它们的命名以大写字母开头。
- 它们只能由 “new” 操作符来执行。
构造函数语法:大写字母开头的函数
创建构造函数:
说明:- 使用 new 关键字调用函数的行为被称为实例化
- 实例化构造函数时没有参数时可以省略 ()
- 构造函数内部无需写return,返回值即为新创建的对象
- 构造函数内部的 return 返回的值无效,所以不要写return
- new Object() new Date() 也是实例化构造函数
总结
- 构造函数的作用是什么?怎么写呢?
构造函数是来快速创建多个类似的对象
大写字母开头的函数- new 关键字调用函数的行为被称为?
实例化- 构造函数内部需要写return吗,返回值是什么?
不需要
构造函数自动返回创建的新的对象
function Goods(name, price, count) {
this.name = name
this.price = price
this.count = count
}
const mi = new Goods('小米', 1999, 20)
console.log(mi)
const hw = new Goods('华为', 3999, 59)
console.log(hw)
实例化执行过程(面试)
说明:
- 创建新的空对象
- 构造函数this指向新对象
- 执行构造函数代码,修改this,添加新的属性
- 返回新对象
目标:能够说出什么是实例成员和静态成员
实例成员:
通过构造函数创建的对象称为实例对象,实例对象中的属性和方法称为实例成员。
说明:
- 实例对象的属性和方法即为实例成员
- 为构造函数传入参数,动态创建结构相同但值不同的对象
- 构造函数创建的实例对象彼此独立互不影响。
静态成员:构造函数的属性和方法被称为静态成员
说明:
- 构造函数的属性和方法被称为静态成员
- 一般公共特征的属性或方法静态成员设置为静态成员
- 静态成员方法中的 this 指向构造函数本身
总结
- 什么是实例成员?
实例对象的属性和方法即为实例成员- 什么是静态成员?
构造函数的属性和方法被称为静态成员
在 JavaScript 中最主要的数据类型有 6 种:
基本数据类型:
字符串、数值、布尔、undefined、null
引用类型:
对象
但是,我们会发现有些特殊情况:
其实字符串、数值、布尔、等基本类型也都有专门的构造函数,这些我们称为包装类型。
JS中几乎所有的数据都可以基于构成函数创建。
引用类型
Object,Array,RegExp,Date 等
包装类型
String,Number,Boolean 等
Object 是内置的构造函数,用于创建普通对象。
推荐使用字面量方式声明对象,而不是 Object 构造函数
学习三个常用静态方法(静态方法就是只有构造函数Object可以调用的)
作用:Object.keys 静态方法获取对象中所有属性(键)
语法:
注意: 返回的是一个数组
作用:Object.values 静态方法获取对象中所有属性值
语法:
注意: 返回的是一个数组
作用:Object. assign 静态方法常用于对象拷贝
语法:
**使用场景:**经常使用的场景给对象添加属性
总结
- 什么是静态方法?
只能给构造函数使用的方法 比如 Object.keys()- Object.keys()方法的作用是什么 ?
获取对象中所有属性(键)- Object.values()方法的作用是什么 ?
获取对象中所有属性值(值)
<script> const obj = {name:'pig',age:7} //1.获得所有的属性名,返回的是数组 console.log(Object.keys(obj)); //['name', 'age'] //2.获得所有的属性值 console.log(Object.values(obj)); //['pig', 7] //3.Object. assign 静态方法常用于对象拷贝 const obj11 = {} Object.assign(obj11,obj) //把obj拷贝给obj11 console.log(obj11); //{name: 'pig', age: 7} //经常使用场景给对象添加属性 Object.assign(obj,{gender:'女'}) console.log(obj); //{name: 'pig', age: 7, gender: '女'} </script>
Array 是内置的构造函数,用于创建数组
创建数组建议使用字面量创建,不用 Array构造函数创建
1. 数组常见实例方法-核心方法
作用:reduce 返回函数累计处理的结果,经常用于求和等
基本语法:
参数:
起始值可以省略,如果写就作为第一次累计的起始值
语法:
累计值参数:
- 如果有起始值,则以起始值为准开始累计, 累计值 = 起始值
- 如果没有起始值, 则累计值以数组的第一个数组元素作为起始值开始累计
- 后面每次遍历就会用后面的数组元素 累计到 累计值 里面 (类似求和里面的 sum )
使用场景:求和运算:
reduce 执行过程:
1.如果没有起始值,则上一次值以数组的第一个数组元素的值
2.每一次循环,把返回值给做为 下一次循环的上一次值
3.如果有起始值,则 起始值做为上一次值
练习 • 员工涨薪计算成本
<script> const arr = [{ name: '张三', salary: 10000 }, { name: '李四', salary: 10000 }, { name: '王五', salary: 20000 }, ] // 涨薪的钱数 10000 * 0.3 ,此时item是对象,要拿到对象的工资属性 // const money = arr.reduce(function (prev, item) { // return prev + item.salary * 1.3 // }, 0) const money = arr.reduce((prev, item) => prev + item.salary * 1.3, 0) console.log(money) </script>
<script>
const spec = { size: '40cm*40cm', color: '黑色' }
//1. 所有的属性值回去过来 数组
// console.log(Object.values(spec))
// 2. 转换为字符串 数组join('/') 把数组根据分隔符转换为字符串
// console.log(Object.values(spec).join('/'))
document.querySelector('div').innerHTML = Object.values(spec).join('/')
</script>
<body> <ul> <li>1</li> <li>2</li> <li>3</li> </ul> <script> // Array.from(lis) 把伪数组转换为真数组 const lis = document.querySelectorAll('ul li') //lis是一个伪数组 // console.log(lis) // lis.pop() 报错 const liss = Array.from(lis) //liss是真数组 liss.pop() console.log(liss) </script> </body>
在 JavaScript 中的字符串、数值、布尔具有对象的使用特征,如具有属性和方法
之所以具有对象特征的原因是字符串、数值、布尔类型数据是 JavaScript 底层使用 Object 构造函数“包装”来的,被称为包装类型。
1. 常见实例方法
案例:把字符串里的内容写到对应的位置
<body> <div></div> <script> const gift = '50g的茶叶,清洗球' // 1. 把字符串拆分为数组 // console.log(gift.split(',')) [,] // 2. 根据数组元素的个数,生成 对应 span标签 // const str = gift.split(',').map(function (item) { // return `<span>【赠品】 ${item}</span> <br>` // }).join('') //arr.join()把数组转成字符串 // // console.log(str) // document.querySelector('div').innerHTML = str document.querySelector('div').innerHTML = gift.split(',').map(item => `<span>【赠品】 ${item}</span> <br>`).join('') </script> </body>
Number 是内置的构造函数,用于创建数值
常用方法:
toFixed() 设置四舍五入保留小数位的长度
<body>
<script>
// toFixed 方法可以让数字指定保留的小数位数
const num = 10.923
// console.log(num.toFixed())
console.log(num.toFixed(1))
const num1 = 10
console.log(num1.toFixed(2))
</script>
</body>
案例 购物车展示
根据后台提供的数据,渲染购物车页面
分析业务模块:
①:渲染图片、标题、颜色、价格、赠品等数据
②:单价和小计模块
③:总价模块
分析业务模块:
①:把整体的结构直接生成然后渲染到大盒子.list 里面
②:那个方法可以遍历的同时还有返回值 map 方法
③:最后计算总价模块,那个方法可以求和? reduce 方法
分析业务模块:
①:先利用map来遍历,有多少条数据,渲染多少相同商品
②:里面更换各种数据,注意使用对象解构赋值
③:利用reduce计算总价
分析业务模块:
①:先利用map来遍历,有多少条数据,渲染多少相同商品
- 可以先写死的数据
- 注意map返回值是 数组,我们需要用 join 转换为字符串
- 把返回的字符串 赋值 给 list 大盒子的 innerHTML
②:更换数据- 先更换不需要处理的数据,图片,商品名称,单价,数量
- 采取对象解构的方式
- 注意 单价要保留2位小数, 489.00 toFixed(2)
②:更换数据 - 处理 规格文字 模块- 获取 每个对象里面的 spec , 上面对象解构添加 spec
- 获得所有属性值是: Object.values() 返回的是数组
- 拼接数组是 join(‘’) 这样就可以转换为字符串了
②:更换数据 - 处理 赠品 模块- 获取 每个对象里面的 gift , 上面对象解构添加 gift
注意要判断是否有gif属性,没有的话不需要渲染
利用变成的字符串然后写到 p.name里面
②:更换数据 - 处理 小计 模块- 小计 = 单价 * 数量
- 小计名可以为: subTotal = price * count
- 注意保留2位小数
关于小数的计算精度问题:
0.1 + 0.2 = ?
解决方案: 我们经常转换为整数
(0.1100 + 0.2100)/ 100 === 0.3
这里是给大家拓展思路和处理方案
③:计算 合计 模块
- 求和用到数组 reduce 方法 累计器
- 根据数据里面的数量和单价累加和即可
- 注意 reduce方法有2个参数,第一个是回调函数,第二个是 初始值,这里写 0
<!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> * { margin: 0; padding: 0; box-sizing: border-box; } .list { width: 990px; margin: 100px auto 0; } .item { padding: 15px; transition: all .5s; display: flex; border-top: 1px solid #e4e4e4; } .item:nth-child(4n) { margin-left: 0; } .item:hover { cursor: pointer; background-color: #f5f5f5; } .item img { width: 80px; height: 80px; margin-right: 10px; } .item .name { font-size: 18px; margin-right: 10px; color: #333; flex: 2; } .item .name .tag { display: block; padding: 2px; font-size: 12px; color: #999; } .item .price, .item .sub-total { font-size: 18px; color: firebrick; flex: 1; } .item .price::before, .item .sub-total::before, .amount::before { content: "¥"; font-size: 12px; } .item .spec { flex: 2; color: #888; font-size: 14px; } .item .count { flex: 1; color: #aaa; } .total { width: 990px; margin: 0 auto; display: flex; justify-content: flex-end; border-top: 1px solid #e4e4e4; padding: 20px; } .total .amount { font-size: 18px; color: firebrick; font-weight: bold; margin-right: 50px; } </style> </head> <body> <div class="list"> <!-- <div class="item"> <img src="https://yanxuan-item.nosdn.127.net/84a59ff9c58a77032564e61f716846d6.jpg" alt=""> <p class="name">称心如意手摇咖啡磨豆机咖啡豆研磨机 <span class="tag">【赠品】10优惠券</span></p> <p class="spec">白色/10寸</p> <p class="price">289.90</p> <p class="count">x2</p> <p class="sub-total">579.80</p> </div> --> </div> <div class="total"> <div>合计:<span class="amount">1000.00</span></div> </div> <script> const goodsList = [ { id: '4001172', name: '称心如意手摇咖啡磨豆机咖啡豆研磨机', price: 289.9, picture: 'https://yanxuan-item.nosdn.127.net/84a59ff9c58a77032564e61f716846d6.jpg', count: 2, spec: { color: '白色' } }, { id: '4001009', name: '竹制干泡茶盘正方形沥水茶台品茶盘', price: 109.8, picture: 'https://yanxuan-item.nosdn.127.net/2d942d6bc94f1e230763e1a5a3b379e1.png', count: 3, spec: { size: '40cm*40cm', color: '黑色' } }, { id: '4001874', name: '古法温酒汝瓷酒具套装白酒杯莲花温酒器', price: 488, picture: 'https://yanxuan-item.nosdn.127.net/44e51622800e4fceb6bee8e616da85fd.png', count: 1, spec: { color: '青色', sum: '一大四小' } }, { id: '4001649', name: '大师监制龙泉青瓷茶叶罐', price: 139, picture: 'https://yanxuan-item.nosdn.127.net/4356c9fc150753775fe56b465314f1eb.png', count: 1, spec: { size: '小号', color: '紫色' }, gift: '50g茶叶,清洗球' } ] //根据数据利用数组map和字符串 join()渲染 const goods = goodsList.map(item => { //对象解构 const { picture, name, count, price, spec, gift } = item //spec模块:上面的对象解构添加spec,获取每个对象里面的spec //:获得所有属性值是:Object.values() 返回的是数组 //Object.values(spec).join('/') //属性值转化成字符串 //处理赠品模块gift //字符串转成数组,用map遍历数组写入span,返回的是数组,然后把span数组转成join字符串 //还要求判断 const str = gift ? gift.split(',').map(item => `<span class="tag">【赠品】${item}</span>`).join('') : '' //价格模块(注意精度问题)转成整数运算后再÷ const subTotal = ((price * 100 * count) / 100).toFixed(2) return ` <div class="item"> <img src=${picture} alt=""> <p class="name">${name} ${str}</p> <p class="spec">${Object.values(spec).join('/')}</p> <p class="price">${price.toFixed(2)}</p> <p class="count">x${count}</p> <p class="sub-total">${subTotal}</p> </div> ` }).join('') document.querySelector('.list').innerHTML = goods /* 计算合计 模块 求和用到数组 reduce 方法 累计器 根据数据里面的数量和单价累加和即可 注意 reduce方法有2个参数,第一个是回调函数,第二个是 初始值,这里写0 */ const subTotalAll = goodsList.reduce((prev,item) => prev + (item.price*item.count*100)/100 , 0).toFixed(2) document.querySelector('.amount').innerHTML = subTotalAll </script> </body> </html>
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了。
举个栗子:蛋炒饭
面向过程,就是按照我们分析好了的步骤,按照步骤解决问题。
面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作。
面向对象是以对象功能来划分问题,而不是步骤。
- 在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工。
- 面向对象编程具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件项目。
- 面向对象的特性:
封装性
继承性
多态性
编程思想-面向过程和面向对象的对比
生活离不开蛋炒饭,也离不开盖浇饭,选择不同而已,只不过前端不同于其他语言,面向过程更多
- 封装是面向对象思想中比较重要的一部分,js面向对象可以通过构造函数实现的封装。
- 同样的将变量和函数组合到了一起并能通过 this 实现数据的共享,所不同的是借助构造函数创建出来的实例对象之间是彼此不影响的
- 总结:
1.构造函数体现了面向对象的封装特性
2.构造函数实例创建的对象彼此独立、互不影响- 封装是面向对象思想中比较重要的一部分,js面向对象可以通过构造函数实现的封装。
- 前面我们学过的构造函数方法很好用,但是 存在浪费内存的问题
- 面向对象编程的特性:比如封装性、继承性等,可以借助于构造函数来实现
前面我们学过的构造函数方法很好用,但是 存在浪费内存的问题
我们希望所有的对象使用同一个函数,这样就比较节省内存,那么我们要怎样做呢?
- 构造函数通过原型分配的函数是所有对象所 共享的。
- JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型对象
- 这个对象可以挂载函数,对象实例化不会多次创建原型上函数,节约内存
- 我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
- 构造函数和原型对象中的this 都指向 实例化的对象
// 1.公共的属性写到 构造函数里面
// 2. 公共的方法写到原型对象身上 节约了内存
<body> <script> // 构造函数 公共的属性和方法 封装到 Star 构造函数里面了 // 1.公共的属性写到 构造函数里面 function Star(uname, age) { this.uname = uname this.age = age // this.sing = function () { // console.log('唱歌') // } } // 2. 公共的方法写到原型对象身上 节约了内存 Star.prototype.sing = function () { console.log('唱歌') } const ldh = new Star('刘德华', 55) const zxy = new Star('张学友', 58) ldh.sing() //调用 zxy.sing() //调用 // console.log(ldh === zxy) // false console.log(ldh.sing === zxy.sing) //true // console.dir(Star.prototype) </script> </body>
原型- this指向
目标:能够说出构造函数和原型对象中的this 指向
构造函数和原型对象中的this 都指向 实例化的对象
<script> let that function Star(uname) { // that = this // console.log(this) this.uname = uname } // 原型对象里面的函数this指向的还是 实例对象 ldh Star.prototype.sing = function () { that = this console.log('唱歌') } // 实例对象 ldh // 构造函数里面的 this 就是 实例对象 ldh const ldh = new Star('刘德华') ldh.sing() console.log(that === ldh) </script>
总结
- 原型是什么 ?
一个对象,我们也称为 prototype 为原型对象- 原型的作用是什么 ?
1.共享方法
2.可以把那些不变的方法,直接定义在 prototype 对象上- 构造函数和原型里面的this指向谁 ?
实例化的对象
练习 • 给数组扩展方法
需求:
①:给数组扩展求最大值方法和求和方法
比如: 以前学过
const arr = [1,2,3]
arr.reverse() 结果是 [3,2,1]
扩展完毕之后:
arr.sum() 返回的结果是 6
<body> <script> //自己定义数组的求和,求最大值方法,数组调用 //1.我们定义的这个方法,任何一个数组的实例对象都可以使用 //2.自定义的方法写到原型身上数组.prototype 身上 //求最大值 const arr = [1,2,3] Array.prototype.max = function () { //展开运算符 //原型函数里面的this指向的是实例对象。这个例子this指向的是arr return Math.max(...this) } //调用 console.log(arr.max()); //const arr = [1,2,3] //const arr = new Array(1, 2) //console.log(arr); //[1, 2] //求和函数 Array.prototype.sum = function(){ return this.reduce((prev,item) => prev + item, 0) } console.log([1,2,3].sum()); </script> </body>
在哪里? 每个原型对象里面都有个constructor 属性(constructor 构造函数)
作用:该属性指向该原型对象的构造函数, 简单理解,就是指向我的爸爸,我是有爸爸的孩子
//prototyped的constructor指向的是对象的构造函数
console.log(Star.prototype.constructor === Star);//true
<script>
//constructor 属性
function Star() {
}
//实例化
const ldh = new Star()
console.log(Star.prototype);
</script>
使用场景:
如果有多个对象的方法,我们可以给原型对象采取对象形式赋值.
但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了
此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。
<body> <script> // constructor 单词 构造函数 // Star.prototype.sing = function () { // console.log('唱歌') // } // Star.prototype.dance = function () { // console.log('跳舞') // } function Star() { } // console.log(Star.prototype) Star.prototype = { // 从新指回创造这个原型对象的 构造函数 constructor: Star, sing: function () { console.log('唱歌') }, dance: function () { console.log('跳舞') }, } console.log(Star.prototype) // console.log(Star.prototype.constructor) // const ldh = new Star() // console.log(Star.prototype.constructor === Star) </script> </body>
总结
- constructor属性的作用是什么?
指向该原型对象的构造函数
思考
构造函数可以创建实例对象,构造函数还有一个原型对象,一些公共的属性或者方法放到这个原型对象身上。但是 为啥实例对象可以访问原型对象里面的属性和方法呢?
对象都会有一个属性 _ proto _ 指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 proto 原型的存在。
注意:
1.__proto__是JS非标准属性
2. [[prototype]]和__proto__意义相同
3. 用来表明当前实例对象指向哪个原型对象prototype
4. __proto__对象原型里面也有一个 constructor属性,指向创建该实例对象的构造函数
<script>
function Star() {
}
const ldh = new Star()
// 对象原型__proto__ 指向 改构造函数的原型对象
console.log(ldh.__proto__)
// console.log(ldh.__proto__ === Star.prototype)
// 对象原型里面有constructor 指向 构造函数 Star
console.log(ldh.__proto__.constructor === Star)
</script>
总结
- prototype是什么?哪里来的?
原型(原型对象)
构造函数都自动有原型- constructor属性在哪里?作用干啥的?
prototype原型和对象原型__proto__里面都有
都指向创建实例对象/原型的构造函数- __proto__属性在哪里?指向谁?
在实例对象里面
指向原型 prototype
对象原型 指向 原型对象
练习 • 根据下面代码,请画图
需求:
①:利用画图工具,画出 构造函数 原型 实例对象 三者的关系
②:要求里面有__proto__ 和 constructor 的指向
③:并在代码打印验证指向正确
继承是面向对象编程的另一个特征,通过继承进一步提升代码封装的程度,JavaScript 中大多是借助原型对象实现继承的特性。
龙生龙、凤生凤、老鼠的儿子会打洞描述的正是继承的含义。
我们来看个代码:
1. 封装-抽取公共部分
把男人和女人公共的部分抽取出来放到人类里面
2. 继承-让男人和女人都能继承人类的一些属性和方法
1.把男人女人公共的属性和方法抽取出来 People
2.然后赋值给Man的原型对象,可以共享这些属性和方法
3.注意让constructor指回Man这个构造函数
<script> //继续抽取 公共的部分放到原型上就可以被继承使用 const person= { eays:2, head:1 } //女人 构造函数 想要 继承 Person function woman(){ } //实例化 //woman通过原型来继承person woman.prototype = person //指回原来的构造函数 woman.prototype.constructor = woman const red = new woman() console.log(red); //woman {eye: 2, head: 1} //男人 构造函数 想要 继承 Person function man(){ } //实例化 const green = new man() console.log(green); //man {eye: 2, head: 1} </script>
3. 问题:
如果我们给男人添加了一个吸烟的方法,发现女人自动也添加这个方法
4. 原因
男人和女人都同时使用了同一个对象,根据引用类型的特点,他们指向同一个对象,修改一个就会都影响
5. 解决:
需求:男人和女人不要使用同一个对象,但是不同对象里面包含相同的属性和方法
答案:构造函数
new 每次都会创建一个新的对象
6. 继承写法完善
<body> <script> //继续抽取 公共的部分放到原型上就可以被继承使用 // const person= { // eays:2, // head:1 // } //避免使用同一个对象时 某个对象特有的方法混淆 //所以使用构造函数,构造函数new出来的对象解构一样,但是对象不一样 function Person(){ this.eyes = 2 this.head = 1 } //女人 构造函数 想要 继承 Person function woman(){ } //实例化 //woman通过原型来继承person woman.prototype = new Person() //指回原来的构造函数 woman.prototype.constructor = woman //给女人添加一个专有的方法(挂载在prototype原型上) 生孩子 //发现下面的男人也有生孩子(原因是都是继承了perpon原型) woman.prototype.baby = function(){ console.log('生宝宝贝'); } const red = new woman() console.log(red); console.log(woman.prototype);//{eays: 2, head: 1, constructor: ƒ} //男人 构造函数 想要 继承 Person function man(){ } //实例化 //man通过原型来继承person man.prototype = new Person() //指回原来的构造函数 man.prototype.constructor = man const green = new man() console.log(green); console.log(man.prototype); //{eays: 2, head: 1, constructor: ƒ} //原型继承核心: //父类构造函数(父类) 子构造函数(子类) //子类的原型 = new 父类 man.prototype = new Person() //子类的原型指回原来的构造函数 man.prototype.constructor = man </script> </body>
基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对象的链状结构关系称为原型链
只要是对象就有对象原型__proto__,指向原型对象
只要是原型对象就有constructor指回构造函数
原型链-查找规则
① 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
② 如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)
③ 如果还没有就查找原型对象的原型(Object的原型对象)
④ 依此类推一直找到 Object 为止(null)
⑤ __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线
⑥ 可以使用 instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
<body> <script> // function Objetc() {} console.log(Object.prototype) console.log(Object.prototype.__proto__) function Person() { } const ldh = new Person() // console.log(ldh.__proto__ === Person.prototype) // console.log(Person.prototype.__proto__ === Object.prototype) console.log(ldh instanceof Person) //true console.log(ldh instanceof Object) //true console.log(ldh instanceof Array) //false console.log([1, 2, 3] instanceof Array) //true console.log(Array instanceof Object) //true </script> </body>
案例 消息提示对象封装
目的: 练习面向对象写插件(模态框)
需求:
分析需求:
1.多个模态框一样的,而且每次点击都会出来一个,怎么做呢?
构造函数。把模态框封装一个构造函数 Modal,每次new 都会产出一个模态框,所以点击不同的按钮就是在做 new 模态框,实例化。
2.模态框有什么功能呢?打开功能(显示),关闭功能,而且每个模态框都包含着2个功能
open功能
close功能
问:
open 和 close 方法 写到哪里?
构造函数的原型对象上,共享方法
所以可以分为三个模块, 构造函数, open方法, close方法
步骤:
①:Modal 构造函数 制作
- 需要的公共属性: 标题(title)、提示信息内容(message) 可以设置默认参数
- 在页面中创建模态框
(1) 创建div标签可以命名为:modalBox
(2) div标签的类名为 modal
(3) 标签内部添加 基本结构,并填入相关数据
①:open方法- 写到构造函数的原型对象身上
- 把刚才创建的modalBox 添加到 页面 body 标签中
- open 打开的本质就是 把创建标签添加到页面中
- 点击按钮, 实例化对象,传入对应的参数,并执行 open 方法
①:close方法- 写到构造函数的原型对象身上
- 把刚才创建的modalBox 从页面 body 标签中 删除
- 需要注意,x 删除按钮绑定事件,要写到open里面添加,因为open是往页面中添加元素,同时顺便绑定事件
<body> <button id="delete">删除</button> <button id="login">登录</button> <!-- <div class="modal"> <div class="header">温馨提示 <i>x</i></div> <div class="body">您没有删除权限操作</div> </div> --> <script> //1.model 构造函数封装-模态框 function Model(title = '', message = '') { this.title = title this.message = message //创建model模态框盒子 //1.1创建div标签 //this才能指向当前的盒子 this.modelBox = document.createElement('div') //1.2给div标签添加类名为model this.modelBox.className = 'model' //1.3model盒子内部填充2个div标签并且修改文字内容 this.modelBox.innerHTML = ` <div class="header">${this.title} <i>x</i></div> <div class="body">${this.message}</div> ` //console.log(this.modelBox); } //new Model('温馨提示', '您没有权限删除操作') //new Model('温馨提示', '请先注册') /*open方法 >- 写到构造函数的原型对象身上 >- 把刚才创建的modalBox 添加到 页面 body 标签中 >- open 打开的本质就是 **把创建标签添加到页面中** >- 点击按钮, 实例化对象,传入对应的参数,并执行 open 方法 */ Model.prototype.open = function () { //判断页面中是否有model盒子,如果有先删除,否则继续添加 if(!document.querySelector('.model')){ //把刚才创建的modalBox 添加到 页面 body 标签中 document.body.append(this.modelBox) //要等盒子显示出来,就可以绑定关闭点击事件 this.modelBox.querySelector('i').addEventListener('click', ()=>{ //这里要用箭头函数,普通函数的this指向的是i //这个this指向实例对象 this.close() }) } } /* >①:close方法 >- 写到构造函数的原型对象身上 >- 把刚才创建的modalBox 从页面 body 标签中 删除 >- 需要注意,x关闭按钮是在模态框里面 所以应该是页面显示这个模态框就要绑定事件 页面显示模态框是在open里面所以绑定关闭事件也写到 open方法里面 */ Model.prototype.close = function () { this.modelBox.remove() } //删除按钮 document.querySelector('#delete').addEventListener('click', () => { //先调用Model构造函数 const del = new Model('温馨提示', '您没有权限删除操作```') //实例对象调用open del.open() }) //登录按钮 document.querySelector('#login').addEventListener('click', () => { //先调用Model构造函数 const log = new Model('温馨提示', '请先注册```') //实例对象调用open log.open() }) </script> </body>
开发中我们经常需要复制一个对象。如果直接用赋值会有下面问题:
问题:直接复制后对复制的对象进行操作源对象会被修改
首先浅拷贝和深拷贝只针对引用类型
浅拷贝:拷贝的是地址!!
常见方法:
- 拷贝对象:Object.assgin() / 展开运算符 {…obj} 拷贝对象
- 拷贝数组:Array.prototype.concat() 或者 […arr]
<body> <script> 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 = {} Object.assign(o, obj) o.age = 20 o.family.baby = '老pink' console.log(o) //{uname: 'pink', age: 20, family: {…}} console.log(obj) //{uname: 'pink', age: 18, family: {…}} </script> </body>
如果是简单数据类型拷贝 值 ,引用数据类型拷贝的是 地址 (简单理解: 如果是单层对象,没问题,如果有多层就有问题)
总结
- 直接赋值和浅拷贝有什么区别?
直接赋值的方法,只要是对象,都会相互影响,因为是直接拷贝对象栈里面的地址
浅拷贝如果是一层对象,不相互影响,如果出现多层对象拷贝还会相互影响- 浅拷贝怎么理解?
拷贝对象之后,里面的属性值是简单数据类型直接拷贝值
如果属性值是引用数据类型则拷贝的是地址
首先浅拷贝和深拷贝只针对引用类型
深拷贝:拷贝的是对象,不是地址
1. 通过递归实现深拷贝
函数递归:
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数
1.1.简单理解:函数内部自己调用自己, 这个函数就是递归函数
1.2.递归函数的作用和循环效果类似
1.3.由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件 return
练习 • 利用递归函数实现 setTimeout 模拟 setInterval效果
需求:
①:页面每隔一秒输出当前的时间
②:输出当前时间可以使用:new Date().toLocaleString()
<body> <script> const obj = { //属性 uname: 'pink', age: 18, //数组 hobby: ['乒乓球', '足球'], //方法 family: { baby: '小pink' } } const o = {} // 拷贝函数 function deepCopy(newObj, oldObj) { debugger for (let k in oldObj) { // k 属性名 oldObj[k] 属性值 // 处理数组的问题 一定先写数组 在写 对象 不能颠倒 if (oldObj[k] instanceof Array) { newObj[k] = [] // newObj[k] 接收 [] hobby // oldObj[k] ['乒乓球', '足球'] //递归把数组装入新对象 deepCopy(newObj[k], oldObj[k]) } else if (oldObj[k] instanceof Object) { newObj[k] = {} deepCopy(newObj[k], oldObj[k]) } else { // k 属性名 uname age oldObj[k] 属性值 18 // newObj[k] === o.uname 给新对象添加属性 newObj[k] = oldObj[k] } } } deepCopy(o, obj) // 函数调用 两个参数 o 新对象 obj 旧对象 console.log(o) o.age = 20 o.hobby[0] = '篮球' o.family.baby = '老pink' console.log(obj) console.log([1, 23] instanceof Object) // 复习 // const obj = { // uname: 'pink', // age: 18, // hobby: ['乒乓球', '足球'] // } // function deepCopy({ }, oldObj) { // // k 属性名 oldObj[k] 属性值 // for (let k in oldObj) { // // 处理数组的问题 k 变量 // newObj[k] = oldObj[k] // // o.uname = 'pink' // // newObj.k = 'pink' // } // } </script> </body>
2. js库lodash里面cloneDeep内部实现了深拷贝
<body> <!-- 先引用 --> <script src="./lodash.min.js"></script> <script> const obj = { uname: 'pink', age: 18, hobby: ['乒乓球', '足球'], family: { baby: '小pink' } } const o = _.cloneDeep(obj) console.log(o) o.family.baby = '老pink' console.log(obj) </script> </body>
3. 通过JSON.stringify()实现
<body> <script> const obj = { uname: 'pink', age: 18, hobby: ['乒乓球', '足球'], family: { baby: '小pink' } } // 把对象转换为 JSON 字符串 // console.log(JSON.stringify(obj)) //JSON.stringify():把对象转化成JSON字符串 //JSON.parse():再把字符串转化成新的对象 const o = JSON.parse(JSON.stringify(obj)) console.log(o) o.family.baby = '123' console.log(obj) </script> </body>
总结
- 实现深拷贝三种方式?
1.1自己利用递归函数书写深拷贝
1.2利用js库 lodash里面的 _.cloneDeep()
1.3利用JSON字符串转换
了解 JavaScript 中程序异常处理的方法,提升代码运行的健壮性。
异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行
总结
- 抛出异常我们用那个关键字?它会终止程序吗?
1.1throw 关键字
1.2会中止程序- 抛出异常经常和谁配合使用?
2.1Error 对象配合 throw 使用
我们可以通过try / catch 捕获错误信息(浏览器提供的错误信息) try 试试 catch 拦住 finally 最后
<body> <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('你看看,选择器错误了吧') // 需要加return 中断程序 // return } finally { // 不管你程序对不对,一定会执行的代码 alert('弹出对话框') } console.log(11) } fn() </script> </body>
总结
- 捕获异常我们用那3个关键字?可能会出现的错误代码写到谁里面
1.1try catch finally
1.2try- 怎么调用错误信息?
2.1利用catch的参数
我们可以通过try / catch 捕获错误信息(浏览器提供的错误信息)
this 是 JavaScript 最具“魅惑”的知识点,不同的应用场合 this 的取值可能会有意想不到的结果,在此我们对以往学习过的关于【 this 默认的取值】情况进行归纳和总结。
目标: 了解函数中 this 在不同场景下的默认值,知道动态指定函数 this 值的方法
普通函数的调用方式决定了 this 的值,即【谁调用 this 的值指向谁】
普通函数没有明确调用者时 this 值为 window,严格模式下没有调用者时 this 的值为 undefined
<body> <button>点击</button> <script> // 普通函数: 谁调用我,this就指向谁 console.log(this) // window function fn() { console.log(this) // window } window.fn() window.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() </script> </body>
总结
- 普通函数this指向我们怎么记忆?
【谁调用 this 的值指向谁】- 普通函数严格模式下指向谁?
严格模式下指向 undefined
箭头函数中的 this 与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在 this !
- 箭头函数会默认帮我们绑定外层 this 的值,所以在箭头函数中 this 的值和外层的 this 是一样的
2.箭头函数中的this引用的就是最近作用域中的this
3.向外层作用域中,一层一层查找this,直到有this的定义
注意情况1:
在开发中【使用箭头函数前需要考虑函数中 this 的值】,事件回调函数使用箭头函数时,this 为全局的 window
因此DOM事件回调函数如果里面需要DOM对象的this,则不推荐使用箭头函数
注意情况2:
同样由于箭头函数 this 的原因,基于原型的面向对象也不推荐采用箭头函数
箭头函数总结
- 函数内不存在this,沿用上一级的,过程:向外层作用域中,一层一层查找this,直到有this的定义
- 不适用:
构造函数,原型函数,字面量对象中函数,dom事件函数- 适用:
需要使用上层this的地方
JavaScript 中还允许指定函数中 this 的指向,有 3 个方法可以动态指定普通函数中 this 的指向
1.call()
2.apply()
3bind()
使用 call 方法调用函数,同时指定被调用函数中 this 的值
语法:
fun.call(thisArg, arg1, arg2, ...)
fun.call(thisArg, arg1, arg2, …)
1.thisArg:在 fun 函数运行时指定的 this 值
2.arg1,arg2:传递的其他参数
3.返回值就是函数的返回值,因为它就是调用函数
思考
- call的作用是?
调用函数,并可以改变被调用函数里面的this指向- call 里面第一个参数是 指定this, 其余是实参,传递的参数整体做个了解,后期用的很少
使用 apply 方法调用函数,同时指定被调用函数中 this 的值
语法:
1.thisArg:在fun函数运行时指定的 this 值
2.argsArray:传递的值,必须包含在数组里面
3.返回值就是函数的返回值,因为它就是调用函数
4.因此 apply 主要跟数组有关系,比如使用 Math.max() 求数组的最大值
<body> <script> const obj = { age: 18 } function fn(x, y) { console.log(this) // {age: 18} 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, 44, 77] const max = Math.max.apply(Math, arr) const min = Math.min.apply(null, arr) console.log(max, min) // 使用场景: 展开运算符求数组最大值 console.log(Math.max(...arr)) </script> </body>
思考
- call和apply的区别是?
1.都是调用函数,都能改变this指向
2.参数不一样,apply传递的必须是数组
bind() 方法不会!!!!调用函数。但是能改变函数内部this 指向
语法:
1.thisArg:在 fun 函数运行时指定的 this 值
2.arg1,arg2:传递的其他参数
3.返回由指定的 this 值和初始化参数改造的 原函数拷贝 (新函数)
4.因此当我们只是想改变 this 指向,并且不想调用这个函数的时候,可以使用 bind,比如改变定时器内部的this指向.
相同点:
都可以改变函数内部的this指向.
区别点:
call 和 apply 会调用函数, 并且改变函数内部this指向.
call 和 apply 传递的参数不一样, call 传递参数 aru1, aru2…形式 apply 必须数组形式[arg]
bind 不会调用函数, 可以改变函数内部this指向.
主要应用场景:
call 调用函数并且可以传递参数
apply 经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值
bind 不调用函数,但是还想改变this指向. 比如改变定时器内部的this指向.
防抖(debounce):单位时间内,频繁触发事件,只执行最后一次
所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间
举个栗子:
北京买房政策:需要连续5年的社保,如果中间有一年断了社保,则需要从新开始计算
比如,我 2020年开始计算,连续交5年,也就是到2024年可以买房了,包含2020年
但是我 2024年断社保了,整年没交,则需要从2025年开始算第一年往后推5年… 也就是 2029年才能买房…
开发使用场景- 搜索框防抖
假设输入就可以发送请求,但是不能每次输入都去发送请求,输入比较快发送请求会比较多
我们设定一个时间,假如300ms, 当输入第一个字符时候,300ms后发送请求,但是在200ms的时候又输入了一个字符,
则需要再等300ms 后发送请求
案例 利用lodash防抖函数来处理-鼠标滑过盒子显示文字
<body> <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) //要求: 鼠标在盒子上移动,鼠标停止之后,500ms后里面的数字就会变化+1 //利用lodash库实现防抖 -- 500毫秒之后采取+1 //语法:_.debounce(fun,时间) box.addEventListener('mousemove', _.debounce(mouseMove, 500)) </script> </body>
案例 利用防抖来处理-鼠标滑过盒子显示文字(手写防抖函数)
要求: 鼠标在盒子上移动,鼠标停止之后,500ms后里面的数字就会变化+1
要求: 鼠标在盒子上移动,鼠标停止之后,500ms后里面的数字就会变化+1
利用防抖的方式实现
核心思路:
利用定时器实现,当鼠标滑过,判断有没有定时器,还有就清除,以最后一次滑动为准开启定时器
①: 写一个防抖函数debounce ,来控制这个操作函数(mouseMove)
②: 防抖函数传递2个参数, 第一个参数 mouseMove函数,第二个参数 指定时间500ms
③: 鼠标移动事件,里面写的是防抖函数
④: 声明定时器变量 timeId
⑤: 但是节流函数因为里面写的函数名 debounce(mouseMove, 500), 是调用函数,无法再次调用执行,所以需要在节流函数里面写return 函数 这样可以多次执行
⑥: 当鼠标每次滑动时都先判断是否有定时器了,如果有定时器,则先清除以前的定时器
⑦: 否则开启定时器, 记得存到变量里面。在设定时间内,调用函数
<body> <div class="box"></div> <script> const box = document.querySelector('.box') let i = 1 function mouseMove() { box.innerHTML = i++ } // //box.addEventListener('mousemove',mouseMove) //手写防抖函数 //核心是利用setTimeout定时器来实现 //1.声明定时器变量 //2.每次鼠标移动(事件触发)的时候都要先判断是否有定时器,如果有先清除以前的定时器 //3.如果没有定时器,则开启定时器。 //4.定时器里写函数调用 //定义防抖函数 function debounce(fn,time){ //1.声明定时器变量 let timer //return返回一个匿名函数 return function(){ //2.每次鼠标移动(事件触发)的时候都要先判断是否有定时器,如果有先清除以前的定时器 if(timer) {clearTimeout(timer)} //3.如果没有定时器,则开启定时器。 timer =setTimeout (function(){ //4.定时器里写函数调用 fn() },time) } } //调用debounce()返回return的结果,相当于调用了一次函数,函数里面有定时器定时调用fn box.addEventListener('mousemove',debounce(mouseMove,500)) </script> </body>
节流(throttle):单位时间内,频繁触发事件,只执行一次
所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数
只有等到了上一个人做完核酸,整个动作完成了, 第二个人才能排队跟上
开发使用场景 – 小米轮播图点击效果 、 鼠标移动、页面尺寸缩放resize、滚动条滚动 就可以加节流
假如一张轮播图完成切换需要300ms, 不加节流效果,快速点击,则嗖嗖嗖的切换
加上节流效果, 不管快速点击多少次, 300ms时间内,只能切换一张图片。
案例 利用lodash节流函数来处理-鼠标滑过盒子显示文字
案例 利用节流来处理-鼠标滑过盒子显示文字
要求: 鼠标在盒子上移动,里面的数字就会变化+1
①: 如果以前方式,每次鼠标移动就会有大量操作,触发频次太高
要求: 鼠标在盒子上移动,里面的数字就会变化+1
利用节流的方式, 鼠标经过,500ms ,数字才显示
核心思路:
利用时间相减:移动后的时间 - 刚开始移动的时间 >= 500ms 我才去执行 mouseMove函数
①: 写一个节流函数throttle ,来控制这个操作函数(mouseMove), 500ms之后才去执行这个函数
②: 节流函数传递2个参数, 第一个参数 mouseMove函数,第二个参数 指定时间500ms
③: 鼠标移动事件,里面写的是节流函数
④: 声明一个起始时间 startTime = 0
⑤: 但是节流函数因为里面写的函数名 throttle(mouseMove, 500), 是调用函数,无法再次调用执行
,所以需要在节流函数里面写return 函数 这样可以多次执行
⑥:记录当前时间 now = Date.now()
⑦:进行判断 如果大于等于 500ms,则执行函数, 但是千万不要忘记 让 起始时间 = 现在时间
<body> <div class="box"></div> <script> const box = document.querySelector('.box') let i = 1 function mouseMove() { box.innerHTML = i++ } // //box.addEventListener('mousemove',mouseMove) //手写防抖函数 //核心是利用setTimeout定时器来实现 //1.声明定时器变量 //2.每次鼠标移动(事件触发)的时候都要先判断是否有定时器,如果有定时器则不开启新定时器 //3.如果没有定时器,则开启定时器,记得存到变量里面 //4.定时器里面调用执行的函数 //5.定时器里面要把定时器清空 //定义节流函数 function throttle(fn,time){ //1.声明定时器变量 let timer = null //return返回一个匿名函数 return function(){ //2.每次鼠标移动(事件触发)的时候都要先判断是否有定时器,如果有定时器则不开启新定时器 if(!timer) { //3.如果没有定时器,则开启定时器,记得存到变量里面 timer =setTimeout (function(){ //4.定时器里写函数调用 fn() //清空定时器(在开启定时器里面用clearTimeout清定时器是无法清掉的(因为定时器还在运作),正确的做法是赋null) timer = null },time) } } } //调用debounce()返回return的结果,相当于调用了一次函数,函数里面有定时器定时调用fn box.addEventListener('mousemove',throttle(mouseMove,500)) </script> </body>
思考
- 节流和防抖的区别是?
节流: 就是指连续触发事件但是在 n 秒中只执行一次函数,比如可以利用节流实现 1s之内 只能触发一次鼠标移动事件
防抖:如果在 n 秒内又触发了事件,则会重新计算函数执行时间- 节流和防抖的使用场景是?
节流: 鼠标移动,页面尺寸发生变化,滚动条滚动等开销比较大的情况下
防抖: 搜索框输入,设定每次输入完毕n秒后发送请求,如果期
间还有输入,则从新计算时间
<body> <div class="box"></div> <script src="./lodash.min.js"></script> <script> const box = document.querySelector('.box') let i = 1 // 让这个变量++ // 鼠标移动函数 function mouseMove() { box.innerHTML = ++i // 如果里面存在大量操作 dom 的情况,可能会卡顿 } // box.addEventListener('mousemove', mouseMove) // lodash 节流写法 // box.addEventListener('mousemove', _.throttle(mouseMove, 500)) // lodash 防抖的写法 box.addEventListener('mousemove', _.debounce(mouseMove, 500)) </script> </body>
案例 页面打开,可以记录上一次的视频播放位置
分析:
两个事件:
①:ontimeupdate 事件在视频/音频(audio/video)当前的播放位置发送改变时触发
②:onloadeddata 事件在当前帧的数据加载完成且还没有足够的数据播放视频/音频(audio/video)的下一帧时触发
谁需要节流?
ontimeupdate , 触发频次太高了,我们可以设定 1秒钟触发一次
思路:
- 在ontimeupdate事件触发的时候,每隔1秒钟,就记录当前时间到本地存储
- 下次打开页面, onloadeddata 事件触发,就可以从本地存储取出时间,让视频从取出的时间播放,如果没有就默认为0s
- 获得当前时间 video.currentTime
<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="./lodash.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script> <script> //:ontimeupdate 事件在视频/音频 (audio/video)当前的播放位置发送改变时触发 //:onloadeddata 事件在当前的数据加载完成且还没有足够的数据播放视频/音频 (audio/video)的下一帧时触发 //谁需要节流? //ontimeupdate , 触发频次太高了,我们可以设定 1秒钟触发一次 // >**思路:** // >1. 在ontimeupdate事件触发的时候,每隔1秒钟,就记录当前时间到本地存储 // >2. 下次打开页面, onloadeddata 事件触发,就可以从本地存储取出时间,让视频从取出的时间播放,如果没有就默认为0s // >3. 获得当前时间 video.currentTime //1.获取元素 // const video = document.querySelector('video') // video.ontimeupdate = function(){ // console.log(11); //可以看到几秒钟后执行了很多次,但是我们不需要执行那么多次 // } //1.获取元素 const video = document.querySelector('video') video.ontimeupdate = _.throttle(()=>{ //console.log(video.currentTime); //获得当前的视频时间 //本地存储 视频已经播放的时间 localStorage.setItem('currenTime',video.currentTime) },1000) //页面打开,可以记录上一次的视频播放位置 //思路: //1.在ontimeupdate事件触发的时候,每隔1秒钟,就记录当前时间到本地存储 //下次打开页面,onloadeddata 事件触发,就可以从本地存储取出时间,让视频从取出的时间播放,如果没有就默认为0s //赋值给video.currentTime video.onloadeddata = ()=>{ //逻辑中断,前面为真则不执行后面 video.currentTime = localStorage.getItem('currentTime') || 0 } </script> </body>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。