ES6

http://es6.ruanyifeng.com/

https://www.jianshu.com/p/fe2aec6f307d

说出至少5个ES6的新特性,并简述它们的作用。(简答题)

https://www.cnblogs.com/xkloveme/p/7456656.html

https://www.cnblogs.com/Wayou/p/es6_new_features.html

https://www.cnblogs.com/leyan/p/6432661.html

1、let关键字,用于声明只在块级作用域起作用的变量。
2、const关键字,用于声明一个常量。
3、解构赋值,一种新的变量赋值方式。使用解构从数组和对象提取值并赋值给独特的变量    

4、Symbol数据类型,定义一个独一无二的值。
5、箭头函数
6、for...of遍历,可遍历具有iterator 接口的数据结构。可以循环任何可迭代(也就是遵守可迭代协议)类型的数据。

7、Set结构,存储不重复的成员值的集合。
8、Map结构,键名可以是任何类型的键值对集合。
9、Promise对象,更合理、规范地处理异步操作。
10、Class类定义类和更简便地实现类的继承。

11、模块

12、Proxy代理,用于编写处理函数,来拦截目标对象的操作。

13、模板字面量:模板字面量本质上是包含嵌入式表达式的字符串字面量. 

 

对ES6的了解

ECMAScript 6.0 是 JavaScript 语言的下一代标准

新增的特性:

声明变量的方式 let const

变量解构赋值

字符串新增方法 includes() startsWith() endsWith() 等

数组新增方法 Array.from() Array.of() entries() keys() values() 等

对象简洁写法以及新增方法 Object.is() Object.assign() entries() keys() values()等

箭头函数、rest 参数、函数参数默认值等

新的数据结构: Set 和 Map

Proxy

Promise对象

async函数 await命令

Class类

Module 体系 模块的加载和输出方式

 

自己实现一个promise

https://cloud.tencent.com/developer/article/1343514

https://github.com/chemdemo/promiseA/blob/master/lib/Promise.js

 

let、const和var的区别?let的产生背景?

ES6推荐使用let声明局部变量,相比之前的var(无论声明在何处,都会被视为声明在函数的最顶部)
let和var声明的区别:

var x = '全局变量';
{
  let x = '局部变量';
  console.log(x); // 局部变量
}
console.log(x); // 全局变量

let表示声明变量,而const表示声明常量,两者都为块级作用域;const 声明的变量都会被认为是常量,意思就是它的值被设置完成后就不能再修改了:

const a = 1
a = 0 //报错

如果const的是一个对象,对象所包含的值是可以被修改的。抽象一点儿说,就是对象所指向的地址没有变就行:

const student = { name: 'cc' }

student.name = 'yy';// 不报错
student  = { name: 'yy' };// 报错

有几个点需要注意:

  • let 关键词声明的变量不具备变量提升(hoisting)特性
  • let 和 const 声明只在最靠近的一个块中(花括号内)有效
  • 当使用常量 const 声明时,请使用大写变量,如:CAPITAL_CASING
  • const 在声明时必须被赋值

开发背景(WHY)为什么要使用let、const关键词来创建变量或常量

•解决ES5使用var初始化变量时会出现的变量提升问题

•解决使用闭包时出错的问题

•解决使用计数的for循环变量时候会导致泄露为全局变量的问题,

•ES5只有全局作用域和函数作用域(对应var声明的全局变量和局部变量时),没有块级作用域(ES6中{}中的代码块)。同理只能在顶层作用域window中和函数中声明函数,不能在块级作用域中声明函数;

2.模板字符串

在ES6之前,我们往往这么处理模板字符串:
通过“\”和“+”来构建模板

$("body").html("This demonstrates the output of HTML \
content to the page, including student's\
" + name + ", " + seatNumber + ", " + sex + " and so on.");

而对ES6来说

  1. 基本的字符串格式化。将表达式嵌入字符串中进行拼接。用${}来界定;
  2. ES6反引号(``)直接搞定;
$("body").html(`This demonstrates the output of HTML content to the page, 
including student's ${name}, ${seatNumber}, ${sex} and so on.`);

