闭包的概念
闭包是这样的一种机制:函数嵌套函数,内部函数可以引用外部函数的参数和变量。参数和变量不会被垃圾回收机制
收回。
什么是闭包?
- 闭包是指有权访问另一个函数作用域中变量的函数,创建闭包最常见的方式就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数局部变量
- 密闭的容器,类似set/map容器,用来存储数据
- 闭包是一个对象,存放数据的格式:key:value
闭包形成的条件*
-
1.函数嵌套
-
2.内部函数引用外部函数
function fun(){ var count = 1; function fun2(){//条件1:函数嵌套 //形成条件2:内部函数引用外部函数 console.log(count); } } fun();
为什么使用闭包?
利用闭包可以突破链作用域,将局部变量传到内部。
垃圾回收机制
1.标记清除
标记清除:js会对变量做一个标记yes or no的标签以供js引擎来处理,当变量在某个环境下被使用则标记为yes,当超出改环境(可以理解为超出作用域)则标记为no,然后对有no的标签进行释放。
2.引用计数
引用计数:对于js中引用类型的变量, 采用引用计数的内存回收机制,当一个引用类型的变量赋值给另一个变量时, 引用计数会+1, 而当其中有一个变量不再等于值时,引用计数会-1, 如果引用计数为0, 则js引擎会将其释放掉。
作用域
全局作用域
不在函数内部作用域和块级内部作用域外的作用域
函数作用域
在函数内部的作用域
块级作用域
在花括号{}内部的作用域 ,
注意:
①对象的{}不属于块级作用域,像for(){},if(){},else{},try{},cath(){}等等的花括号才是块级作用域
②对象的{}的作用域是什么作用域取决于对象所处的作用域,比如对象在全局作域 下定义的,那么对象的{}的作用域就是全局作用域
作用域链
内部作用域访问外部作用域的变量,采取的是链式查找
的方式来决定取哪个值,采取就近原则的方式向上一级一级的作用域来查找变量值,最顶级是全局作用域,如果到全局作用域也没找值,那么就会报错。
只要是代码,就至少有一个作用域, 如果作用域还有作用域,那么在这个作用域中就又可以诞生一个作用域;
// 作用域链: 内部作用域访问外部作用域的变量,采取的是链式查找的方式来决定取哪个值,这种结构我们称为作用域链
let num = 10;
function fn() { //外部函数
let num = 20;
return function fun() { //内部函数
console.log(num);
}
}
const funn = fn()
funn() // 20 ,一级一级访问
闭包的作用
- 拥有全局变量的不被释放的特点
- 拥有局部变量的无法被外部访问的特点
特性:
函数内再嵌套函数 内部函数可以引用外层的参数和变量 参数和变量不会被垃圾回收机制回收
优点
- 延长外部函数局部变量的生命周期 ,可以让一个变量长期在内存中不被释放
- 避免全局变量的污染,和全局变量不同,闭包中的变量无法被外部使用
- 私有成员的存在,无法被外部调用,只能直接内部调用
坏处
- 就是消耗内存、不正当使用会造成内存溢出的问题
闭包可以完成的功能
立即执行函数
const a = 666;
(function getA(){
console.log(a);
})()
模拟块级作用域
for(var i = 0; i < 5; i++) {
(function (j) {
setTimeout(() => {
console.log(j);
},1000)
})(i)
}
封装私有变量
function Person(name) {
this.getName = function(){
return name
}
}
防抖
持续触发不执行,不触发后的一段时间执行
**原理 **既然你不想打印之前已经输入的结果,就清除以前触发的定时器,我们应该存储这个timer的变量一直要在内存当中(内存的泄露)【闭包】
//防抖 避免函数的重复调用 只会调用一次
function Antishake(fn,wait){ //第一个参数是函数 第二个参数是毫秒值
let timer = null //声明一个变量来接收延时器 初始值为null
return function(){
clearTimeout(timer)
timer = setTimeout(() => {
fn() //调用这函数
}, wait);
}
}
let an = Antishake(function(){ //用一个变量接收
console.log('555');
},2000)
document.querySelector('div').onmouseenter = ()=>{
an() //调用一次
}
节流
持续触发也执行,一段时间内只执行一次
**原理 **当触发事件的时候,我们设置一个定时器,再次触发事件的时候,如果定时器存在,就不执行,直到delay时间后,定时器执行执行函数,并且清空定时器,这样就可以设置下个定时器。
function throttle(fn, wait) {
let timer = null; //节点闸
return function () {
if (timer) return; //null false 不是null结果减少true 如果上传没有我就直接跳过 没有人我就上去
timer = setTimeout(() => {
//上车了
fn();
timer = null; //做完之后重新关闭节点闸
}, wait);
};
}
let throttleFn = throttle(() => {
console.log("我上车了");
}, 2000);
document.querySelector("div").onclick = () => {
throttleFn();
};
函数柯里化
其实就是函数颗粒化,将一个函数变成一个个颗粒可以组装,就是这个里面的多个参数 将他变成一个个的函数来传递这个参数。
let add = (a, b) => a + b
console.log(add(2, 3));
// 函数柯里化
let currying = (a) => (b) => a + b
console.log(currying(2)(3));
数组的哪些方法用到了闭包?
forEach
const arr = [1,2,3];
arr.forEach((item,index) => {
setTimeout(() => {
console.log(item);
},1000)
})
js中常用的高阶函数
条件:
- 接受一个或多个函数作为输入
- 输出一个函数
**高阶函数:**指接受函数作为参数或返回函数的函数。这种函数允许你在 JavaScript 中实现一些高级技巧,比如把函数当作数据处理的基本单元来使用。
-
map:对数组中的每个元素进行操作,并返回一个新的数组。
-
filter:过滤数组中的元素,并返回一个新的数组。
-
reduce:对数组中的所有元素进行迭代,将其归约为单个值。
-
sort:对数组中的元素进行排序。
-
forEach:对数组中的每个元素执行一个操作。
-
some:检查数组中是否有至少一个元素符合某个条件。
-
every:检查数组中的所有元素是否都符合某个条件。
filter
作用:创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
语法:array.filter(function(currentValue,index,arr), thisValue)
返回值:符合条件的元素组成的新数组
let nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let newNums = nums.filter((item, index, arr) => {
return item % 2 === 0;
});
console.log(newNums); // [ 2, 4, 6, 8, 10 ]
console.log(nums); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
map
作用:返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。
语法:array.map(function(currentValue,index,arr), thisValue)
返回值:一个新数组
let nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let newNums = nums.map((item, index, arr) => {
return item * 2;
});
console.log(newNums);
// [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
reduce
作用:统计,累加
语法:array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
total:必需。初始值,或者结束后的返回值
currentValue:必需。当前元素
currentIndex:可选,当前元素索引
arr:可选,当前元素所属的数组对象
initialValue:可选,传递给函数的初始值
返回值:累计结果
let nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let newNums = nums.reduce((total, item, index, arr) => {
return total + item;
});
console.log(newNums); //55
find
作用: 方法返回通过测试(函数内判断)的数组的第一个元素的值。
语法:array.find(function(currentValue, index, arr),thisValue)
返回值:返回符合测试条件的第一个数组元素值,如果没有符合条件的则返回 undefined。
let nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let newNums = nums.find((item, index, arr) => {
return item === 3;
});
console.log(newNums); // 3
findIndex
和find类似,返回值为第一个符合条件元素的索引值
let nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let newNums = nums.findIndex((item, index, arr) => {
return item === 3;
});
console.log(newNums); // 2
惰性函数
惰性函数(Lazy Function): 惰性函数表示函数执行的分支只会在函数第一次调用的时候执行,在第一次调用过程中,该函数会被覆盖为另一个按照合适方式执行的函数,这样任何对原函数的调用就不用再经过执行的分支了。
惰性函数的本质就是函数重写,所谓惰性载入,指函数执行的分支只会发生一次。那什么时函数重写呢?由于一个函数可以返回另一个函数,因此可以用新的函数在覆盖旧的函数。
function a() {
console.log('a');
a = function () {
console.log('b');
}
}
a(); //q
a(); //b
第一次调用这个函数时console.log(‘a’)会被执行,打印出a,全局变量a被重定义并被赋予了新的函数,当再一次调用时,console.log(‘b’)被执行。
用处:因为各浏览器之间的行为差异,经常会在函数中包含了大量的if语句,以检查浏览器特性,解决不同浏览器的兼容问题。
惰性函数有两种实现方式:
- 在函数被调用时,再处理函数。函数在第一次调用时,该函数会被覆盖为另外一个按合适方式执行的函数,这样任何对原函数的调用都不用再经过执行的分支了
- 声明函数时就指定适当的函数。
Object与Function 的鸡生蛋与蛋生鸡问题
参考:https://muyiy.cn/blog/5/5.3.html#%E5%BC%95%E8%A8%80
Object()
,String()
等构造器并不是真的由原始构造函数Function()
创建,本质上来说,它们都由JavaScript
底层实现, 是因为这些构造器的__proto__
均指向了构造器Function.prototype
;
Object.prototype
Object.prototype 表示 Object 的原型对象,其 [[Prototype]]
属性是 null,访问器属性 __proto__
暴露了一个对象的内部 [[Prototype]]
。 Object.prototype 并不是通过 Object
函数创建的,为什么呢?看如下代码
function Foo() {
this.value = 'foo';
}
let f = new Foo();
f.__proto__ === Foo.prototype;
// true
实例对象的 __proto__
指向构造函数的 prototype
,即 f.__proto__
指向 Foo.prototype,但是 Object.prototype.__proto__
是 null,所以 Object.prototype 并不是通过 Object 函数创建的,那它如何生成的?其实 Object.prototype 是浏览器底层根据 ECMAScript 规范创造的一个对象。
Object.prototype 就是原型链的顶端(不考虑 null 的情况下),所有对象继承了它的 toString 等方法和属性。
Function.prototype
Function.prototype 对象是一个函数(对象),其 [[Prototype]]
内部属性值指向内建对象 Object.prototype。Function.prototype 对象自身没有 valueOf
属性,其从 Object.prototype 对象继承了valueOf
属性。
Function.prototype 的 [[Class]]
属性是 Function
,所以这是一个函数,但又不大一样。为什么这么说呢?因为我们知道只有函数才有 prototype 属性,但并不是所有函数都有这个属性,因为 Function.prototype 这个函数就没有。
为什么没有呢,我的理解是 Function.prototype
是引擎创建出来的函数,引擎认为不需要给这个函数对象添加 prototype
属性,不然 Function.prototype.prototype…
将无休无止并且没有存在的意义。
function Object
Object 作为构造函数时,其 [[Prototype]]
内部属性值指向 Function.prototype,即
bject.__proto__ === Function.prototype // true
function Function
Function 构造函数是一个函数对象,其 [[Class]]
属性是 Function
。Function 的 [[Prototype]]
属性指向了 Function.prototype
,即
Function.__proto__ === Function.prototype // true
Function & Object 鸡蛋问题
instanceof检查的是右操作数的
prototype`属性是否在左操作数的原型链上 。
Object instanceof Function // true
Function instanceof Object // true
Object instanceof Object // true
Function instanceof Function // true
Object
构造函数继承了 Function.prototype
,同时 Function
构造函数继承了Object.prototype
。这里就产生了 鸡和蛋 的问题。
为什么会出现这种问题?
因为 Function.prototype
和 Function.__proto__
都指向 Function.prototype
。
3个灵魂拷问
Object和Function谁是谁的实例
Object
是Function
的实例
Function.prototype是一个函数?
是的
Function.prototype()
可以执行,不会报错,说明Function.prototype
真的是一个函数。