「前端基础」ES 6 进阶

文章目录

  • 2 新变量申明:let 和 const
    • 2.1 概述
    • 2.2 let 和 const
    • 2.3 相关面试题
  • 3 箭头函数杂谈
    • 3.1 箭头函数
    • 3.2 箭头函数杂谈
  • 4 三个点运算&新版字符串
    • 4.1 函数与对象的语法糖
    • 4.2 新版字符串
  • 5 迭代器与生成器
    • 5.1 字面量加强
    • 5.2 Symbol和迭代器
  • 6 新版对象
    • 6.1 如何创建对象
    • 6.2 属性修饰符
  • 7 ES 6 模块化
    • 7.1 模块化速学
    • 7.2 babel webpack parcel
  • 8 新版的类(上集)
    • 8.1 介绍原型
    • 8.2 介绍 JavaScript 中的类
  • 9 新版的类(下集)
    • 9.1 简单语法
    • 9.2 全部语法
    • 9.3 书籍推荐
  • 10 Promise
    • 10.1 回调与回调地狱
    • 10.2 Promise的用法
    • 10.3 Promise的细节
  • 11 ES6 新增数据类型
    • 11.1 Symbol 与隐藏属性
    • 11.2 Set 和数组去重
    • 11.3 其他类型
  • 12 ES6新增的API(上)
    • 12.1 Object.assign
    • 12.2 Array 新增 API
  • 13 ES6新郑的API(下)
    • String
    • Number
    • Math
  • 14 Proxy与Reflect
    • 14.1 Reflect 反射
    • 14.2 Proxy 代理
    • 14.3 Vue3 将用 Proxy 改写
  • 15 加课:async & await
    • 15.1 复习 Promise
    • 15.2 await 和 async 的用法

ES6 就是 JavaScript 的打脸史

2 新变量申明:let 和 const

2.1 概述

2.2 let 和 const

// ES3 语法
a = 1 
var a = 1
// ES6 语法
let a = 1
const a = 1

a = 1属于哪个对象,取决于运行环境(上下文)。

只想暴露一个全局变量,块({}),C、Java 和 PHP 中都有,但是 JavaScript ES6 之前没有。

// ES6 之前
(function (){
    var a = 1
    window.JonathanBen = function(){
        console.log(a)
    }
}())
JonathanBen() // 1
console.log(a) // Uncaught ReferenceError: a is not defined

// ES6 之后
{
    let a = 1
    window.JonathanBen = function(){
        console.log(a)
    }
}
JonathanBen() // 1
console.log(a) // Uncaught ReferenceError: a is not defined

临时死区:直接使用还没定义的变量,下图蓝色部分就是临时死区。

let 和 const 总结:
let部分:
1 let 的作用域在最近的 {} 之间
2 如果在定义该变量前,使用该变量,会报错(临时死区)。
3 如果重复申明变量,会报错

const部分:
1 2 3 同上
4 在定义时并赋值只有一次(只有一次赋值机会)。

2.3 相关面试题

代码执行时机(守株待兔还是刻舟求剑)

var i 
for(i=0; i<6; i++){
    function fn(){
        console.log(i)
    }
    xxx // 会执行 fn 函数
}
console.log(i) // 6

常见的执行形式xxx = fn()(守株待兔)

var i 
for(i=0; i<6; i++){
    function fn(){
        console.log(i) // 打印:0, 1, 2, 3, 4, 5 
    }
    fn() // 会执行 fn 函数
}
console.log(i) // 6

通过点击事件触发,示例代码。(刻舟求剑)
点击事件触发时,for 循环已经结束,导致 i = 6,然后触发一次点击事件,打印一次6。

var i 
for(i=0; i<6; i++){
    function fn(){
        console.log(i) // 打印:6
    }
    btn.onclick = fn // 通过点击触发 fn 函数
}
console.log(i) // 6

面试题:
快速生成 6 个导航ul>li{导航$}*6

点击每一个都打印 6。代码

现在需要点击哪个导航就需要打印该导航的编号。

ES6 之前语法(立即执行函数)代码

ES6 之后语法(let 解决)代码

ES6 之后语法中关于 let 详解:

for (let i = 0; i < liTags.length; i++) {
    liTags[i].onclick = function () {
      console.log(i)
    }
}

// 等价于

for (let i = 0; i < liTags.length; i++) {
	let i = i // i0, i1, i2, i3, i4, i5
    liTags[i].onclick = function () {
      console.log(i)
    }
}

上面代码等价于后部分,是前部分的相信分解:
首先明确for()中的let ifor(){}中的let i是没有任何关系的。

for()中的let i的作用域就只是括号里面部分(紫色选中部分)。
「前端基础」ES 6 进阶_第1张图片
所以这里JavaScript内部隐式的使用了let i = i,来将()中的i的值,赋值到{}中的的i

上图中出现了 7 次ifor()中 1 次,和for(){}中 6 次。

疑问:既然说for()中的let的作用域只是在上图紫色选中部分,那么怎么传值到for(){}中呢?神奇的魔法
答:
在 for 循环中实际上是有两个作用域的,条件设置的圆括号 () 内是一个父作用域,而代码块大括号{}中是一个子作用域。
「前端基础」ES 6 进阶_第2张图片

3 箭头函数杂谈

3.1 箭头函数

ES3 :

// 具名函数
function xxx(p1, p2){
	console.log(1)
	return 2
}

// 匿名函数
let xxx = function(p1, p2){
	console.log(1)
	return 2
}

ES6:

let xxx = (p1, p2)=>{
	console.log(1)
	return 2
}

// 当只有一个参数时,可以省略()
let xxx = p1 => {
	console.log(1)
	return 2
}

