3月面试实录(未整理完)

5,066 阅读22分钟
  • 以下是我三月里所有面试过的公司问的真实题目
  • 后面还有很多没有写答案总结,写了答案的,如果答得不好,请多多指教。
  • 我已上岸,希望大家加油!

公司个人问题

  • 自我介绍
  • 公司团队规模
  • 公司的业务
  • 你的主要工作
  • 前端技术后端技术
  • 如何说服上司进行项目重构
  • 讲一个你觉得做的不错的项目
  • 项目开发流程
  • 如何进行项目部署
  • 性能监控,数据分析有没有做过
    • 主要是通过navigator Timing api进行监控
    • 引入的是岳鹰监控平台进行监控
  • 团队之间的代码管理和审查

Css

卡片翻转

css盒子模型

盒子模型包括元素的内容,边框(border)、内边距(padding)、外边距(margin)组成 标准盒子模型大小= width(content) + border + padding + margin 怪异盒子大小= width(content+border+padding) + margin

css左右布局的方式

BFC

  • 什么是BFC?如何应用?
    • Block format context , 块级格式化.上下文
    • 一块独立渲染区域 ,内部元素的渲染不会影响边界以外的元素
  • 形成BFC的常见条件
    • float 不是 none
    • position 是 absolute 或 fixed
    • overflow 不是 visible
    • display是flex inline-block
  • BFC的常见应用
    • 清除浮动

Js基础

判断数据类型

1. typeof

  • 判断所有的值类型(undefined,string,number,boolean,symbol)
  • 判断是否是函数 // function
  • 判断是否是引用类型 object(null,[1,2,3],{ a : 2 })

image.png image.png

2. instanceof

  • 用来判断A是否为B的实例,比如A instanceof B
  • instance只能用来判断两个对象是否属于实例关系,而不能判断一个对象实例具体属于哪种类型
  • 所以一般用来判断是否是一个数组,[] instanceof Array

3. Object.prototype.toString.call()

developer.mozilla.org/zh-CN/docs/…

  • toString()是Object的原型方法,调用该方法,默认返回当前对象的[Class],这是一个内部属性,其格式为[object Xxx],其中Xxx就是对象的类型
  • 对于Object对象,直接调用toString()就能返回[object Object],而对于其他对象,则需要通过call/apply来调用才能返回正确的类型信息
let obj = new Object()
obj.toString()  // [object Object]

Object.prototype.toString.call('') ;   // [object String]
Object.prototype.toString.call(1) ;    // [object Number]
Object.prototype.toString.call(true) ; // [object Boolean]
Object.prototype.toString.call(Symbol()); //[object Symbol]
Object.prototype.toString.call(undefined) ; // [object Undefined]
Object.prototype.toString.call(null) ; // [object Null]
Object.prototype.toString.call(new Function()) ; // [object Function]
Object.prototype.toString.call(new Date()) ; // [object Date]
Object.prototype.toString.call([]) ; // [object Array]
Object.prototype.toString.call(new RegExp()) ; // [object RegExp]
Object.prototype.toString.call(new Error()) ; // [object Error]
Object.prototype.toString.call(document) ; // [object HTMLDocument]
Object.prototype.toString.call(window) ; //[object global] window 是全局对象 global 的引用

4. constructor

constructor是原型prototype的一个属性,当函数被定义时,js引擎会为函数添加原型prototype,并且这个prototype中的constructor属性指向函数引用,因此重写prototype会丢失原来的constructor. 注意: 1:null 和 undefined 无constructor,这种方法判断不了。 2:还有,如果自定义对象,开发者重写prototype之后,原有的constructor会丢失,因此,为了规范开发,在重写对象原型时一般都需要重新给 constructor 赋值,以保证对象实例的类型不被篡改。 image.png

什么是闭包

  • 在内层函数中可以访问到外层函数的作用域就是闭包

箭头函数和普通函数的区别

