1

ES6 functions改动

    ES6函数的改变不算太大,都是一些其他语言早就有的功能,而Javascript一直比较欠缺的,比如函数参数默认值,任意参数的表示法,最大的变化应该是支持箭头函数(其他语言称之为LAMBDA表达式),一种对匿名函数的一种简写方式,以下来探讨一下函数在ES6中的一些改变:

1. 默认参数(default parameters)
2. 任意参数(rest parameters ...)
3. spread 操作符(...)
4. new.target 元属性
5. 箭头函数( => )
6. 其他一些改动(miscellaneous)

1.默认参数

    ES6之前一直是通过其他方法来模拟默认参数的,例如逻辑或||符号,ES6版本真正意义上支持这种便利的写法。


// ES5模拟默认参数
function person(name, age) {
    name = name || "James";
    age = age || "18";
    console.log(name + " " + age);
}
// 一般情况下这种写法是没问题的,当逻辑或前面的值为falsy值,整个表达式返回后面的值
// 例如:
  person("Louis"); // ok
person(); // ok
person(undefined, 20); // ok
person("baby", 0); // "baby 18" error, 0为falsy值

上面可以看出这种写法是有一定问题的,各种JS库给出了另一种写法


function person(name, age) {
    if (typeof name === "undefined") {
        name = name || "James"; 
    }
    if (typeof age === "undefined") {
        age = age || "18";
    }
    console.log(name + " " + age);
}
person(undefined, 0); // ok "James 0"

ES6写法


function person(name = "James", age = 18) {
    console.log(name + " " + age);
}

// 1各种写法 默认参数出现在中间
function getRequest(url, timeout = 2000, callback) {
    // do something
}
gerRequest("/foo", undefined, function() {

});

// 2默认参数表达式
let value = 5;
function getValue() {
    return value++;
}
function add(first, second = getValue()) {
    return first + second;
}
add(3); // 8
add(3); // 9
add(1, 1); // 2

// 3后面参数引用前面参数
function add(first, second = first) {
    return first + second;
}
add(2); // 4
add(3, 4); // 7

默认参数TDZ(暂时死区)情况:


// 上面的第三种写法,若写成第一个参数引用第二个参数
function add(first = second, second) {
    return first + second;
}
add(1, 1); // ok 2
add(undefined, 4); // THROW AN ERROR 第二个参数未声明就引用就会抛出错误
// 就相当于
let first = second; // error
let second = 4;

2.任意参数

    ES6任意参数用 ... 表示,任意参数和arguments之间的差别
ES5使用arguments参数来实现对象属性拷贝:


function pick(object) {
    var result = Object.create(null); // 创建一个对象
    // 从第二个参数开始
    for (var i = 1, len = arguments.length; i < len; i++) {
        result[arguments[i]] = object[arguments[i]];
    }
    return result;
}
// arguments将object也计入,所以除开第一个参数要减1
var book = {
    title: "understanding ES6",
    author: "Nicholas C.Zakes",
    year: 2016
};
var o = pick(book, "author", "year");
o.author; // "Nicholas C.Zakes"
o.year; // 2016

上面的pick函数看上去不够直观,因为除第一个参数外不知道要添加几个参数,使用新语法


function pick(object, ...keys) {
    var result = Object.create(null); // 创建一个对象
    for (var i = 0, len = keys.length; i < len; i++) {
        result[keys[i]] = object[keys[i]];
    }
    return result;
}
// keys将object不计入在其内
var book = {
    title: "understanding ES6",
    author: "Nicholas C.Zakes",
    year: 2016
};
var o = pick(book, "author", "year");
o.author; // "Nicholas C.Zakes"
o.year; // 2016

使用rest parameters注意事项

1.要将任意参数放到函数的最后,不能放在中间位置

2.不能用于对象字面量setter中


