Skip to main content

前端知识之ES6

文件引入方式

都是样式的导入方式:

<link href="https://xxx/index.css" rel="stylesheet">
<!-- or -->
<style>
@import url("https://xxx/index.css");
</style>

区别:

  1. 引入的内容不同
    • link 除了引用样式文件,还可以引用图片等资源文件,而 @import 只引用样式文件
  2. 加载顺序不同
    • link 引用 CSS 时,在页面载入时同时加载;@import 需要页面网页完全载入以后加载
  3. 兼容性不同
    • linkXHTML 标签,无兼容问题;@import 是在 CSS2.1 提出的;
  4. JS 的支持不同
    • link 支持使用 Javascript 控制 DOM 去改变样式;而 @import 不支持

href 和 src

为什么linkhref获取资源,而scriptimgsrc

src用于替换当前元素,href用于在当前文档和引用资源之间确立联系。

  • srcsource的缩写,指向外部资源的位置,指向的内容将会嵌入到文档中当前标签所在位置;在请求src资源时会将其指向的资源下载并应用到文档内,例如js脚本,img图片和frame等元素
    • 当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕,图片和框架等元素也如此,类似于将所指向资源嵌入当前标签内。这也是为什么将js脚本放在底部而不是头部
  • hrefHypertext Reference的缩写,指向网络资源所在位置,建立和当前元素(锚点)或当前文档(链接)之间的链接
    • 在文档中添加link标签,浏览器会识别该文档为css文件,就会并行下载资源并且不会停止对当前文档的处理。这也是为什么建议使用link方式来加载css,而不是使用@import方式

ES5 和 ES6

箭头函数

箭头函数特性
  • 语法更加简洁、清晰
  • 箭头函数不会创建自己的this
    • 箭头函数没有自己的this,它会捕获自己在定义时(注意,是定义时,不是调用时)所处的外层执行环境的this,并继承这个this值。所以,箭头函数中this的指向在它被定义的时候就已经确定了,之后永远不会改变
  • 箭头函数继承而来的this指向永远不变
  • call()/apply()/bind()无法改变箭头函数中this的指向
  • 箭头函数不能作为构造函数使用
  • 箭头函数没有自己的arguments
    • 可以在箭头函数中使用rest参数代替arguments对象
  • 箭头函数没有原型prototype
  • 箭头函数不能用作Generator函数,不能使用yeild关键字

ES6新特性

变量和作用域

let声明的变量只在所在块中生效;
let声明的变量可以解决varfor循环结合使用产生的无法取得最新变量值的问题(以往都需要通过闭包来解决这个问题);
let声明的变量不存在变量提升(从undefined->ReferenceError,其实也是一种暂时性死区)、会造成变量暂时性死区(在声明let变量之前都不能用它)、也不允许重复声明

const声明的变量行为与let类似,只是多了两点更强的约束:

  1. 声明时必须赋值;
  2. 声明的变量内存地址不可变

