JavaScript学习笔记七

81 阅读7分钟

seven

一:立即执行函数(IIFE)

1.1 什么是立即执行函数?

中文:立即执行函数(直译:立即调用函数表达式) 英文简写:IIFE 英文全称:immediately invoked function expression 功能性称呼:初始化函数 当然,初始化函数不一定是立即执行函数,但是最好使用立即执行函数。

MDN: IIFE(立即调用函数表达式)是一个在定义时就会立即执行的 JavaScript 函数。

个人理解: 立即执行函数,按照字面理解,就是一个函数,会立即执行,那是什么时候会立即执行呢?按照MDN的解释,就是定义时。

立即执行函数特点:

  • 每次加载时,只执行一次;
  • 自动执行,无需调用;
  • 执行完成就立即销毁。
1.2 它的作用?

作用:

  • 创建独立的作用域,让外部无法访问该作用域内部的变量,从而避免变量污染;
  • 初始化全局变量。

需求:当一个页面加载完成后,接受两个参数,同时每次页面加载完成后,自动执行求得两个参数的和,然后在销毁。

尝试使用常规函数来完成上述需求

function add(a, b){
 return a + b;
}
var result = add(1, 2);
result();
  • 传入两个参数 ✔
  • 加载完成后 ✔
  • 两个参数的和 ✔
  • 自动执行 x
  • 销毁 x

可以看出,实际上还是没有完成需求

使用立即执行函数完成上述需求

var result = (function(a,b){
 return a + b;
}(1, 2));
  • 传入两个参数 ✔
  • 加载完成后 ✔
  • 两个参数的和 ✔
  • 自动执行 ✔
  • 销毁 ✔

完美按需完成

1.3 三种写法

常规写法

W3C建议写法

特殊写法

//常规写法
(function(){})();

//W3C规范写法
(function(){}());

//特殊写法,前面加 + - ! ~ && ||
+ (function(){}());
- (function(){}());
! (function(){}());
~ (function(){}());
true && (function(){}());
false || (function(){}());
1.4 立即执行函数细节
1.4.1 在立即执行函数种函数名是否可以不写?
  • 可以不写;
  • 因为立即执行函数本身就是立即执行完后就会立即销毁,所以写与不写,无关紧要。

验证上述内容

var testing = (function verify(){
    var a = 1;
    var b = 2;
    return a + b;
}());

console.log(testing);
//Print Result:3
console.log(testing.name) 
//Print Result:undefined 
//由此可以得知,函数名已经被忽略,同时联想到函数表达式,是可以打印出函数名的。
1.4.2 是否可以传参?
  • 可以;
  • 立即执行函数,跟常规函数比较其它,就是多了,自动执行以及执行完后立即销毁。

验证上述内容

var result = (function(a,b){
	return a + b;
}(1, 2));
console.log(result);
//Print Result:3
1.4.3 ()---小括号分析

立即执行函数之小括号延深: 1、一定是表达式才能被执行符号执行; 2、() --- 被小括号包裹的,不管是什么内容,一律是表达式;

image-20231129153510043.png

改造函数表达式,后面跟着 () 。就立即执行该函数,然后在赋值给变量名。这个技巧了解就行,感觉没什么作用。

//正常的函数表达式
var testing = function test(){
    console.log(1 + 2);
};
testing();
console.log(testing.name);
//Print result:3
//Print result:test

//函数表达式跟着函数执行符 ()
var testing = function test(){
    console.log(1 + 2);
}();
console.log(testing.name)
//Print result:3
//Print result:Uncaught TypeError: Cannot read properties of undefined (reading 'name')

//函数表达式跟着函数执行符 ()
//使用 return 返回
var testing = function test(){
    return 1 + 2;
}();
console.log(testing);
console.log(testing.name);
//Print result:3
//Print result:undefined
1.5 立即执行函数之面试题

问:以下这段代码是否会报错?如果报错,为什么?如果不报错又为什么?

答:不报错。 不报错的原因如下:

1、可以将这段代码分为两个部分来看;

2、第一部分为,test 函数声明;

3、第二部分为,表达式;

4、这两个部分组合在一起,无任何语法错误,所以不会报错;

5、如果,把表达式中的 6 给去除,则 JavaScript 引擎就会认为此时的 () 为函数执行符号,就会报语法错误。

function test(a){
    
}(6);

二:闭包深入

2.1 代码分析

代码实例:

//闭包现象
function test(){	//声明一个 test 函数
    var arr = [];	//创建一个空数组,也可以说初始化数组,这样感觉有B格。
    for(var i = 0; i < 10; i++){	//for循环,这里会产生变量污染
        arr[i] = function(){		//在调用这个匿名函数时,在页面写入 i 的值
            document.write(i + ' ');	
        }
    }
    return arr;	//在当前环境下返回数组到全局中
}
var myArr = test(); //函数赋值给一个全局变量
for(var j = 0; j < 10; j++){
    myArr[j](); //调用执行函数
}

问:为什么结果是 10 个 10 ?

答:因为闭包的产生,运行过程展开如下:

1、test 函数执行时;

2、在for循环体内,按照其规则 i < 10 ,依次从 0 - 9 的顺序,赋给 arr 数组下标 一个匿名函数体,且匿名函数并没有在整个函数内调用执行;

3、当局部便变量 i 的值为 9 ,进入且完成循环跳出时,其值在后置型自增作用下,已经更新为 10,并存储在 test 函数的 AO 中;

4、通过 return 指令,将 arr 数组返回到全局环境中去,也就是 test 函数赋值给 myArr 全局变量 ;

5、在 test 函数赋值给 myArr 变量时,就已经结束了 test 函数的调用执行,根据此时的执行期上下文就已产生了闭包;

6、闭包的产生,就意为着 test 函数中的作用域链的 AO 不销毁,并被匿名函数关联起来,只是断开了与 test 函数作用域链映射关系;

7、此时的局部变量 i 就包含在当前的 AO 中;

8、在第二个for循环中,当调用执行匿名函数时,匿名函数在自身的 AO 中没有查到变量 i ,则向上找,在 test 函数 AO 中找到变量 i 并调用;

9、匿名函数执行结束后,销毁自身的 AO,并一直钩住 GO 和 test 函数的 AO 不释放。

2.2 改造上面的代码

改造 2.1 章节代码,在页面上呈现 0-9 的自然数。 三种方式来改造上面的代码。

改造一:立即执行函数;

个人理解 对比最初代码,以下代码,是把匿名函数直接修改为立即执行函数,同时也把跟原匿名函数相关的代码全部踢出。

function test(){
    for(var i = 0; i < 10; i++){
    	(function(){
            document.write(i + ' ');	
        }());
    }
}
test();

改造二:传参方式1;

个人理解 改造方式:通过传参的方式,在匿名函数调用执行时,直接用实参传递给形参的方式来展示 0-9 的自然数。 说直白点,就是把变量 j 的值传入匿名函数内,在写入到 HTML 中然后在显示出来。

function test(){
    var arr = [];
    for(var i = 0; i < 10; i++){
        arr[i] = function(num){
            document.write(num + ' ');
        }
    }
    return arr;
}
var myArr = test();
for(var j = 0; j < 10; j++){
    myArr[j](j);
}

改造三:传参方式2;

听小野老师说,这是企业开发中最最常用的一种立即执行函数传参方式。

个人理解

改造方式:万变不离其宗,还是使用立即执行函数以及对匿名函数的改造。如果要具体展开说,有太多要说的,抓重点就是 在立即执行函数中使用每次for循环中变量 i 的值为实参值,进行实参与形参的统一,从而隔绝(相对于 2.1 代码而言)了闭包对匿名函数的影响,其它的就无关重点,这里就不写了。

function test() {
    var arr = [];
    for (var i = 0; i < 10; i++) {
        (function(j){
            arr[j] = function(){
                document.write( j + ' ');
            }
        }(i));
    }
    return arr;
}
var myArr = test();
for (var j = 0; j < 10; j++) {
    myArr[j]();
}
2.3 改造下面代码

需求:改造以下代码,点击 li 后,弹出其数组下标。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
 <ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
 </ul>
 <script>
    var oli = document.querySelectorAll('li');
    for(var i = 0; i < oli.length; i++){
        oli[i].onclick = function(){
            alert(i);
        }
    }
 </script>
</body>
</html>

思路:使用立即执行函数,来去除闭包的影响。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
 <ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
 </ul>
    <script>
        var oli = document.querySelectorAll('li');
            for (var i = 0; i < oli.length; i++) {
                (function (click) {
                    oli[click].onclick = function () {
                        alert(click);
                    }
                }(i));
            }
    </script>
</body>
</html>

三:逗号运算符

逗号运算符,只返回逗号后面的值。

逗号运算符,不只这一点点东西,先简单了解,后续在说。

console.log(5, 6);
//Print Result:6
//逗号的应用
var fn =(
    function test1(){
        return 1;
    },
	function test2(){
        return '2';
    }
)();
console.log(typeof(fn));
//Print Result:string

记录一下,后期在回来弄

for(let i = 0, j = 10; i <= 10; i++, j--) {
    console.log(`i: ${i}, j: ${j}`);
  }