ES6

“暂时性死区”也意味着typeof不再是一个百分之百安全的操作。

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 
  };
}());

Math.trunc方法用于去除一个数的小数部分,返回整数部分。

Math.trunc = Math.trunc || function(x){
    return x < 0 ? Math.ceil(x) : Math.floor(x)
}

指数运算符(**):这个运算符的一个特点是右结合,而不是常见的左结合。多个指数运算符连用时,是从最右边开始计算的。

2 ** 2 // 4
2 ** 3 // 8
这个运算符的一个特点是右结合,而不是常见的左结合。多个指数运算符连用时,是从最右边开始计算的。

// 相当于 2 ** (3 ** 2)
2 ** 3 ** 2
// 512
上面代码中,首先计算的是第二个指数运算符,而不是第一个。

指数运算符可以与等号结合,形成一个新的赋值运算符(**=)。

let a = 1.5;
a **= 2;
// 等同于 a = a * a;

let b = 4;
b **= 3;
// 等同于 b = b * b * b;

箭头函数有几个使用注意点:

  • 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
  • 不可以当做构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
  • 不可以使用arguments对象,该对象在函数体内不存在。如果要使用,可以使用rest参数代替。 
  • 不可以使用yield命令,因此箭头函数不能用作Generator函数。

函数默认参数&解构赋值

// 写法一
function m1({x = 0, y = 0} = {}) {
    return [x, y];
}
// 写法二
function m2({x, y} = { x: 0, y: 0 }) {
    return [x, y];
}

// 函数没有参数的情况
m1() // [0, 0]
m2() // [0, 0]

// x 和 y 都有值的情况
m1({x: 3, y: 8}) // [3, 8]
m2({x: 3, y: 8}) // [3, 8]

// x 有值,y 无值的情况
m1({x: 3}) // [3, 0]
m2({x: 3}) // [3, undefined]

// x 和 y 都无值的情况
m1({}) // [0, 0];
m2({}) // [undefined, undefined]

m1({z: 3}) // [0, 0]
m2({z: 3}) // [undefined, undefined]

ES6规定,只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。

规避限制

const doSomething = (function () {
    'use strict';
    return function(value = 1) {
        return value;
    }
}())

管道机制:即前一个函数的输出时候一个函数的输入

const pipeline = (...funcs) => val => funcs.reduce((pre, next) => next(pre), val);
const plus = a => a + 1;
const mult = a => a * 2;
const addThenMult = pipe(plus, mult);
addThenMult(5)

尾调用优化:函数调用会在内存形成一个“调用记录”,又称“调用帧”,保存调用位置和内存变量等信息。如果在函数A的内部调用函数B,那呢在A的调用帧上方,还会形成一个B的调用帧。等待C运行结束,将结果返回到A,B的调用则才会消失。如果函数B的内部还调用函数C,那就还有一个C的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”。尾调用优化,即只保留内层函数的调用帧,如果所有的函数都是尾调用,那么完全可做到每次执行时调用帧只有一项,这将大大节省内存。

注意:只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则无法进行“尾调用优化”。

// 蹦床函数
const trampoline = (fn) => {
    while(fn && fn instanceof Function){
        fn = fn()
    }
    return fn
}

Array.from()方法可将类数组对象(array-like object)和可遍历(iterable)的对象转为真正的数组。

Array.of()方法用于将一组值转换为数组。

对象中属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object],而对象中只会有最后一个[object Object],前面的均会被覆盖掉。

isPrototypeOf()、getPrototypeOf()、setPrototypeOf()用法示例:

如果Symbol的参数是一个对象,就会调用该对象的toString方法,将其转为字符串,然后再生成一个symbol值。

Symbol实例属性description,直接返回symbol的描述。

使用Set很容易实现并集(Union)、交集(Intersect)和差集(Difference)。

let s1 = [1,2,3]
let s2 = [3,4,5]
 
let union = [...new Set([...s1,...s2])]
let intersect = s1.filter(i => new Set(s2).has(i))
let difference = s1.filter(i => !new Set(s2).has(i))

  

