前端面试题目小结,拿走不谢

3,909 阅读28分钟

CSS+HTML

1、H5新增特性

  • Canvas -- 用于绘画的元素
  • video、audio -- 用于播放视频和音频的媒体。
  • 语义化标签 : header nav main article section aside footer
  • webSocket -- 单个TCP连接上进行全双工通讯的协议。
  • localStorage、sessionStorage -- 本地离线存储。
  • 新的表单控件 -- 如:date、time、email、url、search。

2、重绘&重排

  • 重排(回流):当DOM变化,引起了元素形状大小等几何变化,浏览器会重新计算元素的几何属性,将其放置到正确位置,这个过程叫重排。
  • 重绘:当一个元素的外观发生变化但并未引起布局上的变化,重新把元素绘制出来对的过程叫做重绘

3、BFC

官方:块级格式上下文,是Web页面的可视CSS渲染的一部分,是块盒子的布局过程发生的区域,也是浮动元素与其他元素交互的区域。浏览器对BFC的限制规则是

  • 生成BFC元素的子元素会一个接一个的放置。
  • 垂直方向上他们的起点是一个包含块的顶部,两个相邻子元素之间的垂直距离取决于元素的margin特性。在BFC-- 中相邻的块级元素的外边距会折叠(Mastering margin collapsing)。
  • 生成BFC元素的子元素中,每一个子元素左外边距与包含块的左边界相接触(对于从右到左的格式化,右外边距接触右边界),即使浮动元素也是如此(尽管子元素的内容区域会由于浮动而压缩),除非这个子元素也创建了一个新的BFC(如它自身也是一个浮动元素)。 触发条件:
  • 根元素,即HTML标签
  • 浮动元素:float值为left、right
  • overflow值不为 visible,为 autoscrollhidden
  • display值为 inline-blocktable-celltable-captiontableinline-tableflexinline-flexgridinline-grid
  • 定位元素:position值为 absolute、fixed

我的理解: 内部的盒子会在垂直方向上一个接一个放置;垂直方向上的盒子间距margin决定,但是同属于一个BFC的两个盒子会发生margin重叠;每个盒子左右外边距不会超出包含他的块;BFC的区域不会与float的元素区域重叠;计算高度时浮动元素也要计算在内;

  • 解决margin重叠问题:给其中一个盒子添加float利用规则(BFC的区域不会与float的元素区域重叠)
  • 利用BFC可以清除浮动:计算高度时浮动元素也要计算在内所以可以利用这一点清除浮动

4、编写CSS代码的时候如何优化CSS渲染性能?

  • 内联首屏关键Css,优先加载主要的内容。
  • 异步加载Css
//设置link标签media属性为noexis,浏览器会认为当前样式表不适用当前类型,会在不阻塞页面渲染的情况下再进行下载。加载完成后,将media的值设为screen或all,从而让浏览器开始解析CSS
<link rel="stylesheet" href="mystyles.css" media="noexist" onload="this.media='all'">
//通过rel属性将link元素标记为alternate可选样式表,也能实现浏览器异步加载。同样别忘了加载完成之后,将rel设回stylesheet
<link rel="alternate stylesheet" href="mystyles.css" onload="this.rel='stylesheet'">
  • 合理使用选择器

5、CSS响应式布局有哪些?

  • CSS3媒体查询@media 查询(麻烦,需要考虑的情况很多,不推荐)
  • 在头部meta标签中加入“name=“viewport元标签””代表网页宽度默认等于屏幕宽度
  • 借助bootstrap中的栅格
  • 弹性盒子(推荐使用)

6、CSS盒模型(标准盒模型和IE盒模型)

共同点:两种盒模型都是由 content + padding + border + margin 构成,其大小都是由 content + padding + border 决定的,但是盒子内容宽/高度(即 width/height)的计算范围根据盒模型的不同会有所不同:

  • 标准盒模型:只包含 content
  • IE盒模型:content + padding + border

box-sizing 改变元素的盒模型:

  • box-sizing: content-box :标准盒模型(默认值)。
  • box-sizing: border-box :IE(替代)盒模型。

JS相关

1、闭包(面试+笔试===高频)

闭包是指有权访问另一个函数作用域中的变量的函数

  • 优点:延长变量生命周期、私有化变量
  • 缺点:会导致函数的变量一直保存在内存中,过多的闭包可能会导致内存泄漏
 function fun1() {
      let n = 0;
      function fun2() {
        n++;
        console.log("@"+n);
        return n
      }
      return fun2;
    }
    let res = fun1();
    // 执行12次,fn2中的变量的生命周期变长了,fun2函数中的n被外部使用了
    for (let i = 0; i < 12; i++) {
      console.log(res()+"执行的第"+(i+1)+"次");
    }

2、原型、原型链(高频)

  • 原型链:当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,这就是原型链。 原型对象与实例关系.png
  • 原型链的尽头一般来说都是Object.prototype所以这就是我们新建的对象为什么能够使用toString()等方法的原因。 Object.prototype.toString.call([])//tostring可以返回一个精确的数据类型
  • 原型:在 JS 中,每当定义一个对象(函数)时,对象中都会包含一些预定义的属性。其中每个函数对象都有一个prototype 属性,这个属性指向函数的原型对象

3、this指向问题?

  • 全局函数中,this指向window
  • 作为对象的方法调用 this 指向调用对象
  • 自定义构造函数中,this指向新的实例化对象(new 会改变 this 的指向)
  • 事件绑定中this指向事件源
  • 定时器函数中this指向window

4、JS继承的方式有哪些?

  • 原型链的方式继承
  • 借用构造函数来实现继承
  • 组合继承
  • ES6中使用extendssuper关键字来实现继承

5、JS事件循环机制或js的执行机制?

可参考此篇文章:一次弄懂Event Loop

EventLoop,就像是一个银行叫号系统,负责去找到对应任务队列中的函数,然后放入执行栈中进行调用,任务队列分为宏任务和微任务。在执行过程中,某个宏任务执行结束后,然后查看是否有微任务,如果没有,则执行下一个宏任务,以此类推直到全部执行结束。

js是一个单线程、异步、非阻塞I/O模型、 event loop事件循环的执行机制

所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。 同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。异步 任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程, 某个异步任务可以执行了,该任务才会进入主线程执行。

宏任务包含

script(整体代码)
setTimeout
setInterval
I/O
UI交互事件
postMessage
MessageChannel
setImmediate(Node.js 环境)

微任务

Promise.then
Object.observe
MutationObserver
process.nextTick(Node.js 环境)

请写出下段代码的输出结果(js事件循环机制)

new Promise(resolve => {
	console.log(1);
	setTimeout(() => console.log(2), 0)
	Promise.resolve().then(() => console.log(3)) 
    resolve();
}).then(() => console.log(4)) 
console.log(5)
//结果:1 5 3 4 2 

6、原生ajax

ajax是一种异步通信的方法,从服务端获取数据,达到局部刷新页面的效果。

实现步骤

  • 创建XMLHttpRequest对象;
  • 调用open方法传入三个参数 请求方式(GET/POST)、url、同步异步(true/false);
  • 监听onreadystatechange事件,当readystate等于4时返回responseText;
  • 调用send方法传递参数。

7、事件冒泡、委托(捕获)

  • 事件冒泡指在在一个对象上触发某类事件,如果此对象绑定了事件,就会触发事件,如果没有,就会向这个对象的父级对象传播,最终父级对象触发了事件。

  • 事件委托本质上是利用了浏览器事件冒泡的机制。因为事件在冒泡过程中会上传到父节点,并且父节点可以通过事件对象获取到目标节点,因此可以在父节点上定义监听函数,我们就可以到具体触发事件的元素,由父节点的监听函数统一处理多个子元素的事件,这种方式称为事件代理。

    event.stopPropagation() 或者 ie下的方法 event.cancelBubble = true; //阻止事件冒泡

8、promise?(高频)

是异步编程解决的一种方案,可以浅显的认为他就是个容器,里面存放着未来才会结束的事情的结果。同时Promise也是一个对象,可以从该对象获取异步操作的消息。可以解决回调层层嵌套的问题。

promise有几种状态?三种

  • promise有三种状态pending(等待)、已完成(fullled)、已拒绝(rejected)
  • 一个promise的状态只可能从“等待”转到“完成”态或者“拒绝”态,不能逆向转换,同时“完成”态和“拒绝”态不能相互转换

promise的方法有哪些?

  • promise.all-----同时多个异步操作时,同时发生,等待最后一个结束才结束。只要有一个出错,则都获取不到
const request1 => () => axios.get()
const request2 => () => axios.get()
const request3 => () => axios.get()
const request4 => () => axios.get()
Promise.all([request1(), request2(), request3(), request4()]).then(res => {
    // res中就包含了四个请求得到的结果
})
  • promise.race-----可以在同时多个异步操作时,同时发生,第一个结束就结束 在一些异步处理中,我们想要设置超时时间的话,xhr对象可以调用xhr.abort()让请求结束,但是其他的没有
const asyncFn = () => new Promise(() => {
    // 代码
})
Promise.race([asyncFn(), new Promise((resolve) => {
    setTimeout(() => {
	    resolve()    
    }, 5000)
})])
  • Promise.reject (返回一个失败的 promise)
  • Promise.resolve (返回一个成功的 promise)
  • Promise.prototype.finally (无论成功还是失败都会执行 finally)
  • Promise.prototype.catch (就是调用 promise 自身的 then 方法,只传入失败的回调)

Async和await

  • sync/await是生成器函数的语法糖 async/await是基于promise实现的,他们不能用于普通的函数回调 async/await更符合同步语义 使得异步代码看起来更像同步代码 async/await与promise一样,是非阻塞的 执行async函数返回的都是promise对象

9、函数防抖和节流(高频---笔试+面试)

函数防抖:在规定时间内多次执行代码,只执行最后一次(按钮频繁点击时,只让最后一次生效) 函数节流:定义一个执行频率时间,在执行的过程每隔对应的频率时间执行一次(表单验证中输入内容时、滚动条事件)

平时项目中我们会直接使用lodash库,来解决对应的问题

// 防抖
function debounce(func, wait) {
    let timeout;
    return function () {
        let context = this;
        let args = arguments;
        if (timeout) clearTimeout(timeout);
          timeout = setTimeout(() => {
            func.apply(context, args)
        }, wait);
    }
}

// 节流函数
function throttle(fn, delay) {
    // 记录上一次函数触发的时间
    var lastTime = 0;
    return function() {
        // 记录当前函数触发的时间
        var nowTime = Date.now();
        if (nowTime - lastTime > delay) {
        // 修正this指向问题
            fn.call(this);
        // 同步时间
          lastTime = nowTime;
        }
    }
}
document.onscroll = throttle(function() { console.log('scroll事件触发' + Date.now()) }, 200)

10、let;const;var区别?

不同点:

作用域:

  • Var声明的变量只能是全局对的或者是函数块的
  • let声明的变量它的作用域既可以是全局或者整个函数块
  • var 允许在同一作用域中重复声明,而 let 不允许在同一作用域中重复声明,否则将抛出异常
  • var 在全局环境声明变量,会在全局对象里新建一个属性,而 let 在全局环境声明变量,则不会在全局对象里新建一个属性。
  • var 声明变量存在变量提升;let 声明变量存在暂存死区

相同点:

  • var 和 let 的作用域规则都是一样的,其声明的变量只在其声明的块或子块中可用

11、JS中map和filter区别?

  • 两者都可返回一个新的数组,并且不改变原数组

  • map返回的是每个元素执行return后的结果组成的一个新数组,而不是返回原数组中的元素组成的一个新的数组,新的数组长度和原数组是一致的;然而filter返回的是对原数组的过滤,其元素还是原数组中的元素,长度可能发生变化,但不改变原数组中的元素值。

let arr = [1, 2, 3, 4, 4, 3, 8, 9];
let filterArr=arr.filter((item,index,arr)=>{
    console.log("当前元素值:",item,"当前元素索引:",index,"被遍历的数组",arr);
    return item>4
    //注意:此方法可以返回符合条件的元素组成的新的数组,此方法不会改变原数组
     })
let mapArr=arr.map((item,index,arr)=>{
    console.log("当前元素值:",item,"当前元素索引:",index,"被遍历的数组",arr);
     //return item>4
    //对原数组每个元素判断如果条件为真返回true否则返回flase
    //结果:[false, false, false, false, false, false, true, true]
    //当为return item+"mapArr"  
    //结果是['1mapArr', '2mapArr', '3mapArr', '4mapArr', '4mapArr', '3mapArr', '8mapArr', '9mapArr']
})

12、数组去重(重点掌握数组的方法)

JS中数组去重的几种方式

filter、Arr.from+set、for循环+splice

  • filter 遍历数组,过滤出一个符合条件的新数组

    let newArr = arr.filter((item,index)=>{return item>4})//筛选出原数组中值大于4的元素
    console.log(newArr);
    
  • Set是es6中提供的一种数据结构,它类似数组但与数组不同的是,它的值都是唯一的没有重复值.

    • Set本质也是一个构造函数,因此在使用时需要new,同时Set可以接收一个数组(或者具有 iterable 接口的其他数据结构)作为参数用来初始化Set
    • Set中的值可以是任意类型的,但必须不能重复
    • Set的最大特点就是,里面的值都是唯一的,因此可以用来进行数组去重使用
    • Set中认为NaN和NaN是同一个值,因此Set中只能有一个NaN值(但我们知道事实上NaN和NaN用于是不相等的)
    • Set中两个对象永远是不相等的,即使键和值都是一样的
    • Set也可以为字符串去重
    • 在向Set添加值的时候不会发生类型转换
    • Set 是可遍历的
    let arr = [1,1,1,2,3,4,4,5,6,7,8,8,8]
    arr = Array.from(new Set(arr))// 或者 arr = [...new Set(arr)]
    //输出结果:[1,2,3,4,5,6,7,8]
    

