JS + Web API 总结

205 阅读15分钟

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

  • 原型链详图

原型链.png

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

总结 : 整体代码的执行顺序

  1. script标签
  2. 所有微队列里的微任务
  3. 宏队列中的第一个宏任务
  4. 所有微队列里的微任务

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. 没有及时清除的定时器
    3. 没有及时清除的监听
    4. 没有及时释放的闭包

内存溢出

  • 运行程序需要分配的内存超过了系统能给你分配的最大剩余内存, 抛出内存溢出的错误,程序中断运行
  • 内存泄漏容易造成内存泄漏

垃圾回收机制

是浏览器中的一个线程,每隔一段时间运行一次,判断变量是否能被找到,如果找不到,那么会销毁

  • 判断变量的方法: 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]
  • 实例方法
    • 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中#后面的字符,没有则返回空串
hostwww.wrox.com:80服务器名称和端口号
hostnamewww.wrox.com域名,不带端口号
hrefwww.wrox.com:80/WileyCDA/?q…完整url
pathname"/WileyCDA/"服务器下面的文件路径
port80url的端口号,没有则为空
protocolhttp:使用的协议
search?q=javascripturl的查询字符串,通常为?后面的内容

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事件流

  1. 捕获阶段: 事件从Document节点自上而下向目标节点传播的阶段;
  2. 当前目标阶段: 真正的目标节点正在处理事件的阶段;
  3. 冒泡阶段: 事件从目标节点自上而下向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

各自的相同之处 和 区别:

localStoragesessionStorage 都有接近5M的存储空间 且不参与服务器通信 仅存在于客户端

Cookie 存储空间只有几Kb , 且每次发请求时都会携带在请求头里 如果cookie过多 , 非常影响性能

localStoragesessionStorage 不同之处在于 :
localStorage 只能手动清理 localStorage.clear()
sessionStorage 只要关闭浏览器就会被自动清理