闭包:就是JS的GC机制引起的,
1、底层:是栈,栈的本质:函数执行时候回分配,函数执行完毕会回收
函数执行完了函数也不会回收,这就是闭包
2、高层:函数当做一个对象来处理:函数在调用的时候,会创建一个对象出来
这个对象就包括函数执行的各种细节(包括是谁调用这个函数,执行到哪里去等等)
demo1:
<input type="button" value="aaa"> <input type="button" value="bbb"> <input type="button" value="ccc">
window.onload = function() { var btn = document.querySelectorAll('input') for (var i = 0; i < btn.length; i++) { (function(i) { btn[i].onclick = function() { alert(i) } })(i) } } // es5,通过函数自调用实现闭包
window.onload = function() { var btn = document.querySelectorAll('input') for (var i = 0; i < btn.length; i++) { btn[i].onclick = function() { alert(i) // 3 } } }
demo2:
function show() { let a = 12; document.onclick = function() { alert('出现a的值:' + a); } } show() // 这就是一个简单的闭包,show()调用完毕以后,没有马上销毁,而是在点击事件中引用了
2、执行环境和作用域
执行环境(execution content)定义了变量或函数有权访问其他数据,决定了他们各自的行为。
注意:全局执行环境是最外围的一个执行环境(被认为是window对象,因此所有全局变量和函数都作为window对象的属性和方法创建的)。
某个执行环境中的所有代码执行完毕以后,该环境销毁,保存在其中的所有变量和函数定义也随之销毁(全局执行环境知道应用程序退出---关闭网页或者浏览器---时才会被销毁)。
当代码在执行时,会创建变量对象的一个作用域链(scope chain)。
作用域的用途就是保证对执行环境有权访问的所有变量和函数的有序访问。
作用域的最前端,始终都是当前执行环境的代码所在环境的变量对象。
作用域链中的下一个变量对象来自(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,一直延续到全局执行环境中。
全局执行环境的变量对象始终都是作用域链中的最后一个对象。
简单理解:
作用域:变量的使用范围。
全局作用域: ①函数之外的执行变量 ②在全局作用域中用var关键字创建的变量为全局变量 ③全局变量的可访问范围是程序的任何地方
局部作用域: ①函数体内的执行环境 ②在函数体内用var创建的变量或者函数的形参被称为局部变量 ③局部变量的可访问范围是仅限于本函数中作用域链
在访问一个变量时,会先从本作用域中去找,如没有找到则向上级作用域中去找,以此类推就构成了作用域链。 内层作用域可以访问外层作用域,反之不行
3、闭包
作用:延长变量的生命周期
function A() { var count = 0; function B() { count ++; console.log(count); } return B; } var C = A(); C();// 1 C();// 2 C();// 3 /** * count是函数A中的一个变量,它的值在函数B中被引用,函数B没执行一次,count的值 * 就在原来的基础上加 1。因此,函数A中的count变量会一直保存在内存中。 */
function fn () { var num = 10; return function fn1 (v) { console.log(num); // 10 num = v; console.log(num); // 100 } } var a = fn(); a(100);
JS中的函数好比是一个黑匣子,他可以获取外界的信息,但是外界无法和它直接读取它里面的内容。
将变量num放入黑匣子,通过return返回给函数外部,而且函数fn外的fn1同名函数和fn内部的fn1函数不会相互影响,这样就增强了函数的封装性。
案例:
<script> function fn() { var result = new Array(); for (var i = 0; i < 10; i++) { result[i] = function () { return i; // i = 10 }; } return result; //[f,f,f,f,f,f,f,f,f,f] } var fn1 = fn(); // 没有用new调用函数生成实例对象!!!!! for (var i = 0; i < fn1.length; i++) { console.log(fn1[i]()); // 10 个 10 } </script>
// 第一步:var fn1 = fn() 先执行
// 第二步:执行函数 fn() :在函数体中,先创建一个名为result的空数组,然后
// for循环,每次循环都生成一个函数result[i] = function () { return i;};循环完成以后,此时i = 10,跳出for循环,返回一个数组result = [f,f,f,f,f,f,f,f,f,f]
// 第三部,运行 函数体外部的for循环,每次fn1[i]()调用函数时,都是调用result数组每一项的函数f( result[i] = function () { return i;});return i;此时i跳出函数体内for循环是为10;故打印10
在匿名函数从fn()中被返回后,他的作用域被初始化为为包含fn()函数的活动对象和全局变量对象。这样匿名函数就可以访问在fn()中定义的所以变量,更为重要的是,fn()函数在执行完毕后,其活动对象也不会被销毁,因为匿名函数的作用域链任然在引用这个活动对象。换句话说,当fn()函数返后,其执行环境的作用域链会被销毁,但是它的活动对象仍然留在内存中,知道匿名函数被销毁后,fn()的活动对象才会被销毁。
测试1
var name = "The Window"; var object = { name: "My Object", getNameFunc: function () { return function () { return this.name; }; } }; console.log(object.getNameFunc()()); // The Window var fn = object.getNameFunc(); console.log(window.fn()); // The Window console.log(object.name) // My Object
测试2
this的指向:谁调用指向谁
var name = 'The Window' var object = { name: 'My Object', getNameFunc: function () { console.log(this) console.log(Object.prototype.toString.call(this)) return function () { console.log(this) return this.name } } } console.log(Object.prototype.toString.call(this)) console.log(object.getNameFunc()()) // The Window var fn = object.getNameFunc() console.log(window.fn()) // The Window console.log(object.name) // My Object
高级用法
/** 闭包的高级用法 */ (function (document) { var viewport; var obj = { init: function (id) { viewport = document.querySelector('#' + id); }, addChild: function (child) { viewport.appendChild(child); }, removeChild: function (child) { viewport.removeChild(child); } } window.jView = obj; })(document);
/* 以上代码执行过程可以如下分解: */ var f = function (document) { var viewport; var obj = { init: function (id) { viewport = document.querySelector('#' + id); }, addChild: function (child) { viewport.appendChild(child); }, removeChild: function (child) { viewport.removeChild(child); } } window.jView = obj; }; f(document); /* obj 是在函数f中定义的一个对象,这个对象中定义了一系列方法, 执行window.jView = obj 就是在window全局对象定义了一个变量jView, 并将这个变量指向obj对象,即全局变量jView引用了obj。而obj对象中函数又引用函数f中变量viewport,因此函数f中的viewport不会被GC回收,viewport会一直保存到内存中,所以这种写法满足闭包的条件。 */