《你不知道的JavaScript》整理(一)——作用域、提升与闭包

简介: 最近在读一本进阶的JavaScript的书《你不知道的JavaScript(上卷)》,里面分析了很多基础性的概念。可以更全面深入的理解JavaScript深层面的知识点。

一、函数作用域


1)函数作用域


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


function foo(a) {
  var b = 2;
  function bar() {
    // ...
  }
  var c = 3;
}
bar(); // 失败
console.log( a, b, c ); // 三个全都失败

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


0.jpg

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(); //这就是闭包!
}

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

定时器事件监听器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”都是不同的。


相关文章
|
20天前
|
存储 JavaScript 前端开发
解释 JavaScript 中的作用域和作用域链的概念。
【4月更文挑战第4天】JavaScript作用域定义了变量和函数的可见范围,静态决定于编码时。每个函数作为对象拥有`scope`属性,关联运行期上下文集合。执行上下文在函数执行时创建,定义执行环境,每次调用函数都会生成独特上下文。作用域链是按层级组织的作用域集合,自内向外查找变量。变量查找遵循从当前执行上下文到全局上下文的顺序,若找不到则抛出异常。
21 6
|
20天前
|
JavaScript
闭包(js的问题)
闭包(js的问题)
12 0
|
1月前
|
设计模式 JavaScript 前端开发
js开发:请解释闭包(closure)是什么,以及它的用途。
闭包是JavaScript中的关键特性,允许函数访问并操作外部作用域的变量,常用于实现私有变量、模块化和高阶函数。私有变量示例展示了如何创建无法外部访问的计数器;模块化示例演示了封装私有变量和函数,防止全局污染;高阶函数示例则说明了如何使用闭包创建能接收或返回函数的函数。
15 2
|
1月前
|
存储 缓存 JavaScript
|
1月前
|
自然语言处理 JavaScript 前端开发
探索JavaScript中的闭包:理解其原理与实际应用
探索JavaScript中的闭包:理解其原理与实际应用
19 0
|
1月前
|
自然语言处理 JavaScript 前端开发
深入理解JS的执行上下文、词法作用域和闭包(中)
深入理解JS的执行上下文、词法作用域和闭包(中)
|
1月前
|
存储 自然语言处理 JavaScript
深入理解JS的执行上下文、词法作用域和闭包(上)
深入理解JS的执行上下文、词法作用域和闭包(上)
|
27天前
|
JavaScript 前端开发 Java
深入剖析 JavaScript 闭包
深入探讨JavaScript闭包,了解其定义、特性、优缺点及作用。闭包是函数与其引用环境的组合,允许内层函数访问外层作用域,常驻内存可能导致内存泄露。优点包括创建私有变量,缺点则涉及内存使用。闭包在变量搜索中遵循从内到外的规则,并影响变量的作用域和生存周期。理解闭包有助于优化代码并避免性能问题。
21 1
|
1月前
|
JavaScript 前端开发
js开发:请解释什么是作用域(scope),并说明全局作用域、局部作用域和块级作用域的区别。
JavaScript中的作用域规定了变量和函数的可见性与生命周期。全局作用域适用于整个脚本,变量可通过全局对象访问,可能导致命名冲突和内存占用。局部作用域限于函数内部,每次调用创建新作用域,执行完毕后销毁。ES6引入的块级作用域通过`let`和`const`实现,变量仅在其代码块内有效,并有暂时性死区。作用域机制有助于代码组织和变量管理。
23 1
|
1月前
|
自然语言处理 前端开发 JavaScript
深入理解JavaScript中的闭包与作用域链
在JavaScript编程中,闭包和作用域链是两个非常重要的概念,它们对于理解代码的执行过程和解决一些特定问题至关重要。本文将深入探讨JavaScript中闭包和作用域链的原理和应用,帮助读者更好地理解这些概念并能够在实际项目中灵活运用。