www.jianshu.com/p/231a6f58e…

  1. 箭头函数是匿名函数,不能作为构造函数,不能使用new
  2. 箭头函数不能绑定arguments,取而代之用rest参数...解决
  3. 箭头函数的this永远指向其上下文的this,没有办改变其指向,普通函数的this指向调用它的对象
  4. 箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值

简单说一下this指向问题

1、普通函数调用 this指向windows; 2、对象函数调用 this指向这个对象; 3、构造函数调用 this指向当前实例本身(新创建的对象); 4、call和apply,bind调用 传入什么指向什么,传入的参数;(call可以传多个参数,apply可以传二个参数,第二个数组,不然报错TypeError) 5、箭头函数调用 this指向上级作用域的值(当前函数上下文);

Js数组

数组中查找指定元素

juejin.cn/post/687784…

1. includes

developer.mozilla.org/en-US/docs/… includes() 方法用来判断一个数组是否包含一个指定的值,如果包含则返回 true,否则返回 false。

var a = [1,2,3,4,5,6]

a.includes(2)  // true
a.includes(2,3)  // false
a.includes(5,-2)  // true
a.includes(5,-1)  // false

2. indexOf

developer.mozilla.org/en-US/docs/… indexOf() 方法返回指定元素在数组中的第一个索引,如果不存在,则返回-1。

var array = [2, 5, 9];

array.indexOf(2);     // 0
array.indexOf(7);     // -1
array.indexOf(9, 2);  // 2
array.indexOf(2, -1); // -1
array.indexOf(2, -3); // 0

3. lastIndexOf

developer.mozilla.org/en-US/docs/… lastIndexOf() 方法返回指定元素在数组中的最后一个的索引,如果不存在则返回 -1。从数组的后面向前查找,从 fromIndex 处开始。

var array = [2, 5, 9, 2];

array.lastIndexOf(2);      // 3
array.lastIndexOf(7);      // -1
array.lastIndexOf(2, 3);   // 3
array.lastIndexOf(2, 2);   // 0
array.lastIndexOf(2, -2);  // 0
array.lastIndexOf(2, -1);  // 3

4. some

developer.mozilla.org/zh-CN/docs/… some() 方法测试数组中是不是至少有1个元素通过了被提供的函数测试。它返回的是一个 Boolean 类型的值。

function isBiggerThan10(element, index, array) {
  return element > 10;
}

[2, 5, 8, 1, 4].some(isBiggerThan10);  // false
[12, 5, 8, 1, 4].some(isBiggerThan10); // true

5. every

developer.mozilla.org/zh-CN/docs/… every() 方法测试一个数组内的所有元素是否都能通过某个指定函数的测试。它返回一个布尔值。

function isBigEnough(element, index, array) {
  return element >= 10;
}
[12, 5, 8, 130, 44].every(isBigEnough);   // false
[12, 54, 18, 130, 44].every(isBigEnough); // true

6. filter

developer.mozilla.org/zh-CN/docs/… filter() 方法创建一个新数组, 包含通过所提供函数实现的测试的所有元素。

function isBigEnough(element) {
  return element >= 10;
}

var filtered = [12, 5, 8, 130, 35].filter(isBigEnough);
// filtered is [12, 130, 35] 

7.find

developer.mozilla.org/zh-CN/docs/… find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。

var inventory = [
    {name: 'apples', quantity: 2},
    {name: 'bananas', quantity: 0},
    {name: 'orange', quantity: 5}
];

function findOranges(fruit) { 
    return fruit.name === 'orange';
}

console.log(inventory.find(findOrange));
// { name: 'orange', quantity: 5 }

8. findIndex

developer.mozilla.org/zh-CN/docs/… findIndex() 方法返回数组中满足提供的测试函数的第一个元素的索引。若没有找到对应元素则返回-1。

var inventory = [
    {name: 'apple', quantity: 2},
    {name: 'banana', quantity: 0},
    {name: 'orange', quantity: 5}
];

function findOrange(fruit) { 
    return fruit.name === 'orange';
}

console.log(inventory.findIndex(findOrange));

image.png

数组对象排序

