JS进阶
第一阶段学习目标
一、垃圾回收机制 简称GC
JS中内存的
分配
和回收
都是自动完成
的,内存在不使用
的时候会被垃圾回收器自动回收
JS环境中分配的内存,一般有如下生命周期:
- 内存分配
- 内存使用
- 内存回收
说明
:全局变量一般不会回收(除了关闭页面)一般情况下局部变量的值,不使用了会被自动回收
内存泄露:程序中分配的内存由于某种原因程序
未被释放
或者无法释放
,叫做内存泄露
算法说明:
栈
:由操作系统自动分配释放函数的参数值、局部变量,基本数据类型放到栈里面
堆
:一般由程序员释放,如不释放,则垃圾回收机制回收,复杂数据类型放到堆里面
1、引用计数法与标记清除法
(1)引用计数法
内存不再使用,就是看一个对象是否有指向它的引用,没有就回收
逻辑:
- 跟踪记录被
引用的次数
- 如果被引用一次就
+1
- 减少引用就
-1
- 为
0
就被释放
但是有一个致命问题
,嵌套引用(询问引用):
- 如果两个对象
相互引用
,尽管他们不再使用,垃圾回收器也不会进行回收,导致内存泄漏 - 因为他们的引用次数永远不会是0,如果说大量的相互引用,会导致大量内存泄漏
(2)标记清除
现代的浏览器已经不用
引用计数法了
现代浏览器大多数是基于标记清除算法
的改进算法,总体思想一致
逻辑:
- 标记清除算法将
不再用的对象
定义为无法达到的对象
- 就是从
根部(在JS中就是全局对象)出发
,定时扫描内存中的对象,凡是能从根部到达的对象都是要用的 - 那些
无法从根部出发触及到
的对象被标记为不再使用
,稍后进行回收
二、闭包
一个函数对周围状态的引用捆绑在一起,内存函数中访问其外层函数的作用域
简单理解:闭包 = 内层函数 + 外层函数的变量
闭包作用:封闭函数,提供操作,外部也可以访问函数内部的变量
闭包的基本格式:
function outer(){//外层函数
let i = 1;//外层函数的变量
function fn(){//内层函数
console.log(i)//并且内层函数用到了外层的变量,才能形成闭包
}
return fn;
}
const fun = outer();
fun();
闭包的作用:实现数据的私有
比如,做一个统计函数调用的次数,函数调用一次就加1
错误的写法:
let count = 1;
function fn(){
count++;
console.log(`被调用了${count}次`)
}
fn()
但是,这个count是全局变量,很容易被修改:
正确的写法:
function outer(){
let count = 1;
function fn(){
count++;
console.log(`被调用了${count}次`)
}
return fn;
}
const fun = outer();// 有返回值所以 outer() === fn === function fn()
fun();
变量提升:
是JavaScript中比较“奇怪”的现象,它允许在变量声明之前就被访问(仅存在var声明的变量)只提升声明,不提升赋值
三、函数进阶
1、函数提升
- 会将所有函数声明提升到当前作用域的最前面
- 只提升声明,不提升赋值
2、函数参数
(1)动态参数
每个函数里都有一个
arguments
(动态函数)
arguments
是个伪数组
,只存在函数中
(2)剩余参数
允许将一个不定数量的参数表示一个数组
- …是语法符号,置于最末函数形参之前,用于获取多余实参
- 借助…获取的剩余 实参,是个
真数组
(3)展开运算符
…将一个数组进行展开,在数组中使用
- 不会修改原有的数组
- 典型的运行场景:求最大值、最小值,合并数组
3、箭头函数
引入箭头函数的目的是,更简短的函数写法,不绑定this,箭头函数比函数表达式更简洁
使用场景:适用于那些本来需要匿名函数的地方
基本语法:
原来的写法
const fn = function () {
console.log();
}
箭头函数
const fn = () =>{
console.log();
}
有参数
const fn = (x,y) =>{
console.log(x);
console.log(y);
}
有一个参数,可以省略小括号
const fn = x =>{
console.log(x);
}
只有一行代码,可以省略大括号
const fn = x =>console.log(x);
有return ,可以省略
const fn = x =>{return x+x}
const fn = x => x+x
直接返回一个对象
const fn = name =>({name:name})
(1)箭头函数的参数
箭头函数没有arguments
,但有 ...args
(2)箭头函数this
箭头函数不会创建自己的this
,只会从自己的作用域链的上一层沿用this
DOM事件
回调函数为了简便,还是不推荐用箭头函数
四、解构赋值
1、数组解构
是将
数组的单元值
,快速批量赋值
给一系列变量的简洁语法
基本语法:
- 赋值运算符 = 左侧的[]用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量
- 变量的顺序对应数组单元值的位置,依次进行赋值操作
const [max,min,avg] = [100,60,80];
console.log(max);
console.log(min);
console.log(avg);
典型的应用场景:交换两个变量的值
let a = 1;
let b = 2;
[b,a] = [a,b]
console.log(a,b)
数组解构会出现的问题
- 变量多,单元值少
const [a,b,c] = [1,2]
c的值为undefined
- 变量小,单元值多
const [a,b] = [1,2,3]
3没有变量接受而已
- 利用剩余参数解决变量少单元值多的问题
const [a,...b] = [1,2,3]
- 防止undefined传递单元值的情况,设置默认值
const [a=0,b=0] = []
- 按需导入(跳过某个值)
const [a,b,,d] = [1,2,3,4]
- 支持多维数组的结构
const [a,b,[c,d]] = [1,2,[3,4]];
console.log(a);
console.log(b);
console.log(c);
console.log(d);
2、对象解构
将
对象属性和方法
快速批量赋值
给一系列变量
- 赋值运算符 = 左侧的{}用于批量声明变量,右侧的对象属性值将被赋值给左侧的变量
- 对象属性值将被赋值给属性名相同的变量
- 解构的变量名不要与外部变量重名
- 对象中找不到与变量名相同的属性名值为undefined
const {uname,age} = {uname:'小猪',age:5}
与外部重名时:
const {uname:username,age} = {uname:'小猪',age:5}
数组对象解构:
const pig = [{
uname:'小猪',
age:5
}]
const [{uname,age}] = pig;
console.log(uname);
console.log(age);
多级对象解构:
const pig = {
name:'小猪',
family:{
mother:'猪妈妈',
father:'猪爸爸',
},
age:5
}
const {name,family:{mother,father}} = pig;
console.log(name);
console.log(mother);
五、深入对象
1、创建对象的三种方式
(1)对象字面量创建对象
const o = {
name:'小猪'
}
(2)利用new object() 创建
const o = new object({
name:'小猪'
})
或者
const o = new object()
o.name = '小猪'
(3)构造函数创建
命名规则:
- 以大写字母开头
- 只能用new来执行
function Pic(name.age){
this.name = name;
this.age = age;
}
console.log(new Pic('小猪',12))
说明:
- 使用new来调用叫做实例化
- 实例化构造函数时没有参数可以省略小括号
- 构造函数无需return,返回值即为新创建的对象
在这里插入代码片