编写可维护的 Gruntfile.js

使用Grunt已经有很长一段时间了,不得不感叹其社区的壮大,各种插件层出不穷。而在这期间我也换过几种方式来组织Gruntfile.js,但都不是很理想,直到前段时间看到load-grunt-tasks这个插件以及More maintainable Gruntfiles这篇文章后,我就把项目中的Gruntfile.js都按照该文章作者所述的方式重新组织了一遍。

我就暂且把这种方式用自己的文字记录一下并分享给正在使用Grunt的同学们吧,不过本文也不算是对《More maintainable Gruntfiles》的翻译呐,毕竟我 E 文太差~

JavaScript闭包

闭包是 JavaScript 中的重要特性之一,大多数用过 JavaScript 的程序员也基本上都接触过闭包,不管是否知道或了解闭包这个概念。比如在用 jQuery 的时候:

var count = 0;
$('.btn').onclick = function(e) {
count += 1;
};

闭包,维基百科的解释是:指引用了自由变量的函数。而我个人认为前端大牛johnhax的解释更加容易理解:闭包就是内部函数能访问外部的变量。

所以,要理解闭包,只要理清楚变量作用域这个概念就差不多了。我也把对变量作用域的一些个人理解记录在了前两篇文章中,故这里就只简单说说一个函数它可以访问哪些作用域中的变量:

  1. 该函数自己内部声明的变量
  2. global 作用域中的全局变量
  3. 如果该函数是内部函数,那它还可以访问其外部函数内声明的变量

而对于第三点,就是闭包的行为了,用一个简单的例子来说明。

JavaScript变量作用域(续)

上篇,已经大致明确了以下几点:

  1. JavaScript 没有块级作用域,只有函数 (局部) 作用域和全局作用域
  2. 函数中未使用var关键字声明的变量会成为全局变量
  3. 同名时局部变量访问优先级高于全局变量
  4. JavaScript 具有变量声明提前的特性

接下来根据上篇留下的最后一段代码,继续谈谈变量作用域。

var name = 'global';

function foo() {
  var name = 'foo';
  bar();
}

function bar() {
  console.log(name);
}

foo();

这段代码最终会在控制台打印出'global',而并非'foo'。可以看出,函数运行时能访问到的作用域是它被定义时的作用域,不是被调用时的作用域。

每当谈及 JavaScript 作用域的时候,基本上都会提到“词法作用域”、“执行环境”、“活动对象”、“作用域链”这几个概念,而了解这些概念将有助于理解 JavaScript 中的闭包。我也谈谈我对此的理解,如误欢迎指正,不胜感激。

JavaScript变量作用域

所谓作用域,也就是变量和函数起作用的区域,不同的语言有着不同的实现。而在 JavaScript 中,这也是往往让人迷糊的地方,也是 JavaScript 中必须理解的特性之一。

首先来看看下面的代码:

for (var i = 0; i < 10; i++) {
var foo = 'bar';
}

function test() {
console.log(i); // 10
console.log(foo); // 'bar'
}
test();

从运行结果不难看出,变量i在循环体结束之后仍然可以访问。而诸如 Java、C# 等语言中,循环结束之后便不能再访问到循环体中的变量了。继续看下面代码:

var name = 'heroic';

function foo() {
var name = 'foo';
console.log(name); // 'foo'
}

foo();
console.log(name); // 'heroic'

结合两段代码可以知道,在 JavaScript 中是不存在块级作用域的,只存在函数作用域(也称“本地作用域”)和全局作用域。而作用域中的变量我们分别称其为:局部变量和全局变量。

前辈们(前端的长辈们,嚯嚯)常常反复在说,全局变量是魔鬼啊,魔鬼啊。。。(回声)那到底是嘛原因呢?咱接着往下说。

关于《JavaScript Web Application》中的bind方法

去年底就草草了了的翻过《JavaScript Web Application》
这本书,当时就遇到好几个地方没怎么看明白,当时也没有去深究,就只是简单的过一遍这本书。由于此前 JavaScript 方面的知识匮乏,所以把这本书就缓在阅读队列里了,最近才轮到。其间读了好几本基础和进阶的书,所以这次读这本书,很多之前不明白的地方就豁然明了了。不过好记性不如烂笔头,没准下次忘记了呢。

记得此前书中对 ES5 bind()方法的实现就是一个让我感到迷糊的地方(中文版 P16):

if (!Function.prototype.bind) {
  Function.prototype.bind = function (context) {
    var slice = [].slice
      , args = slice.call(arguments, 1)
      , self = this
      , nop = function () {}  // ① `nop`(函数)的作用?
      , bound = function () {
          // ② 为什么要做`instanceof`判断?
          return self.apply(this instanceof nop ? this : (context || {}),
                              args.concat(slice.call(arguments)));
      };

    // ③ 为什么要设置它俩的原型?
    nop.prototype = self.prototype;
    bound.prototype = new nop();

    return bound;
  };
}

bind():用来动态改变函数调用时其内部this对象的引用,使目标函数基于正确的上下文进行调用。jQuery 中实现为$.proxy()方法,ES5 则自带了。

上面代码中① ② ③都是我此前不明白的地方,不过在这次阅读的过程中,就豁然开朗了。