1.函數的隱式屬性
- [[scope]]:就是常說的作用域,其中存儲了運行期上下文的集合
- 作用域鏈:[[scope]]中所存儲的執行期上下文對象的集合,這個集合呈鏈式鏈接,將這種鏈式鏈接叫做作用域鏈。
每個JavaScript函數都是一個對象,對象中有些屬性屬性可以訪問,但是有些不可以,這些屬性僅供JavaScript引擎存取,[[scope]]就是其中一個。
運行期上下文:函數每次執行時都會產生一個上下文,這個上下文AO爲即時的,因此執行完就被銷燬。
查找變量:從作用域鏈的頂端依次向下查找。
2.作用域鏈
function a() {
}
var glob = 100;
a();
//a被定義時 a. [[scope]] --> 0:GO{}
//a被執行時 a. [[scope]] --> 0:AO{}
-->1:GO{}
function a() {
function b() {
var b = 234;
}
var a = 123;
b();
}
var glob = 100;
a();
//a被定義時 a. [[scope]] --> 0:GO{}
//a被執行時 a. [[scope]] --> 0:a.AO{}
-->1:GO{}
//b被定義時 b. [[scope]] --> 0:a.AO{}
-->1:GO{}
//b被執行時 b. [[scope]] -->0:b.AO{}
--> 1:a.AO{}
-->2:GO{}
3.閉包的產生
閉包:當內部函數被保存到外部時,將會生成閉包。閉包會導致原有作用域鏈不釋放,造成內存泄漏。
閉包不是隻有外部函數最後加一個return內部函數語句才能產生
也可以通過設置全局變量等於內部函數產生
總而言之,只要內部函數活得比外部函數長,就可以產生閉包
//全局變量等於內部函數的方法
var demo;
function test() {
var abc = 100;
function a() {
console.log(abc);
}
demo = a;
}
test(); //若沒有這個語句則會報錯沒有定義demo函數
//只有test執行,才能將a函數賦值給全局變量
demo();
//輸出 100
4.閉包的優點:
- 實現公有變量
eg:函數累加器(不依賴於外部變量,且能反覆執行,即每次執行結果都不一樣)
獨立的功能不能依賴於外部變量
function add() {
var num = 0;
function a() {
console.log(++num);
}
return a;
}
var myAdd = add();
myAdd();
myAdd();
myAdd();
//輸出 1 2 3
- 可以做緩存
function test() {
var food = "apple";
var obj = {
eatFood : function() {
if(food != "") {
console.log("I am eating" + food);
food = "";
} else {
console.log("There is nothing!");
}
},
pushFood : function (myFood) {
food = myFood;
}
}
return obj;
}
var person = test();
person.eatFood();
person.eatFood();
person.pushFood("banana");
person.eatFood();
//輸出
I am eating apple
There is nothing!
I am eating banana
- 變量私有化
function Deng(name, wife) {
//這個變量在函數執行時會在函數的執行上下文中
var prepareWife = "xiaozhang";
this.name = name;
this.wife = wife;
this.divorce = function() {
this.wife = prepareWife;
//注意這個prepareWife前沒有加this
}
}
var deng = new Deng('deng', 'xiaoliu');
//控制檯輸入 deng.divorce
//再輸入 deng.wife
//輸出 xiaozhang
//輸入 deng.prepareWife
//返回 undefined
//因爲這個在構造函數中定義的變量不是對象的方法,因此不能訪問
//這樣就形成了私有化變量
在對象方法中能夠直接使用在對象中定義的變量是因爲 new構造函數後,最後一步會返回構造函數對象,因此會形成閉包,就可以使用函數中定義的變量。
- 模塊化開發,防治污染全局變量(參見學習JS第七節命名空間)
5.立即執行函數
定義:多用於只執行一次的函數,因爲執行一次後立即銷燬,避免佔用過多內存(初始化功能的函數)
//括號的兩種立即執行函數
(function () {
}) ()
//多用這種
(function () {
} ())
//括號優先級最高,因此先將其轉換成函數表達式,因此兩種方式都對
只有表達式才能被執行符號執行
能被執行符號執行的表達式,會放棄名字
!+ - && ()操作符可以將函數聲明變成表達式
function test() {
var a = 123;
}()
//報錯,不能執行
//因爲只是函數聲明,不是表達式
var test = function () {
}()
//可以執行
//執行完之後再輸 test()會報錯
//因爲此時立即執行函數已經銷燬,此時test是個空的變量
var test = function() {
}();
//輸入:test
//返回:undefined
//因爲 被執行符號執行的表達式,會放棄自己的名字
+ var test = function() {
}();
//可以執行
真題解析
var x = 1;
if(function f() {}) {
x += typeof f;
}
console.log(x);
//輸出 1undefined
//括號會將f變成表達式,就不是函數定義了,因此f就沒有被定義
6.立即執行函數與閉包的聯繫
function test() {
var arr = [];
for(var i = 0; i < 10; i++) {
arr[i] = function() {
console.log(i);
//arr裏面保存的函數的 [[scope]]包括
//函數test的AO:{ i=10 }
//全局的GO
}
}
return arr;
}
var myArr = test();
for(var j = 0; j < 10; j++) {
myArr[j]();
}
//輸出 10 10 10 10 10 10 10 10 10 10 10
//加上立即執行函數後
function test() {
var arr = [];
for(var i = 0; i < 10; i++) {
(function (j) {
arr[j] = function() {
console.log(j);
}
//arr裏面保存的函數的 [[scope]]包括
//外層立即執行函數的AO:{ 形參j=實參i }
//函數test的AO:{ i=10 }
//全局的GO
}(i));
}
return arr;
}
var myArr = test();
for(var k = 0; k < 10; k++) {
myArr[k]();
}
//輸出 0 1 2 3 4 5 6 7 8 9
7.逗號表達式:
var a = (表達式1,表達式2);
//a被賦值爲表達式2