前端进阶-ES6函数

版权声明:如有转载请注明出处 http://blog.csdn.net/modurookie ,谢谢 https://blog.csdn.net/MoDuRooKie/article/details/84258289

箭头函数

将函数转换为箭头函数

const upperizedNames = ['Farrin', 'Kagure', 'Asser'].map(function(name) { 
  return name.toUpperCase();
});

将现有的"普通"函数转换为箭头函数只需几步:

  • 删掉关键字 function
  • 删掉圆括号
  • 删掉左右花括号
  • 删掉关键字 return
  • 删掉分号
  • 在参数列表和函数主体之间添加一个箭头(=>
const upperizedNames = ['Farrin', 'Kagure', 'Asser'].map(name => name.toUpperCase());

使用箭头函数

普通函数可以是函数声明函数表达式,但是箭头函数始终是表达式。实际上,它们的全称是“箭头函数表达式

  • 存储在变量中
  • 当做参数传递给函数
  • 存储在对象的属性中
const greet = name => `Hello ${name}!`;
greet('Asser');
//  Hello Asser!

参数列表出现在箭头函数的箭头(即 =>)前面。如果列表中只有一个参数,那么可以像上述示例那样编写代码。但是,如果列表中有两个或多个参数,或者有零个,则需要将参数列表放在圆括号内:

// 空参数列表需要括号
const sayHi = () => console.log('Hello Udacity Student!');
sayHi();
// Hello Udacity Student!

// 多个参数需要括号
const orderIceCream = (flavor, cone) => console.log(`Here's your ${flavor} ice cream in a ${cone} cone.`);
orderIceCream('chocolate', 'waffle');
// Here's your chocolate ice cream in a waffle cone.

简写主体语法

  • 在函数主体周围没有花括号
  • 自动返回表达式
const upperizedNames = ['Farrin', 'Kagure', 'Asser'].map(
  name => name.toUpperCase()
);

常规主体语法

  • 它将函数主体放在花括号内
  • 需要使用 return 语句来返回内容
const upperizedNames = ['Farrin', 'Kagure', 'Asser'].map( name => {
  name = name.toUpperCase();
  return `${name} has ${name.length} characters in their name`;
});

箭头函数很强大!

  • 语法简短多了
  • 更容易编写和阅读的简短单行函数
  • 使用简写主体语法时,自动返回内容

this” 和箭头函数

对于普通函数,this 的值基于函数如何被调用。对于箭头函数,this 的值基于函数周围的上下文。换句话说,箭头函数内的,this 的值与函数外面的 this 的值一样。

普通函数

// 构造函数
function IceCream() {
  this.scoops = 0;
}

// 为 IceCream 添加 addScoop 方法
IceCream.prototype.addScoop = function() {
  setTimeout(function() {
    this.scoops++;
    console.log('scoop added!');
  }, 500);
};

const dessert = new IceCream();
dessert.addScoop(); // scoop added!
console.log(dessert.scoops); // 0
console.log(scoops); // NaN

传递给 setTimeout() 的函数被调用时没用到 newcall()apply(),也没用到上下文对象。意味着函数内的 this 的值是全局对象,不是 dessert 对象。实际上发生的情况是,创建了新的 scoops 变量(默认值为 undefined),然后递增(undefined + 1 结果为 NaN)。

使用闭包

// 构造函数
function IceCream() {
  this.scoops = 0;
}

// 为 IceCream 添加 addScoop 方法
IceCream.prototype.addScoop = function() {
  const cone = this; // 设置 `this` 给 `cone`变量
  setTimeout(function() {
    cone.scoops++; // 引用`cone`变量
    console.log('scoop added!');
  }, 0.5);
};

const dessert = new IceCream();
dessert.addScoop();
console.log(dessert.scoops); // 1

上述代码没有在函数内使用 this,而是将 cone 变量设为 this,然后当函数被调用时查找 cone 变量。这样可行,因为使用了函数外面的 this 值。

将传递给 setTimeout() 的函数替换为箭头函数:

// 构造函数
function IceCream() {
  this.scoops = 0;
}

// 为 IceCream 添加 addScoop 方法
IceCream.prototype.addScoop = function() {
  setTimeout(() => { // 一个箭头函数被传递给setTimeout
    this.scoops++;
    console.log('scoop added!');
  }, 0.5);
};

const dessert = new IceCream();
dessert.addScoop();
console.log(dessert.scoops); // 1

因为箭头函数从周围上下文继承了 this 值,所以这段代码可行!当 addScoop() 被调用时,addScoop() 中的 this 的值指的是 dessert。因为箭头函数被传递给 setTimeout(),它使用周围上下文判断它里面的 this 指的是什么。因为箭头函数外面的 this 指的是 dessert,所以箭头函数里面的 this 的值也将是 dessert

如果我们将 addScoop() 方法改为箭头函数,你认为会发生什么?

// 构造函数
function IceCream() {
    this.scoops = 0;
}

// 为 IceCream 添加 addScoop 方法
IceCream.prototype.addScoop = () => { // addScoop 现在是一个箭头函数
  setTimeout(() => {
    this.scoops++;
    console.log('scoop added!');
  }, 0.5);
};

const dessert = new IceCream();
dessert.addScoop();
console.log(dessert.scoops); // 0

addScoop() 方法外面,this 的值是全局对象。因此如果 addScoop() 是箭头函数,addScoop() 中的 this 的值是全局对象。这样的话,传递给 setTimeout() 的函数中的 this 的值也设为了该全局对象!

默认函数参数

function greet(name = 'Student', greeting = 'Welcome') {
  return `${greeting} ${name}!`;
}

greet(); // Welcome Student!
greet('James'); // Welcome James!
greet('Richard', 'Howdy'); // Howdy Richard!

要创建默认参数,需要添加等号 ( = ) 以及当参数未提供时参数应该设为的默认值。在上述代码中,两个参数的默认值都是字符串,但是可以为任何 JavaScript 数据类型!

默认值和解构

默认值和解构数组

function createGrid([width = 5, height = 5]) {
  return `Generates a ${width} x ${height} grid`;
}

createGrid([]); // Generates a 5 x 5 grid
createGrid([2]); // Generates a 2 x 5 grid
createGrid([2, 3]); // Generates a 2 x 3 grid
createGrid([undefined, 3]); // Generates a 5 x 3 grid

createGrid() 函数预期传入的是数组。它通过解构将数组中的第一项设为 width,第二项设为 height。如果数组为空,或者只有一项,那么就会使用默认参数,并将缺失的参数设为默认值 5

createGrid(); // throws an error
// Uncaught TypeError: Cannot read property 'Symbol(Symbol.iterator)' of undefined

出现错误,因为 createGrid() 预期传入的是数组,然后对其进行解构。因为函数被调用时没有传入数组,所以出现问题。但是,我们可以使用默认的函数参数!

function createGrid([width = 5, height = 5] = []) {
  return `Generating a grid of ${width} by ${height}`;
}
createGrid(); // Generates a 5 x 5 grid

默认值和解构对象

function createSundae({scoops = 1, toppings = ['Hot Fudge']}) {
  const scoopText = scoops === 1 ? 'scoop' : 'scoops';
  return `Your sundae has ${scoops} ${scoopText} with ${toppings.join(' and ')} toppings.`;
}

createSundae({}); // Your sundae has 1 scoop with Hot Fudge toppings.
createSundae({scoops: 2}); // Your sundae has 2 scoops with Hot Fudge toppings.
createSundae({scoops: 2, toppings: ['Sprinkles']}); // Your sundae has 2 scoops with Sprinkles toppings.
createSundae({toppings: ['Cookie Dough']}); // Your sundae has 1 scoop with Cookie Dough toppings.

createSundae(); // throws an error
// Uncaught TypeError: Cannot match against 'undefined' or 'null'.

通过添加空对象作为默认参数,以防未提供参数,现在调用函数时没有任何参数将可行。

function createSundae({scoops = 1, toppings = ['Hot Fudge']} = {}) {
  const scoopText = scoops === 1 ? 'scoop' : 'scoops';
  return `Your sundae has ${scoops} ${scoopText} with ${toppings.join(' and ')} toppings.`;
}
createSundae(); // Your sundae has 1 scoop with Hot Fudge toppings.
function houseDescriptor({houseColor = 'green', shutterColors = ['red']} = {}) {
  return `I have a ${houseColor} house with ${shutterColors.join(' and ')} shutters`;
}

// 下面的可以正常运行
houseDescriptor({});
houseDescriptor();
houseDescriptor({houseColor = 'red'});
houseDescriptor({shutterColors = ['orange', 'blue']});
houseDescriptor({houseColor = 'red', shutterColors = ['orange', 'blue']});

数组默认值与对象默认值

数组默认值相比,对象默认值具备的一个优势是能够处理跳过的选项

function createSundae({scoops = 1, toppings = ['Hot Fudge']} = {}) {}
createSundae({toppings: ['Hot Fudge', 'Sprinkles', 'Caramel']});

使用数组默认值进行解构的同一函数相对比:

function createSundae([scoops = 1, toppings = ['Hot Fudge']] = []) {}
createSundae([undefined, ['Hot Fudge', 'Sprinkles', 'Caramel']]);

因为数组是基于位置的,我们需要传入 undefined 以跳过第一个参数(并使用默认值)来到达第二个参数。

除非你有很充足的理由来使用数组默认值进行数组解构,否则建议使用对象默认值进行对象解构!

类预览

class Dessert {
  constructor(calories = 250) {
    this.calories = calories;
  }
}

class IceCream extends Dessert {
  constructor(flavor, calories, toppings = []) {
    super(calories);
    this.flavor = flavor;
    this.toppings = toppings;
  }
  addTopping(topping) {
    this.toppings.push(topping);
  }
}
  • class
  • extends
  • super()

对 JavaScript 类的错觉

var cookie = new Dessert();

ECMAScript 为我们提供了一些新的关键字,如 classextendssuper 等,但是这并不意味着整个语言的运作机制发生了变化,所以 JS 内核仍然在使用函数和原型继承,我们只是有了一种更清晰易懂的方式来实现相同的功能。语言的基本功能并没有发生改变,所以JS 并不是一门基于类的语言。它使用函数来创建对象,并通过原型(prototype)将它们关联在一起。JS 只是给常规函数和原型披上了一层类的外衣。

JavaScript 类

ES5 “类”总结

function Plane(numEngines) {
  this.numEngines = numEngines;
  this.enginesActive = false;
}

// 由所有实例 "继承" 的方法
Plane.prototype.startEngines = function () {
  console.log('starting engines...');
  this.enginesActive = true;
};

const richardsPlane = new Plane(1);
richardsPlane.startEngines();

const jamesPlane = new Plane(4);
jamesPlane.startEngines();

在上述代码中,Plane 函数是一个构造函数,它将用来创建新的 Plane 对象。具体的 Plane 对象的数据被传递给 Plane 函数,并设置到该对象上。每个 Plane 对象继承的方法被放置在 Plane.prototype 对象上。

需要注意的事项:

  • 构造函数使用 new 关键字被调用
  • 按照惯例,构造函数名以大写字母开头
  • 构造函数控制将被创建的对象的数据的设置
  • 继承”的方法被放在构造函数的原型对象

在 ES6 类都在底层帮你设置了所有这些。

class Plane {
  constructor(numEngines) {
    this.numEngines = numEngines;
    this.enginesActive = false;
  }

  startEngines() {
    console.log('starting engines…');
    this.enginesActive = true;
  }
}

将函数转换为类

在这里插入图片描述

使用 JavaScript 类

类只是一种函数

class Plane {
  constructor(numEngines) {
    this.numEngines = numEngines;
    this.enginesActive = false;
  }

  startEngines() {
    console.log('starting engines…');
    this.enginesActive = true;
  }
}
typeof Plane; // function

没错,它只是个函数!甚至没有向 JavaScript 添加新类型。

静态方法

class Plane {
  constructor(numEngines) {
    this.numEngines = numEngines;
    this.enginesActive = false;
  }

  static badWeather(planes) {
    for (plane of planes) {
      plane.enginesActive = false;
    }
  }

  startEngines() {
    console.log('starting engines…');
    this.enginesActive = true;
  }
}

这样使得 badWeather() 成为 Plane 类中可以直接访问的方法,因此你可以这样调用它:

Plane.badWeather([plane1, plane2, plane3]);

面向对象的 JavaScript

类的优势

  • 设置内容更少
  • 清晰地定义了构造函数
  • 全部都包含起来了

使用类时需要注意的事项

  • class 不是魔术,关键字 class 带来了其它基于类的语言中的很多思想观念。它没有像变魔术一样向 JavaScript 类添加了此功能。
  • class 是原型继承的抽象形式,JavaScript 类实际上使用的就是原型继承
  • 使用类需要用到 new,在创建 JavaScript 类的新实例时,必须使用关键字 new

ES6 中的子类

现在使用新的 superextends 关键字扩展类。

class Tree {
  constructor(size = '10', leaves = {spring: 'green', summer: 'green', fall: 'orange', winter: null}) {
    this.size = size;
    this.leaves = leaves;
    this.leafColor = null;
  }

  changeSeason(season) {
    this.leafColor = this.leaves[season];
    if (season === 'spring') {
      this.size += 1;
    }
  }
}

class Maple extends Tree {
  constructor(syrupQty = 15, size, leaves) {
    super(size, leaves);
    this.syrupQty = syrupQty;
  }

  changeSeason(season) {
    super.changeSeason(season);
    if (season === 'spring') {
      this.syrupQty += 1;
    }
  }

  gatherSyrup() {
    this.syrupQty -= 3;
  }
}

const myMaple = new Maple(15, 5);
myMaple.changeSeason('fall');
myMaple.gatherSyrup();
myMaple.changeSeason('spring');

TreeMaple 都是 JavaScript 类。Maple 类是 Tree 的子类,并使用关键字 extends 将自己设为子类。要让子类可以访问到父类,需要使用关键字 super。注意到 super 有两种使用方式吗?在 Maple 的构造方法中,super 被用作函数。在 MaplechangeSeason() 方法中,super 被用作对象!

super 必须在 this 之前被调用

class Toy {}
class Dragon extends Toy {}
const dragon1 = new Dragon();
dragon1 instanceof Toy; // true

dragon1 变量是 Dragon 类创建的对象,因为 Dragon 类扩展自 Toy 类,所以, dragon1 也是 Toy 的一个实例

猜你喜欢

转载自blog.csdn.net/MoDuRooKie/article/details/84258289