美文网首页
前端面试题整理(一)

前端面试题整理(一)

作者: 宋乐怡 | 来源:发表于2019-07-23 11:01 被阅读0次

    1.父元素伸缩,自元素自适应,10像素间距
    2.选择器优先级
    3.浏览器对用户呈现出一个页面的过程
    4.prevent default
    5.事件的属性和方法
    6.this指向
    7.es5实现继承
    8.prototype和构造函数调用关系
    9.promise并行三个,将结果存入数组,滑动窗口
    10.eventloop对set timeout和promise的处理
    11.vue 样式模块化
    12.怎样区分伪类和伪元素
    13.web存储方式
    14.简述instanceOf为什么不是绝对安全?
    15.String对象的方法
    16.实现千位分隔符 1234567->1,234,567
    17.写出3个以上能改变DOM的js native函数
    18.call和apply的区别
    19.浏览器实现js多线程,提供原生对象是?
    20.快速排序
    21.二分查找
    22.实现布局,子元素宽占父元素的50%,高和宽相等,自适应。
    23.keep-alive原理 TCP原理,VUE<keep-alive>组件原理
    24.vue tab页跳转时, 保存之前的滚动位置
    25.window load event和document DOMContentLoaded event的区别
    26.给定两个排序好的数组a,b,长度分别为n,m,给出一个高效算法,查找A中的哪些元素存在于B中。
    27.实现一个函数,入参是一个字符串,判断是否有重复字母,如果包含,返回第一个重复字母的下标,不包含返回-1。
    28.对象数组a=[{x:7,y:6} , {x:1,y:2} , {x:3,y:4}],按照x的大小进行排序。
    瓜子二手车
    29.阶乘函数
    30.call() apply()和bind()的区别?
    31.实现tooltip, css画钝角三角形
    32.判断一个对象是函数的几种方法。
    33.实现商品列表,一行3个,屏幕宽度不定,商品元素间距50px,距离父元素边距50px。
    34.向一个数组增加元素的方法
    35.盒子模型有哪几种, width100padding10的盒子, 占据宽度是多少?


    1.父元素伸缩,子元素自适应,10像素间距

    答:https://www.jianshu.com/p/19538a0ffb8c

    2.选择器优先级

    答:
    内联样式 > ID 选择器 > 类选择器 = 属性选择器 = 伪类选择器 > 标签选择器 = 伪元素选择器,!important 的属性拥有最高优先级

    html

    3.浏览器对用户呈现出一个页面的过程

    答:dns解析-》tcp链接-》发送HTTP请求-》服务器处理请求并且返回报文-》浏览器解析渲染页面-》链接结束

    DNS解析:

    是一个将网址解析成IP 地址的过程。
    首先从本地域名服务器中查找,如果找不到就继续向上根域名服务器查找,直到顶级域名,这个过程中存在dns优化有的环节。当查找资源时, 会先找缓存,(浏览器缓存-》系统缓存-》路由器缓存等等),也会根据机器的负载量和距离用户的位置进行dns负载均衡。


    image.png
    建立TCP连接:

    A.客户端发送syn到服务器要求连接
    B.服务端向客户端发送ack
    C.客户端收到ack并确认后,向服务端发送ack,连连接建立。

    发送HTTP请求:

    tcp连接建立之后,开始通过HTTP协议传输资源,根据情况判断是否使用HTTPS,HTTP包括请求行,请求报头,请求正文(post,put客户端向服务器传输数据的情况)。keepalive什么的可以在请求头里添加。

    服务器处理请求并返回HTTP报文:

    服务端接到请求开始对tcp进行处理,对http进行解析,按照报文格式封装成HTTP request对象。响应报文码(1xx:请求已接受,2XX:成功,3xx:重定向,4xx:客户端错误,5xx:服务端错误)

    浏览器渲染页面:

    边解析边渲染,解析html,构建dom树,解析css,构建cssom。
    如果dom构建的过程中遇到了css的link,那就会先去加载并构建cssom,这个过程不是一次性的。css和同步的js文件都是阻塞DOM树渲染的,但不阻塞DOM解析,直到js加载并且执行完毕。遇到阻塞的css也会延迟js的执行和dom构建。(因为js可能会修改dom或者cssom),css同样,当cssom构建时,js也会停止被阻塞,等待cssom构建完成。
    CSS阻塞吗?
    1.CSS会阻塞DOM的渲染,不会阻塞DOM树的解析。
    2.CSS会阻塞js的执行。

    defer & async
    1.正常模式
    <script src="script.js"></script>
    遇到这样的js标签,浏览器会立即加载并执行,不等待后续载入的文档元素。

    2.async模式
    <script async src="script.js"></script>
    有async的js文件会和后续的DOM解析渲染并行执行,当js加载完成,立即执行,这时html解析暂停。因此不会按照标签引入顺序执行。

    3.defer模式
    <script defer src="script.js"></script>
    有defer的js文件的加载,也会和文档的解析构建并行。这一点与async一致。
    不同的是,defer的js文件加载完不会立即执行, 会等到所有文档解析完成后,DOMContentLoaded事件触发之前完成, 因此会按照引入顺序执行。

    DOMContentLoaded & onload
    DOM解析完(阻塞DOM的内容解析完,DOM才真正解析完)会触发DOMContentLoaded事件。如果在DOMContentLoaded之后引入css样式表,DOMContentLoaded可能无法获取样式表里的样式,此时DOM树已经构建完成,但外部css文件还没加载完成,这也是css文件放在头部的原因

    onLoad
    页面的所有资源被加载以后触发onLoad事件,会在DOMContentLoaded之后触发。

    这个过程中有两个重要的过成是回流和重绘。计算盒模型的大小位置还有解析颜色字体等 属性,这些都确定下来的时候开始repain,合成一个rendertree渲染树,render-tree中必须同时存在dom和cssom,浏览器开始布局并渲染到屏幕上。首次加载必然会经历回流和重绘的过程。

    重排(回流)和重绘

    // DOM重构,浏览器重排重绘

    无论何时总会有一个初始化的页面布局伴随着一次绘制。(除非你希望你的页面是空白的:))之后,每一次改变用于构建渲染树的信息都会导致以下至少一个的行为:

    部分渲染树(或者整个渲染树)需要重新分析并且节点尺寸需要重新计算。这被称为重排。注意这里至少会有一次重排-初始化页面布局。

    由于节点的几何属性发生改变或者由于样式发生改变,例如改变元素背景色时,屏幕上的部分内容需要更新。这样的更新被称为重绘。

    重排和重绘代价是高昂的,它们会破坏用户体验,并且让UI展示非常迟缓。

    什么情况会触发重排或重绘?
    • 任何改变用来构建渲染树的信息都会导致一次重排或重绘。

    • 添加、删除、更新DOM节点

    • 通过display: none隐藏一个DOM节点-触发重排和重绘

    • 通过visibility: hidden隐藏一个DOM节点-只触发重绘,因为没有几何变化

    • 移动或者给页面中的DOM节点添加动画

    • 添加一个样式表,调整样式属性

    • 用户行为,例如调整窗口大小,改变字号,或者滚动。

    一些重排可能开销更大。想象一下渲染树,如果你直接改变body下的一个子节点,可能并不会对其它节点造成影响。但是当你给一个当前页面顶级的div添加动画或者改变它的大小,就会推动整个页面改变-听起来代价就十分高昂。

    每次重排重绘浏览器都会立即执行吗?

    浏览器一直致力于减少这些消极的影响,浏览器会创建一个变化的队列,浏览器可以向队列添加或变更这些变化,在一个特定的时间或达到一定的数量时,执行一次重排或重绘,通过这种方式,多次重排或重绘会整合起来最终减少重排或重绘的次数,以节省浏览器渲染的开销。

    所以 ,同时set和get样式是非常糟糕的做法

    // no-no!
    el.style.left = el.offsetLeft + 10 + "px";
    
    如何减少重排重绘呢?
    • 离线编辑样式修改,可以通过创建documentFragment临时保存变动,然后一次性更新到dom上。
    • 复制要多次更新的节点,在副本上进行操作,然后一次性替换。
    • 节流防抖
    一个问题,为什么会遇到浏览器展示页面开始没有样式,过一会样式才可用的情况?

    看到的一个答案,有可能是这个原因,但是我不确定。
    开发环境会把css都打包到js里,所以要等js加载好了才有样式,因此会出现这种情况;但是在生产环境下,css会生成css文件,并插入到<style />里,因此就不会出现这种情况了。

    两个优化点:css先加载,js后加载
    js尽量不要修改dom树。

    js

    4.prevent default

    答:取消事件的默认行为,cancelable为true可以使用这个方法。

    5.事件的属性和方法

    答:type/target/currentTarget/cancancelable/bubbles/defaultPrevented/detail/eventPhace/trusted/view
    preventDefault()/stopPropagation()/stopImmediatePropagation()

    6.this指向

    答:在运行时基于函数的执行环境绑定的,
    在事件中,this始终指向currentTarget,
    执行环境的层层嵌套形成了作用域链,在作用域链中访问变量只能自底向上搜索。

    7.es5实现继承
    答:

    构造函数,原型,实例之间的关系?

    每个构造函数都有一个原型对象,每个原型对象都有一个指向构造函数的指针,每个实例有一个指向原型的指针。继承本质上是重写原型对象,将原型代之以另一个类型的实例。

    A.父类构造函数

    function SuperType(name){
    this.name = name;
    this.numbers = [1,2,3,4];
    }

    B.父类原型添加原型方法

    SuperType.prototype.superFunction = function() {
    alert (this.name);
    }

    C.子类构造函数,在构造函数中用call调用父类构造函数,并且定义子类的构造 函数属性

    function SubType(name,age){
    SuperType.call(this,' nick');
    this.age = age;
    }

    D.改写子类原型对象,使之指向父类的实例,创建链条。

    SubType.prototype = new SuperType();

    E.子类的构造函数自己定义,断开与父类构造函数的关系

    SubType.prototype.constructor = SubType();

    F.子类定义自己的原型方法

    SubType.prototype.subFunction = function(){
    alert (this.age);
    }

    G.创建子类实例

    var instance = new SubType('anne','10');

    8.prototype和构造函数调用关系

    答:每个构造函数都有一个原型对象,每个原型对象都有一个指向构造函数的指针,每个实例有一个指向原型的指针。继承本质上是重写原型对象,将原型代之以另一个类型的实例。

    9.promise并行三个,将结果存入数组,滑动窗口

    Promise.all() 并行且按照参数传入顺序输出
    /////
    var promiseArr = [p1,p2,p3,p4,p5,p6,p7,p8];
    var resultArr = [];
    for(let i = 0; i < promiseArr.length; i++){
    let tempArr = [promiseArr[i * 3] , promiseArr[i * 3+1] , promiseArr[i*3+2]];
    resultArr.push(promise.all(tempArr))
    }

    10.eventloop对set timeout和promise的处理

    js是单线程语言,他作为浏览器脚本语言,与用户互动, 以及操作dom,这决定了它只能是单线程语言,否则会有非常麻烦的同步问题。

    H5提出了WebWorker标准。允许js脚本创建多个线程。但是子线程完全受主线程控制。且不得操作DOM,所以这个标准并没有改变js单线程的本质。

    js执行过程中, 所有的任务分为两种,同步任务和异步任务,同步任务在主线程上形成一个执行栈按顺序执行, 遇到异步任务就将这个异步任务添加到任务队列(task queue)中,一旦执行栈中所有的同步任务执行完毕,系统就会去读task queue,拿到执行栈中执行。异步任务的task queue 分为宏任务队列(macrotask)和微任务队列(microtask),不同的异步任务会进入不同的队列,宏任务包括常见的settimeout,setinterval,setImmediate,UI rendering,requestAnimationFrame等,microtask包括promise ,node 的process.nextTick,等。同步任务执行完毕后,js引擎会先去查看microtask queue,若有待执行的微任务就会先执行所有microtask,然后再去执行一个宏,再去执行所有微。
    settimeout会被放进宏任务队列(macrotask队列)一次取出一个执行
    Promise会被放进微任务队列(microtask队列)每次循环执行所有microtask。

    1. vue 样式模块化
    image.png

    12.怎样区分伪类和伪元素

    答:针对作用选择器的效果,伪类需要添加类来达到效果,而伪元素需要增加元素,所以一个叫伪类,另外一个叫伪元素。

    13.web存储方式
    大搜车

    答:web存储目的是在设备不能上网的情况下依然可以运行应用。
    熟知的有cookie,sessionStorage,localStorage,indexDB。

    第一步 离线检测

    开发离线应用的第一步是要确定设备在线还是离线。h5为此定义navigator.online属性,但是存在兼容性问题,h5还定义了两个事件,online和offline,网络状态变化时触发。为了检测设备是否离线,在页面加载后,最好先通过navigator.onLine取得初始状态,然后通过online和offline事件来确定网连接状态是否变化。

    第二步 HTML5的应用缓存(application cache),(manifest)

    appCache是专门为开发离线web应用而设计的。是从浏览器缓存分出来的一块区域,想要在这个缓存中保存数据,可以使用描述文件(manifest file)列出需要缓存和下载的资源。要将描述文件与页面关联起来,可以在<html>中的manifest属性中指定这个文件的路径。例如:
    <html manifest="/offline.manifest"></html>
    描述文件的扩展名以前是.manifest,现在推荐使用.appcache。
    applicationCache对象的status属性可以查看appcache当前的状况。

    第三步 数据存储
    1. cookie(键值对)

    限制

    1.cookie是绑定在特定域名下,这个限制确保了存储在cookie中的信息只能让批准的接受者访问,其他域不能访问。

    2.每个域的cookie总数是有限的。不同浏览器各有不同,超过单个域名的限制个数后,就会清除以前的cookie。

    3.浏览器对cookie的尺寸也有限制。大约都是4kb,如果你尝试创建超过最大尺寸的cookie,会被悄无声息地丢掉。

    cookie的构成:
    名,值,域,路径,失效时间,安全标志。

    Set-Cookie:name=value;domain=.wrox.com;path=/;22-Jan-07 07:10:24 GMT;secure
    

    只有名值对会发送给服务器。所有的名值对都是经过URL编码的,所以必须使用decodeURIComponent()来解码。
    server设置cookie,有效时间内, client每次访问相同域名都会携带这个cookie,默认情况下,当浏览器关闭时cookie会被删除,不过也可以自己设置过期时间,因此, cookie可以在浏览器关闭之后依然保存在用户的机器上。为了绕开浏览器单域名下的cookie个数限制, 产生了子cookie,子cookie是存在单个cookie中的更小段的数据。cookieUtil对象操作cookie的存储方法。

    2. Web Storage

    以window对象的属性存在。包含session storage和local storage。可以使用length属性判断有多少键值对存在于storage中,IE8提供了一个remainingSpace属性,用于获取还可以使用的存储空间的字节数。storage是保存在磁盘上的。

    sessionStorage

    sessionStorage对象用于存储特定于某个会话的数据,也就是该数据只保存到浏览器关闭, 可以跨越页面刷新存在,如果浏览器支持, 浏览器重启后依然可用。大小大约都是2.5MB。

    localStorage

    sessionStorage对象主要用于仅针对会话的小段数据的存储,如果需要跨越会话存储数据,localStorage更合适。要访问同一个localStorage对象,页面必须来自同域名(子域名无效)同协议,同端口。数据保留到通过JS删除或者用户主动清除浏览器缓存。大小大约都是5MB。

    3. INdexedDB

    Indexed Database API简称IndexedDB。是浏览器中保存结构化数据的数据库。大多数操作是以异步请求的方式进行的,差不多每一次IndexedDB操作,都需要你注册onerror或onseccess事件处理程序。

    14.简述instanceOf为什么不是绝对安全?
    BOM

    答:因为在使用框架的情况下,浏览器会存在多个global对象。每个框架中定义的全局变量,会自动成为框架中window对象的属性。由于每个window对象都包含原生类型的构造函数,因此每个框架都有一套自己的构造函数,这个问题会影响到对跨框架传递的对象使用instanceOf操作符。

    1. String对象的方法

    答:方法名---返回值---参数

    • 字符方法 :
    1. charAt()--给定位置的字符串--一个参数(字符位置)
    2. charCodeAt()--给定位置的字符编码--一个参数(字符位置)
    • 字符串操作方法:(参数为负数时的处理机制不同)
    1. concat()--新--任意多个参数拼接
    2. slice()--新--一或两个参数(开始位置,结束位置后一个)
    3. substring()--新--一或两个参数(开始位置,结束位置后一个)
    4. substr()--新--一或两个参数(开始位置,个数)
    • 字符串位置方法:
    1. indexOf()--index--1或2个参数(str, index)
    2. lastIndexOf()--index--1或2个参数(str,index)从后向前
    • trim()方法--新--去掉首尾空格
    • 字符串大小写转换方法:
    1. toLowerCase()--新
    2. toLocaleLowerCase()--新
    3. toUpperCase()--新
    4. toLocaleUpperCase()--新
    • 字符串匹配方法:
    1. match()--array--一个参数(正则表达式或RegExp对象)
    2. search()--第一个匹配项的索引--一个参数(正则表达式或RegExp对象)
    3. replace()--新--两个参数(RegExp/str , str/function)
    4. split()--指定分隔符的array(可将string转为数组)--(RegExp/str , number)
    • localeCompare()方法--1,0,-1--str
    • fromCharCode()方法--转换后的字符串--参数是一系列UTF-16代码单元的数字

    16.实现千位分隔符 1234567->1,234,567
    彩视

    答:

    1. /(?=(\B\d{3})+$)/g
      https://juejin.im/post/5abb5b01f265da237f1e5a92
    2. (123456789).toLocaleString('en-US')(骚操作)

    17.写出3个以上能改变DOM的js native函数

    独到科技

    答:appendChild() / insetBefore() / replaceChild() / removeChild() / createElement()

    18.call和apply的区别

    答: 这两个函数都是非继承而来的方法,用途是在特定的作用域中调用函数,设置函数体内this对象的值。
    apply()
    接收2个参数,第一个是函数运行的作用域,第二个是参数数组,其中,第二个参数可以是Array的实例,也可以是arguments对象。
    call()
    和apply()区别仅仅是接收参数的方式不同,第二个参数必须逐个列出来。

    func.apply(this,[num1,num2]);
    func.apply(this,arguments);
    func.call(this,num1,num2);
    

    19.浏览器实现js多线程,提供原生对象是?

    答:js实现多线程的方式有很多种, ajax,异步回调函数,settimeout setinterval,webworker,多线程webworker是h5标准的一部分,worker是window对象的一个方法。
    这一套标准定义了一系列api,允许一段js程序运行在主线程之外的另一个线程中,把一些一步操作交给worker线程,在主线程工作时,worker线程在后台运行,互不干扰,等到worker完成后再将结果返回给主线程。
    实例化worker对象并传入要执行的js文件名就可以创建一个新的web worker。

    var worker = new Worker("stufftodo.js");
    

    这行代码会导致浏览器下载这个js文件,但只有worker收到消息才会执行文件中的代码。要给worker传递消息,可以使用postMessage()方法,消息内容可以是任何能够被序列化的值,可以序列化为JSON结构的任何值都可以作为参数传递给postMessage(),这就意味着传入的值时被复制过去的,不是直接传递过去的。
    worker返回的也是任何能够被序列化的值。

    worker是通过message和error事件与页面通信的。

    Worker全局作用域

    关于web worker最重要的是,他所执行的js代码完全在另一个作用域中,与当前网页的代码不共享作用域。worker中的代码不能访问DOM,也不能通过任何形式影响页面的外观。
    worker的全局对象是worker本身,web worker本身是一个最小化的运行环境。

    webworker存在两个限制:
    同源限制:worker运行的脚本必须与主线程脚本文件同源。
    访问限制:worker的全局对象和主线程的全局对象不是同一个上下文环境,无法使用document,window,parent等,window改写成self.

    20.快速排序
    彩视
    https://blog.csdn.net/zhao529670074/article/details/80776253

    答:
    1.选基准,pivot,就选数组最左边元素
    2.小于的放pivot左边,大于的放pivot右边
    3.递归
    时间复杂度为O(nlogn)

    var arr=[5,7,2,9,3,8,4,7,1];
    // 每次选择最左边的数作为基数
    function quickSort(arr){
      if (arr.length<2) { return arr; }
      // 定义左指针
      var left=0;
      // 定义右指针
      var right=arr.length-1;
      //开启每一轮的排序
      while(left<right){
        // 寻找右边比arr[0]小的数的下标
        while(arr[right]>=arr[0] && left<right){
          right=right-1;
        }
        // 寻找左边比arr[0]大的数的下标
        while(arr[left]<=arr[0] && left<right){
          left++;
        }
        //当左边指针与右边指针相遇后,交换arr[0]与当前两个指针所在的元素
        if (right==left) {
          let mid=arr[right];
          arr[right]=arr[0];
          arr[0]=mid;
          break;
        }
        // 当左指针小于右指针的位置,交换两个指针当前位置的元素
        let tem=arr[right];
        arr[right]=arr[left];
        arr[left]=tem;
      }
      //递归实现
      return quickSort(arr.slice(0,left)).concat(arr.slice(left,right+1)).concat(quickSort(arr.slice(right+1)));
    }
    //对数组进行排序
    console.log(quickSort(arr));
    

    21.二分查找
    彩视

    答:有序查找
    1.找到有序数组的中间元素
    2.比较key和中间元素的大小,大于去右边找,小于去左边找
    3.大于的情况,把边界low设置为中间元素往后一个,小于的情况,把边界high设置为中间元素往前一个。
    O(nlogn)

    function binarySearch(arr,key){
        var low = 0 , high = arr.length-1;
        while(low <= high){
            let mid = parseInt((low + high)/2);
            if(key > arr[mid]){
                low = mid+1;
            }else if(key < arr[mid]){
                high = mid-1;
            }else {
                return mid;
            }
        }
        return -1;
    }
    var arr = [1,2,3,4,5,6,7,8,9];
    console.log(binarySearch(arr , 9));
    

    22.实现布局,子元素宽占父元素的50%,高和宽相等,自适应。
    跟谁学

    答:原理:padding-top,padding-bottom是根据父元素的宽度计算而不是height。

    <div class="a">
      <div class="b">
    
      </div>
      <div class="b">
    
      </div>
    </div>
    
    .a{
      width:300px;
      height:600px;
      background:pink;
      font-size:0;
    }
    .b{
      width:40%;
      background:#f13a70;
      padding-top:40%;
      display:inline-block;
    }
    
    1. keep-alive原理
      TCP keep-alive
      VUE <keep-alive>组件原理
      北明软件

    答:

    • TCP
      Connection设置为Keep-alive用于告诉客户端本次HTTP请求结束之后并不需要关闭TCP连接,这样可以使下次HTTP请求使用相同的TCP通道,节省TCP连接建立的时间。
      在 HTTP 1.0 时期,每个 TCP 连接只会被一个 HTTP Transaction(请求加响应)使用,请求时建立,请求完成释放连接。当网页内容越来越复杂,包含大量图片、CSS 等资源之后,这种模式效率就显得太低了。所以,在 HTTP 1.1 中,引入了 HTTP persistent connection 的概念,也称为 HTTP keep-alive,目的是复用TCP连接,在一个TCP连接上进行多次的HTTP请求从而提高性能。

    HTTP1.0中默认是关闭的,需要在HTTP头加入"Connection: Keep-Alive",才能启用Keep-Alive;HTTP1.1中默认启用Keep-Alive,加入"Connection: close ",才关闭。

    • VUE <keep-alive>组件
      开发中, 为了优化组件的缓存,切换页面时保存状态,使用keep-alive,keep-alive是vue的一个内置抽象组件,在他的created钩子里面定义了this.cache和this.keys,props定义了include,exclude,max。keep-alive实现了render函数,在keep-alive组件渲染时候会执行。
      由于我们也是在keep-alive内部写dom,所以第一步先获取他的插槽$slots,再获取到他的第一个子节点。
    const slot = this.$slots.default
    const vnode: VNode = getFirstComponentChild(slot)
    

    由于keep-alive只获取第一个子节点,所以一般搭配看component动态组件或者router-view使用。
    接下来,判断组件名字和include,exclude的关系。
    这里match函数做匹配的逻辑兼容了数组,字符串,和正则的情况。
    include的情况,去找缓存。
    命中缓存,就从缓存中拿VNode实例,并且调整key的顺序排在最后一个。
    未命中,就把VNode设置进缓存,如缓存数量超过了配置的max值, 就删掉缓存队列中的第一个。这里有个判断,就是删掉的节点是不是当前的tag,是就直接删掉,不是的话,要调用缓存的组件实例 的$destroy钩子。
    如果是exclude的情况,就直接返回VNode。
    <keep-alive>组件也观测include和exclude的情况,对cache做遍历,如果缓存的节点名和新的规则不匹配,就把缓存的节点删除。


    VUE<keep-alive>.png

    24.vue tab页跳转时, 保存之前的滚动位置

    答:有两种方式:vue-router的滚动行为和keep-alive
    在源码中,对于浏览器默认行为,调用自定义的scrollBehavior函数后传参的savedPosition为保存的position,对于跳转这种则传参的savedPosition为null。
    基本我们可以提炼出:通过跳转的是需要进行数据刷新的;而通过默认浏览器行为的是不需要进行浏览器刷新的。

    • vue-router 中的滚动行为
      滚动行为:这个功能只在支持history.pushState的浏览器中使用,只在通过浏览器触发前进后退按钮时可用。
      貌似不能做条件判断是否要保持状态
      scrollBehavior 在组件生命周期的mounted后beforeUpdated前执行
      savedPosition是个位置对象
    const router = new VueRouter({
      routes: [],
      scrollBehavior(to, from, savedPosition) {
        if (savedPosition) {
          return savedPosition;
        } else {
          return {x:0,y:0}
        }
      }
    })
    

    也可以模拟滚动到锚点的行为

    scrollBehavior (to, from, savedPosition) {
      if (to.hash) {
        return {
          selector: to.hash
        }
      }
    }
    

    可以返回一个promise得到预期位置的描述
    因为scrollBehavior 在组件生命周期的mounted后beforeUpdated前执行,mounted时期发出的异步请求并没有等到返回,滚动事件已经触发,所以等数据请求回来时会发现滚动 位置不准确。所以必须使用settimeout将滚动事件加到异步队列中,位于异步请求之后。(settimeout是加在了本轮主线程的末尾,但是settimeout中的异步事件放到了和异步请求同一个队列中。)

    scrollBehavior (to, from, savedPosition) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve({ x: 0, y: 0 })
        }, 500)
      })
    }
    
    • keep-alive
      可以做tab切换,页面跳转时的滚动位置保存。

    每次切换新标签时, vue都创建了一个新的currentTabComponent。

    路由跳转时,vue-router不保存被切换组件的状态,旧的会被销毁,新组建被创建,走一遍完整的生命周期流程。

    要求被包裹的组件有自己的名字,注意不是路由名字。

    keep-alive是个抽象组件(或称为功能型组件),存在于initLifeCycle中,实际上不会被渲染在DOM树中。它的作用是在内存中缓存组件(不让组件销毁),等到下次再渲染的时候,还会保持其中的所有状态,并且会触发activated钩子函数。因为缓存的需要通常出现在页面切换时,所以常与router-view一起出现。

    可以只缓存一些组件

    如果只想渲染某一些页面/组件,可以使用keep-alive组件的include/exclude属性。include属性表示要缓存的组件名(即组件定义时的name属性),接收的类型为string、RegExp或string数组;exclude属性有着相反的作用,匹配到的组件不会被缓存。假如可能出现在同一router-view的N个页面中,我只想缓存列表页和详情页,那么可以这样写:

    <keep-alive :include="['ListView', 'DetailView']">
      <router-view />
    </keep-alive>
    
    可以实现条件渲染

    上面include的写法不是常用的,因为它固定了哪几个页面缓存或不缓存,假如有下面这个场景:

    现有页面:首页(A)、列表页(B)、详情页(C),一般可以从:A->B->C;
    B到C再返回B时,B要保持列表滚动的距离;
    B返回A再进入B时,B不需要保持状态,是全新的。
    具体细节:
    https://juejin.im/post/5b407c2a6fb9a04fa91bcf0d

    scrollbehavior和keepalive标签有什么区别?

    我的理解是,从本质上讲,scrollbehavior是在缓存里记录了上一个页面的滚动位置信息,而keepalive是把不活动的组件保存在内存中, 而不是直接销毁。
    scrollbehavior只能控制浏览器的返回前进,没法处理tab切换这种,而keepalive可以。

    25.window load event和document DOMContentLoaded event的区别
    独到科技

    答:
    load事件在整个页面全部加载完才会触发,包括所有以来的资源,样式,图片。
    load,页面上所有的资源(图片,音频,视频等)被加载以后才会触发load事件,简单来说,页面的load事件会在DOMContentLoaded被触发之后才触发。

    不同于DOMContentLoaded,一旦DOM加载完,就会触发。
    在这里我们可以明确DOMContentLoaded所计算的时间,当文档中没有脚本时,浏览器解析完文档便能触发 DOMContentLoaded 事件;如果文档中包含脚本,则脚本会阻塞文档的解析,而脚本需要等位于脚本前面的css加载完才能执行。在任何情况下,DOMContentLoaded 的触发不需要等待图片等其他资源加载完成。

    26.给定两个排序好的数组a,b,长度分别为n,m,给出一个高效算法,查找A中的哪些元素存在于B中。
    彩视

    答:

    • ES7的includes 和 filter 做交并差集
    var a = [1,3,5,7,9];
    var b = [1,5,6,8,10];
    let c = [];
    c = a.filter(i => b.includes(i));
    
    • Array.from 和 set不允许重复的特点
    var a = [1,3,5,5,7,9,9,10,10];
    var b = [1,5,5,6,8,10];
    let c = [];
    let aSet = new Set(a);
    let bSet = new Set(b);
    c = Array.from(aSet).filter(i => bSet.has(i))
    console.log(c);
    
    • ES5 filter 和 indexOf
      此方法不能去重
      由于indexOf方法对于NAN始终返回-1,所以有NAN的情况要特殊处理
    let a = [1,3,5,5,7,9,9,10,10,'s',NAN];
    let b = [1,5,5,6,8,10,'s',NAN];
    let ahasNAN = a.some(i => isNAN(i));
    let bhasNAN = b.some(i => isNAN(i));
    let c = [];
    c = (a.filter((i) => b.indexOf(i)>-1)).concat(ahasNAN && bhasNan ? [NAN] : []);//考虑NAN
    c = a.filter((i) => b.indexOf(i)>-1);//不考虑NAN
    

    我没成功


    image.png

    27.实现一个函数,入参是一个字符串,判断是否有重复字母,如果包含,返回第一个重复字母的下标,不包含返回-1。
    大搜车

    答:

    let a = [1, 2, 2, 3, 3, 4, 5]
    let b = [];
    let res = -1;
    for (let i = 0; i < a.length; i++) {
      if (b.includes(a[i])) {
        res = i;
        break;
      } else {
        b.push(a[i]);
      }
    }
    console.log(res);
    console.log(b);
    
    image.png

    28.对象数组a=[{x:7,y:6} , {x:1,y:2} , {x:3,y:4}],按照x的大小进行排序。
    瓜子二手车

    答:
    js page113,Function类型,比较函数,一个函数返回另一个函数

    function compare(x) {
      return function(obj1, obj2) {
        var value1 = obj1.x;
        var value2 = obj2.x;
        if (value1 < value2) {
          return -1;
        } else if (value1 > value2) {
          return 1;
        } else {
          return 0;
        }
      }
    }
     var objArr = [{x:4,y:10},{x:12,y:2},{x:-3,y:9}];
     console.log(objArr.sort(compare('x')));
    
    image.png

    29.阶乘函数
    未经优化的版本

    function factorial(num) {
      if (num <= 1) {
        return 1
      } else {
        return num * factorial(num - 1);
      }
    }
     console.log(factorial(5));
    

    但问题是这个函数的执行与函数名factorial紧紧耦合了,为了消除这种紧密耦合的现象,可以使用arguments.callee。
    arguments对象有个callee属性,callee是个指针,指向拥有这个arguments对象的函数。
    但是严格模式下访问arguments.callee会报错

    function factorial(num) {
      if (num <= 1) {
        return 1
      } else {
        return num * arguments.callee(num-1);
      }
    }
     console.log(factorial(5));
    

    使用具名函数,这种方式在严格模式和非严格模式下都行得通。

    var factorial = function f(num){
        if(num <=1){
            return 1;
        }else{
            return num * f(num-1);
        }
    };
    
    1. call() apply()和bind()的区别?

    答:call和apply改变了函数的this上下文之后立即执行函数,而bind则是返回改变了上下文后的一个函数。

    31.实现tooltip, css画钝角三角形
    瓜子二手

    答:操作边的宽度就可以实现钝角锐角三角形


    image.png

    32.判断一个对象是函数的几种方法。

    1.typeof操作符
    typeof func == "function" ; //true
    typeof操作符一般用来检测基本类型,返回小写字符串,检测引用类型时,除了function返回‘function’之外,其余都返回object,因此不适合检测引用类型。

    1. toString(最佳)
      Object.prototype.toString.call(fn)=== '[object Function]';

    2. Constructor构造函数法


      image.png

      null和undefined没有构造函数,如果不能确定类型,不能用此方法检测。

    3. instanceof操作符
      instanceof操作符假定只有一个全局环境,如果有多个框架,就会有多个不同版本的构造函数,instanceof就不会生效。


      image.png

    33.实现商品列表,一行3个,屏幕宽度不定,商品元素间距50px,距离父元素边距50px。
    瓜子二手车

    <div class='a'>
      <div class='b-wrap'>
    
        <div class='b'>
    
        </div>
        <div class='b'>
    
        </div>
        <div class='b'>
    
        </div>
        <div class='b'>
    
        </div>
      </div>
    
    </div>
    .a {
      width: 500px;
      height: 500px;
      background: #3c3c3c;
      padding: 50px;
    }
    
    .b-wrap {
      width: 100%;
      height: 100%;
      background: pink;
    }
    
    .b {
      float: left;
      background: #aaaa12;
      margin-bottom: 50px;
    // 此处注意,减号左右要有空格, 否则计算属性不生效
      width: calc((100% - 100px)/3);
      padding-top: calc((100% - 100px)/3);
    }
    
    .b:nth-child(2) {
      margin: 0 50px;
    }
    
    image.png

    然后我发现这个方法不对。。。。


    image.png

    用flex:
    这是个遗留问题,我不会了。

    34.向一个数组增加元素的方法
    瓜子二手车

    答:以下三个方法都修改原数组

    • push()
    • unshift() //从前面添加一个元素
    • splice() // 可插入、替换。删除元素项,接受三个参数:起始index,删除的个数,剩下的参数都是用来添加or替换的项

    35.盒子模型有哪几种, width100padding10的盒子, 占据宽度是多少?
    微店

    答:当文档布局时,浏览器渲染引擎会把元素都表示为矩形的盒子。
    box-sizing属性有三个值

    • content-box:默认值
      包含content,如图,元素最终宽度由width+padding+border决定,width属性设置的只是content部分的宽度。


      image.png
    • border-box:
      包含border+padding+content,width设置的宽度是border+padding+content。


      image.png
    • inherit
      从父级继承。

    相关文章

      网友评论

          本文标题:前端面试题整理(一)

          本文链接:https://www.haomeiwen.com/subject/ehqolctx.html