3.箭头函数(Arrow Functions)

ES6 中,箭头函数就是函数的一种简写形式,使用括号包裹参数,跟随一个 =>,紧接着是函数体;

箭头函数最直观的三个特点。

  • 不需要 function 关键字来创建函数
  • 省略 return 关键字
  • 继承当前上下文的 this 关键字
// ES5
var add = function (a, b) {
    return a + b;
};
// 使用箭头函数
var add = (a, b) => a + b;

// ES5
[1,2,3].map((function(x){
    return x + 1;
}).bind(this));
    
// 使用箭头函数
[1,2,3].map(x => x + 1);

细节:当你的函数有且仅有一个参数的时候,是可以省略掉括号的。当你函数返回有且仅有一个表达式的时候可以省略{} 和 return;

4. 函数的参数默认值

在ES6之前,我们往往这样定义参数的默认值:

// ES6之前,当未传入参数时,text = 'default';
function printText(text) {
    text = text || 'default';
    console.log(text);
}

// ES6;
function printText(text = 'default') {
    console.log(text);
}

printText('hello'); // hello
printText();// default

5.Spread / Rest 操作符

Spread / Rest 操作符指的是 ...,具体是 Spread 还是 Rest 需要看上下文语境。

当被用于迭代器中时,它是一个 Spread 操作符:

function foo(x,y,z) {
  console.log(x,y,z);
}
 
let arr = [1,2,3];
foo(...arr); // 1 2 3

当被用于函数传参时,是一个 Rest 操作符:当被用于函数传参时,是一个 Rest 操作符:

function foo(...args) {
  console.log(args);
}
foo( 1, 2, 3, 4, 5); // [1, 2, 3, 4, 5]

9.for...of 和 for...in

for...of 用于遍历一个迭代器,如数组:

let letter = ['a', 'b', 'c'];
letter.size = 3;
for (let letter of letters) {
  console.log(letter);
}
// 结果: a, b, c

for...in 用来遍历对象中的属性:

let stu = ['Sam', '22', '男'];
stu.size = 3;
for (let stu in stus) {
  console.log(stu);
}
// 结果: Sam, 22, 男

10.ES6中的类

ES6 中支持 class 语法,不过,ES6的class不是新的对象继承模型,它只是原型链的语法糖表现形式。

函数中使用 static 关键词定义构造函数的的方法和属性:

class Student {
  constructor() {
    console.log("I'm a student.");
  }
 
  study() {
    console.log('study!');
  }
 
  static read() {
    console.log("Reading Now.");
  }
}
 
console.log(typeof Student); // function
let stu = new Student(); // "I'm a student."
stu.study(); // "study!"
stu.read(); // "Reading Now."

类中的继承和超集:

class Phone {
  constructor() {
    console.log("I'm a phone.");
  }
}
 
class MI extends Phone {
  constructor() {
    super();
    console.log("I'm a phone designed by xiaomi");
  }
}
 
let mi8 = new MI();

extends 允许一个子类继承父类,需要注意的是,子类的constructor 函数中需要执行 super() 函数。
当然,你也可以在子类方法中调用父类的方法,如super.parentMethodName()。
在 这里 阅读更多关于类的介绍。

有几点值得注意的是:

  • 类的声明不会提升(hoisting),如果你要使用某个 Class,那你必须在使用之前定义它,否则会抛出一个 ReferenceError 的错误
  • 在类中定义函数不需要使用 function 关键词

 

const 与 let 变量

使用var带来的麻烦:

function getClothing(isCold) {
  if (isCold) {
    var freezing = 'Grab a jacket!';
  } else {
    var hot = 'It's a shorts kind of day.';
    console.log(freezing);
  }
}

运行getClothing(false)后输出的是undefined,这是因为执行function函数之前,所有变量都会被提升, 提升到函数作用域顶部.

letconst声明的变量解决了这种问题,因为他们是块级作用域, 在代码块(用{}表示)中使用letconst声明变量, 该变量会陷入暂时性死区直到该变量的声明被处理.