对以下数据进行排序

var person = [{name:"Rom",age:12},{name:"Bob",age:22},{name:"Ma",age:5},{name:"Tony",age:25}]
 

sort

developer.mozilla.org/zh-CN/docs/…

  • 升序排列是把数据从小到大进行排列(1、2、3、4、5)
  • 降序排列是把数据从大到小进行排列(5、4、3、2、1)
person.sort((a,b)=>{ return a.age-b.age})//升序
 
person.sort((a,b)=>{ return b.age-a.age})//降序

1.如果没有指明 compareFunction ,那么元素会按照转换为的字符串的诸个字符的Unicode位点进行排序。 2.如果指明了 compareFunction ,那么数组会按照调用该函数的返回值排序。即 a 和 b 是两个将要被比较的元素:

a.如果 compareFunction(a, b) 小于 0 ,那么 a 会被排列到 b 之前; b.如果 compareFunction(a, b) 等于 0 , a 和 b 的相对位置不变。(ECMAScript 标准并不保证这一行为,而且也不是所有浏览器都会遵守,例如 Mozilla 在 2003 年之前的版本); c.如果 compareFunction(a, b) 大于 0 , b 会被排列到 a 之前。 d. compareFunction(a, b)必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。(利用这一特性,可实现随机排序)

for-of 和 for-in 的区别

for-in是ES5标准,遍历的是key(可遍历对象、数组或字符串的key);for-of是ES6标准,遍历的是value(可遍历对象、数组或字符串的value)

for-in

var arr = [1, 2, 4, 5, 7];
for (var index in arr) {
  console.log(myArray[index]);
}

for-in弊端

1.index索引为字符串型数字(注意,非数字),不能直接进行几何运算。 2.遍历顺序有可能不是按照实际数组的内部顺序(可能按照随机顺序)。 3.使用for-in会遍历数组所有的可枚举属性,包括原型。例如上例的原型方法method和name属性都会被遍历出来,通常需要配合hasOwnProperty()方法判断某个属性是否该对象的实例属性,来将原型对象从循环中剔除。

  • 所以for-in更适合遍历对象,通常是建议不要使用for-in遍历数组。
for (var key in myObject) {
  if(myObject.hasOwnProperty(key)){
    console.log(key);
  }
}

for-of

for-of可以简单、正确地遍历数组(不遍历原型method和name)。 因此建议是使用for-of遍历数组,因为for-of遍历的只是数组内的元素,而不包括数组的原型属性method和索引name。

var myArray = [1, 2, 4, 5, 6, 7];
myArray.name = "数组";
myArray.getName = function() { return this.name; }
for (var value of myArray) {
    console.log(value);
}

区别总结

  • for in遍历的是数组的索引(即键名),而for of遍历的是数组元素值。
  • for-in总是得到对象的key或数组、字符串的下标。
  • for-of总是得到对象的value或数组、字符串的值,另外还可以用于遍历Map和Set。

数组去重

segmentfault.com/a/119000001…

1. ES6 Set去重

function unique (arr) {
  return Array.from(new Set(arr))
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
 //[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}]

// 简化
[...new Set(arr)]

2. for嵌套for,然后splice去重

{}和NaN无法去重

function unique(arr) {
	for (let i = 0; i < arr.length; i++) {
		for (let j = i + 1; j < arr.length; j++) {
			if (arr[i] === arr[j]) {
				arr.splice(j, 1)
				j-- // 删除一个数据,索引关系变了,需要减1找到没有判断过的那个数据
			}
		}
	}
	return arr
}

var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 0, 0, 'a', 'a', {}, {}]
console.log(unique(arr))  //NaN和{}没有去重

3. indexOf去重

function unique(arr) {
	if (!Array.isArray(arr)) {
		console.log('is not array')
		return
	}
	let array = []
	for (let i = 0; i < arr.length; i++) {
		if (array.indexOf(arr[i]) === -1) {
			array.push(arr[i])
		}
	}
	return array
}

