编写可维护的JavaScript--基本的格式化、注释、语句和表达式、变量、函数和运算符

基本的格式化

缩进层级

  • 使用制表符进行缩进

每一个缩进层次都用单独的制表符(tab character)表示。所以一个缩进层级是一个制表符,两个缩进层级为两个制表符。这种方法有两个主要的好处:

  1. 制表符和缩进层级之间是一对一的关系,这是符合逻辑
  2. 文本编辑器可以配置制表符的展现长度
  • 使用空格符进行缩进

每个缩进层级由多个空格字符组成。在这种观点中有三种具体的做法:2个空格表示一个缩进,4个空格表示一个缩进,以及8个空格表示一个缩进。

尽管是选择制表符还是选择空格做缩进只是一个个人偏好,但绝对不用将两者混用

语句结尾

有赖于分析器的自动分号插入(Automatic Semicolon Insertion, ASI)机制,JavaScript代码省略分号也是可以正常工作的。ASI会自动寻找代码中应当使用分号但实际没有分号的位置,并插入分号。大多数场景下ASI都会正确插入分号,大多数场景下都会正确插入分号,不会产生错误。但ASI的分号插入规则非常复杂且很难记住。

// 原始代码 
function getData () { 
    return 
    { 
        title: "Maintainable JavaScript",
        author: 'Nicholas C. Zakas' 
    } 
} 
// 分析器会将它理解成 
function getData () { 

    return; 
    { 
        title: "Maintainable JavaScript", 
    author: 'Nicholas C. Zakas' 
    } 
}

在上面这段代码中,函数的getData()的本意是返回一个包含一些数据的对象。然而,return之后新起了一行,导致return后被插入了一个分号,这会导致函数的返回值是undefined

可以通过将左花括号移至与return同一行的位置来修复这个问题

// 这段代码工作正常,尽管没有用分号 
function getData () { 
    return { 
        title: "Maintainable JavaScript", 
        author: 'Nicholas C. Zakas' 
    } 
}

行的长度

  1. Java语言编程规范中规定源码里单行长度不超过80个字符,文档中代码单行长度不超过70个字符。
  2. Android开发者编码风格指南规定单行代码长度不超过100个字符。
  3. 非官方的Ruby编程规范中规定单行代码长度不超过80个字符。
  4. Python编程规范中规定单行代码长度不超过79个字符。

JavaScript风格指南中很少提及行的长度,但Crockford的代码规范中指定一行的长度为80个字符。

 

换行

通常我们会在运算符后换行,下一行会增加两个层级的缩进:

// 好的做法: 在运算符后换行,第二行追加两个缩进
callAFunction (document, element, window, "some string value", true, 123,
        navgation);
        
// 不好的做法:第二行只有一个缩进
callAFunction (document, element, window, "some string value", true, 123,
    navgation);
    
不好的做法:在运算符之前换行了
callAFunction (document, element, window, "some string value", true, 123
    ,navgation);

总是将一个运算符置于行尾,ASI就不会自作主张的插入分号,也就避免了错误的发生。

这个规则有一个例外:当给变量赋值时,第二行的位置应当和赋值运算符的位置保持对齐。比如:

var result = something + anotherThing + yetAnotherThing + somethingElse + 
             anotherSomethingElse

 

空行

有时一端代码的语义和另一段代码不相关,这时就应该使用空行将它们分隔,确保语义有关联的代码展现在一起。

  • 方法之间
  • 在方法中局部变量(local variable)和第一条语句之间
  • 多行或单行注释之前。
  • 在方法内的逻辑片段之间插入空行,提高可读性

命名

JavaScript语言的核心ECMAScript,即是遵照了驼峰式大小写(Camel case)命名法。驼峰法大小写(Camel case)命名法是由小写字母开始的,后续每个单词首字母都大写。比如:

var thisIsMyName
var anotherVariable

变量和函数

变量名应当总是遵守驼峰法大小写命名法,并且命名前缀应当是名词。以名词作为前缀可以让变量和函数区分开来。因为函数名前缀应当是动词:

// 好的写法
var count = 10;
var myName = "Nicholas";
var found = true;

// 不好的写法:变量看起来像函数
var getCount = 10;
var isFound = true;

// 好的写法
function getName () {
    return myName;
}

