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

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

js 的 ready 和 onload 事件的区别


  • onload 是等 HTML 的所有资源都加载完成后再执行 onload 里面的内容,所有资源包括 DOM 结构、图片、视频 等资源;
  • ready 是当 DOM 结构加载完成后就可以执行了,相当于 jQuery 中的 $(function(){ js 代码 });
  • 另外,onload 只能有一个,ready 可以有多个。

js 的两种回收机制


标记清除(mark and sweep)


从语义上理解就比较好理解了,大概就是当变量进入到某个环境中的时候就把这个变量标记一下,比如标记为“进入环境”,当离开的时候就把这个变量的标记给清除掉,比如是“离开环境”。而在这后面还有标记的变量将被视为准备删除的变量。


  • 垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记(可以使用任何标记方式)。
  • 然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记。
  • 而在此之后再被加上的标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。
  • 最后,垃圾收集器完成内存清除工作。销毁那些带标记的值并回收它们所占用的内存空间。


这是 javascript 最常见的垃圾回收方式。至于上面有说道的标记,到底该如何标记 ?

好像是有很多方法,比如特殊位翻转,维护一个列表什么的。


引用计数(reference counting)


  • 引用计数的含义是跟踪记录每个值被引用的次数,当声明一个变量并将一个引用类型的值赋给该变量时,这个时候的引用类型的值就会是引用次数 +1 了。如果同一个值又被赋给另外一个变量,则该值的引用次数又 +1。
  • 相反如果包含这个值的引用的变量又取得另外一个值,即被重新赋了值,那么这个值的引用就 -1 。当这个值的引用次数编程 0 时,表示没有用到这个值,这个值也无法访问,因此环境就会收回这个值所占用的内存空间回收。
  • 这样,当垃圾收集器下次再运行时,它就会释放引用次数为 0 的值所占用的内存。

三张图搞懂 JavaScript 的原型对象与原型链


对于新人来说,JavaScript 的原型是一个很让人头疼的事情,一来 prototype 容易与 proto 混淆,


一、prototype 和 proto 的区别


微信图片_20220513230034.png


var a = {};
console.log(a.prototype);  //undefined
console.log(a.__proto__);  //Object {}
var b = function(){}
console.log(b.prototype);  //b {}
console.log(b.__proto__);  //function() {}


结果:


微信图片_20220513230046.png


微信图片_20220513230130.png


/*1、字面量方式*/
var a = {};
console.log("a.__proto__ :", a.__proto__);  // Object {}
console.log("a.__proto__ === a.constructor.prototype:", a.__proto__ === a.constructor.prototype); // true
/*2、构造器方式*/
var A = function(){};
var a2 = new A();
console.log("a2.__proto__:", a2.__proto__); // A {}
console.log("a2.__proto__ === a2.constructor.prototype:", a2.__proto__ === a2.constructor.prototype); // true
/*3、Object.create()方式*/
var a4 = { a: 1 }
var a3 = Object.create(a4);
console.log("a3.__proto__:", a3.__proto__); // Object {a: 1}
console.log("a3.__proto__ === a3.constructor.prototype:", a3.__proto__ === a3.constructor.prototype); // false(此处即为图1中的例外情况)


结果:


微信图片_20220513230228.png


微信图片_20220513230232.png


var A = function(){};
var a = new A();
console.log(a.__proto__); // A {}(即构造器 function A 的原型对象)
console.log(a.__proto__.__proto__); // Object {}(即构造器 function Object 的原型对象)
console.log(a.__proto__.__proto__.__proto__); // null


结果:


微信图片_20220513230311.png


闭包的理解 ?


一、变量的作用域


要理解闭包,首先必须理解 Javascript 特殊的变量作用域。


变量的作用域无非就是两种:全局变量和局部变量。


Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。


var n = 999;
function f1(){
  alert(n);
}
f1(); // 999


另一方面,在函数外部自然无法读取函数内的局部变量。


function f1(){    
  var n = 999;
}
alert(n); // error


这里有一个地方需要注意,函数内部声明变量的时候,一定要使用 var 命令。

如果不用的话,你实际上声明了一个全局变量!


function f1(){
  n = 999;
}
f1();
alert(n); // 999


二、如何从外部读取局部变量 ?


function f1() {
  var n = 999;
  function f2() {
    alert(n);
  }
  return f2;
}
var result = f1();
result(); // 999


既然 f2 可以读取 f1 中的局部变量,那么只要把 f2 作为返回值,我们不就可以在 f1 外部读取它的内部变量了吗!