// 当只有一个函数体,可以省略 {},同时省略了 return
let xxx (p1, p2) => p1 + p2

this 是 call 的第一个参数

this 太难用了,ES 3 支持 this,ES 6 也支持 this,但是用箭头函数弱化了 this 的用法。

类比 python 类中的 self ,只不过 python 是在函数参数中显式出来的,而 JavaScript 中是隐式在函数参数中。

「前端基础」ES 6 进阶_第3张图片
上图中,init 函数中调用了,this.onClick 函数,然后 onClick 中的 this 实际上是 C #app 元素,是JQuery作者自己指定的(需要看源码或者文档才能知道)。不要想当然认为是 this 是 controller。

「前端基础」ES 6 进阶_第4张图片
因此,上图中的绿框 this 指的是 #app 元素。但是我们想实现 this 指向 controller 对象然后可以调用 getUsers 函数。解决方案之一如下:
「前端基础」ES 6 进阶_第5张图片

用 self 保留 controller 然后调用 onClick 函数,这样 onClick 函数中的this 就是指向 controller 对象。

解决方法之二,用箭头函数:箭头函数没有 this 的概念。
「前端基础」ES 6 进阶_第6张图片
验证箭头函数没有 this:

function f1(){
    console.log(this)
}
f1.call({name: 'Jonathan Ben'}) // {name: "Jonathan Ben"}

let f2 = () => {console.log(this)}
f2.call({name: 'Jonathan Ben'})
// Window {0: global, window: Window, self: Window, document: document, name: "", location: Location, …}

用箭头函数获取 name,因为箭头函数没有 this,只能自己传参了。

let obj = {
    name: 'Jonathan Ben',
    hi: (self)=>{
        console.log(self.name)
    }
}
obj.hi(obj)

箭头函数是很纯粹的函数,之前的函数有’潜规则’

3.2 箭头函数杂谈

function fn(){
}

// 可以如下理解
function fn(this){
	let this = arguments[-1]
}

JavaScript 没有借鉴 CoffeeScript 箭头函数之前,导致用户流逝,所以后面 JavaScript 加入了胖箭头函数=>

没有借鉴 CoffeeScript 的瘦箭头。
「前端基础」ES 6 进阶_第7张图片
JavaScript 在对象中简化函数语法糖(ES 6):


var obj = {
    name: 'Jonathan Ben',
    hi: function(){
        console.log(this.name)
    }
}

// 函数简化为
var obj = {
    name: 'Jonathan Ben',
    hi(){
        console.log(this.name)
    }
}

也没有借鉴?
JavaScript 中

res = null
if(res && res.data && res.data.user){
    console.log(res.data.user)
}

CoffeeScript 中

console.log(res?.data?.user)

vue 中,方法第一次不要用箭头函数,其他层都可以,因为用了箭头函数就永不了this.data

面试题:

var myObject = {
    foo: "bar",
    func: function() {
        var self = this;
        console.log("outer func:  this.foo = " + this.foo);
        console.log("outer func:  self.foo = " + self.foo);
        (function() {
            console.log("inner func:  this.foo = " + this.foo);
            console.log("inner func:  self.foo = " + self.foo);
        }());
    }
};
myObject.func();

// outer func:  this.foo = bar
// outer func:  self.foo = bar
// inner func:  this.foo = undefined
// inner func:  self.foo = bar

4 三个点运算&新版字符串

4.1 函数与对象的语法糖

(1)函数的默认参数

function sum(a, b){
    return a + b
}
sum(1, ) // NaN

// ES 5 参数保底值写法
function sum1(a, b){
    a = a || 0
    b = b || 1
    return a + b
}
sum1(2) // 3

// ES 6 语法糖
function sum2(a=0, b=1){
    return a + b
}
sum2(3) // 4

与 Python 的区别:默认参数是一个数组时,JavaScript 每次调用函数都会创建一个新的数组,而 Python 则不会。
JavaScript:

function push(item, array=[]){
    array.push(item)
    return array
}
console.log(push(1)) // [1]
console.log(push(2)) // [2]

Python:

def push(item, array=[]):
    array.append(item)
    return array
print(push(1)) # [1]
print(push(2)) # [1, 2]

(2)剩余参数

function sum(message){
    let result = 0
    for(let i=1; i<arguments.length; i++){
        result += arguments[i]
    }
    return message + result
}
console.log(sum("结果是", 1, 2, 3, 4, 5)) // 结果是15

function sum1(message){
    // arguments 伪数组转化为真数组
    // ES 5最好写法
    let args = Array.prototype.slice.call(arguments) 
    
    // ES 6 写法,两者等价
    // let args = Array.from(arguments)
    // let args = [...arguments]

    let numbers = args.slice(1)
    result = numbers.reduce((p, v)=> p+v, 0)
    return message + result
}
console.log(sum1("结果是", 1, 2, 3, 4, 5, 6)) // 结果是21

function sum2(message, ...numbers){
    result = numbers.reduce((p, v)=> p+v, 0)
    return message + result
}
console.log(sum2("结果是", 1, 2, 3, 4, 5, 6, 7)) // 结果是28

reduce解释

(3)展开操作

let array1 =[1, 2, 3, 4]

// 数组拼接
// ES 5 语法
let array2 = [0].concat(array1).concat([7])
console.log(array2) // [0, 1, 2, 3, 4, 7]

// ES 6 语法
array2 = [0, ...array1, 7]
console.log(array2) // [0, 1, 2, 3, 4, 7]

(4)析构赋值
1)[a, b] = [b, a]
不加分号的坑!!!需要看阮大的不加分号的情况。
2)[a, b, ...rest] = [10, 20, 30, 40, 50]
3)let {name, age} = frank