13、ES6常用的

(1)Let和Const和var

  • 用来声明变量,与var不同的是,let和const会创建一个块级作用域(花括号包裹的可称之为一个块级作用域)
  • 在for循环中let和const会每循环一次就生成一个块级作用域,而且for循环会给下一个变量重新赋值
  • var、let、const都有变量提升,不同的是var在创建的时候就会给变量初始化值(undefined),而let和const只有当声明的语句执行了才会初始化并赋值,而这时const是必须得赋值。创建到初始化之间的代码块叫就形成了暂时性死区。
  • const声明的变量是常量(不会改变的变量),如果声明的是引用类型,则不能改变其引用地址。

(2)箭头函数

箭头函数和function声明的普通函数区别

  • 箭头函数没有Protype属性不能使用new关键字调用
  • 没有自己的this

14、基本数据类型和引用数据类型的区别

Number String Boolean Null Undefined Symbol

原始类型存储在栈内存中,修改对应的值,值会被覆盖。

引用类型存储在堆内存中,在栈内存中只是一个地址。

15、数据类型判断(掌握)?

16、cookie,localstorage, sessionstrorage 之间有什么区别?

相同点:存储在客户端

不同点

  • 与服务器交互: cookie 是网站为了标示用户身份而储存在用户本地终端上的数据(通常经过加密) cookie 始终会在同源 http 请求头中携带(即使不需要),在浏览器和服务器间来回传递 sessionStorage 和 localStorage 不会自动把数据发给服务器,仅在本地保存
  • 存储大小: cookie 数据根据不同浏览器限制,大小一般不能超过 4k ,sessionStorage 和 localStorage 虽然也有存储大小的限制,但比cookie大得多,可以达到5M或 更大
  • 有效期时间: localStorage 存储持久数据,浏览器关闭后数据不丢失除非主动删除数据 sessionStorage 数据在当前浏览器窗口关闭后自动删除 cookie 设置的cookie过期时间之前一直有效,与浏览器是否关闭无关

常见Jquery面试题

1、jquery中$()是什么?

()函数用于将任何对象包裹成jQuery对象;我们就可以调用定义在jQuery对象上的一些方法。比如:可以将一个选择器字符串传入() 函数用于将任何对象包裹成 jQuery 对象;我们就可以调用定义在 jQuery 对象上的一些方法。比如:可以将一个选择器字符串传入 () 函数,它会返回一个包含所有匹配的 DOM 元素数组的 jQuery 对象。

2、Jquery中的选择器?

ID选择器、类选择器、标签选择器等

$('#LoginTextBox') //ID选择器
$('.active')//类选择器
$("div")//标签选择器

3、如何在点击一个按钮时使用 jQuery 隐藏一个图片?

思路:为按钮设置事件并执行hide() 方法

$('#Button').click(function(){
    $('#ImageToHide').hide();
});

4、 $(document).ready() 是个什么函数?为什么要用它?(重要)

ready() 函数用于在文档进入ready状态时执行代码。当DOM 树完全加载(例如HTML被完全解析DOM树构建完成时),jQuery才允许执行代码。使用$(document).ready()的最大好处在于它适用于所有浏览器,解决了跨浏览器的难题。

5、 JavaScript window.onload 事件和 jQuery.ready 函数有何不同?

前者,需要等待dom树加载完毕以及外部资源完全加载完毕后才会执行后续代码

后者,只需要对dom树进行等待

对比:使用 jQuery $(document).ready() 的优势是你可以在网页里多次使用它,浏览器会按它们在 HTML 页面里出现的顺序执行它们,相反对于 onload 技术而言,只能在单一函数里使用。

6、如何找到所有 HTML select 标签的选中项?

原理:利用属性选择器和 :selected 选择器,结果只返回被选中的选项

<select name="n_select" class="c_select" id="i_select1">
        <option value="1">1</option>
        <option value="2">2</option>
        <option value="3" selected>3</option>
        <option value="4">4</option>
        <option value="5">5</option>
</select>
<scrpit>
 $('[name=NameOfSelectedTag] :selected')
 $('.c_select option:selected');
 $('.c_select').find('option:selected');
    //获取选中的值
    $('[name=NameOfSelectedTag] :selected').text()或者......val()
</scrpit>

7、jQuery 里的 each() 是什么函数?你是如何使用它的?

解释:遍历一个元素的集合

//遍历数组
$(function () { 
	$.each([52, 97], function(index, value) {
  		alert(index + ': ' + value);//0:52  1:97
});
})
//遍历对象
$(function () { 
    var obj = {
        "name": "张三",
        "age": 18
    };
    $.each( obj, function( key, value ) {
        alert( key + ": " + value );//name:张三  和  age:18
    });
})
//遍历一个元素
$(function () { 
	$.each({ name: "张三", age: 19 }, function( k, v ) {
		alert("键:"+ k ,"值:"+v );//键:name,值:张三   和  键:age,值19
	});
})

8、你是如何将一个 HTML 元素添加到 DOM 树中的?

  • append() - 在被选元素的结尾插入内容
  • prepend() - 在被选元素的开头插入内容
  • after() - 在被选元素之后插入内容
  • before() - 在被选元素之前插入内容

9、$(this) 和 this 关键字在 jQuery 中有何不同?

$(this) 返回一个 jQuery 对象,可以对它调用多个 jQuery 方法,比如用 text() 获取文本,用val() 获取值等等。而 this 代表当前元素,它是 JavaScript 关键词中的一个,表示上下文中的当前 DOM 元素。

10、如何使用jQuery来提取一个HTML 标记的属性 例如. 链接的href?

首先需要利用jQuery选择及选取到所有的链接或者一个特定的链接,然后你可以应用attr()方法来获得他们的href属性的值

$('a').each(function(){
   alert($(this).attr('href'));
});

11、如何使用jQuery设置一个属性值?

attr()方法和jQuery中的其它方法一样,能力不止一样. 如果你在调用attr()的同时带上一个值 例如. attr(name, value), 这里name是属性的名称,value是属性的新值

12、jQuery中 detach() 和 remove() 方法的区别是什么?

  • detach() 方法移除被选元素,包括所有的文本和子节点。然后它会保留数据和事件。
  • remove() 方法移除被选元素,包括所有的文本和子节点。该方法也会移除被选元素的数据和事件。

13、利用jQuery来向一个元素中添加和移除CSS类?

利用 addClass() 和 removeClass() 这两个 jQuery 方法。动态的改变元素的class属性可以很简单例如. 使用类“.active"来标记它们的未激活和激活状态

