【阅读笔记】你不知道的Javascript--作用域与闭包1

作用域与闭包

LHS 引用与 RHS 引用

LHS 引用类似于左值引用,即我们要赋值给哪一个变量,此时值是主动的,他寻找变量!
const a = 2 其中值 2 主动赋值给左侧的 a 变量

RHS 引用类似于右值引用,即寻找赋值操作的源头,此时值是被动的,他需要找到发起赋值请求的主动变量;
fn(2) 其中 2 作为实参传入函数,他需要找到被赋值的形参

不能简单地理解为:等号左侧是变量就是 LHS,等号右侧时变量就是 RHS


ReferenceError 异常

一般的,无论 LHS 还是 RHS,在当前作用域找不到对应变量时就会不断往外层作用域查询,直到找到为止;

非严格模式下,若最外层仍然找不到,系统自动创建对应名称全局变量,赋予未定值并返回;
严格模式下,若最外层找不到,直接抛出 ReferenceError 异常

试图对一个非函数类型的值进行函数调用,或者引用 null 或 undefined 类型的值中的属性,那么引擎会抛出 TypeError 异常


欺骗词法作用域

尽量不要使用欺骗,有证明将会导致性能下降

词法作用域可以简单的理解为上一节讲述的作用域概念;

使用 eval()方法,在代码中的某处再插入一行代码,让编译器认为该行代码是原本存在的,即可达到欺骗效果;
如下代码,通过 eval 插入定义变量 b,让下一行代码中的 b 只在本层中找到变量 b,而不向外层查找,此即为欺骗

function foo(str, a) {
  eval(str);
  console.log(a, b);
}
var b = 2;
foo("var b = 3", 1); // 1,3

作用域变量冲突

如下方代码将会导致死循环,因为每次执行 bar 函数,都会设置 i=3,且这个 i 会被默认指定为 for 循环中的 i,那么永远存在 i==3<10,所以必定无限循环!!!

function foo() {
  function bar(a) {
    i = 3; // 此处的i会被误指定为for中的i
    console.log(a + i);
  }
  for (var i = 0; i < 10; i++) {
    bar(i * 2); // 无限循环
  }
}

函数术语释疑

函数声明:即一个正常的函数 function foo(){}
函数表达式:开头关键词不是 function 的任意一个函数 (function(){})()
匿名函数表达式:function(){} 或者 ()=>{}

立即执行函数表达式 IIFE:(function(){})()


立即执行函数详解

有效避免变量污染,IIFE 函数内部的表达式访问范围仅限于该函数,而不会外泄:

var a = 2;
(function () {
  var a = 3;
  console.log(a); // 3
})();

可以为 IIFE 函数传入一个参数,以便调用外部的变量
下面将 window 作为实参传给了内部函数 IIFE 的形参 global

var a = 2;
(function IIFE(global) {
  var a = 3;
  console.log(a); // 3
  console.log(global.a); // 2
})(window);

可以倒置函数的运行过程;
将小括号里面的 def 函数作为参数传递给内部 foo 函数,然后 foo 再次调用并执行 def

var a = 2;
(function IIFE(def) {
  def(window);
})(function def(global) {
  var a = 3;
  console.log(a); // 3
  console.log(global.a); // 2
});

可以把 IIFE 函数末尾的小括号写在外面 (function(){})()
或者写在里面 (function(){}())
二者的运行结果一致,但大家普遍喜欢写在里面


块作用域

一个普通的块内的函数、变量,都会在执行完毕后自行垃圾回收掉

{
    let a = 1;
    function(){}
}

作用域提升

对于 var 声明的变量,它具有作用域提升,但提升的只是声明,而真正的赋值操作仍然保留在原地;
故以下代码输出 undefined,因为真正的赋值在我们使用后

console.log(a); // undefined

var a = 10;

函数声明优先于变量声明;
顾名思义,同名的函数/变量,会优先把函数提升到顶层,然后再到变量声明

foo(); // 1

var foo;

function foo() {
  console.log(1);
}

foo = function () {
  console.log(2);
};

作用域闭包

所谓闭包,即能持有对内部作用域的访问,且该引用不会被垃圾回收掉!
如下代码就展示了闭包的使用效果

  1. 通过执行 foo()得到一个 bar()函数作为返回值
  2. 然后执行 baz()来运行对应的 bar()
  3. 此刻我们可以看到 bar()函数能轻松地访问到内部函数作用域中的 a 值,并且因为变量 baz 的存在,永远不会 GC 掉这个作用域

这就是闭包的基本原理,其他的代码也可以实现,只要记住这个基本原则即可!

function foo() {
  var a = 2;

  function bar() {
    console.log(a);
  }

  return bar;
}
var baz = foo();
baz(); // 2

因为 settimeout 倒计时完毕,是吧事件添加到事件触发线程的任务队列中去,等到整个 JS 执行线程空了才会执行事件触发线程;
所以我们需要在 for 中使用一个 IIFE 函数来保证最终的结果是正确的;

for (var i = 1; i <= 5; i++) {
  (function (j) {
    setTimeout(() => {
      console.log(j);
    }, j * 1000);
  })(i);
}

如果不使用 IIFE 函数,直接写 settimeout,那么最终结果输出 5 个 6,而不是 1-5 逐一输出!


模块

对于一个模块,在 return 中使用对象式写法,将内部对函数暴露出来,使得外部函数可以调用内部函数的词法作用域!
但是不可以暴露模块内的变量;

function CustomModule() {
  var something = "none";
  function doSomething() {
    console.log(something);
  }
  return {
    doSomething: doSomething,
  };
}

var module = CustomModule();
module.doSomething(); // none

模块模式必备的两个条件:

  1. 必须有外部的封闭函数,该函数必须至少被调用一次
  2. 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zhillery

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值