《你不知道的javascript》整理——作用域提升与闭包

咖啡机(K.F.J) 咖啡机(K.F.J)     2022-08-03     193

关键词:

最近在读一本进阶的JavaScript的书《你不知道的JavaScript(上卷)》,里面分析了很多基础性的概念。

可以更全面深入的理解JavaScript深层面的知识点。

 

一、函数作用域

1)函数作用域

就是作用域在一个“Function”里,属于这个函数的全部变量都可以在整个函数的范围内使用及复用。

function foo(a) {
  var b = 2;
  function bar() {
    // ...
  }
  var c = 3;
}

bar(); // 失败
console.log( a, b, c ); // 三个全都失败

上面的“foo”函数内的几个标识符,放到函数外面访问就都会报错,查看源码

 

2)立即执行函数表达式

在任意代码片段外部添加包装函数,可以将内部的变量和函数定义“隐藏”起来,外部作用域无法访问包装函数内部的任何内容。

例如上面的bar、a等几个标识符。这样能够保护变量不被污染。

在写插件的时候经常会用到立即执行函数表达式,为的就是保护里面的变量。

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

“foo”中第一个(  )将函数变成表达式,第二个(  )执行了这个函数。

有一个专用术语:IIFE,代表立即执行函数表达式(Immediately Invoked Function Expression);

1. 进阶用法是把它们当作函数调用并传递参数进去

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

2. 一种变化的用途是倒置代码的运行顺序,在CMD或AMD项目中被广泛使用。

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

 

二、块作用域

JavaScript不支持块作用域。

for(var i=0; i<10; i++) {
  console.log( i );
}

上面的代码中的“i”相当于下面的

var i;
for(i=0; i<10; i++) {
  console.log( i );
}

 

但也有例外,“try/catch”,catch就是一个块作用域。

try{
  undefined(); // 执行一个非法操作来强制制造一个异常 
}  
catch(err) {
  console.log( err ); // 能够正常执行!
}
console.log( err ); // ReferenceError: err not found

 

ES6改变了现状,引入了新的let关键字,let关键字可以将变量绑定到所在的任意作用域中(通常是{ .. }内部)。换句话说,let为其声明的变量隐式地了所在的块作用域。

 

三、提升

函数作用域和块作用域的行为是一样的,可以总结为:任何声明在某个作用域内的变量,都将附属于这个作用域。

1)编译与执行

变量和函数的所有声明都会在任何代码被执行前首先被处理,可以看下面的代码事例。

a = 2;
var a;
console.log(a);//2

这段代码等价于:

var a;//定义声明是在编译阶段进行
a = 2;//赋值声明会被留在原地等待执行阶段
console.log(a);

 

2)函数优先

函数会首先被提升,然后才是变量。

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

var foo函数表达式尽管出现在function foo()的声明之前,但它是重复的声明(因此被忽略了),因为函数声明会被提升到普通变量之前。

而上面的代码相当于:

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

 

四、闭包

1)定义

当函数可以记住并访问所在的作用域时,就产生了闭包,即使函数是在当前作用域之外执行

function foo() {
  var a = 2;
  function bar() { 
    console.log( a );
  }
  return bar;
}
 
var baz = foo();
baz(); // 2 —— 这就是闭包的效果。

1. 将函数“bar”赋值给“baz”,执行“baz”,当前作用域并不在“bar”的作用域,但是可以执行。

2. 闭包还会阻止垃圾回收,当“foo”执行完后,内部作用域仍然存在。这样才能让“baz”执行。

 

2)将函数作为参数传递

function foo() {
  var a = 2;
  function baz() {
    console.log( a ); // 2
  }
  bar( baz );
}
 
function bar(fn) {
  fn(); //这就是闭包!
}

把内部函数baz传递给bar,当调用这个内部函数时(fn),它涵盖的foo()内部作用域的闭包就可以观察到了,因为它能够访问a。

如果将函数当作第一级的值类型并到处传递,你就会看到闭包在这些函数中的应用。

定时器事件监听器Ajax请求跨窗口通信Web Workers或者任何其他的异步(或者同步)任务中,只要使用了回调函数,实际上就是在使用闭包!

 

3)循环和闭包

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

每次打印出来都将会是6,延迟函数的回调会在循环结束时才执行,查看源码

根据作用域的工作原理,实际情况是尽管循环中的五个函数是在各个迭代中分别定义的,但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个i

现在用闭包来实现每次打印不同的i。

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

IIFE会通过声明并立即执行一个函数来创建作用域。setTimeout中的回调可以记住当前的作用域,每个作用域中的参数“j”都是不同的。

 

《你不知道的javascript》整理——对象

...造形式varmyObj=newObject();myObj.key=value; 二、类型对象是JavaScript的基础。1)基本类型在JavaScript中一共有六种主要类型(术语是“语言类型”):string、number、boolean、null、unde 查看详情

《你不知道的javascript》整理——强制类型转换

JavaScript中通常分为两种类型转换,“隐式强制类型转换”(implicitcoercion)和“显式强制类型转换”(explicitcoercion)。下面所有代码的源码可以在此处查看。一、强制转换为字符串(ToString)1)ToString基本类型值的字符串化规则... 查看详情