var person = {name: 'Jonathan Ben', age: 24}

// ES 5 语法
var name = person.name
var age = person.age

// ES 6 语法
// 直观理解 var {name, age} = {name: 'Jonathan Ben', age: 24} 匹配对应
var {name, age} = person 

4)[a=5, b=7] = [1]:类似函数默认值
5)[a, b] = f()[a, , b] = f():赋值函数返回值
6){p: foo, q: bar} = o

7)let {a = 10, b = 5} = {a: 3}let {a:aa = 10, b:bb = 5} = {a: 3}

var person = {
    name2: 'Jonathan Ben',
    age: 24,
    person2: {
        age: 64
    }
}

// 将属性 name2 修改为别名 mingzi
// 注:window 本身有个 name,因此这里要用 name2
var {name2: mingzi, age} = person
console.log(mingzi) // Jonathan Ben

var {person2:{name2:mingzi='Jonathan Lee', age}} = person
console.log(mingzi) // Jonathan Lee
console.log(name2) // Uncaught ReferenceError: name2 is not defined

8)对象浅拷贝

let objA = {
    p1: 1,
    p2: 2
}

let objC = {
    p1: 1111,
    p3: 3
}

// 都是 ES 6 语法
let objB = Object.assign({}, objA, objC)
console.log(objB) // {p1: 1111, p2: 2, p3: 3}
objB = {...objA, ...objC}
console.log(objB) // {p1: 1111, p2: 2, p3: 3}

补充:没有绝对的深拷贝

深拷贝目前最优:JSON.parse(JSON.stringify(data))
JSON.parse(JSON.stringify(data))完全深拷贝的前提:

  1. 没有日期对象,没有正则对象,没有函数,没有循环引用,没有所有普通对象之外的对象。
  2. 拷贝对象没有 undefined(JSON不支持)

9)对象合并

MDN 上更多的例子
(5)对象属性加强
1)obj = { x, y }

右边{x, y}不是结构,结构一般是在=左边。

var [x, y] = [1, 2]

// ES 5 语法
var obj = {
    x:x,
    y:y
}

// ES 6 语法糖
var obj = {
    x,
    y
}

2)obj = {["baz" + quux() ]: 42}
定义的时候就可以给对象赋值变量了,使用[]

function quux(){
    return "quux"
}
var obj = {
    ["baz" + quux()]: 20
}
console.log(obj)

3)函数属性可以缩写

// ES 5 语法糖
var obj = {
    sayHi: function(){}
}

// ES 6 语法糖
var obj = {
    sayHi(){}
}

4.2 新版字符串

(1)换行

// ES 5 定义时,可换行 2 种方式
var string = "
\

string string

\
"
// 结果没有换行符 console.log(string) //

string string

var string1 = "
" + "

string string

"
+ "
"
// 结果没有换行符 console.log(string1) //

string string

// ES 6 换行语法糖 var string2 = `

string string

`
console.log(string2) // 输出结果 4 个回车 // //
//

string string

//

(2)反引号支持字符串插入变量(插值)

var name = "Jonathan Ben"
var string = `${name} 是个好人`
console.log(string) // Jonathan Ben 是个好人

(3)函数接字符串

var name = "Jonathan Ben"
var fn = function(){
    let strings = arguments[0]
    let var1 = arguments[1]
    if(var1 === 'Jonathan Ben'){
        return var1 + strings[1] + '好人'
    }else{
        return var1 + strings[1] + '坏人'
    }
}
fn`${name} 是一个 ${person}`

目前在 React 中 styled-component 已经应用了:
h1 是一个函数接字符串

const Title = styled.h1`
  font-size: 1.5em;
  text-align: center;
  color: palevioletred;
`;

5 迭代器与生成器

5.1 字面量加强

字面量(literal)就是字面直接理解的表达。比如`'hello’等。

非字面量:new Object() 不太理解
(1)进制字面量增强
更安全的二进制字面量 Ob11111001 和更安全的八进制字面量Oo767目的是为了更加容易区分。

// 八进制
// ES 5 
0777 // 511

0888 // 888 
// 0开头是八进制,但是没有8,所以自动转为十进制了

// ES 6 更容易区分
0o777 // 511

(2)字符串支持Unicode
String.fromCodePoint
String.prototype.codePointAt

阮一峰 Unicode与JavaScript详解
阮一峰 字符编码笔记:ASCII,Unicode 和 UTF-8

JavaScript 考虑兼容性,会一直保留之前不好的地方。

(3)正则
正则表达式支持字添加 Unicode;添加y标记,支持粘滞匹配。

5.2 Symbol和迭代器

(1)Symbol
简单来说,就是一个你具体不清楚,但是唯一存在的字符串。

方应杭 JS中的Symbol是什么?

(2)迭代器

遍历:有限的数据集的一个个访问,数组变量。
迭代:版本迭代,无限的,若存在下一个版本就迭代。

生成器:暂时用得少,自己开发东西的时候,应该就会用到了。

function 发布器(){
    var _value = 0
    var max = 3
    return {
        next: function(){
            _value += 1
            if(_value > max){ throw new Error('没有下一个了!')}
            if(_value === max){
                return {
                    value: _value,
                    done:true
                }
            }else{
                return {
                    value: _value,
                    done: false
                }
            }
        }
    }
}

var a = 发布器()
a.next() // {value: 1, done: false}
a.next() // {value: 2, done: false}
a.next() // {value: 3, done: true}
a.next() // Uncaught Error: 没有下一个了!

6 新版对象

