前端八股文(三)JS、ES6 持续更新中。。。
js常见八股
1.addEventListener
的第三个参数
addEventListener
的第三个参数可以控制事件处理函数是在捕获阶段触发还是冒泡阶段触发:
-
true
:在捕获阶段触发。 -
false
或省略:在冒泡阶段触发(默认)。
javascript复制代码element.addEventListener('click', handleEvent, true); // 捕获阶段element.addEventListener('click', handleEvent); // 冒泡阶段
阻止事件传播
可以使用以下方法阻止事件继续传播:
-
阻止冒泡:
e.stopPropagation()
。该方法阻止事件在冒泡阶段传播到父元素。 -
完全阻止传播:
e.stopImmediatePropagation()
。不仅阻止事件冒泡,还阻止同一元素上绑定的其他事件处理函数执行。 -
阻止默认行为:
e.preventDefault()
,用于阻止元素的默认行为,比如阻止<a>
标签的跳转等。
应用场景
-
事件代理:利用冒泡,将事件绑定到父元素,然后根据
event.target
来确定点击的是哪个子元素。 -
拦截事件:通过捕获阶段在事件到达目标元素前对其进行拦截,比如一些特殊需求的控制逻辑。
2.js数据类型?
-
原始数据类型:string、number、boolean、null、undefined、symbol、bigInt
-
引用数据类型:object、array、function等对象
区别:
-
存储地址:原始数据类型存储于栈中,引用数据类型存储于堆中,声明的变量存储的是栈中指向堆的地址
-
拷贝方式:原始类型拷贝值本身,引用类型拷贝地址
-
修改影响:修改原始类型不会影响其他变量,修改引用类型的对象会影响指向同一对象的所有引用。
3.null和undefined的区别?
-
null:变量已声明,被显式赋值为‘null’,可用于对象初始化
-
undefined:变量声明但未复制
null == undefined为真,null === undefined为假
typeof undefined = “undefined”,typeof null = “object”
4.检测js数据类型的方法有哪些?
typeof
-
用于检测简单数据类型,除null,可以检测部分引用数据类型,除了function,其他均为object
-
typeof null = “object”(在 JavaScript 的早期版本中,所有对象(包括
null
)都在内存中以某种方式标识为对象。当时,null
的值被设计成一个指向空对象的引用。因此,typeof null
返回"object"
。) -
在第一版
JS
代码中用32位比特来存储值,通过值的1-3
位来识别类型,前三位为000
表示对象类型。而null
是一个空值,二进制表示都为0,所以前三位也就是000
,所以导致typeof null
返回"object"
instanceof
-
用于检测一个对象是否是某个构造函数的实例,用于检测引用数据类型,返回布尔值
Object.prototype.toString.call()
-
能够检测到所有类型,返回格式: "[object type]",通过 call(obj) 将this指向obj,从而打印出obj的类型
console.log(Object.prototype.toString.call({})); // "[object Object]"console.log(Object.prototype.toString.call([])); // "[object Array]"console.log(Object.prototype.toString.call(function(){})); // "[object Function]"console.log(Object.prototype.toString.call(new Date())); // "[object Date]"console.log(Object.prototype.toString.call(/regex/)); // "[object RegExp]"console.log(Object.prototype.toString.call(null)); // "[object Null]"console.log(Object.prototype.toString.call(undefined)); // "[object Undefined]"console.log(Object.prototype.toString.call("string")); // "[object String]"console.log(Object.prototype.toString.call(42)); // "[object Number]"console.log(Object.prototype.toString.call(true)); // "[object Boolean]"
5.为什么0.1+0.2 == 0.3为假
因为浮点数计算的精度问题,计算时十进制数转换为二进制,可能会导致溢出,0.1+0.2 = 0.30000000000000004
解决办法:(n1+n2).toFixed(2)保留两位小数
6.判断数组的方法
-
Array.isArray():返回true
-
instanceof:a instanceof Array 返回 true
-
Object.prototype.toString.call():Object.prototype.toString.call([1,2,3]) 返回“[object Array]”
-
通过原型链判断:arr.proto___== Array.prototype
7.什么是类数组(伪数组),怎么转换为数组?
类数组是有部分数组属性的对象,例如函数内的arguments对象,document.querySelectorAll()获取到的nodelist
特点:
-
有length属性,可以获取长度;
-
可以通过下标获取元素
-
不能使用数组的所有方法
-
可以通过for循环、for ... of ...遍历
-
typeof(伪数组) = “object”
转换为数组:
-
扩展运算符:[...arr]
-
Array.from(arr)方法
-
Array.prototype.slice.call(arr)
8.数组有哪些方法?(随便看一下即可 太多了)
1. 修改数组的方法
这些方法会直接修改数组本身。
-
push(element1, ..., elementN)
:在数组末尾添加一个或多个元素,返回新长度。 -
pop()
:删除数组的最后一个元素,返回该元素。 -
unshift(element1, ..., elementN)
:在数组开头添加一个或多个元素,返回新长度。 -
shift()
:删除数组的第一个元素,返回该元素。 -
splice(start, deleteCount, item1, ..., itemN)
:从指定位置删除、替换或插入元素,返回被删除的元素组成的数组。 -
fill(value, start, end)
:用一个静态值填充数组中的一部分。 -
copyWithin(target, start, end)
:将数组中的一部分复制到同一数组中的另一位置。
2. 访问数组的方法
这些方法不会修改数组本身。
-
concat(array1, array2, ...)
:合并两个或多个数组,返回新数组。 -
slice(start, end)
:返回数组中从start
到end
的浅拷贝。 -
join(separator)
:将数组元素连接成一个字符串,以separator
为分隔符。 -
toString()
:返回数组的字符串形式,与join(',')
类似。 -
at(index)
:返回指定索引位置的元素(支持负数索引,-1
表示最后一个元素)。
3. 查找和筛选元素的方法
用于查找或筛选符合条件的元素。
-
indexOf(searchElement, fromIndex)
:返回数组中第一次出现指定元素的索引,找不到则返回-1
。 -
lastIndexOf(searchElement, fromIndex)
:返回数组中最后一次出现指定元素的索引,找不到则返回-1
。 -
includes(valueToFind, fromIndex)
:检查数组是否包含指定的值,返回布尔值。 -
find(callback(element, index, array), thisArg)
:返回第一个满足条件的元素,找不到则返回undefined
。 -
findIndex(callback(element, index, array), thisArg)
:返回第一个满足条件的元素的索引,找不到则返回-1
。 -
filter(callback(element, index, array), thisArg)
:返回所有满足条件的元素组成的新数组。
4. 遍历数组的方法
用于遍历数组的每个元素,不会改变数组本身。
-
forEach(callback(element, index, array), thisArg)
:对数组中的每个元素执行一次callback
函数,不返回任何值。 -
map(callback(element, index, array), thisArg)
:返回对每个元素执行callback
后的新数组。 -
reduce(callback(accumulator, currentValue, index, array), initialValue)
:累计计算数组的每个值,返回最终的累计值。 -
reduceRight(callback(accumulator, currentValue, index, array), initialValue)
:从右到左累计计算数组的每个值。 -
some(callback(element, index, array), thisArg)
:只要数组中有一个元素满足条件,就返回true
。 -
every(callback(element, index, array), thisArg)
:数组中的每个元素都满足条件时,返回true
。
5. 排序和变换的方法
用于对数组进行排序、翻转等操作。
-
sort(compareFunction)
:对数组元素进行排序,默认按 Unicode 编码顺序排序(会改变原数组)。 -
reverse()
:反转数组元素的顺序(会改变原数组)。 -
flat(depth)
:将多维数组展平成指定深度的数组,默认为 1 层。 -
flatMap(callback(element, index, array), thisArg)
:对数组的每个元素执行映射,然后将结果展平为一维数组。
6. 其他方法
-
Array.isArray(value)
:检查一个值是否为数组,返回布尔值。 -
keys()
:返回包含数组索引的Iterator
对象。 -
values()
:返回包含数组元素的Iterator
对象。 -
entries()
:返回包含数组索引和值的Iterator
对象。 -
from(arrayLike, mapFn, thisArg)
:从类数组或可迭代对象创建新的数组实例。 -
of(element0, element1, ..., elementN)
:根据给定的元素创建一个新的数组实例。
9.substring和substr的区别
它们都是字符串方法,用于截取字符串的一部分,主要区别在于参数不同
-
substring(startIndex, endIndex)
: 接收两个参数,一个起始索引和结束索引,来指定字符串范围,如果省略第二个参数,则截取到字符串末尾。 -
substr(startIndex, length)
: 接收两个参数,并返回从startIndex
开始,长度为length
的子字符串。如果省略第二个参数,则截取到字符串末尾。
10.浅拷贝和深拷贝的区别?如何实现?
(1)浅拷贝:仅复制第一层数据,与原数据指向同一地址,改变数据会导致原数据也改变
深拷贝:不会改变,地址指向不同
(2)实现浅拷贝:
-
扩展运算符...
-
Object.assign()
-
数组slice方法
slice:如果数组内数据是普通数据类型,就深拷贝;是引用数据类型就浅拷贝
const arr = [1, 2, { a: 3 }];// 使用 slice 方法const newArr = arr.slice(); // 浅拷贝newArr[0] = 10; // 修改普通数据类型console.log(arr[0]); // 输出 1,arr 没有受到影响newArr[2].a = 20; // 修改引用数据类型console.log(arr[2].a); // 输出 20,arr 受到影响
(3)实现深拷贝:
-
递归
function deepCopy(obj) {if (typeof obj !== 'object' || obj === null) {return obj;}let result = Array.isArray(obj) ? [] : {};for (let key in obj) {if (obj.hasOwnProperty(key)) {result[key] = deepCopy(obj[key]);}}return result;}
-
JSON.stringfy()和parse()
11.Object.assign()和扩展运算符是不是浅拷贝?有什么区别?
都是浅拷贝
Object.assign(),第一个参数传入目标对象,后面的参数传入源对象,将源对象全部浅拷贝到目标对象,后者会覆盖前者相同的属性
使用扩展运算符会将值浅拷贝到新的对象或数组中
12.new操作符的实现原理
-
创建一个空对象
-
将空对象的--proto--指向构造函数的prototype
-
将构造函数的this指向这个空对象
-
执行构造函数,返回该对象,或者在构造函数有显式返回对象是返回这个对象
13.for...in...和for...of...的区别
for in:
-
用于遍历对象的可枚举属性,例如对象、数组(不推荐
-
返回的是对象的键值,数组的索引
-
问题:可能会遍历到原型链上的属性,通过hasOwnProperty()可避免
-
// 创建一个数组const arr = [1, 2, 3];// 在数组的原型上添加一个可枚举属性Array.prototype.customProp = 'This is a custom property';// 使用 for...in 遍历数组console.log("Using for...in:");for (let index in arr) {// 使用 hasOwnProperty 过滤掉原型链上的属性if (arr.hasOwnProperty(index)) {console.log(index); // 输出 0, 1, 2 (数组索引)}}// 不使用 hasOwnProperty,直接输出console.log("Without hasOwnProperty:");for (let index in arr) {console.log(index); // 输出 0, 1, 2, customProp}// 使用 for...of 正确遍历数组console.log("Using for...of:");for (let value of arr) {console.log(value); // 输出 1, 2, 3}
for of:
-
用于遍历可迭代的对象,例如数组、伪数组、字符串、map、set
-
返回属性值
14.如何使用for...of...遍历对象?
不能直接遍历的原因:es6新增迭代器iterator,能被其遍历的数据内部都有一个iterator接口,数组、字符串、map、set内部已实现迭代器,而普通对象内没有该接口,因此遍历会报错
1.自定义一个迭代器属性
const a = {a:1,b:2}a[Symbol.iterator] = function*() { // function*{} 定义生成器函数的关键字const keys = Object.keys(a)for(let k of keys){yield([k,a[k]])}}for(let [key,value] of a){console.log(key,value) // 'a' 1 ; 'b' 2}
2.使用对象方法获取键值数组
const a = {a:1,b:2}for(let [key,value] of Object.entries(a)){console.log(key,value) // 'a' 1 ; 'b' 2}
或者Object.keys()、Object.values()
ES6常见面经
1.let、const、var的区别
-
let、const和var的区别
-
作用域不同:let、const具有块级作用域({}包裹);var具有全局作用域、函数作用域
-
变量提升:使用var声明会出现变量提升,将变量初始化提升到全局/函数作用域顶部,可以被访问,值为undefined
-
暂时性死区:用let、const声明变量之前会出现暂时性死区,访问变量会报错ReferenceError;用var声明变量前访问为undefined
-
重复声明:var可以重复声明变量,后声明的会覆盖之前的;let、const不能重复声明
-
-
let和const的区别
-
let在赋值之后可以重新赋值,const不可以重新赋值
-
const声明时必须赋值
-
let一般用于需要改变的普通数据类型,const一般用于常量或复杂数据类型
-
2.箭头函数和普通函数的区别
-
this指向不同:箭头函数的this取决于外层作用域的this指向;普通函数的this取决于调用函数的上下文
-
arguments对象:箭头函数没有arguments对象;普通函数有
-
不能new:箭头函数不能作为构造函数使用new关键字,会报错is not a constructor
-
箭头函数没有prototype
-
使用call、bind、apply不能改变this的指向
3.set和map的区别
-
set:
-
是一种集合,存储唯一值
-
new Set([1,2,3,3]) => Set {1,2,3}
-
add(value)
:添加某个值,返回Set结构本身。 -
delete(value)
:删除某个值,返回一个布尔值,表示删除是否成功。 -
has(value)
:返回一个布尔值,表示该值是否为Set的成员。 -
clear()
:清除所有成员,没有返回值。
-
-
map:
-
是一种存储键值对的集合,键可以存储任何类型
-
new Map()
-
set(key, val):
向Map
中添加新元素 -
get(key):
通过键值查找特定的数值并返回 -
has(key):
判断
Map对象中是否有
Key所对应的值,有返回
true,否则返回
false -
delete(key):
通过键值从Map
中移除对应的数据 -
clear():
将这个Map
中的所有元素删除
-
set和数组的区别?
-
set存储唯一值,用于去重
-
set不可以通过下标访问元素,数组可以
-
set基于哈希结构,查找、添加、删除效率更高
-
set转换为数组:arr = [...new Set([1,2,3,3])]
map和对象的区别?
-
map的键可以存储任何元素,对象只能存储字符串和Symbol
-
map的顺序是键值加入的顺序,对象的键值存储没有顺序
-
map的键值对可以使用for...of...遍历,对象需要借助Object.entries()
-
map的查找、新增、删除效率更高
map和weakMap的区别
它们是 JavaScript
中的两种不同的键值对集合,主要区别如下:
-
map
的键可以是任意类型,weakMap
键只能是对象类型。 -
map
使用常规的引用来管理键和值之间的关系,因此即使键不再使用,map
仍然会保留该键的内存。weakMap
使用弱引用来管理键和值之间的关系,因此如果键不再有其他引用,垃圾回收机制可以自动回收键值对。
4.说说对promise的理解
promise是一种异步编程的解决方案,用于解决回调地狱的问题
-
promise有三种状态,pending、fulfilled、rejected;从pending转变为fulfilled或resolved,这个结果不能逆转
-
Promise构造函数接收一个带有resolve和reject的回调函数
-
resolve的作用是将promise状态从pending变为fulfilled,在异步操作成功时调用,并将异步结果返回,作为参数传递出去
-
reject的作用是将promise状态从pending变为rejected,在异步操作失败时调用,并将结果返回作为参数传递出去
-
Promise
的缺点:
-
无法取消
Promise
,一旦新建它就会立即执行,无法中途取消。 -
如果不设置回调函数,
Promise
内部抛出的错误,不会反应到外部。 -
当处于
pending
状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
promise的常见方法有哪些
-
then():接收成功和失败两个回调函数,resolve=>{}和reject=>{}
-
catch():接收失败的回调函数
-
finally():无论成功失败,都执行的回调函数
-
all([promise1,promise2]):接收promise数组,如果全部resolve,返回结果数组(按照传入的顺序);如果有一个rejected,返回这个失败的promise结果
-
allSettled([p1,p2]):无论成功失败,返回结果数组
-
race([p1,p2,p3]):返回最先改变状态的结果,无论成功失败
-
any([p1,p2,p3]):只要有resolve就返回resolve的结果,除非全部reject,才会返回reject结果数组
-
resolve():返回一个结果为fulfilled的promise,用于将普通对象转换为promise对象
-
reject():返回一个结果为rejected的promise
5.对async和await的理解
async/await是generator的语法糖,为了能让异步代码看起来更像同步代码
通过async声明一个函数为异步函数,await用来标志异步函数内的异步操作,当代码运行到await这一行,会阻塞执行并等待异步方法执行完成,并将resolve结果返回
async声明的异步函数会返回一个promise对象,如果在异步函数内return一个值,会将其包裹为promise对象后返回,如果没有return,就会返回Promise.resolve(undefined)
如果await标志的异步操作rejected,没有用catch接收的话,async声明的异步函数就会返回一个rejected的promise对象
const fun = async()=>{const res = await Promise.reject(7)console.log(res); // 在上一步退出执行了 这里不输出return 111}fun().then(res=>{console.log('fun',res);},rej=>{console.log('rej',rej);})//输出结果:rej 7
如果catch接收则正常返回111:
const fun = async()=>{try{const res = await Promise.reject(7)}catch(err){console.log('catch err',err);}return 111}fun().then(res=>{console.log('fun',res);},rej=>{console.log('rej',rej);})// 输出结果:catch err 7 fun 111
async/await和promise.then有什么区别?
6.es6有哪些新增?
7.es module和commonJS有什么区别?
语法不同
异步加载
8.原型和原型链
原型
原型链
每个实例对象都有一个__proto__
属性指向它的构造函数的原型对象,而这个原型对象也会有自己的原型对象,一层一层向上,直到顶级原型对象null
,这样就形成了一个原型链。
当访问对象的一个属性或方法时,当对象身上不存在该属性方法时,就会沿着原型链向上查找,直到查找到该属性方法位置。
原型链的顶层原型是Object.prototype
,如果这里没有就只指向null
9.对作用域、作用域链的理解
作用域是一个变量或函数的可访问范围,作用域控制着变量或函数的可见性和生命周期。
作用域链: 变量在指定的作用域中没有找到,会依次向一层作用域进行查找,直到全局作用域。这个查找的过程被称为作用域链。
10.apply,call,bind的区别
(1)执行方式不同
apply和call是同步函数,立即执行函数
bind是异步函数,返回一个函数
(2)传入参数不同
第一个参数都是要指向的元素,
apply第二个参数传入参数数组
call和bind可传入多个参数
(3)修改this性质不同
apply和call只是暂时性修改this指向
bind是永久改变this指向
-
let、const和var的区别
-
作用域不同:let、const具有块级作用域({}包裹);var具有全局作用域、函数作用域
-
变量提升:使用var声明会出现变量提升,将变量初始化提升到全局/函数作用域顶部,可以被访问,值为undefined
-
暂时性死区:用let、const声明变量之前会出现暂时性死区,访问变量会报错ReferenceError;用var声明变量前访问为undefined
-
重复声明:var可以重复声明变量,后声明的会覆盖之前的;let、const不能重复声明
-
-
let和const的区别
-
let在赋值之后可以重新赋值,const不可以重新赋值
-
const声明时必须赋值
-
let一般用于需要改变的普通数据类型,const一般用于常量或复杂数据类型
-
-
this指向不同:箭头函数的this取决于外层作用域的this指向;普通函数的this取决于调用函数的上下文
-
arguments对象:箭头函数没有arguments对象;普通函数有
-
不能new:箭头函数不能作为构造函数使用new关键字,会报错is not a constructor
-
箭头函数没有prototype
-
使用call、bind、apply不能改变this的指向
-
set:
-
是一种集合,存储唯一值
-
new Set([1,2,3,3]) => Set {1,2,3}
-
add(value)
:添加某个值,返回Set结构本身。 -
delete(value)
:删除某个值,返回一个布尔值,表示删除是否成功。 -
has(value)
:返回一个布尔值,表示该值是否为Set的成员。 -
clear()
:清除所有成员,没有返回值。
-
-
map:
-
是一种存储键值对的集合,键可以存储任何类型
-
new Map()
-
set(key, val):
向Map
中添加新元素 -
get(key):
通过键值查找特定的数值并返回 -
has(key):
判断
Map对象中是否有
Key所对应的值,有返回
true,否则返回
false -
delete(key):
通过键值从Map
中移除对应的数据 -
clear():
将这个Map
中的所有元素删除
-
-
set存储唯一值,用于去重
-
set不可以通过下标访问元素,数组可以
-
set基于哈希结构,查找、添加、删除效率更高
-
set转换为数组:arr = [...new Set([1,2,3,3])]
-
map的键可以存储任何元素,对象只能存储字符串和Symbol
-
map的顺序是键值加入的顺序,对象的键值存储没有顺序
-
map的键值对可以使用for...of...遍历,对象需要借助Object.entries()
-
map的查找、新增、删除效率更高
-
map
的键可以是任意类型,weakMap
键只能是对象类型。 -
map
使用常规的引用来管理键和值之间的关系,因此即使键不再使用,map
仍然会保留该键的内存。weakMap
使用弱引用来管理键和值之间的关系,因此如果键不再有其他引用,垃圾回收机制可以自动回收键值对。 -
promise有三种状态,pending、fulfilled、rejected;从pending转变为fulfilled或resolved,这个结果不能逆转
-
Promise构造函数接收一个带有resolve和reject的回调函数
-
resolve的作用是将promise状态从pending变为fulfilled,在异步操作成功时调用,并将异步结果返回,作为参数传递出去
-
reject的作用是将promise状态从pending变为rejected,在异步操作失败时调用,并将结果返回作为参数传递出去
-
-
无法取消
Promise
,一旦新建它就会立即执行,无法中途取消。 -
如果不设置回调函数,
Promise
内部抛出的错误,不会反应到外部。 -
当处于
pending
状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。 -
then():接收成功和失败两个回调函数,resolve=>{}和reject=>{}
-
catch():接收失败的回调函数
-
finally():无论成功失败,都执行的回调函数
-
all([promise1,promise2]):接收promise数组,如果全部resolve,返回结果数组(按照传入的顺序);如果有一个rejected,返回这个失败的promise结果
-
allSettled([p1,p2]):无论成功失败,返回结果数组
-
race([p1,p2,p3]):返回最先改变状态的结果,无论成功失败
-
any([p1,p2,p3]):只要有resolve就返回resolve的结果,除非全部reject,才会返回reject结果数组
-
resolve():返回一个结果为fulfilled的promise,用于将普通对象转换为promise对象
-
reject():返回一个结果为rejected的promise
-
async/await是Gnerator的语法糖,能让异步代码看起来同步代码,代码清晰
-
async/await缺点:无法处理reject的回调,要使用try catch接收
-
async/Await与Promise最大区别在于:await b()会暂停所在的async函数的执行;而Promise.then(b)将b函数加入回调链中之后,会继续执行当前函数
-
解构赋值
-
扩展运算符
-
promise、async/await
-
箭头函数
-
模板字符串
-
新增数组方法,for each、map、filter
-
set、map
-
新增class类
-
es module
-
ESModule导入导出关键字:import、export
-
commonjs导入导出:require,module.exports/exports
-
es模块支持动态导入,可以异步加载模块,按需加载提高性能
-
commonjs是同步加载的
-
prototype : js通过构造函数来创建对象,每个构造函数内部都会一个原型
prototype
属性,它指向另外一个对象,这个对象包含了可以由该构造函数的所有实例共享的属性和方法。 -
proto: 当使用构造函数创建一个实例对象后,可以通过
__proto__
访问到prototype
属性。 -
constructor:实例对象通过这个属性可以访问到构造函数
-
全局作用域:可以全局访问
-
最外层函数和最外层定义的变量拥有全局作用域
-
window
上的对象属性方法拥有全局作用域 -
为定义直接复制的变量自动申明拥有全局作用域
-
过多的全局作用域变量会导致变量全局污染,命名冲突
-
-
函数作用域:只能在函数中访问使用
-
在函数中定义的变量,都只能在内部使用,外部无法访问
-
内层作用域可以访问外层,外层不能访问内存作用域
-
-
ES6中的块级作用域:只在代码块中访问使用
-
使用ES6中新增的
let
、const
什么的变量,具备块级作用域,块级作用域可以在函数中创建(由{}包裹的代码都是块级作用域) -
let
、const
申明的变量不会变量提升,const
也不能重复申明 -
块级作用域主要用来解决由变量提升导致的变量覆盖问题
-