14、使用 CDN 加载 jQuery 库的主要优势是什么?

  • 报错节省服务器带宽以及更快的下载速度
  • 如果已有缓存的库就不会再次下载

15、 jQuery.get()jQuery.ajax() 方法之间的区别是什么?

ajax() 方法更强大,更具可配置性, 让你可以指定等待多久,以及如何处理错误。get() 方法是一个只获取一些数据的专门化方法。

16、你要是在一个 jQuery 事件处理程序里返回了false会怎样?(可以阻止事件冒泡)

Vue框架

1. 跨域是什么?如何解决跨域?

由于浏览器的同源策略导致,前端请求后台接口时,如果协议、域名、端口三者有一个不同则会产生跨域问题。

JSONP(前端)

借助于script的src没有跨域问题,利用src引入对应的函数的调用,函数的实参就是对应的数据;但是只能进行 GET 请求,无法实现上传数据等操作。

实现原理:

  • 利用 script 标签,规避跨域,<script src="url">

  • 在客户端声明一个函数,function jsonCallback() {}

  • 在服务端根据客户端传来的信息,查找数据库,然后返回一份字符串。

  • 客户端,利用<script>标签解析为可运行的JavaScript代码,调用 jsonCallback() 函数。

CORS(后台)

后台可以去设置请求头,可以允许某个域名下的某种请求方式跨域请求。

response.writeHead(200, {
  'Access-Control-Allow-Origin': '*
}

一般情况下,在开发环境中,开启所有域名和请求方式,生产环境根据需求指定特定的域名。

代理

开发环境代理

基于vue的开发环境,我们可以去配置devServer里的proxy。根据我们后端提供的结构,进行对应的代理操作 vue.config.js

module.exports = {
  devServer: {
    proxy: '要代理的后台地址'
  }
}
原接口现地址
http://localhost:3000/usershttp://localhost:8080/users

有些时候我们需要对特定前缀的接口进行代理

module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        ws: true,
        changeOrigin: true
      },
      '/foo': {
        target: 'http://localhost:4000'
      }
    }
  }
}
原接口现地址
http://localhost:3000/api/usershttp://localhost:8080/api/users
http://localhost:4000/foo/xxxhttp://localhost:8080/foo/xxx

如果后台在开发接口的过程中没有给我们添加公共前缀

module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        ws: true,
        changeOrigin: true,
        pathRewrite: {
            '^/api': '',
        }
      },
      '/foo': {
        target: 'http://localhost:4000',
        ws: true,
        changeOrigin: true,
        pathRewrite: {
            '^/foo': '/aaa',
        }
      }
    }
  }
}
原接口现地址
http://localhost:3000/usershttp://localhost:8080/api/users
http://localhost:4000/aaa/xxxhttp://localhost:8080/foo/xxx

生产环境

当开发完成后,没有服务了,就不能靠devServer的代理,我们可以使用nginx代理

server {
    listen       90;
    server_name  localhost;

    #charset koi8-r;

    #access_log  logs/host.access.log  main;

    location / {
        root   html;
        index  index.html index.htm;
    }

    // 代理配置 要让什么样的前缀代理到哪个地址
    location /api {
        proxy_pass http://localhost:3000;
    }



    error_page   500 502 503 504  /50x.html;
        location = /50x.html {
        root   html;
    }
}

img

2. 异步的解决

回调函数

Promise

Generator

async/await 是上面的语法糖

基于Promise

3. 如果后台直接返回10万条数据,前端怎么优化

在项目中这个操作是不允许,一般我们是要求后台做分页的

前端在请求的过程中,如果后台没有提供对应的参数我们没有办法优化请求过程,前端能做的优化是前端渲染部分,不能直接把10w条数据直接渲染。可以每次只取其中的n条,渲染在页面上,做分页加载,或者滚动加载

在前端定义两个参数,一个page,一个是limit

根据这两个参数从10w条数据中,去得到对应的列表

for(let i = (page - 1) * limit; i < page * limit - 1; i++) {
    10w条数据[i]
}

4. vue2数据响应式原理

(不兼容IE8)

响应式:当数据改变,页面自动渲染。

想要实现这个功能就要监听数据的改变

在Vue2里利用Object.defineProperty可以给对象中的每个属性添加getter和setter方法。所谓的setter的目的,就是我们在设置数据时可以监听到数据的改变。监听到数据的改变后,就可以通知对应的watcher,watcher去调用页面的render函数,触发页面的渲染

data

Object.defineProperty
let name = "张三"
const stu = {
    name
}
// 这样写不能监听数据的变化

// 可以利用Object.defineProperty
let name = "张三"
const stu = {}
Object.definProperty(stu, "name", {
    set
})

4. Vue3的数据响应式原理

(Vue3不兼容IE)

Vue3中利用的proxy,给数据添加拦截,当我们要修改数据时,可以触发对应的set函数,所谓的setter的目的,就是我们在设置数据时可以监听到数据的改变。监听到数据的改变后,就可以通知对应的watcher,watcher去调用页面的render函数,触发页面的渲染

5. 深浅拷贝

//浅拷贝:是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象;浅拷贝:重新在堆中创建内存,拷贝前后对象的基本数据类型互不影响,但拷贝前后对象的引用类型因共享同一块内存,会相互影响。
//深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象;深拷贝:从堆内存中开辟一个新的区域存放新对象,对对象中的子对象进行递归拷贝,拷贝前后的两个对象互不影响。

深拷贝、浅拷贝、赋值的区别

我的理解: 深拷贝不仅把内容都拷贝到了,而且还更换了内存地址,所以前后不会有影响;浅拷贝,拷贝的是内存地址和值,所以会相互影响。但是用const声明的常量要特别注意,重复赋值会报错。

// 浅拷贝
let obj1 = {
    name : '浪里行舟',
    arr : [1,[2,3],4],
};
let obj3=shallowClone(obj1)
obj3.name = "阿浪";
obj3.arr[1] = [5,6,7] ; // 新旧对象还是共享同一块内存
// 这是个浅拷贝的方法
function shallowClone(source) {
    var target = {};
    for(var i in source) {
        if (source.hasOwnProperty(i)) {
            target[i] = source[i];
        }
    }
    return target;
}
console.log('obj1',obj1) // obj1 { name: '浪里行舟', arr: [ 1, [ 5, 6, 7 ], 4 ] }
console.log('obj3',obj3) // obj3 { name: '阿浪', arr: [ 1, [ 5, 6, 7 ], 4 ] }

// 对象赋值
let obj1 = {
    name : '浪里行舟',
    arr : [1,[2,3],4],
};
let obj2 = obj1;
obj2.name = "阿浪";
obj2.arr[1] =[5,6,7] ;
console.log('obj1',obj1) // obj1 { name: '阿浪', arr: [ 1, [ 5, 6, 7 ], 4 ] }
console.log('obj2',obj2) // obj2 { name: '阿浪', arr: [ 1, [ 5, 6, 7 ], 4 ] }