// 不好的写法:函数看起来像变量
function thnName () {
    return myName;
}

对于函数和方法命名来说,第一个单词应该是动词,这里有一些使用动词常见的约定

动词

含义

can

函数返回一个布尔值

has

函数返回一个布尔值

is

函数返回一个布尔值

get

函数返回一个非布尔值

set

函数用来保存一个值

常量

为了区分普通的变量(变量的值是可变的)和常量(常量的值初始化之后就不能变了),一种通用的命名约定应运而生。这个约定源自于C语言,它使用大写字母和下划线来命名,下划线用以分隔单词,比如:

var MAX_COUNT = 10;
var URL = "http://www.nczonline.net"

使用这种不同的约定来定义普通的变量和常量,使两者非常易于区分。

if (count < MAX_COUNT) {
    dosomething();
}

构造函数

构造函数的命名遵照大驼峰命名法(Pascal Case)

构造函数的命名也常常是名词,因为它们是用来创建某个类型的实例的。

以大驼峰命名法(Pascal case)命名的函数如果是名词的话,前面一定会有new运算符。

var me = Person("Nicholas");
var you = getPerson("Michael");

直接量

JavaScript中包含一些类型的原始值:字符串、数字、布尔值、null和undefined。同样也包含对象直接量和数组直接量。这其中,只有布尔值是自解释(self-explanatory)的,其他的类型或多或少都需要思考一下它们如何才能精确地表达出来。

字符串

在JavaScript中,字符串是独一无二的。字符串可以用双引号括起来,也可以用单引号括起来,比如:

// 合法的JavaScript代码
var name = "Nicholas says, \"Hi.\"";

// 也是合法的JavaScript代码
var name = 'Nicholas says, "Hi"';

两种做法在功效上完全一致。因此在这段示例代码中,在使用双引号括起来的字符串里需要对双引号进行转义,而在使用单引号括起来的字符串则不必如此。

关于字符串还有另外一个问题需要注意,即创建多行字符串。这个特性并非来自JavaScript语言本身,却在几乎所有的(JavaScript)引擎中正常工作

// 不好的写法
var longString = "Here's the story, of a man \
named Brady.";

多行字符串的一种替代写法是使用字符串连接符(+)将字符串分成多份

// Good
var longString = "Here's the story, of a man"+
"named Brady.";

数字

在JavaScript中的数字类型只有一种,因为所有数字形式---整数和浮点数----都是存储为相同的数据类型

// 整数
var count = 10;

// 小数
var price = 10.0;
var price = 10.00;

// 不推荐的小数写法: 没有小数部分
var price = 10.;

// 不推荐的小数写法:没有整数部分
var price = .1;

// 不推荐的写法:八进制写法已经被弃用了
var num = 010;

// 十六进制写法
var num = 0xA2;

// 科学计数法
var num = 1e23;

null

null是一个特殊值,但我们常常误解它,将它和undefined搞混,在下列场景中应当使用null。

  • 用来初始化一个变量,这个变量可能赋值为一个对象
  • 用来和一个已经初始化的变量比较,这个变量可以是也可以不是一个对象
  • 当函数的参数期望是对象时,用作参数传入
  • 当函数的返回值期望是对象时,用作返回值传出

还有下面一些场景不应当使用null。

  • 不用使用null来检测是否传入了某个参数
  • 不要用null来检测一个未初始化的变量

理解null最好的方式是将它当作对象的占位符(placeholder).

undefined

undefined是一个特殊值,null === undefined结果是true。那些没有被初始化的变量都有一个初始值,即undefined,表示这个变量等待被赋值。

typeof的行为也很让人费解,因为不管是值是undefined的变量还是未声明的变量,typeof运算结果都是“undefined”。

当变量未声明时,如果你使用了一个可能(或可能不会)赋值为一个对象的变量时,则将其赋值为null。

// 好的做法
var person = null;
console.log(person === null); // true

将变量初始值赋值为null表明了这个变量的意图,它最终很可能赋值为对象。typeof运算符运算null的类型时返回“object”,这样就可以和undefined区分开了。

对象直接量

创建对象最流行的一种做法是使用对象直接量,在直接量中直接写出所有属性。

// 不好的写法
var book = new Object();
book.title = "Maintainable JavaScript";
book.author = "Nicholas C. Zakas";

