css部分
- 居中布局:居中布局就是既要水平方向居中,又要垂直方向居中
- table + margin实现水平方向居中,table-cell + vertical-align实现垂直方向居中
<div class = 'parent'> <div class = 'child' >z先森</div> </div>
.parent{ display:table-cell; vertical-align:middle; } .child{ display:table; margin: 0 auto; }
- 优点:浏览器的兼容性比较好;
- 缺点:vertical-align属性具有继承性,导致父元素的文本也是居中显示的;
- absolute + transform/margin 实现水平方向和垂直方向居中
<div class = 'parent'> <div class = 'child' >z先森</div> </div>
.parent{ position:relative; } .child{ position:absolute; top:50%; left:50%; transform:translate(-50%,-50%) }
.parent{ position:relative; } .child{ position:absolute; top:50%; left:50%; margin-top:-50%盒子高度; margin-left:-50%盒子宽度; }
- 优点:父级元素是否脱离文档流,不影响子级元素的垂直居中效果。
- 缺点:transform属性是css3中新增的属性,浏览器支持不太好
- flex + justify-content + align-items实现水平和垂直方向居中
<div class = 'parent'> <div class = 'child' >z先森</div> </div>
.parent{ display:flex; justify-content:center; align-items:center; }
- 优点:简单灵活,功能强大
- 缺点:PC兼容不太好,移动端的话需要Android4.0+
- BFC:Block Formatting Content块格式化上下文,形成一个独立的容器,容器里面的盒子和外面的盒子互不干扰,
- 触发BFC的条件:
- ①浮动元素:float除去none以外的值
- ②定位:position:absolute|fixed
- ③display:inline|table-cell|flex|inline-flex(决定了盒子的属性)
- ④overflow除visible以外的值
- BFC块格式上下文的特性
- 内部的盒子在垂直方向上会一个接一个的排列
- 处于同一个BFC的元素会发生margin collapse
- 从左向右格式化的第一个元素的margin-box的左边与容器块的border-box的左边接触,浮动元素也是一样。
- 形成的BFC盒子里面的元素和外面的元素互不干扰,没有任何关系。
- 计算BFC高度的时候,要加上浮动元素的高度。
- 浮动盒区域不叠加到BFC上
- 解决问题
- 父元素触发BFC,浮动的元素撑开父元素的高度
- 形成独立的容器,解决margin-collapse问题
- 解决margin-top的传递问题:给子容器加margin-top,不但没有跟父容器产生间距,反而让父容器整体下移。
- 触发BFC的条件:
- 四个定位
- **static: **静态定位,盒子的位置是常规流布局的位置
- **relative: **相对定位,生成相对定位的元素,相对于其正常位置进行定位。通过top、bottom、left、right来控制盒子的位置,即使有偏移也会占有原来的位置,其他常规流不可以占用这个位置。
- **absolute:**绝对定位,盒子从常规流移除,盒子的定位相对于包含他的最近的一个有position属性的元素,利用top、left即可。
- **fixed :**固定定位,该元素相对于浏览器窗口进行定位。固定定位的元素不会随浏览器窗口的滚动条滚动而变化,也不会受文档流动影响,而是始终位于浏览器窗口内视图的某个位置。
- **如何清除浮动 :**浮动元素所导致的问题:会导致父元素的高度塌陷(height collapse),和后面的元素发生重叠
- 给父元素加适当的高度;比较死板;
- 给父元素触发BFC,这样会把浮动元素的高度计算进去,如果用了
overflow:hidden
,这个属性是溢出隐藏,那么在设置阴影的时候不起作用。 - 伪元素
<style> .clearfix::after{ content:" "; display:block; clear:both; width:0; height:0; font-size:0; overflow:hidden; } </style>
- 在浮动元素之后,父容器结束标签之前,加一个任意的块级标签,并且加
style='clear:both;'
- 盒子模型
- 正常盒子模型:
box-sizing:content-box
- 在浏览器显示的实际宽度:width + padding-left/right + border-left/right
- 在浏览器显示的实际高度:height + padding-top/bottom + border-top/bottom
- 怪异盒子模型:
box-sizing:border-box
- 在浏览器显示的实际宽度:width(已经包含了左右内填充和左右边框)
- 在浏览器显示的实际高度:height(已经包含了上下内填充和上下边框)
- 正常盒子模型:
- 文字超出以省略号显示
{ width:100px; text-overflow:ellipsis; white-space:nowrap; overflow:hidden; }
- **css3新增属性以及常规用法**
- **border-radius:左上角 右上角 右下角 左下角**
- **box-shadow:①x轴的偏移量②y轴的偏移量③阴影的模糊半径④阴影的扩展半径⑤阴影的颜色⑥内阴影**
- **text-shadow:X轴 Y轴 模糊半径 颜色**
- **background-size:x y (背景尺寸)|cover(裁剪)|contain(留白)**
- **background-clip:text 以文字向外进行裁剪保留文字的轮廓背景|border-box|padding-box|content-box**
- **linear-gradient(方位角度|颜色 颜色):线性渐变**
- **radial-gradient(直接放颜色):径向渐变**
- **css选择器的优先级**
- !important(尽量不要使用)
- 内联样式
- ID选择器
- 类选择器|属性选择器|伪类选择器
- 元素选择器
- 通配符选择器
- 继承
- 浏览器默认属性
## JS部分
- 实现数组去重
- **双for循环(冒泡)** 时间复杂度是O(n²)
for(let i = 0;i<arr.length;i++){
let item = arr[i];
for(let j = i + 1;j < arr.length;j++){
if(item === arr[j]){
arr[j] = arr[arr.length-1];
arr.length--;
j--;
}
}
}
- **单for循环** 时间复杂度为O(n) 空间换时间
let obj = {}; for(let i=0;i<arr.length;i++){ let item = arr[i]; if(obj[item] !== undefined){ arr[i] = arr[arr.length - 1]; arr.length--; i--; } obj[item] = item }
- **set方法**
let ary = [1, 2, 2, 3, 4, 2, 1, 3, 1, 3]; ary = Array.from(new Set(ary)); console.log(ary);
- **indexOf**
let ary = []; for(var i = 0;i<arr.length;i++){ if(ary.indexOf(arr[i]) === -1){ ary.push(arr[i]) } } return ary;
- **sort排序方法**
arr = arr.sort(); let ary = [arr[0]]; for(var i = 1;i< arr.length;i++){ if(arr[i]!==arr[i-1]){ ary.push(arr[i]) } } return ary;
- **includes**
let ary = []; for(var i = 0;i<arr.length;i++){ if(!ary.includes(arr[i])){ ary.push(arr[i]) } }
- **reduce + includes**
arr.reduce((prev , cur )=>{ return prev.includes(cur)?prev:[...prev,cur] },[])
- **深克隆**
function cloneDeep(obj){ //基本数据类型 + symbol + function也不用处理 if(typeof obj === null ) return null; if(typeof obj !== 'object' ) return obj;
//对特殊的对象进行深克隆
if(obj instanceof Date) return new Date(obj);
if(obj instanceof RegExp) return new RegExp(obj);
//新开辟一个堆内存
let cloneObj = new obj.constructor;
for(let key in obj){
if(!obj.hasOwnProperty(key)) break;
cloneObj[key] = cloneDeep(obj[key])
}
return cloneObj
}
- **for in|for of|for循环**
- **for循环**
- ```
for(let i = 0,len = arr.length; i < len ;i++){...}
```
- 取值和判断合并,并通过不断的枚举每一项来循环,直到枚举到空值。
**存在问题:**如果当前数组里面存在非Truthy值时,会导致直接结束
```
for(let i = 0,item ; item = arr[i] ;i++){...}
```
- **倒序**
```
for(let i = arr.length;i--){...}
```
- **for in:**专门为循环可枚举对象而设计的。
- 在使用for in的时候,先检测确认该对象的值不是null或undefined。
- for in 会遍历出在原型上新增的属性和方法。
- **Object.hasOwnProperty(prop)** 用来判断某个对象是否含有指定的属性的,返回值为Boolean ,该方法会忽略掉那些从原型链上继承到的属性。
- **for of:**ES6新增的一个方法,①用于解决**for-in用来遍历数组出现的顺序可能随机的问题**。②**forEach不能break和return**③**还支持类数组**④**也支持Map和Set**⑤**for-of不遍历普通对象**
- **for循环和while循环和for of循环 可以通过break等关键字控制循环**
- **实现一个正六边形**
六边形
body {
height: 100%;
width: 100%;
}
.w-comb {
width: 174px;
height: 100px;
background-color: #e4e4e4;
display: inline-block;
position: absolute;
top: 50%;
left: 50%;
/* transform: translate(-50%, -50%); */
margin-top: -50px;
margin-left: -87px;
}
.w-comb1,
.w-comb2 {
background-color: #e4e4e4;
position: absolute;
width: inherit;
height: inherit;
}
.w-comb1 {
transform: rotate(-60deg);
}
.w-comb2 {
transform: rotate(60deg);
}
</style>
let debounce = function (func,wait,immediate){
//result 用来存储函数体执行的返回结果
//timeout 记录定时器
let result = null,
timeout = null;
return function (...args){
//now 控制函数体是否在触发事件首次执行还是等间隔一定的时间
let content = this,
now = immediate&&!timeout;
//清除上一次定时器
clearTimeout(timeout);
timeout = setTimeout(()=>{
timeout = null;
if(!immediate) result = func.apply(content,args)
}.wait);
if(now) result = func.apply(content,args);
return result;
}
}
- **节流:**设定一个频率,在这个频率内只执行一次。不管你触发了多少次,在这个时间频率内只执行一次。
- @params
- func:需要执行的函数体
- wait:设定的频率
- @return
- 返回可被调用的函数体
let throttle = function (func , wait){ //previous记录上一次执行的时间 let timeout = null, result = null, previous = 0; return function (...args){ let now = new Date, content = this; //remainting记录时间的间隔 let remaining = wait - (now - previous); if(remaining <=0){ cleraTimeout(timeout); previous = now; timeout = null; result = func.apply(content , args); }else if(!timeout){ timeout = setTimeout(()=>{ previous = new Date; timeout = null; result = func.apply(content, args) },remainting) } return result; } }
- @params
- JQ中数据类型检测
function toType(obj){ let typeObj = {}; ["Boolean","String","Number","Function","Array","Date","Error","Symbol","Object"].forEach(item=>{ typeObj["[object" + item + "]"] = item.toLowerCase(); }); if(obj == null) return obj + ""; return typeof obj === "object" || typeof obj === "function" ? typeObj[typeObj.toString.call(obj)] || "object" : typeof obj }
- 继承
- **原型继承:**让子类的原型等于父类的实例
function Parent(){ this.x = 100; } function Child(){ this.y = 200; } Child.prototype = new Parent; Child.prototype.getY = function getY(){ return this.y }
- **call继承:**在子类的构造函数中让父类的方法执行,只把父类本身的属性和方法给到子类实例。
function Parent(){ this.x = 100; } Parent.prototype.getX = function getX() { return this.x; }; function Child(){ Parent.call(this); this.y = 300; } Child.prototype.getY = function getY() { return this.y; }; let c1 = new Child; console.log(c1);
- 寄生组合式继承
function Parent(){ this.x = 100; } function Child() { Parent.call(this); this.y = 200; } Child.prototype = Object.create(Parent.prototype); Child.prototype.constructor = Child;
- 数组排序
- 冒泡排序
// 交换位置函数
function swap(arr, i, j) { let temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; return arr; } Array.prototype.bubble = function bubble() { // 外层循环I控制比较的轮数 for (let i = 0; i < this.length - 1; i++) { // 里层循环控制每一轮比较的次数J for (let j = 0; j < this.length - 1 - i; j++) { if (this[j] > this[j + 1]) { // 当前项大于后一项,交换位置 swap(this,j,j+1); } } } return this; } let ary = [12, 8, 24, 16, 1]; ary.bubble(); console.log(ary);
- **选择排序**
Array.prototype.select = function select() { for (let j = 0; j < this.length - 1; j++) { let min = j, temp = null; // 找到比当前项还小的这一项索引 for (let i = min + 1; i < this.length; i++) { if (this[i] < this[min]) { min = i; } } // 让最小的项和当前首位交换位置 swap(this,min,j); } return this; }; let ary = [12, 8, 24, 16, 1]; ary.select(); console.log(ary);
- **插入排序**
Array.prototype.insert = function insert() { // 1.准备一个新数组,用来存储抓到手里的牌,开始先抓一张牌进来 let handle = []; handle.push(this[0]);
// 2.从第二项开始依次抓牌,一直到把台面上的牌抓光
for (let i = 1; i < this.length; i++) {
// A是新抓的牌
let A = this[i];
// 和HANDDLE手里的牌依次比较(从后向前比)
for (let j = handle.length - 1; j >= 0; j--) {
// 每一次要比较的手里的牌
let B = handle[j];
// 如果当前新牌A比要比较的牌B大了,把A放到B的后面
if (A > B) {
handle.splice(j + 1, 0, A);
break;
}
// 已经比到第一项,我们把新牌放到手中最前面即可
if (j === 0) {
handle.unshift(A);
}
}
}
return handle;
} let ary = [12, 8, 24, 16, 1]; ary.insert(); console.log(ary);
- **快速排序**
Array.prototype.quick = function quick() { // 4.结束递归(当数组中小于等于一项,则不用处理) if (this.length <= 1) { return this; } // 1.找到数组的中间项,在原有的数组中把它移除 let middleIndex = Math.floor(this.length / 2); let middleValue = this.splice(middleIndex, 1)[0]; // 2.准备左右两个数组,循环剩下数组中的每一项,比当前项小的放到左边数组中,反之放到右边数组中 let aryLeft = [], aryRight = []; for (let i = 0; i < this.length; i++) { let item = this[i]; item < middleValue ? aryLeft.push(item) : aryRight.push(item); } // 3.递归方式让左右两边的数组持续这样处理,一直到左右两边都排好序为止(最后让左边+中间+右边拼接成为最后的结果) return quick(aryLeft).concat(middleValue, quick(aryRight)); } let ary = [12, 8, 15, 16, 1, 24]; ary.quick(); console.log(ary);
- **希尔排序**
Array.prototype.shell = function shell() { let gap = Math.floor(this.length / 2); while (gap >= 1) { for (let i = gap; i < this.length; i++) { while (i - gap >= 0 && this[i] < this[i - gap]) { swap(this, i, i - gap); i = i - gap; } } gap = Math.floor(gap / 2); } }; let arr = [58, 23, 67, 36, 40, 46, 35, 28, 20, 10]; arr.shell(); console.log(arr);
## vue
- **生命周期**
- **beforecreate:**在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用
- **created:** 在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:**数据观测 (data observer),property 和方法的运算,watch/event 事件回调。**然而,挂载阶段还没开始,`$el` `property` 目前尚不可用。
- **beforeMount:**在挂载开始之前被调用:相关的 render 函数首次被调用。
- **mounted:**实例被挂载后调用,这时** el 被新创建的`vm.$el`替换了**。如果根实例挂载到了一个文档内的元素上,当 mounted 被调用时 `vm.$el`也在文档内。

- **beforeUpdate:**数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前**访问现有的 DOM**,比如手动移除已添加的事件监听器。
- **updated:**由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或 watcher 取而代之。

- **activated:**被 keep-alive 缓存的组件激活时调用。
- **deactivated:**被 keep-alive 缓存的组件停用时调用。
- **beforeDestroy:**实例销毁之前调用。在这一步,实例仍然完全可用。
- **destroyed:**实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。
- **errorCaptured:**当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:**错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。**此钩子可以返回 false 以阻止该错误继续向上传播。
- **vuex: **数据管控仓库;适用于中大型单页项目
- **State:**唯一数据源,并且状态存储是响应式的。
- 通过store.state.xxx来获取相应的状态,
- 如果模块比较多的话,通过在根组件注入到每一个子组件中`Vue.use(Vuex)`,子组件通过`this.$store`来访问状态。
- mapState辅助函数
- **Getter:** 就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
- **Mutation:**更改状态的唯一方法,且必须是同步函数。mutation 都是同步事务
- **Action:**
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
- **Module:**将store分割成多个模块,便于管理和操作
- **computed 和 watch 的区别和运用的场景**
- **computed: **是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;
- **watch: **更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;
- ①当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;②当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
- **vue如何实现数据的双向绑定**

- **实现一个监听器 Observer:** 对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty() 对属性都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。
- **实现一个解析器 Compile: **解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。
- **实现一个订阅者 Watcher:** Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。
- **实现一个订阅器 Dep: **订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。
- **v-show与v-if区别**
- v-show是css的切换,适用于频繁的切换
- v-if是完整的销毁和渲染,则适用于较少的改变。
- **导航守卫**
- **全局守卫**
1.**router.beforeEach:**全局前置守卫 进入路由之前
2.**router.beforeResolve:**全局解析守卫 在调用beforeRouteEenter调用之后调用
3.**router.afterEach:**全局后置钩子 进入路由之后调用
- **路由独享的守卫:** 在路由配置表中直接配置beforeEnter
- **组件内的守卫 可以在路由组件内直接定义以下路由导航守卫**
const Foo = {
template: ...
,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 this
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 this
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用,我们用它来禁止用户离开
// 可以访问组件实例 this
// 比如还未保存草稿,或者在用户离开前,
将setInterval销毁,防止离开之后,定时器还在调用。
}
}
- **在实际项目开发过程中遇到过哪些问题,是怎么解决的?**