js总结

每日一题

  1. 以下哪个会打印出"3"?

    3.toString()  //报错  Uncaught SyntaxError: Invalid or unexpected token 无效或意外的标记 
    3..toString() //"3"
    3...toString() //报错 Uncaught SyntaxError: Unexpected token '.'
    

解析:

运算符优先级的问题,点运算符会被优先识别为数字常量的一部分,然后才是对象属性访问符
在 JS 中,3.13..3 都是合法的数字
3.toString() 会被 JS 引擎解析成 (3.)toString() 报错
3..toString() 会被 JS 引擎解析成 (3.).toString() "3"
3...toString() 会被 JS 引擎解析成 (3.)..toString() 报错


  1. 输出是什么?

    const obj = { 1: 'a', 2: 'b', 3: 'c' }
    const set = new Set([1, 2, 3, 4, 5])
    
    obj.hasOwnProperty('1') //true
    obj.hasOwnProperty(1)  //true
    set.has('1')  //false
    set.has(1)  //true
    

    解析:
    所有对象键(不包括Symbols)都会被存储为字符串,即使你没有给定字符串类型的键。这就是为什么obj.hasOwnProperty('1')也返回true
    上面的说法不适用于Set。在我们的Set中没有“1”set.has('1')返回false。它有数字类型1set.has(1)返回true


扩展:

1. hasOwnProperty

hasOwnProperty()方法返回一个布尔值,该布尔值指示对象是否具有指定的属性作为其自身的属性(而不是继承它)。

  1. 句法

    obj.hasOwnProperty(prop)
    
  2. 用途:

    • 判断自身属性是否存在

      o = new Object();
      o.hasOwnProperty('prop');   // returns false
      o.prop = 'exists';
      function changeO() {
        o.newprop = o.prop;
        delete o.prop;
      }
      o.hasOwnProperty('prop');   // returns true
      changeO();
      o.hasOwnProperty('prop');  // false
      
    • 判断自身属性与继承属性(通过原型链继承的属性)

      function foo() {
        this.name = 'foo'
        this.sayHi = function () {
          console.log('Say Hi')
        }
      }
      
      foo.prototype.sayGoodBy = function () {
        console.log('Say Good By')
      }
      
      let myPro = new foo()
      
      console.log(myPro.hasOwnProperty('name')) // true
      console.log(myPro.hasOwnProperty('toString')) // false
      console.log(myPro.hasOwnProperty('hasOwnProperty')) // fasle
      console.log(myPro.hasOwnProperty('sayHi')) // true
      console.log(myPro.hasOwnProperty('sayGoodBy')) // false
      console.log('sayGoodBy' in myPro) // true
      
    • 遍历一个对象的所有自身属性(不包括原型继承属性)

      function foo() {
          this.name = 'foo'
          this.sayHi = function () {
            console.log('Say Hi')
          }
        }
      
        foo.prototype.sayGoodBy = function () {
          console.log('Say Good By')
        }
      
        let myPro = new foo()
        for (var name in myPro) {
          if (myPro.hasOwnProperty(name)) {
            console.log('this is fog (' +
              name + ') for sure. Value: ' + myPro[name]);
          }
          else {
            console.log(name); // toString or something else
          }
       }
      

2. Set的使用方法

ES6 提供的新的数据结构,类似Array。但是 Set 中成员的值都是唯一的,没有重复的值。

Set 对象可以存储任何类型的唯一值,无论是原始值或者是对象引用。创建 Set 的实例需要用到 Set 构造函数,并且传入的参数必须只能是可迭代对象,例如数组,字符串。

