【译】如何使用Chrome开发者工具的移动设备仿真器

网站测试变得越来越复杂。仅仅在几个浏览器中检查功能的日子一去不复返了。你的最新杰作必须在一系列具有不同操作系统,屏幕分辨率和功能的移动、平板和桌面设备上进行严格评估。在极端情况下,它可能需要与原始开发一样长的时间。

该过程进一步被触摸屏、混合设备和高密度显示器复杂化。如果你使用鼠标和键盘在普通的PC上编码,将很难理解你的杰作如何操作。鼠标悬停等功能不一定会起作用,你的应用程序可能无法使用。但是,如何在开发过程中测试你的系统,避免在多个设备之间运行和切换的痛苦?

幸运的是,所有现代浏览器都提供移动设备仿真工具,并且Chrome中的移动设备仿真工具就是最好的工具之一。它可以在不离开舒服的PC开发环境下,帮助你识别早期的问题。

开发工具

启动Chrome,打开要测试的网页,然后打开开发者工具(菜单》更多工具》开发者工具,Mac快捷键是Cmd+Opt+I或F12,Windows和Linux快捷键是Ctrl+Shift+I)。

现在,你可以通过单击左上角的切换设备工具栏图标来启用浏览器仿真器:

启用移动设备仿真器

现在将出现设备仿真:

移动设备仿真器

当选择响应(Responsive)作为设备类型时,可以更改仿真屏幕的尺寸。

触摸启用仿真

将鼠标移到设备上可查看圆形“触摸”光标。这将对基于触摸的JavaScript事件(如touchstarttouchmovetouchend)做出反应。鼠标特定的事件和CSS效果不会做出反应。

按住Shift键,然后单击并移动鼠标以模拟双指缩放操作。

移动仿真器设备工具栏

值得花一点时间来让自己熟悉移动仿真器上方的工具栏和菜单:

移动手机仿真器设备工具栏