Proxy构造函数:let proxy = new Proxy(target,handler);

target参数表示所要拦截的目标对象,handler参数也是一个对象。

下面是 Proxy 支持的拦截操作一览,一共 13 种。

  • get(target, propKey, receiver):拦截对象属性的读取,比如proxy.fooproxy['foo']

  • set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = vproxy['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.getOwnPropertyNamesObject.getOwnPropertySymbols之和。

  • Reflect.isExtensible(target) 返回一个布尔值,表示当前对象是否可扩展。

  • Reflect.preventExtensions(target) 用于让一个对象变为不可扩展。

  • Reflect.getOwnPropertyDescriptor(target, name) 用于得到指定属性的描述对象,

  • Reflect.getPrototypeOf(target) 用于读取对象的__proto__属性

  • Reflect.setPrototypeOf(target, prototype) 用于设置目标对象的原型(prototype)

设计目的

  1. 将Object对象的一些明显属于语言内部的方法放到Reflect对象上。
  2. 修改某些Object方法的返回结果,让其变得更合理。
  3. 让Object操作都变成函数行为(API操作)。
  4. Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法就能在Reflect对象上找到对应的方法,这就让Proxy对象可以方便调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,总是可以在Reflect上获取默认行为。

原生具备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不会在它的外层作用域引入变量
  • evalarguments不能被重新赋值
  • arguments不会自动反映函数参数的变化
  • 不能使用arguments.callee
  • 不能使用arguments.caller
  • 禁止this指向全局对象
  • 不能使用fn.callerfn.arguments获取函数调用的堆栈
  • 增加了保留字(比如protectedstaticinterface

script加载

默认情况下,浏览器是同步加载JavaScript脚本,即渲染引擎遇到script标签就会停下来,等到执行完脚本,再继续向下渲染。如果是外部脚本,还必须加入脚本下载的时间。

如果脚本体积过大,下载和执行的时间就会很长,因此造成浏览器堵塞,用户就会感觉浏览器卡死,没有任何反应。这显然是很不好的体验,所以浏览器允许脚本异步加载。

defer与async的区别:defer要等到整个页面在内存中正常渲染结束(DOM结构完全生成,以及其他脚本执行完成),才会执行;async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后再继续渲染。总之,defer是渲染完再执行,async是下载完就执行。另外,如果有多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本不能保证加载顺序。

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 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。 

Promise.all()

const p = Promise.all([p1,p2,p3])
  1. 只有p1,p2,p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1,p2,p3的返回值组成一个数组,传递给p的回调函数。
  2. 只要p1,p2,p3之中有一个被rejected,p的状态就变成rejected,此时第一个被rejected的实例的返回值,会传递给p的回调函数。

Promise.race()

const p = Promise.race([p1,p2,p3])

只要p1,p2,p3之中有一个实例率先改变状态,p的状态就跟着改变,那个率先改变的promise实例的返回值,就传递给p的回调函数。

Promise.allSettled()

const p = Promise.allSettled([p1,p2,p3])
  • Promise.allSettled()方法接受一个数组作为参数,数组的每个成员都是一个 Promise 对象,并返回一个新的 Promise 对象。只有等到参数数组的所有 Promise 对象都发生状态变更(不管是fulfilled还是rejected),返回的 Promise 对象才会发生状态变更。
  • 该方法返回的新的 Promise 实例,一旦发生状态变更,状态总是fulfilled,不会变成rejected。状态变成fulfilled后,它的回调函数会接收到一个数组作为参数,该数组的每个成员对应前面数组的每个 Promise 对象。

Promise.any()

const p = Promise.any([p1,p2,p3])

只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。

 Promise.try()

不知道或者不想区分,函数f是同步函数还是异步操作,但是想用 Promise 来处理它。因为这样就可以不管f是否包含异步操作,都用then方法指定下一步流程,用catch方法处理f抛出的错误。(浏览器暂未支持)

  

posted @ 2020-08-14 12:30  671_MrSix  阅读(169)  评论(0编辑  收藏  举报