剛剛學完js以後我感受JavaScript並無什麼東西,好像也不是很難。可是本身運用的時候發現本身學的並不紮實,裏面的知識點仍是挺多的,甚至有些理解了我仍是會出錯。因此爲了打好基礎,我會複習回顧JavaScript的知識點,作好記錄,記錄是我本身的學習反饋,也是同你們的學習交流,找到本身的不足。這個小記錄總結了js中基礎且重要的五個問題,看完後可以深刻的理解預編譯中變量被提高,閉包的做用以及閉包觸發哪些問題等知識點。數組
我會從深度和廣度兩個方面來敘說。緩存
js語言有兩個特色,一個是單線程語言,一個是解釋性語言(讀一句執行一句)。bash
ps:定義變量包括變量聲明和變量賦值,閉包
任何未經聲明就賦值的變量歸window全部,window就是全局的域。 一切聲明的全局變量非局部變量,就都是window的屬性模塊化
預編譯發生在函數執行的前一步函數
1.建立AO對象,AO執行期上下文學習
2.找出形參和變量聲明放到AO{}裏面,且值爲undefinedui
3.實參和形參相統一this
4.找出函數裏面的函數聲明而且函數體爲值spa
而後在AO裏面拿東西,寫的東西是給電腦看的,想拿東西要到電腦裏面定義好的地方去拿。而不是視覺上的效果
來看一個栗子
function fn(a) {
console.log(a); //function a() {}
var a = 123;
console.log(a); //123
function a() {}
console.log(a); //123
var b = function() {}
console.log(b); //function() {}
function d() {}
console.log(d); //function d() {}
}
fn(1);
複製代碼
這道題包括形參名,函數名,變量名都同樣的狀況,因此咱們不能根據本身看到的去判斷,但是看在編譯以前生成的執行期上下文。
AO{
}
AO{
形參a:undefined
變量聲明b:unfined
複製代碼
}
其中有一個形參a,後邊的 var a = 123中有又有一個變量a的聲明由於前邊有一個形參a因此不用重複寫。
AO {
a:1;
變量聲明b:unfined
複製代碼
}
a,d爲函數聲明,b爲函數表達式
AO {
a:function a() {}
變量聲明b:unfined
d:function d() {}
複製代碼
}
這a的值由1變成function a(){},是由於a是一個函數聲明,因此a的值由1變成了函數體。此時預編譯的過程完成。而後再進行編譯,解釋一行執行一行。預編譯過程當中執行的代碼在編譯的時候就不會再執行,如var a = 123; 在執行的過程當中就不會再執行var a,變量聲明被提高優先執行了。因此直接執行a = 123.
全局裏的預編譯與函數裏面的預編譯相似,第一步建立GO對象,第二步找出變量聲明,沒有第三步實參和形參相統一。下面結合具體的例子來看看
function test (){
var a = b = 124;
console.log(window.a); //undefined
console.log(window.b); //124
}
複製代碼
上面的變量b是沒有通過聲明就賦值了124,因此歸window所
var x =1; var y = z = 0;
function add(n) {
return n = n+1;
}
y = add(x);
function add(n) {
return n =n + 3;
}
z = add(x)
複製代碼
請輸出x,y,z的值。最後結果應該x爲1,y和z都是4。由於在預編譯的時候同名的函數會被覆蓋,而不是以咱們的直觀感受來判斷。所以執行因此1,2,4的結果是錯誤的。
//GO{
// test:function test(){};
// a:undefined;
// c:234;
// }
function test (){
console.log(b); //undefined
if(a) {
var b = 100;
}
console.log(b); //undefined
c = 234;
console.log(c);// 234
}
var a;
// AO{
// b:undefined
// }
test();
a= 10;
console.log(c);//234
複製代碼
在沒有執行test()以前 首先生成GO。window === GO
GO{ test:function test(){}; a:undefined; }
執行test生成
AO{ b:undefined } 在test函數中,有一個沒有變量聲明就賦值的變量c,因此應該歸全局全部因而
GO{ test:function test(){}; a:undefined; c:234; }
//GO {
global: undifined
fn: function fn(){}
//}
global = 100;
function fn() {
console.log(global);
global = 200;
conaole.log(global);
var global = 300;
}
// AO {
// global:undefined
// }
fn();
var global;
複製代碼
首先生成GO,而後再生成AO.第一個打印的global由於fn函數裏面就有聲明變量global,不須要到GO裏面去找。因此第一個打印的就是undefined,不是100,第二個打印global=200.
function bar() {
return foo;
foo = 10;
function foo() {
}
var foo = 11;
}
console.log(bar());//function foo(){}
複製代碼
首先console.(bar())就是打印出bar的結果,也就是咱們要知道返回的foo的值。根據預編譯四部曲咱們知道函數聲明的優先級最高,因此foo:function foo(){}
console.log(bar());
function bar() {
foo = 10;
function foo(){
}
var foo = 11;
return foo;
}
複製代碼
在最後對foo賦值11,因此打印出來不是函數聲明而是11.可能有些掘友會問foo是一個局部變量,爲何能夠在全局裏面輸出呢?那是由於全局裏訪問的是全局裏面的bar函數,foo是函數裏面的一個變量。
做用域鏈與原型鏈不一樣,做用域鏈[[scope]],這裏面scope的第一位存放GO,第二位存放AO。做用域鏈是執行期上下文的集合。
函數被執行時會建立一個執行期上下文,一個執行期上下文定義了一個函數執行時的環境,函數每次執行時的執行期上下文都是獨一無二的,因此屢次調用一個函數會致使建立多個執行期上下文,函數每次執行時,都會把新生成的執行期上下文填充到做用域鏈的最頂端。函數執行完畢,執行期上下文被銷燬。
從做用域鏈頂端依次向下查找。
function a() {
function b() {
function c() {}
c();
}
b();
}
a();
複製代碼
這個例子裏面有abc三個函數,函數定義的時候產生GO,函數執行前產生GO.做用域鏈以下
a defined a.[[scope]] ---> 0:GO
a doing a.[[scope]] ---> 0: aAO //aAO表示a的AO
1: GO
b defined b.[[scope]] ---> 0: aAO
1: GO
b doing b.[[scope]] ---> 0: bAO
1: aAO
2: GO
c defined c.[[scope]] ---> 0: bAO
1: aAO
2: GO
c doing c.[[scope]] ---> 0: cAO
1: bAO
2: aAO
3: GO
複製代碼
1.當即執行函數執行完就能夠被銷燬,節省空間針對初始化功能的函數
//(function () {})()
//(function (){}()) //w3c 建議的方法
複製代碼
上面的括號就是將函數聲明變成了函數表達式才能夠被執行,至於爲何不寫函數名是由於當即函數執行完後函數名被銷燬,這兩個特色你可能仍是不明白,不要着急,看完了當即函數的2,3兩點再回來看第一條就明白了。
<script>
function test(a, b, c) {
var a = 123;
}(1, 2, 3)
</script>
複製代碼
最後控制檯會報錯,並且是低級的語法解析錯誤。截圖以下。由於只有表達式纔可以被執行符號()執行。而它只是函數聲明因此不能被執行。
如:123爲數字表達式,1233+234也叫表達式,不是說只有等於號才叫表達式。var test = function test() {}應該拆分紅var test 和 = function test() {}兩部分來理解,前面的是變量聲明,後面的因爲帶有等號因此是函數表達式(變量賦值)。
還有一種函數表達式若是被執行的時候是不會報錯的,如:
<script>
var test = function test() {
var a = 123;
console.log(a);
}()
</script>
複製代碼
與此相似,正負號感嘆號等能夠將函數聲明變成函數表達式,乘號和除號不能夠。
注意 3.可是出現了一個有趣的現象,test函數被執行完以後再找不到test函數了,至關於當即執行函數。
4.一個神奇的例子(阿里巴巴考試題)function test(a, b, c) {
console.log(a + b + c);
}(1, 2, 3);
複製代碼
不會報錯,也不會被執行。按道理是要報錯的,可是語法解析將(1, 2,3);當作了函數表達式。
內部的函數被保存到外部造成閉包。
好比下面的例子裏面,a函數裏面嵌套了b函數,a函數定義和執行以前生成GO和AO,b函數在定義的時候拿到了a的執行期上下文,而且返回了b函數,那麼它的執行期上下文也一同被帶回到函數外部。a函數執行完後它的執行期上下文被銷燬。外部拿到了被銷燬函數a的執行期上下文。
function a() {
var num = 100;
function b() {
num ++;
console.log(num);
}
return b;
}
var demo = a();
demo(); //101
demo(); //102
複製代碼
第一次執行demo輸出101;b函數被返回,第一次demo執行===b(),在b函數執行前會生成b的AO放在做用域鏈的第0位,裏面沒有num,因此王做用域鏈的第一位也就是a的AO裏面找,剛好找到了num=100,num++使aAO{num:101},因此第一次輸出num的值是101。而後銷燬b的AO
第二次執行demo函數輸出102,第二次執行demo也就是執行b函數,b定義的時候拿到的是a的執行期上下文,拿到的是a的勞動成果。b執行以前再次生成b的AO,沒有找到mum變量,因而就去a的AO裏面尋找,此時aAO{num:101},num ++後,aAO{num:102},而後b執行完後銷燬AO。
記得剛開始學習閉包,學完了前男友去接個人時候問我學了什麼,我說閉包。而後他問我閉包的做用我半天答不上來哈哈哈哈,不少初學者剛剛學完以後都是模棱兩可。如今就將閉包的做用進行一次概括總結。
咱們來看一個累加器的例子
function add(){
var count = 0;
function demo(){
count ++;
console.log(count);
}
return demo;
}
var counter = add();
counter();
counter();
counter();
counter();
複製代碼
解析 在這個例子裏面,add函數裏面的demo函數被返回到外部,也就是add函數執行完被銷燬的時候,count變量的值卻被帶到了外部,而後執行一次counter後count的值就加1,實現了累加器的功能。counter調一次就加一次。
閉包能夠當一個隱式存儲結構,咱們來看一個吃香蕉的例子
function eater() {
var food = "";
var obj{
eat : function() {
console.log("i am eating" + food);
food = "";
}
push : function() {
food = myFood;
}
return obj;
}
}
var eater1 = eater();
eater1.push("香蕉");
eater1.eat()// i am eating 香蕉
複製代碼
解析 這裏面返回來了一個obj對象,該對象裏面有兩個函數也一塊兒被返回保存到外部,food這個變量能夠push函數使用,也能夠eat函數使用。兩個函數均可以對它的值進行操做,因此說food至關於一個隱式的存儲體。
調用new的時候 1.在函數體最前面隱式加上this = {} 2.執行this.xxx = xxx 3.隱式返回this
function Person(name, height) {
var that = {};
that.name = name;
that.height = height;
return that;
}
var person = Person('eee',189);
var person = Person('xx', 120)
複製代碼
原始值能夠操做屬性,隱式三段式
//包裝類
var num = 4;
num.len = 3;
//new Number(4).len = 3; delete
//
//new Number(4).len
console.log(num.len);
複製代碼
原始值沒有屬性和方法的,至於爲何可以調用是通過了一個包裝類的過程。可是數組有length屬性,數組length屬性能夠被修改。
這篇比較長,並且不是同一個時間段寫的,不夠好的地方請各位看官指出,以爲個人分享有用的話,歡迎你們關注點贊鴨,後面我將會對js中磨人的this指向作一個全面的總結~~~
最後祝祖國生日快樂,永遠和平,繁榮昌盛鴨~~~