JavaScript专题—技巧集锦

239 阅读45分钟

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') // true

3、如何切换一个元素的类

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')); // false

7、如何检查指定的元素在视口中是否可见

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')); // 9

17、如何向传递的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(); // true

21、如果不存在,如何创建目录

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]); // 2

28、模块化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+0800

44、禁止网页复制粘贴

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与其值1

55、通过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也是在所有同源窗口中都是共享的,只要浏览器不关闭,数据仍然存在。