- 关于许可协议:
CC-BY要求提供协议链接和署名 - 天气服务:和风天气,免费供个人使用
- 不同平台兼容性:
WebExtension Polyfill,wxt - 搜索建议:
bing osjson api,对应为https://api.bing.com/osjson.aspx?query=搜索词baidu,对应为https://suggestion.baidu.com/su?wd=搜索词&ie=utf-8&cb=
- 壁纸:
https://zhuanlan.zhihu.com/p/637298844,https://likepoems.com/,https://bing.img.run/api.html,https://segmentfault.com/a/1190000042270044,https://www.yumus.cn/apidoc - 一言
- 存储:
https://alist.nn.ci/zh/
node.js是一个基于Chrome引擎的JavaScript运行环境,用于在本地运行JavaScript代码
1. 安装
1.1 安装node管理工具nvm
Linux安装
请查看官方下载页面,已提供对应的命令行安装方法,包含docker、nvm等方式
- 主要介绍
python环境的parsel和JavaScript环境的cheerio爬虫库
请求网页数据
- 可以使用常见的各种库进行请求
解析JSON格式数据
- 对于python而言,如果需要解析JSON格式数据,需要使用JSON解析库
import json
# 将字典或文件编码成字符串
# json.dumps(obj, sort_keys=False, indent=None)
# json.dump(obj, fp, sort_keys=False, indent=None)
json.dumps(dict1)
# 将字符串解码成字典或输入到文件
# json.loads(string)
# json.load(fp)
json.load(str1)
js是创始人Brendan Eich于1995.5在10天内设计的一种宽松脚本语言,目的是让网页开发者能快速上手,因此你能看到许多从其他语言得到的借鉴灵感和不少不合理的历史遗留--因为现在改变规则会使上亿的老页面发生改变。标准协会TC39负责js维护和规范js主要有两部分相关内容ECMAScript标准是js的主要基础规范,最出名的是ES6(2015),从这个版本之后改为每年迭代,比如ES2017Web API:浏览器中有很多可用的API。它们建立在核心JavaScript语言之上,为使用JavaScript代码提供额外的超强能力- 浏览器
API内置于Web浏览器中,能从浏览器和电脑周边环境中提取数据,或者通过浏览器完成复杂的操作,通过特定的抽象化的接口,调用浏览器实现的底层低级代码 - 第三方
API不会内置,通常需要从web中某个地方获取代码和信息,允许集成其他平台页面功能
- 浏览器
常见的页面
DOM操作就由Web API定义(定义了表示和修改文档的对象,描述了处理网页内容的方法和接口)。之前曾使用BOM用于描述浏览器和提供的对象接口集合,概念出现于90年代。现代浏览器中BOM的操作已经被并入Web API不同功能中,基本不再使用,Web API不仅覆盖了原有的BOM能力还提供了更丰富的功能MDN文档,尽可能阅读英文版,内容更新更全面
2.1. Object
-
Object静态方法Object.assign(newObj, ...objs):浅拷贝对象,为newObj添加objs有的属性,如果属性有重复,则后面的属性会覆盖前面的属性
深浅拷贝
- 直接复制引用
const a = b:修改会共享 - 浅拷贝:会创建新对象,但其中的元素只是原对象的引用,不会递归复制内层对象,因此第二层及更深层的元素修改会共享
const a = {...b}Object.assign(a, b)
- 深拷贝:会创建新对象,并递归复制内层对象,使得新对象与原对象完全独立
-
创建递归函数手动创建新对象
// 简单的递归拷贝函数,仅对数组和对象进行递归处理 function deepCopy(obj, oldObj) { // 使用键遍历 for (let key in obj) { // 必须先判断是否为数组,因为数组也是对象 if (oldObj[key] instanceof Array) { obj[key] = []; deepCopy(obj[key], oldObj[key]); } else if (oldObj[key] instanceof Object) { obj[key] = {}; deepCopy(obj[key], oldObj[key]); } else { obj[key] = oldObj[key]; } } } -
借助
json格式const a = JSON.parse(JSON.stringify(b)),特殊对象难以表达 -
使用
structuredClone方法:ECMAScript 2021添加的原生方法,通过结构化克隆算法完成拷贝,但有许多类型不支持此算法,比如:函数、许多错误类型、DOM节点、原型链跟踪、属性描述符、正则对象的lastIndex属性等,见结构化克隆算法-
此方法第二个参数中的选项支持转移对象属性到新对象中
const obj = { a: 1, b: { c: 2 } }; const copy = structuredClone(obj); console.log(copy); // { a: 1, b: { c: 2 } } console.log(copy === obj); // false(深拷贝) // 创建一个 ArrayBuffer const buffer = new ArrayBuffer(8); // 克隆并转移所有权 const cloned = structuredClone({ buf: buffer }, { transfer: [buffer] }); console.log(cloned.buf.byteLength); // 8 console.log(buffer.byteLength); // 0 原 buffer 被清空(转移完成)
-
-
借助其他工具库的实现,比如
lodash的cloneDeep方法,处理了许多的特殊类型,但不支持原型链跟踪
-
Object.create(proto, [descriptors]):创建一个新对象,对象原型为proto,属性描述符为descriptorsproto不能是undefined,如果是null说明对象不继承自Object.prototype,也就没有对应的方法,这种对象可以用作干净的字典,没有任何额外属性,也没有任何冲突
Object.values(obj):返回对象obj的属性值组成的数组Object.getOwnPropertyDescriptors(obj):返回对象obj的所有属性描述符组成的对象,在变量特性部分已经介绍过- 获取对象的属性键:
Object.keys(obj):返回对象obj的自身可迭代属性名组成的数组Object.getOwnPropertyNames(obj):返回对象obj的自身属性名(包括不可迭代属性)组成的数组Object.getOwnPropertySymbols(obj):返回对象obj的所有Symbol属性键组成的数组
Reflect.ownKey()可以获取到自身所有属性名,包括Symbol属性键名 Object.getPrototypeOf(obj):返回对象obj的原型对象Object.is(value1, value2):比===更严格的判断两个值是否相等的方法,只有以下情况被认为是相同的- 都是 undefined
- 都是 null
- 都是 true 或者都是 false
- 都是长度相同、字符相同、顺序相同的字符串
- 都是相同的对象(意味着两个值都引用了内存中的同一对象)
- 都是 BigInt 且具有相同的数值
- 都是 symbol 且引用相同的 symbol 值
- 都是数字且都是相同值时,
+0和-0被认为是不同值(与===的区别),两个NaN被认为是相同的
3.1. 闭包
- 闭包:一个函数对周围状态的引用捆绑在一起,内层函数可以访问外层函数的作用域,本质是内部函数+引用的外层函数变量
- 原理:内部函数使用了外部函数中部分变量的引用,产生独特的闭包作用域
closure。如果内部函数被返回给外界,这些变量的生命周期延长,闭包保留了外层作用域 - 作用:
-
创建模块,私有内部变量,使用内部函数修改和获取内部变量,避免全局污染,已可被类取代,但这种写法更方便
function counter() { let count = 0; return function () { return ++count; } } let c = counter(); console.log(c()); console.log(c()); -
只暴露接口方法,隐藏细节实现,比如函数计数器等
-
创建函数工厂,创建带有固定行为的函数
function makeCounter(m) { return (x) => x**m } -
延迟执行,按需计算
function delay(a, b) { return () => a + b } let f = delay(1, 2) console.log(f()) -
保证异步函数执行时也能获取到当前上下文
-
- 限制:
-
由于函数会保留,引用到的变量使用的堆空间不会被垃圾回收,如果闭包的函数很多可能造成内存泄漏
-
如果是异步执行,此时的全局变量会随之后的执行而改变,因此如果是循环执行,得到的变量是相同的,这是因为
js代码的执行是有优先级的,主程序优先级最高,会先执行,而异步任务会在之后执行,此时得到的var变量是同一个(var变量不会受到块级作用域限制),解决方法:- 使用let,let声明的变量在每轮次会重新绑定
- 使用函数包裹,让异步执行获取函数的变量
for (var i = 0; i < 10; i++) { // 输出都是 10 setTimeout(()=>console.log(i), 100) // 输出 0 1 2 3 4 5 6 7 8 9 (function(j){ setTimeout(()=>console.log(j), 100) })(i) } for (let i = 0; i < 10; i++) { // 输出 0 1 2 3 4 5 6 7 8 9 setTimeout(()=>console.log(i), 100) }
-
4.1. 防抖和节流
-
防抖:单位时间内频繁触发事件,只执行最后一次
-
处理的思路在于使用定时器
- 声明一个可存储定时器的变量
- 在事件触发时,判断是否有定时器,有则清除定时器,再重新设置定时器;没有定时器则设置定时器
- 定时器里面执行事件处理逻辑
-
常用于输入验证和搜索建议
function debounce(fn, delay) { let timer = null return function () { if (timer) clearTimeout(timer) timer = setTimeout(() => { fn() }, delay) } }Lodash中有用于防抖的函数,可以使用_.debounce(fn, delay)调用
-
-
节流:在事件触发时,规定一个执行周期,规定时间内只执行一次事件处理逻辑,也可以实现前一个任务还在,则取消触发
- 常用于高频事件比如鼠标移动、页面滚动等
- 处理规定时间内只执行一次的思路在于使用定时器
- 声明一个可存储定时器的变量
- 如果事件触发,先判断是否有定时器,如果有不创建定时器;没有才创建定时器
- 定时器里面执行事件处理逻辑,同时在定时器结束的时候,将定时器置为
null
function throttle(fn, delay) { let timer = null return function () { if (timer) return timer = setTimeout(() => { try { fn() } finally { timer = null } }, delay) } }Lodash中有用于节流的函数,可以使用_.throttle(fn, delay)调用- 处理前一个任务还在,取消触发的思路也类似,只是定时器可以换成一个
flag变量,由于js本身没有锁的实现,可能出现多个线程同时访问锁变量,只适合不强制要求必须只执行一个任务的场景,可以考虑async-mutex
// 简单的实现,可能存在多个任务同时执行,只是在前端可能性极小 function runSkipIfBusy(fn) { let flag = true return function () { if (flag) { flag = false try { fn() } finally { // 防止错误抛出,导致任务无法再次执行 flag = true } } } } // 导入 async-mutex import * as asyncMutex from 'https://cdn.jsdelivr.net/npm/async-mutex@0.5.0/+esm' const mutex = new asyncMutex.Mutex(); async function runSkipIfBusy(fn) { if (mutex.isLocked()) { console.log("任务在执行中") return false; } await mutex.runExclusive(fn); return true; } async function doTask(id) { await runSkipIfBusy(async () => { console.log(`任务 ${id} 开始`); await new Promise(r => setTimeout(r, 2000)); console.log(`任务 ${id} 结束`); }); } doTask(1); doTask(2); doTask(3);-
如果只希望同一时间只有一个任务在运行,可以使用
promise串联队列实现function createMutex() { let chain = Promise.resolve(); // 保存最后一个任务 return async function runExclusive(fn) { const run = async () => await fn(); chain = chain.then(run, run); return chain; }; }
Node接口,这是所有dom节点继承的接口,它允许我们使用相似的方式对待这些不同类型的dom对象- 所有的节点类型都继承了这个接口
- 此接口有一个父接口
EventTarget,可以接收事件、并且可以创建侦听器,具体见事件部分
5.1. document
- 在浏览器全局中有一个
Document接口,接口表示任何在浏览器中载入的网页,并作为网页内容的入口- 描述了任何类型的文档的通用属性与方法,可以操作元素节点
- 在浏览器中有一个
document对象是它的实例,可以访问整个文档
document对象有一些属性可以获取到网站信息document.title: 获取和设置当前网页的标题document.cookie: 获取和添加当前网页的cookiedocument.URL: 获取当前网页的URLdocument.location: 获取当前网页的location对象,同时也可以直接赋值字符串,将浏览器跳转到指定URL(实际上是修改location对象的href属性)document.readyState: 获取当前网页的状态loading: 正在加载interactive: 已经加载完成,但是DOM树还没有构建完成complete: DOM树构建完成
document.visibilityState: 获取当前网页的可见状态,一般情况下可以通过事件监听变化hidden: 当前网页不可见visible: 当前网页可见
- 早期浏览器就提供了的**BOM(Browser Object Model)**对象集合,用于操作浏览器窗口、历史记录、导航、弹窗、屏幕信息等浏览器环境,
BOM主要对象包括window:浏览器窗口的全局对象navigator:浏览器信息与权限screen:屏幕信息location:URL 跳转与刷新history:浏览器当前页面历史栈alert/confirm/prompt:弹窗setTimeout/setInterval:定时器
- 除了这些之外,现代浏览器还提供了不少标准接口工具,主要用于操作浏览器能力、网络、设备和多媒体等
- 网络:
fetch、WebSocket,见异步请求 - 存储:
localStorage、IndexedDB - 多媒体:
AudioContext - 权限/设备:
Notification、Geolocation API、摄像头麦克风 - 浏览器脚本:
Service Worker - 文件处理:
FileReader、Blob、File - 其他:全屏、剪贴板、动画渲染
requestAnimationFrame等
- 网络:
