先編譯,再執行。 瀏覽器
棧溢出是如何產生的?
當調用一個函數時,會給他建立一個執行上下文 push 到棧中,執行完畢從棧中 pop。若函數內部又調用了其餘函數,內部又調用其餘函數...,不斷將執行上下文往棧中 push 卻沒有 pop,超過必定數量就會致使棧溢出報錯。閉包
沒有終止條件的遞歸會一直建立新函數的執行上下文壓入棧中,超過棧容量的最大先以後就會報錯;app
能夠經過把遞歸改形成其餘形式、加入定時器拆分任務等方法來解決。函數
調用棧是 JavaScript 引擎追蹤函數執行的一個機制,當一次有多個函數被調用時,經過調用棧就能追蹤到哪一個函數正在被執行和各個函數間的調用關係。工具
chat* myname = "geek time";
void showName() {
printf("%s \n", myname); // 'geek time'
if(0){
chat* myname = "Hei ha";
}
}
int main(){
showName();
return 0;
}
複製代碼
最終打印爲 'geek time'ui
var myname = "geek time";
function showName() {
console.log(myname);
if (0) {
var myname = "Hei ha";
}
}
showName();
複製代碼
最終打印爲 undefinedthis
function foo() {
for (var i = 0; i < 7; i++) {}
console.log(i);
}
foo(); //7
複製代碼
輸出爲 7,變量 i 在 foo 循環結束後並無被銷燬,說明在建立執行上下文階段,變量 i 就已經被提高了。spa
在其餘語言中,for,if,while,{},函數塊等內部變量執行完後就會被銷燬。設計
經過 var 聲明的變量,在編譯階段被放進變量環境
,而經過 let,const 聲明的被放進詞法環境(Lexical Environment)
;
3d
function bar() {
console.log(myName);
}
function foo() {
var myName = "極客邦";
bar();
}
var myName = "極客時間";
foo();
複製代碼
按上面調用棧順序來分析,那麼結果應該是
極客邦
; 實際答案是
極客時間
每一個執行上下文的環境中都包含了一個外部引用,用來指向外部執行的上下文,上圖中的 outer。
當一段代碼使用一個變量時,JS 引擎首先在「當前執行上下文(bar)」中查找該變量,若沒有,則在 outer 所指向的執行上下文中查找,這個查找鏈條就是做用域鏈
。
由於在 JS 執行過程當中,做用域鏈
是由詞法做用域
決定的。
詞法做用域是由代碼中函數聲明的位置來決定的,因此詞法做用域是靜態做用域,經過它能預測代碼在執行過程當中如何查找標識符。
上圖中,整個詞法做用域鏈的順序是: foo 函數做用域 -> bar 函數做用域 -> main 函數做用域 -> 全局做用域。詞法做用域是代碼階段就決定好的,和函數怎麼調用沒有關係。 再看上面的問題,就知道打印的結果爲何是「極客時間」了。 若是換成下面的:
function foo() {
var myName = "極客邦";
function bar() {
console.log(myName);
}
return 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();
複製代碼
結合上面的做用域鏈與詞法做用域,易得最終輸出結果爲 1。 查找順序以下(圖中標記的 1,2,3,4,5)
function foo() {
var myName = "極客時間";
let test1 = 1;
const test2 = 2;
var innerBar = {
getName() {
console.log(test1);
return myName;
},
setName(newName) {
myName = newName;
}
};
return innerBar;
}
var bar = foo();
bar.setName("極客邦");
bar.getName();
console.log(bar.getName());
複製代碼
getName
與
setName
能夠訪問 foo 中的 myName 和 test1。因此,當 foo 執行完後,這兩個變量成爲 foo 閉包的專屬變量,除了 setName 和 getName 其餘任何地方都沒法訪問 foo 閉包中的變量。調用棧的狀態以下:
經過上圖能夠看出,當執行到 foo 時,閉包就產生了,foo 結束後,getName 與 setName 都引用了clourse(foo)
對象,因此即便 foo 函數結束了,clourse(foo)
依然被其內部的 getName 和 setName 引用,調用這兩個方法時,建立的執行上下文就包含了 clourse(foo)
myName
,因而生成一個閉包環境來存放 myName 變量。當執行 bar.setName()
方法中的 myName = 'xxx' 時,JS 引擎會沿着「當前執行上下文 -> foo 函數閉包 -> 全局執行上下文」的屬性來查找,以下:
Scope
便可查看做用域鏈的狀況。
若是引用閉包的函數是全局變量,那麼閉包會一直存在到頁面關閉;但若是這個閉包之後再也不使用的話,就會形成內存泄漏。
若是引用閉包的函數是局部變量,等函數銷燬後,下次 JS 引擎執行垃圾回收時,判斷閉包若是已再也不被使用,就會回收這塊內存。
綜上所述,若閉包一直使用,則做爲全局變量,不然爲局部變量。
this 是和執行上下文綁定的,執行上下文有全局、函數、eval 執行上下文,故對應的 this 也有這三種。
window
let bar = {
myName: 'x'
}
function foo() {
this.myName = 'xxx'
}
foo.call(bar)
複製代碼
var myObj = {
name: 'x',
showThis() {
console.log(this)
}
}
myObj.showThis() // 等同於 myObj.showThis.call(myObj)
var foo = myObj.shiwThis
foo() // window
複製代碼
在全局環境中調用一個函數,函數內部的 this 指向的是全局變量 window。 經過一個對象來調用其內部的一個方法,該方法的執行上下文中的 this 指向對象自己。
3. 經過構造函數中設置 new 運算符
var myObj = {
name: 'jk',
showThis() {
console.log(this) // myObj
function bar() {
console.log(this)
}
bar() // window
}
}
複製代碼
解決辦法:1. 外層綁定 this 2. 箭頭函數
2. 普通函數中的 this 默認指向全局對象 window 嚴格模式下,默認執行一個函數,這個函數執行上下文中的 this 是 undefined
showName();
var showName = function() {
console.log(2);
};
function showName() {
console.log(1);
}
複製代碼
輸出 1,第一個 showName 帶 var 通過變量提高後被賦值爲 undefined,變量 showName 會被下面同名函數覆蓋,再次執行 showName 就爲 2,具體過程以下
// 編譯
var showName = undefined;
function showName() {
console.log(1);
}
// 執行
showName(); // 1
showName = function() {
console.log(2);
};
showName(); // 2
複製代碼
let myname = "geek time";
{
console.log(myname);
let myname = "Hei ha";
}
複製代碼
最終的打印結果不是 undefined.
而是:Cannot access 'myname' before initialization 緣由:在塊級做用域內,let 變量只是建立被提高,初始化並無被提高,在初始化以前使用變量,會造成一個暫時性死區。