JS预解析/预编译、var和function变量提升;全局对象和活动对象、全局变量和局部变量、全局函数和局部函数;作用域和作用域链;

150 篇文章 2 订阅
36 篇文章 0 订阅

目录

1、什么是预编译

2、全局对象(GO对象)、全局变量、全局函数

3、活动对象(AO对象)、局部变量、局部函数 

4、JS预解析的过程

5、关于JS预解析的题目

6、JS作用域和作用域链

7、关于JS作用域的案例和解析


1、什么是预编译

JavaScript是解释性语言,解释性是指逐行解析、逐行执行。

在JavaScript真正被解析之前,JS解析引擎会把整个文件进行预处理,以消除一些歧义。这个预处理的过程就称为预编译。

2、全局对象(GO对象)、全局变量、全局函数

什么是全局对象? 

在浏览器环境中,JS引擎整合所有 script 标签中的内容,产生了统一的 Window 对象。这个对象就是全局对象(GO对象)。 

全局对象的作用? 

在 JavaScript 中,全局变量是挂载在 window 对象下的变量,所以在网页中的任何位置都可以使用并且访问到这个全局变量。

举例: 

globalName 变量在任何地方都是可以被访问到的,所以它就是全局变量。而在 getName 函数中作为局部变量的 name 变量是不具备这种能力的。 

var globalName = 'global';
function getName() { 
  console.log(globalName) // global
  var name = 'inner'
  console.log(name) // inner
} 
getName();
console.log(name); 
console.log(globalName); //global

注意:

在 JavaScript 中,所有没有经过定义而直接被赋值的变量默认就是一个全局变量比如下面代码中 setName 函数里面的 vName: 

function setName(){ 
  vName = 'haha';
}
setName();
console.log(vName); // haha
console.log(window.vName) // haha

缺点:

当定义很多全局变量时,可能会引起变量命名的冲突,所以在定义变量时应该注意作用域的问题。

 

3、活动对象(AO对象)、局部变量、局部函数 

什么是局部对象/活动对象? 

活动对象:也就激活对象(AO对象)。在函数被调用时产生,用来保存当前函数内部的执行环境,也叫执行期上下文。在函数调用结束时销毁

在函数内部声明的变量,叫局部变量

局部变量作为 AO 对象的属性存在。局部变量只在函数作用域内有效

在函数内部声明的函数,叫局部函数

部函数作为 AO 对象的方法存在。局部函数只在函数作用域内有效。

function a() {
  var b = 1
  console.log(b) // 1
}
a()
console.log(b) // b is not defined

这里的b是作为活动对象中的属性存在,也就是说 b 是局部变量,只在函数 a 中有效。

除了这个函数内部,其他地方都是不能访问到它的。同时,当这个函数被执行完之后,这个局部变量也相应会被销毁。所以会看到在 a 函数外面的 b 是访问不到的。

4、JS预解析的过程

① 浏览器什么时候进入作用域?

  • 当看到script标签的时候   
  • 调用方法的时候

② 进入作用域之后,进行什么操作?

  •  首先进行 js 的预解析:搜寻预解析关键字,执行预解析。
  •  接着对 js 逐行执行:找有没有表达式,如果有的话就把js预解析里面的内容进行修改。

③ js预解析的过程:

1)全局预编译

查找变量声明(通过关键字var声明的变量),作为GO对象的属性名,值为undefined;

var a // 变量声明

var a = 111 // 变量声明+变量赋值

查找函数声明(通过关键字function声明的函数),作为GO对象的属性名,值为function;

function a() {} // 函数声明

var a = function() {} // 函数表达式,不是函数声明

例子解析: 

2)局部/函数预编译

在函数被调用时,为当前函数产生AO对象;

查找形参和变量声明作为AO对象的属性名,值为undefined;

使用实参的值改变形参的值;

查找函数声明,作为AO对象的属性名,值为function;

例子1:

例子2:

答案: function    0  undefined

3)局部变量和形参同名,哪个优先级更高?

只要声明了局部函数,函数的优先级最高;

没有声明局部函数,实参的优先级高;

整体来说,局部函数 > 实参 > 形参 

function fn(a) {
  var a;
  console.log(a) // 1
}
fn(1)

4)预解析过程总结:

首先,JS引擎会整合所有script标签中的内容,然后生成全局对象Window对象;

其次,寻找作用域中的变量声明,赋值为undefined,还有函数声明(var 和 function)(匿名函数没有function声明,所以不会提升),赋值为function;

然后,将其声明提升,也就是提升到作用域的最顶端;

赋值操作依然留在原地不会提升,再从上到下执行;

先是全局预编译,然后是函数预编译。这就是预解析的过程。

局部函数的优先级高于实参,实参高于形参

④ 重复声明 var 变量如何编译?

var 关键词对同一个标识符重复使用时,除了第一次有效外,其他均做忽略处理

⑤ 变量提升和函数提升的优先级: 

函数提升优先级高于变量提升;预解析时,先处理变量声明,再处理函数声明;

5、关于JS预解析的题目

① 题目一

var foo;
function foo(){}
console.log(foo);

预解析过程:

var foo; // 提升变量声明的var,赋值为undefined
var foo; // 提升函数声明抽出的var
foo=function (){}; // 函数声明抽出的赋值
console.log(foo); // function foo(){}

② 题目二

function foo(){}
var foo;
console.log(foo);

预解析过程:

var foo; // 提升变量声明的var,赋值为undefined

var foo; // 提升函数声明抽出的var
foo=function (){}; // 函数声明抽出的赋值
console.log(foo); // function foo(){}

③ 题目三

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

 预解析过程:

var a; // 提升变量声明的var,赋值为undefined
var a; // 提升函数声明抽出的var
a=function (){}; // 函数声明抽出的赋值

