3
9月抽空重新回顾了下ES6所有知识点,整个回顾过程既惊喜又感慨,感慨开发这么久好像真的没有好好的静下心去读一本好的书,大多情况下只是在使用的时候用到了,不熟悉或者感兴趣再去走马观花一通,感慨之余也发现了一些自身的的问题,知识体系还是不够丰富、扎实,一句话:有空多读书总没错,好了不闲扯了,下面我们步入正题

关于JavaScript

如今前端知识日新月异,越是晚入门小伙伴,很多基础层面的东西,接触的真的是少之又少,各种前端框架层粗不穷VueReactNg这三大带有里程碑意义的框架Api我想看的这么文字的盆友应该都有接触,基础的Api使用应该是没有什么难度,但是在遇到问题,解决问题、以及在原有组件以及自我封装组件,搭建框架过程总会遇到一些莫名其妙的问题,解决这个问题,看源码是大众都知道的一些方法,但个人认为这是一种进阶的方法,新手入门建议可以先了解咱们JavaScript的发展史可以比较轻松的认识我们整个技术体系的发展和未来方向,避免一些认知错误

发展史

  1. 互联网早期

    • BS架构:1990年的12月25日,西方的圣诞节,Tim Berners-Lee在他的NeXT电脑上部署了第一套“主机-网站-浏览器”构成的Web系统,这标志BS架构的网站应用软件的开端,也是前端工程的开端。这个时候前端只是简单的Html静态文本简单到基本动画都没有
    • 1995年javascript的诞生:1995年,NetScape(网景)公司的工程师Brendan Eich设计了javascript脚本语言,并集成到了navigator2.0版本中。随后微软也意识到了javascript的潜力,并模仿开发VBScript和JScript应用到了IE中,这直接开启了NetScape和微软的浏览器竞争。由于微软的IE集成在windows操作系统上的优势,NetScape的navigator很快在浏览器市场上落于下风。于是他们把javascript提交到了ECMA,推动制订了ECMAScript标准,成功实现了javascript的标准国际化。虽然第一次浏览器战争最后IE大胜Navigator,但是NetScape的javascript主导了W3C的官方标准。
  2. 互联网发展期

    • 动态页面伊始:由于javascript推动,前端页面开始走向动态化,那时流行的跑马灯、悬浮广告基本是各大门户网站的标配,页面基本都是通过PHP、JSP、ASP动态渲染出来,这也直接导致后端代码的逻辑臃肿,服务端为了更好的管理代码,应运而生MVC模式
    • Ajax出现:前期的动态性完全由服务端推动,每一次的动态更新都需要将页面进行reload操作,不管交互还是性能,都是不值当的 "这个问题直到谷歌在04年应用Ajax技术开发的Gmail和谷歌地图的发布,才得到了解决。" 这背后的秘密就是Ajax技术中实现的异步HTTP请求,这让页面无需刷新就可以发起HTTP请求,用户也不用专门等待请求的响应,而是可以继续网页的浏览或操作。
      Ajax开启了web2.0的时代
    • 前端兼容问题:由于更重浏览器差异性,兼容问题也是层出不穷,不同的浏览器技术标准有不小的差异,不利于兼容开发,这催生了Dojo、Mooltools、YUIExtJS、jQuery等前端兼容框架,其中jQuery应用最为广泛。这些框架中不知道大家用过几个
    • Html5诞生:部分浏览器厂商为了解决适配问题提出过Web Forms 2.0Web Applications 1.0等规章最后整合成HTML5、各大浏览器都在为适配浏览器不断改善自己的浏览器
    • Node.js:2009年,Ryan Dahl以Chrome的V8引擎为基础开发了基于事件循环的异步I/O框架-Node.js。Node.js使得前端开发人员可以利用javascript开发服务器端程序。很快,大量的Node.js使用者就建构了一个用NPM包管理工具管理的Node.js生态系统。node.js也能开发跨平台的桌面应用
    Node 衍生出的NPM包管理工具为整个前端社区提供了一个规范良好的平台
  3. 互联网进击

    • 移动App、Hybrid App:移动互联网的到来,飞速发展,原生app迭代远远满足不了,大家找到折中的方法,损耗部分性能,提高产品产出;jQuery Mobile、Sencha Touch、Framework7如鱼得水;Hybrid技术指的是利用Web开发技术,调用Native相关的API,实现移动与Web二者的有机结合,既能利用Web开发周期短的优势,又能为用户提供Native的体验。
    • ECMAScript6:2015年6月,ECMAScript 6.0发布,该版本增加了很多新的语法,极大的拓展了javascript的开发潜力;一些陈旧的浏览器可以通过Babel进行降级转义适配,ES6将JavaScript推向了另一个历史转折点
    • 进行中:经过历史的沉淀,技术演进,交互升级,React、Vue、Anjular三大框架利用js集合自身优势,完全实现了目前的前后端分离的开发模式;开发体系发展到:NPM和Yarn为代表的包管理工具;ES6及Babel和TypeScript构成的脚本体系;HTML5;CSS3和相应的处理技术;React、Vue、Anjular为代表的框架;Webpack为代表的打包工具;Node.js为基础的Express和KOA后端框架;Hybrid技术。

