1. js基础
1.1 数据类型分类
- 基本类型
- Number: 数字型
- String: 字符串
- Boolean: true/false
- Null
- Undefined
- Symbol (es6新增 用于对象的唯一属性名)
- BigIht (ES2020新增 用于定义特大数值)
- 引用类型 (对象类型)
- Object: 任意对象 (内部数据 无序)
- Array: 数组类型 (内部数据 有序)
- Function: 函数 (可以执行)
1.2 判断数据类型
===
- === 只能判断 undefined与null
typeof
- typeof操作符能判断 number/string/boolean/undefined/function 类型
- 不能判断出null 不能区别数组和对象 返回的数据都是object
instanceof
- 能判断出对象的类型或原型链对象的类型
- 能区别object和array
obj.constructor
- 得到对象的构造函数
Array.isArray()
- 专门判断是否是数组
String.prototype.toString.call(obj)
- 得到构造函数的名称
// 手写instanceof
function newInstanceof (obj,type) {
var protoObj = obj.__proto__
while(protoObj){
if(protoObj === Type.__proto__) {
return true
}
protoObj = protoObj.__proto__
}
return false
}
1.3 数组常用方法
- 更新数组:
- 增: push / unshift
- 减:pop / shift
- 其他:sort / splice / reverse
- 遍历数组:
- map / forEach / reduce / filter / every / some / find / findIndex
- 其他:
- concat / join / slice / includes / indexOf
1.4 作用域和作用域链
1. 作用域
- 理解:
- 一个变量的作用范围 作用: 隔离变量
- 作用域是编写代码时就确定的 并不是在执行时确定
- 作用域分类:
- 全局作用域
- 函数作用域
- 块级作用域
- 用let const声明
- 块: if / while / for 的{}
2. 作用域链
作用域链就是函数逐级向上查找需要变量的过程
如果在当前的作用域没有找到需要的变量 那么就会逐级向上寻找 直到查找到全局作用域
1.5 变量提升和函数提升
变量提升和函数提升只有用var声明才会产生 用let和const 并不会产生变量提升和函数提升 发生变量提升和函数提升的原因:
- 执行全局代码和函数前会进行
预解析
1. 变量提升
- 变量声明语句会提升到当前作用域的最前面执行
- 在变量声明语句之前, 就可以访问到这个变量(undefined)
- 严格模式下访问会报错
函数提升
- 函数声明语句提升到当前作用域的最前面执行
- 在函数声明语句之前, 就可以执行该函数
语句和表达式的区别
-
语句:
做特定操作的一段可以独立存在的代码, 它并不返回可用的数据 每条语句最终都以分号结尾, 但编码时可以省略 -
表达式: (常量值/变量/常量值与变量运算/函数调用) 总是返回一个可以使用的数据值 它不能独立存在 没有分号
如何区分函数定义和执行函数
1.6 this指向
this是一个关键字 在函数内部 函数的调用方式决定了this指向 this在执行期间不能被赋值 每次函数的调用方式不同,this的指向也不同
- this指向:
普通函数/嵌套函数: 指向window
方法调用: 指向调用方法的对象
构造函数: 指向this的实例化对象
事件的回调函数: this指向的事件源
箭头函数 (es6)新增 : 没有this指向 this指向外层的上下文
call / bind / apply 会改变this指向
- 如何改变this指向
call / bind / apply 三个方法
这三个方法的异同:
call和bind的参数可以是无数个 第一个参数是改变的this指向 后面的参数都是形参
apply只能有两个参数 第一个参数是this的指向 第二个参数是参数数组
call和apply 会立即执行函数 bind 会返回一个函数 需要手动调用一次
- 严格模式下
所有函数中指向window的this, 都变为undefined 定义变量时, 不能省略var
1.7 闭包
- 问题1: 如何产生闭包
- 函数嵌套
- 外部函数引用内部函数的变量
- 调用外部函数
- 问题2: 闭包是什么
- 闭包一个引用关系 这个关系存在于内部函数 在内部函数使用外部函数变量的对象
- 问题3: 闭包的作用
- 延长局部变量的生命周期
- 让外部函数可以使用内部函数里的变量
- 问题4: 闭包的应用
- IIDE(立即执行函数)
- 函数柯里化
1.7 原型和原型链相关
- 原型
每个构造函数都有自己的显示原型
prototype
每个实例都有自己的隐式原型__proto__
实例的__proto__
与对应函数的prototype
都指向同一个原型对象
原型对象通过constructor
可以访问到构造函数
- 原型链
从对象的
__proto__
开始, 连接的所有对象, 就是我们常说的原型链, 也可称为隐式原型链
查找对象属性简单说: 先在自身上查找, 找不到就沿着原型链查找,如果还找不到返回undefined
- 查找属性的流程
先在对象自身上查找, 如果有, 直接返回
如果没有, 根据__proto__
在原型对象上查找, 如果有, 直接返回
如果没有根据原型对象的__proto__
在原型对象的原型对象上查找, 一直查找到Object原型对象为止
如果找到了返回, 如果查找不到由于它的__proto__
为null, 只能返回undefined
- instanceof
作用: 判断一个对象类型 对于 A instanceof B
A是实例对象, B是构造函数
如果B的prototype属性所指向的原型对象是A对象的原型链上的某个对象, 返回true, 否则返回false
- 原型链详图
1.8 编码实现继承
方式1 原型链 + 构造函数
// 父类型
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHello = function () {
console.log(`我叫${this.name}, 年方${this.age}`)
}
// 子类型
function Student(name, age, price) {
// 借用父类型的构造函数
Person.call(this, name, age) // 相当于执行this.Person(name, age)
this.price = price
}
// 让子类的原型为父类的实例
Student.prototype = new Person()
// 让原型对象的构造器为子类型
Student.prototype.constructor = Student
方式2 class类 + surper关键字
// 父类
class Person2 {
constructor (name, age) {
this.name = name
this.age = age
}
fn () {}
sayHello () {
console.log(`我叫${this.name}, 年方${this.age}`)
}
}
// 子类 extends 父类: class Teacher extends Person2
class Teacher extends Person2 {
constructor (name, age, course) {
super(name, age) // 继承父类
this.course = course
}
sayHello () {
console.log(`我叫${this.name}, 年方${this.age}, 课程:${this.course}`)
}
}
1.10 面向对象的3大特性
-
封装:
- 将可复用的代码用一个结构包装起来, 后面可以反复使用
- js的哪些语法体现了封装性: 函数 ==> 对象 ==> 模块 ==> 组件 ==> 库
- 封装都要有个特点: 不需要外部看到的必须隐藏起来, 只向外部暴露想让外部使用的功能或数据
-
继承
- 为什么要有继承? 复用代码, 从而减少编码
- js中的继承都是基于原型的继承: ES6的类本质也是
- 编码实现: 原型链+借用构造函数的组合 / ES6的类继承
-
多态: 多种形态
- 理解
- 声明时指定一个类型对象, 并调用其方法,
- 实际使用时可以指定任意子类型对象, 运行的方法就是当前子类型对象的重写的方法
- 产生多态的条件: 1. 继承 2. 方法重写: 子类将从父类继承来的方法重新实现
- 理解
1.11 同步&异步
区分同步和异步
- 同步
从上往下按顺序依次执行, 只有将这个任务完全执行完后, 才执行后面的
会阻塞后面的代码执行
同步回调: 只有执行完回调函数后任务的函数才结束
- 异步
启动任务后, 立即向下继续执行, 等同步代码执行完后才执行回调函数
不会阻塞后面的代码执行
异步回调函数即使触发了, 也是要先放入队列中待执行, 只有当同步任务或前面的异步任务执行完才执行
事件循环机制 (event loop)
-
js事件执行的特点
- js是单线程语言 内部主要通过事件循环来实现单线程异步执行的
- 所有的任务(同步/异步)都在主线程上执行, 形成一个执行链
- 执行链之外有用于存储异步任务的任务队列 (准确的说是两个 分别是宏任务队列和微任务队列)
-
js执行代码顺序为
- 在执行链中执行初始化同步代码
- 执行过程中如果有异步任务,则交给对应的管理模块处理, 管理模块会有特定的事件将回调函数放到任务队列中执行
- 执行链中所有代码都执行完毕后, 会依次取出任务队列中的回调函数放到执行链中依次执行
-
宏任务
- script标签(整体代码)
- SetTimeout/SetInterval
- Ajax请求
- DOM事件监听
- postMessage(H5新增,向其他窗口发送异步消息)
- setImmediate(Node.js环境)
-
微任务
- Promise.then/Prmose.catch (Promise本身是同步, then 和 catch的回调是异步)
- Async/Await
- process.nextTick(node环境)
- Object.observe
总结 : 整体代码的执行顺序
- script标签
- 所有微队列里的微任务
- 宏队列中的第一个宏任务
- 所有微队列里的微任务
3-4一直循环
promise
promise的理解
promise是ES6推出的解决异步编程的方案
- 解决ES5嵌套回调的回调地狱问题 -> 通过promise.then的链式调用
- promise对象的三种状态:
- pending (初始状态 未发生变化)
- resolved/fulfilled (成功的回调)
- rejected (失败的回调)
- promise对象状态变化是不可逆的 只能改变一次
promise的实例方法
- then方法
- then()返回一个新的promise 也是能够链式调用的原因
- 新promise的结果状态由then指定的回调函数执行的结果决定
- then是实例状态发生改变时的回调函数
- catch方法
- 当出现异常 则需要catch方法进行捕获
- finally方法
- 用于指定不管 Promise 对象最后状态如何,都会执行的操作
promise的静态方法
- resolve(value)
- reject(reason)
- all(promises)
- 项目中:一次性发起多个请求
- 批量/一次性发送多个异步请求
- 当都成功时, 返回的promise才成功
- 一旦有一个失败的, 返回的promise就失败了
- race(promises)
- allSettled(promises)
async/await
- async与await是异步编程的终极解决方案 => 消灭回调函数
- 作用: 简化promise的使用, 不用再使用then/catch来指定回调函数
- 执行async函数, 返回promise对象
- await相当于promise的then的成功回调
- try...catch可捕获异常, 相当于promise的catch
1.12 内存管理
内存生命周期
声明变量-> 计算变量 -> 变量赋值 -> 使用变量 -> 销毁变量
内存泄漏
- 内存泄漏的几种情况:
- 意外的全局变量
- 没有及时清除的定时器
- 没有及时清除的监听
- 没有及时释放的闭包
内存溢出
- 运行程序需要分配的内存超过了系统能给你分配的最大剩余内存, 抛出内存溢出的错误,程序中断运行
- 内存泄漏容易造成内存泄漏
垃圾回收机制
是浏览器中的一个线程,每隔一段时间运行一次,判断变量是否能被找到,如果找不到,那么会销毁
- 判断变量的方法: 1.引用记数法 2.标记清除法
2. ES6
2.1 const / let /var
- 相对于var, let和const
- 有块作用域
- 没有变量提升
- 不会添加到window上
- 不能重复声明
- let和const 比较
- let 声明的变量可以改变
- const 明的变量不可以改变
2.2 解构赋值
- 解构对象:
const {id, name} = this.product
- 解构数组:
const [count, setCount] = useState()
- 形参解构:
add ({ id, title }) {} add({id, title})
- 引入模块解构:
import { getProductList } from '@/api'
2.3 各类语法扩展
字符串扩展
- 模板字符串:
我是${name}, 今年${age}
- 方法:
includes() / startsWith() / endswith()
数值的扩展
- 完善二进制(0b)与八进制(0o)表示
- 给Math添加方法: parseInt()与parseFloat() (原本window上有)
- 指数计算: **
函数的扩展
- 箭头函数
- 没有自己的this, 使用外部作用域中的this, 不能通过bind来绑定this
- 不能通过new来创建实例对象
- 内部没有arguments, 可以通过rest参数来代替
- 形参默认值: fn (a=2, b={}) {}
数组的扩展
- 扩展运算符
- 浅拷贝数组: const arr2 = [...arr]
- 合并多个数组: const arr3 = [...arr1, ...arr2]
- 静态方法
- Array.from(): 将类数组对象和可遍历对象转为真数组
- Array.from(new Set(arr)) new Set([1, 3, 2, 3])
- [...new Set(arr)]
- Array.of(1, 2, 3): 将一组值,转换为数组 [1, 2, 3]
- Array.from(): 将类数组对象和可遍历对象转为真数组
- 实例方法
- find() / findIndex(): 查找匹配的元素或下标 map/reduce/filter/every/some/find/splice/slice
- arr.flat(): 将多维数组转为一维数组(也称为: 数组扁平化) [1, [2, 3]] ==> [1, 2, 3]
对象的扩展
- 扩展运算符
- 浅拷贝对象: const obj2 = {...obj1}
- 合并多个对象: const obj3 = {...obj1, ...obj2}
- 属性/方法的简洁写法: {name, getName() {}}
- 静态方法:
- Object.is(value1, value2): 判断2个值是否完全一样 NaN===NaN
- Object.assign(target, ...sources): 将后面任意多个对象合并到target对象上
模块化语法
- export
- export default value
- import: 静态导入, 合并一起打包
- import(): 动态导入, 拆分打包, 用于懒加载
const Home = () => import('./views/Home.vue') {path:'/xxx', component: Home}
button.onclick = () => { import('./views/test.js').then(module => { module.default module.xxx }) }
异步语法
- Promise
- async 函数
- await 表达式
类语法
- class
- extends
- constructor
- super() / super.xxx()
- static
3. Web API总结
BOM 浏览器对象模型 把浏览器当做对象看待 DOM 文档对象模型 主要是操作页面的元素
3.1 BOM
window
关于窗口控制的方法:
moveBy(x,y)
从当前位置水平移动窗体x个像素,垂直移动窗体y个像素moveTo(x,y)
移动窗体左上角到相对于屏幕左上角的(x,y)点resizeBy(w,h)
:相对窗体当前的大小,宽度调整w个像素,高度调整h个像素。resizeTo(w,h)
:把窗体宽度调整为w个像素,高度调整为h个像素scrollTo(x,y)
:如果有滚动条,将横向滚动条移动到相对于窗体宽度为x个像素的位置,将纵向滚动条移动到相对于窗体高度为y个像素的位置scrollBy(x,y)
: 如果有滚动条,将横向滚动条向左移动x个像素,将纵向滚动条向下移动y个像素
window.open()
既可以导航到一个特定的url
,也可以打开一个新的浏览器窗口
window.open('htttp://www.vue3js.cn','topFrame') // 等同于a标签
< a href=" " target="topFrame"></ a>
location
http://foouser:barpassword@www.wrox.com:80/WileyCDA/?q=javascript#contents
属性名 | 例子 | 说明 |
---|---|---|
hash | "#contents" | utl中#后面的字符,没有则返回空串 |
host | www.wrox.com:80 | 服务器名称和端口号 |
hostname | www.wrox.com | 域名,不带端口号 |
href | www.wrox.com:80/WileyCDA/?q… | 完整url |
pathname | "/WileyCDA/" | 服务器下面的文件路径 |
port | 80 | url的端口号,没有则为空 |
protocol | http: | 使用的协议 |
search | ?q=javascript | url的查询字符串,通常为?后面的内容 |
hash
之外,只要修改location
的一个属性,就会导致页面重新加载新URL
location.reload()
,此方法可以重新刷新当前页面。
history
history
对象主要用来操作浏览器URL
的历史记录,可以通过参数向前,向后,或者向指定URL
跳转
常用的属性:
history.go()
接收一个整数数字或者字符串参数:向最近的一个记录中包含指定字符串的页面跳转,
history.go('maixaofei.com')
当参数为整数数字的时候,正数表示向前跳转指定的页面,负数为向后跳转指定的页面
history.go(3) //向前跳转三个记录
history.go(-1) //向后跳转一个记录
history.forward()
:向前跳转一个页面history.back()
:向后跳转一个页面history.length
:获取历史记录数
3.2 DOM
DOM
操作节点的方法
DOM
常见的操作,主要分为:
- 创建节点
createElement
创建元素 - 查询节点
querySelector
获取节点 - 更新节点
innerHTML
可以修改DOM节点的文本内容 - 添加节点
innerHTML
如果原本的DOM节点为空,那么则是新增节点 - 删除节点
removeChild
调用父节点的方法
DOM
事件流
- 捕获阶段: 事件从Document节点自上而下向目标节点传播的阶段;
- 当前目标阶段: 真正的目标节点正在处理事件的阶段;
- 冒泡阶段: 事件从目标节点自上而下向Document节点传播的阶段。
事件冒泡: 事件会由内向外传递给父类元素
事件委派: 将所有绑定给子类的多个事件绑定给共同的祖先元素 原理还是利用事件冒泡
3.3 前后端交互 ajax
http请求和ajax请求
ajax请求是一种特殊的http请求 一般请求浏览器一般会直接显示响应体数据 ajax请求: 浏览器不会对页面进行任何更新操作,只是将数据保存到xhr这个对象上,并调用监听回调函数(onReadyStateChange监听回调)
xhr.status响应状态码
- 2xx: 表示成功处理
- 200 成功通用
- 201 添加成功
- 3xx: 重定向
- 302 让浏览器重定向到location响应头所指定的地址
- 304 浏览器的资源没有变化, 可以使用缓存资源
- 4xx: 客户端请求错误
- 401 未授权, 也就身份校验失败, 一般是token过期或非法
- 404 请求的资源不存在
- 5xx: 服务器端错误
- 500 服务器运行出错,无法完成请求处理
跨域
同源策略: 协议、域名、端口三者都相同才能不违背同源策略
解决跨域的三种方式:
- JSONP
- 原理:用
script
标签发网络请求 - 不足: 只能处理GET请求; 每个请求都需要在后台处理,麻烦
- 原理:用
- CROS
- 原理: 后台给请求头设置
Access-Control-Allow-Origin
为*
- 原理: 后台给请求头设置
- 正反向代理
- 配置proxy(正向代理) 开发环境用正向代理
- 利用nigix(反向代理) 生产环境用反向代理
axios
用axios发送请求是最常见的 axios二次封装 axios常用方法
// 返回一个新的axios对象
axios.create({})
// 请求拦截器
axios.interceptors.request.use(config=>{})
// 响应拦截器
axios.interceptors.response.use(config=>{})
// axios常见的请求方式
axios.get // 查询
axios.post // 添加
axios.put // 更新
axios.delete // 删除
3.4 从输入url到渲染页面过程
简单流程
// 访问: http://www.baidu.com为例
1. 得到服务器对应的IP地址 ==> DNS解析
2. 通过IP连接上服务器 ==> TCP 3次握手
3. 向服务器发请求, 接收服务器返回的响应
4. 解析响应数据(html/css/js)显示页面
解析html => dom树
解析css => cssom树
解析js => 更新dom树/cssom树
生成渲染树 = dom树 + cssom树
布局
渲染
5. 断开连接 ==> 4次挥手
DNS解析 将域名地址解析成ip地址
TCP连接 : TCP三次握手 ===> 建立连接
客户端发送服务端: 我准备好了,请你准备一下 服务端发送客户端: 我也准备好了,请你确定一下 客户端发送服务端: 确定完毕
发送请求(携带请求行/请求头/请求体)
将请求报文发送过去
接收返回响应(接收响应行/响应头/响应体)
接收服务器返回的响应报文
解析渲染页面
遇到HTML,调用HTML解析器,解析成DOM树 遇到CSS,调用CSS解析器,解析成CSSOM样式树 遇到JS,调用JS解析器,解析JS代码 将DOM树和样式树结合生成渲染树 开始渲染 将页面渲染出来
一段时间,断开链接: TCP四次挥手
- 客户端发送服务端:请求数据发送完毕,可以断开了
- 服务端发送客户端:请求数据接受完毕,可以断开了
- 服务端发送客户端:响应数据发送完毕,可以断开了
- 客户端发送服务端:响应数据接受完毕,可以断开了
补充知识: 重绘重排
-
页面显示过程:
- 解析HTML生成DOM树
- 解析CSS生成CSSOM树
- 解析JS更新DOM树和CSSOM树
- DOM树 + CSSOM树生成渲染树(render Tree)
- 布局(也称回流, 确定个节点显示的位置)
- 渲染绘制
-
更新DOM或Style
- 可能会导致局部重排(也称回流, 重新布局)
- 可能会导致局部重绘
-
注意:
- 重排肯定会重绘, 但重绘不一定有重排
- 重排比重绘开销更大, 更消耗性能
-
哪些操作会导致重排
- 浏览器窗口尺寸改变
- 元素位置和尺寸发生改变的时候
- 新增和删除可见元素
- 内容发生改变(文字数量或图片大小等等)
- 元素字体大小变化
- 设置style属性
- 查询某些属性。比如说: offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight
-
哪些操作只会导致重绘
- 更新元素的部分属性(影响元素的外观,风格,而不会影响布局),比如visibility、背景色等属性的改变。 如何减少重排次数
-
更新节点的样式, 尽量通过类名而不是通过style来更新
-
将DOM操作离线处理,比如使用DocumentFragment
-
例子代码
var s = document.body.style;
s.padding = "2px"; // 回流+重绘
s.border = "1px solid red"; // 回流+重绘
s.color = "blue"; // 重绘
s.backgroundColor = "#ccc"; // 重绘
s.fontSize = "14px"; // 回流+重绘
document.body.appendChild(document.createTextNode('abc!')) // 添加node,回流+重绘
3.5 浏览器缓存
缓存就是加快浏览网页的速度, 第一次访问网页时,会把资源缓存到本地, 第二次访问时会直接从本地获取资源 达到提高网站和应用性能的目的。 缓存一共分为两种 一是 强制缓存; 二是 协商缓存
强制缓存: 强制浏览器使用当前缓存 (没有发送请求的)
强制缓存设置过程:
客户端发起请求的时候 携带 Cache-Control 请求头字段
服务端响应的时候,也需要携带 Cache-Contorl 的响应头字段
当下次再次请求的时候,判断自己是否符合强制缓存条件,如果符合,则直接读取缓存,如果不符合,则会走协商缓存
协商缓存: 协商缓存是强制缓存失效后, 浏览器携带缓存标识向服务器发起请求, 并决定是否缓存的过程
协商缓存过程:
客户端第一次向服务器发起请求, 服务器返回Etag(文件的唯一标识) 和 Last-modified(文件的最后一次修改时间)
客户端收集这两个字段
客户端第二次发送请求时携带缓存字段 把Etag改名为 If-None-Match ,把Last-modified改名为 If-Modified-Since,通过请求头的方式传递给服务端
服务器先检查自己最新的Etag 是否等于 服务器的If-None-Match
若相等,再判断自己的 Last-modified和客户端的 If-Modified-Since是否相等
若两个都相等 则响应304状态码 客户端就会读取缓存
若有一个不相等, 则服务器返回最新的数据和最新的Etag 和 Last-modified
HTTP和HTTPS
-
http协议的问题
- 传输的数据都是明文, 如果攻击者截取了传输报文,就可以直接读懂其中的信息
- 不适合传输一些敏感信息,比如:信用卡号、密码等支付信息
-
https协议
- http的升级版, 在http外包装上SSL(Secure Sockets Layer)层
- 是带加密传输、身份认证的网络协议,要比http协议安全
3.5 前台数据存储
localStorage、sessionStorage、Cookie (比较了解的) 还有 indexedDd, webSql
各自的相同之处 和 区别:
localStorage
和 sessionStorage
都有接近5M的存储空间 且不参与服务器通信 仅存在于客户端
Cookie
存储空间只有几Kb , 且每次发请求时都会携带在请求头里 如果cookie
过多 , 非常影响性能
localStorage
和 sessionStorage
不同之处在于 :
localStorage
只能手动清理 localStorage.clear()
sessionStorage
只要关闭浏览器就会被自动清理