目录
面试官:请你谈谈JS的this指向问题
面试官:说一说call apply bind的作用和区别?
面试官:请你谈谈对事件委托的理解
面试官:说一说promise是什么与使用方法?
面试官:说一说跨域是什么?如何解决跨域问题?
面试官:说一说JavaScript有几种方法判断变量的类型?
面试官:说一说JS实现异步的方法?
面试官:说一说数组去重都有哪些方法?
面试官:说一说es6中箭头函数?
面试官:说一说JS变量提升?
每天10道题,100天后,搞定所有前端面试的高频知识点,加油!!!,在看文章的同时,希望不要直接看答案,先思考一下自己会不会,如果会,自己的答案是什么?想过之后再与答案比对,是不是会更好一点,当然如果你有比我更好的答案,欢迎评论区留言,一起探讨技术 之美。
面试官:请你谈谈JS的this指向问题
我:呃~,我们知道this中有个准则就是谁调用就指向谁,这句话潜移默化的会导致我们出现一些误区,现将可能会出错的情况总结如下,并付出代码:
1)我们要知道在全局的时候去得到这个this的话,this都会指向windows,因为我们在全局的情况下使用的东西都会被挂载到window上。
<script>
console.log(this) // 指向window
function a(){
console.log(this)
}
a() // 相当于 window.a(),指向的依旧是 window
</script>
2)我要知道this的指向是会指向上一个调用者的,代码如下:
看完了代码,我们知道虽然本质上是由于a才调用了d函数,但是中间还是有一层是c调用了d函数,所以this指向上一级会有一个就近原则的,这点很重要!!!
<script>
var a = {
b:10,
c:{
b:12,
d:function(){
console.log(this)
}
}
}
a.c.d() // {b: 12, d: ƒ}
</script>
3)我们要知道箭头函数是没有作用域的,也就是说是没有自己的this,它的this永远向的是上一级的this,下面给出一道某大厂的面试题,大家可以猜一下最后的打印结果是什么?
假设你已经仔细的看完了这道面试题,相信你心中已经有了答案是66了,为什么呢?,要知道箭头函数是没有自己的this的,所以需要其去上一级去寻找this,而上一级处于全局作用域,所以打印的便是全局已经挂载的id数66。
<script>
var id = 66
function a(){
setTimeout(()=>{
console.log(this.id)
},500)
}
a({id:22}) // 猜猜结果是什么?
</script>
那我们如何改变this的指向,去控制this指向我们想要的结果呢?给出如下三种方法:
<script>
var id = 66
function a(){
setTimeout(()=>{
console.log(this.id || this)
},500)
}
// call => {} 改变之后并执行一次
a.call({id:22}) // 打印22
// apply => [] 改变之后并执行一次
a.apply([12]) // 打印 [12]
// bind() 不调用,只改变this指向
a.bind(a(id=32)) // 32
</script>
面试官:说一说call apply bind的作用和区别?
我:呃~,好的,总结如下:
call apply bind三个方法都可以用来改变函数的this指向,具体区别如下:
1)fn.call (newThis,params) call函数的第一个参数是this的新指向,后面依次传入函数fn要用到的参数。会立即执行fn函数。
2)fn.apply (newThis,paramsArr) apply函数的第一个参数是this的新指向,第二个参数是fn要用到的参数数组,会立即执行fn函数。
3)fn.bind (newThis,params) bind函数的第一个参数是this的新指向,后面的参数可以直接传递,也可以按数组的形式传入。 不会立即执行fn函数,且只能改变一次fn函数的指向,后续再用bind更改无效。返回的是已经更改this指向的新fn
面试官:请你谈谈对事件委托的理解
我:呃~,好的,事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。说白了就是将还没有出现的事件,挂载到已经出现的事件上。整出代码如下:
<body>
<ul id="ul">
<li>0</li>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<button id="btn">点我添加一个li</button>
<script>
// 事件委托
let ul = document.getElementById("ul")
ul.addEventListener('click',(event)=>{
console.log(event)
event = event || window.event
let target = event.target
if(target.nodeName == 'LI'){
alert(target.innerHTML)
}
})
let btn = document.getElementById('btn')
btn.addEventListener('click',()=>{
let li = document.createElement('li')
li.textContent = ul.children.length
ul.appendChild(li)
})
</script>
</body>
面试官:说一说promise是什么与使用方法?
我:呃~,好的,Promise是ES6提供的一个构造函数,可以使用Promise构造函数new一个实例,Promise构造函数接收一个函数作为参数,这个函数有两个参数,分别是两个函数 resolve和reject,resolve将Promise的状态由等待变为成功,将异步操作的结果作为参数传递过去;reject则将状态由等待转变为失败,在异步操作失败时调用,将异步操作报出的错误作为参数传递过去。实例创建完成后,可以使用then方法分别指定成功或失败的回调函数,也可以使用catch捕获失败,then和catch最终返回的也是一个Promise,所以可以链式调用。
Promise的作用:
Promise是异步微任务,解决了异步多层嵌套回调的问题,让代码的可读性更高,更容易维护 Promise使用
Promise的特点:
1)对象的状态不受外界影响
2)一旦状态改变,就不会再变,任何时候都可以得到这个结果
3)resolve 方法的参数是then中回调函数的参数,reject 方法中的参数是catch中的参数
4)then 方法和 catch方法 只要不报错,返回的都是一个fullfilled状态的promise
应用场景:
解决地狱回调问题
具体使用方法,参考我之前的文章:一文搞懂JS中的Promise
面试官:说一说跨域是什么?如何解决跨域问题?
我:呃,好的,总结内容如下:
什么是跨域:
当前页面中的某个接口请求的地址和当前页面的地址如果协议、域名、端口其中有一项不同,就说该接口跨域了。
跨域限制的原因:浏览器为了保证网页的安全,出的同源协议策略。
跨域解决方案:
cors:
目前最常用的一种解决办法,通过设置后端允许跨域实现。
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader("Access-Control-Allow-Methods", "GET, PUT, OPTIONS, POST");node中间件、nginx反向代理:
跨域限制的时候浏览器不能跨域访问服务器,node中间件和nginx反向代理,都是让请求发给代理服务器,静态页面面和代理服务器是同源的,然后代理服务器再向后端服务器发请求,服务器和服务器之间不存在同源限制。
JSONP:利用的原理是script标签可以跨域请求资源,将回调函数作为参数拼接在url中。后端收到请求,调用该回调函数,并将数据作为参数返回去,注意设置响应头返回文档类型,应该设置成javascript。
面试官:说一说JavaScript有几种方法判断变量的类型?
我:呃,好的,JavaScript有4种方法判断变量的类型,总结如下:
typeof:
常用于判断基本数据类型,对于引用数据类型除了function返回’function‘,其余全部返回’object'。
instanceof:
主要用于区分引用数据类型,检测方法是检测的类型在当前实例的原型链上,用其检测出来的结果都是true
Object.prototype.toString.call()(对象原型链判断方法):
适用于所有类型的判断检测,检测方法是Object.prototype.toString.call(数据) 返回的是该数据类型的字符串。
constructor(用于引用数据类型):
用于检测引用数据类型,检测方法是获取实例的构造函数判断和某个类是否相同,如果相同就说明该数据是符合那个数据类型的,这种方法不会把原型链上的其他类也加入进来,避免了原型链的干扰。
面试官:说一说JS实现异步的方法?
我:呃~,好的,所有异步任务都是在同步任务执行结束之后,从任务队列中依次取出执行。常见的实现异步的方式如下:
回调函数、事件监听、setTimeout(定时器)、Promise、async/await,generator生成器
面试官:说一说数组去重都有哪些方法?
我:呃~,数组去重的方法有很多,举几个例子并简单的加以说明,如下:
利用对象属性key排除重复项:
遍历数组,每次判断对象中是否存在该属性,不存在就存储在新数组中,并且把数组元素作为key,设置一个值,存储在对象中,最后返回新数组。
利用Set类型数据无重复项:
new 一个 Set,参数为需要去重的数组,Set 会自动删除重复的元素,再将 Set 转为数组返回。
filter+indexof 去重:
利用 Array 自带的 filter 方法,返回 arr.indexOf(num) 等于 index 的num。
reduce +includes去重:
利用reduce遍历和传入一个空数组作为去重后的新数组,然后内部判断新数组中是否存在当前遍历的元素,不存在就插入到新数组中。
面试官:说一说es6中箭头函数?
我:呃~,好的,箭头函数相当于匿名函数,简化了函数定义。箭头函数有两种写法,当函数体是单条语句的时候可以省略{}和return。另一种是包含多条语句,不可以省略{}和return。 箭头函数最大的特点就是没有this,所以this是从外部获取,就是继承外部的执行上下文中的this,由于没有this关键字所以箭头函数也不能作为构造函数。
箭头函数比普通函数的定义写法更加简洁明了和快捷。但是两者又有区别:箭头函数没有原型prototype和super,所以无法创建this,其this是通过继承外部函数环境中的变量获取的,所以call、bind、apply都无法改变其this的指向;在找不到最外层的普通函数时,其this一般指向window;箭头函数不能使用new;箭头函数没有arguments;也不能作为generator函数,不能使用yield命令;箭头函数不能用于对象域和回调函数动态this中,一般用在内部没有this引用。
面试官:说一说JS变量提升?
我:呃~,好的,变量提升是指JS的变量和函数声明会在代码编译期,提升到代码的最前面。 变量提升成立的前提是使用Var关键字进行声明的变量,并且变量提升的时候只有声明被提升,赋值并不会被提升,同时函数的声明提升会比变量的提升优先。 变量提升的结果,可以在变量初始化之前访问该变量,返回的是undefined。在函数声明前可以调用该函数。
使用let和const声明的变量是创建提升,形成暂时性死区,在初始化之前访问let和const创建的变量会报错。