对象直接量允许你将所有的属性都括在一对花括号内。

当定义对象直接量时,常常在第一行包含左花括号,每一个属性的名值对都独占一行,并保持一个缩进,最后右花括号也独占一行。比如:

// 好的写法
var book = {
    title: "Maintainable JavaScript",
    author: "Nicholas C. Zakas"
};

数组直接量

// 不好地写法
var colors = new Array("red", "green", "blue");
var numbers = new Array(1, 2, 3, 4);

可以使用两个方括号将数组初始元素括起来,来替代使用Array构造函数地方式来创建数组。(不使用new来创建Array数组)

// 好的做法
var colors = ["red", "green", "blue"];
var numbers = [1, 2, 3, 4]

 

注释

单行注释

单行注释以两个斜线开始,以行尾结束。

// 这是一句单行注释

单行注释有三种使用方法:

  • 独占一行地注释,用来解释下一行代码。这行注释之前总是有一行空行,且缩进层级和下一行代码保持一致。
  • 在代码行的尾部的注释。代码结束到注释之间至少一个缩进。注释(包括之前的代码部分)不应当超过单行最大字符数限制,如果超出了,就将这条注释放置于当前代码行的上方
  • 被注释掉的大段代码(很多编辑器都可以批量注释掉多行代码)

单行注释不应当以连续多行注释的形式出现,除非你注释掉一大段代码。只有当需要注释一段很长的文本时才使用多行注释。

// 好的写法
if (condition) {
    
    // 如果代码执行到这里,则表明通过了所有安全性检查
    allowed();
}

// 不好的写法:注释之前没有空行
if (condition) {
    // 如果代码执行到这里,则表明通过了所有安全性检查
    allowed()
}

// 不好的写法:错误的缩进
if (condition) {

// 如果代码执行到这里,则表明通过了所有安全性检查
    allowed()
}

// 好的写法
var result = something + somethingElse; // somethingElse 不应当取值为null

// 不好的写法:代码和注释之间没有间隔
var result = something + somethingElse; //somethingElse 不应当取值为null

// 好的写法
//if (condition) {
//    doSomething();
//    thenDosomeThingElse();
//}

// 不好的写法: 这里应当用多行注释
// 接下来的这段代码非常难,那么,让我详细解释一下
// 这段代码的作用是先判断条件是否为真
// 只有为真时才会执行。这里的条件是通过
// 多个函数计算出来的,在整个会话生命周期内
// 这个值是可以被修改的
if (condition) {
    // 如果代码执行到这里,则表明通过了所有安全性检查
    allowed();
}

 

多行注释

多行注释可以包裹跨行文本。它以/*开始,以*/结束。多行注释不仅仅可以用来包裹跨行文本,这取决于你,下面这些都是合法的注释。

/* 我的注释 */
/* 另一段注释
这段
注释包含两行*/
/*
又是一段注释
这段注释同样包含两行
*/

还有一种比较推荐的多行注释方法

/*
* 另一个注释
* 这段注释包含两行文本
*/

多行注释总是会出现在将要描述的代码之前,注释和代码之间没有空行间隔。和单行注释一样,多行注释之前应当有一个空行,且缩进层级和其描述的代码保持一致。来看下面这段例子:

//  好的写法
if (condition) {

    /*
    * 如果代码执行的这里
    * 说明通过了所有的安全性检测
    */
    allowed();
} 

// 不好的写法: 注释之前无空行
if (condition) {
    /*
    * 如果代码执行的这里
    * 说明通过了所有的安全性检测
    */
    allowed();
}

// 不好的写法: 星号后没有空格
if (condition) {
    /*
    *如果代码执行的这里
    *说明通过了所有的安全性检测
    */
    allowed();
}

// 不好的写法: 错误的缩进
if (condition) {
    
/*
* 如果代码执行的这里
* 说明通过了所有的安全性检测
*/
    allowed();
}

// 不好的写法: 代码尾部注释不要用多行注释格式
var result = something + somethingElse; /*somethingElse 不应当取值为null*/

 

使用注释

何时添加注释是一个问题,一种通行的指导原则是,当代码不够清晰时添加注释,而当代码很明了时不应当添加注释。

// 不好的写法

//初始化count
var count = 10;

