js的预解析和作用域

预解析和作用域

一.概念

浏览器中有一套专门解析JS代码的程序,这个程序称为JS的解析器浏览器运行整个页面文档时,遇到< script > 标签时JS解析器开始解析JS代码

二.JS解析器的工作步骤

1.预解析代码

主要找一些关键字如 var,function,以及函数的参数等,并存储进仓库里面,也就是内存先扫描全局的代码,在函数执行的时候,然后扫描局部的,也就是函数内部的
变量的初始值是undefind

//声明一个a变量,放进仓库scope
console.log(a);//undefined
var a = 1;

函数的初始值就是该函数的代码块(而不是undefined)

console.log(test);//打印函数代码块
//声明一个函数,放进仓库scope
function test() {...}

//控制台打印函数代码块
ƒ test() {
    return 1;
}

当变量和函数重名时,不管顺序谁前谁后,只留下函数的值

console.log(test);//打印函数代码块
function test() {
    return 1;
}
var test=2;

//控制台打印函数代码块
ƒ test() {
    return 1;
}

注意:这里是预解析的时候,函数是一等公民,比变量优先级高
当函数和函数重名的时候,只会留下后面的那个函数,会遵从上下文机制

console.log(test);
function test() {
    return 1;
}

//重名时,预解析只会留下后面这个函数
function test() {
    return 234;
}
var test=2;

//控制台打印函数代码块
ƒ test() {
    return 234;
}

2.逐行执行代码

当预解析完成之后,就开始执行代码,仓库中变量的值,随时都可能发生变化

1. alert(a);// function a(){alert(3);}
2. var a = 1;
3. alert(a);//1
4. function a() { alert(2); }
5. alert(a);//1
6. var a = 3;
7. alert(a);//3
8. function a() { alert(3); }
9. alert(a);//3

/*
解读代码
  预解析过程:	
  		第2行:找到了一个var 关键字,声明一个变量a
  		第4行:找到了一个function关键字 把a变成了一个函数
  		第6行:找到了一个var关键字,但是由于声明变量名字与函数重名,所以不起作用,a的值还是一个函数,因为
  			 函数比变量的优先级高
  	    第8行:找到了一个function关键字,a还是一个函数,但是把第4行的给覆盖了,因为函数重名,会遵从上下
  	         文机制
		
	    最后预解析得到的结果是:
        a => fn  -> function a(){alert(3);}
        
  原因:  1.当变量和函数重名时,不管顺序谁前谁后,只留下函数的值
  	    2.当函数和函数重名的时候,只会留下后面的那个函数,会遵从上下文机制
  	  
  逐行代码执行过程:
		 第1行: 弹出最后的名字叫a的函数代码块
		 alert(a);// function a(){alert(3);}
		 第3行: 因为第2行将a从函数变成了变量,并赋值为1
		 alert(a);//1
		 第5行: 因为第4行只是声明了一个函数,不会自己执行,所以a的值保持不变
		 alert(a);//1
		 第7行:因为第6行将a的值变成了3
		 alert(a);//3
		 第9行:因为第8行只是声明一函数,不会自己执行,所以a的值保持不变
*/

3. 示例解读

像这种示例,如果一眼无法看出来的话,使用解析器原理去看待题目,就没有解析不出来的,如果熟了,基本上就一眼就可以看出来,因为一步一步解析太麻烦了.
3.1 示例1(全局变量和函数内部变量名相同)

1.var a = 1;
2.function test(x) {
3.    alert(x);
4.    alert(a);
5.    var a = 2;
6.    alert(a);
7. }
8. test();

/*
  解读代码
  预解析过程:扫描代码,寻找var,function,以及函数的参数,放进仓库
  		   先扫描全局的var function
 		   global scope=>{
 		  		a => undefined
 		  		text =>function test(){} 
 		   }
   逐行代码执行过程:
   		 	第1行,a赋值为1
   		 	第2行,只是声明函数,不会执行
   		 	第8行,调用函数,开始进行局部扫描,也就是函数内部进行预解析
   		 	test scope=>{
   		 		x => undefined
   		 		a => undefined
   		 	}
   		 	test()逐行执行函数内部的代码:
   		 	第3行:alert(x)=> undefined
   		 	第4行:alert(a)=> undefined
   		 	第5行,a赋值为2
   		 	第6行:alert(a)=> 2
*/
12345678910111213141516171819202122232425262728293031
3.2 示例2 (变量和函数重名)
当变量和函数重名时,不管顺序谁前谁后,只留下函数的值
1.alert(typeof fn);//function
2.var fn = 10;
3.function fn() { };
4.alert(typeof fn);//number

/*
解析代码
  预解析:
      global  scope=>{
          fn=> function fn() { };
      }
      
   执行代码:
      1.alert(typeof fn); 会弹出function
      2.赋值fn=10,此时fn类型变成了number
      3.不会执行
      4.alert(typeof fn); //number
*/

3.3 示例3(函数内部使用全局变量)

1.var a = 1;
2.function fn() {
3.    alert(a);//1
4.    a = 2;
5.}
6.fn();
7.alert(a);//2

