根据近几年在前端中厂、大厂面试的同学的经历,总结出我们在拿offer前经常被问到的问题,有关JavaScript的问题能拿的出手的不是很多也不会很复杂,还是得需要我们对这门语言精通。
面试官不会精确到问你一个小问题,主要还是让我们谈一谈,说一说,发挥我们的语言组织能力,谈到这个知识点的时候你要是不会回答,就按是什么?有什么作用特点?再进行扩展开来聊。让面试官跟着我们的引导来走。扩展开来还是得按我们会的扩展。怕说了什么重要的自己答不上来,尽量往自己会的方向走。
接下来我们就看一看JavaScript的考点,当问到这些再慢慢学着扩展到下个问题。
1.js数组上面有哪些方法?
1. 增:
- push:将一个或多个元素添加到数组的末尾,并返回新数组的长度
- unshift:将一个或多个元素添加到数组的开头,并返回新数组的长度。
- splice:可以删除现有元素、添加新元素或替换数组中的元素。它接受多个参数,分别表示起始位置、要删除的元素个数(如果是 0 则表示不删除任何元素)、要添加的元素(可选)。 添加元素的位置由起始确定。以数组的形式返回删除的元素。
js
let arr = [1,2,3,4,5]
let len = arr.splice(1,2,6)
console.log(len); //[2,3]
- concat:用于合并两个或多个数组,并返回一个新数组,而不改变现有数组。
2.删:
- pop:从数组中移除最后一个元素,并返回该元素的值。同时,原数组的长度减一。
- shift:从数组中移除第一个元素,并返回该元素的值。同时,原数组的长度减一,并且所有后续元素的索引减一。
- splice
- slice:返回一个新数组,其中包含从开始到结束(不包括结束)的选定元素。原始数组不会被修改。一个参数默认到数组结尾。
js
let fruits = ['apple', 'banana', 'cherry', 'orange', 'pear'];
let selectedFruits = fruits.slice(1, 4);
// selectedFruits 现在为 ['banana', 'cherry', 'orange'],fruits 不变
3.改:
- reverse:用于颠倒数组中元素的顺序,即将数组中的元素倒序排列。
- sort:用于对数组中的元素进行排序。
4.查:
- indexOf:查询数组中的元素是否存在,存在就返回数组中第一个匹配项的索引值,如果不存在则返回 -1。
- lastIndexOf:返回数组中最后一个匹配项的索引,如果不存在则返回 -1。
- filter:创建一个新数组,其中包含通过指定函数测试的所有元素。
js
let numbers = [1, 2, 3, 4, 5];
let evenNumbers = numbers.filter(num => num % 2 === 0);
// evenNumbers 现在为 [2, 4],包含了原数组中所有偶数
- find:返回数组中满足提供的测试函数的第一个元素的值。如果没有找到,则返回
undefined
。
js
let numbers = [1, 2, 3, 4, 5];
let firstEvenNumber = numbers.find(num => num % 2 === 0);
// firstEvenNumber 的值为 2,因为它是数组中第一个偶数
- includes:判断数组是否包含某个元素,如果包含则返回
true
,否则返回false
。
5.转换:
- join:用于将数组中所有元素连接成一个字符串。你可以使用指定的分隔符来连接数组元素,如果不提供分隔符,默认使用逗号作为分隔符。
6.迭代:
- forEach:对数组中的每个元素执行指定的函数一次。它没有返回值(或者说返回值为
undefined
)。
js
let numbers = [1, 2, 3, 4, 5];
numbers.forEach(num => {
console.log(num);
});
// 输出:
// 1
// 2
// 3
// 4
// 5
- map:创建一个新数组,其元素是对原始数组中每个元素调用提供的函数的结果。
js
let numbers = [1, 2, 3, 4, 5];
let doubledNumbers = numbers.map(num => num * 2);
// doubledNumbers 现在为 [2, 4, 6, 8, 10]
- reduce:对数组中的每个元素执行一个提供的 reducer 函数(从左到右),将其结果汇总成单个值。
js
let numbers = [1, 2, 3, 4, 5];
let sum = numbers.reduce((acc, cur) => acc + cur, 0);
// sum 的值为 15,即 1 + 2 + 3 + 4 + 5
//acc是累加器,设置初始值为0,函数再执行一遍acc的值是上次函数执行的结果。
- filter:创建一个新数组,其中包含通过指定函数条件的所有元素。
- set:是 ES6 中引入的一种新的数据结构,它类似于数组,但是成员的值都是唯一的。
7.Array.from()
用来创建新数组的静态方法。它可以将类数组对象或可迭代对象(比如 Set、Map、字符串等)转换成一个新的数组实例。
Array.from()
方法的基本语法如下:
javascriptCopy
Array.from(arrayLike,[mapFunction,[thisArg]])
arrayLike
:要转换为数组的类数组对象或可迭代对象。mapFunction
(可选):对每个元素进行处理的映射函数。thisArg
(可选):执行mapFunction
时使用的this
值。
下面是一些 Array.from()
方法的示例用法:
- 将类数组对象转换为数组:
javascriptCopy
let arr = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
let new = Array.from(arr);
console.log(new); // 输出: ['a', 'b', 'c']
- 使用映射函数处理每个元素:
javascriptCopy
let set = new Set([1, 2, 3]);
let newArray = Array.from(set, x => x * 2);
console.log(newArray); // 输出: [2, 4, 6]
- 使用箭头函数改变
this
值:
javascriptCopy
let arrayLike = 'hello';
let newArray = Array.from(arrayLike, function(char) {
return this.toUpperCase();
}, 'world');
console.log(newArray); // 输出: ['W', 'O', 'R', 'L', 'D']
2.js字符串上有哪些方法?
1.增:
concat:用于连接两个或多个字符串,并返回一个新的字符串,而不会改变原始字符串。
js
const str1 = 'Hello';
const str2 = 'world';
const str3 = '!';
const result = str1.concat( ' ' ,str2, str3);//添加一个空格
2.删:
-
slice:接收两个参数 [包括起始,终止可选),返回删除的字符,不修改原字符串。
如果接受的参数为负数则从字符串末尾从1的索引开始或终止,否则是空。
js
let str = 'hello,world'
let s1 = str.slice(0,2) //he
let s1 = str.slice(0) //hello,world
let s1 = str.slice(-5,-1) //worl
let s1 = str.slice(-2,0) //空
-
substring:接收两个参数,[起始,可选的结束),只接收正数,返回删除的元素,不修改原字符串。
-
substr:被弃用,不推荐使用,和slice相像,但是第二个参数是要删除的个数。
5.改:
-
replace :接收两个参数,被替换和替换的元素,返回一个新的字符串,不修改原字符串。可以使用正则匹配。
-
trim :可以去除字符串两端的空格,换行符,制表符。内部的无法去除。
-
padStart:接收两个参数,第一个是整个字符串可以到达的长度,第二个是需要添加的字符串。字符串长度超过第一个参数就不会再添加。返回新字符串,不修改原字符串。
js
let str ='hello'
console.log(str.padStart(10,'h')); // hhhhhhello
console.log(str.padStart(10)); //补充空格
let str ='hello'
let s1 = str.padStart(10,'h')
console.log(s1,str);//hhhhhhello 和 hello
-
padEnd :和padStart相似,但是从末尾添加。
-
trimStart :移除字符串开头的空格,返回新字符串,不修改原字符串。
-
trimEnd:移除字符串末尾的空格,返回新字符串,不修改原字符串。
-
toupCase:将字符串大写,无法选择需要大写的字符,返回新字符串,不修改原字符串。
-
toLowerCase将字符串小写,无法选择需要小写的字符,返回新字符串,不修改原字符串。
6.查:
-
indexOf:两个参数,需要匹配的字符,起始位置(可选),找到返回第一个匹配项索引,没有返回-1.
-
lastIndexOf:两个参数,需要匹配的字符,起始位置(可选),找到返回最后一个匹配项索引,没有返回-1.
-
includes:两个参数,需要匹配的字符,起始位置(可选),找到为true,没有返回false.
-
startsWith:是否以某些字符串开头。两个参数,需要匹配的字符,起始位置(可选),是为true,没有返回false.
-
endsWith:是否以某些字符串结尾。两个参数,需要匹配的字符,起始位置(可选),是为true,没有返回false.
7.转换:
- split:将字符串分割成子串并存入数组,不影响原字符串。
js
s="dog cat"
let arr = s.split(" ")
arr=['dog','cat']
3.谈谈js当中的类型转换机制
- 是什么: js中有原始类型和引用类型:
- 原始类型:number string boolean symbol null undefined Bigint
- 引用类型:Object Function Array Date RegExp Map Set WeakMap WeakSet
通常开发过程中,会用到一些显示的类型转换的手段来完成逻辑开发 .....
在v8执行的过程当中还存在另一种类型转换 --- 隐式类型转换
通常发生在 : 比较运算符 和算数运算符
-
比较运算符: == === != !== < <= > >= if while
-
算术运算符: + - * / %
[]==![] ???
这个涉及到隐式转换的问题,提到这个问题的,我们需要了解一个东西,那就是由复杂数据类型转换为基本数据类型。将引用类型转换为原始类型,在拆箱过程中会遵循ECMAScript规定的toPrimitive原则,对引用类型进行转换。在js中将复杂数据类型转换为基本数据类型会自动调用toPromitive函数。
!不会参与类型转换,但是!会希望[]变成布尔类型,所以右边的[]会变为true。左边的[]会调用原型上的toString方法转化为字符串。因为等号要使用数字判断,所以会触发Number方法转换为数字比较。
[] == !true
[] == false
'' == false
0 == 0
4. == 和 === 的区别?
- == 是比较两个值,类型不一样会进行隐式转换,=== 是比较两个值的类型和值。
5.深浅拷贝的区别?如何实现一个深拷贝?
- 深浅拷贝通常只针对于引用类型
-
浅拷贝: 只拷贝一层对象,复制这一层对象的原始值,如果有引用类型的话,就复制它的指针。
- 浅拷贝方法
-
object.assign()
let newObj = Object.assign({},obj) -
concat()
let newObj =[].concat(obj) -
数组解构
-
jslet arr = [1,2,3,{n:1}] let newObj = [...arr] arr[3].n = 2 console.log(newObj)
- 浅拷贝函数手写
- 浅拷贝方法
js
function shallowCopy(obj) {
let newObj = {}
for(let key in obj){
if(obj.hasOwnProperty(key)){ //显示原型上面的属性不拷贝
newObj[key] = obj[key]
}
}
return newObj
}
- 深拷贝: 层层拷贝,所有类型的属性值都会被复制,原对象的修改不会影响拷贝后的对象。
-
深拷贝方法
- JSON.parse(JSON.stringify(obj)) ----无法处理undefined和symbol,function,循环引用
- structuredClone(): 无法处理 symbol,funnction
- 能处理循环引用
-
手写深拷贝函数
-
js
function deepCopy(obj){
let newObj = {}
for(let key in obj){
if(obj.hasOwnProperty(key)){//用于检查对象自身是否具有指定的属性(而不是从原型链上继承而来的属性)
if(typeof obj[key] !== 'object' || obj[key] === null){
newObj[key] = obj[key]
}else{
newObj[key] = deepCopy(obj[key])
}
}
}
return newObj
}
6.说说你对闭包的理解?
-
是什么:
当一个函数中的内部函数被拿到函数外部调用,又因为在js中内层作用域总是能访问外层作用域,那么内部函数存在对外部函数中变量的引用,这些变量的集合称之为闭包。
-
使用场景:
- 创建私有变量 (全局变量不易维护)
- 延长变量的生命周期
- 实现颗粒化(柯里化)
-
缺点:
会造成内存泄漏,内存被占据
7.什么是柯里化?
- 是什么: 将一个接收多个参数的函数转变成多个只接受一个参数的函数
8.说说你对作用域的理解?
-
是什么:
变量和函数能够生效的区域,这个区域叫作用域。
-
作用域的划分:
- 全局作用域
- 函数作用域
- 块级作用域(const||let 和{})
-
作用域链 作用域只能从内到外的访问,这种访问规则形成的链状关系我们称之为作用域链
-
词法作用域 指的是函数或者变量定义的区域
9.说说你对原型的理解
-
是什么
显式原型:指的是函数身上自带的prototype属性,通常可以将一些方法和属性添加到到显示原型上,这样所有的实例对象都可以共享这些方法和属性。
隐式原型:__proto__是对象这种结构上的一个属性,其中包含了创建该对象时,隐式继承到的属性。
-
原型链: 创建一个实例对象时,实例对象的隐式原型 === 创建该对象的构造函数的显示原型,在js中对象的查找规则是先在对象中查找,找不到再去对象的隐式原型上查找,顺着隐式原型一层层往上找,直到找到null。
-
可用来实现属性的继承
10. 说说js中的继承
- 是什么: 在js中,让一个子类可以访问父类的属性和方法
11. 说说js中的this
-
是什么: this是函数在运行过程中自动生成的一个对象,用来代指作用域的指向。
-
绑定规则
- 默认绑定:当函数被独立调用时,函数的this指向window。(函数的词法作用域在哪里,this就指向哪个词法作用域,一层层往上找,直到找到最外层。)
- 隐式绑定:当函数被一个对象所调用时,函数的this指向这个对象。
- 隐式丢失:当函数调用前有多个对象,函数的this指向最近的对象。
- 显式绑定:call,apply,bind。
- new绑定:this指向实例对象。
-
箭头函数 箭头函数中的this是它外层非箭头函数的,指向也按照上述的绑定规则。
12.new的实现原理
- 创建一个空对象
- 将构造函数的原型绑定到对象的原型上
- 将构造函数的this指向对象
- 判断构造函数的返回值是否是引用类型,是就返回,不是就返回对象
- 构造函数有返回值,且为引用类型时会覆盖new当中的返回值
手写一个new实现
js
Person.prototype.getName = function() {
return this.name;
}
function Person(name) {
this.name = name;
return function () {}
}
function myNew(...args) { // [Person, 'Tom']
let obj = {}
obj.__proto__ = args[0].prototype
const res = args[0].apply(obj, args.slice(1))
return (typeof res === 'object' && res !== null) ? res : obj
}
let p = myNew(Person, 'Tom')
13. call,apply,bind 区别
this的指向 call:零散传参 call(obj,1,2)
apply:数组传参
bind:零散传参,还会返回一个函数,需要调用这个函数,参数可以写在这个函数里面
14.说说js中的事件模型
- 是什么 捕获,目标,冒泡三个阶段
- 分类
- DOM0级:onclick(无法控制事件在捕获冒泡哪个阶段执行)
- DOM1级:addeventListen(可以控制事件执行在哪个阶段)
- IE模型:attachEvent(无法控制事件在捕获冒泡哪个阶段执行)
15.说说typeof 和 instanceof的区别?
-
typeof
能判断除了null之外的所有原始类型
-
instanceof
主要适用于检查对象是否是由特定类或构造函数创建的实例。
-
Object.prototype.toString.call(x)
- [].toString() 数组版本的toString
- Object.prototype.toString.call([])
- [].toString() 对象版本的toString
该方法会让变量 x 调用对象上的toString函数,而toString返回值为[object Object]类型
-
Array.isArray()
判断是否是数组
16. 说说Ajax的原理
-
是什么 Ajax
-
实现过程
- 创建一个XHR实例对象
- 调用实例对象中的open方法与服务器建立连接
- 调用实例对象的send方法发送请求
- 监听onreadystatechange事件,通过判断readyState的值来获取到最终数据
- 将数据更新到页面
17. 事件代理
- 事件委托 (多个子容器需要绑定相同的事件)
当我们操作大量数据时需要绑定事件,最简便的方式是循环为每一条数据绑定事件,这样会导致性能浪费。 所以需要用的事件代理。绑定父容器实现绑定一个事件代理到所有数据。
html
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
let ul = document.querySelector("ul");
ul.addEventListener("click", (event)=>{
console.log(event.target.innerHTML);
}
);
</script>
18. 说说js当中的事件循环
-
是什么: JS引擎在执行js过程中会区分同步和异步代码,先执行同步再执行异步,异步中同样先执行同步,再执行异步,以此往复的循环。
-
异步
- 宏任务:script、setTimeout、setInterval、setImmediate、I/O、UI-rendering、 postMassage()、 MessageChannel
- 微任务:.then() nextTick(node) MutationObserver回调
-
Event Loop:
- 执行同步代码 (也叫宏任务)
- 执行微任务(完毕)
- 有需要的话就渲染页面
- 执行宏任务(下一次事件循环的开始)
19.let const var区别
-
var
- var可以重复声明变量,let和const不行
- var声明的变量会添加到window上
- var声明的变量会存在声明提升,提升到当前作用域的顶端,let,const不行。
-
let,const
- {}+let或const 会形成块级作用域,无法从外部访问。
- const和let 形成的块级作用域会形成暂时性死区。
- const 声明无法修改变量。
20.包装类
原始类型不能具有属性和方法
js
let a = 1
a.name = 123
console.log(a.name) //undefined v8开始会把a当成字符串对象,当读到定义的a后又会删除
let s = 'asdfa'相当于new String('asdfa')
let s = new String('asdad')可以添加属性