前端面试题
1、闭包 闭包就是能够读取其函数内部变量的函数 子级函数可以访问父级函数的变量,反之不成立 例如 function f1() { var n = 999; function f2() { console.log(n); } return f2; } var result = f1(); result();//999 使用场景 原生的setTimeout传递的第一个函数不能带参数,通过闭包可以实现传参效果。 function f1(a) { function f2() { console.log(a); } return f2; } var fun = f1(1); setTimeout(fun,1000);//一秒之后打印出1 2、深浅拷贝 说白了就是 去 赋值 浅:定义对象A、B。当B拷贝了A的数据,且当B的改变会导致A改变时,此时叫B浅拷贝了A 例子: var A{ name:"martion", data:{num:10} } var B={} B=A; B.name="lucy"; console.log(A.name); ///lucy 深:当B拷贝了A的数据,且当B的改变不会导致A的改变,此时叫B深拷贝了A
可以利用json.parse和json.stringfy的转换 var A={ name:"martin", data:{num:10}, say:function(){ console.log("hello world") } } //开辟了一个新的堆内存地址,假设为placaA var B={}; //又开辟了一个新的堆内存地址,假设为placeB B=JSON.parse(JSON.stringfy(A)); B.name="lucy"; console.log(A.name); //martin
3、var let const 的区别 作用域不同 var是函数作用域,不能跨函数访问,可以跨块访问且快内访问。 let是块作用域, 只能在块作用域里访问,不能跨块访问,也不能跨函数访问。 const 使用时必须初始化(即必须赋值),只能在块作用域里访问,而且不能修改(变量), 但是在对象中可以修改里面的变量值,不能修改指定一个新的对象。
const a = 1;//定义变量
a=2;//报错
const aa = { name : 'wilson' };//定义对象
aa.name = 'lst';//不报错
aa = { name : 'lst'};//报错 块作用域由 { } 包括,if语句和 for语句里面的{ }也属于块作用域 函数作用域 (function a(){ console.log('函数作用域') })() 4、数据类型 基本类型:String 、Number、Undefined、Null、Boolean。占用空间固定,保存在栈中 引用类型:Object、Array、Function、Date。不固定、保存在堆中 堆与栈就是操作系统对进程占用的内存空间的两种管理方式 栈:由编译器自动分配 堆:一般由程序员分配释放 5、v-if 与 v-show 的区别 v-if的原理是根据条件来动态的进行增删DOM元素 v-show是根据判断条件来动态的进行显示与隐藏元素,只是动态的更改样式(display:none)而不需要增删DOM元素 频繁判断展示与隐藏推荐使用v-show,v-if增删DOM会影响页面加载的性能 6、== 与 === == 比较时,先检查两个操作数据类型,如果相同,则进行===比较, 如果不同,则自动进行一次类型转换,转换成相同类型后再进行比较。 ===比较时,如果类型不同,不会自动转换类型,直接返回false. 7、盒子模型 外边距margin、边框border、内边距padding、和实际内容content组成
8、垂直居中 第一种: width:100px; height:100px; position:absolute; top:50%; left:50%; margin-left:-50px;//高度的一半 margin-top:-50px;//宽度的一半 第二种: position: fixed; top:50%; left:50%; transform: translate(-50%,-50%) scale(1);
9、javaScript 创建新节点 createDocumentFragment() //创建一个DOM片段 createElement() //创建一个具体的元素 createTextNode() //创建一个文本节点 添加、移除、替换、插入 appendChild() //添加 removeChild() //移除 replaceChild() //替换 insertBefore() //插入
10、浏览器的内核 IE: trident内核 Firefox:gecko内核 Safari:webkit内核 Opera:以前是presto内核,Opera现已改用Google Chrome的Blink内核 Chrome:Blink(基于webkit,Google与Opera Software共同开发)
11、如何消除一个数组里面重复的元素? var arr1 =[1,2,2,2,3,3,3,4,5,6], arr2 = []; for(var i = 0,len = arr1.length; i< len; i++){ if(arr2.indexOf(arr1[i]) < 0){ arr2.push(arr1[i]); } } console.log(arr2)//1、2、3、4、5、6
12、判断数据类型 1) typeof typeof 123 //number typeof 'abc' //string typeof [1,2,3], //"object" typeof {a:1,b:2,c:3}, //"object" typeof function(){console.log('aaa');}, //"function" typeof undefined, //"undefined" 数组、对象等都是返回object,所以部分不是做判断 2) instanceof 123 instanceof Number, //false 'dsfsf' instanceof String, //false false instanceof Boolean, //false [1,2,3] instanceof Array, //true {a:1,b:2,c:3} instanceof Object, //true 数组、对象可使用instanceof 3) constructor
13、vue的生命周期
是指 vue 实例对象从创建到销毁的过程。
作用:有多个钩子函数,更好地控制vue实例过程时的逻辑。
常用八个钩子函数:创建前后,挂载前后,更新前后,销毁前后。
创建前:el和data属性都还没有初始化,所以不能访问;可以加loading加载事件。
创建后:data已经初始化完成,但是el还不能访问;结束loading事件。
挂载前:el和data完成了初始化,但是数据和模板没有完成,页面的内容还是vue的占位符,
这里也是页面渲染前最后一次机会更改数据。
挂载后:已渲染完成,可以操作DOM。
更新前:需要更改的数据和模板还没结合。
更新后:数据已经更改完成,dom也重新渲染完成。
销毁前:vue实例销毁之前调用。监听、定时器的移除等。
销毁后:实例销毁后,数据与视图的关系会断开,其实dom元素还是存在的,只是不再受vue控制。
第一次加载页面会触发:beforeCreate, created, beforeMount, mounted。
挂载后mounted,DOM渲染已经完成了。
父子件的生命周期顺序:先走到父组件的(挂载前),再子组件走到(挂载后),父组件再挂载到dom。
el的解释:vue实例挂载的元素节点
例如:
new Vue({
el:'#app',
data:{
msg:'hello world'
}
})
14、MVVM --- 前后端分手大师
一句话总局:vm层(视图模型层)通过ajax接口从后台m层(模型层)请求数据,vm层再和v层(视图层)实现数据的双向绑定。
view视图层:也就是用户看到的页面html、css
viewmodel视图模型层:js业务逻辑层
model模型层:数据访问层
是mvc(模型视图控制层)的加强版、数据分离、视图模型层
15、vue路由
vue-router就是用于组件之间的切换跳转传参。
路由有两种跳转的方式:声明式导航(router-link:务必要有to属性)
编程式导航($routrt.push | replace)
编程式更加推荐,因为更好的编写自己的业务逻辑
路由传参的参数有两种写法:
1、params参数:属于路径的一部分,在配置路由的时候需要占位 例如 /detail/18
2、query参数:不需要占位,类似ajax中的queryString 例如 /detail?id=10&lst=10
路由传参的方法
1、纯字符串形式
this.$router.push("/detail/"+this.id+"?id="+this.id) PS : params参数时要在路由配置里面写好占位
2、模板字符串
this.$router.push(`/detail/${this.id}?id=${this.id}`)
3、对象写法 (常用的)
this.$router.push({name:"detailRou",params:{id:this.id},query:{id:this.id}}) PS :需要在路由配置里面写它的名字name
路由传递参数(对象写法),path不可以结合params参数一起使用。
如何指定params参数可传可不传?在配置路由的时候,在占位的后面加上问号?
params参数传的是空串的解决办法,使用undefined解决
this.$router.push({name:'detailRou',params:{id:''||undefined}})
路由组件可以传props数据(了解一下即可)
1、布尔值写法
props:true
2、对象写法
props:{a:1,b:2}
3、函数写法
props:($route)=>{
return {id:$route.params.id,id:$routr.query.id}
}
路由配置参数:
path : 跳转路径
component : 路径相对于的组件
name:命名路由
children:子路由的配置参数(路由嵌套)
props:路由解耦
redirect : 重定向路由
有两种模式:默认是hash模式,会自动带上#号;history模式不会带#号。
16、Set 对象作用
数组去重
var mySet = new Set([1, 2, 3, 4, 4]);
[...mySet]; // [1, 2, 3, 4]
并集
var a = new Set([1, 2, 3]);
var b = new Set([4, 3, 2]);
var union = new Set([...a, ...b]); // {1, 2, 3, 4}
交集
var a = new Set([1, 2, 3]);
var b = new Set([4, 3, 2]);
var intersect = new Set([...a].filter(x => b.has(x))); // {2, 3}
差集
var a = new Set([1, 2, 3]);
var b = new Set([4, 3, 2]);
var difference = new Set([...a].filter(x => !b.has(x))); // {1}
17、vue 父子组件之间的通讯传参方式
1) 父传子 通过props
<child :message="parentMsg"></child>//父组件
export default {//子组件
props: {
message: String //定义传值的类型
}
}
2) 子传父 通过$emit
<div @click.stop="checked(item)"></div>//子组件
checked(e) {
uni.$emit("handClick", e);
}
父组件 引入子组件
import Child from '@/components/Child.vue';
<Child :handClick="handC"></Child>
handC(obj){
console.log(obj); //输出子组件e传过来的值
}
3) 多级嵌套组件 通讯方式
provide:是一个对象,或者是一个返回对象的函数,里面呢就包含要给子孙后代的东西,也就是属性和属性值。
inject:一个字符串数组,或者是一个对象。属性值可以是一个对象,包含from和default默认值。
provide: {
msg: 'hello world!'
}
inject: {
msg: {
from: 'msg',
default: ''
}
}
4)兄弟之间的传递
第一种:
在main.ji里面利用原型prototype声明
Vue.prototype.$bus=new Vue();
子组件一:
this.$bus.$emit('name',this.name);//传
子组件二:
this.$bus.$on('name',(res)=>{
res =》则是子组件一传过来的数据
})
第二种方法
利用父组件做中转站 (组件1传给父组件,父组件再传给组件2)
组件1:this.$emit('name',this.name)
父组件:
<child1 @name="getName"></child1>//接收组件1传来的值
<child2 :name="name">//父组件把值传给组件2
getName(data){
this.name=data;
}
组件2:
prop{
nmae:String;//接收父组件传来的值
}
===================================
<script>
export default {
// 父组件通过provide将自己的数据以对象形式传出去
provide(){
return {
parentValue:"我是父组件的值啊"
}
}
};
</script>
<script>
export default {
// inject:["parentValue"], // 使用一个注入的值作为数据入口:
inject:{
// 使用一个默认值使其变成可选项
parentValue: { // 健名
from: 'parentValue', // 来源
default: 'parentValue' // 默认值
}
}
}
</script>
18、es6 新特性
1) let、const、var
2) 箭头函数
=> 代替了 function()
var perSon= (item,i) => {
console.log('456')
};
this指向不同:箭头函数指向定义时候外层的第一个普通函数的this,如果没,则指向最外层的window;普通函数指向调用它的对象,也就是说谁调用了它就指向谁。
箭头函数定义时就指定了,不可修改this指向(call、apply、bind)。
箭头函数不能作为构造函数,不能使用new。
箭头函数没有原型portotype、没有arguments。
3) 拼接字符串
es6之前都是 通过“\”和“+”来构建。
现在 `${}` 反引号``+${}
比如:url: `/pages/store/home/index?id=${id}`
4)for...of 和 for...in
let letters = ['a', 'b', 'c'];
letters.size = 3;
for (let letter of letters) {
console.log(letter);
}
// 结果: a, b, c
let stus = ["Sam", "22", "男"];
for (let stu in stus) {
console.log(stus[stu]);
}
// 结果: Sam, 22, 男
5)对象和数组的解构
// 对象
const student = {
name: 'Sam',
age: 22,
sex: '男'
}
const { name, age, sex } = student;
//数组
const arr=[100,200,300];
let [x,y,z]=arr;
console.log('输出:',x,y,z)
解构不成功就是undefine
6)二进制和八进制的字面量
通过在数字前面添加 0o 或者0O 即可将其转换为八进制值
let oValue = 0o10;
console.log(oValue); // 8
let bValue = 0b10; // 二进制使用 `0b` 或者 `0B`
console.log(bValue); // 2
19、跨域
什么事跨域:协议、域名、端口号不同的请求,称为跨域
解决方法:JSONP、CROS、代理
常用 代理:webpack提供的devServer,在webpack.config.js也就是vue.config.js
20、vuex状态管理库
vuex是官方提高的一个插件,状态管理库,一般用于大项目,数据多,维护费劲
五大属性:state、mutations、actions、getters、modules
state:仓库存储数据的地方
mutations:修改仓库数据state的唯一手段
actions:编写自己的业务逻辑,也可以处理异步
getters:可以理解为计算属性,用于简化仓库数据
modules:vuex可以多模块开发
组件如何获取到仓库里面的数据:使用vuex中的mapState,在计算属性computed中使用 ...mapState(["obj"])
如何修改state:先派发actions,this.$store.dispatch('add'),然后在actions属性中进行commit,方法名和派发是要一致add,
然后在mutations中引用进行修改
21、cookie、session和localStorage之间的区别
cookie数据存放在客户的浏览器上(不安全),session数据放在服务器上
session关闭窗口后,sessionStorage即被销毁
localStorage保存后数据永远存在不会失效过期,除非用 js手动或定时清除,window.localStorage.clear()
22、v-model的双向绑定原理
通过object.defineProperty劫持数据发生的改变,如果数据发生了改变(在set中进行赋值),然后触发update方法进行更新节点内容,从而实现了数据双向绑定的原理。
23、computed 与 watch的区别
比如购物车的结算:
computed计算属性是有缓存的,在计算某一个属性的改变,如果某一值改变了,计算属性会监听到进行返回,就是一个数据受多个数据影响。(第一次加载会监听)
watch是监听一个值的变化,然后执行对应的回调。(第一次加载不监听)