闭包
函数
函数的特点:
-
保护私有变量
因为每个函数都会生成一个独立的私有作用域
在函数内部定义的变量,叫做私有变量
该变量只能在该函数及其作用域下和下级作用域内使用,外部不能使用 -
函数定义时不解析变量
函数定义时,函数体内的代码完全不执行
任何变量不做解析
知道执行的时候才会解析变量
函数的两个阶段
函数的两个阶段都做了什么事?
定义
- 开辟一个存储空间
- 把函数体内的代码一摸一样的放在这个空间内(不解析变量)
- 把存储空间地址 给到函数名
调用
- 按照函数名的地址 找到 函数的 存储空间
- 形参赋值
- 预解析
- 将函数 存储空间的代码 拿出来执行 (才会解析变量)
重新定义函数的调用阶段
- 按照函数名的地址 找到 函数的 存储空间
- 形参赋值
- 预解析
- 在内存中开辟一个 执行空间
- 将函数存储空间的代码 拿出来 在上一步开辟的执行空间中执行
- 执行完毕后,内存中开辟的执行空间销毁
闭包
概念:
- 广义上来说:JS中的每一个函数都是一个闭包,因为每个函数都可以访问全局变量
- 狭义上来说:js中的函数,访问了外层函数作用域中的变量,才叫闭包
- 也可以说是:作用域链机制的一种特殊情况
柯里化
柯里化(Currying),指的是把接受多个参数的函数变换成接受单一参数的函数,并但会接受余下参数,并且能够返回结果的新函数技术。
主要信息:
- 函数原始状态接受多个参数,可以是一个、二个、三个。。。。
- 转换成另外一个函数,这个函数可以接受小于原始函数的参数个数,并且等到累计接受参数个数与原始函数接受参数一致时,返回结果
function add(x, y) {
return x + y;
}
add(1, 2); // 3
function curryingAdd(x) {
return function (y) {
return x + y;
};
}
curryingAdd(1)(2); // 3
// 分析:实际上就是把add函数的x和y两个参数编程先用一个函数接受x,然后返回一个函数去接受y,之后再返回的那个函数里面进行计算并将结果返回
// 使用柯里化函数实现add(1, 2, 3)、add(1, 2)(3)、add(1)(2)(3) 输出都为6。
/*
分析思路:
1. 如果是多个那就加多一个判断条件
- 这个判断条件判断什么?
- 判断我当前传入的参数个数是否小于原始函数参数个数
2. 根据判断结果,如果当前传入的参数个数相等或大于原始函数参数个数则直接返回计算结果,如果小于则返回一个函数
3. 返回的这个函数用来处理后续传入的函数(剩余参数)
*/
/*
一些问题
1. 怎么获取我原始函数传入了几个参数(形参)
》通过原始函数的.length属性获取在原始函数中有几个形参,.length的值就为几。
2. 怎么让函数记住历史参数,我后续的剩余参数又该怎么和前面的历史参数进行计算
》可以利用闭包记住历史参数,后续参数计算通过递归的方式重复调用自己本身(因为我们不能确定我后面调用一次传入的参数是否与原始参数一致,所以判断条件依旧需要)
3. 这个柯里化函数需要几个参数可以满足上面的要求?
》一个或两个都可以,以两个参数为例,第一个参数作为源处理函数,第二个参数作为,调用这个柯里化函数是传入的参数列表。
*/
function curryFn(fn, currArgs = []) {
// 记录源处理函数的参数个数
// let len = fn.length;
// 记录历史参数
// currArgs = currArgs || [];
return function () {
// 在这个被返回的函数里,将currFn调用时传递的参数(历史参数)与当前这个函数传递进来的参数合并,得到当前已传入的参数总数
let args = [...currArgs, ...arguments];
// 判断当前得到的参数的总数与源处理函数的参数个数,小于则递归调用currFn,继续收集参数
if (args.length < fn.length) {
return curryFn(fn, args);
}
// 所有参数收集完毕,则执行函数,返回结果
return fn(...args);
};
}
// 使用 返回是一个柯里化后的函数
let currySum = curryFn(function (a, b, c) {
return a + b + c;
});
console.log(currySum(1, 2, 3));
console.log(currySum(1)(2, 3));
console.log(currySum(1)(2)(3));
function fn(a) {
return function (b) {
return function (c) {
return a + b + c;
};
};
}
// 参数复用
let curry = curryFn(
function (a, b, c) {
return a + b + c;
},
[10, 20]
);
console.log(curry(60));
// 逐步执行
javascript 的内存管理
认识内存管理
不管是什么样的编程语言,在代码的执行过程中都是需要给他分配内存的,不同的是有些编程语言需要自己手动管理内存,有些编程可以自动管理内存。
不管是什么方式来管理内存,内存的管理都会有如下的生命周期
- 分配申请你需要的内存
- 使用分配的内存
- 不需要使用时,对其进行释放
js的内存管理
对于原始数据类型,直接在栈里面分配
对于引用数据类型,在堆里面分配,并将这块空间的指针返回给你的变量名使用
垃圾回收机制算法
因为内存的大小是有限的,所以当内存里面的对象不在需要时,我们就要对他进行释放,以便腾出空间。
Garbage Collection,简称 GC。
引用计算法(Reference counting)
过程:
- 当声明一个变量并将一个引用类型值赋值给该变量时,该引用次数 加1
- 当这个变量指向其他值时,该值的引用次数便减1
- 当这个值的引用次数为0时,说明没有任何的变量在使用,这个值没法被访问,就会被回收空间,垃圾回收器会在运行时的时候清理掉引用次数为0的值所占用的空间
let user = {username:'xx'};// 对象被引用 计数 +1
let user2 = user; // 对象又被引用 计数 +1
user = null; // 变量不在引用对象 计数 -1
user2 = null; // 变量不在引用对象 计数 -1
// 此时 这个对象 计数为0 可以销毁
// let obj = {name:"xx",o2:obj2};
// let obj2 = {name:"qx",o2:obj}
// obj = null
// obj2 = null
// 循环引用
let boy = {};
let girl = {};
girl.girlFriends = boy;
boy.boyFriend = girl;
boy = null
girl = null
- 缺点:
- 无法检测循环引用:会导致内存泄漏
- 计数也会占用空间
- 优点:
- 执行效率高,程序执行受影响小
标记清除法(mark-Sweep)
过程:
- 垃圾收集器在运行时会给内存中的所有变量都加上一个标记,假设内存中所有对象都是垃圾,全标记为0
- 然后从各个根对象开始遍历,把能够访问到的对象标记置1
- 清理所有标记为0的垃圾,销毁并回收他们所占用的内存空间
- 最后,把所有内存中的对象标记修改为0,等待下一轮垃圾回收
优点:可以解决循环引用的问题,并且整个算法执行过程中没有额外开销
缺点:此方法清理出来的空闲空间是不连续的,会产生内存碎片
其他常见的算法
- 标记整理(mark-compact)
- 在标记清除时产生内存碎片,会导致如果有一个对象需要一块大的连续内存,就可能出现内存不足的情况
- 而标记整理就可以弥补这个问题
- 他的标记步骤是一样的,只是后续不是直接清除垃圾数据,而是先将所有存活的对象向一端移动,然后直接清掉这一端以外的内存
- 分代收集(Generational collection)
- 上述的都是垃圾回收算法,而垃圾回收主要存在于堆的新生代和老生代
- 新生代:存的都是声明周期短的对象,在其中分为两个空间
Semi space from 使用状态空间
和semi space to闲置状态空间
(使用的垃圾回收算法:Scavenge)- 新创建的对象(三个变量 A B C)会在from空间开辟位置进行存储
- 当from空间将满时,进行垃圾回收判断,假设发现 B 没有被其他引用,可以回收,对B进行标记
- 将没有被标记的A C拷贝到to闲置空间中
- 清除掉from空间的全部内容
- 将from空间和to空间进行调换
- 这就是一次新生代当中垃圾回收的过程
- 老生代:从新生代空间经过至少两轮GC仍然存活下来的数据
- 使用的垃圾回收算法
- 标记清除
- 标记整理
- 使用的垃圾回收算法
- 增量收集
- 惰性清理
全停顿
在mark-Sweep和mark-compact之间由于mark-compact需要移动对象,所以他的执行速度不可能很快,所以在取舍上,主要使用mark-Sweep,在空间不足以从新生代中晋升过来的对象进行分配时才使用mark-compact,为了避免出现js应用逻辑于垃圾回收看到不一致的情况,垃圾回收时都需要将应用逻辑停下来,这种行为称为“全停顿”(stop-the-world)
Iterator和Generator 迭代器和生成器
Iterator 迭代器
迭代器就是一种机制,是一种帮助我们对某种数据结构进行遍历的对象
在js中迭代器是一个对象,这个对象符合迭代器协议:定义产生一系列值的标准方式。
在js中就是一个next方法。而这个方法有如下要求
- 无参数或者一个有参数的函数
- 返回一个应当有以下两个属性的对象
done(boolean):表示迭代是否完成
true:表示迭代已完成
false:表示迭代没有完成
value
迭代器的返回值,done为true返回undefined(可以省略)
{
next(){
return {
done:
value:
}
}
}
可迭代对象
当一个实现了 iterator protocol 协议,他就是一个可迭代对象,这个对象要求必须实现[Symbol.iterator]
方法,而且这个方法要返回一个迭代器。那么这个对象才叫可迭代对象。
原生可迭代对象
-
天生自带
[Symbol.iterator]
属性的对象 -
Array
-
String
-
Map
-
Set
-
arguments
-
NodeList
Generator 生成器
生成器是 es6 新增的一种函数控制、使用的方案,他可以让我们更加灵活的去控制函数什么时候继续执行*,什么时候暂停执行。
生成器函数
- 生成器函数需要在 function 后面加一个
*
号 - 生成器函数通过yield关键字来控制执行
- 生成器函数的返回值是一个生成器对象
yield*
yield* 用于委托给另一个Generator或可迭代对象