JS红宝书

299 阅读14分钟

从今天(2021.12.19)起我开始学习红宝书!

第一章

DOM

文档对象模型

BOM

浏览器对象模型:主要针对浏览器窗口和子窗口

  • 弹出新窗口的能力
  • 移动,缩放和关闭浏览器窗口的能力
  • navigator对象,提供关于浏览器的详尽信息
  • location对象,提供浏览器加载页面的详尽信息
  • screen对象,提供源于用户屏幕分辨率的详尽信息
  • performance对象,提供浏览器内存占用,导航行为和时间统计的详尽信息
  • 对cookie的支持

第二章

script元素

  • async: 立即下载脚本,但不阻止其他页面动作;
  • defer: 表示脚本可以延迟到文档完全被解析和显示之后再执行;

defer与async属性只对外部脚本文件才有效,支持H5的浏览器会忽略行内defer属性; defer与async的不同是:标记为async的脚本并不会保证能按他们出现的次序执行

第三章 —— 语言基础

var

  • 函数作用域
  • 变量提升

let

  • 块作用域
  • 不允许同一个块作用域中出现两次
  • 暂时性锁死:let声明的变量不会在作用域中被提升,在let声明之前的执行瞬间被成为暂时性锁死

块级作用域由最近一对包含的花括号{}界定

const

  • extend(let)
  • 声明变量必须初始化

null

  • 空指针对象,初始化对象

转化为字符

  • toString(): 返回为值得等价物
  • String(): 一切皆可用
  • +number
  • 模板字面量

null & undefined 没有toString() 方法

let found = true;  
found.toString()  // "true"

object

  • isPrototypeOf(object) 判断当前对象是否尾另一个对象的原型

第四章 —— 执行上下文与作用域

执行上下文(作用域):全局上下文,函数上下文和块级上下文

原始值 & 引用值

  • 原始值大小固定保存在栈内存
  • 引用值是对象,存储在堆上

垃圾回收

  • 离开作用域的值会被自动标记为可回收,然后在垃圾回收期间回收
  • 标记清理,给不适用的值加上标记

提升性能

  • 不用的全局对象赋值null
  • 使用let const;少使用var
  • 避免“先创建再补充”式的动态属性赋值,在构造函数中一次性声明所有属性;把不行要的属性设置为null
  • 减少浏览器执行垃圾回收的次数 ——> 提升js性能

当内存中引用的次数为0的时候内存才会被回收

造成内存泄漏的原因

  1. 意外的全局变量
  2. 没有清楚定时器
  3. 闭包(将事件处理函数定义在外部)
function bindEvent(){ 
    var obj=document.createElement('xxx') 
    obj.onclick=function(){ // Even if it is a empty function } 
 }
 
 // 将事件处理函数定义在外面
function bindEvent() {
  var obj = document.createElement('xxx')
  obj.onclick = onclickHandler
}
// 或者在定义事件处理函数的外部函数中,删除对dom的引用
function bindEvent() {
  var obj = document.createElement('xxx')
  obj.onclick = function() {
    // Even if it is a empty function
  }
  obj = null
}


闭包为什么会造成内存泄漏? 因为变量得不到释放,一直被占用

第五,六章

RegExp

元字符需要被转义:([{^$|}])?*+.

Sting

提取子字符串

  slice(a, b), sbustring(a, b) // a: 起始位置,b:结束位置
  substr(a, b) // a: 起始位置,b: 子字符串个数

单例内置对象

对象字面量:对象定义的简写形式

  • Object
  • Array
  • String
  • Global
  • Math

Math.random() * total_number_of_choices + first_possibile_value

  • Math.random()*9 + 2 //2 -10

第七章 迭代器与生成器

循环是迭代机制的基础,因为它可以指定迭代的次数,以及每次迭代要执行什么样的操作 什么情况下会用到生成器与迭代器呢?

第八章

创建对象的方法

  • 工厂模式
function createPerson(name, age, job) {
  let o = new Object()
  o.name = name;
  o.age = age;
  o.sayName = function() { }
  return o
}
  • 构造函数模式
function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.sayName = function() {}
}

