三、JavaScript
JS 由哪三部分组成
- ECMAScript
- ECMAScript 是一套标准,定义了一种语言的标准
- 规定了基本语法、数据类型、关键字、具体API的设计规范等,解析引擎设计的参考标准,但与具体实现无关
- 是 JavaScript 的核心
- BOM
- 浏览器对象模型
- BOM 是一套操作浏览器功能的 API
- 通过 BOM 可以操作浏览器窗口,比如:弹出框、控制浏览器跳转、获取分辨率等
- DOM
- 文档对象模型
- DOM 是一套操作页面元素的 API
- DOM 可以把 HTML 看做是文档树,通过 DOM 提供的 API 可以对树上的节点进行操作
JS 数据类型
-
基本数据类型(值类型)
保存在栈内存中的简单数据段,它们的值固定大小,保存在栈空间,按值访问。
数据类型 含义 Number 数值 String 字符串 Boolean 布尔值,true 和 false Null 空 Undefined 未定义 Symbol ES6 引入的一种新的原始数据类型,表示独一无二的值 BigInt ES10 引入的一种任意精度整数 -
对象数据类型(引用类型)
保存在堆内存中的对象,值大小不固定,栈内存中存放的该对象的访问地址指向堆内存中的对象,按址访问。
JavaScript 不允许直接访问堆内存中的位置,因此操作对象时,实际操作对象的引用。
数据类型 含义 Object 对象 Array 数组 Function 函数 Date 日期 RegExp 正则
类型判断
类型强制是 JavaScript 中值从一种类型自动转换为另一种类型
typeof
是一个一元运算符,它返回一个指示操作数类型的字符串instanceof
用于检查对象是否是特定构造函数的实例Object.prototype.toString
是一种返回对象类型的字符串表示形式的方法
常见的类型转换
-
转化为 Number
Number()
方法:let num = Number('123')
parseInt()
转化为整数parseFloat()
转化为浮点数
-
转化为 String
-
使用
String()
方法:let str = String(123)
-
使用
toString()
方法:let str = true.toString()
-
使用
+
运算,加上空引号,隐式转换:let str = 123 + ''
-
使用
JSON.stringify()
方法
-
-
转化为 Boolean
- 使用
Boolean()
方法:let bool = Boolean(123)
- 使用
!!
运算:let bool = !!'123'
- 使用
数组常用方法
方法 | 描述 |
---|---|
join() | 接收一个参数,即字符串分隔符,返回包含所有项的字符串,省略则默认逗号为分隔符 |
toString() | 将数组转换为字符串 |
push() | 将参数添加到原数组末尾,并返回数组的长度。修改原数组 |
pop() | 删除原数组最后一项,并返回删除元素的值;如果数组为空则返回 undefined。修改原数组 |
shift() | 删除原数组第一项,并返回删除元素的值;如果数组为空则返回 undefined |
unshift() | 将参数添加到原数组开头,并返回数组的长度。修改原数组 |
slice() | 返回一个新的数组对象,该对象是由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括 end)。原数组不变 |
splice() | 参数:start,deleteCount,val1,val2,… 表示从start位置开始删除deleteCount项,并从该位置起插入。修改原数组 |
concat() | 合并数组,返回一个新的数组。原数组不变 |
indexOf() | 返回元素在数组中第一次出现的索引位置,如果不存在,则返回 -1 |
lastIndexOf() | 返回元素在数组中最后一次出现的索引位置,如果不存在,则返回 -1 |
includes() | 判断一个数组是否包含指定的值,包含返回 true,否则返回 false |
find() | 返回第一个匹配的值,并停止查找,否则返回 undefined |
reverse() | 将数组反序,并返回该数组。修改原数组 |
sort() | 对数组的元素进行排序,默认按照UTF-16代码单元值序列,并返回该数组。修改原数组 |
some() | 判断数组中是否存在满足的项,存在返回 true,否则返回 false |
every() | 判断数组中的每一项是否都符合条件,都符合返回 true,否则返回 false |
forEach() | 循环遍历数组,对数组每一项都运行传入的函数,没有返回值 |
map() | 循环遍历数组的每一项,返回由每次函数调用的结果构成的数组(返回每一项计算后的结果) |
filter() | 过滤,数组中的每一项运行给定函数,返回满足过滤条件组成的数组 |
keys() | 遍历数组键,返回一个新的数组对象,该对象包含数组中每个索引的键 |
values() | 遍历数组值,返回一个新的数组对象,该对象包含数组中每个项的值 |
数组去重
-
Set
let array = [1, 2, 1, 1, '1']; function unique(array) { return Array.from(new Set(array)); } console.log(unique(array)); // [1, 2, "1"]
-
filter + indexOf
function unique(arr) { return arr.filter(function(item, index, arr) { return arr.indexOf(item, 0) === index; }); }
-
filter + sort
-
for + splice
-
reduce + includes
function unique(arr){ return arr.reduce((prev,cur) => prev.includes(cur) ? prev : [...prev,cur],[]); }
-
hasOwnProperty
function unique(arr) { var obj = {}; return arr.filter(function(item, index, arr){ return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true) }) }
JS 执行上下文(Execution Context)
- 理解
- 代码从上往下开始执行的过程,其实对应一个执行环境,就是执行上下文,当代码执行完,上下文也就会被销毁。
- 运行环境类型
- 全局上下文:当运行代码是处于全局作用域内,则会生成全局执行上下文,也就是 window 函数
- 函数上下文:当调用函数时,都会为函数调用创建一个新的执行上下文呢。指 this 自己(函数环境)
- eval上下文:eval 函数执行时,会生成专属它的上下文,一般不会用
- 一个执行期上下文的生命周期
- 创建阶段:在这个阶段中,执行上下文会分别创建变量对象,建立作用域链,确定this指向
- 执行阶段:当开始执行代码时,会在这个阶段完成变量的赋值,函数的引用,以及其他代码的执行
JS 中 this 的情况
-
设计 this 的目的
- JavaScript 是允许在函数体内引用当前环境的其他变量的,那么问题来了,函数可以在不同的运行环境执行,所以我们就需要一种机制,能够在函数内获得当前运行环境,由此诞生了 this
- this 设计的目的就是指向函数运行时所在的环境
-
绑定 this 的情况
- 默认绑定
- 严格模式下,不能将全局对象 window 作为默认绑定,此时 this 会绑定到 undefined
- 非严格模式下,此时 this 默认指向全局对象 window
- 隐式绑定
- 当函数作为对象的属性存在,通过对象属性执行函数时,此时隐式绑定规则会将 this 绑定到对象上
- 显式绑定
- 通过
call / apply / bind
绑定,该方法能改变 this 指向
- 通过
- 通过 new 绑定
- 在使用 new 调用构造函数后,会构造一个新对象并将函数调用中的 this 绑定到新对象上
- 默认绑定
箭头函数、普通函数
-
普通函数
- 普通函数的 this 指向调用它的人
- 普通函数的函数参数支持重命名
-
箭头函数
-
箭头函数没有自己的 this,因为它不创建自己的执行上下文
-
在箭头函数中,this 的值是由外部作用域决定的
-
箭头函数不能使用
call()
、apply()
或bind()
方法来改变 this 的指向 -
因为箭头函数没有 prototype (原型),因此它不能用于构造函数
-
箭头函数不能使用 new
-
箭头函数不能使用
yield
关键字 -
箭头函数不绑定
arguments
,可以使用...args
代替 -
箭头函数不支持重命名函数参数
-
不适合箭头函数的场景
-
对象方法
-
因为箭头函数没有自己的
this
绑定,会继承外层作用域的this
,可能导致意外行为 -
const obj = { name: 'Alice', sayHello: () => { console.log(`Hello, ${this.name}`); // this.name 会是外层作用域的值,而不是 obj 的值 } };
-
-
动态上下文的回调函数
-
在需要动态绑定
this
的情况下,例如在事件处理函数中,箭头函数无法绑定正确的this
。 -
const button = document.querySelector('#myButton'); button.addEventListener('click', () => { console.log(this); // this 不会是 button 元素 });
-
-
vue的生命周期和
method
-
对象原型
-
构造函数
call、apply、bind
-
call
:一个一个传参,call 比 apply 性能好点,用于改变 this 指向,立即执行 -
apply
:把所有参数用数组形式传参,用于改变 this 指向,立即执行 -
bind
:一个一个传参,用于改变 this 指向,只是预先处理函数,并不会立即执行,因为 bind 返回的是一个函数,执行需要加括号()
let arr = [10,20,30]
let obj = {}
function fn(x,y,z){}
fn.call(obj, ...arr) //基于ES6的展开运算符,可以展开依次传递给函数
fn.apply(obj, arr) //x,y,z分别为10 20 30
JS 为什么是单线程
- JavaScript 的主要用途是用户交互和操作 DOM,
- 线程之间资源共享,相互影响,
- 如果 JavaScript 同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时这两个节点会有很大冲突,
- 为了避免这个冲突,所以决定了它只能是单线程,否则会带来很复杂的同步问题
JS 任务划分
- 前言:JavaScript 是单线程的,通过事件循环(Event Loop)机制实现异步
- js 任务划分如下:
- 同步任务
- 异步任务
- 宏任务
- 微任务
同步任务、异步任务
- 同步任务
- 阻塞模式
- 又叫做非耗时任务,指的是在主线程排队执行的任务
- 按照顺序执行的任务,只有前一个任务执行完毕,后一个才可执行
- 异步任务
- 非阻塞模式
- 又叫做耗时任务,异步任务由 JavaScript 委托给宿主环境(浏览器、node)进行执行
- 不按照顺序执行的任务,每个任务可以独立执行,不需要等待前一个任务完成
宏任务、微任务
- 宏任务(macrotask):
- 宏任务是指需要排队等待 JavaScript 引擎空闲时才能执行的任务
- 宏任务可以由各种事件触发
- 如:
setTimeout
、setInterval
、setImmediate
、new Promise
、I/O
、DOM 事件
- 微任务(microtask):
- 微任务是指在当前任务执行结束后立即执行的任务
- 微任务通常由 JS 引擎本身创建和调度的
- 如:Promise 回调(
.then
、.catch
、.finally
)、process.nextTick
、MutationObserver
、Object.observe
事件循环(Event Loop)及其执行顺序
- 什么是事件循环机制?
- js 会把所有代码分为两类:同步任务、异步任务
- 同步任务都在 JavaScript 主线程上顺序执行,形成一个执行栈
- 异步任务委托给宿主环境(浏览器、node)去执行
- 已完成任务的异步任务对应的回调函数,会被加入到任务队列中等待执行
- JavaScript主线程上的执行栈被清空后,主线程从任务队列中读取异步函数的回调函数,放到执行栈中顺序执行
- 主线程不断重复上面读取再执行的过程的步骤,这就是 Event Loop 事件循环
- 执行顺序
- 同步任务 --> 微任务 --> 宏任务
setTimeout(function() { // 异步任务-宏任务
console.log('setTimeout')
},0)
console.log('script start') // 直接执行
async function async1() { // 函数,调用才执行
console.log('async1 start'); // await外的,直接执行
await async2() // await内的:异步任务-微任务
console.log('async1 end') // await外的,直接执行
}
async function async2() {
console.log('async2 end')
}
// Promise 构造函数是同步执行的
// then 方法是异步执行的
// 注意: new Promise 是会进入到主线程中立刻执行,而 promise.then 则属于微任务
new Promise(function(resolve) { // new Promise 异步任务-宏任务
console.log('promise') // 直接执行
for (var i = 0; i <10000; i++) {
if (i === 10) {
console.log("3")
}
i == 9999 && resolve('4') // 直接执行
}
resolve();
}).then(function(val) { // 第一个.then为异步任务-微任务
console.log(val)
console.log('promise1')
}).then(function(res) { // 第二个.then 由于第一个没有resolve,所以参数res为 undefined
console.log(res)
console.log('promise2')
})
async1(); // 调用
console.log('script end')
// ===== 输出结果 =====
// script start
// promise
// 3
// async1 start
// async2 end
// script end
// 4
// promise1
// async1 end
// undefined
// promise2
// setTimeout
promise 的理解
-
抽象表达
- promise 是 js 中进行异步编程的新的解决方案
-
具体表达
- 从语法上来说:Promise 是一个构造函数
- 从功能上来说:Promise 对象用来封装一个异步操作并可以获取其结果
-
promise 的状态改变(只有2种,只能改变一次)
- pending 变为 resolved
- pending 变为 rejected
-
promise 基本流程
- 构建 Promise 对象时,需要传入一个 executor 函数,主要业务流程都在 executor 函数中执行
- Promise 构造函数执行时立即调用 executor 函数,resolve 和 reject 两个函数作为参数传递给 executor,resolve 和 reject 函数被调用时,分别将promise 的状态改为 fulfilled (完成)或者 rejected (失败)。一旦状态改变,就不会再变,任何时候都可以得到这个结果
- 在 executor 函数中调用 resolve 函数后,会触发 promise.then 设置的回调函数,而调用 reject 函数后,会触发 promise.catch 设置的回调函数
-
为什么要用 promise
- 指定回调函数的方式更加灵活:可以在请求发出甚至结束后指定回调函数
- 支持链式调用,可以解决回调地狱问题
-
promise 方法
Promise.resolve
(value):创建一个新的 Promise 对象,并立即将其状态设置为 resolved,同时将给定的值传递给后续的 then 方法中的 onFulfilled 函数Promise.reject
(error):创建一个新的 Promise 对象,并立即将其状态设置为 rejected,同时将给定的错误对象传递给后续的 then 方法中的 onRejected 函数Promise.then
(onFulfilled, onRejected):注册异步操作成功或失败时的回调函数。onFulfilled 函数在 Promise 对象状态变为 fulfilled 时执行,而 onRejected 函数在 Promise 对象状态变为 rejected 时执行Promise.catch
(onRejected):等同于 Promise.then(undefined, onRejected),用于注册当 Promise 对象状态变为 rejected 时执行的回调函数Promise.all
(promises):同时处理多个 Promise 对象,当所有 Promise 对象都成功或失败时,返回一个新的 Promise 对象Promise.race
(promises):与 Promise.all 类似,但只返回第一个成功或失败的 Promise 对象Promise.any
(promises):用于处理多个 Promise 对象,只要有一个成功,就返回一个成功的 Promise 对象,并包含所有成功或失败的 Promise 对象
-
Promise 构造函数是同步执行还是异步执行,then方法呢?
-
Promise 构造函数是同步执行的
-
then 方法是异步执行的
-
注意:
new Promise
是会进入到主线程中立刻执行,而promise.then
则属于微任务// 语法 new Promise(function(resolve,reject){}) // 例如 new Promise(resolve => { console.log(1); resolve(3); }).then(num => { console.log(num); }); console.log(2); // 输出:123
-
async/await 的理解
- async/await 如何通过同步的方式实现异步?
async/await
是 Generator 的语法糖,就是一个自执行的 generate 函数。利用 generate 函数的特性把异步的代码写成"同步"的形式- async 是异步的意思,await 则可以理解为 async wait,所以可以理解 async 就是用来声明一个异步方法,而 await 是用来等待异步方法执行
- await 关键词只能用在 async 定义的函数内
- 来源
- 首先,js是单线程的
- 所谓单线程,就是:执行代码是一行一行的往下走,即所谓的同步,如果上面的没执行完,那就只能等着实现异步的核心回调钩子,就是 callback 之前的函数嵌套,大量的回调函数,使代码阅读起来晦涩难懂,不直观,形象的称之为回调地狱
- 为力求在写法上简洁明了,可读性强,ES6+ 陆续出现了 Promise、Generator、async/await
- async/await 是参考 Generator 封装的一套异步处理方案,可以理解为 Generator 的语法糖,而 Generator 又依赖于迭代器 Iterator,而 Iterator 的思想又来源于单向列表,故可以理解为源头是单向列表
async/await 与 promise 的关联
- async/await 是写异步代码的新方式,此之前的方法有回调函数和promise
- async/await 是基于promise 实现的,它不能用于普通的回调函数
- async/await 与 promise 一样,是非阻塞的
- async/await 使得异步代码看起来像同步代码,这正是它的魔力所在
- async 函数会隐式地返回一个 promise,该 promise 的 resolve 值就是函数的 return 值
为什么 async/await 比 promise 好
-
代码简洁
- 使用 async 函数可以让代码简洁很多,不需要像 promise 一样需要些 then,不需要写匿名函数处理 promise 的 resolve 值,也不需要定义多余的data变量,还避免了嵌套代码
-
错误捕获
- async/await 让 try/catch 可以同时处理同步和异步错误
- promise 示例中,try/catch 不能处理 JSON.parse 错误
- async/await 示例,catch 能处理 JSON.parse 错误
-
中间值
-
场景:调用 promise1,使用 promise1 返回的结果去调用 promise2,然后使用两者的结果去调用 promise3
const makeRequest = () => { return promise1().then(value1 => { return Promise.all([value1, promise2(value1)]) }).then(([value1, value2]) => { return promise3(value1, value2) }) }
-
使用async/await通过同步的方式实现异步,代码会变得异常简单和直观
const makeRequest = async () => { const value1 = await promise1() const value2 = await promise2(value1) return promise3(value1, value2) }
-
-
错误栈
- 如果 promise 连续调用,对于错误的处理是很麻烦的。你很难知道错误出在哪里
- async/await 中的错误栈会指向错误所在的函数
-
调试
- async/await能够使得代码调试更简单,你不再需要那么多箭头函数,这样你就可以像调试同步代码一样跳过 await 语句
- promise 不能在返回表达式的箭头函数中设置断点
- promise 调试时,如果你在 .then 代码块中设置断点,使用 Step Over 快捷键,调试器不会跳到下一个 .then,因为它只会跳过异步代码
ajax 的理解
-
什么是 ajax
- AJAX 全称 Async Javascript and XML,即异步的 JavaScript 和 XML
- 用于快速创建动态页面,实现无刷新更新数据
- ajax 可以在不重新加载整个网页的情况下,与服务器交换数据,并且更新部分网页内容
- 通过 XMLHttpRequest 对象来向服务器发异步请求,从服务器获得数据,然后用 JavaScript 来操作 DOM 而更新页面
-
优点
- 无刷新更新数据
- 异步与服务器通信
- 前后端负载平衡
- 界面与应用分离
-
缺点
- 安全问题:ajax 暴露了与服务器交互的细节
- 回调地狱问题:多个请求之前如果有先后关系,就会出现回调地狱
- 对搜索引擎的支持比较弱,不利于SEO
- ajax 不能使用 back 和 history 功能,即对浏览器的破坏
- 破坏程序的异常处理机制
- 违背 URL 和资源定位的初衷
- 不能很好的支持移动设备
-
适用场景
- 表单驱动的交互
- 深层次的树的导航
- 对数据进行过滤和操纵相关数据的场景
- 类似投票、yes/no等无关痛痒的场景
-
实现 ajax 异步交互的步骤
- 创建 ajax 的核心对象 XMLHttpRequest
- 通过 XMLHttpRequest 对象的 open() 方法与服务器建立连接
- 构建请求所需的数据内容,并通过 XMLHttpRequest 对象的 send() 方法发送给服务器端
- 通过 XMLHttpRequest 对象提供的 onreadystatechange 事件监听服务器端通信状态
- 接收并处理服务端向客户端响应的数据结果
- 将处理结果更新到 HTML 页面中
// 1、创建 XMLHttpRequest 对象 let xhr = new XMLHttpRequest(); // 2、与服务器建立连接 xhr.open(method, url); // 可以设置请求头,一般不设置 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); // 3、发送请求 xhr.send() // 4、接收响应 xhr.onreadystatechange = function () { if(xhr.readyState == 4 && xhr.status == 200){ const text = xhr.responseText; console.log(text); } } //xhr.responseXML 接收 xml 格式的响应数据 //xhr.responseText 接收文本格式的响应数据
axios 的理解
- 什么是 axios
- 基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中
- 具有强大的拦截器功能,支持请求和响应的拦截处理,同时提供了丰富的配置选项
- 实现前后端的数据交互
- axios 的特性
- 自动转换成 JSON 数据格式
- 客户端支持防御 XSRF/CSRF 跨站请求伪造
- 支持 promise
- 在浏览器中创建 XMLHttpRequests
ajax 和 axios 的区别
- axios 是通过 promise 实现对 ajax 技术的一种封装
- axios 是 ajax,ajax 不止 axios
事件流
- 事件流指的是事件完整执行过程中的流动路径
- 也就是说,事件流是网页中元素接受事件的顺序
- 事件流,即事件发生的顺序
- 事件流分为两个阶段:事件冒泡阶段、事件捕获阶段
事件传播
-
事件传播是一种机制,定义事件如何传播或穿过 DOM 树到达其目标以及随后会发生什么
-
事件传播的三个阶段:事件捕获阶段、事件目标阶段、事件冒泡阶段
- 事件捕获:事件从祖先元素往子元素查找(DOM树结构),直到捕获到事件目标 target。在这个过程中,默认情况下,事件相应的监听函数是不会被触发的。
- 事件目标:当到达目标元素之后,执行目标元素该事件相应的处理函数。如果没有绑定监听函数,那就不执行。
- 事件冒泡:事件从事件目标 target 开始,从子元素往冒泡祖先元素冒泡,直到页面的最上一级标签。
事件捕获
- 从上至下(从父到子)
- 当触发子元素身上的事件时,先触发父元素,然后在传递给子元素,直至到达事件的目标节点为止
- window ----> document ----> html ----> body ----> 目标元素
事件冒泡
- 从下至上(从子到父)
- 事件从目标事件起始,沿着父节点依次向上,且触发父节点上的事件,直至文档根节点window
- 当前元素 ----> body ----> html ----> document ----> window
事件委托
- 事件委托,通俗来说是将元素的事件委托给它的父级或者更外级元素处理。
- 事件委托也叫事件代理,就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
- 事件委托就是利用事件冒泡机制实现的。
- 事件委托是一种技术,您无需将事件侦听器附加到各个元素,而是将单个事件侦听器附加到将为其子元素处理事件的父元素。
- 当您有大量元素或动态添加元素时,此方法非常有用,因为它可以提高性能并减少内存消耗。
变量提升
-
变量提升是指在代码执行过程中,JavaScript 引擎会将变量声明提升到当前作用域的顶部,
-
而不管这些变量的声明语句是在作用域的哪里进行的。
-
这意味着可以在变量声明之前就使用这个变量,而不会报错(即:先使用后声明)
-
注意:变量声明会被提升,但赋值操作不会被提升
console.log(myVar); // 输出 undefined var myVar = 10;
var、let、const 的区别
- 类型不同
- var 和 let 都是变量
- const 是常量
- 作用域不同
- var 是全局变量,可以跨快访问,但是不能跨函数访问
- let 和 const 是块级作用域,存在暂时性死区
- 变量提升
- var 存在变量提升
- let 和 const 不存在变量提升
- 重复声明
- var 允许重复声明,允许在声明之前调用,值为 undefined
- let 和 const 不允许重复声明,先声明后使用,否则报错
节流、防抖
- 节流(Throttle)
- 将多次执行变为每隔一段时间执行
- 当持续触发事件时,保证在一定时间内只调用第一次事件处理函数
- 应用场景:防止用户连续频繁的点击事件
- 例子:提交按钮、王者荣耀技能释放的cd
- 防抖(Debounce)
- 将多次执行变为最后一次执行
- 在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时
- 应用场景:用户在输入框中连续输入一串字符后,只会在输入完成后去执行最后一次查询的ajax请求
- 例子:王者荣耀回城
cookie、sessionStorage、localStorage
- 三者区别
- 存储大小
- cookie 数据大小不能超过4k
- sessionStorage 和 localStorage 虽然也有存储大小的限制,但比 cookie 大得多,可以达到 5M 或更大
- 有效时间
- cookie 设置的过期时间之前一直有效,即使窗口或浏览器关闭
- sessionStorage 数据在当前会话下有效,关闭页面或浏览器窗口会自动删除
- localStorage 存储持久数据,浏览器关闭后数据不丢失除非主动删除数据
- 与服务端的通信
- cookie 的数据会自动的传递到服务器,服务器端也可以写 cookie 到客户端
- sessionStorage 和 localStorage不会自动把数据发给服务器,仅在本地保存
- 数据共享
- cookie 和 localStorage 相同浏览器不同页面可共享(同源页面)
- sessionStorage 相同浏览器同一个页面可共享,不同页面不可共享(同源页面)
- 存储大小
- 应用场景
- 标记用户与跟踪用户行为的情况,推荐使用 cookie
- 适合长期保存在本地的数据(令牌),推荐使用 localStorage
- 敏感账号一次性登录,推荐使用 sessionStorage
- 存储大量数据的情况、在线文档(富文本编辑器)保存编辑历史的情况,推荐使用 indexedDB
加密算法
核心技术要点 | 优点 | 缺点 | 应用场景 | 常见类型 | |
---|---|---|---|---|---|
哈希函数 | 将任意长度的消息映射为固定长度的 hash 值 | 输出结果唯一确定,不可逆,速度快、存储体积小,可以帮助保护数据的完整性和减轻篡改风险 | 安全性不高、容易受到暴力破解 | 数字签名、密码存储、验证下载文件的完整性以及防篡改 | MD5、SHA |
消息认证码 | 消息认证码(MAC)是一个用于进行身份验证和保护消息完整性的算法 | 计算简单,安全性高 | 密钥传输难度大,在网络中传递容易被篡改 | 数据完整性校验、数字证书认证 | HMAC |
对称加密 | 加密和解密使用相同的密钥 | 效率高,算法简单,系统开销小,速度快,适合大数量级的加解密,安全性中等 | 秘钥管理比较难,密钥存在泄漏风险 | 适用于需要高速加解密的场景,适用于加密大量数据,如文件加密、通信加密、电子邮件、Web 聊天等 | AES、DES |
非对称加密 | 加密和解密使用两个不同的密钥(公钥和私钥) | 密钥传输便利,更安全 | 性能消耗高,速度慢 | 适合小数据量,如数字签名、身份认证、安全电子邮件 | RSA、DSA、DSS |
浅拷贝、深拷贝 的区别
- 浅拷贝
- 理解
- 只复制属性指向某个对象的指针,而不是复制对象本身,共享内存地址
- 修改对象属性会影响原对象
- 实现方法
- 直接赋值
Object.assign()
Array.prototype.slice()
Arrary.prototype.concat()
- 使用拓展运算符(…)实现的复制
- 理解
- 深拷贝
- 理解
- 开辟一个新的栈,两个对象属性完全相同,但是对应两个不同的地址
- 完全独立,互不影响
- 实现方法
- 手写循环递归
- lodash 插件的
_.cloneDeep()
方法 jQuery.extend()
JSON.stringify()
- 理解
使用 JSON.Stringify 深拷贝的缺陷
- 使用
JSON.stringify
序列化之后的数据,再JSON.parse
可能会丢失部分数据- 转换的数据会忽略包含 function,undefined,Symbol 类型的属性, 拷贝后的对象会丢失这些属性
- 无法序列化不可枚举属性
- 无法序列化对象的循环引用。如: obj[key] = obj
- 无法序列化对象的原型链
- 转换的数据中包含 NaN,Infinity 值,JSON序列化后的结果会是 null
- 转换的数据中包含 Date 对象,JSON.stringify 序列化之后,会变成字符串
- 转换的数据包含 RegExp 引用类型序列化之后会变成空对象
- 性能问题
- JSON.stringify 的性能相对较低,特别是在拷贝大型对象,或嵌套层次较深的对象时,会消耗大量的时间和内存
什么是闭包?
-
理解
-
定义在函数内部的函数
-
能够读取其他函数内部变量的函数
-
本质上,闭包是将函数内部和函数外部连接起来的桥梁
-
函数
A
内部有一个函数B
,函数B
可以访问到函数A
中的变量,那么函数B
就是闭包
-
-
使用闭包的主要原因
- 返回可以返回其他函数的函数
- 闭包通常用于数据隐私、封装和创建具有持久状态的函数
原型、原型链
-
原型
-
所有引用类型都有一个
__proto__
(隐式原型)属性,属性值是一个普通的对象 -
所有函数都有一个
prototype
(原型)属性,属性值是一个普通的对象 -
所有引用类型的
__proto__
属性指向它构造函数的prototype
let a = [1,2,3]; a.__proto__ === Array.prototype; // true
-
-
原型链
- 当访问一个对象的某个属性时,会先在这个对象本身属性上查找,
- 如果没有找到,则会去它的 proto 隐式原型上查找,即它的构造函数的 prototype,
- 如果还没有找到,就会在构造函数的 prototype 的 proto 中查找,一直往上查找,
- Object.prototype 是整个原型链的终点,其 proto 为 null,即
Object.prototype.proto === null
- 直到
null
还没有找到,则返回undefined
, - 这样一层一层向上查找就会形成一个链式结构,我们称为原型链。
垃圾回收机制(GC)
- 什么是垃圾回收机制
- 垃圾回收机制,即 Garbage Collection,简称 GC
- 程序的运行需要内存,工作过程众会产生垃圾,这些垃圾是程序不用的内存
- 在 JavaScript 中有自动执行的垃圾回收机制,通过一些回收算法,找出不再使用的变量或属性,由 JavaScript 引擎按固定时间间隔周期性的释放所占用的内存
- 垃圾回收策略
- 引用计数算法
- 跟踪记录每个变量值被使用的次数,如果为0,就销毁,回收其所占用的内存空间
- 标记清除法
- 为所有活动对象做上标记,把没有标记的(也就是非活动对象)销毁
- 分代回收法
- V8 中将堆内存分为新生代和老生代两区域,采用不同的策略管理垃圾回收
- 引用计数算法
内存泄漏(Memory Leak)
- 什么是内存泄漏
- 内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果
- 常见的内存泄露
- 不正当的闭包
- 隐式全局变量
- 游离DOM引用
- 定时器
- 事件监听器
- Map、Set 对象
- Console
- 解决方法
- 不需要的时候手动解除对变量的引用
- 在使用完将其置空 null
- 清除定时器
- 移除监听器
- 生产环境下及时清理 console
常见的前端内存问题
- 内存泄漏
- 内存膨胀
- 在短时间内,内存占用极速上升到达一个峰值
- 想要避免需要使用技术手段减少对内存的占用
- 频繁 GC
- 一般出现在频繁使用大的临时变量导致新生代空间被装满的速度极快,而每次新生代装满时就会触发 GC
- 频繁 GC 同样会导致页面卡顿,故不宜使用太多的临时变量
new 操作符做了什么
- 创建对象
- new 操作符会创建一个空对象,这个对象将成为函数的实例
- 分配内存空间
- new 操作符会在堆内存中分配一块足够大小的内存空间,用于存储对象的数据
- 继承
- new 操作符会将新对象的原型链接到构造函数的原型对象上,这样新对象就可以访问构造函数原型对象中定义的属性和方法
- 构造函数的执行
- new 操作符会执行构造函数中的代码,用于初始化新对象的属性
- 错误处理
- 如果构造函数中没有返回其他对象,那么 new 操作符将返回新创建的对象实例。否则,它会返回构造函数中返回的对象
new 操作符具体干了什么呢
- 创建一个空对象,并且
this
变量引用该对象,同时还继承了该函数的原型 - 属性和方法被加入到
this
引用的对象中 - 新创建的对象由
this
所引用,并且最后隐式的返回this
如何实现一个 new
- 创建一个新的空对象。
- 将新对象的原型链接到构造函数的原型对象。
- 将构造函数的作用域赋给新对象,以便在构造函数中使用
this
引用新对象。 - 执行构造函数,并将参数传递给构造函数。
- 如果构造函数没有显式返回一个对象,则返回新对象。
function myNew(constructor, ...args) {
// 创建一个新的空对象
const newObj = {};
// 将新对象的原型链接到构造函数的原型对象
Object.setPrototypeOf(newObj, constructor.prototype);
// 将构造函数的作用域赋给新对象,并执行构造函数
const result = constructor.apply(newObj, args);
// 如果构造函数有显式返回一个对象,则返回该对象;否则返回新对象
return typeof result === 'object' && result !== null ? result : newObj;
}
定义对象的方法有哪些
- 对象字面量:
var obj = {};
原型是Object.prototype
- 构造函数:
var obj = new Object();
Object.create()
:var obj = Object.create(Object.prototype);
Object.create(null)
没有原型Object.create({...})
可指定原型
function Person(){}、var person = Person() 、var person = new Person() 的区别
- function Person(){} 正在声明一个函数
- var person = Person() 将 Person 函数的引用分配给 person 变量
- var person = new Person() 使用“new”关键字创建 Person 类的新实例
JavaScript 交互问题解决方案
- 动态数据加载:使用 AJAX 技术,通过 JavaScript 发送异步请求,从服务器获取数据并动态更新页面内容。
- 表单验证:使用 JavaScript 编写表单验证逻辑,实时检查用户输入是否符合要求,并给出相应的提示。
- 动画效果:使用 JavaScript 和 CSS 实现各种动画效果,如滑动、淡入淡出等,增强用户交互体验。
- 响应式设计:使用 JavaScript 监听浏览器窗口大小变化,根据不同的设备尺寸和分辨率调整页面布局和样式。
- 单页面应用(SPA):使用 JavaScript 框架(如React、Angular、Vue等)构建单页面应用,实现页面无刷新加载和路由控制。
- 数据可视化:使用 JavaScript 图表库(如D3.js、Chart.js等)将数据可视化展示,以更直观地呈现信息。
四、TypeScript
TypeScript(TS)
-
TypeScript 是一种静态类型的 JavaScript 超集,它为 JavaScript 添加了类型注解和其他扩展功能。
-
以下是TypeScript的基础语法:
- 数据类型。TypeScript支持多种数据类型,包括原始类型(如数字、字符串、布尔值、空(null)、未定义(undefined)、Symbol(符号)、BigInt(大整数))和对象类型。
- 变量声明。使用let或const关键字声明变量,并可选择指定类型。例如,
let name:string='John';
表示一个字符串类型的变量name,const age:number=25;
表示一个数字类型的常量age。 - 函数声明。可以使用箭头函数(=>)或function关键字来声明函数,并可以指定参数类型和返回值类型。例如,
const add = (a: number,b: number):number=>{ return a + b; }
声明了一个接受两个数字参数并返回数字的函数。 - 接口。使用接口定义对象的结构和类型。例如,
interface Person{ name: string; age: number; }
定义了一个包含名字和年龄属性的对象结构。 - 类。使用class关键字定义类,并可以定义属性和方法。
- 数组。使用数组类型加上元素类型声明一个数组。例如,
const numbers: number[] = ;
声明了一个包含数字的数组。 - 类型推断。TypeScript会根据上下文自动推断类型,不需要手动指定。
-
这些是TypeScript的基本语法元素。TypeScript还支持更多高级功能,如泛型、枚举、命名空间等。
TS 类型有哪些
- 内置:数字、字符串、布尔值、void、null、undefined
- 用户定义:枚举、类、接口、数组、元组
any、never、unknown、null & undefined、void有什么区别?
any
:动态的变量类型(失去类型检查的作用)never
:永不存在的值的类型。例如:never类型是那些总是会抛出异常 或 根本不会有返回值的函数表达式unknown
:任何类型的值都能赋给unknown类型,但unknown类型的值只能赋给unknown本身 和 any类型null & undefined
:默认下它们是所有类型的子类型void
:没有任何类型。例如:一个函数没有返回值,就可以把返回值定义为null
如何将 unknown 指定为一个具体的类型
- 使用typeof进行类型判断
- 对unknown使用类型断言,注意的是:断言错了语法能通过,但运行会报错
const和readonly的区别
const
防止变量的值被修改readonly
防止变量的属性被修改
keyof 和 typeof 关键字的作用
-
keyof
:获取索引类型的属性名,构成联合类型 -
typeof
:获取一个变量或对象的类型
访问修饰符有哪些
public
:类的所有成员、子类、类的实例都能访问protected
:类及子类的所有成员能访问private
:只有类的成员能访问
如果未指定访问修饰符,则它是隐式公共,这符合TS的便利性。
方法重写
子类具有和父类中声明相同的方法,就是方法覆盖,换句话说,在派生类或子类重新定义基类方法
- 方法要与父类名称一样
- 参数要相同
- 必须有继承
TS 模块的加载机制
假设有一个导入语句:import { a } from “moduleA”
- 编译器通过绝对或相对路径,定位到需要导入的模块文件
- 如果上面解析失败了,没有查找到对应的模块,编译器会尝试定位一个外部模块声明
- 最后,还是不能解析这个模块,就直接抛出一个错误
对 TS 兼容性的理解
- 类型兼容:Y 可以赋值给另一个类型 X,就说 X 兼容类型 Y
- 接口兼容:X=Y 只要目标类型X中声明的属性变量在源类型Y中都存在就是兼容的
- 函数兼容:X=Y Y的每个参数必须都在X里找到对应类型的参数,参数名字无所谓,只看类型