JavaScript 如何理解预解析,作用域,函数变量的调用赋值以及递归函数

JavaScript 预解析/预解释

JavaScript 是弱类型解释型脚本语言
JavaScript 是解释型计算机语言,在程序执行之前,会先将定义的程序,预先解释,也就是预先模拟执行一遍,会找程序中的关键词 var function 两个关键词。

实例

// 先输出

// 如果是没有使用 var 关键词声明的变量,执行结果是会报错 
// 如果使用 var 关键词声明的变量,执行结果是 undefined 
// 如果是报错,之后的所有程序,都不会执行
// 如果是 undefined 之后程序还可以继续执行
// 实际项目中,会有这样提前使用变量的操作
// 如果不是使用 var 声明的变量,会报错,整个程序就无法进行了
console.log(int);

// 后定义变量
var int = 100;

console.log('我是之后的其他程序');

fun();

function fun(){
    console.log(123);
}

这个效果,也就是使用 var 定义的变量,可以先调用,结果是undefined,不会报错;使用 function 声明的函数,可以先调用,后执行,能正常调用函数,都是JavaScript程序预解释/预解析的结果。

预解析原理

预先读取JavaScript中的所有程序内容,找到所有 var 和 function 关键词,来进行预解释和预解析,告诉计算机程序,有哪些变量是使用 var 关键词声明。
如果提前使用 var 声明的变量,告诉计算机程序,这个变量已经存在,只是当前没有赋值,执行结果是undefined,不会报错;如果是使用 function 声明的函数,告诉计算机程序,这个函数已经存在,并且告诉计算机这个函数的内存地址,可以正常调用使用这个函数。

实例

// 提前调用函数和变量
console.log(a,b);
console.log(c,d);
fun1();
fun2();
fun3();

// 定义函数和变量
var a = 100;
var b = 200;
c = 300;
d = 400;
function fun1(){}
function fun2(){}
var fun3 = function(){};

// 正常调用
console.log(a,b);
console.log(c,d);
fun1();
fun2();
fun3();

原理分析

1. JavaScript会预读所有的程序代码
2. 获取所有通过 var 和 function 定义的变量和函数:
 		var 定义的变量有 a,b,fun3;
 		function 定义的函数有 fun1,fun2。
   提前给变量 a,b,fun3 中存储信息 undefined;
   提前给变量 fun1,fun2 中存储的是 内存地址。
以上步骤是预读和存储信息的步骤。
3. 执行代码时:
提前调用变量:a,b 此时记录的信息中有a,b 执行结果是 undefined
 	   	    c,d 没有在记录信息中	      执行结果是 报错
提前调用函数:fun1,fun2时,都会读取其中提前存储的内存地址,正确调用函数。
   	        fun3 时,执行的是 undefined,不是内存地址,无法调用函数。
以上是提前调用函数变量,执行的预解析的过程。
执行赋值之后,var 声明的变量,会重新赋值,覆盖之前的 undefined
 	        function 声明的函数,会重新赋值,仍然是内存地址
特别强调

对预解析所谓的变量提升理解,是不准确的。
错误说法:
将 var,function声明的变量和函数,在程序的最开始重新定义,提升变量、函数和定位的位置。
在JavaScript的源码中是没有这样的操作的。

预解析/预解释的无下限操作

只要是写在程序中的 var 和 function关键词,都会被预解析,不管程序会不会被执行,都会执行预解析。
如果是不执行的程序, var 会进行预解析,如果提前使用,结果是 undefined 。
如果是函数, function 会预解析,但是不执行的函数是不能调用的。
函数不能调用的原因:预解析会执行,也会存储内存地址,但是程序不执行,这个内存地址中就没有内容,执行结果会报错。

实例

console.log(int);
fun();              //  结果会报错

if(false){
   // 当if判断,条件执行结果是true时,才会执行程序
   // 如果条件执行结果是false,var int = 100 是不会执行的
   var int = 100; 

   function fun(){
       console.log(123);
   }
}

console.log(int);
fun();

JavaScript 作用域

变量的作用域

所谓的变量的作用域,就是变量的使用范围,每一个变量都有自己的使用范围。

变量的作用域分为两种

1. 定义在函数之内的变量

包括函数的参数,定义在函数内部的变量,都称为局部作用域变量 / 局部变量
局部变量只能在函数内容部使用,如果外部想要直接调用,是不行的。

实例
// 此时函数中定义的变量,参数a和变量b,都是局部作用域变量
// 只能在函数内部被调用使用
// 函数外部不能直接使用调用
// 只要是在函数外部,调用函数内部的变量,都是报错
function fun1(a){
    var b = 100;
    console.log(a,b);  // 函数内部调用
}
fun1('北京');
console.log(a,b);   // 函数外部调用,报错
2. 定义在函数之外的变量

定义在函数外部的变量,称为全局作用域变量/全局变量
在函数的内部是可以使用全局作用域变量的,但是是有条件的,具体情况看函数变量的调用赋值原则。

