前端基础面试题(Css,Html,Js,Ts)
CSS
1. CSS是什么
CSS(Cascading Styles Sheets,层叠样式表)是一种用于描述网页样式的语言,主要作用是控制网页的外观和布局,其功能包括:设置样式,布局和定位,动画效果,响应式设计等。
2. CSS引用方式
- 行内样式:
<p style="font-size: 12px; ">行内样式:</p> - 嵌入样式:
<style type="text/css"> h1 {font-size:16px;} </style> - 链接样式:
<link href="styles.css" rel="stylesheet" type="text/css" /> - @import指令:
@import url(css/styles2.css) - src引入:
<style src="./styles.css"></style>(通常用于模块化中各模块引入样式)
link引用和@import的区别:
- 使用场景:
<link>在 HTML 文件中直接引入样式;@import在 CSS 文件或<style>中引入其它样式文件。 - 加载并行性:
<link>可并行加载;@import通常串行,可能阻塞并降低性能。 - 兼容性:
<link>兼容更好;早期浏览器对@import支持不佳。 - 优先级与可控性:
<link>可通过属性控制媒体类型与预加载;@import控制较弱。 - 维护与管理:
@import便于在 CSS 中组织模块,但在现代前端更推荐构建工具或预处理器替代。
link引入:
<head><link rel="stylesheet" href="/styles/base.css"><link rel="stylesheet" href="/styles/theme.css" media="(prefers-color-scheme: dark)">
</head>
@import引入(css文件中引入):
/* 在 base.css 顶部 */
@import url("/styles/reset.css");
@import url("/styles/theme.css") screen and (min-width: 768px);
3. CSS三大特性
css 三大特性:层叠、继承、优先级。
- 层叠性:行内、嵌入、外链等应用到某个元素时,按照特定的规则叠加确定最终使用哪个值。
- 继承性:某些样式可以被子元素继承,简化样式、提高代码重用性,优化代码量。
- 优先级:!important 最大,来源优先级(行内-嵌入-外链),选择器优先级(id>class>element)
4. CSS 选择器类型,以及优先级
!important:最大优先级;- ID 选择器:如
#idName; - 类选择器:如
.className; - 元素选择器:如
p,div等; - 属性选择器:如
[type="text"]; - 伪类:一个冒号(:)表示伪类,如
:hover、:first-child、:nth-child(n)等; - 伪元素:两个冒号(::)表示 CSS3 新增的伪元素,如
p::first-letter;
5. CSS 元素哪些可以继承,哪些不行
- 可继承:主要文本相关属性,如文字颜色与大小(color、font-size等),列表样式(list-style),可见性(visibility);
- 不可继承:大多数 CSS 属性是不可继承的,比如宽高、定位,背景相关属性(background);
- 控制继承:强制继承父元素(color: inherit;),强制设置默认值(color: initial;),父元素有就继承,没有使用默认值(color: unset;)
6. 常用的布局方案
- table:数据表格,邮件等;
- 传统布局:盒模型、清除浮动、居中、BFC 等;
- flex:一维布局,居中、响应式等;
- grid:二维布局;
- position 布局:relative 相对谁,居中。
- 响应式布局:媒体查询
@media screen and (min-width: 768px) and (max-width: 1024px),em、rem、vw、vh等;
em:相对父元素的字体大小,适合细致控制间距和排版。
rem:相对根元素的字体大小,使用更为一致,便于管理全局样式。
vw:根据视口宽度调整,适合响应式布局。
vh:根据视口高度调整,适合适应屏幕的元素高度
7. CSS 盒子模型
CSS 盒子模型包含内容区域(Content)、内边距(padding)、边框(border)和外边距(margin)。盒子模型的总宽度和高度的计算方式取决于使用的盒子模型的类型box-sizing: content-box|border-box|inherit;。外边距由较宽的外边距决定两个元素最终离多远。

