文章目录
1. 闭包
- 什么是闭包?
方法里面返回一个方法 或 沟通内外部方法的桥梁- 闭包存在的意义?
延长变量的生命周期、创建私有环境- 特点
闭包会常驻内存,不能大量使用且需要手动回收- 为什么vue里面的数据存储是 data () {return {} }的形式?
- 是一个闭包的设计
- 因为单页面的特点,所以每一个组件都通过闭包的设计方法来生成一个个私有的作用域来存储数据,使得组件间的数据互相不受影响
// 举个栗子
// 例1:
function outer(){
let a1 = 1;
let a2 = 2;
return function inner(){ // 只能在inner方法里面获取到外部outer里面的数据,不能在fn1里面获取outer里面的数据a1或a2
return a1;
}
}
function fn1() {
let getInnerData = outer();
console.log(getInnerData); // 获取不到inner里面的值
}
// 特点: 闭包里面返回什么数据就存储什么数据到内存中且只能在当前方法中进行调用
// 例2:
let counter = function(){
let num = 0;
function changeBy(val) {
num += val;
}
return { // 定义一些闭包的操作方法
add: function() {
changeBy(1);
},
reduce: function() {
changeBy(-1);
},
check: function() {
return num;
},
}
}
let test1 = counter();
let test2 = counter();
test1.add();
test2.add();
test1.add();
console.log(test1.check()); // 2
console.log(test2.check()); // 1
// 特点: 虽然是同一个闭包创建的两个变量,但是却是两个不同的私有作用域进行的数据的存储,两者的数据发生变化不会影响到对方
2. 作用域链
- 查找规则:
类似于原型链的查找方式,首先会在当前作用域里面进行查找,如果没有就会到上一级的作用域链里面进行查找
let name = 'xiaoming';
function fn1() {
let name = 'xiaoma';
function fn2() {
let name = 'xiaozhang';
function fn3() {
let name = 'xiaohua';
console.log(name);
}
fn3()
}
fn2()
}
fn1()
3. js防抖
- 使用场景:输入框…
- 目的:将单身用户因为手速的问题造成的多次操作只取最后一次操作结果
<input placehodler = '请输入电话号码'>
----
// 1.获取到input元素
let tel = document.querySelector('input')
// 2.封装函数防抖的方法
function fn1(fn, wait) { // 参数为(方法, 等待时间)
let timeOut = null; // 定义一个定时器为null
return args => { // 闭包的设计
if (timeOut) cleartTimeout(timeOut); // 清除上一次存在的定时器
timeOut = setTimeout(fn, wait); // 在最后的一次操作之后创建一个定时器
}
}
// 3.给input绑定一个监听事件
tel.addEventListener('input', fn1(demo, 200)) // 调用防抖的函数方法并传入模拟的数据请求方法和等待的时间
4. 节流
- 使用场景: 表单提交等高频次的事件监听
- 目的:将用户手速造成的多次事件执行(如touchmove)通过时间间隔来降低事件触发的频次 eg: 次/3s
<div class="box"> </div>
----
// 获取到input元素
let box1 = document.querySelector('.box')
// 没有节流的情况
box1.addEventListener('touchmove', (e) => {
consoloe.log('发起请求'); // 只要一触摸到box元素就会产生几百次的数据请求,不满足现实场景的使用需求
})
// 拥有节流的情况
// 1.封装节流的函数
function fn1(fn, wait) {
let timeOut = null;
return function () {
if (!timeOut) { // 如果不存在就创建一个定时器 这里和防抖的操作相反
timeOut = setTimeout(() => {
fn(); // 执行数据请求的方法
timeOut = null; // 将定时器又置为空
}, wait)
}
}
}
// 2.模拟一个发起请求的方法
function demo() {
console.log('发起请求');
}
// 3.绑定事件监听
box1.addEventListener('touchmove', fn1(demo, 3000)) // 每3秒就会进行一次模拟数据请求
5. Promise
- 三种状态
pending(进行中): 表示操作正在进行中,既没有失败也没有成功
fulfilled(已完成): 表示操作完成,并返回了结果
rejected(已拒绝):表示操作被拒绝,并返回了失败的原因- 创建promise时需要传递成功和失败两个参数 resolve(成功) reject(失败)
- 处理需要花费一定时间的异步任务时使用promise可以防止发生阻塞的问题
- 通过then来接受成功的回调,使用catch来接受失败的回调,使用finally来接收一定存在的回调
// promise 使用举例
const isParent = true;
const promise = new Promise((resolve, reject) => {
if(isParent) resolve('早起'); // 成功的参数
else reject('不早起'); // 失败的参数
})
promise
.then(res => { // 成功的回调结果
console.log(`今天${res}.`);
})
.catch(res => { // 失败的回调结果
console.log(`今天${res}`);
})
.finally(()=> { // 必须返回的回调结果
console.log('今天天气不错.');
})
// 最后结果是 '今天早起.' '今天天气不错.'
// 举例2
const imgAddress = 'http://res.vmallres.com/cmscdn/CN/2023-05/e38cea3d510e42a9ba851fab8bb72add.png';
// 创建一个imgPromise函数,返回一个promise对象
const imgPromise = (url) => {
return new Promise((resolve, reject) => {
const img = new Image();
img.src = url;
img.onload = () => { // 成功
resolve(img);
};
img.onerror = () => { // 失败
reject(new Error('图片加载失败.'));
};
})
}
imgPromise(imgAddress)
.then(img => {
document.body.appendChild(img); // 成功将img标签元素添加到页面
})
.catch(err => {
document.body.innerHTML = err; // 失败就改写展示错误信息
})
6. js的异步事件轮询(event loop)
- js事件轮询解决的什么问题
事件轮询解决了JavaScript中的异步执行问题。在JavaScript中,所有的同步任务都在主线程上执行,形成一个执行栈。而异步任务则会被添加到任务队列中,等待主线程中的同步任务执行完毕后才会执行。
- 事件轮询的机制
主线程不断地从任务队列中读取事件,并执行对应的回调函数,从而实现异步任务的执行。这样可以避免阻塞主线程,提高程序的执行效率。
- js为什么是单线程
JavaScript被设计成单线程主要是由于其用途与浏览器的交互和操作DOM有关。作为浏览器脚本语言,JavaScript的主要目的是与用户进行互动和操作DOM。如果JavaScript是多线程的,就会引发复杂的同步问题。举个例子,如果一个线程在某个DOM节点上添加内容,而另一个线程同时删除了这个节点,那么浏览器应该以哪个线程为准?这样的情况会导致混乱。因此,为了避免这种复杂性,JavaScript被设计成单线程
小练习:
<script>
setTimeout(function(){
console.log(1)
}, 1000);
new Promise(function(a, b) {
console.log(2)
for(let i = 0; i < 8; i++) {
if(i == 7) console.log(2.1)
}
console.log(3)
}).then(function() {
console.log(4)
});
console.log(5);
</script>
输出的结果是: 同步任务自上而下的执行,异步任务回调之后执行 2, 3, 5, 4, 1
- js的宏微任务
宏任务和微任务的执行顺序是先执行微任务,然后再执行宏任务。
宏任务是指需要在主线程上执行的任务,常见的宏任务包括setTimeout、setInterval和DOM事件等。当一个宏任务执行完毕后,会立即执行在它执行过程中产生的微任务。
微任务是指需要在当前宏任务执行结束后立即执行的任务,常见的微任务包括Promise的.then()、MutationObserver和process.nextTick等。当一个宏任务执行完毕后,会按照产生微任务的顺序依次执行所有的微任务,直到微任务队列为空。
7. 说说你对事件流的理解
-
事件流主要包含事件冒泡和事件捕获
- 事件冒泡:触发节点事件之后会从当前节点开始依次的向父节点触发同类型的事件,直到DOM根节点
- 事件捕获:触发节点事件之后会从DOM根节点开始,依次触发其祖先节点的同类型事件,知道当前节点本身
-
什么是事件捕获
- 将addEventListener绑定事件时,给第三个参数设置为true就形成了事件捕获
-
如何阻止默认事件和事件冒泡
- e.stopPropagation()
- e.preventDefault()
8. 谈谈你对promise的理解
- 作用
promise是一种解决异步问题的方案
- 原理
promise内部存在状态管理器,包含三种状态pending, fufilled, rejected
对象初始化状态为pending;调用resolve之后为pending -> fufilled;调用reject之后为pending -> rejected
promise状态只能由pending -> fufilled/rejected,一旦状态发生改变就不能更改
状态为fufilled时then的成功回调会被调用并执行对应的操作,rejected就相反;then方法每次调用就会返回一个新的promise对象,所以可以使用链式写法。
- 总结
promise是一个对象,代表了未来时间才会知道的结果,不会受外界影响。一旦触发就只能是fufilled或rejected这两种状态,并且不能改变。接受一个构造函数作为参数,参数为resolve, reject,作用是将pending状态分别转为fufilled或者rejected,并且将成功或者失败的返回值传出去。
9. 谈谈你对重排和重绘的理解并说出如何避免
- 重排
当dom树节点大小位置发生改变需要浏览器进行重新计算并渲染的过程就是重排
重排一定会导致重绘
- 重绘
当元素的外观发生变化时,浏览器需要根据元素的新属性进行重新绘制,呈现元素的新外观叫做重绘
- 避免
3.1 重绘
- 元素的显示隐藏尽量使用opacity
- 为元素样式添加类,样式都在类中改变
3.2 重排
- 元素定位使用transfrom代替top, left
- 尽量不使用table布局
- 减少直接操作DOM元素
10. 说说你的原型和原型链的理解
- 原型
1.1 是函数对象的一个属性,定义了构造函数的公共祖先,通过该构造函数产生的对象可以继承属性和方法
1.2 构造函数实例化出来的对象可以使用公共的属性和方法
1.3 只有函数对象才拥有prototyp属性
- 原型链
2.1 js中万物皆对象,如果一直访问_proto_属性就会产生一条原型链,链条的尽头是null
2.2 当js查找对象属性时,如果当前对象查找不到就会沿着原型链一直往上查找
- 总结
3.1 原型主要解决继承问题
3.2 每个对象拥有一个原型对象,通过propt指针指向原型对象,并从原型对象中继承属性和方法
3.3 通过原型链一个对象可以拥有定义在其他对象中的属性和方法
仅供个人学习参考使用,如有错误欢迎指正,谢谢!!!