ECMAScript 6学习笔记

由于受新型冠状病毒的影响,假期又延长了,又不能出门,只好在家认真学习了,就借此机会阅读完阮一峰老师的ECMAScript 6入门教程,顺便将一些es6中常用的特性进行一个整理。相当于读书时代的划重点

1:let和const

用来声明变量,用法类似于var,但所声明的变量只能在变量所在的代码块内有效。
相同点:
都是用于块级作用域,不存在变量提升,一定要在声明后才可以使用,在声明前使用会报错。
不同点:
let定义的变量可以修改值。const定义的变量为常量,赋值后不能在修改。

2:变量解构赋值

解构是指按照一定模式,从数组和对象中提取值,对变量进行赋值。

1.数组解构

let [a, b, c] = [1, 2, 3];
相当于

let a = 1;
let b = 2;
let c = 3;

2.对象解构

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

3.解构用途

交换变量的值

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

从函数返回多个值

// 返回一个数组
function example() {
  return [1, 2, 3];}
let [a, b, c] = example();
// 返回一个对象
function example() {
  return {
    foo: 1,
    bar: 2
  };
}
let { foo, bar } = example();

提取JSON数据

let jsonData = {
  id: 42,
  status: "OK",
  data: [867, 5309]
};
let { id, status, data: number } = jsonData;
console.log(id, status, number);
// 42, "OK", [867, 5309]

输入模块的指定方法

const { SourceMapConsumer, SourceNode } = require("source-map");

3.字符串扩展

1.使用for...of遍历字符串。

      var str = 'abcd';
        for (let value of str){
            console.log(value);
        }

2.模板字符串