function getClothing(isCold) {
  if (isCold) {
    const freezing = 'Grab a jacket!';
  } else {
    const hot = 'It's a shorts kind of day.';
    console.log(freezing);
  }
}

运行getClothing(false)后输出的是ReferenceError: freezing is not defined,因为 freezing 没有在 else 语句、函数作用域或全局作用域内声明,所以抛出 ReferenceError

关于使用letconst规则:

  • 使用let声明的变量可以重新赋值,但是不能在同一作用域内重新声明
  • 使用const声明的变量必须赋值初始化,但是不能在同一作用域类重新声明也无法重新赋值.

模板字面量

在ES6之前,将字符串连接到一起的方法是+或者concat()方法,如

const student = {
  name: 'Richard Kalehoff',
  guardian: 'Mr. Kalehoff'
};

const teacher = {
  name: 'Mrs. Wilson',
  room: 'N231'
}

let message = student.name + ' please see ' + teacher.name + ' in ' + teacher.room + ' to pick up your report card.';

模板字面量本质上是包含嵌入式表达式的字符串字面量.
模板字面量用倒引号 ( `` )(而不是单引号 ( '' ) 或双引号( "" ))表示,可以包含用 ${expression} 表示的占位符

let message = `${student.name} please see ${teacher.name} in ${teacher.room} to pick up your report card.`;

解构

在ES6中,可以使用解构从数组和对象提取值并赋值给独特的变量

解构数组的值:

const point = [10, 25, -34];
const [x, y, z] = point;
console.log(x, y, z);

Prints: 10 25 -34

[]表示被解构的数组, x,y,z表示要将数组中的值存储在其中的变量, 在解构数组是, 还可以忽略值, 例如const[x,,z]=point,忽略y坐标.

解构对象中的值:

const gemstone = {
  type: 'quartz',
  color: 'rose',
  karat: 21.29
};
const {type, color, karat} = gemstone;
console.log(type, color, karat);

花括号 { } 表示被解构的对象,typecolorkarat 表示要将对象中的属性存储到其中的变量

对象字面量简写法

let type = 'quartz';
let color = 'rose';
let carat = 21.29;

const gemstone = {
  type: type,
  color: color,
  carat: carat
};

console.log(gemstone);

使用和所分配的变量名称相同的名称初始化对象时如果属性名称和所分配的变量名称一样,那么就可以从对象属性中删掉这些重复的变量名称。

let type = 'quartz';
let color = 'rose';
let carat = 21.29;
const gemstone = {type,color,carat};
console.log(gemstone);

简写方法的名称:

const gemstone = {
  type,
  color,
  carat,
  calculateWorth: function() {
    // 将根据类型(type),颜色(color)和克拉(carat)计算宝石(gemstone)的价值
  }
};

匿名函数被分配给属性 calculateWorth,但是真的需要 function 关键字吗?在 ES6 中不需要!

let gemstone = {
  type,
  color,
  carat,
  calculateWorth() { ... }
};

for...of循环

for...of循环是最新添加到 JavaScript 循环系列中的循环。
它结合了其兄弟循环形式 for 循环和 for...in 循环的优势,可以循环任何可迭代(也就是遵守可迭代协议)类型的数据。默认情况下,包含以下数据类型:StringArrayMapSet,注意不包含 Object 数据类型(即 {})。默认情况下,对象不可迭代

for循环

const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
for (let i = 0; i < digits.length; i++) {
  console.log(digits[i]);
}

for 循环的最大缺点是需要跟踪计数器和退出条件。
虽然 for 循环在循环数组时的确具有优势,但是某些数据结构不是数组,因此并非始终适合使用 loop 循环。

for...in循环

const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

for (const index in digits) {
  console.log(digits[index]);
}

依然需要使用 index 来访问数组的值
当你需要向数组中添加额外的方法(或另一个对象)时,for...in 循环会带来很大的麻烦。因为 for...in 循环循环访问所有可枚举的属性,意味着如果向数组的原型中添加任何其他属性,这些属性也会出现在循环中。

Array.prototype.decimalfy = function() {
  for (let i = 0; i < this.length; i++) {
    this[i] = this[i].toFixed(2);
  }
};

const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

