ES6基础(面试)

ES6 尚硅谷教学视频

最常用的ES6特性

let, const, class, extends, super, arrow functions, template string, destructuring, default, rest arguments,promise ,set (ES7:async/await,includes)
这些是ES6最常用的几个语法,基本上学会它们,我们就可以走遍天下都不怕啦!我会用最通俗易懂的语言和例子来讲解它们,保证一看就懂,一学就会。

let, const

这两个的用途与var类似,都是用来声明变量的,但是它们减少了var在作用域以及赋值方面的缺陷

①var在作用域方面的缺陷

var name = 'zach'

while (true) {
    var name = 'obama'
    console.log(name)  //obama
    break
}

console.log(name)  //obama

使用var两次输出都是obama,这是因为ES5只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。第一种场景就是你现在看到的内层变量覆盖外层变量。而let则实际上为JavaScript新增了块级作用域。用它所声明的变量,只在let命令所在的代码块内有效。

let name = 'zach'

while (true) {
    let name = 'obama'
    console.log(name)  //obama
    break
}

console.log(name)  //zach

另外一个var带来的不合理场景就是用来计数的循环变量泄露为全局变量,看下面的例子:

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 10

上面代码中,变量i是var声明的,在全局范围内都有效。所以每一次循环,新的i值都会覆盖旧值,导致最后输出的是最后一轮的i的值。而使用let则不会出现这个问题。

var a = [];
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 6

上面代码中,变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。你可能会问,如果每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。

另外,for循环还有一个特别之处,就是循环语句部分是一个父作用域,而循环体内部是一个单独的子作用域。

for (let i = 0; i < 3; i++) {
  let i = 'abc';
  console.log(i);
}
// abc
// abc
// abc

上面代码输出了3次abc,这表明函数内部的变量i和外部的变量i是分离的。

②var在赋值方面的缺陷

从上面的例子我们可以看出,var的赋值是可以很轻易地被改变的

var name = 'zach'

while (true) {
    var name = 'obama'
    console.log(name)  //obama
    break
}

console.log(name)  //obama

这就带来了一个问题,一些程序中比较重要的变量很容易就会被他人,特别是你毫不知情的队友改变了,这就给我们大型程序的开发带来了麻烦。

当然上文仅仅是我设想的一个场景,我们的程序中更多的时候是需要一些约定俗成,值不变的常量,比如说pi。

在ES6中,我们添加了const特性来声明常量。一旦声明,常量的值就不能改变。

const PI = Math.PI

PI = 23 //Module build failed: SyntaxError: /es6/app.js: "PI" is read-only

当我们尝试去改变用const声明的常量时,浏览器就会报错。
const有一个很好的应用场景,就是当我们引用第三方库的时声明的变量,用const来声明可以避免未来不小心重命名而导致出现bug:

const monent = require('moment')

不存在变量提升(即在创建变量前为为暂时性死区)

var命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined。这种现象多多少少是有些奇怪的,按照一般的逻辑,变量应该在声明语句之后才可以使用。

为了纠正这种现象,let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。

// var 的情况
console.log(foo); // 输出undefined
var foo = 2;

// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;

上面代码中,变量foovar命令声明,会发生变量提升,即脚本开始运行时,变量foo已经存在了,但是没有值,所以会输出undefined。变量barlet命令声明,不会发生变量提升。这表示在声明它之前,变量bar是不存在的,这时如果用到它,就会抛出一个错误。

不允许重复声明

let不允许在相同作用域内,重复声明同一个变量。

// 报错
function func() {
  let a = 10;
  var a = 1;
}

// 报错
function func() {
  let a = 10;
  let a = 1;
}

因此,不能在函数内部重新声明参数。

function func(arg) {
  let arg;
}
func() // 报错

function func(arg) {
  {
    let arg;
  }
}
func() // 不报错

ES6 的块级作用域

let实际上为 JavaScript 新增了块级作用域。

function f1() {
  let n = 5;
  if (true) {
    let n = 10;
  }
  console.log(n); // 5
}

上面的函数有两个代码块,都声明了变量n,运行后输出 5。这表示外层代码块不受内层代码块的影响。如果两次都使用var定义变量n,最后输出的值才是 10。

ES6 允许块级作用域的任意嵌套。

{{{{
  {let insane = 'Hello World'}
  console.log(insane); // 报错
}}}};

上面代码使用了一个五层的块级作用域,每一层都是一个单独的作用域。第四层作用域无法读取第五层作用域的内部变量。

内层作用域可以定义外层作用域的同名变量。

