箭头函数笔记

By admin in web前端 on 2019年5月6日

一、function默认参数

  现在可以在定义函数的时候指定参数的默认值了,而不用像以前那样通过逻辑或操作符来达到目的了。
  es5

function sayHello(name){
        //传统的指定默认参数的方式
        let name1 = name||'hubwiz';
        return 'Hello'+name1;
    }

  运用ES6的默认参数

function sayHello2(name='hubwiz'){
        return `Hello ${name}`;
    }
    function sayHello3(name='张三',age=19){
       return `大家好,我叫${name},今年${age}岁`+'\n' +"大家好,我叫"+name+",今年"+age;
    }
  console.log(sayHello());//输出:Hello hubwiz
    console.log(sayHello('汇智网')); //输出:Hello 汇智网
    console.log(sayHello2());  //输出:Hello hubwiz
    console.log(sayHello2('汇智网'));
    console.log(sayHello3());//输出:Hello 汇智网
    console.log(sayHello3('nick',26));

回顾ES3,5

标签: rest spread 箭头函数 JavaScript ES6 前端 web
本博客版权归本人和饥人谷所有,转载需说明来源
内容转载自阮一峰老师的ES6入门

二、rest参数

  rest参数(形式为“...变量名”)可以称为不定参数,用于获取函数的多余参数,这样就不需要使用arguments对象了。
  rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

function add(...values) {
    console.log(...values);//1 2 3
    console.log(values);//[1,2,3]
    let sum = 0;
    for (var val of values) {
        sum += val;
    }
    return sum;
}
console.log(add(1, 2, 3));

  不定参数的格式是三个句点后跟代表所有不定参数的变量名。比如以上示例中,...values 代表了所有传入add函数的参数。
// 具名函数
function xxx(v1,v2){
    console.log('函数体')
    return v1 + v2
}
// 匿名函数
let fn = function (v1,v2){
    console.log(‘函数体’)
    return v1 + v2
}
匿名函数必须赋值才可以使用。或者使用立即执行函数的方式。

1.rest参数

ES6 引入 rest
参数(形式为“…变量名”),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest
参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

function add(...values) {
  let sum = 0;

  for (var val of values) {
    sum += val;
  }

  return sum;
}

add(2, 5, 3) // 10

上面代码的add函数是一个求和函数,利用 rest
参数,可以向该函数传入任意数目的参数。

下面是一个 rest 参数代替arguments变量的例子。

// arguments变量的写法
function sortNumbers() {
  return Array.prototype.slice.call(arguments).sort();
}

// rest参数的写法
const sortNumbers = (...numbers) => numbers.sort();

上面代码的两种写法,比较后可以发现,rest 参数的写法更自然也更简洁。

rest
参数中的变量代表一个数组,所以数组特有的方法都可以用于这个变量。下面是一个利用
rest 参数改写数组push方法的例子。

function push(array, ...items) {
  items.forEach(function(item) {
    array.push(item);
    console.log(item);
  });
}

var a = [];
push(a, 1, 2, 3)

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

// 报错
function f(a, ...b, c) {
  // ...
}

函数的length属性,不包括 rest 参数。

(function(a) {}).length  // 1
(function(...a) {}).length  // 0
(function(a, ...b) {}).length  // 1

三、扩展运算符

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

  它允许传递数组或者类数组直接做为函数的参数而不用通过apply。

    let people=['张三','李四','王五'];
//sayHello函数本来接收三个单独的参数people1,people2和people3
    function sayHello4(people1,people2,people3){
        console.log(`Hello ${people1},${people2},${people3}`);
    }
//但是我们将一个数组以拓展参数的形式传递,它能很好地映射到每个单独的参数
    sayHello4(...people);   //输出:Hello 张三,李四,王五
//而在es5,如果需要传递数组当参数,我们需要使用函数的apply方法
    sayHello4.apply(null,people);   //输出:Hello 张三,李四,王五

上面代码中示例匿名函数,可以拆分成三步看。

2.扩展运算符

四、箭头函数

  箭头函数是使用=>语法的函数简写形式。这在语法上与 C#、Java 8 和 CoffeeScript 的相关特性非常相似

    let array = [1, 2, 3];
//传统写法
    array.forEach(function(v) {
        console.log(v);
    });
//ES6
    array.forEach(v => console.log(v));
