作用域與變量提升
作用域(棧內存、執行上下文)
全局作用域(全局棧內存)
- 瀏覽器打開一個頁面,開始運行時率先形成一個棧內存,這個棧內存又叫全局作用域,爲代碼提供執行環境,在全局作用域下會生成一個全局的大對象叫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”//