JavaScript
闭包
闭包的概念
闭包是指有权访问另一个函数作用域中的变量的函数--《JavaScript高级程序设计》
闭包的特性:
1、内部函数可以访问定义他们外部函数的参数和变量。(作用域链的向上查找,把外围的作用域中的变量值存储在内存中而不是在函数调用完毕后销毁)设计私有的方法和变量,避免全局变量的污染。
1.1.闭包是密闭的容器,,类似于set、map容器,存储数据的
1.2.闭包是一个对象,存放数据的格式为 key-value 形式
2、函数嵌套函数
3、本质是将函数内部和外部连接起来。优点是可以读取函数内部的变量,让这些变量的值始终保存在内存中,不会在函数被调用之后自动清除
闭包形成的条件:
函数的嵌套
内部函数引用外部函数的局部变量,延长外部函数的变量生命周期
闭包的用途:
模仿块级作用域
保护外部函数的变量 能够访问函数定义时所在的词法作用域(阻止其被回收)
封装私有化变量
创建模块
闭包应用场景
闭包的两个场景,闭包的两大作用:保存/保护。 在开发中, 其实我们随处可见闭包的身影, 大部分前端JavaScript 代码都是“事件驱动”的,即一个事件绑定的回调方法; 发送ajax请求成功|失败的回调;setTimeout的延时回调;或者一个函数内部返回另一个匿名函数,这些都是闭包的应用。
闭包的优点:延长局部变量的生命周期
闭包缺点:会导致函数的变量一直保存在内存中,过多的闭包可能会导致内存泄漏
原型和原型链
原型关系:
每个 class都有显示原型 prototype
每个实例都有隐式原型 _ proto_
实例的_ proto_指向对应 class 的 prototype
原型: 在 JS 中,每当定义一个对象(函数也是对象)时,对象中都会包含一些预定义的属性。其中每个函数对象都有一个prototype 属性,这个属性指向函数的原型对象。
原型链:函数的原型链对象constructor默认指向函数本身,原型对象除了有原型属性外,为了实现继承,还有一个原型链指针__proto__,该指针是指向上一层的原型对象,而上一层的原型对象的结构依然类似。因此可以利用__proto__一直指向Object的原型对象上,而Object原型对象用Object.prototype.__ proto__ = null表示原型链顶端。如此形成了js的原型链继承。同时所有的js对象都有Object的基本防范
特点: JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。
作用域和作用域链
创建函数的时候,已经声明了当前函数的作用域==>当前创建函数所处的上下文。如果是在全局下创建的函数就是[[scope]]:EC(G),函数执行的时候,形成一个全新的私有上下文EC(FN),供字符串代码执行(进栈执行)
定义:简单来说作用域就是变量与函数的可访问范围,由当前环境与上层环境的一系列变量对象组成
1.全局作用域:代码在程序的任何地方都能被访问,window 对象的内置属性都拥有全局作用域。
2.函数作用域:在固定的代码片段才能被访问
作用:作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。
作用域链参考链接一般情况下,变量到 创建该变量 的函数的作用域中取值。但是如果在当前作用域中没有查到,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。
this的指向
作为普通函数执行时,this指向window。
当函数作为对象的方法被调用时,this就会指向该对象。
构造器调用,this指向返回的这个对象。
箭头函数 箭头函数的this绑定看的是this所在函数定义在哪个对象下,就绑定哪个对象。如果有嵌套的情况,则this绑定到最近的一层对象上。
基于Function.prototype上的apply、call和bind调用模式,这三个方法都可以显示的指定调用函数的 this 指向。apply接收参数的是数组,call接受参数列表,bind方法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。这个函数的 this指向除了使用new时会被改变,其他情况下都不会改变。若为空默认是指向全局对象window。
DOM的增删改查
dom元素的获取
getElementById("id属性的值")在整个文档中通过元素的ID属性值,获取到这个元素对象
getELementsByTagName("标签名")[context].getELementsByTagName("标签名") 在指定的上下文中,通过元素的标签名获取一组元素集合(HTMLCollection) ,上下文是我们自己来指定的。
document.getElementsByName("name属性的值")上下文只能是document,通过元素的name属性值获取一组节点集合(NodeList),也是一个类数组
[context].getElementsByClassName("类样式的名字") 在指定的上下文中,通过元素的类名获取一组元素集合(HTMLCollection)
[context].querySelector("选择器名")在指定的上下文中基于选择器(类似于CSS选择器)获取到指定的元素对象(获取的是一个元素,哪怕选择器匹配了多个,该方法只获取第一个)
querySelectorAll("选择器名")获取到选择器匹配到的所有元素,结果是一个节点集合(NodeList)
document.head获取HEAD对象
document.body获取BODY对象
document.documentElement获取HTML对象
dom元素的增删改
document. createElement( [标签名])创建一个元素标签(元素对象)
[container].appendChild([newEle])把一个元素对象插入到指定容器的末尾
[container].insertBefore([newEle],[oldEle])把一个元素对象插入到指定容器中某一个元素标签之前
[curEle].cloneNode() 浅克隆,只克隆当前的标签
[curEle].cloneNode(true) 深克隆,当前标签及其里面的内容一起克隆
[container].removeChild([oldEle])在指定容器中删除某一个元素
set/get/removeAttribute设置/获取/删除 当前元素的某一个自定义属性
" == "和 " === "的区别
使用双等号(==)进行相等判断时,如果两边的类型不一致,则会进行强制类型转化后再进行比较。(隐式转换)
使用三等号(
对于String, number这些基础类型,==操作符会先把两边的变量进行类型强制转换成相同的类型再比较是否相等;
对于array, object这些高级类型,==和
对于基础类型和高级类型,== 和
==操作符只要求比较两个变量的值是否相等,
new关键字
首先创建了一个新的空对象
设置原型,将对象的原型设置为构造函数的prototype对象。
让函数的this指向这个对象,执行构造函数的代码(为这个新对象添加属性)
判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
apply,call,bind
apply、call和bind调用模式,这三个方法都可以显示的指定调用函数的this指向。
apply方法接收两个参数:一个是this绑定的对象,一个是参数数组(适合批量传参)
call方法接收的参数,第一个是this绑定的对象,后面的其余参数是传入函数执行的参数。也就是说,在使用 call()方法时,传递给函数的参数必须逐个列举出来。
bind方法通过传入一个对象,返回一个this绑定了传入对象的新函数。这个函数的this指向除了使用new时会被改变,其他情况下都不会改变。(需要手动执行)
箭头函数和普通函数的区别
(1)箭头函数比普通函数更加简洁
如果没有参数,就直接写一个空括号即可
如果只有一个参数,可以省去参数的括号
如果有多个参数,用逗号分割
如果函数体的返回值只有一句,可以省略大括号
如果函数体不需要返回值,且只有一句话,可以给这个语句前面加一个void关键字。最常见的就是调用一个函数:
let fn = () => void doesNotReturn();
(2)箭头函数没有自己的this
箭头函数不会创建自己的this, 所以它没有自己的this,它只会在自己作用域的上一层继承this。所以箭头函数中this的指向在它在定义时已经确定了,之后不会改变。
(3)箭头函数继承来的this指向永远不会改变
(4)call()、apply()、bind()等方法不能改变箭头函数中this的指向
(5)箭头函数不能作为构造函数使用
构造函数在new的步骤在上面已经说过了,实际上第二步就是将函数中的this指向该对象。 但是由于箭头函数时没有自己的this的,且this指向外层的执行环境,且不能改变指向,所以不能当做构造函数使用。
(6)箭头函数没有自己的arguments
箭头函数没有自己的arguments对象。在箭头函数中访问arguments实际上获得的是它外层函数的arguments值。
(7)箭头函数没有prototype
(8)箭头函数不能用作Generator函数,不能使用yeild关键字
JS数据类型
基本类型(值类型): Number(数字),String(字符串),Boolean(布尔),Symbol(符号),null(空),undefined(未定义)在内存中占据固定大小,保存在栈内存中
引用类型(复杂数据类型): Object(对象)、Function(函数)。其他还有Array(数组)、Date(日期)、RegExp(正则表达式)、特殊的基本包装类型(String、Number、Boolean) 以及单体内置对象(Global、Math)等 引用类型的值是对象 保存在堆内存中,栈内存存储的是对象的变量标识符以及对象在堆内存中的存储地址。
Symbol:独一无二的值,属于基础数据类型
BigInt 是一种数字类型的数据,它可以表示任意精度格式的整数。而在其他编程语言中,可以存在不同的数字类型,例如:整数、浮点数、双精度数或大斐波数。
Set:类似于数组,但是值都是唯一的,属于数据结构
WeakSet:与Set类似,但WeakSet的值只能是对象,WeakSet 中的对象都是弱引用,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存
Map:类似于对象,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。属于数据结构
WeakMap:与Map结构类似,但WeakMap只接受对象作为键名(null除外),WeakMap的键名所指向的对象,不计入垃圾回收机制
判断数据类型
console.log(typeof 1);
console.log(typeof true);
console.log(1 instanceof Number);
console.log(true instanceof Boolean);
constructor
Object.prototype.toString.call()
变量提升与函数提升
JS的工作运行方式首先是先解析代码,获取所有被声明的变量,然后在一行一行的执行。所有的变量的声明语句都会被提升到作用域的顶部,这就叫做变量的提升。函数的声明也是一样的,都会提升到它所在的作用域最顶部开始执行。这样的好处是:
(1)提高了性能,如果没有这一步的话,那么每次执行代码前都必须重新解析一遍该变量或者函数,而这是没有必要的,因为变量或者函数的代码并不会改变,解析一遍就可以了。
(2)容错性更好,先使用后定义也不会影响正常使用,不过最好发先声明再使用规范点好。
不过ES6中提出的let和const定义变量,它们没有变量提升机制,所要先声明再使用,否则会报错。
set和map
1、Map是键值对,Set是值得集合,当然键和值可以是任何得值
2、Map可以通过get方法获取值,而set不能因为它只有值
3、都能通过迭代器进行for...of 遍历
4、Set的值是唯一的可以做数组去重,而Map由于没有格式限制,可以做数据存储
MAP和WeakMap
Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
WeakMap 结构与 Map 结构类似,也是用于生成键值对的集合。但是 WeakMap 只接受对象作为键名( null 除外),不接受其他类型的值作为键名。而且 WeakMap 的键名所指向的对象,不计入垃圾回收机制。
深拷贝与浅拷贝
浅拷贝:创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
深拷贝:是对于复杂数据类型在堆内存中开辟了一块内存地址用于存放复制的对象并且把原有的对象复制过来,这2个对象是相互独立的,也就是2个不同的地址。将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象.
JS 分为原始类型和引用类型,对于原始类型的拷贝,并没有深浅拷贝的区别,我们讨论的深浅拷贝都只针对引用类型。
浅拷贝和深拷贝都复制了值和地址,都是为了解决引用类型赋值后互相影响的问题。
但浅拷贝只进行一层复制,深层次的引用类型还是共享内存地址,原对象和拷贝对象还是会互相影响。
深拷贝就是无限层级拷贝,深拷贝后的原对象不会和拷贝对象互相影响。
ES6新特性
新增symbol类型 表示独一无二的值,用来定义独一无二的对象属性名;
const/let 都是用来声明变量,不可重复声明,具有块级作用域。存在暂时性死区,也就是不存在变量提升。(const一般用于声明常量);
变量的解构赋值(包含数组、对象、字符串、数字及布尔值,函数参数),剩余运算符(...rest);
模板字符串(${data});
扩展运算符(数组、对象);
箭头函数;
Set和Map数据结构;
Proxy/Reflect;
Promise;
async函数;
Class;
Module语法(import/export)。
var.let.const
暂时性死区是浏览器的bug:检测一个未被声明的变量类型时,不会报错,会返回undefined
var定义的变量,没有块的概念,可以跨块访问, 不能跨函数访问。可以先使用,后声明,因为存在变量提升;允许在相同作用域内重复声明同一个变量的
let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问。必须先声明后使用
const用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,且不能修改。
var声明的变量会和GO有映射关系;
let和会产生暂时性死区:
const:“基本数据类型”的值,保存在内存地址中,所以const定义的“基础数据类型”不可被改变。
而“引用数据类型”指向的内存地址只是一个指针,通过指针来指向实际数据,也就是说,不可被改变的是指针,而不是数据,所以const定义的“引用数据类型的”常量可以通过属性来修改值。
JS中for循环的几种形式及区别
for 语句
var arr = [1,2,4,6]
for(var i = 0, len = arr.length; i < len; i++){
console.log(arr[i])
}
这是标准for循环的写法也是最传统的语句,字符串也支持,定义一个变量i作为索引,以跟踪访问的位置,len是数组的长度,条件就是i不能超过len。
forEach 语句
forEach 方法对数组的每个元素执行一次提供的CALLBACK函数,forEach是一个数组方法,可以用来把一个函数套用在一个数组中的每个元素上,forEach为每个数组元素执行callback函数只可用于数组.遍历一个数组让数组每个元素做一件事情.那些已删除(使用delete方法等情况)或者未初始化的项将被跳过(但不包括那些值为 undefined 的项)(例如在稀疏数组上);不像map() 或者reduce() ,它总是返回 undefined值,并且不可链式调用。典型用例是在一个链的最后执行副作用。
var arr = [1,5,8,9]
arr.forEach(function(item) {
console.log(item);
})
for-in 语句
一般会使用for-in来遍历对象的属性的,不过属性需要 enumerable,才能被读取到.
for-in 循环只遍历可枚举属性。一般常用来遍历对象,包括非整数类型的名称和继承的那些原型链上面的属性也能被遍历。像 Array和 Object使用内置构造函数所创建的对象都会继承自Object.prototype和String.prototype的不可枚举属性就不能遍历了.
var obj = {
name: 'test',
color: 'red',
day: 'sunday',
number: 5
}
for (var key in obj) {
console.log(obj[key])
}
for-of 语句 (ES 6)
for-of语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。只要是一个iterable的对象,就可以通过for-of来迭代.
var arr = [{name:'bb'},5,'test']
for (item of arr) {
console.log(item)
}
for-of 和 for-in 的区别
for-in 语句以原始插入顺序迭代对象的可枚举属性。for-in会把继承链的对象属性都会遍历一遍,所以会更花时间.
for-of 语句只遍历可迭代对象的数据。
数组去重的方法
1.使用 Set
如果传递一个可迭代对象,它的所有元素将不重复地被添加到新的 Set中。如果不指定此参数或其值为null,则新的 Set为空。
查看Set详情
let res = [...new Set(arr)]
console.log(res)
2.使用filter
用于对数组进行过滤。
它创建一个新数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
查看filter详情
let res = arr.filter((item, index) => {
if (typeof item == "number" && isNaN(item)) {
return isNaN(item);
}
return arr.indexOf(item) === index;
});
3.使用reduce
接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值
查看reduce详情
let res = arr.reduce((pre, cur) => {
return pre.includes(cur) ? pre : [...pre, cur]
}, [])
console.log(res)
4.for循环
JS中的异步编程
回调函数 的方式,使用回调函数的方式有一个缺点是,多个回调函数嵌套的时候会造成回调函数地狱,上下两层的回调函数间的代码耦合度太高,不利于代码的可维护。
Promise 的方式,使用 Promise 的方式可以将嵌套的回调函数作为链式调用。但是使用这种方法,有时会造成多个 then 的链式调用,可能会造成代码的语义不够明确。
generator 的方式,它可以在函数的执行过程中,将函数的执行权转移出去,在函数外部还可以将执行权转移回来。当遇到异步函数执行的时候,将函数执行权转移出去,当异步函数执行完毕时再将执行权给转移回来。因此在 generator 内部对于异步操作的方式,可以以同步的顺序来书写。使用这种方式需要考虑的问题是何时将函数的控制权转移回来,因此需要有一个自动执行 generator 的机制,比如说 co 模块等方式来实现 generator 的自动执行。
async 函数 的方式,async 函数是 generator 和 promise 实现的一个自动执行的语法糖,它内部自带执行器,当函数内部执行到一个 await 语句的时候,如果语句返回一个 promise 对象,那么函数将会等待 promise 对象的状态变为 resolve 后再继续向下执行。因此可以将异步逻辑,转化为同步的顺序来书写,并且这个函数可以自动执行。
Promise
Promise是异步编程的一种解决方案,它是一个对象,可以获取异步操作的消息,他支持链式编程,避免了地狱回调(不便于阅读,不便于异常处理),它比传统的解决方案回调函数和事件更合理和更强大。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
Promise的实例有三个状态(PromiseState):
Pending(进行中)初始默认
Resolved(已完成)成功
Rejected(已拒绝)失败
只能由pending转变为resolved或rejected,转换后不能再改变
promise对象的值(PromiseResult):
保存着异步任务失败或成功的结果,通过reject()或resolve()两个函数修改
基本流程:new Promise()创建对象,此时为pending状态-->执行异步操作,成功了执行resolve(),失败了执行reject()-->promise对象改为resolved状态/rejected状态-->通过then()方法回调onResolved()/onRejected()(失败调用then的第二个,成功调用then的第一个)-->生成新的promise对象
1. Promise 构造函数: Promise (excutor) {}
excutor 函数: 执行器,同步执行 (resolve, reject) => {}
resolve 函数: 内部定义成功时我们调用的函数 value => {}
reject 函数: 内部定义失败时我们调用的函数 reason => {}
说明: excutor 会在 Promise 内部立即同步回调,异步操作在执行器中执行
2. Promise.prototype.then 方法: (onResolved, onRejected) => {}
onResolved 函数: 成功的回调函数 (value) => {}
onRejected 函数: 失败的回调函数 (reason) => {}
说明: 指定用于得到成功 value 的成功回调和用于得到失败 reason 的失败回调
返回一个新的 promise 对象
3. Promise.prototype.catch 方法: (onRejected) => {}
onRejected 函数: 失败的回调函数 (reason) => {}
说明: then()的语法糖, 相当于: then(undefined, onRejected)
4. Promise.resolve 方法: (value) => {}
value: 成功的数据或 promise 对象
如果传入的参数为非promise类型的对象,则返回的结果为成功promise对象
如果传入的参数为promise对象,则参数的结果决定了resolve的结果
说明: 返回一个成功/失败的 promise 对象
5. Promise.reject 方法: (reason) => {}
reason: 失败的原因(无论传什么,都是返回失败)
说明: 返回一个失败的 promise 对象
6.Promise.all
Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。
Promise.all中传入的是数组,返回的也是是数组,并且会将进行映射,传入的promise对象返回的值是按照顺序在数组中排列的,但是注意的是他们执行的顺序并不是按照顺序的,除非可迭代对象为空。
需要注意,Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的,这样当遇到发送多个请求并根据请求顺序获取和使用数据的场景,就可以使用Promise.all来解决。
7.Promise.race
Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
promise的几个关键问题
1. 如何改变 promise 的状态?
(1) resolve(value): 如果当前是 pending 就会变为 resolved
(2) reject(reason): 如果当前是 pending 就会变为 rejected
(3) 抛出异常: 如果当前是 pending 就会变为 rejected
2. 指定多个成功/失败回调函数, 都会调用吗? 当 promise 改变为对应状态时都会调用
3. 改变 promise 状态和指定回调函数先后顺序
(1) 都有可能, 正常情况下是先指定回调再改变状态, 但也可以先改状态再指定回调
(2) 如何先改状态再指定回调? ① 在执行器中直接调用 resolve()/reject()② 延迟更长时间才调用 then()
(3) 什么时候才能得到数据? ① 如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据
② 如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据
4. promise.then()返回的新 promise 的结果状态由什么决定
(1) 简单表达: 由 then()指定的回调函数执行的结果决定
(2) 详细表达: ① 如果抛出异常, 新 promise 变为 rejected, reason 为抛出的异常
② 如果返回的是非 promise 的任意值, 新 promise 变为 resolved, value 为返回的值
③ 如果返回的是另一个新 promise, 此 promise 的结果就会成为新 promise 的结果
5. promise 如何串连多个操作任务
(1) promise 的 then()返回一个新的 promise, 可以改成 then()的链式调用
(2) 通过 then 的链式调用串连多个同步/异步任务
6. promise 异常传透
(1) 当使用 promise 的 then 链式调用时, 可以在最后指定失败的回调,
(2) 前面任何操作出了异常, 都会传到最后失败的回调中处理
7. 中断 promise 链
(1) 当使用 promise 的 then 链式调用时, 在中间中断, 不再调用后面的回调函数
(2) 办法: 在回调函数中返回一个 pendding 状态的 promise 对象
async与await
async/await其实是Generator的语法糖,它能实现的效果都能用then链来实现,它是为优化then链而开发出来的。从字面上来看,async是“异步”的简写,await则为等待,所以很好理解async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。当然语法上强制规定await只能出现在asnyc函数中,await 必须写在 async 函数中, 但 async 函数中可以没有 await。
async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了 async 函数体。
async function async1(){
console.log('async1 start');
await async2();
console.log('async1 end')
}
async function async2(){
console.log('async2')
}
console.log('script start');
async1();
console.log('script end')
Async/Await就是一个自执行的generate函数。利用generate函数的特性把异步的代码写成“同步”的形式,第一个请求的返回值作为后面一个请求的参数,其中每一个参数都是一个promise对象.
async/await 是消灭异步回调的终极武器。
但和 Promise 并不互斥,反而,两者相辅相成。
执行 async 函数,返回的一定是 Promise 对象。
await 相当于 Promise 的 then。
try...catch 可捕获异常,代替了 Promise 的 catch。
宏任务与微任务
JS异步任务可以分为宏任务和微任务
微任务包括:promise 的回调、node 中的 process.nextTick 、对 Dom 变化监听的MutationObserver。
宏任务包括:script 脚本的执行、dom事件回调、ajax回调、setTimeout ,setInterval ,setImmediate 一类的定时器回调,还有如 I/O 操作、UI 渲染等。
宏任务和微任务的执行顺序:微任务先执行,宏任务再执行,每一个宏任务执行完之后,都会检查是否存在待执行的微任务,如果有,则执行完所有的微任务之后,再执行下一个宏任务。宏任务和微任务交替执行。
axios
1. 基于xhr+promise的异步ajax请求库
2. 浏览器端/node端都可以使用
3. 支持请求/响应拦截器
4. 支持请求取消
5. 请求/响应数据转换
6. 批量发送多个请求
说明: 调用axios()并不是立即发送ajax请求,而是需要经历一个较长的流程
流程: 请求拦截器2=>请求拦截器1=>发ajax请求=>响应拦截器1=>响应拦截器2=>请求的回调
注意: 此流程是通过promise串连起来的,请求拦截器传递的是config,响应拦截器传递的是response
axios(config): 通用/最本质的发任意类型请求的方式
axios(url[, config]): 可以只指定 url 发 get 请求
axios.request(config): 等同于 axios(config)
axios.get(url[, config]): 发 get 请求
axios.delete(url[, config]): 发 delete 请求
axios.post(url[, data, config]): 发 post 请求
axios.put(url[, data, config]): 发 put 请求
axios.defaults.xxx: 请求的默认全局配置
axios.interceptors.request.use(): 添加请求拦截器
axios.interceptors.response.use(): 添加响应拦截器
axios.create([config]): 创建一个新的 axios(它没有下面的功能)
axios.Cancel(): 用于创建取消请求的错误对象
axios.CancelToken(): 用于创建取消请求的 token 对象
axios.isCancel(): 是否是一个取消请求的错误
axios.all(promises): 用于批量执行多个异步请求
axios.spread(): 用来指定接收所有成功数据的回调函数的方法
1. 请求拦截器:
在真正发送请求前执行的回调函数
可以对请求进行检查或配置进行特定处理
成功的回调函数, 传递的默认是 config(也必须是)
失败的回调函数, 传递的默认是 error
2. 响应拦截器
在请求得到响应后执行的回调函数
可以对响应数据进行特定处理
成功的回调函数, 传递的默认是 response
失败的回调函数, 传递的默认是 error
1. 整体流程:
request(config) ==> dispatchRequest(config) ==> xhrAdapter(config)
2. request(config):
将请求拦截器 / dispatchRequest() / 响应拦截器 通过 promise 链串连起来, 返回 promise
3. dispatchRequest(config):
转换请求数据 ===> 调用 xhrAdapter()发请求 ===> 请求返回后转换响应数据. 返回 promise
4. xhrAdapter(config):
创建 XHR 对象, 根据 config 进行相应设置, 发送特定请求, 并接收响应数据, 返回 promise
JS垃圾回收机制
项目中,如果存在大量不被释放的内存(堆/栈/上下文),页面性能会变得很慢。当某些代码操作不能被合理释放,就会造成内存泄漏。我们尽可能减少使用闭包,因为它会消耗内存。
浏览器垃圾回收机制/内存回收机制:
浏览器的Javascript具有自动垃圾回收机制(GC:Garbage Collecation),垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存。
标记清除:在js中,最常用的垃圾回收机制是标记清除:当变量进入执行环境时,被标记为“进入环境”,当变量离开执行环境时,会被标记为“离开环境”。垃圾回收器会销毁那些带标记的值并回收它们所占用的内存空间。
谷歌浏览器:“查找引用”,浏览器不定时去查找当前内存的引用,如果没有被占用了,浏览器会回收它;如果被占用,就不能回收。
IE浏览器:“引用计数法”,当前内存被占用一次,计数累加1次,移除占用就减1,减到0时,浏览器就回收它。
优化手段:内存优化
(1)堆内存:fn = null 【null:空指针对象】
(2)栈内存:把上下文中,被外部占用的堆的占用取消即可。
内存泄漏
在 JS 中,常见的内存泄露主要有 4 种,全局变量、闭包、DOM 元素的引用、定时器
继承
(1)第一种是以原型链的方式来实现继承,但是这种实现方式存在的缺点是,在包含有引用类型的数据时,会被所有的实例对象所共享,容易造成修改的混乱。还有就是在创建子类型的时候不能向超类型传递参数。
(2)第二种方式是使用借用构造函数的方式,这种方式是通过在子类型的函数中调用超类型的构造函数来实现的,这一种方法解决了不能向超类型传递参数的缺点,但是它存在的一个问题就是无法实现函数方法的复用,并且超类型原型定义的方法子类型也没有办法访问到。
(3)第三种方式是组合继承,组合继承是将原型链和借用构造函数组合起来使用的一种方式。通过借用构造函数的方式来实现类型的属性的继承,通过将子类型的原型设置为超类型的实例来实现方法的继承。这种方式解决了上面的两种模式单独使用时的问题,但是由于我们是以超类型的实例来作为子类型的原型,所以调用了两次超类的构造函数,造成了子类型的原型中多了很多不必要的属性。
(4)第四种方式是原型式继承,原型式继承的主要思路就是基于已有的对象来创建新的对象,实现的原理是,向函数中传入一个对象,然后返回一个以这个对象为原型的对象。这种继承的思路主要不是为了实现创造一种新的类型,只是对某个对象实现一种简单继承,ES5 中定义的 Object.create() 方法就是原型式继承的实现。缺点与原型链方式相同。
(5)第五种方式是寄生式继承,寄生式继承的思路是创建一个用于封装继承过程的函数,通过传入一个对象,然后复制一个对象的副本,然后对象进行扩展,最后返回这个对象。这个扩展的过程就可以理解是一种继承。这种继承的优点就是对一个简单对象实现继承,如果这个对象不是我们的自定义类型时。缺点是没有办法实现函数的复用。
(6)第六种方式是寄生式组合继承,组合继承的缺点就是使用超类型的实例做为子类型的原型,导致添加了不必要的原型属性。寄生式组合继承的方式是使用超类型的原型的副本来作为子类型的原型,这样就避免了创建不必要的属性。
(7)es6 extends继承
es5继承首先是在子类中创建自己的this指向,最后将方法添加到this中
Child.prototype=new Parent() || Parent.apply(this) || Parent.call(this)
es6继承是使用关键字先创建父类的实例对象this,最后在子类class中修改this
事件委托
事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
事件委托是利用事件的冒泡原理来实现的。就是事件从最内层的节点开始,然后逐步向上传播事件,一层一层的往外执行触发响应,执行顺序由内而外。如果有这样一个机制,那么我们给最外面的节点如div添加点击事件,那么里面的ul,li,a做点击事件的时候,都会冒泡到最外层的div上,所以都会触发,这就是事件委托,委托它们父级代为执行事件。
在触发DOM上的某个事件的时候,就会产生一个事件对象event,这个对象中包含着所有与事件有关的信息,其中包括导致事件的元素、事件的类型以及事件相关的信息。避免对特定的每个节点添加事件监听占用大量内存 ,而是使用事件监听器添加到它们的父元素上。事件监听器会分析从子元素冒泡上来的事件,找到是哪个子元素的事件,并代为触发响应。
事件流
捕获阶段:事件对象通过目标的祖先从Window传播到目标的父级。此阶段也称为捕获阶段。
目标阶段:事件对象到达事件对象的事件目标。此阶段也称为在目标阶段。如果事件类型表明该事件没有冒泡,则该事件对象将在此阶段完成后停止。
冒泡阶段:事件对象以相反的顺序通过目标的祖先传播,从目标的父级开始,到Window结束。这个阶段也称为冒泡阶段。
冒泡和捕获
事件冒泡指在在一个对象上触发某类事件,如果此对象绑定了事件,就会触发事件,如果没有,就会向这个对象的父级对象传播,最终父级对象触发了事件。
事件委托本质上是利用了浏览器事件冒泡的机制。因为事件在冒泡过程中会上传到父节点,并且父节点可以通过事件对象获取到目标节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方式称为事件代理。
event.stopPropagation() 或者 ie下的方法 event.cancelBubble = true;