乐趣区

关于javascript:爪哇学习笔记上下文作用域和闭包

执行上下文

执行上下文(Execution Contexts),简称上下文,是一种标准策略,用于跟踪 ECMAScript 实现对于代码运行时的评估。在任何工夫点,每个理论执行代码的代理最多有一个执行上下文。这称为代理的运行执行上下文(running execution context)。

简而言之,变量或函数的上下文决定了它们能够拜访哪些数据,以及它们的行为。

上下文一共有以下三种:

  • 全局上下文
  • 函数上下文(部分上下文)
  • eval()调用外部的上下文

执行上下文栈

执行上下文堆栈(execution context stack)用于跟踪执行上下文。正在运行的执行上下文始终是此堆栈的顶部元素。每当管制从与以后运行的执行上下文相关联的可执行代码转移到与该执行上下文无关的可执行代码时,就会创立一个新的执行上下文。新创建的执行上下文被压入堆栈,成为运行的执行上下文。

全局上下文

全局上下文是最外层的上下文。依据 ECMAScript 实现的宿主环境,示意全局上下文的对象可能不一样。在浏览器环境中,全局上下文就是咱们常说的 window 对象,因而所有通过 var 定义的全局变量和函数都会成为 window 对象的属性和办法。应用 letconst的顶级申明不会定义在全局上下文中,但在作用域链解析成果上是一样的。

函数上下文

每个函数调用都有本人的函数上下文。当代码执行流进入函数时,函数的上下文被推到一个 上下文栈 上。在函数执行实现之后,上下文栈 就会弹出该函数上下文,将控制权返还给之前的执行上下文。

eval()调用外部的上下文