a=1;
console.log(a); // 1

④ 题目四

var a = 10;
function f1() {
  var b = 2 * a;
  var a = 20;
  var c = a + 1;
  console.log(b)
  console.log(c)
}
f1()

答案:NaN  21 

6、JS作用域和作用域链

① 作用域

在JavaScript中,作用域分为全局作用域和局部作用域。

全局作用域:在<script>标签产生的区域,从计算机的角度可以理解为Window对象;

局部作用域由函数产生的区域,从计算机的角度可以理解为函数的AO对象;

拓展:在ES6之前,只有函数能产生作用域,ES6中有了let之后,就有了块级作用域。

② 作用域链

在JavaScript中,函数存在一个隐式属性 [[ scopes ]]这个属性用来保存当前函数在执行时的环境(上下文),由于在数据结构上是链式的,也称为作用域链。我们可以把它理解为一个数组。

function fn() {}
console.dir(fn) // 打印内部结构

内部结构如下: 

scopes 属性在函数声明时产生,在函数被调用时更新,记录当前函数的执行环境。

在函数被调用时,将该函数的AO对象压入到[[ scopes ]]

梳理作用域链:

function a() {
  console.dir(a)
  function b() {
    console.dir(b)
    function c() {
      console.dir(c)
    }
    c()
  }
  b()
}
a()

调试和打印结果如下:

 

 

③ 作用域链的作用

在访问变量或者函数的时候,会在作用域链上依次查找。先查找自己的AO是否存在变量和函数,不存在的话,再一层一层向上查找,直到找到GO;

外层的函数无法访问内部函数的变量;而内部函数可以访问外部函数的变量。

        

第一个例子的aa打印的是 111

第二个例子的aa打印的是 222

7、关于JS作用域的案例和解析

① 案例1

      function test(num1, num2) {
        console.log(num1)
        console.log(num2)
        var num2 = 234
        console.log(num2)
        num1 = 123
        console.log(num1)
        function num1() {}
        var num1
        num2 = 125
        var num2 = function() {}
        console.log(num1)
        console.log(num2)
      }
      test(1)

解析:

// 执行函数fn前预编译:
// num2: undefined,
// num1: function num1() {} 


function test(num1, num2) {
        console.log(num1) // function num1(){}
        console.log(num2) // undefined
        var num2 = 234
        console.log(num2) // 234
        num1 = 123
        console.log(num1) // 123
        function num1() {}
        var num1
        num2 = 125
        var num2 = function() {}
        console.log(num1) // 123
        console.log(num2) // f(){}
}
test(1)

② 案例2

      function test(a, b) {
        console.log(a)
        a = 3
        console.log(b)
        b = 2

        console.log(b)
        function b() {}
        function d() {}
        console.log(b)
        console.log(d)
      }
      test(1)

解析:

// 预编译:GO -> {test: functiong () {}}
// 解释执行:{test()}

// 执行函数test前预编译:
// a: 1[因为外部实参传入]
// b: function () {}
// d: function () {}

function test (a, b) {
    console.log(a); // 控制台显示1
    a = 3;
    console.log(b); // 控制台显示function b () {}
    b = 2;
    // 解释执行函数test:{ b: 2 }
    console.log(b); // 控制台显示2
    function b () {};
    function d () {};
    console.log(b);  // 控制台显示2,因为b已是变量并被赋值
    
    console.log(d); // 解释执行函数test:{d: function () {}}
}
test(1); 

③ 案例3


      var glob = 100
      function test() {
        console.log(glob)
        glob = 200
        function glob() {}
        console.log(glob)
        var glob
        console.log(glob)
      }
      test()

解析:

// 预编译: 
// glob: undefined
// test: function () {}

var glob = 100; // 解释执行:glob = 100; test()
function test () {
    // 执行函数test前开始预编译
    // glob = function () {}

    console.log(glob); // 控制台显示function glob () {};
    glob = 200
    function glob() {};
    // 解释执行函数test:{glob = 200}
    console.log(glob); // 控制台显示200
    var glob;
    console.log(glob); // 原理同4,控制台显示200
}
test();

④ 案例4

function bar() {
    return foo
    foo = 10
    function foo() {}
    var foo = 11
}
console.log(bar())

解析:

// 预编译:GO -> {bar: function () {}} 
// 解释执行:{bar()} 

// 执行函数bar()前预编译:
// foo: function () {}

function bar () { 
    return foo; // 解释执行函数bar():{foo()} 
    foo = 10; 
    function foo () {} 
    var foo = 11; 
} 
console.log(bar());// 控制台显示function foo () {} 

⑤ 案例5

function Bar() {
    foo = 10
    function foo() {}
    var foo = 11
    return foo
}
console.log(Bar())

解析:

// 预编译:GO -> bar: function () {}
// 解释执行:{bar()} 

// 执行函数bar()前预编译:
// foo: function () {}

function Bar () { 
    foo = 10;  // 解释执行函数bar():{foo: 10}
    function foo () {} 
    var foo = 11; // 解释执行函数bar():{foo: 11} 
    return foo; 
} 
console.log(Bar()); //控制台显示11

⑥ 案例6

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

解析: 

// 预编译:GO -> {fn: function () {}}
// 解释执行:{fn()}

// 执行函数fn前预编译:
// a: function () {},
// b: undefined[因为函数b()为函数表达式]
// d: function () {}

function fn (a) {
      console.log(a); // 控制台显示function a () {}
      var a = 123;
      console.log(a); // 控制台显示123
      function a () {};
      console.log(a); // 控制台显示123
      console.log(b); // 控制台显示undefined
      var b = function () {};
      console.log(b); // 控制台显示function () {}
      console.log(d); // 控制台显示function d () {}
      function d () {};
 }
fn(1); 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值