函数表达式
有两种方式: 1 函数声明 2 函数表达式
函数声明会提升, 函数表达式不会
函数声明, 在执行函数之前会先读取函数声明
- sayhi()//hi
- function sayhi(){console.log('hi');
- }
- // 不会出错
函数表达式
- sayhi()//hi
- var sayhi=function(){
- console.log('hi');
- }
- // 会出错
函数表达式和函数声明中没有函数名的函数, 都是匿名函数
- // 不要这样做!, 有的浏览器用第一个声明, 有的用第二个
- if(condition){
- function sayHi(){
- alert("Hi!");
- }
- } else {
- function sayHi(){
- alert("Yo!");
- }
- }
- // 可以这样做
- var sayHi;
- if(condition){
- sayHi = function(){
- alert("Hi!");
- };
- } else {
- sayHi = function(){
- alert("Yo!");
- };
- }
闭包
闭包是指有权访问另一个函数作用域中的变量的函数, 创建闭包的常见方式, 是在一个函数内部创建另一个函数, 通过另一个函数访问这个函数的局部变量
闭包可以让外侧函数的局部变量驻留内存, 实现局部变量的累加, 过度使用闭包导致性能下降, 内存泄漏
- function a() {
- var age = 1;
- return function() {
- age++;
- return age;
- }
- }
- var x = a();
- console.log(x()) //2
- console.log(x()) //3
但是此处要注意, 先把. x=a()`,a()返回该闭包中的匿名函数, 然后调用 x(), 调用的是 a 函数返回同一个匿名函数若连续多次打印 a()(), 则变量 age 不会累加, 因为每次执行 a()(), 都重新返回了函数
- function a() {
- var age = 1;
- return function() {
- age++;
- return age;
- }
- }
- // var x=a();
- console.log(a()()) //2
- console.log(a()()) //2
- console.log(a()()) //2
由于作用域链的机制导致一个问题, 循环里的匿名函数取得的任何变量都是最后一个值闭包所保存的是整个变量对象, 而不是某个特殊的变量
- function box() {
- var arr = [];
- for (var i = 0; i < 5; i++) {
- arr[i] = function() {
- return i;
- };
- }
- return arr;
- }
- var b = box(); // 得到函数数组
- alert(b.length); // 得到函数集合长度
- for (var i = 0; i < b.length; i++) {
- console.log(b[i]()); // 输出每个函数的值, 都是最后一个值 55555
- }
因为 b 中存的是匿名函数对象, 当 bi 执行匿名函数时, box()中的 for 循环早已执行完毕, i 早已变成 5.
解决方法 1: 让匿名函数自我执行
- function box() {
- var arr = [];
- for (var i = 0; i < 5; i++) {
- arr[i] = (function(i) {
- return i;
- })(i);
- }
- return arr;
- }
- var b = box(); // 得到函数数组
- alert(b.length); // 得到函数集合长度
- for (var i = 0; i < b.length; i++) {
- console.log(b[i]); // 输出 0,12,3,4
- }
解决方法 2: 匿名函数下在创建一个匿名函数, 外侧匿名函数自执行
- function box() {
- var arr = [];
- for (var i = 0; i < 5; i++) {
- arr[i] = (function(num) {
- return function() {
- return num;// 具体数字
- };
- })(i)
- }
- return arr;
- }
- var b = box(); // 得到函数数组
- alert(b.length); // 得到函数集合长度
- for (var i = 0; i < b.length; i++) {
- console.log(b[i]()); // 输出 01234
- }
this 对象
this 对象是在运行时基于函数的执行环境绑定的, 如果 this 在全局范围就是 window, 如果在对象内部就指向这个对象而闭包却在运行时指向 window, 因为闭包并不属于这个对象的属性或方法
- var user = 'window';
- var obj = {
- user: 'obj',
- getUserName: function() {
- return function() {
- return this.user;
- }
- }
- }
- console.log(obj.getUserName()()); //window
解决 1: 强制指向特定对象
- console.log(obj.getUserName().call(obj));//obj
- console.log(obj.getUserName().apply(obj));//obj
解决 2: 复制 this, 得到上一个作用域的 this 对象
- var user = 'window';
- var obj = {
- user: 'obj',
- getUserName: function() {
- var that = this;
- return function() {
- return that.user;
- }
- }
- }
- console.log(obj.getUserName()()); //obj
匿名函数仿块级作用域
for 循环中的 var i 在外部也能访问
- function box(count) {
- for (var i=0; i<count; i++) {}
- var i; // 就算重新声明, 也不会覆盖前面的值, 除非重新初始化
- alert(i);
- }
- box(2);//2
解决 1:
- // 模仿块级作用域(私有作用域)
- (function () {
- // 这里是块级作用域
- })();
- // 使用块级作用域 (私有作用域) 改写
- function box(count) {
- (function () {
- for (var i = 0; i<count; i++) {}
- })();
- alert(i); // 报错, 无法访问
- }
- box(2);
解决 2:ES6 中使用 let
- function box(count) {
- for (let i=0; i<count; i++) {}
- alert(i); //i 报错
- }
- box(2);
参考资料: JavaScript 高级程序设计(第 3 版)
来源: http://www.jianshu.com/p/2089f375e021