//它们同时支持表达式体和语句体。与(普通的)函数所不同的是,箭头函数和其上下文中的代码共享同一个具有词法作用域的this。
    let evens = [1,2,3,4,5];
    let fives = [];
// 表达式体
    let odds = evens.map(v => v + 1);
    let nums = evens.map((v, i) => v + i);
    let pairs = evens.map(v => ({even: v, odd: v + 1}));

// 语句体
    nums.forEach(v => {
        if (v % 5 === 0){
        fives.push(v);
    }
});
    console.log(fives);
  1. 声明一个变量let fn

  2. 声明一个匿名函数。

  3. 将匿名函数作为值赋给变量fn。

含义

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

console.log(...[1, 2, 3])
// 1 2 3

console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5

[...document.querySelectorAll('div')]
// [<div>, <div>, <div>]

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

function push(array, ...items) {
  array.push(...items);
}

function add(x, y) {
  return x + y;
}

var numbers = [4, 38];
add(...numbers) // 42

上面代码中,array.push(…items)和add(…numbers)这两行,都是函数的调用,它们的都使用了扩展运算符。该运算符将一个数组,变为参数序列。

扩展运算符与正常的函数参数可以结合使用,非常灵活。

function f(v, w, x, y, z) { }
var args = [0, 1];
f(-1, ...args, 2, ...[3]);

  具有词法作用域的 this

    let bob = {
        _name: "NICK",
        _friends: ["Amy", "Bob", "Cinne", "Dylan", "Ellen"],
        printFriends() {
            this._friends.forEach(f => console.log(this._name + " knows " + f));
        }
    }
    bob.printFriends();

例子:
  es6

let test = (x, y) => {x++; y--; return x+y};

  相当于es5

function test(x, y) {
    x++;
    y--;
    return x + y;
}

然后再来看看ES6最新的箭头函数

替代数组apply方法

由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了。

// ES5的写法
function f(x, y, z) {
  // ...
}
var args = [0, 1, 2];
f.apply(null, args);

// ES6的写法
function f(x, y, z) {
  // ...
}
var args = [0, 1, 2];
f(...args);

下面是扩展运算符取代apply方法的一个实际的例子,应用Math.max方法,简化求出一个数组最大元素的写法。

// ES5的写法
Math.max.apply(null, [14, 3, 77])

// ES6的写法
Math.max(...[14, 3, 77])

// 等同于
Math.max(14, 3, 77);

上面代码表示,由于JavaScript不提供求数组最大元素的函数,所以只能套用Math.max函数,将数组转为一个参数序列,然后求最大值。有了扩展运算符以后,就可以直接用Math.max了。

另一个例子是通过push函数,将一个数组添加到另一个数组的尾部。

// ES5的写法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1, arr2);

// ES6的写法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
arr1.push(...arr2);

上面代码的ES5写法中,push方法的参数不能是数组,所以只好通过apply方法变通使用push方法。有了扩展运算符,就可以直接将数组传入push方法。

下面是另外一个例子。

// ES5
new (Date.bind.apply(Date, [null, 2015, 1, 1]))
// ES6
new Date(...[2015, 1, 1]);    

  this问题

class Animal {
    constructor(){
        this.type = 'animal'
    }
    says(say){
        setTimeout(function(){
            console.log(this.type + ' says ' + say)
        }, 1000)
    }
}
var animal = new Animal()
animal.says('hi')  //undefined says hi

  运行上面的代码会报错,这是因为setTimeout中的this指向的是全局对象。所以为了让它能够正确的运行,传统的解决方法有两种:
  第一种是将this传给self,再用self来指代this

function says(say) {
    var self = this;
    setTimeout(function () {
        console.log(self.type + ' says ' + say)
    }, 1000)
}
箭头函数只能做赋值,不能当作声明
let fn = (v1,v2) =>{
    console.log('函数体')
    return v1+v2
}

以上就等价于上面的匿名函数
  • 如果一个函数只有一个参数

let fn = v1 => {
    console.log('函数体')
    return v1 
}
fn(11)  //  '函数体'  11

// 还是上面的例子,做个小改动。
let fn = v1 => {
    console.log('函数体')
}
fn(1)  // '函数体' undefined
// 并没有写入return语句,那么js就会默认帮你添加return undefined
  • 如果你的函数体只有一句话,你可以不需要写花括号和return。

 // 示例 let fn1 = (v1,v2) => { return v1+v2 }
