1. 页面中输入一个url到页面渲染经历哪些过程?
回答:
输入URL后,浏览器进行DNS解析,将域名转换为IP地址。
浏览器通过TCP三次握手与服务器建立连接。
浏览器向服务器发送HTTP请求。
服务器处理请求并返回HTTP响应。
浏览器解析HTML文档,构建DOM树。
浏览器加载外部资源,如CSS、JavaScript、图片等。
浏览器解析CSS,生成CSSOM树。
浏览器将DOM树和CSSOM树合并,生成渲染树。
浏览器进行布局计算,确定元素的位置和大小。
浏览器将渲染树绘制到页面上。
2. DNS解析是基于怎样的顺序的?
回答:
浏览器缓存
系统缓存(操作系统DNS缓存)
路由器缓存
ISP DNS服务器
根域名服务器
顶级域名服务器
权威域名服务器
3. 你平时开发的时候需要改host吗?
回答:
是的,在开发过程中,我有时需要修改hosts文件来映射域名到特定的IP地址,以便在本地环境进行调试或访问特定的服务器。
4.你觉得http和https之间什么区别?
回答:
HTTPS是HTTP的安全版,加入了SSL/TLS加密。
HTTPS需要数字证书,HTTP不需要。
HTTPS默认端口是443,HTTP默认端口是80。
HTTPS传输数据加密,比HTTP更安全。
5. 你觉得https是怎么做到安全传输的?
回答:
HTTPS通过以下方式实现安全传输:
对称加密:客户端和服务器协商加密密钥。
非对称加密:服务器使用公钥加密,客户端使用私钥解密。
数字证书:证明服务器身份,由第三方权威机构颁发。
SSL/TLS握手:协商加密算法和密钥。
6. 关于浏览器的缓存机制你有了解吗?
回答:
浏览器的缓存机制包括:
强缓存:通过Expires和Cache-Control,直接使用缓存数据。
协商缓存:通过Last-Modified和ETag,询问服务器数据是否更新。
7. 回流和重绘之间有什么区别?
回答:
回流(Reflow):元素位置、大小、布局变化,浏览器重新计算并绘制页面。
重绘(Repaint):元素样式变化,不涉及布局,浏览器重新绘制元素外观。
8. 让一个矩形的高度等于屏幕宽度的一半, 该怎样?
回答:
.rectangle {
height: 50vw; /* vw表示视口宽度的百分比 */
}
9. css布局, 一行只容纳三个, 换行向左对齐, 怎么实现?
回答:
.container {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
}
.item {
flex: 0 0 33.33%; /* 每个元素占容器的1/3宽度 */
}
10. 如何清除浮动的副作用?
回答:
使用额外标签法:在浮动元素的父元素末尾添加一个空标签,并设置样式
clear:both;
使用伪元素法:在浮动元素的父元素上添加伪元素,并设置样式
content:''; display:block; clear:both;
使用overflow属性:给浮动元素的父元素设置
overflow:hidden;
或overflow:auto;
使用clearfix类:创建一个clearfix类,包含伪元素清除浮动的样式,并将其应用到浮动元素的父元素上。
11. flex布局如设置整盒子之间的间距,都有什么区别?
回答:
在flex布局中设置盒子之间的间距,可以使用以下几种方法:
1. margin:直接在子元素上设置左右边距。
.item {
margin-right: 10px; /* 最后一个元素可能需要清除右边距 */
}
.item:last-child {
margin-right: 0;
}
2. justify-content:在父容器上设置,用于分配剩余空间。
.container {
justify-content: space-between; /* 平均分配剩余空间 */
}
3. gap:在父容器上设置,是CSS Grid布局的一部分,但也可以用于flex布局。
.container {
gap: 10px; /* 设置子元素之间的间距 */
}
- 区别在于:
margin直接作用于子元素,适用于个别元素需要特别间距的情况。
justify-content作用于整个flex容器,用于分配所有子元素之间的空间。
gap是较新的CSS属性,可以更简洁地设置所有子元素之间的间距。
12. var, let, const之间什么区别?
回答:
var声明的变量拥有函数作用域或全局作用域,存在变量提升。
let声明的变量拥有块作用域,不存在变量提升,但可以重新赋值。
const声明的变量拥有块作用域,不存在变量提升,且声明时必须初始化,且不能重新赋值。
13. 如何实现一个const不可更改?
回答:
在JavaScript中,const声明的变量本身是不可重新赋值的,但是如果是对象或数组,其属性或元素是可以被修改的。要实现一个完全不可更改的对象,可以使用Object.freeze()方法:
const obj = Object.freeze({ key: 'value' });
14. 箭头函数和普通函数的区别?
回答:
箭头函数没有自己的this,arguments,super或new.target。
箭头函数不能用作构造函数,不能使用new关键字。
箭头函数没有原型属性。
箭头函数的语法更简洁。
15.箭头函数和普通函数都是在何时确定this指向的?
回答:
普通函数的this指向在函数调用时确定,取决于调用方式。
箭头函数的this指向在函数定义时确定,继承自外围作用域。
16. 几个扩展运算符的基本应用?
回答:
展开数组或对象:[...array] 或 {...object}
函数调用时传递参数:myFunction(...args)
数组合并:[...array1, ...array2]
构建新数组,同时映射元素:array.map(x => [x, x * 2])
17. 能简单介绍一下事件循环机制吗?
回答:
JavaScript的事件循环机制是基于事件队列的。当执行栈为空时,事件循环会从任务队列中取出一个任务并执行。任务分为宏任务(如setTimeout, setInterval)和微任务(如Promise)。每个宏任务执行后,会执行所有的微任务队列中的任务,然后继续下一个宏任务。
18. 一道简单的事件循环的题,说执行顺序,并解释。
回答:
console.log(1);
setTimeout(() => {
console.log(2);
}, 0);
Promise.resolve().then(() => {
console.log(3);
});
console.log(4);
执行顺序:1 -> 4 -> 3 -> 2
解释:首先执行同步代码,打印1和4。然后执行微任务,打印3。最后执行宏任务,打印2。
19. 实现一个sleep函数?
回答:
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 使用
async function demo() {
console.log('等待前');
await sleep(2000);
console.log('等待后');
}
demo();
20. 数组循环的话map和forEach有什么区别?
回答:
map会返回一个新数组,其结果是对原始数组每个元素调用提供的一个函数后返回的结果组成的数组。
forEach不会返回新数组,它返回undefined。它直接对原始数组进行操作,通常用于执行一些操作而不需要返回值。
具体区别如下:
🎃返回值:map返回新数组,forEach不返回值。
🎄**可链式调用:**由于map返回新数组,因此可以继续链式调用其他数组方法,而forEach不可以。
🎍**用途:**map通常用于需要新数组的情况,比如转换数据结构;forEach用于执行副作用,比如直接修改外部变量或执行DOM操作。
示例代码:
// 使用 map
const numbers = [1, 2, 3];
const doubled = numbers.map(n => n * 2); // [2, 4, 6]
// 使用 forEach
numbers.forEach(n => console.log(n)); // 打印 1, 2, 3,但不返回新数组
21. 实现数组方法flat,并通过传参控制多层扁平化?
function customFlat(array, depth = 1) {
if (depth === 0) return array.slice();
return array.reduce((acc, val) => {
return acc.concat(Array.isArray(val) ? customFlat(val, depth - 1) : val);
}, []);
}
// 使用示例
const nestedArray = [1, [2, [3, [4]], 5]];
console.log(customFlat(nestedArray, 1)); // [1, 2, [3, [4]], 5]
console.log(customFlat(nestedArray, 2)); // [1, 2, 3, [4], 5]
console.log(customFlat(nestedArray, Infinity)); // [1, 2, 3, 4, 5]
22. 说一下Vue数据双向绑定原理?
Vue的双向绑定原理主要依赖于Object.defineProperty()函数,它通过以下步骤实现:
Observer:Vue会使用Observer对数据对象的所有属性进行监听,当这些属性发生变化时,能够立即知晓。
Dependency:每个被监听的属性都有一个Dependency实例,用于收集依赖于该属性的Watcher。
Watcher:当组件或指令用到数据对象的属性时,会创建一个Watcher实例,该实例会将自己添加到对应属性的Dependency列表中。
当数据变化时,Observer会通知Dependency,Dependency则会通知所有Watcher,Watcher会触发视图更新。
23. $set是如何实现的,具体用来解决什么问题?
$set是Vue提供的一个实例方法,用于解决对象新增属性不是响应式的问题。其实现原理如下:
Vue.prototype.$set = function (target, key, val) {
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key);
target.splice(key, 1, val);
return val;
}
if (key in target && !(key in Object.prototype)) {
target[key] = val;
return val;
}
const ob = target.__ob__;
if (!ob) {
target[key] = val;
return val;
}
defineReactive(ob.value, key, val);
ob.dep.notify();
return val;
};
$set解决了以下问题:
当向对象添加新属性时,新属性不会触发视图更新,因为Observer没有为该属性设置getter和setter。
通过$set,Vue可以手动触发依赖收集和派发更新,使得新属性也是响应式的。
24. 能详细的说一下,Vue是如何实现派发更新/收集依赖的吗?
Vue通过以下步骤实现派发更新和收集依赖:
- 收集依赖:
当组件渲染时,Watcher实例会在读取数据对象的属性时被创建。
每个属性都有一个Dependency实例,当属性被读取时,Dependency会记录当前的Watcher。
这样,每个属性都维护了一个Watcher列表,这些Watcher依赖于该属性。
- 派发更新:
当数据对象的属性被修改时,Observer会触发该属性的setter。
setter会通知Dependency,Dependency则会遍历其维护的Watcher列表,并调用每个Watcher的update方法。
Watcher的update方法会触发视图的重新渲染或执行用户定义的回调函数。
这个过程确保了当数据变化时,依赖于这些数据的视图或逻辑能够得到相应的更新。