1. 相比于npm和yarn,pnpm的优势是什么?
2. 数组的常用方法有哪些?
2.1、操作方法
增:前三种是对原数组产生影响的增添方法,第四种则不会对原数组产生影响
- push() 接收任意数量的参数,并将它们添加到数组末尾,返回数组的最新长度
- unshift()在数组开头添加任意多个值,然后返回新的数组长度
- splice()传入三个参数,分别是开始位置、0(要删除的元素数量)、插入的元素,返回空数组
- concat()首先会创建一个当前数组的副本,然后再把它的参数添加到副本末尾,最后返回这个新构建的数组,不会影响原始数组
删:前三种是对原数组产生影响的增添方法,第四种则不会对原数组产生影响
- pop()用于删除数组的最后一项,同时减少数组的
length值,返回被删除的项 - shift()用于删除数组的第一项,同时减少数组的
length值,返回被删除的项 - splice()传入两个参数,分别是开始位置,删除元素的数量,返回包含删除元素的数组
- slice()用于创建一个包含原有数组中一个或多个元素的新数组,不会影响原始数组(第一个参数要,第二个参数不要)
let colors = ["red", "green", "blue", "yellow", "purple"];
let colors2 = colors.slice(1);
let colors3 = colors.slice(1, 4);
console.log(colors) // red,green,blue,yellow,purple
concole.log(colors2); // green,blue,yellow,purple
concole.log(colors3); // green,blue,yellow
改:即修改原来数组的内容,常用splice
传入三个参数,分别是开始位置,要删除元素的数量,要插入的任意多个元素,返回删除元素的数组,对原数组产生影响
let colors = ["red", "green", "blue"];
let removed = colors.splice(1, 1, "red", "purple"); // 插入两个值,删除一个元素
console.log(colors); // red,red,purple,blue
console.log(removed); // green,只有一个元素的数组
查:即查找元素,返回元素坐标或者元素值
- indexOf() 返回要查找的元素在数组中的位置,如果没找到则返回 -1
- includes() 返回要查找的元素在数组中的位置,找到返回
true,否则false - find() 返回第一个匹配的元素
面试官:数组的常用方法有哪些? | web前端面试 - 面试官系列
2.2、排序方法
- reverse() 将数组元素方向反转
- sort() 接受一个比较函数,用于判断哪个值应该排在前面
function compare(value1, value2) {
if (value1 < value2) {
return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
}
let values = [0, 1, 5, 10, 15];
values.sort(compare);
alert(values); // 0,1,5,10,15
面试官:数组的常用方法有哪些? | web前端面试 - 面试官系列
2.3、转换方法
join() 接收一个参数,即字符串分隔符,返回包含所有项的字符串
let colors = ["red", "green", "blue"];
alert(colors.join(",")); // red,green,blue
alert(colors.join("||")); // red||green||blue
面试官:数组的常用方法有哪些? | web前端面试 - 面试官系列
2.4、迭代方法
- some() 对数组每一项都运行传入的测试函数,如果至少有1个元素返回 true ,则这个方法返回 true
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let someResult = numbers.some((item, index, array) => item > 2);
console.log(someResult) // true
- every() 对数组每一项都运行传入的测试函数,如果所有元素都返回 true ,则这个方法返回 true
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let everyResult = numbers.every((item, index, array) => item > 2);
console.log(everyResult) // false
- forEach() 对数组每一项都运行传入的函数,没有返回值 。(循环方法)
- filter() 对数组每一项都运行传入的函数,函数返回
true的项会组成数组之后返回
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let filterResult = numbers.filter((item, index, array) => item > 2);
console.log(filterResult); // 3,4,5,4,3
- map() 对数组每一项都运行传入的函数,返回由每次函数调用的结果构成的数组
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let mapResult = numbers.map((item, index, array) => item * 2);
console.log(mapResult) // 2,4,6,8,10,8,6,4,2
- redece()方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
reduce()最简单的用法就是累加和累乘
// 累加和累乘
let arr3 = [1,3,4,6,7];
let sum3 = arr3.reduce((pre,cur)=> pre+cur)
let multiply = arr3.reduce((pre,cur) => pre*cur)
console.log(sum3) // 21
console.log(multiply) // 504
reduce()的高级用法:
1、数组去重
// 数组去重
let arr4 = [1,3,2,5,3,1,2,7,8];
let newArr = arr4.reduce((pre,cur)=>{
if(!pre.includes(cur)){
return pre.concat(cur)
} else {
return pre
}
},[]);
console.log(newArr, '数组去重') // [1, 3, 2, 5, 7, 8]
原文链接:blog.csdn.net/weixin_4444…
3. 说说JavaScript中的数据类型?存储上的差别?
在JavaScript中,我们可以分成两种类型:
- 基本类型
- 复杂类型
基本类型主要为以下6种:
- Number
- String
- Boolean
- Undefined
- null
- symbol
- BigInt
其中Symbol和BigInt是ES6新增的数据类型:
- Symbol 代表独一无二的值,最大的用法是用来定义对象的唯一属性名。
- BigInt是一种数字类型的数据,可以表示任意精度格式的整数,使用 BigInt 可以安全地存储和操作大整数。
(引用)复杂类型统称为Object,主要是下面三种:
- Object
- Array
- Function
基本数据类型和引用数据类型存储在内存中的位置不同:
- 基本数据类型存储在栈中
- 引用类型的对象存储于堆中
当我们把变量赋值给一个变量时,解析器首先要确认的就是这个值是基本类型值还是引用类型值
基本类型
let a = 10;
let b = a; // 赋值操作
b = 20;
console.log(a); // 10值
a的值为一个基本类型,是存储在栈中,将a的值赋给b,虽然两个变量的值相等,但是两个变量保存了两个不同的内存地址
下图演示了基本类型赋值的过程:
引用类型
var obj1 = {}
var obj2 = obj1;
obj2.name = "Xxx";
console.log(obj1.name); // xxx
引用类型数据存放在堆中,每个堆内存对象都有对应的引用地址指向它,引用地址存放在栈中。
obj1是一个引用类型,在赋值操作过程汇总,实际是将堆内存对象在栈内存的引用地址复制了一份给了obj2,实际上他们共同指向了同一个堆内存对象,所以更改obj2会对obj1产生影响
下图演示这个引用类型赋值过程
小结
-
声明变量时不同的内存地址分配:
- 简单类型的值存放在栈中,在栈中存放的是对应的值
- 引用类型对应的值存储在堆中,在栈中存放的是指向堆内存的地址
-
不同的类型数据导致赋值变量时的不同:
- 简单类型赋值,两个对象对应不同的地址
- 复杂类型赋值,是将保存对象的内存地址赋值给另一个变量。也就是两个变量指向堆内存中同一个对象
4. Map 和 Object 的区别
key filed
在 Object 中, key 必须是简单数据类型(整数,字符串或者是 symbol),而在 Map 中则可以是 JavaScript 支持的所有数据类型,也就是说可以用一个 Object 来当做一个Map元素的 key。
概念
- Object
在ECMAScript中,Object是一个特殊的对象。它本身是一个顶级对象,同时还是一个构造函数,可以通过它(如:new Object())来创建一个对象。我们可以认为JavaScript中所有的对象都是Object的一个实例,对象可以用字面量的方法const obj = {}即可声明。
- Map
Map是Object的一个子类,可以有序保存任意类型的数据,使用键值对去存储,其中键可以存储任意类型,通过const m = new Map();即可得到一个map实例。
访问
map: 通过map.get(key)方法去属性, 不存在则返回undefined
object: 通过obj.a或者obj['a']去访问一个属性, 不存在则返回undefined
赋值
map: 通过map.set去设置一个值,key可以是任意类型
object: 通过object.a = 1或者object['a'] = 1,去赋值,key只能是字符串,数字或symbol
删除
map: 通过map.delete去删除一个值,试图删除一个不存在的属性会返回false
object: 通过delete操作符才能删除对象的一个属性,诡异的是,即使对象不存在该属性,删除也返回true,当然可以通过Reflect.deleteProperty(target, prop) 删除不存在的属性还是会返回true。
var obj = {}; // undefined
delete obj.a // true
大小
map: 通过map.size即可快速获取到内部元素的总个数
object: 需要通过Object.keys的转换才能将其转换为数组,再通过数组的length方法去获得或者使用Reflect.ownKeys(obj)也可以获取到keys的集合
迭代
map: 拥有迭代器,可以通过for-of forEach去直接迭代元素,切遍历顺序是确定的
object: 并没有实现迭代器,需要自行实现,不实现只能通过for-in循环去迭代,遍历顺序是不确定的
使用场景
- 如果只需要简单的存储key-value的数据,并且key不需要存储复杂类型的,直接用对象
- 如果该对象必须通过JSON转换的,则只能用对象,目前暂不支持Map
- map的阅读性更好,所有操作都是通过api形式去调用,更有编程体验
5. 数据类型的判断
- typeof: 能判断所有值类型,函数。不可对 null、对象、数组进行精确判断,因为都返回
object。
console.log(typeof undefined); // undefined
console.log(typeof 2); // number
console.log(typeof true); // boolean
console.log(typeof "str"); // string
console.log(typeof Symbol("foo")); // symbol
console.log(typeof 2172141653n); // bigint
console.log(typeof function () {}); // function
// 不能判别
console.log(typeof []); // object
console.log(typeof {}); // object
console.log(typeof null); // object
- instanceof:instanceof运算符用于通过查找原型链来检查某个变量是否为某个类型数据的实例。比如考虑以下代码:
class People {}
class Student extends People {}
const vortesnail = new Student();
console.log(vortesnail instanceof People); // true
console.log(vortesnail instanceof Student); // true
复制代码
其实现就是顺着原型链去找,如果能找到对应的 Xxxxx.prototype 即为 true 。比如这里的 vortesnail 作为实例,顺着原型链能找到 Student.prototype 及 People.prototype ,所以都为 true 。
instanceof底层是如何工作的:
在 MDN 上是这样描述 instanceof 的:
instanceof运算符用于测试构造函数的prototype属性是否出现在对象原型链中的任何位置 换句话说,如果A instanceof B,那么A必须是一个对象,而B必须是一个合法的 JavaScript 函数。在这两个条件都满足的情况下:
判断 B 的 prototype 属性指向的原型对象(B.prototype)是否在对象 A 的原型链上。
如果在,则为 true;如果不在,则为 false。
- Object.prototype.toString.call() :所有原始数据类型都是能判断的,还有 Error 对象,Date 对象等。
Object.prototype.toString.call(2); // "[object Number]"
Object.prototype.toString.call(""); // "[object String]"
Object.prototype.toString.call(true); // "[object Boolean]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(Math); // "[object Math]"
Object.prototype.toString.call({}); // "[object Object]"
Object.prototype.toString.call([]); // "[object Array]"
Object.prototype.toString.call(function () {}); // "[object Function]"
复制代码
在面试中有一个经常被问的问题就是:如何判断变量是否为数组?
Array.isArray(arr); // true
arr.__proto__ === Array.prototype; // true
arr instanceof Array; // true
Object.prototype.toString.call(arr); // "[object Array]"
- constructor方法
除了 undefined 和 null 之外,其他类型都可以通过 constructor 属性来判断类型。
const c = 100
const d = 'warbler'
const e = true
const f = Symbol('f')
const reg = /^[a-zA-Z]{5,20}$/
const foo = () => { }
const arr = []
const obj = {}
const date = new Date();
const error = new Error();
console.log(c.constructor === Number) //=> true
console.log(d.constructor === String) //=> true
console.log(e.constructor === Boolean) //=> true
console.log(f.constructor === Symbol) //=> true
console.log(reg.constructor === RegExp) //=> true
console.log(foo.constructor === Function) //=> true
console.log(arr.constructor === Array) //=> true
console.log(obj.constructor === Object) //=> true
console.log(date.constructor === Date) //=> true
console.log(error.constructor === Error) //=> true
6. 深拷贝和浅拷贝的区别?
- 浅拷贝:会在栈中开辟另一块空间,并将被拷贝对象的栈内存数据完全拷贝到该块空间中,即基本数据类型的值会被完全拷贝,而引用类型的值则是拷贝了“指向堆内存的地址”。
- 深拷贝:不仅会在栈中开辟另一块空间,若被拷贝对象中有引用类型,则还会在堆内存中开辟另一块空间存储引用类型的真实数据。
浅拷贝是拷贝一层,属性为对象时,浅拷贝是复制,两个对象指向同一个地址
深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址
浅拷贝的实现
- 展开运算符...
- Object.assign()
- concat()
- slice()
深拷贝的实现
- JSON.parse(JSON.stringify()):JSON.stringify()
但是
- 当对象中有时间类型的元素时候 -----时间类型会被变成字符串类型数据
- 当对象中有undefined类型或function类型的数据时 --- undefined和function会直接丢失
- 当对象中有NaN、Infinity和-Infinity这三种值的时候 --- 会变成null
- 当对象循环引用的时候 --会报错
- jQuery.extend()方法
// 需要引入jQuery库哦~
let obj = {
name: 'Chen',
hobby: [
'see a film',
'write the code',
'play basketball',
'tourism'
]
}
let obj1 = jQuery.extend(true, {}, obj);
console.log(obj === obj1); // false
obj2.name = 'Forever';
obj2.hobby[1] = 'swim';
obj2.hobby[2] = 'alpinism';
console.log('obj1===>', obj1); // obj1===> { name: 'Chen',hobby: ['see a film', 'write the code', 'play basketball', 'tourism']}
console.log('obj2===>', obj2); // obj1===> { name: 'Chen',hobby: ['see a film', 'swim', 'alpinism', 'tourism']}
复制代码
- 手写递归方法:(递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝)
// 检测数据类型的功能函数
const checkedType = (target) => Object.prototype.toString.call(target).replace(/[object (\w+)]/, "$1").toLowerCase();
// 实现深拷贝(Object/Array)
const clone = (target) => {
let result;
let type = checkedType(target);
if(type === 'object') result = {};
else if(type === 'array') result = [];
else return target;
for (let key in target) {
if(checkedType(target[key]) === 'object' || checkedType(target[key]) === 'array') {
result[key] = clone(target[key]);
} else {
result[key] = target[key];
}
}
return result;
}
复制代码
调用一下手写递归实现深拷贝方法:🙊
const obj = {
name: 'Chen',
detail: {
age: '18',
height: '180',
bodyWeight: '68'
},
hobby: ['see a film', 'write the code', 'play basketball', 'tourism']
}
const obj1 = clone(obj);
console.log(obj1); // { name: 'Chen',detail: { age: '18', height: '180', bodyWeight: '68' }, hobby: [ 'see a film', 'write the code', 'play basketball', 'tourism' ]}
console.log(obj1 === obj); // false
复制代码
7. js 原型和原型链
👉👉 讲的非常非常好👉👉深入JavaScript系列(六):原型与原型链
Person.prototype.constructor 是什么
Person.prototype.constructor === Person // true
复制代码
8. 函数有没有 __ proto __ 属性
let fn = function() {}
// 函数(包括原生构造函数)的原型对象为Function.prototype
fn.__proto__ === Function.prototype // true
复制代码
函数都是由 Function 原生构造函数创建的,所以函数的 __proto__ 属性指向 Function 的 prototype 属性。
9. Promise 如何一次进行多个异步请求
答:利用 Promise.all 。
👉👉 看了就会,手写Promise原理,最通俗易懂的版本!!!
10. 如果想要其中一个请求出错了但是不返回结果怎么办
答:使用 Promise.allSettled 。
11. let,const,var 有什么区别
-
块级作用域
- var没有,let和const有。
-
初始化
- const定义的变量必须要初始化,且不能被修改
- var定义的变量可以修改,如果不初始化,则为undefined
- let是块级作用域,内部使用let定义后,对函数外部无影响。
-
变量提升
- let/const没有变量提升
- var是有变量提升的
-
重复声明
- var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的变量。const和let不允许重复声明变量。
12. 遍历数组的 n 种方法
👉👉 【面试题解】你了解JavaScript常用的的十个高阶函数么?
13. 什么是虚拟 dom
虚拟dom是dom节点在JavaScript对象中的一种抽象数据结构,因为频繁的操作dom树会引发浏览器性能问题,所以需要虚拟dom树。
-
虚拟dom的作用
在每一次响应式数据发生变化引起页面渲染的时候,vue就会对比前后的虚拟dom树,匹配找出需要更新的真实dom树,从而达到提升性能的目的。
-
虚拟dom的实现原理主要包括
- 用
JavaScript对象模拟真实DOM树,对真实DOM进行抽象; diff算法 — 比较两棵虚拟DOM树的差异;pach算法 — 将两个虚拟DOM对象的差异应用到真正的DOM树。
- 用
13. 防抖和节流的区别,使用场景。
-
防抖:多次触发,只执行最后一次。
作用: 高频率触发的事件,在指定的单位时间内,只响应最后一次,如果在指定的时间内再次触发,则重新计算时间
-
节流:规定时间内,只触发一次。
作用: 高频率触发的事件,在指定的单位时间内,只响应第一次
防抖和节流的使用场景
-
防抖(debounce)
1.search搜索时,用户在不断输入值时,用防抖来节约请求资源。
-
节流(throttle)
1.鼠标不断点击触发,mousedown(单位时间内只触发一次);
2.监听滚动事件,比如每隔2-3秒计算一次位置信息等。
手写防抖和节流:juejin.cn/post/698130…
14. 将数组的length设置为0,取第一个元素会返回什么?
设置 length = 0 会清空数组,所以会返回 undefined
15. e.target 和 e.currentTarget有什么区别?
本质区别:
e.target:触发事件的元素e.currentTarget:绑定事件的元素
16. Math.ceil和Math.floor有什么区别?
Math.ceil(): 向上取整,函数返回一个大于或等于给定数字的最小整数。
Math.floor(): 向下取整,函数返回一个小于或等于给定数字的最大整数。
17. 使用原生js给一个按钮绑定两个onclick事件
使用事件监听 绑定多个事件
//事件监听 绑定多个事件
var btn = document.getElementById("btn");
btn.addEventListener("click",hello1);
btn.addEventListener("click",hello2);
function hello1(){
alert("hello 1");
}
function hello2(){
alert("hello 2");
}
18. bind、call、apply 区别
1、相同点
三个都是用于改变this指向;
接收的第一个参数都是this要指向的对象;
都可以利用后续参数传参。
2、不同点
call和bind传参相同,多个参数依次传入的;
apply只有两个参数,第二个参数为数组;
call和apply都是对函数进行直接调用,而bind方法不会立即调用函数,而是返回一个修改this后的函数。
19. 什么是类数组对象?
一个拥有 length 属性和若干索引属性的对象就可以被称为类数组对象。
类数组对象和数组类似,但是不能调用数组的方法。常见的类数组对象有 arguments 和 DOM 方法的返回结果,还有一个函数也可以被看作是类数组对象,因为它含有 length 属性值,代表可接收的参数个数。
常见的类数组转换为数组的方法有以下4种:
//(1)通过 call 调用数组的 slice 方法来实现转换
Array.prototype.slice.call(arrayLike);
//(2)通过 call 调用数组的 splice 方法来实现转换
Array.prototype.splice.call(arrayLike, 0);
//(3)通过 apply 调用数组的 concat 方法来实现转换
Array.prototype.concat.apply([], arrayLike);
//(4)通过 Array.from 方法来实现转换
Array.from(arrayLike);
20. typeof NaN 的结果是什么?
NaN 指“不是一个数字”(not a number),NaN 是一个“警戒值”(sentinel value,有特殊用途的常规值),用于指出数字类型中的错误情况,即“执行数学运算没有成功,这是失败后返回的结果”。
typeof NaN; // "number"
NaN 是一个特殊值,它和自身不相等,是唯一一个非自反(自反,reflexive,即 x === x 不成立)的值。而 NaN !== NaN 为 true。
21. 如何把十进制的0.2转换成二进制?
进制转换是比较基础的,如果大家熟悉 js 的 API ,那么会首先想到这两个方法:
- 十进制转二进制:num.toString(2)
- 二进制转十进制:parseInt(num, 2)
👉👉 Js十进制和二进制转换
22. map和filter有什么区别?
首先,map和filter函数的参数,是完全相同的
array.map(function(currentValue,index,arr), thisValue)
array.filter(function(currentValue,index,arr), thisValue)
- currentValue:数组元素;
- index:索引
- arr:原数组;
- thisValue:作为该执行回调时使用,传递给函数,用作 "this" 的值
但是在用途上,它们是有区别的:
- map方法返回的新数组是原数组的映射,何为映射?就是和原数组的长度相同,数值做相应处理。
- filter方法返回的值是过滤原数组后的新数组,和原数组长度不同,数值不变。
示例
let arr = ["1","2","3"];
let a = arr.map((item,index,a) =>{
return item + 1
});
console.log(a);//["11", "21", "31"]
let b = arr.filter((item,index,a) =>{
return item > 1
})
console.log(b);//["2", "3"]
复制代码
另外,filter可过滤NaN、null、undefined、0
let arr = [NaN,null,undefined,"0",0,1,2,3];
let newArr = arr.filter(item => item);
console.log(newArr);//["0", 1, 2, 3]
复制代码
23. ['1', '2', '3'].map(parseInt) 的返回值是什么?
首先整个题目考校的是两个函数,和一个字符串转数字的概念
-
数组的
map函数,接受三个参数,当前值,当前索引,当前数组。 -
parseInt接受两个参数,需要转换的字符串,基数(基数取值范围2~36)
var new_array = arr.map(function callback(currentValue, index, array) { // Return element for new_array }) parseInt(string, radix) 复制代码 -
根据上面的两个函数的解释,我们可以发现实际上,上面的
['1','2','3'].map(parseInt)其实就是等价于下面的代码。['1','2','3'].map((item, index) => { return parseInt(item, index) }) // parseInt('1', 0) 1 // parseInt('2', 1) NaN // parseInt('3', 2) NaN 复制代码 -
如果我们需要返回1,2,3需要怎么办?
function parseIntFun(item) { return parseInt(item, 10) } ['1','2','3'].map(parseIntFun) // parseInt('1', 10) 1 // parseInt('2', 10) 2 // parseInt('3', 10) 3 复制代码
综上所述,返回值是 [1,NaN,NaN]
题目:parseInt(0.0000005)
parseInt (0.0000005) === 5 为 true吗?答案是肯定的。
👉👉 parseInt(0.0000005) 返回结果是“5”?
👉👉 parseInt规则