var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 0, 0, 'a', 'a', {}, {}]
console.log(unique(arr))  // [1, "true", true, 15, false, undefined, null, NaN, NaN, "NaN", 0, "a", {…}, {…}]  //NaN、{}没有去重

4. includes去重

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
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
    console.log(unique(arr))
    //[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}, {…}]     //{}没有去重

5. filter + hasOwnProperty去重(可以去重所有)

function unique(arr) {
	let obj = {}
	return arr.filter((item, index, arr) => {
		console.log(typeof item + item)
		return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
	})
}

var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 0, 0, 'a', 'a', {}, {}]
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}]   //所有的都去重了

// if (obj.hasOwnProperty(typeof item + item)) {
// 	return false
// } else {
// 	obj[typeof item + item] = true
// 	return obj[typeof item + item]
// }

// if (!obj.hasOwnProperty(typeof item + item)) {
// 	obj[typeof item + item] = true
// 	return obj[typeof item + item]
// }

6. filter + indexOf

function unique(arr) {
	return arr.filter((item, index, arr) => {
    //当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
		return arr.indexOf(item,0) === index
	})
}

var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 0, 0, 'a', 'a', {}, {}]
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, "NaN", 0, "a", {…}, {…}]

Js进阶

深拷贝和浅拷贝的区别

拷贝对象

const obj1 = {
	age: 20,
	name: 'xxx',
	address: {
		city: 'beijing'
	},
	arr: ['a', 'b', 'c']
}

浅拷贝:

  • 只拷贝第一层的对象的属性
  • 是对对象地址的复制,并没有开辟新的栈
  • 复制的结果是两个对象指向同一个地址,修改其中一个对象的属性,则另一个对象的属性也会改变

for...in实现

function simpleCopy(obj) {
	// 判断结果是对象还是数组
	let result = Array.isArray(obj) ? [] : {}
	for (let i in obj) {
		// for...in遍历的是key
		// console.log(i)
		result[i] = obj[i]
	}
	return result
}
let obj2 = simpleCopy(obj1)
console.log(obj2)

Object.assign实现

let obj2 = Object.assign(obj1)
console.log(obj2)

obj2.address.city = 'shanghai'
obj2.arr[0] = 'a1'
console.log(obj1.address.city)
console.log(obj1.arr[0])

直接赋值

深拷贝:

  • 递归拷贝所有层级的属性,
  • 是开辟新的栈
  • 两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性

递归实现

function deepClone(obj) {
	// 判断是否是对象,不是对象,返回结果(返回值类型)
  // obj == null判断了null和undefined两种情况  null==undefined
	if (typeof obj !== 'object' || obj == null) {
		return obj
	}
	// 初始化结果数据,如果是数组赋值为[],否则为{}
	let result = Array.isArray(obj) ? [] : {}

	for (let key in obj) {
		// 判断是否是自己的属性方法,而不是原型属性方法
		// 如果是递归复制
		if (obj.hasOwnProperty(key)) {
			result[key] = deepClone(obj[key])
		}
	}
	return result
}

let obj2 = deepClone(obj1)
console.log(obj2)
obj2.address.city = 'shanghai'
obj2.arr[0] = 'a1'
console.log(obj1.address.city)
console.log(obj1.arr[0])
function deepClone(obj){
    let objClone = Array.isArray(obj)?[]:{};
    if(obj && typeof obj==="object"){
        for(key in obj){
            if(obj.hasOwnProperty(key)){
                //判断ojb子元素是否为对象,如果是,递归复制
                if(obj[key]&&typeof obj[key] ==="object"){
                    objClone[key] = deepClone(obj[key]);
                }else{
                    //如果不是,简单复制
                    objClone[key] = obj[key];
                }
            }
        }
    }
    return objClone;
}    
let a=[1,2,3,4],
    b=deepClone(a);
a[0]=2;
console.log(a,b);

Reflect法

function isObject(obj) {
	if (typeof obj !== 'object' || obj == null) {
		return false
	}
	return true
}

