[译]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中文网查看。

[译]jQuery3新特性

jQuery称霸web江湖已经有十年了,它屹立不倒是有原因的。jQuery提供了用户容易操作DOM的接口,执行Ajax请求,创建动画,以及更多。另外,与DOM API不同,jQuery实现了复合模式。因此,你可以使用在一个无论包含多少(零个、一个或者更多)元素的jQuery集合上调用jQuery方法。

在未来几周内,jQuery随着第3个版本的发布将要达到一个重要的里程碑。jQuery3修复了很多bug,增加了一些新的功能,弃用并删除了一些功能,并更改了一些功能的行为。在这篇文章中,我将要突出介绍jQuery3最重要的变化。

新特性

以下各节中我将讨论在jQuery3中加入的重要特性。

for...of循环

jQuery3可能将使用for…of循环来遍历jQuery集合中的DOM元素。这种新的迭代器是ECMAScript2015(又名ECMAScript 6)规范的一部分。这个新迭代器可以使你遍历迭代对象(包括Array,Map,Set,等等)。

当使用这个新的迭代器时,你在循环中接受到的值不是jQuery对象而是一个DOM元素。这个迭代器将会略微的提高你操作jQuery集合的方式。

为了理解这个迭代器是怎样工作的,假设你想为页面上每一个input元素分配一个ID。jQuery3之前你会这么写:

var $inputs = $('input');

for(var i = 0; i < $inputs.length; i++ ){
 $inputs[i].id = 'input-' + i; 
}

在jQuery3中你可以这么写:

var $inputs = $('input');
var i = 0;

for(input of $inputs){
 input.id = 'input-' + i++; 
}

$.get()$.post()增加了新的方法签名

为了$.get()$.post()的实用功能和$.ajax()一样,jQuery3为其增加了新的方法签名。添加的新的方法签名是:

$.get([settings])

$.post([settings])

settings是一个具有很多属性的对象。你可以提供给$.ajax()使用,它们是相同的对象。想要了解更多关于$.ajax()的,请参考$.ajax()网页中的说明。

唯一的不同是,当settings对象传递给$.get()$.post()而不是$.ajax()时,method属性总是被忽略的。其原因是$.get()$.post()有一个预设的HTTP方法来执行Ajax请求($.get()是GET,$.post()是POST)。基本上,你不能通过$.get()发送一个POST请求的。

思考下面的代码

$.get({
 url: 'https://www.audero.it',
 method: 'POST' // 这个属性会被忽略
})

尽管设置了method属性,这段代码是不会发送一个POST请求,而是一个GET请求。

动画使用requestAnimationFrame()

所有的现在浏览器,包括IE10及以上,都支持requestAnimationFrame。jQuery3将会在内部使用这个API执行动画,使动画更加流畅并且减少对CPU资源的消耗。

unwrap()

jQuery3给unwrap()添加了一个可选参数,这个新的方法签名是:

unwrap([selector])

感谢这个变化,您将能够通过选择器表达式的字符串在已经选择的父元素中去匹配。如果存在匹配项,则匹配的子元素的父元素被移除;否则不执行操作。

改变的特性

jQuery3也修改了一些特性的行为。

:visible:hidden

新版本的jQuery修改了:visible:hidden过滤器的释义。如果元素有任何的布局容器,即使宽度和(或)高度都为0,也将会视为:visible。举例说明,br元素和没有内容的行级元素(内联元素)现在都会被:visible过滤器选中。

因此,如果在一个页面中有以下标记语言:

<div></div>
<br/>

然后你运行以下语句:

console.log($('body :visible').length);

在jQuery1.x和jQuery2.x中你得到的结果为0,但是在jQuery3中你得到的结果是2。

data()

另一个重要的变化是有关data()方法的行为。他已经变得和Dataset API规范一样了。jQuery 3将所有属性的键都转换成驼峰式命名方式。要想理解这个变化,请思考下面的元素:

<div id="container"></div>

如果你使用的jQuery3之前的版本你会这么写:

var $elem = $('#container');
$elem.data({
 'my-property':'hello'
});
console.log($elem.data());

在控制台你将会得到如下的结果:

{my-property:"hello"}

而使用jQuery3你将得到如下的结果:

{myPropery:"hello"}

请注意,在jQuery3中属性名已经变成了驼峰式没有横杠(-),而在以前的版本中,属性名会保持全小写和保留横杠(-)。

Deferred对象

jQuery3改变了Deferred对象的行为,Deferred对象是Promise对象的前身,这次改变提高了Deferred对象对Promise/A+提案的兼容性。Deferred这个对象及其它的历史都是很有趣的。想要了解更多,你可以去阅读官方文档或者阅读我写的书jQuery实战(第三版),这本书也包含了jQuery3。

在jQuery1.x和2.x中,传递给Deferred的回调函数内出现未捕获的异常会阻断程序的执行。不像原生Promise对象那样会抛出异常冒泡至window.onerror(通常冒泡到这里)。如果你没有定义一个函数处理错误事件(通常我们是会处理的),那么异常信息就会显示并且程序会终止执行。

