1、什么是原型?什么是原型链?如何获取对象的原型值?
在js中每一个构造函数都有一个prototype属性,该属性是一个对象,包含了所有当前构造函数的属性和方法。当我们通过构造函数创建对象的时候,这个对象中有一个指针,指向这个构造函数的prototype,这个指针就是原型。
原型链是通过原型组成的一条查找链。
获取原型方法:
obj.__proto__
//或者
Object.getPrototypeOf(obj)
2、什么叫防抖与节流?
都是为了防止短时间内多次执行事件,消耗浏览器性能。防抖就是多用在模糊查询input事件中,多次更新input时数据会发生抖动的处理,在一定时间内取最后一次输入的value值进行请求。节流是在同一时间段,如果时间没有超过指定的时间间隔,那么就不去执行下一次。
// 防抖
function debounce (callback,delay) {
var t = null;
return function () {
clearTimeout(t);
t = setTimeout(callback,delay);
}
}
window.onscroll = debounce(function(){
console.log("调用了一次");
},500)
// 节流;
function throttle (callback,duration){
var lastTime = new Date().getTime();
return function () {
var now = new Date().getTime();
if(now - lastTime > 500){
callback();
lastTime = now;
}
}
}
window.onscroll = throttle(function(){
console.log("调用了一次");
}
3、闭包
闭包就是能够读取其他函数内部变量的函数。
闭包的特点:1、闭包中的变量不会被浏览器回收,需慎用,同时延长了变量的生命周期,用完一定要手动回收掉。2、闭包中有私有作用域。
4、var,let,const的区别
var有三个特点,1、声明提升,2、变量会被覆盖,3、没有块级作用域。(其实也有,要在function里)
let没又声明提升,不可被重复声明,有块级作用域。
const声明之后必须赋值否则报错,用来声明常量且不可修改,且拥有let的特性。
5、怎样不创建第三个变量互换a,b两个变量的值?
es5
//只能互换整数
let a = 1;
let b = 2;
a = a + b;
b = a - b;
a = a - b;
es6
let a = 1;
let b = 2;
[a, b] = [b, a]
6、如何用ES6快速去重
let arr = [12, 43, 23, 12, 43, 55]
let item = [...new Set(arr)]
console.log(item)
7、vue双向绑定的原理
首先要了解Object中的一个defineProperty方法,
语法:Object.defineProperty(obj,property,descriptor)
参数一:obj
绑定属性的目标对象
参数二:property
绑定的属性名
参数三:descriptor
属性描述(配置),且此参数本身为一个对象
属性值1:value
设置属性默认值
属性值2:writable
设置属性是否能够修改
属性值3:enumerable
设置属性是否可以枚举,即是否允许遍历
属性值4:configurable
设置属性是否可以删除或编辑
属性值5:get
获取属性的值
属性值6:set
设置属性的值
vue中的双向绑定其实就是用了defineProperty中set和get的方法来实现的。
手写一个双向绑定:
<!--HTML-->
<div>
<input type="text" id="userName">
<p id="userText"></p>
</div>
//js
let obj = {}
Object.defineProperty(obj, "userName", {
get() {
console.log('get value is:' + userName)
},
set(newData) {
console.log('set newData is:' + newData)
userName = newData
document.getElementById("userName").value = newData
document.getElementById("userText").innerHTML = newData
}
})
document.getElementById("userName").addEventListener("keyup", (e) => {
console.log(e.target.value)
obj.userName = e.target.value
})
8、data为什么是一个函数
因为data其实就是一个闭包,vue是单页面应用程序,会存在n个组件,就是为了确保各个组件的数据不会互相污染,所以用闭包,做了私有作用域
9、vi-if和v-show的区别
v-if是多次的渲染,v-show是记载中就渲染好的,false状态下只是隐藏了起来,禁忌用来做权限管理。
10、什么是虚拟DOM,虚拟DOM是怎样提升VUE渲染效率的?
首先,虚拟DOM的本质是js对象。每一个组件都有一个render函数,这个函数会生成一个虚拟DOM树,在vue渲染视图时,会调用这个render函数,在组件的数据或属性改变时 ,也会调用这个render函数。在初次加载的时候,组件会使用render函数将模板转换成虚拟DOM,再把虚拟DOM转换成真实DOM,渲染到页面上,其实这是vue的一个缺点,首次加载是多消耗了一些性能的;但它的优点是在数据改变时,调用render函数,创建一个新的虚拟DOM树,然后比较新旧虚拟DOM,找到需要修改的虚拟DOM,然后将它改变到真实DOM上,保证对真实DOM做到最小的修改,提升了页面n次渲染的性能。
11、深拷贝和浅拷贝
基本数据类型的赋值都是深拷贝;对象和数组的赋值是浅拷贝。解构赋值可以对一维数组实现深拷贝,多维数组还是浅拷贝。一般用JSON.parse(JSON.stringify(arr))来实现深拷贝,但无法实现函数的深拷贝。
function deepClone(data) {
if (typeof data !== "object") return data
let newData = Array.isArray(data) ? [] : {}
for (key in data) {
if (data.hasOwnProperty(key)) {
if(data[key] && typeof data[key] === "object"){
newData[key] = deepClone(data[key])
}else{
newData[key] = data[key]
}
}
}
return newData
}
var obj = {
age: 13,
name: {
addr: '天边'
}
}
var obj2 = deepClone(obj);
obj2.age = 14
obj2.name.addr = '地心'
console.log(obj, obj2);
12、Promise怎样使用?Promise的链式调用怎样写?手写一个Promise。
//Promise的使用
let flag = true;
const p = new Promise((resolve, reject) => {
if (flag) {
let obj = {
a: 1,
b: "aaa"
}
resolve(obj)
}
})
//Promise的链式调用
p.then((res) => {
return new Promise((resolve, reject) => {
if (res) {
let obj = {
a: res.a,
c: true
}
resolve(obj)
}
})
}).then(res => {
console.log(res)
})
//手写Promise
function myPromise(executor) {
let self = this;
self.status = "pending"; //状态初始化
self.value = null; //成功之后,返回的数据
self.reason = null; //失败的原因
// 暂存状态
self.onFulfilledCallbacks = []
self.onRejectedCallbacks = []
// 返回成功的结果
function resolve(value) {
if (self.status === "pending") {
self.value = value; //保存成功结果
self.status = "fulfilled";
// 状态改变之后依次取出
self.onFulfilledCallbacks.forEach(item => item(value))
}
}
// 返回失败的结果
function reject(reason) {
if (self.status === "pending") {
self.reason = reason; //保存失败结果
self.status = "rejected";
// 状态改变之后依次取出
self.onRejectedCallbacks.forEach(item => item(value))
}
}
try {
// 它是同步的回调,会立即在主线程上执行,被称为executor函数
executor(resolve, reject);
} catch (err) {
reject(err)
}
}
// 把.then方法放在原型上
// 不管是成功还是失败
myPromise.prototype.then = function (onFulfilled, onRejected) {
let self = this;
// 判断传进来的是不是一个方法,如果不是就定义一个方法
onFulfilled = typeof onFulfilled === 'function' ?
onFulfilled : function (data) { resolve(data) }
onRejected = typeof onFulfilled === 'function' ?
onFulfilled : function (err) { throw (err) }
if (self.status === "pending") {
self.onFulfilledCallbacks.push(onFulfilled)
self.onRejectedCallbacks.push(onRejected)
}
}
let demo = new myPromise(function (resolve, reject) {
console.log(123)
setTimeout(() => {
resolve(222)
}, 500);
}).then( data => {
console.log(data)
})
13、常见算法(冒泡排序,快速排序,去重算法,查找出现最多的字符,diff算法)
let arr = [9, 56, 5, 7, 12, 35, 54]
// 冒泡排序
function arr_sort(arr) {
// var temp;
for (var i = 0; i < arr.length - 1; i++) {
for (var j = 0; j < arr.length - i; j++) {
if (arr[j] > arr[j + 1]) {
var swap = arr[j + 1]
arr[j + 1] = arr[j]
arr[j] = swap
}
}
}
return arr
}
console.log(arr_sort(arr))
//快速排序
function quickSort(ary) {
if (ary.length <= 1) {
return ary
}
// 获取数组中间数
let contentValue = ary.splice(Math.floor(ary.length / 2), 1)[0]
// 创建左右空数组,大于中间数的放右边,小于中间数的放左边
let leftArr = []
let rightArr = []
for (let i = 0; i < ary.length; i++) {
let item = ary[i]
item > contentValue ? rightArr.push(item) : leftArr.push(item);
}
return quickSort(leftArr).concat(contentValue, quickSort(rightArr))
}
console.log(quickSort(arr))
// 数组去重
let arr = [1, 1, 65, 23, 22, 37, 92, 22]
function unique(ary) {
let newArr = []
for (let i = 0, len = ary.length; i < len; i++){
if(newArr.indexOf(ary[i]) == -1){
newArr.push(ary[i])
}
}
return newArr
}
console.log(unique(arr))
//-------------------------------------------------------------------------
// 数组去重
let arr = [1, 1, 65, 23, 22, 37, 92, 22]
// 速度最快, 占空间最多(空间换时间)
function unique(array) {
var r = [array[0]]
for (var i = 1; i < array.length; i++) {
if(array.indexOf(array[i]) == i){
r.push(array[i])
}
}
return r;
}
console.log(unique(arr))
// 使用数组查找出出现次数最多的字符,确定是如果是多个,只会存最后一个
var str = "zhaochucichuzuiduodezifu";
let arr = []
for (let i = 0, len = str.length; i < len; i++) {
let index = -1
let j = 0
do {
index = str.indexOf(str[i], index + 1)
if (index != -1) {
j++
}
} while (index != -1);
arr[j] = str[i]
}
console.log('出现次数最多的是' + arr[arr.length-1])
console.log('出现次数是' + (arr.length-1))
//-------------------------------------------------------------------------
// 使用对象,可以查出多个
var str = "iiiiijjjjj";
let obj = {}
for (let i = 0, len = str.length; i < len; i++) {
if (obj[str[i]]) {
obj[str[i]]++
} else {
obj[str[i]] = 1
}
}
let max = 0;
for (key in obj) {
if (obj[key] > max) {
max = obj[key]
}
}
for (item in obj) {
if(obj[item] == max){
console.log('出现次数最多的是:'+ item)
console.log('出现次数是:'+ obj[item])
}
}
14、vue生命周期的理解
生命周期我理解为整个vue组件实例从创建,到销毁的一个过程,这个过程中包含了8个钩子函数,分别是beforeCreate(创建前),created(创建后),beforeMount(挂载前),mounted(挂载后),beforeUpdate(更新前),updated(更新后),beforeDestroy(销毁前),destroyed(销毁后)。特殊情况下会有另外的两个钩子,是需要组件缓存的时候,使用了keep-alive时延伸出来的,分别是activated(缓存组件激活时)和deactivated(缓存组件停用时),当使用deactivated的时候就不会触发销毁前后的两个钩子。
15、vue组件之间是如何通讯的?
a)props/$emit
父组件a通过props的方式向子组件b传递,子组件b通过b组件的$emit来创建自定义事件,父组件a在v-on中方式获取。
b)$emit/$on
实现了任何组件直接的通信,建立一个空的Vue,比如let Event = new Vue(),要传递的组件只要创建自定义事件名和要传入的变量放入$emit()方法里,比如Event.$emit("自定义事件名", data),然后要接收的组件用$on()接收,比如Event.$on("自定义事件名",data => {console.log(data)})。缺点是代码可读性变差,如果项目庞大,隔代传递多,很难找到某个状态发生更变的时机。于是我们看c方法
c)使用vuex
共享状态管理,vuex和localStorage的区别是,vuex存储的数据是响应式的,但页面关闭vuex就销毁了,localStorage会一直在,且只能纯字符串,vuex储存不需要转换。
d)$parent/$children与ref
ref如果在普通的DOM元素上使用,引用指向的就是DOM元素;如果用在子组件上,引用就指向组件实例。这三个方法都可以直接操作组件里的方法,缺点是不能跨级和兄弟间通信。
e)provide/inject
vue2.2.0新增的一个API,这对选项需要一起使用,作用是让一个祖先元素向所有的子孙组件注入一个依赖,不管组件层次有多深,都可以生效。一言而蔽之:祖先组件中通过provider来提供变量,然后在子孙组件中通过inject来注入变量。
// A.vue
export default {
provide: {
name: '浪里行舟'
}
}
// B.vue
export default {
inject: ['name'],
mounted () {
console.log(this.name); // 浪里行舟
}
}
16、MVVM和MVC是什么区别?
首先MVC是,模型、视图、控制器三个单词的缩写,分别对应的是数据,视图和业务逻辑,而MVVM是MVC的发展,在业务逻辑,应用程序越来越复杂的环境下,出现了MV替代了C的框架,以前的C只负责管理自己的生命周期,处理控制器之前的跳转,实现控制器容器。后来应用程序复杂,需要处理数据解析,这部分代码放在了C里,使代码非常臃肿。而MV提供了数据双向绑定,让开发变得更简单。VM算是M和V直接的一个桥梁,它可以视图修改数据,也可以数据修改视图。
17、简述原型,构造函数,实例的理解
构造函数也可以叫工厂函数,可以理解为对象的模板,可以快速的创建一个有基本属性的对象,一般在用构造函数new一个新对象的时候,就是一个实例,实例有实例成员可以供实例来访问,也就是实例的属性,还有一种成员叫静态成员,静态成员是直接给构造函数本身添加的属性,只能给构造函数本身访问,实例访问不了。
一般构造函数如果想定制一个方法,提供给每一个实例使用,不会在构造函数里创建,这样每一个实例对象都会创建一个方法,会大量的占用内存。这时就会用到原型,每一个构造函数都有一个属性,叫prototype,prototype是一个对象,包含的当前构造函数所有的属性和方法,我们可以把所有的方法都放在prototype里, 我们在创建实例对象的时候,这个对象会有一个指针,指向就是prototype,这个指针就是原型。原型链说的就是原型组成的查找链。
18、前端怎样解决跨域问题?
jsonp
19、清除浮动的方式有哪些?
a)使用clear:both,给每个有浮动元素的父元素,增加一个有clear:both的子元素。优点是兼容好,代码少,缺点是会多很多无意义的代码。
b)使用overflow,给每一个浮动的元素加上overflow:hidden。优点是不会多很多无意义代码,缺点是有时要用到这个属性。
c)给需要清楚浮动的元素加一个伪元素,伪元素来做clear:both。最优雅的解决方法,缺点是不兼容老IE。
20、什么是重绘和回流?
在HTML中,每个元素都可以理解成一个盒子,在浏览器解析过程中,会涉及到回流与重绘。回流:布局引擎会根据各种样式计算每个盒子在页面上的大小与位置。当改变大小,显示隐藏,更换不同尺寸的图片等影响盒子布局的属性时,就会回流。
重绘:当计算好盒模型的位置、大小及其他属性后,浏览器根据每个盒子特性进行绘制。当修改颜色,透明度,阴影等,会触发重绘。一般回流必定会导致重绘,但重绘不一定会导致回流。
减少回流的几种方式:
a)避免使用table布局,table中的每个元素的大小和内容都会影响整个table的重新布局;
b)对于复杂动画对其设置定位,使它脱离文档流,就可以减少他对其他元素的影响;
c)减少一条一条的修改DOM样式,最好预定好class,然后更换class。
21、从输入url到页面展示到底发生了什么?
1)输入地址
在我们开始输入url时,浏览器会从历史记录或者书签中找可以对应的url,然后给出智能提示,可以补全地址。
2)浏览器查找域名的 IP 地址
3)浏览器向web服务器发送一个http请求。
4)服务器的永久重定向相应。
5)服务器处理请求。
6)服务器返回请求
7)浏览器显示html
8)加载资源
22、web前端的优化策略
1)减少数据请求,比如css雪碧图,合并压缩样式表,js脚本。利用缓存等;
2)减少首次加载的数据,比如延迟加载(懒加载)
3)样式表和js文件书写的顺序。比如css放在头部,js文件放在尾部的</body>上面,先让用户看到页面样式。
23、项目中遇到的问题
24、项目中package.json文件有什么作用
首先,它记录了当前项目的基本信息,比如项目名称,版本,作者,GitHub地址等,其次它记录了项目引用了哪些第三方模块,这样在下载项目的时候不必下载整个项目的依赖包,直接初始化init项目就可以了。
25、webpack的使用有哪些优化建议?
26、vue常用的指令有哪些,怎样封装指令?
常用的指令有v-if,v-show,v-else,v-bind之类,封装的话有两种方法,一种是全局封装,一种是局部组件封装。
//全局注册
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
//局部注册
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
27、Vue中的key的作用,用到哪些地方
key的作用是标记item的唯一性,在更新数据的时候,vue会用新老虚拟DOM来对比,对比就是用key属性来对比的,在遍历的时候,下标就可以用来当key,但最好还是用唯一值id一类。可以对比的更快更准确。
如果key重复会报错。
28、Vuex的原理
29、路由的几种模式,history的原理
两种,一种是hash,一种是history,hash是#号后面的/index一类,他并不会以为#号后的修改而再次请求,history用的是浏览器提供的 pushState() 和 replaceState()两个方法,实现跳转不刷新页面,两种模式都非常适合SPA单页面应用程序。
hash 优点:纯前端路由,不需要后端干涉,兼容性好。
缺点:url带#号不规范,不美观
history 优点:url规范
缺点:刷新404,兼容性差一点,用了浏览器的pushState() 和 replaceState() 方法。
30、Vue的计算属性和侦听属性
计算属性是用已有的值得到一个新值,当值有更新时,就会触发,包含了get和set方法,用对象的形式写,如果只是获取没有修改,直接像方法一样简写,具有缓存特性,如果变量没有更新,在词使用可以直接获取,不会在重新计算,使用很快捷。
<div id="demo">{{ fullName }}</div>
var vm = new Vue({
el: '#demo',
data: {
firstName: 'kobe',
lastName: 'bryant',
},
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
侦听属性是用来监听一个指定数据的改变,进而调用对应的逻辑处理数据
<div id="app">
<input type="number" name="" v-model="num">
{{price}}
{{total}}
</div>
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
<script >
let vm = new Vue({
el: '#app',
data: {
price:"55",
num:"",
total:""
},
watch:{
num( newVal ){
this.total = this.price * newVal
}
}
})
</script>
总结:计算属性不能执行异步调用,必须同步执行,而watch可以异步使用。
31、Href和src区别
href是引用资源,当他下载的时候不会停止对其他资源的加载,src在下载资源的时候会停止其他资源的加载,等到当前这个资源加载好在继续。
32、图片怎么判断是否显示成功
图片可以加一个onload事件,当图片渲染完成后,就会触发事件。
33、Promise函数的作用, async awite函数手写
34、Flex布局实现瀑布流,实现骰子布局
35、vue路由传参的几种方式
a)params来传值(会显示在url上)
// 声明式
// 父组件
<router-link to = "/跳转路径/传入的参数"></router-link>
// 子组件使用
this.num = this.$route.params.num
// 路由配置
{path: '/a/:num', name: A, component: A}
地址栏中的显示: http://localhost:8080/#/a/123
// 编程式
<button @click="deliverParams(123)">push传参</button>
methods: {
deliverParams (id){
$route.push({
path: `/a/${id}`
})
}
}
// 子组件使用
this.id= this.$route.params.id
// 路由配置
{path: '/a/:id', name: A, component: A}
地址栏中的显示: http://localhost:8080/#/a/123
b)使用name匹配存放在params(不会显示在url上,刷新则消失)
// 声明式
<router-link :to="{name:'Child',params:{id:123}}">进入Child路由</router-link>
//编程式
this.$router.push
//子路由配置
{
path: '/child,
name: 'Child',
component: Child
}
//父路由编程式传参(一般通过事件触发)
this.$router.push({
name:'Child',
params:{
id:123
}
})
//子组件获取
this.$route.params.id
c)使用name匹配存放在query(会显示在url上)
操作与b一样,区别在于会显示在url上
36、vue数据更新视图不更新的原因
如果用下标是修改数组或对象,就会出现这种情况
// 错误案例
new Vue({
el: "#app",
data: {
arr: ["Javascript", 'Vue'],
objInfo: {
'item-1': "123"
}
},
methods: {
upDateData() {
// 借助 $set函数 对数据进行操作
this.$set(this.arr, 0, "CSS")
}
}
})
// 处理方法
new Vue({
el: "#app",
data: {
arr: ["Javascript", 'Vue'],
objInfo: {
'item-1': "123"
}
},
methods: {
upDateData() {
// 借助 splice 函数 对数据进行操作
this.arr.splice(0, 1, "CSS")
}
}
})
37、v-for和v-if哪个优先级高
v-for优先级高。
如何避免v-for带来的性能损耗:
//方法一:将v-if置于外层元素
<ul v-if="data.length > 0">
<li v-for="item in data">
{{item.name}}
</li>
</ul>
//方法二:将遍历的list通过computed过滤一遍
<ul>
<li v-for="item in newData">
{{item.name}}
</li>
</ul>
//js
computed: {
newData: function() {
return this.data.filter((item) => {
return item.isActive
})
}
}
38、https和http有什么区别
a)https的端口是443,http的端口是80,连接方式不一样;
b)https有加密传输,http是明文的,https是有着更高的安全性;
c)https需要申请证书,http不用
39、vue的优化策略
一、代码优化
1、不要随意的在data里加变量,每个变量都会增加get和set的监听。
2、页面合理的使用keep-alive标签来做缓存
3、v-if有阻断性,可以使用v-if来减少渲染;且v-show也有缓存的特性,所以if和show的使用尽量合理。
4、key的使用,保证了diff算法的快捷。
5、提高组件的复用性,因为虚拟dom是挂载到window上的。
6、对于只是展示,不会修改的对象,可以使用Object.ferrze()方法,冻结的对象不会被增加getter和setter。
7、数据持久化使用防抖和节流的策略。
二、加载优化
1、第三方插件按需加载。
2、懒加载
40、路由守卫
三种类型,分别是全局守卫、路由独享守卫、组件内路由守卫。独享守卫和组件内路由守卫没使用过。
常用的是全局守卫,有前置守卫,解析守卫和后置守卫。场景一般是前置(校验token,加载进度条),后置(关闭进度条)。
独享守卫的使用场景是进入该路由要做什么事,在这里处理。
组件内路由守卫配合keep-alive使用。
41、slice和splice的区别
slice是截取起始下标到结束下标,并且不会修改元数据;splice是起始下标和截取个数,并且会修改元数组。