需要注意的是:对于用const声明基本类型,值就保存在内存地址之中,意味着变量不可重新赋值;对于用const声明的对象,对象内容还是可以更改的,只是不能改变其指向。(冻结对象应该用Object.freeze()

解构赋值(按照一定的结构解析出来进行赋值)的使用场景:变量快捷赋值、提取数据、函数参数定义和默认值、遍历某结构

对原生对象方法的扩展

String

  • 加强了对unicode的支持
  • 支持字符串遍历(实际上是部署了iterator接口
  • repeat()方法
  • 模板字符串 ` `

RegExp

  • 构造函数第一个参数是正则表达式,指定第二个参数不再报错取而代之,将使用这些参数创建一个新的正则表达式。
  • u修饰符; 将模式视为 Unicode 码位序列。
  • y修饰符(sticky,粘性匹配); 仅从目标字符串中此正则表达式的 lastIndex 属性所指示的索引进行匹配。不尝试从任何更高版本的索引进行匹配。
  • s修饰符(点号匹配所有字符) ; 允许. 去匹配新的行

Number

  • 二进制和八进制新写法
  • 新方法parseInt()
  • Number.EPSILON极小常量
  • 安全整数 Number.MAX_SAFE_INTEGER Number.MIN_SAFE_INTEGER
  • Math新方法

Function:

  • 函数参数默认值 function(arr = [], ...args){}
  • rest参数 ...
  • 函数内部严格模式
  • 函数的name属性
  • 箭头函数 () => { }

Array: 扩展运算符 ...

Object:

  • 支持简写:同名属性K-V可以只写一个、函数声明可以省略function;支持属性名表达式 (a["string"] = 2)、函数名表达式 (const func = function() {})。(注意:表达式和简写不能同时使用)。
  • ES6中的规范中,为函数对象添加了一个name属性,用来保存函数的名称。
  • 新增了Object方法:
    • Object.is() —— 用于解决=====的部分兼容问题
    • Object.assign() —— 浅复制
    • Object.setPrototypeOf()Object.getPrototypeOf() (Object.proto属性)
    • Object.entries()Object.keys()Object.values()
  • ES6中5种遍历对象属性的方法
    • for...in —— 自身继承的 可枚举属性除Symbol
    • Object.keys() —— 自身非继承的 可枚举属性除Symbol
    • Object.getOwnPropertyNames() —— 自身所有属性键名(包括不可枚举、除Symbol
    • Object.getOwnPropertySymbols() —— 自身的所有 Symbol 属性的键名
    • Reflect.ownKeys() —— 自身的所有键名
补充: Object.is()== 以及 ===

Object.is()== 不同。== 运算符在判断相等前对两边的变量(如果它们不是同一类型)进行强制转换(这种行为将 "" == false 判断为 true),而 Object.is 不会强制转换两边的值。

Object.is()=== 也不相同。差别是它们对待有符号的零和 NaN 不同,例如,=== 运算符(也包括 == 运算符)将数字 -0+0 视为相等,而将 Number.NaNNaN 视为不相等。

Symbol:

  • ES5以前,对象属性都只能是字符串,容易造成重命名导致的冲突。Symbol提供了一种机制,可以保存属性名是独一无二的。
  • Symbol类型的使用注意
    • 1)创建是调用函数,而不是new关键字
    • 2)Symbol类型的属性不会被for-*Object.keys()Object.getPropertyNames()返回,可以用Object.getOwnPropertySymbols()Reflect.ownKeys()

SetMap

Set:

Set是一种类似数组的数据结构,区别在于其存储的成员都是不重复的,由此带来了它的一个应用就是:去重。 Set通过new关键字实例化,入参可以是数组 或 类数组的对象。 值得注意的是:在Set中,只能存储一个NaN,这说明在Set数据结构中,NaN等于NaN

Set实例的方法:操作方法add()delete()has()clear()
遍历方法(比较特殊: MDN 简书 ):keys()values()entries()forEach();
扩展运算符 ... 、数组方法map()filter()方法也可以用于Set结构。

WeakSet类似于Set,主要区别在于:

  1. 成员只能是对象类型;
  2. 对象都是弱引用(如果其他对象都不再引用该对象,垃圾回收机制会自动回收该对象所占的内存,不可预测何时会发生,故WeakSet不可被遍历)

Map: JavaScript对象Object都是键值K-V对的集合,但K取值只能是字符串和SymbolMap也是K-V的集合,然而其K可以取任意类型。 如果需要键值对的集合,MapObject更适合。Map通过new关键字实例化。
Map实例的方法:set()get()has()delete()clear();
遍历方法同Set

WeakMap类似于Map,主要区别在于:

  1. 只接受对象作为键名;
  2. 键名所指向的对象不计入垃圾回收机制。

元编程相关 ProxyReflect

Proxy:

对目标对象加一层 “拦截”“代理”),外界对对象的访问、修改都必须先通过这层拦截层。因而它提供了一个机制可以对外界的访问进行过滤和改写。

用法:var proxy = new Proxy(p,opt); p是要被代理的目标对象,opt是配置对象。
值得注意的是:Proxy不是对目标对象透明的代理——即使不做任何拦截的情况下无法保证代理对象与目标对象行为的完全一致。(主要原因在于代理时,目标对象内部的this会指向代理对象)

Reflect: 与Proxy一样是ES6 为语言层面的用于操作对象提供的新API ,目前它所拥有的对象方法与Proxy对象一一对应
引入目的:

  1. Object对象上一些属于语言内部的方法放在Reflect上(目前都可以放)
  2. 修改Object对象上某些方法的返回值,使得更加合理化(健壮)
  3. Object对象的操作从命令式完全转化为函数式

异步编程 PromiseGeneratorAsync