《你不知道的javascript》整理——值与原生函数

一、值1)数字JavaScript只有一种数值类型:number(数字),包括“整数”和带小数的十进制数。//数字的语法vara=5E10;//50000000000a.toExponential();//"5e+10"varb=a*a;//2.5e+21varc=1/a;//2e-11vard=0.42;vare=.42;//数字前面的0可以省略varf=42.;//小数点后... 查看详情

《你不知道的javascript》——词法/函数/块作用域

 你不知道的JavaScript(上卷)  视频选集1/4P11.作用域是什么P22.词法作用域P33.函数作用域和块作用域P44.提升                   &nb 查看详情

你不知道的javascript--上卷--读书笔记1

作用域是什么?  答:在《你不知道的javascript》书中提到,作用域就是根据名称查找变量的一套规则。古语有“无规矩不成方圆”,但是没有方圆,规矩又给谁用?所以个人理解作用域就是“规矩”+”方圆“。作用域是在创... 查看详情

《你不知道的javascript》系列分享专栏

《你不知道的JavaScript》系列分享专栏你不知道的JavaScript”系列就是要让不求甚解的JavaScript开发者迎难而上,深入语言内部,弄清楚JavaScript每一个零部件的用途《你不知道的JavaScript》已整理成PDF文档,点击可直接下载至本地查... 查看详情

你不知道的javascript(上卷)

第一部分作用域和闭包第1章作用域是什么  1.1编译原理  1.2理解作用域    1.2.1演员表    1.2.2对话    1.2.3编译器有话说    1.2.4引擎和作用域的对话    1.2.5小测验  1.3作用域嵌套  1.4异常  1... 查看详情

你不知道的javascript(上卷)读书笔记之一----作用域

你不知道的Javascript(上卷)这本书在我看来是一本还不错的书籍,这本书用比较简洁的语言来描述Js的那些”坑”,在这里写一些博客记录一下笔记以便消化吸收。1编译原理在此书中,开始便提出:Javascript是一门编译型语言,我... 查看详情

你不知道的javascript-2.词法作用域

考虑以下代码:functionfoo(a){varb=a*2;functionbar(c){console.log(a,b,c);}bar(b*3);}foo(2);//2,4,12在这个例子中有三个逐级嵌套的作用域。 包含着整个全局作用域,其中只有一个标识符:foo。 包含着foo所创建的作用域,其中有三个标识符... 查看详情

作用域闭包《你不知道的javascript(上)》(代码片段)

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是当前词法作用域之外执行。 functionfoo()vara=2;functionbar()console.log(a);//2bar();foo();functionfoo()vara=2;functionbar()console.log(a);returnbar;varbaz=foo();baz();// 查看详情

作用域闭包《你不知道的javascript(上)》(代码片段)

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是当前词法作用域之外执行。 functionfoo()vara=2;functionbar()console.log(a);//2bar();foo();functionfoo()vara=2;functionbar()console.log(a);returnbar;varbaz=foo();baz();// 查看详情

你不知道的javascript(作用域和闭包)

作用域和闭包 ?作用域  引擎:从头到尾负责整个JavaScript的编译及执行过程。  编译器:负责语法分析及代码生成等。  作用域:负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格... 查看详情

你不知道的javascript(上卷)读书笔记之二----词法作用域

...要的工作类型,一种是词法作用域,一种是动态作用域,Javascript采用的是词法作用域,关于动态作用域的有兴趣的可以自行Google。1.词法阶段         首 查看详情

《你不知道的javascript》函数作用域和块作用域

函数中的作用域 所谓函数作用域,就是属于这个函数的全部变量都可以在整个函数的范围内使用及复用。1functionfoo(a){2varb=a;3functionbar(c){4varc=b*2;5console.log(c);6}7bar();//48}9foo(3); 1functionfoo(a){2varb=a;3functionbar(c){4varc=b*2;5con 查看详情

《你不知道的javascript》函数作用域和块作用域

函数中的作用域 所谓函数作用域,就是属于这个函数的全部变量都可以在整个函数的范围内使用及复用。1functionfoo(a){2varb=a;3functionbar(c){4varc=b*2;5console.log(c);6}7bar();//48}9foo(3); 1functionfoo(a){2varb=a;3functionbar(c){4varc=b*2;5con 查看详情

函数作用域和块级作用域--你不知道的javascript

...能访问的值,但是const声明的是常量,也不能修改函数是JavaScript中最常见的作用域单元。本质上,声明在一个函数内部的变量或函数会在所处的作用域中“隐藏”起来,这是有意为之的良好软件的设计原则。但函数不是唯一的作... 查看详情

你不知道的javascript学习笔记1——作用域

处理程序三要素:引擎:编译与执行过程。编译器:语法分析与代码生成等。作用域:收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。&nb... 查看详情

你不知道的javascript-作用域是什么

...函数时传入参数的操作都会导致关联作用域的赋值操作。JavaScript引擎首先会在代码执行前对其进行编译,在这个过程中,像vara=2这样的声明会被分解成两个独立的步骤:首先,vara在其作 查看详情