js基篇-变量类型、数值类型、执行上下文、执行栈、函数值传递等

240 阅读6分钟

变量

  • 在js中有三种变量的定义方法,分别为var、let、const,其中let、const是es6中的语法
  1. var 非块级作用域、存在变量提升,又分为全局变量、局部变量
if( true ) {
    var g = 10
}
console.log(g) // 输出10

// 等价于
var g
if (true) {
    g = 10
}
console.log(g)

var a = 3 // 全局变量

function test () {
    var a = 3 // 局部变量
    b = 5 // 全局变量
}
  1. let变量
  • 是块级变量,在块级内部有效
for (let i = 0; i < 10; i++) {
  // ...
}

console.log(i);
// ReferenceError: i is not defined

for (var i = 0; i < 10; i++) {
  // ...
}

console.log(i);
// 10
  • 不存在变量提升
// var 的情况
console.log(foo); // 输出undefined
var foo = 2;

// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;
  • 暂时性死区
  1. 只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
  2. 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
// 示例1
var tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}

// 示例2
if (true) {
  // TDZ开始
  tmp = 'abc'; // ReferenceError
  console.log(tmp); // ReferenceError

  let tmp; // TDZ结束
  console.log(tmp); // undefined

  tmp = 123;
  console.log(tmp); // 123
}
  • 不予许重复声明
// 报错
function func() {
  let a = 10;
  var a = 1;
}

// 报错
function func() {
  let a = 10;
  let a = 1;
}

function func(arg) {
  let arg; // 报错
}

function func(arg) {
  {
    let arg; // 不报错
  }
}
  1. const 只读的常量。一旦声明,常量的值就不能改变,基本类型和引用类型是有区别的,对于基本类型的值定义好了,更改值会报错,对于引用类型的变量,引用地址发生变更会报错
const PI = 3.1415926
PI = 3 // TypeError: Assignment to constant variable.

const arr = [1, 2, 3]
arr[0] = 1

arr = [1, 3, 5] // 报错

数据类型

  • 基本数据类型:Boolean、Null(typeof Null 'object')、Undefined、Number[范围在-(2^63-1)到2^63-1),在实际应用中后台返回的id超过最大值,以整数的形式返回就会以科学计数法表示]、String、Symbol(es6 语法新定义)
  • 引用类型:Object、Array,栈内存存取引用地址、对内存储数据。

执行上下文

  • 执行上下文是评估和执行Javascript代码的环境的抽象概念。每当在JavaScript中运行任何代码时,它都在执行上下文中运行。

  • 每个执行上下文都提供此关键字(this),该关键字(this)指向正在执行的当前代码所属的对象。

  • 执行上下文分类

    1. 全局执行上下文 - 这是默认或基本执行上下文。不在任何函数内的代码位于全局执行上下文中。它执行两件事:它创建一个全局对象,它是一个窗口对象(在浏览器的情况下)并将其值设置为等于全局对象。程序中只能有一个全局执行上下文。
    1. function执行上下文 - 每次调用函数时,都会为该函数创建一个全新的执行上下文。每个函数都有自己的执行上下文,但是在调用或调用函数时会创建它。可以有任意数量的函数执行上下文。每当创建一个新的执行上下文时,它都会按照定义的顺序执行一系列步骤,我将在本文后面讨论。
    1. Eval函数执行上下文 - 在eval函数内执行的代码也有自己的执行上下文,但由于JavaScript开发人员通常不使用eval,所以我不在这里讨论它。
  • 示例说明

var a = 10,
    b = 20;
funntion fn (x) {
    var a = 100,
        c = 300;
    function bar (x) {
        var a = 1000,
            d = 4000;
    }
    bar(100)
    bar(200)
}
fn(10)

函数执行上下文

创建阶段

  • this值确定(也称this值绑定)
  • LexicalEnvironment 创建
  • VariableEnvironment 创建
  • 执行上下文如下所示
ExecutionContext = {
  ThisBinding = <this value>,
  LexicalEnvironment = { ... },
  VariableEnvironment = { ... },
}
  • 解析创建阶段的三个步骤
  1. this 绑定:在全局执行上下文中,this的值指的是全局对象。(在浏览器中,这指的是Window对象)。在函数执行上下文中,this的值取决于函数的调用方式。如果它由对象引用调用,则将其值设置为该对象,否则将其值设置为全局对象或未定义(在严格模式下)。
let person = {
  name: 'peter',
  birthYear: 1994,
  calcAge: function() {
    console.log(2018 - this.birthYear);
  }
}
person.calcAge(); 
// 'this' refers to 'person', because 'calcAge' was called with //'person' object reference
let calculateAge = person.calcAge;
calculateAge();
// 'this' refers to the global window object, because no object reference was given
  1. Lexical Environment:词汇环境是一种包含标识符变量映射的结构。有两个组成部分:(1)环境记录和(2)对外部环境的引用。(此处标识符是指变量/函数的名称,变量是对实际对象[包括函数类型对象]或原始值的引用)。
    1. 环境记录:存储变量和函数声明的实际位置
- 1. Declarative environment record:函数和参数。功能环境包含声明性环境记录
- 2. Object environment record:环境记录用于定义在全局执行上下文中出现的变量和函数的关联。全局环境包含对象环境记录
    1. 外部环境的引用:对外部环境的引用意味着它可以访问其外部词汇环境
  • 抽象的Lexical Environment如下
GlobalExectionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // Identifier bindings go here
    }
    outer: <null>
  }
}

FunctionExectionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // Identifier bindings go here
    }
    outer: <Global or outer function environment reference>
  }
}
  1. Variable Environment: 变量环境也是一个词汇环境,因此它具有上面定义的词法环境的所有属性。LexicalEnvironment组件和VariableEnvironment组件之间的一个区别是前者用于存储函数声明和变量(let和const)绑定,而后者用于仅存储变量(var)绑定。
let a = 20;
const b = 30;
var c;
function multiply(e, f) {
 var g = 20;
 return e * f * g;
}
c = multiply(20, 30);

GlobalExectionContext = {
  ThisBinding: <Global Object>,
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // Identifier bindings go here
      a: < uninitialized >,
      b: < uninitialized >,
      multiply: < func >
    }
    outer: <null>
  },
  VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // Identifier bindings go here
      c: undefined,
    }
    outer: <null>
  }
}

// 函数multiply调用时才会创建函数执行上下文
FunctionExectionContext = {
 
  ThisBinding: <Global Object>,
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // Identifier bindings go here
      Arguments: {0: 20, 1: 30, length: 2},
    },
    outer: <GlobalLexicalEnvironment>
  },
  VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // Identifier bindings go here
      g: undefined
    },
    outer: <GlobalLexicalEnvironment>
  }
}
  • 你可能已经注意到let、const申明的变量在创建阶段未初始化,而var声明的变量设置为undefined。这就是为什么你可以在声明变量之前访问var定义的变量(虽然是未定义的)但在声明变量之前访问let和const变量时会得到引用错误的原因,也是作用域提升的根本原因

Execution Phase(执行阶段)

  • 在此阶段,完成对所有这些变量的分配,最后执行代码。

执行栈

  • 执行堆栈,在其他编程语言中也称为“调用堆栈”,是具有LIFO(后进先出)结构的堆栈,用于存储在代码执行期间创建的所有执行上下文。

  • 当JavaScript引擎首次遇到