变量提升:JavaScript代码是按顺序执行的吗?
变量提升(Hoisting)
变量提升是指在js代码执行过程中,js引擎把变量的声明部分和函数的声明部分提升到代码开头的“行为”。变量被提升后,会给变量设置默认值,这个默认值就是 undefined
声明与赋值 的 概念
- 变量
=》会执行下面的两步操作var myname = "极客时间"
var myname
myname = "极客时间"
- 函数
```javascript
//无赋值操作
function foo(){
console.log(“foo”)
}
//先声明变量bar,在给bar赋值 var bar = function(){ console.log(“bar”) }


<a name="kxahF"></a>
### javascript 代码的执行流程
> 实际上变量和函数声明在代码里的位置是不会改变的,而且是在编译阶段被javascript引擎放入内存中,编译完成后在进入执行阶段。
<a name="4DqOi"></a>
#### 1.编译阶段
第一部分:变量提升部分代码<br />第二部分:执行部分的代码<br /><br />**执行上下文 **是 js 执行一段代码时的运行环境,在执行上下文中存在一个变量环境的对象(Viriable Environment)该对象保存了变量提升的内容。
<a name="KvvwM"></a>
#### 2.执行阶段
<a name="fuJKX"></a>
#### 代码中出现相同的变量或者函数怎么办?
```javascript
function showName(){
console.log("chu")
}
showName() //初
function showName(){
console.log("初")
}
showName() //初
总结:一段代码如果定义了两个相同名字的函数,那么最终生效的是最后一个函数
思考:当变量名与函数名重复时,谁的优先级较高
showName()
var showName = function () {
console.log(2)
}
function showName() {
console.log(1)
}
// 1
规则
- 如果是同名的函数,js编译阶段会选择最后声明的那个
- 如果是变量和函数同名,那么在编译阶段,变量的声明会被忽略
调用栈:为什么javascript代码会出现栈溢出?
哪些情况下代码会在执行之前就进行编译并创建执行上下文:
- 当 JavaScript 执行全局代码的时候,会编译全局代码并创建全局执行上下文,而且在
整个页面的生存周期内,全局执行上下文只有一份。 - 当调用一个函数的时候,函数体内的代码会被编译,并创建函数执行上下文,一般情况
下,函数执行结束之后,创建的函数执行上下文会被销毁。 - 当使用 eval 函数的时候,eval 的代码也会被编译,并创建执行上下文。
在js中有很多函数,经常会出现在一个函数中调用另外一个函数的情况,调用栈就是用来管理函数调用关系的一种数据结构。
什么是函数调用
函数调用就是运行一个函数,具体使用方式是使用函数名称跟着一对小括号。
var a = 2
function add() {
var b = 10
return a + b
}
add()
javascript 引擎通过一种叫栈的数据结构来管理 这些执行上下文。
什么是栈(stack):后进先出
什么是javascript的调用栈: 把用来管理执行上下文的栈称为执行上下文栈,又称调用栈
分析代码
var a = 2
function add(b, c) {
return b + c
}
function addAll(b, c) {
var d = 10
result = add(b, c)
return a + result + d
}
addAll(3, 6)
第一步:创建全局上下文 并将其压入栈底
第二步 调用addAll函数
第三步: 当执行到add函数调用时,同样会为其创建执行上下文,并将其压入调用栈
如何利用浏览器查看调用栈的信息开发者工具 source
或者 console.trace()
块级作用域:var缺陷以及为什么要引入let和const
作用域(scope)
作用域是指在程序中定义变量的区域,该位置决定了变量的的生命周期。简单来说作用域就是变量与函数的可访问范围,即作用域控制着变量和函数的可见性和生命周期。
- 全局作用域:中的对象在代码中的任何地方都能访问,其生命周期伴随着页面的生命周期。
- 函数作用域:就是在函数内部定义的变量或者函数,并且定义的变量或者函数只能在函数内 部被访问。函数执行结束之后,函数内部定义的变量会被销毁。
- 块级作用域:块级作用域就是使用一对大括号包裹的一段代码,比如函数、判断语句、循环语句, 甚至单独的一个{}都可以被看作是一个块级作用域。
//if块
if(1){}
//while
while(1){}
//函数块
function foo(){}
//for 循环块
for(let i=0;i<100;i++){}
//单独一个块
{}
变量提升带来的问题
1.变量容易在不被察觉的情况下被覆盖掉
??? 疑惑:赋值之后 为什么下面的console 还是依旧输出 undefiend
var myname = " 极客时间 "
function showName() {
console.log(myname); //undefiend
if (0) {
var myname = " 极客邦 "
}
console.log(myname); //undefiend
}
showName()
2.本应销毁的变量没有被销毁
function foo(){
for(var i =0;i<7;i++){
}
console.log(i) //7
}
foo()
ES6中如何解决
let x = 5
const y = 6
x = 7
y = 9 //报错 const声明的变量不可修改
使用 let 关键字声明的变量是可以被改变的,而const 声明的变量其值是不可以被改变的。
javascript 是如何支持块级作用域
function foo(){
var a = 1
let b = 2
{
let b = 3
var c = 4
let d = 5
console.log(a)
console.log(b)
}
console.log(b)
console.log(c)
console.log(d)
}
foo()
第一步:编译并创建执行上下文
函数内部通过var声明的变量,在编译阶段全都被存放到 变量环境 里
通过let声明的变量,在编译阶段会被存放到词法环境中
在函数的作用域内部,通过let声明的变量并没有被存放到词法环境中
思考:
let myname = "极客时间"
{
console.log(myname)
let myname= "ccc"
}
Uncaught SyntaxError: Identifier 'myname' has already been declared
在块级作用域内,let声明的变量被提升,但变量只是创建被提升,初始化并没有提升,在初始化之前使用变量,就会形成一个暂时性死区。
作用域链和闭包: 代码中出现相同的变量,javascript引擎如何选择?
function bar(){
console.log(myName)
}
function foo(){
var myName = "ccc"
bar()
}
var myName = "极客时间"
foo() // 极客时间
作用域链
一级一级查找的链条就称为作用域链
词法作用域
指作用域是由代码中函数声明的位置来决定的,所以词法作用域是静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符。
注意:词法作用域就是代码阶段就决定好的,和函数是怎么调用没有任何关系
块级作用域中的变量查找
function bar() {
var myName = " 极客世界 "
let test1 = 100
if (1) {
let myName = "Chrome 浏览器 "
console.log(test)
}
}
function foo() {
var myName = " 极客邦 "
let test = 2;
{
let test = 3
bar()
}
}
var myName = " 极客时间 "
let myAge = 10
let test = 1
foo()
闭包
在javascript中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,把这些变量的集合称为闭包,比如 外部函数是foo,那么这些变量的集合就称为foo函数的闭包。
function foo() {
var myName = " 极客时间 "
let test1 = 1
const test2 = 2
var innerBar = {
getName: function () {
console.log(test1)
return myName
},
setName: function (newName) {
myName = newName
}
}
return innerBar
}
var bar = foo()
bar.setName(" 极客邦 ")
bar.getName()
console.log(bar.getName())
//1
//1
// 极客邦
this: 从javasvript 执行上下文的视角讲清楚this
this 是和执行上下文绑定的,也就是说每个执行上下文中都有 一个 this
全局执行上下文中的this
window 对象
函数执行上下文
**
1.通过函数的 call 方法
以及 bind(需要自己执行) apply(参数传递为数组 )
let bar = {
myName:"极客邦",
test1:1
}
function foo(){
this.myName = "极客时间"
}
foo.call(bar)
console.log(bar) //极客时间
2.通过对象调用方法设置**
var myObj ={
name:"极客时间",
showThis:function(){
console.log(this)
}
}
myObj.showThis() // myObj 这个对象
相当于转换为
myObj.showThis.call(myObj)
var myObj ={
name:"极客时间",
showThis:function(){
this.name = "极客邦"
console.log(this)
}
}
var foo = myObj.showThis // myObj 这个对象
foo() //window
结论:
在全局环境中调用一个函数,函数内部的this指向的是全局变量 window
使用对象来调用其内部的一个方法,该方法的this是指向对象本身
3.通过构造函数中设置
当执行 new CreateObj()
- 首先创建一个空对象 tempObj
- 接着调用 CreateObj.call 方法,并将tempObj作为call方法的参数,这样当CreateObj 的执行上下文创建时,它的this就指向了tempObj 对象
- 然后执行 CreateObj 函数,此时CreateObj函数执行上下文中的this指向了tempObj
- 最后返回tempObj对象
function CreateObj(){
this.name = "极客时间"
}
var myObj = new CreateObj()
代码演示
var tempObj = {}
CreateObj.call(tempObj)
return tempObj
存在的问题
1.嵌套函数中的this不会从外层函数中继承
var myObj = {
name:"极客时间",
showThis:function(){
console.log(this) //myObj
function bar(){
console.log(this) //window
}
bar()
}
}
myObj.showThis()
解决:
1.在 showThis 函数中声明一个变量 self 用来保存 this**
var myObj = {
name:"极客时间",
showThis:function(){
console.log(this) //myObj
var self = this
function bar(){
console.log(self) //myObj
}
bar()
}
}
myObj.showThis()
2.使用箭头函数
因为箭头函数没有自己的执行上下文,所以它会继承调用函数中的this
**