三、闭包的概念


上一节代码中的 f2 函数,就是闭包。


我的理解是,闭包就是能够读取其他函数内部变量的函数


由于在 Javascript 语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成 定义在一个函数内部的函数


所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁


四、闭包的用途


闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。


怎么来理解呢 ?请看下面的代码。


function f1() {
  var n = 999;
  nAdd = function () { n += 1 }
  function f2() {
    alert(n);
  }
  return f2;
}
var result = f1();
result(); // 999
nAdd();
result(); // 1000


在这段代码中,result 实际上就是闭包 f2 函数。它一共运行了两次,第一次的值是 999,第二次的值是 1000。这证明了,函数 f1 中的局部变量 n 一直保存在内存中,并没有在 f1 调用后被自动清除。


为什么会这样呢 ?


原因就在于 f1 是 f2 的父函数,而 f2 被赋给了一个全局变量,这导致 f2 始终在内存中,而 f2 的存在依赖于 f1,因此 f1 也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。


这段代码中另一个值得注意的地方,就是


  • "nAdd=function(){ n+=1 }" 这一行,首先在 nAdd 前面没有使用 var 关键字,因此 nAdd 是一个全局变量,而不是局部变量。
  • 其次,nAdd 的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以 nAdd 相当于是一个 setter,可以在函数外部对函数内部的局部变量进行操作。


五、使用闭包的注意点


  • 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在 IE 中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
  • 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

闭包面试经典问题


问题:想每次点击对应目标时弹出对应的数字下标 0~4 ,但实际是无论点击哪个目标都会弹出数字 5。


function onMyLoad() {
  var arr = document.getElementsByTagName("p");
  for (var i = 0; i < arr.length; i++) {
    arr[i].onclick = function () {
      alert(i);
    }
  }
}


问题所在:arr 中的每一项的 onclick 均为一个函数实例(Function 对象),这个函数实例也产生了一个闭包域,这个闭包域引用了外部闭包域的变量,其 function scope 的 closure 对象有个名为 i 的引用,外部闭包域的私有变量内容发生变化,内部闭包域得到的值自然会发生改变。


解决办法一


解决思路:增加若干个对应的闭包域空间(这里采用的是匿名函数),专门用来存储原先需要引用的内容(下标),不过只限于基本类型(基本类型值传递,对象类型引用传递)。


//声明一个匿名函数,若传进来的是基本类型则为值传递,故不会对实参产生影响,
//该函数对象有一个本地私有变量 arg(形参) ,该函数的 function scope 的 closure 对象属性有两个引用,一个是 arr,一个是 i
//尽管引用 i 的值随外部改变 ,但本地私有变量(形参) arg 不会受影响,其值在一开始被调用的时候就决定了
for (var i = 0; i < arr.length; i++) {
  (function (arg) {
    arr[i].onclick = function () {
      // onclick 函数实例的 function scope 的 closure 对象属性有一个引用 arg,
      alert(arg);
      //只要 外部空间的 arg 不变,这里的引用值当然不会改变
    }
  })(i); //立刻执行该匿名函数,传递下标 i (实参)
}


解决办法二


解决思路:将事件绑定在新增的匿名函数返回的函数上,此时绑定的函数中的 function scope 中的 closure 对象的 引用 arg 是指向将其返回的匿名函数的私有变量 arg


for (var i = 0; i < arr.length; i++) {
  arr[i].onclick = (function (arg) {
    return function () {
      alert(arg);
    }
  })(i);
}


解决办法三


使用 ES6 新语法 let 关键字


for (var i = 0; i < arr.length; i++) {
  let j = i; // 创建一个块级变量
  arr[i].onclick = function () {
    alert(j);
  }
}

JavaScript 判断一个变量是对象还是数组 ?


typeof 都返回 object


在 JavaScript 中所有数据类型严格意义上都是对象,但实际使用中我们还是有类型之分,如果要判断一个变量是数组还是对象使用 typeof 搞不定,因为它全都返回 object。



第一,使用 typeof 加 length 属性


数组有 length 属性,object 没有,而 typeof 数组与对象都返回 object,所以我们可以这么判断


var getDataType = function(o){
    if(typeof o == 'object'){
        if( typeof o.length == 'number' ){
            return 'Array';
        } else {
            return 'Object';   
        }
    } else {
        return 'param is no object type';
    }
};


第二,使用 instanceof


利用 instanceof 判断数据类型是对象还是数组时应该优先判断 array,最后判断 object。


