2024前端面试(JS专题)
一般比较大一点的公司面试或笔试问题的重点都侧重于js这块,考验你js的功底扎不扎实。所以在准备面试的时候,js一定要复习好,而且要过好几遍,直至熟练回答为止,有的甚至需要手写几遍。毕竟机会都是给有准备的人的。下面给大家整理了一下面试中常问的js问题。大家可以查缺补漏。
目录
8, slice是干嘛的,splice是否会改变原数组,二者区别?
一、前言
一般比较大一点的公司面试或笔试问题的重点都侧重于js这块,考验你js的功底扎不扎实。所以在准备面试的时候,js一定要复习好,而且要过好几遍,直至熟练回答为止,有的甚至需要手写几遍。毕竟机会都是给有准备的人的。下面给大家整理了一下面试中常问的js问题。大家可以查缺补漏。
二、问题
1,JS数据类型
基本数据类型 Number、String、Boolean、Null、Undefined、Symbol
引用数据类型 Object、Array、Date、Function、RegExp
这里面可以拓展的问题很多,一定要多去思考,比如:
- null和undefined的区别?(基本的大家都知道,但怎么回答的让面试官耳目一新呢?)
(1)设计历史:
之前看过相关文献,作者在设计js的时候,先有null后有undefined,因为借鉴了java的语言,null的数据类型时object,会被隐式转换成0,不容易发现错误;所以就设计了undefined,它是一个基本数据类型,转成数值为NaN;
(2)数据:
null已经声明赋值为空;引用数据类型;数值转换为0;
undefined已经声明未赋值;基本数据类型;数值转换为NaN(数值类型,不表示数字);
- symbol数据类型的特点?
symbol 是ES6引入了一种新的基本数据类型(原始数据类型),表示独一无二的值。
(1)Symbol的值是唯一的,用来解决命名冲突的问题;
(2)Symbol值不能与其他数据类型进行运算;
(3)Symbol定义得的对象的属性不能使用for…in 循环遍历,但是可以使用Reflect.ownKeys来获取对象的所有键名;
- 两种数据类型的存放机制?
基本数据类型体积小,存放在栈里面;
引用数据类型体积大,存放在堆里面;
引用数据类型会有个指针放在栈里面,定位堆里面存放的引用数据;
- 如何判断这两种数据类型?
(1)typeof:查找基本数据类型,引用数据类型除了function,其他都为object;
(2)instanceof:查找引用数据类型;
原理:instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype, 返回true,不是返回false;
(3)Object.prototype.toString().call():所有数据类型;
原理:原型链和原型有关,首先toString()这个方法本来应该返回string的字符串形式,但是大多数情况会返回[object,xxxx]形式;因为js重写了某些数据类型的toString()方法;所以这时候我们找到原型上最原始的toSting()方法;call的作用就是转变this的指向;
- 数据类型如何转换?
(1)转为字符串:toString() / String() 不能用于null和undefined
(2)转为数值:Number() / parseInt() 转为整数,parseFloat 转为浮点数
(3)隐式转换:js是一门弱语言,运行期间,变量会根据运行环境自动类型转换;
举例:字符串数据+0/*1可以转换成数值;字符串+数值=字符串
(4)转为布尔值:false(0,null,undefined,'',NaN)
2,== 与 === 的区别?
==比较值
string == number || boolean || number都会隐式转换,通过valueOf()方法通常由js在后台自动转换
===比较值和数据类型,除了比较值,还比较类型
3,JS的宏任务和微任务?
(1)首先得解释一下JS的单线程:
js是单线程的语言,用途(浏览器的脚本语言,主要负责交互,动画,效果)决定了它必须是单线程的语言。单线程是指同一时间只能做一件事。如果能做多件事情的话,假如用户同时有两个操作,一个是删除节点,一个是新增节点,那到底应该选择是新增还是删除呢?所以js在设计的时候就必须是单线程的。
(2)其次解释一下JS代码的执行流程:
同步执行完==》事件循环;同步的任务都执行完了,才会执行事件循环的内容,进入事件循环有哪些:请求、定时器、事件。
(3)然后解释一下事件循环:
进入事件循环也得分哪个先哪个后呀?所以事件循环里面又分微任务和宏任务。
微任务:promise.then();
宏任务:setTimeOut;
执行顺序为:先执行微任务,在执行宏任务,宏任务里面还有微任务,先执行微任务,以此循环下去;记住关键,要执行宏任务的前提是清空了所有的微任务。
(4)最后总结一下代码的执行流程:
同步=》事件循环【微任务,宏任务】=》微任务=》宏任务=》微任务......;
4,实现Jsonp
srcipt标签引入
iframe
后端
配置请求头
可能会有拓展问题:
同源策略:域名,协议,端口号相同。
为什么限制:为了防止恶意ajax请求,修改DOM页面,随意获取cookie隐私数据等;
域名:www.baidu.com
端口:8080,3000
协议:https/http
5,JS作用域
全局作用域:代码在程序的任何地方都能被访问,window对象的内置属性都拥有全局作用域;
函数作用域:只有在固定的代码片段才能被访问;
好处:隔离变量,不同作用域下同名变量不会有冲突。
作用域链:一般情况下,变量取值会在创建这个变量的函数作用域中取值,如果没找到,就会像上级作用域查找,知道查到全局作用域,这个过程就叫作用域链;
除了函数外,js没有块级作用域;
比如:在函数里定义一个变量,在函数外打印这个变量,即使函数调用了,也没办法打印;
如果有笔试题,或者现场出题的话,这个就是高频题了,就需要重点理解了。
注意:
(1)声明的变量是用var还是没有写(就是window);
(2)js变量提升的机制【变量悬挂声明】;
(3)注意本层作用域有没有变量提升;
6,JS作用域+this指向+原型笔试题(高频)
构造函数:this指向的是实例对象,
全局函数:this指向window;
use strict:指向的是undefined;
绑定事件:this指的是被绑定事件的元素;
箭头函数:this和父级this指向相同;
考题一:
function foo(){
getName = function(){
console.log(1)
};
return this;
}
Foo.getName = function(){
console.log(2)
}
Foo.prototype.getName = function(){
console.log(3)
}
var getName = function(){
console.log(4)
}
function getName(){
console.log(5)
}
Foo.getName() //2
getName() //4
Foo().getName() //1
getName() //1
new Foo().getName() //3
考题二:
var o = {
a:10,
b:{
fn:function(){
console.log(this.a)
console.log(this)
}
}
}
o.b.fn() //undefined b函数
考题三:
window.name = 'ByteDance'
function A(){
this.name = 123;
}
A.prototype.getA = function(){
console.log(this);
return this.name+1;
}
let a = new A()
let funcA = function(){
console.log(this)
return this.name+1
}
funcA() //this指的是window; 'ByteDance1'
7, JS判断变量是不是数组,有哪些方法?
(1) Array.isArray(arr)
(2) arr instanceof Array【可写可不写,因为有bug】
(3) Object.prototype.toString.call(arr)
(4) Array.prototype.isPrototype(arr)
(5) arr.constructor.toString()
8, slice是干嘛的,splice是否会改变原数组,二者区别?
slice:选择截取部分内容,参数可以一个,两个,也可以是负数,返回新数组;
splice:会改变原数组;插入、删除、替换;返回删除的元素;
9,JS数组去重?
扩展运算符 + new set
循环
sort排序
代码演示:
(1) new Set
//Array.from(new set(arr))
//...new Set(arr)
var arr1 = [1,2,3,2,4,1];
function unique(arr){
return [...new Set(arr)]
}
console.log(unique(arr1))
(2) 循环
function unique3(arr){
var brr = []
for(var i=0; i<arr.length; i++){
if(brr.indexOf(arr[i]) == -1){
brr.push(arr[i])
}
}
return brr
}
console.log(unique3(arr));
(3) sort
function unique4(arr){
arr = arr.sort()
var brr = []
for(var i=0;i<arr.length;i++){
if(arr[1] !== arr[i-1]){
brr.push(arr[i])
}
}
return brr
}
console.log(unique4(arr));
10,找出多维数组的最大值?
Math.max(..item)
代码演示:
var arr = [
[1,3,7,9],
[22,77,90,78],
[123,567,890,345],
[1002,3004,3009,2890]
]
function fn(arr){
var brr = []
arr.forEach((item,index)=>{
brr.push(Math.max(...item))
})
return brr
}
console.log(fn(arr));
11,给字符串新增方法实现功能?
addPrefix() 添加前缀
代码演示:
String.prototype.addPrefix = function (str){
return str + this
}
console.log('Bryan'.addPrefix('Kobe'));
12,找出一个字符串出现次数最多的字符,出现了多少次?
var str = 'aaaabbbbssssxcccddddsdfscxxasa'
var obj = {};
for(var i=0; i<str.length; i++){
var char = str.charAt(i)
if(obj[char]){
obj[char]++;
}else{
obj[char] = 1;
}
}
console.log(obj);
//统计出来最大值
var max = 0;
for(var key in obj){
if(max < obj[key]){
max = obj[key]
}
}
//拿最大值去对比
for(var key in obj){
if(obj[key] == max){
console.log('最多的字符是'+key);
console.log('出现的次数是'+max);
}
}
13,字符串的方法?
方法 | 功能 |
---|---|
charAt() | 返回指定索引位置的字符 |
indexOf() | 返回字符串中检索指定字符第一次出现的位置 |
replace() | 替换与正则表达式匹配的字串 |
slice() | 提取字符串的片段,返回新的字符串 |
split() | 把字符串分隔为子字符串数组 |
substr() | 从起始索引号提取字符串中指定数目的字符 |
subString() | 提取字符串中两个指定的索引号之间的字符 |
toLowerCase() | 变小写 |
toUpperCase() | 变大写 |
toString() | 返回字符串对象值 |
trim() | 移除字符串首尾空白 |
valueOf() | 返回某个字符串对象的原始值 |
14,new操作符具体做了什么?
(1)创建了一个空对象;
(2)将空对象的原型,指向于构造函数的原型;
(3)将构造函数里的this指向空对象;
(4)将构造函数的属性和方法赋值给空对象;
(5)返回这个对象;
代码演示:
function Fun( name, age ){
this.name = name;
this.age = age;
}
function create( fn,...args ){
//创建一个新对象
var obj = {}
//把对象的原型指向构造函数的原型
Object.setPrototypeOf(obj,fn.prototype)
//改变this的指向
var result = fn.apply(obj.args)
//对构造函数有返回值的处理判断
return result instanceof Object ? result : obj;
}
console.log(create(Fun,18,'张三'));
15,闭包是什么?
(1)是什么:
闭包是一个函数内返回一个函数,外部函数能读取内部函数的变量;
(2)优点:
内部可以读取外部函数的变量,可以封装独有的方法和属性,保持内部的独立性;
(3)可以解决问题:
- 比如说要点击ul下面的每一项li,点击每项弹出每项的内容;
代码演示:
var lis = document.querySelectorAll('li');
for(var i=0; i<lis.length; i++){
(function(i){
lis[i].onclick = function(){
alert(i)
}
lis[i]=null
})(i)
}
- 防抖,柯里化,定时器等
(4)缺点:
变量会驻留在内存中,造成内存损耗问题;内存泄漏(IE)
解决:把闭包的函数设置为null;
16,原型链是什么?
这个问题经常会问,但千篇一律肯定没办法加分,所以需要说清楚,为什么会有原型,为什么会有原型链,在什么场景会用,方法是什么,原理是什么,按照这样的思路去说基本没问题。
我们知道创建对象的方式有三种,字面量,new,构造函数。前两个只能创建单个的对象,但构造函数可以把不同对象的共同属性和方法抽离出来,通过new实例化创建拥有共同属性和方法的不同对象。但是想一想,会出现什么问题呢?
由于js存储数据的方式是引用数据类型存储在堆里面,我们new出来的不同对象都拥有这一个相同的方法,但是都要在堆里面去申请不同的空间去存储,这样就会极大的浪费我们的内容空间。
所以这个时候我们就有了原型prototype。这样不同的对象可以通过原型去查找,节省内存空间,how nice!
也就是说原型可以解决:对象共享属性和共享方法。
总结简洁版(思路如下,自己整理能说明白即可):
(1)原型可以解决什么问题:对象共享属性和共享方法;
(2)谁有原型?函数拥有prototype,对象拥有:`__proto__`
(3)对象查找属性或者方法的顺序:
先在对象本身查找-->构造函数中查找-->对象的原型-->构造函数的原型中-->当前原型的原型;
(4)原型链的最顶端是Null;
17,js的继承有哪些方式?
不需要每个都讲,只需要讲到重点常用的几个即可;
(1)es6
class father{
constructor(){
this.age = 18
}
}
class child extends father{
constructor(){
super()
this.name = '张三'
}
}
let Child = new child()
console.log(Child,Child.name,Child.age)
(2)原型链继承
child.prototype = new parent()
let Child = new child()
console.log(Child, Child.name, Child.age)
//继承的属性和方法在原型上
(3)构造函数继承:
function Father(){
this.name = '张三'
}
function Son(){
this.age = 20
Father.call(this)
}
let o3 = new Son()
console.log(o3);
(4)组合继承
function Father(name,age){
this.name = name
this.age = age
}
function Son(name,age){
Father.call(this,name,age)
}
const son = new Son('刘德华', 20)
console.log(son);
18,call、apply、bind的区别?
(1)相同点:功能一致,都可以改变this指向;
(2)语法:函数.call(fn,name),函数.apply(fn,[...args]),函数.bind();
(3)区别:
- call,apply可以立即指向,bind不会立即指向,因为bind返回的是一个函数需要加入()执行;
- 参数不同,apply第二个参数是数组,call和bind有多个参数需要挨个写;
(4)使用场景:
- 找出最大值:
var arr = [1,2,4,5,78,21]
console.log(Math.max.apply(null,arr1))
- 用bind的情况
var btn = document.querySelector('btn')
var h1s = document.querySelector('h1s')
btn.onclick = function(){
console.log(this.id)
}.bind(h1s) //h1s
19,sort原理了解吗?
之前的版本是插入和快排,现在是冒泡;
使用场景:
(1)升序:
var arr = [1,2,23,21,222,778]
let res = arr.sort(function(a,b){
return a-b
})
console.log(res)
(2)降序:
var arr = [1,2,23,21,222,778]
let res = arr.sort(function(a,b){
return b-a
})
console.log(res)
(3)排序对象中某属性值的顺序:
var obj = [
{name:'张三',age:2},
{name:'李四',age:29},
{name:'王五',age:70}
]
function compare(age){
return function (a,b){
var val1 = a[age]
var val2 = b[age]
return val1-val2
}
}
var arr = obj.sort(compare('age'))
console.log(arr)
20,深浅拷贝的理解?(高频)
首先什么情况会出现深浅拷贝呢?
假设我们有一个已经定义了属性和方法的对象,我们再创建一个新对象,然后用=号把已定义的对象赋值给新创建的空对象;
如果我们修改新对象里的某个属性值,打印原来已定义的对象时,你会发现它的值也发生变化了;
这种情况就是发生了浅拷贝。为什么呢?
这是因为js的数据存储方式是基本数据类型再栈里面,引用数据类型在堆里面,但引用数据的指针,也就是房间号在栈里面存着。如果我们用=号去赋值,就相当于把指针赋值给了另一个对象,也就是说这两个对象的指针都是一样的,所以你修改了任意一个对象的值另一个也会发生变化。
那如果不想出现上面的情况,我们就需要进行深拷贝了,也就是说要在堆里面重新申请一个新空间,指针也是不同的,这样就不会发生连锁反应了。
简洁版思路:
共同点:复制
(1)浅拷贝:只复制引用地址,而未复制真正的值;object.assign()
(2)深拷贝:是复制真正的值(不同的引用)
(3)方式:JSON.parse(JSON.Stringify) 递归形式;
21,本地存储方式的区别?
(1)数据存放有效期:
localStorage:始终有效,持久存储;
sessionStorage:浏览器关闭就没了;
cookie:只在设置的cookie过期时间之前有效,即使窗口或者浏览器关闭也有效,其他不可以设置时间;
(2)存储大小的限制:
cookie存储量不能超过4k;
localStorage、sessionStorage不能超过5M;
22,find和filter区别?
(1)区别一:filter返回新数组,find返回具体的内容;
(2)区别二:find匹配到第一个即返回,filter只要满足条件push到新数组里,返回新数组;
23,some和every区别?
some是只要满足一个条件就返回true;
every是都满足返回为true;
24,合并多个对象?
(1)方法一:Object.assign(a,b)
(2)方法二:obj = {...a,...b}
(3)方法三:循环
function extend(target,source){
for(var obj in source){
target[obj] = source[obj]
}
return target
}
console.log(extend(a,b))
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)