作用域 作用域就是执行上下文,在执行上下文中会有内存分配与执行两个阶段。变量的的生命周期:声明,初始化,赋值 变量提升 在全局作用域或者函数作用域 var 声明变量允许在声明之前使用,值为 undefined,这个就是变量提升 块级作用域中声明的变量会提升到该作用域的头部,即允许在实际声明前使用 暂时性死区 在全局作用域或者函数作用域 let 或 const 声明变量,只允许在声明之后访问,在作用…

作用域

作用域就是执行上下文,在执行上下文中会有内存分配与执行两个阶段。变量的的生命周期:声明,初始化,赋值

  1. 变量提升

    在全局作用域或者函数作用域 var 声明变量允许在声明之前使用,值为 undefined,这个就是变量提升

    块级作用域中声明的变量会提升到该作用域的头部,即允许在实际声明前使用

  2. 暂时性死区

    在全局作用域或者函数作用域 let 或 const 声明变量,只允许在声明之后访问,在作用域头部到声明变量处就叫暂时性死区,暂时性死区不能访问变量

作用域种类

  1. 全局作用域
var name = 'koa';
  1. 函数作用域

普通函数作用域

function koa() {
  var name2 = 'koa';
}

i 被提升到了当前函数作用域头部

var callbacks = [];
for (var i = 0; i < 2; i++) {
  // 这里面的i为全局作用域
  callbacks[i] = function() {
    return i;
  };
}
console.log(callbacks[0]());
console.log(callbacks[1]());
// 输出
// 2
// 2

解决访问:用闭包,把 i 以参数的形式传给内层函数

var callbacks2 = [];
for (var i = 0; i < 2; i++) {
  (function(i) {
    // 这里面的i为函数作用域
    callbacks2[i] = function() {
      return i;
    };
  })(i);
}
console.log(callbacks2[0]());
console.log(callbacks2[1]());
// 输出
// 0
// 1
  1. 块级作用域

用块级作用域解决变量提升问题

let callbacks3 = [];
// let让for循环变成块级作用域
// 块级作用域又把声明的变量提升到作用域顶部
for (let i = 0; i <= 2; i++) {
  // 这里的 i 属于当前块作用域
  callbacks3[i] = function() {
    return i;
  };
}
console.log(callbacks3[0]());
console.log(callbacks3[1]());
// 输出
// 0
// 1
  1. 词法作用域:函数的作用域是基于函数创建的位置
function fn() {
  console.log(a);
}
function fn2() {
  var a = 3;
  fn();
}
var a = 2;
fn2();
// 输出
// 2

fn 函数定义在作用域链中,scope 一定是局部变量,不管如何执行 fn,访问的永远是局部变量

var scope = 'global scope';
function checkscope() {
  var scope = 'local scope';
  function f() {
    return scope;
  }
  return f();
}
console.log(checkscope());
// 输出
// local scope

fn 函数定义在作用域链中,scope 一定是局部变量,不管如何执行 fn,访问的永远是局部变量

var scope = 'global scope';
function checkscope2() {
  var scope = 'local scope';
  function f() {
    return scope;
  }
  return f;
}
console.log(checkscope2()());
// 输出
// local scope

作用域链

当访问一个变量的时候,解释器先会在当前作用域查找该变量标识符,如果没找到就去父作用域找,直到找到该变量的标识符

闭包

闭包是什么

有权访问-另一个函数-作用域中的-变量的-函数

闭包的特点

  1. 闭包可以访问当前函数以外的变量
function bar() {
  var date = '进阶';
  function foo(str) {
    console.log(str + date);
  }
  return foo('前端-');
}
bar();
// 输出
// 前端-进阶
  1. 即使外部函数已经返回,闭包仍能访问外部函数定义的变量
function bar2() {
  var date = '进阶';
  function foo2(str) {
    console.log(str + date);
  }
  return foo2;
}
bar2()('前端-');
bar2()('前端再-');
// 输出
// 前端-进阶
// 前端再-进阶
  1. 闭包可以更新外部变量的值
function bar3() {
  var count = '进阶';
  function foo3(val) {
    count = val;
    console.log('前端-' + count);
  }
  return foo3;
}
bar3()('学习');
// 输出
// 前端-学习

闭包的优缺点

优点

  1. 闭包可以读取到函数内部的变量
  2. 闭包可以让这些变量保存在内存中,实现变量数据共享

缺点

  1. 闭包让函数中定义的变量保存在内存中,不用滥用,否则容易内存泄漏
  2. 闭包会在父函数外部任意修改父函数内部的值

闭包的形式

  1. 理论角度:所有的函数都是闭包,所有函数访问全局变量就是访问自由变量(自由变量不是函数参数 arguments 也不是局部变量,是另一个函数作用域中的变量),此时的作用域是最外层作用域
  2. 实践角度:即使常见它的上下文已经销毁,闭包依旧存在,比如父函数中返回内部函数,在代码中引入了自由变量

业务场景中的闭包

  1. 防抖
// 普通方法防抖
window.onresize = debounce(function() {
  console.log('普通方法防抖');
}, 1000);
var time = '';
function debounce(fn, timeLong) {
  if (time) {
    clearTimeout(time);
    time = '';
  }
  time = setTimeout(function() {
    fn();
  }, timeLong);
}
// 闭包方法防抖
window.onresize = debounce2(function() {
  console.log('闭包方法防抖');
}, 1000);
function debounce2(fn) {
  var timer = null;
  return function() {
    if (timer) {
      //timer第一次执行后会保存在内存里 永远都是执行器 直到最后被触发
      clearTimeout(timer);
      timer = null;
    }
    timer = setTimeout(function() {
      fn();
    }, 1000);
  };
}
  1. 设置私有变量
// 普通方法设置私有变量
class Private {
  constructor(s) {
    this.myname = s;
  }
  foo() {
    console.log(this.myname);
  }
}
var p = new Private('he');
p.foo();
// 闭包方法设置私有变量
let sque = (function() {
  class Squery {
    constructor(s) {
      this.myname = s;
    }
    foo() {
      console.log(this.myname);
    }
  }
  return Squery;
})();
let ss = new sque('jian');
ss.foo();
  1. 拿到正确的 i
// 普通方法没有拿到i
var callbacks = [];
for (var i = 0; i < 2; i++) {
  // 这里面的i为全局作用域
  callbacks[i] = function() {
    return i;
  };
}
console.log(callbacks[0]());
console.log(callbacks[1]());
// 输出
// 2
// 2
// 使用闭包可以
var callbacks2 = [];
for (var i = 0; i < 2; i++) {
  (function(i) {
    // 这里面的i为函数作用域
    callbacks2[i] = function() {
      return i;
    };
  })(i);
}
console.log(callbacks2[0]());
console.log(callbacks2[1]());
// 输出
// 0
// 1

上面的例子是用数组,在真实业务中,有可能是一个 dom 点击事件,比如下面例子

// 比如dom点击事件
var ps = document.getElementsByTagName('p');
for (var i = 0; i < ps.length; i++) {
  ps[i].onclick = function() {
    alert(i);
  };
}

用闭包解决

var ps = document.getElementsByTagName('p');
for (var i = 0; i < ps.length; i++) {
  (function(i) {
    ps[i].onclick = function() {
      alert(i);
    };
  })(i);
}

用块级作用域解决

let ps = document.getElementsByTagName('p');
for (let i = 0; i < ps.length; i++) {
  ps[i].onclick = function() {
    alert(i);
  };
}

暂无评论