56 道高频 JavaScript 与 ES6+ 的面试题及答案(上)

简介: 56 道高频 JavaScript 与 ES6+ 的面试题及答案(上)

前言


本文讲解 56 道 JavaScript 和 ES6+ 面试题的内容。


复习前端面试的知识,是为了巩固前端的基础知识,最重要的还是平时的积累!

注意:文章的题与题之间用下划线分隔开,答案仅供参考。


前端硬核面试专题的完整版在此:前端硬核面试专题,包含:HTML + CSS + JS + ES6 + Webpack + Vue + React + Node + HTTPS + 数据结构与算法 + Git 。


JavaScript


常见的浏览器内核有哪些 ?


  • Trident 内核:IE, 360,搜狗浏览器 MaxThon、TT、The World,等。[又称 MSHTML]
  • Gecko 内核:火狐,FF,MozillaSuite / SeaMonkey 等
  • Presto 内核:Opera7 及以上。[Opera 内核原为:Presto,现为:Blink]
  • Webkit 内核:Safari,Chrome 等。 [ Chrome 的:Blink(WebKit 的分支)]

mouseenter 和 mouseover 的区别


  • 不论鼠标指针穿过被选元素或其子元素,都会触发 mouseover 事件,对应 mouseout。
  • 只有在鼠标指针穿过被选元素时,才会触发 mouseenter 事件,对应 mouseleave。

用正则表达式匹配字符串,以字母开头,后面是数字、字符串或者下划线,长度为 9 - 20


var re=new RegExp("^[a-zA-Z][a-zA-Z0-9_]{9,20}$");

手机号码校验


function checkPhone(){ 
    var phone = document.getElementById('phone').value;
    if(!(/^1(3|4|5|7|8)\d{9}$/.test(phone))){ 
        alert("手机号码有误,请重填");  
        return false; 
    } 
}
^1(3|4|5|7|8)d{9}$,表示以 1 开头,第二位可能是 3/4/5/7/8 等的任意一个,在加上后面的 d 表示数字 [0-9] 的 9 位,总共加起来 11 位结束。
手机号码格式验证方法(正则表达式验证)支持最新电信 199, 移动 198, 联通 166
// 手机号码校验规则
let valid_rule = /^(13[0-9]|14[5-9]|15[012356789]|166|17[0-8]|18[0-9]|19[8-9])[0-9]{8}$/;
if ( ! valid_rule.test(phone_number)) {
     alert('手机号码格式有误');
     return false;
}


这样 phone_number 就是取到的手机号码,即可!


js 字符串两边截取空白的 trim 的原型方法的实现


js 中本身是没有 trim 函数的。


// 删除左右两端的空格
function trim(str){
return str.replace(/(^\s*)|(\s*$)/g, "");
}
// 删除左边的空格 /(^\s*)/g
// 删除右边的空格 /(\s*$)/g

介绍一下你对浏览器内核的理解 ?


内核主要分成两部分:渲染引擎(layout engineer 或 Rendering Engine) 和 JS 引擎。

渲染引擎


负责取得网页的内容(HTML、XML、图像等等)、整理讯息(例如加入 CSS 等),以及计算网页的显示方式,然后会输出至显示器或打印机。


浏览器的内核的不同对于网页的语法解释会有不同,所以渲染的效果也不相同。

所有网页浏览器、电子邮件客户端以及其它需要编辑、显示网络内容的应用程序都需要内核。


JS 引擎


解析和执行 javascript 来实现网页的动态效果。


最开始渲染引擎和 JS 引擎并没有区分的很明确,后来 JS 引擎越来越独立,内核就倾向于只指渲染引擎。



哪些常见操作会造成内存泄漏 ?


内存泄漏指任何对象在您不再拥有或需要它之后仍然存在。


垃圾回收器定期扫描对象,并计算引用了每个对象的其他对象的数量。如果一个对象的引用数量为 0(没有其他对象引用过该对象),或对该对象的惟一引用是循环的,那么该对象的内存即可回收。


  • setTimeout 的第一个参数使用字符串而非函数的话,会引发内存泄漏。
  • 闭包、控制台日志、循环(在两个对象彼此引用且彼此保留时,就会产生一个循环)。

线程与进程的区别 ?


  • 一个程序至少有一个进程,一个进程至少有一个线程。
  • 线程的划分尺度小于进程,使得多线程程序的并发性高。
  • 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

