资讯专栏INFORMATION COLUMN

JavaScript-作用域是什么

xfee / 2601人阅读

摘要:直到抵达最外层的全局作用域,无论找到还是没找到,查找过程都会停止。小结作用域是一套规则,用于确定在何处以及如何查找变量标志符。作用域查找会在找到第一个匹配的标识符时停止。函数作用域匿名和具名例如如下函数这叫做匿名函数表达式。

理解作用域

引擎

从头到尾负责整个JavaScript程序的编译和执行过程

编译器

负责语法分析及代码生成

作用域

负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符有访问权限。

作用域嵌套

当一个块或者函数嵌套在另一个函数或函数中时,就发生了作用域嵌套。

遍历嵌套作用域规则:引擎从当前的执行作用域开始查找变量,如果找不到,就向上一级继续查找。直到抵达最外层的全局作用域, 无论找到还是没找到,查找过程都会停止。

小结

作用域是一套规则,用于确定在何处以及如何查找变量(标志符)。
如果查找目的是对变量进行赋值,就是执行LHS查询
如果查找目的是获取变量的值,就是执行RHS查询

词法作用域

作用域主要两种工作模式:词法作用域和动态作用域

词法阶段

大部分标准语言编译器的第一个工作阶段叫做词法化(也叫单词化)。

简单的说, 词法作用域就是定义在词法阶段的作用域。换句话说,词法作用域是由你在写代码的时候将变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变。

作用域查找会在找到第一个匹配的标识符时停止。在多层的嵌套作用域中可以定义同名的标识符,叫做“遮蔽效应”

作用域查找始终是从运行时所处的最内部作用域开始,逐级向外或者向上查找, 知道遇见第一个匹配的标识符为止。

全局变量会自动成为全局对象(例如浏览器中的window对象)的属性,因此可以不直接通过全局对象的词法名称, 而是间接的通过对全局对象属性的引用来对其进行访问。

例如window.a。通过这种技术可以访问那些被同名变量锁遮蔽的全局变量。但非全局变量如果被遮蔽了,无论如何都无法被访问到。

无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置决定。

小结

词法作用域意味着作用域是由代码书写时候函数声明的位置来决定的。

函数作用域和块作用域 函数中的作用域

函数作用域是指,属于这个函数的全部变量都可以在整个函数的范围内使用以及复用(事实上在嵌套的作用域中也可以使用)。

隐藏内部实现

不应该这样:

function doSomething(a) {
 b = a + doSomethingElse(a * 2);

 console.log(b * 3);
}

function doSomethingElse(a) {
 return a - 1;
}

var b;

doSomething(2);

而是应该这样, 隐藏变量:

function doSomething(a) {
 function doSomethingElse(a) {
   return a - 1;
 }
 var b;

 b = a + doSomethingElse(a * 2);

 console.log(b * 3);
}

doSomething(2);

#### 规避冲突
“隐藏”作用域中的变量和函数所带来的另一个好处,是可以避免同名标识符之间的冲突,两个标识符可能具有相同的名字但是用途却不一样,无意间可能造成命名冲突。 冲突会导致变量的值被意外覆盖。

### 函数作用域

#### 匿名和具名
例如如下函数:

setTimeout(function() {
 console.log("I waited 1 second");
 
}, 1000);

这叫做匿名函数表达式。
匿名函数表达式书写起来简单快捷,但是有几个缺点:

匿名函数在栈追踪中不会显示出有意义的函数名,使得调试很困难

如果没有函数名,当函数需要引用自身时只能使用已经过期的arguments.callee引用。

匿名函数省略了对于代码可读性/可理解性很重要的函数名。

行内函数表达式非常强大且有用----匿名和具名之间的区别并不会对这一点有任何影响。给函数表达式指定一个函数名可以有效解决以上问题。所以,最好始终给函数表达式命名。

setTimeout(function timeoutHandler() { // 有名字了
  console.log("I waited 1 second");
  
}, 1000);
立即执行函数表达式

(function(){})()(function(){}())

提升

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

出现在后面的函数声明还是可以覆盖前面的。

一个普通块内部的函数声明通常会被提升到所在作用域的顶部。

总结

所有的声明(变量和函数)都会被“移动”到各自作用域的最顶端, 这个过程被称为 提升。

声明本身会被提升,而包含函数表达式的赋值在内的赋值操作并不会被提升。

要注意避免重复声明,特别是当普通的var声明和函数声明混合在一起的时候, 否则会引起很多危险的问题。

作用域闭包 定义

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在所在词法作用域以外被执行,这个引用,就叫做闭包。

无论通过何种手段将内部函数传递到所在词法作用域以外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包

本质上讲,无论何时何地,如果将函数当作第一级的值类型并到处传递,你就会看到闭包在这些函数中的应用。

例如在一些定时器、事件监听器、Ajax请求等,只要使用了回调函数,实际上就是在使用闭包

循环和闭包