let set2 = new Set([1, 2, 3, 2, 1]);
console.log(set2); //Set {1, 2, 3} 去重效果
  1. Set的方法和属性

    • add()方法:

      用于向 Set 中添加元素,返回一个新的 Set

      let set1 = new Set();
       set1.add(1);
       set1.add('xkd');
       console.log(set1); // Set { 1, 'xkd' }
      
    • delete()方法:

      用于删除 Set 中的元素,返回一个布尔值,判断是否删除成功。如果删除成功返回 true,删除失败返回 false

       let set1 = new Set([1, 2, 3, 4, 5]);
       console.log(set1.delete(4));  // true
       console.log(set1);  // Set { 1, 2, 3, 5 }
      
    • has()方法:

      用于判断某个值是否为 Set 的成员,返回一个布尔值。

      let set1 = new Set([1, 2, 3, 4, 5]);
       console.log(set1.has(2));  // true
       console.log(set1.has(7));  // false
      
    • clear()方法:

      用于清空 Set 中的全部成员。

      let set1 = new Set([1, 2, 3, 4, 5]);
       set1.clear();
       console.log(set1);  // 输出:Set {}
      
    • size属性:

      属性返回当前 Set 元素总数。

      let set1 = new Set([1, 2, 3, 4, 5]);
       console.log(set1.size);  // 输出:5
      
    1. Set遍历操作:
    • keys()方法:返回键名的遍历器。

      let set_k = new Set(['xkd', 'Iven', 'summer']);
      console.log(set_k.keys()); // [Set Iterator] { 'xkd', 'Iven', 'summer' }
      
    • values()方法:返回键值的遍历器。

      let set_v = new Set(['xkd', 'Iven', 'summer']);
      console.log(set_v.values()); //  [Set Iterator] { 'xkd', 'Iven', 'summer' }
      

      由于 Set 结构没有键名,只有键值,或者说键名和键值是同一个,所以 keys 方法和 values 方法的行为完全一致。

    • entries()方法:返回键值对的遍历器。

      let set_e = new Set(['xkd', 'Iven', 'summer']);
      console.log(set_e.entries());//[Set Entries] { [ 'xkd', 'xkd' ],[ 'Iven', 'Iven' ], [ 'summer', 'summer' ]}
      
    • forEach()方法:使用回调函数遍历每个成员。

      let set_f = new Set(['xkd', 'Iven', 'summer']);
      set_f.forEach(function(value,key){
          console.log(value+":"+key);
      })
      
      // 输出:
      // xkd:xkd 
      // Iven:Iven 
      // summer:summer
      
    1. Set对象的作用:
    • 去重

      let set1 = new Set([1, 2, 3, 4, 2, 4]);
       console.log(set1);  // Set { 1, 2, 3, 4 }
      
    • 并集

      var set1 = new Set([1, 2, 3]);
       var set2 = new Set([7, 6, 1]);
       var union = new Set([...set1, ...set2]); 
       console.log(union);  // Set { 1, 2, 3, 7, 6 }
      
    • 交集

       var set1 = new Set([1, 2, 3]);
       var set2 = new Set([7, 6, 1]);
       let inter = new Set([...set1].filter(x=>set2.has(x))) //{4,5}
       console.log(inter);  // Set { 1 }
      
    • 差集

      var set1 = new Set([1, 2, 3]);
      var set2 = new Set([7, 6, 1]);
      var diff = new Set([...set1].filter(x => !set2.has(x))); // {1}
      console.log(diff);  // Set { 2, 3 }
      
  2. Set类型转换:

    • Array 转为 Set

      let arr1 = ["x", "k", "d"];
      let set1 = new Set(arr1);
      console.log(set1);  // Set { 'x', 'k', 'd' }
      
    • Set 转为 Array :可以通过扩展运算符 ... 来实现。

      let set1 = new Set(["x", "k", "d"]);
      var arr1 = [...set1];
      console.log(arr1);    //[ 'x', 'k', 'd' ]
      
    • String 转为 Set

      let set1 = new Set('xkd');
      console.log(set1);    // Set { 'x', 'k', 'd' }
      

  1. 输出是什么?

    const obj = { a: 'one', b: 'two', a: 'three' }
    console.log(obj) //{ a: "three", b: "two" }
    

    解析:

    如果对象有两个具有相同名称的键,则将替前面的键。它仍将处于第一个位置,但具有最后指定的值。


  1. 输出是什么?

    for (let i = 1; i < 5; i++) {
      if (i === 3) continue
      console.log(i) //1 2 4
    }
    

  1. 输出是什么?

    const a = {}
    const b = { key: 'b' }
    const c = { key: 'c' }
    
    a[b] = 123
    a[c] = 456
    
    console.log(a[b])  //456
    

    解析:

    对象的键被自动转换为字符串。我们试图将一个对象 b 设置为对象 a 的键,且相应的值为 123

    然而,当字符串化一个对象时,它会变成 "[object Object]"。因此这里说的是,a["[object Object]"] = 123。然后,我们再一次做了同样的事情,c 是另外一个对象,这里也有隐式字符串化,于是,a["[object Object]"] = 456

    然后,我们打印 a[b],也就是 a["[object Object]"]。之前刚设置为 456,因此返回的是 456


  1. 当点击按钮时,event.target是什么?

    <div onclick="console.log('first div')">
      <div onclick="console.log('second div')">
        <button onclick="console.log('button')">
          Click!
        </button>
      </div>
    </div>
    

    解析:

    button

    导致事件的最深嵌套的元素是事件的 target。你可以通过 event.stopPropagation 来停止冒泡。

    event.target 返回触发事件的元素
    event.currentTarget 返回绑定事件的元素


  1. 输出是什么?

    const person = { name: 'Lydia' }
    
    function sayHi(age) {
      console.log(`${this.name} is ${age}`)
    }
    
    sayHi.call(person, 21)  //Lydia is 21
    sayHi.bind(person, 21)  // function
    

    解析:

    使用这两种方法,我们都可以传递我们希望 this 关键字引用的对象。但是,.call立即执行的。

    .bind 返回函数的副本,但带有绑定上下文!它不是立即执行的。


扩展

1. call、apply、bind

  • call:

    fun.call(thisArg[, arg1[, arg2[, ...]]])
    

    它会立即执行函数,第一个参数是指定执行函数中 this 的上下文,后面的参数是执行函数需要传入的参数。

  • apply:

    fun.apply(thisArg, [argsArray])
    

    它也会立即执行函数,第一个参数是指定执行函数中 this 的上下文,第二个参数是一个数组,是传给执行函数的参数(与 call 的区别)。

  • bind:

    var foo = fun.bind(thisArg[, arg1[, arg2[, ...]]]);
    

    它不会执行函数,而是返回一个新的函数,这个新的函数被指定了 this 的上下文,后面的参数是执行函数需要传入的参数。


  1. 输出是什么?

    console.log(typeof typeof 1)  //string
    

    解析:

    typeof 1返回"number"typeof "number"返回"string".


  1. 输出是什么?

    function sayHi() {  return (() => 0)()}typeof sayHi()  // number
    

    解析:

    sayHi方法返回的是立即执行函数(IIFE)的返回值.此立即执行函数的返回值是0, 类型是number
    参考:只有7种内置类型:null,undefined,boolean,number,string,object, symbol 和 bigint。function 不是一种类型,函数是对象,它的类型是object。

    typeof sayHi()等同于 (() => 0)()

    console.log((() => 0)())  //0
    

  1. 输出是什么?

    [[0, 1], [2, 3]].reduce(
      (acc, cur) => {
        return acc.concat(cur)
      },
      [1, 2]
    )
    //[1, 2, 0, 1, 2, 3]
    

    解析:

    [1, 2]是初始值。初始值将会作为首次调用时第一个参数acc的值。在第一次执行时,acc的值是[1, 2]cur的值是[0, 1]。合并它们,结果为[1, 2, 0, 1]。第二次执行,acc的值是[1, 2, 0, 1]cur的值是[2, 3]。合并它们,最终结果为[1, 2, 0, 1, 2, 3]

扩展

reduce函数:

reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。

  • 语法

    array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
    
  • 参数

    参数 描述
    function(total,currentValue, currentIndex,arr) 必需。用于执行每个数组元素的函数。
    initialValue 可选。传递给函数的初始值

    function(total,currentValue, currentIndex,arr)参数介绍:

    参数 描述
    total 必需。初始值, 或者计算结束后的返回值。
    currentValue 必需。当前元素
    currentIndex 可选。当前元素的索引
    arr 可选。当前元素所属的数组对象。

    注意:reduce() 对于空数组是不会执行回调函数的。


  1. 输出是什么?

    function* generator(i) {
      yield i;
      yield i * 2;
    }
    
    const gen = generator(10);
    
    console.log(gen.next().value);  // 10
    console.log(gen.next().value);  // 20 
    

    解析:

    一般的函数在执行之后是不能中途停下的。但是,生成器函数却可以中途“停下”,之后可以再从停下的地方继续。当生成器遇到yield关键字的时候,会生成yield后面的值。注意,生成器在这种情况下不 返回 (return )值,而是 生成 (yield)值。

    首先,我们用10作为参数i来初始化生成器函数。然后使用next()方法一步步执行生成器。第一次执行生成器的时候,i的值为10,遇到第一个yield关键字,它要生成i的值。此时,生成器“暂停”,生成了10

    然后,我们再执行next()方法。生成器会从刚才暂停的地方继续,这个时候i还是10。于是我们走到了第二个yield关键字处,这时候需要生成的值是i*2i10,那么此时生成的值便是20。所以这道题的最终结果是10,20