6.1 如何创建对象

  • 初始化对象的 4 种方法
// ES 6 之前
var a = new Object()
var b = {}

// ES 6
var c = Object.create(null)
var d = Object.create(Object.prototype) //等价于前两种创建对象

console.log(a) 
// {}
// __proto__: Object
console.log(b)
// {}
// __proto__: Object
console.log(c)
// {}
console.log(d)
// {}
// __proto__: Object

ECMAScript 2015 就是 ES 6。

  • 属性定义

一个对象中,定义了相同属性名,后面定义的会覆盖前面定义的。

var obj = {
    a: 1,
    b: 2,
    a: 3
}
console.log(obj) // {a: 3, b: 2}

属性名和属性值的变量名相同,则可以简写

var a = 11
var b = 22

var obj = {
    a: a,
    b: b
}
console.log(obj) // {a: 11, b: 22}

// ES 6 简写
var obj = {
    a,
    b
}
console.log(obj) // {a: 11, b: 22}

严格模式(ES 6之前的知识)一般用的很少!!!

  • 对象的变量赋值

ES 6 之前,只能先初始化后变量赋值;ES 6 支持初始化时,变量赋值。

var name = 'xingming'

// ES 6 之前
var obj = {}
obj[name] = 'Jonathan Ben'
console.log(obj) // {xingming: "Jonathan Ben"}

// ES 6 
var obj2 = {
    [name]: 'Jonathan Ben2'
}
console.log(obj2) // {xingming: "Jonathan Ben2"}

对象属性也可以是一个函数、getter 和 setter

getter的一个’奇怪’的应用现象

var i = 0
Object.defineProperty(window, 'a',{
    get(){
        i+=1
        return i
    }
})
a === 1 && a ===2 && a === 3
  • 对象浅拷贝

误区,以下代码不是浅拷贝,而是将变量指向同一个对象,改变其中一个变量的对象属性,另一个也会改变。

var obj = {a:1, b:2, c:3}
var obj2 = obj
obj2.a = 100
console.log(obj2) // {a: 100, b: 2, c: 3}
console.log(obj) // {a: 100, b: 2, c: 3}

浅拷贝的几种方法

var obj = {a:1, b:2, c:3}

// ES 5
var obj1 = {}
for(let key in obj){
	obj1[key] = obj[key]
}
obj1.a = 10
console.log(obj1) // {a: 10, b: 2, c: 3}

// ES 6 
var obj2 = Object.assign({}, obj)
var obj3 = {...obj}
obj2.a = 100
console.log(obj2) // {a: 100, b: 2, c: 3}
obj3.a = 1000
console.log(obj3) // {a: 1000, b: 2, c: 3}
console.log(obj) // {a: 1, b: 2, c: 3}
  • 获取对象原型的方法

推荐写法:Object.getPrototypeOf(a),不推荐写法:a.__proto__

  • JavaScript的对象和JSON对象的区别

JSON是一门存储数据的语言(抄袭JavaScript),没有undefined和函数,JavaScript的undefined就是一个 bug。

6.2 属性修饰符

使用Object.defineProperty()在旧的对象上添加自定义属性,自定义的功能都是JavaScript的坑造成的。

undefined不是关键字(判断关键字方法:关键字是不可以作为变量的),是一个不可修改的变量。

undefinedwindow 上的一个只读属性。

JavaScript 是一门很烂的语言,所有出现一些奇怪的现象见怪不怪。

关于限制对象属性写的属性描述符可以通过get或者writable两种方式来实现。

var obj = {
    get name(){
        return 'Jonathan Ben'
    }
}

Object.defineProperty(obj, 'name2', {
    writable: 'Jonathan Ben'
})

obj

「前端基础」ES 6 进阶_第8张图片
绿框部分直观来看像是obj对象的一个属性,但是直接可以从代码定义部分可以看出,name属性是虚拟出来,实际还是指向get name部分。

configurable

enumerable

Vue 中的 data 和页面中的 message 如何做到双向绑定呢?
「前端基础」ES 6 进阶_第9张图片
Vue官网链接有所介绍
「前端基础」ES 6 进阶_第10张图片

使用 Symbol 定义一个对象,但是用到的极少。Symbol 目前用的最多的地方就是迭代器。

var s = Symbol()

var obj = {
    [s]: 2
}

obj[s] // 2

console.log(Object.keys(obj)) // []
console.log(Object.getOwnPropertySymbols(obj)) // [Symbol()]

注:访问 Symbol 定义的的属性,直接使用Object.keys显示不出来,需要使用getOwnPropertySymbols

强调一下:禁止使用__proto__
「前端基础」ES 6 进阶_第11张图片

ES 6特性有很多,记住常用的,遇到不会的临时抱佛脚。

7 ES 6 模块化

7.1 模块化速学

  1. 什么是模块
  2. 什么是依赖
  3. 如何导入一个模块
  4. import 的各种用法(看 MDN)
  5. export 的各种用法(看 MDN)
  6. 如何使用 babel、webpack 和 parcel

模块化就是逻辑上(不局限于某种形式)将代码进行分开。

发现:函数表达式可以使用 let,如果使用 function 形式定义函数则默认就是 var

「前端基础」ES 6 进阶_第12张图片
import 用法:

import defaultExport from "module-name"; // 默认导入
import * as name from "module-name"; // 全部导入并命名为 name
import { export } from "module-name"; // 导入需要的部分
import { export as alias } from "module-name"; // 导入需要的部分并命名为 alias 
import { export1 , export2 } from "module-name"; // 导入需要的多个部分
import { foo , bar } from "module-name/path/to/specific/un-exported/file";
import { export1 , export2 as alias2 , [...] } from "module-name"; // 导入需要的多个部分,部分用别名
import defaultExport, { export [ , [...] ] } from "module-name"; // 导入默认的部分和需要的多个部分
import defaultExport, * as name from "module-name"; // 导入默认的部分和需要的多个部分,部分用别名
import "module-name"; // 仅仅导入,执行代码

