第十五章 闭包

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

闭包

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

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

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);   // 
123
} 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

你可能感兴趣的:(第十五章 闭包)