// 好的写法

// 改变这个值可能会让它变成青蛙
var count = 10;

添加注释的一般原则是,在需要让代码变得清晰时添加注释。

 

难于理解的代码

难于理解的代码通常都应当加注释。根据代码的用途,你可以用单行注释、多行注释,或是混用这两种注释。关键是让其他人更容易读懂这段代码。比如,这段示例代码摘自YUI类库中的Y.mix()方法。

// 好的写法

if (mode) {
    
    /*
    * 当mode为2时(原型到原型,对象到对象),这里只递归执行一次
    * 用来执行原型到原型的合并操作。对象到对象的合并操作
    * 将会被挂起,在合适的时机执行
    */
    if (mode === 2) {
        Y.mix(receiver.prototype, supplier.prototype, overwrite, whitelist, 0, merge);
    }
    
    /*
    * 根据指定的模型类型,我们可能会从源对象拷贝至原型中
    * 或是从原型拷贝至接受对象中
    */
    from = mode === 1 || mode === 3 ? supplier.prototype : supplier;
    to = mode === 1 || mode === 4? receiver.prototype : receiver;
    
    /*
    * 如果supplier或receiver不含原型属性时 
    * 则逻辑结束,并返回undefined,如果有原型属性
    * 则逻辑结束并返回receiver
    */
    if (!from || !to) {
        return receiver;
    }
} else {
    from = supplier;
    to = receiver;
}

可能被误认为错误的代码

另一个合适添加注释的好时机是当代码看上去有错误时。

while (element && (element = element[[axis]])) { // 提示:赋值操作
    if ( (all || element[TAG_NAME]) && (!fn || fn(element)) ) {
        return element;
    }
}

 

浏览器特性hack(浏览器区别)

JavaScript程序员常常会编写一些低效的、不雅的、彻头彻尾的肮脏代码,用来让低级浏览器正常工作。

var ret = false

if ( !needle || !element || !needle[NODE_TYPE] || !element[NODE_TYPE]) {
    ret = false;
} else if ( element[CONTAINS] ) {
    // 如果needle不是ELEMENT_NODE时,IE和Safari
    if (Y.UA.opera || needle[NODE_TYPE] === 1) {
        ret = element[CONTAINS](neddle);
    } else {
        ret = Y_DOM._bruteContains(element, needle);
    }
} else if (element[COMPARE_DOCUMENT_POSITION]) { // fecko
    if (element === needle || !!(element[COMPARE_DOCUMENT_POSITION](needle) & 16)) {
        ret = true
    }
}
return ret;

 

文档注释