// 深拷贝
let obj1 = {
    name : '浪里行舟',
    arr : [1,[2,3],4],
};
let obj4=deepClone(obj1)
obj4.name = "阿浪";
obj4.arr[1] = [5,6,7] ; // 新对象跟原对象不共享内存
// 这是个深拷贝的方法
function deepClone(obj) {
    if (obj === null) return obj; 
    if (obj instanceof Date) return new Date(obj);
    if (obj instanceof RegExp) return new RegExp(obj);
    if (typeof obj !== "object") return obj;
    let cloneObj = new obj.constructor();
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {
        // 实现一个递归拷贝
        cloneObj[key] = deepClone(obj[key]);
      }
    }
    return cloneObj;
}
console.log('obj1',obj1) // obj1 { name: '浪里行舟', arr: [ 1, [ 2, 3 ], 4 ] }
console.log('obj4',obj4) // obj4 { name: '阿浪', arr: [ 1, [ 5, 6, 7 ], 4 ] }

深拷贝浅拷贝实现方法

浅拷贝

  • 1.Object.assign()
  • 2.函数库lodash的_.clone方法
  • 3.展开运算符...

深拷贝

  • 函数库lodash_.cloneDeep方法

  • jQuery.extend()方法

    $.extend(deepCopy, target, object1, [objectN])//第一个参数为true,就是深拷贝
    var $ = require('jquery');
    var obj1 = {
        a: 1,
        b: { f: { g: 1 } },
        c: [1, 2, 3]
    };
    var obj2 = $.extend(true, {}, obj1);
    console.log(obj1.b.f === obj2.b.f); // false
    
    
  • 手写递归方法

    递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。
    

深拷贝

 var obj = {
      name: "test",
      desc: "origin",
      sendobj: {
        name: "test2",
        desc: "origin2"
      }
    }

    function copy(obj) {
      let newobj = null     // 接受拷贝的新对象
      if (typeof (obj) == 'object' && typeof (obj) !== null) {   // 判断是否是引用类型
        newobj = obj instanceof Array ? [] : {}               // 判断是数组还是对象
        for (var i in obj) {
          newobj[i] = copy(obj[i])                        // 判断下一级是否还是引用类型
        }
      } else {
        newobj = obj
      }
      return newobj
    }

    var obj1 = copy(obj)
    obj1.sendobj.name = "change"
    console.log(obj1);
    console.log(obj);
    console.log(obj1.sendobj.name)//change
    console.log(obj.sendobj.name);//test2

6 排序算法

  • 冒泡排序

    依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。

    冒泡排序

//从小到大排序
const arr = [5, 2, 7, 8, 34, 7, 39, 12, 56, 9, 1]
  function Sort(arr) {
    // 外层循环i控制比较的轮数
    for (let i = 0; i < arr.length; i++) {
      // 里层循环控制每一轮比较的次数j,arr[i] 只用跟其余的len - i个元素比较
      for (let j = 1; j < arr.length - i; j++) {
        // 若前一个元素"大于"后一个元素,则两者交换位置
        if (arr[j - 1] > arr[j]) {
          [arr[j - 1], arr[j]] = [arr[j], arr[j - 1]]
        }
      }
    }
    return arr
  }
  console.log(Sort(arr))	// [1, 2,  5,  7,  7, 8, 9, 12, 34, 39, 56]

  • 插入排序

    插入排序是指在待排序的元素中,假设前面n-1(其中n>=2)个数已经是排好顺序的,现将第n个数插到前面已经排好的序列中,然后找到合适自己的位置,使得插入第n个数的这个序列也是排好顺序的。按照此法对所有元素进行插入,直到整个序列排为有序的过程,称为插入排序。

    插入排序

//将arr[]按升序排列,插入排序法
  function insertSort(arr) {
    for (let i = 1; i < arr.length; i++) {
      //将arr[i]插入到arr[i-1],arr[i-2],arr[i-3]……之中
      for (let j = i; j > 0; j--) {
        if (arr[j] < arr[j - 1]) {
          [arr[j - 1], arr[j]] = [arr[j], arr[j - 1]]
        }
      }
    }
    return arr
  }

7 查找算法

  • 顺序查找
  • 二分查找

10、虚拟DOM

虚拟DOM就是用一个JS对象来模拟一个DOM对象的操作,最终虚拟DOM还是要被转换成真实DOM 虚拟DOM可以提升页面中列表修改时的DOM渲染性能,主要带来的好处是可以通过虚拟DOM渲染成所有的其他的颜色的UI组件比如安卓 IOS,赋予了js开发原生APP的能力。

11、 Vuex是什么,你们在项目中如何使用vuex

Vuex用来简化数据通信,是一个集中式的状态管理,用于存储我们页面中的数据

我们在使用Vuex时,基于路由组件为模块,我们在对应的模块中,去管理我们对应的页面的所有数据,数据存放在state,修改数据使用mutation,获取数据使用action

VueX的五个核心

state 存储对应的数据

mutations 修改state的数据

actions 异步获取数据然后commit给mutation

getters 从state派生出新的数据

modules 用于模块的划分

12、axios请求相关

所有请求都携带token怎么做

使用axios的请求拦截器,在config中设置对应的headers.token为我们登录后拿到的token。

// 封装axios
import axios from "axios";
import { Message } from "element-ui";
import router from "../router";