默认设置有:

  • 设备类型(或简称响应(Responsive)
  • 当前分辨率
  • 缩放(屏幕可以放大或缩小以更好得适合仿真器面板)
  • 竖屏/横屏切换按钮(假设选择了响应(Responsive)之外的设备)

三点菜单允许你显示或隐藏其他设置:

  • 设备边框(如果可用,手机或平板电脑的外观图形)
  • 媒体查询
  • 像素标尺
  • 设备像素比(例如2.0用于仿真的视网膜屏幕)
  • 设备类型(类别如“mobile”或“tablet”)
  • 网络限制(一种在较慢的连接上限制带宽和测试性能的方法)
  • 捕获包括设备边框(如果显示)的屏幕截图。
  • 最后一个选项允许你重置以上这些设置到默认状态。

移动手机仿真器设备工具栏选项

CSS媒体查询工具栏

工具栏下方的栏显示一系列典型的手机,平板电脑和设备尺寸。当选择响应(Responsive)作为设备的宽度设置时,可以单击此选项。

从三点菜单中选择显示媒体查询(Show media queries),以查看在CSS中设置的所有媒体查询的图形颜色编码表示。

Chrome移动设备仿真器媒体查询

  • 蓝色——以最大宽度的查询
  • 绿色——以一个范围内宽度的查询
  • 橘色——以最小宽度为目标的查询

可以单击任何颜色的条形栏以将仿真器屏幕设置为该宽度。

仿真设备选项

左侧的下拉菜单允许你选择设备。为流行的智能手机和平板电脑提供了几十个预设,包括iPhone,iPad,Kindles,Nexus平板电脑,三星Galaxy等。

并非所有设备都会同时显示在这里 – 选择设备下拉菜单中的编辑(Edit)…,或从开发者工具菜单(F1)中选择设置(Settings),然后选择设备(Devices)标签:

Chrome移动设备仿真器设备

你可以启用或禁用设备或通过以下定义输入你自己的设备:

  • 名称
  • 类型,比如“Mobile”或“Tablet”
  • 浏览器user agent字符串
  • 设备分辨率
  • 像素比(例如iPhone Retina屏幕的像素比是2,其中像素密度是所报告的视口分辨率的两倍高)。

请注意,所有浏览器都使用HTTP headers中的用户代理(UA)字符串来标识自己。这可以在客户端或服务器端上检查,并且在web开发的黑暗日子期间(各个浏览器相互之间不兼容),将用于修改或提供不同的用户体验。在极端情况下,浏览用户将被引导到不同的站点。该技术总是有缺陷的,但由于响应的网页设计技术和完全不可持续的市场上提供的设备数量已经变得很大程度上冗余。

带宽限制模拟

限制(Throttling)下拉菜单允许你模拟移动连接或不可靠的酒店和机场WiFi经常遇到的慢网络速度!你可以使用它来确保你的网站或应用程序快速加载,并在所有环境中保持响应。

网络(Network)标签和Chrome的设备工具栏(启用时)可以使用限制下拉菜单。你可以通过选择限制下拉菜单底部的编辑(Edit…)或从开发者工具菜单(F1)中选择设置(Settings)并选择限制(Throttling)选项卡来设置自己的带宽配置:

Chrome移动设备仿真器带宽

单击添加自定义配置文件(Add custom profile),然后输入:

  • 配置文件名称
  • 下载速度(千比特每秒)
  • 上传速度(千比特每秒)
  • 以毫秒为单位的延迟(在进行网络请求时的典型延迟)

仿真移动传感器

智能手机和平板电脑通常具有传感器,例如在台式设备中不存在的GPS,陀螺仪和加速度计。这些可以在Chrome中通过选择更多工具(More tools),然后从开发工具主三点菜单中选择传感器(Sensors)

Chrome移动设备仿真器带宽

将出现一个新面板,你可以定义:

  • 当前的纬度和经度,或从下拉列表中选择一个主要城市。你也可以选择禁用位置,用来模拟当设备无法获取GPS信号时应用程序的反应。
  • 方向。有几个可用的预设方向,或者可以通过单击和拖动来移动设备图像来改变方向。

远程真实设备调试

最后,Chrome允许你通过USB连接真实的Android设备进行远程设备调试。从开发者工具主三点菜单中选择更多工具(More tools),然后选择远程设备(Remote devices)。确保勾选Discover USB debices,然后连接手机或平板电脑,并按照说明进行操作。

Chrome允许你设置端口转发,以便导航到设备上本地服务器上的网址。 Chrome的预览面板显示设备屏幕的同步视图,你可以使用设备或Chrome本身进行交互。

全方位的开发者工具包括应用程序(Application)选项卡,可用于在离线模式下测试Progressive Web Apps。请注意,不同于需要HTTPS的真实应用程序,Chrome允许PWA通过HTTP连接从本地主机运行。

太棒了!我现在不需要任何设备!

Chrome的移动浏览器模拟器非常实用和强大,但它不能替代在真实设备上与你的网站或应用互动,以评估完整的用户体验。

你也应该知道,没有设备模拟器是完美的。例如,Chrome在iPhone或iPad上显示网页的表示,但不会尝试模拟Safari的标准支持或怪异模式(quirks)。

尽管如此,对于快速和讨厌的移动测试,Chrome的设备模拟器是优秀的。它比在真正的智能手机和平板电脑设备之间切换容易得多,你将拥有所有的开发工具。这将节省工作量。

[译]ECMAScript 6中的参数使用

ECMAScript 6(又名ECMAScript2015)是ECMAScript标准的最新版本,在JavaScript中明显提升了参数的使用。现在我们可以使用rest参数、默认值和解构等其它特性。

在本教程中,我们将探讨实参和形参的详情,看看ECMAScript 6对其升级了哪些特性。

Arguments与Parameters

Arguments和parameters通常是相互使用。然而在本教程中我们将做一个区分。通常,parameters(形参)是在函数声明时指定的,而arguments(实参)是调用函数时传入的。思考下面这个函数:

function foo(param1, param2){
    // do something
}
foo(10, 20);

在这个函数中,param1param2是函数的形参,传入函数的值(1020)是实参。

扩展运算符(…)

在ECMAScript 5中,apply()方法传递一个数组作为函数的参数是方便的。例如,常用Math.max()方法去查找一个数组中的最大值,思考这段代码:

var myArray = [5, 10, 50];
Math.max(myArray); // Error: NaN
Math.max.apply(Math, myArray); //50

Math.max()方法不支持数组,它仅仅接受数字。当一个数组传入到Math.max()函数中时,它抛出一个错误。但是当使用apply()方法后,数组作为单独的数字传入,所以Math.max()方法可以处理它。

幸运的是,随着在ECMAScript 6扩展操作符的引入,我们不再需要使用apply()方法。使用扩展操作符,我们可以轻易地将一个表达式扩展为多个参数:

var myArray = [5, 10, 50];
Math.max(...myArray);  // 50

在这里,扩展运算符将myArray展开为函数的一个个单独的参数。在ECMAScript 5中使用apply()模拟扩展运算符是可行的,但是语法是混乱的,并且缺乏扩展运算符的灵活性。扩展运算符不但使用简单而且还具有特性。例如,在函数调用时,它可以多次使用并且可以和其它参数混合使用:

function myFunction() {
    for(var i in arguments){
        console.log(arguments[i]);
    }
}
var params = [10, 15];
myFunction(5, ...params, 20, ...[25]); // 5 10 15 20 25

扩展操作符的另一个优点是,它可以很容易地与构造函数一起使用:

new Date(...[2016, 5, 6]); // Mon Jun 06 2016 00:00:00 GMT-0700 (Pacific Daylight Time)

当然,我们可以在ECMAScript 5中重写上面的代码,但我们需要使用复杂的模式来避免类型错误:

new Date.apply(null, [2016, 4, 24]);    // TypeError: Date.apply is not a constructor
new (Function.prototype.bind.apply(Date, [null].concat([2016, 5, 6])));   // Mon Jun 06 2016 00:00:00 GMT-0700 (Pacific Daylight Time)

浏览器对扩展操作符的支持

桌面浏览器

CHROME FIREFOX INTERNET EXPLORER MICROSOFT EDGE OPERA SAFARI
46 27 Supported 7.1

移动浏览器

CHROME FOR ANDROID FIREFOX MOBILE IE MOBILE OPERA MOBILE SAFARI MOBILE
46 27

浏览器支持详情点我查看

Rest参数

rest参数的语法与扩展运算符一样,但是它不是展开一个数组到参数中,相反它是将参数收拢为一个数组:

function myFunction(...options){
    return options;
}
myFunction('a', 'b', 'c'); //["a", "b", "c"]

如果没有实参,rest参数将被设置为一个空数组:

function myFunction(...options){
    return options
}
myFunction(); // []

当创建一个参数可变的函数(即接受的参数数量是有变化的函数)时,rest参数是非常有用的。得益于数组的好处,reset参数可以容易地替换掉arguments对象(我们将在本教程后面解释)。思考这个使用ECMAScript 5写的函数:

function checkSubstring(string) {
    for(var i = 1; i < arguments.length; i++) {
        if(string.indexOf(arguments[i]) === -1) {
            return false;
        }
    }
    return true;
}
checkSubstring('this is a string', 'is', 'this'); // true

这个函数检查一个字符串是否包含一些子字符串。这个函数的第一个问题是,我们必须得看函数的内部才知道它需要多个参数。第二个问题是循环迭代必须从索引1而不是0开始,因为arguments[0]指向第一个参数。如果我们以后决定在string之前或者之后添加另一个参数,我们可能会忘记更新for循环的逻辑处理。使用rest参数,我们就很容易地避免这些问题:

function checkSubstrings(string, ...keys) {
  for (var key of keys) {
    if (string.indexOf(key) === -1) {
      return false;
    }
  }
  return true;
}
checkSubstrings('this is a string', 'is', 'this');   // true

此函数的输出和前一个函数相同。在这里,string参数是传入的第一个参数,其它的参数放进一个数组中,并赋值给了变量keys

使用rest参数代替arguments对象,提高了代码的可读性,避免了JavaScript中的优化问题。然而,rest参数也不是没有它的局限性。例如,它必须是最后一个参数,否则,将出现语法错误。

function logArguments(a, ...params, b) {
        console.log(a, params, b);
}
logArguments(5, 10, 15);    // SyntaxError: parameter after rest parameter

另一个限制是,在函数声明中只允许存在一个rest参数:

function logArguments(...param1, ...param2) {
}
logArguments(5, 10, 15);    // SyntaxError: parameter after rest parameter

浏览器对rest参数的支持

桌面浏览器

CHROME FIREFOX INTERNET EXPLORER MICROSOFT EDGE OPERA SAFARI
47 15 Supported 34

移动浏览器

CHROME FOR ANDROID FIREFOX MOBILE IE MOBILE OPERA MOBILE SAFARI MOBILE
47 15

浏览器支持详情点我查看

默认参数

ECMAScript 5中的默认参数

在ECMAScript 5中JavaScript不支持默认参数,但有一个简单的解决方法。在函数内使用逻辑or操作符(||),我们很容易在ECMAScript 5中模拟默认参数。思考这个函数:

function foo(param1, param2) {
   param1 = param1 || 10;
   param2 = param2 || 10;
   console.log(param1, param2);
}
foo(5, 5);  // 5 5
foo(5);    // 5 10
foo();    // 10 10

这个函数预计需要有两个参数,但是当它被不带参数调用时,它将使用默认值。在函数内部,缺少的参数会自动设置为undefined。因此,我们可以检测到这些参数,并为它们声明为默认值。检测缺失的参数和设置默认值,我们使用逻辑or操作符(||)。这个操作符检查表达式的第一个值:如果它是true,操作符返回它。如果它是假,操作符返回第二个值。

我们经常在函数中使用这种方式来设置参数的默认值,但是它是有缺陷的。向函数传入0null也会触发默认值,因为这些参数的值会被转换为false。因此,如果我们真的需要给函数传入0null,我们需要另一种方法来检查参数是否缺少:

function foo(param1, param2) {
  if(param1 === undefined){
    param1 = 10;
  }
  if(param2 === undefined){
    param2 = 10;
  }
  console.log(param1, param2);
}
foo(0, null);    // 0, null
foo();    // 10, 10

在这个函数中,对传入的参数进行类型检查,以确保在赋默认值之前,它们是undefined。这种方式需要多写一点的代码,但它是一个更安全的选择,并且允许我们向函数传入参数0null

ECMAScript 6中的默认参数

使用ECMAScript 6,我们不再需要检查undefined值来模拟默认参数了,现在我们可以将默认值放在函数声明中:

function foo(a = 10, b = 10) {
  console.log(a, b);
}
foo(5);    // 5 10
foo(0, null);    // 0 null

正如你所看到的,调用函数时,省略参数会触发默认值,但是传入0null并不会触发默认值。
我们甚至可以使用函数返回值作为参数的默认值:

function getParam() {
    alert("getParam was called");
    return 3;
}
function multiply(param1, param2 = getParam()) {
    return param1 * param2;
}
multiply(2, 5);     // 10
multiply(2);     // 6 (also displays an alert dialog)

注意,函数getParam仅仅在第二个参数缺省时才会被调用。因此当我们传入两个参数调用函数multiply()时,alert并不会显示。

默认参数另一个有趣的特性是,在函数声明中我们可以引用其他参数和变量:

function myFunction(a=10, b=a) {
     console.log('a = ' + a + '; b = '  + b);
}
myFunction();     // a=10; b=10
myFunction(22);    // a=22; b=22
myFunction(2, 4);    // a=2; b=4

你甚至可以在函数声明中执行操作:

function myFunction(a, b = ++a, c = a*b) {
     console.log(c);
}
myFunction(5);    // 36

注意,与其它语言不同,JavaScript是在调用函数时计算默认值。

function add(value, array = []) {
  array.push(value);
  return array;
}
add(5);    // [5]
add(6);    // [6], not [5, 6]

浏览器对默认参数的支持

桌面浏览器

特性 CHROME FIREFOX INTERNET EXPLORER MICROSOFT EDGE OPERA SAFARI
基本支持 49 15 14
在默认参数后,无默认值的参数 49 26 14

移动浏览器

特性 CHROME FOR ANDROID FIREFOX MOBILE IE MOBILE OPERA MOBILE SAFARI MOBILE
基本支持 49 15
在默认参数后,无默认值的参数 46 26

浏览器支持详情点我查看

解构

解构是ECMAScript 6中的新特性,解构能够使我们从数组和对象中提取值并且把这些值赋到一些变量上,这些变量的组成语法类似于数组和对象。这种语法是清晰明确和易于理解,并且当传递参数给函数时特别有用。

在ECMAScript 5中,配置对象经常被用来处理大量的可选参数,特别是当属性的顺序并不重要时。思考这个函数:

function initiateTransfer(options) {
    var  protocol = options.protocol,
        port = options.port,
        delay = options.delay,
        retries = options.retries,
        timeout = options.timeout,
        log = options.log;
    // code to initiate transfer
}
options = {
  protocol: 'http',
  port: 800,
  delay: 150,
  retries: 10,
  timeout: 500,
  log: true
};
initiateTransfer(options);

这种模式JavaScript的开发者经常使用,并且效果非常好,但是我们必须得看函数的内部才知道它需要哪些参数。使用解构参数,在函数声明时,我们可以清晰地表明需要哪些参数:

function initiateTransfer({protocol, port, delay, retries, timeout, log}) {
     // code to initiate transfer
};
var options = {
  protocol: 'http',
  port: 800,
  delay: 150,
  retries: 10,
  timeout: 500,
  log: true
}
initiateTransfer(options);

在这个函数中,我们使用了对象解构模式替代了配置对象。这使得我们的函数不仅更简洁,而且易读性更强。

我们可以结合使用解构参数和常规参数:

function initiateTransfer(param1, {protocol, port, delay, retries, timeout, log}) {
     // code to initiate transfer
}
initiateTransfer('some value', options);

注意,在参数调用时省略参数将会抛出类型错误:

function initiateTransfer({protocol, port, delay, retries, timeout, log}) {
     // code to initiate transfer
}
initiateTransfer();  // TypeError: Cannot match against 'undefined' or 'null'

当我们需要参数需要时,这是预期的行为,但如果我们希望他们是可选的呢?为了防止缺省参数时出现这个错误,我们需要给解构参数赋一个默认值:

function initiateTransfer({protocol, port, delay, retries, timeout, log} = {}) {
     // code to initiate transfer
}
initiateTransfer();    // no error

在这个函数中,一个空对象作为默认值赋值给了解构参数。现在,如果这个函数不带任何参数被调用,并不会报错。

我们也可以给每个解构参数赋默认值:

function initiateTransfer({
    protocol = 'http',
    port = 800,
    delay = 150,
    retries = 10,
    timeout = 500,
    log = true
}) {
     // code to initiate transfer
}

在这个示例中,每一个属性都有一个默认值参数,省去了需要我们手动检查未定义的参数和在函数内部赋默认值。

浏览器对默认解构的支持

桌面浏览器

CHROME FIREFOX INTERNET EXPLORER MICROSOFT EDGE OPERA SAFARI
基本支持 49 2.0 14 7.1
解构参数带有默认值 49 26 14

移动浏览器

特性 CHROME FOR ANDROID FIREFOX MOBILE IE MOBILE OPERA MOBILE SAFARI MOBILE
基本支持 49 1 8
解构参数带有默认值 46 47

浏览器支持详情点我查看

参数传递

有两种方法可以将参数传递给函数:通过引用或者值。修改引用参数具有全局的体现,但是修改值参数仅仅在函数内部有所体现。

在某些编程语言中,比如Visual Basic和Powershell,我们有指定通过引用还是值来传递参数的选项,但JavaScript并没有。

值参数

从技术上讲,JavaScript只能通过值传递。当我们传递一个值参数给函数时,该值会在函数的作用域内创建一个副本。因此,对值的任何更改只会在函数内部有所体现。思考这个示例:

var a = 5;
function increment(a) {
    a = ++a;
    console.log(a);
}
increment(a);   // 6
console.log(a);    // 5

在这里,修改函数内部的参数对原始值没有影响。因此,在函数外部变量被记录时,打印的值仍然是5

引用参数

在JavaScript中,一切都是通过值传递的,但是当我们传递一个对象(包含数组)变量时,“值”是对象的引用,更改变量引用对象的属性会更改底层对象。

思考这个函数:

function foo(param){
    param.bar = 'new value';
}
obj = {
    bar : 'value'
}
console.log(obj.bar);   // value
foo(obj);
console.log(obj.bar);   // new value

正如你所看到的,函数内部的对象属性被修改,但修改后的值在函数外部是可见的。

当我们传递一个非基本值时,如数组或对象,在背后就会创建一个变量并指向内存中的原始对象的位置。然后这个变量传递给函数,并且修改它就会影响到原始对象。

类型检查和缺省或额外的参数

在强类型语言中,我们必须在函数声明中指定参数的类型,但是JavaScript缺乏此特性。在JavaScript中,我们传递给函数的参数类型或参数数量都无关紧要。

假设我们有一个只接受一个参数的函数。当我们调用这个函数时,我们无法限制只能传入一个参数,我们可以传递一个,两个或更多参数!我们甚至可以不传入任何参数,并且函数不会报错。

实参和形参的数量有两种不同:

  • 实参的数量少于形参缺少的参数将等于undefined
  • 实参的数量大于形参额外的参数将被忽略,但是可以通过特殊数组比如变量arguments来检索(稍后讨论)。

必要参数

如果函数调用时缺省一个参数,那么这个参数将被设置为undefined。我们可以利用这种行为,如果省略参数抛出一个错误:

function foo(mandatory, optional) {
    if (mandatory === undefined) {
        throw new Error('Missing parameter: mandatory');
    }
}

在ECMAScript 6中,我们可以更进一步,使用默认参数来设置必要参数:

function throwError() {
    throw new Error('Missing parameter');
}
function foo(param1 = throwError(), param2 = throwError()) {
    // do something
}
foo(10, 20);    // ok
foo(10);   // Error: missing parameter

参数对象

rest参数被添加到ECMAScript 4用来替换arguments对象,但是ECMAScript 4没有实现。随着ECMAScript 6的发布,JavaScript现在正式支持rest参数。它还否决了删除arguments对象的计划。

arguments对象是一个数组对象,可以在所有函数中使用。它允许参数的值通过索引而不是名称传递给函数。该对象允许我们将任何数量的参数传递给函数。思考下面的代码:

function checkParams(param1) {
    console.log(param1);    // 2
    console.log(arguments[0], arguments[1]);    // 2 3
    console.log(param1 + arguments[0]);    // 2 + 2
}
checkParams(2, 3);

这个函数预计只接受一个参数。当我们使用两个参数调用它时,第一个参数通过参数名称param1或者参数对象arguments[0]在函数中是可以访问的,但是第二个参数只能通过arguments[1]来访问。此外注意,arguments对象可以和参数名称一起使用。

arguments对象包含传递给函数每个参数的入口,并第一个入口的索引从0开始。如果我们想在上面的示例中访问更多的参数,我们会写arguments[2]arguments[3]等。

我们甚至可以完全跳过设置命名参数,仅仅使用arguments对象:

function checkParams() {
    console.log(arguments[1], arguments[0], arguments[2]);
}
checkParams(2, 4, 6);  // 4 2 6

事实上,命名参数是为了方便,而不是必要的。类似地,rest参数也可以用来体现所传递的参数:

function checkParams(...params) {
    console.log(params[1], params[0], params[2]);    // 4 2 6
    console.log(arguments[1], arguments[0], arguments[2]);    // 4 2 6
}
checkParams(2, 4, 6);

arguments对象是一个数组对象,但是它缺少数组的方法,比如slice()foreach()。为了在arguments对象上使用数组的方法,该对象首先需要转换为一个真正的数组:

function sort() {
    var a = Array.prototype.slice.call(arguments);
    return a.sort();
}
sort(40, 20, 50, 30);    // [20, 30, 40, 50]

在这个函数中,Array.prototype.slice.call()作为一个快速方法将arguments对象转换为一个数组。接下来,sort()方法排序数组中的项并返回了它。

ECMAScript 6有一个更简单的方法。Array.from()是ECMAScript 6中新增的,它可以把一个数组对象创建成一个数组:

function sort() {
    var a = Array.from(arguments);
    return a.sort();
}
sort(40, 20, 50, 30);    // [20, 30, 40, 50]

length属性

虽然参数对象不是数组,但是它拥有length属性,这可以用来检查传入函数中的参数数量:

function countArguments() {
    console.log(arguments.length);
}
countArguments();    // 0
countArguments(10, null, "string");    // 3

通过使用length属性,我们可以更好的控制传递给函数的参数数量。例如,如果函数需要两个参数工作,我们可以使用length属性来检查传入的参数的数量,如果传入的参数数量比预期要少,则抛出错误:

function foo(param1, param2) {
    if (arguments.length < 2) {
        throw new Error("This function expects at least two arguments");
    } else if (arguments.length === 2) {
        // do something
    }
}

rest参数是数组,所以他们有一个length属性。在ECMAScript中6中,前面的代码可以使用rest参数这么来改写:

function foo(...params) {
  if (params.length < 2) {
        throw new Error("This function expects at least two arguments");
    } else if (params.length === 2) {
        // do something
    }
}

callee和caller属性

callee属性是指当前正在运行的函数,caller是指调用了当前函数的执行函数。在ECMAScript 5严格模式下,这些属性已被废弃,如果尝试访问这些属性会导致类型错误。

arguments.callee属性在递归函数(递归函数是指有规律的自己调用自己的函数)中是有用的,尤其是当函数名称不可用(即匿名函数)时。因为匿名函数没有名称,只能通过arguments.callee属性调用自己。

var result = (function(n) {
  if (n <= 1) {
    return 1;
  } else {
    return n * arguments.callee(n - 1);
  }
})(4);   // 24

严格模式和非严格模式中的参数对象

在ECMAScript 5非严格模式下,arguments对象有一个不同寻常特性:它可以与相应的命名参数的值保持同步。

思考下面的代码:

function foo(param) {
   console.log(param === arguments[0]);    // true
   arguments[0] = 500;
   console.log(param === arguments[0]);    // true
   return param
}
foo(200);    // 500

在这个函数中,一个新值赋给了arguments[0]。因为arguments的值始终和相应的命名参数保持同步,改变arguments[0]的值也将改变param的值。事实上,他们像两个不同名称的相同变量。在ECMAScript 5严格模式下,这个令人困惑的arguments对象的行为已被删除:

"use strict";
function foo(param) {
   console.log(param === arguments[0]);    // true
   arguments[0] = 500;
   console.log(param === arguments[0]);    // false
   return param
}
foo(200);   // 200

此时,改变arguments[0]并不会影响param,并且输出结果和预期的一样。该函数的输出结果在ECMAScript 6中和ECMAScript 5严格模式下是相同的,但是记住,当函数声明中使用默认值时,arguments对象并不受影响:

function foo(param1, param2 = 10, param3 = 20) {
   console.log(param1 === arguments[0]);    // true
   console.log(param2 === arguments[1]);    // true
   console.log(param3 === arguments[2]);    // false
   console.log(arguments[2]);    // undefined
   console.log(param3);    // 20
}
foo('string1', 'string2');

在这个函数中,即使param3有默认值,它也和arguments[2]不相等,因为只有两个参数传递给了函数。换句话说,设置默认值并不影响arguments对象。

总结

ECMAScript 6给JavaScript带来了大大小数以百计的的改进。越来越多的开发者使用ECMAScript 6的特性,很快这些特性不可避免会被使用到。在本教程中,我们已经学会了ECMAScript 6是如何升级JavaScript中的参数应用,但我们仅只是触及了ECMAScript 6的皮毛。语言中其它新的和有趣的特性是值得一看。

链接

(rb, ml, al, il)

首页图片版权:JavaScript Planet

TypeScript已经完全支持以上特性,并且你不用担心兼容性问题。详情点击TypeScript中文网查看。

[译]TypeScript 2.0 Beta发布

今天我们非常兴奋地推出了TypeScript 2.0 beta版。如果你还不熟悉TypeScript,现在你就可以去我们的网站上学习。

获取beta版,你可以下载TypeScript 2.0 Beta for Visual Studio 2015 (此更新需要VS 2015 Update 3),或者运行

npm install -g typescript@beta

这个版本包含大量新特性,如新的方式获取.d.ts文件,但是这里的一些特性仅仅是对其了解。

非空类型

nullundefined是JavaScript bugs中最常见的两个来源。在TypeScript 2.0 之前,nullundefined存在每一类型域中。这意味着如果你有一个带有string类型参数的函数,你不能肯定是单一类型的,实际上有可能是string类型,也有可能是null类型。

在TypeScript 2.0,新的--strictNullChecks编译参数改变这一不确定性。string类型就是string类型,number类型就是number类型。

let foo: string = null; // 错误!

如果你为空呢?我们已经带来两个新的类型:nullundefined。正如你所料,null仅仅是null,undefined仅仅是undefined。对它们自己而言作用不大,但是你可以在联合类型中使用它们去定义是否可以包含null/undefined

let foo: string | null = null; // 正确!

由于作为开发者的你可能经常比编辑器的类型系统懂得更多,因此我们还推出了一个后缀操作符!,这个操作符可以从任何一个表达式从排除nullundefined

declare let strs: string[] | undefined;

// 错误! 'strs' 也许是undefined.
let upperCased = strs.map(s => s.toUpperCase());

// 'strs!' 意味着我们确定它不是'undefined', 因此我们可以调用'map'.
let lowerCased = strs!.map(s => s.toLowerCase());

基于控制流的类型分析

TypeScript对处理可空类型的支持成为可能,要归功于在整个程序中跟踪类型的变化。在2.0中,我们已经开始使用控制流分析,以便更好地了解在指定的位置是什么类型。例如,考虑下面这个函数。

/**
 * @param recipients An array of recipients, or a comma-separated list of recipients.
 * @param body Primary content of the message.
 */
function sendMessage(recipients: string | string[], body: string) {
 if (typeof recipients === "string") {
 recipients = recipients.split(",");
 }

// TypeScript 知道这里的'recipients'是'string[]'类型.
 recipients = recipients.filter(isValidAddress);
 for (let r of recipients) {
 // ...
 }
}

注意,在if块内赋值后,TypeScript知道它已经被处理成为了一个string类型的数组。这类事情可以在早期发现问题并节省你在调试上花费的时间。

let bestItem: Item;
for (let item of items) {
 if (item.id === 42) bestItem = item;
}

// 错误! 如果'items'是空的,'bestItem' 可能还没有被初始化。
let itemName = bestItem.name;

我们非常感谢Ivo Gabe de Wolff,他参与这一特性的实施工作,这也开始了他的论文项目并成长为TypeScript本身的一部分。

简易模块声明

有时候你仅仅想要告诉TypeScript有一个模块存在,并且你可能不关心它具体的内容是什么样子的。以前你要像下面这样写:

declare module "foo" {
 var x: any;
 export = x;
}

但是这样太麻烦的,因此我们使声明模块变得更加简易并且摆脱了样板。在TypeScript 2.0 中你可以这么写

declare module "foo";
declare module "bar";

当你准备好最终要写一个包含具体内容的模块时,你可以返回这些声明并定义你需要的结构。

如果你依靠一个有很多模块的软件包呢?为每一个模块书写一遍可能是痛苦的,但是TypeScript 2.0 通过使用通配符*使之变得容易。

declare module "foo/*";

现在您可以导入任何以foo/开头的路径,TypeScript会认为它存在。
你可以利用这一点,如果你的模块加载器也知道如何导入基于一个特定的模式。例如:

declare module "*!text" {
 const content: string;
 export = content;
}

现在每当你导入一个结尾带有!text的路径时,TypeScript就会知道导入的是string类型。

import text = require("./hello.txt!text");
text.toLowerCase();

关于模块这一部分,博客描述的太简单了,很多人可能不能明白,建议看看GitHub上的这部分内容

下一步

你可能想知道的一个特性是在ES3和ES5中支持async/await,最初,这是定于2.0版本。然而,合理的实现async/await,我们需要重写TypeScript的emitter,这会引起一系列的变化。这样做的同时还要保持TypeScript快速更新,就需要大量的工作和对细节的关注。虽然我们对于今天的实现充满了信心,但是信心不是彻底的测试,并且需要更多的时间用于async/await的稳定。你可以在TypeScript 2.1 中期待它的出现,如果你想要跟踪进度,在GitHub上的Pull request当前是公开的

TypeScript 2.0 还充满了许多有用的新功能,随着时间的推移我们将推出更多细节。如果你想听到更多的新特性,你可以看一下我们的wiki。在未来的几周内,一个更加稳定的候选版本将发布,随后最终的正式版将会发布。

我们很乐意听听你的任何反馈,无论是在下面的评论或GitHub上。黑客快乐!