在介紹做用域特性以前,咱們先來回顧一下js的執行上下文(詳細介紹:https://www.jianshu.com/p/8f19e45fd1f1)
一段<script>或者一個函數以內,都會去生成一個執行環境(execution context,EC)或稱之爲執行上下文。當一段JS代碼執行的時候,JS解釋器會經過兩個階段去產生一個EC。
1.建立階段
o建立變量對象VO
o設置[[Scope]]屬性的值: (指向做用域鏈)
o設置this的值: (指向一個EC,默認undefined)
2.初始化變量對象(即設置變量的值、函數的引用),而後解釋/執行代碼。
注意事項:
全局:針對一段<script>,它會生成一個全局的執行上下文,在執行以前會先去把「變量定義」和「函數聲明」拿出來裝在對象VO。
函數:針對一個函數,它會生成一個函數執行上下文,在函數執行以前會先把「變量定義」、「函數聲明」、「arguments」拿出來封裝在對象VO裏邊。javascript
先執行變量定義後,變量值默認爲undefined;
函數聲明後不會當即執行,若是輸出一下函數,能看到函數體全部的代碼;而須要調用函數後(例如:fn();),函數體中的代碼纔會執行.
arguments 是JavaScript裏的一個內置對象,全部的函數都有屬於本身的一個arguments對象,它包括了函所要調用的參數。
函數申明和函數表達式的區別:
function fn(name){} 這是一個函數聲明,而var a = function(){}這是函數表達式;
若是是函數表達式的寫法,在執行以前先拿出來處理的就是var a;了,這時候a只是一個廣泛變量(值爲undefined)不是函數,這點要注意。html
做用域指的是變量的適用範圍。(詳細介紹:https://www.w3cschool.cn/javascript_prototype/y2cjfozt.html)java
js無塊級做用域(不過es6中let定義的變量只在代碼塊中有效,也就有了塊級做用域)es6
if (true){ var name = 'zhao'; } console.log(name); //zhao,這裏依然能夠獲取到上面代碼塊裏邊的變量。
只有函數和全局做用域數組
var a = 100 function fn(){ var a =200; console.log('fn',a); // fn 200 } console.log('global',a); // global 100,獲取不到函數內部的變量。
var a = 100; function fn1(){ var b =200; function fn2(){ var c =300; // 當前做用域沒有定義的變量,即「自由變量」,a和b都是 console.log(a); // 100 console.log(b); // 200 console.log(c); // 300 } fn2(); } fn1();
PS:自由變量由於在當前做用域沒有定義,因此只能去父級做用域查找.
(注意:父級做用域是在函數「定義」的時候就已經肯定了的,自由變量這種一層層往父級查找的鏈式結構也就是「做用域鏈」)閉包
閉包其實是對js做用域鏈的一種應用形式;主要利用了做用域鏈從父級函數獲取變量的特性,從外部調用父級函數局部變量而且互不污染,或者子函數循環利用父級函數的變量達到某種計算用途。app
閉包特性一:調用函數內部的變量,利用做用域鏈原理,能獲取函數fn1的父級函數的局部變量進行計算。
閉包特性二:讓這些變量的值始終保持在內存中,不會再fn1調用後被自動清除,再次執行fn1的時候還能繼續上一次的計算。
注意:fn2建立的時候與fn1是相互獨立的,其中的變量a也互不影響,比如父親給每一個孩子都準備了一個新的存錢罐。ide
場景一:函數做爲返回值函數
function F1(){ var a =100; // 返回一個函數(函數做爲返回值),爲了閱讀方便也能夠先定義一個函數,而後retrun函數名。 return function(){ console.log(a); //自由變量,去父做用域尋找 } // 另外放回函數的形式也不僅是return,一樣在這裏以事件的形式綁定在Dom上也是同樣,再或者調用其餘方法傳遞一個函數出去。 } var f1 = F1(); var a=200; f1(); // 100
場景二:函數做爲參數傳遞this
var b=111; function f1(){ var a =100; console.log(a,b); } function f2(fn){ var a =200; var b=222; fn(); } f2(f1); // 100,111 //而且若是a在F1()沒有定義的話,就會報錯而不是獲取f2中的a,由於它的定義時的父級做用域及之上(即全局做用域)都沒有定義a;
應用舉例一:setTimeout中的參數傳遞。
因爲直接setTimeout(function(){},200)這麼寫的話,沒辦法傳參,因此能夠用閉包的形式來作。
var Fn=function(num){ var a=10; return function(){ var b=0; a+=num; b+=num; console.log(a,b); } } var fn1=Fn(1); var fn2=Fn(1); //閉包特性一:調用函數內部的變量,利用做用域鏈原理,能獲取函數fn1的父級函數的局部變量a進行計算。 setTimeout(fn1,200); //輸出的a=11,b=1; //閉包特性二:讓變量a的值始終保持在內存中,不會在fn1調用後被自動清除,再次執行fn1的時候還能繼續上一次的計算。 setTimeout(fn1,500); //輸出的a=12,b=1; //特性二的注意事項:fn2建立的時候與fn1是相互獨立的,對應的父級函數Fn的變量a也互不影響,比如父親在每一個孩子出生時都準備了一個新的存錢罐,每一個孩子都用本身的。 setTimeout(fn2,800); //輸出的a=11,b=1;
應用舉例二:建立10個<a>標籤,點擊的時候彈出來對應的序號。
<body> <script type="text/javascript"> for(var i=0;i<10;i++){ (function(i){ var a=document.createElement('a'); a.innerHTML=i+'<br>'; document.body.appendChild(a); a.addEventListener('click',function(e){ e.preventDefault(); //取消默認事件,指a標籤 alert(i); }); })(i); } </script> </body>
核心:this要在執行時才能確認值,定義時沒法確認。
var a = { name: 'A', fn: function (){ console.log(this.name); } } a.fn(); // this === a (即便是b.a.fn(),this也是a) a.fn.call({name: 'B'}); // this === {name: 'B'} var fn1 = a.fn; fn1(); // this === window
this幾種不一樣的運用場景
一、做爲構造函數執行:(例如:new Foo(),this指向這個新對象)
二、做爲對象屬性執行:(this指向對象)
三、做爲普通函數執行:(this指向window)
四、call()、apply()、bind():(this指向傳入的第一個對象參數,bind只有一個參數)
參考:https://www.w3cschool.cn/jsnote/jsnote-this.html
call,apply、bind都屬於Function.prototype的一個方法,他們的做用改變函數的調用對象,它是JavaScript引擎內在實現的,由於屬於Function.prototype,因此每一個Function對象實例(就是每一個方法)都有call,apply,bind屬性。既然做爲方法的屬性,那它們的使用就固然是針對方法的了,這幾個方法是容易混淆的。
call,apply的用法差很少,只是參數稍微不一樣;(apply()接收兩個參數,一個是函數運行的做用域(this),另外一個是參數數組。call()方法第一個參數與apply()方法相同,但傳遞給函數的參數必須列舉出來。)
// 以最簡單window對象爲例 function sum(num1, num2) { return num1 + num2; } console.log(sum.call(window, 10, 10)); //20 console.log(sum.apply(window,[10,10])); //20 這兩都至關於window.sum(10,10); // 即語法:foo.call(this, arg1,arg2,arg3) == foo.apply(this, arguments)
而bind的用法有一點差異。(只是傳一個參數對象,而後返回一個函數給接受的變量,再另外調用執行。)
window.color = "red"; var o = { color: "blue" }; function sayColor(){ alert(this.color); } var OSayColor = sayColor.bind(o); OSayColor(); //blue