匿名函數(shù)這里就不做介紹了
閉包
什么是閉包,可以把閉包理解為,一個函數(shù)可以訪問另外一個函數(shù)中的變量。閉包中的變量會被一直保存在內(nèi)存中,可以避免使用全局變量。(全局變量污染導(dǎo)致應(yīng)用不可預(yù)測,所以推薦使用私有的,封裝的局部變量)
一個栗子,做一個累加器:
var num = 100;
function sum() {
num++;
}
sum();
console.log(num); // 101
sum();
console.log(num); // 102
sum();
console.log(num); // 103
在全局環(huán)境下的變量實現(xiàn)累加很容易,但是如果要局部變量實現(xiàn)累加呢?
function sum() {
var num = 100;
num++;
return num;
}
console.log(sum()); // 101
console.log(sum()); // 101
console.log(sum()); // 101
每一次函數(shù)執(zhí)行,num都被初始化。所以無法完成累加。正確寫法如下:
function foo() {
var age = 100;
return function () {
age++;
return age;
};
}
var f = foo();
console.log(f());
// 101
console.log(f());
// 102
console.log(f());
// 103
以上方法實現(xiàn)了局部變量駐留在內(nèi)存中而實現(xiàn)累加。以上方法也就是閉包,閉包作用域返回的局部變量資源不會被立刻銷毀,所以非必要情況不要使用閉包,會造成資源問題。如果閉包使用結(jié)束,可以用f=null可以解除引用,等待垃圾回收。
循環(huán)里的匿名函數(shù)取值問題
function foo () {
var arr = [];
for (var i = 0; i < 10; i++) {
arr[i] = function () {
return i;
};
}
return arr;
}
var f = foo();
console.log(f[0]);
// demo.js:15 ? () { return i;} 返回了是個匿名函數(shù)
for(var i = 0; i < 10; i++) {
console.log(f[i]());
}
// 打印出10個10
打印出10個10,可是我們想要的是0-9十個數(shù)字。實際上,函數(shù)執(zhí)行結(jié)束的時候,循環(huán)已經(jīng)執(zhí)行完畢了。所以,i最終的值為i++,也就是9++,是10。那么怎么解決這個問題呢
方法1(去掉匿名函數(shù)):
function foo () {
var arr = [];
for (var i = 0; i < 10; i++) {
arr[i] = i;
}
return arr;
}
var f = foo();
console.log(f);
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
方法2:
function foo () {
var arr = [];
for (var i = 0; i < 10; i++) {
arr[i] = (function (num) { //通過自我及時執(zhí)行
return num;
})(i);
}
return arr;
}
var f = foo();
console.log(f);
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
方法3(常用):
function foo () {
var arr = [];
for (var i = 0; i < 10; i++) {
arr[i] = function (num) {
return function () {
return num; //內(nèi)存中常駐一個變量
};
}(i);
}
return arr;
}
var f = foo();
for(var i = 0; i < 10; i++) {
console.log(f[i]());
}
// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
this對象
閉包在運行的時候this指向window
var obj = {
getThis: function () {
return function () {
return this;
};
}
};
console.log(obj.getThis()());
// window 函數(shù)返回的是一個函數(shù),函數(shù)再括號執(zhí)行就是window打點調(diào)用。
var user = 'the window';
var obj = {
user: 'the obj',
getUser: function () {
return function () {
return this.user;
};
}
};
console.log(obj.getUser()());
// the window
如果想改變閉包種this指向,有兩種方法:
1. call/apply
var user = 'the window';
var obj = {
user: 'the obj',
getUser: function () {
return function () {
return this.user;
};
}
};
console.log(obj.getUser().call(obj));
// the obj
2. 作用域
var user = 'the window';
var obj = {
user: 'the obj',
getUser: function () {
var that = this;
return function () {
return that.user;
};
}
};
console.log(obj.getUser()());
// the obj
內(nèi)存泄露
閉包會導(dǎo)致內(nèi)存泄露問題,也就是無法銷毀駐留在內(nèi)存中的元素。
window.onload = function () {
function box() {
var div = document.getElementById('box');
div.onclick = function () {
console.log(div.innerHTML); // 123
};
console.log(div); // <div id="box">123</div>
}
box();
};
解除引用
window.onload = function () {
function box() {
var div = document.getElementById('box');
var text = div.innerHTML;
div.onclick = function () {
console.log(text); // 123
};
div = null;
console.log(div); // null
}
box();
};
模仿塊級作用域
js沒有塊級作用域概念
function f1() {
for (var i = 0; i < 5; i++) {
}
console.log(i); //5 for循環(huán)外面依然可以調(diào)用到i變量
var i;
console.log(i); // 5 依然是5,再次聲明不賦值無效
}
f1(); // 5
使用塊級作用域
function f1() {
(function(){ // 立即執(zhí)行函數(shù)構(gòu)成私有作用域
for (var i = 0; i < 5; i++) {
console.log(i);
}
})(); // 出去這個作用域,變量立刻被銷毀。
console.log(i);
}
f1();
// 0 1 2 3 4 demo.js:7 Uncaught ReferenceError: i is not defined
使用了塊級作用域,匿名函數(shù)中定義的任何變量,都會在執(zhí)行結(jié)束的時候被銷毀。這種技術(shù)經(jīng)常在全局作用域中被用在函數(shù)外部。從而限制向全局作用域中添加過多的變量和函數(shù)。一般來說,我們都應(yīng)該盡可能少向全局作用局添加函數(shù)和變量。在大型項目中,多人開發(fā),過多的全局變量和函數(shù)很容易命名沖突,引起災(zāi)難性后果。如果采用塊級作用域,每個開發(fā)者使用自己的變量,不必?fù)?dān)心影響全局作用域。
私有作用域演示:
(function () {
// 全局私有作用域
var age = 100;
alert(age);
})();
alert(age); //a ge is not defined
相當(dāng)于:
var age = 100;
alert(age); //100
age = null;
alert(age); //null
私有變量
js沒有私有屬性概念,所有的屬性都是公有的。但是,又一個私有變量概念。任何函數(shù)在函數(shù)中定義的變量,都可以認(rèn)為是私有變量,因為不能再函數(shù)的外部訪問這些變量。
function f1() {
var age = 100; // 私有變量 外部無法訪問
}
function Fn() {
this.age = 100; // 公有屬性
this.run = function () { //公有方法
return 'runing';
};
}
var f = new Fn();
console.log(f.age);
// 100
console.log(f.run());
// runing
function Fn() {
var age = 100; //私有屬性
function run() { //私有方法
return 'runing';
};
}
var f = new Fn();
console.log(f.age);
// undefined
console.log(f.run());
// f.run is not a function
訪問方法
function Fn() {
var age = 100;
function run() {
return 'runing';
}
this.publicGo = function () { //對外可見的公共接口
return age + run();
};
this.getAge = function () { //對外可見的公共接口
return age;
};
}
var f = new Fn();
console.log(f.publicGo());
// 100runing
console.log(f.getAge());
// 100
靜態(tài)私有變量
共享于不同對象的屬性:
(function () {
var user = ''; //私有變量
Box = function (value) {
user = value;
};
Box.prototype.getUser = function () {
return user;
}
})();
var xiaoming = new Box('xiaoming');
console.log(xiaoming.getUser()); //xiaoming
var xiaobai = new Box('xiaobai');
console.log(xiaobai.getUser()); //xiaobai
console.log(xiaoming.getUser()); //xiaobai
模塊模式
之前采用的都是構(gòu)造函數(shù)方式來創(chuàng)建私有變量和特權(quán)方法,那么對象字面量方式采用模塊方式來創(chuàng)建。
// 單例對象 就是永遠(yuǎn)只實例化一次,就是字面量方式聲明對象
var obj = { // 第一次實例化,無法第二次實例化,那么就是單例
name: 'xiaoming',
run: function () {
return 'is runing..';
}
}
字面量方式私有化變量函數(shù)
var obj = function () {
var name = 'xiaoming';
function run() {
return 'is runing';
}
return {
getName: function () {
return name + ' ' + run();
}
};
}();
console.log(obj.getName());
// xiaoming is runing