前端面试卷三

219 阅读10分钟

有以下 3 个判断数组的方法,请分别介绍它们之间的区别和优劣Object.prototype.toString.call() 、 instanceof 以及 Array.isArray()

Object.prototype.toString.call()

每一个继承 Object 的对象都有 toString 方法,如果 toString 方法没有重写的

话,会返回 [Object type],其中 type 为对象的类型。但当除了 Object 类型的
对象外,其他类型直接使用 toString 方法时,会直接返回都是内容的字符串,
所以我们需要使用 call 或者 apply 方法来改变 toString 方法的执行上下文。
这种方法对于所有基本的数据类型都能进行判断,即使是 null 和 undefined 。
const an = ['Hello','An'];
an.toString(); // "Hello,An"Object.prototype.toString.call(an); // "[object Array]"
instanceof: instanceof 的内部机制是通过判断对象的原型链中是不是能找到类型的prototype。
使用 instanceof 判断一个对象是否为数组,instanceof 会判断这个对象的原型
链上是否会找到对应的 Array 的原型,找到返回 true,否则返回 false。
但 instanceof 只能用来判断对象类型,原始类型不可以。并且所有对象类型
instanceof Object 都是 true。
[] instanceof Array; // true[] instanceof Object; // true
Array.isArray():
功能:用来判断对象是否为数组,instanceof 与 isArray,当检测 Array 实例时,Array.isArray 优于 instanceof ,因为 Array.isArray 可以检测出 iframes。 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]';   
    }
}

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

1. 浏览器渲染机制

浏览器采用流式布局模型(Flow Based Layout)
浏览器会把 HTML 解析成 DOM,把 CSS 解析成 CSSOM,DOM 和 CSSOM 合并就
产生了渲染树(Render Tree)。
有了 RenderTree,我们就知道了所有节点的样式,然后计算他们在页面上的大
小和位置,最后把节点绘制到页面上。
由于浏览器使用流式布局,对 Render Tree 的计算通常只需要遍历一次就可以完
成,但 table 及其内部元素除外,他们可能需要多次计算,通常要花 3 倍于同
等元素的时间,这也是为什么要避免使用 table 布局的原因之一。
2\. 重绘
由于节点的几何属性发生改变或者由于样式发生改变而不会影响布局的,称为
重绘,例如 outline, visibility, color、background-color 等,重绘的代价是高昂的,
因为浏览器必须验证 DOM 树上其他节点元素的可见性。
3\. 回流(重排)
回流是布局或者几何属性需要改变就称为回流。回流是影响浏览器性能的关键
因素,因为其变化涉及到部分页面(或是整个页面)的布局更新。一个元素的
回流可能会导致了其所有子元素以及 DOM 中紧随其后的节点、祖先节点元素
的随后的回流。
回流必定会发生重绘,重绘不一定会引发回流。
4\. 浏览器优化
现代浏览器大多都是通过队列机制来批量更新布局,浏览器会把修改操作放在
队列中,至少一个浏览器刷新(即 16.6ms)才会清空队列,但当你获取布局信
息的时候,队列中可能有会影响这些属性或方法返回值的操作,即使没有,浏
览器也会强制清空队列,触发回流与重绘来确保返回正确的值。
主要包括以下属性或方法:
1、offsetTop、offsetLeft、offsetWidth、offsetHeight
2、scrollTop、scrollLeft、scrollWidth、scrollHeight
3、clientTop、clientLeft、clientWidth、clientHeight
4、width、height
5、getComputedStyle()
6、getBoundingClientRect()
所以,我们应该避免频繁的使用上述的属性,他们都会强制渲染刷新队列。
5\. 减少重绘与回流
CSS:
1、使用 transform 替代 top
2、使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回
流(改变了布局)
3、避免使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局。
4、尽可能在 DOM 树的最末端改变 class,回流是不可避免的,但可以减少其影
响。尽可能在 DOM 树的最末端改变 class,可以限制了回流的范围,使其影响
尽可能少的节点。
5、避免设置多层内联样式,CSS 选择符从右往左匹配查找,避免节点层级过多。
<div>   <a> 
    <span></span> 
  </a>