function pick(object, ...keys, last) { //... }
// 语法错误

let object = {
    set name(...value) {
        // do something
    }
};
// 语法错误

3.spread操作符

spread操作符和rest parameters一样,都使用 ... 表示,spread操作符允许我们将数组中的参数一个一个传入函数中
例如:


// Math.max()函数, 一般可以加入任意个参数
Math.max(12, 13, 14, 15); // 15

// 以数组的形式
var arr = [1, 2, 3, 4];
Math.max.apply(null, arr); // 4

// 使用 "..."
Math.max(...arr); // 4
// 还可以加入其它的一些参数
Math.max(...arr, 5, 10); // 10

将一个数组去重:


var arr = [1, 2, 2, 4, 4];
// 使用Set将重复的去掉,然后将set对象转变为数组
var mySet = new Set(arr); // mySet {1, 2, 4}

// 方法1,使用Array.from转变为数组
// var arr = Array.from(mySet); // [1, 2, 4]

// 方法2,使用spread操作符
var arr = [...arr]; // [1, 2, 4]

// 方法3, 传统forEach
var arr2 = [];
mySet.forEach(v => arr2.push(v)); 

4.new.target元属性

    函数内部有两个方法 [[call]][[construct]] (箭头函数没有这个方法),当使用new 操作符时, 函数内部调用 [[construct]], 创建一个新实例,this指向这个实例; 不使用new 操作符时, 函数内部调用 [[call]]

    判断一个函数是否使用new操作符,ES5的方法:


function Person(name) {
    if (this instanceof Person) {
        this.name = name;
    } else {
        throw new Error("You must use new operator");
    }
}
var p = new Person("James"); // ok
var p = Person("James"); // error
// 但是可以通过其他方式绕过这种错误
var notPerson = Person.call(p, "Nicholas"); // works

ES6 通过new.target 来判断是否使用new,元属性 是指一个提供目标相关额外信息(比如new)的非对象属性。


function Person(name) {
    if (typeof new.target !== "undefined") {
        this.name = name;
    } else {
        throw new Errow("You must use new operator");
    }
}
var p = new Person("James"); // ok
var notPerson = Person.call(p, "Louis"); // error 

5.箭头函数

箭头函数有以下几个方面的特点:

  1. this, super, arguments和arguments的值由最近一个包含它的非箭头函数定义。(No this, superm arguments and new.target bindings);
  2. 箭头函数内部没有 [[construct]]方法, 因此不能当作构造器,使用new操作符;
  3. 不存在原型(No prototype);
  4. 不能改变this, 在整个箭头函数生命周期this值保持不变;
  5. 不存在arguments对象,不过包含它的函数存在,箭头函数依靠命名参数和rest parameters;
  6. 不能拥有重复的命名参数,ES5只有严格模式下才不允许
1.箭头函数语法
// 语法很简单
let sum = (n1, n2) => n1 + n2;
// 相当于
let sum = function(n1, n2) {
    return n1 + n2;
};

let getTempItem = id => ({ id: id, name: "Temp" });
// 相当于
let getTempItem = function(id) {
    return {
        id: id,
        name: "Temp"
    };
};
2.没有this绑定
let PageHandler = {
    id: "123456",
    init: function() {
        document.addEventListener("click", function(event) {
            this.doSomething(event.type); // error
        }, false);
    },
    doSomething: function(type) {
        console.log("Handling " + type + " for " + this.id);
    }
};
// init函数中的this.doSomething,this指向的是函数内部document对象,
// 而不是PageHandler对象

使用箭头函数改写:



let PageHandler = {
  id: "123456",
  init: function() {
      document.addEventListener("click", event => this.doSomething(evnet.type)
        }, false);
    },
  doSomething: function(type) {
      console.log("Handling " + type + " for " + this.id);
  }
};
// 此处箭头函数this指向包含它的函数,即init,init为PageHandler的方法,
// this指向PageHandler对象实例
3.不能使用new

var MyType = () => {};
var obj = new MyType(); // Error
4.没有arguments对象

箭头函数没有arguments对象,但是可以使用包含函数中的arguments对象


function createArrowFunctionReturningFirstArg() {
    // arguments 为 createArrowFunctionReturningFirstArg中的对象
    return  () => arguments[0]; 
}
var arrowFunction = createArrowFunctionReturningFirstArg(10);
arrFunction(); // 10

6.其他(misllanceous)

其他的一些变化:

  1. 添加name属性用来判断函数名,用于调试;
  2. 规范块级别函数(block-level functions),当执行流结束块级别函数退出,块级别函数提升变量到块顶部;
  3. 对尾部调用(Tail call, 值一个函数返回另一个函数对象)性能进行优化,尤其是都递归函数性能提升很大

总结
  1. ES6对默认参数的支持;
  2. 任意参数和spread操作符
  3. 箭头函数

    总体来说,这些改动都是为编写程序提供了极大的便利,不用再使用workaround来解决语法存在的问题,整体来讲,更加符合语言的书写习惯。


james_sawyer
11 声望0 粉丝

前端爱好者,初学者