闭包、立即执行函数
文章目录
闭包的过程
闭包的概念
当内部函数被保存到外部时,将会生成闭包。闭包会导致原有作用域链不释放,造成内存泄露。
作用域链不释放是因为被子函数继承了,就会导致内存被占用。(内存泄露就是内存占用高的意思)
但凡是内部的函数被保存到了外部,一定生成闭包
function a() {
function b() {
var bbb = 234;
console.log(aaa);
}
var aaa = 124;
return b; // 返回b之后,b函数被定义,继承a函数的指向,然后b函数被保存了出来赋值给demo(下图二),a函数销毁自己的执行期上下文(AO/GO)
}
var glob = 100;
var demo = a(); // demo这个变量是a执行的结果 也就是保存了b函数 此时的b函数被定义
demo(); // 在外部执行b函数 然后创建自己的执行期上下文AO 但是自己的执行期上下文里面没有AO 所以就找继承的a的AO中有没有aaa 没有的话继续找GO 然后在a的AO中找到了 所以执行b函数时是打印124
b被定义时,继承了a的劳动成果,直接链接a的AO和GO
闭包的响应
function a() {
var num = 100;
function b() {
num ++; // num ++ 是先用后变 也就是当前行值不变 打印时会改变
conole.log(num); // b函数执行完之后打印a的AO中的num101 因为b的AO中没有num
}
return b; //返回b函数到demo中 b函数继承a的劳动成果
}
var demo = a(); //a函数赋值给demo demo保存的就是b函数
demo(); // 执行之后打印101
demo(); // 执行之后打印102
// 1. a函数执行时生成AO和继承全局的GO aAO{num:100}
// 2. b函数执行时生成b的AO和继承a的AO和全局的GO bAO{num:100}
// 重点:b函数想要被定义,那就需要a函数被执行,因为a执行b才会被定义(return b),然后b继承a的劳动成果之后a销毁,也就是return b的时候函数b被定义,renturn b执行完之后相当于a函数执行完,然后a函数销毁AO和GO(b函数已经继承之后销毁)。
function a(){
var aa = 345;
function b(){
var bb = 234;
function c(){
var cc = 123; // c函数执行完也会销毁自己的执行期上下文
}
c(); // c函数被执行是b函数的最后一条语句 也就是把c函数内部读完 c函数被定义代表b函数被执行完 然后销毁函数b产生的执行期上下文
}
b(); // b函数被执行是a函数的最后一条语句 也就是把b函数内部读完 b函数被定义代表a函数被执行完 然后销毁函数c产生的执行期上下文
}
a();
// a defined a.[[scope]] 0 : GO
// a doing a.[[scope]] 0 : aAO
// 1 : GO
// b defined a.[[scope]] 0 : aAO
// 1 : GO
// b doing a.[[scope]] 0 : bAO
// 1 : aAO
// 2 : GO
// c defined a.[[scope]] 0 : bAO
// 1 : aAO
// 2 : GO
// c doing a.[[scope]] 0 : cAO
// 1 : bAO
// 2 : aAO
// 3 : GO
闭包的作用
实现公有变量
函数累加器
function add() {
var count = 0;
function demo() {
count ++;
console.log(count);
}
return demo; // 返回函数b
}
var counter = add(); // add函数赋值给counter
counter();
counter();
counter();
counter(); //重复多次实现累加效果 调用一次累加一次
// 这里用的是add函数的AO中的count 因为demo的AO没有count add中的AO执行一次累加一个1
可以做缓存(存储结构)
eater
// 存在存储结构,但是外部不可见。
function test() {
var num = 100;
function a() {
num ++;
console.log(num);
}
function b() {
num --;
console.log(num);
}
return [a,b]; // 返回数组存储a函数和b函数
}
var myArr = test(); // 把返回的a函数和b函数赋值给myArr
myArr[0](); // 数组第一位是a函数 打印结果是101
myArr[1](); // 数组第二位是b函数 打印结果是100
// 因为a函数和b函数都没有num 都共同指向a的AO中的num(同一个地址)
function eater() {
var food = "";
var obj = { // 对象里面可以定义方法
eat : function () {
console.log("i am eating " + food);
food = "";
},
push : function (myFood) { // 传参函数
food = myFood;
}
}
return obj; // 返回对象就意味着把对象里面的两个函数都返回了
}
var eater1 = eater(); // 函数赋值给eater1
eater1.push('banana'); // 函数传参 给food传值 food就相当于隐式的存储结构
eater1.eat(); // 执行函数就有了值
可以实现封装,属性私有化。
Person();
模块化开发,防止污染全局变量
立即执行函数
定义
此类函数没有声明,在一次执行过后即释放。适合做初始化工作。
立即执行函数也有预编译、执行期上下文等,立即执行函数与其他函数的区别只有执行完立即销毁,其他的完全一样。
// 只使用一次的函数又称作为 针对初始化功能的函数
function a() { // 这种函数只运行一次 要求执行完就销毁 否则很占内存空间
....此处省略一万行代码
}
// 立即执行函数写法一 小括号包括了传参部分
(function (){}()) // W3C 建议第一种
// 立即执行函数写法二 小括号不包括传参部分
(function (){})()
// 针对初始化功能的函数写法(执行完立即被释放)
(function (){ // 函数外层包裹小括号 匿名函数(不需要取名字,实在要取个名字也可以)
var a = 123
var b = 234
console.log(a + b)
}()) // 函数最后增加小括号
// 立即执行函数也可以进行传参
(function (a, b, c){
console.log(a + b + c)
}(1, 2, 3)) // 参数写在最后的小括号里
// 立即执行函数也可以有返回值 用完再销毁
var num = (function (a, b, c){ // 这样写会直接把d返回给num 然后销毁函数
var d = a + b + c * 2 - 2
return d
}(1, 2, 3))
函数声明和函数表达式
// 函数声明和函数表达式都能定义函数
// 函数声明
// 只有表达式才能被执行符号执行 能被执行符号执行的表达式函数名会被忽略
function test (){
var a = 123
}() // 函数最后加括号会语法错误 无法执行
// 因为括号前面需要表达式 而这里的括号前面是函数声明体
test(); // 写test能执行函数是因为test是表达式
// 函数表达式
// var test是变量声明 =后面的部分是表达式
var test = function() { // test是表达式 所以这里能执行
console.log('a')
}(); // 这里加了括号 函数名就会被忽略 执行一次之后 控制台输入test就找不到这个函数
// 这种写法和立即执行函数没什么区别 函数执行一次之后就被彻底放弃了
// 函数声明
+ function test() { // +/-/! 加减或者!一个东西叫表达式 所以能执行
console.log('a')
}(); // 加一对括号 函数就被执行了 一个表达式被执行 天生的功能就是忽略函数名
// 这种写法和立即执行函数没什么区别 函数执行一次之后就被彻底放弃了
// 括号是数学运算符 把函数包起来 那函数也就变成表达式了
// 这也就是立即执行函数的第二种写法
(function test() {
console.log('a')
})(); // 打印a之后就找不到test了 函数只执行一次
// 立即执行函数的第一种写法 (2+3*(7-5)) 是先识别最外层的括号 但是是先运算最内层括号
// 由于函数识别是先识别最外层的括号 所以把括号放到最外层也是会把函数变成表达式
(function test() {
console.log('a')
}());
练习
function test(a,b,c,d) {
console.log(a + b + c + d)
}(1,2,3,4)
// 这个函数执行不会报错 如果最后只写一个() 系统会认定是立即执行函数
// 但是如果有参数 系统为了不报错 会理解成下面这样 所以不会执行(因为不是立即执行函数)
function test(a,b,c,d) {
console.log(a + b + c + d)
}
(1, 2, 3, 4) // 系统不会把这部分当运算符
闭包的防范
闭包的防范
function test() {
var arr = []; // 定义一个空数组
for(var i = 0; i < 10; i ++) {
arr[i] = function () {
// 函数体赋值给数组,数组中存储了十个函数体(funcion(){}),i的值在AO中一直改变,最后是10,所以打印的时候获取testAO中的i就是10,可以理解为存的是框架,但框架里面的内容从AO中获取
console.log(i);
}
}
return arr; // 数组返回出去
}
var myArr = test(); // 返回给myArr test()赋值给myArr就等价于arr赋值给myArr
// test执行完 i变成10了
for(var j = 0; j < 10; j ++) {
myArr[j](); // 执行函数test 打印10个10 访问的是同一个i 因为这里打印的都是test的AO中定义的i 而i在循环结束之后变成10了
}
// 把i按顺序输出的方法 立即执行函数
function test() {
var arr = [];
for(var i = 0; i < 10; i ++) {
(function (j) { // 立即执行函数放到for循环里面循环几次就会有几次立即执行函数
arr[j] = function () {
console.log(j + "") // 这里是打印0-9 因为打印的j就是当前的i 因为此时打印的j是立即执行函数自己创建的AO中的j 只不过这个j的值是当前对应的i的值 而每次函数AO都不是同一个所以互不影响 如果打印i 就还是都是10 因为i只存在于test函数中 是子函数共用的爹
}
}(i)) // 把i作为实参传给形参j
}
return arr;
}
var myArr = test();
for(var j = 0; j < 10; j ++) {
myArr[j]();
}
练习
练习一
<ul>
<li>a</li>
<li>a</li>
<li>a</li>
<li>a</li>
</ul>
题目:使用原生js,addEventListener,给每个li元素绑定一个click事件,输出他们的顺序
css:
* {
margin:0;
padding:0;
}
ul {
list-style: none;
}
li: nth-of-type(2n) {
background-color: red;
}
li: nth-of-type(2n + 1) {
background-color: green;
}
html:
<ul>
<li>a</li>
<li>a</li>
<li>a</li>
<li>a</li>
</ul>
<script type="text/javascript">
function test() {
var liCollection = document.getElementsByTagName('li') // 选li
for(i = 0; i < liCollection.length; i ++) {
liCollection[i].onclick = function () { // 把里面的函数绑定在外部(闭包)
console.log(i)
// 打印的值都是4 因为打印的i都是test的AO中的值 循环结束后AO中的i值是4
}
}
}
</script>
正确方法
<script type="text/javascript">
function test() {
var liCollection = document.getElementsByTagName('li') // 选li
for(i = 0; i < liCollection.length; i ++) {
(function (j) {
liCollection[j].onclick = function () {
console.log(j) //把i作为实参传入 这里的j就是打印当前的i值
// 这样打印的就是0 1 2 3.....
}
}(i))
}
}
</script>
// 练习二 之前做过
// 题目一:注意AO和GO各提各的(有重复正常)
GO {
a : 100,
demo : function () {}, // 函数体
f : 123 // 函数内部赋值
}
a = 100;
function demo(e) {
function e() {}
arguments[0] = 2; // 这里是给形参e赋值为2 arguments控制实参的位数和值 但是和实参是两个东西 一个变另外一个也变 这叫相互映射
document.write(e); // 打印出2
if(a) { // 循环是在执行之后才起作业 所以不看
var b = 123;
function c() {
// if里面不能声明function(以前可以)
}
}
var c;
a = 10; // 给a赋值10
var a;
document.write(b); /// 打印AO中的b undefined
f = 123; // 变量未声明 归GO所有
document.write(c); // undefined
document.write(a); // 打印AO中的a 10
}
var a;
demo(1);
document.write(a); // 打印全局的a 100
document.write(f); // 123
AO {
e : function () {}, // 执行时赋值2
b : undefined,
c : undefined,
a : 10
}
// 练习三
写一个方法,求一个字符串的字节长度。(提示:字符串有一个方法charCodeAt();一个中文占两个字节,一个英文占一个字节。
定义和用法
charGodcAt()方法可返回指定位置的字符的Unicodc编码。这个返回值是0-65535之间的整数。(当返回值是<=255时,为英文,当返回值>255时为英文) // 输出指定字母对应的ASCII值
语法
stringObject.charCodeAt(index)
cg:
<script type="javascript/text”>
var str="Hello world!"
document.write(str.charCodeAt(1);,//输出101 这里是输出str第一个字符H的ASCII对应的值
</script>
// 解答过程 方法一 判断字符占位
<script type="javascript/text”>
function retByteslen(target) {
var count = 0;
for(var i = 0; i < target.length; i ++) {
if(target.charCodeAt(i) <= 255) {
count ++ // 小于等于255时只占一个字节
}else if(target.charCodeAt(i) > 255 {
count += 2 // 大于255时占两个字节
})
}
console.log(count) // 这里打印的就是count占位字符数
}
</script>
// 方法二 先按照每个占位1计算 遇到汉字加1
<script type="javascript/text”>
function retByteslen(target) {
var count = target.length;
for(var i = 0; i < target.length; i ++) {
if(target.charCodeAt(i) > 255) {
count ++ // 大于255的加一个字节
}
}
console.log(count) // 这里打印的就是count占位字符数
}
</script>
// 方法三 在方法二的基础上继续简化
<script type="javascript/text”>
function retByteslen(target) {
var count,
var len,
count = len = target.length;
for(var i = 0; i < len; i ++) {
if(target.charCodeAt(i) > 255) {
count ++ // 大于255的加一个字节
}
}
console.log(count) // 这里打印的就是count占位字符数
}
</script>
// 逗号操作符
var a = (2, 3); // a是3
// 逗号表达式的意思是先算前面的表达式,前面的表达式需要计算的可以先计算,然后计算后面的表达式,都计算完之后把后面的表达式的结果返回到前面a
var a = (1 - 1, 1 + 1); // a是2
// 练习四
// 写出下面程序的执行结果
// 立即执行函数
// fAO {f:function(){}}
var f = (
function f() {
return "1";
},
function g() {
return 2; // 逗号运算符,会把后面的值返回,所以f返回的是2
}
)();
typrof f; // number
// 练习五
var x = 1;
if (function f() {}) {
// function被括号括起来就说明它已经不是表达式了 消失了(放弃函数名)
x += typeof f; // 因为functionf消失了,所以f未定义 所以类型转换是"undefined"
}
console.log(x); // 1undefined 这里的undefined是str类型 不然1+undefined=NaN
- 1); // a是2
// 练习四
// 写出下面程序的执行结果
// 立即执行函数
// fAO {f:function(){}}
var f = (
function f() {
return “1”;
},
function g() {
return 2; // 逗号运算符,会把后面的值返回,所以f返回的是2
}
)();
typrof f; // number
```js
// 练习五
var x = 1;
if (function f() {}) {
// function被括号括起来就说明它已经不是表达式了 消失了(放弃函数名)
x += typeof f; // 因为functionf消失了,所以f未定义 所以类型转换是"undefined"
}
console.log(x); // 1undefined 这里的undefined是str类型 不然1+undefined=NaN