ES6扩展 函数扩展 箭头函数

ES6中对函数的使用方式进行了扩展

默认参数

  • 和解构赋值一样,ES6中也可以给函数一个默认参数,并且可以用简单的表达式来设定默认值。
  • 但要注意:用例子来解释,b的默认值表达式中不能出现b或者c,会报错(b、c未定义)。
    1
    2
    3
    4
    function add(a, b = 999 + a, c = 1) {
    console.log(a, b, c);
    }
    add(1);//1 1000 1

结合对象的解构赋值

如果直接调用People()函数,但什么对象都不传进去,此时则使用age的默认值38,name则undefined。
如果调用People()函数的同时将带有name属性值的对象传入则得到hhh 38:

1
2
3
4
5
function People({ name, age = 38 } = {}) {
console.log(name, age);
}
People();//undefined 38,什么对象都不传
People({ name: "hhh" });//hhh 38,传入带有name属性值的对象

或者直接在对象的解构赋值等号右边设置和调用People()函数的时候再传入name属性的效果也是一样的:

1
2
3
4
function People1({ name, age = 38 } = {name:"xiaohuang"}) {
console.log(name, age);
}
People1();//xiaohuang 38

复习:arguments对象

  • 注意: arguments 对象只会包含传入的参数,不会包括函数参数的默认值
    • 默认值是在函数定义时就已经确定的,而不是在调用时传递的, arguments 对象只会包含实际传递给函数的参数,而不会包含默认值
  • arguments 是一个对应于传递给函数的参数的类数组对象。arguments对象是所有(非箭头)函数中都可用的局部变量。你可以使用arguments对象在函数中引用函数的参数
  • 此对象包含传递给函数的每个参数,第一个参数在索引0处。**[]语法可访问它的每一个元素** :
    1
    2
    3
    arguments[0]
    arguments[1]
    arguments[2]
  • 参数也可以被设置:
    1
    arguments[1] = 'new value';
  • arguments对象不是一个 Array实例 ,它类似于Array
  • 除了length属性和索引元素之外没有任何Array属性(length属性确定传递参数的个数)。例如,它没有 pop 方法。
  • 但是它可以被转换为一个真正的Array(下面“结合扩展运算符”中2个例子4种方法都可以将类数组转换为数组)
  • MDN更多参考信息

