深刻理解javascript原型和閉包系列 對原型和閉包等相關知識的講解,由淺入深,通俗易懂,每一個字都值得細細研究。javascript
1、一切都是對象html
1. typeof操做符輸出6種類型:string boolean number undefined function object
2. 數組、null object都是object類型
3. 對象:若干屬性的集合。js中,數組是對象,函數是對象,對象是對象
4. 函數和對象的關係:
1. 函數生成對象:經過new構造函數生成實例對象
2. 函數是一種對象:
1. 全部的函數都有一個prototype(原型)屬性,是一個對象,這個對象中默認的存在一個constructor屬性指向函數自己。
2. 每一個對象都有一個隱藏的屬性:`__proto__`,指向建立這個對象的構造函數的prototype
3. 函數也是對象,固然也有`__proto__`屬性,指向Function.prototype(大寫Function的原型)
4. 總結:
1. 自定義構造函數(也是對象)的`__proto__`指向Function.prototype;
2. Object.`__proto__`也指向Function.protorype;
3. Function.`__proto__`指向Function.prototype(Function這個對象,固然是被Function函數自身建立的,因此指向自身的prototype)
5. Function.prototype這個對象,是被Object建立的對象,它的隱藏`__proto__`屬性,指向Object.prototype,最後,Object.prototype指向Null!前端
2、 繼承
1. 訪問一個對象的屬性時,先在基本屬性中查找,若是沒有找到,沿着`__proto__`這個屬性(指向構造函數的prototype)往上找
2. 實際應用中如何區分一個屬性是自有的仍是在原型中的呢?——obj.hasOwnProperty(proName)
3.每一個函數都有call、apply方法,length、arguments等屬性,確定是繼承的,都是繼承自Function.prototype
4. 原型的靈活性:
在Java和C#中,你能夠簡單的理解class是一個模子,對象就是被這個模子壓出來的一批一批月餅。
壓個啥樣,就得是個啥樣,不能隨便動,動一動就壞了。
而在javascript中,就沒有模子了,月餅被換成了麪糰,你能夠捏成本身想要的樣子。
3、執行上下文
1. **一段代碼**在正真一行一行執行前,瀏覽器事先作了準備工做:
1. 對變量的**聲明**,但不賦值,賦值是在執行到賦值語句時進行的
2. 直接對this進行**賦值**
3. 函數:
1. 函數聲明:function foo(){} //把函數名賦值了!
2. 函數表達式: var foo = function(){} //因爲是var的變量,與第一種狀況同樣,只聲明,不賦值
4. 總結:
1. var出來的變量,函數表達式,只聲明,不賦值,默認undefined
2. this:賦值
3. 函數聲明:賦值
2. 執行**代碼段**前的這個準備工做,就叫作執行上下文/上下文環境/執行環境
3. 這個**代碼段**其實又分爲三種狀況:
1. 全局代碼
2. eval()
3. 函數【重點↓】
4. 函數代碼段中的執行上下文有這些數據:
1. var 出來的變量只聲明,沒賦值
2. this和函數聲明已經賦值
3. 特殊的地方:
1. arguments變量和函數的參數都已經被賦值java
function foo(x){ console.log(arguments); console.log(x); } foo(10); //在進入函數體內部開始執行以前,函數體內部的arguments和參數x已經被賦值了
2. **自由變量的取值做用域:賦值**數組
5. 由此可知,函數每調用一次,都會產生一個新的執行上下文環境,由於不一樣的調用可能會有不一樣的參數傳入
6. 函數在定義的時候(不是調用的時候),就已經肯定了函數體內部**自由變量**的**做用域**瀏覽器
var a =10; function fn(){ console.log(a); //本函數在建立的時候就決定了這個a要取值的做用域:全局做用域 } function bar(f){ var a = 20; f(); } bar(fn); //10
7. 關於this的一點小問題閉包
var obj = { x:10, fn:function(){ function f(){ console.log(this); console.log(this.x); } f(); } }; obj.fn(); //this指window /* obj.fn保存了指向 function(){ function f(){ console.log(this); console.log(this.x); } f(); } 的指針,加一個(): =>obj.fn()表示開始調用: function f(){ console.log(this); console.log(this.x); } f(); 這個f函數在window環境調用執行,因此this指向window */
8. 上下文環境的執行順序
1. 執行全局代碼時,會產生一個全局上下文環境。
2. 每次調用函數時,會產生函數內部的執行上下文環境。
3. 當函數調用完成時,這個上下文環境以及其中的數據都會被消除,再從新回到全局上下文環境。
4. 處於活動狀態的執行上下文環境只有一個。app
var a=10; function bar(x){ var b = 5; fn(x+b); }; function fn(y){ var c = 5; console.log(y+c); }; bar(10); ----------------解析---------------------- /* 1. 產生全局上下文執行環境,該聲明的聲明,該賦值的賦值 1. a = 10 ; fn = function(); bar = function() 2. 到調用bar(10)函數時,進入bar函數內部,產生函數內部的執行上下文環境,該聲明的聲明,該賦值的賦值 1. b = 5; x = 10; arguments = [10] 3. 接着調用fn函數,進入fn函數內部,產生函數內部的執行上下文環境,該聲明的聲明,該賦值的賦值 2. c = 5 ; y = 15; 4. fn函數執行完畢,它對應的上下文執行環境都會被銷燬,裏面保存的數據都沒有了 5. bar函數一樣如此,它對應的上下文執行環境都會被銷燬,數據清除 */
4、自由變量和做用域
1. 做用域:javascript除了全局做用域以外,只有函數能夠產生做用域。因此,在聲明變量時,全局代碼要在代碼前端聲明,函數中要在函數體一開始就聲明好。除了這兩個地方,其餘地方都不要出現變量聲明。並且建議用「單var」形式。
2. 做用域就是一個地盤,最大的用處就是隔離變量,使不一樣做用域下相同的變量名不會產生衝突
3. 每一個函數都會建立本身的做用域,做用域在函數**定義**的時候就已經肯定了,而不是在調用的時候肯定函數
var a = 10,b=20; function fn(x){ var a =100, c = 300; function bar(x){ var a = 1000,d = 4000; console.log(x); console.log(a); console.log(b); console.log(c); console.log(d); } bar(100); bar(200); } fn(10); ----------------------解析:-------------------------- /* 1. 產生全局上下文執行環境,該聲明的聲明,該賦值的賦值 1. a = 10; b=20; fn = function(); 2. 到調用fn(10)函數時,進入fn函數內部,產生函數內部的執行上下文環境,該聲明的聲明,該賦值的賦值 1. a = 100 ; c = 300 ; x = 10 ; arguments = [10] ; bar = function() 3. 到調用bar(100)函數時,進入bar函數內部,產生函數內部的執行上下文環境,該聲明的聲明,該賦值的賦值 1. a = 1000 ; d = 4000 ; x = 100; 4. 到調用bar(200)函數時,進入bar函數內部,產生函數內部的執行上下文環境,該聲明的聲明,該賦值的賦值 1. a = 1000 ; d = 4000 ; x = 200; 2. 與bar(100)函數相比較,同一個做用域,不一樣的調用,產生不一樣的執行上下文環境 5. 總結: 1. 做用域只是一個「地盤」,一個抽象的概念,其中沒有變量。 2. 要經過做用域對應的執行上下文環境來獲取變量的值。 3. 同一個做用域下,不一樣的調用會產生不一樣的執行上下文環境,繼而產生不一樣的變量的值。 4. 做用域中變量的值是在執行過程當中產生的肯定的,而做用域倒是在函數建立時就肯定了。 5. 因此,若是要查找一個做用域下某個變量的值,就須要找到這個做用域對應的執行上下文環境,再在其中尋找變量的值。 */
4. 自由變量:在函數內部中使用的變量x,卻沒有在函數內部聲明,也就是在其餘做用域中聲明的,對這個函數來講,x就是自由變量this
var x= 10; function fn(){ var b = 20; console.log(x+b); //x就是自由變量 } /* 1. b從本做用域中取,x就要到另外的做用域中取, 2. 是到fn的父級做用域取嗎?不是的! */ --------------------分割線--------------------------- var x= 10; function fn(){ console.log(x); } function show(f){ var x = 20; function foo(){ f(); //10 } foo(); } show(fn); //所以:要到建立這個函數的那個做用域中取值——是「建立」,而不是「調用」,切記切記。 //建立fn函數的做用域是window,因此,x = 10; /* 總結一下: 1. 先在當前做用域查找變量x,沒有找到則繼續; 2. 若是當前做用域是全局做用域,則證實x未定義,結束;不然繼續; 3. 不是全局做用域,那就是函數做用域,將**建立該函數的做用域**做爲當前做用域; 4. 跳到第一步循環…… */
5. 必須屢次重複強調:要去**建立**這個函數的做用域取值,而不是「父做用域」。
5、最後的閉包:
1. 通常狀況下,當一個函數被調用完成以後,其執行上下文環境將被銷燬,其中的變量也會被同時銷燬。
2. 但有些狀況下,函數調用完,其執行上下文環境沒有銷燬,其中的變量也還在內存中,這就是閉包的核心內容。
function fn(){ var max = 10 ; return function bar(x){ if(x>max){console.log(x);} }; } var f1 = fn(),max = 100 ; f1(15); ----------------解析:--------------------- /* 1. 執行全部代碼前,先生成全局上下文執行環境,該聲明的聲明,該賦值的賦值 1. fn = function() ; f1 = undefined ; max = 100; 2. 執行fn()函數,進入fn函數的上下文執行環境,聲明、賦值 1. max = 10 ; bar = function() ; 2. 執行fn函數裏的語句,把bar函數return出去,賦值給fn 3. 【重點】此時,fn函數執行完畢了,按理來講,fn的上下文執行環境應該被銷燬,變量從內存清除,但在這裏不能這麼作!! 1. 由於執行fn函數的過程當中返回了一個bar函數,這個bar函數的特別之處在於, 它建立了一個本身獨立的做用域。 2. 在bar函數內部,有一個max自由變量,若是本身沒有,就要到建立這個函數的做用域中找,就是fn函數做用域中的max 3. 因此,這個max不能被銷燬,fn函數的上下文執行環境不能被銷燬,依然存在於執行棧中。 4. 也就是說,執行完var f1 = fn() ,max = 100;以後,全局上下文環境將變爲活動狀態,可是fn()上下文環境依然會在執行上下文棧中。 5. 建立bar函數是在執行fn函數時建立的。fn()早就執行結束了,可是fn()執行上下文環境還存在與棧中,裏面的變量聲明、賦值都還在, 6. 所以bar(15)時,max能夠查找到。若是fn()上下文環境銷燬了,那麼max就找不到值了。 */
6、上下文環境和做用域的關係 1. 上下文環境:就是一個對象,裏面保存了許多屬性,全部變量都在這個對象裏面存着。對於函數來講,上下文環境是在調用函數的時候建立 2. 做用域:是一個地盤,一個抽象的概念,規定了變量起做用的範圍。除了全局做用域以外只有函數才能建立做用域,做用域在函數定義時肯定,而不是在函數調用時肯定。 3. 關係: 1. 要經過做用域對應的執行上下文環境來獲取變量的值。 2. 同一個做用域下,不一樣的調用會產生不一樣的執行上下文環境,繼而產生不一樣的變量的值。 3. 做用域中變量的值是在執行過程當中產生的肯定的,而做用域倒是在函數建立時就肯定了。 4. 若是要查找一個做用域下某個變量的值,就須要找到這個做用域對應的執行上下文環境,再在其中尋找變量的值。