前端面试题
css基础题
实现一个正方形的div,宽度是屏幕宽度(可视宽度)的一半
第一种方式: 50vw
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
.box {
width: 50vw;
height: 50vw;
background-color: skyblue;
}
</style>
</head>
<body>
<div class="box"></div>
</body>
</html>
第二种方式: js获取宽度,计算后设置
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
.box {
background-color: skyblue;
}
</style>
</head>
<body>
<div class="box"></div>
<script>
let oBox = document.querySelector('.box');
let halfClientWidth = document.body.clientWidth / 2;
oBox.style.width = halfClientWidth + 'px';
oBox.style.height = halfClientWidth + 'px';
//这种方式也行
// oBox.style.cssText = `width:${halfClientWidth}px;height:${halfClientWidth}px;`;
</script>
</body>
</html>
js基础题
如何区分基础数据类型和引用数据类型
可以使用typeof xxx !== ‘object’ 来判断是基础数据类型(null除外)
let num = 10;
let str = 'abc';
let isTrue = true;
let isNull = null;
let isUndefined = undefined;
let arr = [1, 2, 3];
let obj = { name: 'andy' };
console.log(typeof num); // number
console.log(typeof str); // string
console.log(typeof isTrue); // boolean
console.log(typeof isNull); // object
console.log(typeof isUndefined); // undefined
console.log(typeof arr); // object
console.log(typeof obj); // object
function judgeType(item) {
if (Array.isArray(item)) {
//数组类型
console.log('array');
} else if (item instanceof Object) {
//对象类型
console.log('object');
} else if (typeof item !== 'object') {
//基础数据类型
console.log(typeof item);
} else {
//null
console.log('null');
}
}
es5实现继承
js中继承的终极方法:
1.在子类的构造函数里面使用call去调用父类的构造函数(这样就可以将属性添加到子类上)
2.将子类的原型对象修改为父类的实例对象(可以父类原型中添加的方法和属性了)
<script>
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function () {
console.log('hello');
};
function Student(name, age, height) {
this.height = height;
Person.call(this, name, age);
}
Student.prototype = new Person();
Student.prototype.constructor = Student;
Student.prototype.run = function () {
console.log('run');
};
let stu = new Student('andy', 23, 178);
console.log(stu);
stu.run();
stu.say();
</script>
es6实现继承 extends + super();
<script>
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
run() {
console.log('run');
}
}
//使用extends关键字实现继承
class Student extends Person {
constructor(myName, myAge, myScore) {
super(myName, myAge); //调用父类的constructor构造函数
this.score = myScore;
}
study() {
console.log('study');
}
}
let stu = new Student('andy', 23, 99);
console.log(stu);
stu.study();
stu.run();
</script>
class 里面的static是什么意思
static 用来定义静态方法,只能通过类来调用,不能通过类的实例来调用
先看一下MDN的解释:
<script>
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
//实例方法必须写在constructor里面
this.hi = function () {
console.log('hi');
};
}
//在这里写的方法会添加到原型上
run() {
console.log('run');
}
//静态方法
static say() {
console.log('hello world');
}
}
let p = new Person('andy', 23);
console.log(p);
p.run();
Person.say();
</script>
如果需要定义静态属性,不可以使用static num = 666;这样的写法,因为不是所有浏览器都支持
可以这样来定义
//静态属性
Person.num = 666;
console.log(p.num); // undefined
console.log(Person.num);
es5的类和es6类的区别
- es5可以自定义原型,es6不可以自定义原型
function Person(name, age) {
this.name = name;
this.age = age;
this.hi = function () {
console.log('hi');
};
}
// Person.prototype.say = function(){
// console.log('say');
// }
//自定义原型对象
Person.prototype = {
constructor: Person,
run() {
console.log('run');
},
};
let p = new Person('andy', 23);
console.log(p);
p.run();
es6的类不允许自定义原型对象,只能动态的添加
<script>
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
this.hi = function () {
console.log('hi');
};
}
run() {
console.log('run');
}
}
Person.prototype.say = function () {
console.log('say');
};
// Person.prototype = {
// say() {
// console.log('say');
// },
// };
let p = new Person('andy', 23);
console.log(p);
p.run();
p.say();
</script>
获取对象类型
xxx.constructor.name
function Student(name) {
this.name = name;
}
let obj = new Object(); // Object
let arr = new Array(); //Array
let stu = new Student(); //Student
console.log(obj.constructor.name); //Object
console.log(arr.constructor.name); //Array
console.log(stu.constructor.name); //Student
Object.prototye.toString.call(xxx);
function Student(name) {
this.name = name;
}
let obj = new Object(); // Object
let arr = new Array(); //Array
let stu = new Student(); //Student
console.log(Object.prototype.toString.call(obj)); // [object Object]
console.log(Object.prototype.toString.call(arr)); // [object Array]
console.log(Object.prototype.toString.call(stu)); // [object Object]
为什么是这样呢?
console.log(Object.prototype.toString.call(stu)); // [object Object]
想一下我们在使用构造函数的时候,new一下发生了什么?
function Student(name) {
this.name = name;
}
function mockNew(fn,...args){
let obj = {};
obj.__proto__ = fn.prototype;
let res = fn.apply(args);
return res instanceof Object ? res : obj;
}
在使用new的时候第一步就是 let obj = {}; 所以这是正常的
console.log(Object.prototype.toString.call(stu)); // [object Object]
instanceof 和 isPrototypeOf
a instanceof B 判断a是不是B的实例对象(B只要是a原型链上的对是true)
c.isPrototypeOf(d) 判断c是不是d的原型对象 (c只要是d原型链上的都是true)
<script>
class Person {
constructor(name) {
this.name = name;
}
}
class Student extends Person {
constructor(name, age) {
super(name);
this.age = age;
}
}
let stu = new Student('andy', 23);
console.log(stu instanceof Student);
console.log(stu instanceof Person);
console.log(Student.prototype.isPrototypeOf(stu));
console.log(Person.prototype.isPrototypeOf(stu));
</script>
对象深拷贝
- JSON.parse(JSON.stringify(obj));
缺点:会丢失undefined 和 function
const target = {
field1: 'andy',
field2: null,
field3: undefined,
field4: ['andy', 'ted'],
field5: { age: 23, height: 178 },
field6() {
console.log(666);
},
};
const newObj = JSON.parse(JSON.stringify(target));
console.log(newObj);
- 手写深拷贝
const target = {
field1: 'andy',
field2: null,
field3: undefined,
field4: ['andy', 'ted'],
field5: { age: 23, height: 178 },
field6() {
console.log(666);
},
};
function clone(obj) {
if (obj && typeof obj === 'object') {
let cloneTarget = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneTarget[key] = clone(obj[key]);
}
}
//返回最终的对象
return cloneTarget;
} else {
return obj;
}
}
let res = clone(target);
console.log(res);
加一句代码 target.target = target;
const target = {
field1: 'andy',
field2: null,
field3: undefined,
field4: ['andy', 'ted'],
field5: { age: 23, height: 178 },
field6() {
console.log(666);
},
};
target.target = target;
function clone(obj, map = new Map()) {
if (obj && typeof obj === 'object') {
let cloneTarget = Array.isArray(obj) ? [] : {};
if (map.has(obj)) {
return map.get(obj);
}
map.set(obj, cloneTarget);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneTarget[key] = clone(obj[key], map);
}
}
//返回最终的对象
return cloneTarget;
} else {
return obj;
}
}
let res = clone(target);
console.log(res);
数组扁平化
概念:将一个多维数组变成一个一维数组
[1,2,3,[4,5,[6]]] --> [1,2,3,4,5,6]
- 方式1:reduce + concat
遍历数组的每一项,若值为数组则递归,如果是数值,直接concat
let arr = [1, 2, 3, [4, 5], [6, 7, 8, [9]]];
function flat(arr) {
return arr.reduce((previous, current) => {
return previous.concat(
Array.isArray(current) ? flat(current) : current
);
}, []);
}
console.log(flat(arr)); //[1,2,3,4,5,6,7,8,9]
- 递归 + concat
function flat(arr) {
let res = [];
arr.forEach((item) => {
if (Array.isArray(item)) {
res = res.concat(flat(item));//因为concat不改变原数组,所以这里要赋值
} else {
res.push(item);
}
});
return res;
}
- 扩展运算符(es6的扩展运算符能将二维数组变为一维)
...可以将二维数组变成一维数组 [1,2,3,[4,5]] --> [1,2,3,4,5]
function flat(arr) {
while (arr.some((item) => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
对象扁平化
const obj = {
a: 1,
b: [1, 2, { c: true }],
c: { e: 2, f: 3 },
g: null,
h: undefined
};
扁平化之后
{
a:1,
b[0]:1,
b:[1]:2,
b[2].c:true,
c.e:2,
c.f:3,
g:null,
h: undefined
}
flatten函数
思路:如果是引用类型就一层一层递归,最后返回result;
const obj = {
a: 1,
b: [1, 2, { c: true }],
c: { e: 2, f: 3 },
g: null,
h: undefined,
};
function flatten(obj) {
let result = {};
for (let key in obj) {
partition(result, key, obj[key]);
}
return result;
}
function partition(result, key, param) {
if (Array.isArray(param)) {
for (let index in param) {
partition(result, key + '[' + index + ']', param[index]);
}
} else if (param instanceof Object) {
for (let k in param) {
partition(result, key + '.' + k, param[k]);
}
} else if (typeof param !== 'object') {
result[key] = param;
} else {
result[key] = null;
}
}
let res = flatten(obj);
console.log(res);
函数防抖
一段时间内的多次触发,只执行最后一次触发
<body>
<input type="text" id="input" />
<script>
let oInput = document.querySelector('#input');
function debounce(fn, delay) {
let timer = 0;
return function (...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
oInput.addEventListener(
'input',
debounce((e) => {
console.log(e.target.value);
}, 800)
);
</script>
</body>
函数节流
一段时间内的多次触发,按照delay间隔执行
<body>
<div class="box" draggable="true"></div>
<script>
let oBox = document.querySelector('.box');
function throttle(fn, delay) {
let timer = 0;
return function (...arg) {
if (timer) return;
timer = setTimeout(() => {
fn.apply(this, arg);
timer = null;
}, delay);
};
}
oBox.addEventListener(
'drag',
throttle(function () {
console.log(this);
}, 200)
);
</script>
</body>
垃圾回收机制
什么是垃圾回收?
就是当函数执行完,需要回收那些再也用不到的一些对象和数据
- 引用计数法
缺陷:会存在循环引用的问题,现代浏览器已经不用这个算法 - 标记清除法
定期从window开始遍历各个属性,判断某个对象是不是可到达,回收不可到达的对象
闭包是内存泄漏吗
闭包不是内存泄露,闭包的数据不会被垃圾回收(符合我们预期的)
如何检测js内存泄露
检测内存泄露也就是检测内存变化,如果内存一直是持续升高的状态就说明是泄露了,如果内存的变化是有规律的升高、降低,那么就不是内存泄露
最常规的检测方法:控制台 --> perfermance -> 选中memory,录制一段时间的操作 -> 查看Heap的走势
heap折线图如果一只升高,则存在内存泄露
正常的heap情况:
Vue相关
内存泄露的场景(Vue为例)
- 被全局变量、全局函数引用,组件销毁时未清除
<template>
<p>memory leak</p>
</template>
<script>
export default {
name: 'MemoryLeak',
data() {
return {
arr: [1, 2, 3, 4],
};
},
methods: {},
mounted() {
//被全局变量、全局函数引用
window.arr = this.arr;
window.printArr = () => {
console.log(this.arr);
};
},
// vue2-beforeDestory
beforeUnmount() {
//组件销毁前要清除
window.arr = null;
window.printArr = null;
},
};
</script>
- 被全局事件、定时器引用,组件销毁时未清除
<template>
<p>memory leak</p>
</template>
<script>
export default {
name: 'MemoryLeak',
data() {
return {
arr: [1, 2, 3, 4],
intervalId: 0,
};
},
methods: {
printArr() {
console.log(this.arr);
},
},
mounted() {
//定时器
this.intervalId = setInterval(() => {
console.log(this.arr);
}, 200);
//全局事件
window.addEventListener('resize', this.printArr);
},
beforeUnmount() {
//组件销毁前一定要清除定时器
if (this.intervalId) {
clearInterval(this.intervalId);
}
//清除全局事件
window.removeEventListener('resize', this.printArr);
},
};
</script>
vueRouter 路由模式的区别
算法题
最大子数组和 leetcode 53
function maxSubArray(arr) {
if (arr.length === 1) return arr[0];
for (let i = 1; i < arr.length; i++) {
let lastItem = arr[i - 1];
if (lastItem > 0) {
arr[i] += lastItem;
}
}
return Math.max(...arr);
}
使用Array模拟Set leetcode 705
实现add 添加
实现remove 移除
实现contains 是否包含
实现getRandom 随机获取一个元素
<script>
class MySet {
constructor() {
this.map = new Map();
this.mySet = [];
}
add(key) {
if (this.map.has(key)) return;
this.mySet.push(key);
let index = this.mySet.length - 1;
this.map.set(key, index);
}
remove(key) {
if (this.map.has(key)) {
let index = this.map.get(key);
this.mySet.splice(index, 1);
this.map.delete(key);//删除key
}
}
getRandom() {
let random = Math.floor(Math.random() * this.mySet.length);
return this.mySet[random];
}
contains(key) {
return this.map.has(key);
}
}
let set = new MySet();
set.add(1);
set.add(2);
set.add(3);
set.add(4);
set.add(5);
console.log(set.contains(1)); //true
console.log(set.contains(2)); //true
console.log(set.contains(3)); //false
set.add(2);
console.log(set.contains(2)); //true
set.remove(2);
console.log(set.contains(2)); //false
console.log(set.getRandom()); //随机获取set值
</script>