for (const index in digits) {
  console.log(digits[index]);
}

forEach 循环 是另一种形式的 JavaScript 循环。但是,forEach() 实际上是数组方法,因此只能用在数组中。也无法停止或退出 forEach 循环。如果希望你的循环中出现这种行为,则需要使用基本的 for 循环。

for...of循环
for...of 循环用于循环访问任何可迭代的数据类型。
for...of 循环的编写方式和 for...in 循环的基本一样,只是将 in 替换为 of,可以忽略索引。

const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

for (const digit of digits) {
  console.log(digit);
}

建议使用复数对象名称来表示多个值的集合。这样,循环该集合时,可以使用名称的单数版本来表示集合中的单个值。例如,for (const button of buttons) {…}

for...of 循环还具有其他优势,解决了 for 和 for...in 循环的不足之处。你可以随时停止或退出 for...of 循环。

for (const digit of digits) {
  if (digit % 2 === 0) {
    continue;
  }
  console.log(digit);
}

不用担心向对象中添加新的属性。for...of 循环将只循环访问对象中的值。

Array.prototype.decimalfy = function() {
  for (i = 0; i < this.length; i++) {
    this[i] = this[i].toFixed(2);
  }
};

const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

for (const digit of digits) {
  console.log(digit);
}

展开运算符

展开运算符(用三个连续的点 (...) 表示)是 ES6 中的新概念,使你能够将字面量对象展开为多个元素

const books = ["Don Quixote", "The Hobbit", "Alice in Wonderland", "Tale of Two Cities"];
console.log(...books);

Prints: Don Quixote The Hobbit Alice in Wonderland Tale of Two Cities

展开运算符的一个用途是结合数组。

如果你需要结合多个数组,在有展开运算符之前,必须使用 Arrayconcat() 方法。

const fruits = ["apples", "bananas", "pears"];
const vegetables = ["corn", "potatoes", "carrots"];
const produce = fruits.concat(vegetables);
console.log(produce);

Prints: ["apples", "bananas", "pears", "corn", "potatoes", "carrots"]

使用展开符来结合数组

const fruits = ["apples", "bananas", "pears"];
const vegetables = ["corn", "potatoes", "carrots"];
const produce = [...fruits,...vegetables];
console.log(produce);

剩余参数(可变参数)

使用展开运算符将数组展开为多个元素, 使用剩余参数可以将多个元素绑定到一个数组中.
剩余参数也用三个连续的点 ( ... ) 表示,使你能够将不定数量的元素表示为数组.

用途1: 将变量赋数组值时:

const order = [20.17, 18.67, 1.50, "cheese", "eggs", "milk", "bread"];
const [total, subtotal, tax, ...items] = order;
console.log(total, subtotal, tax, items);

用途2: 可变参数函数
对于参数不固定的函数,ES6之前是使用参数对象(arguments)处理:

function sum() {
  let total = 0;  
  for(const argument of arguments) {
    total += argument;
  }
  return total;
}

在ES6中使用剩余参数运算符则更为简洁,可读性提高:

function sum(...nums) {
  let total = 0;  
  for(const num of nums) {
    total += num;
  }
  return total;
}

ES6箭头函数

ES6之前,使用普通函数把其中每个名字转换为大写形式:

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

箭头函数表示:

const upperizedNames = ['Farrin', 'Kagure', 'Asser'].map(
  name => name.toUpperCase()
);

普通函数可以是函数声明或者函数表达式, 但是箭头函数始终都是表达式, 全程是箭头函数表达式, 因此因此仅在表达式有效时才能使用,包括:

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

可以如下调用:

greet('Asser');

如果函数的参数只有一个,不需要使用()包起来,但是只有一个或者多个, 则必须需要将参数列表放在圆括号内:

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

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

一般箭头函数都只有一个表达式作为函数主题:

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`;
});

javascript标准函数this

  1. new 对象
const mySundae = new Sundae('Chocolate', ['Sprinkles', 'Hot Fudge']);

sundae这个构造函数内的this的值是实例对象, 因为他使用new被调用.

  1. 指定的对象
const result = obj1.printName.call(obj2);

函数使用call/apply被调用,this的值指向指定的obj2,因为call()第一个参数明确设置this的指向

  1. 上下`文对象
