好程序员 web 前端培训分享 JS 面试题总结一,准备参加面试的小伙伴们一起看一看吧,希望本篇文章能够对从事 web 前端工作的小伙伴们有所帮助。
一、说说你对闭包的认识
( 一 ) 什么是闭包
一句话解释:
能够读取其他函数内部变量的函数。
稍全面的回答:
在js 中变量的作用域属于函数作用域 , 在函数执行完后 , 作用域就会被清理 , 内存也会随之被回收 , 但是由于闭包函数是建立在函数内部的子函数 , 由于其可访问上级作用域 , 即使上级函数执行完 , 作用域也不会随之销毁 , 这时的子函数 ( 也就是闭包 ), 便拥有了访问上级作用域中变量的权限 , 即使上级函数执行完后作用域内的值也不会被销毁。
这里涉及到对函数作用域的认识: js 变量分为全局变量和局部变量 ; 函数内部可以直接读取全局变量 , 而在函数外部自然无法读取函数内的局部变量
( 二 ) 闭包解决了什么问题
1. 可以读取函数内部的变量
2. 让这些变量的值始终保持在内存中。不会在函数调用后被清除
可以通过下面的代码来帮助理解上面所说的:
function addCounter() {
let counter = 0
const myFunction = function () {
counter = counter + 1
return counter
}
return myFunction
}
const increment = addCounter()
const c1 = increment()
const c2 = increment()
const c3 = increment()
console.log('increment:', c1, c2, c3);
// increment: 1 2 3
在这段代码中increment 实际上就是闭包函数 myFunction, 它一共运行了三次,第一次的值是 1 ,第二次的值是 2 ,第三次的值是 3 。这证明了,函数 addCounter 中的局部变量 counter 一直保存在内存中,并没有在 addCounter 调用后被自动清除。
( 三 ) 闭包的应用场景
在开发中, 其实我们随处可见闭包的身影 , 大部分前端 JavaScript 代码都是“事件驱动”的 , 即一个事件绑定的回调方法 ; 发送 ajax 请求成功 | 失败的回调 ;setTimeout 的延时回调 ; 或者一个函数内部返回另一个匿名函数 , 这些都是闭包的应用。
下面是具体应用的栗子:
1. 老掉牙的取正确值问题
for (var i = 0; i < 10; i++) {
setTimeout(function () {
console.log(i) //10 个 10
}, 1000)
}
怎么取到每一次循环的正确值呢? 闭包这样用 :
for (var i = 0; i < 10; i++) {
((j) => {
setTimeout(function () {
console.log(j) //1-10
}, 1000)
})(i)
}
声明了10 个自执行函数,保存当时的值到内部
2. 使用闭包模拟私有变量
私有变量在java 里使用 private 声明就可以了 , 但是在 js 中还没有,但是我们可以使用闭包模拟实现。
var counter = (function () {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val
}
return {
increment: function () {
changeBy(1)
},
decrement: function () {
changeBy(-1)
},
value: function () {
return privateCounter
}
}
})();
counter.value() //0
counter.increment() //1
counter.increment() //2
counter.decrement() //1
匿名函数已经定义就立即执行, 创建出一个词法环境包含 counter.increment 、 counter.decrement 、 counter.value 三个方法 , 还包含了两个私有项 :privateCounter 变量和 changeBy 函数。这两个私有项无法在匿名函数外部直接访问,必须通过匿名包装器返回的对象的三个公共函数访问。
( 四 ) 闭包的缺点
1. 由于闭包会是的函数中的变量都被保存到内存中 , 滥用闭包很容易造成内存消耗过大 , 导致网页性能问题。解决方法是在退出函数之前,将不再使用的局部变量全部删除。
2. 闭包可以使得函数内部的值可以在函数外部进行修改。所有,如果你把父函数当作对象 (object) 使用,把闭包当作它的公用方法 (Public Method) ,把内部变量当作它的私有属性 (private value) ,这时一定要小心,不要随便改变父函数内部变量的值。
二、跨域问题有哪些处理方式
跨域解决方案
1. 通过 jsonp 跨域
2. 跨域资源共享 (CORS)
3. nodejs 中间件代理跨域
4. nginx 反向代理中设置 proxy_cookie_domain
Ⅰ . 通过 jsonp 跨域
通常为了减轻web 服务器的负载,我们把 js 、 css , img 等静态资源分离到另一台 独立域名的服务器上,在 html 页面中再通过相应的标签从不同域名下加载静态资源,而被浏览器允许,基于此原理,我们可以通过动态创建 script ,再请求一个带参网址实现跨域通信。
1. 原生实现
<script>
var script = document.createElement('script');
script.type = 'text/javascript';
// 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
script.src = '
document.head.appendChild(script);
// 回调执行函数
function jsonCallback(res) {
alert(JSON.stringify(res));
}
</script>
服务器端返回如下( 返回即执行全局函数 )
jsonCallback({"status": 0, "user": "admin"})
2. jquery 方式实现
$.ajax({
url: '
type: 'get',
dataType: 'jsonp', // 请求方式为 jsonp
jsonpCallback: "handleCallback", // 自定义回调函数名
data: {}
});
Ⅱ . 跨域资源共享 (CORS)
CORS 是一个 W3C 标准,全称是 " 跨域资源共享 "(Cross-origin resource sharing) 跨域资源共享 CORS 详解。看名字就知道这是处理跨域问题的标准做法。 CORS 有两种请求,简单请求和非简单请求。
· 简单请求
只要同时满足以下两大条件, 就属于简单请求 :
1. 请求方法是以下三种方法之一 :
· HEAD
· GET
· POST
2. HTTP 请求头的信息不超出以下几种字段:
· Accept
· Accept-Language
· Content-Language
· Last-Event-ID
· Content-Type :只限于三个值 application/x-www-form-urlencoded 、 multipart/form-data 、 text/plain
如果是简单请求, 后端处理即可 , 前端什么也不用干 ; 这里注意的是如果前端要带 cookie, 前端也需要单独设置
· 原生 ajax ( 前端 )
var xhr = new XMLHttpRequest();
// 前端设置是否带 cookie
xhr.withCredentials = true;
...
· jquery ( 前端 )
$.ajax({
...
xhrFields: {
withCredentials: true // 前端设置是否带 cookie
},
crossDomain: true, // 会让请求头中包含跨域的额外信息,但不会含 cookie
...
});
· vue 中使用 axios ( 前端 )
axios.defaults.withCredentials = true
· 后端 node
可以借助koa2-cors 快速实现
const path = require('path')
const Koa = require('koa')
const koaStatic = require('koa-static')
const bodyParser = require('koa-bodyparser')
const router = require('./router')
const cors = require('koa2-cors')
const app = new Koa()
const port = 9871
...
// 处理 cors
app.use(cors({
origin: function (ctx) {
return '
},
credentials: true,
allowMethods: ['GET', 'POST', 'DELETE'],
allowHeaders: ['t', 'Content-Type']
}))
// 路由
app.use(router.routes()).use(router.allowedMethods())
// 监听端口
...
Ⅲ .nodejs 中间件代理跨域
跨域原理: 同源策略是浏览器的安全策略 , 不是 HTTP 协议的一部分。服务器端调用 HTTP 接口只是使用 HTTP 协议, 不会执行 js 脚本 , 不需要检验同源策略 , 也就不存在跨域问题。
实现思路:通过起一个代理服务器, 实现数据的转发,也可以通过设置cookieDomainRewrite 参数修改响应头 cookie 中域名 , 实现当前域下 cookie 的写入
· 在 vue 框架下实现跨域
利用node + webpack + webpack-dev-server 代理接口跨域。在开发环境下,由于 vue 渲染服务和接口代理服务都是 webpack-dev-server 同一个,所以页面与代理接口之间不再跨域,无须设置 headers 跨域信息了。后台可以不做任何处理。
webpack.config.js 部分配置
module.exports = {
entry: {},
module: {},
...
devServer: {
historyApiFallback: true,
proxy: [{
context: '/login',
target: ' // 代理跨域目标接口
changeOrigin: true,
secure: false, // 当代理某些 https 服务报错时用
cookieDomainRewrite: ' // 可以为 false ,表示不修改
}],
noInfo: true
}
}
Ⅳ .nginx 反向代理中设置
和使用node 中间件跨域原理相似。前端和后端都不需要写额外的代码来处理, 只需要配置一下 Ngnix
server{
# 监听 9099 端口
listen 9099;
# 域名是 localhost
server_name localhost;
# 凡是 localhost:9099/api 这个样子的,都转发到真正的服务端地址
location ^~ /api {
proxy_pass ;
}
}
对于跨域还有挺多方式可以实现, 这里就不一一列举了。
三、for...in 和 for...of 的区别
1. for...of 是 ES6 新引入的特性,修复了 ES5 引入的 for...in 的不足
2. for...in 循环出的是 key , for...of 循环出的是 value
3. for...of 不能循环普通的对象,需要通过和 Object.keys() 搭配使用
4. 推荐在循环对象属性的时候,使用 for...in, 在遍历数组的时候的时候使用 for...of
四、new 一个对象,这个过程中发生了什么
var obj = new Object("name","sansan");
1. 创建一个新对象,如: var obj = {};
2. 新对象的 _proto_ 属性指向构造函数的原型对象。
3. 将构造函数的作用域赋值给新对象。 ( 也所以 this 对象指向新对象 )
4. 执行构造函数内部的代码,将属性添加给 obj 中的 this 对象。
5. 返回新对象 obj 。
五、js 的防抖和节流是什么
· 防抖 : 在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则重新计时。
使用场景:
1. 给按钮加函数防抖防止表单多次提交。
2. 对于输入框连续输入进行 AJAX 验证时,用函数防抖能有效减少请求次数。
简单的防抖(debounce) 代码 :
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, 2000));
· 节流 : 就是指连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率。
function throttle(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, 2000));
区别:
函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。 比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/69913864/viewspace-2706481/,如需转载,请注明出处,否则将追究法律责任。