第十五章 闭包

匿名函数这里就不做介绍了

闭包

什么是闭包,可以把闭包理解为,一个函数可以访问另外一个函数中的变量。闭包中的变量会被一直保存在内存中,可以避免使用全局变量。(全局变量污染导致应用不可预测,所以推荐使用私有的,封装的局部变量)

一个栗子,做一个累加器:

var num = 100;

function sum() {
    num++;
}

sum();
console.log(num);   // 101
sum();
console.log(num);   // 102
sum();
console.log(num);   // 103

在全局环境下的变量实现累加很容易,但是如果要局部变量实现累加呢?

function sum() {
    var num = 100;
    num++;
    return num;
}


console.log(sum());   // 101
console.log(sum());   // 101
console.log(sum());   // 101

每一次函数执行,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

以上方法实现了局部变量驻留在内存中而实现累加。以上方法也就是闭包,闭包作用域返回的局部变量资源不会被立刻销毁,所以非必要情况不要使用闭包,会造成资源问题。如果闭包使用结束,可以用f=null可以解除引用,等待垃圾回收。

循环里的匿名函数取值问题

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;} 返回了是个匿名函数
for(var i = 0; i < 10; i++) {
    console.log(f[i]());
}
// 打印出10个10

打印出10个10,可是我们想要的是0-9十个数字。实际上,函数执行结束的时候,循环已经执行完毕了。所以,i最终的值为i++,也就是9++,是10。那么怎么解决这个问题呢

方法1(去掉匿名函数):

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) {      //通过自我及时执行
            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;     //内存中常驻一个变量
            };
        }(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 函数返回的是一个函数,函数再括号执行就是window打点调用。
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

内存泄露

闭包会导致内存泄露问题,也就是无法销毁驻留在内存中的元素。

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循环外面依然可以调用到i变量
    var i;
    console.log(i); // 5 依然是5,再次声明不赋值无效
}

f1(); // 5

使用块级作用域

function f1() {
    (function(){    // 立即执行函数构成私有作用域
        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

使用了块级作用域,匿名函数中定义的任何变量,都会在执行结束的时候被销毁。这种技术经常在全局作用域中被用在函数外部。从而限制向全局作用域中添加过多的变量和函数。一般来说,我们都应该尽可能少向全局作用局添加函数和变量。在大型项目中,多人开发,过多的全局变量和函数很容易命名冲突,引起灾难性后果。如果采用块级作用域,每个开发者使用自己的变量,不必担心影响全局作用域。

私有作用域演示:

(function () {
    // 全局私有作用域
    var age = 100;
    alert(age);
})();

alert(age); //a ge is not defined

相当于:

var age = 100;
alert(age); //100
age = null;
alert(age); //null

私有变量

js没有私有属性概念,所有的属性都是公有的。但是,又一个私有变量概念。任何函数在函数中定义的变量,都可以认为是私有变量,因为不能再函数的外部访问这些变量。

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

静态私有变量

共享于不同对象的属性:

(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

模块模式

之前采用的都是构造函数方式来创建私有变量和特权方法,那么对象字面量方式采用模块方式来创建。

// 单例对象   就是永远只实例化一次,就是字面量方式声明对象
var obj = {     // 第一次实例化,无法第二次实例化,那么就是单例
    name: 'xiaoming',
    run: function () {
        return 'is runing..';
    }
}

字面量方式私有化变量函数

var obj = function () {
    var name = 'xiaoming';
    function run() {
        return 'is runing';
    }
    return {
        getName: function () {
            return name + ' ' + run();
        }
    };
}();

console.log(obj.getName());
// xiaoming is runing
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,490评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,581评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,830评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,957评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,974评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,754评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,464评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,847评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,995评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,137评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,819评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,482评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,023评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,149评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,409评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,086评论 2 355

推荐阅读更多精彩内容