js的预解析
在ES6之前,变量使用var
声明,会存在变量的预解析(函数也有预解析)。ES6引了let
和const
,但是现阶段ES6并没有完全普及,而且很多比较老的代码都还是按照ES5的标准甚至是ES3的标准来书写的,要想得心应手的维护之前的代码个人感觉还是很有必要了解下js的预解析机制。
一、变量和函数在内存中的存在形式
JavaScript中的变量类型和其他语言一样,有基本数据类型和引用数据类型。基本数据类型包括:undefined
、null
、boolean
、String
、Number
;引用数据类型主要是对象(包括{}、[]、/^$/、Date、Function等)。
var num = 24;
var obj = {name:'iceman' , age:24};
function func() {
console.log('hello world');
}
以上的代码在内存中的模型为:
- 基本数据类型按照值来操作,引用数据类型按照地址来操作。
- 基本类型在栈区:
引用数据类型:
基本类型是直接存储在栈内存中,而对象是存储在堆内存中,变量只是持有该对象的地址。所以obj持有一个对象的地址oxff44,函数func持有一个地址oxff66。
在以上的代码的基础上再执行:
console.log(func);
console.log(func());
第一行输出的是整个函数的定义部分(函数本身):
上面已经说明了,func存储的是一个地址,该地址指向一块堆内存,该堆内存就保留了函数的定义。
第二行输出的是func函数的返回结果:
由于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);
}
打印结果:
第一次输出num的时候,由于预解析的原因,只声明了还没有定义,所以会输出undefined;第二次输出num的时候,已经定义了,所以输出24。
由于函数的声明和定义是同时进行的,所以func()虽然是在func函数定义声明处之前调用的,但是依然可以正常的调用,会正常输出300。
三、 作用域链
先理解以下三个概念:
- 函数里面的作用域成为私有作用域,window所在的作用域称为全局作用域;
- 在全局作用域下声明的变量是全局变量;
- 在“私有作用域中声明的变量”和“函数的形参”都是私有变量;
在私有作用域中,代码执行的时候,遇到了一个变量,首先需要确定它是否为私有变量,如果是私有变量,那么和外面的任何东西都没有关系,如果不是私有的,则往当前作用域的上级作用域进行查找,如果上级作用域也没有则继续查找,一直查找到window为止,这就是作用域链。
当函数执行的时候,首先会形成一个新的私有作用域,然后按照如下的步骤执行:
- 如果有形参,先给形参赋值;
- 进行私有作用域中的预解析;
- 私有作用域中的代码从上到下执行
函数形成一个新的私有的作用域,保护了里面的私有变量不受外界的干扰(外面修改不了私有的,私有的也修改不了外面的),这也就是闭包的概念。
console.log(total);
var total = 0;
function func(num1, num2) {
console.log(total);
var total = num1 + num2;
console.log(total);
}
func(100 , 200);
console.log(total);
以上代码执行的时候,第一次输出total的时候会输出undefined(因为预解析),当执行func(100,200)的时候,会执行函数体里的内容,此时func函数会形成一个新的私有作用域,按照之前描述的步骤:
- 先给形参num1、num2赋值,分别为100、200;
- func中的代码进行预解析;
- 执行func中的代码
因为在func函数内进行了预解析,所以func函数里面的total变量会被预解析,在函数内第一次输出total的时候,会输出undefined,接着为total赋值了,第二次输出total的时候就输出300。 因为函数体内有var声明的变量total,函数体内的输出total并不是全局作用域中的total。
最后一次输出total的时候,输出0,这里输出的是全局作用域中的total。
console.log(total);
var total = 0;
function func(num1, num2) {
console.log(total);
total = num1 + num2;
console.log(total);
}
func(100 , 200);
console.log(total);
将代码作小小的变形之后,func函数体内的total并没有使用var
声明,所以total不是私有的,会到全局作用域中寻找total,也就说说这里出现的所有total其实都是全局作用域下的。
四、 全局作用域下带var和不带var的区别
在全局作用域中声明变量带var可以进行预解析,所以在赋值的前面执行不会报错;声明变量的时候不带var的时候,不能进行预解析,所以在赋值的前面执行会报错。
console.log(num1);
var num1 = 12;
console.log(num2);
num2 = 12;
输出结果:
- num2 = 12; 相当于给window增加了一个num2的属性名,属性值是12;
- var num1 = 12; 相当于给全局作用域增加了一个全局变量num1,但是不仅如此,它也相当于给window增加了一个属性名num,属性值是12;
问题:在私有作用域中出现一个变量,不是私有的,则往上级作用域进行查找,上级没有则继续向上查找,一直找到window为止,如果window也没有呢?
- 获取值:console.log(total); --> 报错 Uncaught ReferenceError: total is not defined
- 设置值:total= 100; --> 相当于给window增加了一个属性名total,属性值是100
function fn() {
// console.log(total); // Uncaught ReferenceError: total is not defined
total = 100;
}
fn();
console.log(total);
注意:JS中,如果在不进行任何特殊处理的情况下,上面的代码报错,下面的代码都不再执行了
五、 预解析中的一些变态机制
5.1 不管条件是否成立,都要把带var的进行提前的声明
if (!('num' in window)) {
var num = 12;
}
console.log(num); // undefined
JavaScript进行预解析的时候,会忽略所有if条件,因为在ES6之前并没有块级作用域的概念。本例中会先将num预解析,而预解析会将该变量添加到window中,作为window的一个属性。那么 'num' in window 就返回true,取反之后为false,这时代码执行不会进入if块里面,num也就没有被赋值,最后console.log(num)输出为undefined。
5.2 只预解析“=”左边的,右边的是指针,不参与预解析
fn(); // -> undefined(); // Uncaught TypeError: fn is not a function
var fn = function () {
console.log('ok');
}
fn(); -> 'ok'
function fn() {
console.log('ok');
}
fn(); -> 'ok'
建议:声明变量的时候尽量使用var fn = ...的方式。
5.3 自执行函数:定义和执行一起完成
(function (num) {
console.log(num);
})(100);
自执行函数定义的那个function在全局作用域下不进行预解析,当代码执行到这个位置的时候,定义和执行一起完成了。
补充:其他定义自执行函数的方式
~ function (num) {}(100)
+ function (num) {}(100)
- function (num) {}(100)
! function (num) {}(100)
5.4 return下的代码依然会进行预解析
function fn() {
console.log(num); // -> undefined
return function () {
};
var num = 100;
}
fn();
函数体中return后面的代码,虽然不再执行了,但是需要进行预解析,return中的代码,都是我们的返回值,所以不进行预解析。
5.5 名字已经声明过了,不需要重新的声明,但是需要重新的赋值
var fn = 13;
function fn() {
console.log('ok');
}
fn(); // Uncaught TypeError: fn is not a function
经典题目
fn(); // -> 2
function fn() {console.log(1);} (预解析1)
fn(); // -> 2
var fn = 10; // -> fn = 10 (预解析2)
fn(); // -> 10() Uncaught TypeError: fn is not a function
function fn() {console.log(2);} (预解析2)
fn();//不执行
//分析以上代码:
1、首先 预解析的时候(预解析1)fn作为函数声明定义->(预解析2)fn前面已经声明所以此次不再声明->
(预解析3)fn作为函数重新定义赋值地址(因为前面已经声明所以直接定义)。 2、然后 执行的时候前面两个fn()直接调用重新赋值的
function fn() {console.log(2);}
当遇到var fn= 10;时再次对fn进行赋值此时fn为Number类型,所以执行下一条语句的时候就会报错fn不是一个函数,此时代码停止运行,后面代码不再运行。
本文通过链接:http://www.jianshu.com/p/c3276ff58c93,按照个人理解进行了修改编辑,补充了部分没有解释清楚的地方,如果不对之处,望不吝赐教
js的预解析的更多相关文章
- 两个实例轻松理解js函数预解析
js函数预解析 例子1: 先上一段代码,看看能不能写出最终的执行结果. console.log(a); var a = 1; console.log(a); function a(){ console ...
- 怎样理解JS的预解析机制
JS的预解析包括两部分: 1. 变量提升 2. 函数声明 对于变量提升, 可以看下下面这块代码 console.log(name); // undefined var name = "Lil ...
- js的预解析详解
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 2.2、js基础---预解析和严格模式
一.语言特性 1.预解析:js会把变量的声明(仅仅是声明)提到顶部,但是不会突破作用域. alert(a);var a= 12; //结果,undefi ...
- js之预解析
一.所谓的预解析就是:在当前作用域中,JavaScript代码执行之前,浏览器首先会默认的把所有带var和function声明的变量进行提前的声明或者定义. 1)var声明的变量在预解析的时候只是提前 ...
- js的预解析和作用域
预解析指的就是,在js文件或者script里面的代码在正式开始执行之前,进行的一些解析工作.这个工作很简单,就是在全局中寻找var关键字声明的变量和通过function关键字声明的函数. 1.寻找 v ...
- js的预解析和代码执行相关规则
JavaScript解析过程分为两个阶段,一个是编译阶段,另外一个就是执行阶段. 1.编译阶段:又称为预解析阶段,在这个阶段JavaScript解释器将完成把JavaScript脚本代码转换到字节码. ...
- JS函数预解析(小记)
一,预解析:寻找var function 形参 a=undefined fn = function fn(){ alert(); } 二,逐行解读代码: 1.表达式 2. ...
- js代码预解析
1.var一般用于声明变量,预解析代码的时候,等号后面的赋值过程不会执行,所以预解析时的var变量都是未定义的 2.function声明的函数,预解析的时候,值就是函数里面的内容 例:console. ...
随机推荐
- PHP将图片转base64编码以及base64图片转换为图片并保存代码
图片转base64编码 /*图片转换为 base64格式编码*/ $img = 'uploads/01.png'; $base64_img = base64EncodeImage($img); ech ...
- Java新手问题集锦
Java是目前最流行的编程语言之一——它可以用来编写Windows程序或者是Web应用,移动应用,网络程序,消费电子产品,机顶盒设备,它无处不在. 有超过30亿的设备是运行在Java之上的.根据Ora ...
- Linux-网络综合实验
题目要求:财务部门有俩台机器acc-1和acc-2,it部门有俩台机器it-2和it-2,要求acc机器互通,it机器互通,财务部和it部机器通过路由器互通
- Makefile自学
MakeFile的规则 如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接. 如果这个工程的某几个C文件被修改,那么我们只编译被修改的C文件,并链接目标程序. 如果这个工程的头文件被改变了, ...
- Google FireBase - fcm 推送 (Cloud Messaging)
要将 Firebase 添加到您的应用,您需要有一个 Firebase 项目以及适用于您的应用的 Firebase 配置文件. 如果您还没有 Firebase 项目,请在 Firebase 控制台中创 ...
- VMware Workstation Pro下载密钥
热门虚拟机软件VMware Workstation Pro现已更新至14.1.2,14.0主要更新了诸多客户机操作系统版本,此外全面兼容Wind10创建者更新.12.0之后属于大型更新,专门为Win1 ...
- 总结css常用方法
1.自定义滚动条 .test-1::-webkit-scrollbar {/*滚动条整体样式*/ width: 10px; /*高宽分别对应横竖滚动条的尺寸*/ height: 1px; } .tes ...
- 【高并发架构】Redis特点及构件模型
数据结构 redis 相比 memcached 来说,拥有更多的数据结构,能支持更丰富的数据操作.如果需要缓存能够支持更复杂的结构和操作, redis 会是不错的选择. redis 主要有以下几种数据 ...
- 微信报错 config:fail.Error:invalid signature
config:fail.Error:invalid signature 微信公众号报这个错,appid等各项都配置好,经过一番折腾,发现原来ip白明单设置了不是该服务器的ID,重新设置后就可以了
- AWS EC2实例Linux系统创建root用户并更改为root用户登录
对于刚创建AWS EC2实例,或者经常使用AWS 实例的小伙伴们来说,刚创建的EC2实例是没有ROOT权限的,因此不能直接使用ROOT用户去登陆实例,也无法获取到root权限.一般情况下,EC2实例默 ...