JavaScript是解释型语言是毋庸置疑的,但它是不是仅在运行时自上往下一句一句地解析的呢?
事实上或某种现象证明并不是这样的,通过《JavaScript权威指南》及网上相关资料了解到,JavaScript有“预解析”行为。理解这一特性是很重要的,不然在实际开发中你可能会遇到很多无从解析的问题,甚至导致程序bug的存在。为了解析这一现象,也作为自己的一次学习总结,本文逐步引导你来认识JavaScript“预解析”,如果我的见解有误,还望指正。
在ES6之前,变量使用var声明,会存在变量的预解析(函数也有预解析),我相信很多同学在刚开始学JavaScript的时候被预解析搞得团团转,虽然在ES6的时候引入let和const,但是现阶段ES6并没有完全普及,而且很多比较老的代码都还是按照ES5的标准甚至是ES3的标准来书写的。
一、变量和函数在内存中的展示
JavaScript中的变量类型和其他语言一样,有基本数据类型和引用数据类型。基本数据类型包括:undefined、null、boolean、String、Number;引用数据类型主要是对象(包括{}、[]、/^$/、Date、Function等)。
var num = 24; var obj = {name:'iceman' , age:24}; function func() { console.log('hello world'); }
当浏览器加载html页面的时候,首先会提供一个供全局JavaScript代码执行的环境,称之为全局作用域。
基本数据类型按照值来操作,引用数据类型按照地址来操作。
根据以上原则,以上的代码在内存中的模型为:
内存模型.png
基本类型是直接存储在栈内存中,而对象是存储在堆内存中,变量只是持有该对象的地址。所以obj持有一个对象的地址oxff44,函数func持有一个地址oxff66。
在以上的代码的基础上再执行:
console.log(func); console.log(func());
第一行输出的是整个函数的定义部分(函数本身):
第一行代码输出结果.png
上面已经说明了,func存储的是一个地址,该地址指向一块堆内存,该堆内存就保留了函数的定义。
第二行输出的是func函数的返回结果:
第二行代码输出结果.png
由于func函数没有返回值,所以输出undefined。
注意:函数的返回结果,return后面写的是什么,返回值就是什么,如果没有return,默认返回值是undefined。
二、预解析
有了以上的内存模型的理解之后,就能更好的了解预解析的机制了。所谓的预解析就是:在当前作用域中,JavaScript代码执行之前,浏览器首先会默认的把所有带var和function声明的变量进行提前的声明或者定义。
2.1. 声明和定义
var num = 24;
这行简单的代码其实是两个步骤:声明和定义。
声明:var num; 告诉浏览器在全局作用域中有一个num变量了,如果一个变量只是声明了,但是没有赋值,默认值是undefined。
定义:num = 12; 定义就是给变量进行赋值。
2.2. var声明的变量和function声明的函数在预解析的区别
var声明的变量和function声明的函数在预解析的时候有区别,var声明的变量在预解析的时候只是提前的声明,function声明的函数在预解析的时候会提前声明并且会同时定义。也就是说var声明的变量和function声明的函数的区别是在声明的同时有没同时进行定义。
2.3. 预解析只发生在当前的作用域下
程序最开始的时候,只对window下的变量和函数进行预解析,只有函数执行的时候才会对函数中的变量很函数进行预解析。
console.log(num); var num = 24; console.log(num); func(100 , 200); function func(num1 , num2) { var total = num1 + num2; console.log(total); }
输出结果.png
第一次输出num的时候,由于预解析的原因,只声明了还没有定义,所以会输出undefined;第二次输出num的时候,已经定义了,所以输出24。
由于函数的声明和定义是同时进行的,所以func()虽然是在func函数定义声明处之前调用的,但是依然可以正常的调用,会正常输出300。
内存模型.png
三、 作用域链
先理解以下三个概念:
函数里面的作用域成为私有作用域,window所在的作用域称为全局作用域;
在全局作用域下声明的变量是全局变量;
在“私有作用域中声明的变量”和“函数的形参”都是私有变量;