仅为副作用而导入一个模块:
整个模块仅为副作用(中性词,无贬义含义)而导入,而不导入模块中的任何内容(接口)。 这将运行模块中的全局代码, 但实际上不导入任何值。

import "module-name"

export 用法:
导出暂时这3中够用了,最多的就是前两种(可同时使用)。

export { name1, name2,, nameN }; // 导出列表
export default expression; // 默认导出
export * from; // // 导出模块合集

7.2 babel webpack parcel

如果要兼容所有设备支持 importexport 语法。需要通过工具(babel webpack parcel)来翻译使其兼容使用的设备。

8 新版的类(上集)

8.1 介绍原型

Branden Eich 在 1995 年参考 scheme(基于函数式)语言和 self(基于原型)发明了 JavaScript 。

同时他很讨厌 Java,公司又让他蹭 Java 的热度。

「前端基础」ES 6 进阶_第13张图片
原型等价于共用属性

全局对象 window

为什么创建的对象和数组使用 toString() 得到的结果不同?
「前端基础」ES 6 进阶_第14张图片
答:
牵扯到this相关知识,相当于显示传递,实际是调用了call方法,把toString()前面的对象传给了原型,因此可以判断传的是什么。

object.toString.call(object)

8.2 介绍 JavaScript 中的类

「前端基础」ES 6 进阶_第15张图片

9 新版的类(下集)

9.1 简单语法

「前端基础」ES 6 进阶_第16张图片

9.2 全部语法

9.3 书籍推荐

用 java 来实现的对面对象编程的书

10 Promise

10.1 回调与回调地狱

什么是回调?

函数 A 在函数 B 中的调用就是回调。

把函数A传给函数B调用,那么A就是回调函数。

标准的回调:

function 获取用户信息(fn){
  fn('姓名: Jonathan Ben')
}

function 打印用户信息(用户信息){
  console.log('这是我打印的用户信息')
  console.log(用户信息)
}

获取用户信息(打印用户信息)

匿名回调:

function 获取用户信息(fn){
  fn('姓名: Jonathan Ben')
}

获取用户信息(function (用户信息){
  console.log('这是我打印的用户信息')
  console.log(用户信息)
})

函数不光可以通过 return 返回结果,也可以通过回调返回。

回调超过5层就很难理解,代码怎么定义的了。

为了解决回调地狱问题,前端发明了Promise

回调的缺点:
1 回调地狱

2 不知道怎么使用 Node/JQuery
每一套都需要单独记忆

// Node.js
// 通过函数第一个参数 error 判断成功和失败
readFile('C:\\1.txt', function (error, data){
  if(error){
    console.log('成功')
    console.log(data.toString())
  }else{
    console.log('读取文件失败')
  }

})

// JQuery
// 传一个对象里面有 success 和 error 函数,然后判断成功和失败
$.ajax({
  url: '/2.txt',
  success: function (response){
    console.log('成功')
  },
  error: function (){
    console.log('失败')
  }
})

10.2 Promise的用法

then中规定,第一个参数写成功部分, 第二个参数写失败部分。

readFilePromie('C:\\1.txt')
  .then(function(){}, function (){})

简单示例:

function 获取用户信息(name) {
  return new Promise(function(resolve, reject){
    if(name === 'Jonathan Ben'){
      console.log('我认识')
      resolve('是个胖子')
    }else{
      console.log('不认识')
      reject()
    }
  })
}

