闭包
概念
- 函数及外部的作用域就形成了一个闭包。
特点
-
如果没有闭包的特性,那么函数想访问外部的变量,需要通过参数传递
-
闭包能让函数访问外部作用域,即便是外部函数执行完毕,仍能保留变量的状态
-
// 保持局部变量的状态,相当于创建私有变量 function outer() { let a = 0 function inner() { return a++ } return inner }
-
闭包使用不当可能导致内存泄漏:引用的变量无法被释放,可能导致内存被占用
- 引用了大数组,复杂的对象
- 引用了DOM元素
原型-原型链
概念
原型:每个JS对象都有一个内置属性,这个属性指向另一个对象,这个对象被称为“原型对象”
当我们访问一个对象的属性时,会先检查该对象本身是否有,如果该对象没有这个属性,那么JS就会去它的原型对象上找,如果原型对象上没有,则继续往上找,直到找到该属性或者到达原型链的终点(null)
实践
- 实现继承(抽离代码、逻辑)
- 共享属性、方法
- 构造函数通过new创建实例
- class是原型的语法糖
举例
- 要创建一个鲨鱼实例,大象实例
- 如果给他们分别写一个类,那么就会有很多重复代码(身高、体重,吃喝拉撒)
- 如果给他们定义一个动物基类,将这些重复代码(属性、方法)抽离到基类里
- 再通过继承基类对公共属性、方法进行复用,然后在他们自己的类,实现独有的功能
- 这样就可以实现代码复用,提高可维护性和开发效率
this
概念
this是JavaScript中的一个比较灵活关键字,指向函数执行时的上下文,提供一种在对象方法中引用当前对象的方式。
这使得我们可以在对象的方法内部访问对象的其他属性或方法,而不需要必须明确对象名称本身,让我们代码编写起来更加灵活、方便。
几个关键的点
- this是在运行时绑定的
- this的绑定和定义的位置(编写的位置)无关
- this的绑定和调用方式以及调用的位置有关
四种主要规则
- 默认绑定:非严格模式下独立调用时,this默认绑定到全局对象
- 隐式绑定:当函数作为对象的方法被调用时,this绑定到该对象
- 显式绑定:call、apply、bind,将this绑定到指定的对象
- new绑定:通过new调用构造函数时,this绑定到新创建的对象实例
- 箭头函数不会创建自己的this,它会使用外部作用域的this
bind、call、apply
- 显式设置函数内部的this值
function sayHello(message) {
console.log(message + ",我是" + this.name);
}
const person = { name: "李四" };
sayHello.call(person, "早上好"); // 输出 "早上好,我是李四",this 指向 person
sayHello.apply(person, ["中午好"]); // 输出 "中午好,我是李四",this 指向 person
const greetPerson = sayHello.bind(person, "下午好");
greetPerson(); // 输出 "下午好,我是李四",this 指向 person
浏览器兼容
原因:浏览器内核不同
现代工程化的开发架构,通过配置选项
- browserslist(市场份额、版本)
- polyfill、babel
- autoprefixer、postcss
- caniuse
浏览器渲染
-
DNS解析
- 用户输入的URL通常是一个域名地址,直接通过域名是无法找到服务器的,因为服务器本质上是一套拥有IP地址的主机
- 需要通过DNS服务器来解析域名,获取IP地址
- DNS会优先查找缓存,包括浏览器缓存、操作系统缓存、路由器缓存、ISP缓存
- 如果缓存查找失败,就需要通过DNS递归解析,包括根域名服务器、顶级域名服务器、权威域名服务器
- 最终找到IP地址,就可以通过该IP地址去连接服务器,并且IP地址会被发送回用户电脑,缓存起来
-
TCP连接
- TCP的连接会进行三次握手,客户端发送SYN包,服务器接收到后返回一个SYN-ACK包,客户端再次发送一个ACK包,完成握手过程
- 此时TCP连接建立完成,就可以开始传输数据了
-
HTTP请求
- TCP建立连接成功,客户端就可以通过这个连接发送HTTP请求,包括请求方法、URL、协议版本、请求头、请求体
- 服务器接收到HTTP请求后,会处理这个请求,并且返回一个HTTP响应
- HTTP响应包括状态码、响应头、响应内容,这里通常是index.html文件
-
HTML解析和CSS解析
- 浏览器获取到index.html后,可以开始对文档进行解析
- 包括HTML解析来构建DOM Tree,在这个过程中会遇到CSS文件和JS文件
- 遇到CSS文件和JS文件会继续向服务器发送HTTP请求,并将指定的CSS、JS文件进行下载
- 之后对CSS文件进行解析,解析出对应的CSSOM
-
渲染render、布局layout、绘制paint
- DOM Tree和CSSOM共同构建出Render Tree
- 之后在Render Tree上进行布局,计算每个元素尺寸、位置、宽高
- 再由浏览器进行绘制(颜色、边框、圆角、阴影...),显示在屏幕上
跨域
同源:协议、主机、端口必须完全相同
同源策略:不同源就被认为是跨域,跨域请求会被浏览器阻止
跨域限制主要目的是为了安全
- 防止CSRF(跨站请求伪造):攻击者在A网站伪造请求访问B网站的接口,盗取用户数据
- 防止恶意脚本窃取数据
解决方法
-
CORS(跨域资源共享):服务器通过响应头控制跨域请求是否被允许(Access-Control-Allow-x)
-
配置代理(webpack、vite创建一个开发服务器,利用
http-proxy
获取代理配置项后,帮我们做正向代理) -
Nginx反向代理
- 代理静态资源和API服务器
- 仅仅代理API服务器
-
WebSocket不受同源策略的限制
URI、URL、URN
URI(统一资源标识符)= URL(统一资源定位符) + URN(统一资源名称)
URL:标识资源,并且提供访问方式,包括协议+主机+端口+路径+查询参数+hash值,相当于某个人的具体地址,可以直接找到他
URN:标识资源,不提供访问方式,相当于某个人的电话号码,无法直接找到他
正向代理、反向代理
代理(Proxy)是一种网络服务,它充当客户端和服务器之间的中间人。客户端向代理服务器发送请求,代理服务器再将请求转发给目标服务器。服务器返回的响应也会经过代理服务器,然后转发给客户端。
正向代理:客户端的代理,代表客户端向服务器发起请求
- 访问受限资源:国内存在防火墙,无法直接访问Google,使用VPN作为正向代理进行访问
- 保护用户隐私:客户端知道代理服务器IP,目标服务器不知道客户端的真实IP
- 过滤内容,当防火墙使用:学校、公司的电脑屏蔽某些网站
反向代理:服务器的代理,代表服务器接收客户端的请求
- 负载均衡:将请求分发给多台服务器,优化资源使用,提高并发能力
- 安全保护:隐藏服务器的真实IP,拦截恶意攻击,保护服务器安全
- 缓存:将静态资源缓存到代理服务器,减少重复资源的请求,加快访问速度
事件循环
JavaScript是单线程语言,即同一时间只能执行一个任务。如果没有事件循环,那么那些耗时任务(网络请求、定时器)就很容易导致页面卡死。
事件循环是一种机制,主要任务是监视调用栈(call stack)和任务队列(task queue),如果是同步任务,就按顺序放入调用栈直接执行,如果是异步任务,就会放入任务队列中等待执行,当调用栈为空时,会从任务队列中取出任务执行。
事件循环的执行流程:
- 执行同步代码
- 执行所有微任务
- 执行一个宏任务
- 回到步骤2
事件循环保证了JavaScript的单线程执行模型,同时又能不阻塞主线程的情况下,处理大量的异步任务,使浏览器能流畅运行。
浏览器引擎
也称渲染引擎、排版引擎或浏览器内核。常见的浏览器引擎:Webkit(Safari),Blink(Chrome),Gecko(Firefox)
主要组成部分
- HTML解析器
- CSS解析器
- JavaScript引擎
- 渲染引擎
工作流程
- 解析HTML文件,构建DOM树
- 解析CSS文件,构建CSSOM树
- 构建渲染树:将DOM树和CSSOM树合并,构建出渲染树
- 布局:计算每个元素尺寸、位置、宽高
- 绘制:字体、颜色、边框、圆角、阴影...
- 合成:将多个图层合成为最终的页面,显示在屏幕上
- 解析、执行JavaScript代码:V8将JS代码解析成AST,再将AST解析成字节码,或将字节码编译成CPU可直接执行的机器码
优化技术
- 异步加载:async、defer异步加载JS文件,避免阻塞解析HTML
- 延迟渲染:将非关键资源(图片、视频)延迟加载,优先渲染可见区域
- GPU加速:利用GPU进行渲染、合成,还可以提升页面滚动和动画的性能
- 缓存机制:利用缓存(HTTP缓存),减少重复资源的加载时间
- 增量渲染:分块解析和渲染HTML,逐步显示页面内容,提升用户体验
垃圾回收
在 JavaScript 中,内存的分配和释放都是自动完成的,这个过程就称为垃圾回收。
基本思路:确定哪个变量不会再使用,然后释放它占用的内存。
这个过程是周期性的,即垃圾回收程序每隔一定时间就会自动运行。
-
引用计数
-
每个对象都有一个引用计数,记录它被引用的次数
-
如果有其他对象引用它,那么引用计数就+1
-
如果其他对象停止引用它,那么引用计数就-1
-
当一个对象的引用计数为0时,它所占用的内存就会被回收
-
存在循环引用的问题
- 函数中对象循环用,在引用计数策略下,它们的引用计数一直不为0,函数被多次调用,则会导致大量内存不会被释放
- 解决方式:将对应的属性置为null,停止引用
-
-
标记清除
- 核心思路:可达性
- 从根对象(window)开始递归遍历所有可访问的对象,并标记它们为“可达”
- 未被标记的对象被认为是“不可达对象”,也就是垃圾
- 垃圾回收程序会将这些“不可达对象”进行清理,释放它们所占用的内存
- 标记清除很好地解决了循环引用的问题
-
标记整理:将保留的对象搬运汇集到连续的内存空间,从而整合空闲空间,避免内存碎片化
-
分代收集
- 将对象分为“新的”,“旧的”
- 许多对象出现,完成它们的工作很快“死去”,它们就可以被清理
- 那些长期存活的对象,也就是经过几次垃圾清理都没能被清理的对象,会变得老旧,检查的频次就会减少
-
增量回收:将标记过程分成多个小步骤,每次只标记一部分对象,而不是一次性标记所有对象,减少垃圾回收对程序带来的影响
-
空闲时间回收:在CPU空闲时进行垃圾回收
JS引擎应用比较广泛的就是标记清除算法,当然类似于V8引擎为了进行更好的优化,他在算法的实现细节上会结合其他的算法
作用域
描述在代码中定义变量的区域,这个区域决定了变量的可访问性和生命周期。
-
全局作用域:任何部分都可以访问这些全局变量
-
函数作用域:在函数内部声明的变量和函数,只能在函数内部访问
-
块级作用域:let/const,仅在{}块中可见。ES6新特性,对于管理局部变量非常有用,尤其是循环和条件语句中
-
词法作用域
- 作用域在代码编写时就已经确定,而不是在运行时
- 内部函数可以访问外部函数的变量,但外部函数不能访问内部函数的变量
执行上下文
指代码执行时,JS引擎所创建的一种“环境”
类型
- 全局执行上下文:代码首次运行时,浏览器环境是window
- 函数执行上下文:调用函数时创建
组成
-
变量对象(VO)
- 存储变量、函数声明和函数参数
- 在全局上下文中,变量对象是全局对象(window)
- 在函数上下文中,变量对象是活动对象(AO)
-
作用域链:内部函数可访问外部函数的特性,向上查找作用域形成的链条
-
this:在函数执行时绑定的
如何影响JS代码的执行
- JS引擎在执行一段代码时,会创建一个执行上下文。在这个阶段,变量和函数会被提升(var、function)
- 执行阶段,JS引擎会按照代码的顺序逐行执行。变量会被赋值,函数会被调用。
- 执行上下文在此过程中保持对作用域链的追踪,以便需要时解析
- JS引擎使用一个栈来管理多个执行上下文。
SEO
搜索引擎优化,通过优化网站结构、内容和链接等方式,提高网站在搜索引擎结果页的排行,从而增加流量,提升网站曝光度。
-
付费,打广告:成本比较高
-
站内优化
-
关键字优化:文档标题,内容标题(h1-h6),正文
- 高质量内容:原创、高价值、解决问题,而不是低质、重复、搬运的内容
-
HTML语义化:使用适当的标签,使搜索引擎更容易理解页面结构
-
URL优化:使用简短、清晰、包含关键字的URL
-
页面加载速度
- CDN
- JS、CSS、图片进行压缩,减少一些不必要的HTTP请求
-
用户体验优化
- 骨架图
- 面包屑导航
- 动画、变换
-
-
站外优化
- 外链建设:放入知名博客、社区
- 社交媒体:分享平台内容,提高曝光度
-
技术SEO
- SSR:方便搜索引擎直接抓取页面
- HTTPS
- 移动端优化
- Robots.txt:告知搜索引擎,哪些文件需要抓取,哪些文件不需要抓取
总之,SEO是一个长期的过程,需要持续投入和调整(定期更新维护,提高网站活跃度),遵守搜索引擎的规则。
TypeScript
- 静态类型检查
- 类型推断
- 接口,类型别名,枚举
- 泛型
增强了JS的能力,提供类型检查、类型推断,使得开发者能够在编写代码阶段就能发现数据类型导致的错误,编写更加健壮、可维护的代码。