作用域
作用域,即变量(变量作用域又称上下文)和函数生效(能被访问)的区域或集合
主要用于减少名字冲突 增强程序的可靠性
全局作用域
任何不在函数中或是大括号中声明的变量,都是在全局作用域下,全局作用域下声明的变量可以在程序的任意位置访问
局部作用域 (函数作用域)
函数作用域也叫局部作用域,如果一个变量是在函数内部声明的它就在一个函数作用域下面。这些变量只能在函数内部访问,不能在函数以外去访问
函数作用域也曾作为一种模块化的实现方式,通过函数作用域解决命名冲突、污染全局的问题
(function (a) {
// 在这里面声明各种变量、函数都不会污染全局作用域
})(a)
块级作用域
ES6引入了let和const关键字,和var关键字不同,在大括号中使用let和const声明的变量存在于块级作用域中。在大括号之外不能访问这些变量
{
// 块级作用域中的变量
let greeting = 'Hello World!';
var lang = 'English';
console.log(greeting); // 'Hello World!'
}
console.log(lang); // 'English'
console.log(greeting);// 报错:Uncaught ReferenceError: greeting is not defined
暂时性死区
在块级作用域内,let命令声明变量之前,该变量不可用,在语法上称为暂时性死区。
console.log(a) // Uncaught ReferenceError: a is not defined
let a = 1
var xye = 1;
(function () {
console.log(xye)
xye = 2
let xye = 4
console.log(this.xye)
console.log(xye)
})()
以上代码会输出
为什么要引入块级作用域的概念?
问题1 出现一些难以预料的变量提升
var myname = 'Hello'
function showName() {
console.log(myname)
if (0) {
var myname = 'World'
}
console.log(myname)
}
showName()
showName函数中if块呢的代码变量提升了,所以这个函数作用域就先使用了这个函数作用域中的myname了,而内部的myname还没有赋值,是undefined,注意了啊,if(0)虽然是表示里面的代码不会执行,但是编译的前期阶段是不会判断if里面到底是true还是false的,只要遇到了var声明的变量就直接变量提升了,只是后来执行代码阶段的时候不区进行myname的赋值操作,所以下一个myname也是undefined
问题2 本该销毁的i变量并没有被销毁 这就造成了一些内存泄漏问题
function foo(){
for(var i=0;i<7;i++){
}
console.log(i) //7
}
作用域链
当在Javascript中使用一个变量的时候,首先Javascript引擎会尝试在当前作用域下去寻找该变量,如果没找到,再到它的上层作用域寻找,以此类推直到找到该变量或是已经到了全局作用域
如果在全局作用域里仍然找不到该变量,它就会在全局范围内隐式声明该变量(非严格模式下)或是直接报错
闭包
什么是闭包?
函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包。
经典面试题,循环中使用闭包解决 var 定义函数的问题
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}
解决方案1 利用let 的块级作用域解决
for (let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}
//可以将使用var声明的for循环看作
var i = 1;
i++ // i=2
i++ // i=3
i++ // i=4
i++ // i=5
i++ // i=6 i>5
for () {
setTimeout(function timer() {
console.log(i)
}, i * 1000)
} // 输出6 6 6 6 6
//可以将使用let声明的for循环看作
{
let i =1;
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}
{
let i =2;
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}
{
let i =3;
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}
{
let i =4;
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}
{
let i =5;
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}
解决方案2 利用闭包
for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(function timer() {
console.log(j)
}, j * 1000)
})(i)
}
闭包的优缺点
优点
1、可以从内部函数访问外部函数的作用域中的变量,且访问到的变量长期驻扎在内存中,可供之后使用。
2、避免变量污染全局。
3、把变量存到独立的作用域,作为私有成员存在。
缺点
1、对内存消耗有负面影响,因内部函数保存了对外部变量的引用,导致无法被垃圾回收,增大内存使用量,所以使用不当会导致内存泄漏。
2、对处理速度具有负面影响。闭包的层级决定了引用的外部变量在查找时经过的作用域长度。
内存泄漏与垃圾回收
浏览器一般采用标记清除 引用计数两种算法来进行垃圾回收,找出那些不在继续使用的变量,然后释放其内存
内存泄漏一般是指不再用到的内存,没有及时释放 闭包使用不当会出现内存泄漏问题
function foo(){
let value=123;
function bar(){
alert(value)
}
return bar
}
let mybar=foo()
// 这种情况变量value会保存在内存中,如果加上 mybar=null value就会被清除
闭包应用场景
1.缓存
//比如求和操作,如果没有缓存,每次调用都要重复计算,采用缓存已经执行过的去查找,查找到了就直接返回,不需要重新计算
var fn=(function(){
var cache={};//缓存对象
var calc=function(arr){//计算函数
var sum=0;
//求和
for(var i=0;i<arr.length;i++){
sum+=arr[i];
}
return sum;
}
return function(){
var args = Array.prototype.slice.call(arguments,0);//arguments转换成数组
var key=args.join(",");//将args用逗号连接成字符串
var result , tSum = cache[key];
if(tSum){//如果缓存有
console.log('从缓存中取:',cache)//打印方便查看
result = tSum;
}else{
//重新计算,并存入缓存同时赋值给result
result = cache[key]=calc(args);
console.log('存入缓存:',cache)//打印方便查看
}
return result;
}
})();
fn(1,2,3,4,5);
fn(1,2,3,4,5);
fn(1,2,3,4,5,6);
fn(1,2,3,4,5,8);
fn(1,2,3,4,5,6);
2.访问私有变量
function fn(){
var name="hello";
return function(){
return name;
}
}
var fnc = fn();
console.log(fnc())//hello
3.单例模式
class Person{
getName(){
console.log('张三')
}
}
const getSingle = (function(fn){
var result;
return function(){
if(result) return result;
return result=new Person();
}
})()
const instance1= getSingle()
const instance2= getSingle()
instance1===instance2
4.闭包经典问题
function fun(n,o){
console.log(o);
return{
fun:function(m){
return fun(m,n)
}
}
}
var a = fun(0)
a.fun(1)
a.fun(2)
a.fun(3)
var b = fun(0).fun(1).fun(2).fun(3)
var c = fun(0).fun(1)
c.fun(2)
c.fun(3)
// undefined 0 0 0 undefined 0 1 2 undefined 0 1 1