ECMAScript简介

提案规则

权重自上到下

  • Stage 0 - Strawman(展示阶段)
  • Stage 1 - Proposal(征求意见阶段)
  • Stage 2 - Draft(草案阶段)
  • Stage 3 - Candidate(候选人阶段)
  • Stage 4 - Finished(定案阶段)

一般走到草案阶段基本可以在正式标准中看到,Tc39可查看各提案

Tips:这也是在babel中配置presets的来由(可能我们使用了一些仍然在草案甚至征求意见阶段的API)的时候需babel垫片

Babel转码器

配置.babelrc/babel.config.js
// 基本格式
{
    "presets":[]
    "plugins":[]
}

不同环境支持不同的转换方法

  • 命令行 //@babel/cli
  • 浏览器环境 //HTML中引入对应脚本
  • node环境 //Traceur模块

一般我们使用的脚手架默认配置好,但是我们需要配置的什么意思,以及为什么要配置

@babel/register、babel-core/register

改写require命令,为每个require引入的资源进行Babel转义

@babel/polyfill

Babel仅会转换新的语法,但是已有的Api中的Iterator、Generator、Set、Map、Proxy、Reflect、Symbol、Promise是需要用这个垫片处理

@babel/core

暴露出Babel的Api,进行特殊操作


ES6基础篇

基础篇我们主要从es6中添加的一些命令,以及对已有数据类型拓展的汇总

clipboard.png

let & const

let

特性如下

  1. 区别于var的块级作用域
  2. 不存在变量提升问题
  3. 具有暂时性死区
  4. 不能重复命名
// 下面这块很好的体现了`块级作用域`

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

特殊说明下暂时性死区的概念,就是:在块中,let 声明变量之前都是不能使用的

const

const 和 let 特性基本一致,区别于,使用const定义的是显式的常量,一经定义,不可更改

块级作用域

es5之前是有全局作用域、函数作用域,es6推出的块级作用域但凡一个{}就是一个作用域;
外层块级作用域能在内层使用,内层定义在外层访问不到

解构赋值

Tips:解构赋值 对应的还有 拓展操作符,解构不成功则为undefined; 一看就懂,一用代码就简洁

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

数组的解构

let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3

let [ , , third] = ["foo", "bar", "baz"];
third // "baz"

let [x, , y] = [1, 2, 3];
x // 1
y // 3

let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []

对象的解构

let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"

let { baz } = { foo: 'aaa', bar: 'bbb' };
baz // undefined

数组对象的解构

let obj = {
  p: [
    'Hello',
    { y: 'World' }
  ]
};

let { p: [x, { y }] } = obj;
x // "Hello"
y // "World"