{{{{
  let insane = 'Hello World';
  {let insane = 'Hello World'}
}}}};

块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不再必要了。

// IIFE 写法
(function () {
  var tmp = ...;
  ...
}());

// 块级作用域写法
{
  let tmp = ...;
  ...
}

需要注意的地方。ES6 的块级作用域必须有大括号,如果没有大括号,JavaScript 引擎就认为不存在块级作用域。 

class, extends, super

这三个特性涉及了ES5中最令人头疼的的几个部分:原型、构造函数,继承...你还在为它们复杂难懂的语法而烦恼吗?你还在为指针到底指向哪里而纠结万分吗?

有了ES6我们不再烦恼!

ES6提供了更接近传统语言的写法,引入了Class(类)这个概念。新的class写法让对象原型的写法更加清晰、更像面向对象编程的语法,也更加通俗易懂。

在ES5的年代,js是没有特定的class这个概念的,所以我们之前一般通过构造函数定义并生成新对象,如下图:

function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};

var p = new Point(1, 2);

但是上面这种写法跟传统的面向对象语言(比如C++和Java)差异很大,很容易让刚接触的程序员感到困惑。

ES6提供了更接近传统的面向对象语言的写法,引入了Class(类)这个概念。

在ES6中,通过class关键字,我们可以定义类。

但其实ES6的class仅仅是个名字/写法而已(语法糖),因为它的绝大部分功能,ES5都可以做到。

不过新的class写法让对象原型的写法更加清晰、更像面向对象编程的语法,这样就可以更接近企业级的开发语言。

上面的代码用ES6的“类”改写,就是下面这样。

//定义类
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

上面代码首先用class定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。简单地说,constructor内定义的方法和属性是实例对象自己的,而constructor外定义的方法和属性则是所有实力对象可以共享的。

Point类除了构造方法,还定义了一个toString方法。注意,定义“类”的方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。

ES6的类,完全可以看作构造函数的另一种写法。

class Point {
  // ...
}

typeof Point // "function"
Point === Point.prototype.constructor // true

Class之间可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。下面定义了一个Cat类,该类通过extends关键字,继承了Animal类的所有属性和方法。

class Animal {
    constructor(){
        this.type = 'animal'
    }
    says(say){
        console.log(this.type + ' says ' + say)
    }
}

let animal = new Animal()
animal.says('hello') //animal says hello

class Cat extends Animal {
    constructor(){
        super()
        this.type = 'cat'
    }
}

let cat = new Cat()
cat.says('hello') //cat says hello

super关键字,它指代父类的实例(即父类的this对象)。子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。

ES6的继承机制,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。

例子:

     

               

arrow function(箭头函数)

这个恐怕是ES6最最常用的一个新特性了,用它来写function比原来的写法要简洁清晰很多:

function(i){ return i + 1; } //ES5
(i) => i + 1 //ES6

如果函数比较复杂,则需要用{}把代码包起来:

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

除了看上去更简洁以外,arrow function还有一项超级无敌的功能!
长期以来,JavaScript语言的this对象一直是一个令人头痛的问题,在对象方法中使用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传给_this,再用_this来指代this
 says(say){
     var _this= this;
     setTimeout(function(){
         console.log(_this.type + ' says ' + say)
     }, 1000)
}
  • 第二种方法是用bind(this),即
