# 第1-10题

# 1、写React/Vue项目时为什么要在列表组件中写Key,其作用是什么?

是因为要标记当前组件唯一性,作用是在dom结构更改之后能够复用,减少重绘。

key是给每一个Vnode的唯一Id,可以依靠Key,更准确,更快的拿到oldVnode中对应的节点

key的作用是为了在数据变化时强制更新组件,以避免"原地复用"带来的副作用。

(只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出。)

# 2、['1','2','3'].map(parseInt) what & why?

答案 [1, NAN, NAN];

map((item, index))
parseInt(string, radix)
// 接收两个参数,第一个表示被处理的值(字符串),第二个表示为解析时的基数
// radix 一个介于2和36之间的整数(数学系统的基础),表示上述字符串的基数。默认为10。ES5之后,以前是默认8
// ratio 值为 (2, 8)的时候,parseInt的第一个参数必须小于ratio
// 如果该参数小于 2 或者大于 36,则 parseInt() 将返回 NaN。
parseInt('1', 0); // radix为0时,且string参数不以“0x”和“0”开头时,按照10为基数处理。这个时候返回1
parseInt('2', 1); // 基数为1(1进制)表示的数中,2进制中只能存在0和1,所以无法解析,返回NaN
parseInt('3', 2);  // 基数为2(2进制)表示的数中,3进制中只能存在0和1和2,,所以无法解析,返回NaN

['10', '10', '10', '10', '10'].map(parseInt);
// [10, NaN, 2, 3, 4]

# 3、什么是防抖和节流?有什么区别?如何实现

故名思议: 防止抖动和节约流量

防抖 (opens new window): 在设置的规定时间后执行,如果在规定时间内执行,则重置函数,重新设置。

触发高频函数后n秒时间内函数只会执行一次,如果n秒内高频时间再次被触发,则重新计算时间。

节流 (opens new window):在设置的规定时间内执行,如果在规定时间内再次执行,则拒绝执行。

高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率。

# 4、介绍下Map、Set、WeakSet和WeakMap的区别。 (opens new window)

Same-value-zero equality 算法,类似===,区别在于认为NaN等于自身。 Set

  1. 成员不能重复
  2. 只有健值,没有键名,有点类数组(有size)
  3. 方法有add、delete、has、clear
  4. 可以遍历,有keys、values、entries、forEach

WeakSet(不同set的地方)

  1. 成员只能是对象,而不是其他类型的值。
  2. 成员都是弱引用,随时可以消失。可以用来保存DOM节点,不容易造成内存泄漏

(这是因为垃圾回收机制根据对象的可达性(reachability)来判断回收, 如果对象还能被访问到,垃圾回收机制就不会释放这块内存。结束使用该值之后, 有时会忘记取消引用,导致内存无法释放,进而可能会引发内存泄漏。 WeakSet 里面的引用,都不计入垃圾回收机制,所以就不存在这个问题。 因此,WeakSet 适合临时存放一组对象,以及存放跟对象绑定的信息。 只要这些对象在外部消失,它在 WeakSet 里面的引用就会自动消失。)

Map

  1. 本质上是健值对的集合(Hash结构),类似对象。健可以是各类型的值。Object结构"字符串-值",Map结构"值-值";
  2. 有set、get、has、delete、clear、size
  3. 可以遍历, keys、values、entries、forEach

WeakMap

  1. 直接受对象作为键名(null除外),不接受其他类型的值作为键名。
  2. 键名所指向的对象,不计入垃圾回收机制。注意,WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用。
  3. 不能遍历。没有keys、values、entries、forEach,也没有size,
  4. get()、set()、has()、delete()。

因为没有办法列出所有键名,某个键名是否存在完全不可预测, 跟垃圾回收机制是否运行相关。这一刻可以取到键名,下一刻垃圾回收机制突然运行了, 这个键名就没了,为了防止出现不确定性,就统一规定不能取到键名。二是无法清空,即不支持clear方法。

# 5、介绍下深度优先遍历和广度优先遍历,如何实现。

# 6、请分别用深度优先遍历以及广度优先遍历思想实现一个拷贝函数。

