JavaScript真题合集(三)
21. 如何判断一个元素是否在可视区域中
可视区域即我们浏览网页的设备肉眼可见的区域。在日常开发中,判断目标元素是否在视窗之内或者和视窗的距离小于一个值是非常常见的需求。一般用于,图片的懒加载、列表的无限滚动、计算广告元素的曝光情况、可点击链接的预加载…
判断一个元素是否在可视区域,我们常用的有三种办法:
- offsetTop、scrollTop
- offsetTop:元素的上外边框至包含元素的上内边框之间的像素距离。
- scrollTop:页面垂直的滚动距离(随页面滚动变化)。
- 公式:el.offsetTop - document.documentElement.scrollTop <= viewPortHeight,其中viewPortHeight为可视区域的高度。
function isElementInView(el) {
var rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
// 使用示例
var element = document.getElementById('myElement');
if (isElementInView(element)) {
console.log('元素在可视区域中');
} else {
console.log('元素不在可视区域中');
}
// 注意:这里的 `getBoundingClientRect` 方法实际上已经包含了 `offsetTop` 和 `scrollTop` 的逻辑
// 但是为了示例完整性,这里展示了结合使用的思路。
- .getBoundingClientRect
- 这个方法返回一个DOMRect对象,该对象包含了元素的布局信息,如元素的宽度、高度、左边界和右边界等。通过比较这些边界与可视区域的边界,可以判断元素是否在可视区域内。
- 这个方法返回一个DOMRect对象,该对象包含了元素的布局信息,如元素的宽度、高度、左边界和右边界等。通过比较这些边界与可视区域的边界,可以判断元素是否在可视区域内。
function isElementInView(el) {
var rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
// 使用示例与上面相同
- Intersection Observer
- Intersection Observer API 提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport)交叉状态的方法。它提供了一种高效的机制来检测元素是否可见或进入/离开视口。
// 创建一个新的 Intersection Observer 实例
var observer = new IntersectionObserver(function(entries, observer) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
console.log('元素在可视区域中');
// 如果不再需要观察,可以调用 observer.unobserve(element) 停止观察
} else {
console.log('元素不在可视区域中');
}
});
}, {
threshold: 0.1 // 当元素与视窗的交叉比例达到 0.1 时触发回调函数
});
// 开始观察目标元素
var element = document.getElementById('myElement');
observer.observe(element);
// 当你不再需要观察该元素时,可以调用
// observer.unobserve(element);
22. 实现上拉加载,下拉刷新
上拉加载(无限滚动)
- 监听滚动事件:监听页面的滚动事件,通常使用 window 对象的 scroll 事件。
- 判断滚动位置:在滚动事件的处理函数中,检查页面滚动的位置是否接近底部。这可以通过比较 window.innerHeight、document.documentElement.clientHeight(或 document.body.clientHeight)和 window.scrollY(或 document.documentElement.scrollTop)来实现。
- 加载更多内容:当页面滚动到底部附近时,触发一个函数来加载更多内容。这可以通过 AJAX 请求或其他方式实现。
- 防止重复加载:在加载数据时,使用一个标志位或状态变量来防止重复发送请求。
let isLoading = false; // 加载状态
let page = 1; // 当前页码
// 监听滚动事件
window.onscroll = function() {
if (isLoading) return; // 如果正在加载,则直接返回
// 假设距离底部100px时触发加载
if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 100) {
isLoading = true; // 设置加载状态
// 模拟异步加载数据
setTimeout(function() {
// 这里应该是通过AJAX等方式获取数据的代码
// 假设我们得到了新的数据列表 newData
// ...
// 假设我们将新数据添加到了页面上某个元素中
// ...
page++; // 更新页码
isLoading = false; // 加载完成,重置状态
}, 1000); // 假设加载需要1秒
}
};
下拉刷新
- 监听触摸事件:在页面的可滚动区域上监听 touchstart、touchmove 和 touchend 事件。
- 检测下拉动作:在 touchmove 事件中,检测用户的触摸点是否相对于 touchstart 时的位置向下滑动了一定的距离。
- 执行刷新操作:如果检测到下拉动作,执行刷新操作,如重新加载页面数据或执行其他逻辑。
- 重置滚动位置:在刷新操作完成后,将页面的滚动位置重置到顶部或用户开始下拉时的位置。
微信小程序下拉刷新示例:
1.在页面的.json配置文件中开启下拉刷新功能:
{
"enablePullDownRefresh": true
}
2.在对应的.js文件中处理onPullDownRefresh事件:
Page({
// ...
onPullDownRefresh: function() {
// 执行数据刷新逻辑
this.loadData().then(() => {
wx.stopPullDownRefresh(); // 数据加载完毕,停止下拉刷新动画
});
},
loadData: function() {
// 示例:模拟异步加载数据
return new Promise((resolve) => {
setTimeout(() => {
// 真实场景中应调用API获取新数据
// 假设我们获取到了新数据 newData
// ...
// 更新页面数据
// ...
resolve(); // 数据加载完成
}, 1000); // 假设加载需要1秒
});
},
// ...
});
23.正则表达式
- 普通字符:在正则表达式中,普通字符就是直接匹配字符本身。例如,正则表达式 a 就会匹配文本中的字符 a。
- 元字符:元字符在正则表达式中具有特殊含义,它们不是直接匹配字符本身,而是用来表示字符的类别或匹配字符的某些属性。例如,. 表示匹配任意单个字符(除了换行符),* 表示匹配前面的字符或子表达式零次或多次。
- 字符集:字符集用来匹配一个范围内的字符。例如,[abc] 表示匹配字符 a、b 或 c 中的一个,[0-9] 表示匹配任意数字。
- 量词:量词用来指定字符或子表达式出现的次数。例如,? 表示匹配前面的字符或子表达式零次或一次,+ 表示匹配前面的字符或子表达式一次或多次。
- 边界符:边界符用来指定匹配文本的边界。例如,^ 表示匹配文本的开头,$ 表示匹配文本的结尾。
- 分组和引用:使用括号 () 可以将多个字符或子表达式组合成一个整体,并且可以通过 \n(其中 n 是一个数字)来引用之前匹配到的分组内容。
- 常用:
- 整数或者小数:^[0-9]+\.{0,1}[0-9]{0,2}$
- 只能输入数字:^[0-9]*$
- 只能输入n位的数字:^\d{n}$
- 只能输入至少n位的数字:^\d{n,}$
- 只能输入m~n位的数字:^\d{m,n}$
- 只能输入零和非零开头的数字:^(0|[1-9][0-9]*)$
- 只能输入非零的正整数:^\+?[1-9][0-9]*$
- 只能输入非零的负整数:^\-[1-9][]0-9*$
- 验证电话号码:^(\(\d{3,4}-)|\d{3.4}-)?\d{7,8}$
24.js的继承
- 原型链继承 : 当构造一个函数时,会为该函数创建一个prototype对象,这个对象的constructor属性指向该函数。当我们把一个对象的实例赋值给另一个对象的prototype时,就建立了两个对象之间的继承关系。
function Parent() {
this.name = 'parent';
}
function Child() {
this.age = 10;
}
Child.prototype = new Parent();
var child = new Child();
console.log(child.name); // 'parent'
- 借用构造函数继承(类式继承、伪经典继承): 通过在子类型构造函数的内部调用超类型构造函数,可以使用超类型的属性和方法。通过这种方式,子类型可以继承超类型的属性和方法,但不会继承超类型的原型对象。
function Parent() {
this.name = 'parent';
this.colors = ['red', 'blue', 'green'];
}
function Child() {
Parent.call(this);
this.age = 10;
}
var child1 = new Child();
child1.colors.push('black');
console.log(child1.colors); // ['red', 'blue', 'green', 'black']
var child2 = new Child();
console.log(child2.colors); // ['red', 'blue', 'green']
- 组合继承(原型链+借用构造函数): 通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
Child.prototype = new Parent(); // 这里其实是不必要的,只是为了保证原型链的完整性
Child.prototype.constructor = Child;
Child.prototype.sayAge = function() {
console.log(this.age);
};
var child1 = new Child('child1', 10);
child1.colors.push('black');
console.log(child1.colors); // ['red', 'blue', 'green', 'black']
var child2 = new Child('child2', 20);
console.log(child2.colors); // ['red', 'blue', 'green']
- ES6类继承 : 在ES6中,引入了class关键字,使得继承更加简单和直观。
class Parent {
constructor(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
sayName() {
console.log(this.name);
}
}
class Child extends Parent {
constructor(name, age) {
super(name); // 调用父类的constructor
this.age = age;
}
sayAge() {
console.log(this.age);
}
}
let child1 = new Child('child1', 10);
child1.colors.push('black');
console.log(child1.colors); // ['red', 'blue', 'green', 'black']
let child2 = new Child('child2', 20);
console.log(child2.colors); // ['red', 'blue', 'green']
25. js数字防止精度丢失
- 使用mathjs插件
- 创建个math.js文件
import * as $math from 'mathjs'
export const math = {
// +
add () {
return comp('add', arguments)
},
// -
subtract () {
return comp('subtract', arguments)
},
// x
multiply () {
return comp('multiply', arguments)
},
// ÷
divide () {
return comp('divide', arguments)
}
}
function comp (_func, args) {
let t = $math.chain($math.bignumber(args[0]))
for (let i = 1; i < args.length; i++) {
t = t[_func]($math.bignumber(args[i]))
}
// 防止超过6位使用科学计数法
return parseFloat(t.done())
}
- 导入使用
import {
math
} from "@/math.js";
// ×
math.multiply(amount,100)
26. 递归
在递归中,一个函数会调用自身,但每次调用都会带有一个更接近解决方案的参数,直到满足某个基准条件(base case),此时函数不再调用自身,而是直接返回结果。
function factorial(n) {
// 基准条件:0的阶乘是1
if (n === 0) {
return 1;
}
// 递归步骤:n的阶乘等于n乘以(n-1)的阶乘
return n * factorial(n - 1);
}
console.log(factorial(5)); // 输出 120 (5 * 4 * 3 * 2 * 1)
27. 常见面试题集合
1. 创建元素的三种方式:
document.write( );innerHTML = “”;document.createElement( );
------------------------------------------------------------------------------------------------------------------------------------------------------------------
2. 事件委托是什么:
利用事件冒泡的原理,让自己所触发的事件让他的父元素代替执行
------------------------------------------------------------------------------------------------------------------------------------------------------------------
3. 列举javaScript的3种主要数据类型,2种复合数据类型和2种特殊数据类型。
主要数据类型:string, boolean, number
复合数据类型:function, object
特殊类型:undefined,null
------------------------------------------------------------------------------------------------------------------------------------------------------------------
4. 数组方法pop() push() unshift() shift()
push()尾部添加,返回 数组长度
pop()尾部删除,返回 被删除的元素
unshift()头部添加 ,返回 数组长度
shift()头部删除,返回被删除的元素
------------------------------------------------------------------------------------------------------------------------------------------------------------------
5. 怎样添加、移除、移动、复制、创建和查找节点(使用原生JS实现)
1)创建新节点
createDocumentFragment() //创建一个DOM文档片段
createElement() //创建一个具体的元素
createTextNode() //创建一个文本节点
2)添加、移除、替换、插入
appendChild() //追加
removeChild() //移除
replaceChild() //替换
insertBefore() //插入
3)查找
getElementsByTagName() //通过标签名称
getElementsByName() //通过元素的Name属性的值
getElementsByTagName() // 通过类名查找
getElementById() //通过元素Id,唯一性
------------------------------------------------------------------------------------------------------------------------------------------------------------------
6. JavaScript有哪些循环?
while for do while for…in
------------------------------------------------------------------------------------------------------------------------------------------------------------------
7. 事件的三个阶段
捕获阶段、当前目标阶段、冒泡阶段
------------------------------------------------------------------------------------------------------------------------------------------------------------------
8. 常用的鼠标和键盘事件
onmouseup 鼠标按键放开时触发
onmousedown 鼠标按键按下触发
onmousemove 鼠标移动触发
onkeyup 键盘按键按下触发
onkeydown 键盘按键抬起触发
------------------------------------------------------------------------------------------------------------------------------------------------------------------
9. 请解释变量声明提升
作用域内所有变量声明都被提到顶部,被提升的变量初始值为undefined,执行到所在行时才真正赋值。
------------------------------------------------------------------------------------------------------------------------------------------------------------------
10. call和apply的区别
call和apply相同点:改变函数中this的指向
不同点:函数参数的传递形式
call将函数参数依次传入
apply将函数参数用一个数组的形式传入
------------------------------------------------------------------------------------------------------------------------------------------------------------------
11. 构造函数、实例、原型之间的关系
(1)任何函数都具有一个 prototype 属性,该属性是一个对象
(2)构造函数的 prototype 对象默认都有一个 constructor 属性,指向 prototype 对象所在函数
(3)通过构造函数得到的实例对象内部会包含一个指向构造函数的 prototype 对象的指针 proto
------------------------------------------------------------------------------------------------------------------------------------------------------------------
12. call、apply、bind(改变this指向)
(1)call 和 apply立即调用原函数,bind 不会调用而是生成一个改变了 this 指向的新的函数
(2)传参不同,call 调用的时候,参数必须以参数列表的形式进行传递,以逗号分隔的方式依次传递;apply 调用的时候,参数必须是一个数组
(3)第一个参数为要改变的this指向,如果不需要改变this指向 传null
------------------------------------------------------------------------------------------------------------------------------------------------------------------
13. 什么是闭包以及闭包的用途
14. js继承方式及其优缺点
15. this的指向
16. window.onload和$(document).ready的区别。
(1)执行时间
window.onload必须等到页面内包括图片的所有元素加载完毕后才能执行。
$(document).ready()是DOM结构绘制完毕后就执行,不必等到加载完毕。
(2)编写个数不同
window.onload不能同时编写多个,如果有多个window.onload方法,只会执行一个
$(document).ready()可以同时编写多个,并且都可以得到执行
(3)简化写法
window.onload没有简化写法
( d o c u m e n t ) . r e a d y ( f u n c t i o n ( ) ) 可以简写成 (document).ready(function(){})可以简写成 (document).ready(function())可以简写成(function(){});