预解析:
+ 在所有代码开始执行之前, 对代码进行通读并解释, 解释完毕以后再开始执行代码
+ 函数调用问题
=> 在函数定义的时候, 被装进 "盒子" 内的代码是不会执行的
=> 在函数调用的时候, 代码才会执行
=> 预解析有两部分
-> 全局预解析: 打开页面的时候, 会对全局代码进行预解析, 但是函数体内的代码不管
-> 局部预解析: 当你函数调用的时候, 会在函数的私有作用域内进行预解析, 解析完毕执行函数体内的代码
预解析都解释哪些内容:
+ var
+ 声明式函数
var 的预解析
+ 向浏览器内存声明, 有一个变量被定义了, 但是没有赋值
+ 赋值操作是在代码执行阶段才会执行的
分析:
+ 当你使用一个变量的时候
+ 如果报错: xxx is not defined, 说明这个变量没有定义
+ 如果出现 undefined 说明这个变量定义过, 但是没有赋值
// console.log(num)
// 包含两个操作
// 1. var num
// 2. num = 100
// var num = 100
代码:
1 console.log(num)
2 var num = 100
打开浏览器
预解析:
第 1 行 代码, 不需要预解析
第 2 行 代码, 有 var 关键字
进行预解析, var num 在浏览器声明了一个叫做 num 的变量, 但是此时不进行赋值
开始执行代码:
第 1 行 代码, console.log(num) 访问 num 这个变量
=> 因为预解析的时候已经声明了 num 变量
=> 所以此时是 有变量, 但是没有值
=> 所以出现 undefined
第 2 行 代码, 因为 var num 已经在预解析执行过了
=> 这里只有 num = 100 这句代码了
=> 给 num 变量进行赋值
1 console.log(num)
2 var num = 100
等价于
1 var num
2 console.log(num)
3 num = 100
预解析 - 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()
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
函数内的预解析
预解析教会了我们什么
1. 函数名和变量名不要重名
2. 不管是函数还是变量, 进行先声明后使用
3. 在函数内, 不要定义和形参一样的函数名
函数的两个阶段都做了什么
函数定义阶段
1. 在堆内存中开辟一段存储空间
2. 把函数体内的代码当做 字符串 放在开辟出来的存储空间内
3. 把函数名放在栈内存中, 把堆内存中的空间地址赋值给栈内存的变量
函数调用阶段
1. 按照栈内存中变量存储的地址找到函数的存储空间
-> 如果这个空间不是一个函数存储空间, 那么直接报错 xxx is not a function
2. 在调用栈中开辟一段新的 函数执行空间
-> 把函数存储空间内的形参, 代码全部复制过来
3. 在这个执行空间内, 进行形参的赋值
-> 赋值是在函数调用空间内执行的
4. 在这个执行空间内, 进行函数内代码的预解析
-> 对函数体内存储的一段字符串进行通读并解释
5. 把函数体内的 字符串, 当做 js 代码来执行
-> 如果是合法的 js 代码, 那么直接执行
-> 如果不合法, 此时才会报错
6. 代码执行完毕以后, 此次开辟的函数执行空间销毁
// function fn(a, b) {
// var res = a + b
// return res
// }
// var r1 = fn(10, 20)
// var r2 = fn(100, 200)
// console.log(res)
// 变量的赋值: 把一个变量内存储的数据百分之百的复制一份给到另一个变量
// 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)
// console.log(b)
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
+ ...
// 面试题2:
// 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