function deepClone(obj) {
	// 如果Reflect.ownKeys()方法的第一个参数不是对象,会报错。
	if (!isObject(obj)) {
		throw new Error('obj 不是一个对象')
	}
	let result = Array.isArray(obj) ? [...obj] : { ...obj }
	Reflect.ownKeys(result).forEach(key => {
		console.log(key)
		// 是对象进行递归遍历,不是则直接赋值
		result[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
	})
	return result
}

let obj2 = deepClone(obj1)
console.log(obj2)

JSON实现

缺点: 无法实现对对象中方法的深拷贝,会显示为undefined

function deepClone2(obj) {
  var _obj = JSON.stringify(obj),
    objClone = JSON.parse(_obj);
  return objClone;
}

lodash函数库实现深拷贝

let result = _.cloneDeep(test)

封装的通用 js 函数有哪些

判空、格式化日期、防抖节流、本地存储、axios二次封装、获取url参数、生成随机数、await处理promise返回参数、判断浏览器环境手机系统信息、校验函数

简单介绍下js垃圾回收机制

www.cnblogs.com/fundebug/p/…

介绍

一般来说没有被引用的对象就是垃圾,就是要被清除, 有个例外如果几个对象引用形成一个环,互相引用,但根访问不到它们,这几个对象也是垃圾,也要被清除。 基本的垃圾回收算法称为**“标记-清除”**,定期执行以下“垃圾回收”步骤:

  • 垃圾回收器获取根并**“标记”**(记住)它们。
  • 然后它访问并“标记”所有来自它们的引用。
  • 然后它访问标记的对象并标记它们的引用。所有被访问的对象都被记住,以便以后不再访问同一个对象两次。
  • 以此类推,直到有未访问的引用(可以从根访问)为止。
  • 除标记的对象外,所有对象都被删除。

js垃圾回收器的性能

因为js垃圾回收器是每隔一个周期就执行一次垃圾回收。 如果为变量分配的内存数量不大的话,那么垃圾回收器的回收工作量就不大。但是,当垃圾回收器的工作量过大的时候,就很可能会出现卡顿的情况。

js内存机制

event loop(事件循环/事件轮询)

什么是event loop

  • js是单线程运行的
  • 异步就要基于回调来实现
  • event loop就是异步实现的原理

event loop过程

整个过程包含

  • Call stack(调用栈)、
  • WebApis(浏览器api)、
  • Callback Queue(回调函数队列)、
  • event loop(事件轮询)

过程1

  • 同步代码一步一步放在call stack
  • 遇到异步,会先记录,等待时机(可能是同步代码执行完)
  • 时机到了,就移动到callback Queue

过程2

  • 如果同步代码执行完了,Event loop开始工作
  • 轮询查找callback queue,如有则移动到call stack执行
  • 然后继续轮询查找

Vue基础

1. vue 组件通信

2. vue 按需加载组件

  • 异步组件
  • import函数
  • 按需加载,异步加载大组件
<!-- 异步组件 -->
<FormDemo v-if="showFormDemo"/>
<button @click="showFormDemo = true">show form demo</button>

components: {
    FormDemo: () => import('../BaseUse/FormDemo'),
},
data() {
  return {
      showFormDemo: false,
  }
}
  • 按需加载路由组件
{
  path: '/home',
  name: 'Home',
  component: () => import(/* webpackChunkName: "tabbar" */ '@/views/tabBar/home/index.vue'),
  meta: { title: '首页', keepAlive: false, showTab: true } as IRouterMeta
},

3. 封装的通用组件有哪些

4. 组件如何实现 v-model

  • 自定义v-model
  • 主要是组件上的model属性(prop,event)
  • prop是要绑定的数据
  • event要绑定的事件
自定义 v-model -->
<!-- <p>{{name}}</p>
<CustomVModel v-model="name"/>
  
  
<template>
    <!-- 例如:vue 颜色选择 -->
    <input type="text"
        :value="text1"
        @input="$emit('change1', $event.target.value)"
    >
    <!--
        1. 上面的 input 使用了 :value 而不是 v-model
        2. 上面的 change1 和 model.event1 要对应起来
        3. text1 属性对应起来
    -->
</template>

<script>
export default {
    model: {
        prop: 'text1', // 对应 props text1
        event: 'change1'
    },
    props: {
        text1: String,
        default() {
            return ''
        }
    }
}
</script>

5. computed和watch的区别

计算属性computed

  1. 支持缓存,只有依赖数据发生改变,才会重新进行计算
  2. 不支持异步,当computed内有异步操作时无效,无法监听数据的变化 3.computed 属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值
  3. 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用computed 5.如果computed属性属性值是函数,那么默认会走get方法;函数的返回值就是属性的属性值;在computed中的,属性都有一个get和一个set方法,当数据变化时,调用set方法。

侦听属性watch

  1. 不支持缓存,数据变,直接会触发相应的操作; 2.watch支持异步; 3.监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;
  2. 当一个属性发生变化时,需要执行对应的操作;一对多;
  3. 监听数据必须是data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其他操作,函数有两个参数,   immediate:组件加载立即触发回调函数执行,   deep: 深度监听,为了发现对象内部值的变化,复杂类型的数据时使用,例如数组中的对象内容的改变,注意监听数组的变动不需要这么做。注意:deep无法监听到数组的变动和对象的新增,参考vue数组变异,只有以响应式的方式触发才会被监听到。 6.当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的

使用场景

computed          当一个属性受多个属性影响的时候就需要用到computed     最典型的例子: 购物车商品结算的时候 watch     当一条数据影响多条数据的时候就需要用watch     搜索数据

生命周期有什么,哪些在页面第一次加载执行

第一次渲染

  • beforeCreate
  • created
  • beforeMount
  • mounted

更新

  • beforeUpdate
  • updated

销毁

  • beforeDestroy
  • destroyed

keep-live

  • activated 组件激活
  • deactivated 组件停用

什么时候进行异步请求

  • mounted
  • dispatch一个action

vue修饰符有哪些

  • 表单修饰符
    • .lazy 懒加载
    • .trim 去除首尾空格
    • .number 转换成number
  • 事件修饰符
    • .stop 阻止冒泡
    • .prevent 阻止默认行为
    • .self 触发自身元素
    • .once 只触发一次
    • .capture 事件捕获阶段
    • .passive 懒触发事件
    • **.native **把一个vue组件转化为一个普通的HTML标签,触发事件
  • 鼠标按键修饰符
    • .left 左键点击
    • .right 右键点击
    • .middle 中键点击
  • 键值修饰符
    • **.keyCode **对应ASCII码
  • v-bind修饰符(实在不知道叫啥名字)
    • **.sync **对props进行双向绑定

data为什么是一个函数

  • .vue组件编译完之后实际上是一个Class
  • 每次使用这个组件相当于是对组件的实例化
  • 实例化的时候去执行data
  • 如果data不是一个函数,那么每一个组件实例的数据都一样的,也就数据共享了
  • 一个组件的数据进行修改时,其他组件数据也会进行修改

v-show和v-if的区别

  • v-show通过CSS display控制显示和隐藏
  • v-if组件真正的渲染和销毁,而不是显示和隐藏
    • 模板编译成render,with语法,转换成了三元运算
  • 频繁切换显示状态用v-show , 否则用v-if

v-for为什么用key

www.jianshu.com/p/4bd5e745c…

  • 必须用key ,且不能是index和random(随机数)
  • diff算法中通过tag和key来判断,是否是sameNode
  • 减少渲染次数,提升渲染性能

key重复了会怎么样

  • 如果key重复了,在进行增加删除操作时,索引和数组数据关系会错乱
  • diff算法会认为当前节点后的所有节点都进行了更新,会造成多次重复渲染

defineProperty重写了数组方法会不会影响正常使用数组方法

  • 不会
  • 通过Object.create()创建一个新的对象,原型指向数组原型
  • 扩展的数组方法执行时调用的是数组原型上的方法,以及视图更新

keep-live的属性和生命周期

  • include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
  • exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
  • max - 数字。最多可以缓存多少组件实例。

1.activated:页面第一次进入的时候,钩子触发的顺序是created->mounted->activated 2.deactivated :页面退出的时候会触发deactivated,当再次前进或者后退的时候只触发activated

components的name有什么用,场景

blog.csdn.net/weixin_3901…

  1. 当使用组件递归调用时,被递归调用的组件必须定义name属性,因为在组件里面调用自己时,不是使用的在components里注册的组件,而是使用根据name属性查找组件
  2. keep-alive包裹动态组件时,会缓存不活动的组件实例,会出现include和exclude属性,包含或者排除指定name组件
  3. 封装通用组件时,可以通过获取组件实例的name属性,定义为组件名字,方便管理
  4. vue-tools插件调试时没有name属性会报错或警告

如何进行用户鉴权,设计动态路由

juejin.cn/post/684490… addrouter的坑 blog.csdn.net/weixin_3417…

Vue原理

$nexttick内部实现

computed和watch内部实现原理

router的路由方式以及实现原理

  • hash - window.onhashchange
  • H5 history - history.pushState 和 window.onpopstate
  • H5 history 需要后端支持

event-bus实现原理,自己设计一个on,on,emit

性能优化

1. 性能优化有哪些

通用的性能优化:

  • 让加载更快:
    • 减少资源体积:代码压缩
    • 减少访问次数:合并代码,SSR服务器渲染,缓存(http缓存,本地缓存)
    • 使用更快的网络:CDN
  • 让渲染更快
    • CSS放在head,JS放在body最下面
    • 尽早执行JS,用DOMContentLoaded触发
    • 懒加载(图片懒加载,上滑加载更多,分页)
    • 对DOM查询进行缓存
    • 频繁DOM操作,合并到一起插入DOM结构
    • 防抖debounce和节流throttle

Vue性能优化

  • 合理使用v-show和v-if
  • 合理使用computed和watch
  • v-for时加唯一key,避免和v-if同时使用
  • 自定义事件、DOM事件及时销毁
  • 合理使用异步组件
  • 合理使用keep-live
  • data层级不要太深
  • 使用vue-loader在开发环境做模板编译
  • 使用SSR

WebPack性能优化

www.yuque.com/docs/share/… 《Webpack和babel面试题》

2. 防抖和节流的区别

www.jianshu.com/p/c8b86b09d… zhuanlan.zhihu.com/p/72923073 segmentfault.com/a/119000001…

防抖debounce

对于短时间内连续触发的事件(如滚动事件),防抖的含义就是让某个时间期限(如上面的1000毫秒)内,事件处理函数只执行一次。 所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间

  • 持续触发不执行
  • 不触发一段时间后再执行
<input type="text" id="input1">

const input1 = document.getElementById('input1')
// 不能用箭头函数
input1.addEventListener('keyup', debounce(function (e) {
    console.log(e.target)
    console.log(input1.value)
}, 600))


// 防抖
function debounce(fn, delay = 500) {
    // timer 是闭包中的
    let timer = null

    return function () {
        if (timer) {
            clearTimeout(timer)
        }
        timer = setTimeout(() => {
            fn.apply(this, arguments)
            timer = null
        }, delay)
    }
}

节流throttle

如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效。 节流的意思是让函数有节制地执行,而不是毫无节制的触发一次就执行一次。什么叫有节制呢?就是在一段时间内,只执行一次。

  • 持续触发并不会执行多少次
  • 到一定时间再去执行
<div id="div1" draggable="true">可拖拽<div>
  
const div1 = document.getElementById('div1')

div1.addEventListener('drag', throttle(function (e) {
    console.log(e.offsetX, e.offsetY)
}))

// 节流
function throttle(fn, delay = 100) {
    let timer = null

    return function () {
        if (timer) {
            return
        }
        timer = setTimeout(() => {
            fn.apply(this, arguments)
            timer = null
        }, delay)
    }
}

应用实例

resize、scroll、mousemove、输入框持续输入,将输入内容远程校验、多次触发点击事件

工程化

git rebase 和 git merge 有啥区别

www.jianshu.com/p/4079284dd…

手写题

大写转小写小写转大写

Array.prototype.map.call(str,a=>a.toUpperCase(a)==a?a.toLowerCase():a.toUpperCase()).join('');
function upLower(str) {
	return str.split('').map(item => {
		console.log(item)
		return item.toUpperCase() === item ? item.toLowerCase() : item.toUpperCase()
	})
}

console.log(upLower('ABc'))

算法题

1. 找出数组中出现次数最多的数

// 找出数组中出现次数最多的数
const arr = [1, 2, 3, 4, 5, 2, 1]
function fn(arr) {
	let map = new Map()
	let maxIndex = 0
	for (let i = 0; i < arr.length; i++) {
		if (!map.get(arr[i])) {
			map.set(arr[i], 1)
		} else {
			map.set(arr[i], map.get(arr[i]) + 1)
		}
		maxIndex = Math.max(maxIndex, map.get(arr[i]))
	}
	let result = []
	for (let [key, value] of map) {
		if (value === maxIndex) {
			result.push(key)
		}
	}
	return result
}

console.log(fn(arr))
  • 前端跨域的方式
  • cors
  • 数据劫持
  • diff算法,虚拟DOM真实DOM、
  • html5、css3新特性
  • es6新特性
  • webpack常见loader
  • 原型链
  • es5实现继承、es6实现、继承、
  • 2个手写代码:
    • 深拷贝
    • URL字符串转换成对象
  • 原理:Vue框架的东西问的比较多
  • 赋值组件通信
  • 组件定义钩子鉴定,怎么执行
  • promise原理
  • vuex是什么
  • 小程序的页面通信
  • 小程序的性能优化(页面加载太慢怎么办)
  • 小程序的数据存储方式
  • 小程序直播
  • 小程序的双向绑定和vue的双向绑定区别
  • 小程序用户鉴权方式(如何判断用户是否登陆)
  • token,token解析,token的加密方式
  • 你会用uniapp吗
  • cookie和storage的区别
  • set是用来做什么的
  • Generator 函数
  • promise和await的区别,解决了什么问题
  • ajax是通过什么api实现的
  • Reflect
  • 数组的方法有哪些,some和every的区别
  • vuex刷新页面之后没有数据了怎么办
  • 跨域如果不看控制台,如何判断
  • 公司团队规模
  • 公司的业务
  • 前端技术后端技术
  • 如何说服上司进行项目重构
  • 讲一个你觉得做的不错的项目
  • 项目开发流程
  • 你的主要工作
  • 如何进行项目部署
  • 性能监控,数据分析有没有做过
  • 团队之间的代码管理和审查
  • git用的merge还是base
  • 20000s不用api换算成时分秒 求模 求余
  • 技术选型你有做过哪些工作
  • Eslint使用的规范
  • v-for如果没有加key的话会报错吗
  • 你有做过哪些组件的开发
  • 使用第三方库封装的组件,你是如何保证它的灵活性的,比如富文本的封装
  • 如果一个搜索组件,我需要两个或多个输入框,如果保证灵活性
  • 传入options,遍历options然后渲染,这里面会不会有一些坑
  • Vue3和Vue2有什么区别
  • 什么是重排和重绘
  • flex有哪些属性,子元素flex的三个属性都是什么意思
  • flex处理列表换行
  • css中有哪些属性可以继承
  • Es6常用的属性方法
  • let、const和var的区别
  • 箭头函数和普通函数的区别
  • this指向问题
  • image.png
  • 使用asyn/await如何进行错误处理
  • 宏任务和微任务的区别
  • image.png
  • 常用的生命周期分别做什么
  • addEventListenter和传统的事件监听的区别
  • v-for的key有什么作用,用index行不行
  • 虚拟dom是什么