Javascript高级程序设计第四版

160 阅读45分钟

背景

百度部门重组,导致项目不再迭代,本着要滚蛋的,就只能寻求内转,内转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 判断基本类型

  1. null 是一个空对象的引用
  2. undefined 是一个值,当一个变量声明了但是没有赋值 ,那么变量的值就是undefined,对于未声明的变量,只能执行一个有用的操作就是typeof
let message
console.log(message) // 'undefined'
console.log(age) // 报错 
console.log(typeof age) undefined
  1. boolean

  2. Number

    1. 默认10进制
    2. 八进制 开头为0 比如 070 八进制 表示十进制的56
    3. 16进制为 0x开头
    4. 浮点数以.开始 0.1可以简写 .1
    5. e表示10的多少次幂
    6. 最大值为 Number.MAX_VALUE 最小值为Number.MIN_VALUE超出范围则为Infinity或者-Infinity
    7. NAN(Not a Number)不是数值 通过isNaN判断
    8. parseFloat 只解析10进制并且遇到不合法的则停止检测 返回已经检测的值
    9. parseInt()函数 返回是十进制的数,第一个参数是能转化成数字的数 ,第二个参数是进制数 不存在1进制 0进制直接返回0
// 第一个参数 必须是字符串形数字 不能是null undefined true  并且字符串数字的最大一个数必须小于进制数
// 对于负数 检测到不合法则停止检测 对于正数,不合法直接NaN
parseInt('34', 5)//5进制的34 转化为10进制则为19
parseInt('-349', 5) ===parseInt('-34', 5)
  1. String 模版字符串 以及 对象转化为String 调用的是toString 方法

  2. Symbol

    1. 不可以new 不可以改变
    2. 有全局符号注册表 symbol.for() 类似map 没有就注册,没有就返回之前注册的symbol值
    3. symbol.keyFor接受symbol.for返回的symbol 并且返回对应的字符串
    4. 可以用作属性 比如Object[symbol('')]
    5. symbol.Iterator 在for of 使用
    6. symbol.asyncIterator 这个符号作为一个属性 表示:一个方法 返回对象默认的asynceIterator 在for await of语句使用
    7. symbol.hasInstance 这个符号作为一个属性 表示:一个方法,决定一个构造器是否认可一个对象是它的实例
    8. symbol.match。 作为一个属性:由String.prototype.match方法使用
console.log('foobar'.match(/bar/))
 // [ 'bar', index: 3, input: 'foobar', groups: undefined ]
  1. Object

    1. hasOwnProperty("string")判断当前对象是否具有某个属性
    2. isPrototypeOf(Object)判断当前对象是否为另一个对象原型
    3. propertyIsEnumberble()判断是否可以用for in 迭代
    4. toString()返回对象的字符串表示(对象转字符串)
    5. 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;
}


变量、作用域、内存

原始值与引用值

  1. 基本数据类型
    • 存放位置在
    • 复制一个基本数据,则是在栈重新复制,并且压入栈
  1. 复杂数据类型
    • 存放位置
    • 复制一个复杂数据类型,变量名不同,但是都指向了堆的同一块地址
  1. 传递参数:无论基本数据还是复杂数据都传递的是值,只不过是复杂数据类型是通过引用访问对象
function setName(obj){
    obj.name = "John";
    obj = new Object();
    obj.name = "Mike";
}
let person = new Object();
setName(person);
console.log(person.name); // John
  1. 确定类型: instanceof检测复杂数据类型,原理:判断构造函数原型是否出现在实例对象的原型链上面

执行上下文和作用域

上下文:全局上下文、函数上下文、块级上下文

VO :每个上下文关联的变量对象(variable Object)

scope chain: 上下文代码执行的时候会创建作用域链(保障上下文代码访问变量和函数的顺序)

AO:上下文是函数,则活动对象(activation Object)用作变量对象

标识符查找:当前上下文寻找,没找到沿着作用域链找,如果实在未找到,则说明没声明变量

垃圾回收和内存泄漏

  1. 垃圾回收

    1. 标记清除法:标记内存所有变量,执行代码之后,执行的变量取消标记,那么有标记的就是要内存回收的
    2. 引用计数:如果某个变量被引用,则引用数+1,最后引用数为0的则回收;缺点:循环引用无法回收
  2. 内存泄漏

    1. 意外的全局变量:比如 某个函数内 num=10 没有用let const var 声明,则会使全局变量无法回收
    2. 闭包
    3. 没有移除监听器 removeAddEventListener
    4. 定时器未清除

基本引用类型

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 匹配任何模式

实例方法:

  1. exec()接受一个参数,要应用模式的字符串 ,如果没找到则返回null;如果找到,则返回第一个匹配信息的数组,但是这个数组有额外的index(第一个找到的起始位置)和 input() 属性
  2. test() 如果输入的字符串和模式匹配则返回true否则返回false

原始包装类型

  1. Boolean

    1. 重写valueOf方法 返回一个原始值的true或false
    2. 重写toString方法,返回字符串'true'或者'false'
  2. Number

    1. toString()接收一个基数,表示转化为相应的字符串的进制数
    2. toFixed()保留几位小数
    3. toExponential() 科学计数法的小数
  3. String

    1. length 编码长度
    2. charCodeAt(index) 指定位置的ascall码
    3. split(”“) 分隔
    4. slice(start,end) 返回子字符串 [start,end) 为负值情况:start+length,end+length
    5. substring(start,end) 返回子字符串 [start,end) 为负值情况:start +length ,end =0
    6. substr(start,length) 返回子字符串的[start,start+length) 将所有负参数值转换为0
    7. indexOf(“”,start?)第一个参数为子字符串 ,第二个参数则从start位置开始找,返回 index || -1
    8. includes()包含方法返回true 或者false
    9. trim()清理字符串前后空格 trimLeft()、trimright()
    10. repeat(nums)方法:字符串复制nums次
    11. padStart(length,'string') padEnd(length,string),将字符串填充至长度为length,并且填充字符是stirng
    12. toLowerCase() toUpperCase()转化为小写或者大写
    13. match方法和正则exec方法一致
    14. replace(正则,targetString)替换 将字符串正则匹配的替换成targetString
    15. localeCompare(target)比较与target字符串大小
  4. Global

    1. encodeURI 不会编码不属于URL组件的特殊字符 : // ?#

    2. encodeURIComponent 会编码所有特殊字符

    3. eval()执行javaScript字符串 eval定义的任何变量和函数不会提升

    4. Math

      1. max ,min
      2. ceil() 向上取整
      3. floor()向下取整
      4. round()四舍五入
      5. random [0,1)的随机数
      6. abs 绝对值
      7. pow(x,power)x的多少次幂
      8. sqrt()平方根

集合引用类型

Array
  1. Array.from 类数组转数组

  2. Array迭代器方法

    1. keys() 返回下标数组
    2. values()返回值数组
    3. 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);
}
  1. fill()

  2. slice() 和stirng方法一样

  3. splice()

    1. 删除 splice(index ,length)从index位置开始删除,删除长度为Length
    2. 插入 splice(index ,0,.....)第二个参数为0 ,后续参数则是在index位置插入的数
    3. 替换 splice(index , 2,....)在index初删除2个元素,并且添加....个元素
  4. push pop shift unshift

  5. every 每一项满足要求则返回true 否则fasle

  6. filter 过滤

  7. some 有一项满足就返回true

  8. reduce()计算累加

ArrayBuffer

和array大部分方法一致,ArrayBuffer主要用于二进制数据上传

Map weakMap

建立键/值对;保持了插入的顺序;size属性 返回多少键/值对

  1. set
  2. get
  3. has
  4. keys 返回迭代器对象
  5. delete
  6. clear
  7. keys values entries三个迭代方法 和数组一样

weakMap和map区别:

  1. 没有size属性
  2. 键只能是对象
  3. 当键所指向的对象被回收了,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()方法来中断

对象、类与面向对象编程

对象

属性

分为两种:数据属性,访问器属性

  1. 数据属性
  • [[Configurable]] 表示属性是否可以通过delete删除并且重新定义,是否修改它的特性,以及是否可以把它改为访问器属性,一般直接定义在对象上面的属性的[[configurable]]特性为true
  • [[Enumberable]] 表示该属性是否可以通过for in循环 一般为true
  • [[Writable]]这个属性是否可以被修改,默认情况下为true,为false代码可以修改,但是会默认失败也不报错。
  • [[Value]] 属性值 默认undefined

属性特征不能直接修改定义,必须通过Object.defineProperty(),此方法会默认将属性的 configurable、enumberable、writable设置为false

  1. 访问器属性
  • [[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函数
  1. 定义多个属性:Object.defineProperties(obj,{})
  2. 读取属性特性:Object.getOwnPropertyDescriptor(obj,属性)
合并

Object.assign(target,.......obj)方法,浅拷贝

将许多原对象中的属性以及原对象的原型对象可枚举(Enumberable为true)的属性一并复制

增强对象语法

简写、可计算属性、解构赋值

注意点:

  1. 解构不会影响函数的arguments对象
  2. 解构可以不用事先声明变量,如果事先声明了,则用()包起来解构 也可以理解为解构重命名
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'
}

  1. 原型链继承

主要作用:继承方法

[流程图]

问题:所有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' ]
  1. 盗用构造函数继承

主要作用: 继承属性,并且实例化所继承父类的引用属性是相互隔开的(因为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
  1. 组合继承

继承属性和方法

function B(){
  this.b = true;
  A.call(this)
}
B.prototype = new A()
  1. 原型式继承

Object.create(A,B)

原型式继承会继承A的方法和属性,然后也会将B对象的属性方法继承,但是会同名覆盖

  1. 寄生式继承

目的:增强这个对象

let Person = {
  name:'杰瑞'
}
function createAnother(origin){
  let clone = Object.create(origin)
  clone.getName = function(){ //增强对象
    return this.name
  }
  return clone
}
  1. 寄生式组合继承
function B(){
  this.b = true;
  A.call(this) //继承属性
}
A.prototype.constructor = B //规范
B.prototype = A.prototype; // 继承方法
B.prototype.otherFunc = function(){} //增强方法

[流程图]

  1. 类构造函数 constructor,在new操作符创建类的实例的时候,应该调用constructor函数
  2. 类是特殊函数,使用typeof会返回 function
  3. 类中的constructor方法 不会被当成构造函数,使用instanceof 会返回false
class Person{}
let p = new Person()
console.log(p instanceof Person.constructor) //false
  1. 静态属性:static 所有类实例共享
  2. 实现 [Symbol.iterator]方法就可以可迭代,* yield就可以使用生成器
  3. 继承 extends关键词
  4. 使用super关键字调用父类构造函数之间不能使用this
  5. 通过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())

函数

  1. 箭头函数:没有this、arguments、super以及new.target,箭头函数不能实例化
  2. 函数名:函数对象会向外暴露一个name属性,表达他是哪一个函数
function foo(){

}
let a = foo
console.log(a.name)
  1. 参数:arguments是一个类数组,传入多少,参数长度就是多少,具有一个callee属性指向arguments对象所在函数指针
  2. 参数没有传默认值为undfined,传入undefined,默认没有传,但是arguments的length会算+1,即:arguments[2] = undefined
  3. 没有重载,只能同名覆盖
  4. 默认参数作用域:后声明的参数,可以引用先定义的参数
function foo(name ='杰瑞',key = name,value){
}
foo('bar');
  1. 参数会有暂时性死区
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
}

函数内部

  1. arguments

  2. this

    1. 在标准函数中,this引用的是把函数当成方法调用的上下文对象(谁调用函数,该函数内部this就指向谁)
    2. 在箭头函数中,this引用的是定义箭头函数的对象的上下文 (谁定义箭头函数,箭头函数的this就指向谁)
    3. 匿名函数不会绑定某个对象,意味着this会指向window
    4. 独立函数调用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();
  1. 函数caller属性:caller属性引用的是调用当前函数的函数
  2. ES6新增 new.target. 函数是正常调用则new.target为undefined。如果使用new关键字调用,则new.target指向被调用的构造函数

函数属性和方法

  1. call() 参数一个一个传递
  2. apply()第二个参数为数组
  3. 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(拒绝)
  1. executor是一个执行器函数,是同步执行
  2. promise的状态转换是不可撤销的
  3. try catch不能捕获Promise的错误,只能捕获同步代码的错误,Promise属于异步
  4. 当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

  1. 声明一个异步函数
  2. 异步函数没返回值默认返回undefined
  3. 异步函数始终返回Promise对象

await

  1. await会暂停执行异步函数后面的代码
  2. 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));
  1. await 不能在顶级上下文和script标签使用
  2. 异步函数的特质不会扩展到嵌套函数
  3. 执行到 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

  1. window全局对象,var声明的所有全局变量和函数都会成window对象属性和方法

    1. top对象指向最外层窗口(浏览器本身)
    2. parent对象指向当前窗口的父窗口:某个窗口是由某个窗口打开的
    3. self对象始终指向window
  2. css像素比:window.devicePixelRatio属性(dpr)物理像素/css像素,

  3. 窗口大小:window.outerWidth、window.outerHeight、window.innerWidth、window.innerHeight

outerWidth和outerHeight返回浏览器自身大小

innerWidth和innerHeight返回浏览器窗口页面视口的大小(不包含浏览器边框和工具栏)

这个和document.documentElement.clientWidth、document.documentElement.clientHeight没啥区别

  1. 窗口距离:screenTop(浏览器窗口距离屏幕顶部距离) screenLeft
  2. 视口位置
  • window.scrollBy(x,y)基于当前位置,继续滚动向右x距离和向下y距离
  • window.scrollTo(x,y) 滚动到页面xy位置
  1. 打开窗口:window.open()返回一个新建窗口的引用
  2. 对话框:alert 、confirm、prompt
  3. 定时器 setTimeout、setInterval

Location

  1. window.location === document.location
location.href = location.protocol + `//` + location.host + location.pathname + location.search + loaction.hash
  1. 查询字符串: URLSearchParams 提供了get set delete方法
    let search = new URLSearchParams(location.search)
    search.set("key","value")
    search.get("key")
    search.delete("key")
  1. 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();
  1. 操作地址
    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

image.png

    • 节点的操作
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
  1. 获取dom元素
document.querySelector('.className'); // css选择符为.className的第一个元素
document.querySelectorAll('.className') // 返回css选择符为.className的NodeList静态实例
  1. matches方法,接受一个css选择符参数,如果元素匹配则返回true,使用这个方法可以检测该元素能不能被querySelector或querySelectorAll获取
H5
  1. css扩展
document.getElementByClassName('.className')
  1. 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类
  1. 焦点管理:document.activeElement 包含当前拥有焦点的元素
bt.onclick = function() {
   contain.focus();
   console.log(document.activeElement); //botton元素
}
  1. HTML Document扩展

document有readyState属性,表示文档状态

document.readyState = "complete"; // 文档加载完成
document.readyState = "loading"; // 文档正在加载
  1. 插入标记

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);
  1. children属性(HTMLCollection类型) 与childNodes(NodeLists类型)类似;children

区别:children不包含文本节点、注释节点

  1. contains方法:确定一个元素是不是另外一个元素后代
document.documentElement.contains(document.body) // true
  1. scrollIntoView方法:让元素滚动到可视区域
setTimeout(()=>{
    dom.scrollIntoView({
    aliginToTop: true, // 滚动后元素顶部与适口顶部对齐,false为底部对齐,默认true
    scrollIntoViewOptions:{
        behavior: 'smooth', // 平滑过度
        block: 'center', // 垂直对齐
        inline: 'center' // 水平对齐
    }
})
},1000)
元素尺寸
  1. 偏移尺寸 image.png 所有的偏移尺寸都是针对带有定位的父节点,返回值不带单位
offsetParent返回该元素带有定位的父级元素,如果父级元素没有定位则返回body
offsetTop返回该元素上外边框相对 带有定位的父级元素上内边框 的偏移,如果父级元素没有定位则返回相对body上方的偏移
offsetLeft返回该元素左外边框相对带有 定位的父级元素左内边框 的偏移,如果父级元素没有定位则返回相对body左侧的偏移
offsetWidth返回该元素包括padding+border+content的宽度
offsetHeight返回该元素包括padding+border+content的高度度
  1. 滚动尺寸

返回值不带单位 image.png

scrollLeft被卷去的左侧距离
scrollTop被卷去的上侧距离
scrollHeight元素总高度(包括滚动不可见的区域)
scrollwidth元素总宽度(包括滚动不可见的区域)
  1. 客户端尺寸

客户端尺寸由内容区域+内边距

document.documentElement.clientWidth浏览器视口宽度
document.documentElement.clientHeight浏览器视口高度
  1. 元素尺寸getBoundingClientRect()

返回距离视口的位置,Element不包含margin

image.png

元素遍历

之前的children属性和childNodes属性都可以 for of遍历,不会深度遍历

  1. 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();
}
  1. TreeWakker方法

在createNodeIterator基础上 多增加了 parentNode 、firstChild 、lastChild 、nextSibling、 previousSibling方法

image.png

document.createTreeWalker(); // 参数和createNodeIterator一样

事件

DOM事件流分为三个阶段:事件捕获、到达目标、冒泡阶段

image.png

现代浏览器

  1. DOM0,将函数直接赋值事件处理程序属性
let button = document.querySelector(".button");
button.onclick = function (e) {
    console.log("button");
}
  1. 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");
});

  1. 事件对象event

event指向当前元素,不同的事件有额外的属性,一下是他们共有属性

属性或方法类型说明
bubblesbool事件是否冒泡
cancelablebool是否可以取消事件默认行为
currentTarget元素绑定事件的元素
target元素事件目标触发的元素
defaultPreventValuebool为true表示已经调用了preventDefault()方法
detail整数事件相关的其他信息( e.detail ===1)
eventPhase整数表示调用事件处理阶段 1代表捕获 2代表到达,3代表冒泡
preventDefault()函数阻止默认行为 cancelable为true才可以调用
stopPropagation()函数阻止冒泡 bubbles为true才可以调用
stopImmediatePropagation()函数取消后续所有事件捕获或者冒泡,并阻止调用任何事件后续处理程序
trustedbooltrue是浏览器生成的,false表示事件是开发者js创建的
typestring被触发事件的类型

IE浏览器

  1. 事件处理
  • attachEvent 和 detachEvent,在添加事件处理要加on
  • IE只支持冒泡
  • IE事件处理函数中this指向window
button.attachEvent("onclick", () => {
    console.log("button");
});
  1. 事件对象

事件对象event 是window的一个属性

属性或方法类型说明
cancelBubblebool默认false 设置为true可以取消冒泡
returnValuebool默认为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;
    }
  }
};

事件类型

  1. DomContentLoaded
  2. readystatechange事件

readystatechange事件对象event都有一个readyState属性,readystatechange事件与load事件共用时,触发的顺序不能保证,

uninitialized对象存在并尚未初始化
loading正在加载数据
loaded已经加载完数据
interactive对象可以交互,但未加载完成. dom构建好了
complete对象加载完成
  1. load
  2. pageshow

fireFox和opera开发往返缓存的功能, 把整个页面缓存在内存中,再次导航到这个页面就不会触发load事件。

  • pageshow首次会在load之后执行,来自往返缓存后,会在页面状态完全恢复触发,事件必须添加到window上面,事件对象有个persisted属性,是一个bool,如果来自往返缓存则为true
  1. beforeunload

没出现弹窗原因:

    1. IE 和firefox可能支持
    2. 页面关闭速度太快了
window.addEventListener('beforeunload', function(e) {
    e.returnValue = ''; // 有些浏览器需要这个属性被设置来触发事件
    return '';
});

5. pagehide

  • pagehide会在unload之前执行,persisted为true则会表明页面在卸载之后会被保存在往返缓存中。
  1. unload事件

unload事件常用于一个页面导航到另外一个页面,清理引用

  1. resize事件

浏览器窗口被缩放到新高度或者宽度触发

window.addEventListener('resize', function(e) {
    document.documentElement.style.fontSize = window.innerWidth / 375 * 10 + 'px'
})
  1. scroll事件
  2. 焦点事件
事件名称说明
blur失去焦点
focusout失去焦点,会冒泡
focus获取焦点
focusin获取焦点会冒泡
  1. 鼠标和滚轮事件
click
dbclick
mousedown
mouseup
mouseenter鼠标从元素外部移动到元素内部:不冒泡、经过后代元素也不触发
mouseover鼠标从元素外部移动到元素内部:冒泡、经过后代元素触发
mouseleave
mouseout冒泡,经过后代元素会触发(比如一个盒子里面有一个按钮,当你给盒子添加mouseout事件时,你在元素内部按钮区域移动到盒子区域 甚至移出盒子 都会触发mouseout事件)
mousemove鼠标在元素上移动时反复触发
mousewheel鼠标滚轮事件

鼠标对应的事件对象属性

offsetX光标相对于目标元素边界的x坐标
offsetY光标相对于目标元素边界的y坐标
clientXclientY事件触发时,光标元素相对视口的距离
screenXscreenY事件触发时,光标元素相对屏幕的距离(与BOM的screen对象的left,top,差不多,但是screen是浏览器相对屏幕,而这个属性是元素相对屏幕)
  1. 设备事件
  • 苹果公司在Safari浏览器添加orientationchange事件:判断用户设备是垂直模式还是水平模式
  • deviceorientation事件,可以获取设备加速计信息,也就是设备类似于摇一摇 抬屏,在空间上xyz发生变化就会触发
  • devicenotion事件,用于设备实际上在移动(确定设备正在掉落或者正拿在一个行走的人手里)

模拟事件

用document.createEvent()创建一个event对象,用dispatchEvent()方法触发事件

createEvent 接受一个字符串型参数

参数说明初始化方法
UIEvents页面事件
MouseEvents鼠标事件initMouseEvent()
KeyboardEvent键盘事件initKeyboardEvent()
HTMLEventsHTML事件(DOM3分散到其他事件大类当中)
CustomEventDOM3增加的自定义事件initCustomEvent()

初始设置方法的参数和event对象属性一一对应

initCustomEvent接收四个参数

参数说明
type事件类型
bubbles是否冒泡
cancelable事件是否可以取消
detaile.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()方法

  1. 执行的命令
  2. 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),在不同执行上下文(不同源页面和不同工作线程)之间传递信息

  1. 发送消息postMessage()方法

    • message:消息
    • targetOrigin:目标接收源的字符串
    • 可传输对象数组:可选,只有工作线程相关
  2. 接收消息

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
);
参数描述
xpathExpressionXPath 表达式。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对象

  1. target:xhr对象
  2. lengthComputable bool 表示进度信息是否可用
  3. position:收到的字节数
  4. 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相似,但是又有区别

  1. 可以在构造的时候采用对象key value形式,但是map不允许
  2. 在采用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对象一样)

  1. 在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来产生结果

  1. Body.text()
  2. Body.json()
  3. Body.formData()
  4. Body.arrayBuffer()
  5. Body.blob()

客户端存储技术

cookie

cookie是与特定域绑定的,设置cookie后,它会与请求一起发送到创建他的域。这个限制能保证cookie中存储的信息只对被认可的接收者开放,不能被其他域访问。 遵守以下规则就不会在任何浏览器中碰到cookie问题

  1. 不超过300个cookie
  2. 每个cookie不超过4kb
  3. 每个域不超过20个cookie
  4. 每个域不超过80kb
cookie构成
keyvalue
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

注意

  1. 所有的name和value都是URL编码的,因此必须使用decodeURIComponent()解码
  2. samesite为
    • strict则只有同站点才能发送cookie,
    • Lax宽松模式(默认)则部分请求会携带cookie,比如通过链接或者get请求导航到你的网站
    • none则无限制模式,cookie可以在跨站点请求携带,但是必须设置为secure为true

session

session一般为服务端存储,在这里顺带一起复习

  1. session一般存储在服务端
  2. session一般有session_id来管理会话,一般session_id存储在cookie里面
  3. session能存储复杂数据结构,而cookie只能存储简单数据结构
  4. cookie被禁用的时候,可以在url里面携带session_id或者在表单字段里面隐藏的发送session_id
问题一:为什么浏览器关闭后,session_id就消失了?

原因一 session_id以cookie的形式存储,这个 Cookie 默认是“会话 Cookie”(即不设置 ExpiresMax-Age 属性)当浏览器关闭时,会话 Cookie 被自动清除,因此客户端丢失了存储的 Session ID
原因二其实关闭浏览器并不会直接导致 Session 被删除,因此服务器通常为 Session 设置失效时间。如果客户端在超过失效时间内未使用 Session,服务器会认为客户端已停止活动,从而删除 Session 以节省存储空间
解决办法:

  1. 我们可以采用持久性的cookie保存session_id,设置cookie的过期时间来保证下次能获取到session_id
  2. 可以采用localStorage来存储session_id
问题二:如何session共享

www.cnblogs.com/jing99/p/11…

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

  1. 同步声名依赖,是同步加载
  2. 无论被引用多少次,模块永远是单例
  3. 适合服务端

使用ECMAScript

加载顺序会和defer一样

<script type ='module'></script>

导入导出

功能CommonJSESM
导出单个值module.exports = valueexport default value
导出多个值exports.key = valueexport 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基础部分,作者再另起一篇文章吧