let声明可以用来劫持块作用域,并且在这个作用域中声明一个变量。

for循环头部的let声明还会有一个特殊的行为。这个行为指出变量在循环过程中不止被声明一次,每次迭代都会声明。随后每个迭代都会使用上一个迭代结束时的值来初始化这个变量。

模块

模块模式需要具备两个必要条件:

必须有外部的封闭函数,该函数必须至少别调用一次(每次调用都会创建一个新的模块实例)

封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有得状态。

一个具有函数属性的对系那个本身并不是真正的模块。从方便观察的角度看,一个从函数调用锁返回的,只有数据属性而没有闭包函数得对象并不是真正的模块。

现代的模块机制

大多数模块依赖加载器/管理器本质上都是将这种模块定义封装进一个友好的API。

var MyModules = (function Manager() {
  var modules = {};

  function define(name, deps, impl) {
    for (var i = 0; i < deps.length; i++) {
      deps[i] = modules[deps[i]];
    }
    modules[name] = impl.apply(impl, deps);
  }

  function get(name) {
    return modules[name];
  }

  return {
    define: define,
    get: get
  };
})();

MyModules.define("bar", [], function() {
  function hello(who) {
    return "let me introduce: " + who;
  }

  return {
    hello: hello
  };
});

MyModules.define("foo", ["bar"], function(bar) {
  var hungry = "xiaofan";

  function awesome() {
    console.log(bar.hello(hungry).toUpperCase());
  }

  return {
    awesome: awesome
  };
});

var bar = MyModules.get("bar");
var foo = MyModules.get("foo");

console.log(bar.hello("xiaofan"));

foo.awesome();

foobar模块都是通过一个返回公共API的函数来定义的。foo甚至接受bar的实例作为依赖参数,并能响相应的使用它。

总结

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

模块有两个主要特征:

为创建内部作用域而调用了一个包装函数

包装函数的返回值必须包含至少一个对内部函数的引用,这样就会创建涵盖整个包装函数内部作用域的闭包

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/109083.html

相关文章

  • [译] 你想知道的关于 JavaScript 作用域的一切

    摘要:原文链接原文作者你想知道的关于作用域的一切译中有许多章节是关于的但是对于初学者来说甚至是一些有经验的开发者这些有关作用域的章节既不直接也不容易理解这篇文章的目的就是为了帮助那些想更深一步学习了解作用域的开发者尤其是当他们听到一些关于作用域的 原文链接: Everything you wanted to know about JavaScript scope原文作者: Todd Mott...

    Flands 评论0 收藏0
  • 弄懂JavaScript作用域和闭包

    摘要:关于本书,我会写好几篇读书笔记用以记录那些让我恍然大悟的瞬间,本文是第一篇弄懂的作用域和闭包。作用域也可以看做是一套依据名称查找变量的规则。声明实际上是根据你传递给它的对象凭空创建了一个全新的词法作用域。 《你不知道的JavaScript》真的是一本好书,阅读这本书,我有多次哦,原来是这样的感觉,以前自以为理解了(其实并非真的理解)的概念,这一次真的理解得更加透彻了。关于本书,我会写好...

    everfly 评论0 收藏0
  • 理解JavaScript的核心知识点:作用

    摘要:也毫不例外,但在中作用域的特性与其他高级语言稍有不同,这是很多学习者久久难以理清的一个核心知识点。主要使用的是函数作用域。 关于作用域:About Scope 作用域是程序设计里的基础特性,是作用域使得程序运行时可以使用变量存储值、记录和改变程序的状态。JavaScript 也毫不例外,但在 JavaScript 中作用域的特性与其他高级语言稍有不同,这是很多学习者久久难以理清的一个核...

    HelKyle 评论0 收藏0
  • javascript作用域的有序性

    摘要:如果是嵌套的作用域的话,这些嵌套作用域会通过作用域链把嵌套作用域联系在一起。全局没有则报错但是上级作用域没法通过作用域链访问下级作用域。通过作用域链能让引擎对执行环境里所有有权访问的变量和函数进行有序访问。 一 为什么要有作用域 我们知道,变量对于程序来说是至关重要的,如果没有变量存储和访问值,整个程序会受到限制。那么问题来了,既然程序这么需要变量,那么它到底是怎么样去存储变量和使用变...

    1fe1se 评论0 收藏0
  • 理解 JavaScript 作用

    摘要:作用域链前面说,作用域是根据名称查找变量的一套规则。把这样一层一层嵌套的作用域,叫做作用域链。因为这个函数名无法被外部作用域所访问。的进阶用法是给其传入参数这样的好处是可以缩短查询时的作用域链。 上一篇文章中分析了 JS 中的数据类型和变量。这一篇文章将分析作用域,以及回答上一篇文章中变量提升的原因。 什么是作用域 作用域是一套规则,保存着变量,等待被引擎所查找。 var a = 1;...

    dadong 评论0 收藏0

发表评论

0条评论

xfee

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<