如何撬开js的大门-从js的预编译开启😶‍🌫️

526 阅读6分钟

浅聊javascript

之前只是顾着写页面写接口,代码的层原理却是半知半解。这导致自己的代码水平很烂,为了早日修成代码大神,我决定好好阅读你《所不知道的javascript》这本书来撬开js的大门从而疗养自己被面试官diss出来的伤痕。 首先谈到的是,任何一门语言我们把代码写出来之后要执行出结果必须要被解释器(比如java里边的JDK),能够把java代码读懂并且运行出来,而js的解析器(以google为例子)——js执行引擎。v8引擎在执行js代码的时候会有一套规则而熟练掌握并且灵活的运用这些规则是我们的学习目标!!也是我修成代码大神的必经之路,js这门编程语言在创造出来的时候,只要把代码写了就一定会存在三个作用域。全局作用域,函数体内作用域,块级作用域({}和let或const可以形成)。先看段小代码试试!

let a={
age:18
}
function test(){


}

函数也是一种特殊的对象我们可以通过浏览器来检查

image.png

由此可见函数也是一种对象,它也有一些显示的属性如name,length。值得注意的函数也有不能访问的属性但是实实在在存在的。比如说[scope]作用域属性是给js引擎用的,它让v8引擎能更好的清楚这个函数里边有哪些有效的标识符,该怎么走,该放到哪里。这并不是我们程序员要遵从的因此不能访问也正常。 当一个函数没被调用的时候v8引擎是不会去执行里边的逻辑不管你写了多少行代码。只有当函数调用的时候v8引擎才会关关心里边写了啥。另外呢当函数被调用的时候v8引擎会去创建函数的上下文对象出来我们称为AO对象(action object),之后呢v8把这个函数体里边的有效标识符全部记录在这个AO对象当中这样就不会出岔子。这个过程就是编译的过程,编译后就是执行函数。之后调用其它的函数也是一样的......(编译运行)

我们再通过一段代码来理解下

function a(){
  function b(){
     var b=22
     console.log(a)
  }
  var a=111
  b()
   
}
var glob=100
a()

作用域链

我们之前聊到函数的执行会创建一个函数的执行上下文对象,全局也是一个作用域。当v8看到这段代码会创建一个G O 对象。然后先整理在全局作用域有哪些有效标识符,不然待会儿就会出现问题。我来分析这段代码吧!!下图作用域链如下!!

image.png

a的作用域能先访问a自己的上下文对象,当访问不到后才会去访问全局上下文对象。并且a的调用带来了b的定义以及b的作用域链,和b的调用。

预编译

代码在执行前需要进行编译操作,用于确定代码之间的各种关联,如果不进行编译会出现很多很多问题报错, 我们看看下边的代码

console.log(a);
var a=1

这段代码打印为undefined按照正常逻辑来讲应该报错但是却没有报错,这正是因为代码会先执行编译,先去整理全局声明了哪些东西,有哪些标识符,console.log()这段代码执行前就已经发现了有变量a,这也就解释了为什么会有变量声明提升这个概念。因为先存在编译这个过程,发现了全局有这个a了。在v8的眼里这段代码就是下面的样子

var a
console.log(a)//声明提升 undefined
a=1

那我们来看看接下来的一段代码

test()
function test(){
var a=123
console.log(a);
}

我们运行后根本不会报错因为对于函数来讲,函数声明会存在整体提升。它的代码如下

function test(){
   var a=123
   console.log(a);
}
test()

先调用再声明也能出结果其实就是因为在编译的过程中整个函数体先被读取到了也就是说函数整体提升了。

我们可以总结一下: 1.变量声明,声明提升。2.函数声明,整体提升。但是仅仅凭借着这两点我们还不足以来完成复杂代码的分析。我们看看如下代码!

var a=1
function fn(a){
    var a=2
    function a(){}
    var b=a
    console.log(a);
}
fn(3)

比如这段代码我们光靠上边的理论是不足以知道到底打印的是哪个,到底是函数a呢还是变量a呢? 接下来上正菜!!预编译的过程!! 当v8预编译发生在全局的时候会先1.创建G O对象2.找变量声明,将变量名作为GO 的属性名,值为undefined 3.在全局找到函数声明,将函数名作为GO属性名,值为该函数体。如果发生在函数体内的时候第一呢1.创建一个AO对象 2.找形参和变量声明,将形参和变量名作为AO的属性名,值为undefined 3.形参和实参统一 4.在函数体内找函数声明,将函数名作为AO的属性名,值为该函数体 让我们来好好分析一下吧!!

var a=1
function fn(a){
    var a=2
    function a(){}
    var b=a
    console.log(a);
}
fn(3)

-----------------
GO :{
  a:undefined 1(执行var a=1)
  fn:function(){}
}
接下来全局编译完后,全局代码执行执行到有函数调用后开始编译执行fn这个函数。(编译总是发生在执行的前一刻)
这时候
来看看fn这个函数的预编译和执行的过程
AO :{
a: undefined 3(形参和实参的统一) function(){}(找函数体值为函数体) 2(执行a=2)
}
从上边可以看出打印出来为2

我们来上一份大菜来看看吧!!

function fn(a){
 console.log(a);
 var a=123
 console.log(a);
 function a(){}
 console.log(a);
 var b=function(){}
 console.log(b);
 function c(){}
 var c=a
 console.log(c);
 }
 fn(1)
}

我们来好好的看看这份代码吧!!让我们彻底掌握!!

第一步创建GO :{
   fn:function(){}
}
然后就是
AO:{
a:undefined 1 function 123(a=123执行)
b:undefined  function(){}
c:undefined  function c(){} 123(c=a)
}

打印结果为[Function :a] 123 123 [Function:b] 123

接下来我们再来分析一段代码来加深印象!!

funtion test(a,b){
console.log(a);
c=0
var c;
a=3
b=2
console.log(b);
function b(){}
console.log(b);
}
test(1)

-------------

AO:{
a:undefined  1 3,
b:undefined   function b(){}  2,
c:undefined   0
} 

最后,我们来看看一段简短的代码分析吧!!

var global=100
function fn(){
console.log(global);
}
fn()


----------
GO:{
global:undefined  100,
fn:function(){},
}
//AO:{


//}

好啦!!今天的分享就到这里啦!!请多多支持关注哈!!