/*
解析代码
  预解析:
      global  scope=>{
      	  a=>undefined
          fn=> function fn() { };
          
          fn scope=>{
          	 a=>undefined
          }
      }
      
   执行代码:
      1.a赋值等于1
      2.不会执行
      6.执行函数,函数没有参数,也没有var关键字进行声明,不会预解析
      3.第3行:alert(a) 访问的是全局变量 a  弹出1
      4.第4行:将全局变量a 赋值为了2
      7.第7行:弹出2
*/

3.4 示例4(局部变量不会改变全局变量)

1.var a = 1;
2.function fn(a) {
3.    alert(a);//undefined
4.    a = 2;
5.    alert(a);//2
6.}
7.fn();
8.alert(a);//1

/*
解析代码
    预解析:
    	global scope=>{
    		a=> undefined
    		fn=>function fn(a) {...}
    		
    		fn scope=>{
    		   a=>undefined//预解析形参
    		}
    		
    	}
     执行代码:
        先调用fn函数 fn函数内部弹出a为undefined
        函数内部 a赋值为2
        函数内部 执行第二个alert(a) 为2
        
        函数执行完之后,继续执行下一步 alert(a) 为1因为函数函数内部虽然给了a=2
        但是只是改的函数内部的值,不会改全局变量 的a
        
*/

3.5 示例5

console.log(num);// undefined
var num = 24;
console.log(num);// 24
func(100, 200);
function func(num1, num2) {
    var total = num1 + num2;
    console.log(total);// 300
}
12345678
很自然的一段代码,不要受前面的影响,而不知道最最简单的代码了
3.6 示例6.(同名函数)
fn();//2
function fn() { console.log(1); }
fn();//2
var fn = 10;
fn();//报错 fn is not a function
function fn() { console.log(2); }
fn();

/*
解析代码
   预解析:
   global scope=>{
   	  fn =>function fn() { console.log(2); }
   }
   
   同名函数时,后面覆盖前面
   变量和函数同名时,函数是一等公民,优先级高
   
   逐行执行代码时:
   第4行:将fn变成了一个number类型,再执行fn()时,函数不存在了,会直接报错,停止执行
*/

三.声明提升机制

在 JavaScrip 中变量声明和函数声明,声明会被提升到当前作用域的顶部。
讲声明提升机制之前,要先说说JavaScript中的作用域,这里排除ES6
1.作用域
在Javascript中,作用域为可访问变量(包含对象和函数)的集合
也就是说:作用域就是起作用的范围
1.全局作用域
整个页面起作用,在script内部都能访问到全局作用域中有全局对象window,代表一个浏览器窗口,可以直接调用全局作用域中声明的变量和函数,会作为window对象的属性和方法保存变量在所有函数外声明,也就是全局变量,拥有全局作用域

    var a = 123;//全局变量
    function fn() {
        console.log(a);//123
    }
    fn();
    console.log(a);//123

在JavaScript中,函数是唯一拥有自身作用域的代码块
2.局部作用域
局部作用域内的变量只能在函数内部使用,所以也叫函数作用域变量在函数内声明,即为局部变量,拥有局部作用域

    function fn() {
    	var a = 123;//全局变量
        console.log(a);//123
    }
    fn();
    console.log(a);//报错:Uncaught ReferenceError: a is not defined

注意:
可以直接给一个未声明的变量赋值(全局变量),但不能直接使用未声明的变量!
由于局部变量只作用于函数内部,所以不同的函数内部可以相同名称的变量
当全局与局部有同名变量的时候,访问该变量将遵循"就近原则"
2.变量的生命周期
全局变量在页面打开时创建,在页面关闭后销毁局部变量在函数开始执行时创建,函数执行完之后局部变量自动销毁
3.声明提升机制
JavaScript 的变量声明具有声明提升机制,JavaScript引擎在执行的时候,会把所有变量的声明都提升到当前作用域的顶部
我们可以从JavaScript解析器的执行原理来理解声明提升机制,就很好理解了,同样,上述解析器执行的示例代码中,都是声明提升机制的一种体现.当然了,这里是不考虑ES6的.
总结一下:
javascript是没有块级作用域的,函数是javascript中唯一拥有自身作用域的结构
声明变量,实际上就是定义了一个名字,在内存中开辟了存储空间,并且初始为undefined,提升到当前作用域顶部
函数的参数是原始类型的值(数值,字符串,布尔值),传值方式是传值传递,也就是在函数体内修改参数值,不会影响到函数外部

var a = 1;
function fn(a) {
    console.log(a);//3
}
fn(3);
console.log(a);//1 不会改变原始值

函数的参数是复合类型,也就是引用类型(数组,对象,函数)的时候,传值方式是传址传递,传入的是原始值的地址,所以在函数内部修改参数,会改变原始的值(可参考深浅拷贝原理)

var obj = { a: 1, b: 2 };
function fn(obj) {
    obj.a=2;
}
fn(obj);
console.log(obj);//{a: 2, b: 2}

注意:如果函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值!
比如数组:

var arr = [1, 2, 3]
function fn(array) 
    array = [1, 2];
}
fn(arr); 
console.log(arr);//[1, 2, 3]

4.声明提升机制的总结
1.var a 变量声明,提升到作用域顶部,
2.var a=1; 变量声明,提升到作用域顶部,但是赋值部分不会被提升
3.var a=function(){ … } 函数表达式,但也只是变量的声明提升,并不会提升函数值
function a() {…} 函数声明,会全部提升,而且如果函数名字和变量名字相同的话.优先级比变量高,因为是一等公民

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值