线程在执行过程中与进程还是有区别的。

  • 每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
  • 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。


但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。



eval() 函数有什么用 ?


eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码。


实现一个方法,使得:add(2, 5) 和 add(2)(5) 的结果都为 7


var add = function (x, r) {
    if (arguments.length == 1) {
        return function (y) { return x + y; };
    } else {
        return x + r;
    }
};
console.log(add(2)(5));  // 7
console.log(add(2,5));  // 7

alert(1 && 2) 和 alert(1 || 0) 的结果是 ?


alert(1 &&2 ) 的结果是 2


  • 只要 “&&” 前面是 false,无论 “&&” 后面是 true 还是 false,结果都将返 “&&” 前面的值;
  • 只要 “&&” 前面是 true,无论 “&&” 后面是 true 还是 false,结果都将返 “&&” 后面的值;


alert(0 || 1) 的结果是 1


  • 只要 “||” 前面为 false,不管 “||” 后面是 true 还是 false,都返回 “||” 后面的值。
  • 只要 “||” 前面为 true,不管 “||” 后面是 true 还是 false,都返回 “||” 前面的值。


只要记住 0 与 任何数都是 0,其他反推。


下面的输出结果是 ?


var out = 25,
   inner = {
        out: 20,
        func: function () {
            var out = 30;
            return this.out;
        }
    };
console.log((inner.func, inner.func)());
console.log(inner.func());
console.log((inner.func)());
console.log((inner.func = inner.func)());


结果:25,20,20,25


代码解析:这道题的考点分两个


  1. 作用域
  2. 运算符(赋值预算,逗号运算)


先看第一个输出:25,因为 ( inner.func, inner.func ) 是进行逗号运算符,逗号运算符就是运算前面的 ”,“ 返回最后一个,举个栗子


var i = 0, j = 1, k = 2;
console.log((i++, j++, k)) // 返回的是 k 的值 2 ,如果写成 k++ 的话  这里返回的就是 3
console.log(i); // 1
console.log(j); // 2
console.log(k); // 2


回到原题 ( inner.func, inner.func ) 就是返回 inner.func ,而 inner.func 只是一个匿名函数


function () {
    var out = 30;
    return this.out;
}


而且这个匿名函数是属于 window 的,则变成了


(function () {
    var out = 30;
    return this.out;
})()


此刻的 this => window


所以 out 是 25。


第二和第三个 console.log 的作用域都是 inner,也就是他们执行的其实是 inner.func();

inner 作用域中是有 out 变量的,所以结果是 20。


第四个 console.log 考查的是一个等号运算 inner.func = inner.func ,其实返回的是运算的结果,


举个栗子


var a = 2, b = 3;
console.log(a = b) // 输出的是 3


所以 inner.func = inner.func 返回的也是一个匿名函数


function () {
    var out = 30;
    return this.out;
}


此刻,道理就和第一个 console.log 一样了,输出的结果是 25。



下面程序输出的结果是 ?


if (!("a" in window)) {
    var a = 1;
}
alert(a);


代码解析:如果 window 不包含属性 a,就声明一个变量 a,然后赋值为 1。

你可能认为 alert 出来的结果是 1,然后实际结果是 “undefined”。


要了解为什么,需要知道 JavaScript 里的 3 个概念。


首先,所有的全局变量都是 window 的属性,语句 var a = 1; 等价于 window.a = 1;

你可以用如下方式来检测全局变量是否声明:"变量名称" in window。


第二,所有的变量声明都在范围作用域的顶部,看一下相似的例子:


alert("b" in window);
var b;


此时,尽管声明是在 alert 之后,alert 弹出的依然是 true,这是因为 JavaScript 引擎首先会扫描所有的变量声明,然后将这些变量声明移动到顶部,最终的代码效果是这样的:


var a;
alert("a" in window);


这样看起来就很容易解释为什么 alert 结果是 true 了。


第三,你需要理解该题目的意思是,变量声明被提前了,但变量赋值没有,因为这行代码包括了变量声明和变量赋值。


你可以将语句拆分为如下代码:


var a;    //声明
a = 1;    //初始化赋值


当变量声明和赋值在一起用的时候,JavaScript 引擎会自动将它分为两部以便将变量声明提前,

不将赋值的步骤提前,是因为他有可能影响代码执行出不可预期的结果。


