作用域和闭包

124 阅读3分钟

作用域

  • 以一座城市作为类比

全局作用域

  • 只有一个,作用范围辐射整个程序,在程序运行时一直存在

函数作用域

  • 作用范围函数内部及其子函数
  • 作用域的查找只能向上查找
  • 每次调用都会产生一个新的环境,例如:
function hd() {
    let n = 1;
    function sum() {
        console.log(++n);
    };
    sum();
}
hd(); // 1
hd(); // 1

  • 要保留原有的环境,就要让其中的引用类型还在被使用(基本类型不行)
function hd() {
    let n = 1;
    return function sum() {
        console.log(++n);
    };
}
let a = hd();
a(); // 1
a(); // 2
let b = hd();
b(); // 1
b(); // 2

  • 在嵌套一层函数
function hd() {
    let n = 1;
    return function sum() {
        let m = 1;
        function show() {
             console.log(++n);
             console.log(++m);
        }
        show();
    };
}
let a = hd();
a(); // n: 2 m:2
a(); // n: 3 m:2

let/const

  • let/const可以变量声明在作用域块中
{
    let a = 10;
}
console.log(a); //报错

for(let i=1; i<3; i++) {
    setTimeout(function() {
        console.log(i);
    },1000);
}
// 1 2
// 每迭代一次,就会产生一个新的块级作用域。而let 具有块级作用域,且还会被定时函数所使用,就会出现上述结果
  • var只有全局和函数作用域
for(var i=1; i<3; i++) {
    setTimeout(function() {
        console.log(i);
    },1000);
}
// 3 3
// var无块级作用域的概念,直接放在全局作用域。每次迭代造成的改变都会直接影响它的值
  • `var模拟块级作用域
for(var i=1; i<3; i++) {
   (function(i){
        setTimeout(function() {
        console.log(i);
    },1000);
   })(i);
}
// 理念:函数每执行一次会开辟一个新的函数作用域,而因为后面定时函数还会使用该函数中的值,该函数的作用域是不会被回收的

闭包

  • 闭包:指有权访问另一个函数作用域中的变量的函数
  • 例子:选定某一个价格区间的商品
var goods = [  {name: "ipod",price: 400},
    {name: "pen",price: 50},
    {name: "orange",price: 1},
    {name: "ipad", price: 3000}]
function selectePrice(a,b) {
    return function(v) {
        return v.price>=a && v.price<=b;
    }
}
let a = 10;
let b =300;
console.log(goods.filter(selectePrice(a,b)));
  • 动画的抖动
let btns = document.querySelectorAll("button");
btns.forEach(function(item){
  item.addEventListener("click",function() {
    let left = 1;
    setInterval(function() {
      item.style.left = left++ + "px";
    },100)
  })
})

原因是:每次点击产生新的函数作用域,都是left=1,当上一个点击产生的定时器执行到的时候left突然就会便的很大,再次执行到另一个定时器函数又会变小,发生抖动。

改进:

let btns = document.querySelectorAll("button");
btns.forEach(function(item){
  let left = 1;
  item.addEventListener("click",function() {
    setInterval(function() {
      item.style.left = left++ + "px";
    },100)
  })
})

不能存在抖动问题了,但是点击次数越多,速度越快,原因是left所有定时器产生的函数都可以修改,点击之后一个定时器修改后,还未到达该定时器下一次执行的时间,但是到达了其他定时器函数执行的时间又会修改

再次改进:

let btns = document.querySelectorAll("button");
btns.forEach(function(item){
  let start = false;
  item.addEventListener("click",function() {
    if(!start) {
    let left = 1;
    start = setInterval(function() {
      item.style.left = left++ + "px";
    },100)
    }
  })
})
  • 闭包排序
let lessons = [
  {
    title: "媒体查询响应式布局",
    click: 89,
    price: 12
  },
  {
    title: "FLEX 弹性盒模型",
    click: 45,
    price: 120
  },
  {
    title: "GRID 栅格系统",
    click: 19,
    price: 67
  },
  {
    title: "盒子模型详解",
    click: 29,
    price: 300
  }
];


function order(file) {
  return (a,b)=>(a[file]>b[file]?1:-1);
  
}


console.table(lessons.sort(order("price")));

闭包问题

  • 内存泄漏---闭包特性中上级作用域为函数保存数据,从而造成内存泄漏
<body>
  <div desc="houdunren">在线学习</div>
  <div desc="hdcms">开源产品</div>
</body>
<script>
  let divs = document.querySelectorAll("div");
  divs.forEach(function(item) {
    item.addEventListener("click", function() {
      console.log(item.getAttribute("desc"));
    });
  });
</script>

清除不需要的数据

let divs = document.querySelectorAll("div");
divs.forEach(function(item) {
  let desc = item.getAttribute("desc");
  item.addEventListener("click", function() {
    console.log(desc);
  });
  item = null;
});
  • this指向
let hd = {
  user: "后盾人",
  get: function() {
    return function() {
      return this.user;
    };
  }
};
console.log(hd.get()()); //undefined

箭头函数解决

let hd = {
  user: "后盾人",
  get: function() {
    return () => this.user;
  }
};
console.log(hd.get()()); //undefined