// 可以写作如下方式
let fn  = (v1,v2) => v1+v2
// 省略花括号以及return。当你这样写,实际上就默认return v1+v2所以连return 都可以省略掉。但是如果你习惯加花括号,你就必须加上前面的return语句。
  • 最简化的箭头函数

let fn = v1 => v1 * 2

fn(2)  // 4

3.箭头函数

  第二种方法是用bind(this),即

    function says(say){
        setTimeout(function(){
            console.log(this.type + ' says ' + say)
        }.bind(this), 1000)
    }

箭头函数,传统函数,this

this是call 的第一个参数,this是call 的第一个参数,this是call
的第一个参数!!!

如何理解这句话。
看个例子:

var content = {
    el : 'web',
    init : function (){
        console.log(this.el)  
    }
}
// content.init()  等价于
content.init.call(content)  // this就指的是content。

ES3于ES6都支持this。
但是在ES6中,箭头函数的出现,弱化了this的用法。

关于传统函数和箭头函数。

function fn(){
    console.log(this)
}
// 如果直接调用,fn()那么,会直接返回window。
fn.call({name : 'claus'})  // this指针指向了这个对象。 {name : 'claus'}所以打印的结果就是该对象。


let fn1 = () => {
    console.log(this)
}
fn1.call({name : 'claus'})  // window...  并没有接受this参数。

let obj = {
    name : 'claus',
    sayName : (that) => {
        console.log(that.name)
// 如果你直接打印this.name  箭头函数会报错,所以需要如果需要用this 可以传入一个形参来代替this,并且在外面调用的时候将这个this的指针传入即可。
    }
}
obj.sayName(obj)

这就是箭头函数的好处,没有潜规则(隐藏的this是作为函数的第一个参数),之前的function函数参数是会有一个this参数的。只不过讲this参数隐藏了而已。

箭头函数的优点。
亮出代码吧。

let arr = [1,2,3,4,5]

let arr2 = []

for (let i = 0; i < arr.length;i++){
    arr2.push(arr[i] * arr[i]) 
}
console.log(arr2)  // [1, 4, 9, 16, 25]

// 继续简化如下
let arr = [1,2,3,4,5]

let arr2 = arr.map(function (num){
    return num * num
})
console.log(arr2)

// 箭头函数  
let arr = [1,2,3,4,5]
let arr2 = arr.map(num => num * num)
console.log(arr2)  //  [1, 4, 9, 16, 25]
// 并且这种方式是可以叠加的。
let arr2 = arr.map(num => num * num)
              .map(num => num + 1)
              .map(num => num  + num)
  1. 箭头函数让this变的可以理解。

  2. 箭头函数让代码更简洁。

基本用法

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

var f = v => v;

上面的箭头函数等同于:

var f = function(v) {
  return v;
};

如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。

var f = () => 5;
// 等同于
var f = function () { return 5 };

var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
  return num1 + num2;
};

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

var sum = (num1, num2) => { return num1 + num2; }

由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号。

var getTempItem = id => ({ id: id, name: "Temp" });

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

const full = ({ first, last }) => first + ' ' + last;

// 等同于
function full(person) {
  return person.first + ' ' + person.last;
}

箭头函数使得表达更加简洁。

const isEven = n => n % 2 == 0;
const square = n => n * n;

上面代码只用了两行,就定义了两个简单的工具函数。如果不用箭头函数,可能就要占用多行,而且还不如现在这样写醒目。

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

// 正常函数写法
[1,2,3].map(function (x) {
  return x * x;
});

// 箭头函数写法
[1,2,3].map(x => x * x);

另一个例子是

// 正常函数写法
var result = values.sort(function (a, b) {
  return a - b;
});

// 箭头函数写法
var result = values.sort((a, b) => a - b);

下面是rest参数与箭头函数结合的例子。

const numbers = (...nums) => nums;

numbers(1, 2, 3, 4, 5)
// [1,2,3,4,5]

const headAndTail = (head, ...tail) => [head, tail];

headAndTail(1, 2, 3, 4, 5)
// [1,[2,3,4,5]]

  但现在我们有了箭头函数,就不需要这么麻烦了:

class Animal2 {
    constructor(){
        this.type = 'animal'
    }
    says(say){
        setTimeout( () => {console.log(this);
            console.log(this.type + ' says ' + say)
        }, 1000)
    }
}
    var animal2 = new Animal2();
    animal2.says('hi') ; //animal says hi

箭头函数有几个使用注意点。
1.函数体内的this对象,即绑定定义时所在的对象,而不是使用时所在的对象。
  并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,它的this是继承外面的,因此内部的this就是外层代码块的this。
2.不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
3.不可以使用arguments对象,该对象在函数体内不存在。
上面三点中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的。
   此篇终,待续……

使用注意点

箭头函数有几个使用注意点。

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

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

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

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

上面四点中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的。

function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

var id = 21;

foo.call({ id: 42 });
// id: 42

上面代码中,setTimeout的参数是一个箭头函数,这个箭头函数的定义生效是在foo函数生成时,而它的真正执行要等到100毫秒后。如果是普通函数,执行时this应该指向全局对象window,这时应该输出21。但是,箭头函数导致this总是指向函数定义生效时所在的对象(本例是{id:
42}),所以输出的是42。

箭头函数可以让setTimeout里面的this,绑定定义时所在的作用域,而不是指向运行时所在的作用域。下面是另一个例子。

function Timer() {
  this.s1 = 0;
  this.s2 = 0;
  // 箭头函数
  setInterval(() => this.s1++, 1000);
  // 普通函数
  setInterval(function () {
    this.s2++;
  }, 1000);
}

var timer = new Timer();

setTimeout(() => console.log('s1: ', timer.s1), 3100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);
// s1: 3
// s2: 0

上面代码中,Timer函数内部设置了两个定时器,分别使用了箭头函数和普通函数。前者的this绑定定义时所在的作用域(即Timer函数),后者的this指向运行时所在的作用域(即全局对象)。所以,3100毫秒之后,timer.s1被更新了3次,而timer.s2一次都没更新。

箭头函数可以让this指向固定化,这种特性很有利于封装回调函数。下面是一个例子,DOM事件的回调函数封装在一个对象里面。

var handler = {
  id: '123456',

  init: function() {
    document.addEventListener('click',
      event => this.doSomething(event.type), false);
  },

  doSomething: function(type) {
    console.log('Handling ' + type  + ' for ' + this.id);
  }
};

上面代码的init方法中,使用了箭头函数,这导致这个箭头函数里面的this,总是指向handler对象。否则,回调函数运行时,this.doSomething这一行会报错,因为此时this指向document对象。

this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。

所以,箭头函数转成ES5的代码如下。

// ES6
function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

// ES5
function foo() {
  var _this = this;

  setTimeout(function () {
    console.log('id:', _this.id);
  }, 100);
}

上面代码中,转换后的ES5版本清楚地说明了,箭头函数里面根本没有自己的this,而是引用外层的this。

请问下面的代码之中有几个this?

function foo() {
  return () => {
    return () => {
      return () => {
        console.log('id:', this.id);
      };
    };
  };
}

var f = foo.call({id: 1});

var t1 = f.call({id: 2})()(); // id: 1
var t2 = f().call({id: 3})(); // id: 1
var t3 = f()().call({id: 4}); // id: 1

上面代码之中,只有一个this,就是函数foo的this,所以t1、t2、t3都输出同样的结果。因为所有的内层函数都是箭头函数,都没有自己的this,它们的this其实都是最外层foo函数的this。

除了this,以下三个变量在箭头函数之中也是不存在的,指向外层函数的对应变量:arguments、super、new.target。

function foo() {
  setTimeout(() => {
    console.log('args:', arguments);
  }, 100);
}

foo(2, 4, 6, 8)
// args: [2, 4, 6, 8]

上面代码中,箭头函数内部的变量arguments,其实是函数foo的arguments变量。

另外,由于箭头函数没有自己的this,所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向。

(function() {
  return [
    (() => this.x).bind({ x: 'inner' })()
  ];
}).call({ x: 'outer' });
// ['outer']

上面代码中,箭头函数没有自己的this,所以bind方法无效,内部的this指向外部的this。

长期以来,JavaScript语言的this对象一直是一个令人头痛的问题,在对象方法中使用this,必须非常小心。箭头函数”绑定”this,很大程度上解决了这个困扰。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图
Copyright @ 2010-2020 澳门新葡亰官网app 版权所有