sailei1 / blog

1 stars 0 forks source link

你不知道的JavaScript-作用域跟闭包 笔记 #68

Closed sailei1 closed 4 years ago

sailei1 commented 4 years ago

第一章 作用域是什么

var a=2; 变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如 果之前没有声明过),然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对 它赋值。(var a; a=2;)

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

第二章 词法作用域

js 函数作用域 “欺骗”词法作用域:eval(..) 和 with eval() 严格模式下执行有自己的作用域,非严格模式下全局作用域, with 副作用

function foo(obj) { 
  with (obj) {a = 2; }
} 
var o1 = { a: 3};
var o2 = { b: 3};
foo( o1 );
console.log( o1.a ); // 2
foo( o2 );
console.log( o2.a ); // undefined
console.log( a ); // 2——不好,a 被泄漏到全局作用域上了!

性能问题 使用了 eval(..) 或 with JS引擎对代码的优化没有任何意义

第三章 函数作用域和块作用域

函数作用域的含义是指,属于这个函数的全部变量都可以在整个函数的范围内使用及复用(事实上在嵌套的作用域中也可以使用) IIFE (function(){ .. }()) 跟 (function(){…})() 一样 没有本质区别 块作用域 从 ES3 开始,try/catch 结构在 catch 分句中具有块作用域。

try{
    throw 2;
}catch(a){
    console.log( a ); // 2
 }
 console.log( a ); // ReferenceError

在 ES6 中引入了 let 关键字(var 关键字的表亲),用来在任意代码块中声明变量。if (..) { let a = 2; } 会声明一个劫持了 if 的 { .. } 块的变量,并且将变量添加到这个块中。 let 进行的声明不会在块作用域中进行提升。声明的代码被运行之前,声明并不“存在”。

{  console.log( bar ); // ReferenceError!
   let bar = 2;
}

除了 let 以外,ES6 还引入了 const,同样可以用来创建块作用域变量,但其值是固定的(常量)

var foo = true;
if (foo) {
var a = 2;
const b = 3; // 包含在 if 中的块作用域常量
 a = 3; // 正常!
 b = 4; // 错误!
}
console.log( a ); // 3
 console.log( b ); // ReferenceError!

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

第四章 提升

变量和函数在内的所有声明都会在任何代码被执行前首先被处理。 只有声明本身会被提升,而赋值或其他运行逻辑会留在原地。 函数声明会被提升,但是函数表达式却不会被提升.

foo();
function foo() {   //提升
  console.log( a ); //undefined
 var a = 2;
}

foo(); // 不是 ReferenceError, 而是 TypeError!
var foo = function bar() { //不提升
 // ...
};

即使是具名的函数表达式,名称标识符在赋值之前也无法在所在作用域中使用

foo(); // TypeError
bar(); // ReferenceError
var foo = function bar() { 
   // ...
};
 这个代码片段经过提升后,实际上会被理解为以下形式:
var foo;
foo(); // TypeError
bar(); // ReferenceError
foo = function() {
    var bar = ...self... // ...
}

函数会首先被提升,然后才是变量。 要注意避免重复声明,特别是当普通的 var 声明和函数声明混合在一起的时候,否则会引起很多危险的问题!

第五章 作用域闭包

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

拜bar() 所声明的位置所赐,它拥有涵盖 foo() 内部作用域的闭包,使得该作用域能够一直存活,以供 bar() 在之后任何时间进行引用。 bar() 依然持有对该作用域的引用,而这个引用就叫作闭包。

\\闭包形成的模块化
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 = "hippo";
        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( "hippo" )); // Let me introduce: hippo 
     foo.awesome(); // LET ME INTRODUCE: HIPPO

闭包的出现往往是函数式编程的语言里 闭包的本质是静态作用域。因为 JavaScript 没有动态作用域,所以函数访问的都是定义时的作用域,所以闭包才得以实现。

作用如下:

  1. 避免使用全局变量,实现数据隐藏和保持,也就是面向对象的特性:封装。
  2. 当成员变量比较少,以及方法只有一个时,比类更简单实现。
  3. 数据和函数一起封装,适应异步或并发运行。