函数参数的解构

// 设置默认值
function move({x = 0, y = 0} = {}) {
  return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]

注:还有字符串解构(转换成数组)、字符/布尔解构(转换成对象),解构遵循的宗旨是模式、数据格式一致,即可解构,解构失败undefined;进行赋值的过程,不管是数组还是对象解构都可以设置默认值;圆括号()只能在赋值语句的非模式部分即可

用途优势

  • 结构清晰,代码明了
  • 函数中直接返回多个值

    let [a,b,c] = fn()
  • 函数入参清晰多参数
  • JSON对象快速提取对应值
  • 函数设置默认值

字符串

字符串中改动还是蛮多的有添加Unicode表示JSON.stringify()适配非UTF-8编码,下面介绍日常开发实用改变

添加Iterator接口

for of遍历器(es6新增)仅对拥有Iterator接口的适配

模板字符串

`` 反引号 节省我们字符串拼接的痛点

let str1 = 
    'There are <b>' + basket.count + '</b> ' +
      'items in your basket, ';
let str2 = `There arebasket.count ${variable}</b>items in your basket, `;

上面字符串拼接自上到下的升级,同时也可以使用${}动态注入变量

模板编译

<%...%>中放置JavaScript代码,如下

let template = `
<ul>
  <% for(let i=0; i < data.supplies.length; i++) { %>
    <li><%= data.supplies[i] %></li>
  <% } %>
</ul>
`;

可以利用字符串匹配<%...%>封装template转换函数

字符串方法

  • String.fromCodePoint()// 识别大于0xFFFF的字符
  • String.raw() // 返回斜杠都转义的字符
  • codePointAt()

        charAt() // UTF-8
        codePointAt() // UTF-16
  • normalize() //处理合成字符串
  • includes(), startsWith(), endsWith()

    includes():返回布尔值,表示是否找到了参数字符串。
    startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
    endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
  • repeat() // 返回一个新字符,参数是重复次数
  • padStart(),padEnd() // 头/尾部补全字符 两个参数第一个长度、第二个填补的str
  • trimStart(),trimEnd() // 消除头/尾部空格
  • matchAll() // 字符正则匹配 参数正则表达式

数值类型

二进制、八进制表示法

0b(0B)二进制;0o(0O)八进制

方法

  • Number.isFinite(), Number.isNaN()
    判断是否有限、判断参数类型是否为NAN
  • Number.parseInt(), Number.parseFloat()
    将全局方法移植到内置对象下
  • Number.isInteger() 判断是否是整数
  • Number.EPSILON // 常量 表示 1 与大于 1 的最小浮点数之间的差。
  • Math.trunc() // 返回整数
  • Math.sign() // 判断是否正数、负数、零
  • Math.cbrt() // 计算数组的立方根
  • Math.clz32() // 返回 32位前置零个数
  • Math.imul // 返回两个数以 32 位带符号整数形式相乘的结果
  • ...

拓展符号(右结合即右边先运算)

****= 操作符 进行指数运算

//指数拓展符
let a = 2;

a ** 2 **4 // 2 * (2 * 4)

a **= 3 // a = 2 * 2 * 2

数组拓展

展开运算符

...将一个数组转成用逗号分隔的参数排列

console.log(1,2 ...[3,4]) // 1,2,3,4

方法拓展

  • Array.form() // 将类数组转换成数组
  • Array.of() // 用于将一组值,转换为数组

实例方法拓展

  • find()、findIndex() // find找到第一个满足条件的值,findIndex和前者类似,但是返回是下标,找不到返回-1
  • fill() 数组填充(params0:填充的值,params1:开始下坐标,params2:结束下坐标)
  • entries(),keys() 和 values() // 返回数组的键值对
  • includes() //是否包含某个值,返回bool值
  • flat()、flatMap() // 扯平数组

        [1, 2, , [4, 5]].flat() //[1, 2, 4, 5] 默认拉平一层,参数num、Infinity自定义拉平层数目
        // flatMap() 相当于先进行Map再进行flat操作
        // 相当于 [[2, 4], [3, 6], [4, 8]].flat()
        [2, 3, 4].flatMap((x) => [x, x * 2])
        // [2, 4, 3, 6, 4, 8]
  • sort() //ES2019 明确要求排序的稳定性

