【前端丛林】JavaScript这样服用,效果更佳(2)

109 阅读7分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情

前言

哈喽大家好,我是Lotzinfly,一位前端小猎人。欢迎大家来到前端丛林,在这里你将会遇到各种各样的前端猎物,我希望可以把这些前端猎物统统拿下,嚼碎了服用,并成为自己身上的骨肉。今天是我们冒险的第二天,我们会遇到什么样的猎物呢?话不多说,现在开启我们今天的前端丛林冒险之旅吧!

1. 数据类型

  • JS中有七种基本数据类型
  • 六种基本数据类型 Boolean Null Undefined Number String Symbol
  • 一种引用类型 object {} [] /^$/ new Date() Math
类型
Booleantrue或false
Nullnull
Undefinedundefined
Number数字
String字符串
Symbol符号类型

2. 执行上下文

2.1 如何存储

  • 当函数运行时,会创建一个执行环境,这个执行环境就叫执行上下文(Execution Context)
  • 执行上下文中会创建一个对象叫作变量对象(Value Object),基础数据类型都保存在变量对象中
  • 引用数据类型的值保存在堆里,我们通过操作对象的引用地址来操作对象
function task(){
    var a = 1;
    var b = {
    name:'Lotzinfly'
  }
    var c = [1,2,3];
}
let ExecuteContext = {
      VO:{
         a:1,
         b:'xo1',
         c:'xa1'
  }
};

image.png

2.2 如何复制

2.2.1 基本数据

  • 基本数据类型复制的是值本身
var a = 1;
var b = a;
b = 2;
console.log(a);
var ExecuteContext = {
      VO: { a: 1 }
 };
ExecuteContext.VO.b = ExecuteContext.VO.a;
ExecuteContext.VO.b = 2;
console.log(ExecuteContext.VO.a);

image.png

2.2.2 引用数据

  • 引用数据类型复制的是引用地址指针
var m = { a: 1, b: 2 };
var n = m;
n.a = 10;
console.log(m.a);
var ExecuteContext = {
     VO: { m: { a: 1, b: 2 } }
  };
ExecuteContext.VO.b = ExecuteContext.VO.a;
ExecuteContext.VO.a = 10;
console.log(ExecuteContext.VO.a);

image.png

3. 执行上下文生命周期

3.1 生命周期有两个阶段

  • 一个新的执行上下文的生命周期有两个阶段
  1. 创建阶段
  • 创建变量对象
  • 确定作用域链
  • 确定 this 指向
  1. 执行阶段
  • 变量赋值
  • 函数赋值
  • 代码执行

3.2 变量对象

  • 变量对象会保存变量声明(var)、函数参数(arguments)、函数定义(function)
  • 变量对象会首先获得函数的参数变量和值
  • 获取所有用 function 进行的函数声明,函数名为变量对象的属性名,值为函数对象,如果属性已经存在,值会用新值覆盖
  • 再依次所有的var关键字进行的变量声明,每找到一个变量声明,就会在变量对象上建一个属性,值为 undefined ,如果变量名已经存在,则会跳过,并不会修改原属性值, let 声明的变量并不会在此阶段进行处理
  • 函数声明优先级更高,同名的函数会覆盖函数和变量,但同名 var 变量并不会覆盖函数.执行阶段重新赋值可以改变原有的值

3.2.1 基本类型

console.log(a);
var a = 1;
var a = undefined;//变量提升
console.log(a);
a = 1;

3.2.2 变量提升

  • 正常编写
var a = 1;
function fn(m) { console.log('fn'); }
function fn(m) { console.log('new_fn'); }
function a() { console.log('fn_a'); }
console.log(a);
fn(1);
var fn = 'var_fn';
console.log(fn);
//1
//new_fn
//var_fn
  • 真正执行
// 创建阶段
function fn(m) { console.log('fn'); }
function fn(m) { console.log('new_fn'); }
function a() { console.log('fn_a'); }
var a = undefined;
var fn = undefined;
//执行阶段
a = 1;
console.log(a);
fn();
fn = 'var_fn';
console.log(fn);
  • 上下文
// 创建阶段
var globalEC = {
       VO: {
           ...arguments,
           a: () => { console.log('fn_a'); },
           fn: () => { console.log('new_fn'); }
        }
}
var ECStack = [globalEC];
//执行阶段
globalEC.VO.a = 1;
console.log(globalEC.VO.a);
globalEC.VO.fn();
globalEC.VO.fn = 'var_fn';
console.log(globalEC.VO.fn);

3.2.3 激活对象

  • 在函数的调用栈中,如果当前执行上下文处于函数调用栈的顶端,则意味着当前上下文处于激活状态,此时变量对象称为活动对象(AO,Activation Object) VO=>AO
  • 活动变量包含变量对象所有的属性,并有包含 this 指针
function one(m) {
       function two() {
       console.log('two');
      }
}
   one(1);
//执行阶段 VO=>AO
let VO = AO = {
       m:1,
       two: () => { console.log('two'); },}
       let oneEC={
           VO,
           this: window,
           scopeChain:[VO,globalVO]
  }

3.2.4 全局上下文的变量对象

  • 在浏览器里,全局对象为 window
  • 全局上下文的变量对象为 window ,而且这个变量对象不能激活变成活动对象
  • 只在窗口打开,全局上下文会一直存在,所有的上下文都可以直接访问全局上下文变量对象上的属性
  • 只有全局上下文的变量对象允许通过VO的属性名称来间接访问,在函数上下文中是不能直接访问VO对象的
  • 未进入执行阶段前,变量对象中的属性都不能访问!但是进入到执行阶段之后,变量对象转变成了活动对象,里面的属性都能被访问了,对于函数上下文来讲,活动对象与变量对象其实都是同一个对象,只是处于执行上下文的不同生命周期