结合扩展运算符...(剩余参数...

  • 剩余参数...是做聚合的,它会将那些没有对应形参的实参们聚合成一个数组。而扩展运算符...是做展开的,符号都是...,但含义不同。
  • 其实在下面这个例子中...表示剩余参数而不是扩展运算符,sum函数通过将所有的剩余参数1,2,3,4,"ddd"传进args做一个聚合将他们组转换为数组:
    1
    2
    3
    4
    function sum(...args){//将传进来的类数组匹配到的剩余参数都放到args中组成数组
    console.log(args);//[1, 2, 3, 4, "ddd"]
    }
    sum(1,2,3,4,"ddd");//传入一个类数组,通过剩余参数可以转换为数组
  • [1,2,...[1,2,3]];...就表示扩展运算符,意为将[1,2,3]展开来放入另一个数组中。
  • 除了上面例子,我们还可以回顾一下使用扩展运算符将类数组转换为数组的三种不同方法
    1
    2
    3
    4
    5
    6
    7
    function sum() {
    let args = Array.prototype.slice.call(arguments);
    // let args = [...arguments];
    // let [...args] = arguments;
    console.log(args);//[1, 2, 3, 4, "ddd"]
    }
    sum(1, 2, 3, 4, "ddd");//传入一个类数组,通过剩余参数可以转换为数组
  • 注意:无论...表示剩余参数还是扩展运算符,当它作为参数时她都必须在最后一个:
    1
    2
    3
    4
    5
    function op(type, ...nums) {
    console.log(type);//sum
    console.log(nums);//[1, 2, 3, 4, 5]
    }
    op("sum", 1, 2, 3, 4, 5);

补充:回调函数

  • 回调函数就是一个参数,将这个函数作为参数传到另一个函数里面,当那个函数执行完之后,再执行传进去的这个函数。这个过程就叫做回调。
  • 注意:调用回调函数时是不需要()的,直接使用函数名!
  • 下面reduce()方法的第一个参数就是回调函数。

回调函数与调用普通函数的区别

  1. 性能没有区别
  2. 回调函数是作为参数传递的,操作更加灵活
    比如,你定义了函数a,b,c,那么使用回调函数时不用修改 a 的代码就可以让 a 调用 c、d、e….
    当你调用普通函数的时候不修改代码只能在函数a内运行函数b,就失去了变量的灵活性

补充:数组的reduce()方法

  • reduce()方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。即,对数组每个元素执行回调函数,返回值用于下一次计算参数
  • reduce()方法的参数由回调函数和一个初始值参数组成
    • 参数2(initialValue)是可选的,并且如果没有提供该参数,它的默认值就是0(或者在累加字符串时是空字符串)
  • 语法:array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
  • 语法理解:
    1
    2
    3
    4
    5
    6
    reduce(
    function(
    回调函数返回的值,数组当前元素,当前元素索引,调用reduce的数组
    ) ,
    回调函数第一个参数的初始值
    );
  • 返回值:返回计算结果(不改变原数组)
    参数及描述
  • 例子:
    1
    2
    3
    4
    5
    6
    7
    8
    const array1 = [1, 2, 3, 4];
    const reducer = (accumulator, currentValue) => accumulator + currentValue;

    // 0 + 1 + 2 + 3 + 4
    console.log(array1.reduce(reducer));// 10

    // 5 + 1 + 2 + 3 + 4
    console.log(array1.reduce(reducer, 5));// 15

实现加法的函数

  • 通过上面的例子我们可以知道传进来的类数组会被转换为数组numbers,而数组拥有累加方法reduce()
  • 先设置参数total的默认值为0,第一次会将1传入num中,相加以后返回1给total;第二次则将2传入num中,相加以后返回3给total,以此类推得到最终total
    1
    2
    3
    4
    5
    6
    function sum(...numbers) {
    return numbers.reduce(function (a, b) {
    return a + b;
    }, 0);
    }
    console.log(sum(1,2,3,4));//10
    相当于:(注意函数add的调用没有()
    1
    2
    3
    4
    5
    6
    7
    function add(total, num) {
    return total + num;
    }
    function sum(...numbers) {
    return numbers.reduce(add, 0);
    }
    console.log(sum(1, 2, 3, 4));//10

箭头函数=>

使用箭头函数可以省略function

复习:数组的pop()

用于删除最后一个元素,返回值为被删除的元素。

多个参数需要用(),多行代码时需要用{}

1
2
3
4
5
const add1 = (a, b) => {
a += 1;
return a + b;
}
console.log(add1(2,4));//7

相当于:

1
2
3
4
5
const add1 = function (a, b){
a += 1;
return a + b;
}
console.log(add1(2,4));//7

单行代码可省略{}return,直接表示为返回值

1
2
const add2 = (a, b) => a + b;
console.log(add2(1, 3));//4

单个参数时可省略()

这种情况下pop()的返回值会被赋给arr,也就打印出3:

1
2
const popArr1 = arr1 => arr1.pop();
console.log(popArr1([1, 2, 3]));//3

不想要返回值可以添加void使返回值为undefined:

1
2
const popArr2 = arr2 => void arr2.pop();
console.log(popArr2([1, 2, 3]));//undefined

放在大括号里也可以,但没必要,推荐使用void:

1
2
const popArr3 = arr3 => { arr3.pop() };
console.log(popArr3([1, 2, 3]));//undefined

不需要传参时只写()即可

比如:

1
2
3
log222 = () => {
console.log(222);
}

箭头函数和普通函数的区别

箭头函数没有arguments对象

在箭头函数中调用arguments报错:

1
2
3
4
const arg=()=>{
console.log(arguments);
}
arg(1,2,3);//报错:arguments is not defined at arg

但是我们可以使用 剩余参数运算符 代替arguments去拿传入的参数:
注意:剩余参数...是做聚合的,它会将传入的参数们聚合成一个数组

1
2
3
4
const arg=(...args)=>{
console.log(args);
}
arg(1,2,3);//[1, 2, 3]

箭头函数没有自己的this

箭头函数的this总是指向定义时所在的对象(的this),而不是运行时所在的对象。箭头函数根本没有自己的this,导致内部的this指向了外层代码的this,这个指向在定义时就已经确定而不会在调用时指向其执行环境的(变量)对象。
帮助理解:结合下方的例子,say2()中的this指向say2()外层的this,这个外层 就是xiaoming,注意了!xiaoming的this是windows,所以say2()中的this指向 定义 时的 外层代码的this =》xiaoming的this=》windows

  • this:自动引用正在调用当前方法的.前的对象。
  • 箭头函数获取到的this是调用他的对象外层的this,不一定是windows。
  • 例子中say1的this指向xiaoming,而say2的this却指向windows,它和xiaoming同级:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const xiaoming={
    name:"小明",
    say1:function(){
    console.log(this);
    },
    say2:()=>{
    console.log(this);
    }
    }
    xiaoming.say1();//{name: "小明", say1: ƒ, say2: ƒ}
    xiaoming.say2();//Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
  • 更加明显的例子:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    name="小红";
    const xiaoming={
    name:"小明",
    say1:function(){
    console.log(this.name);
    },
    say2:()=>{
    console.log(this.name);
    }
    }
    xiaoming.say1();//小明
    xiaoming.say2();//小红

回调函数中的this

  • 回调函数中的this默认是指向windows的,因为本质上是在函数内callback,并没有.前的对象调用
  • 注意:回调函数和箭头函数不同,回调函数时指向windows,而箭头函数只是指向定义他的对象外层的this,不一定是windows
  • 也就是说,如果回调函数中想要使用this有两种解决方法(如下)

闭包获取

要么在使用回调函数之前利用闭包的特性先使用一个变量_this保存this

1
2
3
4
5
6
7
8
9
10
11
12
13
const xiaoming = {
name:"xiaoming",
age: null,
getAge: function () {
let _this = this;
//...ajax
setTimeout(function () {
_this.age = 15;
console.log(_this);
}, 1000);
}
};
xiaoming.getAge();//{name: "xiaoming", age: 15, getAge: ƒ}

箭头函数获取

要么将回调函数改为箭头函数,这样this取到外层的this,即我们需要的对象xiaoming:

1
2
3
4
5
6
7
8
9
10
11
12
const xiaoming = {
name: "xiaoming",
age: null,
getAge: function () {
//...ajax
setTimeout(() => {
this.age = 15;
console.log(this);
}, 1000);
}
};
xiaoming.getAge();//{name: "xiaoming", age: 15, getAge: ƒ}

call apply bind 并不会影响其 this 的指向

call apply bind 并不会影响其 this 的指向

  • 需要了解:
    • callapplybind 这些函数可以用于显式地改变函数的 this 指向
    • 普通函数的 this 指向可以改变的原因: 普通函数的 this 是在函数调用时动态确定的,所以可以通过callapplybind 这些函数改变 this 指向
  • 这些函数对箭头函数的 this 指向没有影响的原因: 箭头函数有一个固定的 this,它是由定义时的上下文决定的,所以无法通过这些函数来改变
  • 由于 箭头函数没有自己的this指针,所有通过 call()/apply()/bind() 方法调用一个函数时,只能传递参数,不能绑定this,即 他们的第一个参数会被忽略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const obj = {
value: 42,
};

function normalFunction() {
console.log(this.value);
}

const arrowFunction = () => {
console.log(this.value);
};

normalFunction.call(obj); // 输出:42,通过 call 改变普通函数的 this 指向
arrowFunction.call(obj); // 输出:undefined,箭头函数的 this 不受 call 影响

const boundFunction = arrowFunction.bind(obj);
boundFunction(); // 输出:undefined,bind 也无法改变箭头函数的 this 指向
  • 在这个例子中,normalFunction 是一个普通函数,我们使用 call 来显式地改变它的 this 指向,使它指向了 obj,所以可以正常输出 42。然而,当我们尝试使用 callbind 来改变箭头函数 arrowFunctionthis 指向时,它依然会继承外围函数(这里是全局作用域)的 this,所以输出仍然是 undefined