预解析
预解析: 在所有代码开始执行之前, 对代码进行通读并解释, 解释完毕以后再开始执行代码
函数调用问题:
- 在函数定义的时候, 被装进 “盒子” 内的代码是不会执行的
- 在函数调用的时候, 代码才会执行
预解析有两部分:
- 全局预解析:打开页面的时候,会对全局代码进行预解析,但函数体内的代码不管
- 局部预解析:当你函数调用的时候,会在函数的私有作用域下进行预解析,解析完毕执行函数体内代码
预解析都解释哪些内容:
- var
- 声明式函数
var的预解析:
- 向浏览器内存声明, 有一个变量被定义了, 但是没有赋值
- 赋值操作是在代码执行阶段才会执行的
console.log(num)
var num //输出undefined
分析:
当你使用一个变量的时候
- 如果报错: xxx is not defined, 说明这个变量没有定义
- 如果出现 undefined 说明这个变量定义过, 但是没有赋值
console.log(num)
包含两个操作
1. var num
2. num = 100
var num = 100
代码:
console.log(num)
var num = 100
打开浏览器后预解析:
第 1 行 代码, 不需要预解析
第 2 行 代码, 有 var 关键字
进行预解析, var num 在浏览器声明了一个叫做 num 的变量, 但是此时不进行赋值
开始执行代码:
第 1 行 代码, console.log(num) 访问 num 这个变量
因为预解析的时候已经声明了 num 变量
所以此时是 有变量, 但是没有值
所以出现 undefined
第 2 行 代码, 因为 var num 已经在预解析执行过了
这里只有 num = 100 这句代码了
给 num 变量进行赋值
console.log(num)
var num = 100
等价于
var num
console.log(num)
num = 100
console.log(num) //undefined
console.log(num2) //报错,var num2 = 200 不参加预解析,函数内不预解析
var num = 100
function fn(){
var num2 = 200
}
console.log(num)//undefined
if(false){
var num = 100
}
console.log(num)//undefined,folse不执行了
预解析 - function
声明式函数会进行预解析
当代码发现声明式函数的时候=> 会在浏览器内存中声明变量名(函数名), 并且被赋值为一个函数
赋值式函数(函数表达式)=> 按照 var 的规则执行
fn()
function fn() {
console.log('hello world')
}
fn()
/*
1 fn()
2 function fn() { console.log('hello world') }
3 fn()
打开浏览器
+ 预解析:
=> 第 1 行 不需要
=> 第 2 行 需要
-> 在浏览器内声明一个叫做 fn 的变量名, 并且赋值为一个函数 function fn() { console.log('hello world') }
=> 第 3 行 不需要
=> 预解析结束
-> 此时浏览器内有一个叫做 fn 的函数
+ 代码执行
=> 第 1 行, fn()
-> 把 fn 当做一个函数调用一次
-> 因为预解析的时候, 已经声明了 fn 变量, 并且赋值为一个函数
-> 所以这里正常调用
=> 第 2 行, 因为在预解析已经定义过, 直接跳过
=> 第 3 行, fn()
-> 同 第 1 行
1 fn()
2 function fn() { console.log('hello world') }
3 fn()
等价于
1 function fn() { console.log('hello world') }
2 fn()
3 fn()
*/
//fn()
var fn = function () { console.log('你好 世界') }
fn()
/*
1 fn()
2 var fn = function () { console.log('你好 世界') }
3 fn()
打开浏览器
+ 预解析:
=> 第 1 行, 不需要
=> 第 2 行, 需要
-> 告诉浏览器定义了一个叫做 fn 的变量, 但是不赋值
=> 第 3 行, 不需要
=> 在预解析结束的时候
-> 浏览器内只有一个叫做 fn 的变量, 但是还没有赋值也就是 undefined
+ 代码执行
=> 第 1 行, fn()
-> 把 fn 变量当做一个函数来调用一次
-> 因为预解析的时候, fn 只是一个 undefined
-> 这里就是把 undefined 当做一个函数来调用
-> 报错: xxx is not a function
1 fn()
2 var fn = function () { console.log('你好 世界') }
3 fn()
等价于
1 var fn
2 fn()
3 fn = function () { console.log('你好 世界') }
4 fn()
*/
预解析的重名问题:
当代码中 函数名 和 变量名 重名时候
- 以函数为准, 仅仅只是在预解析中
- 不要把函数名和变量名重名
fn()
function fn (){console.log('fn 函数') }
fn()
var fn = 100
fn()
/* 这时候fn是一个数值了,100把函数覆盖了,把一个数值当做函数来调用,报错 fn is not a function */
/*
1 fn()
2 function fn() { console.log('fn 函数') }
3 fn()
4 var fn = 100
5 fn()
打开浏览器
+ 预解析
=> 第 2 行, 在浏览器声明了一个叫做 fn 的变量, 并且赋值为一个函数
=> 第 4 行, 在浏览器声明了一个叫做 fn 的变量, 不赋值
=> 在预解析过程中, 变量 和 函数 重名, 以函数为准
-> 在预解析结束的时候. 浏览器内存中只有一个 fn 变量, 保存的是函数
+ 代码执行
=> 第 1 行, fn()
-> 因为预解析的时候, fn 就是一个函数
-> 正常调用
=> 第 2 行, 预解析已经定义了, 直接跳过
=> 第 3 行, fn()
-> 和 第 1 行 一样
=> 第 4 行, var fn 已经在预解析执行过了
-> 此时剩下 fn = 100
-> 给 fn 变量从新赋值, 100 就把 函数覆盖了
=> 第 5 行, fn()
-> 把一个 数值 当做函数来调用
-> 报错: fn is not a function
*/
fn()
var fn = 100
fn()
function fn() { console.log('fn 函数') }
fn()
/*
1 fn()
2 var fn = 100
3 fn()
4 function fn() { console.log('fn 函数') }
5 fn()
打开浏览器
+ 预解析
=> 第 2 行, 在浏览器声明一个叫做 fn 的变量, 不赋值
=> 第 4 行, 在浏览器声明一个叫做 fn 的变量, 并赋值为一个函数
=> 在预解析过程中, 函数 和 变量 重名, 以函数为准
=> 此时 浏览器中只有一个 fn 变量, 值是一个函数
+ 代码执行
=> 第 1 行, fn()
-> 因为预解析的时候, fn 就是一个函数, 正常调用
=> 第 2 行, var fn 预解析已经执行过
-> 此时剩下 fn = 100
-> 给 fn 从新赋值为 100, 覆盖掉本身保存的函数
=> 第 3 行, fn()
-> 把 一个数值类型 当做函数来调用
-> 报错: fn is not a function
*/
函数内的预解析
预解析教会了我们什么
- 函数名和变量名不要重名
- 不管是函数还是变量, 进行先声明后使用
- 在函数内, 不要定义和形参一样的函数名
函数的两个阶段都做了什么
函数定义阶段
- 在堆内存中开辟一段存储空间
- 把函数体内的代码当做 字符串 放在开辟出来的存储空间内
- 把函数名放在栈内存中, 把堆内存中的空间地址赋值给栈内存的变量
函数调用阶段
-
按照栈内存中变量存储的地址找到函数的存储空间
-> 如果这个空间不是一个函数存储空间, 那么直接报错 xxx is not a function
-
在调用栈中开辟一段新的 函数执行空间
-> 把函数存储空间内的形参, 代码全部复制过来
-
在这个执行空间内, 进行形参的赋值
-> 赋值是在函数调用空间内执行的
-
在这个执行空间内, 进行函数内代码的预解析
-> 对函数体内存储的一段字符串进行通读并解释
-
把函数体内的 字符串, 当做 js 代码来执行
-> 如果是合法的 js 代码, 那么直接执行
-> 如果不合法, 此时才会报错
- 代码执行完毕以后, 此次开辟的函数执行空间销毁
变量的赋值: 把一个变量内存储的数据百分之百的复制一份给到另一个变量
var a = 100
var b = a
console.log(a)
console.log(b)
a = 200
console.log(a)
console.log(b)
在函数的调用过程中
+ 先形参赋值 还是 先预解析
+ 结论: 先形参赋值后预解析
=> 不要在函数内定义和形参一样的函数名
=> 这样你的形参就没有意义了
function fn(a) {
console.log(a)
function a() { console.log('我是 a 函数') }
}
fn(100)
/*
function fn(a) {
console.log(a)
function a() { console.log('我是 a 函数') }
}
fn(100)
打开浏览器
+ 预解析
=> 在浏览器全局声明了一个叫做 fn 的变量, 保存了一个函数
+ 代码执行
=> 函数定义直接调用
=> fn(100)
=> fn 函数代码执行过程
-> 假设先形参赋值后预解析
+ 首先给 a 形参赋值为 数值 100
+ 接下来进行函数内预解析的时候, 定义了一个叫做 a 的变量, 并且赋值为一个函数
+ 在赋值为函数的时候, 就会把 100 覆盖
+ 在之后执行代码的时候, console.log(a) 打印出来的是 函数体
-> 假设先预解析在形参赋值
+ 首先进行函数内的预解析
+ 在预解析过程中, 在函数内定义了一个叫做 a 的变量, 并且赋值为一个函数
+ 接下来进行形参赋值的过程中, 给 a 赋值为 100, 此时就会把函数覆盖掉
+ 在之后执行代码的时候, console.log(a) 打印出来的就是 100
*/
this: 不管函数在哪定义,不管函数怎么定义,直接去看怎么调用的
变量:不管函数怎么调用,就按照定义位置去逐层查看
面试题
//面试题1:
var a = b = 10
a = 20
b = 20
console.log(a)//20
console.log(b)//20
/*
var a = b = 10
a = 20
b = 20
console.log(a)
console.log(b)
预解析:
+ 在浏览器定义了一个叫做 a 的变量
代码执行:
+ b = 10
=> 涉及到的叫做变量赋值机制
=> 直到全局都没有 b 这个变量, 把 b 定义为全局变量, 在进行赋值
+ a = b
=> 因为 b = 10 的执行, 导致全局有了 b 变量
=> 把 b 变量保存的值赋值给 a
=> 此时 a 也是 10
+ ...
*/
var a = b //报错
a = 20
b = 20
console.log(a)
console.log(b)
/*
var a = b
a = 20
b = 20
console.log(a)
console.log(b)
预解析:
+ 在浏览器定义了一个叫做 a 的变量, 没赋值
代码执行:
+ a = b,
=> 访问 b 变量的值赋值给 a
=> 涉及变量访问机制, b 没有定义过, 报错
=> b is not defined
*/