# 面试宝典 **Repository Path**: JunMing64/interview-classic ## Basic Information - **Project Name**: 面试宝典 - **Description**: 面试宝典~~~~~~~~ - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-01-11 - **Last Updated**: 2023-02-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 01- 说一下事件委托的理解? 事件委托就是给子元素绑定的事件,统一委托绑定到其祖先元素身上 #### 为什么为祖先元素绑定事件,其子元素也能触发呢? 原理: **事件冒泡** #### 为什么要用事件委托(事件委托的好处)? 1.性能高 2.对后续新增的元素同样具有事件绑定的效果 #### 如何按需触发? 自定义属性等。 ## 02-说一下call,apply,bind的异同 **相同点:** 1.第一个参数都是用来改变函数内部的this指向; 2.都可以利用后续参数传参 **不同点**: 1.call和apply会直接调用函数,而bind返回的是一个新函数; 2.call和bind可以传递多个参数,而apply只能传递两个参数(第二个参数是伪数组或数组) ## 03-说一下你对BFC的理解? #### BFC是什么? 块级格式化上下文,**是一个特性 ** , 拥有自己的一套渲染规则, 它决定了其**子元素**将如何布局, 以及和其他元素的相互关系和作用 (display:flow-root 开启BFC) #### 如何触发`BFC`特性 1. 根元素 () 2. 浮动元素 (元素的 float不是none) 3. 绝对定位元素 (元素的 position 为 absolute 或 fixed) 4. 行内块元素 (元素的 display 为 inline-block) 5. 表格单元格 (元素的 display 为 tabel-celll, HTML表格单元格默认为该值) 6. overflow 不等于 visible 7. 更多的触发方法参考MDN #### BFC特性有什么作用 1. 可以包含内部的浮动元素 2. 可以排除外部浮动元素带来的影响 3. 可以防止外边距重叠 4. 解决margin塌陷 ## 04-说一下v-show 和 v-if 的异同 **相同点**: 两者都是用来控制元素在页面是否显示 **不同点**: v-if 是通过 JS 来控制元素的创建和销毁的, v-show是通过css来控制元素的显示和隐藏 v-if 具有较高的切换开销 v-show 具有较高的初始渲染开销 **使用场景** 一般需要频繁切换的元素建议v-show, 需要多个分支条件处理的元素使用 v-if 比较合适 ## 05- v-for 和 v-if 为什么不建议放在一行? #### Vue2 在Vue2中 v-for的优先级比v-if的高,Vue会先循环出所有的元素, 再把不符合条件的元素销毁, 这样就多了一些没必要的创建和销毁操作,浪费性能 #### Vue3 在Vue3 中v-if的优先级比v-for的高, 那就更不能放一行了,因为往往v-if判断的时候需要依赖循环项进行, 那个时候循环项都还没有,所以会直接包报错 #### 如何解决 解决方法就是:通过计算属性,先计算出需要循环的那些元素就好了 #### 代码解释 需求:数组中的 angular 不需要渲染。 ```js ``` ## 06-说一下你对 Promise 的理解? #### 是什么? Promise是Es6新增的一个函数 #### 怎么用? 一般作为构造函数来使用,即需要new一下来创建一个Promise实例;他里面有3种状态: 分别是 pending (进行中) , fulfilled (已成功), rejected (已失败); 成功会触发.then, 失败会触发catch,还有一个finally不管是成功还是失败都会触发 #### 解决了什么问题? 它解决的回调地狱的问题 #### 有没有替代方案? Promise虽然解决了回调地狱,但是并不能简化代码,所以一般在工作中我们会配合 async/await来使用 #### Promise相关的静态方法 ###### 1.Promise.race() 可以接收多个Promise实例, 赛跑,竞速原则,只要有一个Promise满足条件,就会执行.then ###### 2.Promise.all() 可以接收多个Promise实例,等待原则,需要等待所有的Promise都执行完毕,才执行.then,有一个失败就会触发catch, 可以用于处理一些并发的任务 ###### 3.Promise.any() 可以得到最先处理成功的结果,都失败才会触发catch ## 07-说一下localStorage, sessionStorage 和 cookie 的差异 #### 生命周期不一样 `localStorage`: 永久存储, 除非手动删除。 `sessionStorage`: 关闭当前**页面**后会自动消失。 `cookie`: 默认关闭浏览器后失效, 如果设置了过期时间,则到达过期时间后才会失效。 #### 生效范围不一样 localStorage:同域都可以共享。 sessionStorage: 只有当前标签才可以访问 cookie: 同域下且path匹配的情况下才能访问 什么是 path 匹配? 例如 [http://www.baidu.com:3000/](https://gitee.com/link?target=http%3A%2F%2Fwww.baidu.com%3A3000%2F) 下创建的 cookie 或者设置 cookie 的时候加了 `path=/`,同域下的任意路径都能访问,因为任意路径都属于 `/` 的子路径,又称为和 `/` 是匹配的。 例如 [http://www.baidu.com:3000/a/b](https://gitee.com/link?target=http%3A%2F%2Fwww.baidu.com%3A3000%2Fa%2Fb) 下创建的 cookie 或者设置 cookie 的时候加了 `path=/a`,同域下和 `/a` 匹配的任意路径都能访问,例如 `/a`、`/a/b`、`/a/c`、`/a/b/c` 等都称为是和 `/a` 匹配,或者称为是 `/a` 的子路径。 #### 存储大小不一样 localStorage:4.98M(不同浏览器下会有差异,例如Safari 浏览器为 2.49M) sessionStorage:4.98M cookie: 4KB #### 操作主体不一样 localStorage/sessionStorage : 只有客户端才能够设置. cookie: 客户端(`document.cookie`)和服务端(`Set-Cookie`)都可以设置。 #### 请求是否会携带 localStorage / sessionStorage:请求时不会自动携带。 cookie:每次请求都会随着请求头带到后端(符合同域且 path 匹配的条件)。 ## 08-Vue Router有几种类型钩子? 全局路由守卫: router.beforeEach , router.afterEach , router.beforeResolve (在组件内守卫和异步路由被解析之后触发) 独享路由守卫: beforeEnter 组件内路由守卫: beforeRouteEnter, beforeRouteLeave, beforeRouteUpdate ## 09-说一下你对Vuex的理解? #### 是什么? Vuex是一个全局的状态管理库. ### vuex的触发流程? 点击按钮的时候, 通过dispatch触发actions, 在actions中发请求,拿到请求结果之后,通过commit触发mutations,并且对mutations的每一次触发都可以通过devtools来观测到。,在mutations中修改state之后,由于state中数据是响应式的,所以凡是用到state数据的组件都会自动更新。如果不涉及到异步操作,也可以直接在视图中commit触发mutations来修改state #### 怎么用? **配置项** 一般使用的时候他里面有 state : 存储数据 mutations: 负责同步更新state中的数据 mutations是唯一可以修改state数据的方式 actions : 异步更新数据(异步操作),例如发送ajax,将请求到的数据通过commit触发mutations来修改state getter: 派生数据,相当于state计算属性 module: 负责模块化管理vuex数据 plugins 等配置项. #### 解决了什么问题? 解决了非关系型组件之间数据的传递和共享问题 #### 衍生问题 ###### mutation里面为什么建议只写同步代码去修改state? 写异步代码会咋样? 写同步代码可以保证,mutation执行完毕后可以得到一个确定的状态,方便使用devtools打个快照存下来 如果mutation里面写异步代码,严格模式下会有警告,因为不好追踪状态是何时变更的,给调试带来困难 ## 10-请说出路由配置项常用的属性及作用 路由配置参数: path: 跳转路径 component:获取跳转页面的地址 name:命名空间 children:子路由的配置参数(路由嵌套) props:路由解耦 redirect: 重定向路由 ## 11-$route 和$ router 的区别? routes: 数组 。路由的配置规则 router:对象。路由对象 $route: 对象。用于接收路由跳转参数 $router: 对象。用于跳转路由 和 传递参数 ## 12-说一下你在vue中踩过的坑 - 1操作data中的数据,发现没有响应式 - 原因: 数组中有很多方法,有的会改变数组(例如pop push),有的不会改变数组(例如slice, filter) - 解决方案:通过Vue.set(对象,属性,值)这种方式就可以达到,对象新添加的属性是响应式的 - 2.在created操作dom的时候,是报错的,获取不到dom,这个时候实例vue实例没有挂载 - 解决方案:Vue.nextTick(回调函数进行获取) ## 13-Vue的$nextTick #### $nextTick是干什么的? 说法一:如果想要在修改数据后立刻得到更新后的`DOM`结构,可以使用`Vue.nextTick()` 说法二:页面的 DOM 还未渲染,这时候也没有办法操作 DOM,如果想要操作 DOM,需要使用 nextTick来解决这个问题 **nextTick的原理**: 核心是利用了如 Promise 、MutationObserver、setImmediate、setTimeout 的原生 JavaScript 方法来模拟对应的微/宏任务的实现,本质是为了利用 JavaScript 的这些异步回调任务队列来实现 Vue 框架中自己的异步回调队列。 **使用场景**? 1、在数据变化后执行某个操作,而这个操作需要使用随着数据变化而变化的 DOM 结构的时候,就可以使用$nextTick 2、在 vue 生命周期中,如果在 created()钩子进行 DOM 操作,也一定要放在的回调函数中。 **$nextTick是什么**? 在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM ## 14-MVVM和MVC的区别? ###### MVC : 传统的设计模式。 设计模式: 一套广泛被使用的开发方式 M: model 模型 模型:就是数据的意思 V : view视图 视图:就是页面的意思 C:controller控制器 控制器:在这里写js业务逻辑,把数据M 渲染到 视图 V (有点类似于我们之前学习的,把数据渲染到页面) ###### MVVC: vue所使用的设计模式 设计模式: 是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。(代码分层, 架构设计) ###### MVVM,一种软件架构模式,决定了写代码的思想和层次 M: model数据模型 (data里定义) V: view视图 (页面标签) VM: ViewModel视图模型 (vue.js源码) ## 15-vue的常用修饰符 .prevent: 阻止事件的默认行为 .stop: 阻止事件冒泡 .once: 只执行一次这个事件 .enter: 监听键盘enter键 ## 16-vue的最大优势? ###### 1.双向数据绑定 ###### 2.数据驱动视图 ###### 3.组件开发 ###### 4.数据视图分离 ###### 5.单页面应用可以实现页面数据局部刷新 ## 17-Vue组件data为什么是一个函数? 因为组件需要复用, 如果data是一个对象,对象是引用类型,就会导致一个地方发生改变,其他页面也随之改变, 如果data是一个函数, 每一次复用的时候就会调用这个函数, 生成一个新的地址,就可以做到每个组件之间的数据互不干扰 ## 18-new做了什么? * 创建一个空对象 * this指向这个空对象 * 执行构造函数中的代码 * 返回一个新对象 ## 19-key值的作用? key值主要是给元素添加一个唯一的标识符,从而提高vue的渲染性能,当数据发生变化的时候,vue会使用diff算法来对比新旧虚拟DOM,如果遇到key值相同的组件则复用, 否则强制更新 ## 20-什么是跨域? 什么是跨域: 当协议名, 域名, 端口号 任一不同就是跨域 为什么会有跨域: 是浏览器处于安全性的考虑,而做出的同源策略 解决方案: **后端处理**: 后端通过CORS处理, 也就是通过设置一个Access-Control-Allow-Origin 这个响应头来允许某些域名访问 **前端处理**: 如果说是用 Vue CLI 创建的项目,可以在 vue.config.js 中配置 devServer 的 proxy 来代理到某个地址。 ## 21-Vue刷新页面数据丢失解决方案? * 将vuex中的数据直接保存到浏览器的缓存中 * 在页面刷新的时候再次请求远程数据,使之动态更新数据 * 在页面刷新前将vuex中的数据先保存到本地存储中,然后在刷新的时候从本地取出 ## 22-keep-alive的作用 keep-alive是vue的内置组件,其主要作用是缓存组件, keep-alive既不会被渲染成DOM元素,也不会出现在其父组件中,在组件切换过程中, 它会把切换出去的组件保留在内存中,防止重复渲染DOM,减少加载时间及性能的消耗,提高用户的体验感 补充回答:被包含在 keep-alive 中创建的组件,会多出两个生命周期的钩子: activated(组件激活时使用) 与 deactivated(组件离开时调用 ## 23-什么是EventLoop(事件循环)? 1.什么是事件循环EventLoop :浏览器内核解析与执行代码的一种规则 2.js代码分为两大类: 2.1 同步代码 :默认 2.2 异步代码 :事件、定时器、ajax、promise.then()、async/await (1)宏任务:事件、定时器、ajax (2)微任务: promise.then、async/await 1. JS 代码的执行分为同步代码和异步代码 2. 当碰到同步代码时直接在执行栈中执行 3. 当碰到异步代码并且时机成熟时(例如定时器时间到了),就把异步代码加入任务队列中 4. 当执行栈中的同步代码执行完毕后 5. 就去任务队列当中把异步代码拿到执行栈中执行 6. 这种反复轮序任务队列中的异步代码并拿到执行栈中执行的操作就是EventLoop,就是事件循环,就是JS的执行机制 web worker 可以开启多线程,可以把比较耗时间的操作放到另外一个线程 3.事件循环执行机制 : **事件循环执行机制** : (1)页面一加载,默认解析最外层的script宏任务 (2)从上往下逐行"解析"代码 (3)如果是同步代码,则立即(执行’ (4)如果是异步代码 :宏任务则放入宏任务队列,微任务则放入微任务队列 (5)等到页面所有的同步代码执行完毕,开始'执行异步代码” (6)先执行微任务队列代码,微任务执行完毕开始执行宏任务。 (7)按照以上规则来解析宏任务,形成一次事件循环。直到所有宏任务全部执行完毕 ## 24-说一下你对节流和防抖的理解? 基本关系: 防抖 (debounce) 和节流 (throttle) 都是用来控制某个函数在一定时间内执行多少次 防抖 (debounce) : 简单的说,当一个动作连续触发,则只执行最后一次 节流 (throttle): 限制一个函数在一定时间内只执行一次 防抖的使用场景: 搜索框搜索输入,等用户最后一次输入完,再发送请求 窗口大小拖拽Resize 节流的使用场景: 滚动条的滚动加载 相同点: 都可以通过setTimeout实现 都是降低回调的执行频率,节省计算资源 在lodash中提供debounce(), throttle() 方法 不同点: 防抖是,让方法合并执行 节流是,让方法均匀执行 ## 25-说一下你对rem的理解? **rem**是一个相对于根元素字体大小的单位. #### 怎么用/原理是什么? 利用媒体查询或`JS`动态检测设备的宽度,不同设备宽度下设置对应的根元素字体大小,根元素字体大小变了,那么所有使用rem做单位的盒子的大小也会随之改变 #### 解决了怎么问题? 解决了移动端适配的问题 #### 有没有替代方案? 现在工作中我也会使用`vm`这个单位来做适配 ## 26-HTML 的语义化你是怎么理解的? #### 什么是HTML语义化 就是根据内容的结构, 选择合适的标签, 便于开发这阅读和写出更完美的代码, 的同时让浏览器更好的解析 #### HTML语义化的好处 为了在没有`CSS`的的情况下,页面也可以呈现出很好的代码结构; 有利于`SEO`和搜索引擎建立良好沟通,有助于爬虫抓取更多的有效信息; 方便其他设备解析,以及便于团队的开发和维护 ## 27--em 和 rem的区别? em是一个相对于父元素字体大小的相对单位,而 rem是相对于根元素的字体大小的单位; em是相对于当前元素或父元素进行换算的,层级越深,换算越复杂, 而rem是相对于根元素计算,可以避免层级关系 ## 28-说一下你对 push, pop, shift, unshift 的理解? **push**是向数组末尾添加元素, 返回值: 返回新数组**长度**; **pop**是删除数组末尾的一个元素, 返回值: 返回**删除**的那个元素 ; 如果数组变为空,则该方法不改变数组,返回`undefine`值。 **shift**是删除数组开头的一个元素, 返回值: 返回**删除**的那个元素; 如果数组是空的,`shift()`方法将不进行任何操作,返回`undefined`的值。 **unshift**是向数组开头添加元素, 返回值: 返回新数组**长度** ## 29-什么是伪数组, 如何将伪数组转换成真数组? #### 什么是伪数组? a. 常见的伪数组具有length属性; b. 可以通过arguments对象获取 c. 通过调取`getElementsByTagName`, `getElementsByClassName`,`document.childNodes`以及自定义对象等,他们返回的对象都属于伪数组,也称为类数组。 #### 如何将伪数组转换成真数组? 1. 使用Es6新增的 Array.from()方法 ```js var obj={ 0:"lily", 1:21, 2:"abc", length:3 } var arr= Array.from(obj); console.log(arr) // ["lily", 21, "abc"] ``` 2. 使用Es6的扩展运算符 3. Array.prototype.slice.call() 4. 数组.slice.call() ## 30-watch 和 computed 的区别? 1. 功能上: computed 是计算属性, watch 是监听一个值的变化, 然后执行对应的回调. 2. 是否调用缓存: computed 中的函数所依赖的数据没有发生变化,调用函数的时候就会从缓存中读取, 而watch在每次监听值的变化的时候都会执行回调 3. 是否调用return: computed中函数必须要用return返回, watch中函数不是必须要用return 4. computed默认第一次加载的时候就开始监听;watch默认第一次加载不做监听,如果需要第一次加载监听,需添加immediate属性,设置watch----搜索框 5. 使用场景: computed---当一个属性受到多个属性影响的时候,使用computed---如: 购物车商品结算。 watch---当一条数据影响多条数据的时候,使用watch----搜索框 ## 31-var , let , const 的区别? ###### 1.变量提升 var声明的变量存在变量提升,即变量可以在声明之前调用,值为undefined let和const不存在变量提升,即它们所声明的变量一定要在声明后使用,否则报错 ###### 2.重复声明 var允许重复声明变量 let和const在同一作用域不允许重复声明变量 ###### 3.块级作用域 var不存在块级作用域 let和const存在块级作用域 ###### 4.修改声明的变量 var和let可以 const声明一个只读的常量。一旦声明,常量的值就不能改变,但对于对象和数据这种引用类型,内存地址不能修改,可以修改里面的值。 ## 32-说一说事件冒泡和事件捕获 #### 什么是事件冒泡? 事件从最内层的元素,从内往外(从下往上)进行传播,直至根节点 #### 怎么阻止事件冒泡? 事件对象**.stopPropagation()** #### 什么是事件捕获? 事件从文档根节点出发,从上往下进行传播,直至到达事件的目标节点 #### js事件流三阶段 * 事件捕获阶段: 事件从顶层对象出发,从上往下,直至目标元素或者根节点 * 处于目标阶段: 处于绑定事件的元素上 * 事件冒泡阶段: 事件由具体的元素先接收,从下往上传播,直至根节点 ## 33-onclick和addEventListener设置点击事件的区别? * onclick: 会覆盖 * addEventListener: 不会覆盖 * addEventListener :可以根据eventType同时绑定多个属性 ## 34-深拷贝与浅拷贝区别? **深拷贝**: 深拷贝拷贝多层,每一级别的数据都会拷贝,修改拷贝后的数据,原数据不会发生改变 **浅拷贝**: 浅拷贝只拷贝一层,更深层次的对象,只拷贝引用, 修改拷贝后的数据,原数据会发生改变 ###### 深拷贝的方法: * 可以使用递归 * JSON对象的Stringify和Parse来实现深拷贝(常用) * lodash函数库实现深拷贝 * 通过jQuery的extend方法实现深拷贝 ###### 浅拷贝的方法 * Object.assing()方法, Es6新增的 ## 35-说一说你对$set()的理解 在 Vue 中, 像已声明的对象添加新的属性 和 通过索引值来修改数组中的值, 视图是不会更新的, 这个时候就得使用this.$set() ,但是使用splice方法视图是会更新的 ## 36-箭头函数和普通函数的区别? 箭头函数是 ES6 新增的特性,用来简化普通函数的写法,规避普通函数 this 指向的痛点。 1. 箭头函数没有原型对象 prototype,不能作为构造函数使用(不能被 new)。 2. 箭头函数没有 arguments,可以使用 `...` 拿到所有实参的集合数组。 3. 箭头函数中的 this 在定义时就已经确定,取决于父级的环境【箭头函数本身没有this指向】。 4. 箭头函数不能通过 call、apply、bind 方法修改它的 this 指向(会忽略第一个参数,其他功能还是可以正常使用)。 5. 箭头函数不能用作 Generator 函数,不能使用 yeild 关键字(function*)。 ## 37-界面访问控制? 在哪做的? 在全局路由前置导航守卫 beforeEach 做了什么? 如果说有 token,看一下访问的页面是不是登录页,如果访问的是登录页,拦截到首页,否则执行 next() 放行; 没有 token,看一下访问的页面在不在白名单,如果在,执行 next 放行,否则拦截到登录页。 ## 38-Token失效怎么处理? **前端主动处理**:在登录成功后存一个时间戳(A),在请求拦截器处用当前的时间戳(B)减去 A,如果大于 Token 的有效时间了,就做退出相关的操作。 **配合后端处理**:在响应拦截器的 error 回调中,根据后端返回的错误状态码,如果是 401,表示 token 过期,直接做退出相关的操作 ## 39-什么是原型链? - 当访问一个对象的某个属性时,会先在这个对象本身属性上查找 - 如果没有找到,则会去它的`__proto__隐式原型`上查找,即它的构造函数的prototype - 如果还没有找到就会再在构造函数prototype的`__proto__隐式原型`中查找 - 直到构造函数原型对象prototype的`__proto__隐式原型`指向为null就停止 - 这样一层一层向上查找就会形成一个链式结构,我们称为原型链。 ![](C:\Users\86157\Pictures\面试\微信图片_20221220210610.png) **为什么要使用原型链?** 1. 为了实现继承,简化代码,实现代码复用 2. 只要是这个链条上的内容,都可以被访问和使用到 **使用原型链有什么作用?** * 继承 * prototype 用来实现基于原型的继承与属性的共享 * 减少了内存占用 * 避免了代码冗余,公共的属性和方法,可以放到原型对象中,这样,通过该构造函数实例化的所有对象都可以使用该对象的构造函数中的属性和方法! ## 40-懒加载 ###### 是什么? 懒加载简单说 就是延迟加载或者按需加载,即在需要的时候进行加载 ![](C:\Users\86157\Pictures\面试\Snipaste_2022-12-21_14-21-03.png) ###### 懒加载的优点? * 提高用户的体验感 * 可以简化代码,增强代码的可读性 * 可以最大程度的减少服务端的资源消耗 ## 41-重绘和回流的区别 **重绘(Painting):** 给一个元素更换颜色或背景,就会重新渲染页面 如: * 颜色或背景颜色 **回流(Layout)/重排:** 当增加或删除dom节点,或者给元素修改宽高时,会改变页面布局,那么就会重新构造dom树然后再次进行渲染, 如: * 添加或删除课件的DOM元素 * 元素的位置发生改变 * 元素的尺寸发生改变(包括 外边距, 内边框, 边框大小, 高度和宽度) * 内容发生变化,比如文本变化或者图片被另一个不同尺寸的图片所替代 * 浏览器的窗口尺寸变化 * 页面一开始渲染的时候(这个肯定避免不了) ## 42-小程序中组件和页面的区别 * 组件的.json文件中需要声明"component": true属性 * 组件的.js文件中调用的是component()函数,页面的渲染调用的是page() * 组件的事件处理函数需要定义到methods节点中 ## 43-小程序中 data 和 properties 的异同 **同:** propperties 属性和 data数据的用法相同,它们都是可读可写的 **异:** * data更倾向于存储组件的私有数据 * properties 更倾向于存储外界传递到组件中的数据 * 本质上小程序中 data 和 properties 是同一个对象的不同引用,说白了两者是完全等价的 * 不建议修改properties数据,如果要修改,最好通过子组件触发父组件的自定义事件实现 ## 44-小程序中组件的生命周期 ###### 组件生命周期需要在lifetimes中使用 * created : 组件实例刚刚被创建时执行 * 注意此时还不能调用setData * 通常情况下,这个生命周期只用于给组件this实例添加一些自定义属性字段 * attached: 组件实例进入页面节点树时执行 * this.data 已被初始化完毕 * 绝大多数操作在这个时机执行(例如发送请求获取数据) * ready: 组件在视图层布局完成后执行 * moved: 组件实例被移动到节树另一个位置时执行 * detached: 组件实例被从页面节点树移除时执行 * 退出一个页面时,会触发页面内每一个自定义组件的detached生命周期 * 此时适合做一些清理性质的操作 * error : 参数为: Object Error , 每当组件方法抛出错误时执行 ## 45-组件所在页面生命周期 ###### 组件所在页面生命周期需要在pageLifetimes节点中使用 * show : 组件所在页面被展示时执行(后台-->前台) * hide : 组件所在页面被隐藏时执行(前台-->后台) * resize : 参数:Object Size , 组件所在页面尺寸发生变化时执行 ## 46-前端常见状态码 2xx : 成功; 3xx: 重定向; 4xx: 请求错误; 5xx: 服务器错误 * 200: 请求成功 * 301: 永久重定向 * 302: 临时重定向 * 304: 缓存 * 400: 错误请求, 语义错误 ,请求无法被服务器理解,或者请求参数有误; * 401: 认证失败(由于 token 过期造成的没有权限) * 403: 服务器拒绝该请求(没有权限) * 405: 请求方式错误 * 404: 找不到请求网页 * 500: 服务器内部错误 * 503: 服务器不可用 ## 47-小程序登录 1.前端通过`wx.login()`拿到code。 2.前端通过调用`wx.request()`发送code到后端 3.后端调用 code2Session 方法并传递 appid(小程序的id)、appsecret(小程序秘钥)、code 等参数到微信的后台。 4.微信的后台返回 session_key(当前我们公司的后端和微信后端通信时的秘钥) 和 openid(用户的唯一标识)到我们的后台。 5.我们的后端生成 token(并把 token 和 openid 进行关联,说白了通过 token 就能知道是哪个用户),并把 token 返回到小程序端。 6.前端存储 token 到本地,后续的所有请求都带上这个 token 到后端。 ## 48-小程序支付 1.创建订单 * 请求创建订单的API接口(后端提供); 把(订单金额,收货地址,订单中包含的商品信息)发送到服务器 * 服务器响应的结果: 订单编号 2.订单预支付 * 请求订单预支付的API接口(后端提供的) : 把(订单编号)发送到服务器 * 服务器响应的结果: 发起微信支付时候的参数 3.发起微信支付: 调用`uni.requestPayment()`这个API,并传递订单预支付对象,发起微信支付 4.微信支付成功后,跳转到支付结果页,再次传递订单编号调用后端接口查询最终支付的状态并展示。 ## 49-函数传参,传递简单数据类型和复杂数据类型的差异 简单数据类型传递的是值的拷贝,函数内部对参数的修改不会影响外部; 复杂数据类型传递的是引用地址,函数内部对参数内容的修改会影响外部,对参数引用的修改不会影响外部 ## 50-for in 和 for of 的区别 #### 适用范围不一样 ###### for in 适用于**可枚举**数据,例如对象,数据,字符串。 ###### 什么是可枚举? 属性的 enumerable值为true咱就称为是可枚举的,通过`Object.getOwnPropertyDescriptors()`验证/查看 ###### for of 适用于可迭代数据,例如Array、String、Map、Set、TypeArray、函数的arguments对象,NodeList对象。 ###### 什么是可迭代? Es6中具有`Symbol.iterator`属性,他对应的值是一个函数,调用函数后可以得到一个对象,每次调用对象的next方法能得到目标的每一项,只要符合这个特点就是可跌代 #### 遍历的范围不一样 for in 原型上的可枚举的属性也能被遍历到。 for of 一般只能遍历自身的(具体和迭代器的内部实现有关) ## 51-说一下你对Vue3的理解/Vue3和Vue2的差异 1\. 性能更高了,得益于响应式的原理换成Proxy,VNode Diff 的算法进行了优化; 2\. 体积更小了,得益于删除了一些不太常用的API,例如filter、Event Bus、Vue3中的API都是按需导入的,能配合Webpack支持Tree Shaking(没有用的代码不回打包)。 3\. 对TS支持更好了,因为它的码源就是用TS写的。 4\. **Composition API(组合 API)**,能把同一功能的数据和业务逻辑进行复用。 5\. 新特性(Teleport、Fragment、Suspense...)。 ## 52-说一下Vite 1. 瞬间启动一个服务,基于请求去加载对应的代码的(不会像webpack一样先打包所有的代码,最后在开启服务器) 2. 使用原生的ESM文件,开发阶段无需打包,所以更快 ## 53-Vue2的选项API和Vue3 composition API 该怎么理解 Vue2选项API 的写法挺好的,我非常喜欢,因为一个萝卜一个坑,例如数据就放到data里面,方法就放到methods里面,非常利于学习和上手,其实Vue2之所以流行起来很大一个原因也是得益于此,但是Vue2 Option的写法对于大型项目来说就显得力不从心,不利于大型项目的复用(mixins虽然可以复用,但是也有新的问题)和维护(代码一多,经常到处找同一功能和业务逻辑),所以组合API的出现就解决了这个问题. ## 54-Vue3(组合API)常用的生命周期钩子 常用的生命周期钩子有7个,可以多次使用同一个钩子,执行顺序和书写顺序相同 `setup`、`onBeforeMount`、`onMounted`、`onBeforeUpdate`、 `onUpdated`、`onBeforeUnmount`、`onUnmounted` ## 55-interface和type的异同 【同】 1\. 都可以描述对象和函数 2\.都可以进行类型拓展,只不过语法不一样,interface用的extends进行继承,type 用的是 & 形成一个交叉类型 【异】 1\.相同的interface会合并 2\.相同的type会报错 3\.type可以描述任意类型 类型 【总】 一般情况下interface更适合用来描述对象,而type用来描述类型之间的关系 ## 56-any和unknown 1\. unknown是更加安全的any类型 2\. 任意类型可以赋值给any, any也可以赋值给任意类型; 任意类型可以赋值给unknown, 但unknown只能给unknown或any ## 57-hash和history路由的异同 【同】 两者都是用来实现前端路由的 (前端路由就是页面地址和组件之间的对应关系) 【异】 **兼容性**:hash兼容到 IE8 , history 兼容到IE10 **实现原理**: hash模式是通过监听 onhashchange 事件做的处理 history 模式是利用 H5 新增的 History 相关的 API 实现的,例如 onpopstate事件、 pushState、replaceState等相关的方法 **刷新页面时,对后端的表现**: 刷新页面时,hash 地址也就是(#号后面的内容),不会作为资源发送到服务端,后端拿到的都是`/`这个请求地址,它只要返回`index.html`页面就好了。 ```js https://www.baidu.com/#/news https://www.baidu.com/#/user ``` 刷新页面时,history 地址对于服务端来说是一个新的请求,后端拿到的是不同的请求地址,也就意味着需要服务端对这些请求做处理,否则会 404。 ```js https://www.baidu.com/news https://www.baidu.com/user ``` 做什么处理呢?匹配到相关 GET 请求,统一返回 `index.html`,`index.html` 加载的有路由相关的代码,所以也就转换为由前端路由来处理啦。 ## 58-CSS3新特性有哪些 * border-radius: 圆角边框 * box-shadow: 盒子阴影 * background-size: 背景图片大小 * transition: 过渡 * transform: 转换(位移, 旋转, 缩放) * animation: 动画 * box-sizing: css3 盒子模型 ## 59-typeof与instanceof的区别 【同】 `typeof`和`instanceof`都是判断数据类型的方法 【异】 * `typeof`会返回一个变量的基本类型, `instanceof`返回的是一个布尔值 * `instanceof`可以准确判断复杂数据类型,但是不能正确判断基本数据类型 * 而`typeof`虽然可以准确的判断基本数据类型(`null`除外), 但是引用数据类型只能判断`function`, 其他的都无法判断 如果需要检测数据类型,可以采用`Object.prototype.toString`方法,统一返回格式`[object xxx]`的字符串 ```js Object.prototype.toString({}) // "[object Object]" Object.prototype.toString.call({}) // 同上结果,加上call也ok Object.prototype.toString.call(1) // "[object Number]" Object.prototype.toString.call('1') // "[object String]" Object.prototype.toString.call(true) // "[object Boolean]" Object.prototype.toString.call(function () {}) // "[object Function]" Object.prototype.toString.call(null) //"[object Null]" Object.prototype.toString.call(undefined) //"[object Undefined]" Object.prototype.toString.call(/123/g) //"[object RegExp]" Object.prototype.toString.call(new Date()) //"[object Date]" Object.prototype.toString.call([]) //"[object Array]" Object.prototype.toString.call(document) //"[object HTMLDocument]" Object.prototype.toString.call(window) //"[object Window]" ``` ## 60-内存泄漏 什么是内存泄漏? 变量的内存没有及时的回收,导致内存资源浪费 ## 61-关于继承 `ES5` 中的组合继承 = 借用构造函数继承(继承的是父级实例上的属性) + 原型继承(父级原型上的方法) ```html // 借用构造函数继承 function Star(name, age, money) { Person.call(this, name, age) this.money = money } ``` ```html // 原型继承 Star.prototype = new Person() Star.prototype.constructor = Star ``` 不过呢,现在 `ES6` 我更喜欢使用 extends 去继承。 ```html class Star extends Person{ constructor(name, age, money) { // 相当于这儿调用了父类的 constructor 的同时,并把父类 constructor 内部的 this 改成了这儿的 this super(name, age) this.money = money } } ``` ## 62-EventBuskeep `main.js` ```ht Vue.prototype.$bus = new Vue() ``` `App.vue` ```html ``` `Child.vue` ```html ``` 为什么Vue3移除了EventBus? * EventBus太过于灵活了,一旦出现问题不太方便追溯,因为任何一个组件都可以进行触发和监听此事件 * 这个东西不是必须的,Vue作者处出于自身体积更小的原因把它干掉了 * 这个 EventBus 就算我们自己实现也足够简单,`不超过五行代码` ```js class EventBus { constructor() { this.bus = {} } $on(evName, callback) { // !#1 $on 的时候先组装好这样一个对象 /* const o = { addCount: [(count) => {console.log(count)}, (count) => {console.log(count)}], addAge: [(age) => {console.log(age)}] } */ if(this.bus[evName]) { this.bus[evName].push(callback) } else { this.bus[evName] = [callback] } } // spread // [...arr] // {...{}} // 函数:rest 剩余运算符 $emit(evName, ...args) { // !#2 $emit 的时候,只需要根据 evName,去对象中取这个 evName 对应的值(数组),循环数组,并调用每一项 this.bus[evName].forEach(callback => callback(...args)) } } const $bus = new EventBus() $bus.$on('addCount', (count) => { console.log(count) }) $bus.$on('addCount', (count) => { console.log(count) }) $bus.$on('addAge', (age) => { console.log(age) }) $bus.$emit('addCount', 3) $bus.$emit('addAge', 18) ``` ## 63-Vue2生命周期钩子 ​ 共分为四个阶段八个钩子 初始化阶段: `beforeCreate` `created` 挂载阶段: `beforeMount` `mounted` 更新阶段: `beforeUpdate` `updated` 销毁阶段: `beforeDestroy` `destroyed` **请问vue第一次渲染会执行哪些钩子?** `beforeCreate` `created` `beforeMounted` `mounted` **请问vue生命周期钩子哪些钩子会执行多次?** `beforeUpdate` `updated` **请问生命周期钩子有哪些钩子是常用的?** created : 最早可以操作data , 一般发送ajax mounted : 最早可以操作dom , 一般操作dom ## 64-map和filter区别 **map**:**返回一个新数组**,map函数之后,数组元素的个数不变,但是按照一定的条件转换,数组元素发生了变化,map() 方法按照原始数组元素顺序依次处理元素。 **注意:** map() 不会对空数组进行检测。 **注意:** map() 不会改变原始数组。 **filter**: **返回一个新的数组**,filter函数之后,数组元素个数可能发生改变,但是数组元素不会发生改变 **注意:** filter() 不会对空数组进行检测。 **注意:** filter() 不会改变原始数组。 ## 65-数组方法有哪些 `forEach`(没有返回值) `map`(返回一个新数组) `filter`(返回一个新数组) `every`(要数组里面都满足条件, 返回布尔值) `some`(数组里面有一个满足条件即可, 返回布尔值) `reduce`(累加器)array.reduce(function(计算结束后的返回值, 当前元素, 当前元素的索引, 当前元素所属的数组对象), 初始值) `find`(返回通过测试的数组的第一个元素的值) `includes`(判断数组是否包含指定的元素, 返回布尔值)array.includes `concat`(用于拼接数组和字符串的) ## 66-Sass 变量用于存储一些信息,它可以重复使用, Sass 变量使用 $ 符号 Sass 变量可以存储以上信息: * 字符串 * 数字 * 颜色 * 布尔值 * 列表 * null值 ```js $myFont: Helvetica, sans-serif; $myColor: red; $myFontSize: 18px; $myWidth: 680px; body { font-family: $myFont; font-size: $myFontSize; color: $myColor; } #container { width: $myWidth; } ``` Sass 变量的作用域只能在当前的层级上有效果,如下所示 h1 的样式为它内部定义的 green,p 标签则是为 red。 ```js $myColor: red; h1 { $myColor: green; // 只在 h1 里头有用,局部作用域 color: $myColor; } p { color: $myColor; } ``` ## 67-实现水平垂直居中 * 利用定位 "父相子绝" ![](C:\Users\86157\Pictures\面试\1.png) * 绝对定位 (margin: auto) ![](C:\Users\86157\Pictures\面试\2.png) * 绝对定位 -- 位偏移(transform) ![](C:\Users\86157\Pictures\面试\3.png) * 利用 flex 弹性布局 ![](C:\Users\86157\Pictures\面试\4.png) ## 68-mixin的优缺点 **优点**: * 提高代码复用性 * 无需传递状态 * 维护方便,只需要修改一个地方即可 **缺点**: * 命名冲突 * 滥用的话后期很难维护 * 数据来源不清晰 * 不能轻易的重复代码 注意: * mixin 中的生命周期函数会和组件的生命周期函数一起合并执行 * mixin 中的 data 数据在组件中也可以使用 * mixin 中的方法在组件内部可以直接调用 * 生命周期函数合并后执行顺序: 先执行 mixin 中的,后执行组件的 * 不同组件中的 mixin 是相互独立的,互不影响的 * data 和 methods:相同的会覆盖,组件内的会覆盖 mixin 中的。 ## 69-闭包 闭包: 内层函数 + 访问外层函数的变量 闭包就是一个函数 闭包的作用: 可以封闭数据, 实现数据私有化 ## 70-作用域链 作用域链: 变量查找机制 如何查找: 就近原则, 从内往外依次查找 ## 71-封装Axios 1、请求文件的封装:在utils/request,js里面创建一个axios实例,然后封装了baseURL,timeout请求拦截器,响应拦截器,并导出. 2、在项日中是怎么使用的:在api目录根据request.is中封装好请求的对象再次创建请求函数并导出,一般在组件挥着vuex中的actions中使用 ## 72-项目开发流程 a. 产品经理收集客户需求,然后用 墨刀 或者 Axure 或者蓝湖等等产品原型工具画出原型图 (可自行百度大致了解一下这些原型工具)。 b. 项目经理召集人员开研发会议(前端、后端、测试都要参加), 跟研发人员捋清业务流程,然后前后端评估项目完成时间(需求排期)。 c. 开发准备阶段:技术组长分配任务;测试人员给出测试用例;前后端一起商量需求中需要联调的部分,进行接口的口头协议交流。 d. 在后端接口还没写好之前,前端可以先依照原型图来完成自己任务模块的静态页面和结构搭建。 e. 后端接口文档写好后,前端开始实现功能和数据铺设等等。 f. 开发完成后,将项目打包测试环境发给测试,测试人员根据测试用例,来进行测试,如果测出 Bug,会将问题提交到禅道 (或者其他的管理软件)上。然后根据他给出的 Bug 来改,改完之后继续给他测直到 ok (回归测试)。 g. 进行项目上线,上线过程中,一般所有开发人员都要在场(因为会有很多意想不到的 Bug),如果出现 Bug 需要紧急修复。一切 ok 之后第一版完成。 h. 项目经理将第一版交付给客户(甲方)对接,如果客户有不满意的地方,需要调整需求,反馈给我们,然后进行改版..........循环这个改版过程 直到客户满意。 ## 73-自定义指令directive - 【作用是什么】:可以对普通 DOM 进行底层操作,例如聚焦输入框。 - 【怎么用】:可以通过 Vue.directive 全局注册一个指令,或者组件内部通过 directives 进行局部注册,它常用的钩子有 inserted,表示被绑定元素插入父节点时调用,还有 update,它表示指令所在组件的 VNode 更新时调用。 - 【场景】:我在实际项目中,用自定义指令处理过图片懒加载,原理就是当图片进入可视区的时候把图片的地址给图片的 src 属性就好啦。 - 【注意】:自定义指令相关的钩子在 Vue3 中发生了变化,主要体现在和组件的生命周期钩子保持了一致,更加容易记忆和理解了。 ## 74-大文件上传和断点续传 **大文件上传:** 核心是利用 Blob.prototype.slice 方法,对源文件进行切分。 然后并发(Promise.all)上传多个小切片(标记下切片的顺序)。 服务端接受完切片时进行合并,如何知道接收完了呢? 一种办法是前端在每个切片中都携带最大切片数量字段,当服务端接受到这个数量的切片时自动合并。 也可以额外发一个请求主动通知服务端进行切片的合并。 **断点续传:** 断点续传的原理在于前端/服务端需要记/住已上传的切片,这样下次上传就可以跳过之前已上传的部分。 前端使用 localStorage 记录已上传切片的 hash,下次点击上传按钮时获取本地的记录,从记录处开始上传。 [https://zhuanlan.zhihu.com/p/467714393](https://gitee.com/link?target=https%3A%2F%2Fzhuanlan.zhihu.com%2Fp%2F467714393) ## 75-网页支付流程 **支付宝支付** 1. 前端传递相关参数(拿电商平台来说,参数至少包含 skuId 以及对应的数量、收获地址等信息)到后端,得到订单编号。 2. 前端传递订单编号、支付方式、回调地址等传到后端,后端返回支付地址到前端。 3. 前端拿到支付地址后直接进行跳转,会打开支付平台(手机端的话会唤起支付宝 App 进行支付,浏览器需要输入账号密码)。其中第四步和第五步也可能不存在,因为后端也可以直接重定向到支付平台。 4. 支付宝通知后台此订单的支付结果。 5. 后台修改订单状态并回调到某个前端的页面。 6. 回调地址中会有订单编号和支付结果等信息。 7. 前端进行展示。 ## 76-双向绑定 **Vue2:** 采用数据劫持结合发布者订阅模式,通过 Object.defineProperty() 来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调来渲染视图。 **Vue3:** 用 Proxy 取代了 Object.defineProperty() **为什么要用 Proxy 取代了 Object.defineProperty() ?** **最核心的原因是性能**,展开来说如下。 - 因为 Proxy 代理的直接是整个对象,例如对象中有 100 个属性,用 defineProperty 劫持至少需要循环 100 次,而 proxy 至少一次搞定。 - defineProperty 对数组中存在大量元素的劫持操作性能不好,所以 Vue2 并没有直接使用 defineProperty 对数组进行劫持,而是提供了额外的方法来处理数组的响应式,例如 $set,其实换成 proxy 就不存在这个问题了,当然 $set 方法也就没有必要存在了。 - Vue2 中,劫持数据的操作在实例创建完成就已经进行完毕了,所以对对象后续新增的属性是劫持不到的,也就意味着后续新增的属性是不具备响应式能力的,所以 Vue 不得不提供了 $set 方法。而换成 proxy 就不存在这个问题了,因为它劫持的整个对象,后续添加的属性也是属于这个对象的,那么 $set 也就没有必要了(干掉 $set 方法本身也是性能的一个体现)。 **什么是Proxy?** Proxy 是 ES6 新增的一个特性,可以理解为,在目标对象之前架设一层"拦截",外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。 ## 77-你会在 Vue 的哪个生命周期钩子里发送请求,为什么? * 首先发送请求的时机肯定是越早越好,那么 beforeCreated 最早 * 但是还有一点,有时候请求前需要依赖 data 里面的数据或调用 methods 里面的方法(请求后可能也需要用) * 而 data 和 methods 都是需要实例创建完毕后(created)才具有的,所以一般我会在 created 里面发请求 *不过有时候这个请求需要依赖 DOM 相关操作的haul,我会选择在 mouted 里面进行,因为 mouted 阶段才能保证页面已经渲染完毕了* ## 78-数组去重 1、利用 ES6 新增的 set去重 ```js var arr = [1,1,8,8,12,12,15,15,16,16]; function unique (arr) { return Array.from(new Set(arr)) } console.log(unique(arr)) //[1,8,12,15,16] ``` 2、利用 for 嵌套 for,然后 splice 去重(ES5 中最常用的方法) ```js var arr = [1, 1, 8, 8, 12, 12, 15, 15, 16, 16]; function unlink(arr) { for (var i = 0; i < arr.length; i++) { // 首次遍历数组 for (var j = i + 1; j < arr.length; j++) { // 再次遍历数组 if (arr[i] == arr[j]) { // 判断连个值是否相等 arr.splice(j, 1); // 相等删除后者 j--; } } } return arr } console.log(unlink(arr)); ``` 3、利用 indexOf 去重 ```js var arr = [1, 1, 8, 8, 12, 12, 15, 15, 16, 16]; function unlink(arr) { if (!Array.isArray(arr)) { console.log('错误!') return } var array = []; for (var i = 0; i < arr.length; i++) { // 首次遍历数组 if (array.indexOf(arr[i]) === -1) { // 判断索引有没有等于 array.push(arr[i]) } } return array } console.log(unlink(arr)); ``` 4、利用 includes 去重 ```js var arr = [1, 1, 8, 8, 12, 12, 15, 15, 16, 16]; function unique(arr) { if (!Array.isArray(arr)) { console.log('type error!') return } var array =[]; for(var i = 0; i < arr.length; i++) { if( !array.includes( arr[i]) ) {//includes 检测数组是否有某个值 array.push(arr[i]); } } return array } console.log(unique(arr)) ``` 5、利用 filter 去重 ```js var arr = [1, 1, 8, 8, 12, 12, 15, 15, 16, 16]; function unlink(arr) { return arr.filter(function (item, index, arr) { //当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素 return arr.indexOf(item, 0) === index; }); } console.log(unlink(arr)); ``` ## 79-数组扁平化 1、扩展运算符实现 ```js let arr = [1, [2, [3, 4]]]; function flatten(arr) { while (arr.some(i => Array.isArray(i))) { arr = [].concat(...arr); } return arr; } console.log(flatten(arr)); //  [1, 2, 3, 4,5] ``` 2、Array.prototype.flat ```js let arr = [1, [2, [3, 4]]]; function flatten(arr) { return arr.flat(Infinity); } console.log(flatten(arr)); //  [1, 2, 3, 4,5] ``` 3、正则 + JSON 实现 ```js let arr = [1, [2, [3, [4, 5]]], 6]; function flatten(arr) { let str = JSON.stringify(arr); str = str.replace(/(\[|\])/g, ''); // 拼接最外层,变成JSON能解析的格式 str = '[' + str + ']'; return JSON.parse(str); } console.log(flatten(arr)); //  [1, 2, 3, 4,5] ``` 4、split + toString 实现 ```js let arr = [1, [2, [3, 4]]]; function flatten(arr) { return arr.toString().split(',').map(i=>Number(i)); } console.log(flatten(arr)); //  [1, 2, 3, 4] ``` 5、reduce实现 ```js let arr = [1, [2, [3, 4]]]; function flatten(arr) { return arr.reduce(function(pre, cur){ return pre.concat(Array.isArray(cur) ? flatten(cur) : cur) }, []) } console.log(flatten(arr));//  [1, 2, 3, 4,5] ``` 6、普通递归实现 ```js let arr = [1, [2, [3, 4, 5]]]; function flatten(arr) { let result = []; for(let i = 0; i < arr.length; i++) { // 当前元素是一个数组,对其进行递归展平 if(Array.isArray(arr[i])) { // 递归展平结果拼接到结果数组 result = result.concat(flatten(arr[i])); } // 否则直接加入结果数组 else { result.push(arr[i]); } } return result; } console.log(flatten(a)); //  [1, 2, 3, 4,5] ``` ## 80-GET和POST的区别 **数据传输方式:** get 通过 URL 传输数据(地址栏拼接),post 请求体传输; **数据安全:** get 数据暴露在 URL 中, 可通过浏览历史记录、缓存等很容易查到数据信息, post 数据因为在请求体内,所以有一定的安全性保证 **数据类型:** get 只允许 ASCLL 字符,post无限制 GET ⽆害 刷新、后退等浏览器操作是⽆害的; post 可能会引起重复提交表单 ## 81-图片懒加载 1、首先,不要将图片地址放到 src 属性中,而是放到其它属性中(data) 2、页面加载完毕后,根据 scrollTop 判断图片是否在用户的视野内,如果在,则将data-original属性中的值取出存放到src属性中 ## 82-iframe **是什么?** iframe 是嵌入式框架,是 html 标签,还是一个内联元素,iframe 元素会创建包含另外一个文档的内联框架(即行内框架),说白了,iframe 用来在页面嵌入其他页面 **优点:** 1、能够原封不动的把嵌入的网页展现出来 2、页面和程序分离,几乎不会受到外界任何 js 或者 css 的影响,便于使用 3、重新加载页面时,不需要重载 iframe 框架页的内容,增加页面重载速度 4、如果遇到加载缓慢的第三方内容如图标和广告,这些问题可以由 iframe来解决 **缺点:** 1、iframe会阻塞主页面的onload事件 2、很多的移动设备无法完全显示框架,设备兼容性差 3、iframe框架页面会增加服务器的http请求,对于大型网站是不可取的 4、iframe和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载。,会产生很多页面,不容易管理 5、代码复杂,无法被一些搜索引擎索引到,这一点很关键,现在的搜索引擎爬虫还不能很好的处理iframe中的内容,所以使用iframe会不利于搜索引擎优化(SEO) ## 83-ES6新特性 * let 和 const * symbol * 模板字符串 * 解构赋值 * Map 和 Set * 箭头函数 * class 关键字 * promise 和 proxy * 模块化 * 展开运算符