var getDataType = function(o){
    if(o instanceof Array){
        return 'Array'
    } else if ( o instanceof Object ){
        return 'Object';
    } else {
        return 'param is no object type';
    }
};

ES5 的继承和 ES6 的继承有什么区别 ?


ES5 的继承时通过 prototype 或构造函数机制来实现。


  • ES5 的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到 this 上(Parent.apply(this))
  • ES6 的继承机制完全不同,实质上是先创建父类的实例对象 this(所以必须先调用父类的 super()方法),然后再用子类的构造函数修改 this


具体的:ES6 通过 class 关键字定义类,里面有构造方法,类之间通过 extends 关键字实现继承。子类必须在 constructor 方法中调用 super 方法,否则新建实例报错。因为子类没有自己的 this 对象,而是继承了父类的 this 对象,然后对其进行加工。如果不调用 super 方法,子类得不到 this 对象。


ps:super 关键字指代父类的实例,即父类的 this 对象。在子类构造函数中,调用 super 后,才可使用 this 关键字,否则报错。



翻转一个字符串


先将字符串转成一个数组,然后用数组的 reverse() + join() 方法。


let a = "hello word";
let b = [...str].reverse().join(""); // drow olleh

说说堆和栈的区别 ?


一、堆栈空间分配区别


  • 栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈;  
  • 堆(操作系统):一般由程序员分配释放, 若程序员不释放,程序结束时可能由 OS 回收,分配方式倒是类似于链表。 

 

二、堆栈缓存方式区别

  • 栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放;  
  • 堆是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。

  

三、堆栈数据结构区别

  • 堆(数据结构):堆可以被看成是一棵树,如:堆排序;  
  • 栈(数据结构):一种先进后出的数据结构。
相关文章
|
2月前
|
JSON JavaScript 前端开发
Javascript基础 86个面试题汇总 (附答案)
该文章汇总了JavaScript的基础面试题及其答案,涵盖了JavaScript的核心概念、特性以及常见的面试问题。
55 3
|
20天前
|
JSON JavaScript 前端开发
[JS]面试官:你的简历上写着熟悉jsonp,那你说说它的底层逻辑是怎样的?
本文介绍了JSONP的工作原理及其在解决跨域请求中的应用。首先解释了同源策略的概念,然后通过多个示例详细阐述了JSONP如何通过动态解释服务端返回的JavaScript脚本来实现跨域数据交互。文章还探讨了使用jQuery的`$.ajax`方法封装JSONP请求的方式,并提供了具体的代码示例。最后,通过一个更复杂的示例展示了如何处理JSON格式的响应数据。
30 2
[JS]面试官:你的简历上写着熟悉jsonp,那你说说它的底层逻辑是怎样的?
|
27天前
|
存储 JavaScript 前端开发
JS的ES6知识点
【10月更文挑战第19天】这只是 ES6 的一些主要知识点,ES6 还带来了许多其他的特性和改进,这些特性使得 JavaScript 更加现代化和强大,为开发者提供了更多的便利和灵活性。
20 3
|
1月前
|
JavaScript 前端开发 索引
JavaScript ES6及后续版本:新增的常用特性与亮点解析
JavaScript ES6及后续版本:新增的常用特性与亮点解析
41 4
|
1月前
|
自然语言处理 JavaScript 前端开发
JavaScript高级——ES6基础入门
JavaScript高级——ES6基础入门
25 1
|
21天前
|
前端开发 JavaScript
JavaScript新纪元:ES6+特性深度解析与实战应用
【10月更文挑战第29天】本文深入解析ES6+的核心特性,包括箭头函数、模板字符串、解构赋值、Promise、模块化和类等,结合实战应用,展示如何利用这些新特性编写更加高效和优雅的代码。
40 0
|
1月前
|
前端开发 JavaScript 小程序
JavaScript的ES6中Promise的使用以及个人理解
JavaScript的ES6中Promise的使用以及个人理解
18 1
|
1月前
|
JavaScript
震惊了,面试官居然问我ES6中class语法糖是怎么实现的
【10月更文挑战第2天】震惊了,面试官居然问我ES6中class语法糖是怎么实现的
震惊了,面试官居然问我ES6中class语法糖是怎么实现的
|
2月前
|
JavaScript 前端开发 Oracle
软件工程师,学习下JavaScript ES6新特性吧
软件工程师,学习下JavaScript ES6新特性吧
43 9
|
1月前
|
Web App开发 JavaScript 前端开发
前端Node.js面试题
前端Node.js面试题
下一篇
无影云桌面