JavaScript-作用域是什么

2019-03-10  本文已影响0人  devZhang

理解作用域

image

作用域嵌套

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

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

小结

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

词法作用域

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

词法阶段

小结

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

函数作用域和块作用域

函数中的作用域

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

隐藏内部实现

不应该这样:

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);

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

  1. 匿名函数在栈追踪中不会显示出有意义的函数名,使得调试很困难
  2. 如果没有函数名,当函数需要引用自身时只能使用已经过期的arguments.callee引用。
  3. 匿名函数省略了对于代码可读性/可理解性很重要的函数名。

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

setTimeout(function timeoutHandler() { // 有名字了
  console.log('I waited 1 second');
  
}, 1000);

立即执行函数表达式

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

提升

总结

作用域闭包

定义

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

循环和闭包

模块

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

  1. 必须有外部的封闭函数,该函数必须至少别调用一次(每次调用都会创建一个新的模块实例)
  2. 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有得状态。

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

现代的模块机制

大多数模块依赖加载器/管理器本质上都是将这种模块定义封装进一个友好的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的实例作为依赖参数,并能响相应的使用它。

总结

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

模块有两个主要特征:

  1. 为创建内部作用域而调用了一个包装函数
  2. 包装函数的返回值必须包含至少一个对内部函数的引用,这样就会创建涵盖整个包装函数内部作用域的闭包
上一篇 下一篇

猜你喜欢

热点阅读