函数的柯里化
总结:思路,每次执行,判断参数是否齐全,如果不齐全,递归调用把原来的 arguments一起上去
scss
function curry(fn,...args){
console.log("...args",args) //就是arguments
// 判断fn需要的参数是否够了
if(args.length >= fn.length){ //fn.length 是add函数的形参个数
return fn(...args) // 收集参数齐所有的参数 即是arguments解构
}
// 递归调用
return (...rest)=>{
console.log('...rest',...rest)// rest就是当前调用的参数 arguments
return curry(fn,...args,...rest) //递归调用把原来的 arguments一起上去 和当前的arguments一起 解构
}
}
//案例一:
const add =(x,y,z)=>x+y+z;
const curryAdd=curry(add);
let b=curryAdd(1)(2)(3)
console.log(b) // 6
数组的扁平
bash
const arr = [
{id: 1, name: '部门1', pid: 0},
{id: 2, name: '部门2', pid: 1},
{id: 3, name: '部门3', pid: 2},
{id: 4, name: '部门4', pid: 3},
{id: 5, name: '部门5', pid: 4},
]
function changeFun(aray,id){
let arr=[];
for(let item of aray){
if(item.pid==id){
arr.push({...item,children:children(aray,item.id)})
}
}
return aray
}
数组的降维度
javascript
let arr12=[1,3,4,[5,6,7,[8,9]]]
console.log('flat',arr12.flat(1)) //[1,3,4,5,6,7,[8,9]]
console.log('flat',arr12.flat(Infinity))// [1,3,4,5,6,7,8,9]
去重
ini
let aray=[2,4,1,2,4,6,8,9,1]
let setArray=new Set(aray)
aray=[...setArray]
console.log(aray) // [2, 4, 1, 6, 8, 9]
乱序
冒泡排序
arduino
// 数组排序(升和降)sort()
// let aray=[2,4,1,6,8,9,20,21,18]
// function riseNumber(a,b){ // 升序
// return a-b;
// }
// function dropNumber(a,b){
// return b-a
// }
// console.log(aray.
sort(riseNumber))//[1, 2, 4, 6, 8, 9, 18, 20, 21] // console.log(aray.sort(dropNumber)) //[21, 20, 18, 9, 8, 6, 4, 2, 1]
ini
// 冒泡排序
// let aray=[2,4,1,6,8,9,20,21,18]
// for(var i=0;i<aray.length-1;i++){
// let isSort=true;
// for(var j=0;j<aray.length-1-i;j++){
// if(aray[j]>aray[j+1]){
// isSort=false;
// let tmp=aray[j]
// aray[j]=aray[j+1]
// aray[j+1]=tmp
// }
// }
// if(isSort) break;
// }
面试题一
const声明对象
虽然const变量不能修改指针,但是可以修改值,比如我们定义一个对象,我们就可以修改对象里的属性值,但是不可以重写整个对象。
["1","2","3"].map(parseInt)
分解
map()
:里面是个回调函数,三个参数:分别是当前值(v)
,下标(i)
,原始数组(arr)
2: 整写法如下['1', '2', '3'].map((v, i, arr) => parseInt(v, i))
从这个简单例子来看,new
操作符做了两件事:
- 创建了一个全新的对象。
- 这个对象会被执行
[[Prototype]]
(也就是__proto__
)链接。
promise和async区别
- promise是个
对象Es6语法
,async是个函数
,es7语法 - promise 处理异步是另一种
地狱回调
,async是测底拉平
,更加优雅 - async的底层是promise
事件捕捉 与冒 泡模型
如何阻止事件冒泡?
ie:阻止冒泡ev.cancelBubble = true;
非IE ev.stopPropagation();
如何阻止默认事件?
(1)return false;(2) ev.preventDefault();
Javascript的事件流模型都有什么?
"事件捕捉":是从上往下,window,document,document.documentelment(获取的html) document,body 、........目标元素
"事件冒泡":是从下往上:反之
"DOM事件流":三个阶段:事件捕捉、目标阶段、事件冒泡
null和undefined的区别?
nul l是一个表示"无"的对象 ,转为数值时为0 ; undefined 是一个表示"无"的原始值 ,转为数值时为NaN 。
当声明的变量还未被初始化时,变量的默认值为undefined。 null用来表示尚未存在的对象,常用来表示函数企图返回一个不存在的对象。
post 和get 区别
前端性能值有哪些,如何量化
首次内容绘制【 First Contentful Paint, FCP】
- 定义: 浏览器首次渲染DOM内容的时间。
- 量化 : 通过
PerformanceObserver
API监测first-contentful-paint
条目。
最大内容绘制【Largest Contentful Paint, LCP】
- 定义: 页面中最大内容元素渲染完成的时间。
- 量化 : 使用
PerformanceObserver
监测largest-contentful-paint
条目。
首次输入延迟【First Input Delay, FID】
- 定义: 用户首次与页面交互到浏览器响应的延迟时间。
- 量化 : 通过
PerformanceObserver
监测first-input
条目。
累计布局偏移【 Cumulative Layout Shift, CLS】
- 定义: 页面生命周期内发生的意外布局偏移总和。
- 量化 : 使用
PerformanceObserver
监测layout-shift
条目,并计算偏移分数。
交互与时间【Time to Interactive, TTI】
- 定义: 页面完全可交互的时间。
- 量化: 通过Lighthouse等工具测量。
首字节时间【Time to First Byte, TTFB】
- 定义: 浏览器接收到服务器响应的第一个字节的时间。
- 量化 : 使用
performance.timing
或PerformanceObserver
监测responseStart
和requestStart
的差值。
Domcontentloaded 事件
- 定义: HTML文档完全加载和解析完成的时间。
- 量化 : 通过
performance.timing.domContentLoadedEventStart
获取。
资源加载时间
- 定义: 页面中各个资源(如图片、CSS、JS)的加载时间。
- 量化: 使用浏览器开发者工具的Network面板查看。
页面完全加载时间
- 定义: 页面所有资源加载完成的时间。
- 量化 : 通过
performance.timing.loadEventEnd
获取。
重绘和重排次数
- 定义: 页面渲染过程中发生的重绘和重排次数。
- 量化: 使用浏览器开发者工具的Performance面板分析。
js 执行时间
- 定义: JavaScript代码执行的总时间。
- 量化: 使用浏览器开发者工具的Performance面板分析。
内存使用
- 定义: 页面运行时的内存占用。
- 量化: 使用浏览器开发者工具的Memory面板查看。
总阻塞时间
- 定义: FCP到TTI之间主线程被阻塞的总时间。
- 量化: 使用Lighthouse等工具测量。
网络请求数量
- 定义: 页面加载过程中发起的HTTP请求总数。
- 量化: 使用浏览器开发者工具的Network面板查看。
速度指数
- 定义: 页面内容视觉填充的速度。
- 量化: 通过Lighthouse等工具测量。
伪数组和数组的区别
- 数组的原型是Array,伪数组的是一个普通对象
- 伪数组:是arguments(函数参数对象)、nodeList(Dom集合)、或者手动({0:"a",1:"b",length:1})
- 伪数组不能直接使用、map、push、foreach等方法,需要用Array.from转换或者用call/apply
token 能放到cookies上吗
- 需结合 Token 校验和加密机制
- 推荐将 Token 置于 HTTP 头(如
Authorization: Bearer <token>
XSS攻击
一种通过向网页注入恶意脚本(如JavaScript、HTML、CSS等)实现攻击的网络安全漏洞
防御XSS攻击的关键措施
-
输入过滤与转义
-
输出编码
- 在将数据输出到页面时,根据上下文(HTML、JavaScript、CSS)选择合适的编码方式5。
-
设置安全HTTP头部
-
使用HttpOnly Cookie
-
避免内联脚本与动态DOM操作
- 减少使用
eval()
、innerHTML
等高风险API,优先通过文本节点操作DOM6。
- 减少使用
CSRF攻击是什么?
是一种利用用户在已登录状态下对目标网站的信任
,通过诱导
用户触发恶意请求
的攻击方式。攻击者伪造用户的身份,执行非用户本意的敏感操作
(如转账、修改密码等
预防
Token 验证 、SameSite Cookie 、双重认证 和 请求来源校验 等
与 XSS 攻击的区别
特性 | XSS 攻击 | CSRF 攻击 |
---|---|---|
利用对象 | 利用用户对网站的信任 | 利用网站对用户浏览器的信任 |
攻击目标 | 窃取用户数据或劫持会话 | 伪造用户身份执行敏感操作 |
防御重点 | 过滤用户输入与输出 | 校验请求来源与身份 |
三栏布局的实现方案有哪些?
浮动布局、flex布局、绝对定位布局、grid布局grid-template-columns
定义三列,中间自适应、圣杯/双飞翼布局
变量提升
JavaScript 预编译阶段的特性 ,表现为变量和函数的声明 会被隐式提升 到当前作用域(全局或函数作用域)的顶端 ,但赋值操作仍保留在原位置
一个图片 url 访问后直接下载怎样实现?
HTML的download
属性
在<a>
标签中添加download
属性,浏览器会直接下载文件:
ini
<a href="图片URL" download="自定义文件名.jpg">下载图片</a>
Blob对象下载
通过Fetch API获取文件流并生成Blob对象:
ini
fetch(url)
.then(res => res.blob())
.then(blob => {
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'filename.jpg';
a.click();
});
- 优点:支持大文件异步下载
服务器端配置方案
设置HTTP响应头
在服务器返回图片时添加Content-Disposition
头:
BOM和BOM对象
BOM是浏览器对象模型,提供了与浏览器窗口交互的接口。常见对象包括window、location、history、navigator、screen等。
window对象
窗口控制
markdown
- `window.open(url)` :打开新窗口或标签页[6](https://www.nowcoder.com/questionTerminal/e1c38e4b631f4054a663b6531690df7e?toCommentId=2794725)
- `window.close()` :关闭当前窗口(仅限通过脚本打开的窗口)[6](https://www.nowcoder.com/questionTerminal/e1c38e4b631f4054a663b6531690df7e?toCommentId=2794725)
- `window.resizeTo(width, height)`:调整窗口大小[4](https://blog.csdn.net/weixin_33806509/article/details/93203542)
定时器
scss
- `setTimeout(func, delay)`:延迟执行一次函数[5](https://blog.csdn.net/lcwben/article/details/59060158)
- `setInterval(func, interval)`:周期性执行函数[5](https://blog.csdn.net/lcwben/article/details/59060158)
- `clearTimeout()`/`clearInterval()`:清除定时器[5](https://blog.csdn.net/lcwben/article/details/59060158)
窗口信息
markdown
- `window.innerWidth`/`window.innerHeight` :获取视口尺寸(包含滚动条)[10](https://blog.csdn.net/qq_34771938/article/details/120353624)
- `window.scrollY` :获取垂直滚动距离[10](https://blog.csdn.net/qq_34771938/article/details/120353624)`
Location 对象(操作 URL 相关)
URL 控制
scss
- `location.href` :获取或设置完整 URL(可用于跳转页面)[2](https://blog.csdn.net/weixin_50077864/article/details/127268410)[6](https://www.nowcoder.com/questionTerminal/e1c38e4b631f4054a663b6531690df7e?toCommentId=2794725)
- `location.reload(force)` :刷新页面(参数 `true` 强制从服务器加载)[2](https://blog.csdn.net/weixin_50077864/article/details/127268410)[10](https://blog.csdn.net/qq_34771938/article/details/120353624)
- `location.replace(url)` :替换当前页面(不可后退)[7](https://blog.csdn.net/azhimei1545/article/details/101508707)[10](https://blog.csdn.net/qq_34771938/article/details/120353624)
URL 解析
markdown
- `location.search` :获取 URL 查询参数(`?` 后的内容)[2](https://blog.csdn.net/weixin_50077864/article/details/127268410)[3](https://blog.csdn.net/weixin_43900284/article/details/113441680)
- `location.hash` :获取锚点部分(`#` 后的内容)[3](https://blog.csdn.net/weixin_43900284/article/details/113441680)[7](https://blog.csdn.net/azhimei1545/article/details/101508707)
History 对象*(浏览器历史记录)
页面导航
scss
- `history.go(n)` :前进/后退指定页数(`1` 前进,`-1` 后退)[2](https://blog.csdn.net/weixin_50077864/article/details/127268410)[6](https://www.nowcoder.com/questionTerminal/e1c38e4b631f4054a663b6531690df7e?toCommentId=2794725)
- `history.back()` :后退一页[3](https://blog.csdn.net/weixin_43900284/article/details/113441680)[6](https://www.nowcoder.com/questionTerminal/e1c38e4b631f4054a663b6531690df7e?toCommentId=2794725)
- `history.forward()` :前进一页[3](https://blog.csdn.net/weixin_43900284/article/details/113441680)[6](https://www.nowcoder.com/questionTerminal/e1c38e4b631f4054a663b6531690df7e?toCommentId=2794725)
历史栈信息
markdown
- `history.length` :获取历史记录中的页面数量[9](https://blog.csdn.net/cainiaoyihao_/article/details/117883001)
Navigator 对象(浏览器信息)
浏览器识别
markdown
- `navigator.userAgent` :获取浏览器标识(用于检测浏览器类型)[5](https://blog.csdn.net/lcwben/article/details/59060158)[9](https://blog.csdn.net/cainiaoyihao_/article/details/117883001)
- `navigator.platform` :获取操作系统信息[7](https://blog.csdn.net/azhimei1545/article/details/101508707)[9](https://blog.csdn.net/cainiaoyihao_/article/details/117883001)
功能检测
markdown
- `navigator.cookieEnabled` :检测是否启用 Cookie[2](https://blog.csdn.net/weixin_50077864/article/details/127268410)[6](https://www.nowcoder.com/questionTerminal/e1c38e4b631f4054a663b6531690df7e?toCommentId=2794725)
- `navigator.onLine` :检测网络连接状态[9](https://blog.csdn.net/cainiaoyihao_/article/details/117883001)
Screen 对象(屏幕信息)
html5 drag APi
dragstart:事件主体是被拖放元素,在开始拖放被拖放元素时触发,。
darg:事件主体是被拖放元素,在正在拖放被拖放元素时触发。
dragenter:事件主体是目标元素,在被拖放元素进入某元素时触发。
dragover:事件主体是目标元素,在被拖放在某元素内移动时触发。
dragleave:事件主体是目标元素,在被拖放元素移出目标元素是触发。
drop:事件主体是目标元素,在目标元素完全接受被拖放元素时触发。
dragend:事件主体是被拖放元素,在整个拖放操作结束时触发
iframe 是什么?有什么缺点?
定义:iframe 元素会创建包含另一个文档的内联框架
提示:可以将提示文字放在之间,来提示某些不支持 iframe 的浏览器
缺点:
会阻塞主页面的 onload 事件
搜索引擎无法解读这种页面,不利于 SEO
iframe 和主页面共享连接池,而浏览器对相同区域有限制所以会影响性能。
一句话概括 RESTFUL
就是用 URL 定位资源,用 HTTP 描述操作。
click 在 ios 上有 300ms 延迟,原因及如何解决?
原因:
要是为了区分用户的单击操作和双击缩放操作
解决
1、使用FastClick库
2、 在HTML文档的<head>
区域添加以下meta标签来禁用双击缩放功能:
ini
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
3、使用CSS属性touch-action: manipulation;
。这个属性可以告诉浏览器某些触摸行为(如双击缩放)应该被禁用,从而减少或消除点击延迟。
Math对象
Math.round(x)
arduino
返回 `x` 四舍五入后的整数。
```
console.log(Math.round(4.7)); // 5
console.log(Math.round(4.3)); // 4
```
Math.ceil(x)
arduino
返回 `x` 向上取整后的整数。
```
console.log(Math.ceil(4.1)); // 5
console.log(Math.ceil(-4.1)); // -4
```
Math.floor(x)
arduino
返回 `x` 向下取整后的整数。
```
console.log(Math.floor(4.9)); // 4
console.log(Math.floor(-4.9)); // -5
```
-
Math.trunc(x)
返回
x
的整数部分(去除小数部分)。javascriptconsole.log(Math.trunc(4.9)); // 4 console.log(Math.trunc(-4.9)); // -4
Math.random()
go
返回一个 0 到 1 之间的随机数(包含 0,不包含 1)。
```
console.log(Math.random()); // 0.1234567890123456
```
如果只需要生成 1 到 10 的随机整数,可以简化代码:
javascript
const randomNumber = Math.floor(Math.random() * 10) + 1;
console.log(randomNumber); // 1 到 10 之间的随机整数
-
原理:
Math.random() * 10
生成[0, 10)
之间的随机小数。Math.floor()
向下取整,得到[0, 9]
的整数。- 加上
1
,将范围调整为[1, 10]
。
生成指定范围的整数
使用 Math.random()
和 Math.floor()
arduino
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
const randomNumber = getRandomInt(1, 10);
console.log(randomNumber); // 1 到 10 之间的随机整数
-
原理:
Math.random()
生成一个[0, 1)
之间的随机小数。- 乘以
(max - min + 1)
将范围扩展到[0, max - min + 1)
。 - 使用
Math.floor()
向下取整,得到[0, max - min]
的整数。 - 加上
min
,将范围调整为[min, max]
。
Math.abs(x)
go
返回 `x` 的绝对值。
```
console.log(Math.abs(-5)); // 5
```
价格精确度
ini
//精确度 封装方法 相加
export function addFun(num1, num2) {
var len1 = 0;
var len2 = 0;
var m = 0;
num1=Number(num1)
num2=Number(num2)
try {
len1 = num1.toString().split(".")[1].length;
} catch (e) {
len1 = 0;
}
try {
len2 = num2.toString().split(".")[1].length;
} catch (e) {
len2 = 0;
}
m = Math.pow(10, Math.max(len1, len2));
var m1 = num1 * m;
m1 = new Number(m1.toFixed(0));
var m2 = num2 * m;
m2 = new Number(m2.toFixed(0));
return (m1 + m2) / m;
}
//精确度 封装方法 减
export function subtractFun(num1, num2) {
var len1 = 0;
var len2 = 0;
var m = 0;
num1=Number(num1)
num2=Number(num2)
try {
len1 = num1.toString().split(".")[1].length;
} catch (e) {
len1 = 0;
}
try {
len2 = num2.toString().split(".")[1].length;
} catch (e) {
len2 = 0;
}
m = Math.pow(10, Math.max(len1, len2));
var m1 = num1 * m;
m1 = new Number(m1.toFixed(0));
var m2 = num2 * m;
m2 = new Number(m2.toFixed(0));
return (m1 - m2) / m;
}
//精确度 封装方法剩
export function multiplyFun(num1, num2) {
var len1 = 0;
var len2 = 0;
var m = 0;
num1=Number(num1)
num2=Number(num2)
try {
len1 = num1.toString().split(".")[1].length;
} catch (e) {
len1 = 0;
}
try {
len2 = num2.toString().split(".")[1].length;
} catch (e) {
len2 = 0;
}
m = Math.pow(10, Math.max(len1, len2) + 1);
var m1 = num1 * m;
m1 = new Number(m1.toFixed(0));
var m2 = num2 * m;
m2 = new Number(m2.toFixed(0));
var m3 = m * m;
var m4 = m1 * m2 / m3;
return m4;
}
// //精确度 封装方法 除
export function divideFun(num1, num2) {
var len1 = 0;
var len2 = 0;
var m = 0;
num1=Number(num1)
num2=Number(num2)
try {
len1 = num1.toString().split(".")[1].length;
} catch (e) {
len1 = 0;
}
try {
len2 = num2.toString().split(".")[1].length;
} catch (e) {
len2 = 0;
}
m = Math.pow(10, Math.max(len1, len2) + 1);
var m1 = num1 * m;
m1 = new Number(m1.toFixed(0));
var m2 = num2 * m;
m2 = new Number(m2.toFixed(0));
var m3 = m1 / m2;
return m3;
}
把价格转千分位
1. 使用 toLocaleString()
方法
最简单且原生支持的方法,直接调用数字的 toLocaleString()
方法:
ini
const number = 1234567.89;
const formatted = number.toLocaleString(); // 输出:"1,234,567.89"(根据系统区域设置)
说明:
- 自动适配本地化格式(如小数点符号、千分位分隔符可能因系统区域不同而变化)。
- 兼容性好,推荐优先使用。
2. 自定义正则表达式
手动处理整数部分,用正则表达式插入逗号:
ini
function formatNumber(num) {
// 处理小数部分(如果有)
const parts = num.toString().split('.');
const integerPart = parts[0].replace(/\B(?=(\d{3})+(?!\d)/g, ',');
const decimalPart = parts[1] ? '.' + parts[1] : '';
return integerPart + decimalPart;
}
const number = 1234567.89;
const formatted = formatNumber(number); // 输出:"1,234,567.89"
说明:
\B(?=(\d{3})+(?!\d))
正则表达式匹配每三位数字的位置并插入逗号。- 完全控制格式,不受区域设置影响。
3. 处理字符串逆序插入逗号
手动从右到左遍历字符串,每三位插入逗号:
ini
function formatNumber(num) {
const str = num.toString().split('.');
let integerPart = str[0];
let result = '';
let counter = 0;
// 逆序遍历整数部分
for (let i = integerPart.length - 1; i >= 0; i--) {
counter++;
result = integerPart[i] + result;
if (counter % 3 === 0 && i !== 0) {
result = ',' + result;
}
}
// 处理小数部分
const decimalPart = str[1] ? '.' + str[1] : '';
return result + decimalPart;
}
const number = 1234567.89;
const formatted = formatNumber(number); // 输出:"1,234,567.89"
说明:
- 不依赖正则表达式,直接操作字符串。
- 代码稍长,但逻辑清晰。
4. 使用 Intl.NumberFormat
更灵活的国际化 API,可指定区域和格式:
ini
const number = 1234567.89;
const formatter = new Intl.NumberFormat('en-US'); // 指定为美式格式
const formatted = formatter.format(number); // 输出:"1,234,567.89"
说明:
- 支持更多格式选项(如货币符号、小数位数等)。
- 语法:
new Intl.NumberFormat(locales, options)
。
总结
方法 | 优点 | 缺点 |
---|---|---|
toLocaleString() |
最简单,原生支持,兼容性好 | 依赖系统区域设置 |
正则表达式 | 完全控制格式,代码简洁 | 需要理解正则逻辑 |
手动逆序处理 | 不依赖正则,逻辑透明 | 代码较长 |
Intl.NumberFormat |
国际化支持,高度可配置 | 语法稍复杂 |
推荐使用场景:
- 快速实现:优先用
toLocaleString()
或Intl.NumberFormat
。 - 固定格式需求:选择正则表达式或手动逆序处理。
js常见时间方法
方法 | 返回值说明 | 示例(假设当前时间 2023-09-25 15:30:45 ) |
---|---|---|
new Date() |
创建当前时间的 Date 对象 |
2023-09-25T07:30:45.000Z |
Date.now() |
当前时间的时间戳(毫秒,UTC) | 1695634245000 |
new Date().getTime() |
当前时间的时间戳(毫秒,UTC) | 1695634245000 |
new Date().valueOf() 或者 +new Date() |
当前时间的时间戳(毫秒,UTC) | 1695634245000 |
Date.parse(dateString) |
解析时间字符串返回时间戳(UTC) | Date.parse("2023-09-25") → 1695600000000 |
new Date('2023-09-25').getTime() |
当前时间的时间戳(毫秒,UTC) | 1695634245000 |
获取时间各部分
方法 | 返回值范围 | 示例结果 |
---|---|---|
getFullYear() |
年份(四位数) | 2023 |
getMonth() |
月份(0-11) | 8 (9月) |
getDate() |
日期(1-31) | 25 |
getDay() |
星期(0-6) | 1 (周一) |
getHours() |
小时(0-23) | 15 |
getMinutes() |
分钟(0-59) | 30 |
getSeconds() |
秒(0-59) | 45 |
getMilliseconds() |
毫秒(0-999) | 0 |
对象的遍历方式有哪些
总结
方法 | 描述 | 是否遍历继承属性 | 是否遍历 Symbol 属性 | 是否遍历不可枚举属性 |
---|---|---|---|---|
for...in |
遍历对象自身的和继承的可枚举属性(不包括 Symbol 属性) | 是 | 否 | 否 |
Object.keys() |
返回对象自身可枚举属性的键数组 | 否 | 否 | 否 |
Object.values() |
返回对象自身可枚举属性的值数组 | 否 | 否 | 否 |
Object.entries() |
返回对象自身可枚举属性的键值对数组 | 否 | 否 | 否 |
Object.getOwnPropertyNames() |
返回对象自身所有属性的键数组(包括不可枚举属性,不包括 Symbol 属性) | 否 | 否 | 是 |
Object.getOwnPropertySymbols() |
返回对象自身所有 Symbol 属性的数组 | 否 | 是 | 是 |
Reflect.ownKeys() |
返回对象自身所有属性的键数组(包括 Symbol 属性和不可枚举属性) | 否 | 是 | 是 |
for...of + Object.keys/values/entries |
结合 Object.keys 、Object.values 或 Object.entries 使用 |
否 | 否 | 否 |
根据具体需求选择合适的遍历方式,可以有效操作 JavaScript 对象。
手写瀑布流布局
6. 完整代码示例
以下是完整的 HTML、CSS 和 JavaScript 代码:
HTML
html
<div class="waterfall-container">
<div class="waterfall-item">Item 1</div>
<div class="waterfall-item">Item 2</div>
<div class="waterfall-item">Item 3</div>
<div class="waterfall-item">Item 4</div>
<div class="waterfall-item">Item 5</div>
<div class="waterfall-item">Item 6</div>
<div class="waterfall-item">Item 7</div>
<div class="waterfall-item">Item 8</div>
<div class="waterfall-item">Item 9</div>
<div class="waterfall-item">Item 10</div>
</div>
CSS
css
.waterfall-container {
display: flex;
justify-content: space-between;
padding: 10px;
}
.waterfall-column {
flex: 1;
margin: 0 5px;
}
.waterfall-item {
background-color: #f0f0f0;
border: 1px solid #ccc;
margin-bottom: 10px;
padding: 10px;
box-sizing: border-box;
}
JavaScript
javascript
function waterfallLayout(containerSelector, itemSelector, columns) {
const container = document.querySelector(containerSelector);
const items = document.querySelectorAll(itemSelector);
container.innerHTML = '';
const columnElements = [];
for (let i = 0; i < columns; i++) {
const column = document.createElement('div');
column.className = 'waterfall-column';
container.appendChild(column);
columnElements.push(column);
}
items.forEach(item => {
let minHeightColumn = columnElements[0];
columnElements.forEach(column => {
if (column.offsetHeight < minHeightColumn.offsetHeight) {
minHeightColumn = column;
}
});
minHeightColumn.appendChild(item.cloneNode(true));
});
}
waterfallLayout('.waterfall-container', '.waterfall-item', 3);
window.addEventListener('scroll', () => {
const { scrollTop, clientHeight, scrollHeight } = document.documentElement;
if (scrollTop + clientHeight >= scrollHeight - 10) {
loadMoreItems();
}
});
function loadMoreItems() {
const container = document.querySelector('.waterfall-container');
const newItems = [
'<div class="waterfall-item">New Item 1</div>',
'<div class="waterfall-item">New Item 2</div>',
'<div class="waterfall-item">New Item 3</div>',
];
newItems.forEach(item => {
const div = document.createElement('div');
div.innerHTML = item;
container.appendChild(div.firstChild);
});
waterfallLayout('.waterfall-container', '.waterfall-item', 3);
}
手写poromise
kotlin
class MyPromise {
constructor(executor) {
this.status = 'pending'; // 初始状态
this.value = undefined; // 成功时的值
this.reason = undefined; // 失败时的原因
this.onFulfilledCallbacks = []; // 成功回调队列
this.onRejectedCallbacks = []; // 失败回调队列
// 定义 resolve 函数
const resolve = (value) => {
if (this.status === 'pending') {
this.status = 'fulfilled'; // 状态改为成功
this.value = value; // 保存成功值
// 执行所有成功回调
this.onFulfilledCallbacks.forEach(fn => fn());
}
};
// 定义 reject 函数
const reject = (reason) => {
if (this.status === 'pending') {
this.status = 'rejected'; // 状态改为失败
this.reason = reason; // 保存失败原因
// 执行所有失败回调
this.onRejectedCallbacks.forEach(fn => fn());
}
};
// 执行 executor
try {
executor(resolve, reject);
} catch (error) {
reject(error); // 如果 executor 抛出错误,直接 reject
}
}
// 实现 then 方法
then(onFulfilled, onRejected) {
// 如果 onFulfilled 不是函数,则创建一个默认函数
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
// 如果 onRejected 不是函数,则创建一个默认函数
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
// 返回一个新的 Promise
const promise2 = new MyPromise((resolve, reject) => {
// 如果当前状态是 fulfilled
if (this.status === 'fulfilled') {
setTimeout(() => {
try {
const x = onFulfilled(this.value); // 执行成功回调
resolvePromise(promise2, x, resolve, reject); // 处理返回值
} catch (error) {
reject(error); // 如果回调抛出错误,直接 reject
}
}, 0);
}
// 如果当前状态是 rejected
if (this.status === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.reason); // 执行失败回调
resolvePromise(promise2, x, resolve, reject); // 处理返回值
} catch (error) {
reject(error); // 如果回调抛出错误,直接 reject
}
}, 0);
}
// 如果当前状态是 pending
if (this.status === 'pending') {
// 将回调函数加入队列
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value); // 执行成功回调
resolvePromise(promise2, x, resolve, reject); // 处理返回值
} catch (error) {
reject(error); // 如果回调抛出错误,直接 reject
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason); // 执行失败回调
resolvePromise(promise2, x, resolve, reject); // 处理返回值
} catch (error) {
reject(error); // 如果回调抛出错误,直接 reject
}
}, 0);
});
}
});
return promise2;
}
}
// 处理 then 返回值的函数
function resolvePromise(promise2, x, resolve, reject) {
// 如果 promise2 和 x 是同一个对象,抛出 TypeError
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
// 如果 x 是一个 Promise
if (x instanceof MyPromise) {
x.then(resolve, reject);
} else {
// 否则直接 resolve
resolve(x);
}
}