JavaScript的世界里,对于异步编程存在如下几种方案:

  1. 回调函数
  2. 事件触发监听
  3. 发布订阅者模式;
  4. Promise。

Promise来源于社区,代表一个对象,它代表异步操作未来的一个结果(承诺)。 它总共有三个状态,pending\fulfilled\rejected。支持链式调用,支持错误传递,支持以同步代码的方式写异步操作。

Generator函数是ES6提供的异步编程解决方案。对于Generator函数,可以将它理解为一个状态机,封装了多个内部状态;此外它还是一个遍历器生成函数,这个函数可以遍历出状态机的所有状态。
函数特征:关键字function与函数名之间有*,函数体内部yield关键字。

  • 生成器函数与普通函数的区别:函数调用后不执行,而是返回一个指针对象(遍历器对象)。调用对象的next()方法,执行一段yield逻辑。故函数的分段执行的,yield是暂停执行的标志,next()可以恢复执行。
  • yieldreturn的区别:yield有记忆功能,return没有;一个函数可以多次执行yield,但只会return一次

async函数是Generator函数的语法糖,它进行了改进

  1. 自带执行器
  2. 返回值是Promise;

对比: 使用Promise的异步代码存在大量自有API的调用,操作本身的语义夹杂其中,不是很清晰;
Generator函数实现的异步代码语义比Promise清晰,但需要一个执行器;
async函数的写法最简洁、符合语义,不需要执行器。

语言层面类、模块的支持

class:
ES6 开始,JavaScript 提供了 class 关键字来定义类,尽管,这样的方案仍然是基于原型运行时系统的模拟,大部分功能ES5可以实现。

  • 构造函数的prototype属性在 ES6 的“类”上面继续存在。事实上,类中所有方法都定义在类的prototype属性上面(因而也是不可枚举的)。
  • constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。(默认构造函数);constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。

注意区别:类必须使用new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。

类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。

实例属性除了定义在constructor()方法里面的this上面,也可以定义在类的最顶层。

私有方法、静态方法、实例方法?


module:
在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定

export和import;一个文件即为一个模块,除非导入否则外部无法读取模块属性;

export支持:变量函数
export命令可以出现在模块的任何位置,只要处于模块顶级作用域就可以。如果处于块级作用域内,就会报错,import也是如此。
输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面,改写接口。
由于import是静态执行,所以不能使用表达式和变量,这些在运行时才能得到结果的语法结构。
使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。但是,用户肯定希望快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出。
模块之间也可以继承。

JS中对象分类、及其它原生对象

JS中对象分类、及其它原生对象

Iterator

ES6之前在JS中只有Array和对象可以表示“集合”这种数据结构,ES6中增加了:SetMap。 由此,四种之间互相组合又可以定义新的数据结构。这些新定义的数据结构如何访问呢? 遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。 任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。遍历器对象本质上是一个指针对象。

只要为某个数据结构部署了Iterator接口,则可以称此数据结构是可遍历的。iterator属性部署在Symbol上。如下对象默认部署了Iterator结口:Array Set Map String等 。
部署iterator结构的要点:

  1. Symbol.iterator上部署;
  2. 必须包含next()函数。

默认调用iterator接口的场景:解构赋值、...扩展运算符、yeild*for-of循环内部调用的即是调用数据机构内部的Symbol.iterator方法。

ES6ES5 继承的区别

ES6 中有类 class 的概念,类 class 的继承是通过 extends 来实现的,ES5 中是通过设置构造函数的 prototype属性,来实现继承的。

ES6ES5 中的继承有 2 个区别,第一个是,ES6 中子类会继承父类的属性,第二个区别是,super().call(this) 是不同的,在继承原生构造函数的情况下,体现得很明显,ES6 中的子类实例可以继承原生构造函数实例的内部属性,而在 ES5 中做不到。

哪些类型能被扩展操作符...扩展

适用类型:数组、对象、字符串。
复杂数据类型都可以,当转化为可迭代数据结构时可设置 对象的迭代器 对扩展运算符扩展出来的值进行操作。
基础数据只有string可以使用扩展运算符

让不同的浏览器兼容ES6的方法

针对 ES6 的兼容性问题,很多团队为此开发出了多种语法解析转换工具,把我们写的 ES6 语法转换成 ES5,相当于在 ES6 和浏览器之间做了一个翻译官。比较通用的工具方案有 babeljsxtraceures6-shim 等。