data.teleport();

函数是对象的方法, this指向就是那个对象,此处this就是指向data.

  1. 全局对象或 undefined
teleport();

此处是this指向全局对象,在严格模式下,指向undefined.

javascript中this是很复杂的概念, 要详细判断this,请参考this豁然开朗

箭头函数和this

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

function IceCream() {
    this.scoops = 0;
}

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


----------

标题

const dessert = new IceCream();
dessert.addScoop();

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

解决此问题的方式之一是使用闭包(closure):

// 构造函数
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!'); 
    console.log(dessert.scoops);//1
  }, 0.5);
};

const dessert = new IceCream();
dessert.addScoop();

箭头函数的作用正是如此, 将setTimeOut()的函数改为剪头函数:

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

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

const dessert = new IceCream();
dessert.addScoop();

默认参数函数

function greet(name, greeting) {
  name = (typeof name !== 'undefined') ?  name : 'Student';
  greeting = (typeof greeting !== 'undefined') ?  greeting : 'Welcome';

  return `${greeting} ${name}!`;
}

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

greet() 函数中混乱的前两行的作用是什么?它们的作用是当所需的参数未提供时,为函数提供默认的值。但是看起来很麻烦, ES6引入一种新的方式创建默认值, 他叫默认函数参数:

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

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

默认值与解构

  1. 默认值与解构数组
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

Returns: Generates a 5 x 5 grid

  1. 默认值与解构函数

就像使用数组默认值解构数组一样,函数可以让对象成为一个默认参数,并使用对象解构:

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(); // Your sundae has 1 scoop with Hot Fudge toppings.
  1. 数组默认值与对象默认值

默认函数参数只是个简单的添加内容,但是却带来很多便利!与数组默认值相比,对象默认值具备的一个优势是能够处理跳过的选项。看看下面的代码:

function createSundae({scoops = 1, toppings = ['Hot Fudge']} = {}) { … }

createSundae() 函数使用对象默认值进行解构时,如果你想使用 scoops 的默认值,但是更改 toppings,那么只需使用 toppings 传入一个对象:

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

将上述示例与使用数组默认值进行解构的同一函数相对比。

function createSundae([scoops = 1, toppings = ['Hot Fudge']] = []) { … }

对于这个函数,如果想使用 scoops 的默认数量,但是更改 toppings,则必须以这种奇怪的方式调用你的函数:

createSundae([undefined, ['Hot Fudge', 'Sprinkles', 'Caramel']]);

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

Javascript类

ES5创建类:

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

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

ES6类只是一个语法糖,原型继续实际上在底层隐藏起来, 与传统类机制语言有些区别.

class Plane {
  //constructor方法虽然在类中,但不是原型上的方法,只是用来生成实例的.
  constructor(numEngines) {
    this.numEngines = numEngines;
    this.enginesActive = false;
  }
  //原型上的方法, 由所有实例对象共享.
  startEngines() {
    console.log('starting engines…');
    this.enginesActive = true;
  }
}

console.log(typeof Plane); //function

javascript中类其实只是function, 方法之间不能使用,,不用逗号区分属性和方法.

静态方法
要添加静态方法,请在方法名称前面加上关键字 static

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;
  }
}
  • 关键字class带来其他基于类的语言的很多思想,但是没有向javascript中添加此功能
  • javascript类实际上还是原型继承
  • 创建javascript类的新实例时必须使用new关键字

super 和 extends

使用新的super和extends关键字扩展类:

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); //super用作函数
    this.syrupQty = syrupQty;
  }

  changeSeason(season) {
    super.changeSeason(season);//super用作对象
    if (season === 'spring') {
      this.syrupQty += 1;
    }
  }

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

使用ES5编写同样功能的类:

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

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

function Maple (syrupQty, size, leaves) {
  Tree.call(this, size, leaves);
  this.syrupQty = syrupQty || 15;
}

Maple.prototype = Object.create(Tree.prototype);
Maple.prototype.constructor = Maple;

