7.1 IIFE(immediately invoked function expression)立即执行函数
7.1.1 立即执行函数定义和声明
- 立即执行函数 --> 初始化函数
- 自动执行 --> 执行完成以后立即释放
(function(){}()); // w3c推荐写法
(function(){})(); // 使用比较多的写法
(function(){})
(function(){}) // 立即执行函数不加;报错
7.1.2 表达式和执行符号
- 表达式的定义
- 执行符号 ---> (); ()括号中任何东西都属于表达式
(1); // 表达式
(1 + 2); // 表达式
(function(){}); // 表达式
(a = 1); // 表达式
- 表达式中会自动忽略函数的名称- 表达式中会自动忽略函数的名称
var a = 10;
// Boolean: false null undefined '' 0 NaN
if(function b(){}) { // (function b(){});属于表达式,表达式忽略函数名称,所以typeof(b),此时b是属于未定义的状态,所以返回undefined
a += typeof(b);
}
console.log(a); // 10undefined
7.1.3 证明立即执行函数立即执行和执行完成以后销毁
// 普通函数
var t1 = function fn() {
console.log('我是普通函数;');
}
t1(); // 需要执行符号()才能被调用执行。
console.log(t1); // function fn(){}
// 立即执行函数
var t2 = (function fn2() {
console.log('我是立即执行函数;')
})(); // 不需要调用,直接立即执行
console.log(t2); // undefined 执行后立即销毁,所以变量t2存储的是undefined
7.1.4 立即执行函数的参数
// 对于参数的默认值和参数的对应方式都与普通函数一样。
(function (a, b) {
// a b 形式参数
// 1 2 实际参数
console.log(a + b); // 3
})(1, 2);
7.1.5 立即执行函数的返回值
var num = (function(a, b){
console.log(a + b);
return a + b;
})(2, 4);
console.log(num);
7.1.6 函数表达式为什么要叫函数表达式
- 因为根据表达式的定义,表达式分为两种类型:赋值和单纯求值的。
// 所以,这就相当于将函数赋值给一个变量,从而符合表达式的定义,所以又叫做函数表达式
var num = function fn(){};
7.1.7 下列函数是否能够执行,一定是表达式才能被执行符号()执行
// ()括住的东西都是表达式,所以表达式()能够执行
(function fn1() {
console.log(1); // 1
})();
// var fn2 = function(){}是函数表达式, 然后var fn2 = function(){}();可以执行
var fn2 = function() {
console.log(1); // 1
}();
console.log(fn2); // undefined
// function fn3(){}属于函数声明,所以函数声明()会直接报语法错误
function fn3() {
console.log(1); // Uncaught SyntaxError: Unexpected token ')';
}();
7.1.8 问题:函数表达式的形式也能实现和立即执行函数同样的效果,为啥JS还要搞个()呢?
// 我的理解: 立即执行函数有多种的形式和写法,但是立即执行函数的核心是立即执行和执行后销毁,通过 表达式 + ()执行符号 的方式能够实现和立即执行函数的效果,但是规范的写法毕竟相对的可读性和可维护性更高。
// 二者执行效果相同
var num = function fn() {
console.log(1);
}();
console.log(num); // undefined
var num = (function fn2(){
console.log(1);
})();
console.log(num); // undefined
7.1.9 将函数声明转换成表达式
- 只有表达式才能被执行符号执行,执行符号能让函数声明变成表达式
function test() { //因为是函数声明所以报语法错误
console.log(1); // Uncaught SyntaxError: Unexpected token ')';
}();
+ function test() {
console.log(1); // 1
}();
- function test() {
console.log(1); // 1
}();
! function test() {
console.log(1); // 1
}();
1 && function test() {
console.log(1); // 1
}();
7.1.10 立即执行函数的实例
function fn(a) {
console.log(1); // Uncaught SyntaxError: Unexpected token ')';
}();
function fn(a) {
console.log(1); // 不执行,这是为什么呢?
}(6, 5);
// 因为()中有值,JS引擎在解析的时候,认为(6, 5)是表达式,而不是执行符号(),所以此时并不是函数声明 + 执行函数(),所以不会报错。此时是函数声明 + (6, 5)表达式组成的。
7.2 逗号运算符,
- 逗号运算符,返回逗号最后的值
var num = (2 - 1, 6 + 5);
console.log(num); // 11
var num = (2 - 1, 6 + 5, 24 + 1);
console.log(num); // 25
\
7.3 面试题
function fn() {
var arr = [];
for(var i = 0; i < 10; i++) {
arr[i] = function() {
document.write(i + ' ');
}
}
return arr;
}
var myArr = fn();
for(var j = 0; j < 10; j++) {
myArr[j](); // 打印什么? 打印10个10,为什么呢?
}
// 原因:
function fn() {
var i = 0;
var arr = [];
for(; i < 10;) {
arr[i] = function() {
document.write(i + ' ');
}
i++;
}
return arr;
}
var myArr = fn();
// 打印10次10的原因:fn函数运行,进行循环,循环10次,每次将匿名函数function(){}添加到arr数组中,充当数组元素,但是该匿名函数并没有执行,反而作为闭包返回,在全局的环境下进行调用myArr[j],匿名函数作为闭包,虽然fn函数执行完应该销毁,但是由于匿名函数作为闭包一直捆绑着fn函数的AO环境,所以能够访问到fn环境中的变量i,当myArr[j]()调用匿名函数时,fn函数的AO环境中的变量i = 10,所以打印10次10;
for(var j = 0; j < 10; j++) {
myArr[j]();
}
// 解题思路
function fn() {
var arr = [];
for(var i = 0; i < 10; i++) {
(function(j) {
arr[j] = function() {
document.write(j + ' ');
}
})(i);
}
}
var myArr = fn();
for(var j = 0; j < 10; j++) {
myArr[j]();
}
// 第二种:
function fn() {
var arr = [];
for(var i = 0 ; i < 10; i++) {
arr[i] = function(num) {
document.write(num + ' ');
}
}
return arr;
}
var myArr = fn();
for(var j = 0; j < 10; j++) {
myArr[j](j);
}
// 第三种
function fn() {
for( var i = 0; i < 10; i++) {
(function(){
document.write(i + ' ');
})();
//
document.write(i + ' ');
}
}
//这fn2不会引用fn的AO环境,因为fn2执行完成后销毁自己的环境执行上下文,所以说fn2并没有和fn的AO环境进行捆绑, 所以fn2不是闭包,但fn1是闭包,fn1函数的作用域链和全局GO捆绑在一起。
function fn1() {
function fn2() {
console.log(2);
}
console.log(1);
fn2();
};
fn1();
// 点击每个li,并且打印出对应的值
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
var Oli = document.querySelectorAll('li');
for(var i = 0; i < Oli.length; i++) {
Oli[i].onclick = function() {
console.log(i); // 点击后打印出5个5
}
}
// 因为匿名函数没有点击的时候并不会执行,所以这5个匿名函数都是闭包,自己的AO环境和GO全局环境捆绑在一起,所以当匿名函数执行的时候,i = 5; 所以打印5个5
// 解决思路:
// 也就是说,当for循环每一次的时候,都执行一次立即执行函数,立即执行函数能够立即执行,然后在自己的AO环境中保存当前的i的值,执行完成后,销毁自己的环境,但是匿名函数function(){}是闭包,将立即执行函数的AO和自己的作用域链进行捆绑,导致立即执行函数AO无法释放,从而访问到立即执行函数AO环境中i的值。
for(var i = 0; i < Oli.length; i++) {
(function(j) {
Oli[j].onclick = function() {
console.log(j);
}
})(i);
}
var fn = (
function test1() {
return 1;
},
function test2() {
return '2';
}
)();
console.log(fn); // string
// 逗号运算符返回逗号后的数据,所以变量fn保存的是function test2(){}函数,然后执行test2函数返回字符串'2'; 然后typeof进行判断,返回字符串string
累加器,初始化值0,用闭包写
function accumulator() {
var num = 0;
function add() {
num++;
console.log(num);
};
function reduce() {
num--;
console.log(num);
};
return { add, reduce };
}
var obj = accumulator();
obj.add(); // 1
obj.reduce(); // 0
obj.add(); // 1
缓存器,一个班级,学生名字保存在一个数组里,两个方法写在函数中的一个对象中,第一个方法是加入班级,第二个方法是离开班级,每次加入或者离开都需要打印新的学生名单。
function studentClass() {
var studentsName = [];
var operation = {
join : function(name) {
studentsName.push(name);
console.log(studentsName);
},
leave: function(name) {
var index = studentsName.indexOf(name);
if(index !== -1) {
studentsName.splice(index, 1);
console.log(studentsName);
return;
}
console.log('班级没有此人');
},
};
return operation;
}
var obj = studentClass();
obj.join('迪迦');
obj.join('杰克');
obj.leave('盖亚');