前端面试笔记
汇总 HTML、CSS、JavaScript、框架、网络和工程化等前端面试知识与问答。
面试笔记

HTMl
HTML语义化
-
语义化标签是一种写HTML标签的方法论,明确了html的书写规范
-
实现方法是,通过使用语义化的标签名,例如 标题:h1-h6,段落:p, 文章:article,内容:main, 边栏:aside,导航:nav
-
优点
-
利于seo,适合搜索引擎搜索
-
适合人类阅读,利于团队维护
-
HTML5 有哪些新增的标签
-
文章相关
- header,main,footer,nav,section,article,figure,mark
-
多媒体
- video, audio, svg canvas
-
表单
- type=email type=tel
Canvas 和 SVG 的区别
-
Canvas 主要使用笔刷来绘制2D 图形的
-
SVG主要通过标签来绘制不规则矢量图的
-
相同:都是主要用来绘制2D图形的
-
不同点
-
svg 节点过多渲染慢,canvas 性能好一些,但是写起来复杂
-
svg支持分层和事件,canvas不支持,但是可以使用库实现
-
DOM 事件模型
-
先经历从上到下的捕获阶段,再经历从下到上冒泡阶段
-
addEventListener 的第三个参数可以选择阶段 可以使用 event.stopPropagation() 来阻止捕获或者冒泡
SEO
-
内部优化
-
合理的title, description keywords
-
一般不超过80个字符, 词语之间英文‘-’隔开
-
-
- 一般不超过150个字
-
- 一般三个关键字
-
-
代码语义化
-
非装饰性的图片必须➕alt
-
对于不现实的内容,谨慎使用display:none
-
重要内容放在html 前面
-
少使用iframe
- 搜索引擎抓取不到iframe 的内容
外部优化
- 在各个搜索网站提交自己的网站。
微格式
-
微格式是建立在已有的,被广泛采用的标准基础之上的一组简单开放的数据格式
-
把语义嵌入到HTML中,
-
- <a href="http://www.bbn.cn" rel="home page>web design blog
-
-
给标签添加一些语义属性,让标签具有结构和意义
替换元素和非替换元素
-
替换元素(内容不由css控制)
- iframe, img, audio, vadio,
页面可见性(是否正在看当前页面)
-
document.hidden, 是否为当前用户观看的页面
-
document.visibilityState
-
visibile: 标签页面为浏览器当前激活tab 而且窗口不是最小化状态
-
hidden:表示页面不是被激活的tab, 或者窗口最小化了
-
prerender : 表示页面正在重新生成,对用户不可见
-
-
visibilitychange 事件
- 监听 document.visibilityState 变化的
CSS
CSS 中的单位有哪些
-
绝对单位
-
cm
-
mm
-
in
-
pc
-
pt
-
px
-
-
相对单位
-
em
- 在font-size 中使用相对于父元素的字体大小
-
ex
- 字符‘x’高度
-
ch
- 数字0的宽度
-
rem
- 根元素的字体大小
-
lh
- 元素的line-height
-
vw
- 视窗的宽度
-
vh
- 视窗的高度
-
wmin
- 视窗宽高取最小值
-
wmax
- 视窗取最大的一条边
-
BFC 是什么
-
是块级格式化上下文
-
页面上的一块渲染区域, 有一套属于自己的渲染规则, 它决定了元素如何对齐内容,进行布局 以及与其他元素的关系和相互 当涉及到可视化布局的时候,BFC提供一个环境,HTML 按照一定的规则进行布局 body 是一个天然的BFC
-
规则
-
内部的盒子会在垂直方向一个接着一个的放置
-
box 垂直方向距离由margin 决定, 属于同一个BFC的相邻的box 的margin 会发生重叠
-
每个盒子的左外边框紧挨着包含快的左边框,即使浮动元素也如此
-
BFC区域不会与浮动Box 重叠
-
BFC 就是页面上的一个隔离的单独容器,在容器里面的子元素不会影响外面元素
-
计算BFC的高度时 浮动元子元素也参与计算
-
-
触发BFC的条件
-
根元素
-
float
- left, right
-
position 绝对定位
- absolute , fixed
-
overflow 不为 visible 的块元素
- auto , scroll hidden
-
display
- inline-block table-cell
-
flex
-
-
解决了什么问题
-
清除浮动
-
防止垂直方向的margin 合并
-
高度坍塌
-
-
参考答案
- BFC指的是一个独立的布局环境, BFC 内部元素布局与外界互不影响 触发BFC的方式有很多例如
- 设置浮动
- overflow: auto scroll hidden
- position: absolute/fixed
常见bfc 的应用
- 解决浮动导致父元素高度坍塌的问题
- 解决非浮动元素被浮动元素覆盖的问题
- 解决外边距垂直方向重合的问题
-
什么是IFC, GFC, FFC
-
IFC: 行内格式化上下文: 将一块区域按照航煤元素的形式来格式化
-
GFC: 网格布局格式化上下文, 将一块区域按网格的形式来格式化
-
FFC: 弹性格式化上下文, 将一块区域以弹性盒的形式格式化
-
如何实现垂直居中
-
table 自带功能
-
将元素装为table(display:table) 子元素display: table-cal
-
margin-top -50%
-
translate-y -50%
-
position:absolute ; margin:auto;
-
flex布局
-
100%高度的after before 加上 inline block
隐藏元素的方法有哪写
-
完全隐藏
-
元素从渲染数上消失
-
display: none
-
-
-
-
视觉隐藏
-
屏幕无可见,但是占据空间
-
opacity: 0
-
visibility:hidden
-
position: absolute left: -999px top:-999px
-
margin-left:-999px(将元素移出视觉范围)
-
裁剪:
-
-
-
语义隐藏
浮动
-
出现背景
- 出现浮动是为了做文字环绕效果,让文字环绕图片,从而使网页实现类似word 中图文混排的效果
-
规则
-
浮动元素会脱离文档流
-
浮动元素会互相贴靠,宽度不够时会重启一行
-
-
清除浮动
-
给父元素设置高度(高度要大于子元素的高度)
-
clear 属性
- 缺点: margin 失效
-
隔墙法(在两个浮动元素中间添加一个空dev 并添加clear: both)
- 缺点: 增加了多余的节点
-
内墙法: 在浮动元素的内部添加一个子颜色,并添加clear: both
-
伪元素法: 将浮动元素添加伪元素,并设置clear:both 属性
-
overflow:hidden(BFC)
-
定位
-
position 属性
-
static(静态定位)
- 就是指标准流, 在标准流中,块级元素独占一行,内嵌元素共享一行,静态定位是html 的默认值,静态定位元素不会受到 top,bottom,left right
-
relative(相对定位)
-
所谓相对定位就是相对于自己原来的位置进行定位,相对定位最大的特点就是不会脱离标准流,相对于自己原来的位置上进行偏移
- 作用,:微调元素 做绝对定位的参考值,子绝父相
-
-
absolute(绝对定位)
-
相对于离自己最近,定了位的元素 ,进行偏移,
-
如果没有定位元素,会参考document 进行定位,
-
top: 文档的左上角
-
bottom: 浏览器窗口首次刷新的底部位置进行定位
-
-
如何有设置元素,定位是位置是元素的左上角(在border 里面 )
-
-
谁用绝对定位的元素会脱离标准流,
-
绝对定位的盒子会变成block
-
margin 会失效
-
-
fixed 固定定位
-
可以看作是一个特殊的绝对定位, 定位元素也会脱离文档流
-
固定定位的特殊点是相对于浏览器窗口进行定位
-
-
sticky 粘性定位 css3
- 固定定位和相对定位的混合, 元素在跨越特定阀值前为相对定位,之后为固定定位。
-
css 属性的计算过程
-
大致过程 顺序
-
确定声明值
- 代码中写的css ,就是声明的内容
-
层叠冲突
- 当代码中粗线多个地方对同一个元素的同一个属性作用时,就会出现层叠冲突,此时会根据选择器的权重值来计算,使用那个属性值
-
使用继承
- 如果该条属性没有被作者设置,那么会去看祖级元素是否有可以继承的值
-
使用默认值
- 最终作者没有设置样式的属性,会使用浏览器的默认样式
-
css 的层叠继承规则总结
-
层叠
-
重要性
- 作者样式表优先默认样式表
-
专用性 (选择器权重)
- 行内样式 > ID > class> 标签
-
源代码次序
- 权重值一样,后声明的覆盖先声明的
-
css 引入的方式有哪些
-
外联: link 标签
-
内联: style 标签
-
内嵌: @import
-
@import url("xxx.css")
-
支持媒体查询 @import url("xxx.css") print
-
和 link 的区别
-
- link 是标签 , import 是css 的一种方式
-
2.. 加载顺序的差别 在a.css中使用import b.css时,只有当a.css 下载,解析后,浏览器才会知道b.css 需要下载,这时才回下载 b.css,然后解析,构建render tree
-
- import 是css2.1 提出的,有兼容性问题
-
- 当使用js 控制DOM 去改变样式时,只能通过link 标签,import 不是dom ,无法被控制
-
-
calc 计算属性(css3 的新增功能)
- 它可以计算出任何的时间 长度, 数值,角度,频率
css 媒体查询
-
媒体查询的方式
-
- link
-
- xml
-
- @import url("") screen
-
- @media css3
- @media screen { // style code }
-
css 过渡属性 transition
-
transition 是复合属性
-
transition-property
- 需要设置的过渡属性
-
transitiion-duration
- 过渡需要的时间
-
transition-timing-function
- 过渡函数
-
transition-delay
- 过渡的延迟时间
-
-
可以通过 ontransitionend 监听过渡效果的完成
css 动画
-
animation : 动画名 时间;
-
动画声明: @keyframes 动画名 { 0%{} 50%{} 100%{} }
css 优先级 如何确定
- 选择器越具体优先级越高, 相同有优先级,后出现的,覆盖前面的。 属性后面加 !important 优先级最高,但是要少用
css 如何清除浮动
-
给父元素增加 overflow:hidden (触发BFC)
-
给父元素增加伪类: parent:afert:{ content: ''; display: block; 或者table clear:both; }
-
tips: 现在不使用 浮动布局,以前IE 不支持 flex 布局才会有浮动布局的。
盒模型 box-sizing
-
content-box(firefox )
-
width指的是 content区域的宽度,而不是实现宽度
- 实际宽度 = width+ padding+ border
-
-
border-box(IE 盒模型)
- wdith 指的是 左右padding +左右border
-
相同点都是指定宽度的,不同点 border-box更好用,计算方便
JS
数据类型
-
变量声明
-
var
-
没有块级作用域
-
有全局作用域和函数作用域的概念
-
不初始化默认值为 undefined
-
存在变量提升
-
全局作用域用var 声明的变量会挂载到Window对象上
-
统一作用域允许重复声明
-
-
let
-
有块级作用域概念
-
不存在变量提升
-
有暂时性死区
-
不存在全局作用域概念
-
同一块作用域不允许重复声明
-
-
const
-
和 let 相同只有两点差别
-
必须立即初始化,
-
不能重复赋值
-
-
-
-
JS 数据类型有哪些
-
Null
-
Undefind
-
Boolean
-
String
-
Number
- 是双精度浮点数
-
Object
-
Bigint
-
Symbol
-
-
如何判断数据类型
-
typeof 判断 基础数据类型 返回小些的类型名
-
instanceof 判断由object 生成的数据类型
-
Object.prototype.toString.call(value) /// [object Type]
-
-
浮点数精度问题 0.1+0.2 === 0.3000000004
-
导致的原因
-
js中的数字都是以64 为浮点数存储的,
-
精度丢失的原因
-
浮点数表示是用IEEE 754 标准, 符号位 + 指数位 + 尾数位
-
符号位: 第一位表示正负符号, 1表示负数,0表示正数
-
指数位: 中间 十一位存储指数, 用户来表示次方数
-
尾数位置: 最后52位,存储小数部分,超出部分自动进一舍零
-
-
-
解决方案:
-
math.js
-
decimal.js
-
big.js
-
-
-
原型和原型链的概念
-
原型
- 每个 JavaScript 函数都有一个原型属性(prototype),这个属性是一个对象,它包含了可以由该函数创建的所有实例共享的属性和方法。 当创建一个函数时,该函数的 prototype 属性会自动获得一个 constructor 属性,指向函数自身。 通过原型,可以实现属性和方法的继承
-
原型链解决了什么问题
-
在es6之前,没有class 的情况下,实现继承
- a = new Array() a 是 Array 的实例 a 拥有Array.prototype里的属性 (a 通过 a.proto 获取到Array.prototype) Array 继承了Object a 是Object 的间接实例,a 也拥有Object.prototype 里的实例 (通过a.proto.proto 获取到的Object.prototype)
-
缺点不支持私有属性(js 创造的时候忘记加私有属性了)
-
-
-
this 是什么
- 它是 call 的第一个参数 例如 a={ fn(){} }
a.fn() ==>a.fn.call(a) fn 中的this 就是 a
-
new 做了些什么
-
创建临时对象
-
绑定原型
-
将this 指定到 临时对象
-
执行构造函数
-
返回临时对象
-
[Summary] function myNew(Constructor, ...args) {
// 等价于: let obj = {}; obj.proto = Constructor.prototype; const obj = Object.create(Constructor.prototype);
// 2. 绑定 this 并执行构造函数 // 如果构造函数有返回值,result 会接收到它 const result = Constructor.apply(obj, args);
// 3. 处理返回值 // 如果构造函数返回了一个对象(比如 {}、new Date()、new Map()等), // 则返回该对象;否则返回我们最开始创建的那个对象。 const isObject = typeof result === 'object' && result !== null; const isFunction = typeof result === 'function';
return (isObject || isFunction) ? result : obj; }
-
-
Object.create 做了些什么
-
创建临时对象
-
绑定原型
-
-
JS 如何实现类
-
function Dog (name){ this.name = name } Dog.prototype.say = function(){ console.log(this.name) } const d1 = new Dog("xxx") d1.say() // xxx
- 缺点 ts 中不好写
-
class Dog { constructor(name){ this name. = name } say(){ console.log(this.name) } } const d1 = new Dog("xxx") d1.say() // xxx
-
-
js 如何实现继承
-
通过构造函数实现继承
- function Animal (legs){ this.legs = legs } Animal.prototype.kind = "动物"
-
function Dog(name){ this.name = name Animal.cal(this,4) // 继承 属性 }
// 继承 原型 var f = function(){} f.prototype = Animal.prototype Dog.prototype = new f()
Dog.prototype.say = function(){ console.log(this.name) }
Code- class 实现继承 - class Aminal {
construct(legs){ this.legs = legs } }
class Dog extends Animal { construct(name){ super(4) this.name = name } say(){ console.log(this) } }
-
函数
- js 中规定, 声明的函数中,它的prototype.constructor = 函数本身
-
JS 的立即执行函数是什么
-
声明一个匿名函数并立即执行它
-
作用:创建一个局部作用域
-
优点:兼容性好
-
缺点: 写法丑
-
-
JS 闭包是什么 有什么作用
-
闭包是js 一种语法特性 闭包 = 函数 + 自由变量
-
作用
-
避免污染全局环境
-
提供局部变量的局部访问器
-
维持变量不被垃圾回收
-
-
优点: 好用
-
缺点: 使用不好导致内存泄漏
-
-
箭头函数
-
箭头函数没有自己的this,它的this是继承自外层作用域的this, 避免this 绑定错误的问题
-
没有arguments对象
-
不是作为构造函数使用,不能使用new 操作父
-
好处
-
代码更简洁,语法更清晰,可以减少代码量。
-
不需要使用bind方法来绑定this,避免了this绑定错误的问题。
-
-
-
作用域类型
-
全局作用域
-
模块作用域
-
函数作用域
-
块级作用域(ES6)
-
-
async 和 defer 区别
-
async : 啥时候加载完啥时候执行
-
defer : 脚本总是在 DOM 准备好时执行(但在DOMContentLoaded事件之前)
-
-
Instanceof 原理
-
通过 找左侧实例的原型,直到null left.proto === right.prototype
-
手写
- function myInstanceof(left, right) { let rightPrototype = right.prototype; // 获取构造函数的显式原型 let leftProto = left.proto; // 获取实例对象的隐式原型 while (true) { // 说明到原型链顶端,还未找到,返回 false if (leftProto === null) { return false; } // 隐式原型与显式原型相等 if (leftProto === rightPrototype) { return true; } // 获取隐式原型的隐式原型,重新赋值给 leftProto leftProto = leftProto.proto } }
-
-
== 隐试转换的原理
-
字符串和数字
- 将字符串转为数字
-
布尔和其他类型
- 会将布尔转为数字
-
对象和原始类型
-
会将对象转为原始类型
- 不是基本类型,先调用valueOf,还不是基本类型,调用toString,,还不是就报错 判断完成
-
-
-
浏览器内核
-
GUI 渲染线程
-
主要负责页面的渲染,解析HTML CSS 构建DOM 树, 布局和绘制
-
当页面需要重绘绘者由于某种 操作引发的回流时将执行该线程
-
该线程和js 线程互斥,当执行js引擎线程时,GUI 线程被刮起。当任务队列空闲时,主线才会执行GUI 渲染
-
-
JavaScript 引擎线程
-
负责处理JS 脚本,执行代码
-
负责执行准备待执行的事件,比如,定时器结束,异步操作成功后,将一次进入任务队列,等待js 引擎线程的执行
-
当js 引擎线程执行了js 脚本时间过长时,页面渲染将会阻塞。
-
-
定时器触发线程
-
执行异步定时器一类的函数的线程,如: settimeout, setinterval
-
主线程依次执行代码时,遇到定时器,会将定定时器交给该线程处理,当计数完成时,事件触发线程会计数完毕的事件,加入到任务队列的尾部,等待JS 引擎线程的执行
-
-
事件触发线程
-
异步http 请求线程
-
-
尺寸和位置
-
DOM 对象 相关尺寸和位置的属性
-
只读属性
-
clientWidth / clientHeight
- content+padding 指元素可视区域的宽度和高度
-
offsetWidth / offsetHeight
- content+padding + border
-
clientTop / clientLeft
- border 的宽高
-
offsetTop / offsetLeft
- 当前元素距离外层最近定位元素的位置,通过offetParent 可以获取离自己最近的定位元素
-
scrollHight / scrollWidth
- 滚动区域的宽高
-
-
可读可写的
-
scrollTop / scrollLeft
- 元素被卷起的高度和宽度
-
dom.style.xx
- 修改行内样式
-
-
-
Event 事件对象的尺寸和位置属性
-
clientX / clientY
- 鼠标点击的位置,相对于浏览器窗口的可视区域
-
screenX / screenY
- 鼠标位置相对于整个屏幕的位置
-
offsetX / offsetY
- 鼠标点击点,相对于鼠标所在的元素的左上角为原点
-
pageX / pageY
- 细分主题 1
-
-
-
手写
-
手写节流 throttle
-
技能冷却中,被触发后n秒后不能再次出发
-
const throttle = (fn,time)=>{ let flag = false let timer = null return (...args)=>{ if(flag) return fn.call(undefined, ...args) flag = true timer = setTimeout(() => { flag = false clearTimeout(timer) }, time); } }
-
-
手写 防抖 debounce
-
回城被打断, 触发后倒计时 n秒后执行,如果在倒计时没结束时被再次触发,则重新倒计时n秒
-
const debounce = (fn, time) => { let timer = null return (...args) => { clearTimeout(timer) timer = setTimeout(() => { fn.call(undefined, ...args) }, time); } }
-
-
发布订阅模式
- class eventHub { queueMap = new Map()
-
on(name, fn) { const q = this.queueMap.get(name) || [] q.push(fn) this.queueMap.set(name, q) } emit(name, data) { const queue = this.queueMap.get(name) || [] queue.map(fn => { fn(data) }) } off(name, fn) { const queue = this.queueMap.get(name) || [] const index = queue.indexOf(fn) if (index < 0) return queue.splice(index, 1) this.queueMap.set(name, queue) } }
Code- 手写 AJAX - const ajax = (method, url, data, success, fail) => {
const request = new XMLHttpRequest() request.open(method, url) request.onreadystatechange = function () { if (request.readyState === 4) { if (request.status >= 200 && request.status < 300 || request.status == 304) { success(request) } else { fail(request) } } } request.send(data) }
Code- 手写promise - function runMicroTask(fn) {
// eslint-disable-next-line no-undef if (process?.nextTick) { // node // eslint-disable-next-line no-undef process?.nextTick(fn); } else if (window.queueMicrotask) { // 浏览器 window.queueMicrotask(fn); } else { // 其他清情况 setTimeout(fn, 0); } }
function isPromise(obj) { return !!(obj && typeof obj === "object" && typeof obj.then === "function"); }
class MyPromise { _state = "padding"; _result = undefined; _handlers = [];
constructor(executor) { try { executor(this.#resolve.bind(this), this.#reject.bind(this)); } catch (error) { this.#reject(error); // } }
// 修改状态 changeState(state, result) { if (this._state === "padding") { this._state = state; this._result = result; } this._runHandler(); }
_runHandler() { if (this._state === "padding") { return; }
Codewhile (this._handlers.length) { const handler = this._handlers.shift(); this._runOneHandler(handler); }
}
_runOneHandler({ onFulFilled, onRejected, resolve, reject }) { runMicroTask(() => { const nextExecutor = this._state === "fulfilled" ? onFulFilled : onRejected; const nextResolve = resolve; const nextReject = reject;
Codeif (!(nextExecutor instanceof Function)) { this._state === "fulfilled" ? nextResolve(this._result) : nextReject(this.result); } try { const nextResult = nextExecutor(this._result); if (isPromise(nextResult)) { nextResult.then().then((res) => { nextResolve(res); }, nextReject); } else { nextResolve(nextResult); } } catch (error) { nextReject(error); } });
}
#resolve(data) { this.changeState("fulfilled", data); }
#reject(reason) { this.changeState("rejected", reason); }
then(onFulFilled, onRejected) { return new MyPromise((resolve, reject) => { this._handlers.push({ onFulFilled, onRejected, resolve, reject }); this._runHandler(); }); }
catch(onRejected) { return this.then(null, onRejected); } finally(fn) { return this.then( (data) => { fn(); return data; }, (reason) => { fn(); throw reason; } ); }
static resolve(data) { // if (data instanceof MyPromise) { // return data; // } return new MyPromise((resolve, reject) => { if (isPromise(data)) { data.then(resolve, reject); } else { resolve(data); } }); }
static reject(reason) { return new MyPromise((resolve, reject) => { reject(reason); }); } static all(promises) { return new MyPromise((resolve, reject) => { const result = []; let count = 0; let fulfilledCount = 0; for (const p of promises) { let index = count++;
Codeif (isPromise(p)) { p.then( (data) => { fulfilledCount++; result[index] = data; if (fulfilledCount === count) { resolve(result); } }, (reason) => { reject(reason); } ); } else { fulfilledCount++; result[index] = p; } } if (count === 0) { resolve(result); } });
} // 返回所有promise 的状态和结果 static allSettled(promises) { const result = []; for (const p of promises) { if (isPromise(p)) { result.push( p.then( (value) => ({ status: "fulfilled", value }), (reason) => ({ status: "rejected", reason }) ) ); } else { result.push({ status: "fulfilled", value: p, }); } } return MyPromise.all(result); } static race(promises) { return new Promise((resolve, reject) => { for (const p of promises) { MyPromise.resolve(p).then(resolve, reject); } }); } }
Code- 手写 promise.all - 功能,输入list,当全部成功 时 resolve,遇到一个reject 时reject - myAll = function (list) {
const result = [] let count = list.length return new Promise((resolve, reject) => { list.forEach((promise, index) => { promise.then(res => { result[index] = res --count if (count === 0) { resolve(result) } }, err => { reject(err) }) });
}) }
Code- 手写深拷贝 - 方法一 - JSON.parse(JSON.stringify(obj)) - 缺点:
不支持 Date 正则 undefind 函数等数据
Code- 方法二 - 递归法
判断类型,检查环!
Code- function deepClone(obj) {
const cache = new WeakMap() const clone = (a) => {
Code// 检车引用环 if (cache.has(a)) { return cache.get(a) } // 判断对象 if (a instanceof Object) { let result = undefined if (a instanceof Function) { // 函数 if (a.prototype) { result = function () { return a.apply(this, arguments) } } else { // 箭头函数 result = (...args) => { return a.apply(undefined, args) } } } else if (a instanceof Array) {// 数组 result = [] } else if (a instanceof Date) { // 日期 result = new Date(a.valueOf()) } else if (a instanceof RegExp) { // 正则 result = new RegExp(a.source, a.flags) } else { result = {} } cache.set(a, result) // 拷贝函数的属性 for (const key in a) { result[key] = clone(a[key]) } return result } else { // 基础数据类型 return a }
} const newobj = clone(obj)
return newobj
}
Code- 数组去重 - 1. 使用 使用 Set 和展开操作符 - [...new Set([1,1,2,2,1,1])] - 2. Array.from + Set - Array.from(new Set([1,1,2,2,1,1])) - 3. 遍历法 使用map 计数 - function uniq(list) {
const map = new Map() const result = [] list.forEach(item => { if (map.has(item)) { map.set(item,item) result.push(item) } }); return result }
Code- 手写事件委托 - document.querySelector("ul").addEventListener("click", function (e) {
if (e.target.tagName.toLowerCase() === "li") { // 执行逻辑 } }) // bug 在于如果点击了li里面的span ,就没有触发fn
Code- // 升级
function delegate(element, eventType, selector, fn) { element.addEventListener(eventType, e => { let el = e.target while (!el.matches(selector)) { if (element == el) { el = null break; } el = el.parentNode } el && fn.call(el, e, el) }) return element }
Code- 好处: 节省监听器,动态监听 - 坏处: 不容易排插当前元素有哪些监听事件 - target 和 currentTarget 的区别 - currentTarget 表示的监听的元素
target 表示触发的元素(点击了那个元素,target 就是那个)
Code- 手写拖拽 DIV - 要点 - 注意监听范围,不能只监听div - 不要使用drag 事件, 很难用 - 使用 transform, 比较高效,top 可能会造成卡顿 reflow - 记录鼠标开始位置,移动时算出,移动的距离,
元素的初始位置+移动的距离
Code- let drag = false;
let position = [0, 0]; let translate = [0, 0];
const box = document.querySelector(".box"); box.addEventListener("mousedown", function (event) { drag = true; position = [event.clientX, event.clientY]; });
box.addEventListener("mouseup", function () { drag = false; });
document.addEventListener("mousemove", function (event) {
if (drag) {
const moveX = translate[0] + event.x - position[0];
const moveY = translate[1] + event.y - position[1];
box.style.transform = translate(${moveX}px,${moveY}px);
translate = [moveX, moveY];
position = [event.x, event.y];
}
});
纯函数
-
一个函数的返回结果只依赖于它的参数
-
函数在执行过程里面没有任何副作用【不会造成外部变量的改变】
HTTP
GET 和 POST 有什么区别
-
- 幂等性 GET 是读, POST是写。
- 用浏览器打开网页发送的是GET请求,想要POST打开网页需要使用form 标签,