Maple.prototype.changeSeason = function(season) {
  Tree.prototype.changeSeason.call(this, season);
  if (season === 'spring') {
    this.syrupQty += 1;
  }
}

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

super 必须在 this 之前被调用

在子类构造函数中,在使用 this 之前,必须先调用超级类。

class Apple {}
class GrannySmith extends Apple {
  constructor(tartnessLevel, energy) {
    this.tartnessLevel = tartnessLevel; // 在 'super' 之前会抛出一个错误!
    super(energy); 
  }
}

 

async 函数以及 awit 命令

 

export 与 export default有什么区别

export 与 export default 均可用于导出常量、函数、文件、模块等

在一个文件或模块中,export、import 可以有多个,export default 仅有一个

通过 export 方式导出,在导入时要加 { },export default 则不需要

使用 export default命令,为模块指定默认输出,这样就不需要知道所要加载模块的变量名; export加载的时候需要知道加载模块的变量名

export default 命令的本质是将后面的值,赋给 default 变量,所以可以直接将一个值写在 export default 之后

 

箭头函数和普通函数有什么区别?

https://www.cnblogs.com/biubiuxixiya/p/8610594.html

https://www.jianshu.com/p/e5fe25edd78a

普通函数的this指向调用它的那个对象

箭头函数的this永远指向其上下文的  this ,任何方法都改变不了其指向,如 call() ,  bind() ,  apply()(或者说箭头函数中的this指向的是定义该函数时所在的对象)

箭头函数通过 call()  或   apply() 方法调用一个函数时,只传入了一个参数,对 this 并没有影响。

箭头函数是匿名函数,不能作为构造函数,不能使用new

箭头函数没有原型属性

箭头函数不绑定arguments,取而代之用rest参数...解决

箭头函数不能当做Generator函数,不能使用yield关键字

箭头函数

(1)特点

不需要function关键字来创建函数

省略return关键字

继承当前上下文的 this 关键字

当箭头函数有且仅有一个参数的时候,可以省略掉括号

(2)注意事项

在使用=>定义函数的时候,this的指向是定义时所在的对象,而不是使用时所在的对象

不能够用作构造函数,这就是说,不能够使用new命令,否则就会抛出一个错误

不能够使用arguments对象

不能使用yield命令

 

使用解构赋值,实现两个变量的值的交换(编程题)。

let a = 1;let b = 2;

[a,b] = [b,a];

 

使用解构赋值,完成函数的参数默认值(编程题)。

function demo({name="王德发"}){     console.log(name);

}

 

利用数组推导,计算出数组 [1,2,3,4] 每一个元素的平方并组成新的数组。(编程题)

var arr1 = [1, 2, 3, 4];var arr2 = [for (i of arr1) i * i];console.log(arr2);

 

使用模板字符串改写下面的代码。(ES5 to ES6改写题)

let iam  = "我是";let name = "王德发";let str  = "大家好,"+iam+name+",多指教。";

改:

let iam  = `我是`;let name = `王德发`;let str  = `大家好,${iam+name},多指教。`;

 

用对象的简洁表示法改写下面的代码。(ES5 to ES6改写题)

let name = "王德发";

let obj = {

"name":name,

"say":function(){

              alert('hello world');

    }

};

改:

let name = "王德发";

let obj = {

name,

 say(){

        alert('hello world');

    }

};

 

用箭头函数的形式改写下面的代码。(ES5 to ES6改写题)

arr.forEach(function (v,i) {

console.log(i);

console.log(v);

});

改:

arr.forEach((v,i) => {

console.log(i);

console.log(v);

});

 

设计一个对象,键名的类型至少包含一个symbol类型,并且实现遍历所有key。(编程题)let name = Symbol('name');

let product = {

[name]:"洗衣机",    

"price":799

};

Reflect.ownKeys(product);

 

有一本书的属性为:{“name”:“《ES6基础系列》”, ”price”:56 };要求使用Proxy对象对其进行拦截处理,name属性对外为“《ES6入门到懵逼》”,price属性为只读。(练习题)

let book  = {

"name":"《ES6基础系列》",

"price":56 

};

