作用域与变量提升
作用域(栈内存、执行上下文)
全局作用域(全局栈内存)
- 浏览器打开一个页面,开始运行时率先形成一个栈内存,这个栈内存又叫全局作用域,为代码提供执行环境,在全局作用域下会生成一个全局的大对象叫window。
- 浏览器打开,生成的全局作用域一般不销毁,直到页面关闭。
全局变量
- 在全局作用域下声明的变量就是全局变量
- 在全局下定义的变量会默认给window新增一个属性名,属性名是变量名,属性值是变量所存储的值;
- // 在全局作用域下定义的函数,也会给window新增键值对;属性名是函数名,属性值时函数的空间地址;
- c = 100; //在函数里,也会给window新增键值对
- 当全局作用域中存在n,window中也存在n,会先找全局,再找window;
// let : let声明的变量不能给window新增键值对;const声明的常量也不能新增键值对;var声明的变量可以给window新增键值对;
const e=0;
let d=1;
var f=3;
console.log(window.d,window.e,window.f);//undefined undefined 3
- 全局变量的区别
-
用var和function 声明的变量会在全局作用域下率先创建,而且也会给window增加属性,属性名是变量名,属性值是变量名储存的值(let,const不可以)
var s = 12;
function fn(){}
console.log(window.s)
console.log(window.fn)
let a = 12;
`console.log(window.a) // undefined`` -
var跟function可以重复创建同一个变量名(let不可以)
var a = 12;
var a = 13;
console.log(a) // 13
let a = 12; // 报错 SyntaxError(语法错误)
`let a = 13;`` -
var和function有变量提升(let没有)
b = 12 // 等价于window.b = 12因为window.可以省略
var b = 12(有变量提升)
-
创建变量和赋值
var a,b,c = 12; //undefined undefined 12
//创建变量a,b,c,但是只给c赋值
var a = b = c = 12; // 等价于 var a = 12; b = 12; c = 12;
var a = 12,b = 13, c = 14; // 创建三个变量,给每一个变量都进行赋值
var a, b, c = 100; // 代码时被var声明过,只是a,b没有被赋值
console.log(b); //undefined
var d = e = f = 200; // e,f没有被var;只有d被var过;但是def都被赋值200;
console.log(e); // 200
私有作用域(私有栈内存)
- 全局作用域生成之后才会有私有作用域,私有作用域属于全局作用域
- 函数执行时会形成一个私有作用域(私有栈内存)为函数内的代码执行提供环境。
- 函数执行会形成私有的作用域,会保护里面的私有变量不受外界的干扰;
创建函数时
- 首先开辟一个堆内存生成一个16进制的空间地址
- 把函数体里的代码以字符串的格式存储进去
- 把16进制的地址赋值给函数名
执行函数时
私有变量
- 在私有作用域中定义的变量就是私有变量(var、function、let、const····)
- 形参也是私有变量
- 在私有作用域里使用一个变量,如果自己私有作用域里有,直接用自己的,如果没有,就取上一级作用域的
- 函数外边不能拿到函数里边的变量
- 在函数私有作用域中定义的变量,不会给window新增键值对;
- 在函数体中函数不会给window新增键值对,函数外面不能调用此函数
变量提升(声)
- 变量提升就是浏览器解析代码的一个过程,发生在js代码之前。
- 变量提升的含义
- 在当前作用域,代码执行之前。浏览器会从头到尾检测一遍所有变量,给带var和function进行提前声明和定义。
- 带var的会只声明(创建变量),不定义(赋值)
- 带function的既声明(创建变量),又定义(赋值)
-
当函数执行的时候,才会对函数体中的代码发生变量提升;
-
let会形成块级作用域,let和{}结合就会形成块级作用域
{
let w = 13
}
if(true){
var s = 13;
let w = 12;
}
console.log(w)
console.log(s)
console.log(a)
解决暂时性死区
console.log(typeof a)
let a = 12;
const w;//const创建常量必须赋值
console.log(w)
变量提升的特殊情况
- 变量提升发生在等号左边
var a = function () {}//此处,浏览器变量提升时,只识别var,不识别function
- 不管if条件是否成立,if里的代码都要进行变量提升
- 在老版本浏览器里,if条件里的function既声明,又定义
- 在新版本浏览器里,if条件里的函数只声明,不定义
- 条件一旦成立,马上给函数进行定义,然后再执行代码
console.log(num)//undefined
if(false){
// 只要进入到块级作用域,立即开辟堆内存;对函数赋值
var num = 12;
}
console.log(num)//undefined
console.log(fn); // undefined
// 在老版本浏览器里,if条件里的function既声明又定义,
// 在新版本浏览器里,if条件里的函数只声明不定义
if(false){
// 条件一旦成立,第一件事就是给函数名赋值,然后在执行代码
fn()
function fn(){}
}
console.log(fn) // undefined
console.log(fn); // undefined
// 在老版本浏览器里,if条件里的function既声明又定义,
// 在新版本浏览器里,if条件里的函数只声明不定义
if(true){
// 条件一旦成立,第一件事就是给函数名赋值,然后在执行代码
fn()
function fn(){}
}
console.log(fn) // fn(){}
- 在函数里,虽然return下面的代码不执行,但是要进行变量提升,return 后面的代码不进行变量提升的;
function fn() {
console.log(ss); // undefined
return function f(){
};//return返回值没有变量提升,中断下面代码执行
var ss = 34;//此处的ss仍然要变量提升。永远是undefined
}
fn()
- 自执行函数、匿名函数不进行变量提升
// 当代码执行到这一行时,先开辟一个空间地址,然后再执行
(function(){
})()
var fn = function(){};
//
var obj = {
fn:(function(){
console.log(100);
// 如果fn的属性值时一个自执行函数,那么当代码以键值对存储的时候
//(当代码执行到这一行时,(会先开辟堆内存空间,然后再立即形成栈内存,
代码执行)自执行函数就会运行),并且把自执行函数的执行结果赋值给属性名fn;
return function(){
}
})()
};
console.log(200)
obj.fn();
obj.fn();// 执行函数中返回的小函数执行
5.如果变量名重名,不再进行重复声明,但是要重新赋值;
var num = 1;
console.log(num);// 1
var num = 2;
console.log(2);
fn();// 4
function fn(){
console.log(1);
}
function fn(){
console.log(2);
}
fn();// 4
function fn(){
console.log(3);
}
fn=100;
function fn(){
console.log(4);
}
fn();// 报错
6.函数里的 let ,词法解析的时候定义了一下,let没有变量提升
//1.let 声明的变量不进行变量提升;
var a = 1;
function bar() {
console.log(b)//Cannot access 'a' before initialization//初始化前无法访问“a”
let b = 10;//词法解析的时候定义了一下,let没有变量提升
}
bar();
//2.let声明的变量在同一个作用域下不允许重名;
// 在代码运行之前,会对当前作用域下带let进行解析,判断是否有重名的变量;有的话,就直接报错;
// let a=1;
// let a=2;
- 给window新增键值对是发生在变量提升的阶段;
//console.log(window);
//var a=100;
对象里的in属性
in属性,检测一个对象中有没有某个属性名,如果有就返回true,反之返回false
let obj = {
name:1
}
console.log('name' in obj)//true
console.log('d' in obj)//false
console.log(fn); // undefined
// var和function给window增加属性的过程是变量提升的时候发生的
if ('fn' in window) {
// 如果条件成立,进来的后的第一件事就是给fn赋值,然后在执行代码
fn(); // 'erYa'
function fn() {
console.log('erYa');
}
}
fn();
函数的作用域查找
函数的上一级作用域是谁,在函数定义的时候就已经确定了,函数在哪创建的,他的上一级作用域就是谁,跟函数在哪执行没有关系。
作用域链查找机制
在私有作用域中,函数执行,如果要使用一个变量,获取变量所对应的值时,自己作用域要是有,就使用自己的,要是没有,就向上一级作用域查找,上一级还没有,在向上一级查找,直到找到全局作用域,如果还没有就报错—>这种一级一级向上查找的机制就是【作用域链查找机制】
/*
n = 1
fn:fn
x = f
*/
var n = 1;
function fn() {
/*
n = 2 1 0
f:f
*/
var n = 2;
function f() {
/*
*/
n--;
console.log(n); // 1 0
}
f();
return f;
}
var x = fn();
x();
console.log(n); //1
闭包
- 函数执行形成的私有作用域就是闭包,他可以保护里边的私有变量不受外界干扰,
- 还可以保存变量
- 利用了函数执行会形成不销毁的作用域,可以保存变量的特点=>这个机制也叫闭包
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
* {
padding: 0;
margin: 0;
}
#box {
width: 500px;
margin: 10px auto;
}
ul {
list-style: none;
position: relative;
top: 1px;
text-align: center;
display: flex;
}
li {
border: 1px solid black;
line-height: 50px;
height: 50px;
font-size: 30px;
margin-right: 20px;
padding: 0 10px;
}
#box div {
width: 500px;
text-align: center;
height: 300px;
line-height: 300px;
font-size: 50px;
color: orangered;
border: 1px solid black;
display: none;
}
#navList li.active {
border-bottom-color: white;
}
#box div.active {
display: block;
}
</style>
</head>
<body>
<div id="box">
<ul id="navList">
<li class="active">erYa</li>
<li>jinYu</li>
<li>xiaoHua</li>
</ul>
<div class="active">长的这俊哩</div>
<div>你好帅啊</div>
<div>哈哈</div>
</div>
<script>
let navList = document.querySelectorAll('#navList li');
let tabList = document.querySelectorAll('#box div');
console.log(navList, tabList);
// for (var i = 0; i < navList.length; i++) {
// // 把当前的i保存到元素结构上
// navList[i].setAttribute('myIndex', i);
// // 给每一个li绑定点击事件
// navList[i].onclick = function () {
// // this:点击谁,this就是谁
// //点击相应的li,获取到li结构上的myIndex属性,当做实参传递给fn方法
// fn(this.getAttribute('myIndex'))
// }
// }
function fn(index) {
// 清除每一个li和每一个div的样式名
for (var i = 0; i < tabList.length; i++) {
navList[i].className = ''
tabList[i].className = ''
}
// 给相应的li和div加上样式名
navList[index].className = 'active'
tabList[index].className = 'active'
}
// for (var i = 0; i < navList.length; i++) {
// // 当往元素的堆内存中存储键值对的时候,自执行函数就已经运行了
// navList[i].onclick = (function(index){
// // 函数return了一个引用数据类型值,而且return的值被外界所接收,所以这个作用域不销毁
// // 把return后面这个小函数赋值给onclick
// // 其实就是利用了函数执行会形成不销毁的作用域,可以保存变量的特点=>这个机制也叫闭包
// return function(){
// fn(index)
// }
// })(i)
// }
/*
利用了闭包可以保存私有变量的特点,
而且这个闭包是不销毁的作用域
i=0
navList[0].onclick = (function(index){ // 0
return function(){
fn(index)
}
})(i) // 0
i=1
navList[1].onclick = (function(index){ // 1
return function(){
fn(index)
}
})(i) // 1
i=2
navList[0].onclick = (function(index){ // 2
return function(){
fn(index)
}
})(i) // 2
*/
// for (let i = 0; i < navList.length; i++) {
// // ES6中let在for循环的大括号会形成块级作用域
// navList[i].onclick = function () {
// fn(i)
// }
// }
/*
{i=0
navList[0].onclick = function () {fn(i)}
}
{i=1
navList[2].onclick = function () {fn(i)}
}
{i=2
navList[2].onclick = function () {fn(i)}
}
*/
</script>
</body>
</html>
块级作用域
//
console.log(num);// undefined
console.log(fn);// undefined
if([]){
// 只要进到当前if条件中,会立即对fn进行赋值;
// 支持es6的浏览器,会把这个if的大阔号解析成一个块级作用域;
fn()
var num=100;
function fn(){console.log("a")}
}
console.log(fn);//fn(){console.log("a")}
//
console.log(fn);// undefined
for(var i=0;i<2;i++){
function fn(){}
}
console.log(fn);//function fn(){}
//
console.log(f); //undefined
let i = 0;
while (i < 1) {
i++
function f() {
}
}
console.log(f); //function fn(){}
- 块级作用域,和形参重名,函数赋值不能影响外面
var b = {
a: "hello"
};
function fn(b) {
b.a = "world";
if (true) {
function b(){}//块级作用域,和形参重名,函数赋值不能影响外面
}
console.log(b);//{a: "world"}
}
fn(b)
- 在块级作用域下var和function是不可以重名的
// 在块级作用域下var和function是不可以重名的
if (!("aa" in window)) {
var aa = 1;
function aa() {
console.log(aa)
}
}
console.log(aa);//报错Identifier 'aa' has already been declared 已声明标识符“aa”//