作用域
局部作用域
局部作用域分为函数作用域和块作用域。
函数作用域
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。函数执行完毕后,函数内部的变量实际被清空了
块作用域
在 JavaScript 中使用 {}
包裹的代码称为代码块,代码块内部声明的变量外部将【有可能】无法被访问。
JavaScript 中除了变量外还有常量,常量与变量本质的区别是【常量必须要有值且不允许被重新赋值】,常量值为对象时其属性和方法允许重新赋值。
<script>
// 必须要有值
const version = '1.0.0';
// 不能重新赋值
// version = '1.0.1';
// 常量值为对象类型
const user = {
name: '小明',
age: 18
}
// 不能重新赋值
user = {};
// 属性和方法允许被修改
user.name = '小小明';
user.gender = '男';
</script>
总结:
let
声明的变量会产生块作用域,var
不会产生块作用域const
声明的常量也会产生块作用域- 不同代码块之间的变量无法互相访问
- 推荐使用
let
或const
全局作用域
<script>
标签和 .js
文件的【最外层】就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。
全局作用域中声明的变量,任何其它作用域都可以被访问。
总结:
- 为
window
对象动态添加的属性默认也是全局的。 - 函数中未使用任何关键字声明的变量为全局变量。
- 尽可能少的声明全局变量,防止全局变量被污染
作用域链
作用域链本质上是底层的变量查找机制,在函数被执行时,会优先查找当前函数作用域中查找变量,如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域,示例代码所示:
<script>
// 全局作用域
let a = 1
let b = 2
// 局部作用域
function f() {
let c
// let a = 10;
console.log(a) // 1 或 10
console.log(d) // 报错
// 局部作用域
function g() {
let d = 'yo'
console.log(b) // 2
}
// 调用 g 函数
g()
}
console.log(c) // 报错
console.log(d) // 报错
f();
</script>
总结:
- 嵌套关系的作用域串联起来形成了作用域链
- 相同作用域链中按着从小到大的规则查找变量
- 子作用域能够访问父作用域,父级作用域无法访问子级作用域
垃圾回收机制
内存的生命周期
内存分配:声明变量、函数、对象的时候,系统会自动为他们分配内存
内存使用:读写内存,即使用变量、函数
内存回收:使用完毕,由垃圾回收器自动回收不再使用的内存
全局变量一般不会回收(除非关闭页面)
一般情况下局部变量的值,不用了就会被自动回收掉
内存泄露:程序中分配的内存由于某种原因程序未释放或无法释放的叫做内存泄漏
垃圾回收机制的算法
引用计数法:浏览器寻找对象是否被引用,如果没被引用则作为垃圾回收(现在基本上不用),会有致命问题嵌套引用
标记清除法:思想和上述类似,将“不再使用的对象”定义为“无法达到的对象”;从根部能找到的对象就行标记
闭包
闭包是一种比较特殊的函数,使用闭包能够访问函数作用域中的变量。从代码形式上看闭包是一个做为返回值的函数,如下代码所示:
<body>
<script>
// 1. 闭包 : 内层函数 + 外层函数变量
// function outer() {
// const a = 1
// function f() {
// console.log(a)
// }
// f()
// }
// outer()
// 2. 闭包的应用: 实现数据的私有。统计函数的调用次数
// let count = 1
// function fn() {
// count++
// console.log(`函数被调用${count}次`)
// }
// 3. 闭包的写法 统计函数的调用次数
function outer() {
let count = 1
function fn() {
count++
console.log(`函数被调用${count}次`)
}
return fn
}
const re = outer()
// const re = function fn() {
// count++
// console.log(`函数被调用${count}次`)
// }
re()
re()
// const fn = function() { } 函数表达式
// 4. 闭包存在的问题: 可能会造成内存泄漏
</script>
</body>
总结:
1.怎么理解闭包?
- 闭包 = 内层函数 + 外层函数的变量
2.闭包的作用?
- 封闭数据,实现数据私有,外部也可以访问函数内部的变量
- 闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来
3.闭包可能引起的问题?
- 内存泄漏
变量提升
变量提升允许在变量声明之前被访问,会将所有var声明的变量提升至当前作用域的最前面。只提升声明不提升赋值
function fn(){
console.log(num)
var num=10
}
fn()
函数提升
函数提升是函数在声明之前即可被调用了,只提升函数声明不提升函数调用
<script>
// 调用函数
foo()
// 声明函数
function foo() {
console.log('声明之前即被调用...')
}
// 不存在提升现象
bar() // 错误
var bar = function () {
console.log('函数表达式不存在提升现象...')
}
</script>
函数参数
默认值
<script>
// 设置参数默认值
function sayHi(name="小明", age=18) {
document.write(`<p>大家好,我叫${name},我今年${age}岁了。</p>`);
}
// 调用函数
sayHi();
sayHi('小红');
sayHi('小刚', 21);
</script>
动态参数
arguments
是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参。
function add(){
let sum=0
for(let i=0;i<arguments.length;i++){
sum=sum+arguments[i]
}
console.log(sum)
}
add(1,2,5)
add(1,2)
arguments
是一个伪数,arguments
的作用是动态获取函数的实参
剩余参数
function add(a,...arr){
console.log(arr.length)
}
add(1,21,2)
//...可以作为展开运算符使用
const arr=[12,2,3]
console.log(...arr)//12,2.3
总结:
...
是语法符号,置于最末函数形参之前,用于获取多余的实参- 借助
...
获取的剩余实参,是个真数组
箭头函数
<body>
<script>
// const fn = function () {
// console.log(123)
// }
// 1. 箭头函数 基本语法
// const fn = () => {
// console.log(123)
// }
// fn()
// const fn = (x) => {
// console.log(x)
// }
// fn(1)
// 2. 只有一个形参的时候,可以省略小括号
// const fn = x => {
// console.log(x)
// }
// fn(1)
// // 3. 只有一行代码的时候,我们可以省略大括号
// const fn = x => console.log(x)
// fn(1)
// 4. 只有一行代码的时候,可以省略return
// const fn = x => x + x
// console.log(fn(1))
// 5. 箭头函数可以直接返回一个对象
// const fn = (uname) => ({ uname: uname })
// console.log(fn('刘德华'))
</script>
</body>
总结:
- 箭头函数属于表达式函数,因此不存在函数提升
- 箭头函数只有一个参数时可以省略圆括号
()
- 箭头函数函数体只有一行代码时可以省略花括号
{}
,并自动做为返回值被返回
箭头函数动态参数
箭头函数中没有 arguments
,只能使用 ...
动态获取实参
const fn=(...arr)=>{
return Math.max(...arr)
console.log(...arr)
}
console.log(fn(1,2,3,9))
箭头函数 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
// }
// }
// obj.sayHi()
const obj = {
uname: 'pink老师',
sayHi: function () {
console.log(this) // obj
let i = 10
const count = () => {
console.log(this) // obj
}
count()
}
}
obj.sayHi()
</script>
解构赋值
数组解构
数组解构是数组的批量赋值给语法
<script>
let [a, b, c] = [1, 2, 3]
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
</script>
总结:
- 变量的数量大于单元值数量时,多余的变量将被赋值为
undefined
- 变量的数量小于单元值数量时,可以通过
...
获取剩余单元值,但只能置于最末位 - 允许初始化变量的默认值,且只有单元值为
undefined
时默认值才会生效
注:支持多维解构赋值,比较复杂后续有应用需求时再进一步分析
对象解构
对象解构是将对象属性和方法批量赋值给变量。
<script>
// 普通对象
const user = {
name: '小明',
age: 18
};
//变量名要和属性名一样
const {name, age} = user
console.log(name) // 小明
console.log(age) // 18
//变量名更改
//const {name:uname,age:agee}=user
</script>
多级对象解构
const obj={
name:'小谬',
family:{
dad:'父亲',
mom:'母亲'
}
}
const {name,family:{dad,mom}}=obj
console.log(name,dad,mom)
forEach遍历数组
forEach()用于遍历数组
const list=[1,2,3,]
list.forEach(function (item,index){
console.log(item,index)
})
filter筛选数组
filter() 方法 筛选数组符合条件的元素,并返回筛选之后元素的新数组
<body>
<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>
</body>