- 本文是自己在面试时遇到的,面的都是十几k的职位
- 所有的答案都是我在翻阅资料,思考验证之后写出的,但能力有限,本人的答案未必是最优的,如果您有更好的答案,欢迎给我留言
- 本文答案也借用优秀答案,已经添加链接,如有侵权,请联系我删除
- 如果有错误或者不严谨的地方,请务必给予指正,以免误导他人
- 如果喜欢或者有所启发,欢迎点赞,对我也是一种鼓励
面面试,收获真的挺大的,感觉打开了前进的路, 建议看到题后自己先写一下试试,知其然还需知其所以然,把原理搞明白
最好重点看下js打印结果那些,考察的基础, 本来想着再花几天时间把下面那些js为什么打印解释下,但一想不如自己去花时间去找一下比较好,就没再细写,因为本文挺长的,更详细的会发到博客
js
1. 以 let str = 'abc' 实现reverse
let str = 'abc'
function rev(str) {
let arr = str.split('')
let tem = []
for (let i = arr.length-1; i >= 0; i--) {
tem.push(arr[i])
}
return tem.join('')
}
rev(str)
console.log(rev(str))
2. 针对 var arr = [3,4,5,22,1,3]; 写一个排序函数,对数组进行由小到大排序 (注意不要使用js内置的排序相关函数)
// 冒泡排序: 随便拿出一个数与后者比较,如果前者比后者大,那么两者交换位置,
let arr = [5,2,3,1,4]
function bubbleSort(arr) {
let arrLen = arr.length
for (let i = 0; i < arrLen; i++){
for (let j = i; j < arrLen; j++){
let tempi = arr[i];
let tempj = arr[j];
if (tempi > tempj) {
arr[i] = tempj;
arr[j] = tempi;
}
}
}
return arr
}
console.log(bubbleSort(arr))
/* 快速排序 :
* 1. 以一个数为基准(基准值可以任意选择,选择中间的值容易理解
* 2. 按照顺序,将每个元素与"基准"进行比较,形成两个子集,一个"小于3",另一个"大于等于3"。
* 3. 对两个子集不断重复第一步和第二步,直到所有子集只剩下一个元素为止
*/
let arr = [5, 2, 3, 1, 4]
function quickSort(arr) {
// 如果数组为1那就没必要比较了
if (arr.length <= 1) { return arr }
// 取中间索引值
let pivotIndex = Math.floor(arr.length / 2)
// 获取中间值
let pivot = arr.splice(pivotIndex, 1)[0]
let left = []
let right = []
for (let i = 0; i < arr.length; i ++) {
if(arr[i] < pivot) {
left.push(arr[i])
} else {
right.push(arr[i])
}
}
// 使用递归不断重复这个过程
return quickSort(left).concat([pivot], quickSort(right))
}
console.log(quickSort(arr))
3. 数组去重至少2种
// 1. from && set
let arr = [1, 1, 4, 50, 50, 6, 2, 2]
let res = Array.from(new Set(arr))
console.log(res)
// 2. filter && indexOf
let arr = [2, 2, 33, 44, 33]
let res = arr.filter((item, index, arr) => {
return arr.indexOf(item) == index
})
console.log(res)
// 3. forEach && includes
let arr = [2, 2, 33, 44, 33]
let res = []
arr.forEach((item) => {
if (!res.includes(item)) {
res.push(item)
}
})
console.log(res)
// 4. filter && Map
let arr = [2, 2, 33, 44, 33]
const tem = new Map();
let res = arr.filter((item) => !tem.has(item) && tem.set(item, 1))
console.log(res)
3. 去掉 var str = “ abc beijing ”;中的所有空格
let str = ' adb ddd '
let res = str.replace(/\s+/g, "")
console.log(res)
4. 判断字符串 var str = ‘abdcddsseeed’ 出现最多的字符,并统计出现次数
let str = 'asdasddsfdsfadsfdghdadsdfdgdasd';
function getMax(str) {
let obj = {};
for (let i in str) {
if (obj[str[i]]) {
obj[str[i]]++
} else {
obj[str[i]] = 1
}
}
let num = 0
let number = 0
for (var i in obj) {
//如果当前项大于下一项
if (obj[i] > num) {
//就让当前值更改为出现最多次数的值
num = obj[i]
number = i
}
}
console.log('最多的值是' +number+ '出现次数为' + num);
}
getMax(str);
5. js 实现继承
// 构造函数继承
function Super(){
this.name = ["张三","李四"]
}
function Sub(){
Super.call(this)
}
let a = new Sub();
a.name.push("王五")
// “张三","李四","王五"
console.log(a.name);
let b = new Sub()
//// “张三","李四"
console.log(b.name)
// Class 继承
class Fathter {
constructor(name) {
this.name = name
}
hello() {
console.log('Hello, ' + this.name)
}
}
class Son extends Fathter {
constructor(name) {
// 记得用super调用父类的构造方法
super(name)
console.log(name)
}
}
let res = new Son('张三')
res.hello()
6. 实现一个数据的深度拷贝,必问原生如何实现
- 简单粗暴, 但函数、正则、Symbol都没有被正确的复制
JSON.parse(JSON.stringify(obj));
- 考虑对象相互引用以及 Symbol 拷贝的情况
7. 各打印什么? 为什么?
function Foo(){
getName = function () {
console.log(1);
}
return this;
}
Foo.getName = function() {
console.log(2);
};
Foo.prototype.getName = function(){
console.log(3);
};
var getName = function(){
console.log(4);
};
function getName(){
console.log(5)
};
Foo.getName(); // 2
getName(); // 4
Foo().getName(); // 1
getName(); // 1
new Foo.getName(); // 2
new Foo().getName(); // 3 相当于 var f = new Foo() f.getName()
new new Foo().getName(); // 3
// 这道题相当经典了 花点时间自己去看下吧
8. 输出什么? 为什么
function fun() {
this.a = 0
this.b = function() {
console.log(this)
console.log(this.a)
}
}
fun.prototype = {
b: function() {
this.a = 20
console.log(this.a)
},
c: function() {
this.a = 30
console.log(this.a)
}
}
var my_fun = new fun();
//私有方法 this=>my_fun
my_fun.b();
console.log(my_fun.a);
//公有方法 this=>my_fun this.a = 30(私有属性a修改为30)
my_fun.c();
console.log(my_fun.a);
var my_fun2 = new fun();
console.log(my_fun2.a);
//this=>my_fun2.__proto__ 当前实例·通过原型链在共有属性增加的了一a:30
my_fun2.__proto__.c();
console.log(my_fun2.a);
console.log(my_fun2.__proto__.a);
// 0,0,30,30,0,30,0,30
9. 输出什么 为什么
function setName(obj){
obj.name = "Tom"
obj = new Object()
obj.name = 'Mike'
}
var person = new Object()
setName(person)
console.log(person.name) // "Tom"
10. 如何查找数组里的最大值或者最小值
// ES5 的写法
let arr = [ 2,4,5,1]
console.log(Math.min.apply(null, arr))
// ES6 的写法
console.log(Math.max(...[14, 3, 77, 30]))
11. javascript 12345678 改为 12,345,678, 也就是千位分隔
- 写不出来才可以这样写哈 要不面试官打你啊哈哈
let num = 12345678
console.log(num.toLocaleString())
- 求模
let num = 12345678
function toThousands(num) {
var result = '', counter = 0;
num = (num || 0).toString();
for (var i = num.length - 1; i >= 0; i--) {
counter++;
result = num.charAt(i) + result;
if (!(counter % 3) && i != 0) { result = ',' + result; }
}
return result;
}
console.log(toThousands(num))
- 正则
// 3-1
let num = 12345678
function toThousands(num) {
return (num || 0).toString().replace(/(\d)(?=(?:\d{3})+$)/g, '$1,');
}
console.log(toThousands(num))
// 3-2
let num = 12345678
function toCurrency(num){
var parts = num.toString().split('.');
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
return parts.join('.');
}
console.log(toCurrency(num))
12. 闭包问题
function f1(){
var n=999;
nAdd=function(){n+=1}
function f2(){
console.log(n);
}
return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000
13. 输出结果, 为什么
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
console.log(object.getNameFunc()()); // the window
14. 输出结果, 为什么
var length = 1
function fn() {
console.log(this.length)
}
var obj = {
length: 3,
me:function(fn){
fn() // 1
arguments[0]() // 2
}
}
obj.me(fn, 1)
15. let str = '你好,abc' 字符串长度
let str = '你好,abc'
function getLength(str){
let length = 0
let reg = /[\u4e00-\u9fa5]/
for (let i = 0; i < str.length; i++) {
if (reg.test(str.charAt(i))) {
length +=2
} else {
length ++
}
}
return length
}
console.log(getLength(str))
16. Object.defineProperty()有什么缺点 有什么方法
- 无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应。
- 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue里,是通过递归以及遍历data 对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象,不管是对操作性还是性能都会有一个很大的提升。
利用 Proxy
参考 -- 摒弃 Object.defineProperty,基于 Proxy 的观察者机制探索
17. prototype 和 proto 区别是什么
prototype是构造函数的属性。
proto 是每个实例都有的属性,可以访问 [[prototype]] 属性。
实例的__proto__ 与其构造函数的prototype指向的是同一个对象。
function Student(name) {
this.name = name;
}
Student.prototype.setAge = function(){
this.age=20;
}
let Jack = new Student('jack');
console.log(Jack.__proto__);
//console.log(Object.getPrototypeOf(Jack));;
console.log(Student.prototype);
console.log(Jack.__proto__ === Student.prototype);//true
。
18. 打印什么 为什么
var a = {n:1};
var b = a; // 持有a,以回查
a.x = a = {n:2};
console.log(a.x);// --> undefined
console.log(b.x);// --> {n:2}
19. 打印什么 为什么
var y = 1;
if (function f(){}) {
y += typeof f;
}
console.log(y); // 1undefined
20. 打印什么 为什么
var a = 0
function b(){
console.log(a) // fun a
a = 10
console.log(a) // 10
return;
function a(){}
}
b()
console.log(a) // 0
21. 说一下防抖和节流 什么场景下用
防抖
概念:
1. 在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
2. 多次触发事件后,事件处理函数只执行一次,并且是在触发操作结束时执行。
应用场景:
1. 文本输入的验证(连续输入文字后发送 ajax 请求进行验证,验证一次就好)
2. 给按钮加函数防抖防止表单多次提交。
生活实例:
如果有人进电梯(触发事件),那电梯将在10秒钟后出发(执行事件监听器),这时如果又有人进电梯了(在10秒内再次触发该事件),我们又得等10秒再出发(重新计时)。
代码
function debounce(func, interval = 100){
let timer;
return (...args) => {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
func.apply(func, args);
}, interval);
}
}
节流
概念:
规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效
应用场景:
滚动刷新如 touchmove, mousemove, scroll。
生活实例:(实例忘记参考谁的了,看到我添加连接)
1. 我们知道目前的一种说法是当 1 秒内连续播放 24 张以上的图片时,在人眼的视觉中就会形成一个连贯的动画,所以在电影的播放(以前是,现在不知道)中基本是以每秒 24 张的速度播放的,为什么不 100 张或更多是因为 24 张就可以满足人类视觉需求的时候,100 张就会显得很浪费资源
2. 假设电梯一次只能载一人的话,10 个人要上楼的话电梯就得走 10 次,是一种浪费资源的行为;而实际生活正显然不是这样的,当电梯里有人准备上楼的时候如果外面又有人按电梯的话,电梯会再次打开直到满载位置,从电梯的角度来说,这时一种节约资源的行为(相对于一次只能载一个人)。。
代码
function throttle(fn, interval = 100) {
let canRun = true; // 通过闭包保存一个标记
return (...args) => {
if (!canRun) return; // 在函数开头判断标记是否为true,不为true则return
canRun = false; // 立即设置为false
setTimeout(() => { // 将外部传入的函数的执行放在setTimeout中
fn.apply(fn, args)
// 最后在setTimeout执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。当定时器没有执行的时候标记永远是false,在开头被return掉
canRun = true;
}, interval);
};
}
22. 监测数组类型有几种方式 有什么区别
- instanceof
- instanceof不一定能保证检测的结果一定正确,因为只要是在原型链上的都会返回true ,arr instanceof Object 也返回true
- Array.isArray()
- 兼容性问题
- Object.prototype.toString.call()
- 较好方案
23. 观察者模式和发布订阅模式的写一下 说一下也行 区别是什么
24. 打印什么 为什么
function foo(a) {
var a;
return a;
}
function bar(a) {
var a = 'bye';
return a;
}
[foo('hello'), bar('hello')] // hello bye
// 在两个函数里, a作为参数其实已经声明了, 所以 var a; var a = 'bye' 其实就是 a; a ='bye'
25. 说一下原型链
原型链解决的主要是继承问题。
每个对象拥有一个原型对象,通过 proto (读音: dunder proto) 指针指向其原型对象,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null(==Object.proptotype.proto 指向的是null==)。这种关系被称为原型链 (prototype chain),通过原型链一个对象可以拥有定义在其他对象中的属性和方法。 构造函数 Parent、Parent.prototype 和 实例 p 的关系如下:(==p.proto === Parent.prototype==)
26 输出什么
var name = "World!";
(function() {
if(typeof name=== 'undefined'){
var name='Jack';
console.log('Goodbye'+name);
} else {
console.log('hello'+name);
}
})()
// Goodbye Jack
27. 一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
跳到 n 阶假设有 f(n)种方法。
往前倒退,如果青蛙最后一次是跳了 2 阶,那么之前有 f(n-2)种跳法; 如果最后一次跳了 1 阶,那么之前有 f(n-1)种跳法。
所以:f(n) = f(n-1) + f(n-2)。就是斐波那契数列
参考
28. 打印什么
class Chameleon {
static colorChange(newColor) {
this.newColor = newColor
return this.newColor
}
constructor({ newColor = 'green' } = {}) {
this.newColor = newColor
}
}
const freddie = new Chameleon({ newColor: 'purple' })
freddie.colorChange('orange')
// 会报错
colorChange是一个静态方法。静态方法被设计为只能被创建它们的构造器使用(也就是 Chameleon),并且不能传递给实例。因为 freddie 是一个实例,静态方法不能被实例使用,因此抛出了 TypeError 错误。
29. 打印什么 为什么
function* generator(i) {
yield i;
yield i * 2;
}
const gen = generator(10);
console.log(gen.next().value); // 10
console.log(gen.next().value); // 20
一般的函数在执行之后是不能中途停下的。但是,生成器函数却可以中途“停下”,之后可以再从停下的地方继续。当生成器遇到yield关键字的时候,会生成yield后面的值。注意,生成器在这种情况下不 返回 (return )值,而是 生成 (yield)值。
首先,我们用10作为参数i来初始化生成器函数。然后使用next()方法一步步执行生成器。第一次执行生成器的时候,i的值为10,遇到第一个yield关键字,它要生成i的值。此时,生成器“暂停”,生成了10。
然后,我们再执行next()方法。生成器会从刚才暂停的地方继续,这个时候i还是10。于是我们走到了第二个yield关键字处,这时候需要生成的值是i*2,i为10,那么此时生成的值便是20。所以这道题的最终结果是10,20。
30 打印什么
const num = parseInt("7*6", 10);
console.log(num) // 7
parseInt检查字符串中的字符是否合法. 一旦遇到一个在指定进制中不合法的字符后,立即停止解析并且忽略后面所有的字符。
31. 继承优缺点
32. class 实现队列 链表
33. array.sort如何实现的
34. 实现object.create
function create(proto) {
function F() {};
F.prototype = proto;
F.prototype.constructor = F;
return new F();
}
35. 说一下Reflect
36. 说一下generator
zhangxiang958-理解 ES6 generator
37 .说一下proxy
框架(vue)本人一直在用vue
1. mvvm
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div id='app'>
<h3>姓名</h3>
<p>{{name}}</p>
<h3>年龄</h3>
<p>{{age}}</p>
</div>
</body>
</html>
<script>
document.addEventListener('DOMContentLoaded', function(){
let opt = {el:'#app', data:{name:'检索中...', age:30}}
let vm = new Vue(opt)
setTimeout(() => {
opt.data.name = '王永峰'
}, 2000);
}, false)
class Vue{
constructor(opt){
this.opt = opt
this.observe(opt.data)
let root = document.querySelector(opt.el)
this.compile(root)
}
// 为响应式对象 data 里的每一个 key 绑定一个观察者对象
observe(data){
Object.keys(data).forEach(key => {
let obv = new Observer()
data["_"+key] = data[key]
// 通过 getter setter 暴露 for 循环中作用域下的 obv,闭包产生
Object.defineProperty(data, key, {
get(){
Observer.target && obv.addSubNode(Observer.target);
return data['_'+key]
},
set(newVal){
obv.update(newVal)
data['_'+key] = newVal
}
})
})
}
// 初始化页面,遍历 DOM,收集每一个key变化时,随之调整的位置,以观察者方法存放起来
compile(node){
[].forEach.call(node.childNodes, child =>{
if(!child.firstElementChild && /\{\{(.*)\}\}/.test(child.innerHTML)){
let key = RegExp.$1.trim()
child.innerHTML = child.innerHTML.replace(new RegExp('\\{\\{\\s*'+ key +'\\s*\\}\\}', 'gm'),this.opt.data[key])
Observer.target = child
this.opt.data[key]
Observer.target = null
}
else if (child.firstElementChild)
this.compile(child)
})
}
}
// 常规观察者类
class Observer{
constructor(){
this.subNode = []
}
addSubNode(node){
this.subNode.push(node)
}
update(newVal){
this.subNode.forEach(node=>{
node.innerHTML = newVal
})
}
}
</script>
2. computed 和 methods 区别
- computed 会基于响应数据缓存,methods不会缓存
- diff之前先看data里的数据是否发生变化,如果没有变化computed的方法不会执行,但methods里的方法会执行
3. webpack常用配置有哪些, 如何进行打包优化 , 原理是什么
提取公共模块,区分开发环境,移除重复不必要的 css 和 js 文件等方面说。
4. vuex数据流
5. 说一下数据双向绑定
6. key的作用
使用 v-for更新已渲染的元素列表时,默认用就地复用策略。列表数据修改的时候,他会根据key值去判断某个值是否修改:如果修改,则重新渲染这一项;否则复用之前的dom,仅修改value值。
7. 什么是函数式组件
函数式组件,即无状态,无法实例化,内部没有任何生命周期处理方法,非常轻量,因而渲染性能高,特别适合用来只依赖外部数据传递而变化的组件。
8. webpack 如何打包scss
9. template编译原理
关于 Vue 编译原理这块的整体逻辑主要分三个部分,也可以说是分三步,这三个部分是有前后关系的:
- 第一步是将 模板字符串 转换成 element ASTs(解析器)
- 第二步是对 AST 进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器)
- 第三步是 使用 element ASTs 生成 render 函数代码字符串(代码生成器)
其他
1. 说下你遇到的难题 讲下你的项目吧。不要说业务逻辑
该题是遇到问题最多的 根据自身情况回答
2. rem的原理 和 vw相比有什么区别
vw: 兼容性问题 ios8、安卓4.4及以上才完全支持
rem:和根元素font-size值强耦合,系统字体放大或缩小时,会导致布局错乱;弊端之二:html文件头部需插入一段js代码
3. 说下重绘和回流 怎么避免
参考yck面试经下面有链接
重绘和回流是渲染步骤中的一小节,但是这两个步骤对于性能影响很大。
- 重绘是当节点需要更改外观而不会影响布局的,比如改变 color 就叫称为重绘
- 回流是布局或者几何属性需要改变就称为回流。
回流必定会发生重绘,重绘不一定会引发回流。回流所需的成本比重绘高的多,改变深层次的节点很可能导致父节点的一系列回流。
- 改变 window 大小
- 改变字体
- 添加或删除样式
- 文字改变
- 定位或者浮动
- 盒模型
重绘和回流其实和 Event loop 有关
- 当 Event loop 执行完 Microtasks 后,会判断 document 是否需要更新。因为浏览器是 60Hz 的刷新率,每 16ms 才会更新一次。
- 然后判断是否有 resize 或者 scroll ,有的话会去触发事件,所以 resize 和 scroll 事件也是至少 16ms 才会触发一次,并且自带节流功能。
- 判断是否触发了 media query 更新动画并且发送事件
- 判断是否有全屏操作事件
- 执行 requestAnimationFrame 回调
- 执行 IntersectionObserver
- 回调,该方法用于判断元素是否可见,可以用于懒加载上,但是兼容性不好
- 更新界面
- 以上就是一帧中可能会做的事情。如果在一帧中有空闲时间,就会去执行 requestIdleCallback 回调。
4. 说下跨域 原理是什么、
5. 怎么判断页面是否加载完成?
Load 事件触发代表页面中的 DOM,CSS,JS,图片已经全部加载完毕。
DOMContentLoaded 事件触发代表初始的 HTML 被完全加载和解析,不需要等待 CSS,JS,图片加载。
6. cdn 的原理
由于用户访问源站业务有性能瓶颈,通过cdn技术把源站的内容缓存到多个节点。用户向源站域名发起请求时,请求会被调度至最接近用户的服务节点,直接由服务节点直接快速响应,有效降低用户访问延迟,提升可用性。
7. 从URL输入到页面展现到底发生什么
- DNS 解析:将域名解析成 IP 地址
- TCP 连接:TCP 三次握手
- 发送 HTTP 请求
- 服务器处理请求并返回 HTTP 报文
- 浏览器解析渲染页面
- 断开连接:TCP 四次挥手
说一下 import 和 require 的区别
参考import、require、export、module.exports 混合使用详解
css
1. 高度已知,三栏布局,左右宽度300,中间自适应
/* 浮动布局 */
.layout.float .left{
float:left;
width:300px;
background: red;
}
.layout.float .center{
background: yellow;
}
.layout.float .right{
float:right;
width:300px;
background: blue;
}
/* 绝对定位布局 */
.layout.absolute .left-center-right>div{
position: absolute;
}
.layout.absolute .left{
left:0;
width: 300px;
background: red;
}
.layout.absolute .center{
left: 300px;
right: 300px;
background: yellow;
}
.layout.absolute .right{
right:0;
width: 300px;
background: blue;
}
/* flex布局 */
.layout.flexbox{
margin-top: 110px;
}
.layout.flexbox .left-center-right{
display: flex;
}
.layout.flexbox .left{
width: 300px;
background: red;
}
.layout.flexbox .center{
flex:1;
background: yellow;
}
.layout.flexbox .right{
width: 300px;
background: blue;
}
2. 水平垂直居中
按照固定宽高和不固定宽高分类各说几个方法