第二十八节: ES6 Iterator与Proxy

1. Iterator

1.1 迭代器的理解

迭代器是一种接口、是一种机制。

为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

//以前的循环本质跟踪索引
let arr =[1,3,4,8,99];
console.log(arr.length);
for(let i=0; i<length; i++){
  console.log(arr[i]);
}
迭代器的理解
//迭代器是个方法,执行后创建迭代对象。迭代对象上有next()方法,每次调用next方法,返回一个结果对象。迭代对象中value属性为数据,done属性判断迭代是否继续(false继续,true结束)
let arr = [12,3,45,88,23,66]    //查看数组身上有个迭代器函数Symbol(Symbol.iterator): ƒ values()
let obj =arr[Symbol.iterator]()  //迭代函数arr[Symbol.iterator]执行后返回迭代对象
obj.next();//调用这个对象上的next()方法,会返回结果对象{value: 12, done: false}。value的值第一个值,done:false表示数组没迭代完需要继续调用next方法arr[Symbol.iterator]().next()
console.log(obj.next());  //返回的结果对象中done为true时,迭代结束
terator 的作用有三个:
  1. 为各种数据结构,提供一个统一的、简便的访问接口;
  2. 使得数据结构的成员能够按某种次序排列;
  3. 主要供for...of消费。
1.2 Iterator的本质

Iterator本质上,就是一个指针对象。

过程是这样的:

(1)创建一个指针对象,指向当前数据结构的起始位置。

