JS面试题总结
- 一、this指向问题
- (1)this的理解
- 二、手写节流(throttle)和防抖(debounce)
- 三、js事件循环机制(宏任务,微任务,同步任务,异步任务)
- 四、跨域
- 五、es6的新特性
- 六、Symbol(es6新增)
- 七、let var const的区别
- 八、介绍下set,weakset,map,weakmap的区别
- 九、原型和原型链
- 10、了解JS的继承吗?ES5继承与ES6继承的区别
- 11、js如何定义一个类并实现继承;要求不能使用es6的语法(手写一个寄生组合式继承)
- 12、讲一讲Promise;语法
- 13、如何中止promise链式调用
- 14、promise发生错误,怎么捕捉
- 15、手写promise;手写promise.all;手写promise.any;手写promise.finally
- 16、考查作用域的题(var)
- 17、什么是事件委托,为什么在父元素上绑事件,子元素上点击可能触发。
- 18、js数据类型,怎样判断数据类型;基本数据类型和引用数据类型的区别;怎么判断一个数组是不是Array类型(insatnceof,object.prototype.toString.call);堆和栈是什么
- 19、undefined和null的区别
- 20、箭头函数与普通函数的区别是什么?
- 21、构造函数可以使用new生成实例,那么箭头函数可以吗?为什么?
- 22、循环(遍历)的方法,各个方法的区别
- 23、除了for...of还有哪些方法可以遍历字符串,数组中reduce方法有哪些参数,是怎样使用的
- 24、怎么让对象的一个属性不可被改变
- 71.对浏览器的理解
- 72.对浏览器内核的理解
- 73.浏览器所用的内核
- 74.浏览器内核比较
- 75.浏览器的渲染原理
- 25、flat数组扁平化
- 26、什么是闭包;闭包的作用;使用闭包实现每秒钟打印数组中的一个数组[5,4,3,2,1]
- 27、输出什么/函数每秒依次输出
- 28、手写:ajax请求,解释状态码含义;
- 29、手写ajax请求,用promise封装
- 30、== 和===的区别
- 65.JS隐式转换
- 66.JS的垃圾回收机制
- 69.Cookie,localstorage,sessionstorage的区别
- 67.JS的设计模式
- 52.考查原型和函数的打印题
- 53.手写代码:浏览器打开了一个页面,在控制台执行脚本,获取页面TagName种类。
- 70.输出以下代码的执行结果并解释为什么?(连续赋值的坑)
- 56.操作DOM的方法
- 57.求两个数组的并集和交集
- 58.数组的常用方法
- 59.深拷贝和浅拷贝
- 60.手写instanceOf
- 61.数组去重
- 62.New的实现(new实现、new的过程,new干了什么)
- 63.函数柯里化
- 64.二分查找
- 如何让if(a==1&&a==2&&a==3)条件成立?
- 36.两个数组合并成一个数组。请把两个数组 ['A1', 'A2', 'B1', 'B2', 'C1', 'C2', 'D1', 'D2'] 和 ['A', 'B', 'C', 'D'],合并为 ['A1', 'A2', 'A', 'B1', 'B2', 'B', 'C1', 'C2', 'C', 'D1', 'D2', 'D']。
- 0.1+0.2 !== 0.3,如何让它等于
- 8.Console.log(typeof typeof typeof null)
- Let mySet = new Set();
- ParseInt(071)和parseInt(‘071’)分别会输出多少,js中有哪些表示数值的方式
- 35.[‘1’,’2’,’3’].map(parseInt) what? Why?
- 实现 convert 方法,把原始 list 转换成树形结构,要求尽可能降低时间复杂度。
- git常用命令
- 51.linux常用命令
一、this指向问题
(1)this的理解
JS是一个文本作用域的语言,也就是,变量的作用域在写这个变量的时候确定。this设计的目的就是在函数体内部,指代函数当前的运行环境。This具体来说有四种:
(1)默认的this绑定,非严格模式下,this就是全局变量Node环境中的global,浏览器环境下的window。严格模式下,默认的指向是undefined(es6中的类会自动使用严格模式)
(2)隐式绑定:使用obj.foo()这样的语法调用函数的时候,函数foo中的this绑定到obj对象。
(3)显示绑定:调用call,apply,bind方法
(4)构造绑定:new Foo(),foo中的this引用这个新创建的对象。
this指向最后调用它的对象,如果没有调用,则指向window对象(独自运行的函数也指向window;立即执行函数也指向window);
构造函数中this指向构造函数的实例;
箭头函数始终指向函数定义时的this.
es6中类会默认使用严格模式,禁止this指向全局对象,指向undefined。
(2)怎样改变this的指向问题
通过es6的箭头函数(指向函数定义时的this)
通过call,bind,apply函数改变this指向。
(3)Call,bind,apply三者的区别
Call,bind,apply这三个函数的第一个参数都是this的指向对象,第二个参数不同,call和bind都是接收参数列表,apply接收的是一个包含多个参数的数组(也可以是类数组)。而且bind函数还会返回一个新的函数。
(4)容易判读错的几种this情况
es6中类会默认使用严格模式,禁止this指向全局对象,所以会指向undefined
class C{//类
a(){
console.log(this);
}
b=()=>{
console.log(this);
}
}
c = new C();
c.a();//this指向C
f = c.a;
f();//this指向undefined es6中类会默认使用严格模式,禁止this指向全局对象,所以会指向undefined
c.b()//this指向C
var name = "windowsName";
var a = {
fn:function(){
console.log(this.name);
}
}
window.a.fn();//undefined 指向a
var name = "windowsName";
var a = {
name:'Cherry',
func1:function(){
console.log(this.name)
},
func2:function(){
console.log(this)//this指向a(object)
setTimeout(function(){
console.log(this)//this指向window,自己执行的所以指向window
this.func1()//报错,window里面没有func1函数
},100);
}
}
a.func2()
(5)this指向问题,说输出;这两中a,b写法,在内存消耗上有什么区别
a消耗小,a是直接定义在原型链上的;b相当于在每一个实例上赋值。
class C{
a(){
console.log(this);
}
b=()=>{
console.log(this);
}
}
c = new C();
c.a();//c
f = c.a;
f();//undefined//es6中类禁止指向全局对象,指向undefined
c.b();//c
二、手写节流(throttle)和防抖(debounce)
用户点击过快会导致卡顿,可以使用节流/防抖限制函数的执行次数,达到优化的目的。
节流(throttle):
高频事件触发,但在n秒内只执行一次。
防抖(debounce):
事件被触发n秒后执行回调,如果在n秒内事件又被触发,就重新计时。
function throttle(fn,delay){//手写节流函数
var preTime = Date.now();//初始时间
return function(){
var context = this,
args = arguments,
nowTime = Date.now();//触发事件的时间
if(nowTime - preTime >= delay){//如果两次时间间隔超过了指定时间,则执行函数
preTime = Date.now();//把初始时间更新为最新的时间
return fn.apply(context,args);
}
}
}
function debounce(fn,wait){//手写防抖函数
var timer = null;//创建一个标记来存放定时器的返回值
return function(){
var context = this;
args = arguments;
if(timer){
clearTimeout(timer);//如果定时器存在,清除定时器重新计时
timer = null;
}
timer = setTimeout(()=>{//设置定时器,使事件间隔指定时间后执行
fn.apply(context,args);//确保上下文环境为当前的this,所以不能直接用fn(直接用fn会指向window);因为不确定入参的数量,所以还可以传入扩展后的arguments
},wait);
}
}
三、js事件循环机制(宏任务,微任务,同步任务,异步任务)
(1) JS事件循环机制概念
js是一个单线程的脚本语言。当执行栈(执行环境的栈)中所有任务都执行完毕后,去看微任务队列中是否有事件存在,如果存在,则依次执行微任务队列中事件的回调,直到微任务队列为空。然后去宏任务队列中取出一个事件,把对应的回调加入当前执行栈,当执行栈中所有任务都执行完毕后,再去检查微任务队列中是否有事件存在。这个循环的过程就是事件循环。
微任务:promise.then;promise.nextTick
宏任务:setTimeout,setInterval,setImmediate,I/O,Ui交互事件,script(整体代码)
打印顺序的题:同步任务->微任务->宏任务
New promise中的直接执行,是同步任务
Async函数内,await和await之前的都是同步任务;await执行之后放入微任务队列中。
(2)为什么js是单线程
JS是单线程的原因主要和JS的用途有关,JS主要实现浏览器与用户的交互,以及操作DOM。
如果JS被设计为多线程,如果一个线程要修改一个DOM元素,另一个线程要删除这个DOM元素,这时浏览器就不知道该怎么办,为了避免复杂的情况产生,所以JS是单线程的。
为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。
(3)线程和进程是什么?举例说明
进程:cpu分配资源的最小单位(是能拥有资源和独立运行的最小单位)
线程:是cpu最小的调度单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)
栗子:比如进程=火车,线程就是车厢
- 一个进程内有多个线程,执行过程是多条线程共同完成的,线程是进程的部分。
一个火车可以有多个车厢 - 每个进程都有独立的代码和数据空间,程序之间切换会产生较大的开销;线程可以看作轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器,线程之间切换的开销小。
【多列火车比多个车厢更耗资源】
【一辆火车上的乘客很难换到另外一辆火车,比如站点换乘,但是同一辆火车上乘客很容易从A车厢换到B车厢】 - 同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的
【一辆火车上不同车厢的人可以共用各节车厢的洗手间,但是不是火车上的乘客无法使用别的火车上的洗手间】
(4)js引擎的执行栈
执行栈也叫执行上下文栈,用于存储代码执行期间创建的所有上下文,具有先进后出的特点。 也就是当代码第一次执行的时候,会把浏览器创建的全局执行上下文压入栈中;当以后调用函数时,会把调用函数所创建的函数执行上下文压入栈中。当函数执行完,就会将其从栈中弹出。当所有的代码都执行完毕之后,就把全局执行上下文弹出,执行上下文栈就为空了。(执行上下文分为三种,全局执行上下文,它是由浏览器创建的,也就是常说的window对象;函数执行上下文,它是函数被调用时被创建的,同一个函数被多次调用,会产生多个执行上下文;eval函数执行上下文)
(5)setTimeout、Promise、Async/Await 的区别
(1)SetTimeOut的回调函数放到宏任务队列中,等到执行栈清空以后执行。
(2)promise本身是同步的立即执行函数。当执行resolve或reject的时候,此时是异步操作。Promise.then里的回调函数会放到相应宏任务的微任务队列里,等宏任务里面的同步代码执行完再执行。
(3)Async函数返回一个promise对象,当函数执行的时候,一旦遇到await就会先返回。等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了ASYNC函数体。
四、跨域
(1)为什么会出现跨域问题?
因为浏览器的同源策略的限制。
(2)同源策略
同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器正常的功能都有可能受影响。可以说web是构建在同源策略基础之上的。浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的JS脚本和另一个域的内容进行交互。所谓同源就是指两个页面具有相同的协议,域名,端口号。
(3)为什么需要同源策略(跨域限制)
(1)如果没有XMLHttpRequest同源策略,那么黑客可以进行CSRF攻击(登录正常网站之后,本地产生了cookie,正常网站没有登出的情况下访问了危险网站,危险网站伪装成用户向安全网站发送请求,cookie会自动附加到请求头,这样就会被安全网站认为是用户的操作。也就是说攻击者盗用了你的身份,以你的身份发送恶意请求。----->处理方法:验证码;对来源进行验证;使用token)。
(2)如果没有DOM同源策略,那么不同域的iframe之间可以相互访问。
(4)什么是跨域
当2个URL中的协议,域名,端口号中任意一个不相同时,就算作不同域。不同域之间相互请求资源,就是跨域。
跨域的请求能发出去,服务端能收到请求并返回结果,只是结果被浏览器给拦截了。
(5)怎么允许跨域(跨域解决办法)
A、JSONP
在页面上,js脚本,css样式文件,图片这三种资源是可以与页面本身不同源的。jsonp就利用了script标签进行跨域取得数据。
JSONP允许用户传递一个callback参数给服务器端,然后服务器端返回数据时会将这个callback参数作为函数名来包裹住JSON数据。这样客户端就可以随意定制自己的函数来自动处理返回的数据了。
JSONP只能解决get请求,不能解决post请求。
<script>
function callback(data){
console.log(data);
}
</script>
<script src="http://localhost:80/?callback=callback"></script>
使用ajax实现跨域:
<script src="http://code.jquery.com/jquery-latest.js"></script>
$.ajax({
url:'http://localhost:80/?callback=callback',
method:'get',
dataType:'jsonp', //=> 执行jsonp请求
success:(res) => {
console.log(res);
}
})
function callback(data){
console.log(data);
}
B、 CORS跨域资源共享:
浏览器会自动进行CORS通信,实现CORS通信的关键是后端。服务端设置Access-Control-Allow-Origin就可以开启CORS。该属性表示哪些域名跨域访问资源。
主要设置以下几个属性:
Access-Control-Allow-Origin//允许跨域的域名
Access-Control-Allow-Headers//允许的header类型
Access-Control-Allow-Methods//跨域允许的请求方式
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
简单请求
只要同时满足以下两大条件,就属于简单请求。
对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。
非简单请求(预检请求)
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为预检请求(preflight),该请求时option方法得到,通过该请求来指导服务端是否允许跨域请求。
C、Nginx反向代理
通过nginx配置一个代理服务器将客户机请求转发给内部网络上的目标服务器;并将服务器上返回的结果返回给客户端。
D、webpack (在vue.config.js文件中)中 配置webpack-dev-server
devServer: {
proxy: {
'/api': {
target: "http://39.98.123.211",
changeOrigin: true, //是否跨域
},
},
},
五、es6的新特性
增加了块级作用域,关键字let,常量const
解构赋值
模块(将JS代码分割成不同功能的小块进行模块化)
模板字符串(使用反引号创建字符串,字符串里面可以包含用${}包裹的变量)
箭头函数;扩展运算符(…[1,2,3]->1,2,3)
Class类
Promise异步对象
For…of循环
Map,set,weakmap,weakset
新增的数组方法:Array.from()将set类变为数组;Array.find()找出第一个符合条件的数组成员
字符串方法:string.includes(value)->是否包含某个值,是返回true
symbol数据类型
Proxy代理
六、Symbol(es6新增)
symbol不能使用new,因为symbol是原始数据类型,不是对象。
每一个symbol的值都是不相等的,是唯一的。主要是为了防止属性名冲突。
symbol不能和其他的数据类型比较,比较的话会报错
如果symbol和symbol比较,值为false。
七、let var const的区别
(1)var定义的变量,没有块的概念,可以跨块访问(块{}),不能跨函数访问
Let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问
Const用来定义常量,使用时必须初始化(必须赋值),只能在块作用域里面访问,且不能修改。
(2)var可以先使用后声明,因为var有变量提升;let必须先声明后使用
(3)Var允许相同作用域内重复定义,let和const不允许重复定义
(4)Var声明的全局变量会自动成为window属性,但是let和const不会
(5)TDZ(临时性死区):凡是在使用let声明的变量之前,这些变量都是不可使用的。
if (true) {
// TDZ开始
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError
//在没有声明变量之前tmp都是错误的
let tmp; // TDZ结束
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); // 123
}
25.考查作用域的题(var)
八、介绍下set,weakset,map,weakmap的区别
九、原型和原型链
10、了解JS的继承吗?ES5继承与ES6继承的区别
11、js如何定义一个类并实现继承;要求不能使用es6的语法(手写一个寄生组合式继承)
12、讲一讲Promise;语法
13、如何中止promise链式调用
14、promise发生错误,怎么捕捉
15、手写promise;手写promise.all;手写promise.any;手写promise.finally
16、考查作用域的题(var)
17、什么是事件委托,为什么在父元素上绑事件,子元素上点击可能触发。
18、js数据类型,怎样判断数据类型;基本数据类型和引用数据类型的区别;怎么判断一个数组是不是Array类型(insatnceof,object.prototype.toString.call);堆和栈是什么
19、undefined和null的区别
20、箭头函数与普通函数的区别是什么?
21、构造函数可以使用new生成实例,那么箭头函数可以吗?为什么?
22、循环(遍历)的方法,各个方法的区别
23、除了for…of还有哪些方法可以遍历字符串,数组中reduce方法有哪些参数,是怎样使用的
24、怎么让对象的一个属性不可被改变
71.对浏览器的理解
72.对浏览器内核的理解
73.浏览器所用的内核
74.浏览器内核比较
75.浏览器的渲染原理
25、flat数组扁平化
26、什么是闭包;闭包的作用;使用闭包实现每秒钟打印数组中的一个数组[5,4,3,2,1]
27、输出什么/函数每秒依次输出
(1)怎么修改可以让上面代码从1到5秒依次输出0-4,写出你能想到的所有方法
28、手写:ajax请求,解释状态码含义;
29、手写ajax请求,用promise封装
30、== 和===的区别
65.JS隐式转换
66.JS的垃圾回收机制
69.Cookie,localstorage,sessionstorage的区别
67.JS的设计模式
为什么JS单例模式不会产生互锁现象(异步)
52.考查原型和函数的打印题
53.手写代码:浏览器打开了一个页面,在控制台执行脚本,获取页面TagName种类。
70.输出以下代码的执行结果并解释为什么?(连续赋值的坑)
56.操作DOM的方法
57.求两个数组的并集和交集
58.数组的常用方法
59.深拷贝和浅拷贝
60.手写instanceOf
61.数组去重
62.New的实现(new实现、new的过程,new干了什么)
63.函数柯里化
64.二分查找
如何让if(a1&&a2&&a==3)条件成立?
36.两个数组合并成一个数组。请把两个数组 [‘A1’, ‘A2’, ‘B1’, ‘B2’, ‘C1’, ‘C2’, ‘D1’, ‘D2’] 和 [‘A’, ‘B’, ‘C’, ‘D’],合并为 [‘A1’, ‘A2’, ‘A’, ‘B1’, ‘B2’, ‘B’, ‘C1’, ‘C2’, ‘C’, ‘D1’, ‘D2’, ‘D’]。
0.1+0.2 !== 0.3,如何让它等于
8.Console.log(typeof typeof typeof null)
Let mySet = new Set();
mySet.add({});//mySetx.size?? 1
mySet.add({});//mySetx.size?? 2