前端综合学习笔记—异步、ES6/7、Module、Promise同步 vs 异步

综合技术 2018-03-13

先看下面的 demo,根据程序阅读起来表达的意思,应该是先打印
100

,1秒钟之后打印
200

,最后打印
300

。但是实际运行根本不是那么回事

console.log(100)
setTimeout(function () {
    console.log(200)
}, 1000)
console.log(300)

再对比以下程序。先打印 100
,再弹出 200
(等待用户确认),最后打印 300
。这个运行效果就符合预期要求。

console.log(100)
alert(200)  // 1秒钟之后点击确认
console.log(300)

这俩到底有何区别?—— 第一个示例中间的步骤根本没有阻塞接下来程序的运行,而第二个示例却阻塞了后面程序的运行。前面这种表现就叫做 异步(后面这个叫做 同步 ),即不会阻塞后面程序的运行。

异步和单线程

JS 需要异步的根本原因是 JS 是单线程运行的,即在同一时间只能做一件事,不能“一心二用”。

一个 Ajax 请求由于网络比较慢,请求需要 5 秒钟。如果是同步,这 5 秒钟页面就卡死在这里啥也干不了了。异步的话,就好很多了,5 秒等待就等待了,其他事情不耽误做,至于那 5 秒钟等待是网速太慢,不是因为 JS 的原因。

讲到单线程,我们再来看个真题:

第一问:下面代码的执行过程和结果

var a = true;
setTimeout(function(){
    a = false;
}, 100)
while(a){
    console.log('while执行了')
}

这是一个很有迷惑性的题目,不少候选人认为
100ms

之后,由于
a

变成了
false

,所以
while

就中止了,实际不是这样,因为 JS
是单线程的,所以进入 while
循环之后,没有「时间」(线程)去跑定时器了,所以这个代码跑起来是个死循环!

前端异步的场景

  • 定时
    setTimeout
    setInverval
  • 网络请求,如
    Ajax


    加载

Ajax 代码示例

console.log('start')
$.get('./data1.json', function (data1) {
    console.log(data1)
})
console.log('end')

img 代码示例(常用于打点统计)

console.log('start')
var img = document.createElement('img')
// 或者 img = new Image()
img.onload = function () {
    console.log('loaded')
    img.onload = null
}
img.src = '/xxx.png'
console.log('end')

ES6/7 新标准的考查

第二问:ES6 箭头函数中的 this
和普通函数中的有什么不同

箭头函数

箭头函数是 ES6 中新的函数定义形式, function name(arg1, arg2) {...}
可以使用 (arg1, arg2) => {...}
来定义。示例如下:

// JS 普通函数
var arr = [1, 2, 3]
arr.map(function (item) {
    console.log(index)
    return item + 1
})

// ES6 箭头函数
const arr = [1, 2, 3]
arr.map((item, index) => {
    console.log(index)
    return item + 1
})

箭头函数存在的意义,第一写起来更加简洁,第二可以解决 ES6 之前函数执行中 this
是全局变量的问题,看如下代码

function fn() {
    console.log('real', this)  // {a: 100} ,该作用域下的 this 的真实的值

    var arr = [1, 2, 3]
    // 普通 JS
    arr.map(function (item) {
        console.log('js', this)  // window 。普通函数,这里打印出来的是全局变量,令人费解
        return item + 1
    })
    // 箭头函数
    arr.map(item => {
        console.log('es6', this)  // {a: 100} 。箭头函数,这里打印的就是父作用域的 this
        return item + 1
    })
}
fn.call({a: 100})

第三问:ES6 模块化如何使用?

ES6 中模块化语法更加简洁,直接看示例。

如果只是输出一个唯一的对象,使用
export default

即可,代码如下

// 创建 util1.js 文件,内容如
export default {
    a: 100
}

// 创建 index.js 文件,内容如
import obj from './util1.js'
console.log(obj)

如果想要输出许多个对象,就不能用
default

了,且
import

时候要加
{...}

,代码如下

// 创建 util2.js 文件,内容如
export function fn1() {
    alert('fn1')
}
export function fn2() {
    alert('fn2')
}

// 创建 index.js 文件,内容如
import { fn1, fn2 } from './util2.js'
fn1()
fn2()

