关于前端面试题总结
- vue
- vue双向数据绑定原理,又称vue响应式原理
- watch和watchEffect有什么区别?
- 谈一谈你对Mixin混入的理解?
- Vue 的 nextTick 的原理是什么?
- keep-alive的理解
- vue兄弟组件传值
- 路由传值的方式有哪几种
- v-if和v-show的区别
- v-if和v-for的优先级
- Vue 组件 data 为什么必须是函数
- vue中key值作用
- vuex的使用和解释
- vue-router中router和route的区别
- Vue中的常见指令有那些?
- vue单页面应用无刷新更新组件怎么实现的
- vue中diff算法的原理
- vue-router 有哪几种导航钩子?
- Vue实例的生命周期讲一下, mounted阶段真实DOM存在了嘛?
- vue 如何自定义指令?具体的 api 写法?
- webpack
- 小程序相关
- JS相关
- 数组有哪些常用方法,引出下一个问题,slice和splice区别
- instanceof原理
- new对象的过程
- 手写new
- 浏览器渲染机制
- 请描述一下 cookies sessionStorage和localstorage区别
- 什么是BFC,什么创建?有什么作用
- var let const 的区别?
- promise理解、手写promise
- Promise.all和Promise.race的区别,应用场景
- async await函数
- 微任务和宏任务的区别
- 作用域和作用域链
- 判断数据类型的方法有哪些,有什么区别
- 原型、原型链?
- this指向问题
- 改变this指向(call、apply、bind)
- 闭包、闭包的使用场景
- 节流和防抖
- 如何解决内存泄漏?
- 严格模式
- js继承方式有哪些?
- 数组遍历的方法和区别
- 在浏览器中输入地址之后发生了什么过程?
- 三次握手
- 回流和重绘
- JSONP的原理,以及为什么不是真正的 ajax
- ajax
- ajax和axios的区别
- websocket
- JavaScript的同源策略
- 前端项目性能优化方案有哪些
- http缓存机制
- 深拷贝和浅拷贝
- 简单说一下什么是堆什么是栈
- 事件委托
- css相关
- 手写代码
vue
vue双向数据绑定原理,又称vue响应式原理
是通过数据劫持结合发布者-订阅者模式的方式来实现的。其核心就是通过Object.defineProperty()方法设置set和get函数来实现数据的劫持,在数据变化时发布消息给订阅者,触发相应的监听回调。
watch和watchEffect有什么区别?
- 执行时机: watchEffect是立即执行的,在页面加载时会主动执行一次;而watch它不会立即执行,但可以配置 immediate,使其主动触发
- 参数不同: watchEffect只需要传递一个回调函数,不需要传递侦听的数据,它会在页面加载时主动执行一次,来收集依赖;而watch至少要有两个参数(第三个参数是配置项),第一个参数是侦听的数据,第二个参数是回调函数
- 结果不同: watchEffect获取不到更改前的值;而watch可以同时获取更改前和更改后的值
谈一谈你对Mixin混入的理解?
mixin就是将这些多个相同的逻辑抽离出来,各个组件只需要引入mixin,就能实现写一次代码,各个组件都能用且不会相互影响
- 在生命周期中: mixin的生命周期会先执行,其次执行组件的生命周期
- 有数据名或者方法名同名时: 会优先使用组件中的数据或方法名,mixin就会无效
原理分析:
- 优先递归处理mixins;
- 先遍历合并parent中的key,调用mergeField方法进行合并,然后保存在变量options;
- 再遍历child,合并补上parent中没有的key,调用mergeField方法进行合并,保存在变量options;
- 通过mergeField函数进行合并;
- 合并策略包括:替换型、合并型、队列型、叠加型;
- 替换行策略有:props、methods、inject、computed;
- 合并型策略是data,通过set方法进行合并和重新赋值,就是将新的同名参数替代旧的参数;
- 队列型策略有生命周期函数和watch,原理是将函数存入一个数据,然后正序遍历依次执行。
- 叠加型有component、directives、filters,通过原型链进行层层的叠加
Vue 的 nextTick 的原理是什么?
1为什么需要 nextTick
Vue 是异步修改 DOM 的并且不鼓励开发者直接接触 DOM,但有时候业务需要必须对数据更改--刷新后的 DOM 做相应的处理
这时候就可以使用 Vue.nextTick(callback)这个 api 了。
2.知识储备(可以不说,但是自己要知道,以防不测)
事件循环中宏任务和微任务这两个概念
常见的宏任务有 script, setTimeout, setInterval, setImmediate(一种执行更加频繁的定时器)
常见的微任务有 ,Promise.then(), async
3.最终答案:
nextTick 的原理是 vue 通过异步队列控制 DOM 更新
nextTick底层是promise,所以是微任务。这个一定要知道
keep-alive的理解
作用: 用来缓存组件内部的状态的,避免重复渲染
生命周期: 被keep-alive包含的组件中,会多出来两个生命周期:activated 与 deactivated。
- activated: 组件第一次渲染的时候会调用,以及每次激活的时候会调用
- activated调用时机: 第一次时会在mounted之后、beforeRouteEnter守卫传给 next 的回调函数之前调用,并且给因为组件被缓存了,再次进入缓存路由、组件时,不会触发这些钩子函数,beforeCreate created beforeMount mounted 都不会触发
- deactivated: 组件离开路由的时候会调用
vue兄弟组件传值
通过中间事件来进行传值,event bus 通过new vue来实现,然后$emit和 $on方式来传值
vuex
路由传值的方式有哪几种
v-if和v-show的区别
我是这么理解v-if和v-show的,其实v-if的本质就是动态创建和删除元素节点嘛,我一般遇到切换不频繁的话就用v-if,频繁的话就用v-show 因为v-if频繁切换的话会大量的创建和删除元素,会消耗性能。v-show的话就是控制css样式加了一个display: none;
v-if和v-for的优先级
1.在vue2中,v-for的优先级高于v-if,把它们放在同一个标签上时,页面每次渲染的时候都会重复的进行判断是十分消耗性能的
2.在vue3中,又恰好相反v-if的优先级是高于v-for的,同样vue3也不能把它们两者写在一起,正因为v-if优先于v-for,并且v-if又依赖v-for的数据源,在这个情况下将会出现报错的情况。
3.一般我们在处理v-if和v-for连用的场景时我们可采取下列方式:
(1)在循环渲染之前先用filter过滤下不需要的数据,也可以使用computed计算属性进行过滤
(2)如果需要根据条件渲染单个项,可以将 v-if 放在内层元素上。
疑问:将v-for和v-if写在同层级和v-for中嵌套v-if有什么区别呢?
回答:将 v-if 和 v-for 写在同一层级时,Vue 会在渲染组件时,先根据 v-for 遍历出所有的子元素,并将它们都生成对应的 Virtual DOM,然后再根据 v-if 条件判断是否需要将这些元素渲染出来。如果元素不符合条件,则将它们对应的 Virtual DOM 从渲染队列中移除。
将 v-if 放在内层元素上时,Vue 可以先根据 v-for 遍历出所有的子元素,但只有符合 v-if 条件的子元素会被渲染,不符合条件的子元素不会生成对应的 Virtual DOM。这样可以避免不必要的性能消耗,提高页面渲染速度。
Vue 组件 data 为什么必须是函数
- 因为组件是需要在多个地方使用的
- 如果data是一个对象,对象是引用类型。 一旦某一个地方修改,就会全部修改
- ata是一个函数,每一次复用组件的时候就会从这个函数返回一个新的对象。 这样组件在复用的时候就可以做到数据互不干扰。
vue中key值作用
vue在渲染的时候他会比较新的dom和旧的dom是否一致,如果结构一致的话呢vue会复用旧的dom,如果加key的话可以给dom添加一个唯一标识,让vue强制更新dom
vuex的使用和解释
里面有state用来保存数据的,mutations是用来修改state的状态的它里面有两个参数,一个是state还有一个是传入的新值,actions是用来进行异步操作的,modules就是属于把vue进行了一个模块化了提高了可维护性
mutations用this.$store.commit(),actions用this. $store.dispath()来操作数据
vue-router中router和route的区别
router是路由实例对象,他其中包含路由跳转的方法
route是路由信息对象,他包含路由相关的一些信息
Vue中的常见指令有那些?
v-text/v-html/v-for/v-show/v-if/v-else/v-bind/v-on/v-model
vue单页面应用无刷新更新组件怎么实现的
其实vue-router有两种模式hash模式和history模式
hash模式是v-router的默认模式,他不会访问服务器获取页面,他只会修改访问历史记录。
history模式和hash模式的区别在于history模式地址栏不带‘#’号
history模式在用户输入地址时会访问服务器
history还提供了两个方法,pushState和replaceState修改浏览器历史记录
以及popState事件监听状态变化
vue中diff算法的原理
就是在数据发生变化的时候,vue是先根据真实的dom生成一个虚拟dom,当虚拟dom某个节点的数据改变的时候会生成一个新的vnode,然后新的vnode和旧的vnode比较,如果有不一样的地方就直接修改真实的dom上,然后把旧VNode改成新的VNode来实现一个更新的
vue-router 有哪几种导航钩子?
1.全局导航钩子:router.beforeEach(to,from,next)作用:跳转前进行判断拦截、组件内的钩子、单独路由独享组件
2、路由独享钩子可以在路由配置上直接定义 beforeEnter
3、组件内的导航钩子有三种:
beforeRouteEnter 在进入当前组件对应的路由前调用
beforeRouteUpdate 在当前路由改变,但是该组件被复用时调用
beforeRouteLeave 在离开当前组件对应的路由前调用
Vue实例的生命周期讲一下, mounted阶段真实DOM存在了嘛?
Vue实例从创建到销毁的过程,就是生命周期。
也就是:开始创建->初始化数据->编译模板->挂载dom->数据更新重新渲染虚拟 dom->最后销毁。这一系列的过程就是vue的生命周期。所以在mounted阶段真实的DOM就已经存在了。
beforeCreate:vue实例的挂载元素el和数据对象data都还没有进行初始化,还是一个 undefined状态
created: 此时vue实例的数据对象data已经有了,可以访问里面的数据和方法, el还没有,也没有挂载dom
beforeMount: 在这里vue实例的元素el和数据对象都有了,只不过在挂载之前还是虚拟的dom节点
mounted: vue实例已经挂在到真实的dom上,可以通过对 dom操作来获取dom节点
beforeUpdate: 响应式数据更新时调用,发生在虚拟dom打补丁之前,适合在更新之前访问现有的 dom,比如手动移除已添加的事件监听器
updated: 虚拟dom重新渲染和打补丁之后调用,组成新的 dom已经更新,避免在这个钩子函数中操作数据,防止死循环。
activated: 当组件keep-alive激活时被调用
deactivated:当组件keep-alive停用时被调用
beforeDestroy: vue实例在销毁前调用,在这里还可以使用,通过this也能访问到实例,可以在这里对一些不用的定时器进行清除,解绑事件。
destroyed:vue实例销毁后调用,调用后所有事件监听器会被移除,所有的子实例都会被销毁。
vue 如何自定义指令?具体的 api 写法?
自定义指令directive方法里面有两个参数,一个是指令的名称,一个是函数,里面的钩子函数有bind、inserted(节点插入的时候触发)、updated(更新时触发)
Vue.directive('focus', {
//第一个参数永远是el,表示原生的js对象
bind: function (el) { //当指令绑定到元素上的时候,会立即执行bind函数,只执行一次,此时元素还没有插入到DOM中,focus聚焦此时不会生效
el.focus()
},
inserted: function (el) { //当元素插入到DOM中的时候,会执行inserted函数,只执行一次
el.focus()
},
updated: function () { //当VNode的时候,会执行updated函数,可能出发多次
}
});
<input type="text" v-focus />
webpack
1、webpack的作用是什么,谈谈你对它的理解?
现在的前端网页功能丰富,特别是SPA(single page web application 单页应用)技术流行后,JavaScript的复杂度增加和需要一大堆依赖包,还需要解决Scss,Less……新增样式的扩展写法的编译工作。
所以现代化的前端已经完全依赖于webpack的辅助了。
现在最流行的三个前端框架,可以说和webpack已经紧密相连,框架官方都推出了和自身框架依赖的webpack构建工具。
react.js+WebPack
vue.js+WebPack
AngluarJS+WebPack
2、webpack的工作原理?
WebPack可以看做是模块打包机:它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Sass,TypeScript等),并将其转换和打包为合适的格式供浏览器使用。在3.0出现后,Webpack还肩负起了优化项目的责任。
3、webpack打包原理
把一切都视为模块:不管是 css、JS、Image 还是 html 都可以互相引用,通过定义 entry.js,对所有依赖的文件进行跟踪,将各个模块通过 loader 和 plugins 处理,然后打包在一起。
按需加载:打包过程中 Webpack 通过 Code Splitting 功能将文件分为多个 chunks,还可以将重复的部分单独提取出来作为 commonChunk,从而实现按需加载。把所有依赖打包成一个 bundle.js 文件,通过代码分割成单元片段并按需加载
4、webpack的核心概念
Entry:入口,Webpack 执行构建的第一步将从 Entry 开始,可抽象成输入。告诉webpack要使用哪个模块作为构建项目的起点,默认为./src/index.js
output :出口,告诉webpack在哪里输出它打包好的代码以及如何命名,默认为./dist
Module:模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。
Chunk:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。
Loader:模块转换器,用于把模块原内容按照需求转换成新内容。
Plugin:扩展插件,在 Webpack 构建流程中的特定时机会广播出对应的事件,插件可以监听这些事件的发生,在特定时机做对应的事情。
5、Webpack的基本功能有哪些?
代码转换:TypeScript 编译成 JavaScript、SCSS 编译成 CSS 等等
文件优化:压缩 JavaScript、CSS、html 代码,压缩合并图片等
代码分割:提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载
模块合并:在采用模块化的项目有很多模块和文件,需要构建功能把模块分类合并成一个文件
自动刷新:监听本地源代码的变化,自动构建,刷新浏览器
代码校验:在代码被提交到仓库前需要检测代码是否符合规范,以及单元测试是否通过
自动发布:更新完代码后,自动构建出线上发布代码并传输给发布系统。
6、gulp/grunt 与 webpack的区别是什么?
三者都是前端构建工具,grunt和gulp在早期比较流行,现在webpack相对来说比较主流,不过一些轻量化的任务还是会用gulp来处理,比如单独打包CSS文件等。grunt和gulp是基于任务和流(Task、Stream)的。
类似jQuery,找到一个(或一类)文件,对其做一系列链式操作,更新流上的数据, 整条链式操作构成了一个任务,多个任务就构成了整个web的构建流程。webpack是基于入口的。
webpack会自动地递归解析入口所需要加载的所有资源文件,然后用不同的Loader来处理不同的文件,用Plugin来扩展webpack功能。
7、webpack是解决什么问题而生的?
如果像以前开发时一个html文件可能会引用十几个js文件,而且顺序还不能乱,因为它们存在依赖关系,同时对于ES6+等新的语法,less, sass等CSS预处理都不能很好的解决……,此时就需要一个处理这些问题的工具。
小程序相关
wxs与wx js有什么区别
wxs可以在wxml中使用,并且可以在wxml中调用
书写格式:
<wxs module="math" src="utils/math.wxs"></wxs>
<view>{{ math.sum(1, 2) }}</view>
JS相关
数组有哪些常用方法,引出下一个问题,slice和splice区别
push, pop, shift unshift
1.splice改变原数组,slice不改变原数组。2.splice除了可以删除之外,还可以插入。3.splice可传入3个参数,slice接受2个参数。
instanceof原理
a instanceof B
检测a的原型链(_ proto _)上有没有b的prototype,有就返回true,没有false
new对象的过程
1.创建一个空对象
2.将空对象的原型指向原来的对象原型
3.为新对象绑定新的this改变this指向
4.判断结果是否为null,如果是null的话就返回新对象,如果不是就返回执行结果
手写new
//定义构造函数
function Fun(age, name) {
this.age = age
this.name = name
return 1
}
function myNew(fn, ...args) {
//1、先创造空对象
//其实等于var obj = Object.create({})
var obj = {}
//2、obj的__proto__指向原型
Object.setPrototypeOf(obj, fn.prototype)
//3、改变this指向,执行构造函数内部函数
var result = fn.apply(obj, args)
//4、判断return
return result instanceof Object ? result : obj
}
浏览器渲染机制
首先html代码会转化成dom
然后css代码转化成cssom(CSS Object Model)
然后结合dom和cssom生成一个渲染树
生成布局最后绘制在屏幕上
请描述一下 cookies sessionStorage和localstorage区别
他们区别在于存储大小不一样
cookies只能存储4k,sessionStorage和localstorage可以存储5兆甚至更多
还有存储时间不一样
localstorage是关闭浏览器也会存在,除非手动删除
sessionStorage关闭浏览器就会自动删除
cookies是根据设置的过期时间存储
什么是BFC,什么创建?有什么作用
BFC其实就是块级格式化上下文,让元素成为一个独立的空间不受外界影响也不影响外面布局
如何创建:给父级元素添加样式:
overflow:hidden
display:flex
position: absolute;
position: fixed;
作用:
其实就是解决当父级元素没有高度的时候,子元素浮动会使父元素高度塌陷的问题
var let const 的区别?
var可以重复声明,全局作用域,有变量提升
let和const是块级作用域,没有变量提升
const不能被赋值
promise理解、手写promise
Promise是异步编程的一种解决方案,它是一个对象,可以获取异步操作的消息,避免了地狱回调
Promise有三种状态: pending(等待态),fulfilled(成功态),rejected(失败态) ;状态一旦改变,就不会再变。创造promise实例后,它会立即执行。
Promise拥有一个then方法,用以处理resolved或rejected状态下的值
then方法接收两个函数作为参数,第一个参数是Promise执行成功时的回调,第二个参数是Promise执行失败时的回调。
then方法返回一个新的Promise对象,因此可以通过链式调用then方法
function xxx(onResolved) {
// 这里叫做promise2
return new Promise((resolve,inject) => {
//计时器模拟请求
setTimeout(()=>{
if(){
resolve(res)
} else{
inject(err)
}
})
});
};
xx.then(fun,fun)
Promise.all和Promise.race的区别,应用场景
all()方法可以完成并行任务, 它接收一个数组,数组的每一项都是一个 promise对象。当数组中所有的 promise的状态都达到 resolved的时候,all方法的状态就会变成 resolved,如果有一个状态变成了 rejected,那么 all方法的状态就会变成 rejected。
race() 接受的参数也是一个每项都是 promise的数组,当最先执行完的事件执行完之后,就直接返回该 promise对象的值。如果第一个 promise对象状态变成 resolved,那自身的状态变成了resolved;反之第一个 promise变成 rejected,那自身状态就会变成 rejected。
finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。
async await函数
async/await函数是异步代码的新方式
async/await是基于promise实现的
async/await使异步代码更像同步代码
await 只能在async函数中使用,不能再普通函数中使用,要成对出现
默认返回一个promise实例,不能被改变
await下面的代码是异步,后面的代码是同步的
微任务和宏任务的区别
宏任务和微任务是事件循环的两种类型
宏任务一般是处理相对较大的任务,比如定时器,永久定时器、网络请求等
微任务是处理较小的任务
微任务执行时机是比宏任务高的
微任务:process.nextTick、MutationObserver、Promise.then catch finally
宏任务:setTimeout、setInterval、setImmediate、requestAnimationFrame
作用域和作用域链
作用域就是变量和函数起的作用范围
作用域链就是通过js查找的方式变量和函数向上查找的范围
判断数据类型的方法有哪些,有什么区别
-
typeof:一般判断基本数据类型
-
instanceof :一般判断引用数据类型,主要的作用就是判断一个实例是否属于某种类型,或者判断一个实例是否是其父类型或者祖先类型的实例。(原型链知识)
-
constructor:通过原型链继承属性判断。null和undefined是无效的对象,JS对象的constructor是不稳定的,这个主要体现在自定义对象上,当开发者重写prototype后,原有的constructor会丢失,constructor会默认为Object。
-
Object.prototype.toString.call():较为准确的判断方法。toString是Object原型对象上的一个方法,该方法默认返回其调用者的具体类型。
原型、原型链?
原型:每个对象中都会有一个固定属性:_ proto__,这个属性指向对象的prototype原型属性
原型链:当访问一个对象的属性的时候,如果这个对象不存在这个属性,那么他就会去他的原型对象上去找,一直找下去,直到找到null为止
this指向问题
- 当this出现在全局函数的时候,他指向Window
- 当一个函数为对象的属性的时候,他指向这个对象
- 当元素绑定事件的时候他会指向被点击的元素
改变this指向(call、apply、bind)
1、apply 、 call 、bind 三者都是用来改变函数的this对象的指向的;
2、apply 、 call 、bind 三者第一个参数都是this要指向的对象,也就是想指定的上下文;
3、apply 、 call 两者都可以利用后续参数传参; 但是传参的方式不一样,apply是数组,call是正常传参形式
4、bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用。
闭包、闭包的使用场景
闭包就是内部函数可以访问外部函数的变量,他的特点就是函数内再嵌套函数,参数和变量不会被垃圾回收机制回收
缺点:可能会会导致内存泄漏
最常用的场景就是节流和防抖
节流和防抖
防抖
debounce(function () {
console.log(..)
}, 2000)
debounce(fn, delay) {
let timer = null;
return function () {
if ( timer !== null ) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.call(this);
}, delay);
}
}
节流
const throttle=(func, delay) => {
// 缓存一个定时器
let timer = null
// 这里返回的函数是每次用户实际调用的节流函数
return function(...args) {
if (!timer) { //判断timer是否有值,如果没有则说明定时器不存在即可继续执行
timer = setTimeout(() => { //关
func.apply(this, arguments)
timer = null; //开
}, delay)
}
}
}
export default throttle
函数防抖:在多次触发事件时,只触发最后一次
函数节流:在多次触发事件时,会减少触发的频率
如何解决内存泄漏?
- 避免创建全局的变量
- 用定时器时记得清除
- 移除事件监听
- 使用严格模式
严格模式
- 变量需要定义
- 禁止使用with语句
- 禁止this指向全局变量
js继承方式有哪些?
- 原型链继承 核心: 将父类的实例作为子类的原型
- class类继承
- 借用构造函数(call):在子类构造函数中使用 call() 或 apply() 方法调用父类构造函数,并将子类实例作为参数传递给父类构造函数
- 组合继承(组合原型链继承和借用构造函数继承):可以继承父类原型上的属性,可以传参,可复用
数组遍历的方法和区别
-
forEach:遍历开始以后无法停止,如果要遍历整个数组,那就使用这个方法;
-
map:根据当前数组映射出一个新的数组;
-
some:遍历整个数组,返回值true就停止循环(返回false继续循环)
返回值:如果数组中的有一项回调函数返回true,那么结果为true,否则为false;(或者这样理解:数组别遍历完,那么结果为false,否则为true)
-
every:与some相反,返回false就停止循环(返回true就继续循环)
-
filter:过滤数组,返回一个新的数组
在浏览器中输入地址之后发生了什么过程?
- 域名解析,他会先从浏览器缓存里面找,如果找不到就会使用服务器来解析域名
- 建立TCP连接 三次握手
- 发出请求
- 响应请求
- 解析html
- 呈现页面
三次握手
- 第一次握手:客户端向服务器发送SYN同步报文段,请求建立连接
- 第二次握手:服务器确认收到客户端的连接请求,并向客户端发送SYN同步报文,表示要向客户端建立连接
- 第三次握手:客户端收到服务器端的确认请求后,处于建立连接状态,向服务器发送确认报文,客户端是在收到确认请求后,先建立连接,服务器是在收到最后客户端的确认后,建立连接,发起连接请求的一定是客户端
回流和重绘
回流就是因为元素的尺寸位置发生了变化需要重新排列
重绘就是元素更新了外观,不影响布局
什么时候发生重绘?
背景色改变
样式发生改变的时候
区别
回流必定会引起重绘,重绘一定不会引起回流
JSONP的原理,以及为什么不是真正的 ajax
Jsonp是通过动态创建 script 标签,然后通过标签的 src 属性获取 js 文件 中的 js 脚本,该脚本的内容是一个函数调用,参数就是服务器返回的数据,为了处理这些 返回的数据,需要事先在页面定义好回调函数,本质上使用的并不是 ajax 技术
ajax
原理: Ajax就是通过XmlHttpRequest对象来向服务器发送异步请求,从服务器获得数据,然后用js来操作DOM而更新页面
作用: ajax用来与后台交互
常用的请求方式:post、get、put
- XMLHttpRequest是ajax的核心机制,是一种支持异步请求的技术。就是js可以及时向服务器提出请求和处理响应,而不阻塞用户。达到无刷新的效果。
实现步骤
//创建ajax对象
var xhr = new XMLHttpRequest();
//告诉 Ajax 请求地址以及请求方式
xhr.open('get', 'http://www.example.com');
//发送请求
xhr.send();
//获取服务器端给与客户端的响应数据
xhr.onload = function () {
console.log(xhr.responseText);
}
ajax和axios的区别
Axios和Ajax都是用于浏览器和服务器之间进行异步数据交换的技术
Axios是一个基于Promise的HTTP库,而Ajax是对原生XHR的封装
import axios from 'axios';
axios.get('/api/data')
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error);
websocket
其实是HTML5一种新的协议
特点 : 服务器可以主动向客户端推送消息,客户端也可以主动向服务器发送信息
http和websocket的区别 http协议是短链接,因为请求之后,都会关闭连接,下次请求需要重新打开链接。websocket协议是一种长连接,只需要通过一次请求来初始化连接,然后所有请求和响应都是通过TCP链接进行通信。
JavaScript的同源策略
协议(protocol)相同: 同为https或http
端口相同: 如未指定,http默认为80端口,https默认为443端口
域名相同
前端项目性能优化方案有哪些
精灵图合并,减少HTTP请求;压缩HTML、CSS、JavaScript文件;
http缓存机制
HTTP 缓存主要由 强缓存(强制缓存)和协商缓存
强制缓存在规定时间内有效,可直接使用,且状态码为 200,若已失效则执行协商缓存
协商缓存利用发送给服务器特定字段,由服务器对字段进行校验,来确定是否使用缓存,可使用则状态码为 304
深拷贝和浅拷贝
浅拷贝: 拷贝了一个对象,但是只拷贝的对象的第一层,当对象的属性是引用类型,拷贝的这个属性,依旧是拷贝的地址引用。
深拷贝: 深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。
let obj = {
name: 'lwf',
color: ['红色', '蓝色']
}
let o = {}
//浅拷贝
copy = (newObj, oldObj) => {
for (let k in oldObj) {
newObj[k] = oldObj[k]
}
}
//深拷贝
deepCopy = (newObj, oldObj) => {
//遍历旧的对象
for (let k in oldObj) {
//判断k的值是不是数组,数组先判断是因为数组也是Object类型
if (oldObj[k] instanceof Array) {
newObj[k] = []
deepCopy(newObj[k], oldObj[k])
} else if (oldObj[k] instanceof Object) { //判断是不是对象
newObj[k] = {}
deepCopy(newObj[k], oldObj[k])
} else {
newObj[k] = oldObj[k]
}
}
}
deepCopy(o, obj)
copy(o, obj)
o.name = 'cll'
o.color.push('绿色')
console.log(o);
console.log(obj);
简单说一下什么是堆什么是栈
- 堆和栈都是操作系统自带的一个内存空间;
- 栈是有系统自动分配的内存空间,栈空间里面存储的是可以直接 访问的;
- 堆是后期程序员可以开辟扩大的,一般由程序员主动分配才释放;
- 栈优点:速度快;堆优点:内存大;
事件委托
事件委托其实就是把子元素的事件变更到父元素上,其实它的原理就是通过事件冒泡实现的,事件冒泡是从事件源到根节点,从内向外进行传播,如果想要阻止冒泡的话e.stopPropagation()
css相关
解决1px像素问题
原因: 2 倍图的 1px 其实要实现的是 0.5px,直接写 1px 会偏粗,但写 0.5 又会有兼容性问题
1.box-shadow(向元素添加阴影)
-webkit-box-shadow:0 1px 1px -1px rgba(255, 0, 0, 0.5);
box-shadow:0 1px 1px -1px rgba(255, 0, 0, 0.5);
2.伪元素+transform
.scale-1px{
position: relative;
border:none;
}
.scale-1px:after{
content: '';
position: absolute;
bottom: 0;
background: #000;
width: 100%;
height: 1px;
/*核心是利用transform缩放边框*/
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
}
3.transform:scale(0.5)(旋转元素)
div {
height: 1px;
background: #000;
transform: scaleY(0.5);
transform-origin: 0 0;
}
Css实现单行文本、多行文本、截断
单行文本
div {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
什么叫优雅降级和渐进增强?
渐进增强: 一开始针对低版本的浏览器构建页面,然后向上兼容
优雅降级: 一开始针对高版本的浏览器构建页面,向下兼容
行内元素有哪些?块级元素有哪些? 空(void)元素有那些?行内元素和块级元素有什么区别?
行内元素: span、a、label、img、input、select、b、strong
块级元素: div、ul、ol、h1-h6、p、tabel、from、ol
空元素: br、img、hr、input、link、meta
区别:
块级元素会独占一行,宽度会自动填满父元素,相邻的行内元素会排列到一行,直到排不下才会换行,宽度随元素的内容而变化
块级元素可以设置宽高,行内元素设置宽高无效
块级元素可以设置margin、padding,行内元素margin、padding水平方向有效,垂直方向无效
媒体查询
@media screen and (max-width:1200px) { … } /* 中型设备(台式电脑,1200px 以下) /
小屏幕 768px-992px
@media screen and (max-width:992px){ … }/ 小型设备(平板电脑,992px 以下) /
超小屏幕 768px屏幕以下
@media screen and (max-width:768px){ … }/ 超小型设备(手机,768px 以下) */
手写代码
数组扁平化/拍平
方式1:使用基础的递归遍历
function flatten(arr) {
let result = [];
// 此处也可使用for...of遍历
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;
}
方式2:使用toString方法,再用split转为数组,再map成数字
var arr = [1,2,3,[4,5], [6,7,[8,9,[10,11]]]]
var newArr = arr.toString().split(',')
// arr.toString()结果为'1,2,3,4,5,6,7,8,9,10,11'
var resArr = newArr.map(item => Number(item))
console.log(resArr) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
方式3:直接使用ES6的flat方法
//falt()方法会按照指定的深度递归遍历数组,arr.flat([depth]),参数depth不填时默认值为1
//depth为Infinity表示展开任意深度的嵌套数组。
function flatten(arr) {
return arr.flat(Infinity);
}
手写ajax原始请求
//1.创建 XMLHttpRequest 对象
var xhr = new XMLHttpRequest();
//2.规定请求的类型、URL 以及是否异步处理请求。
xhr.open('GET',url,true);
//3.发送信息至服务器时内容编码类型
//设置响应头
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
//4.发送请求
xhr.send(null);
//5.接受服务器响应数据
xhr.onreadystatechange = function () {
if(xhr.readyState ===4){
//判断响应状态码 200 404 403 401 500
//2xx 成功
if(xhr.status >= 200 && xhr.status < 300){
}else{
}
}
}
判断字符串回文
function checkStr(str) {
return str === str.split('').reverse().join('')
}
命名方式中划线改小驼峰
function transName(arr) {
let res = arr.map(e => {
let items = e.split('-').map((item, index) => {
if (index) {
let first = item.substring(0,1)
let rest = item.substring(1)
return first.toUpperCase()+rest
}else{
return item.toLowerCase()
}
})
return items.join('')
})
return res
}
数组去重
const res = Array.from(new Set(arr));
function unique1 (arr) {
let newArray = []
for (let i = 0; i < arr.length; i++) {
if (newArray.indexOf(arr[i]) === -1) {
newArray.push(arr[i])
}
}
return newArray
}
实现日期格式化函数
dateFormat(new Date('2020-12-01'), 'yyyy/MM/dd') // 2020/12/01
dateFormat(new Date('2020-04-01'), 'yyyy/MM/dd') // 2020/04/01
dateFormat(new Date('2020-04-01'), 'yyyy年MM月dd日') // 2020年04月01日
const dateFormat = (dateInput, format) => {
var day = dateInput.getDate()
var month = dateInput.getMonth() + 1
var year = dateInput.getFullYear()
format = format.replace(/yyyy/, year)
format = format.replace(/MM/, month)
format = format.replace(/dd/, day)
return format
}
将js对象转化为树形结构
// 转换前:
source = [{id: 1,pid: 0,name: 'body'}, {id: 2,pid: 1,name: 'title'}, {id: 3,pid: 2,name: 'div'}]
// 转换为:
tree = [{id: 1,pid: 0,name: 'body',children: [{id: 2,pid: 1,name: 'title',children: [{id: 3,pid: 1,name: 'div'}]}}]
function jsonToTree(data) {
if (!Array.isArray(data)) {
return result
}
// 1. result为一个新的大厅,来存放已经组好队的家庭
let result = []
// 2.使用map,将当前对象的id与当前对象对应存储起来
// PS:相当于我们在大厅喊话,只用一次就可以找到父亲,也就是O(1)
let map = {}
data.forEach(item => {
map[item.id] = item
})
// 3.询问每一个人,它的父亲是谁
data.forEach(item => {
let parent = map[item.pid]
// 如果有父亲,就把父亲喊出来,把孩子交给父亲
if (parent) {
(parent.children || (parent.children = [])).push(item)
}
else {
// 如果没有父亲,说明这个人就是父亲,直接把他带到排好序的大厅
result.push(item)
}
})
// 新的大厅为已经排好的家庭
return result
}
console.log(jsonToTree(source))