</div>

<style> 
  span { color: red }   div > a > span { color: red } </style>
对于第一种设置样式的方式来说,浏览器只需要找到页面中所有的 span 标签
然后设置颜色,但是对于第二种设置样式的方式来说,浏览器首先需要找到所
有的 span 标签,然后找到 span 标签上的 a 标签,最后再去找到 div 标签,
然后给符合这种条件的 span 标签设置颜色,这样的递归过程就很复杂。所以
我们应该尽可能的避免写过于具体的 CSS 选择器,然后对于 HTML 来说也尽
量少的添加无意义标签,保证层级扁平。
将动画效果应用到 position 属性为 absolute 或 fixed 的元素上,避免影响其他元
素的布局,这样只是一个重绘,而不是回流,同时,控制动画速度可以选择
requestAnimationFrame,,避免使用 CSS 表达式,可能会引发回流。
将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响
别的节点,例如 will-change、video、iframe 等标签,浏览器会自动将该节点变
为图层。
CSS3 硬件加速(GPU 加速),使用 css3 硬件加速,可以让 transform、opacity、
filters 这些动画不会引起回流重绘 。但是对于动画的其它属性,比如
background-color 这些,还是会引起回流重绘的,不过它还是可以提升这些动画
的性能。
JavaScript:
1、避免频繁操作样式,最好一次性重写 style 属性,或者将样式列表定义为 class
并一次性更改 class 属性。
2、避免频繁操作 DOM,创建一个 documentFragment,在它上面应用所有 DOM
操作,最后再把它添加到文档中。
3、避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个
变量缓存起来。
4、对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素
及后续元素频繁回流。

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

观察者模式中主体和观察者是互相感知的,发布-订阅模式是借助第三方来实现
调度的,发布者和订阅者之间互不感知

观察者模式

var subject = {    //要监听的主题对象
    observers: [],    //保存所有的观察者
    attach(observer){    //订阅方法
        this.observers.push(observer)
    }
    notify(){
        this.observer.forEach(item=>item.update())    //派发事件
    }
}

var observer = {
    update(){
        console.log('更新了')
    }
}

subject.attach(observer)
subject.notify()

订阅发布模式

var publisher = {    //发布者
    publish(chanel){
        chanel.publish()
    }
}

var chanel = {    //平台
    subscribers : [],    //保存所有的订阅者
    publish(){
        this.subscriber.forEach(item=>item.update())
    }
    subcribe(subcriber){
        this.subcribers.push(subcriber)
    }
}

var subcriber = {    //订阅者
    update(){
        console.log('更新了')
    }
    subcribe(){
        changel.subcribe(this)
    }
}
subscribe.subscribe(pubsub)
publisher.publish(pubsub)

两种模式本质都是一样的,主要关键点都在于注册(添加到注册数组中)和触发(触发注册数组中的内容),只是订阅/发布模式对注册和触发进行了解耦。可以看到,使用订阅发布模式中发布者触发publish的时候,可以选择触发哪一些订阅者集合(因为publish参数传递了中间集合,可以定义多个chanel集合),而观察者模式则只能触发所有的被观察对象。

聊聊 Redux 和 Vuex 的设计思想

链接:https://zhuanlan.zhihu.com/p/53599723

不管是 Vue,还是 React,都需要管理状态(state),比如组件之间都有共享
状态的需要。什么是共享状态?比如一个组件需要使用另一个组件的状态,或
者一个组件需要改变另一个组件的状态,都是共享状态。
在软件开发里,有些通用的思想,比如隔离变化,约定优于配置等,隔离变化
就是说做好抽象,把一些容易变化的地方找到共性,隔离出来,不要去影响其
他的代码。约定优于配置就是很多东西我们不一定要写一大堆的配置,比如我
们几个人约定,view 文件夹里只能放视图,不能放过滤器,过滤器必须放到
filter 文件夹里,那这就是一种约定,约定好之后,我们就不用写一大堆配置文
件了,我们要找所有的视图,直接从 view 文件夹里找就行。
根据这些思想,对于状态管理的解决思路就是:把组件之间需要共享的状态抽
取出来,遵循特定的约定,统一来管理,让状态的变化可以预测。根据这个思
路,产生了很多的模式和库.

