《JavaScript模式》

9787512329232

模式是针对普遍问题的解决方案。更进一步说,模式是解决一类特定问题的模板。

模式会有助于将问题分解为一个个像积木一样的小模块,并集中精力处理从各种烦琐的细节中提炼出来的该问题所特有的部分。

模式通过提供一个通用的词典来帮助我们更好地进行交流,因此学习并识别模式是很重要的。

第一章:简介

JavaScript是一门与众不同的语言。它没有类,并且函数时用于很多任务的顶层类对象。

模式

  • 广义上模式是指,“重现事件或者对象的主题……它是一个可以用来产生其他事物的末班或者模型”。

  • 在软件开发过程中,模式是指一个通用问题的解决方法。一个模式不仅仅是一个可以用来复制粘贴的代码解决方案,更多地是提供了一个更好的实践经验、有用的抽象化表示和解决一类问题的模板。

  • 学习和识别模式是非常重要的,理由如下(为什么要学习模式):

    • 通过学习模式,可以帮助我们使用经过实践证明有效的经验来编写代码,而无需做很多无用的工作。(大概是节约时间吧🤔)
    • 模式提供了某种程度上的抽象。大脑在一定的时间内仅能记住一定数量的内容,因此当思考更复杂的问题时,使用模式可以让您集中精力去用已有的模式来解决该问题,而不需被一些低层次的细节所困扰。(真实说到了我的心底啊😂)
    • 模式可以改善开发者和开发团队之间的交流,通常开发者是采取远程交流而不是面对面交流。为一些编码技术或者方法贴上标签并命名,可以很方便地确保双发能够了解对方切实想表达的意思。栗子一个,如果需要表达“用大括号括上一个函数,并在刚刚定义这个函数的结束位置放置一个括号来调用该函数”这层意思时,直接使用“立即函数IIFE”这样的称法更为简单明了。
  • 相对于JavaScript的设计模式来说,过去设计模式主要是从强类型的视角进行研究的,列入C++或Java等。有时将这些模式严格地应用于JavaScript这样弱类型动态语言是没有必要的,这些模式是适用于处理请类型语言的一些本性和基于类的继承。

JavaScript基本概念

最近我对js基本概念就是碎碎念了~

面向对象

  • JS中只有五种基本类型不是对象:数值类型、字符串类型、布尔类型、Null类型和Undefined类型。其中前三个类型有对应的以基本类型封装形式体现的对象表示,且三者类型的值可以实现向对象的转换。

  • 在JavaScript中,一旦定义好了变量,同时也就已经在处理对象了。

    1. 首先该变量会自动成为内置对象的一个属性,成为激活对象(如果是全局变量,则成为全局对象的一个属性);
    2. 该变量实际上也是伪类,因为它拥有其自身的属性(attributes),该属性决定了该变量是否可以被修改、被删除和一个在for-in循环中进行枚举(就那四个属性了,我不说了)。
  • 对象主要有两种类型:

    1. 原生的(Native):在ECMAScript标准中有详细描述。
    2. 主机的(Host):在主机环境中定义的(如浏览器环境)
  • 原生对象可以进一步分为内置对象(例如数组、日期对象等)和用户自定义对象(例如var myObj = {})等

  • 主机对象包含window对象和所有DOM对象(BOM还没说完的啊😒)。

原书中创建类啊、constructor啊、prototype啊都比较浅,就不说了。

第二章:基本技巧

编写可维护的代码

软件bug的修改是需要成本的,并且这项成本总是在不断地增加,特别是对于已经广泛发布的产品代码而言就更是如此。最好是一发现bug就立马修复,但这种情况只发生在刚写完这些代码后不久。否则,一旦转移到新的任务上,忘记了这部分代码,就需要重新阅读这些代码:

  • 花时间重新学习和理解相应的问题;
  • 花时间理解当时用于解决相应问题的代码。

对于大公司而言,还有另一个问题,就是最终修改代码的人,往往并不是当初写代码的人,也不是发现bug的人。因此介绍理解自己以前写的代码的时间,或者减少理解团队中他人写的代码的时间,就变得非常关键。同时,这也影响到开发完成时间(商业收入)和开发者的情绪,毕竟开发新产品更能让人兴奋,而不是花那么多时间在老项目维护上。

另一个事实在于,软件开发人员通常读代码比写代码更耗时间。一半我们专注于某个问题时,会坐下来花一个下午的时间编写出来大量的代码。这些代码可能当天就运行,但想要称为一项成熟的应用项目,需要很多工作,譬如:

  • 发现bug
  • 项目加入新的特性
  • 项目需要在新的环境中运行
  • 代码变化意图
  • 代码在新的框架体系下需要完全重写,甚至是应用一种新的语言

因为这些,最初几小时的代码,最终需要花费几周的工时来阅读。这就是为什么创建一个易于维护的代码是一个项目成功与否的关键。

易于维护的代码意味着具有如下特性:

  • 阅读性好
  • 具有一致性
  • 预见性好
  • 看起来如同一个人编写
  • 有文档

尽量少用全局变量

全局变量的问题

全局变量的问题在于它们在整个JavaScript应用或者Web页面内共享。它们生存与同一个全局命名空间内,总有可能发生的命名冲突。

网页竟然会包含一些非页面开发人员编写的代码,如:

