以下问题大部分偏初中级,答案也都是简明扼要,可以在面试时应付一下,免得无点可说在第一面就被PASS。如果这些问题能让你快速回忆起平时所学,串联起前后的技能点,在面试时有个好的发挥,也就不费我整理这些面试题的用心。最后,这些答案仅供参考,不要死记硬背。愿你们都能找到满意的工作。
我是李古拉雷,曾全栈开发工程师,前今日头条前端架构师,今创业公司CEO。关注我的公众号可以获得更多前端技术和职场经验。我的人生信条分享即价值!
JS篇
目录
7.promise、generator、async/await怎么使用,有什么区别?
15.jQueryajax 、fetch、 axios 有什么异同,适用场景有哪些?
28.介绍下广度优先遍历(BFS)和深度优先遍历(DFS)?
1.let、var、const区别?
答:
1.var声明的变量会挂载在window上,而let和const声明的变量不会
2.var声明变量存在变量提升,let和const不存在变量提升
3.let和const声明形成块作用域,var变量提升不会形成作用域
4.同一作用域下let和const不能声明同名变量,而var可以
5.var和let可以可以修改声明的变量,const不可以
6.const定义的变量时必须初始化
7.let、const 存在暂时性死区
2.什么是函数柯里化?
答:属于高阶函数应用,传递给函数部分参数来调用柯里化函数,让它返回一个函数去处理剩下的参数。
// 把接受多个参数的函数转换成接受一个单一参数的函数
// 柯里化
var foo = function(x) {
return function(y) {
return x + y
}
}
foo(3)(4) // 7
// 普通方法
var add = function(x, y) {
return x + y;
}
add(3, 4) //7
3.什么是Promise对象,有哪些用法?
答:
Promise对象的定义
Promise是异步编程的一种解决方案,它是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。可以用来解决“回调地狱”的问题。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。promise对象是一个构造函数,用来生成Promise实例;
Promise对象的特点
(1)对象的状态不受外界影响,promise对象代表一个异步操作,有三种状态,pending(进行中)、fulfilled(已成功)、rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态,这也是promise这个名字的由来“承若”;
(2)一旦状态改变就不会再变,任何时候都可以得到这个结果,promise对象的状态改变,只有两种可能:从pending变为fulfilled,从pending变为rejected。这时就称为resolved(已定型)。如果改变已经发生了,你再对promise对象添加回调函数,也会立即得到这个结果,这与事件(event)完全不同,事件的特点是:如果你错过了它,再去监听是得不到结果的。
有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。
Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
Promise对象的用法
是一个构造函数,这个构造函数里有两个参数,分别是:resolve(成功之后的回调函数)、reject(失败之后的回调函数)。
因为promise表示的是一个异步操作,每当我们new一个promise实例,就表示一个具体的异步操作,那么这个异步操作的结果就只能有两种状态:成功/失败,两者都需要回调函数resolve/reject返回。所以内部拿到操作的结果后,无法使用return把操作结果返回给调用者,这时候只能用回调函数的形式来把成功或失败的结果返回给调用者。
promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数
then方法可以接受连个回调函数作为参数,第一个回调函数是promise对象的状态变为resolved时调用,第二个回调函数是promise对象的状态变为rejected时调用,其中,第二个函数是可选的,不一定要提供,这两个函数都接受promise对象传出的值作为参数;
*通过。then指定回调函数的时候,成功的回调函数必须传,失败的回调函数可以胜利。
如果前面的promise执行失败,不详影响后续操作终止,捕获异常的两种方式:
①可以为每个promise指定失败回调;
function(err){
console.log(……)
})
②最后加catch(function(err){
console.log(……)
})//表示如前面有任意一个有报错,立即报错,并终止后面的;如果前面无报错,前面正常执行。
4.如何通过Promise对象实现ajax?
getJSON是对XMLHTTPRequest对象的封装,用于发出一个针对JSON数据的HTTP请求,并且返回一个promise对象,需要注意的是,在getJSON内部,resolve函数和reject函数调用时,都带有参数;
如果调用resolve函数和reject函数时带有参数,那么他们的参数会被传递给回调函数,reject函数的参数通常是Error对象的实例,表示抛出的错误,resolve函数的参数除了正常的值以外,还可以是另一个promise实例;
5.什么是REST,用起来有什么好处?
答:
REST是一种设计API的模式。最常用的数据格式是JSON。由于JSON能直接被JavaScript读取,所以,以JSON格式编写的REST风格的API具有简单、易读、易用的特点。通过REST模式设计的API可以把web app 全部功能进行封装,可以很容易的实现前后端分离,使的前端代码易编写,后端代码易测试。
6.什么是闭包,举个例子说明一下
答:
“闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。”
举例:创建闭包最常见方式,就是在一个函数内部创建另一个函数。下面例子中的 closure 就是一个闭包,
function func(){
vara =1 ,b = 2;
funciton closure(){
return a+b;
}
return closure;
}
7.promise、generator、async/await怎么使用,有什么区别?
答:
我们知道JavaScript是单线程语言,如果没有异步编程非得卡死。
以前,异步编程的方法有下面四种
回调函数
事件监听
发布/订阅
Promise对象
现在据说异步编程终极解决方案是——async/await
更详细的介绍参考下面这篇文章:
https://www.jianshu.com/p/1c9e9c161612
8.apply/call/bind 有什么区别?
答:
这三者的作用就是改变函数运行时this的指向。
call方法:
语法:call([thisObj[,arg1[, arg2[, [,.argN]]]]])
定义:调用一个对象的一个方法,以另一个对象替换当前对象。
说明:call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。
如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj。
apply方法:
语法:apply([thisObj[,argArray]])
定义:应用某一对象的一个方法,用另一个对象替换当前对象。
说明:如果 argArray 不是一个有效的数组或者不是 arguments 对象,那么将导致一个 TypeError。
如果没有提供 argArray 和 thisObj 任何一个参数,那么 Global 对象将被用作 thisObj, 并且无法被传递任何参数。
bind方法:
语法:bind(thisArg[, arg1[, arg2[, ...]]])
定义:将接受多个参数的函数变换成接受一个单一参数。
说明:bind()方法所返回的函数的length(形参数量)等于原函数的形参数量减去传入bind()方法中的实参数量(第一个参数以后的所有参数),因为传入bind中的实参都会绑定到原函数的形参。
9.什么是变量提升、函数提升?
答:
变量提升:
简单说就是在js代码执行前引擎会先进行预编译,预编译期间会将变量声明与函数声明提升至其对应作用域的最顶端,函数内声明的变量只会提升至该函数作用域最顶层。
当函数内部定义的一个变量与外部相同时,那么函数体内的这个变量就会被上升到最顶端。
举例来说:
console.log(a); // undefined
var a = 3;
//预编译后的代码结构可以看做如下运行顺序
var a; // 将变量a的声明提升至最顶端,赋值逻辑不提升。
console.log(a); // undefined
a = 3; // 代码执行到原位置即执行原赋值逻辑
函数提升:
函数提升只会提升函数声明式写法,函数表达式的写法不存在函数提升。
函数提升的优先级大于变量提升的优先级,即函数提升在变量提升之上。
10.什么是事件冒泡,它是如何工作的?如何阻止事件冒泡
答:
在一个对象上触发某类事件(比如单击onclick事件),如果此对象定义了此事件的处理程序,那么此事件就会调用这个处理程序,如果没有定义此事件处理程序或者事件返回true,那么这个事件会向这个对象的父级对象传播,从里到外,直至它被处理(父级对象所有同类事件都将被激活),或者它到达了对象层次的最顶层,即document对象(有些浏览器是window)
阻止事件冒泡的几种方法
第一种: event.stopPropagation();
第二种: return false;
第三种: event.preventDefault();
11.简单说说js中的继承
答:
有以下六种方法
1.原型链继承
JavaScript实现继承的基本思想:通过原型将一个引用类型继承另一个引用类型的属性和方法。
2.借用构造函数继承(伪造对象或经典继承)
JavaScript实现继承的基本思想:在子类构造函数内部调用超类型构造函数。
通过使用apply()和call()方法可以在新创建的子类对象上执行构造函数。
3.组合继承(原型+借用构造)(伪经典继承)
JavaScript实现继承的基本思想:将原型链和借用构造函数的技术组合在一块,从而发挥两者之长的一种继承模式。
将原型链和借用构造函数的技术组合到一起,从而取长补短发挥两者长处的一种继承模式。
4.原型式继承
JavaScript实现继承的基本思想:借助原型可以基于已有的对象创建新对象,同时还不必须因此创建自定义的类型。
5.寄生式继承
JavaScript实现继承的基本思想:创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真正是它做了所有工作一样返回对象。
寄生式继承是原型式继承的加强版。
6.寄生组合式继承
JavaScript实现继承的基本思想:通过借用函数来继承属性,通过原型链的混成形式来继承方法。
12.常用的js数组操作方法有哪些?
答:
1. Array.shift()------删除并返回第一个元素
作用:从数组中删除第一个元素(即下标为0的元素),并返回该元素。
注意:1)删除元素之后,数组的长度-1。
2)如果数组是空的,那么 shift() 方法将不进行任何操作,返回 undefined 值。
2.Array.pop()------删除并返回最后一个元素
作用:从数组中删除最后一个元素(即下标为length-1的元素),并返回该元素。
注意:1)删除元素之后,数组的长度-1。
2)如果数组是空的,那么 shift() 方法将不进行任何操作,返回 undefined 值。
3. Array.push(param1[,param2,...paramN])------尾部添加元素
作用:在数组的尾部添加一个元素,并返回新数组的长度。
注意:1)它是直接修改该数组,而不是重新创建一个数组。
2)它和pop是一对相反的先进后出的栈功能方法。
3)它可以同时给一个数组添加多个元素。
4. Array.unshift(newElement1[,newElement2,...newElementN])------头部添加元素
作用:在数组的头部添加一个或多个元素,并返回新数组的长度。
注意:1)它是直接修改该数组,而不是重新创建一个数组。
2)IE浏览器不支持该方法。
5.Array.join([separator])------转换成字符串
作用:把数组的所有元素放入到一个字符串中。
注意:1)参数separator表示字符串中元素的分隔符,可以为空,默认为半角逗号。
2)该方法并不修改数组。
6. Array.contact(array1[,array2,...arrayN])------连接数组
作用:将两个或两个以上的数组连接成一个数组,并返回连接后的数组。
注意:1)该方法并不会改变现有的数组,而是返回被连接的多个数组的一个副本。
2)如果多个数组里有值相同的元素,那也不会重复出现,而不会把重复的元素过滤掉。
7.Array.reverse()------反转数组
作用:把数组的所有元素顺序反转。
注意:1)该方法会直接修改数组,而不会创建新的数组。
8. Array.slice(start[, end])------截取数组
作用:截取数组中指定位置的元素,并返回一个新的子数组。
注意:1)该方法并不会改变现有的数组,而是原数组的一个子数组。
2)参数start是必选,表示开始下标,如果start为负数,表示从末尾开始,-1表示最后一个元素,依次类推。
3)end是可选表示结束下标,如果没有指定,表示到结尾元素。
9.Array.splice()------删除指定元素
作用:从数组指定位置删除指定数量的元素,并返回被删除的元素。
注意:1)该方法会直接修改数组。
2)splice() 方法与 slice() 方法的作用是不同的,splice() 方法会直接对数组进行修改,而slice只是截取原数组的一部分后返回一个子数组,并不会修改原数组。
10. Array.toString()------转换成字符串
作用:数组转换为字符串,并返回该字符串。
注意:1)该方法和不带参数的join()方法效果一样。
13.js数组去重,能用几种方法实现?
答:
1.使用es6 set方法 [...new Set(arr)]
let arr = [1,2,3,4,3,2,3,4,6,7,6];
let unique = (arr)=> [...new Set(arr)];
unique(arr);//[1, 2, 3, 4, 6, 7]
2.利用新数组indexOf查找
indexOf() 方法可返回某个指定的元素在数组中首次出现的位置。如果没有就返回-1。
3.for 双重循环
通过判断第二层循环,去重的数组中是否含有该元素,如果有就退出第二层循环,如果没有j==result.length就相等,然后把对应的元素添加到最后的数组里面。
let arr = [1,2,3,4,3,2,3,4,6,7,6];
let result = [];
for(var i = 0 ; i < arr.length; i++) {
for(var j = 0 ; j < result.length ; j++) {
if( arr[i] === result[j]){
break;
};
};
if(j == result.length){
result.push(arr[i]);
};
};
console.log(result);
4.利用for嵌套for,然后splice去重
functionunique(arr){
for(vari=0; i<arr.length; i++){
for(varj=i+1; j<arr.length; j++){
if(arr[i]==arr[j]){ //第一个等同于第二个,splice方法删除第二个
arr.splice(j,1);
j--;
}
}
}
returnarr;
}
5.利用filter
let arr = [1,2,3,4,3,2,3,4,6,7,6];
let unique = (arr) => {
return arr.filter((item,index) => {
return arr.indexOf(item) === index;
})
};
unique(arr);
5.let arr = [1,2,3,4,3,2,3,4,6,7,6];
let unique = (arr) => {
return arr.filter((item,index) => {
return arr.indexOf(item) === index;
})
};
unique(arr);
6.利用Map数据结构去重
let arr = [1,2,3,4,3,2,3,4,6,7,6];
let unique = (arr)=> {
let seen = new Map();
return arr.filter((item) => {
return !seen.has(item) && seen.set(item,1);
});
};
unique(arr);
14.数组中的forEach和map的区别?
答:
相同点
都是循环遍历数组中的每一项
forEach和map方法里每次执行匿名函数都支持3个参数,参数分别是item(当前每一项),index(索引值),arr(原数组)
匿名函数中的this都是指向window
只能遍历数组
都不会改变原数组
区别
map方法
1.map方法返回一个新的数组,数组中的元素为原始数组调用函数处理后的值。
2.map方法不会对空数组进行检测,map方法不会改变原始数组。
3.浏览器支持:chrome、Safari1.5+、opera都支持,IE9+,
若arr为空数组,则map方法返回的也是一个空数组。
forEach方法
1.forEach方法用来调用数组的每个元素,将元素传给回调函数
2.forEach对于空数组是不会调用回调函数的。
无论arr是不是空数组,forEach返回的都是undefined。这个方法只是将数组中的每一项作为callback的参数执行一次。
15.jQueryajax 、fetch、 axios 有什么异同,适用场景有哪些?
答:
1.jQuery ajax
$.ajax({
type: 'POST',
url: url,
data: data,
dataType: dataType,
success: function () {},
error: function () {}
});
优缺点:
本身是针对MVC的编程,不符合现在前端MVVM的浪潮
JQuery整个项目太大,单纯使用ajax却要引入整个JQuery非常的不合理(采取个性化打包的方案又不能享受CDN服务)
2.axios
axios({
method: 'post',
url: '/user/12345',
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
})
.then(function (response) {
console.log(response);
})
客户端支持防止CSRF/XSRF
自动转换JSON数据
取消请求
转换请求和响应数据
拦截请求和响应
支持 Promise API
从 node.js 发出 http 请求
从浏览器中创建 XMLHttpRequest
axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,它本身具有以下特征:
为什么要用axios?
4)fetch没有办法原生监测请求的进度,而XHR可以
3)fetch不支持abort,不支持超时控制,使用setTimeout及Promise.reject的实现的超时控制并不能阻止请求过程继续在后台运行,造成了量的浪费
2)fetch默认不会带cookie,需要添加配置项
1)fetchtch只对网络请求报错,对400,500都当做成功的请求,需要封装去处理
脱离了XHR,是ES规范里新的实现方式
更加底层,提供的API丰富(request, response)
更好更方便的写法
符合关注分离,没有将输入、输出和用事件来跟踪的状态混杂在一个对象里
优缺点:
}
console.log("Oops, error", e);
} catch(e) {
console.log(data);
let data = response.json();
let response = await fetch(url);
try {
3.fetch
提供了一些并发请求的接口(重要,方便了很多的操作)
客户端支持防止CSRF
支持 Promise API
从 node.js 创建 http 请求
优缺点:
});
console.log(error);
.catch(function (error) {
})
16.es6 扩展运算符可以解决哪些问题?
答:
.. 代表着扩展运算符或Rest(剩余)运算符
17.是么是js事件循环 event loop?
答:
主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。
这里推荐一下阮一峰老师的这篇讲解event loop的博文:http://www.ruanyifeng.com/blog/2014/10/event-loop.html
讲的比较详细,深入浅出很容易理解。
18.Math.min()和Math.max()大小比较?
答:
Math.max() < Math.min()
Math.min()如果没有参数,则返回 Infinity。Infinity 是 javascript 中全局对象的一个属性,在浏览器环境中就是 window 对象的一个属性,表示无穷大。
而 Math.max() 没有传递参数时返回的是 -Infinity。
因此 Math.min() 要比 Math.max() 大。
19.怎么实现对象的深浅拷贝?
答:
浅拷贝很容易,只需要使用赋值运算符(=)即可
20.文件上传如何做断点续传?
答:
文件断点续传是HTML5引入的新特性,HTML5的FILE api,有一个slice方法,可以将BLOB对象进行分割。前端通过FileList对象获取到相应的文件,按照指定的分割方式将大文件分段,然后一段一段地传给后端,后端再按顺序一段段将文件进行拼接。
断点续传原理
目前比较常用的断点续传的方法有两种,一种是通过websocket接口进行文件上传,另一种是通过ajax,两种方法各有千秋,虽然websocket听起来比较高端些,但是除了用了不同的协议外其他的算法基本上都是很相似的,并且服务端要开启ws接口,这里用相对方便的ajax来说明断点上传的思路。
说来说去,断点续传最核心的内容就是把文件“切片”然后再一片一片的传给服务器。
首先是文件的识别,一个文件被分成了若干份之后如何告诉服务器你切了多少块,以及最终服务器应该如何把你上传上去的文件进行合并?
因此在文件开始上传之前,我们和服务器要有一个“握手”的过程,告诉服务器文件信息,然后和服务器约定切片的大小,当和服务器达成共识之后就可以开始后续的文件传输了。
前台要把每一块的文件传给后台,成功之后前端和后端都要标识一下,以便后续的断点。
当文件传输中断之后用户再次选择文件就可以通过标识来判断文件是否已经上传了一部分,如果是的话,那么我们可以接着上次的进度继续传文件,以达到续传的功能。
21.js如何处理防抖和节流?
答:
在进行窗口的resize、scroll,输入框内容校验等操作时,如果事件处理函数调用的频率无限制,会加重浏览器的负担,导致用户体验非常糟糕。
此时我们可以采用debounce(防抖)和throttle(节流)的方式来减少调用频率,同时又不影响实际效果。
函数防抖
函数防抖(debounce):当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。
如下,持续触发scroll事件时,并不执行handle函数,当1000毫秒内没有触发scroll事件时,才会延时触发scroll事件。
function debounce(fn, wait) {
var timeout = null;
return function() {
if(timeout !== null) clearTimeout(timeout);
timeout = setTimeout(fn, wait);
}
}
// 处理函数
function handle() {
console.log(Math.random());
}
// 滚动事件
window.addEventListener('scroll', debounce(handle, 1000));
函数节流
函数节流(throttle):当持续触发事件时,保证一定时间段内只调用一次事件处理函数。
节流通俗解释就比如我们水龙头放水,阀门一打开,水哗哗的往下流,秉着勤俭节约的优良传统美德,我们要把水龙头关小点,最好是如我们心意按照一定规律在某个时间间隔内一滴一滴的往下滴。
如下,持续触发scroll事件时,并不立即执行handle函数,每隔1000毫秒才会执行一次handle函数。
var throttle = function(func, delay) {
var prev = Date.now();
return function() {
var context = this;
var args = arguments;
var now = Date.now();
if (now - prev >= delay) {
func.apply(context, args);
prev = Date.now();
}
}
}
function handle() {
console.log(Math.random());
}
window.addEventListener('scroll', throttle(handle, 1000));
总结
函数防抖:将几次操作合并为一此操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。
函数节流:使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数。
区别: 函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。
22.事件委托以及优缺点
答:
优点:
1.减少事件注册,节省内存。比如,
2.在table上代理所有td的click事件。
3.在ul上代理所有li的click事件。
4.简化了dom节点更新时,相应事件的更新。比如
5.不用在新添加的li上绑定click事件。
6.当删除某个li时,不用移解绑上面的click事件。
缺点:
1.事件委托基于冒泡,对于不冒泡的事件不支持。
2.层级过多,冒泡过程中,可能会被某层阻止掉。
3.理论上委托会导致浏览器频繁调用处理函数,虽然很可能不需要处理。所以建议就近委托,比如在table上代理td,而不是在document上代理td。
4.把所有事件都用代理就可能会出现事件误判。比如,在document中代理了所有button的click事件,另外的人在引用改js时,可能不知道,造成单击button触发了两个click事件。
23.介绍this各种情况
答:
- this的情况:
1.以函数形式调用时,this永远都是window
2.以方法的形式调用时,this是调用方法的对象
3.以构造函数的形式调用时,this是新创建的那个对象
4.使用call和apply调用时,this是指定的那个对象
5.箭头函数:箭头函数的this看外层是否有函数
如果有,外层函数的this就是内部箭头函数的this
如果没有,就是window
6.特殊情况:通常意义上this指针指向为最后调用它的对象。这里需要注意的一点就是如果返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例
24.== 和 ===的区别,什么情况下用相等==
答:
==:运算符称作相等,用来检测两个操作数是否相等,这里的相等定义的非常宽松,可以允许进行类型转换
===:用来检测两个操作数是否严格相等
1、对于string,number等基础类型,==和===是有区别的
不同类型间比较,==之比较“转化成同一类型后的值”看“值”是否相等,===如果类型不同,其结果就是不等
同类型比较,直接进行“值”比较,两者结果一样
2、对于Array,Object等高级类型,==和===是没有区别的
3、基础类型与高级类型,==和===是有区别的
对于==,将高级转化为基础类型,进行“值”比较,因为类型不同,===结果为false
25.介绍下原型链(解决的是继承问题吗)
答:
JavaScript原型:
每个对象都会在其内部初始化一个属性,就是prototype(原型)。
原型链:
当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去prototype里找这个属性,这个prototype又会有自己的prototype,于是就这样一直找下去,也就是我们平时所说的原型链的概念。
特点:
JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。
26.JS里垃圾回收机制是什么,常用的是哪种,怎么处理的?
答:
JS的垃圾回收机制是为了以防内存泄漏,内存泄漏的含义就是当已经不需要某块内存时这块内存还存在着,垃圾回收机制就是间歇的不定期的寻找到不再使用的变量,并释放掉它们所指向的内存。
JS中最常见的垃圾回收方式是标记清除。
工作原理:是当变量进入环境时,将这个变量标记为“进入环境”。当变量离开环境时,则将其标记为“离开环境”。标记“离开环境”的就回收内存。
工作流程:
1. 垃圾回收器,在运行的时候会给存储在内存中的所有变量都加上标记。
2. 去掉环境中的变量以及被环境中的变量引用的变量的标记。
3. 再被加上标记的会被视为准备删除的变量。
4. 垃圾回收器完成内存清除工作,销毁那些带标记的值并回收他们所占用的内存空间。
27.Promise和setTimeout的区别?
答:
回顾JavaScript事件循环并发模型,我们了解了setTimeout和Promise调用的都是异步任务,这一点是它们共同之处,也即都是通过任务队列进行管理/调度。那么它们有什么区别吗?
任务队列
前文已经介绍了任务队列的基础内容和机制,可选择查看,本文对任务队列进行拓展介绍。JavaScript通过任务队列管理所有异步任务,而任务队列还可以细分为MacroTask Queue和MicoTask Queue两类。
MacroTask Queue
MacroTask Queue(宏任务队列)主要包括setTimeout,setInterval, setImmediate, requestAnimationFrame, NodeJS中的`I/O等。
MicroTask Queue
MicroTask Queue(微任务队列)主要包括两类:
独立回调microTask:如Promise,其成功/失败回调函数相互独立;
复合回调microTask:如 Object.observe, MutationObserver 和NodeJs中的 process.nextTick ,不同状态回调在同一函数体;
MacroTask和MicroTask
JavaScript将异步任务分为MacroTask和MicroTask,那么它们区别何在呢?
依次执行同步代码直至执行完毕;
检查MacroTask 队列,若有触发的异步任务,则取第一个并调用其事件处理函数,然后跳至第三步,若没有需处理的异步任务,则直接跳至第三步;
检查MicroTask队列,然后执行所有已触发的异步任务,依次执行事件处理函数,直至执行完毕,然后跳至第二步,若没有需处理的异步任务中,则直接返回第二步,依次执行后续步骤;
最后返回第二步,继续检查MacroTask队列,依次执行后续步骤;
如此往复,若所有异步任务处理完成,则结束;
28.介绍下广度优先遍历(BFS)和深度优先遍历(DFS)?
答:
1.广度优先遍历
英文缩写为BFS即Breadth FirstSearch。其过程检验来说是对每一层节点依次访问,访问完一层进入下一层,而且每个节点只能访问一次。对于上面的例子来说,广度优先遍历的 结果是:A,B,C,D,E,F,G,H,I(假设每层节点从左到右访问)。
先往队列中插入左节点,再插右节点,这样出队就是先左节点后右节点了。
广度优先遍历树,需要用到队列(Queue)来存储节点对象,队列的特点就是先进先出。例如,上面这颗树的访问如下:
首先将A节点插入队列中,队列中有元素(A);
将A节点弹出,同时将A节点的左、右节点依次插入队列,B在队首,C在队尾,(B,C),此时得到A节点;
继续弹出队首元素,即弹出B,并将B的左、右节点插入队列,C在队首,E在队尾(C,D,E),此时得到B节点;
继续弹出,即弹出C,并将C节点的左、中、右节点依次插入队列,(D,E,F,G,H),此时得到C节点;
将D弹出,此时D没有子节点,队列中元素为(E,F,G,H),得到D节点;
。。。以此类推。。
2.深度优先遍历
英文缩写为DFS即Depth First Search.其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次。对于上面的例子来说深度优先遍历的结果就是:A,B,D,E,I,C,F,G,H.(假设先走子节点的的左侧)。
深度优先遍历各个节点,需要使用到栈(Stack)这种数据结构。stack的特点是是先进后出。整个遍历过程如下:
先往栈中压入右节点,再压左节点,这样出栈就是先左节点后右节点了。
首先将A节点压入栈中,stack(A);
将A节点弹出,同时将A的子节点C,B压入栈中,此时B在栈的顶部,stack(B,C);
将B节点弹出,同时将B的子节点E,D压入栈中,此时D在栈的顶部,stack(D,E,C);
将D节点弹出,没有子节点压入,此时E在栈的顶部,stack(E,C);
将E节点弹出,同时将E的子节点I压入,stack(I,C);
…依次往下,最终遍历完成。
代码:也是以二叉树为例。
29.for in和for of的区别
答:
1.推荐在循环对象属性的时候,使用for...in,在遍历数组的时候的时候使用for...of
2.for...in循环出的是key,for...of循环出的是value
3.注意,for...of是ES6新引入的特性。修复了ES5引入的for...in的不足
4.for...of不能循环普通的对象,需要通过和Object.keys()搭配使用
30.typeof和instanceof 区别?
答:
在javascript中,判断一个变量的类型可以用typeof
(1) 数字类型、typeof返回的值是number。比如说:typeof(1),返回值是number
(2) 字符串类型,typeof返回的值是string。比如typeof(“123”返回值时string)
(3) 布尔类型,typeof返回的值是boolean。比如typeof(true)返回值时boolean
(4) 对象、数组、null返回的值是object。比如typeof(window),typeof(document),typeof(null)返回的值都是object
(5) 函数类型,返回的值是function。比如:typeof(eval),typeof(Date)返回的值都是function。
(6) 不存在的变量、函数或者undefined,将返回undefined。比如:typeof(abc)、typeof(undefined)都返回undefined
在javascript中,instanceof用于判断某个对象是否被另一个函数构造。
使用typeof运算符时采用引用类型存储值会出现一个问题,无论引用的是什么类型的对象,它都返回”object”。ECMAScript引入了另一个Java运算符instanceof来解决这个问题。Instanceof运算符与typeof运算符相似,用于识别正在处理的对象的类型。与typeof方法不同的是,instanceof方法要求开发者明确地确认对象为某特定类型
31.常见的继承有几种方法
答:
1.原型链继承
2.构造函数继承(经典继承)
3.组合方式继承(构造函数 + 原型链)
4.es6方法继承