在非严格模式下,eval 函数外部变量的申明会影响调用上下文(callerContext

"use strict";

var x = 1;
let y = 3;
eval("var x = 2;let y = 4;");
eval("console.log(x, y);"); // 严格模式输入 1 3;非严格模式输入 2 3
console.log(x, y); // 严格模式输入 1 3;非严格模式输入 2 3

如果调用上下文的代码或 eval 码是严格模式代码,则 eval 代码不能实例化调用 eval 的调用上下文的变量环境中的变量或函数绑定。相同,这样的绑定在一个新的 VariableEnvironment 中实例化,只有 eval 代码能够拜访。由 letconstclass申明引入的绑定总是在新的 LexicalEnvironment 中实例化。

What’s the difference between “LexicalEnvironment” and “VariableEnvironment” in spec

A LexicalEnvironment is a local lexical scope, e.g., for let-defined variables. If you define a variable with let in a catch block, it is only visible within the catch block, and to implement that in the spec, we use a LexicalEnvironment. VariableEnvironment is the scope for things like var-defined variables. vars can be thought of as “hoisting” to the top of the function. To implement this in the spec, we give functions a new VariableEnvironment, but say that blocks inherit the enclosing VariableEnvironment.

所以,非严格模式下,应用 var 申明的变量会影响调用上下文,由 letconstclass申明的变量不会影响调用上下文。

作用域

在代码执行之前,所有 ECMAScript 代码都必须与作用域(Realms)相关联。从概念上讲,一个作用域由一组外在对象、一个 ECMAScript 全局环境、在该全局环境范畴内加载的所有 ECMAScript 代码以及其余相干的状态和资源组成。

当咱们创立了一个函数或者 {} 块,就会生成一个新的作用域。须要留神的是,通过 var 创立的变量只有函数作用域,而通过 letconst 创立的变量既有函数作用域,也有块作用域。

作用域分为以下两种:

  • 词法作用域(动态作用域)
  • 动静作用域

词法作用域

What is lexical scope?

词法作用域指一个函数由定义即可确定能拜访的作用域,在编译时即可推导进去。

function foo() {
    let a = 5;

    function foo2() {console.log(a);
    }

    return foo2;
}

动静作用域

动静作用域,指函数由调用函数的作用域链确定的可拜访作用域,是动静的。

function fn() {console.log('隐式绑定', this.a);
}
const obj = {
    a: 1,
    fn
}

obj.fn = fn;
obj.fn();

作用域链

每一个作用域都有对其父作用域的援用。当咱们应用一个变量的时候,Javascript 引擎 会通过变量名在以后作用域查找,若没有查找到,会始终沿着作用域链始终向上查找,直到 global 全局作用域。作用域链决定了各级上下文中的代码在拜访变量和函数时的程序。

作用域链加强

某些语句会导致在作用域链前端长期增加一个上下文,这个上下文在代码执行后会被删除。

  • try/catch语句的 catch
  • with语句

    function buildUrl() {
      let qs = "?debug=true";
    
      with(location) {let url = href + qs;}
    
      return url;
    }

this 指向问题

this 是在执行时动静读取上下文决定的,而不是创立时

全局上下文中的 this

无论是否在严格模式下,在全局执行上下文中(在任何函数体内部)this都指代全局对象

// 在浏览器中,window 对象同时也是全局对象
console.log(this===window); // true

函数上下文中的 this

在函数外部,this的值取决于函数被调用的形式

函数间接调用

函数间接调用中,非严格模式下 this 指向的是 window,严格模式下this 指向的是undefined

function foo() {console.log('函数外部 this', this);
}

foo();

隐式绑定

this 指向它的调用者,即谁调用函数,他就指向谁。

function fn() {console.log('隐式绑定', this.a);
}
const obj = {
    a: 1,
    fn
}

obj.fn = fn;
obj.fn(); // 1

显式绑定

通过 callapplycall 办法扭转 this 的行为。

var name = 'a';
function foo() {console.log('函数外部 this', this.name);
}

foo(); // a
foo.call({name: 'b'}); // b
foo.apply({name: 'c'}); // c
const bindFoo = foo.bind({name: 'd'});
bindFoo(); // d

构造函数的 new 绑定

当一个函数用作构造函数时(应用 new 关键字),它的 this 被绑定到正在结构的新对象。

new关键字会进行如下操作:

  1. 创立一个空的简略 JavaScript 对象(即{});
  2. 为步骤 1 新创建的对象增加属性__proto__,将该属性链接至构造函数的原型对象;
  3. 将步骤 1 新创建的对象作为 this 的上下文;
  4. 如果该函数没有返回对象,则返回 this。

箭头函数中的 this

箭头函数 中,this与关闭词法上下文的 this 保持一致。

setTimeout 回调函数中的 this

setTimeout回调函数中的 this 指向window。能够应用箭头函数作为回调函数,让回调中的 this 指向父级作用域中的 this。

闭包

MDN 的解释:

一个函数和对其四周状态(lexical environment,词法环境)的援用捆绑在一起(或者说函数被援用突围),这样的组合就是闭包(closure)。

上面介绍一些闭包的利用:

事件处理(异步执行)的闭包

解决 var 的变量晋升问题

    let lis = document.getElementsByTagName('li');

    for(var i = 0; i < lis.length; i++) {(function(i) {lis[i].onclick = function() {console.log(i);
            }
        })(i);
    }

实现公有变量

function People(num) { // 构造函数
    let age = num;
    this.getAge = function() {return age;};
    this.addAge = function() {age++;};
}

let tom = new People(18);
let pony = new People(20);
console.log(tom.getAge()); // 18
pony.addAge();
console.log(pony.getAge()); // 21

装璜器函数

A function decorator is a higher-order function that takes one function as an argument and returns another function, and the returned function is a variation of the argument function — Javascript Allongé

  • 函数防抖与函数节流(详情点击这里)
  • once(fn)

    function once(fn){
      let returnValue;
      let canRun = true;
      return function runOnce(){if(canRun) {returnValue = fn.apply(this, arguments);
              canRun = false;
          }
          return returnValue;
      }
    }
    var processonce = once(process);
    processonce(); //process
    processonce(); //

应用闭包绑定函数上下文(实现 bind 函数的性能)

Function.prototype.myBind = function() {
    let fn = this,
        args = [...arguments],
        obj = args.shift();
    return function() {return fn.apply(object, args.concat(...arguments));
    }
}
退出移动版