说说浏览器和 Node 事件循环的区别

链接:www.jianshu.com/p/b221e6e36…

其中一个主要的区别在于浏览器的 event loop 和 nodejs 的 event loop 在处理
异步事件的顺序是不同的,nodejs 中有 micro event;其中 Promise 属于 micro event
该异步事件的处理顺序就和浏览器不同.nodejs V11.0 以上这两者之间的顺序就相同了

function test () {   
    console.log('start')   
    setTimeout(() => {     
        console.log('children2')     
        Promise.resolve().then(() => {      
            console.log('children2-1')    
        })   
    }, 0)   
    setTimeout(() => {     
        console.log('children3')     
        Promise.resolve().then(() =>       {
            console.log('children3-1')}
        )
    }, 0)   
    promise.resolve().then(() => {console.log('children1')})   
    console.log('end') 
}
test()// 
以上代码在 node11以下版本的执行结果(先执行所有的宏任务,再执行微任务)
// start // end // children2 // children3 // children2-1 // children3-1 //children1// 
以上代码在 node11 及浏览器的执行结果(顺序执行宏任务和微任务)
// start// end// children1// children2// children2-1// children3// children3-1 

介绍模块化发展历程,可从 IIFE、AMD、CMD、CommonJS、UMD、webpack(require.ensure)、ES Module、这几个角度考虑

链接:blog.csdn.net/dadadeganhu…
链接:www.cnblogs.com/lvdabao/p/j…

IIFE:自执行函数,引入外部变量问题
script标签:加载顺序不能错
CommonJs:nodejs服务器端加载方案,同步
AMD,CMD:CommonJs的进阶,浏览器端异步加载方案,require.js,sea.js,前者加载模块时就会执行,后者使用模块式才会执行,依赖就近原则。
ES Module:ES6 import加载
UMD:混合的加载方案,集成上述。
webpack:自己的模块加载方法。

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

在全局作用域中,用 let 和 const 声明的全局变量并没有在全局对象中,只是一个块级作用域(Script)中。

怎么获取? 在定义变量的块级作用域中就能获取,既然不属于顶层对象,那就不加 window(global)

cookie 和 token 都存放在 header 中,为什么不会劫持 token?

浏览器发送请求的时候不会自动带上token,而cookie在浏览器发送请求的时候会被自动带上。csrf就是利用的这一特性,所以token可以防范csrf,而cookie不能。
token 是放在 jwt 里面下发给客户端的 而且不一定存储在哪里 不能通过document.cookie 直接拿到,通过 jwt+ip 的方式可以防止被劫持即使被劫持也是无效的jwt

聊聊 Vue 的双向数据绑定,Model 如何改变 View,View 又是如何改变 Model 的

1、从 M 到 V 的映射(Data Binding),这样可以大量节省你人肉来update View的代码
2、从 V 到 M 的事件监听(DOM Listeners),这样你的 Model会随着View触发事件而改变

请把两个数组合并“['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){    //传入要合并的两个数组
    let j = 0;    //这里也可以把j放在外面,用于arr2插入后还是A,B而不是B,A的情况,这种情况下j不用每次都从0开始,优化代码
    var arr = [...arr1]    //要返回的数组
    for(let i=0;i<arr2.length;i++){  
        const reg = new RegExp(arr2[i])    //创建一个正则用于模式匹配
        for(;j<arr.length;j++){    // j=0也可以放这里 每次都是从头开始找
            if(!reg.test(arr[j])){    //没有匹配到的时候,如A遇到B1
                arr.splice(j,0,arr2[i])    //就把A插入到B1前面
                break    // 用于一旦找到就不再找的情况
            }
        }
    }
    return arr
}