目录
1.作用域
- 作用域规定了变量能够别访问的"范围",离开这个"范围",变量便不能被访问
- 作用域分为:
- 局部作用域
- 全局作用域
1.1局部作用域
局部作用域分为函数作用域和块作用域
1.函数作用域:在函数内部声明的变量只能在函数内被访问,外部无法直接访问
<script>
function getSum() {
const num = 10
}
console.log(num);
</script>
总结:
- 函数内部声明的变量,在函数外部无法被访问
- 函数的参数也是函数内部的局部变量
- 不同函数内部声明的变量无法相互访问
- 函数执行完毕,函数内部的变量实际被清空了
2. 块级作用域:使用"{}" 包裹的代码称为代码块,代码块内部声明的变量外部将(有可能)无法访问
<script>
for (let t = 0; t < 6; t++) {
console.log(t);
}
console.log(t); //报错,无法访问到t
</script>
总结:
- let声明的变量会产生块作用域,var不会产生块作用域
- const声明的常量也会产生块作用域
- 不同代码块之间的变量无法相互访问
- 推荐使用const和let
1.2全局作用域
script标签和.js文件的最外层就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问.全局作用域中声明的变量,任何其他作用域都可以被访问
<script>
const num = 10
function fn() {
console.log(num)
}
</script>
注意:
- 为window对象动态添加的属性默认也是全局的,不推荐
- 函数中未使用任何关键字声明的变量为全局变量,不推荐
- 尽可能少的声明全局变量,防止全局变量被污染
1.3作用域链
作用域链本质上是底层的变量查找机制
- 在函数被执行时,会优先查找当前函数作用域中查找变量
- 如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域
<script>
let a = 1
let b = 2
function f() {
let a = 3
function g() {
a = 5 //全局作用域
console.log(a); //5(打印离得最近的a)
}
g()
}
f()
</script>
总结:
- 嵌套关系的作用域串联起来形成作用域链
- 相同作用域中按着从小到大的规则查找变量
- 子作用域能够访问父作用域,父作用域无法访问子级作用域
1.4js垃圾回收机制GC
1.4.1js环境中分配的内存,一般有如下生命周期:
- 内存分配 : 当我们声明变量,函数,对象的时候,系统会自动为他们分配内存
- 内存使用 : 即读写内存,也就是使用变量,函数等
- 内存回收 : 使用完毕,由垃圾回收器自动回收不再使用的内存
<script>
//为变量分配内存
const age = 18
//为对象分配内存
const obj = {
age: 19
}
//为函数分配内存
function fn() {
const age = 18
console.log(age);
}
</script>
说明:
- 全局变量一般不会回收(关闭页面回收)
- 一般情况下局部变量的值,不用了,会被自动回收掉
内存泄漏:程序中分配的内存由于某种原因程序未释放或无法释放叫做内存泄漏
1.4.2堆栈空间分配区别:
- 栈:由操作系统自动分配函数的参数值,局部变量等,基本数据类型放到栈里面
- 堆:一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收.复杂数据类型放堆里面
1.4.3.算术说明 :
常见浏览器垃圾回收算法 : 引用计数法 和 标记清算法
(1).引用计数法:
IE采用的引用计数算法,定义"内存不再使用",就是看一个对象是否指向它的引用,没有引用就收回对象
算法:
1.跟踪记录被引用的次数
2.如果被引用了一次,那么就记录次数1,多次引用会累加++
3.如果减少一个引用就减1--
4.如果引用次数是0,则释放内存
缺点 : 嵌套引用(循环引用)
如果两个对象相互引用,尽管他们已经不再使用,垃圾回收器无法进行回收,导致内存泄漏
<script>
function fn() {
// o1,o2都是局部变量(函数执行完自动被回收)
// o1,o2内存放的是对象的地址(指向空对象)
let o1 = {}
let o2 = {}
// 对象之间相互引用,计数不会为0
o1.a = o2
o2.a = o1
return "引用计数无法回收"
}
fn()
</script>
(2).标记计数法:
现在浏览器大多是基于标记清除算法
核心:
1.标记清楚算法将"不再使用的对象"定义为"无法到达的对象"
2.从根部(在js中就是全局对象)出发定时扫描内存中的对象,.但凡能从根部到达的对象,都是需要使用的
3.那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收
1.5闭包
1.5.1概念:一个函数对周围状态引用捆绑在一起,内层函数中访问到其外层函数的作用域.(函数嵌套函数不会产生闭包,里层函数用到外层函数的变量才会产生闭包)
简单理解:闭包 = 内层函数 + 外层函数的变量
<script>
function outer() {
const a = 1
function f() {
// 里层函数使用到外层函数变量a,产生闭包
console.log(a);
}
f()
}
outer()
</script>
闭包的基本格式:
<script>
// 基本语法
// 外部也可以访问到 函数内部的变量
function outer(){
let i=1
function fn(){
console.log(i)
}
return fn
}
// outer() 等价于 fn
const fun=outer()
// fun() 等价于 fn() 即使用到内部函数变量(局部)i
fun()
</script>
1.5.2.闭包的应用:实现数据的私有
比如:统计函数调用的次数,函数调用一次,就++
<script>
// i是全局变量,修改后则会破坏功能,容易被修改(不推荐)
// let i=0
function fn() {
let i = 0
function count() {
i++
console.log("函数被调用" + i + "次");
}
return count
}
const fun = fn()
// 调用一次fun,则表示count被调用即i开始计数
fun()
</script>
1.5.2.闭包可能会引起内存泄漏
上述代码 从全局出发查找由"const fun = fn()"可知,count被引用是一个全局的,即只有程序被关闭了才会被回收,所以count中的i会一直被使用,所以i不会被垃圾机制回收(i一直没有被释放),会产生内存泄漏
1.6变量提升
1.6.1 在js中,仅使用var声明的变量,允许这种变量在声明之前被访问
<script>
// 1.把所有var声明的变量提升到当前作用域的最前面
// 2.只提升 声明,不提升 赋值
// var num
console.log(num); //undefined
var num = 10
console.log(num); //10
function fn() {
// var num
console.log(num); //undefined
var num = 10
}
</script>
注意:
1.变量没有声明就被访问时会报语法错误,在var 声明 的变量前被访问不会报错
2.变量在var声明之前即被访问,变量值为undefined
3.let/const声明的变量不存在变量提升
4.变量提升出现在相同作用域中
5.实际开发中推荐先声明再访问变量
2.函数进阶
2.1函数提升(函数在声明前就可以被调用)
<script>
// 在全局作用域,相当于已经将fn函数提升到最前面
fn() //函数提升
function fn() {
console.log("函数提升");
}
// 只提升声明不提升赋值
// 即提升的是var fun,则fun()调用报错
fun() //报错
var fun = function () {
console.log("函数表达式");
}
</script>
总结:
1.函数提升能够使函数的声明调用更灵活
2.函数表达式不存在提升的现象
3.函数提升出现在相同作用域中
2.2函数参数
2.2.1 动态参数
arguments是函数内部内置的为数组变量,包含了调用函数时传入的所有实参
比如:写一个求和函数(根据用户输入的参数,再求和)
<script>
function getSum() {
//arguments动态参数,只存在函数里面
console.log(arguments);
}
getSum(1, 2)
</script>
例题:写一个求和函数(根据用户输入的参数来求和)
<script>
function getSum() {
let sum = 0
for (let i = 0; i < arguments.length; i++) {
sum += arguments[i]
}
console.log(sum);
}
getSum(1, 2, 3, 4) //10
getSum(22, 45, 88, 100, 12) //267
</script>
总结:
1.arguments是一个伪数组,只存在于函数中
2.arguments的作用是动态获取函数的参数
3.可以通过for循环依次得到传递过来的实参
2.2.2 剩余参数(较推荐)
剩余参数允许将一个不定量的参数表示为一个伪数组
1." ... " 是语法符号,置于最末函数形参之前,用于获取多余的实参
2.借助" ... "获取的剩余实参,是一个真数组
<script>
function getSum(a, b, ...arr) {
console.log(arr);
}
getSum(1, 2)
getSum(89, 100, 66)
</script>
拓展:
1.展开运算符 " ... ",将数组进行展开
<script>
const arr = [1, 2, 3, 4, 5, 6, 7]
console.log(...arr); //1 2 3 4 5 6 7
</script>
2.说明:
2.1.不会修改原数组
3.典型运用场景:求数组最大/小值,合并数组
<script>
const arr = [1, 2, 3, 4, 5, 6, 7]
// 数组没有max方法,求最大值只能这样写
console.log(Math.max(1, 2, 3, 4, 5, 6, 7));
// ...arr 等同于 1, 2, 3, 4, 5, 6, 7
console.log(Math.max(...arr));
// 数组合并
const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
// 数组合并方法concat
const arr3 = arr1.concat(arr2)
console.log(arr3);
// 使用 ...arr方法
const arr4 = [...arr1, ...arr2]
console.log(arr4);
</script>
2.3箭头函数
目的:更简洁的函数写法,并且不绑定this,比函数表达式更简洁
2.3.1基本语法:
- 只有一个参数可以省略小括号
- 如果函数体只有一行代码,可以写到一行上,并且无需写return直接返回值
- 加括号的函数体返回对象字面量表达式
<script>
// 箭头函数
const fn = () => {
console.log(123); //123
}
fn()
// 如果只有一个参数可以不用写小括号
const fun = x => {
console.log(x); //1
}
fun(1)
// 如果函数体只有一行代码,则可以省略大括号
const f1 = x => console.log(x); //1
f1(1)
// 还可以省略return
const f2 = x => x + x //2
console.log(f2(1));
// 箭头函数可以直接返回一个对象
//其中对象的大括号与函数的大括号冲突,最好用一个小括号包着
const f3 = (uname) => ({ uname: uname })
console.log(f3("张三"));
</script>
2.3.2箭头函数参数:
箭头函数没有arguments动态参数,但是有剩余参数...arr
<script>
const getSum = (...arr) => {
let sum = 0
for (let i = 0; i < arr.length; i++) {
sum += arr
}
return sum
}
console.log(getSum(1, 2, 3, 4)); //10
</script>
2.3.3.箭头函数中的this
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this
<script>
console.log(this); //window
// 普通函数
function f1() {
// 指向函数的调用者window
console.log(this);
}
f1()
//对象中的方法
const obj = {
name: 'aa',
sayHi: function () {
// this指向obj
console.log(this);
}
}
obj.sayHi()
// 箭头函数的this
const f2 = () => {
// 局部作用域
console.log(this); //window
}
// this指向上一层作用域
f2()
// 对象方法的箭头函数
const obj2 = {
name: "张三",
sayHi: () => {
// this找上一层,此时这层是obj2
console.log(this); //window
}
}
obj2.sayHi()
// 对象方法的嵌套箭头函数
const obj3 = {
name: "张三",
sayHi: function () {
let i = 1
// 上一层this-->obj3
const count = () => {
// 内部无this
console.log(this); //指向obj3
}
count()
}
}
obj3.sayHi()
</script>
事件回调函数使用箭头函数时,this为全局的window,因此DOM事件回调为了方便,不推荐使用箭头函数