function getEmpty(o) {
  if (Object.prototype.toString.call(o) === '[object Object]') {
    return {};
  }
  if (Object.prototype.toString.call(o) === '[object Array]') {
    return [];
  }
  return o;
}

function deepCopyDFS(origin) {
  let queue = [];
  let mep = new Map();

  let target = getEmpty(origin);
  if (target !== origin) {
    queue.push([origin, target]);
    mep.set(origin, target);
  }
  while (queue.length) {
    let [ori, tar] = queue.shift();

    for (let key in ori) {
      if (map.get(ori[key])) {
        tar[key] = map.get(ori[key])
      }
      tar[key] = getEmpty(ori[key]);
      if (tar[key] !== ori[key]) {
        queue.push([ori[key], tar[key]]);
        map.set(ori[key], tar[key]);
      }
    }
  }
  return target;
}

# 7、ES5/Es6的继承,除了写法以外还有什么区别。

优先理解 constructor、prototype、proto (opens new window)

区别:

  • ES5的继承机制,是先创造一个独立的子类的实例对象,然后再将父类的方法添加到这个对象上面,即"实例在前,继承在后"。
  • ES6的继承机制,则是先将父类的属性和方法加到一个空对象上面,然后再将该对象作为子类的实例,即"继承在前,实例在后"。

这也是为什么ES6的继承必须先调用super方法,因为这一步会生成一个继承父类的this的对象,没有这一步就无法继承父类。

const Person = function (name) {
  this.name = name;
}

function _new(Fuc) {
  return function () {
    const obj = Object.create();
    obj._proto_ = Fuc.prototype;
    const result = Person.apply(obj, angument);
    return typeof result === 'object' ? result : obj;
  }
}

class point {
}

class ColorPoint extends Point {
  constructor() {
    // ES6规定,子类必须在constructor()方法中调用super(), 否则就会报错。
    // 这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例和属性
    // 然后再对其进行加工,添加子类自己的实例属性和方法。如果不调用super()方法,子类就得不到自己的this对象。
    super(); // 父类的构造函数(返回的是子类的实例) 也可以当作对象使用。
    // point.prototype.constructor.call(this)。
  }
}

class A {
  constructor() {
    console.log(new.target.name);
  }
}

class B extends A {
  constructor() {
    super();
  }
}

new A() // A
new B() // B


class A {
  p() {
    return 2;
  }
}

class B extends A {
  constructor() {
    super();
    console.log(super.p()); // 2 等价与A.prototype.p();
    // 如果super作为对象,用在静态方法之中,这时super将指向父类,而不是父类的原型对象。
  }
}

let b = new B();
// (1)子类的__proto__属性,表示构造函数的继承,总是指向父类。
// (2)子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。
class A {
}

class B extends A {
}

B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true

# 8、setTimeout、Promise、Async/Await的区别

宏观/微观的区分

  • setTimeout的回掉函数放到宏任务队列里,等到执行栈清空以后执行。
  • promise的回掉函数放到微任务队列里,等同步代码执行完执行。
  • async表示声明一个异步函数,await后面跟一个表达式,在遇到时会立即执行表达式,然后让出执行栈,同步代码继续执行。

# 9、Async/Await如何通过同步的方式实现异步

var fetch = require('node-fetch');

function* gen() {
  var url = 'https://api.github.com/users/github';
  var result = yield fetch(url);
  console.log(result.bio);
}

var g = gen();
var result = g.next();

result.value.then(function (data) {
  return data.json();
}).then(function (data) {
  g.next(data);
});

# 10:(头条)异步笔试题

注意点: await 会让出执行栈 await后面的表达式会先执行一遍,将await后面的代码加入到microtask中,然后就会跳出整个async函数来执行后面的代码。

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    console.log('async2');
}
console.log('script start');
setTimeout(function() {
    console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});
console.log('script end');

// 1. script start
// 2. async1 start
// 3. async2
// 4. promise1
// 5. script end
// 6. async1 end
// 7. promise2
// 8.setTimeout
Last Updated: 3/31/2022, 9:04:41 AM