- content-box: 默认值。总宽度 = width + padding-left + padding-right + border-left + border-right;
- border-box :总宽度 = width
- inherit 指定 box-sizing 属性的值,应该从父元素继承
8. 浮动
浮动属性允许元素向左(left)或右(right)浮动,使其在其包含块的文本流周围。一般来说,浮动的元素都会被从其原本的流位置移除。
浮动问题:
- 脱离文档流,导致布局问题。
- 父容器高度塌陷(即父元素无法包围浮动的子元素)。
清除浮动: - 为父元素添加 overflow:hidden,BFC;
- 方法二:浮动后面添加
<div style="clear: both;"></div> - 方法三:伪元素清除浮动
.container::after {content: '';display: block;clear: both;
}
9. BFC
BFC 全称:Block Formatting Context, 名为 “块级格式化上下文”。作用有 3 条
- 包含内部浮动:解决 float 引起的父容器高度塌陷问题;
- 排除外部浮动: 解决重叠问题,设置自适应两栏布局。
- 阻止外边距重叠:避免margin重叠问题.
怎样触发 BFC: - overflow: hidden
- display: inline-block
- position: absolute
- position: fixed
- display: table-cell
- display: flex
10. 居中对齐
- 块级元素: margin: auto;
- 行内元素:text-align: center;
- 绝对定位:
position: absolute; left: 50%; top: 50%;transform: translate(-50%, -50%);; - Flex 居中:
display: flex;justify-content: center; /* 主轴(水平方向默认) */align-items: center; /* 交叉轴(垂直) */; - Grid 居中:
display: grid;justify-self: center;align-self: center;
11. rgba和opacity的透明效果有什么不同
opacity:opacity是一个属性。opacity属性的值,可以被其子元素继承,给父级div设置opacity属性,那么所有子元素都会继承这个属性,并且,该元素及其继承该属性的所有子元素的所有内容透明度都会改变。
rgba(0,0,0,0.5):rgba是一个属性值。rgba设置的元素,只对该元素的背景色有改变,并且,该元素的后代不会继承该属性。
补充:rgba只是一个属性值,在background 里用改变背景色,在color里是改字体颜色,shadow里是改阴影色,不止是能够改元素的背景色,要看具体是在哪个属性上用
12. display:none与visibility:hidden的区别
这两个属性都是让元素隐藏,不可见。两者区别如下:
(1)在渲染树中:
- display: none 会让元素完全从渲染树中消失,渲染时不会占据任何空间;
- visibility: hidden 不会让元素从渲染树中消失,渲染的元素还会占据相应的空间,只是内容不可见。
(2)是否是继承属性
- visibility: hidden 是继承属性,子孙节点消失是由于继承了hidden,通过设置visibility: visible可以让子孙节点显示;
- display: none 是非继承属性,子孙节点会随着父节点从渲染树消失,通过修改子孙节点的属性也无法显示;
(3)修改常规文档流中元素的 display 通常会造成文档的重排,但是修改visibility属性只会造成本元素的重绘;
(4)如果使用读屏器,设置为display:none的内容不会被读取,设置为visibility:hidden的内容会被读取。
(5)display:none 隐藏对应的元素,在文档布局中不再给它分配空间,它各边的元素会合拢,就当他从来不存在。
(6)visibility:hidden 隐藏对应的元素,但是在文档布局中仍保留原来的空间。
13. 定位布局 position中的relative、absolute、fixed、sticky它们之间的区别
- relative: 相对定位,相对于自己本身在正常文档流中的位置进行定位。
- absolute: 生成绝对定位,相对于最近一级定位不为static的父元素进行定位。
- fixed: 生成绝对定位,相对于浏览器窗口或者frame进行定位。
- static: 默认值,没有定位,元素出现在正常的文档流中。
- sticky: 生成粘性定位的元素,容器的位置根据正常文档流计算得出。
14. CSS3
CSS 第三个版本,css3 提供了一系列新特性和功能,比如动画、渐变、圆角、阴影等功能,还有响应式设计、模块化设计等。
新特性:
- 选择器:新增的选择器如 :nth-child()、:nth-of-type()、:not() 等。
- 背景:支持多个背景层、背景大小、背景修饰等。
- 边框:圆角边框(border-radius)、阴影(box-shadow)等。
- 文本:文本阴影(text-shadow)、字体平滑(font-smoothing)等。
- 渐变:线性渐变(linear-gradient)和径向渐变(radial-gradient)。
- 动画:关键帧动画(@keyframes)和过渡(transition)。
- 媒体查询:用于响应式设计,能够根据设备的不同特性调整样式。
15. CSS 预处理器
CSS 预处理器为 CSS 的编写提供了增强功能,如变量、嵌套、混合、继承等。比如:less 和 sass
- Less 是基于 JavaScript,是在客户端处理的。
- Sass 是基于 Ruby 的,是在服务器端处理的。
- 关于变量在 Less 和 Sass 中的唯一区别就是 Less 用@,Sass 用$。
- 输出设置,Less 没有输出设置,Sass 提供 4 中输出选项:nested, compact, compressed 和 expanded。
16. CSS 优化
CSS 优化是指通过一些技术和最佳实践来提高 CSS 的效率、可维护性和加载性能,从而改善网页的响应速度和用户体验。以下是一些常用的 CSS 优化技巧:
- 合并和最小化 CSS 文件
- 使用 CSS 预处理器
- 减少选择器的复杂性,以提高匹配速度。
- 使用简短的属性名,来减少代码量
- 避免使用过多的通用选择器:避免使用通用选择器,因为它们会遍历所有的 DOM 元素。
- 缓存静态资源
- 响应式设计使用 media 属性进行条件加载
- 使用 CSS 图像精灵,将多个小图像合并为一张大图
- 避免使用 CSS 表达式
- 对于动画和变化,可以使用 transform 和 opacity 属性来利用 GPU 加速,减少重排和重绘的次数。
- 使用 CSS variables 来定义重复的值,使样式表更加灵活和可维护。
- 将关键 CSS(首屏渲染所需的 CSS)内联到 HTML 中,或提取到一个小的 CSS 文件中,以减小首次渲染时间。
HTML
1. HTML5 新增标签
- 布局标签(语义化标签)(
<header><nav><article><section><aside><footer>) - 视频标签(video)
- 音频标签(audio)
新的API
- 音视频:audio 和 video 元素
- 绘图图形:canvas 元素
- 本地存储:localStorage,sessionStorage
- 多线程操作:Web Worker (Web Worker 是HTML5 新特性,允许我们在 js 主线程之外开辟新线程,并将一段 js 脚本运行其中,它赋予了开发者利用 js 操作多线程的能力 )
语义化的优点如下:
- 在没有CSS样式情况下也能够让页面呈现出清晰的结构
- 有利于SEO和搜索引擎建立良好的沟通,有助于爬虫抓取更多的有效信息,爬虫是依赖于标签来确定上下文和各个关键字的权重
- 方便团队开发和维护,语义化更具可读性,遵循W3C标准的团队都遵循这个标准,可以减少差异化
2. localStorage、sessionStorage、cookie 三者区别对比
- localStorage
- 生命周期:关闭浏览器后数据依然保留,除非手动清除,否则一直在
- 作用域:相同浏览器的不同标签在同源情况下可以共享localStorage
- sessionStorage
- 生命周期:关闭浏览器或者标签后即失效
- 作用域:只在当前标签可用,当前标签的iframe中且同源可以共享
- cookie
- 是保存在客户端的,一般由后端设置值,可以设置过期时间
- 储存大小只有4K
- 一般用来保存用户的信息的
- 在http下cookie是明文传输的,较不安全
- cookie属性有
- http-only:不能被客户端更改访问,防止XSS攻击(保证cookie安全性的操作)
- Secure:只允许在https下传输
- Max-age: cookie生成后失效的秒数
- expire: cookie的最长有效时间,若不设置则cookie生命期与会话期相同
3. 回流重绘
- 回流
- render树中一部分或全部元素需要改变尺寸、布局、或着需要隐藏而需要重新构建,这个过程叫做回流
- 回流必将引起重绘
- 重绘
- render树中一部分元素改变,而不影响布局的,只影响外观的,比如颜色。该过程叫做重绘
- 页面至少经历一次回流和重绘(第一次加载的时候)
4. 从浏览器输入url后都经历了什么
- 先进行DNS域名解析,先查看本地hosts文件,查看有没有当前域名对应的ip地址,若有直接发起请求,没有的话会在本地域名服务器去查找,该查找属于递归查找,如果本地域名服务器没查找到,会从根域名服务器查找,该过程属于迭代查找,根域名会告诉你从哪个与服务器查找,最后查找到对应的ip地址后把对应规则保存到本地的hosts文件中。
- 如果想加速以上及之后的http请求过程的话可以使用缓存服务器CDN,CDN过程如下:
- 用户输入url地址后,本地DNS会解析url地址,不过会把最终解析权交给CNAME指向的CDN的DNS服务器
- CDN的DNS服务器会返回给浏览器一个全局负载均衡IP
- 用户会根据全局负载均衡IP去请求全局负载均衡服务器
- 全局负载均衡服务器会根据用户的IP地址,url地址,会告诉用户一个区域负载均衡设备,让用户去请求它。
- 区域负载均衡服务器会为用户选择一个离用户较近的最优的缓存服务器,并把ip地址给到用户
- 用户想缓存服务器发送请求,如果请求不到想要的资源的话,会一层层向上一级查找,知道查找到为止。
- 进行http请求,三次握手四次挥手建立断开连接
- 服务器处理,可能返回304也可能返回200
- 返回304说明客户端缓存可用,直接使用客户端缓存即可,该过程属于协商缓存
- 返回200的话会同时返回对应的数据
- 客户端自上而下执行代码
- 其中遇到CSS加载的时候,CSS不会阻塞DOM树的解析,但是会阻塞DOM树的渲染,并且CSS会阻塞下面的JS的执行
- 然后是JS加载,JS加载会影响DOM的解析,之所以会影响,是因为JS可能会删除添加节点,如果先解析后加载的话,DOM树还得重新解析,性能比较差。如果不想阻塞DOM树的解析的话,可以给script添加一个defer或者async的标签。
- defer:不会阻塞DOM解析,等DOM解析完之后在运行,在DOMContentloaed之前
- async: 不会阻塞DOM解析,等该资源下载完成之后立刻运行
- 进行DOM渲染和Render树渲染
- 获取html并解析为Dom树
- 解析css并形成一个cssom(css树)
- 将cssom和dom合并成渲染树(render树)
- 进行布局(layout)
- 进行绘制(painting)
- 回流重绘(回流必将引起重绘,重绘不一定引起回流)
JavaScript
1. JS数据类型
- JS数据基础类型:String、Number、Boolean、Null、undefined、Symbol(ES6)、BigInt(ES6)
- 基本数据类型的数据直接存储在栈中。
- JS引用数据类型:Object(对象)、Array(数组)、function(函数)
- 引用数据类型的数据存储在堆中,每个对象在堆中有一个引用地址。
- 引用类型在栈中会保存他的引用地址,以便快速查找到堆内存中的对象。
2. 数据类型判断
- typeof —— 快速判断原始类型与函数
- 优点:语法简单,适合判断基础类型和函数。
- 缺点:对null和对象子类型不精确(数组、日期等都会是“Object”),NaN也是“number”,在JavaScript中,不同的对象都是使用二进制存储的,如果二进制前三位都是0的话,系统会判断为是Object类型,而null的二进制全是0,自然也就判断为Object。
console.log(typeof true); // "boolean"
- instanceof —— 判断实例与构造函数的原型链关系
- 优点:可判断对象是否由某构造函数创建(或原型链继承)。
- 缺点:跨 iframe/realm 失效;对原始类型无效;可被自定义原型链影响。
console.log([] instanceof Array); // true
- Object.prototype.toString.call() —— 精确识别内建类型
- 优点:最稳定、跨 iframe 也可靠;能区分数组、日期、正则、错误、Promise 等。
- 缺点:稍显啰嗦。
Object.prototype.toString.call([]); // [object Array]
3. ==和===的区别
===是严格意义上的相等,会比较两边的数据类型和值大小
- 数据类型不同返回false
- 数据类型相同,但值大小不同,返回false
== 是非严格意义上的相等
- 两边类型相同,比较大小
- 两边类型不同,根据下方表格,再进一步进行比较。
4. 箭头函数与普通函数的区别
- this 绑定:箭头函数没有自己的 this,词法绑定外层 this, call()、apply()、bind()等方法不能改变箭头函数中this的指向 ;普通函数的 this 由调用方式决定(默认、对象调用、new、call/apply)。
- arguments:箭头函数没有 arguments,可用剩余参数 …args 替代;普通函数有类数组 arguments。
- 原型与构造:箭头函数没有 prototype,不能作为构造函数 new;普通函数有 prototype,可 new。
- super/new.target:箭头函数没有自己的 super 和 new.target,取自外层;普通函数在类/构造中有自己的。
- 绑定能力:箭头函数不受 bind/call/apply 改变 this 的影响(this 已固定),普通函数会受影响。
- 语法与返回:箭头函数支持表达式隐式返回;普通函数必须显式 return。
- 适用场景:箭头函数适合回调、闭包、保持外层 this 的场景;普通函数适合方法、构造器、需要动态 this 的场景。
5. js的call、apply和bind区别
这三个方法都用于改变函数执行时的 this 指向(传入的第一个参数都是绑定this的指向),但行为与调用时机不同:
- call:立即调用,参数按序传入。
- apply:立即调用,参数以数组(或类数组)传入。
- bind:不立即调用,返回一个新的“绑定了 this 和部分参数”的函数,可稍后调用或作为回调使用。
| 方法 | 是否立即执行 | 参数形式 | 返回值 | 典型用途 |
|---|---|---|---|---|
| call | 是 | 零散参数:fn.call(thisArg, a, b, c) | 函数执行结果 | 立即以指定 this 调用 |
| apply | 是 | 数组或类数组:fn.apply(thisArg, [a, b, c]) | 函数执行结果 | 立即以指定 this 调用 |
| bind | 否 | 先绑定零散参数:fn.bind(thisArg, a, b) | 新函数(绑定 this/参数) | 作为回调、事件处理,保持 this |
注意:在箭头函数中,this 是词法绑定,call/apply/bind 无法改变箭头函数的 this。
function greet(greeting, punctuation) {return `{this.name}${punctuation}`;
}const person = { name: "Alice" };// call:立即执行,参数逐个传
console.log(greet.call(person, "Hello", "!")); // "Hello, Alice!"// apply:立即执行,参数数组传
console.log(greet.apply(person, ["Hi", "!!"])); // "Hi, Alice!!"// bind:不执行,返回新函数
const greetAlice = greet.bind(person, "Hey");
6. 字面量创建对象和new创建对象的区别
字面量:
- 字面量创建对象更简单,方便阅读
- 不需要作用域解析,速度更快
const obj = { a: 1, b: 2, sayHi() { console.log('hi'); } };
const arr = [1, 2, 3];
const regex = /abc/i;
new 调用:
- 性能:现代引擎对字面量和内置构造器均有优化。创建简单对象/数组时,字面量通常更快且更省代码。
- 可读性:配置/数据使用字面量;需要定义可复用实例、原型方法、继承时用 new(构造函数或 class)。
// 使用内置构造器
const obj2 = new Object();
obj2.a = 1;const arr2 = new Array(1, 2, 3);
const date = new Date();// 自定义构造函数或类
function Person(name) {this.name = name;
}
Person.prototype.sayHi = function() { console.log('hi'); };
const p = new Person('Tom');class User {constructor(name) { this.name = name; }sayHi() { console.log('hi'); }
}
const u = new User('Jerry');
- 字面量和new创建出来的对象会继承Object的方法和属性,他们的隐式原型会指向Object的显式原型,
- 而 Object.create(null)创建出来的对象原型为null,作为原型链的顶端,自然也没有继承Object的方法和属性
7. 作用域,作用域链
- 规定变量和函数的可使用范围称作作用域。
- 每个函数都有一个作用域链,查找变量或者函数时,需要从局部作用域到全局作用域依次查找,这些作用域的集合称作作用域链。
8. 执行栈与执行上下文
执行上下文分为:
- 全局执行上下文
- 创建一个全局的window对象,并规定this指向window,执行js的时候就压入栈底,关闭浏览器的时候才弹出
- 函数执行上下文
- 每次函数调用时,都会新创建一个函数执行上下文
- 执行上下文分为创建阶段和执行阶段
- 创建阶段:函数环境会创建变量对象:arguments对象(并赋值)、函数声明(并赋值)、变量声明(不赋值),函数表达式声明(不赋值);会确定this指向;会确定作用域
- 执行阶段:变量赋值、函数表达式赋值,使变量对象编程活跃对象
- 模块执行上下文(ESM)
- 每个模块有自己的作用域与绑定
- eval执行上下文
执行栈:
- 栈特点:先进后出
- 当进入一个执行环境,就会创建出它的执行上下文,然后进行压栈,当程序执行完成时,它的执行上下文就会被销毁,进行弹栈。
- 栈底永远是全局环境的执行上下文,栈顶永远是正在执行函数的执行上下文
- 只有浏览器关闭的时候全局执行上下文才会弹出
特点:
- 同步代码会按照调用链依次入栈、出栈。
- 栈空间有限,递归或深层嵌套可能导致“栈溢出”(RangeError: Maximum call stack size exceeded)。
- 栈溢出解决方案:
- 减少栈空间的需求,不要定义占用内存较多的auto变量,应该将此类变量修改成指针,从堆空间分配内存。
- 函数参数中不要传递大型结构/联合/对象,应该使用引用或指针作为函数参数。
- 减少函数调用层次,慎用递归函数,例如A->B->C->A环式调用。
9. 事件循环
JavaScript 是单线程的:同一时刻只能执行一个调用栈。为了同时处理异步任务(定时器、网络、DOM 事件、Promise 等),它采用“事件循环”机制来协调“执行栈”和“任务队列”。
- 执行宏任务script,
- 进入script后,所有的同步任务主线程执行
- 所有宏任务放入宏任务执行队列
- 所有微任务放入微任务执行队列
- 先清空微任务队列,
- 再取一个宏任务,执行,再清空微任务队列
- 依次循环
宏任务(部分示例)
- setTimeout / setInterval
- MessageChannel / postMessage(浏览器)
- I/O 回调(XHR/readFile 等最终回调入宏任务)
- UI 事件回调(点击、滚动等)
- setImmediate(Node)
微任务
- Promise.then/catch/finally
- queueMicrotask
- MutationObserver(浏览器)
- process.nextTick(Node,甚至比普通微任务更早)
10. 变量提升
- 对所有函数声明进行提升(除了函数表达式和箭头函数),引用类型的赋值
- 开辟堆空间
- 存储内容
- 将地址赋给变量
foo(); // OK
function foo() { console.log('foo'); }
- 对变量进行提升,只声明,不赋值,值为undefined
console.log(a); // undefined
var a = 10;
console.log(a); // 10
var let const 的区别:
- var
- var声明的变量可进行变量提升,let和const不会
- var可以重复声明
- var在非函数作用域中定义是挂在到window上的
- let
- let声明的变量只在局部起作用
- let防止变量污染
- 不可重复声明
- const
- 具有let的所有特征
- 不可被改变
- 如果使用const声明的是对象的话,是可以修改对象里面的值的
11. js列举和数组操作相关的方法
| 顺序 | 方法名 | 功能 | 返回值 | 是否改变原数组 | 版本 |
|---|---|---|---|---|---|
| 1 | push() | (在结尾)向数组添加一或多个元素 | 返回新数组长度 | Y | ES5 |
| 2 | shift() | 移除数组的第一项 | 返回被删除的数据 | Y | ES5 |
| 3 | unshift() | (在开头)向数组添加一或多个元素 | 返回新数组长度 | Y | ES5 |
| 4 | pop() | 删除数组的最后一位 | 返回被删除的数据 | Y | ES5 |
| 5 | reverse() | 反转数组中的元素 | 返回反转后数组 | Y | ES5 |
| 6 | sort() | 以字母顺序(字符串Unicode码点)对数组进行排序 | 返回新数组 | Y | ES5 |
| 7 | splice() | 在指定位置删除指定个数元素再增加任意个数元素 (实现数组任意位置的增删改) | 返回删除的数据所组成的数组 | Y | ES5 |
| 8 | concat() | 通过合并(连接)现有数组来创建一个新数组 | 返回合并之后的数组 | N | ES5 |
| 9 | join() | 用特定的字符,将数组拼接形成字符串 (默认",") | 返回拼接后的新数组 | N | ES5 |
| 10 | slice() | 裁切指定位置的数组 | 被裁切的元素形成的新数组 | N | ES5 |
| 11 | toString() | 将数组转换为字符串 | 新数组 | N | ES5 |
| 12 | slice() | 裁切指定位置的数组 | 被裁切的元素形成的新数组 | N | ES5 |
数组去重的方法:
- 使用 Set(适合原始值)
const obj = {};
const arr = [1, 1, 2, 3, 'a', 'a', true, true, null, undefined,obj, obj, {x:1}]
console.log([...new Set(arr)]) // 输出: [1, 2, 3, 'a', true, null, undefined,obj, {x:1}]
- Map + 过滤:按某个键去重(对象数组)
const users = [{ id: 1, name: 'A' },{ id: 2, name: 'B' },{ id: 1, name: 'A2' }
];
const seen = new Map();
const result = [];
users.forEach(item=>{if (!seen.has(item.id)) {seen.set(item.id, true);result.push(item);}
})
console.log(result) // 输出: [{ id:1, name:'A' }, { id:2, name:'B' }]
- 经典兼容方案:索引判断(无需 Set/Map)
const arr = [1, 2, 2, 'a', 'a', NaN]
const result = [];
for (let i = 0; i < arr.length; i++) {if (result.indexOf(arr[i]) === -1) {result.push(arr[i]);}
}
console.log(result) // 输出: [1,2,a,NaN]
// 注意:indexOf 对 NaN 不工作,会保留多个 NaN
12. 深浅拷贝
- 浅拷贝
- 浅拷贝创建一个新的对象,但是只复制原始对象的基本数据类型的字段或引用(地址),而不复制引用指向的对象。这意味着新对象和原始对象中的引用指向相同的对象。
- 如果原始对象中的字段是基本数据类型,那么这些字段会被复制到新对象中,而如果字段是引用类型,则新对象和原始对- 象的对应字段将引用同一个对象。
- 因此,对新对象所做的修改可能会影响到原始对象,因为它们共享相同的引用。
// 常见浅拷贝方式
var arr1 = new Array(12,23,34)
var arr2 = arr1; //这就是一个最简单的浅拷贝//对于一维数据是深拷贝效果,但是对于多维数据是浅拷贝效果。
const obj = { a: 1, b: { c: 2 } };
const shallow = { ...obj }
obj.a = 2;
shallow.c = 3;
console.log(obj); // { a: 2, b: { c: 3 } }
console.log(shallow); // { a: 1, b: { c: 3 } }
// 更改c值后两个对象c值都改变了,因为c值不在第一层依然为浅拷贝,单a值在第一层为深拷贝
const shallow = Object.assign({}, obj) // 一层
const shallowArr = arr.slice(); // 或 arr.concat(); 一层
- 深拷贝
- 深拷贝创建一个新的对象,并且递归地复制原始对象的所有字段和引用指向的对象,而不仅仅是复制引用本身。
- 深拷贝会递归复制整个对象结构,包括对象内部的对象,确保新对象和原始对象之间的所有关系都是独立的。
- 这意味着对新对象所做的修改不会影响到原始对象,因为它们拥有彼此独立的副本。
// 深拷贝方式
// structuredClone(浏览器/Node 17+)
// 特点:原生、快、支持循环引用、支持多种内建类型(Date、RegExp、Map、Set、ArrayBuffer、TypedArray、Error、Blob 等)
// 不支持:函数、DOM 节点、原型与不可枚举属性的保留(克隆为普通对象结构)
const deep = structuredClone(obj);// JSON 序列化(简易、有限)
// 特点:简单;只适合“可 JSON 化”的纯数据
// 丢失:函数、Symbol、undefined、原型、Map/Set、Date(变字符串)、RegExp(丢失)、循环引用会报错
const deep = JSON.parse(JSON.stringify(obj));// 第三方库
// lodash.cloneDeep:成熟稳定,覆盖广泛数据结构,但不保留原型链与不可枚举属性
import _ from 'lodash';
const deep = _.cloneDeep(obj);
//
13. 原型和原型链
原型: 原型分为隐式原型和显式原型,每个对象都有一个隐式原型,它指向自己的构造函数的显式原型。
原型链: 获取对象时,如果这个对象上本身没有这个属性时,它就会去它的原型__proto__上去找,如果还找不到,就去原型的原型上去找…一直直到找到最顶层(Object.prototype)为止,Object.prototype对象也有__proto__属性,值为null
- 所有实例的__proto__都指向他们构造函数的prototype
- 所有的prototype都是对象,自然它的__proto__指向的是Object()的prototype
- 所有的构造函数的隐式原型指向的都是Function()的显示原型
- Object的隐式原型是null
(1) prototype:所有的函数都有原型prototype属性,这个属性指向函数的原型对象。
(2) __proto__:这是每个对象(除null外)都会有的属性,叫做__proto__,这个属性会指向该对象的原型。
(3) constructor: 每个原型都有一个constructor属性,指向该关联的构造函数。
14. 闭包
概念:函数执行,形成私有的执行上下文,使内部私有变量不受外界干扰,起到保护和保存的作用
作用:避免命名冲突、解决循环绑定引发的索引问题、可以使用函数内部的变量,使变量不会被垃圾回收机制回收
应用:设计模式中的单例模式、for循环中的保留i的操作、防抖和节流、函数柯里化
缺点:会出现内存泄漏的问题
function makeCounter() {let count = 0; // 被内部函数引用的外部变量return function() {count++;return count;};
}const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2
// 返回的函数保留了对 makeCounter 作用域中变量 count 的访问。
// 即使 makeCounter 已结束,count 仍被闭包“记住”
垃圾回收机制:
- 标记清除法
- 垃圾回收机制获取根并标记他们,然后访问并标记所有来自它们的引用,然后在访问这些对象并标记它们的引用…如此递进结束后若发现有没有标记的(不可达的)进行删除,进入执行环境的不能进行删除
- 引用计数法
- 当声明一个变量并给该变量赋值一个引用类型的值时候,该值的计数+1,当该值赋值给另一个变量的时候,该计数+1,当该值被其他值取代的时候,该计数-1,当计数变为0的时候,说明无法访问该值了,垃圾回收机制清除该对象
15. 事件冒泡,事件捕获
- 事件冒泡
- 在addEventListener中的第三属性设置为false(默认)
- 从下至上(儿子至祖宗)执行
- 事件捕捉
- 在addEventListener中的第三属性设置为true
- 从上至下(祖宗到儿子)执行
16. 防抖节流
- 防抖
- n秒后在执行该事件,若在n秒内被重复触发,则重新计时
function debounce(func, delay) {let timeoutreturn function () {let arg = arguments// 如果已经执行,清楚定时器重新执行if (timeout) clearTimeout(timeout)timeout = setTimeout(() => {func(arg)}, delay);}
- 节流
- n秒内只运行一次,若在n秒内重复触发,只有一次生效
function throttle(fn, wait) {let previous = 0return function () {let now = Date.now()let _this = thislet args = arguments// 当前时间减去第一次执行时间大于设定等待时间后才执行if (now - previous > wait) {fn.apply(_this, arguments)previous = now}}
17. 跨域的方式及特点
- JSONP
- JSONP通过同源策略涉及不到的"漏洞",也就是像img中的src,link标签的href,script的src都没有被 同源策略限制到
- JSONP只能get请求
- CORS
- 通过自定义请求头来让服务器和浏览器进行沟通 详细描述
- nginx代理跨域
- nginx模拟一个虚拟服务器,因为服务器与服务器之间是不存在跨域的
- 发送数据时 ,客户端->nginx->服务端
- 返回数据时,服务端->nginx->客户端
18. js模块化
- 为什么要使用模块化
- 防止命名冲突
- 更好的分离,按需加载
- 更好的复用性
- 更高的维护性
- exports和module.exports的区别
- 导出方式不一样
- exports.xxx=‘xxx’
- module.export = {}
- exports是module.exports的引用,两个指向的是用一个地址,而require能看到的只有module.exports
- 导出方式不一样
- import 与 require 的区别
- ES Modules(ESM)
- 语法:import / export
- CommonJS(CJS)
- 语法:require / module.exports / exports
- ES Modules(ESM)
// math.mjs or type: module
export const add = (a, b) => a + b;
export default function sub(a, b) { return a - b; }// 使用
import sub, { add } from './math.js';// math.cjs or CommonJS 环境
const add = (a, b) => a + b;
module.exports = { add };// 使用
const { add } = require('./math');
- JS模块包装格式
- commonjs
- 同步运行,不适合前端
- AMD
- 异步运行
- 异步模块定义,主要采用异步的方式加载模块,模块的加载不影响后面代码的执行。所有依赖这个模块的语句都写在一个回调函数中,模块加载完毕,再执行回调函数
- CMD
- 异步运行
- seajs 规范
- commonjs
- ES6和commonjs的区别
- commonjs模块输出的是值的拷贝,而ES6输出的值是值的引用
- commonjs是在运行时加载,是一个对象,ES6是在编译时加载,是一个代码块
- commonjs的this指向当前模块,ES6的this指向undefined
19. js性能优化方式
- 垃圾回收
- 闭包中的对象清楚
- 防抖节流
- 分批加载(setInterval,加载10000个节点)
- 事件委托
- 少用with
- requestAnimationFrame的使用
- script标签中的defer和async
- CDN
TypeScript
1. TypeScript与JavaScript的区别
Typescript 是 JavaScript 的超集,可以被编译成 JavaScript 代码。 用 JavaScript 编写的合法代码,在 TypeScript 中依然有效。它给JavaScript添加了可选的静态类型和基于类的面向对象编程,如类、接口、继承、泛型等。
TypeScript 的特性:
- 类型批注:通过类型批注提供在编译时启动类型检查的静态类型。
- 类型推断:ts 中没有批注变量类型会自动推断变量的类型
- 类型擦除:在编译过程中批注的内容和接口会在运行时利用工具擦除
- 接口:ts 中用接口来定义对象类型(interface)
- 枚举:用于取值被限定在一定范围内的场景
- Mixin:可以接受任意类型的值
- 泛型:写代码时使用一些之后才指定的类型
- 命名空间:名字只在该区域内有效,其他区域可重复使用该名字而不冲突
- 元组:元组合并了不同类型的对象,相当于一个可以装不同类型数据的数组
与 JavaScript 的区别:
- TS 是一种面向对象编程语言,而 JS 是一种脚本语言(尽管 JS 是基于对象的)。
- TS 支持可选参数, JS 则不支持该特性。
- TS 支持静态类型,JS 不支持。
- TS 支持接口,JS 不支持接口。
2. TypeScript 的数据类型
- boolean(布尔类型):let flag:boolean = true;
- number(数字类型):let num:number = 123;
- string(字符串类型):let str:string = ‘this is ts’;
- array(数组类型):let arr:Array = [1, 2];
- tuple(元组类型):允许表示一个已知元素数量和类型的数组,let tupleArr:[number, string, boolean];
- enum(枚举类型):enum Color {Red, Green, Blue}
- any(任意类型):let num:any = 123;
- null 和 undefined 类型:let num:number | undefined; // 数值类型 或者 undefined
- void 类型:用于标识方法返回值的类型,表示该方法没有返回值。
- never 类型:never是其他类型 (包括null和 undefined)的子类型,可以赋值给任何类型,代表从不会出现的值。但是没有类型是 never 的子类型,这意味着声明 never 的变量只能被 never 类型所赋值。
- object 对象类型:let obj:object;
3. 枚举类型
枚举是一种对数字值集合进行命名的方式。它们可以增加代码的可读性,并提供一种便捷的方式来使用一组有意义的常量。
enum Color {Red,Green,Blue
}let selectedColor: Color = Color.Red;
4. 类型声明和类型推断的区别
类型声明是显式地为变量或函数指定类型,而类型推断是TypeScript根据赋值语句右侧的值自动推断变量的类型。
// 类型声明
let x: number;
x = 10;
// 类型推断
let y = 20; // TypeScript会自动推断y的类型为number
5. 什么是接口(interface),它的作用,接口的使用场景。接口和类型别名(Type Alias)的区别
接口是用于描述对象的形状的结构化类型。它定义了对象应该包含哪些属性和方法。在TypeScript中,接口可以用来约束对象的结构,以提高代码的可读性和维护性。
interface Person {name: string;age: number;
}
function greet(person: Person) {return `Hello, ${person.name}!`;
}
接口和类型别名的区别:
- 接口定义了一个契约,描述了对象的形状(属性和方法),以便在多个地方共享。它可以被类、对象和函数实现。
- 类型别名给一个类型起了一个新名字,便于在多处使用。它可以用于原始值、联合类型、交叉类型等。与接口不同,类型别名可以用于原始类型、联合类型、交叉类型等,而且还可以为任意类型指定名字。
6. 什么是泛型(generic),如何创建泛型函数和泛型类,实际用途
泛型是一种在定义函数、类或接口时使用类型参数的方式,以增加代码的灵活性和重用性。在TypeScript中,可以使用来创建泛型。
function identity<T>(arg: T): T {return arg;
}
// 调用泛型函数
let output = identity<string>("hello");
7. 处理可空类型(nullable types)和undefined类型
在TypeScript中,可空类型是指一个变量可以存储特定类型的值,也可以存储null或undefined。(通过使用可空类型,开发者可以明确表达一个变量可能包含特定类型的值,也可能不包含值(即为null或undefined)。
let numberOrNull: number | null = 10;
numberOrNull = null; // 可以赋值为null let stringOrUndefined: string | undefined = "Hello";
stringOrUndefined = undefined; // 可以赋值为undefined
8. 联合类型和交叉类型
联合类型表示一个值可以是多种类型中的一种,而交叉类型表示一个新类型,它包含了多个类型的特性。
- 联合类型示例:
let myVar: string | number;
myVar = "Hello"; // 合法
myVar = 123; // 合法
- 交叉类型示例:
interface A {a(): void;
}
interface B {b(): void;
}
type C = A & B; // 表示同时具备 A 和 B 的特性
9. 命名空间(Namespace)和模块(Module)
模块:
- 在一个大型项目中,可以将相关的代码组织到单独的文件,并使用模块来导入和导出这些文件中的功能。
- 在一个 Node.js 项目中,可以使用 import 和 export 关键字来创建模块,从而更好地组织代码并管理依赖关系。
export function sayHello(name: string) {return `Hello, ${name}!`;
}
// app.ts
import { sayHello } from './greeter';
console.log(sayHello('John'));
命名空间
- 在面向对象的编程中,命名空间可以用于将具有相似功能或属性的类、接口等进行分组,以避免全局命名冲突。
- 这在大型的 JavaScript 或 TypeScript 应用程序中特别有用,可以确保代码结构清晰,并且不会意外地重复定义相同的名称
namespace MathUtil {export const PI = 3.14159;export function areaOfCircle(r: number): number {return PI * r * r;}// 未导出的成员仅在命名空间内部可见const internalScale = 2;export class Vector2 {constructor(public x: number, public y: number) {}length(): number { return Math.sqrt(this.x * this.x + this.y * this.y); }}
}// 使用(同一编译单元或全局):
const a = MathUtil.areaOfCircle(2);
const v = new MathUtil.Vector2(3, 4);
10. 类型断言(Type Assertion)
类型断言允许手动指定一个值的类型。这在需要明确告诉编译器某个值的类型时非常有用。
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
11. 可选参数和默认参数
- 可选参数允许函数中的某些参数不传值,在参数后面加上问号?表示可选。
- 默认参数允许在声明函数时为参数指定默认值,这样如果调用时未提供参数值,则会使用默认值。
// 可选参数
function greet(name: string, greeting?: string) {if (greeting) {return `${greeting}, ${name}!`;} else {return `Hello, ${name}!`;}
}// 默认参数
function greet(name: string, greeting: string = "Hello") {return `${greeting}, ${name}!`;
}
12. const和readonly的区别
- const用于声明常量值。一旦被赋值后,其值将不能被重新赋值或修改。
- 常量必须在声明时就被赋值,并且该值不可改变。
- 常量通常用于存储不会发生变化的值,例如数学常数或固定的配置值。
const PI = 3.14;
PI = 3.14159; // Error: 无法重新分配常量
- readonly关键字用于标记类的属性,表明该属性只能在类的构造函数或声明时被赋值,并且不能再次被修改。
- readonly属性可以在声明时或构造函数中被赋值,但之后不能再被修改。
- readonly属性通常用于表示对象的某些属性是只读的,防止外部代码修改这些属性的值。
class Person {readonly name: string;constructor(name: string) {this.name = name; // 可以在构造函数中赋值}
}let person = new Person("Alice");
person.name = "Bob"; // Error: 无法分配到"name",因为它是只读属性
const主要用于声明常量值,而readonly则用于标记类的属性使其只读。