12.输出的是什么?

function getInfo(member, year) {
  member.name = "Lydia";
  year = "1998";
}

const person = { name: "Sarah" };
const birthYear = "1997";

getInfo(person, birthYear);

console.log(person, birthYear);  //{ name: "Lydia" }, "1997"

解析:

普通参数都是 传递的,而对象则不同,是 引用 传递。所以说,birthYear是值传递,因为他是个字符串而不是对象。当我们对参数进行值传递时,会创建一份该值的 复制 。(可以参考问题46)

变量birthYear有一个对"1997"的引用,而传入的参数也有一个对"1997"的引用,但二者的引用并不相同。当我们通过给 year赋值"1998"来更新year的值的时候我们只是更新了year(的引用)。此时birthYear仍然是"1997".

person是个对象。参数member引用与之 相同的 对象。当我们修改member所引用对象的属性时,person的相应属性也被修改了,因为他们引用了相同的对象. personname属性也变成了 "Lydia".


  1. 输出是什么?
class Dog {
  constructor(name) {
    this.name = name;
  }
}

Dog.prototype.bark = function() {
  console.log(`Woof I am ${this.name}`);
};

const pet = new Dog("Mara");

pet.bark();  //Woof I am Mara

delete Dog.prototype.bark;

pet.bark();  //Uncaught TypeError: pet.bark is not a function

解析:

我们可以用delete关键字删除对象的属性,对原型也是适用的。删除了原型的属性后,该属性在原型链上就不可用了。在本例中,函数bark在执行了delete Dog.prototype.bark后不可用, 然而后面的代码还在调用它。

当我们尝试调用一个不存在的函数时TypeError异常会被抛出。在本例中就是 TypeError: pet.bark is not a function,因为pet.barkundefined.


  1. 输出是什么?

    const person = { name: "Lydia" };
    
    Object.defineProperty(person, "age", { value: 21 });
    
    console.log(person);  //{ name: "Lydia", age: 21 }
    console.log(Object.keys(person));  // ["name"]
    

    解析:

    通过defineProperty方法,我们可以给对象添加一个新属性,或者修改已经存在的属性。而我们使用defineProperty方法给对象添加了一个属性之后,属性默认为 不可枚举(not enumerable). Object.keys方法仅返回对象中 可枚举(enumerable) 的属性,因此只剩下了"name".

    defineProperty方法添加的属性默认不可变。你可以通过writable, configurableenumerable属性来改变这一行为。这样的话, 相比于自己添加的属性,defineProperty方法添加的属性有了更多的控制权。


  1. 输出是什么?

    const name = "Lydia";
    age = 21;
    
    console.log(delete name); //false
    console.log(delete age);  //true
    

    解析:

    delete操作符返回一个布尔值:true指删除成功,否则返回false. 但是通过 var, constlet 关键字声明的变量无法用 delete 操作符来删除。

    name变量由const关键字声明,所以删除不成功:返回 false. 而我们设定age等于21时,我们实际上添加了一个名为age的属性给全局对象。对象中的属性是可以删除的,全局对象也是如此,所以delete age返回true.


  1. 输出是什么?

    const settings = {
      username: "lydiahallie",
      level: 19,
      health: 90
    };
    //第二个参数是数组的情况
    const data = JSON.stringify(settings, ["level", "health"], 4);
    console.log(data); //"{"level":19, "health":90}"
    
    //第二个参数是函数的情况,第三个参数是缩紧空格数
    function replacer(key, value) {
      if (typeof value === "string") {
        return undefined;
      }
      return value;
    }
    
    var foo = {foundation: "Mozilla", model: "box", week: 45, transport: "car", month: 7};
    var jsonString = JSON.stringify(foo, replacer, 4);
    console.log(jsonString);  
    //{
    //    "week": 45,
    //    "month": 7
    //}
    

    解析:

    JSON.stringify的第二个参数是 替代者(replacer). 替代者(replacer)可以是个函数或数组,用以控制哪些值如何被转换为字符串。

    如果替代者(replacer)是个 数组 ,那么就只有包含在数组中的属性将会被转化为字符串。在本例中,只有名为"level""health" 的属性被包括进来, "username"则被排除在外。data 就等于 "{"level":19, "health":90}".

    而如果替代者(replacer)是个 函数,这个函数将被对象的每个属性都调用一遍。函数返回的值会成为这个属性的值,最终体现在转化后的JSON字符串中(译者注:Chrome下,经过实验,如果所有属性均返回同一个值的时候有异常,会直接将返回值作为结果输出而不会输出JSON字符串),而如果返回值为undefined,则该属性会被排除在外。


  1. 输出是什么?

    let num = 10;
    
    const increaseNumber = () => num++;
    const increasePassedNumber = number => number++;
    
    const num1 = increaseNumber();
    const num2 = increasePassedNumber(num1);
    
    console.log(num1); // 10
    console.log(num2); // 10
    

    解析:

    一元操作符 ++ 先返回 操作值, 再累加 操作值。num1的值是10, 因为increaseNumber函数首先返回num的值,也就是10,随后再进行 num的累加。

    num210因为我们将 num1传入increasePassedNumber. number等于10num1的值。同样道理,++ 先返回 操作值, 再累加 操作值。) number10,所以num2也是10.


  1. 输出什么?

    const value = { number: 10 };
    const multiply = (x = { ...value }) => {
      console.log(x.number *= 2);
    };
    multiply();  // 20
    multiply();  //20
    multiply(value);  //20
    multiply(value);  //40
    

    解析:

    在ES6中,我们可以使用默认值初始化参数。如果没有给函数传参,或者传的参值为 "undefined" ,那么参数的值将是默认值。上述例子中,我们将 value 对象进行了解构并传到一个新对象中,因此 x 的默认值为 {number:10}

    默认参数在调用时才会进行计算,每次调用函数时,都会创建一个新的对象。我们前两次调用 multiply 函数且不传递值,那么每一次 x 的默认值都为 {number:10} ,因此打印出该数字的乘积值为20

    第三次调用 multiply 时,我们传递了一个参数,即对象value*=运算符实际上是x.number = x.number * 2的简写,我们修改了x.number的值,并打印出值20

    第四次,我们再次传递value对象。x.number之前被修改为20,所以x.number * = 2打印为40


  1. 输出什么?

    [1, 2, 3, 4].reduce((x, y) => console.log(x, y));  //1 2  undefined 3  undefined 4
    

    解析:

    reducer 函数接收4个参数:

    1. Accumulator (acc) (累计器)
    2. Current Value (cur) (当前值)
    3. Current Index (idx) (当前索引)
    4. Source Array (src) (源数组)

    reducer 函数的返回值将会分配给累计器,该返回值在数组的每个迭代中被记住,并最后成为最终的单个结果值。

    reducer 函数还有一个可选参数initialValue, 该参数将作为第一次调用回调函数时的第一个参数的值。如果没有提供initialValue,则将使用数组中的第一个元素。

    在上述例子,reduce方法接收的第一个参数(Accumulator)是x, 第二个参数(Current Value)是y

    在第一次调用时,累加器x1,当前值“y”2,打印出累加器和当前值:12

    例子中我们的回调函数没有返回任何值,只是打印累加器的值和当前值。如果函数没有返回值,则默认返回undefined。在下一次调用时,累加器为undefined,当前值为“3”, 因此undefined3被打印出。

    在第四次调用时,回调函数依然没有返回值。累加器再次为 undefined ,当前值为“4”。undefined4被打印出。


  1. 输出什么?

    // index.js
    console.log('running index.js');
    import { sum } from './sum.js';
    console.log(sum(1, 2));
    
    // sum.js
    console.log('running sum.js');
    export const sum = (a, b) => a + b;
    

    解析:

    import命令是编译阶段执行的,在代码运行之前。因此这意味着被导入的模块会先运行,而导入模块的文件会后执行。

    这是CommonJS中require()import之间的区别。使用require(),您可以在运行代码时根据需要加载依赖项。如果我们使用require而不是importrunning index.jsrunning sum.js3会被依次打印。


  1. 输出什么?

    const name = "Lydia Hallie"
    console.log(name.padStart(13)) //" Lydia Hallie"
    console.log(name.padStart(2))  //"Lydia Hallie"
    

    解析:

    使用padStart方法,我们可以在字符串的开头添加填充。传递给此方法的参数是字符串的总长度(包含填充)。字符串Lydia Hallie的长度为12, 因此name.padStart(13)在字符串的开头只会插入1(13 - 12 = 1)个空格。

    如果传递给padStart方法的参数小于字符串的长度,则不会添加填充。

扩展

String.prototype.padStart()

padStart() 方法用另一个字符串填充当前字符串(如果需要的话,会重复多次),以便产生的字符串达到给定的长度。从当前字符串的左侧开始填充。

语法
str.padStart(targetLength [, padString])
参数
  • targetLength:当前字符串需要填充到的目标长度。如果这个数值小于当前字符串的长度,则返回当前字符串本身。
  • padString(可选):填充字符串。如果字符串太长,使填充后的字符串长度超过了目标长度,则只保留最左侧的部分,其他部分会被截断。此参数的默认值为 " "(U+0020)。
返回值
  • 在原字符串开头填充指定的填充字符串直到目标长度所形成的新字符串。
示例
'x'.padStart(5, 'ab') // 'ababx''x'.padStart(4, 'ab') // 'abax''xxx'.padStart(2, 'ab') // 'xxx' ----如果原字符串的长度,等于或大于指定的最小长度,则返回原字符串。'abc'.padStart(10, '0123456789') // '0123456abc'----如果用来补全的字符串与原字符串,两者的长度之和超过了指定的最小长度,则会截去超出位数的补全字符串。'x'.padStart(4) // '   x'----如果省略第二个参数,默认使用空格补全长度。
常见用途:
  • padStart()的常见用途是为数值补全指定位数。下面代码生成 10 位的数值字符串。

    '1'.padStart(10, '0') // "0000000001"
    '12'.padStart(10, '0') // "0000000012"
    '123456'.padStart(10, '0') // "0000123456"
    
  • 另一个用途是提示字符串格式。

    '12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
    '09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"
    

String.prototype.padEnd()

  • 从当前字符串的末尾(右侧)开始填充。用法和String.prototype.padStart()相同。

  1. 输出什么?

    function* startGame() {
      const 答案 = yield "Do you love JavaScript?";
      if (答案 !== "Yes") {
        return "Oh wow... Guess we're gone here";
      }
      return "JavaScript loves you back ❤️";
    }
    
    const game = startGame();
    console.log(game.next().value); // Do you love JavaScript?
    console.log(game.next("Yes").value); // JavaScript loves you back ❤️
    

    解析:

    generator函数在遇到yield关键字时会“暂停”其执行。首先,我们需要让函数产生字符串Do you love JavaScript?,这可以通过调用game.next().value来完成。上述函数的第一行就有一个yield关键字,那么运行立即停止了,yield表达式本身没有返回值,或者说总是返回undefined, 这意味着此时变量答案undefined

    next方法可以带一个参数,该参数会被当作上一个 yield 表达式的返回值。当我们调用game.next("Yes").value时,先前的 yield 的返回值将被替换为传递给next()函数的参数"Yes"。此时变量 答案 被赋值为 "Yes"if语句返回false,所以JavaScript loves you back ❤️被打印。


  1. 输出什么?

    console.log(String.raw`Hello\nworld`);
    

    解析:

    String.raw函数是用来获取一个模板字符串的原始字符串的,它返回一个字符串,其中忽略了转义符(\n\v\t等)。但反斜杠可能造成问题,因为你可能会遇到下面这种类似情况:

    const path = `C:\Documents\Projects\table.html`
    String.raw`${path}`
    

    这将导致:

    "C:DocumentsProjects able.html"
    

    直接使用String.raw

    String.raw`C:\Documents\Projects\table.html`
    

    它会忽略转义字符并打印:C:\Documents\Projects\table.html

    上述情况,字符串是Hello\nworld被打印出。


  1. 输出什么?

    async function getData() {
      return await Promise.resolve("I made it!");
    }
    
    const data = getData();
    console.log(data);  //Promise {<pending>}
    

    解析:

    异步函数始终返回一个promise。await仍然需要等待promise的解决:当我们调用getData()并将其赋值给data,此时datagetData方法返回的一个挂起的promise,该promise并没有解决。

    如果我们想要访问已解决的值"I made it!",可以在data上使用.then()方法:

    data.then(res => console.log(res))
    

    这样将打印 "I made it!"


  1. 输出什么?

    const myLifeSummedUp = ["☕", "💻", "🍷", "🍫"];
    for(let item in myLifeSummedUp){
      console.log(item);
    } // 0,1,2,3
    for(let item of myLifeSummedUp){
      console.log(item)
    }//"☕", "💻", "🍷", "🍫"
    

    解析:

    记忆技巧:in i开头所以是 index of o开头所以是 object.

    通过for-in循环,我们可以遍历一个对象自有的、继承的、可枚举的、非Symbol的属性。在数组中,可美剧属性是数组元素的“键”,即他们的索引。类似于下面这个对象:

    {0: "☕", 1: "💻", 2: "🍷", 3: "🍫"}

    其中键则是可枚举属性,因此0,1,2,3被记录。

    通过for-of循环,我们可以迭代可迭代的对象(包括Array,Map,Set,String,arguments等)。当我们迭代数组时,在每次迭代中,不同属性的值将被分配给变量item,因此"☕", "💻", "🍷", "🍫"被打印。


prototype__proto__constructor的区别和联系:

代码:

function Foo() {...};
let f1 = new Foo();
  • 需要牢记亮点:

    1. __proto__constructor属性是对象独有的.
    2. prototype是函数独有的,因为函数也是对象,故函数也有__proto__,constructor属性
    • __proto__属性的作用就是当访问一个对象的属性时,如果对象内部不存在该属性,那么就会去他的__prpto__属性所指向的那个对象中(父对象)去找,一直找,直到找到或者直到__proto__属性的终点null,再往上找就相当于在null上取值,会报错。通过__proto__属性将对象连接起来的这条链路即我们所谓的原型链
    • prototype属性的作用就是让该函数所实例化的对象们都可以找到公用的属性和方法,即f1.__proto__ === Foo.prototype
    • constructor属性的含义就是指向改对象的构造函数,所有函数(此时看成对象了)最终的构造函数都指向Function.

  1. 输出什么?

    function checkAge(age) {  if (age < 18) {    const message = "Sorry, you're too young."  } else {    const message = "Yay! You're old enough!"  }  return message}console.log(checkAge(21)) //ReferenceError: message is not defined
    

    解析:

    constlet声明的变量是具有块级作用域的,块是大括号({})之间的任何东西, 即上述情况if / else语句的花括号。由于块级作用域,我们无法在声明的块之外引用变量,因此抛出ReferenceError


  1. 输出什么?

    console.log("I want pizza"[0]) //"I"
    

    解析:

    可以使用方括号表示法获取字符串中特定索引的字符,字符串中的第一个字符具有索引0,依此类推。在这种情况下,我们想要得到索引为0的元素,字符'I'被记录。

    请注意,IE7及更低版本不支持此方法。在这种情况下,应该使用.charAt()


  1. 输出什么?

    // module.js 
    export default () => "Hello world"
    export const name = "Lydia"
    
    // index.js 
    import * as data from "./module"
    
    console.log(data) //{ default: function default(), name: "Lydia" }
    

    解析:

    使用import * as name语法,我们将module.js文件中所有export导入到index.js文件中,并且创建了一个名为data的新对象。在module.js文件中,有两个导出:默认导出和命名导出。默认导出是一个返回字符串“Hello World”的函数,命名导出是一个名为name的变量,其值为字符串“Lydia”

    data对象具有默认导出的default属性,其他属性具有指定exports的名称及其对应的值。


import * as name

用法import * as obj from,这种写法是把所有的输出包裹到obj对象里。如下:

  • 示例1:

    // module.js
    export function fn1(data){
      console.log(1)
    }
    export function fn2(data){
      console.log(2)
    }
    //index.js
    import * as Fn from './index.js'
    Fn.fn1()  // 1
    Fn.fn2()  // 2
    
  • 示例二

    // test.js
    let myName = "Jon";
    let myAge = 18;
    let myfn = function(){
        return "我是"+myName+"!今年"+myAge+"岁了"
    }
    export {
        myName as name,
        myAge as age,
        myfn as fn
    }
    //index.js
    import {fn,age,name} from "./test.js";
    console.log(fn()); //我是Jon!今年19岁了
    console.log(age); //19
    console.log(name); //Jon
    //或者
    import * as info from "./test.js"; //通过*来批量接收,as 来指定接收的名字
    console.log(info.fn()); //我是Jon!今年18岁了
    console.log(info.age); //18
    console.log(info.name); //Jon
    
    
  • 示例三

    重命名export和import,如果导入的多个文件中,变量名字相同,即会产生命名冲突的问题,为了解决该问题,ES6为提供了重命名的方法,当你在导入名称时可以这样做。

    /*************test1.js*****************/
    export let myName = "我来自test1.js";
    /*************test2.js*****************/
    export let myName = "我来自test2.js";
    
    /*************index.js****************/
    import {myName as name1} from "./test1.js";
    import {myName as name2} from "./test2.js";
    console.log(name1); //我来自test1.js
    console.log(name2); //我来自test2.js
    

  1. 输出什么?

    class Person {
      constructor(name) {
        this.name = name
      }
    }
    
    const member = new Person("John")
    console.log(typeof member)  //object
    

    解析:

    类是构造函数的语法糖,如果用构造函数的方式来重写Person类则将是:

    function Person() {
    this.name = name
    }
    

    通过new来调用构造函数,将会生成构造函数Person的实例,对实例执行typeof关键字将返回"object",上述情况打印出"object"


  1. 输出什么?

    let newList = [1, 2, 3].push(4)console.log(newList.push(5))  //Uncaught TypeError: newList.push is not a function
    

    解析:

    .push方法返回数组的长度,而不是数组本身!通过将newList设置为[1,2,3].push(4),实际上newList等于数组的新长度:4

    然后,尝试在newList上使用.push方法。由于newList是数值4,抛出TypeError。


  1. 输出什么?

    function giveLydiaPizza() {  return "Here is pizza!"}const giveLydiaChocolate = () => "Here's chocolate... now go hit the gym already."console.log(giveLydiaPizza.prototype)console.log(giveLydiaChocolate.prototype)
    

    解析:

    常规函数,例如giveLydiaPizza函数,有一个prototype属性,它是一个带有constructor属性的对象(原型对象)。然而,箭头函数,例如giveLydiaChocolate函数,没有这个prototype属性。尝试使用giveLydiaChocolate.prototype访问prototype属性时会返回undefined


  1. 输出什么?

    const name = "Lydia"console.log(name()) //TypeError: name is not a function
    

    解析:

    浏览器的几种报错类型:

    • SyntaxError 语法错误。
    • TypeError类型错误,通常是 *** is not a function,即***不是一个函数。
    • ReferenceError引用错误,通常是 *** is not defined,即***未定义,不同于undefined,underfind不是报错,而是一种数值类型。
    • RangeError是当一个值超出有效范围时发生的错误。主要有几种情况,一是数组长度为负数,二是Number对象的方法参数超出范围,以及函数堆栈超过最大值。
    • URIError是URI相关函数的参数不正确时抛出的错误,主要涉及encodeURI()、decodeURI()、encodeURIComponent()、decodeURIComponent()、escape()和unescape()这六个函数。
    • eval函数没有被正确执行时,会抛出EvalError错误。该错误类型已经不再在ES5中出现了,只是为了保证与以前代码兼容,才继续保留。

  1. 输出什么?

    const getList = ([x, ...y]) => [x, y]
    const getUser = user => { name: user.name, age: user.age }
    
    const list = [1, 2, 3, 4]
    const user = { name: "Lydia", age: 21 }
    
    console.log(getList(list)) // [1, [2, 3, 4]]
    console.log(getUser(user)) // undefined
    

    解析:

    getList函数接收一个数组作为其参数。在getList函数的括号之间,我们立即解构这个数组。您可以将其视为:

    [x, ...y] = [1, 2, 3, 4]
    

    使用剩余的参数... y,我们将所有剩余参数放在一个数组中。在这种情况下,其余的参数是234y的值是一个数组,包含所有其余参数。在这种情况下,x的值等于1,所以当我们打印[x,y]时,会打印[1,[2,3,4]]。检查可以如下

    const [x, ...y] = [1, 2, 3, 4];
    console.log(y)  //[2, 3, 4]
    

    getUser函数接收一个对象。对于箭头函数,如果只返回一个值,我们不必编写花括号。但是,如果您想从一个箭头函数返回一个对象,您必须在圆括号之间编写它,否则不会返回任何值!下面的函数将返回一个对象:

    const getUser = user => ({ name: user.name, age: user.age })
    

    由于在这种情况下不返回任何值,因此该函数返回undefined


  1. 输出什么?
const info = {
  [Symbol('a')]: 'b'
}

console.log(info) //{[Symbol('a')]: 'b'}
console.log(Object.keys(info)) // []

解析:

Symbol类型是不可枚举的。Object.keys方法返回对象上的所有可枚举的键属性。Symbol类型是不可见的,并返回一个空数组。记录整个对象时,所有属性都是可见的,甚至是不可枚举的属性。

这是Symbol的众多特性之一:除了表示完全唯一的值(防止对象意外名称冲突,例如当使用2个想要向同一对象添加属性的库时),您还可以隐藏这种方式对象的属性(尽管不完全。你仍然可以使用Object.getOwnPropertySymbols()方法访问 Symbol


  1. 输出什么?

    class Person {
      constructor() {
        this.name = "Lydia"
      }
    }
    
    Person = class AnotherPerson {
      constructor() {
        this.name = "Sarah"
      }
    }
    
    const member = new Person()
    console.log(member.name) //Sarah
    

    解析:

    我们可以将类设置为等于其他类/函数构造函数。在这种情况下,我们将Person设置为AnotherPerson。这个构造函数的名字是Sarah,所以新的Person实例member上的name属性是Sarah


  1. 输出什么?
function nums(a, b) {
  if
  (a > b)
  console.log('a is bigger')
  else 
  console.log('b is bigger')
  return 
  a + b
}

console.log(nums(4, 2)) //a is bigger undefined
console.log(nums(1, 2)) //b is bigger undefined

解析:

在JavaScript中,我们不必显式地编写分号(;),但是JavaScript引擎仍然在语句之后自动添加分号。这称为自动分号插入。例如,一个语句可以是变量,或者像throwreturnbreak这样的关键字。

在这里,我们在新的一行上写了一个return语句和另一个值a + b。然而,由于它是一个新行,引擎并不知道它实际上是我们想要返回的值。相反,它会在return后面自动添加分号。你可以这样看:

return;
a + b

这意味着永远不会到达a + b,因为函数在return关键字之后停止运行。如果没有返回值,就像这里,函数返回undefined。注意,在if/else语句之后没有自动插入!


  1. 输出什么?
const person = {
  name: "Lydia",
  age: 21
}

for (const [x, y] of Object.entries(person)) {
  console.log(x, y)  //name  Lydia  age  21
}

解析:

Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组,上述情况返回一个二维数组,数组每个元素是一个包含键和值的数组:

[['name','Lydia'],['age',21]]

使用for-of循环,我们可以迭代数组中的每个元素,上述情况是子数组。我们可以使用const [x,y]for-of循环中解构子数组。x等于子数组中的第一个元素,y等于子数组中的第二个元素。

第一个子阵列是[“name”,“Lydia”],其中x等于name,而y等于Lydia。第二个子阵列是[“age”,21],其中x等于age,而y等于21


38.输出什么?

let name = 'Lydia'function getName() {  console.log(name)  let name = 'Sarah'}getName()  //ReferenceError

解析:

每个函数都有其自己的执行上下文。getName函数首先在其自身的上下文(范围)内查找,以查看其是否包含我们尝试访问的变量name。上述情况,getName函数包含其自己的name变量:我们用let关键字和Sarah的值声明变量name

带有let关键字(和const)的变量被提升,但是与var不同,它不会被初始化。在我们声明(初始化)它们之前,无法访问它们。这称为“暂时性死区”。当我们尝试在声明变量之前访问变量时,JavaScript会抛出ReferenceError: Cannot access 'name' before initialization

如果我们不在getName函数中声明name变量,则javascript引擎会查看原型练。会找到其外部作用域有一个名为name的变量,其值为Lydia。在这种情况下,它将打印Lydia


  1. 输出什么?

    const food = ['🍕', '🍫', '🥑', '🍔']const info = { favoriteFood: food[0] }info.favoriteFood = '🍝'console.log(food)    //['🍕', '🍫', '🥑', '🍔']
    

    解析:

    我们将info对象上的favoriteFood属性的值设置为披萨表情符号“🍕”的字符串。字符串是原始数据类型。在JavaScript中,原始数据类型通过值起作用

    在这种情况下,我们将info对象上的favoriteFood属性的值设置为等于food数组中的第一个元素的值,字符串为披萨表情符号('🍕' )。字符串是原始数据类型,并且通过值进行交互,我们更改info对象上favoriteFood属性的值。food数组没有改变,因为favoriteFood的值只是该数组中第一个元素的值的复制,并且与该元素上的元素没有相同的内存引用食物[0]。当我们记录食物时,它仍然是原始数组['🍕','🍫','🥑','🍔']

    若info.favoriteFood的值是引用类型值,如下:

    const food = [{fruit: '🥑'} ,'🍕', '🍫', '🥑', '🍔']
    const info = { favoriteFood: food[0] }
    
    info.favoriteFood.fruit = '🍝'
    
    console.log(food) //[{fruit: '🍝'}, '🍕', '🍫', '🥑', '🍔']
    

  1. 输出什么?

    function* generatorOne() {
      yield ['a', 'b', 'c'];
    }
    
    function* generatorTwo() {
      yield* ['a', 'b', 'c'];
    }
    
    const one = generatorOne()
    const two = generatorTwo()
    
    console.log(one.next().value) // ['a', 'b', 'c']
    console.log(two.next().value) // a
    

    解析:

    通过 yield 关键字, 我们在 Generator 函数里执行yield表达式. 通过 yield* 关键字, 我们可以在一个Generator 函数里面执行(yield表达式)另一个 Generator 函数, 或可遍历的对象 (如数组).

    在函数 generatorOne 中, 我们通过 yield 关键字 yield 了一个完整的数组 ['a', 'b', 'c']。函数one通过next方法返回的对象的value 属性的值 (one.next().value) 等价于数组 ['a', 'b', 'c'].


  1. 哪些方法修改了原数组?

    const emojis = ['✨', '🥑', '😍']emojis.map(x => x + '✨')emojis.filter(x => x !== '🥑')emojis.find(x => x !== '🥑')emojis.reduce((acc, cur) => acc + '✨')emojis.slice(1, 2, '✨') emojis.splice(1, 2, '✨')
    

    解析:

    使用splice方法,我们通过删除,替换或添加元素来修改原始数组。在这种情况下,我们从索引1中删除了2个元素(我们删除了'🥑''😍'),同时添加了✨emoji表情。

    mapfilterslice返回一个新数组,find返回一个元素,而reduce返回一个减小的值。


  1. 输出什么?

    function compareMembers(person1, person2 = person) {
      if (person1 !== person2) {
        console.log("Not the same!")
      } else {
        console.log("They are the same!")
      }
    }
    
    const person = { name: "Lydia" }
    
    compareMembers(person)  // They are the same!
    

    解析:

    对象通过引用传递。当我们检查对象的严格相等性(===)时,我们正在比较它们的引用。

    我们将“person2”的默认值设置为“person”对象,并将“person”对象作为“person1”的值传递。

    这意味着两个值都引用内存中的同一位置,因此它们是相等的。

    运行“ else”语句中的代码块,并记录They are the same!


  1. 结果是什么?

    Promise.resolve(5) //Promise {<fulfilled>: 5}
    

    解析:

    我们可以将我们想要的任何类型的值传递Promise.resolve,无论是否promise。该方法本身返回带有已解析值的Promise (<fulfilled>)。如果您传递常规函数,它将是具有常规值的已解决promise。如果你通过了promise,它将是一个已经resolved的且带有传的值的promise。

    上述情况,我们传了数字5,因此返回一个resolved状态的promise,resolve值为5


  1. 输出什么?

    const person = {
      name: "Lydia",
      age: 21
    }
    
    const changeAge = (x = { ...person }) => x.age += 1
    const changeAgeAndName = (x = { ...person }) => {
      x.age += 1
      x.name = "Sarah"
    }
    
    changeAge(person)
    changeAgeAndName()
    
    console.log(person) //{ name: "Lydia", age: 22 }
    

    解析:

    函数 changeAge 和函数 changeAgeAndName 有着不同的参数,定义一个 生成的对象 { ...person }。这个对象有着所有 person 对象 中 k/v 值的副本。

    首项, 我们调用 changeAge 函数并传递 person 对象作为它的参数。这个函数对 age 属性进行加一操作。person 现在是 { name: "Lydia", age: 22 }

    然后,我们调用函数 changeAgeAndName ,然而我们没有传递参数。取而代之,x 的值等价 new 生成的对象: { ...person }。因为它是一个新生成的对象,它并不会对对象 person 造成任何副作用。person 仍然等价于 { name: "Lydia", age: 22 }


  1. 哪一个方法会返回 'Hello world!'

    const myMap = new Map()const myFunc = () => 'greeting'myMap.set(myFunc, 'Hello world!')//1myMap.get('greeting')//2  ---返回Hello worldmyMap.get(myFunc)//3myMap.get(() => 'greeting')
    

    解析:

    当通过 set 方法添加一个键值对,一个传递给 set方法的参数将会是键名,第二个参数将会是值。在这个case里,键名为 函数 () => 'greeting',值为'Hello world'myMap 现在就是 { () => 'greeting' => 'Hello world!' }

    1 是错的,因为键名不是 'greeting' 而是 () => 'greeting'。3 是错的,因为我们给get 方法传递了一个新的函数。对象受 引用 影响。函数也是对象,因此两个函数严格上并不等价,尽管他们相同:他们有两个不同的内存引用地址。


  1. 将会发生什么?

    let config = {  alert: setInterval(() => {    console.log('Alert!')  }, 1000)}config = null
    

    解析:

    一般情况下当我们将对象赋值为 null, 那些对象会被进行 垃圾回收(garbage collected) 因为已经没有对这些对象的引用了。然而,setInterval的参数是一个箭头函数(所以上下文绑定到对象 config 了),回调函数仍然保留着对 config的引用。只要存在引用,对象就不会被垃圾回收。因为没有被垃圾回收,setInterval 的回调每1000ms (1s)会被调用一次。


  1. 输出什么?

    console.log(`${(x => x)('I love')} to program`)  //I love to program
    

    解析:

    带有模板字面量的表达式首先被执行。相当于字符串会包含表达式,这个立即执行函数 (x => x)('I love') 返回的值. 我们向箭头函数 x => x 传递 'I love' 作为参数。x 等价于返回的 'I love'。这就是结果 I love to program

    x => x 匿名函数是

    (x) => {return x}
    

    的缩写,当只有一个参数时参数可以省略小括号,当只含有一个表达式时,可省略掉了{ ... }和return


  1. 输出什么?

    class Counter {
      #number = 10
    
      increment() {
        this.#number++
      }
    
      getNum() {
        return this.#number
      }
    }
    
    const counter = new Counter()
    counter.increment()
    
    console.log(counter.#number)  //SyntaxError
    

    解析:

    在 ES2020 中,通过 # 我们可以给 class 添加私有变量。在 class 的外部我们无法获取该值。当我们尝试输出 counter.#number,语法错误被抛出:我们无法在 class Counter 外部获取它!


  1. 以下哪一项会对对象 person 有副作用?

    const person = {
     name: "Lydia Hallie",
     address: {
      street: "100 Main St"
     }
    };
    
    Object.freeze(person);
    
    //A: person.name = "Evan Bacon"
    //B: delete person.address
    //C: person.address.street = "101 Main St"
    //D: person.pet = { name: "Mara" }
    

    解析:

    使用方法 Object.freeze 对一个对象进行 冻结不能对属性进行添加,修改,删除

    然而,它仅 对对象进行 冻结,意味着只有 对象中的 直接 属性被冻结。如果属性是另一个 object,像案例中的 addressaddress 中的属性没有被冻结,仍然可以被修改。

  2. 以下哪一项会对对象 person 有副作用?

    const person = { name: "Lydia Hallie" };Object.seal(person);//A: person.name = "Evan Bacon"//B: person.age = 21//C: delete person.name//D: Object.assign(person, { age: 21 })
    

    解析:

    使用 Object.seal 我们可以防止新属性 被添加,或者存在属性 被移除.

    然而,你仍然可以对存在属性进行更改。


  1. 我们需要向对象 person 添加什么,以致执行 [...person] 时获得形如 ["Lydia Hallie", 21] 的输出?

    const person = {  name: "Lydia Hallie",  age: 21}[...person] // ["Lydia Hallie", 21]
    

    解析:

    *[Symbol.iterator]() { for (let x in this) yield* Object.values(this) }
    

    对象默认并不是可迭代的。如果迭代规则被定义,则一个对象是可迭代的(An iterable is an iterable if the iterator protocol is present)。我们可以通过添加迭代器symbol [Symbol.iterator] 来定义迭代规则,其返回一个 generator 对象,比如说构建一个 generator 函数 *[Symbol.iterator]() {}。如果我们想要返回数组 ["Lydia Hallie", 21]: yield* Object.values(this),这个 generator 函数一定要 yield 对象 personObject.values


  1. 哪一个选项会导致报错?

    const emojis = ["🎄", "🎅🏼", "🎁", "⭐"];
    
    /* 1 */ emojis.push("🦌");
    /* 2 */ emojis.splice(0, 2);
    /* 3 */ emojis = [...emojis, "🥂"];  //报错 const不能重新赋值
    /* 4 */ emojis.length = 0;
    

    解析:

    const 关键字意味着我们不能 重定义 变量中的值,它 仅可读。然,值本身不可修改。数组 emojis 中的值可被修改,如 push 新的值, 拼接,又或者将数组的长度设置为0


  1. 输出什么?

    class Bird {
     constructor() {
      console.log("I'm a bird. 🦢");
     }
    }
    
    class Flamingo extends Bird {
     constructor() {
      console.log("I'm pink. 🌸");
      super();
     }
    }
    
    const pet = new Flamingo();
    

    解析:

    我们创建了类 Flamingo 的实例 pet。当我们实例化这个实例,Flamingo 中的 constructor 被调用。首相,输出 "I'm pink. 🌸", 之后我们调用super()super() 调用父类的构造函数,BirdBird 的构造函数被调用,并输出 "I'm a bird. 🦢"


  1. 选择哪一个?

    const teams = [
     { name: "Team 1", members: ["Paul", "Lisa"] },
     { name: "Team 2", members: ["Laura", "Tim"] }
    ];
    
    function* getMembers(members) {
     for (let i = 0; i < members.length; i++) {
      yield members[i];
     }
    }
    
    function* getTeams(teams) {
     for (let i = 0; i < teams.length; i++) {
      // ✨ SOMETHING IS MISSING HERE ✨
     }
    }
    
    const obj = getTeams(teams);
    obj.next(); // { value: "Paul", done: false }
    obj.next(); // { value: "Lisa", done: false }
    
    //A: yield getMembers(teams[i].members)
    //B: yield* getMembers(teams[i].members) ---true
    //C: return getMembers(teams[i].members)
    //D: return yield getMembers(teams[i].members)
    

    解析:

    为了遍历 teams 数组中对象的属性 members 中的每一项,我们需要将 teams[i].members 传递给 Generator 函数 getMembers。Generator 函数返回一个 generator 对象。为了遍历这个 generator 对象中的每一项,我们需要使用 yield*.

    如果我们没有写 yieldreturn yield 或者 return,整个 Generator 函数不会第一时间 return 当我们调用 next 方法.

(记录在公众号或各帖子上看到的觉得有意思的题目,欢迎关注一起学习

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