You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// 有多种形式,以下为最常见的一种,即创建一个匿名函数并将其赋值给变量 functionNamevarfunctionName=function(arg0,arg1,arg2){// 函数体};// 只在 Firefox、Safari、Chrome 和 Opera 有效alert(functionName.name);// 空字符串
当然函数表达式就没有声明提升这种特征了。以下是一个比较常见的坑...
// 千万别这样做!// 因为有的浏览器会返回 first 的这个 function,而有的浏览器返回的却是第二个if(true){functionfoo(){return'first';}}else{functionfoo(){return'second';}}foo();// 相反,这样情况,我们要用函数表达式varfoo;if(true){foo=function(){return'first';};}else{foo=function(){return'second';};}foo();
7.1. 递归
先说个段子:要想理解递归,首先要理解...递归。
说正经的,递归就是函数自己调用自己。
functionfactorial(num){// 递归结束条件if(num<=1)return1;// 通过在全局 VO 中,找到自身函数的指针后调用自身returnnum*factorial(num-1);}varanotherFactorial=factorial;factorial=null;// 报错!因为修改了全局 VO 中 factorial 指针的指向(null)alert(anotherFactorial(4));
这么写主要问题就是递归函数与自身的函数名耦合,一旦修改了原本的函数名,则会导致错误。
这时可以利用 arguments.callee 指针来成功寻找到正在执行的函数。
接下来又有一个坑:arguments 在严格模式下无法使用。
不过我们可以使用命名函数表达式来完美解决:
varfactorial=functionf(num){// 递归结束条件if(num<=1)return1;// 函数名 f 只在内部作用域里有效returnnum*f(num-1);};typeoff;// undefined
7.2. 闭包
先下定义:闭包是指【有权】访问(另一个函数作用域)中的「变量」的「函数」。
闭包首先是一个函数
能力就是有权访问变量
范围在另一个函数作用域内
我们日常在使用 JavaScript 中,在外部函数中定义的内部函数能够访问外部函数中的变量。
所以,最常见的闭包方式就是在一个函数内部创建并返回另一个函数。
functionfoo(arg0,arg1){returnfunctionbar(){// 内部的 bar 函数能够访问外部函数 foo 的 arg0 和 arg1 变量。returnarg0+arg1;}}vartest=foo('a','b');// test 是一个指针,指向返回的 bar 函数test();// ab,调用闭包后,得到 arg0 + arg1 的值typeofbar;// undefined,当然返回的是一个匿名函数
varname="The Window";varobject={name: "My Object",getNameFunc: function(){returnfunction(){returnthis.name;};}};alert(object.getNameFunc()());// The Window (非严格模式下),此处有两个括号,因为 object.getNameFunc() 是一个匿名函数,后一个括号是匿名函数调用。
为什么匿名函数没有取得其包含作用域(或外部作用域)的 this 对象呢?
前面曾经提到过,每个函数在被调用时都会自动取得两个特殊变量:this 和 arguments。而内部函数在搜索这两个变量时,只会搜索到其 AO 为止,所以永远不能直接访问到外部函数中的 this 和 arguments。
不过若是我们将外部作用域中的 this 对象保存在一个闭包能够访问到的变量里,就可以让闭包访问到该对象了:
varname="The Window";varobject={name: "My Object",getNameFunc: function(){varthat=this;// 使用 that 保存外部函数的 this(防止被内部函数的 this 屏蔽)returnfunction(){returnthat.name;// 由于访问的是内部 AO 中没有的变量 that,所以在 SC 中外部的 AO 上搜索,得到外部函数的 this。};}};alert(object.getNameFunc()());// My Object
接下来再看几个特殊的栗子:
varname="The Window";varobject={name: "My Object",getName: function(){returnthis.name;}};object.getName();// My Object,很好理解 this 就是指向 object(object.getName)();// My Object,将函数包了起来,但还是通过 object.getName 调用,this 还是指向 object(object.getName=object.getName)();// The Window(非严格模式下),看起来很奇怪,将函数 getName 赋值为 getName,再调用赋值后的结果// 因为赋值表达式操作的是 getName 函数本身,所以 this 的值没有得到维持,调用时指向了 widnow。
7.2.3. 内存泄漏
因为 IE9 之前对于 JScript 对象和 COM 对象使用不同的垃圾收集机制。所以如果闭包的作用域中保存一个 HTML 元素,那么该元素将无法被销毁╮(╯▽╰)╭。
7.3. 模仿块级作用域
我们都知道在 ES6 之前是木有块级作用域的╮(╯▽╰)╭,在块语句中定义的变量实际上是定义在函数 AO 上的。
零、前言
《JavaScript 高级程序设计(第三版)》第7章 函数表达式,学习笔记整理。
一、第7章 函数表达式
7.0. 函数定义
在 JavaScript 中定义函数有两种方法:
7.0.1. 函数声明
7.0.2. 函数表达式
当然函数表达式就没有声明提升这种特征了。以下是一个比较常见的坑...
7.1. 递归
说正经的,递归就是函数自己调用自己。
这么写主要问题就是递归函数与自身的函数名耦合,一旦修改了原本的函数名,则会导致错误。
这时可以利用
arguments.callee
指针来成功寻找到正在执行的函数。接下来又有一个坑:arguments 在严格模式下无法使用。
不过我们可以使用命名函数表达式来完美解决:
7.2. 闭包
我们日常在使用 JavaScript 中,在外部函数中定义的内部函数能够访问外部函数中的变量。
所以,最常见的闭包方式就是在一个函数内部创建并返回另一个函数。
那么内部的 bar 函数是怎么保存外部 foo 函数的两个参数的呢?
所以接下来举几个栗子:
1. 普通函数
2. 闭包示例
从上述讨论我们可以清晰地看出:闭包的原理就是内部的函数仍然引用着外部函数的 AO,使得外部函数的 AO 仍然保存在内存中。所以我们可以通过将闭包设置为 null 来解除对该函数的引用,回收其占用的内存。
7.2.1. 闭包与变量
下面我们来简单讨论下作用域链机制的副作用(坑):闭包只能取得外部函数中任何变量的最后一个值。
先来看一个栗子:
其实很好理解,因为闭包在 SC 中保存的是一个指针而已,外部函数执行完毕后 AO 中的变量自然更新为最后一个值啦╮(╯▽╰)╭。
但是我们可以创建另一个立即执行的匿名函数强制让闭包的行为符合预期:
7.2.2. 关于 this 对象
我们知道,this 是在运行时根据函数的执行环境动态绑定的:
不过,匿名函数的执行环境具有全局性,因此内部的 this 通常指向 window,见下例:
为什么匿名函数没有取得其包含作用域(或外部作用域)的 this 对象呢?
前面曾经提到过,每个函数在被调用时都会自动取得两个特殊变量:this 和 arguments。而内部函数在搜索这两个变量时,只会搜索到其 AO 为止,所以永远不能直接访问到外部函数中的 this 和 arguments。
不过若是我们将外部作用域中的 this 对象保存在一个闭包能够访问到的变量里,就可以让闭包访问到该对象了:
接下来再看几个特殊的栗子:
7.2.3. 内存泄漏
因为 IE9 之前对于 JScript 对象和 COM 对象使用不同的垃圾收集机制。所以如果闭包的作用域中保存一个 HTML 元素,那么该元素将无法被销毁╮(╯▽╰)╭。
7.3. 模仿块级作用域
我们都知道在 ES6 之前是木有块级作用域的╮(╯▽╰)╭,在块语句中定义的变量实际上是定义在函数 AO 上的。
如果实在需要块级作用域,可以通过立即执行函数进行模拟。
在一个大型程序中过多的全局变量和函数很容易造成命名冲突,这样可以有效减少在全局作用域中添加变量和函数。也可以说是模块化的基础。ps jQuery 源码中最外层函数就是这样的一个立即执行函数...
7.4. 私有变量
严格来说,JavaScript 中没有私有成员的概念...╮(╯▽╰)╭,不过在任何函数中定义的变量,外部都无法访问到,可以认为是私有变量。
私有变量包括:
那么如果我们需要访问这些私有变量该怎么办呢?
在此就要引入一个概念:特权方法(privileged method)有权访问私有变量和私有函数的公有方法。
其实有两种在对象上创建特权方法的方式,第一种就是利用闭包在构造函数中定义特权方法。基本模式如下:
但是,这样定义特权方法有一个问题:必须使用构造函数模式。而之前已经讨论过了构造函数会为每个实例都创建一组新方法,浪费内存,而接下来介绍的第二种方法,使用静态私有变量就可以避免这个问题。
7.4.1. 静态私有变量
基本思想就是:在私有作用域(立即执行函数)中定义私有变量或函数,并在内部通过函数表达式定义构造函数和它的公有方法。基本模式如下:
显然,通过上一篇对于对象继承的讨论我们知道:定义在原型对象上的属性和方法是所有实例共享的,而这些方法(如 publicMethod)所操作的对象,即私有变量和函数也是同一个。所以通过 MyObject 构造函数创造的实例都有权访问私有变量(而且是同一个),这就是静态私有变量。
7.4.2. 模块模式
模块模式(module pattern)是为「单例」创建私有变量和特权方法。
单例(singleton)指的就是只有一个实例的对象。比如 JavaScript 中就是以对象字面量来创建单例对象的。
模块模式通过为单例添加私有变量和特权方法使其得到增强:
如上面代码所示,模块模式就是使用了一个返回对象的匿名函数:
模块模式的应用场景主要是在需要对单例进行某些初始化,同时又需要维护其私有变量时是非常有用的。
7.4.3. 增强的模块模式
应用场景:单例必须是某种类型的实例,同时还必须添加某些属性和(或)方法对其进行加强的情况。
7.5. 小结
1. 在 JavaScript 中,使用函数表达式可以无须对函数命名,从而实现动态编程,还有强大的匿名函数,以下是函数表达式的特点:
2. 闭包:在外部函数中又定义了一个内部函数,这个内部函数有权访问外部函数的所有变量,原理如下:
3. 使用闭包可以模仿块级作用域:
4. 闭包还可以用于在对象中创建私有变量:
综上:JavaScript 中函数表达式和闭包都是很给力的特性。不过,因为创建闭包必须维护额外的作用域,所以过度使用可能会占用大量内存。
The text was updated successfully, but these errors were encountered: