背景
百度部门重组,导致项目不再迭代,本着要滚蛋的,就只能寻求内转,内转offer失败,以及字节秋招三面又挂了,心情瞬间爆炸,字节三面挂了两次,二面挂了一次,好在内转到了百度feed下,本文是根据导师对我的指导,在Q4阶段打基础,本着先是MDN学习web开发,后续又转到红宝书。本是在百度的如流知识库,后因为转正失败,就赶紧搬运吧,记录自己的基础成长过程。 吐槽:百度加班熬死我了,实习期间每天都睡不好觉,被需求熬的难受。
HTML中的JavaScript
script元素
- async:表示立即下载脚本,并且执行脚本,但是不能阻止其他的页面动作,比如下载其他资源和其他脚本资源
- charset 很少用,字符集
- crossorigin 默认不使用,默认值为anonymous,如果要使用则设置值为 use-credentials,请求会携带cookie
- defer 表示脚本可以延迟到文档完全被解析和显示之后执行,在DOMcontentLoaded事件之前
- src要执行的代码外部文件
- type:值为module则会被解析成ES6模块,此时可以用import export语句
注意:使用了src引入外部文件,那么再在script标签里写js代码,则js代码不会生效
noscript元素:可以显示浏览器不支持脚本时显示的内容,如果支持那么noscript元素里面的内容不会显示
语言基础
变量
var 变量会自动声明提升到函数作用域顶部
function test(){
if(false){
if(false){
var name = "hello";
}
}
console.log(name) //undefined
}
test();
let 声明范围是块作用域;
let 不允许重复声明,不会声明提升,会有暂时性死区
let 在全局作用域不会成为window属性,然而var却会
function test(){
{
let name = "zhangsan";
}
console.log(name)
}
test(); //报错 name is not defined
const 与let 相似,但是比let多一个限制条件:声明变量同时必须初始化,后续尝试修改const会报错;
但是一定不能修改吗?const 声明限制只是适用于它指向变量的引用, 比如const 声明的是一个Object 那么你修改Object里面的任何值是可以的,那必须Object不准修改呢? obj = Object.freeze(obj) 这样修改obj不会报错,但是会默认失败
数据类型
typeof 判断基本类型
- null 是一个空对象的引用
- undefined 是一个值,当一个变量声明了但是没有赋值 ,那么变量的值就是undefined,对于未声明的变量,只能执行一个有用的操作就是typeof
let message
console.log(message) // 'undefined'
console.log(age) // 报错
console.log(typeof age) undefined
-
boolean
-
Number
- 默认10进制
- 八进制 开头为0 比如 070 八进制 表示十进制的56
- 16进制为 0x开头
- 浮点数以.开始 0.1可以简写 .1
- e表示10的多少次幂
- 最大值为 Number.MAX_VALUE 最小值为Number.MIN_VALUE超出范围则为Infinity或者-Infinity
- NAN(Not a Number)不是数值 通过isNaN判断
- parseFloat 只解析10进制并且遇到不合法的则停止检测 返回已经检测的值
- parseInt()函数 返回是十进制的数,第一个参数是能转化成数字的数 ,第二个参数是进制数 不存在1进制 0进制直接返回0
// 第一个参数 必须是字符串形数字 不能是null undefined true 并且字符串数字的最大一个数必须小于进制数
// 对于负数 检测到不合法则停止检测 对于正数,不合法直接NaN
parseInt('34', 5)//5进制的34 转化为10进制则为19
parseInt('-349', 5) ===parseInt('-34', 5)
-
String 模版字符串 以及 对象转化为String 调用的是toString 方法
-
Symbol
- 不可以new 不可以改变
- 有全局符号注册表 symbol.for() 类似map 没有就注册,没有就返回之前注册的symbol值
- symbol.keyFor接受symbol.for返回的symbol 并且返回对应的字符串
- 可以用作属性 比如Object[symbol('')]
- symbol.Iterator 在for of 使用
- symbol.asyncIterator 这个符号作为一个属性 表示:一个方法 返回对象默认的asynceIterator 在for await of语句使用
- symbol.hasInstance 这个符号作为一个属性 表示:一个方法,决定一个构造器是否认可一个对象是它的实例
- symbol.match。 作为一个属性:由String.prototype.match方法使用
console.log('foobar'.match(/bar/))
// [ 'bar', index: 3, input: 'foobar', groups: undefined ]
-
Object
- hasOwnProperty("string")判断当前对象是否具有某个属性
- isPrototypeOf(Object)判断当前对象是否为另一个对象原型
- propertyIsEnumberble()判断是否可以用for in 迭代
- toString()返回对象的字符串表示(对象转字符串)
- valueOf()返回对象对应的字符串、数值、或者bool类型
Object.prototype.world='xxxxxxxx'
let obj = {}
Object.defineProperty(obj, 'name', {
value: 'hello',
writable: false,
enumerable: true,
configurable: false
})
console.log(obj.hasOwnProperty('name'))
for( let i in obj) {
console.log(i) name world
}
hasOwnProperty 只会找实例本身的属性,而in操作符则会找实例本身以及原型对象。
语句
一个不常见的with语句 :将代码作用于设置为特定对象
let url =location.href;
let hostName = location.hostname;
let protocal = location.protocol;
let questr = location.search;
with (location){
let url =url;
let hostName = hostname;
let protocal = protocol;
let questr = search;
}
变量、作用域、内存
原始值与引用值
- 基本数据类型
-
- 存放位置在栈
- 复制一个基本数据,则是在栈重新复制,并且压入栈
- 复杂数据类型
-
- 存放位置 堆
- 复制一个复杂数据类型,变量名不同,但是都指向了堆的同一块地址
- 传递参数:无论基本数据还是复杂数据都传递的是值,只不过是复杂数据类型是通过引用访问对象
function setName(obj){
obj.name = "John";
obj = new Object();
obj.name = "Mike";
}
let person = new Object();
setName(person);
console.log(person.name); // John
- 确定类型: instanceof检测复杂数据类型,原理:判断构造函数原型是否出现在实例对象的原型链上面
执行上下文和作用域
上下文:全局上下文、函数上下文、块级上下文
VO :每个上下文关联的变量对象(variable Object)
scope chain: 上下文代码执行的时候会创建作用域链(保障上下文代码访问变量和函数的顺序)
AO:上下文是函数,则活动对象(activation Object)用作变量对象
标识符查找:当前上下文寻找,没找到沿着作用域链找,如果实在未找到,则说明没声明变量
垃圾回收和内存泄漏
-
垃圾回收
- 标记清除法:标记内存所有变量,执行代码之后,执行的变量取消标记,那么有标记的就是要内存回收的
- 引用计数:如果某个变量被引用,则引用数+1,最后引用数为0的则回收;缺点:循环引用无法回收
-
内存泄漏
- 意外的全局变量:比如 某个函数内 num=10 没有用let const var 声明,则会使全局变量无法回收
- 闭包
- 没有移除监听器 removeAddEventListener
- 定时器未清除
基本引用类型
Date
let date = new Date()
- Data.now() 返回执行日期的毫秒数
- getFullYear()
- getMonth()
- getDate()
- getDay() 这个返回的是周几
- getHours()
- geMinutes()
- getSeconds()
- getMilliseconds()
RegExp
let expression = / patten/flags
patten表示正则表达式,flags 这是多个标记。通过**\进行转义字符**
- g 全局模式 :
- i 不区分大小写
- m多行
- y粘附模式
- u unicode模式
- s dotAll 匹配任何模式
实例方法:
- exec()接受一个参数,要应用模式的字符串 ,如果没找到则返回null;如果找到,则返回第一个匹配信息的数组,但是这个数组有额外的index(第一个找到的起始位置)和 input() 属性
- test() 如果输入的字符串和模式匹配则返回true否则返回false
原始包装类型
-
Boolean
- 重写valueOf方法 返回一个原始值的true或false
- 重写toString方法,返回字符串'true'或者'false'
-
Number
- toString()接收一个基数,表示转化为相应的字符串的进制数
- toFixed()保留几位小数
- toExponential() 科学计数法的小数
-
String
- length 编码长度
- charCodeAt(index) 指定位置的ascall码
- split(”“) 分隔
- slice(start,end) 返回子字符串 [start,end) 为负值情况:start+length,end+length
- substring(start,end) 返回子字符串 [start,end) 为负值情况:start +length ,end =0
- substr(start,length) 返回子字符串的[start,start+length) 将所有负参数值转换为0
- indexOf(“”,start?)第一个参数为子字符串 ,第二个参数则从start位置开始找,返回 index || -1
- includes()包含方法返回true 或者false
- trim()清理字符串前后空格 trimLeft()、trimright()
- repeat(nums)方法:字符串复制nums次
- padStart(length,'string') padEnd(length,string),将字符串填充至长度为length,并且填充字符是stirng
- toLowerCase() toUpperCase()转化为小写或者大写
- match方法和正则exec方法一致
- replace(正则,targetString)替换 将字符串正则匹配的替换成targetString
- localeCompare(target)比较与target字符串大小
-
Global
-
encodeURI 不会编码不属于URL组件的特殊字符 : // ?#
-
encodeURIComponent 会编码所有特殊字符
-
eval()执行javaScript字符串 eval定义的任何变量和函数不会提升
-
Math
- max ,min
- ceil() 向上取整
- floor()向下取整
- round()四舍五入
- random [0,1)的随机数
- abs 绝对值
- pow(x,power)x的多少次幂
- sqrt()平方根
-
集合引用类型
Array
-
Array.from 类数组转数组
-
Array迭代器方法
- keys() 返回下标数组
- values()返回值数组
- entries()返回[index,value]的数组
let arr = ['red','yellow','blue', 'green'];
for(let i of arr.keys()){
console.log(i);
}
for(let value of arr.values()){
console.log(value);
}
for(let [index,value] of arr.entries()){
console.log(index,value);
}
-
fill()
-
slice() 和stirng方法一样
-
splice()
- 删除 splice(index ,length)从index位置开始删除,删除长度为Length
- 插入 splice(index ,0,.....)第二个参数为0 ,后续参数则是在index位置插入的数
- 替换 splice(index , 2,....)在index初删除2个元素,并且添加....个元素
-
push pop shift unshift
-
every 每一项满足要求则返回true 否则fasle
-
filter 过滤
-
some 有一项满足就返回true
-
reduce()计算累加
ArrayBuffer
和array大部分方法一致,ArrayBuffer主要用于二进制数据上传
Map weakMap
建立键/值对;保持了插入的顺序;size属性 返回多少键/值对
- set
- get
- has
- keys 返回迭代器对象
- delete
- clear
- keys values entries三个迭代方法 和数组一样
weakMap和map区别:
- 没有size属性
- 键只能是对象
- 当键所指向的对象被回收了,weakMap会自动回收
set weakSet
set方法和属性与map相同,weakMap 和weakSet也相同
迭代器与生成器
迭代器
是一次性使用对象,每次调用next()方法都会返回IteratorResult对象 IteratorResult包含两个属性 done 表示下一次能否迭代 false表示可以继续迭代
true表示下一次不可迭代,下一次不可迭代返回值为undefiend,要想成为可迭代对象,必须实现 [Symbol.iterator]() 方法或者原型链上面有这个方法
{done:false,value:"当前值"}
class MyIterator {
constructor(data) {
this.data = data;
this.index = 0;
}
// 实现[Symbol.iterator]()方法
[Symbol.iterator]() {
let data = this.data;
let index = this.index;
return {
next(){
if (index < data.length){
return { value: data[index++], done: false };//这里index++是关键
} else {
return { done: true };
}
},
return(){
console.log("终止迭代")
return { done: true }
}
}
}
}
const data = [1, 2, 3, 4, 5];
const myIterator = new MyIterator(data);
for (const item of myIterator) {
console.log(item);
return;
}
生成器
生成器形式是一个函数,函数名称加一个* 表示他是一个生成器,只要是可以定义函数的地方都可以定义生成器
- 生成器也实现了Iteratir接口,有next()方法
- 有done和value属性
- 默认值是undefined 可以通过生成器函数的返回值制定
- 生成器函数只会在初次调用next()后开始执行
function * fn(){ return 'foo'}
let generatorObj = fn()
console.log(generatorObj) // suspended
console.log(generatorObj.next()) // {done:true,value:"foo"}
- 通过yield中断执行,且yield只能在内部使用,不能嵌套过深
- 和iterator类似,有内部返回对象有return()方法,同时还有thorw()方法来中断
对象、类与面向对象编程
对象
属性
分为两种:数据属性,访问器属性
- 数据属性
- [[Configurable]] 表示属性是否可以通过delete删除并且重新定义,是否修改它的特性,以及是否可以把它改为访问器属性,一般直接定义在对象上面的属性的[[configurable]]特性为true
- [[Enumberable]] 表示该属性是否可以通过for in循环 一般为true
- [[Writable]]这个属性是否可以被修改,默认情况下为true,为false代码可以修改,但是会默认失败也不报错。
- [[Value]] 属性值 默认undefined
属性特征不能直接修改定义,必须通过Object.defineProperty(),此方法会默认将属性的 configurable、enumberable、writable设置为false
- 访问器属性
- [[Configurable]]
- [[Enumberable]]
- [[get]] :获取函数
- [[set]]:设置函数
let obj = {name:'tom'}
Object.defineProperty(obj, 'name', {
enumerable: true,
configurable: false,
get() {
console.log('网络请求或者拦截行为')
return 'hello world'
},
set(value) {
console.log(value, '赋值操作')
}
})
obj.name = '杰瑞'//会执行set函数
// console.log(obj.name) //会执行get函数
- 定义多个属性:Object.defineProperties(obj,{})
- 读取属性特性:Object.getOwnPropertyDescriptor(obj,属性)
合并
Object.assign(target,.......obj)方法,浅拷贝
将许多原对象中的属性以及原对象的原型对象可枚举(Enumberable为true)的属性一并复制
增强对象语法
简写、可计算属性、解构赋值
注意点:
- 解构不会影响函数的arguments对象
- 解构可以不用事先声明变量,如果事先声明了,则用()包起来解构 也可以理解为解构重命名
let personName,personAge;
let obj ={
name:'杰瑞',
age:18
}
({name:personName,age:personAge}=obj)
创建对象
new操作符
- 创建一个新对象
- 更改原型指向: obj__proto__ = Person.prototype
- 绑定this : obj.call(Person,...args)
- 执行构造函数内部代码(为新对象添加属性)
- return obj
原型链
[流程图]
修改对象原型
如下操作会更改对象原型,但是constructor属性就不指定构造函数了,如果在对象里面写constructor进行指定,那么constructor默认会可以被枚举,而原型的constructor默认不能被枚举,因此采用Object.defineProperty来定义constructor属性
Person.prototype = {}
继承
function A(){
this.a ='a'
}
function B(){
this.b = 'b'
}
- 原型链继承
主要作用:继承方法
[流程图]
问题:所有B的实例会共享A的属性,如果A上面的属性是引用类型,则会同步修改
B实例化无法为A传参数
function A(){
this.a =['a','b','c']
}
function B(){
this.b = true;
}
B.prototype = new A()
let b = new B()
let instance = new B()
b.a.push('d')
console.log(instance.a) //[ 'a', 'b', 'c', 'd' ]
- 盗用构造函数继承
主要作用: 继承属性,并且实例化所继承父类的引用属性是相互隔开的(因为this上下文不同)
function B(...args){
this.b = true;
A.call(this,...args)
}
缺点,无法继承父类的原型的方法
function A(){
this.a =['a','b','c']
}
A.prototype.getA = function(){
return this.a
}
function B(){
this.b = true;
A.call(this)
}
let b = new B()
let instance = new B()
b.a.push('d')
console.log(instance.get()) // get is not a Function
- 组合继承
继承属性和方法
function B(){
this.b = true;
A.call(this)
}
B.prototype = new A()
- 原型式继承
Object.create(A,B)
原型式继承会继承A的方法和属性,然后也会将B对象的属性方法继承,但是会同名覆盖
- 寄生式继承
目的:增强这个对象
let Person = {
name:'杰瑞'
}
function createAnother(origin){
let clone = Object.create(origin)
clone.getName = function(){ //增强对象
return this.name
}
return clone
}
- 寄生式组合继承
function B(){
this.b = true;
A.call(this) //继承属性
}
A.prototype.constructor = B //规范
B.prototype = A.prototype; // 继承方法
B.prototype.otherFunc = function(){} //增强方法
[流程图]
类
- 类构造函数 constructor,在new操作符创建类的实例的时候,应该调用constructor函数
- 类是特殊函数,使用typeof会返回 function
- 类中的constructor方法 不会被当成构造函数,使用instanceof 会返回false
class Person{}
let p = new Person()
console.log(p instanceof Person.constructor) //false
- 静态属性:static 所有类实例共享
- 实现 [Symbol.iterator]方法就可以可迭代,* yield就可以使用生成器
- 继承 extends关键词
- 使用super关键字调用父类构造函数之间不能使用this
- 通过new.target属性判断是否是抽象类
if(new.target === Person){
throw new Error('不可以实例化')
}
代理与反射
代理
ES6新增代理与反射:提供了拦截并向基本操作嵌入额外行为能力
使用代理的主要目的是可以定义捕获器(拦截器)
捕获器会接收:target(目标对象)、property(查询属性)、receiver(代理对象)三个参数
let target = {
name:'杰瑞'
}
const handler = {
get(target, property,receiver) {
console.log(target, property,receiver)
}
}
let proxy = new Proxy(target, handler)
proxy.name
反射
所有可以捕获的方法,都有对应的反射API,这些方法与捕获器拦截的方法具有相同的名称和函数签名(函数签名:函数的名称、参数类型、参数顺序和返回值类型)
目的:提供默认行为,比如get,返回属性值
let target = {
name:'杰瑞'
}
const handler = {
get(target, property,receiver) {
Reflect.get(...arguments)
// Reflect.get(target, property,receiver)
}
}
let proxy = new Proxy(target, handler)
proxy.name
空代理
proxy =new Proxy(target,reflect)
捕获器不变式:防止捕获器定义出现反常行为,比如目标对象的一个属性不可配置不可写的数据属性,那么捕获器返回一个与该属性不同值的时候会跑出TypeError
撤销代理
Proxy暴露了revocable方法用于中断代理对象与目标对象的之间联系
撤销代理是幂等的
let target = {
name:'杰瑞'
}
const handler = {
get(target, propKey,receiver) {
console.log(target, propKey,receiver)
}
}
let {proxy,revoke} = Proxy.revocable(target, handler)
proxy.name
revoke()
proxy.name
代理this问题
一般代理对象this没问题,但是当对象的实例的私有变量的值是weakMap存储会有问题
原因:代理对象不等于原对象。
const wm = new WeakMap()
class Person{
constructor(name){
wm.set(this, name)
}
getName(){
return wm.get(this)
}
}
let p = new Person('杰瑞')
let proxy = new Proxy(p,{})
console.log(proxy.getName()) //undefined
let proxyPerson = new Proxy(Person,{})
let Proxy_p = new proxyPerson('汤姆')
console.log(Proxy_p.getName())
但是一般不用存储,直接使用this还是可以正常代理。
原因:Proxy 默认将方法调用中的 this 绑定到被代理对象上
class Person{
constructor(name){
this.name = name
}
getName(){
return this.name
}
}
let p = new Person('杰瑞')
let proxy = new Proxy(p,{})
console.log(proxy.getName()) //杰瑞
let proxyPerson = new Proxy(Person,{})
let Proxy_p = new proxyPerson('汤姆')
console.log(Proxy_p.getName())
函数
- 箭头函数:没有this、arguments、super以及new.target,箭头函数不能实例化
- 函数名:函数对象会向外暴露一个name属性,表达他是哪一个函数
function foo(){
}
let a = foo
console.log(a.name)
- 参数:arguments是一个类数组,传入多少,参数长度就是多少,具有一个callee属性指向arguments对象所在函数指针
- 参数没有传默认值为undfined,传入undefined,默认没有传,但是arguments的length会算+1,即:arguments[2] = undefined
- 没有重载,只能同名覆盖
- 默认参数作用域:后声明的参数,可以引用先定义的参数
function foo(name ='杰瑞',key = name,value){
}
foo('bar');
- 参数会有暂时性死区
function foo(name ='杰瑞',key = name,value =defaultValue){
let defaultValue = '默认值';
console.log(name,key,value)
}
参数扩展与收集
function foo(name ='杰瑞',...args){
console.log(arguments.length) //5
console.log(args) // [ 1, 2, 3, '汤姆' ]
}
foo('bar',...[1,2,3],'汤姆');
函数声明和函数表达式
函数声明:如下代码进行了函数的声明,会函数提升
console.log(nums(30,20))
function nums(num1,num2){
return num1+num2
}
函数表达式:不会函数提升,需要先赋值,再使用
console.log(nums(30,20))
let nums = function(num1,num2){
return num1+num2
}
函数内部
-
arguments
-
this
- 在标准函数中,this引用的是把函数当成方法调用的上下文对象(谁调用函数,该函数内部this就指向谁)
- 在箭头函数中,this引用的是定义箭头函数的对象的上下文 (谁定义箭头函数,箭头函数的this就指向谁)
- 匿名函数不会绑定某个对象,意味着this会指向window
- 独立函数调用this指向window
var name = '123';
let obj = {
name: '345',
getName: function() {
console.log('对象里的this调用', this.name); //某个对象调用 this指向obj
function getInfo() {
console.log('独立调用', this.name);
}
let func =() =>{
console.log('箭头函数',this.name);//箭头函数 this指向定义箭头函数的对象的上下文
}
func();
getInfo();//独立调用 this指向window
}
};
obj.getName();
- 函数caller属性:caller属性引用的是调用当前函数的函数
- ES6新增 new.target. 函数是正常调用则new.target为undefined。如果使用new关键字调用,则new.target指向被调用的构造函数
函数属性和方法
- call() 参数一个一个传递
- apply()第二个参数为数组
- bind(),参数一个一传,但是返回的是一个函数,需要再次调用
尾递归优化
尾调用:外部函数返回值是一个内部函数
尾调用原理:外部栈帧不会再被用,能够被内存回收后没影响
闭包
引用了另一个函数作用域中的变量函数
本质:作用域链
用处:保存私有变量
立即调用函数表达式
立即调用的匿名函数称为立即调用的函数表达式
(function (){
// 块级作用域
})()
常见一个用途是锁定参数值
原因:IIFE 执行时形成了一个私有的局部作用域。这个作用域是独立的,与全局作用域和其他函数作用域隔离开,因此函数内部的参数在 IIFE 作用域中被独立存储,即便函数外无法直接访问这些参数值。
let data = [];
for (var i = 0; i < 5; i++) {
data[i] = function(x) {
// 在这里,i 的值是被保存下来的
return function() {
console.log(x);
};
}(i);//这里传入的参数是 i 的值
}
data[3](); // 输出: 3
Promise
new Promise(executor(resolve,reject))
三种状态
- pending(待定)
- fulfilled(解决也称为resolved)
- rejected(拒绝)
- executor是一个执行器函数,是同步执行
- promise的状态转换是不可撤销的
- try catch不能捕获Promise的错误,只能捕获同步代码的错误,Promise属于异步
- 当executor内部是resolve()或者是reject()之后,Promise的状态已经确定,并且如果是resolve()或者reject(),后续的代码依然会执行,然而throw Error 后续代码则不会执行
let p1 =new Promise((resolve,reject)=>{
console.log('p1')
resolve(1)
// throw new Error('p1 error') // 如果抛出异常 那么p1 end 不会执行
console.log('p1 end')
return 2
}).then(res=>{
console.log(res)
})
.catch(err =>{
console.log('p1 catch')
})
Promise实例方法
Promise.prototype.then
接收两个参数,onResolved处理函数和onRejected处理函数
-
- 如果只想提供onReject处理函数则onResolved为null
- 如果没有显式返回语句则Promise.resolve()会包装一个默认值undefined
- then方法会返回一个新的Promise
let p1 = Promise.resolve('resolved');
let p2 = p1.then();
let p3 = p1.then(()=>undefined);
let p4 = p1.then(()=>{});
let p5 = p1.then(()=>Promise.resolve());
setTimeout(console.log, 0, p1)
setTimeout(console.log, 0, p2)
setTimeout(console.log, 0, p2===p1) // false
setTimeout(console.log, 0, p3) // Promise {<fulfilled>: undefined}
setTimeout(console.log, 0, p4) // Promise {<fulfilled>: undefined}
setTimeout(console.log, 0, p5) // Promise {<fulfilled>: undefined}
let q1 = Promise.reject('杰瑞');
let q2 = q1.then(null, (res) => {
console.log(res);// resolved
});
Promise.prototype.catch
给Promise添加拒绝处理程序,只接收一个参数onRejected处理程序
-
- catch方法会返回一个新的Promise
let q1 = Promise.reject('rejected');
let q2 = q1.then(null, (res) => {
console.log(res);// rejected
});
let q3 = q1.catch((res) => {
console.log(res);// rejected
});
Promise.prototype.finally
给Promise添加onFinally处理程序,在fulfilled或者rejected之后都会执行
-
- 返回一个新的Promise,如果返回是一个待定的Promise 或者处理程序抛出错误,则会返回相应的Promised:待定或拒绝
let p1 = Promise.resolve('杰瑞');
let p2 = p1.finally(()=>new Promise(()=>{}))
setTimeout(console.log, 0,'resolved Finally', p2) // resolved Finally Promise { <pending> }
let q1 =Promise.reject('Error');
let q2 = q1.finally(()=>new Promise(()=>{}))
let q3 = q1.catch((res)=>{console.log(res)}).finally(()=>new Promise((res)=>{console.log('finally')}))
setTimeout(console.log, 0,'q2', q2) // q2 Promise { <pending> }
setTimeout(console.log, 0,'q3', q3) // q3 Promise { <pending> }
静态方法
可迭代对象一般指数组
- Promise.all :接收一个可迭代对象,所有对象resolved返回一个数据对应数组,有一个失败,则返回失败的promise结果
- Promise.race :接收一个可迭代对象,返回第一个resolved的结果
- Promise.allSettled :接收一个可迭代对象,返回对应数组,包含了失败和成功
- Promise.any :一旦有个promise敲定(不管成功与否)则返回敲定的结果
异步函数
async和await最大的一个用处是异步代码同步化,async函数内部可以try catch捕获错误
async
- 声明一个异步函数
- 异步函数没返回值默认返回undefined
- 异步函数始终返回Promise对象
await
- await会暂停执行异步函数后面的代码
- await会拒绝执行Promise.reject的后续代码
async function foo(){
await Promise.reject('error');
console.log('foo'); //这行代码不会执行
}
foo().catch(err => console.log( err));
async function foo(){
await Promise.resolve('resolve');
console.log('foo'); // resolve则后续会执行
return 'foo';
}
foo()
.then((res) => undefined)
.catch(err => console.log( err));
- await 不能在顶级上下文和script标签使用
- 异步函数的特质不会扩展到嵌套函数
- 执行到 await 时,后面的代码就会整体被安排进一个新的微任务,此后的函数体变为异步执行
异步函数策略
1.串行化异步任务(平行执行)
function task(taskName) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(taskName)
resolve()
}, 1000)
})
}
let p1 = task('executor 1').then(res => {
return task('executor 2')
}).then( res => {
return task('executor 3')
}).then(res => {
return task('executor 4')
})
2. 实现sleep
async function sleep (delay){
return new Promise((resolve) => {
setTimeout(() => resolve(), delay);
});
}
async function main(){
const t1 = Date.now();
await sleep(1000);
console.log(Date.now() - t1);
}
main();
BOM
window
-
window全局对象,var声明的所有全局变量和函数都会成window对象属性和方法
- top对象指向最外层窗口(浏览器本身)
- parent对象指向当前窗口的父窗口:某个窗口是由某个窗口打开的
- self对象始终指向window
-
css像素比:window.devicePixelRatio属性(dpr)物理像素/css像素,
-
窗口大小:window.outerWidth、window.outerHeight、window.innerWidth、window.innerHeight
outerWidth和outerHeight返回浏览器自身大小
innerWidth和innerHeight返回浏览器窗口页面视口的大小(不包含浏览器边框和工具栏)
这个和document.documentElement.clientWidth、document.documentElement.clientHeight没啥区别
- 窗口距离:screenTop(浏览器窗口距离屏幕顶部距离) screenLeft
- 视口位置
- window.scrollBy(x,y)基于当前位置,继续滚动向右x距离和向下y距离
- window.scrollTo(x,y) 滚动到页面xy位置
- 打开窗口:window.open()返回一个新建窗口的引用
- 对话框:alert 、confirm、prompt
- 定时器 setTimeout、setInterval
Location
- window.location === document.location
location.href = location.protocol + `//` + location.host + location.pathname + location.search + loaction.hash
- 查询字符串: URLSearchParams 提供了get set delete方法
let search = new URLSearchParams(location.search)
search.set("key","value")
search.get("key")
search.delete("key")
- URL方法
new URL 返回一个url对象 和location对象差不多,但是比loaction对象多了searchParams属性(相当于URLSearchParams),
const url = new URL(window.location.href);
url.searchParams.set('room_id', roomId);
url.searchParams.set('source', 'live_more');
window.location.href = url.toString();
- 操作地址
window.location= url;
window.location.href = url; // 常见
window.location.assign(url);
window.location.replace(url); // 重新加载不能回退到前一页
window.location.reload(); // 可能从缓存加载
window.location.reload(true); // 重新从服务器加载
navigator
客户端标识浏览器的标准
navigator.userAgent. 返回浏览器的用户代理字符串
navigator.plugins. 返回浏览器安装的插件数组
screen
screen保存客户端能力的信息,也就是浏览器窗口外面的客户端显示器信息
history
histroy.go(n); //前进n页
histroy.back(); // 后退一页
histroy.forward(); //前进一页
历史状态管理参考文章
路由两种模式:hash、history
hash模式有一个hashChange事件会在页面哈希(#)变化时触发,浏览器不会发送请求到server
history模式:允许开发者直接更改前端路由,即更新浏览器 URL 地址而不重新发起请求。
使用history.pushState方法和popState方法以及replaceState方法 和对应的pushState、replaceState和popState事件
DOM
DOM(Document Object Model)document节点表示每个文档的根节点,根节点的唯一子节点是元素,称为documentElement,文档元素是文档最外层元素,所有其他元素都是文档元素之内,每个文档只能由一个文档元素,dom有12种节点类型,每个节点类型都有nodeType属性,表示该节点的类型
document.documentElement = html元素的引用
节点层级
Node类型
-
所有节点类型都继承了Node类型都有nodeName、nodeValue、childNodes以及nodeType属性
-
childNodes属性值是一个NodeList实例,是一个类数组,可以通过中括号和item()方式来访问
document.documentElement.childNodes[0]; // head document.documentElement.childNodes.item(1) // body document.documentElement.nodeType // 1
-
每一个node类型都有一个parentNode属性,指向该node节点的父元素
document.documentElement.parentNode // document
-
- 节点的操作
let returnNode = someNode.appendChild(node); // 在someNode的childNodes列表末尾添加节点
returnNode = someNode.insertChild(node,参照节点); // 如果参照节点为null则添加到末尾
returnNode = someNode.replaceChild(newNode,移除节点); // 这个返回的是移除节点的引用
returnNode = someNode.removeChild(node); // 返回被移除的节点
Document类型
-
- document是HTMLDocument实例
document.nodeType; // 9
document.firstChild === document.documentElement === html ;
document.body === document.documentElement.childNodes('body的索引') ===body;
document.title;
document.domain;
document.cookie;
-
- 定位元素
document.getElementById('ID'); // id=‘ID’节点的引用
document.getElementByTagName('div'); // div标签的 nodeList(类数组)
document.getElementByName(); //返回具有name属性的元素
一共6种方式获取dom元素
document.querySelector()
document.querySelectorAll()
document.getElementByClassName()
-
- 文档写入 write() writeln() open() close()
write和writeln 经常用于动态包含外部资源(js文件)
open和close用于打开和关闭网页输出流 ???? 开了一个新页面,在控制台弄的
Element类型
nodeType=1 (之前写document.documentElement 就是一个element类型)
nodeName为标签名
parentNode为 document 或者element类型
所有element类型都有className id lang dir title属性
-
- 取得属性
let element = Document.getElementById('id')
element.setAttribute('key', value);
element.getAttribute('key');
element.removeAttribute('key');
自定义属性命名规范为: data-* 可以通过元素的dataset属性来获取数据,比如:data-myName、
data-myname、data-my-Name、data-my-name 要以dom.dataset['myName']获取数据
-
- 创建元素
let div = document.createElement('div')
// 通过 appendChild等添加到文档中
Text类型
nodeType = 3
nodeName = '#text'
nodeValue为文本
text节点的文本信息可以通过nodeValue 和data属性访问 修改文本信息有如下方法
appendData(text); // text末尾添加文本
deleteData(offset, count); //在offset位置删除count个字符
insertData(offset, text);
replaceData(offset, count, text);
spliteText(offset); // offset将当前text节点拆为2个text节点
- 创建节点
let textNode = document.createTextNode('文本或者用strong em修饰文本的标签')
MutationObserver接口
mutationObserver接口可以在DOM被修改时异步执行回调,mutationObserver可以观察整个文档或者DOM树的一部分,
调用new mutationObserver(executor) 并且传入回调函数executor,回调函数有两个参数:mutationRecords(消息队列记录)、mutationObserver
返回一个observer对象,其中observe方法接受两个参数: 观察的对象、观察的属性
observe方法
let contain = document.querySelector(".contain");
let observer = new MutationObserver((mutationRecords,mutationObserver) => {
console.log("contain 变化了", mutationRecords, mutationObserver);
})
observer.observe(contain, {childList: true});
let bt = document.querySelector(".button");
bt.onclick = function() {
console.log("添加");
let item = document.createElement("div");
item.className = "item";
contain.appendChild(item);
}
disconnect方法
要终止观察回调,则用disconnect()方法。调用disconnect()方法不会结束mutationObserver的生命周期
还可以继续使用observe方法进行观察
takesRecords方法
清空消息队列
DOM扩展
Selector API
- 获取dom元素
document.querySelector('.className'); // css选择符为.className的第一个元素
document.querySelectorAll('.className') // 返回css选择符为.className的NodeList静态实例
- matches方法,接受一个css选择符参数,如果元素匹配则返回true,使用这个方法可以检测该元素能不能被querySelector或querySelectorAll获取
H5
- css扩展
document.getElementByClassName('.className')
- classList属性:给所有元素添加了classList属性,访问类名,有add contains remove toggle来增删改类名
let contain = document.querySelector(".contain");
contain.classList.add("flex");
contain.classList.contains("flex"); // true
contain.classList.remove("flex");
contain.classList.toggle("flex") // 有flex则删除,没有则添加flex类
- 焦点管理:document.activeElement 包含当前拥有焦点的元素
bt.onclick = function() {
contain.focus();
console.log(document.activeElement); //botton元素
}
- HTML Document扩展
document有readyState属性,表示文档状态
document.readyState = "complete"; // 文档加载完成
document.readyState = "loading"; // 文档正在加载
- 插入标记
innerHTML,插入的innerHTML 会被解析成DOM子树,但是innerHTML插入的script标签是不会执行的
。插入style在IE浏览器不行,其他浏览器可以(chrome)
innerText,会获取所有字节点的文本内容,会按照深度优先顺序将所有文本节点的值拼接起来
outerHTML:返回自身和所有后代元素的HTML字符串,写入时,相当于把自身替换
outerText和outerHTML一样
contain.outerHTML = `<p>这是一段文本</p>`;
let p = document.createElement("p");
p.append(document.createTextNode("这是一段文本"));
contain.parentNode.replaceChild(p,contain);
- children属性(HTMLCollection类型) 与childNodes(NodeLists类型)类似;children
区别:children不包含文本节点、注释节点
- contains方法:确定一个元素是不是另外一个元素后代
document.documentElement.contains(document.body) // true
- scrollIntoView方法:让元素滚动到可视区域
setTimeout(()=>{
dom.scrollIntoView({
aliginToTop: true, // 滚动后元素顶部与适口顶部对齐,false为底部对齐,默认true
scrollIntoViewOptions:{
behavior: 'smooth', // 平滑过度
block: 'center', // 垂直对齐
inline: 'center' // 水平对齐
}
})
},1000)
元素尺寸
- 偏移尺寸
所有的偏移尺寸都是针对带有定位的父节点,返回值不带单位
| offsetParent | 返回该元素带有定位的父级元素,如果父级元素没有定位则返回body |
|---|---|
| offsetTop | 返回该元素上外边框相对 带有定位的父级元素上内边框 的偏移,如果父级元素没有定位则返回相对body上方的偏移 |
| offsetLeft | 返回该元素左外边框相对带有 定位的父级元素左内边框 的偏移,如果父级元素没有定位则返回相对body左侧的偏移 |
| offsetWidth | 返回该元素包括padding+border+content的宽度 |
| offsetHeight | 返回该元素包括padding+border+content的高度度 |
- 滚动尺寸
返回值不带单位
| scrollLeft | 被卷去的左侧距离 |
|---|---|
| scrollTop | 被卷去的上侧距离 |
| scrollHeight | 元素总高度(包括滚动不可见的区域) |
| scrollwidth | 元素总宽度(包括滚动不可见的区域) |
- 客户端尺寸
客户端尺寸由内容区域+内边距
| document.documentElement.clientWidth | 浏览器视口宽度 |
|---|---|
| document.documentElement.clientHeight | 浏览器视口高度 |
- 元素尺寸getBoundingClientRect()
返回距离视口的位置,Element不包含margin
元素遍历
之前的children属性和childNodes属性都可以 for of遍历,不会深度遍历
- NodeIterator
let Iterator = document.createNodeIterator(root,whatToShow, filter)
| root | 需要开始深度优先遍历的根节点 |
|---|---|
| whatToShow | 需要遍历的子节点类型 NodeFilter对象中的某个值,表示哪种节点需要遍历 |
| filter、NodeFilter、函数 | 是否接受或者跳过的特定节点 |
Iterator只有nextNode()和previousNode(),分别是在遍历前进一步和后退一步
let filter = (node)=>{
if (node.className === 'child') {
return NodeFilter.FILTER_ACCEPT; // 仅接受 className 为 'child' 的节点
} else {
return NodeFilter.FILTER_SKIP; // 跳过其他节点,但继续检查子孙节点
// 还有一个 NodeFilter.REJECT //拒绝节点
}
}
let contain = document.querySelector(".contain");
let domIterator = document.createNodeIterator(contain, NodeFilter.SHOW_ALL,filter);
let node = domIterator.nextNode();
while(node){
console.log(node);
node = domIterator.nextNode();
}
- TreeWakker方法
在createNodeIterator基础上 多增加了 parentNode 、firstChild 、lastChild 、nextSibling、 previousSibling方法
document.createTreeWalker(); // 参数和createNodeIterator一样
事件
DOM事件流分为三个阶段:事件捕获、到达目标、冒泡阶段
现代浏览器
- DOM0,将函数直接赋值事件处理程序属性
let button = document.querySelector(".button");
button.onclick = function (e) {
console.log("button");
}
- DOM2事件处理
- 通过addEventListener 和removeEventListener, 则无法removeEventListener回收处理函数
- 第三个参数默认false表示事件冒泡阶段触发事件处理函数,为true表示在事件捕获触发事件处理函数
- 事件处理函数是一个匿名函数,则removeEventListener不会回收事件处理程序
let button = document.querySelector(".button");
// 这种情况可以回收
function click() {
console.log("button");
}
button.addEventListener("click", click,true); // 事件捕获阶段执行
button.removeEventListener("click", click,false); // 默认fasle 在事件冒泡触发
// 这种情况没办法回收事件处理程序
button.addEventListener("click", () => {
console.log("button");
});
button.removeEventListener("click", () => {
console.log("button");
});
- 事件对象event
event指向当前元素,不同的事件有额外的属性,一下是他们共有属性
| 属性或方法 | 类型 | 说明 |
|---|---|---|
| bubbles | bool | 事件是否冒泡 |
| cancelable | bool | 是否可以取消事件默认行为 |
| currentTarget | 元素 | 绑定事件的元素 |
| target | 元素 | 事件目标触发的元素 |
| defaultPreventValue | bool | 为true表示已经调用了preventDefault()方法 |
| detail | 整数 | 事件相关的其他信息( e.detail ===1) |
| eventPhase | 整数 | 表示调用事件处理阶段 1代表捕获 2代表到达,3代表冒泡 |
| preventDefault() | 函数 | 阻止默认行为 cancelable为true才可以调用 |
| stopPropagation() | 函数 | 阻止冒泡 bubbles为true才可以调用 |
| stopImmediatePropagation() | 函数 | 取消后续所有事件捕获或者冒泡,并阻止调用任何事件后续处理程序 |
| trusted | bool | true是浏览器生成的,false表示事件是开发者js创建的 |
| type | string | 被触发事件的类型 |
IE浏览器
- 事件处理
- attachEvent 和 detachEvent,在添加事件处理要加on
- IE只支持冒泡
- IE事件处理函数中this指向window
button.attachEvent("onclick", () => {
console.log("button");
});
- 事件对象
事件对象event 是window的一个属性
| 属性或方法 | 类型 | 说明 |
|---|---|---|
| cancelBubble | bool | 默认false 设置为true可以取消冒泡 |
| returnValue | bool | 默认为true 设置为false可以取消事件默认行为 |
| srcElement | 元素 | 事件目标 与target一样 |
| type | 元素 | 触发事件类型 |
夸浏览器做兼容性
let button = document.querySelector(".button");
let handler = function () {
console.log("button");
}
EventUtil.addHandler(button, "click", click);
EventUtil.removeHandler(button, "click", click);
var EventUtil = {
addHandler: (element, type, handler) => {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
},
removeHandler: (element, type, handler) => {
if (element.removeEventListener) {
element.removeEventListener(type, handler, false);
} else if (element.detachEvent) {
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
},
getEvent: (event) => {
return event || window.event;
},
getTarget: (event) => {
return event.target || event.srcElement;
},
preventDefault: (event) => {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
stopPropagation: (event) => {
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
};
事件类型
- DomContentLoaded
- readystatechange事件
readystatechange事件对象event都有一个readyState属性,readystatechange事件与load事件共用时,触发的顺序不能保证,
| uninitialized | 对象存在并尚未初始化 |
|---|---|
| loading | 正在加载数据 |
| loaded | 已经加载完数据 |
| interactive | 对象可以交互,但未加载完成. dom构建好了 |
| complete | 对象加载完成 |
- load
- pageshow
fireFox和opera开发往返缓存的功能, 把整个页面缓存在内存中,再次导航到这个页面就不会触发load事件。
- pageshow首次会在load之后执行,来自往返缓存后,会在页面状态完全恢复触发,事件必须添加到window上面,事件对象有个persisted属性,是一个bool,如果来自往返缓存则为true
- beforeunload
没出现弹窗原因:
-
- IE 和firefox可能支持
- 页面关闭速度太快了
window.addEventListener('beforeunload', function(e) {
e.returnValue = ''; // 有些浏览器需要这个属性被设置来触发事件
return '';
});
5. pagehide
- pagehide会在unload之前执行,persisted为true则会表明页面在卸载之后会被保存在往返缓存中。
- unload事件
unload事件常用于一个页面导航到另外一个页面,清理引用
- resize事件
浏览器窗口被缩放到新高度或者宽度触发
window.addEventListener('resize', function(e) {
document.documentElement.style.fontSize = window.innerWidth / 375 * 10 + 'px'
})
- scroll事件
- 焦点事件
| 事件名称 | 说明 | |
|---|---|---|
| blur | 失去焦点 | |
| focusout | 失去焦点,会冒泡 | |
| focus | 获取焦点 | |
| focusin | 获取焦点会冒泡 | |
- 鼠标和滚轮事件
| click | | |
|---|---|---|
| dbclick | | |
| mousedown | | |
| mouseup | | |
| mouseenter | 鼠标从元素外部移动到元素内部:不冒泡、经过后代元素也不触发 | |
| mouseover | 鼠标从元素外部移动到元素内部:冒泡、经过后代元素触发 | |
| mouseleave | | |
| mouseout | 冒泡,经过后代元素会触发(比如一个盒子里面有一个按钮,当你给盒子添加mouseout事件时,你在元素内部按钮区域移动到盒子区域 甚至移出盒子 都会触发mouseout事件) | |
| mousemove | 鼠标在元素上移动时反复触发 | |
| mousewheel | 鼠标滚轮事件 | |
鼠标对应的事件对象属性
| offsetX | 光标相对于目标元素边界的x坐标 |
|---|---|
| offsetY | 光标相对于目标元素边界的y坐标 |
| clientXclientY | 事件触发时,光标元素相对视口的距离 |
| screenXscreenY | 事件触发时,光标元素相对屏幕的距离(与BOM的screen对象的left,top,差不多,但是screen是浏览器相对屏幕,而这个属性是元素相对屏幕) |
- 设备事件
- 苹果公司在Safari浏览器添加orientationchange事件:判断用户设备是垂直模式还是水平模式
- deviceorientation事件,可以获取设备加速计信息,也就是设备类似于摇一摇 抬屏,在空间上xyz发生变化就会触发
- devicenotion事件,用于设备实际上在移动(确定设备正在掉落或者正拿在一个行走的人手里)
模拟事件
用document.createEvent()创建一个event对象,用dispatchEvent()方法触发事件
createEvent 接受一个字符串型参数
| 参数 | 说明 | 初始化方法 |
|---|---|---|
| UIEvents | 页面事件 | |
| MouseEvents | 鼠标事件 | initMouseEvent() |
| KeyboardEvent | 键盘事件 | initKeyboardEvent() |
| HTMLEvents | HTML事件(DOM3分散到其他事件大类当中) | |
| CustomEvent | DOM3增加的自定义事件 | initCustomEvent() |
初始设置方法的参数和event对象属性一一对应
initCustomEvent接收四个参数
| 参数 | 说明 |
|---|---|
| type | 事件类型 |
| bubbles | 是否冒泡 |
| cancelable | 事件是否可以取消 |
| detail | e.detail属性 |
IE createEventObject()创建事件返回event对象
初始化对象直接赋值 event.screenX = xxxx;
fireEvent('事件名称',event对象)触发事件
表单
富文本编辑
- 嵌入一个iframe标签 并且将designMode属性设置on
- 其他元素指定contenteditable属性
<!-- <iframe name="richedit" style="height: 800px;width: 1024px;"></iframe> -->
<div contenteditable style="height: 800px;width: 1024px;"></div>
<script>
window.onload = function () {
frames['richedit'].document.designMode = "on";
}
</script>
富文本交互
使用execCommand()方法
- 执行的命令
- bool ,表示是否为命令提供用户界面的bool值和执行命令的必须值(如果不需要则为null),为跨浏览器兼容第二个参数应该始终为false,应为fireFox在第二个参数为true时会报错
- 值
富文本选择
getSelection()方法,获得富文本编辑器选区,这个方法在document 和window对象上,返回当前文本的Selection对象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<iframe name="richedit" style="height: 800px; width: 1024px;" src="about:blank"></iframe>
<script>
window.onload = function () {
var iframeDoc = frames['richedit'].document;
iframeDoc.designMode = "on";
iframeDoc.addEventListener('mouseup', function () {
var selection = iframeDoc.getSelection();
if (!selection.isCollapsed) {
var span = iframeDoc.createElement('span');
span.style.backgroundColor = 'red';
var range = selection.getRangeAt(0);
range.surroundContents(span);
selection.removeAllRanges();
}
});
};
</script>
</body>
</html>
豫章故郡,洪都新府。星分翼轸,地接衡庐。襟三江而带五湖,控蛮荆而引瓯越。物华天宝,龙光射牛斗之墟;人杰地灵,徐孺下陈蕃之榻。雄州雾列,俊采星驰,台隍枕夷夏之交,宾主尽东南之美。都督阎公之雅望,棨戟遥临;宇文新州之懿范,襜帷暂驻。十旬休假,胜友如云;千里逢迎,高朋满座。腾蛟起凤,孟学士之词宗;紫电清霜,王将军之武库。家君作宰,路出名区;童子何知,躬逢胜饯。\n' + '时维九月,序属三秋。潦水尽而寒潭清,烟光凝而暮山紫。俨骖騑于上路,访风景于崇阿。临帝子之长洲,得天人之旧馆。层峦耸翠,上出重霄;飞阁流(一作 翔)丹,下临无地。鹤汀凫渚,穷岛屿之萦回;桂殿兰宫,即(一作 列)冈峦之体势。\n' + ' 披绣闼,俯雕甍。山原旷其盈视,川泽纡其骇瞩。闾阎扑地,钟鸣鼎食之家;舸舰弥津,青雀黄龙之舳。云销雨霁,彩彻区明。落霞与孤鹜齐飞,秋水共长天一色。渔舟唱晚,响穷彭蠡之滨;雁阵惊寒,声断衡阳之浦。\n' + ' 遥襟甫畅,逸兴遄飞。爽籁发而清风生,纤歌凝而白云遏。睢园绿竹,气凌彭泽之樽;邺水朱华,光照临川之笔。四美具,二难并。穷睇眄于中天,极娱游于暇日。天高地迥,觉宇宙之无穷;兴尽悲来,识盈虚之有数。望长安于日下,目吴会于云间。地势极而南溟深,天柱高而北辰远。关山难越,谁悲失路之人;萍水相逢,尽是他乡之客。怀帝阍而不见,奉宣室以何年。\n' + ' 嗟乎!时运不齐,命途多舛;冯唐易老,李广难封。屈贾谊于长沙,非无圣主;窜梁鸿于海曲,岂乏明时?所赖君子见机,达人知命。老当益壮,宁移白首之心?穷且益坚,不坠青云之志。酌贪泉而觉爽,处涸辙以犹欢。北海虽赊,扶摇可接;东隅已逝,桑榆非晚。孟尝高洁,空余报国之情;阮籍猖狂,岂效穷途之哭!\n' + ' 勃,三尺微命,一介书生。无路请缨,等终军之弱冠;有怀投笔,慕宗悫之长风。舍簪笏于百龄,奉晨昏于万里。非谢家之宝树,接孟氏之芳邻。他日趋庭,叨陪鲤对;今兹捧袂,喜托龙门。杨意不逢,抚凌云而自惜;钟期既遇,奏流水以何惭?\n'
富文本提交
form.addEventListener('submit', function (e) {
let targe = e.target
target.elements['comments'].value = frames['richedit'].document.body.innerHTML
})
JS API
关于JS API涉及很多书写的代码,所以作者想另起一篇来说明。本文先不书写了
跨上下文信息
XDM(cross-document messaging),在不同执行上下文(不同源页面和不同工作线程)之间传递信息
-
发送消息postMessage()方法
- message:消息
- targetOrigin:目标接收源的字符串
- 可传输对象数组:可选,只有工作线程相关
-
接收消息
window对象会触发message事件,事件对象有三个重要信息
- data:作为第一个参数传递给postMessage()的字符串数据
- origin:发消息的文档源
- source:发送消息的文档中的window对象代理
发送消息由于是字符串,最好JSON.stringify()和JSON.parse()转化
Encoding
实现字符串与定型数组之间转换
文本批量编码 TextEncoder 所谓批量:js引擎同步编码整个字符串
文本批量解码 TextDecoder
let textcoder =new TextEncoder();
let string = '知不足而奋进,望远山而前行'
console.log(textcoder.encode(string))
/*
[
231, 159, 165, 228, 184, 141, 232,
182, 179, 232, 128, 140, 229, 165,
139, 232, 191, 155, 239, 188, 140,
230, 156, 155, 232, 191, 156, 229,
177, 177, 232, 128, 140, 229, 137,
141, 232, 161, 140
]
*/
let encodeArr = textcoder.encode(string)
let textdecoderr =new TextDecoder();
console.log(textdecoderr.decode(encodeArr)) // 知不足而奋进,望远山而前行
XML
XML解析为DOM文档新增了DOMParser类型,使用它要创建一个实例,然后调用parseFromString()方法,接收两个参数1.XML字符串、2.内容类型(始终应该是text/html) 返回值是一个Document实例
const parser = new DOMParser();
const doc = parser.parseFromString(htmlString, "text/html");
let serializer = new XMLSerializer()
let xml = serializer.serializeToString(dom)
[流程图]
如果在解析出错,仍然会返回一个Document对象,只不过document元素是
FireFox和Opera返回. Safari和chrome会把元素嵌入在发生错误解析的位置,由于有差异,最好采用try/catch来判断是否发生解析错误,如果没错误通过getElementsByTagName来查找文档是否包含元素
XPath
XPath是为了在DOM文档中定位特定节点而创建的
const result = document.evaluate(
xpath,
document,
null,
XPathResultType
null
);
| 参数 | 描述 |
|---|---|
| xpathExpression | XPath 表达式。todo:了解一下语法 |
| contextNode | 查询的上下文节点。 |
| namespaceResolver | 解析命名空间的方法(通常为 null)。 |
| resultType | 查询结果的返回类型(例如节点集、单个节点等)。 |
| result | 一个现有的结果对象(可选)。 |
resultType:XPathResult.${varable}_TYPE
varable:
- ANY 返回合适XPath表达式的数据类型
- NUMBER 返回数值
- STRING 返回字符串
- BOOLEAN 返回boolean
- UNORDERED_NODE_ITERATOR 返回匹配节点类型的集合,顺序不一定与文档一致
- ORDERED_NODE_ITERATOR 返回匹配节点类型的集合,顺序与文档一致
- UNORDERED_NODE_SNAOSHOT 回匹配节点类型的集合快照(),顺序与文档不一致
- ORDERED_NODE_SNAOSHOT
- ANY_UNORDERED_NODE
- FIRST_ORDERED_NODE 返回只有一个节点的节点集合,包含文档中第一个匹配的节点
JSON
序列化:JSON.stringify
序列化选项:
JSON.stringify(‘序列化对象’,[] || function, 每一级缩进的空格数)
如果是数组,则返回的结果就是对象中含有数组值的属性
const obj = {
name: "Alice",
age: 25,
job: "Developer",
country: "USA",
};
const jsonString = JSON.stringify(obj, ["name", "job"]);
console.log(jsonString); // {"name":"Alice","job":"Developer"}
如果是函数,则有type 和value值为参数,返回处理后的结果
const obj = {
name: "Alice",
age: 25,
job: "Developer",
country: "USA",
};
const replacer = (key, value) => {
if (key === "age") {
return value + 5;
}
return value;
};
const jsonString = JSON.stringify(obj, replacer);
console.log(jsonString); // {"name":"Alice","age":30,"job":"Developer","country":"USA"}
第三个参数:表示缩进空格数
JSON.stringify(object, null, 4) //4个空格缩进
JSON.stringify(object, null, ‘----’) //4个----缩进
解析JSON.parse()
与stringify差不多,只是还原字符串为json格式,第二个参数为function(type,value),当value为undefined,则会还原对象中删除key
XHR
XHR基本使用
let xhr =new XMLHttpRequest();
xhr.open('get','url', false);
xhr.send(null)
if (xhr.status === 200) {
console.log(xhr.responseText)
}
open接受三个参数:
- 请求方法
- 请求地址
- 请求是否异步 // false为同步。true为异步,以上代码发的是一个同步请求,会等待response才会执行下面的请求
send接受作为请求体发送的数据,如果不发送请求体,则为null
收到响应之后xhr对象的以下属性会被填充数据
| responseText | 响应体返回文本 |
|---|---|
| responseXML | 响应体返回XML |
| status | 响应的http状态 |
| statusText | 响应的http状态描述 |
readyState属性
| 0 | 未初始化(Unimitialized) | 没有调用open()方法 |
|---|---|---|
| 1 | 已打开(Open) | 调用open方法,没调用send方法 |
| 2 | 已发送(Sent) | 调用send方法,但是没收到响应 |
| 3 | 接收中(Receiving) | 收到部分响应 |
| 4 | 完成(Complete) | 收到所有响应可以使用 |
readystatechange事件
每次readyState从一个值变为另一个值都会触发readystatechange事件,一般在open()事件之前就赋值,保障能收到数据
let xhr =new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText);
}
}
xhr.open('get','url', false);
xhr.send(null);
if(xhr.status === 200){
console.log(xhr.responseText);
}
abort事件
收到响应之前想取消异步请求,调用abort()方法
xhr.abort()
timeout事件
xhr设置timeout超时时间,在超时时间内没有响应则触发ontimeout事件,超时readyState会变为4依旧会触发onreadystatechange事件
let xhr =new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText);
}
}
xhr.open('get','url', false);
xhr.timeout = 1000;
xhr.ontimeout = function() {
console.log('请求超时');
xhr.abort();
}
xhr.send(null);
if(xhr.status === 200){
console.log(xhr.responseText);
}
HTTP头部
| | | |
|---|---|---|
| cookie | 页面cookie | |
| Host | 发送请求所在域 | |
| Referer | 发送请求的页面URI | |
| User-agent | 浏览器用户代理字符串 | |
setRequestHeader(key, value) 必须在open()之后 send()之前调用
getRequestHeader(key)
getAllRequestHeaders
进度事件
最初只针对XHR ,后续也添加到了其他事件中
| loadstart | 收到响应的第一个字节时触发 (视频) |
|---|---|
| progress | 接收响应期间反复触发 |
| error | 请求出错时触发 (图片) |
| abort | 调用abort()时触发 |
| load | 成功接收完响应时触发 (图片) |
| load end | 通信完成时,error abort load之后触发 |
load事件
xhr 用load事件替换readystatechange事件,这样无需关心readyState
let xhr =new XMLHttpRequest();
xhr.onload = function() {
if ( xhr.status === 200) {
console.log(xhr.responseText);
}
}
xhr.open('get','url', true);
xhr.timeout = 1000;
xhr.ontimeout = function() {
console.log('请求超时');
}
xhr.send(null);
progress事件
progress会收到event对象
- target:xhr对象
- lengthComputable bool 表示进度信息是否可用
- position:收到的字节数
- totalSize:是响应Content-Length头部定义的总字节数 // todo求证livec那里去看一下
let xhr =new XMLHttpRequest();
xhr.onload = function() {
if ( xhr.status === 200) {
console.log(xhr.responseText);
}
}
xhr.onprogress = function(e) {
console.log(`进度: ${e.position} of ${e.totalSize}`);
}
xhr.open('get','url', false);
xhr.timeout = 1000;
xhr.ontimeout = function() {
console.log('请求超时');
}
xhr.send(null);
if(xhr.status === 200){
console.log(xhr.responseText);
}
为了保证正确执行,必须在open()调用之前添加onprogress事件处理程序
跨资源共享
对于简单请求,Get POST等发送时会有一个额外的请求头origin,origin头部包含发送请求的页面源(协议、域名、端口) 跨域XHR允许访问status和statusText属性,也允许同步请求,但是也增加了些额外限制
- 不能使用setRequestHeader()设置自定义头部
- 不能发送和接收cookie
- getAllResponseHeader()方法始终返回空字符串
预检请求
Option请求方法包含以下头部
- origin
- Access-Control-Request-Method 请求希望使用的方法(Get Post等等)
- Access-Control-Request-Header 可选药使用都好分隔的自定义头部列表
发送这个option请求后服务器会响应如下头部与浏览器沟通这些信息
- Access-Control-Allow-orgin:
- Access-Control-Allow-Methods:
- Access-Control-Allow-Headers:
- Access-Control-Max-Age:缓存预检请求的秒数
凭据请求
跨域一般不允许携带凭证(cookie SSL)但是如果请求想要携带cookie,则设置withCredentials属性设置为true,表明请求会发送凭据,如果服务器允许带凭据的请求,可以在响应中包含如下头部Access-Control-Allow-Credentials:true
代替性跨源技术
图片探测技术
img标签是不会有跨域
const loadImage = (src) => {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = (event) => {
resolve(event.target);
};
img.onerror = (event) => {
reject(event);
};
img.crossOrigin = 'anonymous';
img.src = src;
});
};
JSONP
Jsonp利用了script标签不会跨域,参数是一个回调函数,会在script标签加载完成之后立即执行
function handleResponse(response){
};
let script = document.createElement("script");
script.src = 'http://baidu.com/jsonp?callback=handleResponse';
Fetch API
XMLHttpReqeust可以选择异步,Fetch则必须是异步的,可以通过Response的status(状态码)和statusText(状态文本),如果遇到重定向,响应对象的redirected会被设置为true返回的状态码依旧为200
fetch('url',{
method: 'POST',
headers:{
'Content-Type': 'application/json'
},
credentials:true
}).then(response => {
return response.text();
}).then(data => {
console.log(data);
})
加载Blob文件
response对象有blob方法,,返回一个blob对象实例
const img = document.getElementById('img');
fetch('url',{
method: 'POST',
headers:{
'Content-Type': 'application/json'
},
}).then(response => {
return response.blob();
}).then(blob => {
img.src = URL.createObjectURL(blob);
})
中断请求
xhr提供了abort方法,fetch则也提供了signal信号终止
abortControllor = new AbortController();
fetch('url',{
method: 'POST',
signal: abortControllor.signal,
}).then(response => {
return response.blob();
}).then(blob => {
img.src = URL.createObjectURL(blob);
})
abortControllor.abort();
Headers对象
Header对象与map相似,但是又有区别
- 可以在构造的时候采用对象key value形式,但是map不允许
- 在采用append方法时,会在后续增加用逗号隔开
let headers = new Headers({
url: 'https://www.example.com'
})
headers.append('foo', 'value')
headers.append('foo', 'bar')
console.log(headers.get('foo')) // value, bar
Request对象
reqeust对象接受两个参数,input(一般指url) 和init对象(和fetch的init对象一样)
- 在fetch中使用request
let request = new Request(url,init);
fetch(reqeust).then(response => response)
Response对象
和request相似,但是大多数情况下产生Response对象主要是调用fetch
Request、Response、Body混入
Request 和Response都是用了FetchAPI的body混入,已实现两者承担有效载荷能力。这个混入为两个类型提供了只读的body属性(实现为ReadableStream)Body混入提供了5个方法,用于将ReadableStream转存到缓冲区的内存里,将缓冲区转换为某种js对象类型,以及通过Promise来产生结果
- Body.text()
- Body.json()
- Body.formData()
- Body.arrayBuffer()
- Body.blob()
客户端存储技术
cookie
cookie是与特定域绑定的,设置cookie后,它会与请求一起发送到创建他的域。这个限制能保证cookie中存储的信息只对被认可的接收者开放,不能被其他域访问。 遵守以下规则就不会在任何浏览器中碰到cookie问题
- 不超过300个cookie
- 每个cookie不超过4kb
- 每个域不超过20个cookie
- 每个域不超过80kb
cookie构成
| key | value |
|---|---|
| name | 名称,经过url编码 |
| value | 值,必须经过URL编码 |
| domain | 所在域 |
| path | 路径,一般设置为/ 如果设置为/path,那么只有path路径下才携带这个cookie |
| expires/max-age | 过期时间 |
| size | 大小 |
| httpOnly | 设置为true则可以通过document.cookie获取 |
| secure | 安全标志,为true的话只有使用了SSL安全链接才会发送cookie |
| samesite | 设置为strict则跨域请求不会携带cookie,设置为lax则部分跨域请求会携带cookie,如果设置为none则跨域可以携带cookie |
注意
- 所有的name和value都是URL编码的,因此必须使用decodeURIComponent()解码
- samesite为
- strict则只有同站点才能发送cookie,
- Lax宽松模式(默认)则部分请求会携带cookie,比如通过链接或者get请求导航到你的网站
- none则无限制模式,cookie可以在跨站点请求携带,但是必须设置为secure为true
session
session一般为服务端存储,在这里顺带一起复习
- session一般存储在服务端
- session一般有session_id来管理会话,一般session_id存储在cookie里面
- session能存储复杂数据结构,而cookie只能存储简单数据结构
- cookie被禁用的时候,可以在url里面携带session_id或者在表单字段里面隐藏的发送session_id
问题一:为什么浏览器关闭后,session_id就消失了?
原因一 session_id以cookie的形式存储,这个 Cookie 默认是“会话 Cookie”(即不设置 Expires 或 Max-Age 属性)当浏览器关闭时,会话 Cookie 被自动清除,因此客户端丢失了存储的 Session ID
原因二其实关闭浏览器并不会直接导致 Session 被删除,因此服务器通常为 Session 设置失效时间。如果客户端在超过失效时间内未使用 Session,服务器会认为客户端已停止活动,从而删除 Session 以节省存储空间
解决办法:
- 我们可以采用持久性的cookie保存session_id,设置cookie的过期时间来保证下次能获取到session_id
- 可以采用localStorage来存储session_id
问题二:如何session共享
Web Storage
stroage和其他对象一样,但是增加了以下方法
| 方法 | 说明 |
|---|---|
| clear | 删除所有值,没在fireFox实现 |
| getItem(name) | 取得给定name值 |
| key(index) | 取得给定树值位置的名称 |
| removeItem(name) | 删除给定name的名/值对 |
| setItme(name, value) | 设置给定name的值 |
sessionStorage对象在页面关闭之后就会被清除,loaclStorage则不会
每当Storage发生变化,都会触发storage事件,setItem、delete、removeItem()
window.addEventListener('storage', (e) => {
})
这个事件对象有四个属性 domain、key、newValue、oldValue
模块
CommonJs
- 同步声名依赖,是同步加载
- 无论被引用多少次,模块永远是单例
- 适合服务端
使用ECMAScript
加载顺序会和defer一样
<script type ='module'></script>
导入导出
| 功能 | CommonJS | ESM |
|---|---|---|
| 导出单个值 | module.exports = value | export default value |
| 导出多个值 | exports.key = value | export const key = value |
| 导入单个值 | const value = require('./x') | import value from './x.js' |
| 导入多个值 | const { key } = require('./x') | import { key } from './x.js' |
| 导入所有内容 | const x = require('./x') | import * as x from './x.js' |
| 动态导入 | 不支持 | import('./x.js') |
| 支持静态分析 | 不支持 | 支持(如 Tree Shaking) |
| 执行时加载模块 | 运行时动态加载(CommonJS) | 编译时静态加载(ESM) |
行文到此,部分基础就到这了,关于worker部分和JS API基础部分,作者再另起一篇文章吧