1.闭包(Closure)
概念:一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域
闭包的注意点:
- 闭包不一定有return。什么时候用到return?外部如果想要使用闭包的变量,此时则需要return
- 闭包一定会有内存泄漏吗
js关于return的使用
//普通形式 统计调用次数
let i = 0
function fn() {
i++
console.log(`函数被调用了${i}次`)
}
//闭包形式 统计调用次数 实现数据私有,此时i作为局部变量,无法直接修改i的值
function count(){
let i = 0
function fn() {
i++
console.log(`函数被调用了${i}次`)
}
return fn
}
const fun = count()
使用闭包的作用:使用数据的私有
js内存泄漏
function fn(){
let count =1
function fun() {
count++
console.log(`函数被调用了${count}次`)
}
return fun
}
const result = fn()
result() //2
result() //3
什么存在内存泄漏? count变量 借助JS垃圾回收机制的标记清除法可以看到:
- result是一个全局变量,代码执行完毕不会立即销毁
- result使用fn函数
- fn用到fun函数
- fun函数里面用到count
- count被引用就不会被回收,所以一直存在
- 此时,闭包引起了内存泄漏
注意:不是所有的内存泄漏都要手动回收result=null
,比如react里很多闭包不能回收
2.事件循环(eventloop)
- js是单线程,防止代码阻塞,我们把代码任务分为:同步、异步
- 同步代码给js引擎执行,异步代码交给宿主环境(比如浏览器)
- 同步代码放入执行栈中,异步代码等待时机成熟推入任务队列排队
- 执行栈执行完毕,会去任务队列看是否有异步任务,有就送到执行栈执行,反复循环查看执行,这个过程称为事件循环
3.宏任务、微任务
js把异步任务分为宏任务和微任务
宏任务是由宿主(浏览器、Node)发起 在浏览器中:
- script(代码块)
- 事件
- 网络请求(Ajax/Fetch)
- setTimeout()一次性定时器/setInterval()定时器
微任务是由Js引擎发起的任务,
- Promise。Promise本身同步,then/catch的回调函数是异步的
- process.nextTick
- Async/Await
- object.observe等等
执行顺序:先微任务再宏任务
4.节流跟防抖
防抖:
指连续触发事件但是在设定的一段时间内中只执行最后一次 例如:设定1000毫秒执行,当你触发事件了,他会1000毫秒后执行,但是在还剩500毫秒时又触发了,那就会重新开始1000毫秒之后再执行
应用场景:
- 搜索框搜索输入
- 文本编辑器实时保存
代码实现思路?利用定时器,每次触发前先清掉以前的定时器
js
let timerId = null
document.querySelector('.ipt').onkeyup = function(){
//防抖
if(timerId !==null) {
clearTimeout(timerId)
}
timerId = setTimeout(() =>{
console.log("我是防抖")
},1000)
}
节流:
指连续触发事件但是在设定的一段时间内中只执行一次函数。 例如:设定1000毫秒执行,那你在这1000毫秒内触发多次,也只是在这个1000毫秒后执行一次
应用场景:
- 高频事件,例如快速点击、鼠标滑动、resize事件、scroll事件
- 下拉加载
- 视频播放记录时间等
实现思路:利用定时器,等待定时器执行完毕,才重新开始定时器
js
let timerId = null
document.querySelector('.ipt').onmouseover = function(){
//节流
if(timerId !==null) {
return
}
timerId = setTimeout(() =>{
console.log("我是节流")
timerId = null
},100)
}
lodash库,利用里面的debounce(防抖)和throttle(节流)来实现
5.原型跟原型链
原型:
每个函数都有prototype属性,称之为原型。因为这个属性的值是个对象,也称之为原型对象。
作用:
- 存放一些属性跟方法
- 在js中实现继承
__proto__
:每个对象都有这个属性。这个属性指向它的原型对象
js
const arr = new Array(1,2,3)
arr.reverse()
console.log(arr.__proto__ === Array.prototype) //true
原型链:
对象都有__proto__
属性,这个属性指向它的原型对象,原型对象也是对象,也有__proto__
属性,指向原型对象的原型对象,这样一层一层形成的链式结构称之为原型链,最顶层找不到则返回null
对象如何找属性|方法: 先在对象本身找=>构造函数中找=>对象原型中找=>构造函数原型中找=>对象上一层原型中查找
6.作用域
注意:
- 除了函数外,js是没有块级作用域
- 作用域链:内部可以访问外部的变量,但是外部不能访问内部的任何变量。如果内部有,优先查找内部,没有再找外部
- 注意声明变量是用var还是没有写(默认就是window.)
- 注意:js有变量提升的机制(变量悬挂声明)
- 优先级:声明变量>声明普通函数>参数>变量提升
js
function c (){
var b =1;
function a(){
console.log(b) //undefined 因为内部有
var b=2
console.log(b) //2
}
a();
console.log(b) //1
}
c();
//如果在函数a中,没有声明 var b=2,那么打印出来的全是1
//注意:本层作用域有没有此变量,谨防变量提升
js
var name = "a"
(function(){
//此时name在这个作用域内有,所以变量悬挂
if(typeof name == 'undefined'){
var name = 'b'
console.log("111"+name)
}else{
console.log("222"+name);
}
})()
最终打印:111b
js
function fun(){
console.log(a) //function a(){}
var a = 2;
function a(){}
}
fun()
//注意:普通声明函数是不看写函数的顺序
//如果只写以下形式没有括号
console.log(fun); //那么打印的就是这个函数体
//如果写new
console.log( new fun()); //那么打印就是一个对象,并且会执行函数体内代码
js作用域+this指向+原型考题一
function Foo(){
getName = function(){console.log(1)} //这个是全局变量 window.
return this; //普通函数this指向window
}
Foo.getName = function(){console.log(2)}
Foo.prototype.getName = funtion(){console.log(3)}
var getName = function(){console.log(4)}
function getName(){
console.log(5)
}
Foo.getName(); //2
getName(); //4
Foo().getName(); //1 因为Foo() 则函数调用,相当于window.getName()
getName(); //1 因为后者覆盖了前者
new Foo().getName(); //3
js作用域+this指向+原型考题二
var o = {
a:10,
b:{
fn:function(){
console.log(this.a);
console.log(this);
}
}
}
o.b.fn(); //这个this指向的是o.b这个对象
//打印出来
//undefined 因为o.b这个对象并没有a这个变量
//{fn:f} 打印b对象
window.name = 'xxx';
function A(){
this.name = 123;
}
A.prototype.getA = function(){
console.log(this);
return this.name +1;
}
let a = new A()
let funcA = a.getA;
funcA() //这里的this代表window