获取用户信息('Jonathan B1en')
  .then(
    function (d) {console.log(d)},
    function (){console.log('看来不认识呀')}

Web 服务的客户端:

const service = createWebService('http://example.com/data');

service.employees().then(json => {
  const employees = JSON.parse(json);
  console.dir(employees)
});

function createWebService(baseUrl) {
  return new Proxy({}, {
    get(target, propKey, receiver) {
      return () => httpGet(baseUrl + '/' + propKey);
    }
  });
}

function httpGet(url){
  return new Promise(function(resolve, reject){
    resolve('{"result":true, "count":42, "desc": "这是一个伪造的 url 的get 请求返回的 JSON 对象"}')
  })
}

10.3 Promise的细节

11 ES6 新增数据类型

这一部分讲的内容都是不常用的内容。

「前端基础」ES 6 进阶_第17张图片

11.1 Symbol 与隐藏属性

Symbol 前面不能加 new

Symbol 翻译为符号

Symbol 是唯一的

Symbol 可以作为对象的属性

Symbol 属于基本数据类型,Number、String、Boolean、Symbol、null、undefined(六种)

Object 是一种复杂数据类型

简单理解:Symbol 就是全局唯一字符串

唯一用法作为对象的属性的标识符

ES6之前做不到对象属性隐藏。

当面试官问Symbol有什么用的时候一定要举这个栗子。

在块级作用域中,用 Symbol 可以创建一个隐藏属性(私有属性)

{
  let a = Symbol()

  let obj = {
    name: "Jonathen Ben",
    age: 24,
    [a]: '隐藏属性'
  }
  console.log(obj[a]) // 只能块内访问
  window.object = obj
}

console.log(object)
object.name
object.age
// 块外访问不到 隐藏属性

「前端基础」ES 6 进阶_第18张图片

11.2 Set 和数组去重

面试题:如何给数组去重

Set 是去重后的数组。可以对数字,字符串,null,undefined,对象都可以去重。

不用 Set 怎么去重,但是只能简单实现一个去重数组。

function unique(array){
  let hash = {}
  let result = []
  for(let i=0; i< array.length; i++){
    hash[array[i]] = true
  }
  for(let key in hash){
    result.push(key)
  }
  return result
}

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

缺点:
1 无法区分数字和字符串
2 无法区分对象

因为 JavaScript 中对象只支持字符串标识符

11.3 其他类型

  • map

map 对象保存键值对,最大改进就是:相比较Plain Object 的键只能用字符串,map可以任意值都可以作为一个键或值。

set 设置
get 获取

Object.key获取不到信息

需要for…of

然后又有3修饰符

for(let i of map.entries()){} // 默认方式
for(let i of map.keys()){}
for(let i of map.values()){}
  • WeakSet

数组和 Set 中放了对象设计到垃圾回收(Garbage Collection),对象占内存。

GC:找出开发者访问不到的对象,进行回收。

弱引用不属于GC计算的范围,所以浏览器很可能吧弱引用的东西删掉。

Set 和 WeakSet的区别:
WeakSet 是弱引用,且不能告诉所有的引用(相比 Set 没有 entries 方法),因为可能会随时消失。

  • WeakMap

因为是弱引用,所有 key 必须是对象,值任意都行

类似 WeakMap 效果。

  • TypedArray

二进制文件处理(音频、视频、图片以及文件的处理)。除非你自己开发一个轮子,才用的上。

12 ES6新增的API(上)

12.1 Object.assign

如果目标对象中的属性具有相同的键,则属性将被源对象中的属性覆盖。后面的源对象的属性将类似地覆盖前面的源对象的属性。

浅拷贝和深拷贝
浅拷贝只拷贝 stack 区,深拷贝是 stack 区和 heap 区都拷贝

因为不知道完美的深拷贝需要拷贝哪些东西,所以一般说拷贝都是浅拷贝。

Object.assign 拷贝示例:

let a = {
  a1: 'a',
  a2: 2
}

let b = {
  obj:{
    name: 'b'
  }
}
Object.assign(a, b)

a.obj.name = 'c'

console.log(a.obj.name) // c
console.log(b.obj.name) // c

toString__proto__ 都是不可枚举的

创建属性的两个方式对属性定义的默认状态的影响:

let a = {
    a1: 'a1'
}

let b = {
    get b1(){
        return 'b1'
    }
}
let c = {}

let d = {}

Object.defineProperty(c, 'c1', {
    get(){
        return 'c1'
    }
})

Object.defineProperty(d, 'd1', {
    value: 'd1'
})


console.log(Object.getOwnPropertyDescriptor(a, 'a1'))
console.log(Object.getOwnPropertyDescriptor(b, 'b1'))
console.log(Object.getOwnPropertyDescriptor(c, 'c1'))
console.log(Object.getOwnPropertyDescriptor(d, 'd1'))

「前端基础」ES 6 进阶_第19张图片
可以看出直接创建属性,enumerableconfigurable 的默认值都是 true ,通过 Object.defineProperty 来创建,默认值都是false

通过 Object.assign 可以复制一个对象:

const obj = {a: 1}
const copy = Object.assign({}, obj})
console.log(copy); // { a: 1 }

12.2 Array 新增 API

new Array(5) 生成的数组是没有对应下标的数组,因此不可以 map。

let arr = new Array(5)
console.log(arr)

let arr2 = Array.from({length:5})
// ES5的等价写法
// let arr2 = Array.apply(null, {length:5})
console.log(arr2)

「前端基础」ES 6 进阶_第20张图片

方方遇到的面试题:创建一个填充字符串的指定长度的数组。

// ES6 
function setArrES6(n, fill){
  return Array.from({length: n}, item => fill)
}

// ES5
function setArrES5(n, fill){
  return new Array(n+1).join(fill).split('')
}

但是 ES5 的代码有缺陷,就是 split 切分时,如果字符串是相同的字符,则都会被切开。

Array.find():只找到第一个就不往下找了
Array.filter():找到所有满足条件的。

let arr = [
  {name: 'n1', age: 80},
  {name: 'n1', age: 20},
  {name: 'n1', age: 20}
]

console.log(arr.find(item => item.age === 20))
console.log(arr.filter(item => item.age === 20))
// { name: 'n1', age: 20 }
// [ { name: 'n1', age: 20 }, { name: 'n1', age: 20 } ]

Array.copyWithin : 不常用
Array.entriesArray.keysArray.values 都是可迭代对象,可以通过 next() 一个一个打出

13 ES6新郑的API(下)

功能不是很强大,大概知道意思就行。

String

ES6 之前,就可以解决这些问题了,只是ES6出了一个统一的接口。

  • String.prototype.includes()
let string = '12345'
// ES6
console.log(string.includes('12'))
// ES5
console.log(string.indexOf('12') > -1)
console.log(string.search(/2/) > -1)
  • String.prototype.repeat() 将字符串重复几次
  • String.prototype.startsWith()
let string = '12345'
// ES6
console.log(string.startsWith('1'))
// ES5
console.log(string.indexOf('123') === 0)
  • String.prototype.endsWith()
let string = '12345'
// ES6
console.log(string.endsWith('5'))
// ES5
function endsWith(string, subString){
  return string.lastIndexOf(subString) === string.length - subString.length
}
console.log(endsWith(string, '45'))

Number

  • Number.EPSILON 表示 1 与 Number 可表示的大于 1 的最小的浮点数之间的差值。

用途:
i 为1的时候可以 log 出来?

因为 JavaScript 的浮点精度问题会导致死循环,谨慎执行代码。

