资讯专栏INFORMATION COLUMN

ECMAScript6 新特性——“函数的扩展”

Jiavan / 2862人阅读

摘要:返回空字符串,返回将一个具名函数赋值给一个变量,则和的属性都返回这个具名函数原本的名字。不可以使用对象,该对象在函数体内不存在。等到运行结束,将结果返回到,的调用帧才会消失。

1 函数参数的默认值

ES6允许为函数的参数设置默认值,即直接写在参数定义的后面:

function log(x = "message.",y = "duration infomation.") {
    console.log(x, y);
}

log(); //message. duration infomation.
log("hello world.", "connecting."); //hello world. connecting.

参数变量是默认声明的,所以不能用let和const再次声明

function add(x = 0, y = 0) {
    let x; //报错
    const y; //报错
    console.log(x + y);
}
add();

与结构赋值默认值结合使用

function add({x = 0, y = 0}) {
    console.log(x + y);
}
add({}); //0
add({y: 1}); //1

//当add()时,则会报错
add(); //报错

//如何才能在add()时不报错呢?
//这就需要用到双重默认值,即默认为函数传入一个对象
function add({x = 0, y = 0} = {}) {
    console.log(x + y);
}
add({}); //0
add({y: 1}); //1
add(); //0

如下:

function add({name = "your name here", age = "your age here"} = {}) {
    console.log(`${name}, ${age}`);
}
add(); //your name here, your age here
add({name: "Oliver"}); //Oliver, your age here
add({name: "Troy", age: 18}); //Troy, 18

函数的length属性

指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。

function add(x,y = 0) {
    console.log(x + y);
}
console.log(add.length); //1 函数预期传入1个参数,因为有一个参数已经有默认值了

作用域

如果参数默认值是一个变量,则该变量所处的作用域,与其他变量的作用域规则是一样的,即先是当前函数的作用域,然后才是全局作用域。

var x = 10;
function log(x, y = x) { //x已经在内部生成,使用的将是内部的x
    console.log(y);
}
log(2); //2

var x = 10;
function log(y = x) { //x不存在,使用的将是外部的x
    console.log(y);
}
log(); //10

实际应用

省略参数抛出错误:

function throwIfMissing() {
    throw new Error("Missing parameter.");
}
function foo(x = throwIfMissing()) {
    return x;
}
console.log(foo(10)); //10
console.log(foo()); //Uncaught Error: Missing parameter.
2 rest参数

rest参数(形式为“...变量名”),用于获取函数的多余参数,这样就不需要使用arguments对象了

function foo(...values) {
    let sum = 0;
    for(let val of values) sum += val;
    console.log(sum);
}
foo(1,2,3);

注意,rest参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。

function foo(x, ...values) {
    let sum = 0;
    for(let val of values) sum += val;
    console.log(x, sum);
}
foo(1,2,3); //x是1,sum是2+3

function foo(x, ...values, y) {
    let sum = 0;
    for(let val of values) sum += val;
    console.log(x, sum);
}
foo(1,2,3); //Rest parameter must be last formal parameter
3 扩展运算符

扩展运算符(spread)是三个点(...)。它好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。

let a = [1,2,3,4,5,6,7];
console.log(...a); //1 2 3 4 5 6 7
a.push(...a);
console.log(a); //[1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7]

该运算符主要用于函数的调用

let a = [1,2,3,4,5,6,7];
function add(...args) {
    let sum = 0;
    for (let val of args) console.log(sum += val);
    console.log(`done. final sum: ${sum}`);
}
add(...a); //依次传入a数列中的值
//1
//3
//6
//10
//15
//21
//28
//done. final sum: 28

替代数组的apply方法

扩展运算符可以展开数组,所以不再需要apply方法

//ES5
let a = [1,2,3,4,5,6,7];
console.log(Math.max.apply(null,a)); //7
//ES6
console.log(Math.max(...a)); //7

又比如push函数

let a = [];
let num = [1,2,3,4,5,6,7];
a.push(...num);
console.log(a.toString()); //1,2,3,4,5,6

扩展运算符的应用

合并数组

let a = [];
let a1 = [1,2,3];
let a2 = [4,5];
let a3 = [6,7,8,9,10];
a = [...a1, ...a2, ...a3];
console.log(a.toString()); //1,2,3,4,5,6,7,8,9,10

与解构赋值结合

let [...rest] = [1,2,3,4,5,6];
console.log(rest); //[1,2,3,4,5,6]

函数的返回值

var dateFields = readDateFields(database);
var d = new Date(...dateFields);

字符串

console.log([..."hello"]); //["h", "e", "l", "l", "o"]

扩展运算符能够识别32位Unicode字符

可以正确返回字符串长度:

console.log([..."hello"]); //["h", "e", "l", "l", "o"]
function len(...args) {
    for (let val of args) console.log([...val].length);
}
len("hello", "another one 哈哈"); //14

类似数组的对象

var nodeList = document.getElementsByTagName("p");
var array = [...nodeList];

含有Iterator接口对象

只要具有Iterator接口的对象,都可以使用扩展运算符

Map,Set,Generator等

4 name属性

函数的name属性,返回该函数的函数名。

如果将一个匿名函数赋值给一个变量,ES5的name属性,会返回空字符串,而ES6的name属性会返回实际的函数名。

var x = function() {};
console.log(x.name); //"" ES5返回空字符串,ES6返回x