第四问:ES6 class 和普通构造函数的区别

class

class 其实一直是 JS 的关键字(保留字),但是一直没有正式使用,直到 ES6 。 ES6 的 class 就是取代之前构造函数初始化对象的形式,从语法上更加符合面向对象的写法。例如:

JS 构造函数的写法

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

MathHandle.prototype.add = function () {
  return this.x + this.y;
};

var m = new MathHandle(1, 2);
console.log(m.add())

用 ES6 class 的写法

class MathHandle {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  add() {
    return this.x + this.y;
  }
}
const m = new MathHandle(1, 2);
console.log(m.add())

注意以下几点,全都是关于 class 语法的:

  • class
    是一种新的语法形式,是 class Name {...}
    这种形式,和函数的写法完全不一样
  • 两者对比,构造函数函数体的内容要放在 class
    中的 constructor
    函数中, constructor
    即构造器,初始化实例时默认执行
  • class 中函数的写法是 add() {...}
    这种形式,并没有 function
    关键字

使用 class 来实现继承就更加简单了,至少比构造函数实现继承简单很多。看下面例子

JS 构造函数实现继承

// 动物
function Animal() {
    this.eat = function () {
        console.log('animal eat')
    }
}
// 狗
function Dog() {
    this.bark = function () {
        console.log('dog bark')
    }
}
Dog.prototype = new Animal()
// 哈士奇
var hashiqi = new Dog()

ES6 class 实现继承

class Animal {
    constructor(name) {
        this.name = name
    }
    eat() {
        console.log(`${this.name} eat`)
    }
}

class Dog extends Animal {
    constructor(name) {
        super(name) //调用父对象的构造函数
        this.name = name
    }
    say() {
        console.log(`${this.name} say`)
    }
}
const dog = new Dog('哈士奇')
dog.say()
dog.eat()

注意以下两点:

  • 使用
    extends

    即可实现继承,更加符合经典面向对象语言的写法,如 Java
  • 子类的
    constructor

    一定要执行
    super()

    ,以调用父类的
    constructor

第五问:ES6 中新增的数据类型有哪些?

Set 和 Map

Set 和 Map 都是 ES6 中新增的数据结构,是对当前 JS 数组和对象这两种重要数据结构的扩展。由于是新增的数据结构,目前尚未被大规模使用,但是作为前端程序员,提前了解是必须做到的。先总结一下两者最关键的地方:

  • Set
    类似于数组,但数组可以允许元素重复, Set
    不允许元素重复
  • Map
    类似于对象,但普通对象的 key
    必须是字符串或者数字,而 Map
    key
    可以是任何数据类型

Set

// 例1
const set = new Set([1, 2, 3, 4, 4]);
console.log(set) // Set(4) {1, 2, 3, 4}

// 例2
const set = new Set();
[2, 3, 5, 4, 5, 8, 8].forEach(item => set.add(item));
for (let item of set) {
  console.log(item);
}
// 2 3 5 4 8

Set
实例的属性和方法有

  • size
    :获取元素数量。
  • add(value)
    :添加元素,返回 Set 实例本身。
  • delete(value)
    :删除元素,返回一个布尔值,表示删除是否成功。
  • has(value)
    :返回一个布尔值,表示该值是否是 Set 实例的元素。
  • clear()
    :清除所有元素,没有返回值。
const s = new Set();
s.add(1).add(2).add(2); // 添加元素

s.size // 2

s.has(1) // true
s.has(2) // true
s.has(3) // false

s.delete(2);
s.has(2) // false

s.clear();
console.log(s);  // Set(0) {}

Set
实例的遍历,可使用如下方法

  • keys()
    :返回键名的遍历器。
  • values()
    :返回键值的遍历器。不过由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以 keys()
    values()
    返回结果一致。
  • entries()
    :返回键值对的遍历器。
  • forEach()
    :使用回调函数遍历每个成员。
let set = new Set(['aaa', 'bbb', 'ccc']);

for (let item of set.keys()) {
  console.log(item);
}
// aaa
// bbb
// ccc

for (let item of set.values()) {
  console.log(item);
}
// aaa
// bbb
// ccc