最流行的一种格式来自于javaDoc文档格式:多行注释以单斜线加双星号(/**)开始,接下来的描述信息,其中使用@符号来表示一个或多个属性。来看一段来自YUI的源码的例子

/**
返回一个对象,这个对象包含被提供对象的所有属性
后一个对象的属性会覆盖前一个对象的属性
传入一个单独的对象,会创建一个它的浅拷贝(shallow copy)
如果需要深拷贝(deep copy),请使用'clone()'
@method merge
@param {Object} 被合并的一个或多个对象
@return {Object} 一个新的合并后的对象
**/
Y.merge = function () {
    var args     = arguments,
        i        = 0,
        len      = args.length,
        result   = {};
    for (; i < len; ++i) {
        Y.mix(result, args[i], true)
    }
    
    return result;
};

当使用文档注释时,你应当确保对如下内容添加注释

所有的方法

  • 应当对方法、期望的参数和可能的返回值添加注释描述

所有的构造函数

  • 应当对自定义类型和期望的参数添加注释描述

所有包含文档化方法的对象

  • 如果一个对象包含一个或多个附带文档注释的方法,那么这个对象也应当适当地针对文档生成工具添加文档注释。
  • 当然,注释地详细个格式和用法最终还是由你所选择地文档生成工具决定的

 

语句和表达式

在JavaScript中,诸如if和for之类的语句有两种写法,使用花括号包裹的多行代码或者不使用花括号的单行代码。比如:

// 不好的写法,尽管这是合法的JavaScript的代码
if (condition)
    dosomething()
    
// 不好的写法,尽管是合法的JavaScript代码
if (condition) doSomething();

// 好的写法
if (condition) {
    doSomething();
}

// 不好的写法,尽管是合法的JavaScript代码
if (condition) { doSomething(); }

 

所有的块语句都应当使用花括号,包括:

  1. if
  2. for
  3. while
  4. do...while...
  5. try...catch...finally

花括号的对其方式

关于块语句的另一个话题是花括号的对齐方式。有两种主要的花括号对齐风格。第一种风格是,将左花括号放置在块语句中第一句代码的末尾,比如:

if (condition) {
    doSomething();
} else {
    doSomethingElse();
}

 

花括号的第二种对齐风格是将左花括号放置于块语句首行的下一行。比如:

if (condition) 
{
    doSomething();
} else 
{
    doSomethingElse();
}

推荐使用第一种花括号对齐格式

 

块语句间隔

块语句间隔有三种主要的风格:

  • 第一种风格是,在语句名、圆括号和左花括号之间没有空格间隔
if(condition){
    doSomething();
}
  • 第二种风格是,在左圆括号之前和右圆括号之后各自添加一个空格。比如:
if (condition) {
    doSomething();
}
  • 第三种风格是在左圆括号和右圆括号前后各添加一个空格
if ( condition ) {
    doSomething();
}

推荐第二种风格

 

switch 语句

执行过程

  • case后面的值:就是用来和表达式的值进行匹配的值
  • break:表示中断的意思
  • default: 所有的值都不匹配的时候,执行default

执行流程:

  • 首先计算式的值
  • 将这个计算出来的值依次和case后面的进行比较,一旦有匹配的,就执行对应的语句,遇到break就结束
  • 如果所有case都匹配,就执行语句体 n+ 1

缩进

对于JavaScript程序员来说,switch语句的缩进格式是一个由争议的话题。很多人使用Java风格的switch语句,看起来像下面这样:

switch(condition) {
    case "first":
        // 代码
        break;
        
    case "second":
        // 代码
        break;
        
    case "third":
        // 代码
        break;
      
    default:
        // 代码  
}

这种格式的独特之处在于:

  • 每条case语句相对于switch关键字都缩进一个层级
  • 从第二条case语句开始,每条case语句前后各有一个空行

还有另外一种格式:

switch(condition) {
case "first":
    // 代码
    break;
case "second":
    // 代码
    break;
case "third":
    // 代码
    break;
default:
    // 代码  
}

这种写法和前一种写法的主要不同之处在于,case关键字保持和switch关键字左对齐。同样要注意,在语句中并没有空行的存在

 

case 语句的“连续执行”

“执行完一个case后连续执行(fall through)下一个case”,这是否是一种广被认可的实践,也是备受争议的一个问题。

有很多人认为case的连续执行是一种可接受的编程方法,我很同意这种观点,只要程序逻辑非常清晰即可,比如:

switch (condition) {
    
    // 很明显的依次执行
    case "first";
    case "second":
        // 代码
        break;
        
    case "third":
        // 代码
        
        /* fall through */
    default:
        // 代码
}

Crockford的编程规范是禁止switch语句中出现连续执行(fall through)的

 

default

switch语句中另外一个需要讨论的议题是,是否需要default。很多人认为无论何时都不应当省略default,哪怕default什么都不做。比如:

switch(condition) {
    case "first":
        // 代码
        break;
        
    case "second":
        // 代码
        break;
    
    default:
        // default 中没有逻辑
}

还有一种是在默认行为且写了注释的情况下省略default,来看这段例子。

switch(condition) {
    case "first":
        // 代码
        break;
        
    case "second":
        // 代码
        break;
    
    // 没有 default
}

 

with语句

with语句可以更改包含的上下文解析变量的方式。通过with可以用局部变量和函数的形式来访问特定对象的属性和方法,这样就可以将对象前缀统统省略掉。

比如:

var book = {
    title: "Maintainable JavaScript",
    author: "Nicholas C. Zakas"
}

var message = "The book is";

with (book) {
     message += thtle;
    message += "by" + author;
}

在这个例子中,with语句花括号内的代码中的book的属性都是通过局部变量来读取的,以增强标识符的解析。

 

在严格模式下,with语句是被明确禁止的,如果使用则报语法错误。这表明ECMAScript委员会确信with不应当继续使用。

 

for循环

for循环有两种:一种是传统的for循环,是JavaScript从C和Java中继承而来;另外一种是for-in循环,用来遍历对象的属性。这两种循环咋一看很类似,但却有着完全不同的用法。传统的for循环往往用于遍历数组成员,比如:

var values = [1,  2, 3, 4, 5, 6, 7],
    i,len;
for (i=0, len=values.length; i < len; i++) {
    process(values[i])
}

有两种方法可以更改循环的执行过程(除了使用return或throw语句)。第一种方法是使用break语句。不管所有的循环迭代有没有执行完毕,使用break总是可以立即退出循环。比如:

var values = [1, 2, 3, 4, 5, 6, 7],
    i, len;
    
for (i=0, len=values.length; i < len; i++) {
    if (i == 2) {
        break; // 迭代不会继续
    }
    process(values[i])
}

第二种更改循环过程的放过是使用continue。continue语句可以立即退出(本次循环),而进入下一次循环迭代。来看下面这个例子。

var values = [1, 2, 3, 4, 5, 6, 7],
    i, len;
    
for (i=0, len=values.length; i < len; i++) {
    if (i == 2) {
        continue; // 跳过本次迭代
    }
    process(values[i])
}

Crockford的编程规范不允许使用continue。他主张代码中与其使用continue不如使用条件语句。比如,上一个例子可以修改成这样。

var values = [1, 2, 3, 4, 5, 6, 7],
    i, len;
    
for (i=0, len=values.length; i < len; i++) {
    if (i != 2) {
        process(values[i])
    }
}

 

for-in循环

for-in循环是用来遍历对象属性的。不用定义任何控制条件,循环将会有条不紊地遍历每个对象属性,并返回属性名而不是值。比如:

var prop;

for (prop in object) {
    console.log("Property name is" + prop);
    console.log("Property value is" + object[prop]);
}

for-in循环有一个问题,就是它不仅遍历对象的实例属性(instance property),同样还遍历从原型继承来的属性。当遍历自定义对象的属性时,往往会因为意外的结果而终止。出于这个原因,最好使用hasOwnProperty()方法来为for-in循环过滤出实例属性。来看下面这个例子。

var prop
for (prop in object) {
    if(Object.hasOwnPropertty(prop)) {
        console.log("Property name is" + prop);
        console.log("Property value is" + object[prop]);
    }
}

Crockford的编程规范要求所有的for-in循环都必须使用hasOwnProperty()。默认情况下,对于循环体中没有使用hasownProperty()的for-in循环。JSLint和JSHint都会给出警告。推荐总是在for-in循环中使用hasOwnProperty(),除非你想查找原型链,这时就应当补充注释,比如:

var prop
for (prop in object) {
    console.log("Property name is" + prop);
    console.log("Property value is" + object[prop]);
}

关于for-in循环,还有一点需要注意,即for-in循环是用来遍历对象的。一个常见的错误用法是使用for-in循环来遍历数组成员。

 

变量、函数和运算符

变量声明

所有的var语句都提前到包含这段逻辑的函数的顶部执行。比如:

function doSomething() {
    var result = 10 + value;
    var value = 10;
    return result;
}

 

function deSomethingWithItems(items) {
    
    for (var i=0, len=items.length; i< len; i++ ) {
        doSomething(items[i])
    }
}

在ECMAScript5之前,JavaScript中并没有块级变量声明(block-level variable declaration),因此这段代码实际上等价于下面这段代码。

function doSomethingWithItems(items) {
    
    var i, len;
    
    for (i=0, len=items.length; i < lem; i++) {
        doSomething(items[i])
    }
}

变量声明提前意味着:在函数内部任意地方定义变量和在函数顶部定义变量是完全一样的。

 

Dojo编程风格指南规定,只有当变量之间有关联性时,才允许使用单var语句。

建议将所有的var语句合并为一个语句,每个变量的初始化独占一行。赋值运算符应当对齐。对于那些没有初始值的变量来说,它们应当出现在var语句的尾部。比如:

function doSomethingWothItems(items) {
    var value   = 10,
        result  = value + 10,
        i,
        len;
        for (i=0, len=items.length; i < lem; i++) {
            doSomething(items[i])
        }
}

为了保持成本最低,推荐合并var语句,这可以让你的代码更短、下载更快

 

函数声明

和变量声明一样,函数声明也会被JavaScript引擎提前。因此,在代码中函数的调用可以出现在函数声明之前。

// 不好的写法
doSomething();

function doSomething() {
    alert("Hello world!");
}

这段代码是可以正常运行的,因为JavaScript引擎将这段代码解析为:

function doSomething() {
    alert("Hello world!");
}

doSomething();

由于JavaScript的这种行为,我们推荐总是先声明JavaScript函数然后使用函数。

 

此外,函数声明不应当出现在语句块之内

// 不好的写法
if (condition) {
    function doSomething() {
        alert("Hi!");
    }
} else {
    function doSomething() {
        alert("Yo!");
    }
}

这种场景是ECMAScript的一个灰色地带,应当尽可能地避免。函数声明应当在条件语句地外部使用。

 

函数调用间隔

一般情况下,对于函数调用写法推荐的风格是,在函数名和左括号之间没有空格。

这样做是为了将它和块语句(block statement)区分开来,比如:

// 好的写法
doSomething(item);

// 不好的写法: 看起来像一个块语句
doSomething (item);

// 用来做对比的块语句
while (item) {
    // 代码逻辑
}

 

立即调用的函数

JavaScript中允许声明匿名函数(本身没有命名的函数),并将匿名函数赋值给变量或者属性

var doSomething = function() {
    // 函数体
}

这种匿名函数同样可以通过在最后加上一对圆括号来立即执行并返回一个值,然后将这个值赋值给变量。

// 不好的写法
var value = function () {
    
    // 函数体
    return {
        message: "Hi"
    }
}();

为了让立即执行的函数能够被一眼看出来,可以将函数用一对圆括号包裹起来。比如:

好的写法
var value = (function () {
    
    // 函数体
    
    return {
        message: "Hi"
    }
}());

这段代码在第一行就有了一个标识符(左圆括号),表明这是一个立即执行的函数/添加一对圆括号并不会改变代码的逻辑

 

严格模式

ECMAScript引入了“严格模式”(strict mode),希望通过这种方式来谨慎地解析执行JavaScript,以减少错误。通过使用如下指令脚本以严格模式执行。

"use strict"

这条编译指令(pragma)不仅用于全局,也适用于局部,比如一个函数内。但是不推荐将"use strict"用在全局作用域中。

不好的写法 - 全局的严格模式
"use strict"
function doSomething() {
    // 代码
}

// 好的写法
function doSomething() {
    "use strict"
    // 代码
}

如果希望在多个函数中应用严格模式而不必写很多行"use strict"的话,可以使用立即执行的函数

// 好的写法
(function () {
    "use strict";
    function doSomething () {
        // 代码
    }
    function doSomethingElse () {
        // 代码
    }
})

 

相等

由于JavaScript具有强制类型转换机制(type coercion),JavaScript中的判断相等操作是很微妙的。

发生强制类型转换最常见的场景就是,使用了判断相等运算符==和 !=的时候。当要比较的两个值的类型不同时,这两个运算符都会有强制类型转换(当数据类型相同时则不会有强制类型转换)。

// 比较数字5和字符串5
console.log(5 == '5'); // true

// 比较数字25和十六进制的字符串25
console.log(25 == "0x19"); // true

当发生强制类型转换时,字符串会被转换为数字,类似使用Number()转换函数。

 

如果一个布尔值和数字比较,布尔值会首先转换为数字,然后进行比较,false值变为0,true变为1.来看例子:

// 数字1和true
console.log(1 == true); // true

// 数字0和false
console.log(0 == false); // true

// 数字2和true
console.log(2 == true); // false

如果其中一个值是对象而另一个不是,则会首先调用对象的valueOf()方法,得到原始类型值再进行比较。如果没有定义valueOf(),则调用toString().

还有一种涉及到强制类型转换的场景和null和undefined有关。根据ECMAScript标准规范的描述,这两个特殊值被认为是相等。

console.log(null == undefined); // true

由于强制类型转换的缘故,我们推荐不用使用== 和 !=,而应当使用===和 !==

eval()

在JavaScript中,eval()的参数是一个字符串,eval()会将传入的字符串当作代码来执行。开发者可以通过这个函数来载入外部的JavaScript代码,或者随即生成JavaScript代码并执行它。比如:

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值