作用域
局部作用域
分为函数作用域和块作用域
var不会产生块作用域
全局作用域
函数中未使用任何关键字声明的变量为全局变量
作用域链
子作用域能够访问父作用域,父级作用域无法访问子级作用域像在g函数中声明一个b f函数是无法访问的
垃圾回收机制GC
什么是垃圾回收机制
垃圾回收机制的算法
引用计数法
但它却存在一个致命的问题:嵌套引用(循环引用) 如果两个对象相互引用,尽管他们已不再使用,垃圾回收器不会进行回收,导致内存泄露。
标记清除法
闭包
闭包 = 内层函数 + 外层函数的变量
关键是返回函数,把函数赋值给全局的变量
典型例子
总结
变量提升和函数提升
打印出undefined 因为var有变量提升,会把var num提到当前的作用域的最前面但是没有赋值
函数提升
能在声明前调用函数 因为代码执行前会把所有函数的声明都提到当前作用域最前面
这个函数调用错误 因为函数表达式没有提升,它是一个赋值操作
函数参数
动态参数arguments
剩余参数 ...other
总结
展开运算符
可以将数组展开 ...arr1=1,2,3
典型应用:求最大最小值和合并数组
箭头函数(重要)
箭头函数更适用于那些本来需要匿名函数的地方
基本语法
1. 箭头函数属于表达式函数,因此不存在函数提升
2、只有一个参数可以省略小括号
3、如果函数体只有一行代码,可以写到一行上,并且无需写 return 直接返回值
4.加括号的函数体返回对象字面量表达式,就是返回对象时 对象本生带{} 函数体也有{} 分不清
就把函数体的{}写成()如下:
箭头函数的参数
用箭头函数求和
箭头函数this的指代
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this
就是看上一层作用域的this指向谁(一般函数是谁调用函数 this指向谁)
数组解构
就是把数组里的元素快速批量地赋值给变量的语法
典型应用
必须加分号的情况
变量和单元值数量不相等时
1、变量的数量大于单元值数量时,多余的变量将被赋值为 undefined
2、利用剩余参数解决变量少 单元值多的情况:剩余参数返回的还是一个数组
3.防止有undefined传递单元值的情况,可以设置默认值
4、按需导入,忽略某些返回值
5、支持多维数组的结构
对象解构
对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法
1. 赋值运算符 = 左侧的 {} 用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量
2. 对象属性的值将被赋值给与属性名相同的变量
3. 注意解构的变量名不要和外面的变量名冲突否则报错
4.对象中找不到与变量名一致的属性时变量值为 undefin
变量名要和属性名一样
要想变量名和属性名不一样咋办
数组对象解构
多级对象结构
选出data进行传参
forEach遍历数组
与map函数最大的区别在于forEach没有返回值
1.forEach 主要是遍历数组
2. 参数当前数组元素是必须要写的, 索引号可选
筛选数组filter方法
筛选商品综合案例
<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: '100.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',
},
]
// 写出渲染函数
function render(arr) {
let str = ''
arr.forEach(item => {
const { name, picture, price } = item
str += `
<div class="item">
<img src="${picture}" alt="">
<p class="name">${name}</p>
<p class="price">${price}</p>
</div> `
})
document.querySelector('.list').innerHTML = str
}
render(goodsList)
//根据价格进行筛选 用filter函数
document.querySelector('.filter').addEventListener('click', e => {
const { tagName, dataset } = e.target
let arr = goodsList
if (tagName === 'A') {
// console.log(111);
if (dataset.index === '1') {
// console.log(222)
arr = goodsList.filter(item => item.price > 0 && item.price <= 100)
} else if (dataset.index === '2') {
// console.log(222)
arr = goodsList.filter(item => item.price > 100 && item.price <= 300)
}
else if (dataset.index === '3') {
arr = goodsList.filter(item => item.price >= 300)
}
}
console.log(arr)
render(arr)
})
</script>
创建对象的方法
构造函数
new实例化的过程
实例成员和静态成员
基本包装类型
为什么基本数据类型string可以用属性和方法如num.toFix(),因为js底层把简单数据类型包装成了引用数据类型,如const str=‘pink’ 底层实际是const str=new String('pink')
Object静态方法
三个常用静态方法
1.Object.keys 静态方法获取对象中所有属性(键)
返回一个数组
2.Object.values 静态方法获取对象中所有属性值,返回的是一个数组
3.Object. assign 静态方法常用于对象拷贝
也经常使用的场景给对象添加属性
Array数组对象的方法
这些方法都可以去mdn查,不需要死记硬背
reduce方法
数组常见实例方法
数组其它常见方法
join可以选择拼接中间加符号
典型应用
数组常见方法- 伪数组转换为真数组 静态方法 Array.from()
<script>
// const arr = ['red', 'blue', 'green']
// const re = arr.find(function (item) {
// return item === 'blue'
// })
// console.log(re)
const arr = [
{
name: '小米',
price: 1999
},
{
name: '华为',
price: 3999
},
]
// 找小米 这个对象,并且返回这个对象
// const mi = arr.find(function (item) {
// // console.log(item) //
// // console.log(item.name) //
// console.log(111)
// return item.name === '华为'
// })
// 1. find 查找
// const mi = arr.find(item => item.name === '小米')
// console.log(mi)
// 2. every 每一个是否都符合条件,如果都符合返回 true ,否则返回false
const arr1 = [10, 20, 30]
const flag = arr1.every(item => item >= 20)
console.log(flag)
</script>
字符串常见方法
<script>
//1. split 把字符串 转换为 数组 和 join() 相反
// const str = 'pink,red'
// const arr = str.split(',')
// console.log(arr)
// const str1 = '2022-4-8'
// const arr1 = str1.split('-')
// console.log(arr1)
// 2. 字符串的截取 substring(开始的索引号[, 结束的索引号])
// 2.1 如果省略 结束的索引号,默认取到最后
// 2.2 结束的索引号不包含想要截取的部分
// const str = '今天又要做核酸了'
// console.log(str.substring(5, 7))
// 3. startsWith 判断是不是以某个字符开头
// const str = 'pink老师上课中'
// console.log(str.startsWith('pink'))
// 4. includes 判断某个字符是不是包含在一个字符串里面
const str = '我是pink老师'
console.log(str.includes('pink')) // true
</script>
渲染赠品案例
用到splite函数把字符串拆成数组,map函数对数组每个元素进行加工处理一下,返回新数组,如何再用join函数把数组转为字符串
<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('')
// // console.log(str)
// document.querySelector('div').innerHTML = str
document.querySelector('div').innerHTML = gift.split(',').map(item => `<span>【赠品】 ${item}</span> <br>`).join('')
</script>
</body>
购物车综合案例
<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茶叶,清洗球'
}
]
document.querySelector('.list').innerHTML = goodsList.map(function (item) {
const { name, price, picture, count, spec, gift } = item
// 注意这些html标签不能多一个空格也不能少一个空格
// 用Object.values获得对象的属性值 返回数组
const text = Object.values(spec).join('/')
// 有些数据没有gift所以要进行一个判断 gift.split字符串转数组 再用map处理每个元素返回新数组 再join变成字符串
const str = gift ? gift.split(',').map(item => `<span class="tag">【赠品】${item}</span>`).join('') : ''
// 因为小数的计算有精度问题 所以先转为整数先加减再除
const subPrice = ((price * 100 * count) / 100).toFixed(2)
return `
<div class="item">
<img src="${picture}" alt="">
<p class="name">${name}${str}</p>
<p class="spec">${text}</p>
<p class="price">${price}</p>
<p class="count">x${count}</p>
<p class="sub-total">${subPrice}</p>
</div>`
}).join('')
</script>
原型prototype(重要)
原来把方法写在构造函数里存在的问题:内存浪费,因为没创建一个实例对象,就有会内存中分配一次空间给function,因此引出了原型prototype,把对象的公共方法放在prototype下,那么分配一次,每次创建实例对象都会去prototype下找这个方法。
什么是原型 下面这页ppt很重要
1.每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型对象
2.构造函数和原型对象中的this 都指向 实例化的对象
3.我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
总结
数组方法扩展
扩展求最大值、最小值、求和
全都挂在到构造函数的prototype属性下 :精髓在于用了this而不是把设形参,this指向实例对象即调用者
<script>
// 自己定义 数组扩展方法 求和 和 最大值
// 1. 我们定义的这个方法,任何一个数组实例对象都可以使用
// 2. 自定义的方法写到 数组.prototype 身上
// 1. 最大值
const arr = [1, 2, 3]
Array.prototype.max = function () {
// 展开运算符
return Math.max(...this)
// 原型函数里面的this 指向谁? 实例对象 arr
}
// 2. 最小值
Array.prototype.min = function () {
// 展开运算符
return Math.min(...this)
// 原型函数里面的this 指向谁? 实例对象 arr
}
console.log(arr.max())
console.log([2, 5, 9].max())
console.log(arr.min())
// const arr = new Array(1, 2)
// console.log(arr)
// 3. 求和 方法
Array.prototype.sum = function () {
return this.reduce((prev, item) => prev + item, 0)
}
console.log([1, 2, 3].sum())
console.log([11, 21, 31].sum())
</script>
constructor 属性
在原型对象prototype里有个属性叫constructor,它的值就是构造函数
用下图的方式对原型对象赋值会把constructor的值给修改不再指向原来的构造函数,所以要重新对constructor进行赋值。但如果是对原型对象进行追加的形式进行赋值就不会改掉constructor的值。总之就是采用直接赋值的方式不要忘了给constructor赋值
直接赋值的形式
追加的形式
对象原型
实例对象都会有一个属性 __proto__ 指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 __proto__ 原型的存在。
关键是记住和理解这个图
、
原型继承
上述方法有一个问题:男人和女人都同时使用了同一个对象,根据引用类型的特点,他们指向同一个对象,修改一个就会都影响
解决办法,把公共的属性和方法写到一个新的构造函数中,然后给prototype进行new 对象()进行赋值,这样指向的就是俩个不同的存储空间
公式:子类的原型=new 父类
原型链
每一个对象都有__proto__属性,它指向构造函数的prototype,构造函数的prototype也是一个对象有__proto__属性,指向Object的prototype,Object的prototype是一个对象有__proto__属性,指向null
可以使用 instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
模态框综合案例
箭头函数没有this,里面的this指向上一级作用域的this对象 这个案例里有this的妙用
这里还有新增节点、删除结点的操作,详细看上一篇文章
<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. 模态框的构造函数
function Modal(title = '', message = '') {
// 公共的属性部分
this.title = title
this.message = message
// 因为盒子是公共的
// 1. 创建 一定不要忘了加 this
this.modalBox = document.createElement('div')
// 2. 添加类名
this.modalBox.className = 'modal'
// 3. 填充内容 更换数据
this.modalBox.innerHTML = `
<div class="header">${this.title} <i>x</i></div>
<div class="body">${this.message}</div>
`
// console.log(this.modalBox)
}
// 2. 打开方法 挂载 到 模态框的构造函数原型身上
Modal.prototype.open = function () {
if (!document.querySelector('.modal')) {
// 把刚才创建的盒子 modalBox 渲染到 页面中 父元素.appendChild(子元素)
document.body.appendChild(this.modalBox)
// 获取 x 调用关闭方法
this.modalBox.querySelector('i').addEventListener('click', () => {
// 箭头函数没有this 上一级作用域的this
// 这个this 指向 m
this.close()
})
}
}
// 3. 关闭方法 挂载 到 模态框的构造函数原型身上
Modal.prototype.close = function () {
document.body.removeChild(this.modalBox)
}
// 4. 按钮点击
document.querySelector('#delete').addEventListener('click', () => {
const m = new Modal('温馨提示', '您没有权限删除')
// 调用 打开方法
m.open()
})
// 5. 按钮点击
document.querySelector('#login').addEventListener('click', () => {
const m = new Modal('友情提示', '您还么有注册账号')
// 调用 打开方法
m.open()
})
</script>
</body>
浅拷贝
浅拷贝数组、对象的方法
如果对象里面还有引用类型就会出现问题
总结
递归函数
利用递归函数实现 setTimeout 模拟 setInterval效果
深拷贝
1.利用递归实现深拷贝
深拷贝是做到拷贝出来的新对象和旧对象互不影响,要想实现深拷贝用到函数递归,遇到普通基本类型数据直接赋值就行,当遇到数组、对象再次调用递归函数,但一定要先数组再对象,因为数组也是对象。
<script>
const obj = {
uname: 'pink',
age: 18,
hobby: ['乒乓球', '足球'],
family: {
baby: '小pink'
}
}
const o = {}
// 拷贝函数
function deepCopy(newObj, oldObj) {
debugger
for (let k in oldObj) {
// 处理数组的问题 一定先写数组 在写 对象 不能颠倒
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>
2.利用js库 lodash里面的 _.cloneDeep() 有返回值
3.利用JSON字符串转换
异常处理
throw抛异常
try/catch 捕获错误信息
debugger 就是打断点
this指向
函数里的this 对象里没有this
this指向-普通函数
普通函数没有明确调用者时 this 值为 window,严格模式下没有调用者时 this 的值为 undefined
this指向-箭头函数
改变this
call函数
apply函数
call和apply的区别是?都是调用函数,都能改变this指向 参数不一样,apply传递的必须是数组
apply的使用场景,求最大值
bind函数
例子 改变setTimeout函数的this指向
总结
防抖debounce
用法:lodash提供的_.debounce函数
2.用手写的防抖函数
为什么是返回一个函数,因为 box.addEventListener('mousemove', debounce(mouseMove, 200))中的的debounce是带小括号的只会执行一次
<script>
const box = document.querySelector('.box')
let i = 1 // 让这个变量++
// 鼠标移动函数
function mouseMove() {
box.innerHTML = ++i
// 如果里面存在大量操作 dom 的情况,可能会卡顿
}
// 防抖函数
function debounce(fn, t) {
let timeId
return function () {
// 如果有定时器就清除
if (timeId) clearTimeout(timeId)
// 开启定时器 200
timeId = setTimeout(function () {
fn()
}, t)
}
}
// box.addEventListener('mousemove', mouseMove)
box.addEventListener('mousemove', debounce(mouseMove, 200))
</script>
节流throttle
用法:lodash提供的_.throttle函数
手写throttle函数
在定时器里面是无法清除定时器的,可以用timer=null 来清除定时器
总结
节流案例
记录视频播放事件
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
<script>
// 1. 获取元素 要对视频进行操作
const video = document.querySelector('video')
video.ontimeupdate = _.throttle(() => {
// console.log(video.currentTime) 获得当前的视频时间
// 把当前的时间存储到本地存储
localStorage.setItem('currentTime', video.currentTime)
}, 1000)
// 打开页面触发事件,就从本地存储里面取出记录的时间, 赋值给 video.currentTime
video.onloadeddata = () => {
// console.log(111)
video.currentTime = localStorage.getItem('currentTime') || 0
}
</script>