css相关
1. margin和padding有什么不同?
作用对象不同。padding是作用于自身的,margin是作用于外面的。
2. vw与百分比有什么区别?
百分比有继承关系
vw只和设备(屏幕)的宽度有关系。
3. 行内元素与块级元素的区别
行内元素不换行,不可以设置大小,大小由内容决定
块级元素的宽度默认继承父级元素的宽度
4. 如何让谷歌浏览器支持小字体?(谷歌支持最小字体是12px,)
使用:transform: scale(0.8); -webkit-transform: scale(0.8);
js相关
1. let和var区别
声明提升、没有块级作用于、声明覆盖
2. 手写深拷贝
function deepClone (obj) {
// A instanceof B B的prototype是否在A的原型链上
if (obj === null) return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (typeof obj !== 'object') return obj;
// 构造器最大的指向 [] => Array(基类) {} => Object
// constructor属性引用了该对象的构造函数.对于Object对象,该指针指向原始的 Object() 函数
// 只有对象才有constructor, {}的constructor还是{},[]的constructor还是[]
const newObj = new obj.constructor();
for (let keys in obj) {
// 检测属性是否为对象的自有属性
if (obj.hasOwnProperty(keys)) {
newObj[keys] = deepClone(obj[keys]);
}
}
return newObj;
}
const person = {
a: 1,
b: [1, 2, 3],
c: {
d: 1,
e: [1, 2]
}
}
const newPerson = deepClone(person);
newPerson.a = 2;
newPerson.b[0] = 2;
console.log(person);
console.log(newPerson);
3. call apply bind的区别
回答: 1. bind不会立即执行函数,只会改变this指向,call和apply会立即实行函数且改变this指向
2. call和apply的区别在于传参,call的传参是对象,apply传参是数组
apply方法
apply接受两个参数,第一个参数是this的指向,第二个参数是函数接受的参数,以数组的形式传入,且当第一个参数为null、undefined的时候,默认指向window(在浏览器中),使用apply方法改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次。
call方法
call方法的第一个参数也是this的指向,后面传入的是一个参数列表(注意和apply传参的区别,多个参数)。当一个参数为null或undefined的时候,表示指向window(在浏览器中),和apply一样,call也只是临时改变一次this指向,并立即执行。
bind方法
bind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入,call则必须一次性传入所有参数),但是它改变this指向后不会立即执行,而是返回一个永久改变this指向的函数。
例子:fn.call({id: 1}); fn.apply([]);
4. 什么是闭包? 为什么要有闭包
答:闭包就是能够读取其他函数内部变量的函数。创建闭包最常见方式,就是在一个函数内部创建另一个函数,且子函数引用了父函数的变量。
a. 避免变量被污染
b. 私有化
c. 保存变量,常驻内存
5. 闭包的应用场景
防抖,节流,库的封装(保证数据私有性)
6. new关键字在new的一瞬间做了什么(const person = new Person())?
a. 创建一个空对象(let obj = new Object())
b. 设置它的原型链(obj._proto_ = Person.prototype)
c. 改变this指向(let result = Person.call(obj))
d. 判断返回值类型
if (typeof result === 'object') {
person = result;
} else {
person = obj;
}
7. const obj = Object.create(null) 和 const obj2 = {}有什么区别?
obj没有_proto_, 是一个纯对象。 而obj2有原型
8. 普通函数和构造函数的返回值是什么?
普通函数返回一个undefined,构造函数返回新创建的对象
9. 什么是事件委托?
事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
10. 原生JS实现事件委托
<ul>
<li>1</li>
<li>2</li>
</ul>
<button id="btn">按钮</button>
let ul = document.getElementById('ul');
ul.onclick = function (event) {
event = event || window.event;
const target = event.target;
// 规定就是大写
if (target.nodeName == 'LI') {
// 委托的事情
alert(target.innerHTML);
}
}
let btn = document.getElementById('btn');
btn.onclick = function () {
let li = document.createElement('li');
li.textContent = ul.children.length;
ul.appendChild(li);
}
11. 手写Promise
function myPromise (excutor) {
// 1. 执行结构
let self = this;
self.status = 'pending'; // 状态
self.value = null; // 成功结果
self.reason = null; // 失败原因
// 8. 添加缓存数组
self.onFulfilledCallbacks = [];
self.onRejectedCallbacks = [];
// 4. 判断状态
// 成功
function resolve (value) {
if (self.status === 'pending') {
self.value = value; // 保存成功结果
self.status = 'fulfilled';
// 10. 状态改变, 依次取出
self.onFulfilledCallbacks.forEach(item => item(value));
}
}
// 失败
function reject (reason) {
if (self.status === 'pending') {
self.reason = reason; // 失败的原因
self.status = 'rejected';
// 10. 状态改变, 依次取出
self.onRejectedCallbacks.forEach(item => item(reason));
}
}
// 3. 执行一遍
try {
excutor(resolve, reject);
} catch (err) {
reject(err);
}
}
// 2. then
myPromise.prototype.then = function (onFulfilled, onRejected) {
// 5. 状态改变 => 调用then方法
onFulfilled = typeof onFulfilled === 'function' ?
onFulfilled : function (data) {resolve(data)};
onRejected = typeof onRejected === 'function' ?
onRejected : function (err) {throw err};
let self = this;
// // 9. 先添加进去
// if (self.status === 'pending') {
// self.onFulfilledCallbacks.push(onFulfilled);
// self.onRejectedCallbacks.push(onRejected);
// }
if (self.status === 'fulfilled') {
return new myPromise((resolve, reject) => {
try {
let x = onFulfilled(self.value);
// 判断传进来的值是否是一个promise,是的话继续.then,否则抛出结果
x instanceof myPromise ? x.then(resolve, reject) : resolve(x);
} catch (err) {
reject(err);
}
});
}
if (self.status === 'rejected') {
return new myPromise((resolve, reject) => {
try {
let x = onRejected(self.value);
x instanceof myPromise ? x.then(resolve, reject) : resolve(x);
} catch (err) {
reject(err);
}
})
}
if (self.status === 'pending') {
return new myPromise((resolve, reject) => {
self.onFulfilledCallbacks.push(() => {
let x = onFulfilled(self.value);
x instanceof myPromise ? x.then(resolve, reject) : resolve(x)
})
self.onRejectedCallbacks.push(() => {
let x = onRejected(self.value);
x instanceof myPromise ? x.then(resolve, reject) : resolve(x);
})
})
}
}
myPromise.prototype.catch = function (fn) {
return this.then(null, fn);
}
let demo = new myPromise((resolve, reject) => {
console.log('各位同学很帅!');
// 6. 尝试异步
setTimeout(() => {
reject(11);
}, 500);
});
// 7. 异步失败
demo.then(data => console.log(data))
12. 前端跨域问题(首先,跨域不是问题,是一种安全机制)
由于同源策略(协议http,域名localhost,端口号8080都要一致)导致的
可以使用nginx处理,node webpack都可以
一. 后端处理方案(node):cors
const express = require('express');
const app = express();
// req浏览器请求对象 res浏览器响应对象
app.all('*', function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*'); // 允许所有请求来源
res.header('Access-Control-Allow-Headers', '*'); // 允许所有请求头
res.header('Access-Control-Allow-Methods', '*'); // 允许所有请求方法
next(); // 交给下一层处理
});
二. 前端处理方案
// 脚手架代理 webpack处理
webpack.config.js代码如下
// 脚手架配置
var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
module.export = {
mode: 'development',
devServer: {
// 自己配置脚手架代理
proxy: {
// 加api目的的为了匹配代理
'/api': {
target: 'http:localhost:3000',
pathRewrite: {'/api': ''} // 将'/api'替换成''
},
'/api1': {
target: 'http:localhost:3000',
pathRewrite: {'/api': ''} // 将'/api'替换成''
}
}
},
plugins: [
new HtmlWebpackPlugin({
template: 'index.html'
})
]
}
13. React的事件和原生标签的点击事件有什么区别?
React使用事件代理模式(绑定在根节点上),原生会绑定在具体的元素上,对性能不好
性能与网络相关面试题
1. TCP为什么需要三次握手?(TCP是全双工,客户端可以和服务端通信,服务端也可以和客户端通信)
答1(较正式回答):因为TCP是全双工,所以要经过 3 次交互才能确认双方的发送能力和接收能力,并且 TCP 握手必须是 3 次,如果是 2 次握手,不能证明服务器端的发送能力和客户端的接收能力(服务端不能确认客户端是否能接受到自己的信息);也不能是 4 次握手,因为 3 次已经能证明的事情,再交互握手 1 次完全没有必要。
答2(自总结回答):因为TCP是全双工,客户端需要确认服务端接收能力,服务端也要确认客户端的接收能力,如果只有2次握手,只能证明客户端发送能力和服务端接收能力,不能证明客户端接收能力,因此需要第三次握手确认。
2. TCP断开为什么要四次握手(四次挥手)?
因为TCP是双向对等传输,所有有两个方向的连接,需要两个FIN才能断开连接。
客户端向服务端发送FIN断开请求时,服务端回应ACK只是断开了客户端到服务端连接(服务端到客户端连接未断开),因此服务端还需要发FIN断开请求,客户端回复ACK则断开服务端到客户端连接。
3. 在浏览器中输入URL并回车后都发生了什么?
url -> DNS域名系统 -> 拿到真实IP -> 建立连接(TCP三次握手) -> 拿数据,渲染页面 -> 四次挥手
详细流程:
url: https://www.baidu.com
url => 统一资源定位符,俗称网址
url是IP的一个映射(比如https://www.baidu.com实际是去找192.168.x.x这种IP)
https:超文本传输协议(加密的传输协议,http和TCP之间加了一层TCL或者SSL的安全层)
www: 万维码(即服务器)
baidu.com:域名
第一次访问过程:
解析url
DNS域名系统匹配真实IP,下方是具体流程
url -> DNS域名系统 -> 拿到真实IP -> 建立连接(TCP三次握手) -> 拿数据,渲染页面 -> 四次挥手
第二次访问:
将域名解析的IP存在本地 => 读取浏览器缓存
4. 从哪些点做性能优化?
a. 加载
减少http请求(精灵图,文件的合并)
减小文件大小(资源压缩,图片压缩,视频压缩,代码压缩)
CDN(第三方库,大文件,大图)
SSR服务端渲染,预渲染
懒加载
分包
b. 减少dom操作,避免回流,文档碎片
回流(reflow):对于DOM结构中的各个元素都有自己的盒子模型,这些都需要浏览器根据各种样式(浏览器的、开发人员定义的等)来计算并根据计算结果将元素放到它该出现的位置,这个过程称之为reflow;
重绘(repaint):当各种盒子的位置、大小以及其他属性,例如颜色、字体大小等都确定下来后,浏览器于是便把这些元素都按照各自的特性绘制了一遍,于是页面的内容出现了,这个过程称之为repaint。
懒加载代码
<script>
let num = document.getElementsByTagName('img').length;
let img = document.getElementsByTagName('img');
let n = 0;
function lazyload() {
// 可见区域
let seeHeight = document.documentElement.clientHeight();
// 滚动距离
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
for (let i = n; i < num; i++) {
if (img[i].offsetTop < seeHeight + scrollTop) {
if (img[i].getAttibute("src") == "./img2/time.gif") {
img[i].src = img[i].getAttribute("data-src");
}
n = i + 1;
}
}
}
window.onscroll = lazyload();
</script>
5. 性能优化有哪些?
页面加载性能、动画与操作性能(首选translate和定位,脱离文档流对这个性能更好)、内存占用、电量消耗
笔试题
1.
var length = 10;
function fn () {
console.log(this.length); // window
}
var obj = {
length: 6,
method: function (fn) {
console.log(this); // 这个this指向obj
fn();
arguments[0](); // arguments输出后是arguments[0]是fn,对象.函数,this指向对象,arguments的length是2,因此这里输出2
}
}
obj.method(fn, 1);
答案:输出10,2(this指向上一个调用者,即对象.函数(), 函数的this就是指向对象)
2.
var j = {
a: 10,
b: {
a: 12,
fn: function () {
console.log(this.a);
}
}
}
j.b.fn();
答案:12 (这个this指向他的上一级b)
3.
var id = 66;
function fn5() {
setTimeout(() => {
console.log(this.id);
}, 500);
}
fn5({id: 22});
fn5.call({id: 22});
答案:66, 22(箭头函数没有作用域,所以没有this,这里的this指向上一层,所以this指向window)
4.
var length = 10;
function test () {
console.log(this.length);
}
var obj = {
length: 100,
action: function (test) {
test();
arguments[0]();
}
}
obj.action(test, 1, [2, 3], 4);
答案:10, 4
5.
var a = 10;
function test () {
console.log(a);
a = 100;
console.log(this.a);
var a;
console.log(a);
}
test();
// 详细分析:作用域问题 es5: 预解析:var function () {}
var a;
function () {};
a = 10;
// 逐行解析
test();
// 预解析
var a;
console.log(a);
a = 100;
console.log(this.a);
console.log(a);
// 特别注意:如果全局变量和局部变量同名,全局变量是不会作用于局部变量的作用域
答案:undefined, 10, 100;
6.
var a = 10;
function test () {
a = 100;
console.log(a);
a = 100;
console.log(this.a);
var a;
console.log(a);
}
test();
答案:100, 10, 100;
7.
var a = 10;
function test () {
console.log(a);
a = 100;
console.log(this.a);
console.log(a);
}
test();
答案:10, 100, 100;
8.
var a = 10;
function f1 () {
var b = 2 * a;
var a = 20;
var c = a + 1;
console.log(b);
console.log(c);
}
f1();
var a;
function f1 () {}
a = 10;
f1();
var b;
var a;
var c;
b = 2*a;
a = 20;
c = a + 1;
console.log(b); // NaN
console.log(c); // 21
答案:NaN,21
// Promise.all和Promise.race的区别
1. Promise.all
Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组(注意,返回的结果顺序与Promise.all接收的顺序是一样的,即使返回的结果更慢),而失败的时候返回最先被reject失败状态的值。
const res1 = new Promise((resolve, reject) => {
resolve("成功1");
});
const res2 = new Promise((resolve, reject) => {
resolve("成功2");
});
const res3 = new Promise((resolve, reject) => {
reject("失败");
});
// 也可以这样写
// const res3 = Promise.reject("失败");
// 全部成功时
Promise.all([res1, res2]).then(res => {
console.log(res); // ["成功1", "成功2"]
}).catch(err => {
console.log(error);
});
// 存在失败的情况时
Promise.all([p3, p1, p2]).then(res => {
console.log(res);
}).catch(err => {
console.log(err); // "失败"
});
2. Promise.race
Promise.race([res1, res2, res3])里面哪个结果获得快,就返回哪个结果,不管结果本身是成功还是失败状态。
const res1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success');
}, 1000);
});
const res2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('failed');
}, 500);
});
Promise.race([p1, p2]).then(res => {
console.log(res);
}).catch(err => {
console.log(err); // 打开的是failed
});
// 还在写
usecallback usememo
虚拟dom
类数组怎么转换数组
可选链怎么用
数组去重
前端自适应方案
hook diff
react diff算法
useReducer
类组件和函数组件最大的区别,什么时候使用
ES6的新特性了解多少,说几个常用的
Redux
Webpack
排序算法
防抖节流有什么不一样
常见的网络攻击
前端优化方案
HTML meta属性干啥用的
● 你的性能优化指标是怎么确定的?平均下来时间减短了多少?
● 你的性能是如何测试的?有两种主流的性能测试⽅法你是怎么选的?
● 你是根据哪些指标进⾏针对性优化的?
● 你的这个优化⽅法在实际操作中碰到过什么问题吗?有没有进⼀步做过测试?