says(say){
    setTimeout(function(){
    console.log(self.type + ' says ' + say)
}.bind(this), 1000)

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

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

当我们使用箭头函数时,函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,它的this是继承外面的,因此内部的this就是外层代码块的this。

但是在这里我们要说的是,arrow function也并不是一劳永逸的,特别是在你并不知道this指向哪的时候,你需要慎用arrow function。

因为arrow function是 Lexical scoping(词法作用域),this指向定义Arrow Function时外围, 而不是运行时的对象。

词法作用域: 变量的作用域是在定义时决定而不是执行时决定,也就是说词法作用域取决于源码,通过静态分析就能确定,因此词法作用域也叫做静态作用域。

下面我们将根据文章:Do ES6 Arrow Functions Really Solve “this” In JavaScript? 来分析。

如下代码:

// run this in node v4 to see the "expected" behavior

this.test = "attached to the module";

var foo = {
  test: "attached to an object"
};

// a method to create methods
foo.method = function(name, cb){
  this[name] = cb;
};

// use an arrow function and get
// lexical analysis of "this"
foo.method("bar", () => {
  // not what you expected, maybe?
  console.log(this.test); 
});

foo.bar();

输出为:“attached to the module”而不是“attached to an object”

因为就像我们所说的,arrow function是 Lexical scoping,因为在foo.method("bar", () => {的外围并没有包含着它的代码,所以,foo.bar()中的this.test值将永远是“attached to the module”。

使用箭头函数注意:

  • 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
  • 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 Rest 参数代替。

template string

这个东西也是非常有用,当我们要插入大段的html内容到文档中时,传统的写法非常麻烦,所以之前我们通常会引用一些模板工具库,比如mustache等等。

大家可以先看下面一段代码:

$("#result").append(
  "There are <b>" + basket.count + "</b> " +
  "items in your basket, " +
  "<em>" + basket.onSale +
  "</em> are on sale!"
);

我们要用一堆的'+'号来连接文本与变量,而使用ES6的新特性模板字符串``后,我们可以直接这么来写:

$("#result").append(`
  There are <b>${basket.count}</b> items
   in your basket, <em>${basket.onSale}</em>
  are on sale!
`);

用反引号(\来标识起始,用`来标识开始和结尾,用${}来引用变量,而且所有的空格和缩进都会被保留在输出之中。

destructuring(解构)

ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

看下面的例子:

let cat = 'ken'
let dog = 'lili'
let zoo = {cat: cat, dog: dog}
console.log(zoo)  //Object {cat: "ken", dog: "lili"}

用ES6完全可以像下面这么写:

let cat = 'ken'
let dog = 'lili'
let zoo = {cat, dog}
console.log(zoo)  //Object {cat: "ken", dog: "lili"}

反过来可以这么写:

let dog = {type: 'animal', many: 2}
let { type, many} = dog
console.log(type, many)   //animal 2

default, rest

default很简单,意思就是默认值。大家可以看下面的例子,调用animal()方法时忘了传参数,传统的做法就是加上这一句type = type || 'cat'来指定默认值。

function animal(type){
    type = type || 'cat'  
    console.log(type)
}
animal()

如果用ES6我们而已直接这么写:

function animal(type = 'cat'){
    console.log(type)
}
animal()

最后一个rest语法也很简单,ES6引入rest参数(形式为“…变量名”),用于获取函数的多余参数。rest参数之后不能再有其他参数(即只能是最后一个参数)。

function animals(...types){
    console.log(types)
}
animals('cat', 'dog', 'fish') //["cat", "dog", "fish"]

而如果不用ES6的话,我们则得使用ES5的arguments

例子:

ES5:

function fn(){
    for(var i = 0; i<arguments.length ; i++){
        console.log(arguments[i]);
    }
}
fn(1,2,3);//1,2,3

ES6:

function newFn(...args){
	args.filter((obj)=>{console.log(obj)})
}
newFn(1,2,3);//1 2 3

promise

  • Promise对象代表一个异步操作,有三种状态,pending进行中,fulfilled已成功,rejected已失败,只有异步操作的结果,才可以决定当前是哪一种状态,任何其他操作都无法改变这个状态,这也就是promise名字的由来
  • 一旦状态改变,就不会再变,promise对象状态改变只有两种可能,从pending改到fulfilled或者从pending改到rejected,只要这两种情况发生,状态就凝固了,不会再改变,这个时候就称为定型resolved。

Promise的基本用法

let promise1 = new Promise(function(resolve,reject){
    setTimeout(function(){
        resolve('ok')
    },1000)
})
promise1.then(function success(val){
    console.log(val)
})

promise的介绍

如何使用promise

promise的执行顺序

使用promise解决回调地狱的问题(使用回调函数的then函数将其串联起来)

promise的具体需求

========================================更新,ES7,(async/await),Generator的语法糖

 

set、map (ES6新增容器)

  • set :

                 用于放置 无序的 不重复多个 value 的集合体(即没有重复值的数组)

                 可用于数组去重 Array.from (new Set ( 需要去重的arr ))

  • map:

                 用于放置 无序的 key 不重复多个 key-value 的集合体(即对象数组或嵌套数组),用的较少

注意,由于set 没有 length属性,需要进行遍历时不能使用普通循环的length和 for in 循环,需要使用 for of 循环,直接得到set容器内的值。

 

总结

以上就是ES6最常用的一些语法,可以说这20%的语法,在ES6的日常使用中占了80%。总体来看,ES6正在朝着更简洁、更友好、可读性更强、更模块化的方向发展,这些特性都能支持其成为一个企业级的开发语言,可以去开发大型的复杂应用。


原文地址:https://www.jianshu.com/p/ebfeb687eb70
 

发布了142 篇原创文章 · 获赞 54 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/CWH0908/article/details/91352915