将一个具名函数赋值给一个变量,则ES5和ES6的name属性都返回这个具名函数原本的名字。

var x = function y() {};
console.log(x.name); //"y"

Function构造函数返回的函数实例,name属性的值为“anonymous”。

console.log((new Function).name); //anonymous

bind返回的函数,name属性值会加上“bound ”前缀。

function foo() {};
foo.bind({}).name // "bound foo"

(function(){}).bind({}).name // "bound "
5 箭头函数

ES6允许使用“箭头”(=>)定义函数。

let add = (x,y) => x + y;
console.log(add(1,2)); //3

function add(x,y) {
    return x + y;
}
console.log(add(1,2)); //3

如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。

let add = (x, y) => {
    console.log("done");
    return x + y;
};
console.log(add(1, 2)); //3

如果箭头函数直接返回一个对象,必须在对象外面加上括号。

let add = (x, y) => ({ result: x + y });
console.log(add(1, 2).result); //3

箭头函数可以与变量解构结合使用。

let add = ({ name, age }) => ({ result: `${name},${age}` });
console.log(add({ name: "Oliver", age: 18 }).result); //Oliver,18

箭头函数的一个用处是简化回调函数

var arr = [1, 2, 3, 4, 5];
arr.map(function(item, index, array) {
    console.log(item);
});

arr.map((item, index, array) => { console.log(item); });

//上面两种函数写法功能相同,下面的明显较为简洁

使用注意点

函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替。

不可以使用yield命令,因此箭头函数不能用作Generator函数。

6 ES7函数绑定

ES7中函数绑定运算符(::)作用是代替call、apply、bind调用。

7 尾调用优化

尾调用

尾调用(Tail Call)就是指某个函数的最后一步是调用另一个函数:

function g() {
    console.log("done.");
}

function j() {
    console.log("almost done.");
}

function log(x) {
    if (x > 0) {
        return g();
    }
    return j();
}
log(10); //done.
log(0); //almost done.

g()和j()的调用都属于尾调用,因为都是函数的最后一步

尾调用优化

函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用帧上方,还会形成一个B的调用帧。等到B运行结束,将结果返回到A,B的调用帧才会消失。如果函数B内部还调用函数C,那就还有一个C的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。

尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。

注意,只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”。

function addOne(a) {
    var one = 1;

    function inner(b) {
        return b + one;
    }
    return inner(a);
}

尾递归

递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误。但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。

所以确保最后只调用自身就可以了,做法是将所有参数传入要调用的函数中去:

// function log(n) {
//     if (n === 1) {
//         return 1
//     };
//     return n * log(n - 1);
// }
// console.log(log(5)); //120

function log(n, total) {
    if (n === 1) {
        return total
    };
    return log(n - 1, n * total);
}
console.log(log(5, 1)); //120
8 ES7函数参数的尾逗号

ES7提案允许函数的最后一个参数有尾逗号。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/79157.html

相关文章

  • ECMAScript6(5):函数扩展

    摘要:否则调用时依然需要传参报错注意这里不能用触发默认值这里我们还需要单独讨论一下默认参数对的影响很明显,默认参数并不能加到中。关于作用域集中在函数扩展的最后讨论。那如果函数的默认参数是函数呢烧脑的要来了如果基础好那就根本谈不上不烧脑。 参数默认值 ES5中设置默认值非常不方便, 我们这样写: function fun(a){ a = a || 2; console.log(a); ...

    afishhhhh 评论0 收藏0
  • ECMAScript6 特性——“let和const命令”

    摘要:基本用法所声明的变量,只在命令所在的代码块内有效。在循环中适合使用不存在变量提升不像那样会发生变量提升现象暂时性死区只要块级作用域内存在命令,它所声明的变量就绑定这个区域,不再受外部的影响。块级作用域实际上为新增了块级作用域。 1 let 基本用法 所声明的变量,只在let命令所在的代码块内有效。 { let b = 100; console.log(b); //100...

    PascalXie 评论0 收藏0
  • ECMAScript6 特性——“正则扩展

    摘要:第二个参数指定修饰符,如果存在则使用指定的修饰符。属性表示是否设置了修饰符属性的属性返回正则表达式的正文的属性返回正则表达式的修饰符字符串必须转义,才能作为正则模式。 1 RegExp构造函数 ES6 允许RegExp构造函数接受正则表达式作为参数。第二个参数指定修饰符,如果存在则使用指定的修饰符。 var regexp = new RegExp(/xyz/i, ig); consol...

    Shisui 评论0 收藏0
  • ECMAScript6 特性——“数值扩展

    摘要:二进制和八进制表示法提供了二进制和八进制数值的新的写法,分别用前缀或和或表示。用来检查是否为有穷以及是否为这两个新方法只对数值有效,非数值一律返回。引入了和这两个常量,用来表示这个范围的上下限。因为有精度限制,超过的次方的值无法精确表示。 1 二进制和八进制表示法 ES6提供了二进制和八进制数值的新的写法,分别用前缀0b(或0B)和0o(或0O)表示。 console.log(0b10...

    Dean 评论0 收藏0
  • ECMAScript6 特性——“变量解构赋值”

    摘要:数组的解构赋值允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构。如果变量名与属性名不一致,必须写成下面这样。 1 数组的解构赋值 ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。 基本用法 { var [a,[b,c],d,,...f] = [1,[2,3],4,5,6,7]; console...

    Eric 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<