1、如何隐藏所有指定的元素
const hide = (el) => Array.from(el).forEach(e => (e.style.display = 'none'));
hide(document.querySelectorAll('img'))2、如何检查元素是否具有指定的类
const hasClass = (el, className) => el.classList.contains(className)
hasClass(document.querySelector('p.special'), 'special') // true3、如何切换一个元素的类
const toggleClass = (el, className) => el.classList.toggle(className)
toggleClass(document.querySelector('p.special'), 'special')4、如何获取当前页面的滚动位置
const getScrollPosition = (el = window) => ({
x: el.pageXOffset !== undefined ? el.pageXOffset : el.scrollLeft,
y: el.pageYOffset !== undefined ? el.pageYOffset : el.scrollTop
});
getScrollPosition(); // {x: 0, y: 200}5、如何平滑滚动到页面顶部
const scrollToTop = () => {
const c = document.documentElement.scrollTop || document.body.scrollTop;
if (c > 0) {
window.requestAnimationFrame(scrollToTop);
window.scrollTo(0, c - c / 8);
}
}
scrollToTop()6、如何检查父元素是否包含子元素
const elementContains = (parent, child) => parent !== child && parent.contains(child);
elementContains(document.querySelector('head'), document.querySelector('title')); // true elementContains(document.querySelector('body'), document.querySelector('body')); // false7、如何检查指定的元素在视口中是否可见
const elementIsVisibleInViewport = (el, partiallyVisible = false) => {
const { top, left, bottom, right } = el.getBoundingClientRect();
const { innerHeight, innerWidth } = window;
return partiallyVisible ? ((top > 0 && top < innerHeight) || (bottom > 0 && bottom < innerHeight)) && ((left > 0 && left < innerWidth) || (right > 0 && right < innerWidth)) : top >= 0 && left >= 0 && bottom <= innerHeight && right <= innerWidth;
};
elementIsVisibleInViewport(el); // 需要左右可见
elementIsVisibleInViewport(el, true); // 需要全屏(上下左右)可以见8、如何获取元素中的所有图像
const getImages = (el, includeDuplicates = false) => {
const images = [...el.getElementsByTagName('img')].map(img => img.getAttribute('src'));
return includeDuplicates ? images : [...new Set(images)];
};
getImages(document, true); // ['image1.jpg', 'image2.png', 'image1.png', '...']
getImages(document, false); // ['image1.jpg', 'image2.png', '...']9、如何确定设备是移动设备还是台式机/笔记本电脑
const detectDeviceType = () =>{
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ? 'Mobile' : 'Desktop';
}
detectDeviceType(); // "Mobile" or "Desktop"10、如何获取当前URL
const currentURL = () => window.location.href currentURL() // 'https://google.com'11、如何创建一个包含当前URL参数的对象
const getURLParameters = url => {
return (url.match(/([^?=&]+)(=([^&]*))/g) || []).reduce( (a, v) => ((a[v.slice(0, v.indexOf('='))] = v.slice(v.indexOf('=') + 1)), a), {} );
}
getURLParameters('http://url.com/page?n=Adam&s=Smith'); // {n: 'Adam', s: 'Smith'}
getURLParameters('google.com'); // {}12、如何将一组表单元素转化为对象
const formToObject = form => {
return Array.from(new FormData(form)).reduce( (acc, [key, value]) => ({ ...acc, [key]: value }), {} );
}
formToObject(document.querySelector('#form')); // { email: 'test@email.com', name: 'Test Name' }13、如何从对象检索给定选择器指示的一组属性
const get = (from, ...selectors) => {
return [...selectors].map(s => s.replace(/\[([^\[\]]*)\]/g, '.$1.') .split('.') .filter(t => t !== '') .reduce((prev, cur) => prev && prev[cur], from) );
}
const obj = { selector: { to: { val: 'val to select' } }, target: [1, 2, { a: 'test' }] };
get(obj, 'selector.to.val', 'target[0]', 'target[2].a'); // ['val to select', 1, 'test']14、如何在给定元素上触发特定事件且能选择地传递自定义数据
var event = new Event('build');
var elem = document.querySelector('#id');
elem.addEventListener('build', function (e) { ... }, false); // 监听事件
elem.dispatchEvent(event); // 触发事件
var myEvent = new CustomEvent(eventname, {
detail: { // detail可以存放一些初始化的信息,可以在触发的时候调用 },
bubbles: true, //是否冒泡
cancelable: false //是否取消默认事件
});
var event = new CustomEvent("cat", {"detail":{ "hazcheeseburger":true }});
var elem = document.querySelector('#id');
elem.addEventListener("cat", function(e) { process(e.detail) }); // 监听事件
elem.dispatchEvent(event); // 触发事件
// 给定元素上触发特定事件且能选择地传递自定义数据
const triggerEvent = (el, eventType, detail) => {
return el.dispatchEvent(new CustomEvent(eventType, { detail }));
}
triggerEvent(document.getElementById('myId'), 'click');
triggerEvent(document.getElementById('myId'), 'click', { username: 'bob' });15、如何获得给定毫秒数的可读格式
const formatDuration = ms => {
if (ms < 0) ms = -ms;
const time = {
day: Math.floor(ms / 86400000),
hour: Math.floor(ms / 3600000) % 24,
minute: Math.floor(ms / 60000) % 60,
second: Math.floor(ms / 1000) % 60,
millisecond: Math.floor(ms) % 1000
};
return Object.entries(time).filter(val => val[1] !== 0).map(([key, val]) => `${val} ${key}${val !== 1 ? 's' : ''}`) .join(', ');
};
formatDuration(34325055574); // '397 days, 6 hours, 44 minutes, 15 seconds, 574 milliseconds'16、如何获得两个日期之间的差异
const getDaysDiffBetweenDates = (dateInitial, dateFinal) => {
return (dateFinal - dateInitial) / (1000 * 3600 * 24);
}
getDaysDiffBetweenDates(new Date('2017-12-13'), new Date('2017-12-22')); // 917、如何向传递的URL发出GET请求
const httpGet = (url, callback, err = console.error) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onload = () => callback(xhr.responseText);
xhr.onerror = () => err(xhr);
xhr.send();
};
httpGet( 'https://jsonplaceholder.typicode.com/posts/1', console.log );18、如何对传递的URL发出POST请求
const httpPost = (url, data, callback, err = console.error) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-type', 'application/json; charset=utf-8');
xhr.onload = () => callback(xhr.responseText);
xhr.onerror = () => err(xhr);
xhr.send(data);
};
const newPost = { userId: 1, id: 1337, title: 'Foo', body: 'bar bar bar' };
const data = JSON.stringify(newPost);
httpPost( 'https://jsonplaceholder.typicode.com/posts', data, console.log );19、如何将字符串复制到剪贴板
const el = document.createElement('textarea');
el.value = str; el.setAttribute('readonly', '');
el.style.position = 'absolute';
el.style.left = '-9999px';
document.body.appendChild(el);
const selected = document.getSelection().rangeCount > 0 ? document.getSelection().getRangeAt(0) : false;
el.select(); document.execCommand('copy');
document.body.removeChild(el);
if (selected) {
document.getSelection().removeAllRanges();
document.getSelection().addRange(selected);
}
};
copyToClipboard('Lorem ipsum');20、如何确定页面的浏览器选项卡是否聚焦
const isBrowserTabFocused = () => !document.hidden; isBrowserTabFocused(); // true21、如果不存在,如何创建目录
const fs = require('fs');
const createDirIfNotExists = dir => (!fs.existsSync(dir) ? fs.mkdirSync(dir) : undefined);
createDirIfNotExists('test');22、addEventListener和attachEvent的区别
addEventListener第三个参数涉及到冒泡和捕获,是true时为捕获,是false则为冒泡。或者是一个对象{ passive: true },针对的是Safari浏览器,禁止/开启使用滚动的时候要用到。前者是标准浏览器中的用法,后者IE8以下addEventListener可有冒泡,可有捕获;attachEvent只有冒泡,没有捕获。前者事件名不带on,后者带on前者回调函数中的this指向当前元素,后者指向window
23、数组去重
【方案一】设置tmp为对象,对象的键存储数组元素的值,最终返回对象的所有键
function array_unique (arr) {
let tmp = {};
let len = arr.length;
if (!len) {
return [];
}
for(let i = 0; i < len; i++) {
if (!tmp[arr[i]]) {
tmp[arr[i]] = true;
}
}
return Object.keys(tmp);
}【方案二】设置tmp为数组,数组中存储唯一的元素,最终返回tmp
示例1:
function array_unique (arr) {
let tmp = [];
let len = arr.length;
if (!len) {
return [];
}
for(let i = 0; i < len; i++) {
if (tmp.indexOf(arr[i]) === -1) {
tmp.push(arr[i])
}
}
return tmp;
}示例2:
function array_unique (arr) {
let tmp = [];
let len = arr.length;
if (!len) {
return [];
}
for(let i = 0; i < len; i++) {
if (!tmp.includes(arr[i]) {
tmp.push(arr[i]);
}
}
return tmp
}示例3:
function array_unique (arr) {
let tmp = [];
let len = arr.length;
if (!len) {
return [];
}
for(let i = 0; i < len; i++) {
if (tmp.findIndex((v) => JSON.stringify(v) === JSON.stringify(arr[i])) === -1) {
tmp.push(arr[i]);
}
}
return tmp;
}示例4:
function array_unique (arr) {
let tmp = [];
let len = arr.length;
if (!len) {
return [];
}
for(let i = 0; i < len; i++) {
if (!tmp.find((v) => JSON.stringify(v) === JSON.stringify(arr[i]))) {
tmp.push(arr[i]);
}
}
return tmp;
}24、数据的处理-数组
【1】数组的添加
首位添加:
let arr = [1,2,3,4,5];
arr.unshift(9,10);
arr.splice(0,0,9,10);末位添加:
let arr = [1,2,3,4,5];
arr.push(6);
arr.splice(arr.length,0,6);中间添加:
let arr = [1,2,3,4,5];
arr.splice(1,0,9,10);【2】数组的删除
首位删除:
let arr = [1,2,3,4,5];
arr.shift();
arr.splice(0,1);末位删除:
let arr = [1,2,3,4,5];
arr.pop();
arr.splice(arr1.length-1,1);中间删除:
let arr = [1,2,3,4,5];
arr.splice(1,2);【3】数组的替换
首位替换:
let arr = [1,2,3,4,5];
arr.splice(0,1,8,9);末位替换:
let arr = [1,2,3,4,5];
arr.splice(arr.length-1,1,8,9);中间替换:
let arr = [1,2,3,4,5];
arr.splice(3,1,9,10);【4】数组的查询
查询符合定条件的第一个值:
let jsonArr = [
{id:'1',name:'lisi',age:30},
{id:'2',name:'zhangsan',age:20},
{id:'3',name:'lisi',age:30}
];
jsonArr.find(item=>item.age===30);倒序查询符合条件的第一个值:
function lastFind(jsonArr,callback){
let _jsonArr = jsonArr.reverse();
let obj = _jsonArr.find(callback);
return obj;
}
let jsonArr = [
{id:'1',name:'lisi',age:30},
{id:'2',name:'zhangsan',age:20},
{id:'3',name:'wangermazi',age:30},
{id:'4',name:'xiaoming',age:18},
{id:'5',name:'wuming',age:30},
];
lastFind(jsonArr,item=>item.age==30);查询符合条件的所有值:
let jsonArr = [
{id:'1',name:'lisi',age:30},
{id:'2',name:'zhangsan',age:20},
{id:'3',name:'lisi',age:30},
];
jsonArr.filter(item=>item.age===30);查询符合条件的第一个值的索引:
let arr = [1,2,1,4];
arr.indexOf(1);
let jsonArr = [
{id:'1',name:'lisi',age:30},
{id:'2',name:'zhangsan',age:20},
{id:'3',name:'lisi',age:30}
];
jsonArr.findIndex(item=>item.age===30);倒序查询符合条件的第一个值的索引:
let arr = [1,2,1,4];
arr.lastIndexOf(1);
function lastFindIndex(jsonArr,callback){
let _jsonArr = jsonArr.reverse();
let index = _jsonArr.findIndex(callback);
return index>-1 ? _jsonArr.length-index-1 : -1;
}
let jsonArr = [
{id:'1',name:'lisi',age:30},
{id:'2',name:'zhangsan',age:20},
{id:'3',name:'wangermazi',age:30},
{id:'4',name:'xiaoming',age:18},
{id:'5',name:'wuming',age:30},
];
lastFindIndex(jsonArr,item=>item.age===30);查询符合条件的所有值的索引:
function findAllIndex(jsonArr,callback){
let res= [];
for(let i=0; i<jsonArr.length; i++){
if (callback(jsonArr[i])){
res.push(i);
}
}
return res;
}
let jsonArr = [
{id:'1',name:'lisi',age:30},
{id:'2',name:'zhangsan',age:20},
{id:'3',name:'wangermazi',age:30},
{id:'4',name:'xiaoming',age:18},
{id:'5',name:'wuming',age:30},
];
findAllIndex(jsonArr,item=>item.age==30);【5】数组的判断
数组中的所有值是否都满足给定条件:
let arr = [1,2,3,4,5];
arr.every(item=>item>0);
let userList = [
{id:'1',name:'lisi',age:30},
{id:'2',name:'zhangsan',age:20},
{id:'3',name:'wangermazi',age:30},
{id:'4',name:'xiaoming',age:18},
{id:'5',name:'wuming',age:30},
];
userList.every(item=>item.age>=18);数组中是否包含满足给定条件的值:
let arr = [1,2,3,4,5];
arr.includes(4);
let userList = [
{id:'1',name:'lisi',age:30},
{id:'2',name:'zhangsan',age:20},
{id:'3',name:'wangermazi',age:30},
{id:'4',name:'xiaoming',age:18},
{id:'5',name:'wuming',age:30},
];
!!userList.find(item=>item.age===20);
userList.some(item=>item.age===20);【6】数组的加工
数组值拼接:
let arr = [1,2,3,4,5];
arr.join('');
arr.reduce((pre,cur)=>pre+=cur,'');数组合并:
let arr11 = [1,2,3];
let arr22 = [4,5,6];
let arr33 = [7,8,9];
let res = [...arr11,...arr22,...arr33];
let res = arr11.concat(arr22,arr33);
let res = [].push(...arr11, ...arr22,...arr33);数组值加工:
let list = [1,2,3,4,5];
list.reduce((pre,cur)=>pre.push(cur+'%') && pre,[]);
list.reduceRight((pre,cur)=>pre.push(cur+'%') && pre,[]);
list.map(item=>item +='%');【7】数组排序
数组倒序:
let arr = [9,7,4,5,6,11];
arr.reverse();数组按条件排序:
let arr = [9,7,4,5,6,11]
arr.sort((pre,next)=>pre-next);【8】数组的遍历
let arr2 = [4,5,6];
arr2.forEach((item,index,arr)=>{
console.log(`当前项:${item},对应的索引是:${index},遍历的数组:${JSON.stringify(arr)}`)
});
for(let i=0;i<arr2.length;i++){
console.log(`当前项:${arr2[i]},对应的索引是:${i},遍历的数组是:${JSON.stringify(arr2)}`)
}【9】数组类型判断
let arr = [];
Array.isArray(arr);【10】数组的浅拷贝
let lis = [1,2,3,4,5]
let cloneLis = lis.slice();
let cloneLis = [].concat(lis);【11】数组的深拷贝
let lis = [{a:1},{a:2}];
let cloneLis = JSON.parse(JSON.stringify(lis));【12】数组的动态创建
function generateArr(len,val){
return Array(len).fill(val);
}25、数组实现栈与对象实现栈
【1】数组实现栈
class Stack {
private items: any[];
constructor() {
this.items = [];
}
// 入栈
push(item:any) {
this.items.push(item);
}
// 出栈
pop() {
return this.items.pop();
}
// 返回栈顶元素
peek() {
return this.items[this.items.length - 1];
}
// 判断栈是否为空
isEmpty() {
return this.items.length === 0;
}
// 清空栈栈内元素
clear() {
this.items = [];
}
// 获取栈内元素数量
size():number{
return this.items.length;
}
// 将栈内元素转为字符串
toString(){
return this.items.toString();
}
}【2】对象实现栈
class Stack {
interface StackObj {
[propName: number]: any;
}
private items: StackObj;
private count: number;
constructor() {
this.items = {};
this.count = 0;
}
// 入栈
push(item: any) {
this.items[this.count] = item;
this.count++;
}
// 出栈
pop() {
if(this.isEmpty()){
return undefined;
}
this.count--;
const result = this.items[this.count];
delete this.items[this.count];
return result;
}
// 返回栈顶元素
peek() {
if(this.isEmpty()){
return undefined;
}
return this.items[this.count - 1];
}
// 判断栈是否为空
isEmpty() {
return this.count === 0;
}
// 清空栈内元素
clear() {
this.items = {};
this.count = 0;
}
// 获取栈内元素数量
size():number{
return this.count;
}
// 将栈内元素转为字符串
toString(){
if (this.isEmpty()){
return "";
}
let objString = `${this.items[0]}`;
for (let i = 1; i < this.count; i++){
objString = `${objString},${this.items[i]}`
}
return objString;
}
}26、Array()、new Array()、Array.of()
Array使不使用new效果都是一样的。Array方法,如果参数是一位的话,这个参数表示的是数组的长度,并创建此长度的空数组;Array方法,如果参数是多位的话则每一个参数都是数组的一项,会按顺序返回数组;Array.of()接收任意个参数,将按顺序成为返回数组中的元素,并返回这个新数组。
console.log(Array(3)) // [empty x 3]
console.log(Array(3, 4)) // [3, 4]
console.log(new Array(3)) // [empty x 3]
console.log(new Array(3, 4)) // [3, 4]
console.log(Array.of(3)) // [3]
console.log(Array.of(3, 4)) // [3, 4]
// 创建一个长度为100,值为对应下标的数组
[...Array(100).keys()];
Array(100).join(",").split(",").map((v, i) => i);
Array(100).fill().map((v, i) => i);27、实现 arr[-1] = arr[arr.length - 1]
function createArr(...elements) {
let handler = {
get(target, key, receiver) { // 第三个参数传不传都可以
let index = Number(key); // 或者 let index = ~~key
if (index < 0) {
index = String(target.length + index);
}
return Reflect.get(target, index, receiver);
}
};
let target = [...elements]; // 创建一个新数组
return new Proxy(target, handler);
}
var arr1 = createArr(1, 2, 3);
console.log(arr1[-1]); // 3
console.log(arr1[-2]); // 228、模块化CommonJS和AMD/CMD
【1】CommonJs
require()用来引入外部模块;
exports对象用于导出当前模块的方法或变量,唯一的导出口;
module对象就代表模块本身;
global Node环境全局对象
【2】AMD
有依赖:
require(['dep1','dep2'], function(dep1, dep2){...})
define(['dep1','dep2'], function(dep1, dep2){...})要是没什么依赖,就定义简单的模块:
define(function(){
var exports = {};
exports.method = function(){...};
return exports;
});【3】CMD:define(id?, deps?, factory)
Id:模块标识
deps:模块依赖
factory:可以是一个函数,也可以是一个对象或字符串
29、ES2020新增的特性
【1】String.prototype.matchAll
const matches = [];
const regex = /([a-z]*)ing/g;
const regex1 = /(\w+)(?=ing)/g;
const test = "climbing, oranges, jumping, flying, carrot";
test.match(regex); // ["climbing", "jumping", "flying"]
test.match(regex1); // ["climb", "jump", "fly"]
[...test.matchAll(regex)].map(match => match[1]); // ["climb", "jump", "fly"]
while (true) {
const match = regex.exec(test);
if (match === null) break;
matches.push(match[1]);
}
matches // ["climb", "jump", "fly"]【2】动态import()
动态导入语法允许我们将import作为能够返回promise的函数进行调用。
async function openSidePanel(type = "desktop") {
const sidePanel = await import(`components/${type}/SidePanel`);
sidePanel.open();
}【3】BigInt
JavaScript可以处理的最大数量为2^53,即Number.MAX_SAFE_INTEGER,主要用于时间戳和唯一标识符。
console.log(Number.MAX_SAFE_INTEGER); //9007199254740991
BigInt(Number.MAX_SAFE_INTEGER) + 2n; //9007199254740993n【4】Promise.allSettled
如果一项或多项失败,Promise.all就会被reject,而Promise.allSettled就不会这样,您不需要担心1%的reject。Promise.allSettled允许我们传递一系列的Promise,这些Promise将在全部结束后,Promise的返回值是一个装满Promise结果的数组。Promise.allSettled在考试方面可要比Promise.all好得多。
const promises = [
fetch('/api1'),
fetch('/api2'),
fetch('/api3'),
];
Promise.allSettled(promises).then((results) => results.forEach((result) => console.log(result.status)));
// "fulfilled"
// "fulfilled"
// "rejected"【5】globalThis
globalThis全局变量,可以放松的不去考虑window或global而统一前端或后端代码。
之前的写法:
(typeof window !== "undefined" ? window : (typeof process === 'object' &&
typeof require === 'function' &&
typeof global === 'object') ? global : this);globalThis写法:
globalThis.something = "Hello"; // Works in Browser and Node.【6】可选链操作符
const test = {
name: "foo",
age: 25,
address: {
number: 44,
street: "Sesame Street"
},
}
if (test?.address?.city?.name) {
console.log("City name exists!");
}【7】空位合并运算符
const person = {
name: "Dave",
age: 30,
squadNumber: 0
};
const squadNumber = person.squadNumber ?? "unassigned";
console.log(`${person.name}s squad number is ${squadNumber}`);【8】import.meta
Node可以通过__dirname或__filename属性与CommonJS一起使用此功能,而浏览器则通过import.meta使用此功能。
const fs = require("fs");
const path = require("path");
const bytes = fs.readFileSync(path.resolve(__dirname, "data.bin"));
const response = await fetch(new URL("../cool-image.jpg", import.meta.url));30、箭头函数你所不知道的一面
普通函数: 会被挂载在prototype上;
constructor里bind的函数: 会被挂载在prototype和实例上;
箭头函数:会被挂载在实例上。
示例:
class A {
constructor() {
this.b = this.b.bind(this);
}
a() {
console.log('a');
}
b(){
console.log('b')
}
c = () => {
console.log('c')
}
}31、手写源码
【1】promise
class Mypromise {
constructor(fn) {
this.state = "pending";
this.successFun = [];
this.failFun = [];
let resolve = val => {
if (this.state !== "pending") return;
this.state = "success";
setTimeout(() => {
this.successFun.forEach(item => item.call(this, val));
});
};
let reject = err => {
if (this.state !== "pending") return;
this.state = "fail";
setTimeout(() => {
this.failFun.forEach(item => item.call(this, err));
});
};
try {
fn(resolve, reject);
}
catch (error) {
reject(error);
}
}
then(resolveCallback, rejectCallback) {
resolveCallback = typeof resolveCallback !== "function" ? v => v : resolveCallback;
rejectCallback = typeof rejectCallback !== "function" ? err => { throw err; } : rejectCallback;
return new Mypromise((resolve, reject) => {
this.successFun.push(val => {
try {
let x = resolveCallback(val);
x instanceof Mypromise ? x.then(resolve, reject) : resolve(x);
} catch (error) {
reject(error);
}
});
this.failFun.push(val => {
try {
let x = rejectCallback(val);
x instanceof Mypromise ? x.then(resolve, reject) : reject(x);
} catch (error) {
reject(error);
}
});
});
}
static all(promiseArr) {
let result = [];
let count = 0;
return new Mypromise((resolve, reject) => {
for (let i = 0; i < promiseArr.length; i++) {
promiseArr[i].then(res => {
result[i] = res;
count++;
if (count === promiseArr.length) {
resolve(result);
}
}, err => {
reject(err);
});
}
});
}
static race(promiseArr) {
return new Mypromise((resolve, reject) => {
for (let i = 0; i < promiseArr.length; i++) {
promiseArr[i].then(res => {
resolve(res);
}, err => {
reject(err);
});
}
});
}
}【2】防抖节流
防抖是在规定时间内再次触发需要清除,这个很容易就想到了setTimeout,节流是在单位时间内触发了一次就不再生效了,可以用一个flag标志来控制。
防抖:
function debounce(fn, delay=300) {
let timer;
return function() {
var args = arguments;
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}节流:
function throttle(fn, delay) {
let flag = true;
return () => {
if (!flag) return;
flag = false;
timer = setTimeout(() => {
fn();
flag = true;
}, delay);
};
}
function throttle(fn, delay) {
let startTime = new Date();
return () => {
let endTime = new Date();
if (endTime - startTime >= delay) {
fn();
startTime = endTime;
} else {
return;
}
};
}【3】EventEmitter
class EventEmitter {
constructor() {
this.events = {};
}
on(type, callBack) {
if (!this.events) this.events = Object.create(null);
if (!this.events[type]) {
this.events[type] = [callBack];
} else {
this.events[type].push(callBack);
}
}
off(type, callBack) {
if (!this.events[type]) return;
this.events[type] = this.events[type].filter(item => {
return item !== callBack;
});
}
once(type, callBack) {
function fn() {
callBack();
this.off(type, fn);
}
this.on(type, fn);
}
emit(type, ...rest) {
this.events[type] && this.events[type].forEach(fn => fn.apply(this, rest));
}
}
// 使用如下
const event = new EventEmitter();
const handle = (...rest) => {
console.log(rest);
};
event.on("click", handle);
event.off("click", handle);
event.once("click ", handle);
event.emit("click");【4】call、apply、bind
Function.prototype.call= function(context, ...args) {
if (!context || context === null) {
context = window;
}
let fn = Symbol();
context[fn] = this;
return context[fn](...args);
};
Function.prototype.apply = function(context, ...args) {
if (!context || context === null) {
context = window;
}
let fn = Symbol();
context[fn] = this;
return context[fn](args);
};
Function.prototype.bind = function (context, ...args) {
if (!context || context === null) {
context = window;
}
let fn = Symbol();
context[fn] = this;
let _this = this;
const result = function (...innerArgs) {
// 此时this指向指向result的实例,这时候不需要改变this指向
if (this instanceof _this) {
this[fn] = _this;
this[fn](...[...args, ...innerArgs]);
delete this[fn];
} else {
context[fn](...[...args, ...innerArgs]);
delete context[fn];
}
};
result.prototype = Object.create(this.prototype);
return result;
};【5】new操作符
function myNew(fn, ...args) {
let obj = {}; // 创造一个实例对象
obj.__proto__ = fn.prototype; // 生成的实例对象继承构造函数原型
//obj=Object.create(fn.prototype);
let result = fn.call(obj, ...args); // 改变构造函数this指向为实例对象
// 如果构造函数执行的结果返回的是一个对象或者函数,那么返回这个对象或函数
if ((result && typeof result === "object") || typeof result === "function") {
return result;
}
return obj; // 不然直接返回obj
}【6】instanceof
function myInstanceof(left, right) {
let leftProp = left.__proto__;
let rightProp = right.prototype;
while (true) {
// 遍历到了原型链最顶层
if (leftProp === null) {
return false;
}
if (leftProp === rightProp) {
return true;
} else {
// 遍历赋值__proto__做对比
leftProp = leftProp.__proto__;
}
}
}【7】深拷贝
function deepClone(target) {
let result;
if (typeof target === 'object') {
if (Array.isArray(target)) {
result = [];
for (let i in target) {
result.push(deepClone(target[i]))
}
} else if(target===null) {
result = null;
} else if(target.constructor===RegExp){
result = target;
} else {
result = {};
for (let i in target) {
result[i] = deepClone(target[i]);
}
}
} else {
result = target;
}
return result;
}【8】原生js手写一个路由
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>手写router</title>
</head>
<body>
<button type="button" onclick="history.go(-1)">返回</button>
<h2>push模式</h2>
<ul>
<li onclick="Router.push(baseUrl)">首页</li>
<li onclick="Router.push(baseUrl+'news')">新闻</li>
<li onclick="Router.push(baseUrl+'product')">产品</li>
</ul>
<h2>replace模式</h2>
<ul>
<li onclick="Router.replace(baseUrl)">首页</li>
<li onclick="Router.replace(baseUrl+'news')">新闻</li>
<li onclick="Router.replace(baseUrl+'product')">产品</li>
</ul>
<div id="app"></div>
<script>
var app = document.getElementById("app");
var baseUrl = "/router/"; // 根路径
function RouterClass(opts) {
this.routes={};
this.curUrl="";
this.mode="";
if(opts){
this.mode=opts.mode;
if(this.mode==='history'){
this.eventHistoryRouter();
}else{
this.eventHashRouter();
}
} else {
this.eventHashRouter();
}
}
RouterClass.prototype.route = function(path, callback) {
this.routes[path] = callback || function(){};
}
// 监听hash模式路由
RouterClass.prototype.eventHashRouter = function() {
// 监听load事件,防止刷新页面数据丢失
window.addEventListener("load", this.hashRouter.bind(this));
window.addEventListener("hashchange", this.hashRouter.bind(this))
}
// hash模式
RouterClass.prototype.hashRouter = function() {
this.curUrl = window.location.hash.slice(1) || '/';
this.routes[this.curUrl]();
}
// history模式
RouterClass.prototype.historyRouter = function() {
this.curUrl = window.location.pathname;
this.routes[this.curUrl]();
}
// 监听history模式
RouterClass.prototype.eventHistoryRouter = function() {
window.addEventListener("load", this.historyRouter.bind(this));
// 监听回退事件 打个比方:就是你点浏览器那个返回的箭头按钮时触发的事件
window.addEventListener("popstate", this.historyRouter.bind(this));
}
// push模式页面跳转
RouterClass.prototype.push = function(url) {
if (this.mode === 'history') {
window.history.pushState({}, null, url);
this.routes[url]();
} else{
url = "#" +url;
window.location.href = url;
}
}
// replace模式页面跳转
RouterClass.prototype.replace = function(url) {
if (this.mode==='history'){
window.history.replaceState({}, null, url);
this.routes[url]();
}else {
url = "#" + url;
window.location.replace(url);
}
}
// 初始化使用
var Router = new RouterClass({
mode:"history" //hash:带#号,history:不带#号
});
// 构造一个函数,根据url 改变 #app 中的内容,对页面进行渲染
Router.route(baseUrl, function(){
app.innerHTML = "首页";
})
Router.route(baseUrl + 'news', function(){
app.innerHTML = "新闻页面";
})
Router.route(baseUrl + 'product', function(){
app.innerHTML = "产品页面";
})
</script>
</body>
</html>32、延迟函数delay
const delay = ms => new Promise((resolve, reject) => setTimeout(resolve, ms));
const getData = status => new Promise((resolve, reject) => {
status ? resolve('done') : reject('fail');
});
const getRes = async (data) => {
try {
const res = await getData(data)
const timestamp = new Date().getTime();
await delay(1000);
console.log(res, new Date().getTime() - timestamp);
}
catch (error) {
console.log(error)
}
}
getRes(true) // 隔1秒
33、分割指定长度的元素数组
const listChunk = (list, size = 1,cacheList = []) => {
const tmp = [...list];
if (size <= 0) {
return cacheList;
}
while (tmp.length) {
cacheList.push(tmp.splice(0, size))
}
return cacheList;
};
console.log(listChunk([1, 2, 3, 4, 5, 6, 7,8, 9])) // [[1], [2], [3], [4], [5], [6], [7], [8], [9]]
console.log(listChunk([1, 2, 3, 4, 5, 6, 7,8, 9], 3)) // [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
console.log(listChunk([1, 2, 3, 4, 5, 6, 7,8, 9], 0)) // []
console.log(listChunk([1, 2, 3, 4, 5, 6, 7,8, 9], -1)) // []34、获取数组交集
const intersection = (list, ...args) => list.filter(item => args.every(list => list.includes(item)));
const intersection = (list, ...args) => list.filter(item => args.every(list => list.includes(item)));
console.log(intersection([2, 1], [2, 3])) // [2]
console.log(intersection([1, 2], [3, 4])) // []35、函数柯里化
const curring = fn => {
const { length } = fn;
const curried = (...args) => {
return (args.length >= length ? fn(...args) : (...args2) => curried(...args.concat(args2)));
};
return curried;
}
const listMerge = (a, b, c) => [a, b, c];
const curried = curring(listMerge)
console.log(curried(1)(2)(3)) // [1, 2, 3]
console.log(curried(1, 2)(3)) // [1, 2, 3]
console.log(curried(1, 2, 3)) // [1, 2, 3]36、字符串前面空格去除与替换
const trimStart = str => str.replace(new RegExp('^([\\s]*)(.*)$'), '$2');
console.log(trimStart(' abc ')) // 'abc '
console.log(trimStart('123 ')) // '123 '37、字符串后面空格去除与替换
const trimEnd = str => str.replace(new RegExp('^(.*?)([\\s]*)$'), '$1');
console.log(trimEnd(' abc ')) // ' abc'
console.log(trimEnd('123 ')) // '123'38、获取当前子元素是其父元素下子元素的排位
const getIndex = el => {
let index = 0;
if (!el) {
return -1;
}
do {
index++;
} while (el = el.previousElementSibling);
return index;
};39、获取当前元素相对于document的偏移量
const getOffset = el => {
const { top, left } = el.getBoundingClientRect();
const { scrollTop, scrollLeft } = document.body;
return {
top: top + scrollTop,
left: left + scrollLeft
}
};40、获取数据类型
const dataType = obj => {
return Object.prototype.toString.call(obj).replace(/^\[object(.+)\]$/, '$1').toLowerCase();
};41、判断是否是移动端
const isMobile = () => 'ontouchstart' in window;
42、fade动画
const fade = (el, type = 'in') {
el.style.opacity = (type === 'in' ? 0 : 1);
let last = +new Date();
const tick = () => {
const opacityValue = (type === 'in' ? (new Date() - last) / 400 : -(new Date() - last) / 400);
el.style.opacity = +el.style.opacity + opacityValue
last = +new Date();
if (type === 'in' ? (+el.style.opacity < 1) : (+el.style.opacity > 0)) {
requestAnimationFrame(tick);
}
};
tick();
};43、将指定格式的字符串解析为日期字符串
const dataPattern = (str, format = '-') => {
if (!str) {
return new Date();
}
const dateReg = new RegExp(`^(\\d{2})${format}(\\d{2})${format}(\\d{4})$`);
const [, month, day, year] = dateReg.exec(str);
return new Date(`${month}, ${day} ${year}`);
};
console.log(dataPattern('12-25-1995')) //Mon Dec 25 1995 00:00:00 GMT+080044、禁止网页复制粘贴
const html = document.querySelector('html');
html.oncopy = () => false;
html.onpaste = () => false;45、input框限制只能输入中文
const input = document.querySelector('input[type="text"]');
const clearText = target => {
const { value } = target;
target.value = value.replace(/[^\u4e00-\u9fa5]/g, '');
};
input.onfocus = ({target}) => {
clearText(target)
}
input.onkeyup = ({target}) => {
clearText(target)
}
input.onblur = ({target}) => {
clearText(target)
}
input.oninput = ({target}) => {
clearText(target)
}46、去除字符串中的html代码
const removeHTML = (str = '') => str.replace(/<[\/\!]*[^<>]*>/ig, '')
console.log(removeHTML('<h1>哈哈哈哈<呵呵呵</h1>')) // 哈哈哈哈<呵呵呵47、命名回调函数
回调函数命名会使阅读代码更容易。
const double = a => a * 2;
const arr = [1, 2, 3].map(double);48、用函数代替长条件语句
// 优化前
if (score === 100 || remainingPlayers === 1 || remainingPlayers === 0) {
quitGame();
}
// 优化后
const winnerExists = () => {
return score === 100 || remainingPlayers === 1 || remainingPlayers === 0;
};
if (winnerExists()) {
quitGame();
}49、Map > Object > switch > if
// 优化前
const getValue = (prop) => {
switch (prop) {
case 'a': {
return 1;
}
case 'b': {
return 2;
}
case 'c': {
return 3;
}
}
};
const val = getValue('a');
// 优化后 Object
const obj = {
a: 1,
b: 2,
c: 3
};
const val = obj['a'];
// 优化后 Map
const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
const val = map.get('a');50、变量类型
【1】对于==的判断
优先比类型,同类型,比大小,不同类型,如果为非原始,调ToPrimitive,为对象调valueOf,还非原始调toString,最后还非原始则报错,如果为原始则进行类型对比,如果不同类型再转换,之后对比大小;再比null与undefined,再比string和number,再比boolean与any,再比object与string、number、symbol。
- 优先对比数据类型是否一致,不一致则进行隐式转换,一致则判断值的大小,得出结果。
- 继续判断两个类型是否为null与undefined,如果是则返回true。
- 接着判断是否为string与number,如果是把string转换为number再对比大小。
- 判断其中一方是否为boolean,如果是就转为number再进一步判断。
- 判断一方是否为object,另一方为string、number、symbol,如果是则把object转为原始类型再判断。
// 以下结果都为true
console.log([5]==5,['5']==5);
console.log({name:'5'}=='[object Object]');
console.log('5'==5,true==1,false==0);
console.log(undefined==null);
console.log([5,6]=='5,6',['5','6']=='5,6');
console.log([]==![]);【2】对于===的判断
- 直接判断两者类型是否相同,不同则返回false,如果相同再比较大小,不会进行任何隐式转换。
- 对引用类型来说,比较的是引用内存地址,除非两者存储的内存地址相同才相等,反之false。
const a=[]
const b=a
a===b //true
const a=[]
const b=[]
a===b //false【3】类型判断
【3.1】原始类型判断
原始类型:string、number、undefined、boolean、symbol、bigint。
原始类型string、number、undefined、boolean、symbol、bigint和function通过typeof直接判断类型。
【3.2】非原始类型判断(包括null)
判断数组:
- 使用Array.isArray()判断数组。
- 使用[] instanceof Array判断是否在Array的原型链上,即可判断是否为数组。
- 使用[].constructor === Array通过其构造函数判断是否为数组。
- 使用Object.prototype.toString.call([])判断值是否为'[object Array]'来判断数组。
判断对象:
- Object.prototype.toString.call({})结果为'[object Object]'则为对象。
- {} instanceof Object判断是否在Object的原型链上,即可判断是否为对象。
- {}.constructor === Object通过其构造函数判断是否为对象。
判断函数:
- 使用typeof func判断func是否为函数。
- 使用func instanceof Function判断func是否为函数。
- 使用func.constructor === Function判断是否为函数。
- 使用Object.prototype.toString.call(func)判断值是否为'[object Function]'判断是否为函数。
判断null:
- 使用null===null来判断是否为null。
- 使用typeof a == 'object' && !a判断a是否为null。
- 使用Object.prototype._ proto_=== a判断a是否为null。
判断是否为NaN:
- 使用isNaN(a)判断a是否为非数值。
- 使用a !== a判断a是否为非数值。
其他判断:
- Object.is(a,b)判断a与b是否完全相等,与===基本相同,不同在于Object.is判断+0不等于-0,NaN等于自身。
- prototypeObj.isPrototypeOf(object)判断object的原型是否为prototypeObj,不同于instanceof,此方法直接判断原型,而非instanceof 判断的是右边的原型链。
类型验证函数:
function dataType(x) {
// null
if (x === null) return 'null';
const primitive = ['number', 'string', 'undefined','symbol', 'bigint', 'boolean', 'function'];
let type = typeof x;
//原始类型以及函数
if (primitive.includes(type)) return type;
//对象类型
if (Array.isArray(x)) return 'array';
if (Object.prototype.toString.call(x) === '[object Object]') return 'object';
if (x.hasOwnProperty('constructor')) return x.constructor.name;
const proto = Object.getPrototypeOf(x);
if (proto) return proto.constructor.name;
// 无法判断
return "can't get this type";
}51、深拷贝与浅拷贝
【1】浅拷贝
- Object.assign({}, obj)浅拷贝object。
- obj1={ ...obj }通过展开运算符浅拷贝obj。
- Object.fromEntries(Object.entries(obj))通过生成迭代器再通过迭代器生成对象。
- Object.create({}, Object.getOwnPropertyDescriptors(obj))浅拷贝obj。
- Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj))浅拷贝obj。
for (const key in obj) {
if(obj.hasOwnProperty(key)) {
obj1[key] = obj[key];
}
}
for (const key of Object.keys(obj)) {
obj1[key] = obj[key]
}【2】深拷贝
weakMap的键为弱引用类型,且必须为对象类型,深拷贝使用弱引用的好处,就是可以优化垃圾回收,weakMap存放的是拷贝表,此拷贝表在拷贝完成之后就没有作用了,之前存放的拷贝对象,经过深拷贝给新拷贝容器,则这些旧对象在销毁之后,对应于拷贝表里的对象也应该随之清除,不应该还保留,这就是使用弱引用来保存表的原因。
function deepClone(a, weakMap = new WeakMap()) {
const type = typeof a;
if (a === null || type !== 'object') return a;
if (s = weakMap.get(a)) return s;
const allKeys = Reflect.ownKeys(a);
const newObj = Array.isArray(a) ? [] : {};
weakMap.set(a, newObj);
for (const key of allKeys) {
const value = a[key];
const T = typeof value;
if (value === null || T !== 'object') {
newObj[key] = value;
continue;
}
const objT = Object.prototype.toString.call(value);
if (objT === '[object Object]' || objT === '[object Array]') {
newObj[key] = deepClone(value, weakMap);
continue;
}
if (objT === '[object Set]' || objT === '[object Map]') {
if (objT === '[object Set]') {
newObj[key] = new Set();
value.forEach(v => newObj[key].add(deepClone(v, weakMap)));
} else {
newObj[key] = new Map();
value.forEach((v, i) => newObj[key].set(i, deepClone(v, weakMap)));
}
continue;
}
if (objT === '[object Symbol]') {
newObj[key] = Object(Symbol.prototype.valueOf.call(value));
continue;
}
newObj[key] = new a[key].constructor(value);
}
return newObj;
}52、原型与原型链
【1】原型
- 只有对象类型才有原型。
- 普通对象的原型为__proto__属性,此属性其实是个访问器属性,并不是真实存在的属性。
- 普通函数有2个属性,一个是是__proto__,一个是函数专有的prototype属性,函数有双重身份,即可以是实例也可以是构造器。
- 箭头函数虽然属于函数,由Function产生,但是没有prototype属性没有构造器特性,也就没有constructor,就不能作为构造器使用。
Reflect.getPrototypeOf(obj) === Object.getPrototypeOf(obj);
Reflect.getPrototypeOf({}) === Object.getPrototypeOf({}) === {}.__proto__;
Object.prototype.__proto__ === null;
Object.create(null); // 等价于{}.__proto__ = null【2】原型链
- 所有函数都是由Function函数构造器实例化而来。
- 所有实例的原型都指向构造它的构造器的prototype
- 每个构造器自身特有的方法就是静态方法,原型上的方法可供所有继承它的实例使用
- 构造器也是函数,也是被Function实例化出来的,所以构造器的__proto__就是Function,但是构造器的prototype属性指向的原型。
- 只有由Function实例化的函数才拥有直接使用Function.prototype上面的内置方法,创建函数只能通过原始函数构造器生成。
- 普通函数作为构造器使用时相当于类,类的prototype就是实例的原型,我们可以给原型添加属性,给类添加属性时就相当于给构造器添加静态属性。
- 普通函数在创建实例的时候,会生成一个实例的原型,此原型指向Object.prototype,这么一来实例也继承了对象的原型,则实例也属于对象类型。
【3】原型继承的实现
// 采用__proto__能实现原型继承,但__proto__是访问器,并不推荐
function foo(v) {
this.v = v;
}
function boo(v) {
this.v = v;
}
boo.prototype.__proto__ = foo.prototype
const b = new boo(3);
// 通过直接修改原型也可以实现原型继承
function foo(v) {
this.v = v;
}
function boo(v) {
this.v = v;
}
boo.prototype = Object.create(foo.prototype, {
constructor: {
value: boo,
enumerable: false,
writable: true,
configurable: true
}
});
const b = new boo(3);
// 借助空构造器来实现原型继承
function foo(v) {
this.v = v
}
function boo(v) {
this.vv = v
}
function o() {}
o.prototype = foo.prototype;
boo.prototype = new o();
boo.prototype.constructor = boo;
const b = new boo(3);
// class的extends会将子类的__proto__设置为父类
boo.__proto__ = foo;53、实现class与extends
【1】实现class
class使用new关键字生成实例,构造器也是通过new来实例化,那么可以推断class本质也是个构造器。
const Class = (function () {
function Constructor(name) {
this.name = name
}
//添加原型方法
Constructor.prototype.getName = function name(name) {
console.log('原型方法getName:' + this.name);
}
//添加原型属性
Constructor.prototype.age = '原型属性age';
//添加静态方法
Constructor.log = function log() {
console.log('我是构造器的静态方法log');
}
//添加静态属性
Constructor.isWho = '构造器静态属性isWho';
return Constructor;
})()
const i = new Class('我是实例');【2】实现extends
//父类
const Parent = (function () {
function Constructor(age) {
this.age = age;
}
Constructor.prototype.getName = function () {
console.log(this.name);
}
return Constructor;
})()
//子类
const Class = (function (_Parent = null) {
if (_Parent) {
Constructor.prototype = Object.create(_Parent.prototype, {
constructor: {
value: Constructor,
enumerable: false,
writable: true,
configurable: true
}
})
Constructor.__proto__ = _Parent;
}
function Constructor(name, age) {
_Parent ? _Parent.call(this, age) : this;
this.name = name;
}
Constructor.prototype.getAge = function () {
console.log(this.age);
}
return Constructor;
})(Parent)54、使用WeakMap优化闭包
闭包中可以使用WeakMap存放外部没有用到的变量,WeakMap会自动清除没有用到的变量。
function foo(){
let a = { name:'me' };
let b = { who:'isMe' };
let wm = new WeakMap();
function bar(){
console.log(a); // a被闭包保留
wm.set(b, 1); // 弱引用b对象
return wm; //wm被闭包保留
}
return bar;
}
const wm = foo()();
console.dir(wm) // No properties即为空
function foo(){
let a = { name:'me' };
let wm = new WeakMap();
function bar(){
console.log(a);
wm.set(a, 1);
return wm;
}
return bar;
}
const wm=foo()();
console.dir(wm); // 保存了对象a与其值155、通过arguments调用的回调函数中的this指向
const obj = {
foo(callback) {
callback();
}
}
obj.foo(function () {
console.log(this) // window ,严格下undefined
})
const obj = {
foo(callback) {
arguments[0]();
}
}
obj.foo(function () {
console.log(this) // arguments对象 ,严格下arguments对象
})56、使用Symbol实现apply、call、bind
【1】手动实现apply
Function.prototype.Apply = function (thisArg, args = Symbol.for('args')) {
const fn = Symbol('fn'); // 生成一个不重复的键
thisArg[fn] = this || window; // 把foo函数作为传入this的一个方法
args === Symbol.for('args')
? thisArg[fn]()
: thisArg[fn](...args) // 调用这方法,传参
delete thisArg[fn]; // 使用完删除
}【2】手动实现call
Function.prototype.Call = function (thisArg) {
const fn = Symbol('fn'); // 生成一个不重复的键
thisArg[fn] = this || window; // 把foo函数作为传入this的一个方法
const args = Array.from(arguments).slice(1);
args.length ? thisArg[fn](...args) : thisArg[fn](); // 调用这方法,传参
delete thisArg[fn]; // 使用完删除
}【3】手动实现bind
Function.prototype.Bind = function (thisArg) {
const fn = Symbol('fn'); // 生成一个不重复的键
thisArg[fn] = this || window; // 把foo函数作为传入this的一个方法
const f = thisArg[fn]; // 负责一份函数
delete thisArg[fn]; // 删除原来对象上的函数,但是保留this指向
const args = Array.from(arguments).slice(1);
return function () {
const arg = args.concat(...arguments)
f(...arg);
}
}57、浏览器是如何执行js代码的
- 浏览器在最开始运行js代码的入口就是html中的script标签所涵盖的代码。
- 当GUI渲染线程解析到script标签,则会把标签所涵盖的js代码加入到宏任务队列中。
- js引擎先取第一个宏任务,即script的代码块,然后主线程在调用栈中解析js代码。
- 所有代码解析完成之后开始运行js代码,遇到同步代码直接执行,遇到异步代码,如果是宏任务类型的异步代码,那么会通知WebApis在对应的线程中处理异步任务,此时js主线程继续执行下面的代码,在其他线程处理完毕之后如果有回调函数,则异步线程会将回调函数加入到宏任务队列尾部,如果是微任务类型的异步代码,也同宏任务处理,只不过是把回调函数加入到微任务队列中,其执行的优先级高于宏任务队列。
- 当同步代码全部执行完成,主线程将会一直检测任务队列,如果有异步微任务,则执行完全部的微任务。
- 进一步执行浏览器渲染进程绘制页面,之后就是开始下一轮的事件循环,就又回到取宏任务执行
注意事项:所有的微任务都是由宏任务中执行的代码产生,一开始只有宏任务队列有任务。异步代码不是直接放在执行栈中执行,而是要派发给其他线程处理,处理完后的回调放在任务队列中存储,等同步队列执行完之后才会取回异步回调代码进行执行。
58、WebWorker多线程
基于js单线程的局限性,如果执行一个很耗时间的函数,那么主线程将会被长时间占用,从而导致事件循环暂停,浏览器无法及时渲染和响应,造成页面崩溃,幸运的是html5支持了多线程webworker。
// test.html(主线程)
const w= new Worker('postMessage.js');
w.onmessage=function(e){
console.log(e.data);
}
w.postMessage('b'); // b is cat
w.terminate(); // 手动关闭子线程
// postMessage.js(worker线程)
this.addEventListener('message', (e) => {
switch (e.data) {
case 'a': this.postMessage(e.data+' is tom');
break;
case 'b': this.postMessage(e.data + ' is cat');
break;
default: this.postMessage(e.data + " i don't know");
this.close(); // 自身关闭
break;
}
})59、AMD、CMD、CommonJS与ES6模块化
模块化的引入主要是用于解决命名冲突、代码复用、代码可读性、依赖管理等。
【1】AMD异步模块定义
- AMD并非原生js支持,它依赖于RequireJS函数库。
- RequireJS主要解决多个js文件之间的依赖关系、浏览器加载大量js代码导致无响应、异步加载模块。
- RequireJS通过define(id?, dependencies?, factory)定义模块,id可选,为定义模块的标识,默认为模块文件名不包括后缀,dependencies可选,是当前模块依赖的模块路径数组,factory为工厂方法,初始化模块的函数或者对象,如果为函数将会只执行一次,如果是对象将作为模块的输出。
- 通过require(dependencies, factory)导入模块,其中dependencies为需要导入的模块路径数组,factory为当模块导入之后的回调函数,此函数的参数列表为对应导入的模块。
- 通过require.config配置各模块路径和引用名。
require.config({
baseUrl: "js/lib",
paths: {
"jquery": "jquery.min", // 实际路径为js/lib/jquery.min.js
"underscore": "underscore.min",
}
});【2】CMD通用模块定义
- CMD并非原生js支持,它依赖于SeaJS函数库。
- CMD推崇一个文件一个模块,推崇依赖就近,定义模块define(id?, deps?, factory),id可选,为定义模块的标识,默认为模块文件名不包括后缀,deps可选,是当前模块依赖的模块路径数组,一般不在其中写依赖,而是在factory中在需要使用的时候引入模块,factory函数接收3各参数,参数一require方法,用来内部引入模块的时候调用,参数二exports是一个对象,用来向外部提供模块接口,参数三module也是一个对象上面存储了与当前模块相关联的一些属性和方法。
- 通过seajs.use(deps, func)加载模块,deps为引入到模块路径数组,func为加载完成后的回调函数。
注意事项:AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块,CMD推崇就近依赖,只有在用到某个模块的时候再去require。
【3】CommonJS
- CommonJS模块规范,通常用于Nodejs中的模块化。
- 拥有4个环境变量module、exports、require、global。
- 通过module.exports(不推荐exports)导出模块对象,通过require(模块路径)加载模块。
- 当一个模块同时存在exports和module.exports时后者覆盖前者。
- 规范中__dirname代表当前模块文件所在的文件夹路径,__filename代表当前模块文件夹路径+文件名。
- CommonJS通过同步的方式加载模块,其输出的模块是一个拷贝对象,所以修改原的模块不会对被引入的模块内部产生影响,且模块在代码运行时加载。
【4】ES6模块化
- 通过export或者export default导出模块接口,通过import xxx from '路径' 导入模块。
- 对于export导出的接口可以使用import { 接口 } from '路径',通过解构的方式按需导入。
- 对于export default默认导出的,可以使用import xxx from '路径',来导入默认导出的接口,一个模块只能有一个默认导出,可以有多个export。
- 还可以通过别名的方式设置导出和导入的接口名,如export { a as foo },import foo as b from 路径。
- es6模块是在代码编译时加载,导出的模块是只读引用,如果原始模块中的值被改变了,那么加载的值也会随之改变,所以是动态引用。
60、script标签之async与defer
【1】使用async属性
- script标签设置这个值,表示引入的js需要异步加载和执行,此属性只适用于外部引入的js。
- 在有async的情况下脚本异步加载和执行,不会阻塞页面加载,但并不保证其加载的顺序,使用此方式加载的js文件最好不要包含其他依赖。
【2】使用defer属性
- script标签设置这个值,表示引入的js需要异步加载和执行,不会阻塞页面加载,会在文档被解析完成后执行,它会按照原来的执行顺序执行,对于有依赖关系的也可使用。
- html4.0中定义了defer,html5.0中定义了async。
【3】不同情况
- 如果只有async,那么脚本在下载完成后异步执行。
- 如果只有defer,那么脚本会在页面解析完毕之后执行。
- 如果都没有,那么脚本会在页面中马上解析执行,停止文档解析阻塞页面加载。
- 如果都有那么同async,当然此情况一般用于html的版本兼容下,如果没有async则defer生效,不过还是推荐直接把script标签放在body底部。
61、改变数组本身的api
- pop() 尾部弹出一个元素。
- push() 尾部插入一个元素。
- shift() 头部弹出一个元素。
- unshift() 头部插入一个元素
- sort([func]) 对数组进行排序,func有2个参数,其返回值小于0降序,反之升序。
- reverse() 原位反转数组中的元素。
- splice(pos, deleteCount, ...items) 返回修改后的数组,从pos开始删除deleteCount个元素,并在当前位置插入items。
- copyWithin(pos[, start[, end]]) 复制从start到end(不包括end)的元素,到pos开始的索引,返回改变后的数组,浅拷贝。
- arr.fill(val[, start[, end]]) 从start到end默认到数组最后一个位置,不包括end,填充val,返回填充后的数组。
62、window之location、navigator
【1】location对象
- location为全局对象window的一个属性,且window.location === document.location,其中的属性都是可读写的,但是只有修改href和hash才有意义,href会重新定位到一个URL,hash会跳到当前页面中的anchor名字的标记,而且页面不会被重新加载。
- 可以通过上述属性来获取URL中的指定部分,或者修改href和hash达到重新定位与跳转。
- 添加hash改变监听器,来控制hash改变时执行的代码。
- location.origin == location.protocol + '//' + location.host)。
// 这行代码将会使当前页面重定向到http://www.baidu.com
window.location.href = 'http://www.baidu.com';
// 如果使用hash并且配合input输入框,那么当页面刷新之后,鼠标将会自动聚焦到对应id的input输入框,
<input type="text" id="target">
<script>
window.location.hash = '#target';
</script>
window.addEventListener("hashchange", funcRef);
// 或者
window.onhashchange = funcRef;location方法:
- 通过调用window.location.assign方法打开指定url的新页面window.location.assign('http://www.baidu.com'),表示在当前页面打开百度,可回退。
- replace(url)表示在当前页面打开指定url,不可回退。
- reload([Boolean])表示将会重新加载当前页面,如果参数为false或者不填,则会以最优的方式重新加载页面,可能从缓存中取资源,如果参数为true则会从服务器重新请求加载资源。
【2】navigator对象
- window.navigator对象包含有关浏览器的信息,用来查询一些关于运行当前脚本的应用程序的相关信息。
- navigator.appCodeName 只读,该属性仅仅是为了保持兼容性,且任何浏览器总是返回'Gecko'。
- navigator.appName 只读,返回当前浏览器的官方名称,不要指望该属性返回正确的值。
- navigator.appVersion 只读,返回当前浏览器的版本,不要指望该属性返回正确的值。
- navigator.platform 只读,返回当前浏览器的所在系统平台。
- navigator.product 只读,返回当前浏览器的产品名称,如"Gecko"。
- navigator.userAgent 只读,返回当前浏览器的用户代理字符串。
63、ajax与fetch
【1】ajax
- ajax可以让页面在不刷新的情况下发起请求获取数据。
- 实例化一个网络请求对象:const XHR = new XMLHttpRequest()。
- 发送一个请求:XHR.open(method, url, [ async, [ user, [ password]]]),method为请求方法,url为请求地址,async表示是否采用异步请求,默认值为true,user和password在请求需要用户和密码的时候使用。
- 发生请求主体内容:XHR.send(body),其格式可以为FormData、ArrayBuffer、Document、序列化字符串,在收到响应后,响应的数据会自动填充XHR对象的属性。
- 设置请求头的类型与值:XHR.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')。
- 监听实例的onreadystatechange属性方法:XHR.onreadystatechange = function () { }。
readyState有5个请求状态:
- 0 表示 请求还未初始化,尚未调用open()方法。
- 1 表示 已建立服务器链接,open()方法已经被调用。
- 2 表示 请求已接受,send()方法已经被调用,并且头部和状态已经可获得。
- 3 表示 正在处理请求,下载中,responseText属性已经包含部分数据。
- 4 表示 完成,下载操作已完成。
请求实例XHR的属性:
- XHR.responseText 为字符串形式的响应数据,可能是JSON格式需要JSON.parse解析。
- XHR.responseXML 为xml形式的响应数据,通过XHR.responseType = 'document'和XHR.overrideMimeType('text/xml')来解析为XML。
- XHR.withCredentials属性表示是否使用cookies、authorization等凭证字段。
- XHR.timeout属性用来设置请求超时时间。
- XHR.ontimeout属性用来设置请求超时的回调函数,函数的参数为事件对象。
- XHR.abort()方法用来终止网络请求。
- XHR.getAllResponseHeaders()方法用来获取所有的响应头。
- XHR.getResponseHeader(name)方法用来获取指定的响应头。
进度事件:
- loadstart 在收到响应的第一个字节触发。
- progress 在接收期间不断触发。
- error 发生错误。
- abort 调用abort方法而终止。
- load 接收到完整数据,可代替readystatechange与readyState判断。
- loadend 在通信完成或abort error load事件后触发。
注意事项:通过XHR.addEventListener(eventname, callback)方法添加对应的事件监听,其回调函数接收一个事件对象参数progress,它有3个属性用于查看当前进度相关信息,lengthComputable为boolean值,表示进度是否可用,position表示已经接收的字节数,totalSize表示总需要传输的内容长度即Content-Length字节数,通常在分片传输内容的时候用到。
/** 最简单的发起一个请求*/
const XHR = new XMLHttpRequest();
XHR.open('get','http://127.0.0.1:3000/test?key=value');
XHR.send();
XHR.addEventListener('load',(e)=>{
// 服务端返回的是查询参数
console.log(XHR.response) // {"key":"value"}
});
/** 基于XMLHttpRequest封装一个请求方法*/
// 发送的数据
const data = { name: 'tom' };
// 请求配置
const config = {
type: "post",
url: "http://127.0.0.1:3000/test",
data: data,
dataType: 'application/json',
success: function (res) {
console.log(res);
},
error: function (e) {
console.log(e);
}
};
// 请求构造器
function Ajax(conf) {
this.type = conf.type || 'get';
this.url = conf.url || '';
this.data = conf.data || {};
this.dataType = conf.dataType || '';
this.success = conf.success || null;
this.error = conf.error || null;
}
// send方法
Ajax.prototype.send = function () {
if (this.url === '') return;
const XHR = new XMLHttpRequest();
XHR.addEventListener('load', () => {
if (XHR.status >= 200 && XHR.status < 300 || XHR.status == 304) {
typeof this.success === 'function' && this.success(XHR.response);
}
})
XHR.addEventListener('error', (e) => {
typeof this.error === 'function' && this.error(e);
})
if (this.type.toLowerCase() === 'get') {
XHR.open('get', this.url);
XHR.send(null);
} else {
XHR.open(this.type, this.url);
XHR.setRequestHeader('Content-Type', this.dataType || 'application/x-www-form-urlencoded');
let data = this.data;
if (this.dataType === 'application/json') {
data = JSON.stringify(this.data);
}
XHR.send(data);
}
}
// 发送请求
const ajax = new Ajax(config).send();【2】fetch
- fetch用于替代XMLHttpRequest方式的网络请求,使用起来比XHR更加方便。
- fetch方法接受2个参数,参数1为请求url或Request对象,参数2为可选配置对象。
- then的回调函数接受一个Response对象参数,其对象拥有9个属性,8个方法。
Response对象的9个属性:
- type 只读 包含Response的类型。
- url 只读 包含Response的URL。
- useFinalURL 布尔值,用来标识是否是该Response的最终URL。
- status 只读 包含Response的状态码。
- ok 只读 布尔值,用来标识该Response成功。
- redirected 只读 表示该Response是否来自一个重定向,如果是的话,它的URL列表将会有多个。
- statusText 只读 包含了与该Response状态码一致的状态信息。
- headers 只读 包含此Response所关联的Headers对象。
- bodyUsed 只读 布尔值,用来标识该Response是否读取过Body。
Response对象的8个方法:
- clone 创建一个Response对象的克隆。
- error 返回一个绑定了网络错误的新的Response对象。
- redirect(url, status) 用另一个URL创建一个新的response。
- arrayBuffer 接受一个Response流,并等待其读取完成,并resolve 一个ArrayBuffer对象。
- blob()方法使用一个Response流,并将其读取完成。
- formData 将Response对象中的所承载的数据流读取并封装成为一个对象。
- json 使用一个Response 流,并将其读取完成,解析结果是将文本体解析为JSON。
- text 提供了一个可供读取的"返回流",它返回一个包含USVString对象,编码为UTF-8。
// 配置对象具体配置
const config = {
method: 'GET', // 请求方法
headers: { // 头信息
'user-agent': 'Mozilla/4.0 MDN Example',
'content-type': 'application/json'
},
body: JSON.stringify({ // 请求的body信息,Blob, FormData等
data: 1
}),
mode: 'cors', // 请求的模式,cors、 no-cors或same-origin
credentials: 'include', // omit、same-origin或include,在当前域名内自动发送cookie, 必须提供这个选项
cache: 'no-cache', // default、no-store、reload、no-cache、force-cache或者only-if-cached
redirect: 'follow', // 可用的redirect模式: follow(自动重定向), error(如果产生重定向将自动终止并且抛出一个错误), 或者manual (手动处理重定向).
referrer: 'no-referrer', // no-referrer、client或一个URL,默认是client
referrerPolicy: 'no-referrer', // 指定referer HTTP头
integrity: 'sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=', // 包括请求的subresource integrity值
};
// 发起请求
fetch('http://biadu.com' [, config]);64、WebSocket
- WebSocket是一种在单个TCP连接上进行全双工通信的协议。
- new WebSocket(url[, protocols])创建实例,参数1 url,以ws://或wss://(加密)开头,参数2 protocols,是单协议或者包含协议的字符串数组。
WebSocket实例的属性:
- binaryType 返回websocket连接所传输二进制数据的类型(blob, arraybuffer)。
- bufferedAmount 只读 返回已经被send()方法放入队列中但还没有被发送到网络中的数据的字节数。所有数据被发送至网络,该属性值将被重置为0,若在发送过程中连接被关闭,属性值不会重置为0。
- extensions 只读 返回服务器选择的扩展名。
- onclose 用于指定连接失败后的回调函数。
- onmessage 用于指定当从服务器接受到信息时的回调函数。
- onopen 用于指定连接成功后的回调函数。
- protocol 只读 服务器选择的下属协议。
- readyState 只读 当前的链接状态,共4个:0 建立连接、1 已经连接、2 正在关闭、3 连接已经关闭或没有连接成功。
- url 只读 WebSocket的绝对路径。
WebSocket实例的方法:
- close(code, reason) code 可选 数字状态码 默认1005,reason 连接关闭的原因。
- send(data) 向服务器发送数据(ArrayBuffer,Blob等)。
// 必须传入绝对URL,可以是任何网站
const s = new WebSocket('ws://www.baidu.com');
s.readyState // 0 建立连接 1 已经建立 2 正在关闭 3 连接已关闭或没有链接成功
s.send('hello') // 发送的数据必须是纯文本
s.onopen = function () {}
s.onerror = function () {}
s.onmessage = function (event) {
// 当接收到消息时
console.log(event.data) // 数据是纯字符
}
s.close() // 关闭连接
s.onclose = function (event) {
// event.wasClean 是否明确的关闭
// event.code 服务器返回的数值状态码
// event.reason 字符串,服务器返回的消息
}65、短轮询、长轮询与WebSocket
【1】短轮询
server收到请求不管是否有数据到达都直接响应http请求,服务端响应完成,就会关闭这个TCP连接;如果浏览器收到的数据为空,则隔一段时间,浏览器又会发送相同的http请求到server以获取数据响应。
缺点:消息交互的实时性较低。
const xhr = new XMLHttpRequest();
// 每秒发送一次短轮询
const id = setInterval(() => {
xhr.open('GET', 'http://127.0.0.1:3000/test?key=value');
xhr.addEventListener('load', (e) => {
if (xhr.status == 200) {
console.log(xhr.response);
// 如果不需要可以关闭
clearInterval(id);
}
})
xhr.send();
}, 1000)【2】长轮询
server收到请求后如果有数据,立刻响应请求,如果没有数据就会停留一段时间,这段时间内,如果server请求的数据到达,就会立刻响应,如果这段时间过后,还没有数据到达,则以空数据的形式响应http请求,若浏览器收到的数据为空,会再次发送同样的http请求到server。
缺点:server没有数据到达时,http连接会停留一段时间,这会造成服务器资源浪费。
function ajax() {
const xhr = new XMLHttpRequest();
xhr.open('GET', 'http://127.0.0.1:3000/test?key=value');
xhr.addEventListener('load', (e) => {
if (xhr.status == 200) {
console.log(xhr.response)
// 如果不需要可以关闭
if (xhr.response != '') return;
ajax();
}
})
xhr.send();
}【3】短轮询和长轮询的相同点与不同点
相同点:
- 当server的数据不可达时,长轮询和短轮询都会停留一段时间。
- 都是实时从服务器获取数据更新。
不同点:
- 长轮询是在服务器端停留,短轮询是在浏览器端的停留。
- 短轮询隔一段时间向服务器发起请求,不管服务器数据有没有变化都直接返回结果,长轮询则在服务器数据有发生变化的时候才返回结果,如果在一定时间没有变化那么将会超时自动关闭连接。
【4】WebSocket
- 为了解决http无状态、被动性以及轮询问题,html5推出了websocket协议,浏览器和服务器只需完成一次握手,两者即可建立持久性连接,并进行双向通信。
- 基于http进行握手,发生加密数据,保持连接不断开。
35、长连接与短连接
【1】短连接
- 客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接,HTTP/1.0默认使用短连接。
- 当浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源,浏览器就会重新建立一个HTTP会话。
短连接步骤:建立连接—数据传输—关闭连接...建立连接—数据传输—关闭连接。
【2】长连接
- 从HTTP/1.1起,默认使用长连接,使用长连接的HTTP协议,会在响应头加入这行代码Connection:keep-alive。
- 当打开一个网页后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。
- keep-alive不会永久保持连接,它有一个保持时间。
长连接步骤:建立连接—数据传输...(保持连接)...数据传输—关闭连接。
【3】长短轮询和长短连接区别
- HTTP协议的长连接和短连接,实质上是TCP协议的长连接和短连接。
- 长短连接通过双方请求响应头是否设置Connection:keep-alive来决定使用,而是否轮询,是根据服务端的处理方式来决定的,与客户端没有关系。
- 长短连接通过协议来实现,而长短轮询通过服务器编程手动实现。
66、存储
【1】Cookie
- 客户端请求服务器时,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie,当客户端再次请求服务器时,客户端将该Cookie一同提交给服务器,服务器通过检查该Cookie来获取用户状态。
- cookie只在域名不同的情况下不支持跨域,忽略协议与端口,域名相同时,Cookie是共享的,可通过domain设置域,path设置域下的共享路径。
- 前端通过document.cookie对cookie进行读写操作。
- 创建cookie是后端的事。
cookie属性:
- name cookie名,不能重复,不可更改。
- value cookie的值。
- domain cookie绑定的域名,默认绑定当前域,多级域名不可交换cookie,如果设置以点开头的域名,则所有子域名可以访问,如设置.baidu.com,则a.baidu.com可访问其上级域名的cookie。
- path cookie所能使用的路径,默认'/'路径,只要满足当前匹配路径以及子路径都可以共享cookie。
- maxAge cookie失效时间,单位秒,正数为失效时间,负数表示当前cookie在浏览器关闭时失效,0表示删除cookie。
- secure cookie是否使用安全协议传输,如HTTPS、SSL,默认不使用,只在HTTPS等安全协议下有效,该属性并不能对客户端的cookie进行加密,不能保证绝对的安全性。
- version 当前cookie使用的版本号,0表示遵循Netscape的Cookie规范(多数),1表示遵循W3C的RFC2109规范(较严格),默认为0。
- same-site 规定浏览器不能在跨域请求中携带Cookie,减少CSRF攻击。
- HttpOnly true表示就不能通过js脚本来获取cookie的值,用来限制非HTTP协议程序接口对客户端Cookie进行访问,可以有效防止XSS攻击。
【2】Session
- session对象存储特定用户的属性及配置信息。
- 页面之间的跳转,session对象不会丢失,一旦用户关闭整个会话或session超时失效时会话结束。
- 用户第一次请求服务器时,服务器根据用户提交的相关信息,创建对应的session ,请求返回时将此session的唯一标识信息sessionID返回给浏览器,浏览器接收到服务器返回的sessionID信息后,会将此信息存入到Cookie 中,同时Cookie记录此sessionID属于哪个域名。
- 用户第二次访问服务器时,请求会自动判断此域名下是否存在Cookie信息,如果存在自动将Cookie信息也发送给服务端,服务端会从Cookie中获取sessionID,再根据sessionID查找对应的session信息,如果没有找到说明用户没有登录或者登录失效,如果找到session证明用户已经登录可执行后面操作。
- session的运行依赖session id,而session id是存在Cookie中的。
【3】cookie与session的区别
- cookie数据存放在浏览器上,session数据放在服务器上。
- cookie不是很安全,考虑到安全应当使用session,用户验证这种场合一般会用session。
- session保存在服务器,客户端不知道其中的信息,cookie保存在客户端,服务器知道其中的信息。
- session会在一定时间内保存在服务器上,当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookie。
- session中保存的是对象,cookie中保存的是字符串。
- session不能区分路径,同一个用户在访问一个网站期间,所有的session在任何一个地方都可以访问到,而cookie中如果设置了路径参数,那么同一个网站中不同路径下的cookie互相是访问不到的。
【4】localStorage与sessionStorage
localStorage:
- 用于存储本地数据,可持久化,永不过期,除非主动删除。
- 只要在相同的协议、主机名、端口下,就能读取/修改到同一份localStorage数据,一般用于跨页面共享数据。
- 可通过window.addEventListener("storage", function(e){}设置localStorage事件监听,当存储区域的内容发生改变时,将会调用回调。
localStorage.setItem("b", "isaac"); // 设置b为"isaac"
localStorage.getItem("b"); // 获取b的值,为"isaac"
localStorage.key(0); // 获取第0个数据项的键名,此处即为“b”
localStorage.removeItem("b"); // 清除b的值
localStorage.clear(); // 清除当前域名下的所有localStorage数据sessionStorage:
- 用于本地存储一个会话中的数据,这些数据只有在同一个会话中的页面才能访问,会话结束数据也随之销毁。
- 仅在当前网页会话下有效,关闭页面或浏览器后就会被清除。
- 主要用于存储当前页面独有的数据,不与浏览器其他页面共享。
sessionStorage.setItem(name, num); // 存储数据
sessionStorage.setItem('value2', 119);
sessionStorage.valueOf(); // 获取全部数据
sessionStorage.getItem(name); // 获取指定键名数据
sessionStorage.sessionData; // sessionStorage是js对象,也可以使用key的方式来获取值
sessionStorage.removeItem(name); // 删除指定键名数据
sessionStorage.clear();【5】cookie、session、localStorage与sessionStorage的区别:
- 数据存储方面:cookie数据始终在同源的http请求中携带,即cookie在浏览器和服务器间来回传递。cookie数据还有路径的概念,可以限制cookie只属于某个路径下,sessionStorage和localStorage不会自动把数据发送给服务器,仅在本地保存。
- 存储数据大小:cookie数据不能超过4K,每次http请求都会携带cookie、所以cookie只适合保存很小的数据,sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大。
- 数据存储有效期:sessionStorage仅在当前浏览器窗口关闭之前有效,localStorage始终有效,窗口或浏览器关闭也一直保存,本地存储,因此用作持久数据,cookie只在设置的cookie过期时间之前有效,即使窗口关闭或浏览器关闭。
- 作用域不同:sessionStorage不同的浏览器窗口不会共享数据,即使是同一个页面,localStorage在所有同源窗口中都是共享的,只要浏览器不关闭,数据仍然存在,cookie也是在所有同源窗口中都是共享的,只要浏览器不关闭,数据仍然存在。