所以,知道了这些概念以后,重新回头看一下题目的代码,其实就等价于:


var a;
if (!("a" in window)) {
    a = 1;
}
alert(a);


这样,题目的意思就非常清楚了:首先声明 a,然后判断 a 是否在存在,如果不存在就赋值为1,很明显 a 永远在 window 里存在,这个赋值语句永远不会执行,所以结果是 undefined。


提前这个词语显得有点迷惑了,你可以理解为:预编译。


下面程序输出的结果是 ?


var a = 1;
var b = function a(x) {
  x && a(--x);
};
alert(a);


这个题目看起来比实际复杂,alert 的结果是 1。


这里依然有 3 个重要的概念需要我们知道。


  • 首先,第一个是 变量声明在进入执行上下文就完成了
  • 第二个概念就是函数声明也是提前的,所有的函数声明都在执行代码之前都已经完成了声明,和变量声明一样


澄清一下,函数声明是如下这样的代码:


function functionName(arg1, arg2){
    //函数体
}
如下不是函数,而是函数表达式,相当于变量赋值:
var functionName = function(arg1, arg2){
       //函数体
   };


澄清一下,函数表达式没有提前,就相当于平时的变量赋值。


  • 第三需要知道的是,函数声明会覆盖变量声明,但不会覆盖变量赋值

为了解释这个,我们来看一个例子:


function value(){
    return 1;
}
var value;
alert(typeof value);    //"function"


尽管变量声明在下面定义,但是变量 value 依然是 function,也就是说这种情况下,函数声明的优先级高于变量声明的优先级,但如果该变量 value 赋值了,那结果就完全不一样了:


function value(){
    return 1;
}
var value = 1;
alert(typeof value);    //"number"


该 value 赋值以后,变量赋值初始化就覆盖了函数声明。


重新回到题目,这个函数其实是一个有名函数表达式,函数表达式不像函数声明一样可以覆盖变量声明,但你可以注意到,变量 b 是包含了该函数表达式,而该函数表达式的名字是 a。不同的浏览器对 a 这个名词处理有点不一样,在 IE 里,会将 a 认为函数声明,所以它被变量初始化覆盖了,就是说如果调用 a(–x) 的话就会出错,而其它浏览器在允许在函数内部调用 a(–x),因为这时候 a 在函数外面依然是数字。


基本上,IE 里调用 b(2) 的时候会出错,但其它浏览器则返回 undefined。


理解上述内容之后,该题目换成一个更准确和更容易理解的代码应该像这样:


var a = 1,
    b = function(x) {
      x && b(--x);
    };
alert(a);


这样的话,就很清晰地知道为什么 alert 的总是 1 了。


下面程序输出的结果是 ?


function a(x) {
    return x * 2;
}
var a;
alert(a);
alert 的值是下面的函数
function a(x) {
    return x * 2;
}


这个题目比较简单:即函数声明和变量声明的关系和影响,遇到同名的函数声明,不会重新定义。



下面程序输出的结果是 ?


function b(x, y, a) {
        arguments[2] = 10;
        alert(a);
}
b(1, 2, 3);


结果为 10。


活动对象是在进入函数上下文时刻被创建的,它通过函数的 arguments 属性初始化。


三道判断输出的题都是经典的题

var a = 4;
function b() {
  a = 3;
  console.log(a);
  function a(){};
}
b();


明显输出是 3,因为里面修改了 a 这个全局变量,那个 function a(){} 是用来干扰的,虽然函数声明会提升,就被 a 给覆盖掉了,这是我的理解。


不记得具体的,就类似如下


var baz = 3;
var bazz ={
  baz: 2,
  getbaz: function() {
    return this.baz
  }
}
console.log(bazz.getbaz())
var g = bazz.getbaz;
console.log(g()) ;


第一个输出是 2,第二个输出是 3。


这题考察的就是 this 的指向,函数作为对象本身属性调用的时候,this 指向对象,作为普通函数调用的时候,就指向全局了。


还有下面的题:


var arr = [1,2,3,4,5];
for(var i = 0; i < arr.length; i++){
  arr[i] = function(){
    alert(i)
  }
}
arr[3]();


典型的闭包,弹出 5 。


JavaScript 里有哪些数据类型


一、数据类型  

  • undefiend 没有定义数据类型      
  • number 数值数据类型,例如 10 或者 1 或者 5.5      
  • string 字符串数据类型用来描述文本,例如 "你的姓名"      
  • boolean 布尔类型 true | false ,不是正就是反      
  • object 对象类型,复杂的一组描述信息的集合
  • function 函数类型