let proxy = new Proxy(book,{

get:function(target,property){

if(property === "name"){

return "《入门到懵逼》";

                     }else{            

return target[property];

              }

      },

set:function(target,property,value){

if(property === 'price'){

                     target[property] = 56;

                     }

       }

});

 

阅读下面的代码,并用for...of改成它。(ES5 to ES6改写题)

let arr = [11,22,33,44,55];

let sum = 0;

for(let i=0;i<arr.length;i++){

    sum += arr[i];

}

改:

let arr = [11,22,33,44,55];

let sum = 0;

for(value of arr){

    sum += value;

}

 

关于Set结构,阅读下面的代码,回答问题。(代码阅读题)。

let s = new Set();

s.add([1]);

s.add([1]);

console.log(s.size);

问:打印出来的size的值是多少?
2。如果回答为1的,多必是记得Set结构是不会存储相同的值。其实在这个案例中,两个数组[1]并不是同一个值,它们分别定义的数组,在内存中分别对应着不同的存储地址,因此并不是相同的值。所以都能存储到Set结构中,size为2。

 

关于Map结构,阅读下面的代码,回答问题。(代码阅读题)

let map = new Map();

map.set([1],"ES6系列");

let con = map.get([1]);

console.log(con);

问:打印出来的变量con的值是多少,为什么?
答:undefined。因为set的时候用的数组[1]和get的时候用的数组[1]是分别两个不同的数组,只不过它们元素都是1。它们是分别定义的两个数组,并不是同一个值。新手避免在这里犯错。如果想达到预期的效果,你要保证get的时候和set的时候用同一个数组。比如:

let map = new Map();

let arr = [1];

map.set(arr,"ES6系列");

let con = map.get(arr);

console.log(con); //ES6系列

 

定义一个类Animal,通过传参初始化它的类型,如:“猫科类”。它有一个实例方法:run,run函数体内容可自行定义。

class Animal {    

  constructor(type){        

    this.type = type;

  }

  run(){

    alert('I can run');

       }

}

 

基于Animal类,定义一个子类Cat并继承Animal类。初始化Cat类的昵称name和年龄age。并拥有实例方法eat,eat函数体内容可自行定义。

class Cat extends Animal{    

  constructor(type,name,age){        

    super(type);       

    this.name = name;        

    this.age = age;

    }

   eat(){

        alert('I am eating');

    }

}

 

promise的优点

https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/0014345008539155e93fc16046d4bb7854943814c4f9dc2000

promise的理解与使用 
     promise对象有两个特点:(1)对象的状态不受外界外界影响;(2)一旦状态改变,就不会再改变,任何时候都可以得到这个结果。

promise对象代表一个异步操作,有三种状态;pending(进行中),fulfilled(已成功)和rejected(已失败)

只有异步操作的结果,可以决定当前是哪一种状态promise的状态改变,只有两种可能:从pending变为fulfilled和从pending变成rejected.只要这两种状态情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为resolved(已定型).

基本用法 

                         new Promise( function(resolve, reject) { ... } ) 
    promise接受一个函数作为参数,该函数的两个参数分别是resolve和reject.它们是两个函数,由JavaScript引擎提供,不用自己部署.
then方法 。Promise实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的,它的作用是是为Promise实例添加状态改变时的回调函数.then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数.并且then方法返回的是一个新的Promise实例。

                                        

模板字符串

在以往的时候我们在连接字符串和变量的时候需要使用+来实现字符串的拼接,但是有了模版语言后我们可以使用`string${varible}string`这种进行连接。这里需要注意的两个知识点:

      ${字符串变量} 和 反引号

  

module体系
      历史上js是没有module体系,无法将一个大程序拆分成一些小的程序。ES6中module体系通过import和export实现。

 

Class的用法

Class语法相对原型、构造函数、继承更接近传统语法,它的写法能够让对象原型的写法更加清晰、面向对象编程的语法更加通俗。contructor内部定义的方法和属性是实例对象自己的,不能通过extends 进行继承。在ES6中,子类的构造函数必须含有super()函数,super表示的是调用父类的构造函数,虽然是父类的构造函数,但是this指向的却是Cat。

 

