1.闭包(Closure)
概念:一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域
闭包的注意点:
- 闭包不一定有return。什么时候用到return?外部如果想要使用闭包的变量,此时则需要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()
使用闭包的作用:使用数据的私有
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毫秒之后再执行
应用场景:
- 搜索框搜索输入
- 文本编辑器实时保存
代码实现思路?利用定时器,每次触发前先清掉以前的定时器
let timerId = null
document.querySelector('.ipt').onkeyup = function(){
//防抖
if(timerId !==null) {
clearTimeout(timerId)
}
timerId = setTimeout(() =>{
console.log("我是防抖")
},1000)
}
节流:
指连续触发事件但是在设定的一段时间内中只执行一次函数。 例如:设定1000毫秒执行,那你在这1000毫秒内触发多次,也只是在这个1000毫秒后执行一次
应用场景:
- 高频事件,例如快速点击、鼠标滑动、resize事件、scroll事件
- 下拉加载
- 视频播放记录时间等
实现思路:利用定时器,等待定时器执行完毕,才重新开始定时器
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__:每个对象都有这个属性。这个属性指向它的原型对象
const arr = new Array(1,2,3)
arr.reverse()
console.log(arr.__proto__ === Array.prototype) //true
原型链:
对象都有__proto__属性,这个属性指向它的原型对象,原型对象也是对象,也有__proto__属性,指向原型对象的原型对象,这样一层一层形成的链式结构称之为原型链,最顶层找不到则返回null
对象如何找属性|方法: 先在对象本身找=>构造函数中找=>对象原型中找=>构造函数原型中找=>对象上一层原型中查找
6.作用域
注意:
- 除了函数外,js是没有块级作用域
- 作用域链:内部可以访问外部的变量,但是外部不能访问内部的任何变量。如果内部有,优先查找内部,没有再找外部
- 注意声明变量是用var还是没有写(默认就是window.)
- 注意: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
//注意:本层作用域有没有此变量,谨防变量提升
var name = "a"
(function(){
//此时name在这个作用域内有,所以变量悬挂
if(typeof name == 'undefined'){
var name = 'b'
console.log("111"+name)
}else{
console.log("222"+name);
}
})()
最终打印:111b
function fun(){
console.log(a) //function a(){}
var a = 2;
function a(){}
}
fun()
//注意:普通声明函数是不看写函数的顺序
//如果只写以下形式没有括号
console.log(fun); //那么打印的就是这个函数体
//如果写new
console.log( new fun()); //那么打印就是一个对象,并且会执行函数体内代码
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
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
7.new操作符具体做了什么
- 创建了一个空的对象
- 将空对象的原型,指向于构造函数的原型
- 将空对象作为构造函数的上下文(改变this指向)
- 对构造函数有返回值的处理判断
//示例一
function Fun(age){
this.age = age
}
new Fun(18);
//示例二
function Fun(age,name){
this.age = age
this.name = name
}
function create(fn,...args){
//1. 创建了一个空的对象
var obj = {};//var obj = Object.create({})
//2. 将空对象的原型,指向于构造函数的原型
Object.setPrototypeOf(obj,fn.prototype)
//3. 将空对象作为构造函数的上下文(改变this指向)
var result = fn.apply(obj,args)
//4. 对构造函数有返回值的处理判断
retrun result instanceof Object ? result:obj;
}
console.log(create(Fun,18,"xx"))
8.call、bind、apply区别
共同点:可以改变函数体内的this指向。语法函数.名称()
区别:
1.执行情况不同
var str = "你好"
var obj = {str:"这是obj对象内的str"}
function(){
console.log(this,this.str)
}
fun.call(obj); //参考打印内容,call会立即执行
fun();
打印结果如下
var str = "你好"
var obj = {str:"这是obj对象内的str"}
function(){
console.log(this,this.str)
}
fun.apply(obj); //参考打印内容,apply会立即执行
fun();
打印结果如下:
var str = "你好"
var obj = {str:"这是obj对象内的str"}
function(){
console.log(this,this.str)
}
fun.bind(obj);//bind不会立即执行,因为bind返回的是函数,得加()才能执行
fun();
打印结果如下
console.log(fun.bind(obj))打印:
由上述可知:call、apply可以立即执行,bind不会立即执行,因为bind返回的是一个函数,需要加入()执行
2.参数不同
var str = "你好"
var obj = {str:"这是obj对象内的str"}
function(name,age){
this.name= name
this.age= age
console.log(this,this.str)
}
fun.call(obj,'张三',88);
var str = "你好"
var obj = {str:"这是obj对象内的str"}
function(name,age){
this.name= name
this.age= age
console.log(this,this.str)
}
fun.apply(obj,['张三',88]);
var str = "你好"
var obj = {str:"这是obj对象内的str"}
function(name,age){
this.name= name
this.age= age
console.log(this,this.str)
}
fun.bind(obj,'张三',88)();
由上述可知:参数不同,apply第二个参数是数组,call跟bind有多个参数需要依次写
9.localStorage、seesionStorage、cookie
共同点:在客户端存放数据 区别:
- 存储容量
- localStorage:通常提供5MB的存储空间(不同浏览器可能有所不同)。
- sessionStorage:与 localStorage 的存储容量相似,但是数据仅存在于会话期间。
- Cookie:每个域名下存储的数据量通常限制为4KB左右。
- 生命周期
- localStorage:数据没有过期时间,除非用户手动清除浏览器缓存或使用JavaScript代码清除数据。
- sessionStorage:数据持续到页面会话结束为止,即关闭浏览器标签页或窗口时数据会被清除。
- Cookie:可以设置一个过期时间(Expires/Max-Age属性),如果没有设置,则默认是Session Cookie,在浏览器关闭时自动删除。
- 数据作用范围
- localStorage:数据绑定于文档源(协议+域名+端口),任何同源的所有页面窗口都可以共享访问这些数据。
- sessionStorage:数据不仅绑定于文档源,还绑定于文档结构树(即同一窗口或标签页)。这意味着即使是相同网站的不同标签页也不能共享 sessionStorage 数据。
- Cookie:可以通过设置domain和path来控制哪些路径或子域可以访问该cookie。同时,cookies也支持跨域请求携带(通过设置
SameSite属性)。
- 安全性
- localStorage 和 sessionStorage:不支持直接设置安全标识,如HttpOnly等。这意味着它们不能防止XSS攻击。此外,它们不会随着HTTP请求自动发送给服务器。
- Cookie:支持设置
Secure标志,确保cookie只通过HTTPS连接传输;还可以设置HttpOnly标志,禁止JavaScript访问cookie内容,从而有效防御XSS攻击。此外,cookie会在每次请求同源资源时自动附带发送(取决于path和domain设置以及是否设置了SameSite)。
- 适用场景
- localStorage:适合需要长期保存且大小较大的数据,比如用户偏好设置。
- sessionStorage:适用于临时性的、一次性的对话状态维护,例如存储表单输入数据以防刷新丢失。
- Cookie:主要用于保持用户的登录状态,个性化设置,追踪用户行为等,并且能够随请求发送给服务器。
//保存数据
localStorage.setItem('key', 'value'); // 或者 localStorage.key = 'value';
//读取数据
let value = localStorage.getItem('key'); // 或者 let value = localStorage.key;
//删除特定数据
localStorage.removeItem('key');
//清空所有数据
localStorage.clear();
使用方法与 localStorage 基本相同,只需将上述代码中的 localStorage 替换成 sessionStorage 即可。
由于 `cookie` 不是直接在 JavaScript 的全局对象中定义的,所以需要通过 `document.cookie` 来访问它们。需要注意的是,`document.cookie` 返回的是一个字符串,包含了所有的 cookie,以键值对的形式出现,并且每个键值对之间用分号加空格隔开。
//保存数据
document.cookie = "username=John Doe; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/";
//`"username=John Doe"` 是你想要设置的 cookie 的键值对。
// `expires=Fri, 31 Dec 9999 23:59:59 GMT` 设置了 cookie 的过期时间,不设置则默认为 session cookie,即关闭浏览器后失效。
// `path=/` 指定了 cookie 在网站中的可用路径,默认是设置该 cookie 的页面路径。
//读取
//由于 `document.cookie` 返回的是所有有效 cookie 的一个长字符串,所以需要编写函数来解析出特定的 cookie 值。
function getCookie(name) {
let matches = document.cookie.match(new RegExp( "(?:^|; )" + name.replace(/([\.$?*|{}$$ $$ $$ \\\/\+^])/g, '\\$1') +
"=([^;]*)"
));
return matches ? decodeURIComponent(matches[1]) : undefined;
}
let username = getCookie("username");
console.log(username); // 输出: John Doe 或者 undefined 如果cookie不存在
//要删除一个 cookie,你可以将它的过期时间设置为过去的某个时间点。
document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/";