JS高级学习
创始人
2024-03-25 01:16:43
0

一、集合引用类型

1.1 选择 Object 还是 Map

  1. 内存占用
    Map大约可以比多存储50%得键值对

  2. 插入性能
    Map 插入性能更加

  3. 查找速度
    Object 查找速度更快。在把 Object 当成数组使用得情况下(比如使用连续整数作为属性),浏览器引擎可以进行优化,在内存中使用更高效得布局。

  4. 删除性能
    delete 删除得是 Object 的引用,不会真正释放内存;任何情况下都会返回 true;在性能上一直饱受诟病。对于大多数浏览器引擎来说,Map 的 delete() 操作都比插入和查找更快。

1.2 使用弱映射

1.2.1 私有变量

const User = (() => {const wm = new WeakMap()class User {constructor(id) {this.idProperty = Symbol('id')this.setId(id)}setPrivate(property, value) {const privateMembers = wm.get(this) || {}privateMembers[property] = valuewm.set(this, privateMembers)}getPrivate(property) {return wm.get(this)[property]}setId(id) {this.setPrivate(this.idProperty, id)}getId(id) {return this.getPrivate(this.idProperty)}}return User
})()const user = new User(123)
alert(user.getId()) // 123
user.setId(456)
alert(user.getId()) // 456
alert(wm.get(user)[user.idProperty]) // ReferenceError: wm is not defined

这样,拿不到弱映射中的健,也就无法取得弱映射中对应的值。

1.2.2 DOM 节点元数据

因为 WeakMap 实例不会妨碍垃圾回收,所以非常适合保存关联元数据。

const map = new Map()
const loginButton = docement.querySelector('#login')map.set(loginButton, { disabled: true })

上面的代码,假设原来的登录按钮从 DOM 树中被删掉了,但由于映射中还保存着按钮的引用,所以对应的 DOM 节点任然会逗留在内存中。

如果使用的是弱映射,如下代码,那么从 DOM 树中被删除后,垃圾回收程序就可以立即释放其内存(假设没有其他地方引用这个对象):

const wm = new WeakMap()
const loginButton = docement.querySelector('#login')wm.set(loginButton, { disabled: true })

1.3 使用 Set 实现并集(Union)、交集(Intersect)和差集(Difference)

let a = new Set([1, 2, 3])
let b = new Set([4, 3, 2])// 并集
let union = new Set([...a, ...b])
// Set {1, 2, 3, 4}// 交集
let intersect = new Set([...a].filter(x => b.has(x)))
// set {2, 3}// (a 相对于 b 的)差集
let difference = new Set([...a].filter(x => !b.has(x)))
// Set {1}

二、继承

2.1 组合继承

也叫伪经典继承,综合了原型链和盗用构造函数,基本思路是使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。

原型链的问题

  1. 包含的引用值会在所有实例间共享
  2. 子类型在实例化时不能传参
function SuperType() {this.colors = ['red', 'blue', 'green']
}function SubType() {}// 继承 SuperType
SubType.prototype = new SuperType()const instance1 = new SubType()
instance1.colors.push('black')
console.log(instance1.colors) // ['red', 'blue', 'green', 'black']const instance2 = new SubType()
console.log(instance2.colors) // ['red', 'blue', 'green', 'black']

盗用构造函数

为了解决包含引用值的问题,基本思路就是在子类构造函数中调用父类构造函数

function SuperType(name) {this.name = namethis.colors = ['red', 'blue', 'green']
}function SubType() {// 继承 SuperType, 传递参数SuperType.call(this, 'Bob')
}const instance1 = new SubType()
instance1.colors.push('black')
console.log(instance1.colors) // ['red', 'blue', 'green', 'black']
console.log(instance1.name) // 'Bob'const instance2 = new SubType()
console.log(instance2.colors) // ['red', 'blue', 'green']

**缺点:**必须在构造函数中定义方法,因此函数不能重用。此外,子类不能访问父类在原型上定义的方法。

综合例子