解释清楚 null 和 undefined


null 用来表示尚未存在的对象,常用来表示函数企图返回一个不存在的对象。  null 表示"没有对象",即该处不应该有值。


null 典型用法是:


  • 作为函数的参数,表示该函数的参数不是对象。
  • 作为对象原型链的终点。

当声明的变量还未被初始化时,变量的默认值为 undefined。 undefined 表示"缺少值",就是此处应该有一个值,但是还没有定义。

  • 变量被声明了,但没有赋值时,就等于 undefined。
  • 调用函数时,应该提供的参数没有提供,该参数等于 undefined。
  • 对象没有赋值的属性,该属性的值为 undefined。
  • 函数没有返回值时,默认返回 undefined。

未定义的值和定义未赋值的为 undefined,null 是一种特殊的 object,NaN 是一种特殊的 number。


讲一下 1 和 Number(1) 的区别*


  • 1 是一个原始定义好的 number 类型;
  • Number(1) 是一个函数类型,是我们自己声明的一个函数(方法)。

讲一下 prototype 是什么东西,原型链的理解,什么时候用 prototype ?


prototype 是函数对象上面预设的对象属性。


函数里的 this 什么含义,什么情况下,怎么用 ?


  • this 是 Javascript 语言的一个关键字。
  • 它代表函数运行时,自动生成的一个内部对象,只能在函数内部使用。
  • 随着函数使用场合的不同,this 的值会发生变化。
  • 但是有一个总的原则,那就是 this 指的是,调用函数的那个对象


情况一:纯粹的函数调用

这是函数的最通常用法,属于全局性调用,因此 this 就代表全局对象 window。      

function test(){       
  this.x = 1;        
  alert(this.x);      
}      
test(); // 1


为了证明 this 就是全局对象,我对代码做一些改变:

     

var x = 1;      
function test(){        
  alert(this.x);      
}      
test(); // 1


运行结果还是 1。


再变一下:    

  

var x = 1;      
function test(){        
  this.x = 0;      
}      
test();
alert(x); // 0


情况二:作为对象方法的调用  


函数还可以作为某个对象的方法调用,这时 this 就指这个上级对象。  


function test(){        
  alert(this.x);      
}
var x = 2      
var o = {};      
o.x = 1;      
o.m = test;      
o.m(); // 1


情况三: 作为构造函数调用  


所谓构造函数,就是通过这个函数生成一个新对象(object)。这时的 this 就指这个新对象。


function Test(){        
  this.x = 1;      
}      
var o = new Test();
alert(o.x); // 1


运行结果为 1。为了表明这时 this 不是全局对象,对代码做一些改变:

   

var x = 2;      
function Test(){        
  this.x = 1;      
}      
var o = new Test();      
alert(x); // 2


运行结果为 2,表明全局变量 x 的值没变。


情况四: apply 调用  


apply() 是函数对象的一个方法,它的作用是改变函数的调用对象,它的第一个参数就表示改变后的调用这个函数的对象。因此,this 指的就是这第一个参数。     

 

var x = 0;      
function test(){        
  alert(this.x);      
}      
var o = {};      
o.x = 1;      
o.m = test;      
o.m.apply(); // 0


apply() 的参数为空时,默认调用全局对象。因此,这时的运行结果为 0,证明 this 指的是全局对象。

 

如果把最后一行代码修改为


o.m.apply(o); // 1


运行结果就变成了 1,证明了这时 this 代表的是对象 o。


apply 和 call  什么含义,什么区别 ?什么时候用 ?


call,apply 都属于 Function.prototype 的一个方法,它是 JavaScript 引擎内在实现的,因为属于 Function.prototype,所以每个 Function 对象实例(就是每个方法)都有 call,apply 属性。


既然作为方法的属性,那它们的使用就当然是针对方法的了,这两个方法是容易混淆的,因为它们的作用一样,只是使用方式不同。


语法:


foo.call(this, arg1, arg2, arg3) == foo.apply(this, arguments) == this.foo(arg1, arg2, 
arg3);


  • 相同点:两个方法产生的作用是完全一样的。
  • 不同点:方法传递的参数不同。


