- 我的博客需要缩宽页面观看,图片无法均放,很抱歉。
1. 事件代理(委托)是什么?
2. 如何阻止事件冒泡 和 默认行为?
3. 查找,添加,移动,删除 DOM节点
4. 如何减少DOM操作
5. 解释 jsonp原理,为何它不是真正的ajax ?
- 浏览的同源策略(服务端没有同源策略)和跨域
JSONP(JSON with Padding)是一种跨域访问的技术手段,用于在不同域名之间传输数据,在前端应用中非常常见。JSONP的原理如下:
-
1. 前端页面向后端服务器发送一个带有 callback 参数的请求,例如:
http://www.example.com/data?callback=handleResponse
-
2. 后端服务器会根据请求中 callback 参数指定的函数名,构造一个以该函数名为函数体的 JavaScript 代码,并将需要传递的 JSON 数据包裹在其中,返回给前端页面,例如:
handleResponse({ "id": "123", "name": "John Doe", "age": 35});
-
3. 前端页面接收返回的 JavaScript 代码,直接以
script
标签的形式添加到页面中。后端返回的 JavaScript 代码会调用页面上定义的handleResponse
函数,并以 JSON 数据作为参数传入。 -
JSONP 之所以不是真正意义上的 AJAX 技术,是因为 AJAX 基于 XmlHttpRequest 对象进行通信,而 JSONP 是通过添加
script
标签实现的,是一种与 AJAX 类似但不同的实现方式。JSONP 也存在一些安全和性能上的问题,因此逐渐被 CORS(跨域资源共享)所取代。 -
以下列举一些常用的可以绕过跨域的标签:
-
img
:可以通过引入图片的方式跨域访问。 -
link
:可以通过引入样式表的方式跨域访问。 -
script
:可以通过引入JavaScript文件,或者JSONP的方式跨域访问。
需要注意的是,虽然这些标签可以绕过跨域限制,但也应该遵守安全限制,尽量避免跨域攻击和其他安全风险。同时,CORS 技术也提供了更加灵活和安全的跨域通信方式。
6. 闭包是什么,有什么新特性,有盒影响?
Closure(闭包)是指一个函数能够访问其词法作用域之外的变量的能力。也就是说,当一个内部函数在其外部函数之外被调用时,它仍然可以访问到该外部函数的变量。闭包可以使用变量和函数保护,避免与其他代码发生冲突。
-闭包具有以下特性:
- 可以访问包含该函数的外部函数的变量。
- 闭包函数在其外部函数被调用后仍然可以访问外部函数中的变量。
- 闭包函数可以访问外部函数中的实例变量,即使该变量在实例化之后进行了改变。
闭包可以带来一些影响,如内存泄露和性能问题,直接影响程序的执行效率和稳定性。需要注意在编码中合理运用闭包,避免出现问题。
下面是一个闭包的示例:
function outerFunction() {
var outerVar = 'I am outside!';
function innerFunction() {
console.log(outerVar);
}
return innerFunction; // 返回闭包函数
}
// 执行 outerFunction() 函数,把返回的内部函数赋值给 myFunc
var myFunc = outerFunction();
myFunc(); // 执行 myFunc() 函数,输出 "I am outside!"
6. 函数声明和函数表达式的区别?
- 主要在于定义函数的方式不同。
- 函数声明(Function Declaration)直接定义了一个新的函数,而函数表达式(Function Expression)将函数定义为一个值,可以在其他函数中使用。具体来说,函数声明可以被提升到当前作用域的顶部,所以在函数声明定义之前调用该函数也不会出错;
- 而函数表达式按照普通变量的方式定义,需要在声明之后才能使用。
下面是一个函数声明和函数表达式的示例:
// 函数声明
function sum(a, b) {
return a + b;
}
// 函数表达式
var sum = function(a, b) {
return a + b;
};
7. new Object() 和 Object.create() 区别?
-
new Object()
用于创建一个新的Object对象, - 而
Object.create()
则是利用现有的对象来创建一个新对象,使得新对象可以继承原型对象的属性和方法,具体差异如下:
new Object()
使用 Object 构造函数来创建新对象,使用{}
语法糖更为常见,返回的是一个空对象,不继承任何属性和方法。Object.create()
方法只能通过Object.prototype
调用,可以接收一个参数作为新对象的原型,返回一个新对象,继承了原型中的属性和方法。{ }等同于new Object(),有原型Object.prototype,Object.create(null) 没有原型,Object.create({...}) 可指定原型
以下是一个使用 Object.create()
方法创建新对象的示例:
// 创建一个发动机对象
var engine = {
power: '400kw',
brand: 'BMW'
};
// 创建一个车辆对象,继承发动机对象
var car = Object.create(engine);
car.color = 'red';
car.model = 'X5';
console.log(car.power); // "400kw"
console.log(car.color); // "red"
在上面的示例中,car
对象通过 Object.create
方法被创建,并继承了 engine
对象,因此可以直接访问 engine
对象的 power
和 brand
属性。
8. 手写trim 保证浏览器兼容性
9. 获取多个数字中最大值
-
在JavaScript中,
arguments
是一个与函数相关的特殊变量,它包含了函数被调用时传递的所有实参。然而,arguments
并不是一个真正的数组,它只是一个类数组对象
,因此它没有像数组那样的方法(如slice()、forEach()
等)。 -
为了能够在
arguments
上使用数组方法,可以使用Array.prototype.slice.call(arguments)
将其转换为真正的数组
。这行代码的作用是将arguments
对象转换为一个数组,并将该数组的副本存储在nums
变量中。 -
具体来说,
Array.prototype.slice()
是一个用于从现有数组中返回选定元素的方法
。当该方法被调用时,它会返回一个新的数组
,该数组包含了被选元素的副本。在这个例子中,我们使用call()
方法来将slice()
方法应用到arguments对象上
,并将其转换为一个数组
10. 如何使用 js 实现继承
11.如何捕获 js 中的异常?
12. 什么是JSON?
- JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,基于 JavaScript 语言的一个子集。JSON 格式描述了一种结构化的数据,用于网站前后端之间的数据传输与交互,也常被用于数据存储和配置文件中。
13. 获取当前页面url参数
- JavaScript 可以使用 location 对象来获取当前页面的 URL,并使用正则表达式。
- 传统方式:location.search
- 新API ,
URLSearchParams
对象来获取 URL 参数。
14. 手写深拷贝
注意,Object.assign不是深拷贝!!!
- 深拷贝指的
- 是创建一个新对象,这个新对象与原始对象的所有属性都具有相同的值,但是这
两个对象在内存中是独立的
,修改其中一个对象的属性不会影响到另一个对象的属性
。可以使用递归或循环遍历对象的属性来实现深拷贝
。
下面是一个更为优化的深拷贝函数:
- 下面对代码的每一行进行解释:
function deepClone(obj, map = new WeakMap()) {
:声明一个函数 deepClone,参数为 obj 和可选参数 map,用于实现深拷贝。将 map 的初始值设置为一个空的弱映射表。if (typeof obj !== 'object' || obj === null) { return obj; }
:判断 obj 是否为对象或数组,或者是否为 null,如果不是则直接返回 obj。if (map.has(obj)) { return map.get(obj); }
:如果 map 中已经存在当前要拷贝的对象(即已经遍历过该对象),则直接返回对应的克隆对象,避免进入死循环。const cloned = Array.isArray(obj) ? [] : {};
:声明一个变量 cloned,如果 obj 是数组则将 cloned 赋值为一个空数组,如果 obj 是对象则将 cloned 赋值为一个空对象。map.set(obj, cloned);
:将当前要拷贝的对象和对应的克隆对象的映射关系记录在 map 中。Object.keys(obj).forEach(key => { cloned[key] = deepClone(obj[key], map); });
:使用Object.keys
方法获取 obj 所有可枚举属性的 key,然后遍历每个 key,对 cloned 对象的这个 key 赋值为递归调用 deepClone 函数后返回的值,并传入当前的 map 映射表。return cloned;
:返回最终生成的克隆对象。
- 优化点如下:
- 添加了一个可选参数
map
,用于记录每个需要拷贝的对象和对应的克隆对象的映射关系,用于处理循环引用。这里使用了 JavaScript 的弱引用WeakMap
,如果某个拷贝对象已经在该映射表中有对应的克隆对象,则直接返回该克隆对象,避免进入死循环。 - 使用
Object.keys
替换循环遍历对象的方式,更加简洁高效。
15. 介绍一下 RAF requestAnimationFrame
-
requestAnimationFrame(简称RAF)是一个浏览器提供的API,用于在执行动画时实现更高效、更平滑的帧率。它告诉浏览器在下一次重绘前执行指定的回调函数。这样,浏览器可以在最佳时间执行动画,从而提高动画性能。
-
requestAnimationFrame具有以下特点和优势:
1.帧率与浏览器刷新率同步:requestAnimationFrame会自动调整回调函数的执行频率,以匹配浏览器的刷新率。这可以确保动画更加流畅,减少闪烁和卡顿现象
。
2. 节省资源:当浏览器标签页处于非激活状态或最小化时,RAF 会暂停回调函数的执行
。这可以节省CPU和GPU资源,降低设备功耗
。
3.适用于各种动画类型:RAF 适用于各种类型的动画,如CSS属性变化、Canvas绘制、SVG动画等。
4.简化动画代码:使用requestAnimationFrame可以简化动画代码,替代传统的setTimeout
或setInterval
方法。
css
#div1 {
width: 100px;
height: 50px;
background-color: cadetblue;
}
html 中
<div id="div1"></div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"/>
// 使用 RAF 实现动画效果
function animate() {
requestAnimationFrame(animate);
// 在这里更新动画效果
}
// 停止动画效果
function stopAnimate() {
cancelAnimationFrame(animate);
}
- 需要注意的是,RAF 并不是在所有浏览器中都实现了,因此可能需要使用
polyfill
来确保兼容性。不过,现代浏览器中大多已实现了requestAnimationFrame
16. 前端性能如何优化,一般从哪几个方面考虑?
- 1. 资源压缩与合并:
- 压缩 CSS、JavaScript 文件,减小文件大小,加快加载速度。例如使用
UglifyJS(JavaScript)
和CSSNano(CSS)
等工具进行压缩。 - 合并多个 CSS、JavaScript 文件,减少 HTTP 请求数。可以使用 Webpack、Gulp 等构建工具实现。
- 图片优化:
- 选择合适的图片格式,如
使用 WebP 格式
可以显著减小图片体积。 - 对
图片进行压缩
,可以使用TinyPNG、ImageOptim 等工具
。 - 使用
CSS sprites(雪碧图)
将多个小图标合并到一张图片中,减少 HTTP 请求数。 - 懒加载图片:只在图片进入可视区域时开始加载,例如使用
IntersectionObserver API
或第三方库如lozad.js
实现。 - 使用响应式图片,根据设备屏幕大小和分辨率加载合适尺寸的图片,可以使用
<picture>和srcset
属性实现。 - 缓存策略:
- 利用 HTTP 缓存,设置合适的
Cache-Control、ETag
等响应头,避免不必要的资源重新请求。 - 使用
Service Workers
缓存资源,实现离线访问。 - 代码优化:
- 删除无用的 CSS、JavaScript 代码,减小文件体积。
- 使用 CSS、JavaScript 的最新特性,提高性能。
- 避免使用耗性能的 CSS 属性,如
box-shadow、border-radius 等
。 - 优化 JavaScript 代码,避免长时间的阻塞操作,使用
Web Workers
处理复杂任务。 - 优化 DOM 操作和渲染:
- 减少 DOM 操作次数,尽量避免不必要的 DOM 重排(reflow)和重绘(repaint)。
- 使用
虚拟 DOM 技术
,如React、Vue
等框架,避免直接操作 DOM
。 - 使用
requestAnimationFrame
实现动画效果,避免使用setInterval
或setTimeout
。 - 对于大量数据的列表展示,使用
虚拟列表
(virtual list)或窗口化(windowing)技术,如react-window 等库
,仅渲染可视区域内的元素
。 - 网络优化:
- 使用
CDN(内容分发网络)
加速静态资源的访问。
尽量减少重定向次数。 - 使用 HTTP/2 协议,实现多路复用、头部压缩等优化。
预加载(preload)
、预获取(prefetch)
关键资源,提升性能。- 服务端渲染(SSR):
- 对于首屏内容较多的应用,使用服务端渲染可以减少首屏加载时间,提高用户体验。
这些方面的优化需要根据具体项目进行权衡和取舍,以达到最佳的性能表现。
17. Map 和 Set
- 是 JavaScript 中的两个非常有用的
数据结构
。它们分别用于存储键值对
和唯一值
。 - Map示例
// 创建一个空的 Map
let myMap = new Map();
// 添加键值对
myMap.set('name', 'John Doe');
myMap.set('age', 30);
// 获取键值
console.log(myMap.get('name')); // 输出:John Doe
console.log(myMap.get('age')); // 输出:30
// 删除一个键值对
myMap.delete('name');
// 获取 Map 的大小
console.log(myMap.size); // 输出:1
// 检查 Map 是否包含某个键
console.log(myMap.has('name')); // 输出:false
// 遍历 Map
myMap.forEach((value, key) => {
console.log(key + ' = ' + value);
});
- Set示例:
// 创建一个空的 Set
let mySet = new Set();
// 添加唯一值
mySet.add(1);
mySet.add(2);
mySet.add(3);
// 检查 Set 中是否包含某个值
console.log(mySet.has(1)); // 输出:true
// 获取 Set 的大小
console.log(mySet.size); // 输出:3
// 删除某个值
mySet.delete(1);
// 遍历 Set
mySet.forEach((value) => {
console.log(value);
});
两者的 区别 和特点
- Map 的特点:
-
- 存储键值对。
-
- 键可以是任何类型(例如、数字、对象、函数等)。
-
- 键的插入顺序保留。
-
- 提供内置方法,如 set、get、has、delete 等。
- Set 的特点
-
- 存储唯一值。
-
- 值可以是任何类型。
-
- 值的插入顺序保留。
-
- 提供内置方法,如 add、has、delete 等。
- 从上面的示例中,我们可以看到 Map 和 Set 在使用和操作上有很大的不同。
-
Map
适用于需要存储键值对并根据键查找值的场景
-
- 而
Set
适用于需要存储唯一值并检查某个值是否存在的场景。
- 而
18. Map 与 Object 的区别?
- 1. 键类型:
Map 可以接受任何类型的键
(例如对象、函数等),而Object 只接受字符串
和Symbol 类型的键
。 - 2. 遍历顺序:Map 中的键值对按照插入顺序进行遍历,而 Object 的遍历顺序可能因引擎实现而有所不同。
- 3. 性能:在需要频繁地添加和删除键值对的场景下,
Map 的性能通常优于 Object
。 - 4. 内置方法:
Map 提供了一些内置方法
,如size、has、delete
等,而Object 需要使用其他方法(如 Object.keys())获取类似的功能
。
Map 与 Object 的示例:
// 使用 Map
let map = new Map();
map.set('name', 'John Doe');
map.set(123, 'A Number');
map.set({
key: 'aKey' }, 'An Object Key');
console.log(map.get(123)); // 输出:A Number
// 使用 Object
let obj = {
};
obj['name'] = 'John Doe';
obj['123'] = 'A Number';
obj[{
key: 'aKey' }] = 'An Object Key'; // 注意:这里对象作为键会被转换为字符串 '[object Object]'
console.log(obj['123']); // 输出:A Number
Set 使用场景
(集合)是ES6中新增的一种数据类型,用于存储唯一的值。Set中的值不能重复,类似于数学中的集合概念。使用Set可以简单、高效地进行去重操作。
以下是一个使用Set实现数组去重的例子:
const arr = [1, 2, 3, 2, 1];
const set = new Set(arr);
const uniqueArr = Array.from(set); // [1, 2, 3]
在这个例子中,我们首先创建了一个包含重复元素的数组arr,然后使用new Set()方法将其转换为一个Set对象。由于Set不允许重复的值存在,所以重复的元素被自动过滤掉了。最后,我们将Set对象转换回数组uniqueArr,得到了去重后的结果
18. Set 和 数组的区别
- Set 和数组(Array)都是用于存储数据的数据结构,但它们有一些重要的区别。
- 1. 数据类型和顺序:数组可以包含不同类型的元素,并且元素按照它们在数组中出现的顺序进行排序。而 Set 只能包含唯一的值,这些值
无序排列
。
例如,下面是一个数组,其中包含三个不同类型的元素:
[1, "string", true]
而下面是一个 Set,其中包含两个唯一的数字:
Set {
1, 2}
- 2. 操作和方法:Set 提供了一些适用于唯一值的方法,如 add、has 和 delete。另一方面,数组提供了许多有用的方法,例如 push、pop、shift 和 unshift 等。
例如,以下代码演示了如何使用 Set 的基本操作:
const mySet = new Set();
mySet.add(1);
mySet.add(2);
mySet.add(3);
console.log(mySet.has(2)); // 输出 true
mySet.delete(2);
console.log(mySet.has(2)); // 输出 false
而以下代码演示了如何使用数组的基本操作:
const myArray = [1, 2, 3];
myArray.push(4);
myArray.pop();
console.log(myArray); // 输出 [1, 2, 3]
- 3. 用例:由于 Set 只能包含唯一的值,它通常用于检测重复项或过滤重复项。另一方面,数组通常用于存储和操作大量元素。
以下代码演示了如何使用 Set 来删除重复项:
const myArray = [1, 2, 3, 2, 1];
const mySet = new Set(myArray);
console.log([...mySet]); // 输出 [1, 2, 3]
以下代码演示了如何在数组中搜索一个元素:
const myArray = [1, 2, 3];
console.log(myArray.indexOf(2)); // 输出 1
19. WeakMap 和 WeakSet 区别?
-
WeakMap 和 WeakSet 都是 ES6 新增的数据结构,它们与 Map 和 Set 类似,但具有一些特殊的行为。
-
WeakMap 是一个键值对集合,其中的键
只能是对象
。与 Map 不同的是,当被引用的对象被垃圾回收后,WeakMap 中对应的键值对也将被自动删除
,这就意味着 WeakMap 中的键是弱引用
,不会阻止垃圾回收器回收它们所引用的对象。
以下是使用 WeakMap 的一个简单示例:
let wm = new WeakMap();
let obj1 = {
};
let obj2 = {
};
let obj3 = {
};
wm.set(obj1, "value associated with obj1");
wm.set(obj2, "value associated with obj2");
console.log(wm.get(obj1)); // "value associated with obj1"
console.log(wm.get(obj2)); // "value associated with obj2"
obj1 = null;
console.log(wm.get(obj1)); // undefined
-
在上面的示例中,我们创建了一个 WeakMap,并使用 obj1 和 obj2 添加了一些键值对。
-
然后,我们将 obj1 变量设置为 null,这将导致它引用的对象被垃圾回收。
-
-由于 obj1 不再引用任何对象,WeakMap 中对应的键值对也将被自动删除。
-
WeakSet 与 WeakMap 类似,但是它存储的是
对象的集合
,而不是键值对。WeakSet 的对象也是弱引用
,它也会自动删除被垃圾回收的对象。
以下是使用 WeakSet 的一个示例:
let ws = new WeakSet();
let obj1 = {
};
let obj2 = {
};
let obj3 = {
};
ws.add(obj1);
ws.add(obj2);
console.log(ws.has(obj1)); // true
console.log(ws.has(obj2)); // true
obj1 = null;
console.log(ws.has(obj1)); // false
- 在上面的示例中,我们创建了一个 WeakSet,并添加了一些对象。
- 然后,我们将 obj1 变量设置为 null,这将导致它引用的对象被垃圾回收。
- 由于 obj1 不再引用任何对象,WeakSet 中对应的对象也将被自动删除。
20. 使用 reduce 求数组和
- sum:累加器,它保存了上一次回调函数的返回值,或者在第一次调用时使用传递给 reduce 方法的初始值(本例中为 0)。
使用 reduce 记录重复的数值
// 使用reduce 计数
const arr = [10, 20, 30, 40, 50, 10, 20, 30, 20]
const n = 30
const count = arr.reduce((count, val) => {
// count 参数累加器,count +1 只要匹配到 n数值,就累加一个
return val === n ? count + 1 : count
}, 0)
console.log('count', count);
传统方式输出字符串
// 传统输出字符串
const arr = [
{
name: '章三', age: '20' },
{
name: '测试', age: '数据' },
{
name: '传统', age: '方式' }
]
const str = arr.map(item => {
return `${
item.name} - ${
item.age}`
}).join('\n')
console.log(str); // 打印结果如下
/* 章三 - 20
测试 - 数据
传统 - 方式 */
- 使用数组的map方法将原始数组arr中的每个对象映射为一个字符串。
- 在每个字符串中包含对象的name和age属性,并以“-”连接。
- 然后使用join方法将所有字符串合并为一个字符串,各个字符串之间用换行符分隔。 最终得到的字符串赋值给变量str。
使用 reduce 输出字符串
// 使用 reduce 输出字符串
const arr = [
{
name: '章三', age: '20' },
{
name: '测试', age: '数据' },
{
name: '传统', age: '方式' }
]
const str = arr.reduce((s, item) => {
return `${
s}${
item.name} - ${
item.age}\n`
}, '')
console.log(str);// 打印结果如下
/* 章三 - 20
测试 - 数据
传统 - 方式 */
const str = arr.reduce((s, item) => { return
${s}${item.name} - ${item.age}\n
},‘’)
- 这里使用了 reduce() 方法对数组进行遍历,并将每个对象的 name 和 age 属性拼接成字符串。
- 在这个函数中,第一个参数是回调函数,它会接收两个参数:累加器 s 和当前正在遍历的元素 item。
- 回调函数会返回一个字符串,这个字符串会被传递给下一个元素作为累加器 s 的值。
- 最后,reduce() 方法返回累加器的最终值。
注意,reduce() 方法的第二个参数是初始值,用于初始化累加器 s。在这里,初始值为空字符串 ‘’。
这里打印出了最终拼接好的字符串。该字符串包含了数组中所有对象的 name 和 age 属性。每个对象之间用换行符 \n 分隔开来。