function SuperType(name) {this.name = namethis.colors = ['red', 'blue', 'green']
}SuperType.prototype.sayName = function() {console.log(this.name)
}function SubType(name, age) {// 继承属性SuperType.call(this, name)// 自身实例属性this.age = age
}// 继承方法
SubType.prototype = new SuperType()
SubType.prototype.constructor = SubType

2.2 原型式继承

即使不定义类型也可以通过原型实现对象之间的信息共享,适用于这种情况:有一个对象,想在它的基础上再创建一个新对象。

// 定义创建一个临时构造函数,将传入的对象赋值给这个构造函数的原型
function object(o) {function F() {}F.prototype = oreturn new F()
}const person = {name: 'Bob',friends: ['Jay', 'Lida']
}const anotherPerson = object(person)
anotherPerson.name = 'Greg'
anotherPerson.friends.push('Rob')const another = object(person)
anotherPerson.name = 'Alun'
anotherPerson.friends.push('Court')console.log(person.friends) // ['Jay', 'Lida', 'Rob', 'Court']

2.3 寄生式继承

创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象

// 定义创建一个临时构造函数,将传入的对象赋值给这个构造函数的原型
function object(o) {function F() {}F.prototype = oreturn new F()
}function creatAnother(original) {const clone = object(original) // 通过调用函数创建一个新对象clone.sayHi = function() { // 以某种方式增强这个对象console.log('Hi')}return clone // 返回这个对象
}

注意:通过寄生式继承给对象添加函数会导致函数难以重用,与构造函数类似。

2.4 寄生式组合继承

组合继承其实也存在效率问题。主要问题就是父类构造函数始终会被调用两次。寄生式组合继承通过盗用构造函数继承属性,但使用混合式原型链继承方法。基本思路是不通过调用父类构造函数给子类原型赋值,而是取得父类原型的一个副本。

组合继承

function SuperType(name) {this.name = name
}SuperType.prototype.sayName = function() {console.log(this.name)
}function SubType(name) {// 继承属性SuperType.call(this, name) // 第二次调用 SuperType
}// 继承方法
SubType.prototype = new SuperType() // 第一次调用 SuperType
SubType.prototype.constructor = SubType

寄生式组合继承

function inheritPrototype(subType, superType) {const prototype = Object.create(superType.prototype) // 创建对象prototype.constructor = subType // 增强对象subType.prototype = prototype // 赋值对象
}function SuperType(name) {this.name = name
}SuperType.prototype.sayName = function() {console.log(this.name)
}function SubType(name) {SuperType.call(this, name)
}inheritPrototype(SuperType, SuperType)

2.5 类继承

ES6 继承方式,使用 extends 关键字,就可以继承任何拥有[[ Constructor ]] 和原型的对象。既可以继承一个类,也可以继承普通的构造函数。

注意: super 关键字只能在派生类中使用,而且仅限于类构造函数、实例方法和静态方法内部。在类构造函数中使用 super 可以调用父类构造函数。