4. 作用域

4.1 作用域

  • 在JS中,作用域是用来规定变量访问范围的规则
function one() {
    var a = 1;
}
console.log(a);

4.2 作用域链

  • 作用域链是由当前执行环境与上层执行环境的一系列变量对象组成的,它保证了当前执行环境对符合访问权限的变量和函数的有序访问

4.2.1 作用域链

function one() {
     var a = 1;
     function two() {
         var b = 2;
         function three() {
             var c = 3;
             console.log(a, b, c);
       }
        three();
   }
     two();
}
one();
// 1.创建全局上下文
var globalExecuteContextVO = { one: `()=>{var a = 1;}` }
var globalExecuteContext = {
    VO: globalExecuteContextVO,
    scopeChain: [globalExecuteContextVO]
}
var executeContextStack = [globalExecuteContext];//2.执行one,创建one执行上下文
var oneExecuteContextVO = {
    a: 1,
    two: `()=>{var b = 2 ;}`
}
var oneExecuteContext = {
    VO: oneExecuteContextVO,
    scopeChain: [oneExecuteContextVO, globalExecuteContext.VO]
}
//2.执行two,创建two执行上下文
var twoExecuteContextVO = {
    b: 2,
    three: `()=>{var c = 3 ;}`
}
var twoExecuteContext = {
VO: twoExecuteContextVO,
    scopeChain: [twoExecuteContextVO, oneExecuteContext.VO, globalExecuteContext.VO]
}
//3.执行three,创建three执行上下文
var threeExecuteContextVO = {
    c: 3
}
var threeExecuteContext = {
VO: threeExecuteContextVO,
    scopeChain: [threeExecuteContextVO, twoExecuteContext.VO, oneExecuteContext.VO, globalExecuteContext.VO]
}
function getValue(varName) {
       for (let i = 0; i < threeExecuteContext.scopeChain.length; i++) {
             if (varName in threeExecuteContext.scopeChain[i]) {
            return threeExecuteContext.scopeChain[i][varName];
          }
     }
}
//console.log(a, b, c);
console.log(
getValue('a'),
getValue('b'),
getValue('c'),

)

4.2.2 作用域链

  • scopeChain 其实是在创建函数的时候确定的
function one() {
     var a = 1;
     function two() {
        console.log(a);
     }
     return two;
}
var a = 2;
var two = one();
two();
// 1.创建全局上下文
var globalExecuteContextVO = { one: `()=>{var a = 1;}`, a: undefined, two: undefined }
var globalExecuteContext = {
    VO: globalExecuteContextVO,
    scopeChain: [globalExecuteContextVO]
}
//2.开始执行
globalExecuteContextVO.a = 2;
//3.开始执行onevar oneExecuteContextVO = { a: undefined, two: `()=>{console.log(a)}` }
var oneExecuteContext = {
    VO: oneExecuteContextVO,
    scopeChain: [oneExecuteContextVO, globalExecuteContextVO]
}
oneExecuteContextVO.a = 1;
//4.给two赋值
globalExecuteContextVO.two = oneExecuteContextVO.two;
//5.执行two
var twoExecuteContextVO = {}
var twoExecuteContext = {
    VO: twoExecuteContextVO,
    //scopeChain是在创建此函数据的时候就决定了,跟在哪里执行无关
    scopeChain: [twoExecuteContextVO, oneExecuteContextVO, globalExecuteContextVO]

}

5. 闭包

  • 闭包有两部分组成,一个是当前的执行上下文A,一个是在该执行上下文中创建的函数B
  • 当B执行的时候引用了当前执行上下文A中的变量就会产出闭包
  • 当一个值失去引用的时候就会会标记,被垃圾收集回收机回收并释放空间
  • 闭包的本质就是在函数外部保持内部变量的引用,从而阻止垃圾回收
  • 调用栈的并不会影响作用域链,函数调用栈是在执行时才确定,而作用域规则是在代码编译阶段就已经确定了
  • MDN定义:闭包是指这样的作用域foo ,它包含了一个函数fn ,这个函数fn1可以调用被这个作用域所封闭的变量a 、函数等内容

5.1 闭包

  • Call Stack 为当前的函数调用栈
  • Scope 为当前正在被执行函数的作用域链
  • Local 为当前的活动对象
function one() {
      var a = 1;
      var b = 2;
      function two() {
           var c = 3;
           debugger;
           console.log(a,c);
     }
    return two;
  }
let two = one();
two();
function one() {
      var a = 1;
      var b = 2;
      function two() {
         debugger;
         console.log(a);
         }
    two();
 }
one();

image.png

5.2 闭包优化

  • 中间没用到的变量闭包会被忽略
function one() {
     var a = 1;
     function two() {
         var b = 2;
         function three() {
             var c = 3;
             debugger;
             console.log(a, b, c);
          }
         three();
      }
   two();
 }
one();
function one() {
     var a = 1;
     function two() {
         var b = 2;
         function three() {
              var c = 3;
              debugger;
              console.log(a, c);
         }
        three();
     }
    two();
}
one();

5.3 arguments

function log(a, b) {
      debugger;
      console.log(a, b);
}
log(1, 2);

image.png

结尾

好啦,这期的前端丛林大冒险先到这里啦!这期的猎物有点多,而且不好对付,不过总算啃了下来。希望大家可以好好品尝并消化,迅速升级,接下来我们才更好地过五关斩六将,我们下期再见。拜拜!