作用域和作用域链
作用域
作用域指的是在程序中定义变量的区域,这个区域决定了变量的可见性和生命周期。在不同的作用域中,可以定义同名的变量,它们互不影响。作用域可以分为以下几种:
-
全局作用域:在这个作用域中定义的变量在整个程序中都是可见的,直到程序结束。
-
函数作用域:在函数内部定义的变量只在该函数内部可见,函数执行完毕后,这些变量就会被销毁。
-
块作用域:在花括号
{}
内部定义的变量,只在这些花括号内部可见。例如,在if
语句、for
循环或者直接使用花括号定义的代码块中或者使用let和const声明的变量。
作用域链
作用域链是一种底层变量的查找机制,在函数执行时,会优先查找当前函数作用域中的变量,如果没有找到则会依次逐级查找父级作用域直到全局作用域。变量只能向上查找,不能向下查找。
垃圾回收机制
内存生命周期
1、内存分配:声明变量、函数、对象时,系统会自动分配内存
2、内存使用:读写内存,即使用变量、函数
3、内存回收:使用完毕,由垃圾回收机制自动回收不再使用的内存
注意
1、全局变量一般不会回收
2、一般情况下局部变量的值,不用了会被自动回收
内存泄漏
程序中分配内存由于某种原因程序未释放或无法释放
去除缓存的两个方法
1、引用计数:这是一种不太常用的垃圾回收方式。是针对值为引用类型数据的变量进行计数,引用一次计数+1。当一个对象的引用次数为0时,该对象被认为是无用的,可以被垃圾回收器回收。
弊端:对象循环引用
2、标记清除:从根部(全局对象)出发定时扫描内存中的对象,凡是能从根部到达的对象,都是需要使用的。那些无法从根部出发触及到的对象标记为不再使用。由垃圾回收机制进行回收。
闭包
什么是闭包
一个函数能够访问其外部函数的作用域(函数嵌套,内层函数 + 外层函数)
闭包的特点
1、访问外部变量:内部函数可以访问和操作外部函数的变量,即使外部函数已经执行完毕。
2、数据封装:闭包可以用来创建私有变量,因为外部函数的变量不会被外部直接访问,只能通过闭包函数(内部函数)进行访问和修改。
3、数据私有化
4、不会被垃圾回收机制回收
function fnA(){
let a = 1
function fnB(){
console.log(a);
}
return fnB
}
const fnC = fnA()
fnC()
注意事项
1、内存泄漏:由于闭包会持续引用外部变量,可能会导致内存泄漏,特别是当外部变量是大型对象或者数组时。
2、性能问题:过度使用闭包可能会影响性能,因为它们增加了垃圾回收的复杂性。
变量提升
变量声明被提升
只有var声明的变量,变量在声明时会被提升到当前作用域最前面声明,但在赋值之前它们的值是undefined
。
let
和 const
不完全提升
与var
不同,let
和const
声明的变量不会被提升到作用域的顶部,而是被提升到它们所在代码块的顶部,并且在代码块内形成了一个“暂时性死区”。
函数参数
使用动态参数( arguments
对象)
arguments
对象是一个类数组对象,包含了函数调用时传入的所有参数。它不是真正的数组,所以没有数组的方法,但是可以通过索引访问每个参数。
function sum() {
var total = 0;
for (var i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}
console.log(sum(1, 2, 3, 4)); // 输出:10
使用剩余参数(REST )
ES6 引入了 REST 参数,它允许我们将一个不定数量的参数表示为一个数组。
function sum(...args) {
return args.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4)); // 输出:10
展开运算符(...)
允许一个数组或类数组对象在需要多个参数(函数参数)或多个元素(数组元素)的地方被展开
使用场景
合并数组
const array1 = [1, 2];
const array2 = [3, 4];
const combinedArray = [...array1, ...array2]; // [1, 2, 3, 4]
求数组最大/小值
const arr= [1, 2, 3];
console.log(Math.max(...arr));
console.log(Math.min(...arr));
复制数组
const originalArray = [1, 2, 3];
const copiedArray = [...originalArray];
插入元素到数组中
const array = [1, 2, 4];
const newArray = [0, ...array, 5]; // [0, 1, 2, 4, 5]
在函数中传递参数
const args = [1, 2, 3];
function sum(a, b, c) {
return a + b + c;
}
const result = sum(...args); // 6
与 new
操作符一起使用
const dateFields = [2024, 11, 14];
const date = new Date(...dateFields);
对象复制和合并
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const combinedObj = { ...obj1, ...obj2 }; // { a: 1, b: 3, c: 4 }
注意
1、展开运算符只能展开可迭代对象,这意味着它不能直接用于非可迭代对象,如普通对象(除非使用对象的[Symbol.iterator]
属性)。
2、展开运算符会创建数组或对象的浅拷贝,这意味着如果数组或对象中包含嵌套的数组或对象,只有第一层会被展开。
3、在函数参数中使用展开运算符时,如果参数是undefined
或null
,它们会被当作空数组处理。
箭头函数
箭头函数是ES6中引入的一种新的函数定义方式,它提供了一种更简洁的函数写法。箭头函数的语法比传统的函数表达式更简洁,并且没有自己的this
、arguments
、super
或new.target
。可以替代匿名函数
基本语法
没有参数或一个参数
// 没有参数
const func = () => {
// 函数体
};
// 一个参数
const func = x => {
return x;
};
多个参数
const func = (x, y) => {
return x + y;
};
隐式返回
const func = x => x * x;
箭头函数的特点
1、this
值的继承: 箭头函数不绑定自己的this
值,它会捕获其所在上下文的this
值作为自己的this
值,这使得箭头函数非常适合用于回调函数。
2、没有arguments
对象: 箭头函数没有自己的arguments
对象,但可以通过剩余参数(...args
)来访问函数参数。
3、不能作为构造函数: 箭头函数不能用作构造函数,尝试这样做会抛出错误。
4、没有new.target
: 箭头函数内部没有new.target
属性,这个属性通常用于确定函数是否作为构造函数被调用。
5、不绑定super
: 箭头函数不绑定super
,因此不能在箭头函数中使用super
关键字。
6、不创建Array
和Object
: 在箭头函数中使用Array
和Object
字面量时,它们不会被特殊处理,例如[]
和{}
会被当作普通表达式。
数组解构
数组解构是ES6中引入的一个特性,它允许我们通过一个简洁的语法将数组中的值赋给多个变量。这种语法形式类似于对象解构,但是专门用于数组。
基本用法
1、赋值运算符:左侧的 [ ] 用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量
2、变量的顺序对应数组单元值的位置依次进行赋值操作
基本解构
const arr = [1, 2, 3];
const [a, b, c,d] = arr;
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
console.log(d); // undefined
忽略某些元素
如果你不想为数组中的每个元素都创建一个变量,可以使用下划线 _
或者任意无效标识符来忽略它们
const arr = [1, 2, 3, 4];
const [a, , , d] = arr;
console.log(a); // 1
console.log(d); // 4
重命名变量
在解构时,你可以为变量指定新的名字,不必与数组中的元素顺序对应。
const arr = [1, 2, 3];
const [x, y, z] = arr;
console.log(x); // 1
console.log(y); // 2
console.log(z); // 3
默认值
如果数组中的某个元素是undefined
,可以为解构的变量提供一个默认值。
const arr = [1, undefined, 3];
const [a, b = 2, c] = arr;
console.log(a); // 1
console.log(b); // 2
consol