let i = 0;
while(i !== 1){
  i += 0.1
}
console.log("i si 1")

解决方法:

let i = 0;
// ES 6
while(Math.abs(i - 1) < Number.EPSILON){
  i += 0.1
}
// ES 5
while(Math.abs(i - 1) < 0.000001){
  i += 0.1
}
console.log("i si 1")
  • Number.isInteger
let data = 1.0
// ES6
console.log(Number.isInteger(data))
// ES5
console.log(data === parseInt(data, 10))
  • Number.isSafeInteger 安全整数范围为 -(253 - 1)到 253 - 1 之间的整数,包含 -(253 - 1)和 253 - 1。

  • Number.isFinite 来检测传入的参数是否是一个有穷数

// JavaScirpt 中的 PI 是 长度为 17 的有效数字
console.log(Number.isFinite(Math.PI))
  • Number.isNaN

NaN 不是 JavaScript 定义的。

Math

  • Math.acosh 返回一个数的反双曲余弦值

  • Math.hypot 返回所有参数的平方和的平方根

console.log(Math.hypot(3, 4)) // 5
  • Math.imul 该函数将两个参数分别转换为 32 位整数,相乘后返回 32 位结果,类似 C 语言的 32 位整数相乘。

  • Math.sign 函数返回一个数字的符号, 指示数字是正数,负数还是零(正负 0 ),不是数就返回 NaN。

  • Math.trunc

Math.truncparseInt的区别:
parseInt当输入的数值(22位及以上)需要转化为科学计数法的时候,会将数值科学技术法作为字符串,导致转为整型的过程中只截取 . 之前的一个数值。因此,为了解决这个问题,ES6 推出了 Math.trunc。换句话来说 Math.truncparseInt 的升级版。

console.log(parseInt(123.1)) // 123
console.log(parseInt(80121230123120381029381390123098)) // 8
// '8.012123012312038e+31' 截取 . 前面的数字 8

14 Proxy与Reflect

14.1 Reflect 反射

「前端基础」ES 6 进阶_第21张图片
「前端基础」ES 6 进阶_第22张图片
反射打个比方就是,山和湖中山的倒影。特性①山和倒影是一模一样的,② 虚的。

  • get

如果 name 属性部署了读取函数(getter),则读取函数的 this 绑定 receiver

var myObject = {
  foo: 1,
  bar: 2,
  get baz() {
    return this.foo + this.bar;
  },
};

var myReceiverObject = {
  foo: 4,
  bar: 4,
};

Reflect.get(myObject, 'baz', myReceiverObject) // 8

等价于下述代码,使用 baz 函数然后通过 call 改变 this(上述方法中 get 不能使用 call,会报错因此需要用 Reflect 来实现。)

var myObject = {
  foo: 1,
  bar: 2,
  baz: function () {
    return this.foo + this.bar;
  },
};

var myReceiverObject = {
  foo: 4,
  bar: 4,
};

console.log(myObject.baz.call(myReceiverObject))
  • set
var myObject = {
  foo: 4,
  set bar(value) {
    return this.foo = value;
  },
};

var myReceiverObject = {
  foo: 0,
};

Reflect.set(myObject, 'bar', 1, myReceiverObject);
myObject.foo // 4
myReceiverObject.foo // 1
  • apply
// normal
let min = Math.min
console.log(min(1, 2, 3))
// ES5 apply
let min2 = (...args) => Math.min.apply(Math, args)
console.log(min2(1, 2, 3))
console.log(Math.min.apply(undefined, [1, 2, 3]))
// ES6 Reflect 
console.log(Reflect.apply(Math.min, undefined, [1, 2, 3]))

「前端基础」ES 6 进阶_第23张图片

  • construct
function Greeting(name) {
  this.name = name;
}
// new 的写法
const instance = new Greeting('张三');
// Reflect.construct 的写法
const instance = Reflect.construct(Greeting, ['张三']);

第一种形式:

  • Reflect.get(target, name, receiver)
  • Reflect.set(target, name, value, receiver)
  • Reflect.apply(target, thisArg, args)
  • Reflect.has(target, name)

以上这几种 Reflect 的方法,对应对象的就是 target.xxxxxx 就是对应的名字),例如:taget.apply(thisArg, args)

第二种形式:

  • Reflect.defineProperty(target, name, desc)
  • Reflect.getPrototypeOf(target)
  • Reflect.setPrototypeOf(target, prototype)
  • Reflect.isExtensible(target)
  • Reflect.preventExtensions(target)
  • Reflect.getOwnPropertyDescriptor(target, name)

以上这几种 Reflect 的方法,对应对象的就是 Object.xxxxxx 就是对应的名字),例如:Object.defineProperty(target, name, desc)

第三种形式:没有对象对应的方式。

  • Reflect.construct(target, args)
  • Reflect.deleteProperty(target, name)
  • Reflect.ownKeys(target) // 还没讲

14.2 Proxy 代理

「前端基础」ES 6 进阶_第24张图片
「前端基础」ES 6 进阶_第25张图片
Reflect 的每一种方法都有替代的方法

Proxy 简单用法:

let beiProxy = {}

let proxy = new Proxy(beiProxy, {
  get: function (target, key){
    console.log('target: ', target)
    console.log('key: ', key)
    return Reflect.get(target, key)
  },

  set: function (target, key, value){
    console.log('target: ', target)
    console.log('key: ', key)
    console.log('value: ', value)
    return Reflect.set(target, key, value)
  },
})

proxy.name = 'Jonathan Ben'
console.log('************')
console.log(proxy)

console.log('------------')
console.log(beiProxy)

