内容
js也是面向对象的语言。
这里的js高级就是es6+,包含了es6以上的语法。
作用域
作用域被分为全局作用域和局部作用域,局部作用域包含函数作用域和块级作用域。
局部作用域
函数作用域
1-函数的参数也是函数内部的一个局部变量
2-函数执行完毕,里面的变量实际被清空了。
3-var:函数作用域;存在变量提升;可重复定义;在最外层声明的变量作为window的属性。(在函数内声明的,捕获作为window的属性)
块级作用域
1-var声明的变量,不存在块级作用域,也就是说在块级{}比如for和if里面用var声明变量,外面也是可用访问到的。
全局作用域
1-不声明的变量,会被视为全局变量
2-写在最外层的是全局变量,当有两个script标签,其实也可以访问到,如下代码,是可用正常输出i的
<body>
<script>
let i =1
</script>
<script>
console.log(i)
</script>
</body>
作用域链
1-很简单就是一个就近原则,从使用的地方,逐步向外查找声明,先找到谁声明了就是什么作用域,上图a的作用域就是f()中的函数作用域。
2-作用域链就是一种变量查找机制。
js垃圾回收机制
垃圾回收机制是了解这种回收机制的执行过程,为闭包做铺垫。
什么是垃圾回收机制
内存泄漏不是说一些东西泄露出来了。而是内存垃圾没有及时得到释放。
内存的生命周期
分配—–使用——回收
for (let i = 1; i <= 3; i++) {
}
let num = 10
function fn() {
const str = 'andy'
console.log(str)
}
fn()
fn()
fn()
</script>
如上我调用三次fn(),不就声明了三次str吗?为什么不冲突?
其实第一次str用完后,这个局部变量就被垃圾回收器自动回收了,第二次调用的时候,第一个已经没了,
垃圾回收的算法说明
引用计数法
如图当有两个指向堆,那么就有两个应用,当person=1;p=null后,就没有指向了,那么引用为0,就会回收。
当嵌套引用时,会内存泄漏
如上,当这个函数执行完毕后,o1和o2因为是局部变量会被回收,但是内部堆里面的两个指向引用都为1,则不会被回收,导致内存泄漏。
标记清除法
先扫描内存中的对象,然后从根部global(就是全局对象一般为window)出发寻找,找到的的就有用,找不到的就说明无用了,清除掉。
还是那个对象的相互嵌套引用,但是当函数执行完毕后,o1和o2已经被回收掉了,虽然里面还有嵌套,但是已经无法从根部访问到它们,所以会被标记清除,就不会再内存泄漏了。
闭包
简单来说闭包就是,一个外函数包含了一个内函数,内函数还使用了外函数的变量。
我们可以看到,闭包被叫做closure闭合的,local是局部的,global是全局的。
误区:闭包并不是这个外函数outer,而是由内函数和外函数的变量组成。
闭包就是能够读取其他函数内部变量的私有变量
搞个闭包的概念是吃饱了撑着?它有什么作用?
闭包能让外部访问到函数内部的变量。
// 简单的写法
// function outer() {
// let a = 10
// function fn() {
// console.log(a)
// }
// fn()
// }
// outer()
// 常见的闭包的形式 外部可以访问使用 函数内部的变量
// function outer() {
// let a = 100
// function fn() {
// console.log(a)
// }
// return fn
// }
// outer() === fn === function fn() {}
// const fun = function fn() { }
// // 常见的写法2
// function outer() {
// let a = 100
// return function () {
// console.log(a)
// }
// }
// const fun = outer()
// fun() // 调用函数
// 常见的写法3
// function outer() {
// let a = 100
// return function () {
// return a
// }
// }
// // console.log(outer())
// const fun1 = outer()
// // 调用函数
// console.log(fun1())
如上,其实就是重新调用了一次内函数,实现了使用外函数的变量。
问题
如果闭包就是能够读取其他函数内部变量的变量值,那么为什么不直接return变量?如下
function f1(){
var n=123;
return n;
}
x=f1();
alert(x); ///123
因为这里拿到的是一个值,而闭包还有很多功能
- 直接 return 返回的是变量值,闭包返回的是执行环境;
- 闭包不是为了让函数外部拿到内部变量。而是为了得到的私有变量不被随意修改;
- return 出来的是一个值,不是变量本身,此处的 return 是取得私有变量值的一种方法,跟闭包没有严格关系;
- 闭包作为一个可以访问函数内部变量的函数,更多的是可以在其中添加其他的条件,过滤无效的值。如果直接return 变量,然后赋值的话,还要在后续去判断这个值的有效性。
参考链接https://segmentfault.com/q/1010000006178363
所以如果直接返回拿到的只是一个值,不是私有变量;而闭包是通过重新访问内函数,达到访问外函数私有变量的效果,然后添加其他条件,过滤无效的值,如果直接返回变量值,那么无法判断这个值的合法性。
闭包的应用,统计函数调用的次数
如上,如果直接这样写,这个count是全局变量,就容易会被篡改
就像上面提到的直接返回return变量,它得到的只是一个值赋予一个变量,如果我们是去利用这个变量去做一个统计,那么它是可以被随意修改的,但是使用闭包的方法,就不会被随意篡改。
闭包可能引起的问题
闭包可能会引起内存泄漏,因为当那个私有变量不需要再被使用,但是如上result—-fn—fun—count,这个count是能被根部找到的,所以标记清除法和引用计数法的回收机制,都无法回收这个count,则导致了内存泄漏。
变量提升
1-变量提示只有var定义的变量才会有,let和const是没有的,它的机制是在代码执行前,先去查找当前作用域下所有的var声明的变量,然后提升到当前作用域的最前面。
2-只提升声明,不提升赋值
console.log(num + '件')
var num = 10
上面这段代码打印的是undefined件,而不是10件
因为它等同于下面代码,只提升了声明,但不提升赋值。
var num
console.log(num + '件')
num = 10
误区:
我以前的理解是var会直接提升到所有代码的最上面,自然以为它变成了全局作用域,其实它只能提升到当前作用域,在一个函数中使用var,也只是把声明提升到函数所有代码的最上面,不会提升到函数外面。
<script>
let num = 1
function fn() {
console.log(num)
var num = 10
}
</script>
等于
<script>
let num = 1
function fn() {
var num
console.log(num)
num = 10
}
</script>
函数进阶
函数提升
函数提升即,函数其实在声明之前就可以被调用。如下
fn()
function fn(){
console.log('函数提升')
}
这段代码是可以正常打印的,但是我在声明前就使用了它
执行机制
只提示函数声明,不提升函数调用,把函数声明提升到当前作用域的最前面。
函数表达式必须先声明后调用如下这一段代码会报错
因为这种函数表达式的声明方法,就没有函数提升,它是一种变量赋值,有变量提升,var fun会被提升到当前作用域最前面,但是后面的赋值(function的内容),是不提升的,自然无法运行。
函数参数
函数参数有三种,默认参数,动态参数,剩余参数。
箭头函数
动态参数
动态参数,简而言之就是当不知道用户要传递多少个参数进来,如需求:不管用户传递多少个值,都要累加,这种情况下我们就无法写参数来接收,只能通过动态参数,通过动态参数arguments这个函数内部伪数组变量来实现。
<script>
function getSum() {
// arguments 动态参数 只存在于 函数里面
// 是伪数组 里面存储的是传递过来的实参
// console.log(arguments) [2,3,4]
let sum = 0
for (let i = 0; i < arguments.length; i++) {
sum += arguments[i]
}
console.log(sum)
}
getSum(2, 3, 4)
getSum(1, 2, 3, 4, 2, 2, 3, 4)
</script>
注意这个argument只在当前函数内有效,在外面使用打印是打印不了的
剩余参数
剩余参数可以把多余的参数写入一个数组,如下代码
<script>
function getSum(a, b, ...arr) {
console.log(arr) // 使用的时候不需要写 ...
}
getSum(2, 3)
getSum(1, 2, 3, 4, 5)
</script>
其中a,b是变量,当用户传递过来的参数超过两个,则把多余的写入到arr数组中(arr这个名字自己随便设置,这个数组只能在当前函数使用)
注意:arr数组在使用中,不需要加…
剩余参数和动态参数的区别
1-它们都能实现多个不确定参数的传入使用
2-动态参数使用的是argument伪数组,剩余参数是自己设置的数组名称,它是真数组
3-开发中建议使用剩余参数,因为箭头函数中没有argument。
展开运算符
在函数参数里面的…是剩余参数,在数组中使用的叫展开运算符。
如上,它可以把数组展开,那有什么用呢?
1-平时我们判断数组里的值的最大值和最小值,是写一个for循环去慢慢判断,当有了展开运算符,我们就可以把这个数组展开当做多个数字去使用Math对象提供的方法如max()和min()去判断。
2-我们还可以合并数组,把两个数组展开,然后按照正常赋值给一个数组。
<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>
箭头函数
基本语法
箭头函数属于表达式函数,没有函数提升。
<script>
// 1-原函数表达式
const fn = function(){
console.log('原函数表达式')
}
fn()
// 2-箭头函数
const fn1 = () => {
console.log('箭头函数')
}
fn1()
// 3-箭头函数当只有一个参数的时候,小括号可以省略,注意没有参数的话要写小括号
const fn2 = x => {
console.log(x)
}
fn2(2)
//4-只有一行代码可以省略大括号
const fn3 = x => console.log(x)
fn3('只有一行代码可以省略大括号')
//5-当只有一行return代码可以写成
const fn4= (x, y) => x + y
console.log(fn4(1,2))
</script>
以前的去除实际默认事件就有箭头函数的写法,比普通写法简洁很多。
6-箭头函数还可以直接返回对象
因为对象是大括号,函数体也是大括号,所以把函数体的大括号换成了小括号
箭头函数参数
箭头函数this
箭头函数的this不是以前的谁调用就指向 谁,箭头函数没有自己的this,会沿用作用域链上一层的this
<script>
// 以前this的指向: 谁调用的这个函数,this 就指向谁
// console.log(this) // window
// // 普通函数
// function fn() {
// console.log(this) // window
// }
// window.fn()
// // 对象方法里面的this
// const obj = {
// name: 'andy',
// sayHi: function () {
// console.log(this) // obj
// }
// }
// obj.sayHi()
// 2. 箭头函数的this 是上一层作用域的this 指向
// const fn = () => {
// console.log(this) // window
// }
// fn()
// 对象方法箭头函数 this
// const obj = {
// uname: 'pink老师',
// sayHi: () => {
// console.log(this) // this 指向谁? window,它沿用了上一层的this
// }
// }
// obj.sayHi()
const obj = {
uname: 'pink老师',
sayHi: function () {
console.log(this) // obj
let i = 10
const count = () => {
console.log(this) // obj 沿用上一层的this
}
count()
}
}
obj.sayHi()
</script>
在事件中的匿名函数,还是不建议使用箭头函数。
解构赋值
数组解构
它的作用就是批量声明赋值,把数组里面的值赋予给声明的不同的变量
<script>
// const arr = [100, 60, 80]
// 数组解构 赋值
// // const [max, min, avg] = arr
const [max, min, avg] = [100, 60, 80]
// // const max = arr[0]
// // const min = arr[1]
// // const avg = arr[2]
console.log(max) // 100
console.log(avg) // 80
// 交换2个变量的值
let a = 1
let b = 2; // 这里必须加上分号
[b, a] = [a, b]
console.log(a, b)
</script>
<script>
// const pc =['小米','海尔','联想','方正']
// const [xm,hr,lx,fz] = ['小米','海尔','联想','方正']
// console.log(xm,hr,lx,fz)
function getValue(){
return[100,60]
}
let[max,min] = getValue()
console.log(max,min)
</script>
注意解构的时候也要加上let或者const,否则变量就会变成未声明的,就会变成全局变量。
js必须加分号的两种情况
一种是立即执行函数,一种是数组的直接使用。
const arr = [1, 2, 3]
const str = 'pink';
[1, 2, 3].map(function (item) {
console.log(item)
})
如上,如果不加分号,编译器会人为这个[1,2,3]不应该出现在这,会把这一行语句上移,就变成了
const arr = [1, 2, 3]
const str = 'pink' [1, 2, 3].map(function (item) {
console.log(item)
})
所以就会报错。
数组解构细节和问题
// 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
对象解构
注意对象解构的变量必须和属性名相同,它们提供名字来对应上。
为什么数组解构不用呢?因为数组是有序的,对象是无序的,无序就得用名字来对应
// 对象解构
const obj = {
uname: 'pink老师',
age: 18
}
// obj.uname
// obj.age
// const uname = 'red老师'
// 解构的语法
const { uname, age } = {age: 18, uname: 'pink老师' }
当然这个变量名字也是可以改的,不然如果原先已经声明过了,岂不是这个对象就不能解构了?但是有一定的规则。(旧变量名字:新变量名字)
const { uname: username, age } = { uname: 'pink老师', age: 18 }
这样如果原先就声明了uname变量,在对象解构的时候就能用username代替
数组对象的解构
const pig = [
{
uname: '佩奇',
age: 6
}
]
const [{ uname, age }] = pig
// const [{ uname }] = pig 你不一定要获取完,你就获取uname也行,那么age就没值
console.log(uname)
console.log(age)
当数组里面有两个对象,变量名放在不同大括号中,也用逗号隔开,如下图
<script>
const goods =[
{
goodsName:'xiaomia',
price:9999
},
{
userName:'zdq',
age:21
}
]
const[{goodsName,price},{userName,age}] = goods
console.log(goodsName,price,userName,age)
</script>
多级对象解构
对象包对象
数组包对象,对象又包对象
const person = [
{
name: '佩奇',
family: {
mother: '猪妈妈',
father: '猪爸爸',
sister: '乔治'
},
age: 6
}
]
const [{ name, family: { mother, father, sister } }] = person
console.log(name)
console.log(mother)
console.log(father)
console.log(sister)
真实案例JSON数据的使用
<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选出当做参数传递给 函数
// const { data } = msg
// msg 虽然很多属性,但是我们利用解构只要 data值
function render({ data }) {
// const { data } = arr
// 我们只要 data 数据
// 内部处理
console.log(data)
}
render(msg)
// 需求3, 为了防止msg里面的data名字混淆,要求渲染函数里面的数据名改为 myData
function render({ data: myData }) {
// 要求将 获取过来的 data数据 更名为 myData
// 内部处理
console.log(myData)
}
render(msg)
</script>
数组的forEach方法和map方法遍历数组
<script>
// forEach 就是遍历 加强版的for循环 适合于遍历数组对象
const arr = ['red', 'green', 'pink']
const result = arr.forEach(function (item, index) {
console.log(item) // 数组元素 red green pink
console.log(index) // 索引号
})
// console.log(result) 这里是undefined 因为forEach根本没有返回值
</script>
一般匿名函数会用箭头函数代替,如(item,index)=>{}
1-map是返回一个新数组,但是forEach不返回东西。
2-参数有三个,分别是数组当前项值item和数组值的索引index,还有原始数组input,item必须写,其他两个可以不写。
3-forEach通过语句可以改变原数组,map通过语句也不改变原数组,因为它生成了一个新数组,操作也在新数组中操作。
filter方法
filter过滤,筛选
<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>
你可以自己设置函数体内容,返回相应条件的新数组。
其中currentvalue必须传入,索引index可以不传入参数
综合案例
<!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>商品渲染</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.list {
width: 990px;
margin: 0 auto;
display: flex;
flex-wrap: wrap;
}
.item {
width: 240px;
margin-left: 10px;
padding: 20px 30px;
transition: all .5s;
margin-bottom: 20px;
}
.item:nth-child(4n) {
margin-left: 0;
}
.item:hover {
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2);
transform: translate3d(0, -4px, 0);
cursor: pointer;
}
.item img {
width: 100%;
}
.item .name {
font-size: 18px;
margin-bottom: 10px;
color: #666;
}
.item .price {
font-size: 22px;
color: firebrick;
}
.item .price::before {
content: "¥";
font-size: 14px;
}
.filter {
display: flex;
width: 990px;
margin: 0 auto;
padding: 50px 30px;
}
.filter a {
padding: 10px 20px;
background: #f5f5f5;
color: #666;
text-decoration: none;
margin-right: 20px;
}
.filter a:active,
.filter a:focus {
background: #05943c;
color: #fff;
}
</style>
</head>
<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: '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',
},
]
// 1. 渲染函数 封装
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>
`
})
// 追加给list
document.querySelector('.list').innerHTML = str
}
render(goodsList) // 页面一打开就需要渲染
// 2. 过滤筛选
document.querySelector('.filter').addEventListener('click', e => {
// e.target.dataset.index e.target.tagName
const { tagName, dataset } = e.target
// 判断
if (tagName === 'A') {
// console.log(11)
// arr 返回的新数组
let arr = goodsList
if (dataset.index === '1') {
arr = goodsList.filter(item => 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)
}
// 渲染函数
render(arr)
}
})
</script>
</body>
</html>
1-这里面的data-index就是一个自定义属性
2-e.taget.dataset就是自定义的属性的对象
3-e.target.tagName就是记录你点的是什么标签,这里就是A