1、this指向问题
(1)全局作用域下 this指向window
(2)箭头函数中的this指向上下文
(3)构造函数中的this指向调用者
(4)当某个函数为对象的⼀个属性时,在这个函数内部this指向这个对象
(5)当⼀个元素被绑定事件处理函数时,this指向被点击的这个元素
(6)严格模式下全局作用域中this指向undefined
2、改变this指向的三个方法
call apply bind
相同点:
都是用来改变函数的this对象的指向的,第一个参数都是this要指向的对象,都可以利用后续参数传参
不同点:
call/apply 可以直接执行该函数,而 bind 不会立刻执行。
bind 与前两者作用类似,都是改变函数内部的 this 指向,区别在于 bind 会创建一个新的函数实例,每次调用该实例时,都会在被绑定的环境中运行。简单来说就是,bind方法返回一个新函数,以后调用了才会执行,但apply、call会立即执行。
call和apply的区别在于,call可以传递多个参数,而apply只能传递两个参数,第二个参数以数组的形式进行接收。
call和apply的第一个参数如果是null和underfined this指向window。
3、什么是闭包,闭包的优缺点?
闭包就是能够读取其他函数内部变量的函数,在本质上,闭包是将函数内部和函数外部连接起来的桥梁。只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数”。
闭包的用途:
1.可访问函数内部的变量
2.在内存中维持一个变量。防止函数内部的变量执行完成后,被销毁,使其一直保存在内存中。
闭包的缺点:
滥用闭包会造成内存泄露
因为闭包中引用到的包裹函数中定义的变量永远不会被释放
所以我们不再改变量或函数的时候应该及时释放这个闭包函数。(使该函数等于null)
//1. 用最外层的函数来包裹内层函数和受保护的变量
function parents(){
var total=1000;
//2. 将内部的函数对象return到外部
return function(num){
total-=num;
document.write(`花了${num}元,还剩下${total}元<br>`);
}
}
//3. 调用外部函数来获得内层函数,并保存在一个外部变量中
var child=parents();
console.log(child);
child(100);
child(200);
4、如何解决内存泄漏?
内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
通俗点就是指由于疏忽或者错误造成程序未能释放已经不再使用的内存,不再用到的内存却没有及时释放,从而造成内存上的浪费。
造成内存泄漏的几种情况:
- 定义全局变量
解决方式:使用 var let const 来定义变量。或者在js文件开头添加 ‘use strict',开启严格模式。
2.闭包 在使用闭包的时候,就会造成严重的内存泄漏,因为闭包中的局部变量,会一直保存在内存中。
解决方式:使该函数等于null。
3.定时器
定时器setInterval或者setTimeout在不需要使用的时候,没有被clear,导致定时器的回调函数及其内部依赖的变量都不能被回收,这就会造成内存泄漏。
解决方式:当不需要interval或者timeout的时候,调用clearInterval或者clearTimeout
4.事件监听 DOM.addEventListener("click", callback)
垃圾回收机制不好判断该事件是否需要被解除,导致 callback 不能被释放,此时需要手动解除绑定:DOM.removeEventListener(callback)
5.元素引用没有清理
var a = document.getElementById('id');
document.body.removeChild(a);
// 不能回收,因为存在变量a对它的引用。虽然我们用removeChild移除了,但是还在对象里保存着#的引用,即DOM元素还在内存里面。
解决方法: a = null;
6.console
控制台日志记录对总体内存内置文件的影响,也是个重大的问题,同时也是容易被忽略的。记录错误的对象,可以将大量的数据保留在内存中。
传递给console.log的对象是不能被垃圾回收,所以没有去掉console.log可能会存在内存泄漏
5、如何解决跨域?
首先,在不同源的情况下才会发生跨域,不同源是指协议、域名、端口不同,解决跨域的方案有以下几种:
1.JSONP跨域
jsonp的原理就是利用<script>标签没有跨域限制,通过<script>标签src属性,发送带有callback参数的GET请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据。
jsonp的缺点:只能发送get一种请求。
2.跨域资源共享(CORS)
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。
它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
CORS需要浏览器和服务器同时支持。
3.nginx代理跨域
nginx代理跨域,实质和CORS跨域原理一样,通过配置文件设置请求响应头Access-Control-Allow-Origin…等字段。
6、深拷贝和浅拷贝
浅拷贝:
Object.assign()
函数库lodash的 _.clone 方法
es6的展开运算符 …
Array.prototype.concat()
Array.prototype.slice()
深拷贝:
JSON.parse(JSON.stringify())
函数库lodash的 _.cloneDeep 方法
jQuery.extend()方法
手写递归方法
7、webpack是怎么打包的,babel又是什么?
Webpack:把所有依赖打包成一个 bundle.js文件,通过代码分割成单元片段并按需加载。Webpack是以公共JS的形式来书写脚本的,但对AMD/CMD的支持也很全面,方便旧项目进行代码迁移。
把项目当做一个整体,通过一个给定的主文件(如:index.js),Webpack将从这个文件开始找到项目的所有依赖文件,使用loaders处理它们,最后打包为一个(或多个)浏览器可识别的JavaScript文件。
babel将es6、es7、es8等语法转换成浏览器可识别的es5或es3语法。
8、vue路由参数的传递和接收的方式?
router-link 标签跳转
this.$router.push()
this.$router.replace()
this.$router.go(n):(0:当前页,-1上一页,+1下一页,n代表整数)
一、声明式传参:
//路由参数配置(显示)
const router = new VueRouter({
routes: [{
path: '/about/:id',
component: User
}]
})
//声明式导航使用
<router-link to="/about/12">跳转</router-link>
//路由参数配置
const router = new VueRouter({
routes: [{
name: 'user1',
path: '/about/:id',
component: User
}]
})
//声明式导航使用(不显示)
<router-link :to="{ name: 'user1', params: { id: 123 } }">跳转</router-link>
二、编程式传参
//传参
this.$router.push({
path: '/deviceInfo',
query: { deviceId: '202101075343' }
});
//接收参数
this.$route.query.deviceId
//传参
router.push({
name: 'device',
params: { deviceId: '202101075343' }
})
//接收参数
this.$route.params.deviceId
9、watch、methods 和 computed 的区别?
watch 为了监听某个响应数据的变化。computed 是自动监听依赖值的变化,从而动态返回内容,主要目的是简化模板内的复杂运算。所以区别来源于用法,只是需要动态值,那就用 computed ;需要知道值的改变后执行业务逻辑,才用 watch。
methods是一个方法,它可以接受参数,而computed 不能,computed 是可以缓存的,methods 不会。computed 可以依赖其他 computed,甚至是其他组件的 data。
10、去重
方法1、双重for循环
var arr = [1, 2, 3,4 ,5,6, 4, 3, 8, 1]
// 数组去重:
// 方法1: 双重for 循环
function newArrFn (arr) {
// 创建一个新的空数组
let newArr = []
for(let i = 0;i<arr.length;i++){
// 设置一个开关,如果是true,就存进去,不是就不存
let flag = true
for(let j = 0;j<newArr.length;j++){
// 原数组和新数组作比较,如果一致,开关变为 false
arr[i] === newArr[j] ? flag = false : flag
};
flag ? newArr.push(arr[i]) : newArr
};
return newArr
}
console.log(newArrFn(arr));
方法二、Set
var arr = [1, 2, 3,4 ,5,6, 4, 3, 8, 1]
// 数组去重:
// 方法4: set
function newArrFn (arr) {
// .new Set方法,返回是一个类数组,需要结合 ...运算符,转成真实数组
return ([...new Set(arr)])
}
console.log(newArrFn(arr));
方法三、set + Array.from
var arr = [1, 2, 3,4 ,5,6, 4, 3, 8, 1]
// 数组去重:
// 方法5: set + Array.from
function newArrFn (arr) {
// .new Set方法,返回是一个类数组,需要结合 Array.from ,转成真实数组
return (Array.from(new Set(arr)) )
}
console.log(newArrFn(arr));
方法四、filter + indexOf
var arr = [1, 2, 3,4 ,5,6, 4, 3, 8, 1]
// 数组去重:
// 方法6 :filter + findIndex
function newArrFn (arr) {
// 利用indexOf检测元素在数组中第一次出现的位置是否和元素现在的位置相等,
// 如果相等,说明数组中没有重复的
return Array.prototype.filter.call(arr, function (item, index) {
return arr.indexOf(item) === index
})
}
console.log(newArrFn(arr));
方法五、Map
var arr = [1, 2, 3,4 ,5,6, 4, 3, 8, 1]
// 数组去重:
// 方法11 :Map
function newArrFn (arr) {
let newArr = []
let map = new Map()
for(let i = 0;i<arr.length;i++){
// 如果 map里面不包含,就设置进去
if (!map.has(arr[i])) {
map.set(arr[i], true)
newArr.push(arr[i])
}
};
return newArr
}
console.log(newArrFn(arr));
方法六、reduce
var arr = [1, 2, 3,4 ,5,6, 4, 3, 8, 1]
// 数组去重:
// 方法12 :reduce
function newArrFn (arr) {
let newArr = []
return arr.reduce((prev, next,index, arr) => {
// 如果包含,就返回原数据,不包含,就把新数据追加进去
return newArr.includes(next) ? newArr : newArr.push(next)
}, 0)
}
console.log(newArrFn(arr));