记录总结下最近遇到的前端笔试新问题
目录
一、操作数组方法
1.Array.isArray(arr)
2.Object.prototype.toString.call(arr) === "[object Array]"
3.arr instanceof Array
1)跨帧问题
2)修改Array.prototype
3)模拟数组的对象
二、闭包
三、yield关键字
1.定义
2.题目
四、onclick事件
1.题目
2.关键点
1)保留btn.onclick = a();
2)保留btn.onclick = a;
编辑
五、类
1.题目
1)this指向及可行性分析
2)方法重名
六、提高DOM操作效率的方法
1.减少不必要的DOM访问和修改
2.使用合适的DOM操作方法
1)innerHTML与其他方法的选择
2)insertBefore方法
3)使用DocumentFragment
3.优化事件处理
1)事件委托
2)及时移除不再需要的事件处理程序
4.利用现代浏览器特性和优化技巧
1)使用requestAnimationFrame
2)采用CSS硬件加速
七、==
1.题目
2.结果
3.分析
1)null
2)true 、 false
3)字符串
4)补充NaN
(1)比较
(2)检测
一、操作数组方法
1.Array.isArray(arr)
优:ES5引入的专门用于检测数组的方法,准确,直观且易使用
劣:它不会受到全局变量或对象遮蔽的影响,因为它直接使用全局的Array构造函数
但是旧的环境不可用(ES5之前的环境不可用)
2.Object.prototype.toString.call(arr) === "[object Array]"
优:可以在任何js环境使用,不依赖版本
准确,因为Object.prototype.toString方法返回的字符串对于每种内置对象类型都是唯一的
劣:比较冗长且不直观
3.arr instanceof Array
大多数情况有效,也兼容旧环境,但是优潜在的局限性,有可能是不准确的。
1)跨帧问题
如果数组是在不同的执行上下文(例如,不同的iframe或window对象)中创建的,那使用instanceof可能会失败。因为每个全局执行上下文都有自己的Array构造函数,所以在一个上下文中创建的数组不会被视为在另一个上下文中的Array实例。
2)修改Array.prototype
如果Array.prototype被修改(例如,添加了新的属性或方法),可能会影响instanceof的结果。
3)模拟数组的对象
instanceof无法区分真正的数组和模拟数组的对象(即具有length属性和索引属性的对象)。这些模拟数组的对象可能通过其他方式(如Array.isArray或Object.prototype.toString.call)被识别为非数组。
二、闭包
document.getElementsByTagName('li'):获取所有的<li>元素。
通过for循环遍历,onclick函数是一个闭包,捕获每次迭代时i的当前值,点击事件发生时,调用捕获了i值的onclick函数。
<li>Click me</li>
<li>Click me</li>
<li>Click me</li>
<li>Click me</li>
let element = document.getElementsByTagName('li');
for(let i=0; i<element.length; i++) {
element[i].onclick = function () {
alert(i);
}
}
点击四个li,分别弹出的是0 1 2 3。
三、yield关键字
1.定义
在js中,yield关键字主要用于生成器函数。生成器函数是一种特殊的函数,它用于在函数执行的过程中产生一个值,并暂停函数执行,直到下一次调用生成器的.next()方法。
2.题目
function* func() {
yield 177;
yield 935;
return 130;
}
const res = func();
for(let item of res) {
console.log(item);
}
在代码中,function*声明一个生成器函数。
输出:
以上生成器函数func()通过yield关键字依次返回177和935。for...of循环会继续执行直到生成器函数中没有更多的值可以yield。
return语句在生成器函数中的行为特殊:它会结束迭代过程,但返回的值并不会通过for...of循环或其他迭代方法直接暴露给外部。可以通过调用生成器对象的.return()方法来获取返回值。
四、onclick事件
1.题目
如果注释掉(1)或注释掉(2)分别会发生什么?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button id="button">按钮</button>
<script>
function a() {
console.log(1);
return function b() {
console.log(2);
}
}
let btn = document.getElementById('button');
btn.onclick = a(); // (1)
btn.onclick = a; // (2)
</script>
</body>
</html>
2.关键点
当为onclick事件处理器分配一个函数时,应该传递函数本身,而不是调用它(即不要加括号)。如果加了括号,函数会立即执行,并且onclick会被赋予该函数的返回值(如果有的话)。
1)保留btn.onclick = a();
点击按钮前:
这行代码执行,会立即调用函数a,函数a内部首先会执行console.log(1),打印出1,然后函数a返回了函数b,并且这个返回值(函数b)又被赋值给了btn.onclick。
点击按钮时:
当按钮被点击,会触发onclick事件,此时由于之前已经将函数a返回的函数b赋值给了btn.onclick,所以会执行函数b,控制台输出2。
2)保留btn.onclick = a;
点击按钮前:
这行代码将a本身分配给btn.onclick,此时btn.onclick被赋值为函数a本身。
在js中,当把一个函数作为事件处理函数赋值给onclick等事件属性时,并不会立即执行该函数,而是在对应的事件触发(按钮被点击)时才会执行,控制台没有输出。
点击按钮时:
当按钮被点击时,就会触发onclick事件,此时会执行函数a。函数a内部首先会执行console.log(1),所以控制台会输出1。然后函数a返回了另一个函数b,但由于这里只是将a赋值给onclick且没有对返回值做进一步处理,所以不会执行函数b,也不会输出2。
五、类
1.题目
class Dog {
static dog() {
console.log(this);
}
bark() {
console.log('bark');
}
}
let dog = new Dog();
1)this指向及可行性分析
(1)dog.bark()时的this指向
当通过实例dog调用实例方法bark时,this指向调用该方法的实例本身。在这里,dog是Dog类的一个实例,所以在bark方法内部,this就指向Dog这个实例对象。
(2)Dog.bark()时的this指向
Dog是一个类,而bark是实例方法,不是类(静态方法)。通过类名直接调用实例方法时不合法的操作,在严格模式下会抛出错误,在非严格模式下,this会指向全局对象(浏览器环境中通常是window,在Node.js环境中时global),但这种调用方式不符合正常的面向对象编程规范,应该通过实例来调用实例方法。
3)Dog.dog()时的this指向
dog是Dog类中的静态方法。当通过类名Dog调用静态方法dog时,静态方法内部的this指向类本身,也就是Dog。
4)dog.dog()时的this指向
虽然代码中定义了Dog类的静态方法dog,但通过实例调用静态方法是不合法的操作,在严格模式下会报错,非严格模式下,this会指向实例dog,但这种调用方式不符合正常的面向对象编程规范,应该通过类名来调用静态方法。
2)方法重名
在js中,类中的静态方法和非静态方法是可以重名的。
它们处于不同的作用域,静态方法是属于类本身的,通过类名来调用;
非静态方法是属于类的实例的,通过实例来调用。
语法上允许它们重名,但在实际代码中,为了代码的清晰性和可维护性,通常不这样做。
六、提高DOM操作效率的方法
1.减少不必要的DOM访问和修改
批量操作:
如果需要对多个DOM元素进行一系列相关操作(修改样式、添加属性等),尽量将操作集中起来进行,而不是逐个元素进行多次DOM访问和修改。
缓存DOM元素引用:
当需要多次访问同一个DOM元素时,在第一次获取到该元素后,将其缓存到一个变量中,后续直接使用这个变量。
const myElement = document.getElementById('my-id');
// 后续多次需要使用myElement的操作就可以直接使用这个变量,而不是再次调用getElementById
myElement.style.color ='red';
myElement.addEventListener('click', () => {
// 处理点击事件
});
2.使用合适的DOM操作方法
1)innerHTML与其他方法的选择
innerHTML可以用于快速设置或获取一个元素内部的HTML内容。在某些情况下,它的速度相对比较快,比如当需要替换整个元素内部的HTML内容时。
缺点:
可能会导致原有的事件绑定丢失,并存在一定安全风险(如果设置的内容来自不可信源,可能会有XSS攻击)
对于只需要添加或修改单个元素或少量元素的情况,使用createElement、appendChild等方法更合适。
const list = document.getElementById('my-list');
const newItem = document.createElement('li');
newItem.textContent = '新的列表项';
list.appendChild(newItem);
2)insertBefore方法
可以用于在指定元素之前插入新元素等更精细的操作。
3)使用DocumentFragment
DocumentFragment是一个轻量级的DOM节点,它可以用于在内存中构建DOM结构,然后再将其整体插入到实际的DOM树中。这样可以避免多次直接对实际的DOM树进行插入操作,从而提高效率。
const fragment = document.createDocumentFragment();
for (let i = 0; i < 5; i++) {
const newItem = document.createElement('li');
newItem.textContent = '列表项'+ (i + 1);
fragment.appendChild(newItem);
}
const list = document.getElementById('my-list');
list.appendChild(fragment);
3.优化事件处理
1)事件委托
事件委托是指将一个元素的事件处理委托给它的父元素或更高层次的祖先元素。当页面中有多个具有相同类型事件(如多个按钮的点击事件)的元素时,不需要为每个元素单独设置事件处理程序,而是可以将事件处理程序设置在它们的公共祖先元素上,通过判断事件的目标元素(event.target)来确定具体是哪个子元素触发了事件。
const parentElement = document.getElementById('parent');
parentElement.addEventListener('click', (event) => {
if (event.target.matches('button')) {
// 这里处理按钮点击事件,event.target就是具体触发事件的按钮
console.log('按钮被点击了');
}
});
2)及时移除不再需要的事件处理程序
4.利用现代浏览器特性和优化技巧
1)使用requestAnimationFrame
当需要再浏览器中进行动画相关的DOM操作时,使用requestAnimationFrame可以让动画更流畅,并且它会根据浏览器的刷新频率自动调整执行时机,以确保最佳的时间点进行操作。
2)采用CSS硬件加速
对于一些涉及到样式改变的DOM操作,如元素的评议、旋转、缩放等,可以通过设置相关的CSS属性并利用浏览器的CSS硬件加速特性来提高操作速度。
const element = document.getElementById('moving-element');
element.style.transform = 'translateX(100px)';
七、==
1.题目
console.log("" == null)
console.log(1 == true)
console.log(0 == false)
console.log("123" == 123)
2.结果
3.分析
1)null
在js中,"" == null 会进行类型转换来判断两边的值是否相等。
这里的 "" 是一个空字符串,null 是一个表示无值的特殊关键字。
比较时遵循如下规则:
(1)如果一边是字符串,另一边是null或undefined,则两边的值都不会被转换成数字,而是直接认为它们不相等,除非进行显示转换或使用了宽松相等运算符(==)的特殊规则。
(2)在使用 == 时,null 和 undefined 被认为是相等的,但 null 和 undefined 与任何字符串(包括空字符串)比较时,如果不进行显示转换,它们不会相等。
有一个特殊规则涉及到空字符串 "" 和数字 0 以及 false 之间的比较。在js中, "" 在宽松相等比较下会被视为 0 ,但这并不影响它与 null 的结果。
console.log("" == null)
console.log("" == undefined)
console.log("aaa" == null)
console.log("aaa" == undefined)
console.log(0 == null)
console.log(0 == undefined)
console.log(null == undefined) // true
2)true 、 false
在js的宽松相等比较时,当比较一个数字和一个布尔值时,布尔值会先被转换为数字。 true 转换为数字时的值是 1 ,所以 1 和转换后的 true 是相等的,输出 true。
false 转换为数字的值是 0 ,所以 0 和转换后的 false 是相等的,输出为 true。
但是如果用 === 严格相等,需要值和类型都想等:
console.log(1 === true),左侧是数字,右侧是布尔值,为false。
更多:
console.log(1 == true) // true
console.log(0 == false) // true
console.log("1" == true) // true
console.log("0" == false) // true,“0”字符串转换为数字是0
console.log("" == false) // true,空值转换为数字0
console.log(1 === true) // false
console.log(0 === false) // false
console.log("1" === true) // false
console.log("0" === false) // false
3)字符串
宽松相等比较时,字符串和数字比较时,字符串优先被转换为数字。
console.log("123" == 123)
console.log("" == 0)
4)补充NaN
(1)比较
NaN与任何值(包括它自身)比较的结果都是false
NaN的类型是number,尽管表示的不是一个数字,但是它在js中的类型是number,用typeof NaN的结果是number。
console.log(NaN == NaN)
console.log(NaN === NaN)
console.log(NaN == null)
console.log(NaN == undefined)
console.log(0 == NaN)
(2)检测
使用 Number.isNaN() 函数 或 isNaN() 函数
console.log(Number.isNaN(NaN))
console.log(Number.isNaN(12))