ECMAScript6(5):函数的扩展

(function(a){}).length; //1

(function(a = 5){}).length; //0

(function(a, b, c=5){}).length; //2

(function(…args){}).length; //0, rest参数也不计入 length

rest 参数

rest 参数形式为 ...变量名, 它会将对应的全部实际传递的变量放入数组中, 可以用它来替代 arguments:

function f(…val){

console.log(val.join());

}

f(1, 2); //[1, 2]

f(1, 2, 3, 4); //[1, 2, 3, 4]

function g(a, …val){

console.log(val.join());

}

g(1, 2); //[2]

g(1, 2, 3, 4); //[2, 3, 4]

否则这个函数 g 你的这样定义函数, 比较麻烦:

function g(a){

console.log([].slice.call(arguments, 1).join());

}

这里需要注意2点:

  • rest参数必须是函数的最后一个参数, 它的后面不能再定义参数, 否则会报错。

  • rest参数不计入函数的 length 属性中

建议:

  • 所有配置项都应该集中在一个对象,放在最后一个参数,布尔值不可以直接作为参数。这样方便调用者以任何顺序传递参数。

  • 不要在函数体内使用arguments变量,使用rest运算符(…)代替。因为rest运算符显式表明你想要获取参数,而且arguments是一个类似数组的对象,而rest运算符可以提供一个真正的数组。

  • 使用默认值语法设置函数参数的默认值。

扩展运算符

扩展运算符类似 rest运算符的逆运算, 用 ... 表示, 放在一个(类)数组前, 将该数组展开成独立的元素序列:

console.log(1, …[2, 3, 4], 5); //输出1, 2, 3, 4, 5

扩展运算符的用处很多:

- 可以用于快速改变类数组对象为数组对象, 也是用于其他可遍历对象:

[…document.querySelectorAll(‘li’)]; //[

  • ,
  • ,
  • ];
    • 结合 rest 参数使函数事半功倍:

    function push(arr, …val){

    return arr.push(…val); //调用函数时, 将数组变为序列

    }

    • 替代 apply 写法

    var arr = [1, 2, 3];

    var max = Math.max(…arr); //3

    var arr2 = [4, 5, 6];

    arr.push(…arr2); //[1, 2, 3, 4, 5, 6]

    new Date(…[2013, 1, 1]); //ri Feb 01 2013 00: 00: 00 GMT+0800 (CST)

    • 连接, 合并数组

    var more = [4, 5];

    var arr = [1, 2, 3, …more]; //[1, 2, 3, 4, 5]

    var a1 = [1, 2];

    var a2 = [3, 4];

    var a3 = [5, 6];

    var a = […a1, …a2, …a3]; //[1, 2, 3, 4, 5, 6]

    • 解构赋值

    var a = [1, 2, 3, 4, 5];

    var [a1, …more] = a; //a1 = 1, more = [2, 3, 4, 5]

    //注意, 扩展运算符必须放在解构赋值的结尾, 否则报错

    • 字符串拆分

    var str = “hello”;

    var alpha = […str]; //alpha = [‘h’, ‘e’, ‘l’, ‘l’, ‘o’]

    […‘x\uD83D\uDE80y’].length; //3, 正确处理32位 unicode 字符

    建议:使用扩展运算符(…)拷贝数组。

    name 属性

    name 属性返回函数的名字, 对于匿名函数返回空字符串。不过对于表达式法定义的函数, ES5 和 ES6有差别:

    var fun = function(){}

    fun.name; //ES5: “”, ES6: “fun”

    (function(){}).name; //“”

    对于有2个名字的函数, 返回后者, ES5 和 ES6没有差别:

    var fun = function baz(){}

    fun.name; //baz

    对于 Function 构造函数得到的函数, 返回 anonymous:

    new Function(“fun”).name; //“anonymous”

    new Function().name; //“anonymous”

    (new Function).name; //“anonymous”

    对于 bind 返回的函数, 加上 bound 前缀

    function f(){}

    f.bind({}).name; //“bound f”

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

    (new Function).bind({}).name; //“bound anonymous”

    箭头函数

    箭头函数的形式如下:

    var fun = (参数列表) => {函数体};

    如果只有一个参数(且不指定默认值), 参数列表的圆括号可以省略; (如果没有参数, 圆括号不能省略)

    如果只有一个 return 语句, 那么函数体的花括号也可以省略, 同时省略 return 关键字。

    var fun = value => value + 1;

    //等同于

    var fun = function(value){

    return value + 1;

    }

    var fun = () => 5;

    //等同于

    var fun = function(){

    return 5;

    }

    如果箭头函数的参数或返回值有对象, 应该用 () 括起来:

    var fun = n => ({name: n});

    var fun = ({num1=1, num2=3}={}) => num1 + num2;

    看完之前的部分, 箭头函数应该不陌生了:

    var warp = (…val) => val;

    var arr1 = warp(2, 1, 3); //[2, 1, 3]

    var arr2 = arr1.map(x => x * x); //[4, 1, 9]

    arr2.sort((a, b) => a - b); //[1, 4, 9]

    使用箭头函数应注意以下几点:

    - 不可以将函数当做构造函数调用, 即不能使用 new 命令;

    - 不可以在箭头函数中使用 yield 返回值, 所以不能用过 Generator 函数;

    - 函数体内不存在 arguments 参数;

    - 函数体内部不构成独立的作用域, 内部的 this 和定义时候的上下文一致; 但可以通过 call, apply, bind 改变函数中的 this。关于作用域, 集中在ES6函数扩展的最后讨论。

    举几个箭头函数的实例:

    实例1: 实现功能如: insert(2).into([1, 3]).after(1)insert(2).into([1, 3]).before(3)这样的函数:

    var insert = value => ({

    into: arr => ({

    before: val => {

    arr.splice(arr.indexOf(val), 0, value);

    return arr;

    },

    after: val => {

    arr.splice(arr.indexOf(val) + 1, 0, value);

    return arr;

    }

    })

    });

    console.log(insert(2).into([1, 3]).after(1));

    console.log(insert(2).into([1, 3]).before(3));

    实例2: 构建一个管道(前一个函数的输出是后一个函数的输入):

    var pipe = (…funcs) => (init_val) => funcs.reduce((a, b) => b(a), init_val);

    //实现 2 的 (3+2) 次方

    var plus = a => a + 2;

    pipe(plus, Math.pow.bind(null, 2))(3); //32

    实例3: 实现 λ 演算

    //fix = λf.(λx.f(λv.x(x)(v)))(λx.f(λv.x(x)(v)))

    var fix = f => (x => f(v => x(x)(v)))(x => f(v => x(x)(v)));

    建议:箭头函数取代 Function.prototype.bind,不应再用 self / _this / that 绑定 this。其次,简单的、不会复用的函数,建议采用箭头函数。如果函数体较为复杂,行数较多,还是应该采用传统的函数写法。

    这里需要强调,以下情况不能使用箭头函数:

    1. 定义字面量方法

    let calculator = {

    array: [1, 2, 3],

    sum: () => {

    return this.array.reduce((result, item) => result + item); //这里的 this 成了 window

    }

    };

    calculator.sum(); //“TypeError: Cannot read property ‘reduce’ of undefined”

    1. 定义原型方法

    function Cat(name) {

    this.name = name;

    }

    Cat.prototype.sayCatName = () => {

    return this.name; //和上一个问题一样:这里的 this 成了 window

    };

    let cat = new Cat(‘Mew’);

    cat.sayCatName(); //undefined

    1. 绑定事件

    const button = document.getElementById(‘myButton’);

    button.addEventListener(‘click’, () => {

    this.innerHTML = ‘Clicked button’; //这里的 this 本应该是 button, 但不幸的成了 window

    });

    1. 定义构造函数

    let Message = (text) => {

    this.text = text;

    };

    let helloMessage = new Message(‘Hello World!’); //TypeError: Message is not a constructor

    1. 不要为了追求代码的简短丧失可读性

    let multiply = (a, b) => b === undefined ? b => a * b : a * b; //这个太难读了,太费时间

    let double = multiply(2);

    double(3); //6

    multiply(2, 3); //6

    函数绑定

    ES7 中提出了函数绑定运算, 免去我们使用 call, bind, apply 的各种不方便, 形式如下:

    objName::funcName

    以下几组语句两两等同

    var newFunc = obj::func;

    //相当于

    var newFunc = func.bind(obj);

    var result = obj::func(…arguments);

    //相当于

    var result = func.apply(obj, arguments);

    如果 :: 左边的对象原本就是右边方法中的 this, 左边可以省略

    var fun = obj::obj.func;

    //相当于

    var fun = ::obj.func;

    //相当于

    var fun = obj.func.bind(obj);

    :: 运算返回的还是对象, 可以进行链式调用:

    $(‘.my-class’)::find(‘p’)::text(“new text”);

    //相当于

    $(‘.my-class’).find(‘p’).text(“new text”);

    尾调用优化

    尾调用是函数式编程的概念, 指在函数最后调用另一个函数。

    //是尾调用

    function a(){

    return g();

    }

    function b§{

    if(p>0){

    return m();

    }

    return n();

    }

    function c(){

    return c();

    }

    //以下不是尾调用

    function d(){

    var b1 = g();

    return b1;

    }

    function e(){

    g();

    }

    function f(){

    return g() + 1;

    }

    尾调用的一个显著特点就是, 我们可以将函数尾部调用的函数放在该函数外面(后面), 而不改变程序实现结果。这样可以减少函数调用栈的开销。

    这样的优化在 ES6 的严格模式中被强制实现了, 我们需要做的仅仅是在使用时候利用好这个优化特性, 比如下面这个阶乘函数:

    function factorial(n){

    if(n <= 1) return 1;

    return n * factorial(n - 1);

    }

    factorial(5); //120

    这个函数计算 n 的阶乘, 就要在内存保留 n 个函数调用记录, 空间复杂度 O(n), 如果 n 很大可能会溢出。所以进行优化如下:

    “use strict”;

    function factorial(n, result = 1){

    if(n <= 1) return result;

    return factorial(n - 1, n * result);

    }

    factorial(5); //120

    当然也可以使用柯里化:

    var factorial = (function factor(result, n){

    if(n <= 1) return result;

    return factor(n * result, n - 1);

    }).bind(null, 1);

    factorial(5); //120

    函数的尾逗号

    这个仅仅是一个提案: 为了更好地进行版本控制, 在函数参数尾部加一个逗号, 表示该函数日后会被修改, 便于版本控制器跟踪。目前并未实现。

    作用域

    这里仅仅讨论 ES6 中的变量作用域。除了 let 和 const 定义的的变量具有块级作用域以外, varfunction 依旧遵守词法作用域, 词法作用域可以参考博主的另一篇文章[javascript函数、作用域链与闭包]( )

    首先看一个例子:

    var x = 1;

    function f(x, y=x){

    console.log(y);

    }

    f(2); //2

    这个例子输出了2, 因为 y 在初始化的时候, 函数内部的 x 已经定义并完成赋值了, 所以, y = x 中的 x 已经是函数的局部变量 x 了, 而不是全局的 x。当然, 如果局部 x 变量在 y 声明之后声明就没问题了。

    var x = 1;

    function f(y=x){

    let x = 2

    console.log(y);

    }

    f(); //1

    那如果函数的默认参数是函数呢?烧脑的要来了:

    var foo = “outer”;

    function f(x){

    return foo;

    }

    function fun(foo, func = f){

    console.log(func());

    }

    fun(“inner”); //“outer”

    如果基础好, 那就根本谈不上不烧脑。因为, 函数中的作用域取决于函数定义的地方, 函数中的 this 取决于函数调用的方式。(敲黑板)

    但如果这样写, 就是 inner 了, 因为func默认函数定义的时候 fun内的 foo 已经存在了。

    var foo = “outer”;

    function fun(foo, func = function(x){

    return foo;

    }){

    console.log(func());

    }

    fun(“inner”); //“inner”

    技巧: 利用默认值保证必需的参数被传入, 而减少对参数存在性的验证:

    function throwErr(){

    throw new Error(“Missing Parameter”);

    }

    function fun(necessary = throwErr()){

    //…如果参数necessary没有收到就使用参数, 从而执行函数抛出错误

    }

    //当然也可以这样表示一个参数是可选的

    function fun(optional = undefined){

    //…

    }

    箭头函数的作用域和定义时的上下文一致, 但可以通过调用方式改变:

    window && (window.name = “global”) || (global.name = “global”);

    var o = {

    name: ‘obj-o’,

    foo: function (){

    setTimeout(() => {console.log(this.name); }, 500);

    }

    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值