1. 第三方JS脚本
2. 来自广告合作伙伴的脚本
3. 来自第三方用户的跟踪与分析脚本的代码
4. 各种小工具、徽章和按钮

JavaScript总是在不知不觉中就出人意料地创建了全局变量。特性一是JS可直接使用变量,甚至无需声明。第二个特性是JS会有个暗示全局变量(implied globals)的概念,即任何变量,如果未经声明,就为全局对象所有。

function sum(x,y) {
    // 反模式:暗示全局变量
    result = x + y; // result会自动成为全局变量
    return result;
}

另一种创建隐式全局变量的反模式是带有var声明的链式赋值。

// 反模式,不要使用
function foo() {
    var a = b = 0;
}

这是因为JS的从右至左的操作符优先级导致的。首先,优先级较高的表示是b=0执行,此时b没声明。此表达式的返回值为0,它被赋值给var声明的局部变量a,最终代码其实是这样的:

var a = (b = 0);

而如果对链式赋值的所有变量都进行了声明,就不会出现期望的全局变量了!

  • 避免全局变量使用的原因:
    • 代码健壮性
    • 代码可移植性

变量释放时的副作用

  • 隐式全局变量与明确定义的全局变量有细微的不同,不同之处在于是否能用delete操作符撤销变量
    • 使用var创建的全局变量(这类变量在函数外部创建)不能删除
    • 不适用var创建的隐式全局变量(就算它在函数内部创建)可以删除

所以这类隐式全局变量严格来讲不是真正的变量,而是全局对象的属性。只要属性才可以通过delete操作符删除,但变量不可以。

如果正面使用var声明的全局变量不是全局对象的属性? up!哈哈 ES5 strict模式中,为没有声明的变量赋值会抛出错误。

Single var Pattern

  • 只使用一个var在函数顶部进行变量声明是一种非常有用的模式:
    • 提供一个单一的地址以查找到函数需要的所有局部变量
    • 防止出现变量在定义前就被使用的逻辑错误
    • 帮助记牢要声明的变量,以尽可能少地使用全局变量
    • 更少的编码

提升:凌散变量的问题

JavaScript允许在函数的任意地方声明多个变量,无论在哪里声明,效果都等同于在函数顶部进行声明。这就是所谓的“提升”。这个就是之前学的预解释,不多说了。

myname = "global"; //隐式全局变量
function func() {
    alert(myname);  // -> undefined
    var myname = "local";
    alert(myname)  // -> local
}
  • 以上例子说明两个问题:变量提升+全局变量冲突

for循环

for玄幻经常用在遍历数组或者类数组对象,如argumentsHTMLCollection。这种模式的问题在于每次循环迭代时都要访问数组的长度。这样会使代码变慢,特别是myAry不是数组而是HTMLCollection。你懂得,大多数dom的函数都是返回类数组对象,它们还带dom映射呢!!

容器的麻烦在于它们在document下是活动的查询(就是我刚刚说的dom映射啦~),所以通常dom操作时非常耗时的。

  • 所以,这就是为什么好的for循环模式是将已遍历过的数组(容器)的长度缓存起来
for (var i = 0, len = myAry.length; i < len; i++) { //... }

在浏览器端,通过将HTML容器上需要遍历的次数缓存起来都会提高速度。其中在Safari 3中速度回提高两倍,而在IE7中速度回提高170倍。牛啊!!!难怪IE7那么腊鸡。

注意,当要在循环中修改容器时(例如新增加一个DOM元素),需要修改容器的长度。其实就是‘len’前置优化,但如果增减元素会出现数组塌陷。

在JSLint中推荐使用++--操作符。

  • for模式中的两个变量引出了一些细微操作,原因是:
    • 使用了最少的变量(而非最多)
    • 逐步减至0,这样通常更快,因为同0比较比同数组的长度比较,或同非0数组比较更有效

for-in循环

for-in会把非自有属性都遍历到,要小心,多使用hasOwnProperty方法。

不要增加内置的原型

增加构造函数的原型是一个增加功能性的强大方法,但是有时候会过于强大,导致影响可维护性。

  • 因此最好的方法是不要给内置的原型增加属性。以下情况除外:
    • 当未来的ECMAScript版本或者JavaScript的具体实现可能将该功能作为一个统一的内置方法时可用。
    • 如果检查了自定义的属性或方法不存在时可用。
    • 您准确的用文档记录下来,并和团队交流清楚时可用。

switch模式

可以使用标准的switch模式增加代码可读性和健壮性。

避免使用隐式类型转换

==

避免使用eavl()

eval()是一个恶魔。

使用parseInt()的数值约定

在使用parseInt(),最好设置几进制数的第二个参数。

  • 若将一个字符串转换为数组的方法:
    • +"08"
    • Number("08")

编码约定

关于代码大括号,像是for、if还是要加大括号。

大括号的位置也很重要,例如:

function func() {
    return 
    {
        name: "szy"
    };
}
// 以上代码相当于
function func() {
    return undefined;
    {
        name: "szy"
    };
}

命名运动

分隔单词

一般都是小驼峰命名法,对于构造函数就用大驼峰命名法。如果是其它不是函数的变量的话,单词全部小写,俩俩之间用下划线相连first_name。这种表示法可以很方便的明显区分函数和其它标志----基本常量和对象。

Last Updated: 6/29/2018, 3:06:00 PM