Javascript 的变量提升与预解析
一、什么是变量提升
在ES6之前,JavaScript没有块级作用域(一对花括号{}即为一个块级作用域),只有全局作用域和函数作用域。变量提升即将变量声明提升到它所在作用域的最开始的部分
二、怎么实现变量提升
Js 运行前有一个预编译的过程,预编译完成后,在一步步执行。那么在预编译的过程中,会执行三个动作:
1.分析参数,
2.分析变量声明,
3.分析函数声明。
具体步骤如下:
1.函数在运行的瞬间,生成一个活动对象(Active Object),简称 AO
2.分析参数
函数先接收形参,添加到 AO 的属性,并为这个值赋上 undefined,如:AO.age = undifened.
接收实参,添加到 AO 的属性,覆盖之前的undefined 值。
3.分析变量声明,如 var age; || var age = 23;
如果在参数分析时,AO 并没有age 属性,则先为 AO 添加 age 属性为 undefine,即 AO.age = undefine.
如果此时 AO 中已有age 属性,则不做任何操作。
4.分析函数的声明
函数的声明一般有3种形式:
函数关键词声明:function age() {};
函数字面量声明:var age = function () {};
构造函数声明:var age = new Function();
如果函数是函数关键词声明,即 function age () {}, 则把整个函数提至顶端,覆盖上一步中 AO.age 属性,即此刻 AO.age = function () {};
如果函数是函数字面量声明,即 var age = function () {}, 则将 age 当成变量声明处理,即 var age。
第三种形式暂不讨论。
代码示例1
<script>
function t1(age) {
console.log(age);
var age = 27;
console.log(age);
function age() {}
console.log(age);
}
t1(3);
</script>
预编译阶段的故事:
1.创建 AO 对象
2.分析参数
接收形参 age,并赋值 undefine。AO.age = undefine
接收实参 3,覆盖 上一步的 undefine。AO.age = 3
3.分析变量声明 (var age = 27)
由于第2步已有 age 属性,固这一步啥也不做。AO.age = 3.
4.分析函数声明 (function age() {})
由于是函数关键字声明,函数被提升到最顶端,替代第2步中的 age 属性,即AO.age = function () {}.
于是,原本的代码预编译后等价于:
<script>
function t1(age) {
var age= function () {}
console.log(age); // function () {}
age = 27;
console.log(age); // console.log(age); //
}
t1(3);
</script>
代码示例2
<script>
function t1(age) {
var age;
console.log(age);
age = 23;
console.log(age);
function age() {
console.log(age);
}
age();
console.log(age)
}
t1(22)
</script>
预编译阶段的故事:
1.创建 AO 对象
2.分析参数
接收形参 age,并赋值 undefine。AO.age = undefine
接收实参 22,覆盖 上一步的 undefine。AO.age = 22
3.分析变量声明 (var age;)
由于第2步已有 age 属性,固这一步啥也不做。AO.age = 22.
4.分析函数声明 (function age() {console.log(age)})
由于是函数关键字声明,函数被提升到最顶端,替代第2步中的 age 属性,即AO.age = function () {console.log(age)}.
于是,原本的代码预编译后等价于:
<script>
function t1(age) {
var age= function() {
console.log(age);
};
console.log(age); // function () {console.log(age)}
age = 23;
console.log(age); // age(); // 报错:age is not a function
console.log(age)
}
t1(22)
</script>
执行阶段:执行到第10步时,由于age 在第7步被变量覆盖,所以再以函数的方式调时,发生了报错。
示例代码3
<script>
function t1(age) {
console.log(1,age);
var age = function() {
console.log(2,age);
};
console.log(3,age);
age();
console.log(4,age);
}
t1(23)
</script>
预编译阶段的故事:
1.创建 AO 对象
2.分析参数
接收形参 age,并赋值 undefine。AO.age = undefine
接收实参 23,覆盖 上一步的 undefine。AO.age = 23
3.分析变量声明
在这个例子中,没有变量的声明(var age = function () {console.log(age)},属于函数的字面量声明),固啥也不做,AO.age = 23。
4.分析函数声明 (var age = function () {console.log(age)})
由于是函数字面量声明,可以当做变量声明分析,即 var age = function age() {console.log(age)} 等价于 var age; age = function () {console.log(age)}。由于第2步中已有age属性,所以这一步什么都不做。AO.age = 23。
于是,原本的代码预编译后等价于:
<script>
function t1(age) {
age = 23;
console.log(1,age); // 1, 23
age = function() {
console.log(2,age); // 2, function() {console.log(2, age)}
};
console.log(3,age); // 3, function() {console.log(2, age)}
age();
console.log(4,age); // 4, function() {console.log(2, age)}
}
t1(23)
</script>
执行阶段:第一个log打印时,输出的是预编译的结果,即 1,23, 然后在第5行,age 被覆盖成函数,但没有被调用,所以先走第8行的 log,即答应结果为 3,function (){...},然后在第9行,age 函数被调用,运行第6行函数内的log,所以打印出来的结果为 2, function (){...},最后执行第10行的 log,由于age 没有被修改,所以打印的仍为函数,结果为 4,function (){...}。因此,4个log 的打印顺序为:1,3,2,4。
至此,JS 预编译的过程我们差不多走通了。下面我们来总结变量提升的规律。
三、变量提升的规律
1:所有的声明都会提升到作用域的最顶上去。
2:同一个变量只会声明一次,其他的会被忽略掉。
3:函数声明的优先级高于变量声明的优先级,并且函数声明和函数定义的部分一起被提升。
四、最佳实践
无论变量还是函数,都必须先声明后使用。以此来规范我们的代码,增强可读性和可维护性。
PS2: 另有一道 JS 基础的综合训练题,感兴趣的朋友可以研读研读,做做练习。一道常被人轻视的前端JS面试题
Javascript 的变量提升与预解析的更多相关文章
- javascript中变量提升的理解
网上找了两个经典的例子 var foo = 1; function bar() { if (!foo) { var foo = 10; } alert(foo); } bar(); // 10 var ...
- JavaScript的变量提升机制
变量提升 JavaScript的变量提升有两种,用var声明的变量以及用function声明的变量. 用var声明的变量 我们先来看下面这段代码,a的值是多少 代码1 console.log(a); ...
- JavaScript:变量提升和函数提升
第一篇文章中提到了变量的提升,所以今天就来介绍一下变量提升和函数提升.这个知识点可谓是老生常谈了,不过其中有些细节方面博主很想借此机会,好好总结一下. 今天主要介绍以下几点: 1. 变量提升 2. 函 ...
- function——函数声明头的提升和预解析
函数: 即function语句的集合,就是将多个语句封装到一起: 函数的执行要会自己遍历,遇见函数 a():执行语句,就要移交控制权,函数执行完毕之后,控制权又移交回来了! 函数的参数要罗列在func ...
- JavaScript之变量(声明、解析、作用域)
声明(创建) JavaScript 变量 在 JavaScript 中创建变量通常称为"声明"变量. 一.我们使用 var 关键词来声明变量: var carname; 变量声明之 ...
- JavaScript中变量提升是语言设计缺陷
首先纠正下,文章标题里的 “变量提升” 名词是随大流叫法,“变量提升” 改为 “标识符提升” 更准确.因为变量一般指使用 var 声明的标识符,JS 里使用 function 声明的标识符也存在提升( ...
- JavaScript的变量提升
在JavaScript中,var变量具有函数级作用域,而且是整个函数作用域.为什么会是整个函数作用域呢?因为var变量具有变量(声明)提升功能,能将变量声明隐式的提升到函数体的顶部.这样做的一个好处就 ...
- javascript Hoisting变量提升
1. 看人家举的两个例子,我认为这里的判断是否定义: !var 其实就是 指是否在函数function里面定义了.只有在funciton里面定义了了,js才hoist到最上面去找这个变量的值,否则就按 ...
- JavaScript中变量提升------Hoisting
原谅链接:http://www.cnblogs.com/damonlan/archive/2012/07/01/2553425.html 因为这个问题很是经典,而且容易出错,所以在介绍一次.哈哈.莫怪 ...
随机推荐
- Scrapy+Scrapy-redis+Scrapyd+Gerapy 分布式爬虫框架整合
简介:给正在学习的小伙伴们分享一下自己的感悟,如有理解不正确的地方,望指出,感谢~ 首先介绍一下这个标题吧~ 1. Scrapy:是一个基于Twisted的异步IO框架,有了这个框架,我们就不需要等待 ...
- wireshark 抓包过滤器使用
目录 wireshark 抓包过滤器 一.抓包过滤器 二.显示过滤器 整理自陈鑫杰老师的wireshark教程课 wireshark 抓包过滤器 过滤器分为抓包过滤器和显示过滤器,抓包过滤器会将不满足 ...
- 我们来说说self.setinterval
学了js的你,肯定知道setInterval方法是按照指定的周期(以毫秒计)来调用函数或计算表达,setInterval方法会不停地调用函数,直到clearInterval被调用或窗口被关闭,这个se ...
- 通过hook实现禁止shift+delete快捷键
实现全局hook必须要将hook代码封装在dll里,所以此程序有两个文件:noShiftDeleteHook.dll和noShiftDelete.exe noShiftDeleteHook.dll / ...
- JUC--ConcurrentHashMap
HashMap HashTable HashTable锁住整个表 会存在复合操作上的问题“若不存则添加” “若存在则删除” 也是不安全的 效率低 ConcurrentHashMap:采用锁分段机制 ...
- 【转载的】这张图能容易理解sql joins,收藏下!
- Eclipse install new software无反应
一个问题可以有不同的解决方案 其他人提供了不少方案 我遇到了这个问题 但是这些解决方案都无济于事 于是 我就采取了一个新方案: 然后重新解压,找到里面的eclipse.exe重新打开就可以了 现在有反 ...
- 高可用Redis(十):Redis原生命令搭建集群
1.搭建Redis Cluster主要步骤 1.配置开启节点 2.meet 3.指派槽 4.主从关系分配 2.环境说明 两台虚拟机,IP地址分别为:192.168.81.100和192.168.81. ...
- MySQL存储过程中的事务执行失败之后获取错误信息
1.表结构: 2. 存储过程中: 代码如下: BEGINDECLARE CONTINUE HANDLER FOR SQLEXCEPTIONBEGINROLLBACK;GET DIAGNOSTICS C ...
- poj1988 Cube Stacking 带权并查集
题目链接:http://poj.org/problem?id=1988 题意:有n个方块,编号为1-n,现在存在两种操作: M i j 将编号为i的方块所在的那一堆方块移到编号为j的方块所在的那 ...