实例
var int = 100;
function fun2(){
    console.log(int);
}

fun2();

函数变量的调用赋值原则

调用变量的原则

会先在当前作用域中,找是否有这个变量。
如果有,就直接使用这个变量;如果没有,去父级作用域寻找这个变量。
如果父级作用域有这个变量,就使用这个变量;如果父级作用域没有这个变量,再向上一层寻找,如果找到这个变量,就直接使用。
如果所有的作用域都没有这个变量,执行结果是报错。
只会向父级作用域找变量,不会向子级作用域找变量。
函数之外的调用,只能调用全局作用域的变量。如果全局作用域没有该变量,则会调用函数内部赋值语句升级定义的全局变量。

赋值变量的原则

会先在当前作用域中,找是否有这个变量。
如果有,就直接对这个变量赋值;如果没有,去父级作用域寻找这个变量。
如果父级作用域有这个变量,就对这个变量赋值;如果父级作用域没有这个变量,再向上一层寻找,如果找到这个变量,就直接进行赋值。
如果所有的作用域都没有这个变量,赋值语句会升级为定义变量语句,并且定义的是全局作用域变量。
只会向父级作用域找变量,不会向子级作用域找变量

实例

赋值和调用情况1
// 只有局部变量int,没有全局变量int
function fun(){
    var int = 100;
    int = 200;             // 赋值的是局部int
    console.log(int);      // 调用的是局部int 200
}
fun();
console.log(int);         // 调用全局int,不存在,报错

赋值和调用情况2
// 只有全局变量int,没有局部变量int
var int = '北京';
function fun(){
    int = 200;             // 赋值的是全局int
    console.log(int);      // 调用的是全局int 200
}
fun();
console.log(int);         // 调用全局int 200

赋值和调用情况3
// 全局、局部作用域都有变量int
var int = '北京';
function fun(){
    var int = 100;
    int = 200;             // 赋值的是局部int
    console.log(int);      // 调用的是局部int 200
}
fun();
console.log(int);         // 调用全局int '北京'

赋值和调用情况4
// 全局、局部作用域都没有变量int
function fun(){
    int = 200;             // 赋值语句升级为定义变量语句,定义int并且赋值200,int还是一个全局变量,函数外部能调用
    console.log(int);      // 调用的是函数内的int 200,但是是全局变量
}
fun();
console.log(int);         // 调用的是函数内部,升级为全局变量的int 200

递归函数

定义

递归函数是一种特殊的函数,就是在函数内部,调用函数自己本身。
实际当中,我们基本上用不到递归函数,只是作为概念,了解掌握几个相应的demo就可以了。

语法

function fun1(){
    // 在函数fun1中调用函数fun1自己本身
    fun1()
}

实例

// 倒序输出
for(var i = 5 ; i >= 1 ; i--){
    console.log(i);
}           // 输出结果是 5,4,3,2,1

// 递归方式输出
function fun(num){
    num--;
    if(num >=1){
        fun(num);
    }
    // 将执行的输出,写在递归调用之下
    console.log(num);          // 输出结果是 0,1,2,3,4
}
fun(5);

递归原理分析:

第一次调用函数,实参赋值是5
    执行程序num-- 执行结果是4,判断true,调用函数本身,此时先调用函数,先不输出
    
    第二次调用函数,实参赋值是4
    	执行程序num-- 执行结果是3,判断true,调用函数本身,此时先调用函数,先不输出
    	
    	第三次调用函数,实参赋值是3
    	    执行程序num-- 执行结果是2,判断true,调用函数本身,此时先调用函数,先不输出
    	    
    	    第四次调用函数,实参赋值是2
    	    	执行程序num-- 执行结果是1,判断true,调用函数本身,此时先调用函数,先不输出
    	    	
    	    	第五次调用函数,实参赋值是1
    	    	    执行程序num-- 执行结果是0,判断false,不再执行调用函数,执行 console.log,输出程序,此时,输出的数值是0
    	    	    执行完输出,当前函数的执行完毕,继续执行之前的输出。
    	    	    
    	    	执行 console.log,输出程序,此时,输出的数值是1
    	    	执行完输出,当前函数的执行完毕,继续执行之前的输出。
    	    	
    	    执行 console.log,输出程序,此时,输出的数值是2
    	    执行完输出,当前函数的执行完毕,继续执行之前的输出。
    	    
    	执行 console.log,输出程序,此时,输出的数值是3
    	执行完输出,当前函数的执行完毕,继续执行之前的输出。
    	
    执行 console.log,输出程序,此时,输出的数值是4
    执行完输出,当前函数的执行完毕,所有的函数执行都完毕,整个递归结束。

递归总结

1. 调用递归函数,一定要有条件,无条件的调用递归函数,会一直执行调用,不会停止。
2. 调用递归函数,进入递归函数,会由外至内执行,结束递归函数,会从内至外执行。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值