# 前端面试题及答案汇总(三) 21-30

# 第 21 题:有以下 3 个判断数组的方法,请分别介绍它们之间的区别和优劣

  1. Object.prototype.toString.call()

每一个继承 Object 的对象都有 toString 方法,如果 toString 方法没有重写的话,会返回 [Object type],其中 type 为对象的类型。但当除了 Object 类型的对象外,其他类型直接使用 toString 方法时,会直接返回都是内容的字符串,所以我们需要使用 call 或者 apply 方法来改变 toString 方法的执行上下文。

const an = ["Hello", "An"];
an.toString(); // "Hello,An"
Object.prototype.toString.call(an); // "[object Array]"
1
2
3

这种方法对于所有基本的数据类型都能进行判断,即使是 null 和 undefined 。

Object.prototype.toString.call("An"); // "[object String]"
Object.prototype.toString.call(1); // "[object Number]"
Object.prototype.toString.call(Symbol(1)); // "[object Symbol]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(function() {}); // "[object Function]"
Object.prototype.toString.call({ name: "An" }); // "[object Object]"
1
2
3
4
5
6
7

Object.prototype.toString.call() 常用于判断浏览器内置对象时。

  1. instanceof

instanceof 的内部机制是通过判断对象的原型链中是不是能找到类型的 prototype

使用 instanceof 判断一个对象是否为数组,instanceof 会判断这个对象的原型链上是否会找到对应的 Array 的原型,找到返回 true,否则返回 false。

[] instanceof Array; // true
1

instanceof 只能用来判断对象类型,原始类型不可以。并且所有对象类型 instanceof Object 都是 true。

  1. Array.isArray()
  • 功能:用来判断对象是否为数组

  • instanceof 与 isArray

当检测 Array 实例时,Array.isArray 优于 instanceof ,因为 Array.isArray 可以检测出 iframes

var iframe = document.createElement("iframe");
document.body.appendChild(iframe);
xArray = window.frames[window.frames.length - 1].Array;
var arr = new xArray(1, 2, 3); // [1,2,3]

// Correctly checking for Array
Array.isArray(arr); // true
Object.prototype.toString.call(arr); // true
// Considered harmful, because doesn't work though iframes
arr instanceof Array; // false
1
2
3
4
5
6
7
8
9
10
  • Array.isArray()Object.prototype.toString.call()

Array.isArray()是 ES5 新增的方法,当不存在 Array.isArray() ,可以用 Object.prototype.toString.call() 实现。

if (!Array.isArray) {
  Array.isArray = function(arg) {
    return Object.prototype.toString.call(arg) === "[object Array]";
  };
}
1
2
3
4
5

# 第 22 题:介绍下重绘和回流(Repaint & Reflow),以及如何进行优化

1. 浏览器渲染机制

  • 浏览器采用流式布局模型(Flow Based Layout)
  • 浏览器会把 HTML 解析成 DOM,把 CSS 解析成 CSSOM,DOM 和 CSSOM 合并就产生了渲染树(Render Tree)。
  • 有了 RenderTree,我们就知道了所有节点的样式,然后计算他们在页面上的大小和位置,最后把节点绘制到页面上。
  • 由于浏览器使用流式布局,对 Render Tree 的计算通常只需要遍历一次就可以完成,但 table 及其内部元素除外,他们可能需要多次计算,通常要花 3 倍于同等元素的时间,这也是为什么要避免使用 table 布局的原因之一。

2. 重绘

由于节点的几何属性发生改变或者由于样式发生改变而不会影响布局的,称为重绘,例如outline, visibility, colorbackground-color等,重绘的代价是高昂的,因为浏览器必须验证 DOM 树上其他节点元素的可见性。

3. 回流

回流是布局或者几何属性需要改变就称为回流。回流是影响浏览器性能的关键因素,因为其变化涉及到部分页面(或是整个页面)的布局更新。一个元素的回流可能会导致了其所有子元素以及 DOM 中紧随其后的节点、祖先节点元素的随后的回流。

<body>
  <div class="error">
    <h4>我的组件</h4>
    <p><strong>错误:</strong>错误的描述…</p>
    <h5>错误纠正</h5>
    <ol>
      <li>第一步</li>
      <li>第二步</li>
    </ol>
  </div>
</body>
1
2
3
4
5
6
7
8
9
10
11

在上面的 HTML 片段中,对该段落(<p>标签)回流将会引发强烈的回流,因为它是一个子节点。这也导致了祖先的回流(div.error 和 body – 视浏览器而定)。此外,<h5><ol>也会有简单的回流,因为其在 DOM 中在回流元素之后。大部分的回流将导致页面的重新渲染。