模板字符串是增强版的字符串,使用`来表示字符串的使用,也可以用来定义多行字符串,或者在字符串中使用变量。

//普通字符串
        let a = `this is str`;
        console.log(a);
        //多行字符串
        let glsl = `void main(){
            var a;
        }`
        console.log(glsl);
        //包含变量的字符串
        let x = 1,y = 2;
        let b = `x:${x}\n y:${y}`
        console.log(b);

3.includes(), startsWith(), endsWith()

之前判断一个字符串中是否包含某个字符串时,使用的是indexOf方法,在es6中新增了三个方法进行判断字符串中是否包含指定的字符。

includes():判断字符串中是否包含某个字符串。
startsWith():判断字符串是否以某个字符串开头。
endsWith():判断字符串是否以某个字符串结尾。

        var string = `这是一个字符串。`
        console.log(string.includes('字'));//true
        console.log(string.startsWith('这'));//true
        console.log(string.endsWith('。'));//true

4.repeat(n)

表示将原字符串复制n次,返回一个新的字符串。

console.log(string.repeat(3));
//这是一个字符串。这是一个字符串。这是一个字符串。

5.padStart(),padEnd()

根据指定的参数在开头或者结尾补全字符串。如:

console.log('x'.padStart(5, 'ab')) // 'ababx'
console.log('x'.padStart(4, 'ab')) // 'abax'
console.log('x'.padEnd(5, 'ab')) // 'xabab'
console.log('x'.padEnd(4, 'ab')) // 'xaba'

6.trimStart(),trimEnd()

trim()用于消除前后的空格,trimStart()用于消除字符串开头的空格。trimEnd()用于消除字符串结尾的空格。

4.数值的扩展

1.Number.isFinite(), Number.isNaN()

isFinite用于判断一个数值是否是有限的(即不是Infinity)。如果参数类型不是数值,Number.isFinite一律返回false

Number.isFinite(15); // true
Number.isFinite(0.8); // true
Number.isFinite(NaN); // false
Number.isFinite(Infinity); // false
Number.isFinite(-Infinity); // false
Number.isFinite('foo'); // false
Number.isFinite('15'); // false
Number.isFinite(true); // false

Number.isNaN()用来检查一个值是否为NaN。如果参数类型不是NaNNumber.isNaN一律返回false

Number.isNaN(NaN) // true
Number.isNaN(15) // false
Number.isNaN('15') // false
Number.isNaN(true) // false
Number.isNaN(9/NaN) // true
Number.isNaN('true' / 0) // true
Number.isNaN('true' / 'true') // true

与全局的isFinite()isNaN()有什么区别?
全局的isFinite()isNaN()会先调用Number()将非数值转成数值。而这两个新方法只对数值有效,Number.isFinite()对于非数值一律返回false, Number.isNaN()只有对于NaN才返回true,非NaN一律返回false
1.Number.isInteger()
使用Number.isInteger()可以判断变量是否为整数类型。

console.log(Number.isInteger(12.5));//false
console.log(Number.isInteger(12));//true

2.Math.trunc()

Math.trunc()去除小数部分,返回整数部分。

console.log(Math.trunc(12.33));//12

5.函数的扩展

1.函数参数的默认值

在定义函数的时候,可以在函数中使用默认值,当调用函数时,传递的变量值为空时,如果有默认值,使用变量的时候将使用默认值,通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。如下所示:

  function init(a,b,c = 10){
            console.log(a);//12
            console.log(b);//1
            console.log(c);//10
        }
        init(12,1);

2.箭头函数

ES6 允许使用“箭头”(=>)定义函数。如下两个函数是一样的。

var fun = function(a,b){
            console.log(a+b);
        }
        var fun1 = (a,b) => {
            console.log(a+b);
        }
        fun(1,2);
        fun1(1,2);

箭头函数注意点

  • 函数体内的this对象,是定义时所在的对象,不是使用是所在的对象。
  • 不可当作构造函数来使用,也就是不能使用new来创建对象。
  • 不可使用arguments对象,如果要使用类似的功能,可以使用 rest
  • 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
    关于第一点函数体内的this对象,是定义时所在的对象,示例如下所示:
      function foo() {
            setTimeout(() => {
                console.log('id:', this.id);//42
            }, 100);
            setTimeout(function(){
                console.log('id:', this.id);//21
            },200)
        }
        var id = 21;
        foo.call({ id: 42 });

不适用场合

  • 第一个场合是定义对象的方法,且该方法内部包括this。
  • 第二个场合是需要动态this的时候,也不应使用箭头函数。

3.尾调用

尾调用就是指某个函数的最后一步是调用另一个函数。如下所示:

function f(x) {
  if (x > 0) {
    return m(x)
  }
  return n(x);
}

上面代码中,函数mn都属于尾调用,因为它们都是函数f的最后一步操作。

6.数组扩展

1.扩展运算符

扩展运算符是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。

扩展运算符的作用

  • 替代函数的 apply 方法
    由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了。
function fun(a,b,c){
            console.log(a+b+c);
        }
        fun.apply(null,[1,2,3]);
        fun(...[1,2,3])
  • 复制数组
        var a = [1,2,3];
        var b = [...a];
        b[1]= 0;
        console.log(a);//1,2,3
        console.log(b);//1,0,3
  • 合并数组
        var a = [1,2,3];
        var b = [...a];
        b[1]= 0;
        console.log(a);//1,2,3
        console.log(b);//1,0,3
        var c = [...a,...b];
        console.log(c);

2.Array.from()

Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。

let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};

// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']

// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

3.Array.of()

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

Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1

7.对象的扩展

1.属性的可枚举性和遍历

对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。述对象的enumerable属性,称为“可枚举性”,如果该属性为false,就表示某些操作会忽略当前属性。

目前,有四个操作会忽略enumerable为false的属性。

  • for...in循环:只遍历对象自身的和继承的可枚举的属性。
  • Object.keys():返回对象自身的所有可枚举的属性的键名。
  • JSON.stringify():只串行化对象自身的可枚举的属性。
  • Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。

2.属性遍历

对象的属性遍历一共有5种方法。

1.for...in

for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。

var obj = {
            a:'a1',
            b:'b1',
            c:12
        }
        ergodic();
        function ergodic(){
            for(let key in obj){
                console.log(key + '=' + obj[key]);
            }
        }

2.Object.keys(obj)

Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。

var obj = {
            a:'a1',
            b:'b1',
            c:12
        }
        ergodic();
        function ergodic(){
            var array = Object.keys(obj);
            array.forEach(key => {
                console.log(key + '=' + obj[key]);
            })
        }

3.Object.getOwnPropertyNames(obj)

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

var obj = {
            a:'a1',
            b:'b1',
            c:12
        }
        ergodic();
        function ergodic(){
            var array = Object.getOwnPropertyNames(obj);
            array.forEach(key => {
                console.log(key + '=' + obj[key]);
            })
        }

4.Object.getOwnPropertySymbols(obj)

Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。

5.Reflect.ownKeys(obj)

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

var obj = {
            a:'a1',
            b:'b1',
            c:12
        }
        ergodic();
        function ergodic(){
            var array = Reflect.ownKeys(obj);
            array.forEach(key => {
                console.log(key + '=' + obj[key]);
            })
        }

3.扩展运算符

对象的扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。对象的扩展运算符等同于使用Object.assign()方法。

var obj = {
            a:'a1',
            b:'b1',
            c:12,
        }
        var obj1 = {...obj};
        console.log(obj1);

4.Null 判断运算符

ES2020引入了一个新的Null判断运算符??。它的行为类似||,但是只有运算符左侧的值为nullundefined时,才会返回右侧的值。

5.Object.is()

用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。但与===不同之处只有两个:一是+0不等于-0,二是NaN等于自身。

+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

6.Object.assign()

Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。

const target = { a: 1 };

const source1 = { b: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

注意点

  • Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
    -一旦遇到同名属性,Object.assign的处理方法是替换,而不是添加。

8.Symbol

Symbol是一种新的数据类型,属性名使用Symbol类型是独一无二的,不会跟其它的属性名发生冲突。Symbol是一个原始类型的值,不是对象,不能添加给它添加属性。Symbol可以接受一个字符串作为参数,如果参数是一个对象,会先调用它的toString方法转成字符,再生成一个Symbol值。Symbol函数的参数只是当前Symbol值的一个描述,因些两个参数相同的Symbol是不相等的。Symbol值不能与其它类型的值进行运算。

// 没有参数的情况
let s1 = Symbol();
let s2 = Symbol();

s1 === s2 // false
let sym = Symbol('My symbol');

"your symbol is " + sym
// TypeError: can't convert symbol to string
`your symbol is ${sym}`
// TypeError: can't convert symbol to string

Symbol值可以转化为字符串和布尔类型的值。

let sym = Symbol('My symbol');

String(sym) // 'Symbol(My symbol)'
sym.toString() // 'Symbol(My symbol)'

let sym = Symbol();
Boolean(sym) // true

1.Symbol.for()

Symbol.for()可以接受一个字符串作为参数,然后生成一个Symbol值,如果参数相同,生成的Symbol值也相等。

console.log(Symbol.for('s') === Symbol.for('s'));//true
console.log(Symbol('s') === Symbol('s'));//false

通过Symbol.for()创建的Symbol值可以使用Symbol.keyFor()获取参数名。

let s3 = Symbol.for('s3');
console.log(Symbol.keyFor(s3));//s3
let s4 = Symbol('s4');
console.log(Symbol.keyFor(s4));//undefined

2.Symbol.hasInstance

对象的Symbol.hasInstance属性指向一个内部方法,当其它对象在使用instanceof时,会调用这个方法。

       const Even = {
            [Symbol.hasInstance](obj){
                return Number(obj) % 2 === 0;
            }
        }
        console.log(1 instanceof Even) // false
        console.log(2 instanceof Even) // true

3.Symbol.match

对象的Symbol.match属性指向一个函数,当执行str.match(myObject)时,如果该属性存在时,将返回该方法的返回值。

        const MyMatcher = {
            [Symbol.match](str){
                return 'haha'
            }
        }
        console.log('test'.match(MyMatcher));//haha

9.Set 和 Map 数据结构

1.Set

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

Set常用方法

  • Set.prototype.constructor:造函数,默认就是Set函数。
  • Set.prototype.size:返回成员的数量。
  • Set.prototype.add(value):往Set中添加成员。
  • Set.prototype.delete(value):删除指定的成员。
  • Set.prototype.has(value):判断是否包含value
  • Set.prototype.clear():清空所有成员。
  • Set.prototype.keys():返回键名的遍历器
  • Set.prototype.values():返回键值的遍历器
  • Set.prototype.entries():返回键值对的遍历器
  • Set.prototype.forEach():使用回调函数遍历每个成员

2.WeakSet

WeakSetSet类似,成员的值是唯一的,没有重复的值。但WeakSet只能存储对象。并且WeakSet中存储的是弱引用,当其它对象不再引用该对象时,垃圾回收器将自动回收该对象的内存。WeakSet适合临时存放一组对象,以及存放跟对象绑定的信息。只要这些对象在外部消失,它在 WeakSet 里面的引用就会自动消失。WeakSet不能被遍历。

常用方法

  • WeakSet.prototype.add(value):向 WeakSet实例添加一个新成员。
  • WeakSet.prototype.delete(value):清除 WeakSet 实例的指定成员。
  • WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在 WeakSet实例之中。

3.Map

标准对象的键值是字符串或者Symbol,不能使用对象作为键,如果使用对象作为键,将自动转成字符串。如果需要以对象作为键,将会用到Map。如果对同一个键多次赋值,后面的值将覆盖前面的值。

        var map = new Map();
        var obj = {a:'a'};
        map.set(obj,'这是对象作为key');
        map.set('key','haha');
        map.set('key','haha1');
        console.log(map.get('key'));//haha1
        console.log(map.get(obj));//这是对象作为key

常用方法

  • Map.prototype.size():返回Map的长度。
  • Map.prototype.set(key, value)set方法设置键名key对应的键值为value,然后返回整个 Map结构。如果key已经有值,则键值会被更新,否则就新生成该键。
  • Map.prototype.get(key)get方法读取key对应的键值,如果找不到key,返回undefined
  • Map.prototype.has(key)has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。
  • Map.prototype.delete(key)delete方法删除某个键,返回true。如果删除失败,返回false
  • Map.prototype.clear():清空所有值。
  • Map.prototype.keys():返回键名的遍历器。
  • Map.prototype.values():返回键值的遍历器。
  • Map.prototype.entries():返回所有成员的遍历器。
  • Map.prototype.forEach():遍历 Map 的所有成员。

WeakMap

WeakMap结构与Map结构类似,也是用于生成键值对的集合。

WeakMap与Map的区别

  • 首先,WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。
  • 其次,WeakMap的键名所指向的对象,不计入垃圾回收机制。一旦不再需要时,我们就必须手动删除引用。

10.Proxy

Proxy是一种代理,在目标对象之前设置一层‘拦截’,在访问该对象时,都要先访问这个代理对象,通过这个代理对象可以进行过滤和修改等操作。

var proxy = new Proxy(target, handler);

new Proxy生成一个Proxy实例,target表示需要拦截的目标对象,handler用于定制拦截行为。如下所示为一个拦截get方法的代码。

        var o = new Proxy({},{
            get:function(target,key){
                return 'haha'
            }
        })
        console.log(o.name);//haha
        console.log(o['type']);//haha

注意,要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。

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)

11.Reflect

ReflectProxy一样都可以操作对象。使用Reflect可以拿到对象原始的方法。

// 老写法
'assign' in Object // true

// 新写法
Reflect.has(Object, 'assign') // true

静态方法

  • Reflect.apply(target, thisArg, args)
  • Reflect.construct(target, args)
  • Reflect.get(target, name, receiver)
  • Reflect.set(target, name, value, receiver)
  • Reflect.defineProperty(target, name, desc)
  • Reflect.deleteProperty(target, name)
  • Reflect.has(target, name)
  • Reflect.ownKeys(target)
  • Reflect.isExtensible(target)
  • Reflect.preventExtensions(target)
  • Reflect.getOwnPropertyDescriptor(target, name)
  • Reflect.getPrototypeOf(target)
  • Reflect.setPrototypeOf(target, prototype)

12.Promise

Promise是一种异步编程的解决方案,相对传统的回调函数和事件要强大。Promise是一个对象,它可以获取异步操作的消息。

Promise特点

  • Promise的状态不受外界的影响。Promise对象代表一个操作,它包含三种状态:pending(进行中),fulfilled(已成功)和rejected(已失败)。只有异步操作的结果才能决定是哪种状态,其它操作无法改变Promise的状态。
  • 一旦状态改变,就不会再变, 任何时候都可以获取到这个结果。Promise的状态变化只有两种,从
    pending(进行中)变成fulfilled(已成功),或者从pending(进行中)变成rejected(已失败)。
    Promise缺点
  • Promise创建后会立即执行,无法中途取消。
  • 如果不设置回调函数,Promise内部发生错误时无法反应到外部。
  • 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

1.基本用法

Promise对象是一个构造函数,通过构造函数来创建Promise实例。如下所示:

        let promise = new Promise(function(resolve,reject){
            setTimeout(resolve,500);
        });
        promise.then(function(value){
            console.log('promise执行结束')
        },function(error){
            console.log('promise执行时发生了错误')
        })

Promise构造函数接受一个函数作为参数,函数包括resolvereject两个函数,resolve的作用是将Promise对象的状态从pending(进行中)变成fulfilled(已成功)。在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

注意
因为立即 resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。,如下所示:

      let p = new Promise(function(resolve,reject){
            console.log(1);
            resolve(2);
            console.log(3);
        })
        p.then(function(value){
            console.log(value);
        })
        //1
        //3
        //2

resolve函数的参数除了正常的值以外,还可能是另一个 Promise实例,比如像下面这样。

let p1 = new Promise(function(resolve,reject){
            setTimeout(() => {
                reject('执行错误')
            }, 3000);
        })
        
        let p2 = new Promise(function(resolve,reject){
            setTimeout(() => {
                resolve(p1);
            }, 1500);
        })
        p2.then(function(value){
            console.log(value)
        }).catch(error =>{
            console.log(error)
        })

2.Promise.prototype.then()

then方法是Promise原型对象上的一个方法,该方法的作用是Promise在发生状态改变后的回调函数。then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。then方法将返回一个Promise对象,因此可以采用链式写法。如下所示:

        let p3 = new Promise(function(resolve,reject){
            resolve('p3');
        })
        p3.then(function(value){
            console.log(value);
            return new Promise(function(resolve,reject){
                setTimeout(() => {
                    resolve('p3返回的Promise');
                }, 1000);
            })
        }).then(function(value){
            console.log(value)
        })
        //p3
        //p3返回的Promise'

3.Promise.prototype.catch()

Promise.prototype.catch方法是.then(null, rejection).then(undefined, rejection)的别名,用于指定发生错误时的回调函数。如下所示:

let p3 = new Promise(function(resolve,reject){
            resolve('p3');
        })
        p3.then(function(value){
            console.log(value);
            return new Promise(function(resolve,reject){
                setTimeout(() => {
                    reject(new Error('p3的Promise返回错误信息'));
                }, 1000);
            })
        }).then(function(value){
            console.log('result'+value)
        }).catch(function(error){
            console.log(error);
        })
        //p3
        //p3的Promise返回错误信息'

如果Promise的状态已经变成了resolved,再抛出异常是无效的。因为 Promise 的状态一旦改变,就永久保持该状态,不会再变了。如下所示:

      const promise = new Promise(function(resolve, reject) {
            resolve('ok');
            throw new Error('test');
        });
        promise.then(function(value) { 
            console.log(value) 
        })
        .catch(function(error) { 
            console.log(error) 
        });

Promise对象的错误具有‘冒泡’性质,它会一直向后传递,直到捕获为止。错误总是被下一个catch捕获,一般来说,不要在then方法里面定义 Reject状态的回调函数(即then的第二个参数),总是使用catch方法。如下所示:

const promise = new Promise(function(resolve, reject) {
            reject('发生了错误');
        });
        promise.then(function(value) { 
            console.log("success-"+value) 
        })
        .catch(function(error) { 
            console.log("error="+error) 
        });
        //error=发生了错误

4.Promise.prototype.finally()

finally方法是不论Promise的状态是什么样的,都会执行finally方法,如下所示:

const promise = new Promise(function(resolve, reject) {
            reject('发生了错误');
        });
        promise.then(function(value) { 
            console.log("success-"+value) 
        })
        .catch(function(error) { 
            console.log("error="+error) 
        }).finally(function(value){
            console.log('finally');
        });
        //error=发生了错误
        //finally

finally方法不接受任何参数。finally方法总是会返回原来的值。

5.Promise.all()

Promise.all()将多个Promise包装成一个Promise实例。

let p1 = new Promise(function(resolve,reject){
            resolve('p1');
        })
        let p2 = new Promise(function(resolve,reject){
            resolve('p2');
        })
        let p3 = new Promise(function(resolve,reject){
            resolve('p3');
        })
        let p= Promise.all([p1,p2,p3]).then(function(value){
            console.log('success:' + value)
        }).catch(function(error){
            console.log('fail='+error);
        })

p的状态由p1p2p3决定,分成两种情况。

  • 只有p1p2p3的状态全为fulfilled时,p的状态才为fulfilled。此时p1p2p3的返回值组成一个数组,传递给p的回调函数。
  • 只要p1p2p3中有一个状态为rejected时,p的状态就为rejected。此时第一个被reject的实例的返回值,会传递给p的回调函数。

注意,如果作为参数的 Promise实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()catch方法。如下所示:

let p1 = new Promise(function(resolve,reject){
            resolve('p1');
        })
        let p2 = new Promise(function(resolve,reject){
            reject('p2');
        }).catch(function(error){

        })
        let p3 = new Promise(function(resolve,reject){
            resolve('p3');
        })
        let promise = Promise.all([p1,p2,p3]).then(function(value){
            console.log('success:' + value)
        }).catch(function(error){
            console.log('fail='+error);
        })

6.Promise.race()

Promise.race()Promise.all()类似,都可以将多个Promise包装成一个Promise实例。 不同之处是只要有一个实例率先改变状态,Promise.race()实例的结果也将改变状态,如下所示:

let p1 = new Promise(function(resolve,reject){
            resolve('p1');
        })
        let p2 = new Promise(function(resolve,reject){
            reject('p2');
        }).catch(function(error){

        })
        let p3 = new Promise(function(resolve,reject){
            resolve('p3');
        })
        let promise = Promise.race([p1,p2,p3]).then(function(value){
            console.log('success:' + value)
        }).catch(function(error){
            console.log('fail='+error);
        })
        //success:p1

7.Promise.allSettled()

Promise.allSettled()也是接受一组 Promise 实例作为参数,包装成一个新的 Promise实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。该方法由 ES2020。如下所示:

      let p1 = new Promise(function(resolve,reject){
            resolve('p1');
        })
        let p2 = new Promise(function(resolve,reject){
            reject('p2');
        })
        let p3 = new Promise(function(resolve,reject){
            resolve('p3');
        })
        let promise = Promise.allSettled([p1,p2,p3]).then(function(value){
            debugger
            console.log('success:' + value)
        }).catch(function(error){
            console.log('fail='+error);
        })

该方法返回的新的 Promise 实例,一旦结束,状态总是fulfilled,不会变成rejected。状态变成fulfilled后,Promise 的监听函数接收到的参数是一个数组,每个成员对应一个传入Promise.allSettled()Promise实例。

8.Promise.resolve()

Promise.resolve()将现有对象转化为Promise对象。如下所示:

const jsPromise = Promise.resolve($.ajax('/whatever.json'));

Promise.resolve方法的参数分成四种情况。

  • 参数是一个 Promise 实例,如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
  • 参数是一个thenable对象。thenable对象指的是具有then方法的对象,Promise.resolve方法会将这个对象转为 Promise对象,然后就立即执行thenable对象的then方法。
  • 参数不是具有then方法的对象,或根本就不是对象。如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved
  • 不带有任何参数。Promise.resolve()方法允许调用时不带参数,直接返回一个resolved状态的 Promise对象。

9.Promise.reject()

Promise.reject()返回一个新的Promise对象,该对象的状态为rejected

13.Iterator 和 for...of 循环

Iterator是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据类型只要部署了Iterator接口,就能完成遍历操作。

Iterator作用

  • 为各种数据类型提供一个统一的,简便的访问接口。
  • 使用数据结构的成员按照某种次序进行排列。
  • 使用 for...of进行遍历。

1.默认的Iterator 接口

凡是部署了Symbol.iterator属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。

原生具备 Iterator 接口的数据结构如下。

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的 arguments 对象
  • NodeList 对象

如下所示:

       var array = [1,2,3,4];
        var iterator = array[Symbol.iterator]();
        console.log(iterator.next());
        console.log(iterator.next());
        console.log(iterator.next());
        console.log(iterator.next());
        console.log(iterator.next());

14.Generator

Generator是一种异步编程的解决方案,语法行为与传统的函数不同。Generator是一种状态机函数,封装了多个内部状态。执行Generator函数会返回一个遍历器对象,可依次遍历Generator函数内部的每一个状态。

Generator 特征

  • function与函数名之间有一个*。如function* test(){}
  • 函数内部可以使用yield表达式,定义不同的内部状态。
          function* helloGenerator(){
                yield 'hello';
                yield 'Generator';
                return 'end'
            }
            var it = helloGenerator();
            for(var item of it){
                console.log(item);
            }

Generator函数的调用跟调用普通函数一样,直接在函数名后面加一个括号,但与普通函数不一样的是,Generator函数执行后返回的是一个Iterator对象,可以调用next方法,或者使用for...of进行遍历。

1.yield 表达式

由于Generator函数返回的是一个Iterator对象,只有在执行next方法时才会遍历下一个状态,通过使用yield表达式可以设置一种暂停标注。当执行完next方法后,将yield后面的值作为value返回,执行完一条后并暂停,只有当调用下一个next方法才会往下执行并继续查找yield表达式,当查找不到yield时,将返回并结束遍历。yield只能在Generator函数中使用,在其它地方使用时将报错。

2.Generator.prototype.throw()

Generator函数返回的对象,都有一个throw方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获。

3.yield* 表达式

使用yield*表达式,用来在一个 Generator 函数里面执行另一个 Generator 函数。

function* foo() {
  yield 'a';
  yield 'b';
}
function* bar() {
  yield 'x';
  yield* foo();
  yield 'y';
}

// 等同于
function* bar() {
  yield 'x';
  yield 'a';
  yield 'b';
  yield 'y';
}

// 等同于
function* bar() {
  yield 'x';
  for (let v of foo()) {
    yield v;
  }
  yield 'y';
}

for (let v of bar()){
  console.log(v);
}
// "x"
// "a"
// "b"
// "y"

4.Generator用途

  • 异步操作的同步化表达
    通过使用Generator可以处理异步函数,用来代替回调函数。将异步操作写在yield表达式上,等调用next方法后再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield表达式下面,反正要等到调用next方法时再执行。如下所示:
function* loadUI() {
  showLoadingScreen();
  yield loadUIDataAsynchronously();
  hideLoadingScreen();
}
var loader = loadUI();
// 加载UI
loader.next()

// 卸载UI
loader.next()
  • 控制流程管理
    利用for...of循环会自动依次执行yield命令的特性,提供一种更一般的控制流管理的方法。如下所示:
let steps = [step1Func, step2Func, step3Func];

function* iterateSteps(steps){
  for (var i=0; i< steps.length; i++){
    var step = steps[i];
    yield step();
  }
}
  • 部署 Iterator 接口
    利用 Generator 函数,可以在任意对象上部署 Iterator 接口。如下所示:
function* iterEntries(obj) {
  let keys = Object.keys(obj);
  for (let i=0; i < keys.length; i++) {
    let key = keys[i];
    yield [key, obj[key]];
  }
}

let myObj = { foo: 3, bar: 7 };

for (let [key, value] of iterEntries(myObj)) {
  console.log(key, value);
}

// foo 3
// bar 7
  • 作为数据结构
    Generator可以看作是数据结构,更确切地说,可以看作是一个数组结构,因为 Generator 函数可以返回一系列的值,这意味着它可以对任意表达式,提供类似数组的接口。如下所示:
function* doStuff() {
  yield fs.readFile.bind(null, 'hello.txt');
  yield fs.readFile.bind(null, 'world.txt');
  yield fs.readFile.bind(null, 'and-such.txt');
}

for (task of doStuff()) {
  // task是一个函数,可以像回调函数那样使用它
}

5.Generator 函数的异步应用

异步是指一个任务不是连续完成的,可以理解成该任务被分成为两段,先执行第一段,然后再执行其它的任务,等做好准备后再执行第二段的任务。

比如,有一个任务是读取文件进行处理,任务的第一段是向操作系统发出请求,要求读取文件。然后,程序执行其他任务,等到操作系统返回文件,再接着执行任务的第二段(处理文件)。这种不连续的执行,就叫做异步。

6.async 函数

async函数是Generator的方法糖。async就是将Generator函数的*换成了async,将yield换成了awaitasync函数返回一个Promise对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

      let readFile = function(){
            return new Promise(function(resolve,reject){
                setTimeout(() => {
                    resolve('文件读取成功')
                }, 2000);
            })
        }

        let asyncReadFile = async function(){
            let rf1 = await readFile()
            let rf2 = await readFile();
            console.log(rf1.toString());
            console.log(rf2.toString());
        }
        asyncReadFile();

7.await命令

await命令后面是一个Promise对象,如果是其它值,将直接返回对应的值。如下所示:

async function f() {
  // 等同于
  // return 123;
  return await 123;
}

f().then(v => console.log(v))
// 123

await命令后面的Promise对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到。如下所示:

async function f() {
  await Promise.reject('出错了');
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出错了

任何一个await语句后面的Promise 对象变为reject状态,那么整个async函数都会中断执行。

async function f() {
  await Promise.reject('出错了');
  await Promise.resolve('hello world'); // 不会执行
}

使用try...catch结构,实现多次重复尝试。

const superagent = require('superagent');
const NUM_RETRIES = 3;

async function test() {
  let i;
  for (i = 0; i < NUM_RETRIES; ++i) {
    try {
      await superagent.get('http://google.com/this-throws-an-error');
      break;
    } catch(err) {}
  }
  console.log(i); // 3
}

test();

上面代码中,如果await操作成功,就会使用break语句退出循环;如果失败,会被catch语句捕捉,然后进入下一轮循环。

使用注意点

  • await命令后面的Promise对象,运行结果可能是rejected,最好将 await命令放在try...catch中。
  • 多个 await命令如果不存在先后顺序,最好同时时行。
  • await命令只能在async中使用,在其它地方使用将会报错。

15.

  • async函数可以保留运行堆栈。

8.async函数实现原理

async的实现原理就是将Genernator函数与自动执行器包装在一个函数里。如下所示:

async function fn(args) {
  // ...
}

// 等同于

function fn(args) {
  return spawn(function* () {
    // ...
  });
}

所有的async函数都可以写成上面的第二种形式,其中的spawn函数就是自动执行器。

16.Class

ES6可以使用class关键字创建类,相比使用构造函数创建类更具有面向对象的思想,如下所示:

        class Point{
            constructor(x,y){
                this.x = x;
                this.y = y;
            }
            toString(){
                console.log("x="+this.x + " y="+this.y);
            }
        }
        let point = new Point(12,13);
        point.toString();

类的内部所有定义的方法,都是不可枚举的(non-enumerable)。如下所示:

class Point{
            constructor(x,y){
                this.x = x;
                this.y = y;
            }
            toString(){
                console.log("x="+this.x + " y="+this.y);
            }
        }
        let point = new Point(12,13);
        point.toString();
        console.log(Object.keys(Point.prototype));//[]
        console.log(Object.keys(point));// ["x", "y"]

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

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

class MyClass {
  constructor() {
    // ...
  }
  get prop() {
    return 'getter';
  }
  set prop(value) {
    console.log('setter: '+value);
  }
}

let inst = new MyClass();

inst.prop = 123;
// setter: 123

inst.prop
// 'getter'

2.Class表达式

函数一样,类也可以使用表达式的形式定义。如下所示:

        const Circle = class C{
            getRaidus(){
                console.log('圆的半径为10')
            }
        }
        const circle = new Circle();
        circle.getRaidus();

3.静态方法

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

class Foo {
  static classMethod() {
    return 'hello';
  }
}

Foo.classMethod() // 'hello'

var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function

4.实例属性

Class定义的类中,添加属性可以在构造函数中添加,也可以在类的顶层定义属性,如下所示:

      const Circle = class C{
            raidus = 20;
            constructor(){
                this.x = 10;
                this.y = 10;
            }
            getRaidus(){
                console.log(`x=${this.x};x=${this.y};radius=${this.raidus};`)
            }
        }
        const circle = new Circle();
        circle.getRaidus();

5.静态属性

静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性。

// 老写法
class Foo {
  // ...
}
Foo.prop = 1;

// 新写法
class Foo {
  static prop = 1;
}

6.私有属性

class加了私有属性。方法是在属性名之前,使用#表示。如下所示:

        class Foo {
            #privateValue = 42;
            getPrivateValue() {
                return this.#privateValue;
            }
        }

        var foo = new Foo();
        console.log(foo.getPrivateValue())

7.继承

Class可以通过extends关键字实现继承,这比 ES5的通过修改原型链实现继承,要清晰和方便很多。如下所示:

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

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

class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y); // 调用父类的constructor(x, y)
    this.color = color;
  }

  toString() {
    return this.color + ' ' + super.toString(); // 调用父类的toString()
  }
}

子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。如果子类没有定义constructor方法,这个方法会被默认添加,代码如下。也就是说,不管有没有显式定义,任何一个子类都有constructor方法。

8.super

super既可以当作函数来使用,也可以使用对象来使用。

  • super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数。
class A {}

class B extends A {
  constructor() {
    super();
  }
}
  • super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
class A {
  p() {
    return 2;
  }
}

class B extends A {
  constructor() {
    super();
    console.log(super.p()); // 2
  }
}

let b = new B();

super指向父类的原型对象,所以定义在父类实例上的属性和方法,无法通过super来调用的。

9.Object.getPrototypeOf()

Object.getPrototypeOf可以用来从子类上获取父类。

Object.getPrototypeOf(ColorPoint) === Point
// true

可以使用这个方法判断,一个类是否继承了另一个类。

10.类的 prototype 属性和proto属性

Class创建的类,同样有prototype__proto__属性,同样存在两条继承链。

  • 子类的__proto__属性,表示构造函数的继承,总是指向父类。。
  • 子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。
    class A {

        }

        class B extends A {

        }
        console.log(B.__proto__ === A);//true
        console.log(B.prototype.__proto__ === A.prototype);//true

11.实例的 proto 属性

子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性,也就是说子类原型的原型是父类的原型。

      class A {

        }

        class B extends A {

        }
        let a = new A();
        let b = new B();
        console.log(b.__proto__.__proto__ === a.__proto__);//true

17.Module

ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";。

严格模式

  • 变量必须声明后再使用
  • 函数的参数不能有同名属性,否则报错
  • 不能使用with语句
  • 不能对只读属性赋值,否则报错
  • 不能使用前缀 0 表示八进制数,否则报错
  • 不能删除不可删除的属性,否则报错
  • 不能删除变量delete prop,会报错,只能删除属性delete global[prop]
  • eval不会在它的外层作用域引入变量
  • evalarguments不能被重新赋值
  • arguments不会自动反映函数参数的变化
  • 不能使用arguments.callee
  • 不能使用arguments.caller
  • 禁止this指向全局对象
  • 不能使用fn.callerfn.arguments获取函数调用的堆栈
  • 增加了保留字(比如protectedstaticinterface

1.export 命令

模块主要由两个命令组成,exportimport命令。export用于向外部导出接口。import用于引入外部导出的接口。一个单独的文件就是一个模块,模块类的变量和函数在外部无法获取,如果希望外部能够访问,需要使用export输入变量。export命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错。

export const url = 'http://www.gzcopright.cn';
function test (){

};

function util (){

}
export {test,util as Util};

2.import命令

使用export导出的变量,在其它JS文件中使用import可以加载模块。import命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面,改写接口。import后面的from指定模块文件的位置,可以是相对路径,也可以是绝对路径,.js后缀可以省略。import命令具有提升效果,会提升到整个模块的头部,首先执行,这种行为的本质是,import命令是编译阶段执行的,在代码运行之前。

import{url,Util,log} from './module.js'

function test(){
    console.log(url)
}

test();

如果多次执行import语句,只会执行一次,如下所示:

import 'lodash';
import 'lodash';

3.模块的整体加载

除了指定加载某个输出值,可以使用*来加载所有输出的变量,如下所示:

import * as obj from './module.js'

function test(){
    console.log(obj.url)
}

test();

4.export default 命令

export default用于指定模块的默认输出,一个模块只能有一个默认输出。export default输出的命令在使用import时,不需要使用{},并且名称可以随便取。如下所示:

export default function () {
  console.log('foo');
}

// import-default.js
import customName from './export-default';
customName(); // 'foo'

export default命令其实只是输出一个叫做default的变量,所以它后面不能跟变量声明语句。

// 正确
export var a = 1;

// 正确
var a = 1;
export default a;

// 错误
export default var a = 1;

5.export 与 import 的复合写法

如果在同一模块中,先输入后输出同一个模块,importexport可以写在一起。

export { foo, bar } from 'my_module';

// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };

6.模块的继承

模块之间是可以继承的,如下所示,circleplus模块,继承了circle模块。

// circleplus.js

export * from 'circle';
export var e = 2.71828182846;
export default function(x) {
  return Math.exp(x);
}

上面代码中的export *,表示再输出circle模块的所有属性和方法。

7.import()

使用import()可以动态加载模块,使用方法跟import一样,唯一的区别就是import需要放在代码的顶层,不支持动态导入。

适用场合

  • 按需加载,import()可以在需要的时候,再加载某个模块。
button.addEventListener('click', event => {
  import('./dialogBox.js')
  .then(dialogBox => {
    dialogBox.open();
  })
  .catch(error => {
    /* Error handling */
  })
});
  • 条件加载。import()可以放在if代码块,根据不同的情况,加载不同的模块。
if (condition) {
  import('moduleA').then(...);
} else {
  import('moduleB').then(...);
}
  • 动态的模块路径。import()允许模块路径动态生成。
import(f())
.then(...);

上面代码中,根据函数f的返回结果,加载不同的模块。

18.Module 的加载实现

加载js文件,使用的是<script>,默认是同步加载,既浏览器引擎解析dom时,如果碰到了script标签时会先加载js脚本。当js文件太大时,会阻塞线程出现卡死的情况,所以浏览器允许脚本异步加载,下面就是两种异步加载的语法。

<script src="path/to/myModule.js" defer></script>
<script src="path/to/myModule.js" async></script>

deferasync都支持异步加载脚本。但两者有一定的区别

  • defer加载的脚本需要等面布渲染完成后再运行,如果有多个script使用defer时,每个文件是按顺序执行了。
  • async是加载完后就执行,如果有多个script使用async时,顺序是不固定的。

浏览器加载ES6模块,也使用<script>标签,但是要加入type="module"属性。浏览器对于带有type="module"<script>,都是异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于打开了<script>标签的defer属性。

1.ES6 模块与 CommonJS 模块的差异

  • ES6 模块输出的是值的引用,而CommonJS 模块输出的是值拷贝。
  • ES6 模块是编译时加载接口,而CommonJS 模块是运行时加载。

19.编程风格

  • let 取代 var,常量使用const
  • 静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。
  • 使用数组成员对变量赋值时,函数的参数如果是对象的成员,如果函数返回多个值,优先使用解构赋值。
  • 单行定义的对象,最后一个成员不要以逗号结尾,多行定义的对象,最后一个成员以逗号结尾。对象尽量静态化,一旦定义,就不得随意添加新的属性。如果添加属性不可避免,要使用Object.assign方法。
  • 使用扩展运算符(...)拷贝数组。使用 Array.from方法,将类似数组的对象转为数组。
  • 立即执行函数可以写成箭头函数的形式。那些使用匿名函数当作参数的场合,尽量用箭头函数代替。因为这样更简洁,而且绑定了 this
  • 注意区分 ObjectMap,只有模拟现实世界的实体对象时,才使用 Object。如果只是需要key: value的数据结构,使用 Map结构。因为 Map有内建的遍历机制。
  • 总是用 Class,取代需要 prototype的操作。因为 Class 的写法更简洁,更易于理解。
  • 使用import取代require。使用export取代module.exports
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • 一、let 和 constlet:变量声明, const:只读常量声明(声明的时候赋值)。 let 与 var 的...
    dadage456阅读 757评论 0 0
  • 本文为阮一峰大神的《ECMAScript 6 入门》的个人版提纯! babel babel负责将JS高级语法转义,...
    Devildi已被占用阅读 1,970评论 0 4
  • defineProperty() 学习书籍《ECMAScript 6 入门 》 Proxy Proxy 用于修改某...
    Bui_vlee阅读 648评论 0 1
  • [TOC] 参考阮一峰的ECMAScript 6 入门参考深入浅出ES6 let和const let和const都...
    郭子web阅读 1,771评论 0 1
  • 本人是android开发的,由于最近React Native的火热,再加上自己完全不懂JS的语法,俗话说的好"落后...
    Bui_vlee阅读 277评论 0 0