False Begginer - 7.立即执行函数、闭包、逗号运算符

460 阅读5分钟

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('盖亚');