回流必定会发生重绘,重绘不一定会引发回流。

4. 浏览器优化

现代浏览器大多都是通过队列机制来批量更新布局,浏览器会把修改操作放在队列中,至少一个浏览器刷新(即 16.6ms)才会清空队列,但当你获取布局信息的时候,队列中可能有会影响这些属性或方法返回值的操作,即使没有,浏览器也会强制清空队列,触发回流与重绘来确保返回正确的值。

主要包括以下属性或方法:

  • offsetTopoffsetLeftoffsetWidthoffsetHeight

  • scrollTopscrollLeftscrollWidthscrollHeight

  • clientTopclientLeftclientWidthclientHeight

  • widthheight

  • getComputedStyle()

  • getBoundingClientRect()

    所以,我们应该避免频繁的使用上述的属性,他们都会强制渲染刷新队列。

# 第 23 题:介绍下观察者模式和订阅-发布模式的区别,各自适用于什么场景

参考这

  • 在观察者模式中,观察者是知道 Subject 的,Subject 一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。

  • 在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。

  • 观察者模式大多数时候是同步的,比如当事件触发,Subject 就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)。

  • 观察者 模式需要在单个应用程序地址空间中实现,而发布-订阅更像交叉应用模式。

观察者模式

var subject = {
  observers: [],
  notify() {
    this.observers.forEach(observer => {
      observer.update();
    });
  },
  attach(observer) {
    this.observers.push(observer);
  }
};
var observer = {
  update() {
    alert("updated");
  }
};
subject.attach(observer);
subject.notify();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

发布订阅模式

var publisher = {
  publish(pubsub) {
    pubsub.publish();
  }
};
var pubsub = {
  subscribes: [],
  publish() {
    this.subscribes.forEach(subscribe => {
      subscribe.update();
    });
  },
  subscribe(sub) {
    this.subscribes.push(sub);
  }
};
var subscribe = {
  update() {
    console.log("update");
  },
  subscribe(pubsub) {
    pubsub.subscribe(this);
  }
};
subscribe.subscribe(pubsub);
publisher.publish(pubsub);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 第 24 题:聊聊 Redux 和 Vuex 的设计思想

# 第 25 题:说说浏览器和 Node 事件循环的区别

# 第 26 题:介绍模块化发展历程

参考

# 第 27 题:全局作用域中,用 const 和 let 声明的变量不在 window 上,那到底在哪里?如何去获取?。

# 第 28 题:cookie 和 token 都存放在 header 中,为什么不会劫持 token?

# 第 29 题:聊聊 Vue 的双向数据绑定,Model 如何改变 View,View 又是如何改变 Model 的

# 第 30 题:两个数组合并成一个数组

请把俩个数组 [A1, A2, B1, B2, C1, C2, D1, D2] 和 [A, B, C, D],合并为 [A1, A2, A, B1, B2, B, C1, C2, C, D1, D2, D]。

function concatArr(arr1, arr2) {
    if(!arr1 || !arr2) {
        return null;
    } 
    if(arr1.length === 0) {
        return arr2;
    } else if (arr2.length === 0) {
        return arr1;
    }
    const len1 = arr1.length;
    const len2 = arr2.length;
    let p1 = 0;
    let p2 = 0;
    let p = arr1[p1].slice(0, 1).charCodeAt() < arr2[p2].slice(0, 1).charCodeAt() ? arr1[p1].slice(0, 1) : arr2[p2].slice(0, 1);
    let res = [];
    while(p1 < len1 && p2 < len2) {
        if(arr1[p1].slice(0, 1) === p) {
            res.push(arr1[p1++]);
        } else if(arr2[p2].slice(0, 1) === p) {
            res.push(arr2[p2++]);
        } else {
            p = arr1[p1].slice(0, 1).charCodeAt() < arr2[p2].slice(0, 1).charCodeAt() ? arr1[p1].slice(0, 1) : arr2[p2].slice(0, 1);
        }
    }
    if(p1 < len1) {
        while(p1 < len1) {
            res.push(arr1[p1++]);
        }
    }
    if(p2 < len2) {
        while(p2 < len2) {
            res.push(arr2[p2++]);
        }
    }
    return res;
}
var a1 = ['A1', 'A2', 'C1', 'C2', 'D1', 'D2'] 
 var a2 = ['A', 'B', 'D']
 const arr = concatArr(a1, a2);
 console.log(arr);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40