(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。

(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。

(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。

1.3. 普通函数实现Iterator
    //单单抽离出来的迭代方法
    let obj = {
      0: 11,
      1: 22,
      2: 33,
      3: 44,
      4: 55,
      5: 66,
      length:5
    }
    function diedai() {
      let i=0;
      return {
        next: function () {
          //这里使用i,i就不会被销毁,就形成了闭包
          let value=obj[i];    //值
          let done = i >= obj.length;  //判断条件
          i++
          return {
            value,//简写
            done,
          }
        }
      }
    }
   let res= diedai(obj);
   console.log(res.next());
   console.log(res.next());
   console.log(res.next());
   console.log(res.next());
   console.log(res.next());
   console.log(res.next());
   console.log(res.next());
// 自定义迭代器的方法
//方法一,借用数组的迭代器
// 希望自定义对象也能使用迭代器。所以对象属性要按类数组定义,有length属性
// let obj ={
//   0:11,
//   1:22,
//   2:33,
//   3:44,
//   4:55,
//   5:66,
//   length:6,
//   [Symbol.iterator] : Array.prototype[Symbol.iterator]//把数组原型上的迭代函数赋值给自定义对象上的属性[Symbol.iterator]
// }
// console.log(Array.prototype);//数组的迭代就在数组构造函数的原型上。打印数组原型上的属性,就可以看到迭代器Symbol(Symbol.iterator): ƒ values()
// // 有了以上还是不可以迭代,还要添加上length
// for(let val of obj){
//   console.log(val);
// }
//方法二自己定义一个迭代器(更好的理解迭代器的原理)
let obj ={
  0:11,
  1:22,
  2:33,
  3:44,
  4:55,
  5:66,
  length:6,
  [Symbol.iterator] :function(){
      let i=0;
      let This=this;   //这里的this是obj,所以定义一个This=this,内部的next方法才能使用到这个this
      return {
        next: function(){   
          //这里使用i,i就不会被销毁,就形成了闭包
          let value=This[i];    //值
          let done = i >= This.length;  //判断条件
          i++
          return {
            value,//简写
            done,
          }
        }
      }
    }
}
for(let val of obj){       //for of 循环是obj对象会自动调用[Symbol.iterator]方法,所以它是个方法,返回一个对象,对象里有next方法,next方法执行返回{value:值,done:true/false}
  console.log(val);
}
    let obj = {
      0: 11,
      1: 22,
      2: 33,
      3: 44,
      4: 55,
      5: 66,
      length: 5
    }
    function myIter() {
      let i = 0;
      return {
        next() {
          let done = (i >= obj.length);
          let value = !done ? obj[i++] : undefined;
          return {
            value,
            done,
          }
        }
      }
    }
    let res = myIter(obj);
    console.log(res.next());
    console.log(res.next());
    console.log(res.next());
    console.log(res.next());
    console.log(res.next());
    console.log(res.next());
    console.log(res.next());

原生具备 Iterator 接口的数据结构如下。
  • Array
  • Map
  • Set
  • String
  • 函数的 arguments 对象
  • NodeList 对象
<body>
  <ul>
    <li>0</li>
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
  </ul>
  <script>
    // NodeList 对象例子
    let aLi = document.getElementsByTagName('li');
    console.log(aLi);  //打印后可以看到这个数组上也有迭代器方法Symbol(Symbol.iterator): ƒ values()。所以也可以使用for of方法
    for (let val of aLi) {
      console.log(val);
    }
  </script>
</body>
//下面的例子是数组的Symbol.iterator属性。
    let arr = ['a', 'b', 'c'];
    console.log(arr);
    let iter = arr[Symbol.iterator]();
    console.log(iter);
    iter.next() // { value: 'a', done: false }
    iter.next() // { value: 'b', done: false }
    iter.next() // { value: 'c', done: false }
    iter.next() // { value: undefined, done: true }

//下面是另一个类数组的对象调用数组的Symbol.iterator方法的例子。 
let iterable = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3,
  [Symbol.iterator]: Array.prototype[Symbol.iterator]
};

for (let item of iterable) {
  console.log(item); // 'a', 'b', 'c'
}
//注意,普通对象部署数组的Symbol.iterator方法,并无效果。
let iterable = {
  a: 'a',
  b: 'b',
  c: 'c',
  length: 3,
  [Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
  console.log(item); // undefined, undefined, undefined
}
//字符串的包装类是一个类似数组的对象,也原生具有 Iterator 接口。
var someString = "hi";
console.dir(String);
console.log(typeof someString[Symbol.iterator]);// "function"
var iterator = someString[Symbol.iterator]();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

2. Proxy 代理

Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”,即对编程语言进行编程。

讲通俗一点就是扩展(增强)了对象,方法(函数)的一些功能

ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。

Proxy其实是设计模式的一种,代理模式

2.1. 语法使用

new Proxy(target,handle)

  1. 参数
    第一个参数: target 是你要代理的对象
    第二个参数,handle是对代理对象做什么操作
    {
    set(){},
    get(){},
    deleteProperty(){},
    has(){},
    apply(),
    ......
    }
    返回值,返回一个新的对象
let proxy = new Proxy(target, handle);
  //代理的基本使用
    let obj = {
      name: 'xiaoming',
    }
    let myname = obj.name;  //js内置获取对象属性的行为,通过元对象操作
    console.log(myname);
    let proxy = new Proxy(obj, {  //写上代理哪个对象
      //里面写拦截函数
      //如果代理对象没有任何拦截行为,那么代理对象上的操作都是用原生的行为
    })
    console.log(proxy);    //Proxy {name: 'xiaoming'}打印后有Proxy表示代理对象

Proxy 对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。

2.2 如果没有做任何拦截设置

如果handler没有设置任何拦截,那就等同于直接通向原对象。

var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"

上面代码中,handler是一个空对象,没有任何拦截效果,访问proxy就等同于访问target。

let obj = {
    name : 'wuwei'
}
console.log(obj.name);

// 我希望你在获取name 属性的时候做一些事情,那么我们就可以用代理模式
let newObj = new Proxy(obj,{
    get(target,property){   // target就是代理对象obj,property就是用户访问的属性
        // console.log(target,property);   // {name: "wuwei"} "aaa"
        console.log(`你访问了${property}属性`)
        return target[property];
    }
})

2.3 添加拦截处理程序

2.3.1 get 获取拦截

get 拦截程序接受三个参数

  1. target 代理的目标对象
  2. prop 操作的属性
  3. receiver 代理对象
    //代理的拦截行为(函数方法)对js底层对象获取属性功能进行拦截(操作代理对象获取属性时触发)
    //对象的操作 获取 obj.name  修改obj.name='bb'   删除 delete obj.name  判断对象是否有XX属性 XX in obj
    let obj = {
      name: 'xiaoming',
      age: 18
    }
    let proxy = new Proxy(obj, {
      //  获取拦截
      get(target, prop, receiver) {
        //自己想扩展的功能
        console.log(arguments);
        // return 111;      //方法一 return的值就是操作对象属性的值
        // return target[prop]   //方法二 通过参数的代理目标对象和属性获取原来的值
        // return Reflect.get(...arguments)//方法三 通过Reflect 反射到js默认的对象获取属性的行为上
        if (prop in target) {
          return Reflect.get(...arguments)
        } else {
          return `你获取的对象${prop}不存在于当前对象上`
          // throw new Error(`你获取的对象${prop}不存在于当前对象上`)//也可以选择抛出一个错误
        }
      }
    })
    console.log(proxy);
    console.log(proxy.name);  //没有拦截之前,获取得到的是默认的值。添加了拦截就会自动走对象的get方法。get获取三个参数:访问的代理目标对象target、操作的属性prop、receiver代理对象
    console.log(proxy.age);
    console.log(proxy.hobbies); //原来js不存在的js属性会返回undefined

//访问代理对象上不具有的属性就报错
let obj = {
            name: 'aabb'
        }
        let proxy = new Proxy(obj, {
            get(target, key, receiver) {
                if (!(key in receiver)) {
                    throw new ReferenceError(`属性${key}不存在`)
                }
                return target[key]
            }
        })
        console.log(proxy.name);  // aabb
        console.log(proxy.age);   // 报错

get拦截的用处--批量创建HTML标签,属性、内容自动添加

    // let oD = document.createElement('div')   //以前js创建标签创建div标签
    // oD.className = 'aa'//给标签添加class属性
    // oD.innerHTML = '这里是内容部分'   //标签加内容
    let body = document.getElementsByTagName('body')[0]     //获取标签
    // body.appendChild(oD)    //标签放到页面上
    //get拦截的用处--批量创建HTML标签,属性、内容自动添加
    let pro = new Proxy({}, {  //代理空对象重要的是走get方法
      get(target, prop, receiver) {  //target是空对象 prop是属性div  receiver是代理对象
        return function (attr = {}, child = []) {  //接收两个参数  属性罗列  子项  都有默认值防意外
          let oBox = document.createElement(prop)//创建一个标签 
          for (let key in attr) {
            oBox[key] = attr[key]   //添加属性
          }
          for (let item of child) {
            if (typeof item === 'string') {//判断是字符串添加节点,不是字符串直接添加  
              item = document.createTextNode(item)
            }
            oBox.appendChild(item)  //添加节点
          }
          return oBox
        }
      }
    })
    let aa = pro.li()
    console.log('aa是没有传参数建立的空标签', aa);
    let bb = pro.div(      //返回函数,功能
      {
        className: 'aa',
        id: 'bb',
        style: 'backgroun-color:#ccc'
      }, [
      '字符串内容',
      document.createTextNode('<p>文本内容</p>')
    ]
    )
    console.log('bb是传参数建立的标签', bb);

    let ul = pro.ul(
      {
        id: 'idul',
        className: 'aa',
        style: 'background-color:#ccc;font-size:30px',

      }, [
      document.createTextNode('<li>产品说明</li>'),
      document.createTextNode('<li>运输方式</li>')
    ]
    )
    console.log('创建ul标签', ul);
    body.appendChild(ul)    //页面中标签
2.3.2 set 设置拦截

set 拦截程序接受4个参数

  1. target 代理的目标对象
  2. prop 操作的属性
  3. value 被写入的属性值
  4. receiver 代理对象
    //设置拦截(给代理对象属性赋值)
    let obj = {
      name: 'xiaoming',
      age: 18
    }
    let proxy = new Proxy(obj, {
      set(target, prop, value, receiver) {  //目标对象  操作的属性  赋值的值 代理对象
        // target[prop] = value;   //方法直接操作代理对象修改
        // Reflect.set(...arguments)   //方法二 反射
        // console.log(arguments);
        // return 222;//基本没用
        if (prop === 'age' && !(typeof value === 'number')) {    //age值不是数字类型就会报错,其他属性不受影响
          throw new Error('age属性值必须是一个数字类型')
        } else {
          Reflect.set(...arguments)
        }
      }

    })
    console.log(proxy);
    proxy.name = 'mingming';
    proxy.age = '123';   //年龄一般是数字,这里赋值字符串会报错
    console.log(proxy);
    console.log(obj);
var obj = new Proxy({},{
    set(target,prop,value){
        if(prop == 'age'){
            if(!Number.isInteger(value)){
                throw new TypeError('年龄必须为整数')
            }
            if(value > 200){
                throw new RangeError('年龄超标了,必须小于200岁')
            }
        }
        target[prop] = value;
    }
})

obj.a = '12.5';
obj.name = 'wuwei';
console.log(obj);    //Proxy {a: "12.5", name: "wuwei"}

// 以下是报错
obj.age = 12.6;   // Uncaught TypeError: 年龄必须为整数
obj.age = 201;    // Uncaught RangeError: 年龄超标了,必须小于200岁
2.3.3 has 包含拦截

使用in 操作符检查对象中是否包含某个属性. 触发has拦截

has 拦截程序接受2个参数

  1. target 代理的目标对象
  2. prop 需要判断的属性
    //判断是否为对象属性的拦截has
    let obj = {
      name: 'xiaoming',
      age: 18
    }
    console.log('name' in obj);    //判断obj对象上是否有属性name时,name要加引号。否则就为false。这是js的默认行为
    console.log('age' in obj)
    console.log('hobby' in obj)   //没有这个属性,所以false


    let pro = new Proxy(obj, {
      has(target, prop) {  //两个参数,目标对象,判断的哪个属性
        console.log(arguments)
        console.log(`判断属性${prop}是否存在于当前对象中`)
        // return prop in target//方法一得到值操作原生的
        return Reflect.has(...arguments)//方法二通过反射

      }
    })
    console.log('name' in pro);    //判断属性是否属于拦截对象
let obj = {
      a: 1,
      b: 2
    }
    let proxy = new Proxy(obj, {
      has(target, prop) {
        console.log(`判断属性${prop}是否存在于当前对象中`)

        return prop in target
      }
    })
    console.log('a' in proxy);

2.3.4 deleteProperty 删除拦截

通过delete 操作符删除属性的时候,就会触发deleteProperty 拦截

deleteProperty 拦截处理程序接受2个参数

  1. target 代理的目标对象
  2. prop 删除操作的属性
    // 拦截删除行为
    let obj = {
      name: 'xiaoming',
      age: 18
    }
    // console.log(delete obj.name); //js原生的默认行为
    let pro = new Proxy(obj, {
      deleteProperty(target, prop) {//两个参数目标对象,操作哪个属性  
        console.log(arguments)
        console.log(`你要删除${prop}属性`);
        // return delete target[prop] //方法一 原生的
        return Reflect.deleteProperty(...arguments)//通过反射,反射到js底层的 deleteProperty
      }
    })
    console.log(delete pro.name);

2.4 函数拦截

所有的代理拦截中, 只有apply 和 constructor 的代理目标是一个函数. apply 和construct 拦截方法覆写函数内部的[[ Call ]]和 [[ Constructor ]],这些内部方法.

函数的普通的调用实际是调用函数内部的[[ Call ]]方法。通过new来操作一个函数底层是调用的 [[ Constructor ]]

2.4.1 apply 拦截

apply拦截接受三个参数

  1. target 代理的目标函数
  2. thisArg 函数被调用时内部this的值
  3. argumentsList 传递给函数的数组
//代理函数修改tihs
function haha() {
  console.log(this);
  console.log(arguments);
}
let bb = new Proxy(haha,{   //第一个参数为代理的函数
  apply(target,thisArg,Arg){     //apply 拦截
    // console.log(arguments);//参数 目标函数、this指向、实参组成的数组
    //里面的this始终是window,要从外面改变this,this才会改变
    // target.apply(thisArg,Arg)   //方法一 通过代理的目标函数来执行的 
    Reflect.apply(...arguments)//方法二Reflect
  }
})
let obj ={name:'obj'}
bb.apply(obj,[10,20,30])
//代理函数修改tihs的分析与理解
function haha() {
  console.log(this);
  console.log(arguments);
}
let bb = new Proxy(haha,{  
  apply(target,thisArg,Arg){    
    Reflect.apply(target,obj,Arg)     //代理对象中手动修改this,一般不会这么操作
  }
})
let obj ={name:'obj'}
bb(10,20,30)
2.4.2 construct 拦截

construct 拦截 new 操作符调用函数

  1. target 代理目标函数
  2. argumentsList 传递给函数的参数数组
    function fn(a, b) {
      this.a = a,
        this.b = b
      return 44
    }
    let proxy = new Proxy(fn, {
      apply(target, thisArg, argumentsList) {
        return target.apply(this.Arg, argumentsList)

      },    //底层apply叫[[ Call ]]
      construct(target, argumentsList) {
        console.log(arguments)   //目标函数 参数 代理对象
        // return new target(...argumentsList)   
        return Reflect.construct(...arguments)//更好的写法
      }             //底层construct叫 [[ Constructor ]]
    })

    let res = new proxy(10, 20)   //有new走construct  没有走apply
    console.log(res)  //使用new返回一个对象
    console.log(fn(5, 6));
2.5 取消代理 Proxy.revocable()

Proxy.revocable方法返回一个可撤销代理实例, 参数与Proxy构造函数一致。

    let target = { name: 'aa', age: 19 };
    let handler = {};
    let { proxy, revoke } = Proxy.revocable(target, handler); //通常采用解构方式
    //Proxy.revocable方法参数和new Proxy一样,返回值不一样,返回一个对象,对象中的proxy属性为代理对象,其实就是new Proxy()创建的对。 revoke是一个方法用来取消代理
    // new Proxy()    //这种代理返回值是代理对象proxy
    proxy.foo = 123;
    console.log(proxy.foo)
    console.log(proxy);
    revoke();
    console.log(proxy.foo)    //报错,取消代理后无法获取

Proxy.revocable方法返回一个对象,该对象的proxy属性是Proxy实例,revoke属性是一个函数,撤销代理调用的函数。上面代码中.

当执行revoke函数之后,任何与代理对象的交互都会触发错误。

2.6. Proxy支持的拦截操作
  • 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),返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
    let obj = { name: 'aa', age: 19 };
    console.log(Object.getOwnPropertyNames(obj)) //原生的js底层的一个方法获取key,组成数组
    let pro = new Proxy(obj, {  //通过代理的
      ownKeys() {
        console.log(arguments)
        return []
      }
    })
    console.log(Object.getOwnPropertyNames(pro))     //参数为代理对象,就会触发代理对象里的ownKeys的拦截
  • 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)。
2.7.reflect 反射
function fn(a,b){
    return a+b
}
var newFn = new Proxy(fn,{
  apply(target,context,args){
    // console.log(target,context,args);
    // console.log(arguments);
    // console.log(...arguments);
    return Reflect.apply(...arguments); 
  }
  
})

console.log(newFn(3,2));   // 5

如果要增强方法需要跟Reflect配合

我也可以在反射时干些事情

    //反射调用函数
    function aa() {
      return 10
    }
    let pro = new Proxy(aa, {//代理aa函数
      apply() {
        return '函数原生执行返回值为' + Reflect.apply(...arguments) + '。原生执行返回值的三次方为' + Reflect.apply(...arguments) ** 3//函数原生的执行
      }
    })
    console.log(pro());
    function fn() {
      return 5;
    }
    var newFn = new Proxy(fn, {
      apply(target, context, args) {
        // console.log(target, context, args);
        // console.log(arguments);
        // console.log(...arguments);
        return Reflect.apply(...arguments) ** 3;   //反射结果的3次方
      }
    })
    console.log(newFn(3, 2));   // 125

2.8 Reflect.apply()

和fn.apply()很相似

Reflect.apply(target,context,args) 有三个参数

target: 需要调用的函数

context: this指向

args : 参数数组

let num =Math.ceil(5.01);   //向上取整
console.log(num);
//也可以用反射
let num2 = Reflect.apply(Math.ceil,null,[5.01]) //反射Math.ceil的apply方法,调用Math.ceil原生的方法 ,不需要修改this指向,参数是数组   
console.log(num2);

就是调用函数的不同的方式而已,更高级,js底层些的函数调用方法

function show(...args){
    console.log(this);
    console.log(args);
}
// 正常调用 
show(1,2,3,4);                        // this是window, args是[1,2,3,4]
// call调用函数
show.call('aaa',1,2,3,4);             // this是aaa,    args是[1,2,3,4]
// apply调用函数
show.apply('aaa',[1,2,3,4]);          // this是aaa,    args是[1,2,3,4]
// reflect.apply调用函数
Reflect.apply(show,'aaa',[1,2,3,4]);  // this是aaa,    args是[1,2,3,4]

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容

  • 1. Iterator 1.1 迭代器的理解 迭代器是一种接口、是一种机制。 为各种不同的数据结构提供统一的访问机...
    时光如剑阅读 565评论 0 2
  • Es6 1.数组 1.扩展运算符 是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参...
    林深不見鹿阅读 219评论 0 0
  • 原本想稍微整理一下 ES 新特性,没想到花了相当多的时间,本文也巨长,依然推荐使用 简悦[https://gith...
    401阅读 1,851评论 0 5
  • 1.let和const命令 暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,...
    尘满面鬓微霜阅读 373评论 0 0
  • 本文为阮一峰大神的《ECMAScript 6 入门》的个人版提纯! babel babel负责将JS高级语法转义,...
    Devildi已被占用阅读 1,969评论 0 4