构造函模式与工厂模式区别

  1. 没有显示创建对象
  2. 属性和方法直接赋值给了this
  3. 没有return
  • 原型模式
  1. 每个函数都会创建一个prototype属性,这个属性是个对象,包含应该由特定引用类型的实例共享的共享的属性与方法;每个原型对象自动获得一个名为constructor的属性,指回构造函数
  2. 使用原型对象的好处是,在它上面定义的属性和方法可以被对象实例共享
  3. 每次调用构造函数创建一个新实例,这个实例内部[[Prototype]]指针就会被赋值为构造函数的原型对象
  1. 实例与构造函数原型之间有直接联系,但实例与构造函数之间没有
  2. 构造函数通过prototype属性链接到原型对象
  3. 实例通过__proto__链接到原型对象
  4. 同一个构造函数创建的两个实例共享同一个原型对象 正常的原型链都会终止于Object的原型对象;Object原型的原型是null
  • for-in VS Object.keys() 枚举顺序是不确定的;
  • getOwnPropertyNames() 枚举是确定的

继承

  • 原型链 通过原型继承多个引用类型的属性和方法

原型链:每个构造函数有个原型对象,原型有一个属性指回构造函数;实例有个内部指针指向原型; 如果原型是另外一个类型的实例呢?那就意味着这个原型本身有个内部指针,指向另一个原型

  • 使用call, apply 方法以新创建的对象为上下文执行构造函数
function SuperType() {
  this.color = ['red', 'blue', 'green']
}

function SubType() {
   // 继承Supertype
  SuperType.call(this)
}

let instance1 = new SubType();
instance1.color.push('black')
instance1.color()  //'red', 'blue', 'green', 'black'

let instance2 = new SubType()
instance2.color() //'red', 'blue', 'green'
  • 传递参数
function SuperType(name) {
  this.name = name
}

function SubType() {
  SuperType.call(this, 'olivia')
  this.age = '29'
}

let instance = new SubType()
instance.name  //olivai
instance.age  //29
  • 原型式继承:Object.create()

let person = {
  name: 'olivia',
  frineds: [1,2,3]
}

let person1 = Object.create(person)
person1.name = 'alice'
person1.frineds.push(4)

let person2 = Object.create(person)
person1.name = 'jane'
person1.frineds.push(5)

console.log(person.frineds) // [ 1, 2, 3, 4, 5 ]
  • extends关键字

在类构造函数中使用super(),

  • 可以调用父类的构造函数,并将返回的实例赋值给this
  • 不能在super前使用this
 super() => super.constructor()

// 有构造函数的类;
// constructor关键字用于在类定义块内部创建类的构造函数
class Bar {
  constructor() {}
}

函数

  • 函数式编程

用函数编程;分为两类:过程式声明式
“过程式”:沿着流程或者步骤走
“声明式”:只表达要做什么,不关心内部实现细节

  • 不可变性

不直接更改原始数据,而是创建数据的副本,所有操作都使用副本来进行。

  • 高阶函数
  • 函数名:指向函数的指针

使用不带括号的函数名会访问函数指针,而不会执行函数

  • arguments: 使用function定义函数时,可以在函数内部访问arguments对象,从中取得传来的每个参数
  • this
  1. 标准函数中,this引用的是把函数当成方法调用的上下文对象;函数被调用时才能确定this指向
  2. 箭头函数中,this引用的是定义箭头函数的上下文
  • apply、call、bind: 以指定的this调用方法;

bind会创建一个新的函数实例,this值会绑定到传给bind()对象

  • 尾调用优化
  1. 代码在严格模式下
  2. 外部函数的返回值是对尾函数的调用
  3. 尾调用函数不需要执行额外逻辑
  4. 尾调用函数不返回闭包;
  • 闭包

引用了另一个函数作用域中变量的函数 `` window.identity = 'this window'

window.identity = 'this window'

let object = {
  identity: 'My Object',
  getIdentity() {
    return function() {
     // 内部函数永远不可能直接访问外部函数的this
      return this.identity  // this.window
    }
  },
   getIdentity() {
    let that = this
    return function() {
     // 内部函数永远不可能直接访问外部函数的this
      return that.identity  // My Object
    }
  },
}

第十一章

Promise

不能用try...catch捕获错误

  • 串行化异步任务(下一个请求依赖前一个请求的返回值)
function delayedResolve(str) {
  return new Promise((resolve, reject) => {
    console.log(str)
    setTimeout(resolve, 1000)
  })
}
delayedResolve('p1 executor')
.then(() => delayedResolve('p2 executor'))
.then(() => delayedResolve('p3 executor'))
.then(() => delayedResolve('p4 executor'))

async await

  • async 如果不包含await关键字,其执行基本跟普通函数没区别
async function foo() {
  console.log(await Promise.resolve('foo'))
}

async function bar() {
  console.log(await 'bar')
}

async function baz() {
  console.log('baz')
}

foo()
bar()
baz()

// baz,foo,bar
  • js运行时在碰到await关键字,会记录在哪里暂停执行,等到await右边的值可用了,js会向队列中推送一个任务,这个任务会恢复异步函数的执行;
async function foo() {
  console.log(2)
  await null
  console.log(4)
}
console.log(1)
foo()
console.log(3)

// 1,2,3,4
  • await 后面跟一个promise
async function foo() {
  console.log(2)
  console.log(await Promise.resolve(8))
  console.log(9)
}

async function bar() {
  console.log(4)
  console.log(await 6)
  console.log(7)
}

console.log(1)
foo()
console.log(3)
bar()
console.log(5)

// 1,2,3,4,5,8,9,6,7
  • 串行执行期约
const add2 = x => x+2
const add3 = x => x+3
const add4 = x => x+4

const add10 = async x => {
  for(const fn of [add2, add3, add4]) {
    x = await fn(x)
  }
  return x
}
add10(10).then(console.log)
// 19

第十二章 Bom (Browser Object Model)

window对象在浏览器中有两重身份,一个是Global对象,另一个是js接口

window 对象

  • 通过var声明的所有全局变量都会变成window对象的属性和方法(使用let or const,则不会添加到全局变量)
  • document.compatMode: 判断浏览器模式(怪异,正常(‘CSS1Compat’))
  // innerWidth,innerHeight -> 返回浏览器窗口中页面视口的大小(不包含浏览器边框&工具栏)
  console.log('innerWidth', window.innerWidth)
  console.log('innerHeight', window.innerHeight)
  // console.log('outerWidth', window.outerWidth)
  // console.log('outerHeight', window.outerHeight)

  // 标准模式:clientWidth, clientHeight 返回页面视口的宽度和高度
  console.log(' 标准模式 clientWidth', document.documentElement.clientWidth)
  console.log(' 标准模式 clientHeight', document.documentElement.clientHeight)

   // 非标准模式:clientWidth, clientHeight 返回页面视口的宽度和高度
   console.log('非标准模式 clientWidth', document.body.clientWidth)
   console.log('非标准模式 clientHeight', document.body.clientHeight)
  • window.open: 通过newWin.opener 与打开它的窗口通信 检查window.open的返回值,可以判断是否屏蔽弹窗
  • setTimeout(): 会返回一个timeoutId,取消可以用clearTimeout(timeoutId)

最好不要使用setInterval(),

  • 使用setTimeout不一定要记录超时id,会在满足条件下停止,否则开启另一个超时id;
  • setInterval,一个任务结束和下个任务之间的时间间隔是无法保障的 使用setTimeout代替 setInterval
// 
const max = 10;
let num = 0;

const fun = () => {
  num ++
  if(num > max) {
    clearTimeout(setIimeoutId)
  }else {
    console.log('num', num)
    setTimeout(fun, 1000)
  }
}

const setIimeoutId = setTimeout(fun, 1000)

location 对象

它既是window对象,也是document对象

  • 查询字符串
let getQueryStringArgs = () => {
  let args = {}
  let qs = location.search.length > 0 ? location.search.substring(1) : ''
  for(let key of qs.split('&').map(item => item.split('='))){
    let name = decodeURIComponent(key[0])
    let value = decodeURIComponent(key[1])
    if(name.length) {
      args[name] = value
    }
  }
  return args
}
  • URLSearchParams: 检查和修改查询字符串 new URLSearchParams
const qs = '?&rsv_spt=1&rsv_iqid=0x88fb9af2000bd02e&issp=1&f=8&rsv_bp=1&rsv_idx=2'
new URLSearchParams(qs).get('rsv_iqid')

- 修改浏览器地址的方法

  1. location.assign()
  2. location.href
  3. windown.location

以上三种都会在浏览器历史记录增加响应记录

  1. location.replace() // 不会增加历史记录 - reload(): 重新加载
    • location.reload() // 可能从缓存中获取
    • location.reload(true) // 从服务器中获取

screen 对象

保存浏览器窗口外面的客户端显示器的信息

history 对象

navigator 对象

  • window.navigator.userAgent
  • navigator.geolocation // 感知当前设备的地理位置

第14章 Dom(Document Object Model)

Node 类型

  • 元素节点 (1)
  • 文本节点 (3)
  • 节点关系
  1. childNodes中只有一个节点,则它的previousSibling & nextSibling属性都是null
  2. firstChild = someNode.childNodes[0]
  3. lastChild = someNode.childNodes[someNode.length - 1]
  4. appendChild

Document 类型

  • document.documentElement: 取得对<html>的引用
  • document.body: 取得<body>的引用
  • document.title: 页面的title
  • document.domain: 不同子域的页面无法通过js通信,把每个页面的domain设置成相同值就可以访问了 domain 一旦放松就不能收紧了
  • getElementsByName(): 同一字段的单选按钮,必须有相同的name属性,才能确保把正确的值发送给服务器
  • 文档写入: write(),writeln(),open(),close()

Element类型

nodeType === 1

  1. HTML元素
  2. getAttribute(), setAttribute()
  3. childNodes属性包含元素所有的子节点,这些子节点可能是其他元素,文本节点,注释或处理指令。不同浏览器在识别这些节点时,表现有明显不同

Text类型

nodeType === 3

DocumentFragment 类型

可以使用此属性,操作dom,避免浏览器多次渲染;

let customFrag = document.createDocumentFragment()

const myUl = document.getElementsByTagName('ul')[0]
for(let i =0; i<5;i++) {
  let myLi = document.createElement('li')
  myLi.appendChild(document.createTextNode('alice'))
  customFrag.appendChild(myLi)
}

myUl.appendChild(customFrag)

selectors API

1. querySelector()

document.querySelector('.classname')

2. querySelectorAll()
它是静态的快照,而非“实时”的查询;nodeList是动态的
3. classList:类列表集合

  • add(value): 向类名列表中添加指定的字符串值value 4. compatMode: 浏览器模式
  • “CSS1Compat”: 标准模式
  • “BackCompat”:混杂模式

样式

  • style.getPropertyValue()
  • getComputedStyle(): 获取style集合
  • offsetHeight/offsetWidth: content + padding + border
  • clientWidth/clientHeight: content + border
  • scrollWidth/scrollHeight:没有滚动条出现的宽高
  • scrollTop: 内容区域隐藏起来的像素数
  • getBoundingClientRect(): 返回DOMReact对象,包含left,top,right,bottom,height,width

事件

  • 事件流
    页面接收事件的顺序
  • 事件冒泡(IE事件流)
    从最具体的元素触发,向上传播至没有那么具体的元素;
  • 事件捕获
    从document元素捕获,然后沿DOM树依次向下传播,直达目标元素
  • DOM事件流
    事件捕获,到达目标和事件冒泡
  • DOM0事件处理程序
    元素.onclick = function(){}

btn.onclick = null, 移除事件处理程序

  • DOM2事件处理程序

优势,可以添加多个事件处理函数 addEventListener(事件名,事件处理函数,布尔值) / removeEventListener('click', () => {})
true:捕获阶段处理
false:冒泡阶段处理(默认值)

  • IE事件处理程序
  1. attachEvent('onclick', () => function),detachEvent
  • DOM事件对象

this对象始终等于currentTarget的值, 而target只包含事件的实际目标
如何事件处理程序直接添加在了意图的目标,则this,currentTarget,和target的值一样

  • IE事件对象
    window.event
  • 用户界面事件
  1. load/unload
  2. error/select/resize/scroll
  3. clientX, clientY (鼠标光标在客户端视口中的坐标)
  4. pageX和pageY (鼠标在页面的坐标)
  5. screenX和screenY (屏幕坐标)

没有滚动条pageX与clientX相同

表单

  1. 阻止表单提交(event.preventDefault())
  2. 没有提交按钮的表单在按回车键时不会提交
  3. submit()提交表单时,submit事件不会触发
  4. elements: 表单中所有控件的集合

跨上下文消息(XDM)

  • postMessage(messageContent, origin) origin: 发送消息的文档源
    如果不想限制接受目标,则可以给postMessage()的第二个参数传* 在onmessage事件处理程序中检查数据源是否正确
   window.addEventListener('message', (event) => {
      // 确保来自预期发送者
      if(event.origin == 'xxxx') {
        // 对数据进行一些处理
        processMessage(event.data)
        // 可选,向来源窗口发送一条消息
        event.source.postMessage('received', "")
      }
    })

拖放事件

在某个元素被拖动时,会顺序触发以下事件

  • dragstart
  • drag
  • dragend 在把元素拖动到一个有效放置目标上时,会顺序触发以下事件
  • dragenter
  • dragover
  • dragleave or drop