console.log('************')
console.log(proxy.name)

通过,Proxy 来控制 game 中的血条不能为负数。

// 直接将target 写入 Proxy参数, 因此是一个匿名对象,这样防止直接修改 target 对象本身。
let proxy = new Proxy({lives: 3}, {
  get(target, name){
    return Reflect.get(target, name)
  },

  set(target, name, value){
    if(name === 'lives' && value < 0){
      value = 0
    }
    return Reflect.set(target, name, value)
  }
})
proxy.lives = 1
console.log(proxy.lives) // 1

proxy.lives = -1
console.log(proxy.lives) // 0

很多东西都可以用 Proxy 来做,但用 Proxy 来做自由度比较大。

实现一些不借助代理根本无法实现的功能。

无限代理Tree

tree.branch1.branch2.twig 如果是对象 tree,想要创建 branch2 的属性 twig 。因为 branchundefined,会报错。


// TypeError: Cannot read property 'branch2' of undefined
let tree = {}
tree.branch1.branch2.twig = "green";

类似于存放文件的时候,但是路径中文件夹不存在这种感觉,需要先创建所需的文件夹,最后存储文件。

那么可以通过 Proxy 来快速实现。

function Tree() {
  return new Proxy({}, handler);
}
let handler = {
  get: function (target, key, receiver) {
    if (!(key in target)) {
      target[key] = Tree();  // 自动创建一个子树
    }
    return Reflect.get(target, key, receiver);
  }
};

let tree = Tree();
console.log(tree) // {}
tree.branch1.branch2.twig = "green";
console.log(tree) // { branch1: { branch2: { twig: 'green' } } }
tree.branch1.branch3.twig = "yellow";
console.log(tree) 
// {
//   branch1: { branch2: { twig: 'green' }, branch3: { twig: 'yellow' } }
// }

「前端基础」ES 6 进阶_第26张图片

14.3 Vue3 将用 Proxy 改写

Vue 2 中存在一个bug:没有添加绿色框中的代码(也就是预先定义好 age),会导致点击按钮 “添加年龄” 后,不会显示 18。因为Vue 2 中的 data 是通过 JavaScript 中对象的 defineProperty 方法中的 gettersetter 来双向绑定的。既然 defineProperty 中对象的参数中没有捕获到 age 属性,那么就不会响应式的改变。因此,预先定义好 age(相当于绑定了) ,就可以达到点击按钮显示年龄的效果。
「前端基础」ES 6 进阶_第27张图片
为了解决这个问题,因此可以通过 Proxy 代理来解决。通过上面无限代理 tree 可以实现。

let data = {
  obj: {
    name: 'Jonathan Ben'
  }
}

function crateProxy(target) {
  return new Proxy(target, {
    get: function (target, key, receiver) {
      if (!(key in target)) {
        target[key] = Tree()  // 自动创建一个子树
      }
      return Reflect.get(target, key, receiver)
    }
  })
}

let proxy = crateProxy(data)
proxy.obj.age = 24
console.log(data)

15 加课:async & await

15.1 复习 Promise

摇色子 Promise

function 摇色子(){
  return new Promise((resolve, reject)=>{
    setTimeout(() => {
      let n = parseInt(Math.random() * 6 + 1)
      resolve(n)
    }, 3000)
  })
}

摇色子().then(
  x => console.log("色子的点数是" + x),
  () => console.log("摇失败了!!!")
)

15.2 await 和 async 的用法

async 就是为了标记,表示该函数是一个异步函数。
await 只能放到 async 函数中,同时 await 后面需要跟一个返回 Promise 的函数并调用。如果等待的不是 Promise 对象,则返回该值本身。
其实 asyncawait 像是 Promise 的语法糖

function 摇色子(){
  return new Promise((resolve, reject)=>{
    setTimeout(() => {
      let n = parseInt(Math.random() * 6 + 1)
      resolve(n)
    }, 3000)
  })
}

async function test(){
  let n = await 摇色子()
  console.log(n);
}

test()

如果有错误,则这里需要用 try...catch 来处理

function 赌大小(猜测){
  return new Promise((resolve, reject)=>{
    setTimeout(() => {
      let n = parseInt(Math.random() * 6 + 1)
      console.log('开始赌博了:')
      if(n > 3 ){
        if(猜测 === '大')
          resolve(n)
        else  
          reject(n)
      }else{
        if(猜测 === '小')
          resolve(n)
        else  
          reject(n)
      }
    }, 1000)
  })
}

async function test(){
  try{
    let n = await 赌大小('大')
    console.log('好嗨哦' + n); 
  }catch(error){
    console.log('输光了' + error)
  }
  
}

test()

如果一次来两个筛子赌大小?这里需要使用 Promise.all 来实现

function 赌大小(猜测) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      let n = 1 //parseInt(Math.random() * 6 + 1, 10);
      console.log("开始赌博了:");
      if (n > 3) {
        if (猜测 === "大"){
          resolve(n);
        }else{
          console.log("error");
          reject(n);
        } 
        
      } else {
        if (猜测 === "小"){
          resolve(n);
        } else{
          console.log("error");
          reject(n);
        } 
      }
      console.log('***')
    }, 5000);
  });
}

// 链式写法
Promise.all([赌大小("大"), 赌大小("大")])
  .then(
    (n) => console.log('执行成功了' + n),
    (n) => console.log('执行失败了' + n)
  )

// async await 写法
async function test() {
  try {
    let n = await Promise.all([赌大小("大"), 赌大小("大")]);
    console.log("好嗨哦" + n);
  } catch (error) {
    console.log("输光了" + error);
  }
}

test();

你可能感兴趣的:(前端)