每个函数对象会有一些方法可以去修改函数执行时里面的 this,比较常见得到就是 call 和 apply,通过 call 和 apply 可以重新定义函数的执行环境,即 this 的指向。


function add(c, d) {
  console.log(this.a + this.b + c + d);
}
var o = { a: 1, b: 3 };
add.call(o, 5, 7);    //1+3+5+7=16
//传参的时候是扁平的把每个参数传进去
add.apply(o, [10, 20]);   //1+3+10+20=34
//传参的时候是把参数作为一个数组传进去
//什么时候使用 call 或者 apply
function bar() {
  console.log(Object.prototype.toString.call(this));
  // 用来调用一些无法直接调用的方法
}
bar.call(7); // "[object Number]"

异步过程的构成要素有哪些?和异步过程是怎样的 ?


总结一下,一个异步过程通常是这样的:


  • 主线程发起一个异步请求,相应的工作线程接收请求并告知主线程已收到(异步函数返回);
  • 主线程可以继续执行后面的代码,同时工作线程执行异步任务;
  • 工作线程完成工作后,通知主线程;
  • 主线程收到通知后,执行一定的动作(调用回调函数)。


  1. 异步函数通常具有以下的形式:A(args..., callbackFn)。
  2. 它可以叫做异步过程的发起函数,或者叫做异步任务注册函数。
  3. args 和 callbackFn 是这个函数的参数。


所以,从主线程的角度看,一个异步过程包括下面两个要素:


  • 发起函数(或叫注册函数) A。
  • 回调函数 callbackFn。


它们都是在主线程上调用的,其中注册函数用来发起异步过程,回调函数用来处理结果。

举个具体的例子:


setTimeout(fn, 1000);


其中的 setTimeout 就是异步过程的发起函数,fn 是回调函数。

注意:前面说的形式 A(args..., callbackFn) 只是一种抽象的表示,并不代表回调函数一定要作为发起函数的参数。

例如:


var xhr = new XMLHttpRequest();
xhr.onreadystatechange = xxx; // 添加回调函数
xhr.open('GET', url);
xhr.send(); // 发起函数


发起函数和回调函数就是分离的。


说说消息队列和事件循环


微信图片_20220513225459.png


  • 主线程在执行完当前循环中的所有代码后,就会到消息队列取出这条消息(也就是 message 函数),并执行它。
  • 完成了工作线程对主线程的通知,回调函数也就得到了执行。
  • 如果一开始主线程就没有提供回调函数,AJAX 线程在收到 HTTP 响应后,也就没必要通知主线程,从而也没必要往消息队列放消息。
异步过程的回调函数,一定不在当前的这一轮事件循环中执行。


相关文章
|
10天前
|
JSON JavaScript 前端开发
Javascript基础 86个面试题汇总 (附答案)
该文章汇总了JavaScript的基础面试题及其答案,涵盖了JavaScript的核心概念、特性以及常见的面试问题。
28 3
|
16天前
|
前端开发 JavaScript
JavaScript 面试系列:如何理解 ES6 中 Generator ?常用使用场景有哪些?
JavaScript 面试系列:如何理解 ES6 中 Generator ?常用使用场景有哪些?
|
1月前
|
JavaScript
ES6学习(9)js中的new实现
ES6学习(9)js中的new实现
|
2月前
|
JavaScript 前端开发
常见的JS面试题
【8月更文挑战第5天】 常见的JS面试题
53 3
|
6天前
|
JavaScript 前端开发 Oracle
软件工程师,学习下JavaScript ES6新特性吧
软件工程师,学习下JavaScript ES6新特性吧
25 9
|
17天前
|
JSON JavaScript 前端开发
JavaScript第五天(函数,this,严格模式,高阶函数,闭包,递归,正则,ES6)高级
JavaScript第五天(函数,this,严格模式,高阶函数,闭包,递归,正则,ES6)高级
|
11天前
|
前端开发 JavaScript
ES6新标准下JS异步编程Promise解读
ES6新标准下JS异步编程Promise解读
19 3
|
11天前
|
JavaScript 安全
ES6中JS类实现的解读
ES6中JS类实现的解读
15 2
|
2月前
|
JSON 前端开发 JavaScript
|
1月前
|
缓存 JavaScript 前端开发
JavaScript模块化开发:ES6模块与CommonJs的对比与应用
JavaScript模块化开发:ES6模块与CommonJs的对比与应用
21 2
下一篇
无影云桌面