for (let item of set.entries()) {
  console.log(item);
}
// ["aaa", "aaa"]
// ["bbb", "bbb"]
// ["ccc", "ccc"]

set.forEach((value, key) => console.log(key + ' : ' + value))
// aaa : aaa
// bbb : bbb
// ccc : ccc

Map

Map
的用法和普通对象基本一致,先看一下它能用非字符串或者数字作为 key
的特性。

const map = new Map();
const obj = {p: 'Hello World'};

map.set(obj, 'OK')
map.get(obj) // "OK"

map.has(obj) // true
map.delete(obj) // true
map.has(obj) // false

需要使用 new Map()
初始化一个实例,下面代码中 set get has delete
顾名即可思义(下文也会演示)。其中, map.set(obj, 'OK')
就是用对象作为的 key
(不光可以是对象,任何数据类型都可以),并且后面通过 map.get(obj)
正确获取了。 Map
实例的属性和方法如下:


  • size

    :获取成员的数量

  • set

    :设置成员 key 和 value

  • get

    :获取成员属性值

  • has

    :判断成员是否存在

  • delete

    :删除成员

  • clear

    :清空所有
const map = new Map();
map.set('aaa', 100);
map.set('bbb', 200);

map.size // 2

map.get('aaa') // 100

map.has('aaa') // true

map.delete('aaa')
map.has('aaa') // false

map.clear()

Map 实例的遍历方法有:


  • keys()

    :返回键名的遍历器。

  • values()

    :返回键值的遍历器。

  • entries()

    :返回所有成员的遍历器。

  • forEach()

    :遍历 Map 的所有成员。
const map = new Map();
map.set('aaa', 100);
map.set('bbb', 200);

for (let key of map.keys()) {
  console.log(key);
}
// "aaa"
// "bbb"

for (let value of map.values()) {
  console.log(value);
}
// 100
// 200

for (let item of map.entries()) {
  console.log(item[0], item[1]);
}
// aaa 100
// bbb 200

// 或者
for (let [key, value] of map.entries()) {
  console.log(key, value);
}
// aaa 100
// bbb 200

Promise


Promise

CommonJS
提出来的这一种规范,有多个版本,在 ES6 当中已经纳入规范,原生支持 Promise
对象,非 ES6 环境可以用类似 Bluebird
Q
这类库来支持。

Promise
可以将回调变成链式调用写法,流程更加清晰,代码更加优雅。

简单归纳下 Promise
三个状态
两个过程
一个方法
,快速记忆方法: 3-2-1

三个状态:
pending


fulfilled


rejected

两个过程:

  • pending→fulfilled(resolve)
  • pending→rejected(reject)

一个方法:
then

当然还有其他概念,如
catch

Promise.all/race
,这里就不展开了。

关于 ES6/7 的考查内容还有很多,本小节就不逐一介绍了,如果想继续深入学习,可以在线看《 ES6入门
》。

您可能感兴趣的

ES6函数名称总结 当对一个函数递归或者栈追踪的时候,就会觉得函数名称是多么的重要。否则满屏的 anonymous 分分钟想打人。今天阅读《你不知道的JavaScript》(下卷)的时候,发现作者有对name进行总结,于是在这里记录一下,以便加深记忆,日后查阅。 下面是ES6中名称...
Promise.all 应用一例 在做小程序地图开发的时候,需要在地图上放置不同图标的 markers ,而这些图标又来自网络,恰恰由于微信小程序的限制,不支持网络图片作为 markers 的 iconPath 值 微信小程序文档 但注意到 也支持临时路径 这句话,则可以先通过 ...
What I talk about when I talk about Transpiling Ja... This long overdue blog post is all about transpiling JavaScript. As I've been procrastinating for weeks and still haven't finished the entire ar...
记一次 ES6 modules export default bug 最近发现以前写的代码中有一个bug: 我在export的时候这样写的: import SimpleHeader from ‘./simple-header' export default { SimpleHeader } 然后这样 i...
Learning Gutenberg: Modern JavaScript Syntax One of the key changes that Gutenberg brings to the WordPress ecosystem is a heavy reliance on JavaScript. Helpfully, the WordPress team have reall...