1.变量作用域
全局变量和局部变量 特殊:函数内部可以直接读取全局变量,函数外部自然无法读取函数内的局部变量 注意:是var ,let const 声明的变量 外部才无法读取,不然就算是全局变量
1.对于全局变量来说生存周期是永久的,除非主动销毁。 2.一般对于函数作用域或者局部作用域(let const),会随着函数调用的结束而被销毁。 3.当函数内部调用外部变量就产生了闭包 这时候变量的回收策略可以参考引用计数等垃圾回收策略。
var n = 999;
function f1() {
t = 888;
console.log(n)
}
f1(); // 999 函数内部可以直接读取全局变量
console.log(t); //error 函数外部自然无法读取函数内的局部变量
2.什么是闭包
闭包是一个函数,是有权访问另外一个函数作用域中的变量的函数。
function outer() {
var a = '1'
var inner = function () {
console.info(a)
}
return inner // inner 就是一个闭包函数,因为他能够访问到outer函数的作用域
}
//也有说法说的是
一个函数访问另一个函数的作用域,那被访问的变量的所在函数是闭包。
因此outer是闭包
2.1闭包形成
- 形成: 函数中嵌套函数
- 作用: 函数内部调用外部变量、构造函数的私有属性、延长变量生命周期
- 优点: 希望一个变量长期存在内存中、模块化代码避免全局变量的污染、私有属性
- 缺点: 无法回收闭包中引用变量,容易造成内存泄漏
2.2作用
- 1.在外面的全局作用域可以访问里面的作用域(延长变量生命周期)
function f3() {
var n = "shuzi";
function f4() { console.log(n); }
return f4;
}
var fun = f3();
fun();
3.闭包缺点
3.1 this指向问题
闭包函数是在window作用域下执行的,this指向windows
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function () {
return function () {
return this.name;
};
}
};
alert(object.getNameFunc()());//The Window
3.2 内存泄露问题
function showId() {
var el = document.getElementById("app")
el.onclick = function(){
aler(el.id) // 这样会导致闭包引用外层的el,当执行完showId后,el无法释放
}
}
// 改成下面
function showId() {
var el = document.getElementById("app")
var id = el.id
el.onclick = function(){
aler(id) // 这样会导致闭包引用外层的el,当执行完showId后,el无法释放
}
el = null // 主动释放el,解除引用避免内存泄露
}
4.闭包应用
简单的有:
- ajax请求的成功回调
- 事件绑定的回调方法
- setTimeout的延时回调
- 函数内部返回另一个匿名函数
我们详细讲下以下几个应用场景:
- 构造函数的私有属性
- 计算缓存
- 函数节流、防抖
4.1构造函数的私有属性
某些不希望被外部修改的私有属性可以通过闭包的方式实现。
function Person(param) {
var name = param.name; // 私有属性
this.age = 18; // 共有属性
this.sayName = function () { console.log(name)}
this.sayAge = function () { console.log(this.age)}
}
const tom = new Person({ name: 'tom' });
tom.age += 1; // 共有属性,外部可以更改
tom.sayName(); // tom
tom.sayAge();//19
tom.name = 'jerry';// 共有属性,外部不可更改
tom.sayName(); // tom
4.2计算缓存
var square = (function () {
var cache = {};
return function (n) {
if (!cache[n]) {
cache[n] = n * n;
}
return cache[n];
}
})();
console.log(square(3))
4.3函数节流,防抖动
// 节流
function throttle(fn, delay) {
var timer = null,
firstTime = true;
return function () {
if (timer) {
return false;
}
var that = this;
var args = arguments;
fn.apply(that, args);
timer = setTimeout(function () {
clearTimeout(timer);
timer = null;
}, delay || 500); }
}
// 防抖
function debounce(fn, delay) {
var timer = null;
return function () {
var that = this;
var args = arguments;
clearTimeout(timer);// 清除重新计时
timer = setTimeout(function () {
fn.apply(that, args); }, delay || 500); }
}
4.4 for循环打印当前索引号
<ul> <li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
var li = document.getElementsByTagName('li');
var len = li.length;
for (var i = 0; i < len; i++) {
li[i].onclick = function () {
console.log(i); //此处没有变量i,因此需要向逐级向上寻找。
}
}
当我们触发点击事件的时候,for循环已经执行完毕,
此时i=4,因此无论我们点击那个li标签都只会打印出4
(function(){ })()立即执行函数 ,立即执行函数也是闭包
解决方法:再加一个函数形成闭包,使用立即执行函数,将i套现
var li = document.getElementsByTagName('li');
var len = li.length;
for (var i = 0; i < len; i++) {
li[i].onclick = function (i) {
return function () {
console.log(i) //此处没有变量i,因此需要向逐级向上寻找。
}
}(i);
}
var li = document.getElementsByTagName('li');
var len = li.length;
for (var i = 0; i < len; i++) {
(function (i) {
li[i].onclick = function () {
console.log(i);
})(i)
}
匿名函数1里面再写入一个匿名函数2,
这个匿名函数2需要的num值会在他的父级函数匿名函数1里面去寻找,
而匿名函数1里面的num值就是传入的这个参数i,和上面例子中的i是一样的,
function box(){
var arr = [];
for(var i=0;i<5;i++){
arr[i] = (function(num){
return num;
})(i);
}
return arr;
}
//alert(box()
5.闭包高频题
闭包找到的是同一地址中父级函数中对应变量最终的值
1.
for ( var i = 0 ; i < 5; i++ ) {
setTimeout(function(){
console.log(i);
}, 0);
}
setTimeout事件的时候,for循环已经执行完毕
输出: 5,5,5,5,5
2.
for ( var i = 0 ; i < 5; i++ ) {
(function(j){
setTimeout(function(){
console.log(j);
}, 0)})(i);
}
输出:0 1 2 3 4
3.
for ( let i = 0 ; i < 5; i++ ) {
setTimeout(function(){
console.log(i);
},0);
}
输出: 0 1 2 3 4
4.
var scope = 'global scope';
function checkscope() {
var scope = 'local scope';
return function f() {
console.log(scope);
};
}
checkscope()();
输出:local scope
5.
var scope = 'global scope';
function checkscope() {
return function f() {
console.log(scope);
};
}
checkscope()();
输出:global scope
6.
var obj = {
name: 'tom',
sayName() {
var name = 'alan';
console.log(this.name);
}
}
obj.sayName();// 'tom'
7.
var obj = {
name: 'tom',
sayName() { console.log(this.name)}
}
obj.sayName(); // tom
8.
var name = 'jerry';
var obj = {
name: 'tom',
sayName() {
return function () { console.log(this.name) }
}
}
obj.sayName()(); // jerry
9.
function fun(a, b) {
console.log(b)
return {
fun: function (c) { return fun(c, a)}
};
}
var d = fun(0); // undefined
d.fun(1); //0
d.fun(2); //0
d.fun(3); //0
闭包找到的是同一地址中父级函数中对应变量最终的值
1.
function outerFn(){
var i = 0;
function innerFn(){
i++;
console.log(i);
}
return innerFn;
}
var inner = outerFn(); //每次外部函数执行的时候,都会开辟一块内存空间,外部函数的地址不同,都会重新创建一个新的地址
inner();
inner();
inner();
var inner2 = outerFn();
inner2();
inner2();
inner2();
输出:1 2 3 1 2 3
2.
function outerFn(){
var i = 0;
function innnerFn(){
i++;
console.log(i);
}
return innnerFn;
}
var inner1 = outerFn();
var inner2 = outerFn();
inner1();
inner2();
inner1();
inner2();
输出:1 1 2 2
3.
function fn(){
var a = 3;
return function(){
return ++a;
}
}
alert(fn()()); //4
alert(fn()()); //4
4. 与区别在于是不是同一块地址
function a() {
var i = 0;
function b() { alert(++i); }
return b;
}
var c = a();
c(); //1
c(); //2