2023前端面试题及答案整理(JS面试题)
Posted suli77
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2023前端面试题及答案整理(JS面试题)相关的知识,希望对你有一定的参考价值。
ES6
let / const
全局作用域中,用 const 和 let 声明的变量不在 window 上,那到底在哪里?如何去获取?
ES6规定,var 命令和 function 命令声明的全局变量,依旧是顶层对象的属性,但 let、const、class命令声明的全局变量,不属于顶层对象的属性。只在一个块级作用域(Script)中,获取时不加 window/global
就好:
let aa = 1;
const bb = 2;
console.log(aa); // 1
console.log(bb); // 2
箭头函数
箭头函数与普通函数(function)的区别是什么?构造函数(function)可以使用 new 生成实例,那么箭头函数可以吗?为什么?
箭头函数是普通函数的简写,可以更优雅的定义一个函数,和普通函数相比,有以下几点差异:
1、函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。
2、不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
3、不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。
4、不可以使用 new 命令,因为:
- 没有自己的 this,无法调用 call,apply。
- 没有 prototype 属性 ,而 new 命令在执行时需要将构造函数的 prototype 赋值给新的对象的 proto
new 过程大致是这样的:
function newFunc(father, ...rest)
var result = ;
result.__proto__ = father.prototype;
var result2 = father.apply(result, rest);
if (
(typeof result2 === 'object' || typeof result2 === 'function') &&
result2 !== null
)
return result2;
return result;
题目来源:Daily-Interview-Question 第58题
Promise
10 个 Ajax 同时发起请求,全部返回展示结果,并且至多允许三次失败,说出设计思路
这个问题相信很多人会第一时间想到 Promise.all
,但是这个函数有一个局限在于如果失败一次就返回了,直接这样实现会有点问题,需要变通下。以下是两种实现思路
// 以下是不完整代码,着重于思路 非 Promise 写法
let successCount = 0
let errorCount = 0
let datas = []
ajax(url, (res) =>
if (success)
success++
if (success + errorCount === 10)
console.log(datas)
else
datas.push(res.data)
else
errorCount++
if (errorCount > 3)
// 失败次数大于3次就应该报错了
throw Error('失败三次')
)
// Promise 写法
let errorCount = 0
let p = new Promise((resolve, reject) =>
if (success)
resolve(res.data)
else
errorCount++
if (errorCount > 3)
// 失败次数大于3次就应该报错了
reject(error)
else
resolve(error)
)
Promise.all([p]).then(v =>
console.log(v);
);
ES6中,promise能实现异步的原理是什么?我new一个promise,然后对这个promise实例使用typeof,结果是什么?promise实例接收的参数是一个函数,函数接收两个参数一个resolve,一个reject,为什么resolve后可以实现继续执行后续代码?(TODO)
待续…
面向对象相关
JS是一门面向对象的语言,说说面向对象语言的三大特征?
封装、继承、多态
说说你是如何理解JS中封装、继承这两个特点的?
封装指的是隐藏逻辑实现过程,只对外暴露属性和方法,使用者只需要知道如何使用即可,而不需要关心内部如何实现的,目的在于简化实现过程,做到多处复用,提高开发效率,当需要维护的时候,业务逻辑和工具功能是分开的,降低迭代成本。
继承指的是建立一个对象与另一个对象之间的父子关系,使得子对象可以拥有父级对象的属性和方法。继承的目的在于实现功能共享,通过“借用”父对象的特性,从而避免重复写功能模块,避免多余内存空间的占用。
声明一个函数fn,对其使用typeof,结果返回什么?然后我给fn添加一个属性,请问会报错吗?为什么?
结果返回是 function 字符串。添加属性不会报错,因为在js中,函数并不是真正的函数, function 本身是一个 object 对象,而对象是可以添加属性和方法的,所以不会报错。
定义一个字符串,对这个字符串使用typeof,结果返回什么?既然返回结果是string,而属性和方法是对象才有的,那为什么会在我定义的字符串上可以使用split,join(),length等方法和属性?
字符串形式的 string 。
所有的字符串都是大写字母开头的String对象的实例,当我们对字符串使用方法和属性时,其实字符串本身并没有对应的方法,这个时候,javascript会沿着原型链往上寻找,最终在对象 String 上找到了对应的方法和属性,而这个过程对于开发者是无感的,所以看上去像是调用了字符串本身的方法。
new 一个构造函数,如果函数返回 return
、 return null
, return 1
, return true
会发生什么情况?
如果函数返回一个对象,那么new 这个函数调用返回这个函数的返回对象,否则返回 new 创建的新对象
如果一个构造函数,bind了一个对象,用这个构造函数创建出的实例会继承这个对象的属性吗?为什么?
不会继承,因为根据 this 绑定四大规则,new 绑定的优先级高于 bind 显示绑定,通过 new 进行构造函数调用时,会创建一个新对象,这个新对象会代替 bind 的对象绑定,作为此函数的 this,并且在此函数没有返回对象的情况下,返回这个新建的对象
JS 垃圾回收机制
谈谈垃圾回收机制的方式及内存管理?
一、垃圾回收机制——GC
Javascript 具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存。
原理:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存。
通常情况下有两种实现方式:
- 标记清除
- 引用计数
标记清除: js 中最常用的垃圾回收方式就是标记清除。
当变量进入环境时,例如,在函数中声明一个变量,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。
引用计数的含义是跟踪记录每个值被引用的次数。
当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是 1。如果同一个值又被赋给另一个变量,则该值的引用次数加 1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减 1。当这个值的引用次数变成 0 时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾回收器下次再运行时,它就会释放那些引用次数为 0 的值所占用的内存。
二、内存管理
-
Javascript 引擎基础 GC 方案是
(simple GC):mark and sweep(标记清除),即:
1)遍历所有可访问的对象;
2)回收已不可访问的对象。
-
GC 的缺陷
和其他语言一样,JavaScript 的 GC 策略也无法避免一个问题:GC 时,停止响应其他操作,这是为了安全考虑。而 Javascript 的 GC 在 100ms 甚至以上,对一般的应用还好,但对于 JS 游戏,动画对连贯性要求比较高的应用,就麻烦了。这就是新引擎需要优化的点:避免 GC 造成的长时间停止响应。
-
GC 优化策略
-
1)分代回收(Generation GC)
这个和 Java 回收策略思想是一致的。目的是通过区分“临时”与“持久”对象;多回收“临时对象”区(young generation),少回收“持久对象”区(tenured generation),减少每次需遍历的对象,从而减少每次 GC 的耗时。
-
2)增量 GC
这个方案的思想很简单,就是“每次处理一点,下次再处理一点”,如此类推。
-
哪些操作会造成内存泄漏?
意外的全局变量引起的内存泄漏
function foo(arg)
bar = '全局变量'; // 没有声明变量 实际上是全局变量=>window.bar
原因:全局变量,不会被回收。
为什么不能泄漏到全局?平时不都会定义一些全局变量么:
全局变量根据定义无法被垃圾回收机制收集。需要特别注意用于临时存储和处理大量信息的全局变量。如果必须使用全局变量来存储数据,请确保将其指定为null或在完成后重新分配它。
解决:使用严格模式避免,函数内使用var定义,块内使用let、const。
闭包引起的内存泄漏
function bindEvent()
var obj = document.createElement("XXX");
var unused = function ()
console.log(obj,'闭包内引用obj obj不会被释放');
;
// obj = null;
原因:闭包可以维持函数内局部变量,使其得不到释放,造成内存泄漏。
解决:将事件处理函数定义在外部,解除闭包,或者在定义事件处理函数的外部函数中,删除对dom的引用。例如上述案例,手动解除引用 obj = null
。
被遗忘的定时器和回调函数
var someResource = getData();
setInterval(function()
var node = document.getElementById('Node');
if(node)
node.innerhtml = JSON.stringify(someResource));
// 定时器也没有清除
// node、someResource 存储了大量数据 无法回收
, 1000);
原因:当不需要 setInterval
或者 setTimeout
时,定时器没有被clear,定时器的回调函数以及内部依赖的变量都不能被回收,造成内存泄漏。
解决:在定时器完成工作的时候,手动清除定时器
没有清理 DOM 元素引用
var refA = document.getElementById('refA');
document.body.removeChild(refA); // dom删除了
console.log(refA, "refA"); // 但是还存在引用能console出整个div 没有被回收
refA = null;
console.log(refA, "refA"); // 解除引用
原因: 保留了DOM节点的引用,导致GC没有回收
解决:refA = null
IE9以下浏览器环境导致的循环引用
var element = document.getElementById('something');
var myObject = new Object();
myObject.element = element; // element属性指向dom
element.someThing = myObject; // someThing回指myObject 出现循环引用(两个对象一直互相包含 一直存在计数)。
原因:IE9以下 BOM 和 DOM 对象使用 C++ 以 COM 对象的形式实现的。COM 的垃圾收集机制采用的是引用计数策略,这种机制在出现循环引用的时候永远都释放不掉内存。
解决:不使用它们的时候,手动切断链接:myObject.element = null; element.someThing = null;
闭包
能讲下 JavaScript 的闭包么?
一个函数包含了另一个函数或另一个对象,里面这个对象或函数都可以使用外部函数的变量或参数,此时就形成了闭包。
你平常怎么用闭包的?
匿名自执行函数避免全局污染(模块化开发)
创建了一个匿名立即执行函数,由于外部无法引用它内部的变量,因此在函数执行完后会立刻释放资源,不污染全局对象。
const msg = (function ()
const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
const today = new Date()
const msg = 'Today is ' + days[today.getDay()] + ', ' + today.getDate()
return
getMsg()
return msg
())
console.log(msg.getMsg())
缓存数据
有时候我们需要获取一个处理过程很耗时的对象,并且往往会把这个复杂的处理过程封装成一个方法。但每次需要用到它时都需要调用它从而花费很长时间,那么我们就需要将计算出来的值存储起来,当调用这个函数的时候,首先在缓存中查找,如果找不到,则进行计算,然后更新缓存并返回值,如果找到了,直接返回查找到的值即可。闭包就可以做到这一点,因为它不会释放外部的引用,从而函数内部的值可以得以保留。
var CachedSearchBox = (function ()
var cache = ,
count = [];
return
attachSearchBox: function (dsid)
if (dsid in cache) //如果结果在缓存中
return cache[dsid]; //直接返回缓存中的对象
var fsb = new uikit.webctrl.SearchBox(dsid); //新建
cache[dsid] = fsb; //更新缓存
if (count.length > 100) //保正缓存的大小<=100
delete cache[count.shift()];
return fsb;
,
clearSearchBox: function (dsid)
if (dsid in cache)
cache[dsid].clearSelection();
;
)();
拿到正确的值
for(var i = 0; i < 10; i++)
setTimeout(function()
console.log(i) //10个10
, 1000)
解决方案:for循环中声明10个自执行函数,保存当时的值到内部
for (var i = 0; i < 10; i++)
(function(j)
setTimeout(() =>
console.log(j)
, 1000);
)(i)
高级排他
场景:一个ul li列表,鼠标移入时高亮当前li标签,移除之前li标签的高亮状态。
常规写法:两层for循环,外层遍历li标签,给每个li添加 onmouseover 事件;里层for循环用来在高亮当前li标签之前,移除所有li标签高亮状态。
闭包写法:定义一个preIndex变量存储上次高亮li标签的索引,for循环内部使用立即执行函数+闭包保存index索引,这样就使li标签与index索引一一对应,然后在onmouseover事件触发后,根据preIndex找到之前高亮标签并移除高亮状态,并设置当前标签高亮,最后将preIndex设置为当前高亮标签的index索引。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
* margin: 0; padding: 0; list-style: none;
ul li width: 100%; height: 20px; margin-bottom: 5px; background-color: grey;
li.active background-color: yellow;
</style>
</head>
<body>
<ul>
<li class="active"></li>
<li></li>
<li></li>
</ul>
<script>
window.onload = function()
let list = document.querySelectorAll('li')
// 记录移动前选中li的对应索引
let preActiveIndex = 0
for (let i = 0; i < list.length; i++)
(function(j)
const li = list[i]
li.onmouseover = function()
// 清除
list[preActiveIndex].className = ''
// 设置
this.className = 'active'
// 赋值
preActiveIndex = j
)(i)
// 常规写法
// window.onload = function()
// let list = document.querySelectorAll('li')
// for (let i = 0; i < list.length; i++)
// const li = list[i]
// li.onmouseover = function()
// for (let j = 0; j < list.length; j++)
// list[j].className = ''
//
// this.className = 'active'
//
//
//
</script>
</body>
</html>
闭包会产生哪些问题?
闭包会使函数中的变量不能及时释放,造成内存消耗过大,从而导致网页的性能问题。不过目前浏览器引擎都基于 V8,而 V8 引擎有个 gc 回收机制,不用太过担心变量不会被回收。
Event Loop
setTimeout、Promise、Async/Await 的区别
这题主要是考察这三者在事件循环中的区别,事件循环中分为宏任务队列和微任务队列。
其中setTimeout的回调函数放到宏任务队列里,等到执行栈清空以后执行;
promise.then里的回调函数会放到相应宏任务的微任务队列里,等宏任务里面的同步代码执行完再执行;async函数表示函数里面可能会有异步方法,await后面跟一个表达式,async方法执行时,遇到await会立即执行表达式,然后把表达式后面的代码放到微任务队列里,让出执行栈让同步代码先执行。
你知道 Event Loop (事件循环)的机制吗?
事件循环机制从整体上告诉了我们 JavaScript 代码的执行顺序Event Loop
即事件循环,是指浏览器或Node
的一种解决javaScript
单线程运行时不会阻塞的一种机制,也就是我们经常使用异步的原理。
先执行宏任务队列,然后执行微任务队列,然后开始下一轮事件循环,继续先执行宏任务队列,再执行微任务队列。
- 宏任务:script/setTimeout/setInterval/setImmediate/ I/O / UI Rendering
- 微任务:process.nextTick()/Promise
上诉的 setTimeout 和 setInterval 等都是任务源,真正进入任务队列的是他们分发的任务。
优先级
- setTimeout = setInterval 一个队列
- setTimeout > setImmediate
- process.nextTick > Promise
for (const macroTask of macroTaskQueue)
handleMacroTask();
for (const microTask of microTaskQueue)
handleMicroTask(microTask);
概念性问题
观察者模式与发布订阅模式有什么区别
可以把观察者模式想象成顾客与微商的关系。顾客关注了微商的商品,微商就会记住顾客,每当微商有新产品时,就会直接私聊来通知所有关注了自己的顾客。这里的顾客就相当于观察者,微商就是被观察者。
而发布订阅模式可以看做是顾客通过「商城平台」关注了商家的商品,商家一旦上新就通过「商城平台」向关注了自己的顾客群发消息,这里的顾客就是订阅者,「商城平台」就是事件总线,商家就是发布者。
通过上面两个例子就能看出,观察者模式的模型跟发布/订阅者模型里,差距就差在有没有一个中央的事件总线。如果有这个事件总线,我们就可以认为是个发布订阅模型。如果没有,那么就可以认为是个观察者模型。因为其实它们都实现了一个关键的功能:发布事件-订阅事件并触发事件。
讲一下面向对象的三大特征?
封装继承和多态
什么是重写,什么是重载?
重写就是子类覆盖掉父类的方法
重载就是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
更多面试问答
https://download.csdn.net/download/suli77/87425350
前端面试题及答案整理(转)
各公司秋招很快就开始了,最近在准备面试的东西,干脆将发现的各类 面试题整理一下共享出来,大部分面试题是没有标准答案的,我给出的答案也是仅供参考,如果有更好的解答欢迎在评论区留言。Part1 手写代码
现场手写代码是现在面试中很常见的一类面试题,考察基础的数据结构与算法能力。
1 数组去重的实现
- 基本数组去重
Array.prototype.unique = function(){
var result = [];
this.forEach(function(v){
if(result.indexOf(v) < 0){
result.push(v);
}
});
return result;
}
- 利用hash表去重,这是一种空间换时间的方法
Array.prototype.unique = function(){
var result = [],hash = {};
this.forEach(function(v){
if(!hash[v]){
hash[v] = true;
result.push(v);
}
});
return result;
}
上面的方法存在一个bug,对于数组[1,2,‘1‘,‘2‘,3],去重结果为[1,2,3],原因在于对象对属性索引时会进行强制类型转换,arr[‘1’]和arr[1]得到的都是arr[1]的值,因此需做一些改变:
Array.prototype.unique = function(){
var result = [],hash = {};
this.forEach(function(v){
var type = typeof(v); //获取元素类型
hash[v] || (hash[v] = new Array());
if(hash[v].indexOf(type) < 0){
hash[v].push(type); //存储类型
result.push(v);
}
});
return result;
}
- 先排序后去重
Array.prototype.unique = function(){
var result = [this[0]];
this.sort();
this.forEach(function(v){
v != result[result.length - 1] && result.push(v); //仅与result最后一个元素比较
});
}
- 利用ES6 Set去重
//ES6环境下
Array.prototype.unique = function(){
return [...new Set(this)];
}
2 快速排序的实现
方法一(尽可能不用js数组方法):
function quickSort(arr){
qSort(arr,0,arr.length - 1);
}
function qSort(arr,low,high){
if(low < high){
var partKey = partition(arr,low,high);
qSort(arr,low, partKey - 1);
qSort(arr,partKey + 1,high);
}
}
function partition(arr,low,high){
var key = arr[low]; //使用第一个元素作为分类依据
while(low < high){
while(low < high && arr[high] >= arr[key])
high--;
arr[low] = arr[high];
while(low < high && arr[low] <= arr[key])
low++;
arr[high] = arr[low];
}
arr[low] = key;
return low;
}
方法二(使用js数组方法):
function quickSort(arr){
if(arr.length <= 1) return arr;
var index = Math.floor(arr.length/2);
var key = arr.splice(index,1)[0];
var left = [],right = [];
arr.forEach(function(v){
v <= key ? left.push(v) : right.push(v);
});
return quickSort(left).concat([key],quickSort(right));
}
另外要知道,快速排序的平均时间复杂度O(nlogn),最坏情况是有序的情况,时间复杂度为n的平方,另外快速排序是不稳定的。
Part2 JavaScript相关
1 JavaScript基础数据类型
JavaScript数据类型包括原始类型和引用类型,原始类型有五个:
Number(数值) String(字符串) Boolean(布尔) Null(空) Undefined(未定义)
引用类型有一个:
Object(对象)
通过typeof(x)可以返回一个变量x的数据类型“number”、“string”、“boolean”、“undefined”、"object",这里要注意一点:typeof运算符对于null类型返回的是object。
《JavaScript高级程序设计》:
这实际上是JavaScript最初实现中的一个错误,后来被ECMAScript沿用了。现在null被认为是对象的占位符,从而解释了这一矛盾。但是从技术上来说,它仍然是原始值。
2 谈一谈JavaScript作用域链
当执行一段JavaScript代码(全局代码或函数)时,JavaScript引擎会创建为其创建一个作用域又称为执行上下文(Execution Context),在页面加载后会首先创建一个全局的作用域,然后每执行一个函数,会建立一个对应的作用域,从而形成了一条作用域链。每个作用域都有一条对应的作用域链,链头是全局作用域,链尾是当前函数作用域。
作用域链的作用是用于解析标识符,当函数被创建时(不是执行),会将this、arguments、命名参数和该函数中的所有局部变量添加到该当前作用域中,当JavaScript需要查找变量X的时候(这个过程称为变量解析),它首先会从作用域链中的链尾也就是当前作用域进行查找是否有X属性,如果没有找到就顺着作用域链继续查找,直到查找到链头,也就是全局作用域链,仍未找到该变量的话,就认为这段代码的作用域链上不存在x变量,并抛出一个引用错误(ReferenceError)的异常。
3 如何理解JavaScript原型链
JavaScript中的每个对象都有一个prototype
属性,我们称之为原型,而原型的值也是一个对象,因此它也有自己的原型,这样就串联起来了一条原型链,原型链的链头是object,它的prototype比较特殊,值为null。
原型链的作用是用于对象继承,函数A的原型属性(prototype property)是一个对象,当这个函数被用作构造函数来创建实例时,该函数的原型属性将被作为原型赋值给所有对象实例,比如我们新建一个数组,数组的方法便从数组的原型上继承而来。
当访问对象的一个属性时, 首先查找对象本身, 找到则返回; 若未找到, 则继续查找其原型对象的属性(如果还找不到实际上还会沿着原型链向上查找, 直至到根). 只要没有被覆盖的话, 对象原型的属性就能在所有的实例中找到,若整个原型链未找到则返回undefined;
4 JavaScript变量声明提前
《JavaScript权威指南》中是这样解释的:JavaScript变量在声明之前已经可用,JavaScript的这个特性被非正式的称为声明提前
(hoisting),即JavaScript函数中声明的所有变量(但不涉及赋值)都被“提前”至函数的顶部。
从一个例子来看:
var scope = "global";
function myFunc(){
console.log(scope);
var scope = "local";
}
控制台打印出来的不是“global”而是“undefined”,这是因为在myFunc这个函数的作用域中,局部变量scope声明被提前至函数顶部,而此时,scope仅声明,未赋值,因此输出undefined。实际上,上面的代码和下面的效果是一样的:
var scope = "global";
function myFunc(){
var scope;
console.log(scope);
scope = "local";
}
5 如何理解和应用JavaScript闭包
关于闭包具体的定义文献中给的概念很抽象,我认为闭包是一种使函数能够都去其它函数的局部变量的语法机制。
举个例子:
function outFunc(){
var name = "Vicfeel";
function inFunc(){
console.log(name);
}
return inFunc;
}
inFunc(); //控制台显示"Vicfeel"
这这个例子我们可以看出,在函数inFunc中依然可以访问outFunc的局部变量name。
闭包应用举例,模拟类的私有属性,利用闭包的性质,局部变量只有在sayAge方法中才可以访问,而name在外部也访问,从而实现了类的私有属性。
function User(){
this.name = "Vicfeel"; //共有属性
var age = 23; //私有属性
this.sayAge:function(){
console.log("my age is " + age);
}
}
var user = new User();
console.log(user.name); //"Vicfeel"
console.log(user.age); //"undefined"
user.sayAge(); //"my age is 23"
要了解详细的闭包,推荐一下 阮一峰的网络日志-学习Javascript闭包(Closure)。
6 new构建对象的本质
function User(){
this.name = "Vicfeel";
this.age = 23;
}
var user = new User();
通过new操作符,实际上在构造函数User中完成了如下操作:
- 创建一个新的对象,这个对象的类型是object;
- 设置这个新的对象的内部、可访问性和prototype属性为构造函数(指prototype.construtor所指向的构造函数)中设置的;
- 执行构造函数;
- 返回新创建的对象。
function User(){
//this = {};
//this.constructor = User;
this.name = "Vicfeel";
this.age = 23;
//return this;
}
var user = new User();
如果构造函数默认返回的新创建的this对象,如果手动return 一个变量的话,如果该变量是原始类型则无效,如果是对象,则返回该对象。
7 JavaScript代理
当我们需要对很多元素添加事件的时候,可以通过将事件添加到它们的父节点而将事件委托给父节点来触发处理函数。
比如我们需要向一个ul中动态添加很多个li,需要遍历li逐个添加点击事件
<ul id=‘list‘></ul>
var count = 100;
var ulList = document.getElementById("list");
//动态构建节点
for(var i = count;i--;){
var liDom = document.createElement(‘li‘);
ulList.appendChild(liDom);
}
//绑定点击事件
var liNode = ulList.getElementByTagName("li");
for(var i=0, l = liNodes.length; i < l; i++){
liNode[i].onClick = function(){
//li点击事件
}
}
众所周知,DOM操作是十分消耗性能的。所以重复的事件绑定简直是性能杀手。而事件代理的核心思想,就是通过尽量少的绑定,去监听尽量多的事件。如何做呢?答案是利用事件冒泡机制,对其父节点ul进行事件绑定(Event Bubble),然后通过event.target来判断是哪个节点触发的事件,从而减少很多EventHandler的绑定。
var count = 100;
var ulList = document.getElementById("list");
//动态构建节点
for(var i = count;i--;){
var liDom = document.createElement(‘li‘);
ulList.appendChild(liDom);
}
//绑定点击事件
var liNode = ulList.getElementByTagName("li");
liNode.onClick = function(e){
if(e.target && e.target.nodeName.toUpperCase == "LI") {
// li点击事件
}
}
发现新内容会持续更新...
博文作者:vicfeel
博文出处:http://www.cnblogs.com/vicfeel
本文版权归作者和博客园共有,欢迎转载,但须保留此段声明,并给出原文链接,谢谢合作!
如果阅读了本文章,觉得有帮助,您可以为我的博文点击“推荐一下”!
以上是关于2023前端面试题及答案整理(JS面试题)的主要内容,如果未能解决你的问题,请参考以下文章