Set数据结构

Set本身是一个构造函数,类似于数组,符合数学上集合的概念,即元素没有重复。可以使用Set方便的实现数组去重复。

  

es6的...扩展运算符是浅拷贝还是深拷贝,...是怎么使用的?

...是深拷贝,

demo:

var arr = [1,2,3,4,5] var [ ...arr2 ] = arr

此题可以跟面试官聊以下深拷贝和浅拷贝的区别

 

es6如何转为es5-babel

1、直接安装Babel法:

1.1) 首先全局安装Babel。

1
2
3
4
5
$ npm install -g babel-cli
 
//也可以通过直接将Babel安装到项目中,在项目根目录下执行下面命令,同时它会自动在package.json文件中的devDependencies中加入babel-cli
//在执行安装到项目中命令之前,要先在项目根目录下新建一个package.json文件。
$ npm install -g babel-cli --save-dev

如果将babel直接安装到项目中,它会自动在package.json文件中的devDependencies中加入babel-cli。如下所示:

1
2
3
4
5
6
//......
{
  "devDependencies": {
    "babel-cli": "^6.22.2"
  }
}

1.2) Babel的配置文件是.babelrc,存放在项目的根目录下。使用Babel的第一步,就是配置这个文件。

这个文件的完整文件名是 “.babelrc”,注意最前面是有个“.”的。由于我的电脑是Windows系统,所以在新建这个文件的时候老是提示 “必须键入文件名” 的错误。后来谷歌了下,发现创建这个文件的时候,把文件名改成“.babelrc.”,注意是前后都有一个点,这样就可以保存成功了

1
2
3
4
{
  "presets": [],
  "plugins": []
}

1.3) presets字段设定转码规则,官方提供以下的规则集,你可以根据需要安装。

点击此处到Babel中文官网presets配置页面:Babel Plugins

1
2
3
4
5
6
7
8
9
10
11
# ES2015转码规则
$ npm install --save-dev babel-preset-es2015
 
# react转码规则
$ npm install --save-dev babel-preset-react
 
# ES7不同阶段语法提案的转码规则(共有4个阶段),选装一个
$ npm install --save-dev babel-preset-stage-0
$ npm install --save-dev babel-preset-stage-1
$ npm install --save-dev babel-preset-stage-2
$ npm install --save-dev babel-preset-stage-3

1.4) 根据官网的提示,当我们用npm安装好这些插件工具之后,我们需要将这些规则加入到.babelrc中去。如下所示:

1
2
3
4
5
6
7
8
{
    "presets": [
      "es2015",
      "react",
      "stage-2"
    ],
    "plugins": []
  }

1.5) 转码、转码的规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 转码结果输出到标准输出
$ babel test.js
 
# 转码结果写入一个文件
# --out-file 或 -o 参数指定输出文件
$ babel a.js --out-file b.js
# 或者
$ babel a.js -o b.js
 
# 整个目录转码
# --out-dir 或 -d 参数指定输出目录
$ babel src --out-dir lib
# 或者
$ babel src -d lib
 
# -s 参数生成source map文件
$ babel src -d lib -s

 

自己实现一个promise
function Promise(executor){ //executor执行器
    let self = this;
    self.status = 'pending'; //等待态
    self.value  = undefined; // 表示当前成功的值
    self.reason = undefined; // 表示是失败的值
    function resolve(value){ // 成功的方法
        if(self.status === 'pending'){
            self.status = 'resolved';
            self.value = value;
        }
    }
    function reject(reason){ //失败的方法
        if(self.status === 'pending'){
            self.status = 'rejected';
            self.reason = reason;
        }
    }
    executor(resolve,reject);
}

Promise.prototype.then = function(onFufiled,onRejected){
    let self = this;
    if(self.status === 'resolved'){
        onFufiled(self.value);
    }
    if(self.status === 'rejected'){
        onRejected(self.reason);
    }
}
module.exports = Promise;
posted @ 2019-01-21 11:20  一粒一世界  阅读(502)  评论(0编辑  收藏  举报