class Vehicle {constructor() {this.hasEngine = true}identify(name) {console.log(name)}
}class Bus extends Vehicle {constructor() {// 不要在调用 super() 之前引用 this,否则会抛出 ReferenceErrorsuper() // 相当于 super.constructor()console.log(this instanceof Vehicle) // trueconsole.log(this) // Bus { hasEngine: true }}identify() {super.identify('bus')}
}const bus = new Bus()
bus.identify() // 'bus'

三、BOM

3.1 window 对象

在浏览器中有两重身份,一个是 ECMAScript 中的 Global 对象,另一个就是浏览器窗口的 JavaScript 接口

3.1.1 窗口关系

  1. window.top: 始终指向最上层(最外层)窗口,即浏览器窗口本身。
  2. window.parent: 始终指向当前窗口的父窗口。
  3. window.self: 始终指向 window(self 和 window 就是同一个对象)。

如果当前窗口是最上层窗口,则 parent 等于 top(都等于 window)。

3.1.2 窗口位置

相对位置:

  1. window.screenLeft: 窗口相对于屏幕左侧的位置,返回值是 CSS 像素。
  2. window.screenTop: 窗口相对于屏幕顶部的位置,返回值是 CSS 像素。

移动窗口:

可以使用 moveTo() 和 moveBy() 方法移动窗口。这两个方法都接收两个参数,其中 moveTo() 接收要移动的新位置的绝对坐标 x 和 y;而 moveBy() 则接收相对当前位置在两个方向上移动的像素数。

// 把窗口移动到左上角
window.moveTo(0, 0)
// 把窗口向下移动 100 像素
window.moveBy(0, 100)

注意:依浏览器而定,以上方法可能会被部分或全部禁用。

3.1.3 窗口大小

所有现代浏览器都支持 4 个属性:

  1. window.innerWidth: 返回浏览器可视区宽度(不包括浏览器边框和工具栏)。
  2. window.innerHeight: 返回浏览器可视区高度(不包括浏览器边框和工具栏)。
  3. window.outerWidth: 返回浏览器窗口自身宽度。
  4. window.outerHeight: 返回浏览器窗口自身高度。

页面视口大小还可以通过 document.documentElement.clientWidth、document.documentElement.clientHeight、document.body.clientWidth、document.body.clientHeight 等获取

在部分移动浏览器中,document.documentElement.clientWidth、document.documentElement.clientHeight 返回布局视口的大小,即渲染页面的实际大小。布局视口是相对于可见视口的概念,可见视口只能显示整个页面的一小部分。

3.1.4 视口位置

  1. window.pageXOffset/window.scrollX: 滚动条到相对视口的左侧距离。
  2. window.pageYOffset/window.scrollY: 滚动条到相对视口的顶部距离。

可以使用 scroll()、scrollTo()、scrollBy() 方法滚动页面。都接收表示相对视口距离的 x 和 y 坐标,这两个参数在前两个方法中表示要滚动到的坐标,在最后一个方法中表示滚动的距离。

// 相对于当前视口向下滚动 100 像素
window.scrollBy(0, 100)// 相对于当前视口向右滚动 100 像素
window.scrollBy(100, 0)// 滚动到页面左上角
window.scrollTo(0, 0)// 滚动到距离屏幕左边及顶边各 100 像素的位置
window.scrollTo(100, 100)

这几个方法也都接收一个 ScrollToOptions 字典,除了提供偏移值,还可以通过 behavior 属性设置是否平滑滚动

// 正常滚动
window.scrollTo({left: 100,top: 100,behavior: 'auto'
})// 平滑滚动
window.scrollTo({left: 100,top: 100,behavior: 'smooth'
})

3.2 history 对象

表示当前窗口首次使用以来用户的导航历史记录。

3.2.1 导航

  1. go()

    // 后退一页
    history.go(-1)// 前进一页
    history.go(1)
    
  2. back(): 后退一页

  3. forward(): 前进一页

判断是否为窗口中的第一个页面: history.length === 1

3.2.2 历史状态管理

默认情况下,处理 location.hash 之外,只要修改 location 的一个属性,就会导致页面重新加载新 URL。而状态管理 API 则可以让开发者改变浏览器 URL 而不会加载新页面。

history.pushState()

这个方法接收3个参数:一个 state 对象、一个新状态的标题和一个(可选的)相对 URL
history.pushState({ foo: 'bar', 'my title', 'bar.html'})

方法执行后,状态信息就会被推到历史记录中(会触发 window 对象上的 popstate 事件)。为防止滥用,这个状态的对象大小是有限制的,通常在 500KB ~ 1MB 以内

// popstate 事件的事件对象有一个 state 属性,其中包含通过 pushState() 第一个参数传入的 state 对象
window.addEventListener('popstate', e => {const state = e.stateif (state) // 第一个页面加载时状态是 null
})

history.replaceState()

传入与 pushState 同样的前两个参数来覆盖当前状态。

四、DOM

4.1 Text 类型

纯文本(包含空格)。

4.1.1 基础介绍

  • nodeType 等于 3
  • nodeName 值为 “#text”
  • nodeValue 值为节点中包含的文本
  • parentNode 值为 Element 对象
  • 不支持子节点

Text 节点中包含的文本可以通过 nodeValue 属性访问,也可以通过 data 属性访问,这两个属性包含相同的值。修改 nodeValue 或 data 的值,也会在另一个属性反映出来。

操作文本的方法:

  • appendData(text): 向节点末尾添加文本 text
  • deleteData(offset, count): 从位置 offset 开始删除 count 个字符
  • insertData(offset, text): 在位置 offset 插入 text
  • replaceData(offset, count, text): 用 text 替换从位置 offset 到 offset + count 的文本
  • splitText(offset): 在位置 offset 将当前文本节点拆分为两个文本节点
  • substringData(offset, count): 提取从位置 offset 到 offset + count 的文本

hello world!

4.1.2 规范化文本节点

合并相邻的文本节点。

const element = document.createElement('div')
const textNode = document.createTextNode('Hello')
element.appendChild(textNode)const anotherTextNode = document.createTextNode(' World!')
element.appendChild(anotherTextNode )alert(element.childNodes.length) // 2
element.normalize()
alert(element.childNodes.length) // 1

4.2 焦点管理

document.activeElement,始终包含当前拥有焦点的DOM元素。页面加载时,可以通过用户输入(按 Tab 键或代码中使用 focus()方法)让某个元素自动获得焦点。

const button = document.getElementById('myButton')
button.focus()console.log(document.activeElement === button) // true

默认情况下,document.activeElement在页面刚加载完之后会设置为document.body。而在页面完全加载之前,document.activeElement的值为 null。

document.hasFocus(),该方法返回布尔值,表示文档是否拥有焦点。

4.3 HTMLDocument 扩展

4.3.1 readyState 属性

document.readyState 有两个可能的值:

  1. loading,表示文档正在加载
  2. complete,表示文档加载完成
if (document.readyState === 'complete') {// 文档加载完成执行操作
}

4.3.2 compatMode 属性

表示浏览器处于什么渲染模式

  1. CSS1Compat,表示标准模式
  2. BackCompat,表示混杂模式

4.3.3 head 属性

document.head,取得元素

4.4 插入标记

4.4.1 innerHTML 属性

将 HTML 字符串解析为相应的 DOM 树。

4.4.2 outerHTML 属性

返回调用它的元素(及所有后代元素)的 HTML 字符串。

如果使用 outerHTML 设置 HTML,比如:

div.outerHTML = '

This is a paragraph.

'// 相当于 const p = document.createElement('p') p.appendChild(document.createTextNode('This is a paragraph.')) div.parentNode.replaceChild(p, div)

4.4.3 insertAdjacentHTML() 与 insertAdjacentText()

接收两个参数:要插入标记的位置和要插入的 HTML 或文本。第一个参数值必须是下面其中一个:

  • beforebegin,插入当前元素前面,作为一个前同胞节点
  • afterbegin,插入当前元素内部,作为新的子节点或放在第一个字节点前面
  • beforeend,插入当前元素内部、作为新的子节点或放在最后一个子节点后面
  • afterend,插入当前元素后面,作为下一个同胞节点

4.5 srollIntoView()

滚动浏览器窗口或容器元素以便包含元素进入视口。参数如下:

  • alignToTop 是一个布尔值

    • true,窗口滚动后元素的顶部与视口顶部对齐
    • false,窗口滚动后元素的底部与视口底部对齐
  • srollIntoViewOptions 是一个选项对象

    • behavior,定义过渡动画,可取值为smoothauto,默认为auto
    • block,定义垂直方向的对齐,可取值为startendcenternearest,默认为start
    • inline,定义水平方向的对齐,可取值为startendcenternearest,默认为nearest
  • 不传参数等同于 alignToTop 为 true

// 确保元素可见
document.forms[0].srollIntoView()// 将元素平滑地滚入视口
document.forms[0].srollIntoView({behavior: 'smooth',block: 'start'
})

4.6 元素尺寸

4.6.1 偏移尺寸

  • offsetHeight,元素的高度(包括水平滚动条高度(如果可见)),height + padding(上下值) + border(上下值)
  • offsetWidth,元素的宽度(包括垂直滚动条宽度(如果可见)),width+ padding(左右值) + border(左右值)
  • offsetLeft,元素左边框外侧距离最近的带有position属性(relative,absolute,fixed)的父元素的左边框内侧像素数
  • offsetTop,元素上边框外侧距离最近的带有position属性(relative,absolute,fixed)的父元素的上边框内侧像素数
  • offsetParent,返回最近的带有position属性(relative,absolute,fixed)的父元素

4.6.2 客户端尺寸

  • clientHeight,元素可视内容区加上、下内边距高度,height + padding-top + padding-bottom
  • clientWidth,元素可视内容区加左、右内边距宽度,width + padding-left + padding-right
  • clientTop,元素上边框的像素数,border-top
  • clientLeft,元素左边框的像素数,border-left

4.6.3 滚动尺寸

  • scrollHeight,没有滚动条时,元素内容区总高度,height + padding-top + padding-bottom
  • scrollWidth,没有滚动条时,元素内容区总宽度,width + padding-left + padding-right
  • scrollTop,内容区顶部隐藏的像素数,可设置以改变元素的滚动位置
  • scrollLeft,内容区左侧隐藏的像素数,可设置以改变元素的滚动位置

4.6.4 确定元素尺寸

getBoundingClientRect(),包含6个属性:left、top、right、bottom、height、width。这些属性给出了元素在页面中相对于视口的位置。

  • left,元素左边框外侧距离视口左侧的像素数
  • top,元素上边框外侧距离视口顶部的像素数
  • right,left + width
  • bottom,top + height
  • width,元素自身总宽度,width+ padding(左右值) + border(左右值)
  • height,元素自身总高度,height + padding(上下值) + border(上下值)

五、事件

JavaScript 与 HTML 的交互是通过事件实现的,事件代表文档或浏览器窗口中某个有意义的时刻。

5.1 事件处理程序

事件意味着用户或浏览器执行的某种动作。比如,单机(click)、加载(load)、鼠标悬停(mouseover)。为响应事件而调用的函数被称为事件处理程序(或事件监听器)。

5.1.1 DOM0 事件处理程序

每个元素(包括 window 和 document)都有通常小写的事件处理程序属性,比如 onclick。把这个属性赋值为一个函数即可:

const btn = document.getElementById('btn')
btn.onclick = function() {}

5.1.2 DOM2 事件处理程序

为事件处理程序的赋值和移除定义了两个方法:addEventListener()removeEventListener()。这两个方法暴露在所有 DOM 节点上,它们接收3个参数:事件名、事件处理函数和一个布尔值,true 表示在捕获阶段调用事件处理程序,false(默认值)表示在冒泡阶段调用事件处理程序。

const btn = document.getElementById('btn')
const handler = function() {}
btn.addEventListener('click', handler, false)
btn.removeEventListener('click', handler, false)

用 addEventListener 添加的事件可以以添加顺序依次触发,removeEventListener 必须传入与添加时同样的参数来移除。

5.1.3 IE 事件处理程序

IE 实现了 DOM 类似的方法,即attachEvent()detachEvent()。这两个方法接收两个同样的参数:事件处理程序的名字和事件处理函数。因为 IE8 及更早版本只支持事件冒泡,所以使用attachEvent()添加的事件处理程序会添加到冒泡阶段。

const btn = document.getElementById('btn')
const handler = function() {}
btn.attachEvent('onclick', handler)
btn.detachEvent('onclick', handler)

注意:attachEvent() 的第一个参数是“onclick”,而且给同一个元素添加多个事件处理程序时会反向触发。

5.2 事件类型

5.2.1 用户界面事件

  • load:在 window 上当页面加载完成后触发(包括所有图像、javaScript文件、css文件等外部资源),在元素上当图片加载完成后触发,在元素上当相应对象加载完成后触发
  • unload:在 window 上当页面完全卸载后触发,在元素上当相应对象卸载完成后触发
  • beforeunload:在 window 上触发,给用户阻止页面被卸载的机会
  • DOMContentLoaded:在 window 上触发,DOM 树构建完成后立即触发,不用等待外部资源下载
  • abort:在元素上当相应对象加载完成前被用户提前终止下载时触发
  • error:在 window 上当 JavaScript 报错时触发,在元素上当无法加载指定图片时触发,在元素上当无法加载相应对象时触发
  • select:在文本框(