jQuery3遵循原生Promise对象的模式。因此,抛出的异常被当作失败,接着失败回调函数执行。一旦失败回调函数执行完成,进程就会继续,下面的的成功回调函数将被执行。

为了帮助你理解两者不同,让我们看一个小例子。思考下面的代码:

var deferred = $.Deferred();

deferred
 .then(function(){
 throw new Error('An error'); 
 })
 .then(
 function(){
 console.log('Success 1'); 
 },
 function(){
 console.log('Failure 1');
 }
 )
 .then(
 function(){
 console.log('Success 2');
 },
 function(){
 console.log('Failure 2');
 }
 );

deferred.resolve();

在jQuery1和jQuery2中,只有第一个函数(抛出错误的函数)被执行。另外,因为我没有为window.onerror定义任何处理函数,所以控制台将会输出“Uncaught Error: An error”并且程序执行将会终止。

在jQuery3中,行为是完全不同的。你将在控制台上看到“Failure 1”和“Success 2”两条信息。异常被第一个失败回调函数处理,并且一旦被处理,随后的成功回调函数也会被执行。

SVG文档

没有任何版本的jQuery,包括jQuery3,官方正式支持SVG文档。然而,事实是很多方法是可以工作的,还有另外一些,比如类名操作在以前是不可正常使用的,但是它们在jQuery3中更新后也可以正常使用了。因此,在即将到来的jQuery版本中,你可以放心地在SVG文档上使用addClass()hasClass()方法。

已废弃或者已移除的方法和属性

除目前所描述的改进外,jQuery还移除和废弃了一些特性。

废弃bind()unbind()delegate()undelegate()

很久以前(jQuery1.7版本时)jQuery就引入了on()方法,它提供了统一的接口替换bind()delegate()live()方法。与此同时jQuery也提供了off()方法,同样提供了统一的接口替换unbind()undelegated()die()方法。从on()off()方法引入时就不推荐使用bind()delegate()unbind()undelegate()方法,但它们还是一直存在着。

jQuery3废弃了这些方法并且打算在未来的版本(可能是jQuery4)会移除它们。在你所有的项目中坚持使用on()off()方法,这样你就不必担心未来的版本更新。

移除load()unload()error()方法

jQuery3抛弃了已经废弃的load()unload()error()方法。这些方法很久以前(在jQuery1.8时)就被废弃了,但是他们仍旧留在jQuery中。如果你使用的插件依赖其中的一个或多个方法,升级到jQuery3后你的代码将会崩溃。因此,在升级时注意。

移除contextsupportselector属性

jQuery3抛弃了已经废弃的contextsupportselector属性。正如我在上一节讲到的,如果你仍旧在项目中使用这些属性或者使用的插件依赖这些属性,升级到jQuery3后你的代码将会崩溃。

Bug修复

jQuery3修复了在以前版本中存在的重大bug。在下面章节中,我将介绍两点,这两点在你的工作中会有很大的不同。

width()height()的值不再四舍五入

jQuery3修复了width()height()及所有其它相关方法的bug。这些方法将不再将结果像素值四舍五入到整数值,因为四舍五入后在某些情况下很难对元素进行定位。

为了理解这个问题,假设你有一个宽度为100px的容器元素,它里面包含三个宽度为三分之一(33.333333%)的元素:

<div class="container">
 <div>My name</div>
 <div>is</div>
 <div>Aurelio De Rosa</div>
</div

在jQuery3之前,如果你尝试用以下代码来获取子元素的宽度……

$('.container div').width();

……你得到的结果是33。原因是jQuery将33.33333四舍五入了。在jQuery3中,这个bug被修复了,因此你得到结果是更加精确的(即浮点数的结果)。

wrapAll()

新版本jQuery修复了一个当传递函数给wrapAll()时发生的bug。在jQuery之前,当传递一个函数给wrapAll()时,jQuery集合中每一个元素都被包裹。换句话说,这种行为和传递一个函数给wrap()是一样的。

除了修复这个问题,还有一个变化:由于这个函数在jQuery3中只会被调用一次,因此jQuery集合每个元素的索引不能传入这个方法中。最终这个方法的上下文(this)将指向jQuery集合中的第一个元素。

下载jQuery3 beta1

如果你对这篇文章感兴趣,你可能想要尝试jQuery3第一个beta版。您可以通过访问下面两个url来获得。

它在npm上也是可用的,你可以通过运行下面的命令来下载:

npm install jquery@3.0.0-beta1

总结

很多人认为jQuery将会死掉,认为在现代网页开发中已经没有一席之地。然而其仍在持续发展,jQuery使用统计(在排名前100万的网站中使用率为78.5%)反驳了这些说法。

在这篇文章中,我已经带着你介绍了jQuery3将会带来的重要特性变化。或许你可能已经注意到了,这个版本是不太会对你现有的项目造成崩溃,因为它没有引入一些重大的变化。尽管如此,在升级期间还是有一些事项需要注意得,如Deferred对象的改进。在更新第三方插件时也要注意,审查项目将帮助你发现异常行为或者崩溃的功能。

  • 本文章翻译自What’s New in jQuery 3
  • 本人英文水平有限,翻译不正确不通顺的地方,敬请指出。