// 通过create方法,创建一个新的axios对象
const instance = axios.create({
  baseURL: "http://localhost:3000/", // 未来公共接口地址是什么,就写什么
  timeout: 5000, // 设置超时时间
});
// 请求拦截器
instance.interceptors.request.use(
  (config) => {  
    const token = localStorage.getItem("token");
    if (token) {
      config.headers.authorization = token;
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// 响应拦截器
instance.interceptors.response.use(
  (response) => {
    // 想这么写一定要和后台进行沟通
    if (response.data.code === 200) {
      // 请求成功 方便我们获取请求到的数据 不需要再res.data.data
      if (response.headers["x-total-count"]) {
        return {
          data: response.data.data,
          total: +response.headers["x-total-count"],
        };
      } else {
        return response.data.data;
      }
    } else {
      // 当接口中有错误时,直接显示错误信息,不需要在每次请求的时候都去判断错误,然后显示错误信息
      Message.error(response.data.msg);

      return Promise.reject(response.data.msg);
    }

    // return response
  },
  (error) => {
    console.log([error]);
    // 判断错误代码是不是401
    if (error.response.status === 401) {
      Message.error("token失效");
      // 跳转到登录
      router.push("/login");
    }
    return Promise.reject(error);
  }
);

export default instance;

登陆验证流程

  • 在组件中引入封装好的axios对象,creat一个axios对象并设置baseurl和超时时间
  • 设置请求拦截器,request.use,判断config中是否有token,有的话returnconfig,没有的话抛出promise错误信息
  • 设置响应拦截器:response.use,请求成功获取到请求的数据,否则抛出erro错误信息
  • 导出创建的axios对象

token放在localstorage里安全么?

不安全,但是后台也会对前端传递的token进行验证,不通过返回401

如何做统一的错误处理

利用响应拦截器,判断对应的错误代码,做出对应的相应错误判断

13、 React 类组件和函数组件的区别

  • 类组件有状态和生命周期, 函数式组件没有(现在可以利用hook来解决对应的问题)
  • this指向的问题,所有的状态及函数的使用,都需要使用this.事件绑定中,this指向有问题,需要用.bind 函数式组件没有
  • 类组件是个class 函数式组件是一个function
  • 函数式组件渲染的速度更快

14、为什么vue和react在渲染列表都需要添加key

添加key是为了提高对列表操作的性能。

key的作用是用来优化虚拟DOM的diff算法的。在修改列表中某个值时,没有key的话,虚拟DOM需要把新的数据重新的生成虚拟DOM结构,然后替换到原先列表的位置。如果有key可以直接找到对比后不一样的虚拟节点进行修改。

15、Vue路由拦截(某些页面需要登陆才可访问)

Vue 路由拦截(对某些页面需要登陆才能访问) - 阿泽码农 - 博客园 (cnblogs.com)

16、http状态码(了解)

2XX 成功

  • 200 OK,表示从客户端发来的请求在服务器端被正确处理
  • 204 No content,表示请求成功,但响应报文不含实体的主体部分
  • 206 Partial Content,进行范围请求

3XX 重定向

  • 301 moved permanently,永久性重定向,表示资源已被分配了新的 URL
  • 302 found,临时性重定向,表示资源临时被分配了新的 URL
  • 303 see other,表示资源存在着另一个 URL,应使用 GET 方法丁香获取资源
  • 304 not modified,表示服务器允许访问资源,但因发生请求未满足条件的情况
  • 307 temporary redirect,临时重定向,和302含义相同

4XX 客户端错误

  • 400 bad request,请求报文存在语法错误
  • 401 unauthorized,表示发送的请求需要有通过 HTTP 认证的认证信息
  • 403 forbidden,表示对请求资源的访问被服务器拒绝
  • 404 not found,表示在服务器上没有找到请求的资源

5XX 服务器错误

  • 500 internal sever error,表示服务器端在执行请求时发生了错误
  • 503 service unavailable,表明服务器暂时处于超负载或正在停机维护,无法处理请求

17、为啥有的请求之前会自动有一个options请求

当我们的后台通过cors解决跨域时,发POST请求会遇到options请求。

先发一个options请求目的是为了确保接口可以正常请求,只有options请求成功了,才有继续发送post请求

18、什么是三次握手

TCP建立连接要经历三次握手

第一次

第一次握手:建立连接时,客户端发送syn包(seq=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。

第二次

第二次握手:服务器收到syn包,必须确认客户端的SYN(ack=j+1),同时自己也发送一个SYN包(seq=k),即SYN+ACK包,此时服务器进入SYN_RECV状态。

第三次

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。 ****

19、什么是四次挥手

对于一个已经建立的连接,TCP使用改进的四次挥手来释放连接(使用一个带有FIN附加标记的报文段)。TCP关闭连接的步骤如下:

第一步,当主机A的应用程序通知TCP数据已经发送完毕时,TCP向主机B发送一个带有FIN附加标记的报文段(FIN表示英文finish)。

第二步,主机B收到这个FIN报文段之后,并不立即用FIN报文段回复主机A,而是先向主机A发送一个确认序号ACK,同时通知自己相应的应用程序:对方要求关闭连接(先发送ACK的目的是为了防止在这段时间内,对方重传FIN报文段)。

第三步,主机B的应用程序告诉TCP:我要彻底的关闭连接,TCP向主机A送一个FIN报文段。

第四步,主机A收到这个FIN报文段后,向主机B发送一个ACK表示连接彻底释放。

20、Vue和React路由的原理

都有两种模式 hash模式和history模式,让url改变,不改变访问的页面,通过url中内容变化,让js渲染不同的内容到页面上

hash模式就是改变#后面的内容,js检测到url的改变后,渲染不同的内容到页面上

history模式利用html5的hisotry.pushState让url改变,但是访问的页面不变,原理同上

21、例举出你所知的在VUE里动态绑定单个及多个class样式的方法?

<div :class="className"></div>//直接绑定
<div :class="{className: Boolean}"></div>//对象的方法
<div :class="{className: Boolean, className2: Boolean}"></div>//对象的方法
<div :class="['box', {className: Boolean}]"></div>//数组的对象的方法

22、修改vue里的数组或对象后然而页面没有更新,你是怎么解决的?(重点)

出现的原因:如果我们要对对象进行遍历,显示到页面,这个时候对象中新增的属性,由于在Vue2中我们是通过Object.defineProperty实现数据响应式也就是说给对象加getset方法,但是这俩并不能监听到对象新增的属性,所以会出现以上情况。

解决方案

  • 如果是给对象添加少量的新属性,可以直接采用Vue.set()
//对于直接修改数组中的属性时无法实现响应式-------解决方法
var vm = new Vue({
  data: {
    arr: ['a', 'b', 'c']
  }
})
vm.arr[1] = 'x' // 不是响应性的(直接修改数组中某个元素的值)
vm.arr.length = 4 // 不是响应性的
// Vue.set
Vue.set(vm.arr, indexOfItem, newValue)
// this.$set
vm.$set(vm.arr, indexOfItem, newValue)
//对于对象中新增或移除属性时无法实现响应式-------解决方法
var vm = new Vue({
  data:{
    age:35
  }
})
vm.child = 2// vm.child 是非响应式的(新增一个child属性)

Vue.set(vm.非根节点对象, 键, 值)
this.$set(this.非根节点对象,键,值)
  • 如果需要为新对象添加大量的新属性,则通过Object.assign()创建新对象
  • 如果你实在不知道怎么操作时,可采取$forceUpdate()进行强制刷新 (不建议)

23、不同的页面有不同的 title 用单页面应用可以解决吗? 怎么解决!

我们在路由守卫中,获取到当前的路由信息,从路由信息中获取对应的页面的标题,使用document.title = 标题解决问题。需要我们手动的给每一个路由配置添加title。

24、组件中的data为什么是个函数,而不是一个对象?

每个Vue组件都是一个实例,共享data的属性,当data中是引用数据类型时,修改一个data就会影响所有的数据变化。

25、在登录注册密码加密操作

我们使用的md5加密,前端传递过去的内容是经过md5加密的,后台拿到数据库中也是加密的密码和我的密码进行比对,如果一样则登录成功

26、Vue中Computed和Watch的区别(重要)

  • computed用来监控自己定义的变量,该变量不在data里面声明,直接在computed里面定义,然后就 可以在页面上进行双向数据绑定展示出结果或者用作其他处理,具有缓存特性(值如果不变化会复用)
  • watch主要用于监控vue实例的变化,它监控的变量必须在data里面声明才可以,它可以监控一个 变量,也可以是一个对象,一般用于监控路由、input输入框的值特殊处理等等,它比较适合的场景是 一个数据影响多个数据,它不具有缓存性
  • 计算属性不能执行异步任务,计算属性必须同步执行。

27、父子-子父-非父子通信方式(重要)

  • 父->子:propos
<!-- vue是最大的根组件,其实我们这里的father是vue的子组件,son是father的子组件 -->
  <div id="app">
    <father></father>
  </div>
  <!-- 父组件模版内容 -->
  <template id="father">
    <div>
      父组件:<input type="text" v-model="msg">
      <hr>
      <!-- info是我们自定义的属性名加上数据绑定,就可以接收到父组件inp框输入的数据了 -->
      <son :info="msg"></son>
    </div>
  </template>
  <!-- 子组件模版内容 -->
  <template id="son">
    <div>
      <!-- 渲染接收到的数据info -->
      <p>子组件得到父组件的数据是:{{ info }}</p>
    </div>
  </template>
  <script>
  Vue.component('father', {
    //父组件
    template: '#father',
    data() {
      return {
        msg: "",
      }
    },
    //定义局部组件的方式定义子组件
    components: {
      //子组件
      son: {
        template: "#son",
        //props中的元素名必须和我们在父组件中自定义的属性名一致
        props: ["info"]
      }
    }
  });
  new Vue({
    el: "#app",
  })
</script>
  • 子->父:$emit
 <div id="app">
    <father></father>
  </div>
  <!-- 父组件 -->
  <template id="father">
    <div>
     <p>父组件接收到的数据是:{{ msg }}</p>
      <hr>
      <!-- 自定义一个事件accept,当触发的时候就可以拿到传过来的数据 -->
      <son @accept="accept" :msg="msg"></son>
    </div>
  </template>
  <!-- 子组件 -->
  <template id="son">
    <div>
      子组件:<input type="text" v-model="msg" placeholder="请输入你要发送的内容">
      <!-- 点击按钮,发送子组件的数据 -->
      <button @click="sendOut">发送数据</button>
    </div>
  </template>
   Vue.component('father', {
    template: '#father',
    data() {
      return {
        msg: "",
      }
    },
    methods: {
      accept(value) {
        this.msg = value;
      }
    },
    components: {
      son: {
        template: "#son",
        data() {
          return {
            msg: this.msg
          }
        },
        methods: {
          sendOut() {
            // 通过this.$emit来触发accept事件,并将数据以参数(this.msg)的形式传给father
            this.$emit('accept', this.msg);
          },
        },
      }
    }
  });
  new Vue({
    el: "#app",
  })
  • 非父子:事件总线 onon emit(创建发出的事件不建议使用)

28、一个页面从输入URL 到页面加载显示完成,这个过程中都发生了什么?

  • 浏览器地址栏输入url
  • 浏览器会先查看浏览器缓存--系统缓存--路由缓存,如有存在缓存,就直接显示。如果没有,接着第三步
  • 域名解析(DNS)获取相应的ip
  • 浏览器向服务器发起tcp连接,与浏览器建立tcp三次握手
  • 握手成功,浏览器向服务器发送http请求,请求数据包
  • 服务器请求数据,将数据返回到浏览器
  • 浏览器接收响应,读取页面内容,解析html源码,生成DOM树
  • 解析css样式、浏览器渲染,js交互绑定多个域名,数量不限;

29、js模块化和组件是啥?

js模块化:具备特定功能的js文件,需要哪些功能就去拆分他并引入

组件:具备特定功能效果的代码集合

30、routeroute 和router 的区别是什么?

$route 是某个“路由信息对象”局部,包括 path,params,hash,query,fullPath,matched, name 等路由信息参数

routerVueRouter的实例,相当于一个全局的路由器对象,里面含有很多属性和子对象,例如history对象,经常用的跳转链接就可以用this.router.push会往history栈中添加一个新的记录。返回上一个history也是使用router 为 `VueRouter` 的实例,相当于一个**全局**的路由器对象,里面含有很多属性和子对 象,例如 history 对象,经常用的跳转链接就可以用 `this.router.push` 会往 history 栈中添加一个 新的记录。返回上一个 history 也是使用`router.go `方法

31、Vue路由传值的方式有哪几种?

Vue-router 传参可以分为两大类,分别是编程式的导航 router.push 和声明式的导航

1、router.push

  • 字符串:直接传递路由地址,但是不能传递参数 this.$router.push("home") 对象
  • 命名路由 :这种方式传递参数,目标页面刷新会报错this.$router.push({name:"news",params:{userId:123})
  • 查询参数 :和 name 配对的式 params,path 配对的是 query this.$router.push({path:"/news',query:{uersId:123})
  • 接收参数this.$route.query

2、声明式导航

  • 字符串 <router-link to:"news"></router-link>
  • 命名路由<router-link to:"{name:'news',params:{userid:1111}}"</router-link>
  • 查询参数<router-link to:"{name:'/news',query:{userid:1111}}"></router-link>

32、导航解析流程

完整的导航解析流程:

  1. 导航被触发(/index=>/about)
  2. 在失活的组件(index)中调用beforeRouteLeave守卫。
  3. 调用全局的beforeEach守卫。
  4. 在复用的组件中调用beforeRouteUpdate(如果有复用的话)
  5. 调用路由独享守卫beforeEnter
  6. 解析异步路由组件
  7. 在被激活的组件中调用beforeRouteEnter
  8. 调用全局的beforeResolve守卫(全局解析守卫)
  9. 导航被确认
  10. 调用全局后置守卫 afterEach守卫
  11. 触发DOM更新
  12. 用创建好的组件实例调用beforeRouteEnter守卫中传给next的回调函数。

33、路由守卫

分类:全局路由、单个路由独享、组件内部路由

  • 每个路由守卫的钩子函数都有三个参数:to进入的目标路由、from离开自那个路由、next控制路由 在跳转时执行操作,必须执行

全局路由守卫

  • beforeEach(to,from, next)-----路由跳转前触发,常用于登陆验证
  • beforeResolve(to,from, next)-----在beforeEach和 组件内beforeRouteEnter 之后,afterEach之前调用
  • afterEach(to,from)-----

全局路由直接挂载到 router 实例上

//全局验证路由
const router = createRouter({
  history: createWebHashHistory(),
  routes
});

// 白名单, 不需要验证的路由
const whiteList = ['/','/register']

router.beforeEach((to,from,next)=>{
  if(whiteList.indexOf(to.path) === 0) {
    // 放行,进入下一个路由
    next()
  } else if(!(sessionStorage.getItem('token'))){
    next('/');     
  } else {
    next()
  }  
})

34、Vue生命周期共分为几个阶段?

Vue 实例从创建到销毁的过程,就是生命周期。也就是从开始创建、初始化数据、编译模板、挂载 Dom→渲染、更新→渲染、卸载等一系列过程,我们称这是 Vue 的生命周期

生命周期

35、keep-alive(Vue 该如何实现组件缓存?)

组件化编程的过程中,频繁的组件切换,组件的实例都会重新创建。我们可以使用Vue内置的组件<Keep-alive>来缓存未被激活的组件,当再次激活此组件时,此组件会从缓存中快速渲染。

keep-alive本质上就是一个组件

在created的时候,初始化两个对象分别用于缓存虚拟DOM和VNode对应的健集合

在destory的时候,删除 this.cache 中缓存的VNode实例。

在mounted的时候,对include和exclude中的数据进行实时监听。然后实时更新或删除。

第一步:获取keep-alive包裹着的第一个子组件对象及其组件名;

第二步:根据设定的黑白名单(如果有)进行条件匹配,决定是否缓存。不匹配,直接返回组件实例(VNode),否则执行第三步;

第三步:根据组件ID和tag生成缓存Key,并在缓存对象中查找是否已缓存过该组件实例。如果存在,直接取出缓存值并更新该key在 this.keys中的位置(更新key的位置是实现LRU置换策略的关键),否则执行第四步;

第四步:在 this.cache对象中存储该组件实例并保存 key 值,之后检查缓存的实例数量是否超过 max 的设置值,超过则根据LRU置换策略删除最近最久未使用的实例(即是下标为0的那个key)。

第五步:最后并且很重要,将该组件实例的 keepalive属性值设置为 true

36、路由传参

query传参-----将需要的参数以 【key = value】的方式放在URL地址中。

params动态传参-----在定义路由信息时,需要以占位符【:参数名】的方式将需要传递的参数指定到路由地址中

37、vue自定义指令?

微信小程序

1、微信小程序支付流程

  • 客户端通过wx.login获取用户临时登录凭证code,发送到后端服务器换取openId
  • 客户端将**openId**以及相应需要的商品信息发送到后端
  • 服务器在接收到商品Id、商品信息、openId后,经过系列算法,生成服务器订单数据
  • 客户端接收返回的信息发起微信支付**wx.requestPayment的参数**,发起微信支付

2、封装微信小程序的数据请求

  • 封装axios请求,设置对应的baseurl和url
  • 在api文件夹中创建所有请求的js文件,导入封装的axios
  • 在页面中调用封装的请求方法

3、小程序页面间有哪些传递数据的方法?

  • 给html元素添加data-*属性来传递值,然后通过e.target.dataset或onload的param参数获取(注:data-名称不能有大写字母、不可以存放对象)
  • 设置id的方法标识来传值,通过e.currentTarget.id获取设置的id的值,然后通过设置全局对象的方式来传递数值
  • 在navigator中添加参数数值

4、小程序和H5的区别

  • 小程序加载速度更快(同样的服务器及外部环境下,网站需要加载代码,特别是有些网站js效果写的比较多,加载速度会更慢点)
  • 功能强大(调用设备信息、摄像头等)
  • 打开方式
  • 安全性(小程序更好些)

笔试题:

1、this指向的问题

  var myobj={
      foo:"bar",
      func:function(){
        var self=this;
        console.log(this);//myobj
        console.log(this.foo);
        console.log(self.foo);
        (function(){
          console.log(this);//window
          console.log(this.foo);
          console.log(self.foo);
        }())
      }
    }
    myobj.func()

2、手写一个简单的闭包

  function fun1() {
      let n = 0;
      function fun2() {
        n++;
        console.log("@"+n);
        return n
      }
      return fun2;
    }
    let res = fun1();
    // 执行12次
    for (let i = 0; i < 12; i++) {
      console.log(res()+"执行的第"+(i+1)+"次");
    }

3、封装一个求数组平均数的函数

 var arr = [10,20,30,56,89];
    function avg(array) {//封装求平均值函数
        var len = arr.length;
        var sum = 0;
        for(var i = 0;i<len;i++){
            sum +=array[i];
        }
        return sum/len;
    }
    console.log(avg(arr));

4、判断一字符串中某个字母出现的次数如: let str = "aabcdefgha"

let str = "aabcdefgha";
      function getMost(str) {
        //创建一个结果对象,暨最后返回的对象
        var result = {};
        //for...in...遍历,看str是否在result中,是的话让当前对象中某个key对应的值++,默认0开始加
        for (let i in str) {
          if (str[i] in result) {
            result[str[i]]++;
          } else {
            // 如果是上述不满足,则让当前对象的中某个key的值等于1
            var object = {};
            object[str[i]] = 1;
            result = Object.assign(result, object);//obj.assign浅拷贝,讲obj拷贝给result
          }
        }
        return result;
      }
      var result = getMost(str);
      console.log(result); //{a:3,b:1,c:1,d:1,e:1,f:1,g:1,h:1}

5、字符串翻转

6、数组转成树状结构

    const SourceArr = [
        { id: '1', parentId: 0, name: '标题1' },
        { id: '1.1', parentId: '1', name: '标题1-1' },
        { id: '1.1.1', parentId: '1.1', name: '标题1-1-1' },
        { id: '2', parentId: 0, name: '标题2' },
        { id: '2.1', parentId: '2', name: '标题2-1' },
        { id: '2.2', parentId: '2', name: '标题2-2' },
        { id: '2.2.2', parentId: '2.2', name: '标题2-2-2' },
        { id: '3', parentId: 0, name: '标题3' },
     ]
    function getData (arr) {
        // filter实现
        let res = arr.filter(item => {
          item.children = arr.filter(etem => {
            return item.id === etem.parentId
          })
          //过滤出一级标题组成新的数组
          return item.parentId==0
        })

        return res
      }
      const TargetArr = getData(SourceArr)
      console.log('TargetArr', TargetArr)

7、手写防抖和节流

优化问题:

1、网页首屏有图片,网页加载出现长时间空白

  • 压缩优化图片,减少大小
  • 占位图懒加载

2、图片懒加载是如何实现的?

在图片img标签上添加data-set属性(名字自拟比如:data-src),然后在图片的默认src上存放一张占位图(可以是低像素的原图),然后当浏览器窗口滚动到视图范围判断一下,如果是的话就将src的地址换成data-set的地址,这样就实现图片的懒加载。

小优化:由于浏览器的滚动条滚动事件会频繁触发,我们可以使用节流函数来解决

总结:

  1. 拿到所有的图片 dom
  2. 遍历每个图片判断当前图片是否到了可视区范围内。
  3. 如果到了就设置图片的 src 属性。
  4. 绑定 window 的 scroll 事件,对其进行事件监听。

写到最后

这是自己结合网上的资源以及自己面试的时候遇到的总结,如果冒犯请联系本人,若有不足,还请大佬指正