数组的空位

ES6中明确将空位装换成undefined;数组实例方法、map、扩展操作符存在差异

总之避免空位的出现是很必要的

对象类型

简洁写法

对象中的属性、方法只要key、value名字一致,即可简写成一个如下

// 属性
let a = 'a';
let b = 'b';
let obj = { a, b }
// 等同于
let a = 'a';
let b = 'b';
let obj = { a:a, b:b }

// 方法
let obj = 
{
    a,
    b,
    c(){
        console.log('这是一个对象方法')
    }
}
// 等同于
let obj = 
{
    a,
    b,
    c:function(){
        console.log('这是一个对象方法')
    }
}

属性名表达式

let propKey = 'foo';

let obj = {
  [propKey]: true,
  ['a' + 'bc']: 123
};
// 需要注意的是如果属性名是一个对象默认会装换成[object Object]

拓展操作符

// 合并对象
let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);

// 同数组拓展操作符后可跟表达式 如:
const obj = {
  ...(x > 1 ? {a: 1} : {}),
  b: 2,
};

拓展方法

可枚举性

每个属性都有一个描述对象,用来控制属性的行为Object.getOwnPropertyDescriptor(obj,'key')可获取对应属性的描述行为,
描述中enumerable标识是否可遍历

总之:操作中引入继承的属性会让问题复杂化,大多数时候,我们只关心对象自身的属性。所以,尽量不要用for...in循环,而用Object.keys()代替

遍历方法
  1. for in
  2. Object.getOwnPropertyNames()

    1. 返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
  3. Object.getOwnPropertySymbols()

    1. 返回一个数组,包含对象自身的所有 Symbol 属性的键名。
  4. Reflect.ownKeys()

    1. 返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
  5. Object.is()

    1. 传统判断值是否相等使用==或者===,前者会强制类型转换,后者NAN不等于自身,并+0等于-0,此方法就解决了上述问题
  6. Object.assign()

    1. Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target),第一个参数就是目标对象,只会处理可枚举的数据类型,并Symbol值属性也会被拷贝
    2. 数组处理将值一一对应成对象类型
  7. Object.getOwnPropertyDescriptors()

    1. 对ES5中Object.getOwnPropertyDescriptor()的拓展,返回对象中所有属性(非继承)的值
  8. 原型操作方法替代__proto__

    1. Object.setPrototypeOf()(写操作)、
    2. Object.getPrototypeOf()(读操作)、
    3. Object.create()(生成操作
  9. 对象键值对获取

    1. Object.keys(),
    2. Object.values(),
    3. Object.entries()
    4. Object.fromEntries() // Object.fromEntries()方法是Object.entries()的逆操作,用于将一个键值对数组转为对象

遍历规则如下:
  • 首先遍历所有数值键,按照数值升序排列。
  • 其次遍历所有字符串键,按照加入时间升序排列。
  • 最后遍历所有 Symbol 键,按照加入时间升序排列。
Super 关键字

指向当前对象的原型对象。

const proto = {
  x: 'hello',
  foo() {
    console.log(this.x);
  },
};

const obj = {
  x: 'world',
  foo() {
    super.foo();
  }
}

Object.setPrototypeOf(obj, proto);

obj.foo() // "world"

上面代码中,super.foo指向原型对象proto的foo方法,但是绑定的this却还是当前对象obj,因此输出的就是world。


ES6进阶篇

进阶篇,我们主要对新增的Symbol、ArrayBuffer以及Set和Map数据结构介绍、Promise 到 async的演变和差异,异步遍历的思想,以及Class、Module的使用

Symbol类型

ES6引入的新的数据类型,保证了数据的独一无二的特性,可以用来标识对象的额唯一key值
// 新建类型
let a = Symbol()
// 添加描述(用于标识Symbol值)
let a = Symbol('描述文件')

特性:

  1. 不能new操作 注意Symbol()是一个类似string类型的值,不是对象
  2. 不能与其他类型值进行计算
  3. 可以显性的转换成字符类型/布尔值
  4. 座位对象的属性的时候需要注意和字符型属性,赋值、取值区别开来

方法:

  1. Symbol.for()、Symbol.keyFor()
    返回同一个标识的Symbol类型,keyFor返回当前Symbol类型的唯一标识
  2. Symbol.isConcatSpreadable
    判断数组concat()操作的时候是否可以会别呗展开
  3. Symbol.match
  4. Symbol.replace
  5. Symbol.search
  6. Symbol.match
  7. Symbol.iterator
  8. Symbol.toPrimitive
  9. Symbol.toStringTag
  10. Symbol.unscopables

Set、Map数据结构

Set

Set 数据结构。它类似于数组,但是成员的值都是唯一的,没有重复的值。

特性:

  1. 去重
    Set() 去重的判断相等依据和 === 基本一致,但是在判断NAN的时候会判断相等​​​,并且两个对象总是不相等的​​​
  2. 属性

    • size Set实例成员的总数
  3. 方法

    • add()
    • delete()
    • has() bool值,是否是set成员
    • clear() 清除所有set成员

WeakSet

Set 的区别

  • 只能存储对象类型,不能是其他数据类型
  • 不进入垃圾回收机制
  • 没有size属性,同时不能遍历

Map

Map 的数据类型类似对象,但是对象的key值可以是任意类型的Map:值-值,不同于传统Object:字符串-值
属性
  1. 属性

    • size 成员总数
  2. 方法

    • set()
    • get()
    • has()
    • delete()
    • clear()

WeakMap

和Map的结构类似

Map的差异

  • 只接收对象作为键名
  • WeakMap的键名所指向的对象,不计入垃圾回收机制

用途

  • 在 DOM 对象上保存相关数据
  • 数据缓存

    const cache = new WeakMap();function countOwnKeys(obj) {    if (cache.has(obj)) {        console.log('Cached');        return cache.get(obj);    } else {        console.log('Computed');        const count = Object.keys(obj).length;        cache.set(obj, count);        return count;    }}
  • 私有变量

ArrayBuffer

操作二进制数据的一个接口。早就存在,属于独立的规格(2011 年 2 月发布),ES6 将它们纳入了 ECMAScript 规格,并且增加了新的方法。它们都是以数组的语法处理二进制数据,所以统称为二进制数组

注意:二进制数组并不是真正的数组,而是类似数组的对象。

与Array的区别

  • 属性类型

    • Array 可以是基础数据类型,也可以是复杂数据类型
    • ArrayBuffer 只能是 0 和 1 组成的二进制数据
  • 数据存放规则

    • Array 复杂数据类型存放在堆中
    • ArrayBuffer 存放在栈中,读取数据更快
  • 规格定义区别

    • Array 无需初始定义大小,并且在使用的时候可以缩放大小
    • ArrayBuffer 初始化需要定义大小,并不能再次修改

ArrayBuffer 对象

存储二进制数据的一段内存,不能直接进行读写,需要通过视图进行操作
    new ArrayBuffer(32) //分配一个32位字节的内存
属性
- byteLength 返回分配区域的字节长度
方法
- slice() 操作ArrayBuffer 对象 生成一个新的内存地址
- isView() bool值返回是否是TypedArray视图实例(是否是一个视图)

TypedArray 视图

构造方法
通过构造方法生成 视图

注:存在溢出问题

  • TypedArray(buffer, byteOffset=0, length?)
  • TypedArray(length) // 直接分配内存
  • TypedArray(typedArray) // 直接复制一个视图的值,生成一个新的视图
  • TypedArray(arrayLikeObject) // 直接生成TypedArray实例
实例属性
- buffer // 返回 ArrayBuffer 对象
- byteLength // 占据内存长度(成员长度)
- byteOffset // 视图从ArrayBuffer中开始位置
- length // 字节长度
实例方法
- set() // 复制数组
- subarray() // 返回新的视图
- slice() // 返回新的视图
- of() // 用于将参数转为一个TypedArray实例
- from() // from接受一个可遍历的数据结构(比如数组)作为参数,返回一个基于这个结构的TypedArray实例。​ 可有两个参数,第二个参数是fun

DataView 视图

实例属性
- buffer // 返回 ArrayBuffer 对象
- byteLength // 占据内存长度(成员长度)
- byteOffset // 视图从ArrayBuffer中开始位置

应用场景

  1. 网络请求中 blob 数据类型
  2. Canvas 读取二进制像素数据
  3. WebSocket 传输二进制数据
  4. File new FileReader() 读取 ArrayBuffer对象

Promise/async

Promise 异步编程的解决方案,社区的提案, async 结局了Promise的回调地狱

Promise

有 padding(进行中)、fulfilled(成功)、rejected(失败)三种状态,状态已经改变就并不会变动
回调
  1. then() 返回结果
  2. catch() 捕获异常
  3. finally() 异步回调后都会执行,不管成功还是失败
方法
  1. all(<array>)
    多个promise回调 等待内部所有Promise都返回成功结果/凡是一个返回reject才执行then(),多异步方法同时执行,所有都回调了再执行then() 方法
  2. race(<array>)
    同all,区别在于,只返回最先返回结果的异步请求,不管成功还是失败
  3. any()
    和recede 区别是一组Promise中只有所有都reject之后才会返回失败
  4. allSettled()
    不同于all的点是不管成功还是失败都需要等所有异步都返回接口才会返回,所以他的状态永远是fulfilled

注意:

  1. 在reject、resolve中使用return,避免后续代码执行问题
  2. Promise.all 如果 子Promise已经拥有了 catch() 则 all() 的 catch() 不再触发​​

Async

async 只是对 promise 的写法上的一种语法糖

async: 函数 返回一个Promise对象,return 返回Promise的结果

await: 后面默认是一个Promise对象

try catch(): 捕获错误

顶层await
解决异步模块加载的问题

class

将传统实例对象通过构造方法生成的过程放到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);

// class 方法
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

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

差异点: 严格模式、不存在变量提升、this指向

取值函数(getter)和存值函数(setter)

与 ES5 一样,在“类”的内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为

静态方法 Static

静态方法不会被继承

静态属性

//现有
class Foo {
}

Foo.prop = 1;
Foo.prop // 1

// 提案
Static prop = 1

私有方法/私有属性

私有方法只能通过命名规则或者Symbol数据类型定义,私有属性有个提案使用的是#

Module

ES6 提出的Moduel是前端发展过程中演变从CommonJs递进

Module 语法

  1. 静态化 (编译时加载)
  2. 严格模式 遵循ES5的严格模式
  3. export 和 export default 暴露模块
  4. import 引入模块
  5. import() 解决了按需加载/条件加载/动态模块路径的问题
  6. export * from <moduel> 实现模块的继承

Module 的加载实现

  1. defer、async
    defer是“渲染完再执行”,async是“下载完就执行”。另外,如果有多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序的。
  2. ES6 和 Commonjs 差异
    CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
  3. 循环加载问题

    1. Commonjs
      CommonJS 模块遇到循环加载时,返回的是当前已经执行的部分的值,而不是代码全部执行后的值,两者可能会有差异。所以,输入变量的时候,必须非常小心。
    2. ES6
      使用 函数 提升的优势解决报错

好久不见
685 声望7 粉丝

努力吃胖,努力健身,快乐打球,快乐code