dataTransfer 对象

event.dataTransfer.setData('text','some text')
event.dataTransfer.setData('url','http://www.baidu.com')
event.dataTransfer.getData()

Web 组件

1. HTML模板

  • 使用DocumentFragment

批量向HTML中添加元素;可以一次性添加所有子节点,最多只会有一次布局重排 document.appendChild(), 会导致多次布局重排

   const fragment = document.createDocumentFragment()
   fragment.appendChild(document.createElement('p'))
   fragment.appendChild(document.createElement('p'))
   fragment.appendChild(document.createElement('p'))
   console.log(fragment.children.length)
   document.getElementById('foo').appendChild(fragment)

2.错误处理
a. try...catch...finally

  • finally存在会忽略try/catch中的return;
  • err.message: 向用户显示错误信息
try {
  const abc = 1;
  abc = 2
}catch(err) {
  console.log(err.message)
}

3.报错类型

  • ferenceError:找不到对象时发生
  • SyntaxError: 语法错误
  • TypeError: 变量不是预期类型,或者访问不存在

JSON

  1. JSON.Stringify(value, 过滤器, 缩进值)

XMLHttpRequest 对象

const xhr = new XMLHttpRequest()
1. 首先调用open方法
xhr.open(method: 'post | get', url: String, async: Boolean)
2. 要发送定义好的请求
xhr.send(null)
3. 收到服务器响应,XHR对象的以下属性会被填充上数据
responseText: 作为响应体返回的文本
responseXML: 
status: 响应http状态
statusText: 响应的http状态描述
if((xhr.status >= 200 & xhr.status < 300) || xhr.status === 304){
  console.log(xhr.responseText)
}

status状态码
304: 资源未修改过,从浏览器缓存中拿取

xhr对象,有个readyState属性,
0:尚未调用open方法
1:已经调用open,但为调用send
2:已发送send,尚未收到响应
3:接受中,已收到部分响应
4:完成,已经收到到全部响应

redayState改变会触发 onreadystatechange 事件

完整的XHR发送接收请求

const xhr = new XMLHttpRequest()
xhr.onreadystatechange = function() {
  if(xhr.readyState === 4) {
    if((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
      console.log(xhr.responseText)
    }
  }
}

xhr.open('post', url, true)
xhr.send(null)

请求头

  • 发送额外请求头:setRequestHeader()

为保证请求头发送出去,必须在open()之后,send()之前调用setRequestHeader()

get请求(查询)

查询字符串中的每个名和值都必须使用encodeURIComponent()编码

post请求(发送应该保存的数据)

提交表单,需要把Content-Type设置成:application/x-www.formurlencoded

跨域方法

  • 图片src
  • jsonp

回调,(在页面接收到响应之后应该调用的函数)
数据,作为参数传给回调函数的JSON数据 callback({"name":"olivia"})

代理反射

为开发者提供了拦截并向基本操作嵌入额外行为的能力 代理:目标对象的抽象
在代理对象上执行的所有操作都会无障碍传播到目标对象上

  • 创建代理
const proxy= new Proxy(target, handler)    
target: 目标对象   
handler:处理程序对象

创建空代理,可以传一个简单地对象字面量作为处理程序对象

  • 定义捕获器 捕获器:在处理程序对象中定义的“基本操作的拦截器”。每个handle对象可以包含零个或者多个捕获器,每个捕获器对应一种基本操作,可以直接或间接在代理对象上调用
    只有在代理对象上执行,才会触发捕获器
const target = {
  name: 'olivia'
}

const handler = {
  // tarpTarget: 接收到的目标对象
  // property: 要查询的属性
  // receiver:代理对象
  get(tarpTarget, property, receiver) {
    console.log(tarpTarget === target)
    console.log(property)
    console.log(receiver)
    return '我是捕获器'
  }
}
const proxy = new Proxy(target, handler)
proxy.age = '1993'
console.log(proxy.age)
  • 反射:处理程序对象中所有可以捕获的方法都有对应的反射(Reflect)
  • 撤销代理:revoke()

开发者并不需要手动重建原始行为,而是可以通过调用全局Reflect对象上来轻松重构

const target = {
  name: 'olivia'
}

const handler = {
  get() {
    return Reflect.get(...arguments)
  }
}
const proxy = new Proxy(target, handler)
console.log(proxy.name) // olivia