es6声明变量的方法:var、function、let、const、import、class。
顶层对象的属性与全局变量挂钩,被认为是JavaScript语言最大的设计败笔之一。这样的设计带来了几个很大的问题,首先是没法在编译时就报出变量未声明的错误,只有运行时才能知道(因为全局变量可能是顶层对象的属性创造的,而属性的创造是动态的);其次程序员很容易不知不觉地就创造了全局变量;最后,顶层对象的属性是到处可以读写的,这非常不利于模块化编程。另一方面,window对象有实体含义,值的是浏览器的窗口对象,顶层对象是一个有实体含义的对象也是不合适的。
var、function命令声明的全局变量依旧是顶层对象的属性,let、const、class命令声明的全局变量不属于顶层对象的属性。也就是说,从es6开始,全局变量将逐步与顶层对象的属性脱钩(进行剥离)。
获取顶层对象
(typeof window !== 'undefined' ? window : (typeof process === 'object' && typeof require === 'function' && typeof global === 'object') ? global : this) function getGlobal(){ if(typeof self !== 'undefined'){ return self } if(typeof window !== 'undefined'){ return window } if(typeof global !== 'undefined'){ return global } throw new Error('unable to locate global object') }
解构赋值报错:等号右边的值,要么转为对象以后不具备Iterator接口,要么本身就不具备Iterator接口。事实上,只要某种数据结构具有Iterator接口,都可以采用数组形式的解构赋值。
解构赋值允许指定默认值。es6内部使用严格相等运算符(===),判断一个位置是否有值。只有当一个数组(对象中属性)成员的值严格等于undefined,默认值才会生效。
对象的解构赋值是下面形式的简写。
let {foo:foo,bar:bar} = {foo:'aaa',bar:'bbb'}
也就是说,对象的解构赋值的内部机制是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。
let {foo:baz} = {foo:'aaa',bar:'bbb'} baz //'aaa' foo //error:foo is not defined
前者是匹配模式。后者才是变量。
在模板字符串(template string)中使用反引号,需要在其前面用反斜杠转义。
使用模板字符串表示多行字符串,所有空格和缩进都会被保留在输出之中。
ES6 提供了二进制和八进制数值的新的写法,分别用前缀0b
(或0B
)和0o
(或0O
)表示。
函数参数制定了默认值以后,函数的length属性将返回没有指定默认值的参数的个数。
一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等初始化结束,这个作用于就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。
var x = 1 function fn(x,y = x){ console.log(y) } fn(2) //2 fn() //undefined
利用参数默认值,可以指定某个参数不得省略,如果省略就会抛出错误。
function throwIfMiss(){ throw new Error('Missing Parameter') } function fn(mustBeProvidedParam = throwIfMiss()){ return mustBeProvidedParam } fn()
将参数默认值设为undefined
,表明这个参数是可以省略的。
函数的length属性,不包括rest参数(函数的多余参数)。
es6规定,只要函数参数使用了默认值、解构赋值、扩展运算符,那么函数就不能显式设定为严格模式,否则报错。有两种方法可以规避这种限制:
第一种是设定全局性的严格模式
'use strict'; function doSomething(a, b = a) { // code }
第二种是把函数包在一个无参数立即执行函数里面。
const doSomething = (function () { 'use strict'; return function(a,b = a) { // code }; }());
箭头函数有几个使用注意点:
- 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
- 不可以当做构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
- 不可以使用arguments对象,该对象在函数体内不存在。如果要使用,可以使用rest参数代替。
- 不可以使用yield命令,因此箭头函数不能用作Generator函数。
Array.from()方法可将类数组对象(array-like object)和可遍历(iterable)的对象转为真正的数组。
Array.of()方法用于将一组值转换为数组。
对象中属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object],而对象中只会有最后一个[object Object],前面的均会被覆盖掉。
isPrototypeOf()、getPrototypeOf()、setPrototype()用法示例:
如果Symbol的参数是一个对象,就会调用该对象的toString方法,将其转为字符串,然后再生成一个symbol值。
Symbol实例属性description,直接返回symbol的描述。
使用Set很容易实现并集(Union)、交集(Intersect)和差集(Difference)。
let s1 = new Set([1,2,3]) let s2 = new Set([3,4,5]) let union = new Set([...s1,...s2]) let intersect = new Set([...s1].filter(i => s2.has(i))) let difference = new Set([...s1].filter(i => !s2.has(i)))
Proxy构造函数:let proxy = new Proxy(target,handler);
target参数表示所要拦截的目标对象,handler参数也是一个对象。
下面是 Proxy 支持的拦截操作一览,一共 13 种。
-
get(target, propKey, receiver):拦截对象属性的读取,比如
proxy.foo
和proxy['foo']
。 -
set(target, propKey, value, receiver):拦截对象属性的设置,比如
proxy.foo = v
或proxy['foo'] = v
,返回一个布尔值。 -
has(target, propKey):拦截
propKey in proxy
的操作,返回一个布尔值。 -
deleteProperty(target, propKey):拦截
delete proxy[propKey]
的操作,返回一个布尔值。 -
ownKeys(target):拦截
Object.getOwnPropertyNames(proxy)
、Object.getOwnPropertySymbols(proxy)
、Object.keys(proxy)
、for...in
循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()
的返回结果仅包括目标对象自身的可遍历属性。 -
getOwnPropertyDescriptor(target, propKey):拦截
Object.getOwnPropertyDescriptor(proxy, propKey)
,返回属性的描述对象。 -
defineProperty(target, propKey, propDesc):拦截
Object.defineProperty(proxy, propKey, propDesc)
、Object.defineProperties(proxy, propDescs)
,返回一个布尔值。 -
preventExtensions(target):拦截
Object.preventExtensions(proxy)
,返回一个布尔值。 -
getPrototypeOf(target):拦截
Object.getPrototypeOf(proxy)
,返回一个对象。 -
isExtensible(target):拦截
Object.isExtensible(proxy)
,返回一个布尔值。 -
setPrototypeOf(target, proto):拦截
Object.setPrototypeOf(proxy, proto)
,返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。 -
apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如
proxy(...args)
、proxy.call(object, ...args)
、proxy.apply(...)
。 -
construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如
new proxy(...args)
。
Proxy.revocable()方法返回一个可取消的Proxy实例。
Reflect
对象一共有 13 个静态方法。
- Reflect.apply(target, thisArg, args) 用于绑定
this
对象后执行给定函数 -
Reflect.construct(target, args) 提供了一种不使用
new
,来调用构造函数的方法。 -
Reflect.get(target, name, receiver)
-
Reflect.set(target, name, value, receiver)
-
Reflect.defineProperty(target, name, desc) 用来为对象定义属性
-
Reflect.deleteProperty(target, name) 用于删除对象的属性
-
Reflect.has(target, name) 方法对应
name in obj
里面的in
运算符。 -
Reflect.ownKeys(target) 用于返回对象的所有属性,基本等同于
Object.getOwnPropertyNames
与Object.getOwnPropertySymbols
之和。 -
Reflect.isExtensible(target) 返回一个布尔值,表示当前对象是否可扩展。
-
Reflect.preventExtensions(target) 用于让一个对象变为不可扩展。
-
Reflect.getOwnPropertyDescriptor(target, name) 用于得到指定属性的描述对象,
-
Reflect.getPrototypeOf(target) 用于读取对象的
__proto__
属性 -
Reflect.setPrototypeOf(target, prototype) 用于设置目标对象的原型(prototype)
设计目的
- 将Object对象的一些明显属于语言内部的方法放到Reflect对象上。
- 修改某些Object方法的返回结果,让其变得更合理。
- 让Object操作都变成函数行为(API操作)。
- Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法就能在Reflect对象上找到对应的方法,这就让Proxy对象可以方便调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,总是可以在Reflect上获取默认行为。
promise
实例的状态都变成fulfilled
,或者其中有一个变为rejected
,才会调用Promise.all
方法后面的回调函数。
一个实例率先改变状态,Promise.race()的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给Promise.rece()的回调函数
Promise.allSettled()
方法只有等到所有这些参数实例都返回结果,不管是fulfilled
还是rejected
,包装实例才会结束。
Promise.any()只要参数实例有一个变成fulfilled
状态,包装实例就会变成fulfilled
状态;如果所有参数实例都变成rejected
状态,包装实例就会变成rejected
状态。
Promise.try()
原生具备Iterator接口的数据结构
- Array
- Map
- Set
- String
- TypedArray
- 函数的arguments对象
- NodeList对象
类相当于实例的原型,所有在类中定义的方法都会被实例继承。如果在一个方法前,加上static关键字,表示该方法不会被实例继承,而是直接通过类调用,这就成为静态方法。
如果静态方法中包含this关键字,这个this指的是类,而不是实例。
class Foo{ static bar(){ this.baz() } static baz(){ console.log('hello') } baz(){ console.log('world') } } Foo.bar() // hello
提案:私有属性、方法,前面加#
new.target 属性
new
是从构造函数生成实例对象的命令。ES6 为new
命令引入了一个new.target
属性,该属性一般用在构造函数之中,返回new
命令作用于的那个构造函数。如果构造函数不是通过new
命令或Reflect.construct()
调用的,new.target
会返回undefined
,因此这个属性可以用来确定构造函数是怎么调用的
需要注意的是,子类继承父类时,new.target
会返回子类,利用这个特点,可以写出不能独立使用,必须继承后才能使用的类。
class Father{ constructor(){ if(new.target === Father){ throw new Error('本类不能实例化') } } } class Son extends Father{ constructor(x,y){ super() this.x = x; this.y = y } }
严格模式主要有以下限制:
- 变量必须声明后再使用
- 函数的参数不能有同名属性,否则报错
- 不能使用
with
语句 - 不能对只读属性赋值,否则报错
- 不能使用前缀 0 表示八进制数,否则报错
- 不能删除不可删除的属性,否则报错
- 不能删除变量
delete prop
,会报错,只能删除属性delete global[prop]
eval
不会在它的外层作用域引入变量eval
和arguments
不能被重新赋值arguments
不会自动反映函数参数的变化- 不能使用
arguments.callee
- 不能使用
arguments.caller
- 禁止
this
指向全局对象 - 不能使用
fn.caller
和fn.arguments
获取函数调用的堆栈 - 增加了保留字(比如
protected
、static
和interface
)
import()可以实现动态加载,返回值为promise实例。同时动态加载多个模块可以配合使用Promise.all()方法。
浏览器对于带有type="module"
的<script>
,都是异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于打开了<script>
标签的defer
属性。
利用顶层的this等于undefined这个语法点,可以侦测当前代码是否在ES6模块之中。
<script type='module'> console.log(this === undefined) // true </script>
ES6 模块与 CommonJS 模块的差异
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
- ES6 模块之中,顶层的
this
指向undefined
;CommonJS 模块的顶层this
指向当前模块
CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import
,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import
有点像 Unix 系统的“符号连接”,原始值变了,import
加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
未完待续...