| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> JavaScript知识库 -> JavaScript高级程序设计第十章--- 函数(二) -> 正文阅读 |
|
[JavaScript知识库]JavaScript高级程序设计第十章--- 函数(二) |
作者:recommend-item-box type_blog clearfix |
第十章(二)本章内容:
10.11 函数表达式函数表达式看起来就像一个普通的变量定义和赋值,即创建一个函数再把它赋值给一个变量functionName。这样创建的函数叫作匿名函数(anonymous funtion),因为function 关键字后面没有标识符。(匿名函数有也时候也被称为兰姆达函数)。未赋值给其他变量的匿名函数的name 属性是空字符串。
理解函数声明与函数表达式之间的区别,关键是理解提升。比如,以下代码的执行结果可能会出乎
如果把上面的函数声明换成函数表达式就没问题了:
10.12 递归递归函数通常的形式是一个函数通过名称调用自己。
这是经典的递归阶乘函数。虽然这样写是可以的,但如果把这个函数赋值给其他变量,就会出问题:
在写递归函数时使用arguments.callee 可以避免这个问题。
不过,在严格模式下运行的代码是不能访问arguments.callee 的,因为访问会出错。此时,可以使用命名函数表达式(named function expression)达到目的。 10.13 尾调用优化ECMAScript 6 规范新增了一项内存管理优化机制,让JavaScript 引擎在满足条件时可以重用栈帧。具体来说,这项优化非常适合“尾调用”,即外部函数的返回值是一个内部函数的返回值。比如:
即外部函数的返回值是一个内部函数的返回值时,因为返回值相同所以可以直接将outer函数弹出栈外,入栈inner函数进行计算返回后弹出。 10.13.1 尾调用优化条件P307 差异化尾调用和递归尾调用是容易让人混淆的地方。无论是递归尾调用还是非递归尾调用,都可以应用优化。引擎并不区分尾调用中调用的是函数自身还是其他函数。不过,这个优化在递归场景下的效果是最明显的,因为递归代码最容易在栈内存中迅速产生大量栈帧。
10.13.2 尾调用优化的代码可以通过把简单的递归函数转换为待优化的代码来加深对尾调用优化的理解。下面是一个通过递归计算斐波纳契数列的函数:
显然这个函数不符合尾调用优化的条件,因为返回语句中有一个相加的操作。 解决这个问题也有不同的策略,比如把递归改写成迭代循环形式。不过,也可以保持递归实现,但将其重构为满足优化条件的形式。为此可以使用两个嵌套的函数,外部函数作为基础框架,内部函数执行递归:
这样重构之后,就可以满足尾调用优化的所有条件,多次调用也不会对浏览器产生威胁。 写成迭代:
10.14 闭包闭包指的是那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的。 关于闭包的分析及例题 函数执行时,每个执行上下文中都会有一个包含其中变量的对象。全局上下文中的叫变量对象,它会在代码执行期间始终存在。而函数局部上下文中的叫活动对象,只在函数执行期间存在。
对于上面这个例子, 结构如下图所示: 函数内部的代码在访问变量时,就会使用给定的名称从作用域链中查找变量。函数执行完毕后,局部活动对象会被销毁,内存中就只剩下全局作用域。 当使用闭包时,
上面这个代码执行完后,compare会被赋值为createComparisonFunction中的一个内部函数,执行后会具有下面这种结构:
同时,由于这个匿名函数compare会一直拥有对父函数活动对象的引用,所以createComparisonFunction()执行完毕后,其执行上下文的作用域链会销毁,但它的活动对象仍然会保留在内存中,直到匿名函数被销毁后才会被销毁:
把compareNames 设置为等于null 会解除对函数的引用,从而让垃圾回收程序可以将内存释放掉。
10.14.1 this对象在闭包中使用this 会让代码变复杂。如果内部函数没有使用箭头函数定义,则this 对象会在运行时绑定到执行函数的上下文。
匿名函数在这种情况下不会绑定到某个对象,这就意味着this 会指向window,除非在严格模式下this 是undefined。 例:
分析: 这里至于为什么返回的是window对象的值? 每个函数在被调用时都会自动创建两个特殊变量:this 和arguments。内部函数永 换一种写法:
这里与之前一个例子的区别在于,在定义匿名函数之前,先把外部函数的this 保存 因为getIdentityFunc()的上下文对象是object,所以会将object对象保存到变量that中,这样在内部函数中调用时,会从作用域链找到这个that的变量并且没有名称冲突,所以会返回object的对应属性。
10.14.2 内存泄漏闭包产生的引用会导致垃圾处理程序无法回收(引用计数)。 要赋值null才能彻底保证空间回收。 10.15 立即调用的函数表达式立即调用的匿名函数又被称作立即调用的函数表达式(IIFE,Immediately Invoked FunctionExpression)。它类似于函数声明,但由于被包含在括号中,所以会被解释为函数表达式。紧跟在第一组括号后面的第二组括号会立即调用前面的函数表达式。下面是一个简单的例子:
第一个括号内部会被看做一个函数表达式,
因此最后面的第二个括号会被看作执行这个函数,函数会立即执行。
使用IIFE 可以模拟块级作用域,即在一个函数表达式内部声明变量,然后立即调用这个函数。这样位于函数体作用域的变量就像是在块级作用域中一样。
前面的代码在执行到IIFE 外部的console.log()时会出错,因为它访问的变量是在IIFE 内部定义的,在外部访问不到。在ECMAScript 5.1 及以前,为了防止变量定义外泄,IIFE 是个非常有效的方式。这样也不会导致闭包相关的内存问题,因为不存在对这个匿名函数的引用。为此,只要函数执行完毕,其作用域链就可以被销毁。 在ECMAScript 6 以后有了块级作用域,IIFE 就没有那么必要了。 10.16 私有变量任何定义在函数或块中的变量,都可以认为是私有的,因为在这个函数或块的外部无法访问其中的变量。私有变量包括函数参数、局部变量,以及函数内部定义的其他函数。即一般方法不能直接访问,但通过特殊方法可以访问的变量。 通过闭包可以创建出能够访问私有变量的公有方法,即特权方法。 特权方法是能够访问函数私有变量(及私有函数)的公有方法。在对象上有两种方式创建特权方法。第一种是在构造函数中实现,比如:
这个模式是把所有私有变量和私有函数都定义在构造函数中。然后,再创建一个能够访问这些私有成员的特权方法。这样做之所以可行,是因为定义在构造函数中的特权方法其实是一个闭包,它具有访 在上面的例子中,不能直接访问privateVariable 和privateFunction(),唯一的办法是使用publicMethod()。 如下面的例子所示,可以定义私有变量和特权方法,以隐藏不能被直接修改的数据:
这段代码中的构造函数定义了两个特权方法:getName()和setName()。每个方法都可以构造函 私有变量name 对每个Person 实例而言都是独一无二的,因为每次调用构造函数都会重新创建一套变量和方法。不过这样也有个问题:必须通过构造函数来实现这种隔离。正如第8 章所讨论的,构造函数模式的缺点是每个实例都会重新创建一遍新方法。使用静态私有变量实现特权方法可以避免这个问题。 10.16.1 静态私有变量特权方法也可以通过使用私有作用域定义私有变量和函数来实现。
在这个模式中,匿名函数表达式创建了一个包含构造函数及其方法的私有作用域。首先定义的是私有变量和私有函数,然后又定义了构造函数和公有方法。公有方法定义在构造函数的原型上,与典型的原型模式一样。注意,这个模式定义的构造函数没有使用函数声明,使用的是函数表达式。 基于同样的原因(但操作相反),这里声明MyObject 并没有使用任何关键字。因为不使用关键字声明的变量会创建在全局作用域中,所以MyObject 变成了全局变量,可以在这个私有作用域外部被访问。 这个模式与前一个模式的主要区别就是,私有变量和私有函数是由实例共享的。因为特权方法定义
这里的Person 构造函数可以访问私有变量name,跟getName()和setName()方法一样。使用这种模式,name 变成了静态变量,可供所有实例使用。这意味着在任何实例上调用setName()修改这个变量都会影响其他实例。 像这样创建静态私有变量可以利用原型更好地重用代码,只是每个实例没有了自己的私有变量。最 10.16.2 模块模式在一个单例对象上实现了相同的隔离和封装。单例对象(singleton)就是只有一个实例的对象。按照惯例,JavaScript 是通过对象字面量来创建单例对象的,如下面的例子所示:
创建了一个单独的对象。 模块模式是在单例对象基础上加以扩展,使其通过作用域链来关联私有变量和特权方法。模块模式的样板代码如下:
模块模式使用了匿名函数返回一个对象。在匿名函数内部,首先定义私有变量和私有函数。之后,创建一个要通过匿名函数返回的对象字面量。这个对象字面量中只包含可以公开访问的属性和方法。因为这个对象定义在匿名函数内部,所以它的所有公有方法都可以访问同一个作用域的私有变量和私有函数。 即公有方法通过闭包可以访问私有变量和私有函数。 在模块模式中,单例对象作为一个模块,经过初始化可以包含某些私有的数据,而这些数据又可以通过其暴露的公共方法来访问。以这种方式创建的每个单例对象都是Object 的实例,因为最终单例都由一个对象字面量来表示。 10.16.3 模块增强模式另一个利用模块模式的做法是在返回对象之前先对其进行增强。这适合单例对象需要是某个特定类型的实例,但又必须给它添加额外属性或方法的场景。
本质上还是通过闭包来访问私有变量,通过为返回对象添加内部函数,在内部函数中获取私有方法和变量。但无法直接获取私有方法和变量,实现了私有。 如上例在公共方法中调用了私有方法。 小结函数是JavaScript 编程中最有用也最通用的工具。ECMAScript 6 新增了更加强大的语法特性,从而让开发者可以更有效地使用函数。
|
|
JavaScript知识库 最新文章 |
ES6的相关知识点 |
react 函数式组件 & react其他一些总结 |
Vue基础超详细 |
前端JS也可以连点成线(Vue中运用 AntVG6) |
Vue事件处理的基本使用 |
Vue后台项目的记录 (一) |
前后端分离vue跨域,devServer配置proxy代理 |
TypeScript |
初识vuex |
vue项目安装包指令收集 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 | -2024/12/27 6:33:22- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |
数据统计 |