前端知识之ES6
文件引入方式
link 和 @import
都是样式的导入方式:
<link href="https://xxx/index.css" rel="stylesheet">
<!-- or -->
<style>
@import url("https://xxx/index.css");
</style>
区别:
- 引入的内容不同
link
除了引用样式文件,还可以引用图片等资源文件,而@import
只引用样式文件
- 加载顺序不同
link
引用CSS
时,在页面载入时同时加载;@import
需要页面网页完全载入以后加载
- 兼容性不同
link
是XHTML
标签,无兼容问题;@import
是在CSS2.1
提出的;
- 对
JS
的支持不同link
支持使用Javascript
控制DOM
去改变样式;而@import
不支持
href 和 src
为什么link
用href
获取资源,而script
和img
用src
?
src
用于替换当前元素,href
用于在当前文档和引用资源之间确立联系。
src
是source
的缩写,指向外部资源的位置,指向的内容将会嵌入到文档中当前标签所在位置;在请求src资源时会将其指向的资源下载并应用到文档内,例如js脚本,img图片和frame等元素- 当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕,图片和框架等元素也如此,类似于将所指向资源嵌入当前标签内。这也是为什么将js脚本放在底部而不是头部
href
是Hypertext 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
声明的变量可以解决var
与for
循环结合使用产生的无法取得最新变量值的问题(以往都需要通过闭包来解决这个问题);
let
声明的变量不存在变量提升(从undefined
->ReferenceError
,其实也是一种暂时性死区)、会造成变量暂时性死区(在声明let变量之前都不能用它)、也不允许重复声明
const
声明的变量行为与let
类似,只是多了两点更强的约束:
- 声明时必须赋值;
- 声明的变量内存地址不可变
需要注意的是:对于用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.NaN
与 NaN
视为不相等。
Symbol
:
- ES5以前,对象属性都只能是字符串,容易造成重命名导致的冲突。Symbol提供了一种机制,可以保存属性名是独一无二的。
Symbol
类型的使用注意- 1)创建是调用函数,而不是
new
关键字 - 2)
Symbol
类型的属性不会被for-*
、Object.keys()
、Object.getPropertyNames()
返回,可以用Object.getOwnPropertySymbols()
和Reflect.ownKeys()
。
- 1)创建是调用函数,而不是
Set
和 Map
Set
:
Set
是一种类似数组的数据结构,区别在于其存储的成员都是不重复的,由此带来了它的一个应用就是:去重。Set
通过new
关键字实例化,入参可以是数组 或 类数组的对象。 值得注意的是:在Set
中,只能存储一个NaN
,这说明在Set
数据结构中,NaN
等于NaN
。
Set
实例的方法:操作方法add()
、delete()
、has()
和clear()
;
遍历方法(比较特殊: MDN 简书 ):keys()
、values()
、entries()
和 forEach()
;
扩展运算符 ...
、数组方法map()
、filter()
方法也可以用于Set
结构。
WeakSet
类似于Set
,主要区别在于:
- 成员只能是对象类型;
- 对象都是弱引用(如果其他对象都不再引用该对象,垃圾回收机制会自动回收该对象所占的内存,不可预测何时会发生,故
WeakSet
不可被遍历)
Map
:
JavaScript
对象Object
都是键值K-V
对的集合,但K
取值只能是字符串和Symbol
,Map
也是K-V
的集合,然而其K
可以取任意类型。
如果需要键值对的集合,Map
比Object
更适合。Map
通过new
关键字实例化。
Map
实例的方法:set()
、get()
、has()
、delete()
和clear()
;
遍历方法同Set
。
WeakMap
类似于Map
,主要区别在于:
- 只接受对象作为键名;
- 键名所指向的对象不计入垃圾回收机制。
元编程相关 Proxy
和 Reflect
Proxy
:
对目标对象加一层 “拦截”(“代理”),外界对对象的访问、修改都必须先通过这层拦截层。因而它提供了一个机制可以对外界的访问进行过滤和改写。
用法:var proxy = new Proxy(p,opt)
; p
是要被代理的目标对象,opt
是配置对象。
值得注意的是:Proxy
不是对目标对象透明的代理——即使不做任何拦截的情况下无法保证代理对象与目标对象行为的完全一致。(主要原因在于代理时,目标对象内部的this会指向代理对象)
Reflect
:
与Proxy
一样是ES6
为语言层面的用于操作对象提供的新API
,目前它所拥有的对象方法与Proxy
对象一一对应
引入目的:
- 将
Object
对象上一些属于语言内部的方法放在Reflect
上(目前都可以放) - 修改
Object
对象上某些方法的返回值,使得更加合理化(健壮) - 让
Object
对象的操作从命令式完全转化为函数式
异步编程 Promise
、Generator
和 Async
在JavaScript
的世界里,对于异步编程存在如下几种方案:
- 回调函数
- 事件触发监听
- 发布订阅者模式;
- Promise。
Promise
来源于社区,代表一个对象,它代表异步操作未来的一个结果(承诺)。
它总共有三个状态,pending\fulfilled\rejected
。支持链式调用,支持错误传递,支持以同步代码的方式写异步操作。
Generator
函数是ES6
提供的异步编程解决方案。对于Generator
函数,可以将它理解为一个状态机,封装了多个内部状态;此外它还是一个遍历器生成函数,这个函数可以遍历出状态机的所有状态。
函数特征:关键字function
与函数名之间有*
,函数体内部yield
关键字。
- 生成器函数与普通函数的区别:函数调用后不执行,而是返回一个指针对象(遍历器对象)。调用对象的
next()
方法,执行一段yield
逻辑。故函数的分段执行的,yield
是暂停执行的标志,next()
可以恢复执行。 yield
与return
的区别:yield
有记忆功能,return
没有;一个函数可以多次执行yield
,但只会return
一次
async
函数是Generator
函数的语法糖,它进行了改进
- 自带执行器
- 返回值是
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中对象分类、及其它原生对象
Iterator
ES6
之前在JS
中只有Array
和对象可以表示“集合”这种数据结构,ES6
中增加了:Set
和Map
。 由此,四种之间互相组合又可以定义新的数据结构。这些新定义的数据结构如何访问呢? 遍历器(Iterator
)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。 任何数据结构只要部署Iterator
接口,就可以完成遍历操作。遍历器对象本质上是一个指针对象。
只要为某个数据结构部署了Iterator
接口,则可以称此数据结构是可遍历的。iterator
属性部署在Symbol
上。如下对象默认部署了Iterator
结口:Array Set Map String
等 。
部署iterator
结构的要点:
- 在
Symbol.iterator
上部署; - 必须包含
next()
函数。
默认调用iterator
接口的场景:解构赋值、...
扩展运算符、yeild*
。for-of
循环内部调用的即是调用数据机构内部的Symbol.iterator
方法。
ES6
与 ES5
继承的区别
ES6 中有类 class 的概念,类 class 的继承是通过 extends 来实现的,ES5 中是通过设置构造函数的 prototype属性,来实现继承的。
ES6
与 ES5
中的继承有 2
个区别,第一个是,ES6
中子类会继承父类的属性,第二个区别是,super()
与 .call(this)
是不同的,在继承原生构造函数的情况下,体现得很明显,ES6
中的子类实例可以继承原生构造函数实例的内部属性,而在 ES5
中做不到。
哪些类型能被扩展操作符...
扩展
适用类型:数组、对象、字符串。
复杂数据类型都可以,当转化为可迭代数据结构时可设置 对象的迭代器 对扩展运算符扩展出来的值进行操作。
基础数据只有string可以使用扩展运算符
让不同的浏览器兼容ES6的方法
针对 ES6
的兼容性问题,很多团队为此开发出了多种语法解析转换工具,把我们写的 ES6
语法转换成 ES5
,相当于在 ES6
和浏览器之间做了一个翻译官。比较通用的工具方案有 babel
,jsx
,traceur
,es6-shim
等。