六、构造器、原型和数组
现在,我们已经习惯于在不使用检查器或 IDE 测试代码的情况下优化 JavaScript,现在是时候深入研究更复杂的优化了,特别是涉及到内存和对象创建的时候。 在本章中,我们将讨论如何使用构造器、原型和数组来优化更大的 JavaScript 代码库。
我们计划在本章中涵盖以下主题:
- 使用构造器和实例函数进行构建
- 使用原型的备用构造器
- 阵列的性能
使用构造器和实例函数构建
在这里,我们将以以下方式学习使用构造器和实例函数构建:
简单说句话
根据技能水平的不同,我们中的一些人可能知道,也可能不知道 JavaScript 中的原型是什么。 如果您是听说过 JavaScript 中的原型但没有在日常生活中使用它们的读者之一,那么您不必担心,因为我们将快速介绍基本概念以及如何将它们应用于 JavaScript 性能。
如果你知道闭包,继承,父母和孩子关系,等等是什么,觉得你属于后一类,所以想跳过这一章,我鼓励你继续阅读,至少浏览这一章, 我们作为 JavaScript 开发人员,在使用 JavaScript 多年的过程中,往往会忘记一些常见的概念,而持续关注那些影响性能的因素。
关心和提供函数名
仔细看看下面所示的这个简单的函数,看看您是否发现这个函数有什么不寻常之处。
现在,当我们查看代码时,我们可以看到一个名为AuthorName
的简单函数,它持有author
参数。 函数使用一个use strict
声明中讨论第二章,提高代码性能 JSLint【显示】,这迫使开发工具或任何其他类似的检查员来治疗范围错误的警告。 然后使用return
关键字返回author
参数。**
这看起来很正常; 然而,令许多 JavaScript 开发人员感到困惑的是函数名的结构。 注意,AuthorName
以大写a开头。 在 JavaScript 中,当我们用大写字母声明一个函数名时,实际上是在告诉 JavaScript 解释器我们在声明一个构造器。
构造器只是一个 JavaScript 函数,它的工作方式与其他函数相同。 我们甚至可以使用简单的console.log
功能打印作者的名字到控制台,如下图所示,使用开发人员工具:
如果我们使用这个代码在about:blank
开发工具控制台或虚拟 HTML 页面中运行,我们将看到控制台输出作为名称,正如我们所期望的。 问题是,为了有效地使用构造器,我们需要将它们与new
关键字一起使用。
现在您可能会问,我们如何才能知道我们现有的 JavaScript 代码是否使用了构造器。 想象一个非常大的代码库,到处都是函数; 如果甚至开发人员工具选项都没有告知我们需要使用new
而不是static
函数调用的实例,我们如何检查这一点?
幸运的是,有一个办法。 如果我们记得第 2 章,使用 JSLint提高代码性能,JSLint 可以告诉我们是否需要使用new
关键字。 我已经添加了前面的代码示例,并在 JSLint 中启用了控制台和浏览器对象。 看看 JSLint 在以下截图中给出的错误:
正如我们从 JSLint 的中看到的,在第 11 行我们给出了一个错误,Missing 'new'
作为我们唯一的错误,表明我们有一个构造器,我们需要这样使用它。
理解实例
现在解决这个问题的简单方法是将AuthorName
函数的名称改为驼峰形式; 即,我们将A
改为小写(a
)。 但这里我们要把它作为一个例子,你可能会问为什么? 在 JavaScript 中,每次我们写一个对象,一个变量,一个函数,一个数组,等等,我们都在创建对象。
通过使用实例,我们降低了对象的使用。 在 JavaScript 中,一个实例在内存中只计算一次。 例如,假设我们使用document.getElementById()
方法。 每个与该对象保存在一起的变量都有一个内存计数,但是,如果它在用new
关键字声明的对象中,该计数只被计数一次,而不是在getElementById()
每次出现时重用。 通过使用new
关键字,我们创建了构造器的一个实例(在本例中是AuthorName
),它允许我们以与通常使用它相同的方式重用该函数。
使用 new 创建实例
创建一个新实例非常简单; 我们可以简单地调用一个新的实例来运行一个函数,如下面的截图所示,在我们的console.log
函数中使用new
关键字,在第 11 行:
如果我们在一个空白页面或一个简单的 HTML 页面中运行此代码,我们将看到我们的日志并没有按照我们期望的方式输出。 但是我们可以在 Chrome 的开发工具的控制台面板上看到一个对象返回AuthorName {}
。 这表明我们实际上是在记录一个对象的新实例,而不是作者的名字。
为了正确显示这个名称,我们需要一个关键字来声明对这个构造器实例的引用。 为此,我们将使用this
关键字; 在 JavaScript 中,this
是一个在作用域中执行的精确点的引用。
JavaScript 中的this
关键字是指在使用脚本时,脚本执行点存在的作用域和变量。 例如,当您在函数中使用this
关键字时,它可以引用同样在同一作用域中(或函数内部)的变量。 通过使用this
关键字,我们可以在代码执行的某个点指向变量和对象。
作用域是一段带有自己变量和属性的 JavaScript 代码。 作用域可以包括单个 JavaScript 对象的全局作用域(即整个 JavaScript 文件),函数级作用域(其中变量和属性在函数中设置),或者如前所述,包括构造器,因为构造器也是函数。
让我们用this
关键字重写AuthorName
构造器,这样我们就可以引用我们的作用域并将我们的值打印到控制台面板。 我们需要在构造器中创建一个初始化式,以便返回作用域变量。 初始化式(有时称为init
函数)在构造器中指定某些变量,并在创建时赋值属性。
在这里,我们创建一个变量与一个this
关键字前缀表明我们引用实例构造器在我们之后,我们的函数称为init
,等于一个函数,就像我们会使用一个变量声明一个函数。 让我们在下面的截图中查看一下代码:
看看第 13 行和第 15 行; 在 13 上,我们声明一个变量author1
,使用一个new``AuthorName
构造器作为字符串参数Chad Adams
。 在本例中,author1
是AuthorName
构造器的一个实例,其中Chad Adams
作为唯一参数。
还要注意,在console.log
的第 15 行上,我们有一个init()
函数,它是构造器的内部函数。 我们也可以在构造器中创建其他函数,例如打印自定义日志信息,如下图所示:
正如第 11 行所示,我们现在添加了一个helloInfo()
函数,作用域为AuthorName
构造器,该函数使用author
参数打印出一个自定义消息。 然后,在第 20 行,我们通过简单地调用构造器的函数在console.log
之外调用它,构造器有自己的console.info
函数包装在里面。
这有助于将我们的逻辑限制在代码库中的单个对象中,并保持代码的良好组织。 这叫做对象定向; 它非常适合重用代码,但可能会导致 JavaScript 的性能问题。 我们来举个例子。 在这里,我们有两个相同代码的示例,每个示例都封装在console.time
和console.timeEnd
函数中。 下面的截图显示了我们审核过的代码和渲染代码的结果时间:
所以总的时间大约是 2.5 毫秒。 这个不是太糟糕,但现在让我们看看如果我们使用简单的非构造器会发生什么,以及呈现相同输出的速度会是什么。 如下面的截图所示,我将构造器分开并创建了两个单独的函数。
我还在次要函数中调用了主要的authorName
函数,就像在我们的console.log
函数中一样,以打印作者的名字。 让我们运行下一个截图中更新后的代码,看看它的运行速度比构造器方法快还是慢。 但是,请记住,根据我们系统的速度和浏览器,结果可能会有所不同。
因此,对于静态函数,我们的结果将停留在 4 毫秒左右,这比我们的实例构建对象要长。 所以,这是 JavaScript 中静态函数比原型函数更好的用法!
使用原型替代构造器
在这里,我们将学习使用原型的备用构造器的概念。
从记忆角度理解原型
我们已经介绍了如何在构造器中创建实例函数,我们还学习了如何在一个构造器中使用this
关键字。 但是,还有一件事需要讨论:在构造器外部附加另一个实例方法的能力,这在很多方面都很有帮助。 首先,如果需要,它允许我们作为开发人员在预先编写的构造器之外创建函数。 其次,它还使我们的内存使用量较小。 在深入研究之前,让我们重新编写构造器代码来使用原型,如下面的截图所示:
现在查看更新后的代码,我们可以看到构造器被删除了,但被拉出了构造器:然后它们被移动到AuthorName
函数的原型中,使用的函数名与前面使用的相同。 现在,你可以注意到,在第 10 行和第 13 行,我们可以在原型函数中使用这个,因为我们引用构造器的实例来打印该实例的特定变量。
哪个更快,原型函数还是构造器?
您可能还注意到,在第 16 行到第 22 行中,我再次在函数调用中添加了和console.timeEnd
函数。 所以你认为原型与标准构造器相比会更快还是更慢? 在下面的截图中,我们可以看看结果:
哇,发射原型需要 4.2 毫秒,而使用构造器方法需要 2.1 毫秒; 这里发生了什么? 实际上,我们在构造器之后创建了函数。 输出很慢,但这在原型中是可以预料到的,因为意图是用原型添加构造器。
此时,我们可能会想“哦,我不知道,我不应该再编写原型了!” 现在,在我们开始从项目文件中删除原型之前,我想解释一下原型的可伸缩性。 这是真的,当调用构造器时,原型可能会更慢……” 我说的小是什么意思?” 对于像这个特定示例这样的小型原型使用,我们可以看到原型运行得比传统构造器方法慢。
这就是问题所在; 对于较大的项目,大型应用中的构造器可能有 50 个函数、200 个函数,依此类推。 当我们一次又一次地调用那些较大的构造器时,仅仅调用构造器的实例就会占用相当大的内存,因为它必须准备好包含在其中的所有函数。
通过使用原型方法,这些初始构造器调用将在内存中存储一次。 对于小型原型的使用,性能优势是不明显的,因为我们使用内存,就像我们有一个简单的静态函数,但一旦它被设置,它就在内存中,不需要像静态 JavaScript 函数那样被召回或重新处理。
关于原型继承的另一件事是,尽管使用原型继承可能会导致性能问题,但它对大型代码库非常有帮助。 如果项目有范围问题或使用可能导致冲突的库,请考虑使用名称空间。 它的工作原理类似于原型类,但功能类似于简单的静态函数,前面加了一个名称空间以防止冲突。
阵列性能
在处理性能时,我们通常不会考虑数组,但这里有几点值得一提。 首先,当您试图处理大量数据时,大型数组可能会很混乱,而且会影响性能。 通常,对于数组,我们只需要担心两件事:搜索和数组大小。
优化数组搜索
让我们创建一个数组,其中有很多的值; 在这里,我创建了一个数组,名为myArray
,其中有 1001 个值,一个键值字符串和数组的索引。 您可以在 Packt Publishing 网站上示例代码的Chapter_6
文件夹中的06_09.js
文件中获取完整版本。 以下是总数组的部分代码示例:
在数组中查找值有两种方法; 第一个使用了indexOf()
函数,这是一个特定于数组的函数,它查找每个值并返回搜索值的索引。 另一种方法是直接指定索引值,这将返回值(假设我们知道所需值的索引)。
让我们尝试一个实验,我们将使用预先制作的 1001 个值的myArray
,并使用indexOf()
函数遍历它们,然后再次使用一个数组。 我们已经在我们的myArray
之后添加了代码,我们已经把这个代码块包裹在console.time
和console.timeEnd
函数中,如图所示,在 Chrome开发工具中呈现的时间:
这表明我们搜索这个大数组的结果大约是 5.9 毫秒。 现在,为了进行比较,我将保留indexFound
变量,尽管我们可以简单地指定数组值的下标。 我们也将使用相同的索引值进行搜索,即541
。 让我们更新我们的代码,如这里所示,并查看我们的结果在 Chrome开发工具:
看起来我们的结果削减了我们的索引搜索性能时间相当多。 所以,当你在 JavaScript 中构造数组时,只在需要时使用indexOf
,如果可能的话,尝试直接调用索引。 那么为什么时间输出如此不同呢? 这是简单的; 在第二个示例中,我们手动指示数组的位置,而不是让 JavaScript 自己查找键。 这加快了 JavaScript 解释器遍历数组并提供值的速度。
小结
在本章中,我们学习了构造器的正确使用。 我们学习了 JavaScript 中使用new
关键字的实例,发现使用构造器可以在限定代码范围的同时加快静态代码的速度。
我们了解了原型,以及它们如何能够很好地扩展大型应用,而在较小的项目中却没有增加什么价值。 最后,我们还学习了如何使用indexOf
函数搜索数组以及数组的性能损失。
在下一章,我们将看看如何编写 JavaScript 来优化我们的项目的文档对象模型。
版权属于:月萌API www.moonapi.com,转载请注明出处