JavaScript 数据类型详解:基本类型与引用类型的区别及其检测方法
1、基本数据类型:number string boolean null undefined symbol
- 按值访问:基本数据类型直接存储值、因此通过变量访问时操作的是实际值
- 不可变性:基本数据类型值是不可变的,任何对TM的操作都是返回一个新值,而不会修改原值
- 值比较:基本数据乐行比较时直接比较他们的值
- 存储在栈内存:栈内存用于存储基本数据类型的值,栈时一种后进先出的(LIFO)的数据类型,操作非常高效
2、引用数据类型: object array data function
- 按需引用访问:引用数据类型通过引用来访问,意味着多个变量可以引用同一个对象,操作该对象时会影响所有引用他的变量
- 可变性:引用类型的值是可变的,对对象、数组等的修改会直接影响原引用
- 引用比较:在比较引用类型时,比较的是引用(内存地址),而不是他们的内容
- 存储在堆内存中:堆内存(Heap)用于存储引用类型的对象。栈内存只保存对象引用地址,实际数据存储在堆内存中。
3、数据类型的检测:js 提供了两种常用的方式来检测变量的基本类型 typeof 和 instanceof
-
typeof 用于检测基本数据类型和和函数类型,但对于引用数据类型的结果不够准确。他的输出主要是
typeof "hello"返回"string"typeof 123返回"number"typeof true返回"boolean"typeof undefined返回"undefined"typeof function() {}返回"function"typeof null返回"object"(这是一个历史遗留问题、JavaScript最初的实现中,值是由一个类型标签和实际数据值表示的。对象的类型标签是0,而null表示的是空指针,通常在大多数平台下值为0x00,因此null的类型标签也是0,typeof null因此返回"object"。这个设计决策导致了typeof null返回"object"的结果,尽管null并不是一个对象。)typeof []返回"object"typeof {}返回"object"
-
instanceof 用于检测引用数据类型,可以判断某个对象是否是某一个构造函数的势力。他通过检测对象的原型链,判断构造函数的prototype 是否出现在对象的原型链上
[] instanceof Array返回true{}instanceof Object 返回truefunction() {} instanceof Function返回true
-
Object.prototype.toString.call (缺点:不能细分为谁谁的实例)
JavaScript 原型和原型链
原型链
-
对象之间的继承关系通过构造函数的prototype指向父类对象,直到指向Object对象为止形成的指向链条。
-
通俗讲: 原型链是原型对象创建过程的历史记录。
-
注:在javascript中,所有的对象都拥有一个__proto__属性指向该对象的原型(prototype) 。
-
当一个对象我们不知道准确的是谁构造的时候,我们就把它看成object的实例化对象 也就是说,我们的构造函数的prototype的__proto__指向的是object.prototype
-那么object.prototype也是一个对象,我们有一句话叫万物皆对象,所以object.prototype就到顶了,object.prototype的__proto__就是 null
- 查找顺序:当你访问某个属性时,JavaScript 采用以下顺序查找: 看对象本身是否有这个属性。 如果没有,查看该对象的 proto 是否有这个属性。 如果还没有,继续查找 proto.proto,依此类推,直到查到 Object.prototype。如果在整个原型链中都未找到,返回 undefined。
JavaScript 闭包
闭包是什么:JS中内层函数可以访问外层函数的变量,使内部私有变量不受外界干扰,起到保护和保存的作用,我们把这个特性称作闭包
好处:
- 隔离作用域,保护私有变量;有了闭包才有局部变量,要不然都是全局变量了。
- 让我们可以使用回调,操作其他函数内部;
- 变量长期驻扎在内存中,不会被内存回收机制回收,即延长变量的生命周期;
坏处:
- 内层函数引用外层函数变量,内层函数占用内存。如果不释放内存,过多时,易引起内存泄露。
应用场景: for循环中的保留i的操作 / 防抖和节流
内存泄露、垃圾回收机制
-
内存泄露: 是指不再用的内存没有被及时释放出来,导致该段内存无法被使用就是内存泄漏,内存泄漏指我们无法在通过js访问某个对象,而垃圾回收机制却认为该对象还在被引用,因此垃圾回收机制不会释放该对象,导致该块内存永远无法释放,积少成多,系统会越来越卡以至于崩溃
-
垃圾回收机制: 就是垃圾收集器按照固定的时间间隔,周期性地寻找那些不再使用的变量,然后将其清楚或释放内存。(标记清除/引用计数)
JavaScript 深浅拷贝
-
浅拷贝:将原对象或原数组的引用直接赋给新对象,新数组,新对象只是对原对象的一个引用,而不复制对象本身,新旧对象还是共享同一块内存。(拓展运算符。。。)
-
深拷贝:开辟一个新的栈,两个对象的属性完全相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。(JSON.stringify和JSON.parse / 递归/Object.assgin )
-
优点:方便,将字符串parse后创建新对象(新地址)
-
缺点:如果被拷贝的对象中某个属性的值为undefined,则拷贝之后该属性会丢失,如果被拷贝的对象中有正则表达式,则拷贝之后的对象正则表达式会变成Object
JavaScript 如何改变this指向(call、apply与bind区别)
-
call、bind、apply 都是 JavaScript 中用于改变函数执行上下文(即 this 指向)的方法。
-
传参 call、bind可以传递无数个参数,apply只有两个参数,第二个参数为数组
-
返回 call和apply方法是直接调用函数并改变函数上下文,而bind方法则是返回一个新函数,稍后调用时绑定指定的上下文。
JavaScript 箭头函数和普通函数的区别
- 箭头函数是普通函数的简写,但是它不具备很多普通函数的特性
- this指向问题,箭头函数的this指向它定义时所在的对象,而不是调用时所在的对象
- 没有arguments对象,不能使用arguments
- 不会进行函数提升
- 箭头函数实现了一种更加简洁的书写方式。箭头函数内部没有
arguments,也没有prototype属性,所以不能用new关键字调用箭头函数。 - 箭头函数和普通函数最大的区别在于其内部this永远指向其父级对象的this
JavaScript 浏览器存储,他们的区别
- localStorage:永久保存,以键值对保存,存储空间5M
- sessionStorage:关闭页签/浏览器时清空
- cookie:随着请求发送,通过设置过期时间删除
- session:保存在服务端
(localStorage/sessionStorage是window的属性,cookie是document的方法)
JavaScript var let const的区别
var声明的变量存在变量提升,即变量可以在声明之前调用,var允许重复声明变量var不存在块级作用域let和const不存在变量提升,即它们所声明的变量一定要在声明后使用,否则报错let和const存在块级作用域l
常用的数组方法有哪些?
- 改变原数组:push、pop、shift、unshift、sort、splice、reverse
- 不改变原属组:concat、join、map、forEach、filter、slice
(slice切片的意思,根据传入的起始和终止下标,获取该范围数组。splice可根据传入参数个数不同实现删除、插入操作,直接操作原数组。第1个参数为起始下标,第2个为删除个数,第3个为要增加的数据)
继承方式有哪些
- 原型继承:不能传参
- 组合继承: 调用了两次父类的构造函数,不共享父类引用属性
- 寄生组合继承:
- ES6的extend:子类只要继承父类,可以不写 constructor ,一旦写了,则在 constructor 中的第一句话必须是 super
ES6新特性
- let 和 const 关键字:
let用于声明块级作用域的变量,const` 用于声明常量,其值不能被重新赋值。 - 模板字符串:使用反引号 ``来创建字符串,可以方便地进行字符串拼接和表达式嵌入。
- 箭头函数:具有更简洁的语法,并且在词法作用域上与普通函数有所不同。
- 解构赋值:可以方便地从数组或对象中提取值并进行赋值。
- 默认参数:函数参数可以设置默认值。
- 扩展运算符:用于展开数组或对象。
- 类和继承:提供了更接近传统面向对象编程的语法来创建类和实现继承。
- Promise 对象:用于处理异步操作,提供了更清晰的异步编程方式。
- 模块:支持模块的导入和导出
网络原理
** 从浏览器输入url后都经历了什么**
- 先进行DNS域名解析,先查看本地hosts文件,查看有没有当前域名对应的ip地址,若有直接发起请求,没有的话会在本地域名服务器去查找,没找到的话就去根服务器查找最后查找到对应的ip地址后把对应规则保存到本地的hosts文件中。
- 进行http请求,三次握手四次挥手建立断开连接
- 服务器处理,可能返回304也可能返回200
- 客户端自上而下执行代码渲染页面
TCP协议和HTTP 协议
-
TCP协议在建立过程中会进行三次握手四次挥手,三次握手确保双方同步并避免无效连接,四次挥手则正常终止连接或异常终止连接。
-
HTTP协议是超文本传输协议(Hyper Text Transfer Protocol),是用于从万维网服务器传输超文本到本地浏览器的传送协议。HTTP是一个基于TCP/IP通信协议来传递数据的。
HTTP与HTTPS有什么区别
- HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全,为了保证这些隐私数据能加密传输,于是网景公司设计了SSL(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。
如何解决前端跨域问题
- JSONP跨域,CORS,nginx代理跨域,nodejs中间件代理跨域,WebSocket协议跨域
项⽬开发中是⽤什么⼯具来管理代码的
说⼀下你是⽤过 的⼯具⽤法(git、svn) git包管理⼯具,加⼊⼀个项⽬组,正式开始开发,⼀般有以下⼏个步骤: 1.git clone git@gitlab.com⸺使⽤git clone克隆远程项⽬到本地,克隆到本地后只有⼀个分⽀ master
- git checkout -b test⸺本地创建⼀个test分⽀并为测试环境代码分⽀并切换⾄test,这⾥根据原项 ⽬仓库的测试环境代码分⽀命名,有些使⽤dev,这⾥假定原项⽬测试环境代码分⽀为test,测试test 分⽀代码跟master分⽀代码相同
- git pull origin test⸺拉取远程test分⽀代码并合并到本地test分⽀,经过本步操作,本地test分⽀ 就是测试环境代码了,以后开发分⽀的代码都要先合并到test分⽀推到远程测试环境经过测试后才能进 ⾏线上部署
- git checkout master⸺再切回master分⽀
- git checkout -b develop-branch⸺创建开发分⽀develop-branch并切换⾄develop-branch分 ⽀,这⼀步就是要进⼊开发了,⼀个需求⼀个开发分⽀,直到功能开发完毕
- git add new_file.php⸺把开发过程中新建的⽂件添加进版本管理
- git commit -m '备注'⸺提交所有变更
- git checkout test⸺切换⾄测试分⽀
- git merge develop-branch⸺合并新开发的变更到test分⽀
- git push origin test⸺test推到远程分⽀,此时测试环境可以拉取test分⽀代码,然后进⾏测试
- git checkout master && git merge develop-branch⸺测试通过后,切换到master分⽀然后合 并develop-branch到master分⽀
- git push origin master⸺master分⽀推到远程仓库,进⾏后续的部署上线
# JS异步、事件循环与消息队列、微任务与宏任务
- JavaScript 是单线程、异步、非阻塞、解析类型脚本语言,设计用于处理浏览器网页交互,决定其为单线程,处理任务从上往下依次进行。
- 虽为单线程,但浏览器渲染进程提供多个线程,如事件触发线程、定时触发器线程、异步 http 请求线程、GUI 渲染线程等。当遇到计时器、DOM 事件监听或网络请求等异步任务时,JS 引擎将其交给相应线程处理,实现异步非阻塞。
- 事件循环机制和消息队列由事件触发线程控制,JS 引擎线程遇到异步会交给其他线程,待时机成熟,事件触发线程将回调函数加入消息队列,JS 引擎线程在执行栈为空时从消息队列取出任务执行。
- 异步任务分为宏任务和微任务,宏任务如
setTimeout等,微任务如promise等。宏队列可以有多个,微任务队列只有一个,一个事件循环中,宏任务执行完毕会检查微任务队列,执行完微任务队列再进行下一个宏任务。 - 一个页签是一个进程,一个进程包含多个线程,页面渲染会调用多个线程处理,JS 单线程指执行处理 JS 的线程只有一个即 JS 引擎线程,相关异步任务处理完成的回调函数存于消息队列,待主线程空闲时依次处理,此为事件循环机制。
项⽬常⻅优化点
- ⾸屏加载优化
- 路由懒加载
path: '/',
name: 'home',
component: () => import('./views/home/index.vue'),
meta: { isShowHead: true } }
- 开启 Gzip 就是⼀种压缩技术,需要前端提供压缩包,然后在服务器开启压缩,⽂件在服务器压缩后传 给浏览器,浏览器解压后进⾏再进⾏解析。⾸先安装 webpack 提供的compression-webpack-plugin 进⾏压缩,然后在 config.js:
const CompressionWebpackPlugin = require('compression-webpack-plugin') const productionGzipExtensions = ['js', 'css']......plugins: [
new CompressionWebpackPlugin({
algorithm: 'gzip',
test: new RegExp('\\.('productionGzipExtensions.join('|') + ')$'),
threshold: 10240,
minRatio: 0.8
}
)]
- 启动 CDN 加速
- 我们继续采⽤ cdn 的⽅式来引⼊⼀些第三⽅资源,就可以缓解我们服务器的压⼒,原理是将我们的压 ⼒分给其他服务器点。
前端视角下的 SEO 优化方法
一、页面加载性能优化
-
代码压缩与合并
- JavaScript 压缩:在前端开发中,我们会使用很多 JavaScript 代码来实现各种功能,如交互效果、数据获取等。在部署到生产环境时,应该对 JavaScript 文件进行压缩。例如,通过工具如 UglifyJS 等,可以去除代码中的空格、注释,缩短变量名等,从而减小文件大小。例如,原始的 JavaScript 代码可能有 100KB,经过压缩后可能会减小到 30KB 左右,这大大提高了文件的加载速度。
- CSS 压缩:同样,CSS 文件也可以进行压缩。使用工具如 Clean - CSS,可以将 CSS 代码中的多余空格、注释去除,合并相似的规则等。例如,一个未压缩的 CSS 文件可能有 50KB,压缩后可能减小到 15KB 左右,这样浏览器能够更快地下载和解析 CSS 文件,加速页面渲染。
- HTML 压缩:对于 HTML 文件,也可以通过一些工具或构建流程来压缩。这包括去除多余的空格、换行符等。例如,在一个复杂的 HTML 页面中,压缩前文件大小可能是 20KB,压缩后可能减小到 15KB 左右,有助于提高页面加载效率。
-
资源懒加载与预加载
- 图片懒加载:在页面中,尤其是包含很多图片的长页面(如电商产品列表页、图片展示网站等),采用图片懒加载是个很好的策略。懒加载意味着图片只有在进入浏览器的可视区域时才会加载。可以使用 JavaScript 库,如 Lozad.js 来实现。例如,一个电商产品列表页可能有 50 张产品图片,如果全部同时加载,会导致页面初始加载速度很慢。采用懒加载后,只有当用户滚动到某个产品图片所在位置时,该图片才会加载,这样可以显著提高页面的初始加载速度,同时也节省了用户的流量。
- 脚本和样式预加载:对于一些关键的脚本和样式文件,可以采用预加载的方式。通过在 HTML 的
<head>标签中使用<link rel="preload">(用于样式和脚本等资源)或<link rel="prefetch">(用于可能在后续页面需要的资源)标签。例如,如果有一个用于实现页面主要交互功能的 JavaScript 文件,在页面加载初期就可以使用<link rel="preload" href="main - script.js" as="script">来预先加载这个文件,这样当浏览器解析到需要使用这个脚本的位置时,它已经被加载好了,从而提高页面的响应速度。
-
优化 CSS 加载顺序和渲染阻塞
-
把关键的 CSS(例如用于布局和首屏显示的 CSS)放在文档头部加载,通过
<link rel="stylesheet" href="styles.css">放在<head>标签内。这是因为 CSS 会阻塞页面的渲染,直到 CSSOM(CSS Object Model)构建完成。如果关键的 CSS 没有及时加载,页面可能会出现闪烁(FOUC - Flash of Unstyled Content)现象。例如,对于一个具有固定导航栏布局的页面,用于导航栏样式的 CSS 应该优先加载,以确保导航栏能够正确地显示在页面顶部,给用户一个良好的第一印象。 -
对于非关键的 CSS(如用于打印或不同屏幕分辨率的样式),可以使用媒体查询来加载,例如
<link rel="stylesheet" href="print - styles.css" media="print">,这样这些 CSS 文件不会阻塞页面的初始渲染,只有在用户触发打印操作或者满足特定媒体查询条件时才会加载。
-
二、HTML 结构优化
-
语义化标签的正确使用
- 语义化标签能够让搜索引擎更好地理解页面内容的结构和含义。例如,
<header>标签用于表示页面的头部区域,通常包含网站的 logo、导航菜单等内容。搜索引擎通过识别这个标签,就可以知道这个区域是页面内容的开头部分,用于提供导航和品牌标识等重要信息。 - 对于文章内容,应该使用
<article>标签来包裹独立的文章主体,并且在文章内部可以使用<h1>-<h6>标签来表示标题的层次结构。比如,一篇博客文章可以用<article>标签包裹,文章标题用<h1>标签,文章中的章节标题可以用<h2>标签,这样搜索引擎就能很容易地理解文章的内容层次,就像人类阅读书籍时通过章节标题来理解内容结构一样。 - 另外,
<footer>标签用于表示页面的底部区域,通常包含版权信息、联系方式等内容。搜索引擎可以通过这个标签识别出页面内容的结束部分和一些重要的附属信息。
- 语义化标签能够让搜索引擎更好地理解页面内容的结构和含义。例如,
-
合理设置 HTML 标签属性
-
Meta 标签优化
- 标题标签(Title Tag) :标题标签是搜索引擎结果页面(SERP)中显示的页面标题,也是搜索引擎判断页面主题的重要依据。应该确保标题标签准确、简洁地反映页面内容,并且包含核心关键词。例如,对于一个旅游攻略页面,标题可以是 “[目的地名称] 旅游攻略 - 景点推荐、美食介绍、行程安排”,长度最好控制在 50 - 60 个字符左右,以免在 SERP 中被截断。
- 描述标签(Meta Description) :描述标签是对页面内容的简短概述,虽然它不会直接影响搜索引擎排名,但会影响用户是否点击你的链接。应该编写吸引人的描述内容,并且包含相关关键词。例如,“探索 [目的地名称] 的魅力!我们的旅游攻略涵盖了热门景点、当地美食以及精心安排的行程,让您的旅行轻松愉快。” 长度一般在 150 - 160 个字符左右。
-
图像 alt 属性优化:对于页面中的图像,要设置合理的 alt 属性。alt 属性是当图像无法加载或者搜索引擎在索引图像时,用于描述图像内容的文字。例如,在一个产品页面中,产品图片的 alt 属性可以是 “[产品名称] - [产品的详细描述,如颜色、款式等] 产品图片”。这不仅有助于搜索引擎理解图像内容,还能提高网站的无障碍访问性。
-
三、JavaScript 对 SEO 的优化处理
-
避免 JavaScript 阻塞页面渲染
- 尽量将 JavaScript 脚本放在页面底部(在
</body>标签之前)加载。这是因为 JavaScript 的加载和执行会阻塞页面的渲染。例如,在一个包含大量 JavaScript 代码用于实现页面交互功能(如表单验证、动画效果等)的页面中,如果将这些脚本放在<head>标签中,浏览器在加载和执行这些脚本时,会暂停页面的 HTML 和 CSS 渲染,导致用户看到空白页面的时间变长。将脚本放在页面底部可以让浏览器先渲染页面的基本结构和内容,然后再加载和执行 JavaScript 代码,从而提高用户体验和搜索引擎对页面的初始抓取效率。
- 尽量将 JavaScript 脚本放在页面底部(在
-
确保搜索引擎可索引动态内容
- 在单页应用(SPA)或大量使用 JavaScript 动态加载内容的页面中,搜索引擎可能无法正确索引动态内容。为了解决这个问题,可以采用服务器端渲染(SSR)或者预渲染(Prerendering)的方式。例如,对于一个基于 React 或 Vue 的 SPA,使用 Next.js(对于 React)或 Nuxt.js(对于 Vue)等框架可以实现服务器端渲染。这些框架会在服务器端生成 HTML 内容,使得搜索引擎能够获取完整的页面内容,就像传统的多页面应用一样。另外,也可以使用预渲染工具,如 Prerender - SPA - Plugin(用于 Vue 和 React),在构建过程中生成 HTML 快照,供搜索引擎索引。
四、移动优化
-
响应式设计实现
- 使用 CSS 媒体查询来实现响应式设计。例如,对于一个网站的导航菜单,在大屏幕设备(如桌面电脑)上可以采用水平布局,通过设置
@media (min - width: 1024px)样式规则,让导航菜单项水平排列,并且有足够的间距。而在小屏幕设备(如手机)上,通过@media (max - width: 767px)样式规则,将导航菜单改为垂直布局,可能还需要添加一个汉堡菜单图标来隐藏和显示菜单选项。这样,网站在不同大小的屏幕上都能提供良好的用户体验,这也是搜索引擎(如谷歌)在移动搜索排名中非常看重的一点。
- 使用 CSS 媒体查询来实现响应式设计。例如,对于一个网站的导航菜单,在大屏幕设备(如桌面电脑)上可以采用水平布局,通过设置
-
优化移动页面性能
- 考虑到移动设备的网络和性能限制,需要特别关注移动页面的加载速度。除了前面提到的代码压缩、资源懒加载等通用优化方法外,还可以针对移动设备优化图片格式。例如,在移动页面中,尽量使用 WebP 格式的图片,因为 WebP 格式在相同画质下比 JPEG 等传统格式文件大小更小。可以通过 JavaScript 或服务器端配置来根据用户设备类型提供合适格式的图片,从而提高移动页面的加载速度,提升在移动搜索引擎中的排名。
[Axios与Ajax的区别]
Axios和Ajax是两种常用于处理网络请求的技术,它们在功能和使用上有所不同。
Axios是一个基于Promise的HTTP客户端库,可以在浏览器和Node.js中使用。它提供了一种简洁的API来发送各种HTTP请求,如GET、POST、PUT和DELETE。Axios的特点包括请求和响应拦截器、自动转换JSON数据、客户端支持防御XSRF等。由于Axios返回的是Promise对象,它可以很好地与async/await语法一起使用,使得异步代码更加简洁和易于维护。
Ajax(Asynchronous JavaScript and XML)是一种在无需重新加载整个页面的情况下,与服务器交换数据并更新部分网页的技术。它依赖于JavaScript和XMLHttpRequest对象来实现这一功能。Ajax允许在后台与服务器进行少量数据交换,使得网页能够只更新部分内容而不影响用户的操作。Ajax技术的优点在于能够快速响应用户的操作,提高用户体验,但它也有一些缺点,如不支持浏览器的后退按钮、可能存在安全问题、对搜索引擎的支持较弱等。
在实际开发中,选择使用Axios还是Ajax取决于具体的需求。如果需要一个现代化、支持Promise的库,并且在Node.js环境中也需要进行HTTP请求,那么Axios是一个更好的选择。而如果项目中已经使用了如jQuery这样的库,并且需要兼容较老的浏览器,那么继续使用Ajax可能更合适。