# 第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
- 成员不能重复
- 只有健值,没有键名,有点类数组(有size)
- 方法有add、delete、has、clear
- 可以遍历,有keys、values、entries、forEach
WeakSet(不同set的地方)
- 成员只能是对象,而不是其他类型的值。
- 成员都是弱引用,随时可以消失。可以用来保存DOM节点,不容易造成内存泄漏
(这是因为垃圾回收机制根据对象的可达性(reachability)来判断回收, 如果对象还能被访问到,垃圾回收机制就不会释放这块内存。结束使用该值之后, 有时会忘记取消引用,导致内存无法释放,进而可能会引发内存泄漏。 WeakSet 里面的引用,都不计入垃圾回收机制,所以就不存在这个问题。 因此,WeakSet 适合临时存放一组对象,以及存放跟对象绑定的信息。 只要这些对象在外部消失,它在 WeakSet 里面的引用就会自动消失。)
Map
- 本质上是健值对的集合(Hash结构),类似对象。健可以是各类型的值。Object结构"字符串-值",Map结构"值-值";
- 有set、get、has、delete、clear、size
- 可以遍历, keys、values、entries、forEach
WeakMap
- 直接受对象作为键名(null除外),不接受其他类型的值作为键名。
- 键名所指向的对象,不计入垃圾回收机制。注意,WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用。
- 不能遍历。没有keys、values、entries、forEach,也没有size,
- 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
第11-20题 →