公告

👇微信扫码添加好友👇

Skip to content

每日一题 - 202406

6-30

十万内数字转中文

题目描述

比如数字12345,我们转化为一万二千三百四十五。 比如数字10002,我们转化为一万零二。(数字在10万以内)

代码

javascript
function numToChinese(num) {
  const numStr = String(num);
  const numMapper = [
    "零",
    "一",
    "二",
    "三",
    "四",
    "五",
    "六",
    "七",
    "八",
    "九"
  ];

  const unitMapper = [, , "十", "百", "千", "万"];

  let res = "";

  for (let i = 0; i < numStr.length; i++) {
    const chNum =
      numStr[i] === "0" && res[res.length - 1] === "零"
        ? ""
        : numMapper[numStr[i]];
    const unit = numStr[i] === "0" ? "" : unitMapper[numStr.length - i] || "";
    res = res + chNum + unit;
  }

  return res[res.length - 1] === "零" ? res.slice(0, -1) : res;
}

6-29

无序不相等数组中,选取 N 个数,使其和为 M

javascript
function sum(list) {
  return list.reduce((acc, cur) => acc + cur, 0);
}
function backtrack(list, res, tempList, T, start) {
  // 如果不限定每个数字只能使用一次, 下面增加一行代码 if (tempList.length > list.length) return;
  if (sum(tempList) === T) return res.push([...tempList]);

  for (let i = start; i < list.length; i++) {
    tempList.push(list[i]);
    // 如果不限定每个数字只能使用一次, 下面代码改为backtrack(list, res, tempList, T, i);
    backtrack(list, res, tempList, T, i + 1);
    tempList.pop();
  }
}
// 从一个无序,不相等的数组中,选取N个数,使其和为M实现算法
function nSum(list, T) {
  const res = [];
  backtrack(list, res, [], T, 0);
  return res;
}

// test
const r = nSum([1, 3, 6, 4, 2, 7], 7);
console.log(r);

6-28

拍平数组

题目描述

最新的ES规范其实也加入了这个方法,功能是将一个数组压平,比如[1, 2, [3, [4, 5, [6, [7, 8]]]]], 会被处理成[1, 2, 3, 4, 5, 6, 7, 8]

更进一步,实现可以压平指定深度的数组。

代码

javascript
function flatten(list) {
  if (list.length === 0) return [];
  const head = list[0];
  if (head instanceof Array) {
    list[0] = flatten(head);
  } else {
    list[0] = [list[0]];
  }
  return list[0].concat(flatten(list.slice(1)));
}

function flattenDepth(list, n) {
  if (list.length === 0) return [];
  if (n === 0) return list;
  const head = list[0];
  if (head instanceof Array) {
    list[0] = flattenDepth(head, n - 1);
  } else {
    list[0] = [list[0]];
  }
  return list[0].concat(flattenDepth(list.slice(1), n));
}

// test

const a = flatten([1, 2, [3, [4, 5, [6, [7, 8]]]]]);

console.log(a);

const b = flattenDepth([1, 2, [3, [4, 5, [6, [7, 8]]]]], 2);

console.log(b);

const c = flattenDepth([1, 2, [3, [4, 5, [6, [7, 8]]]]], Number.MAX_VALUE);

console.log(c);

6-27

剪枝叶

题目描述

有一条马路,马路上有很多树,树的高度不一。现在要统一剪树,剪到高度为 h。 意思就是,比 h 高的树都剪到 h,比 h 低的树高度不变。所有的树剪掉的总长度为 C。 现在要使 C>某个值的情况下(假设为 MM),使 h 最大。问怎么确定 h?

代码

javascript
function cutTree(list, MM, range) {
  if (list.length === 0) return 0;
  let start = 0;
  let end = Math.max(...list);

  while (start <= end) {
    const mid = start + ((end - start) >> 1);
    let res = 0;
    for (let i = 0; i < list.length; i++) {
      if (list[i] > mid) {
        res = res + list[i] - mid;
      }
    }
    if (res > MM) {
      if (res - MM <= range) return mid;
      end = mid - range;
    } else {
      start = mid + range;
    }
  }

  return -1;
}

// test
const a = cutTree([10, 8, 9, 7, 7, 6], 16, 1);
const b = cutTree([10, 8, 9, 7, 7, 6], 20, 1);
const c = cutTree([10, 8, 9, 7, 7, 6], 15, 1);

console.log(a, b, c);

6-26

大数相加

题目描述

如何实现两个非常大的数字(已经超出了Number范围)的加法运算。

思路

将两个数字前面补0至相同的长度,然后从低位到高位进行相加, 同时用一个变量记录进位的信息即可。

代码

javascript
function bigNumberSum(a, b) {
  // 123456789
  // 000009876

  // padding
  let cur = 0;
  while (cur < a.length || cur < b.length) {
    if (!a[cur]) {
      a = "0" + a;
    } else if (!b[cur]) {
      b = "0" + b;
    }
    cur++;
  }

  let carried = 0;
  const res = [];

  for (let i = a.length - 1; i > -1; i--) {
    const sum = carried + +a[i] + +b[i];
    if (sum > 9) {
      carried = 1;
    } else {
      carried = 0;
    }
    res[i] = sum % 10;
  }
  if (carried === 1) {
    res.unshift(1);
  }

  return res.join("");
}

6-25

日期格式化

javascript
export function parseTime(time, pattern) {
  if (arguments.length === 0 || !time) {
    return null
  }
  const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
  let date
  if (typeof time === 'object') {
    date = time
  } else {
    if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
      time = parseInt(time)
    } else if (typeof time === 'string') {
      time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), '');
    }
    if ((typeof time === 'number') && (time.toString().length === 10)) {
      time = time * 1000
    }
    date = new Date(time)
  }
  const formatObj = {
    y: date.getFullYear(),
    m: date.getMonth() + 1,
    d: date.getDate(),
    h: date.getHours(),
    i: date.getMinutes(),
    s: date.getSeconds(),
    a: date.getDay()
  }
  const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
    let value = formatObj[key]
    // Note: getDay() returns 0 on Sunday
    if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }
    if (result.length > 0 && value < 10) {
      value = '0' + value
    }
    return value || 0
  })
  return time_str
}

6-24

什么是限流?常见的限流算法有哪些?

限流是一种控制流量的技术,保护系统免受突发流量的影响。基本原理是控制请求的速率或数量,确保系统在可承受的范围内运行。

常见的限流算法有:

(1)漏桶算法:系统请求先进入漏桶,再从漏桶中逐一取出请求执行,控制漏桶的流量。

(2)令牌桶算法:系统请求会得到一个令牌,从令牌桶中取出一个令牌执行,控制令牌桶中令牌的数量。

(3)计数器算法:系统请求被计数,通过比较当前请求数与限流阈值来判断是否限流。

(4)滑动窗口:基于一个固定大小的时间窗口,允许在该时间窗口内的请求数不超过设定的阈值。这个时间窗口随着时间的推移不断滑动,以适应不同时间段内的请求流量。

6-23

JS常用的十个高阶函数

高阶函数是对其他函数进行操作的函数,可以将它们作为参数或返回它们。

简单来说,高阶函数是一个函数,它接收函数作为参数或将函数作为输出返回。

  1. map

  • map()返回一个新的数组,数组中的元素为原始数组调用函数处理后的值。

  • map()不会对空数组进行检测。

  • map()不会改变原始数组。

传递给 map() 方法的回调函数接受 3 个参数:currentValueindexarray

  1. currentValue必须。当前元素的的值。

  2. index可选。当前元素的索引。

  3. arr可选。当前元素属于的数组对象。

javascript
    let arr = [10, 20, 45, 50, 65, 150, 70, 40];
    let newArr = arr.map((item) => {
        return item * 2;
    });
    console.log(newArr)// [20, 40, 90, 100, 130, 300, 140, 80]
  1. filter

  • filter()方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。

  • filter()不会对空数组进行检测。

  • filter()不会改变原始数组。

传递给 filter() 方法的回调函数接受 3 个参数:currentValueindexarray

  1. currentValue必须。当前元素的的值。

  2. index可选。当前元素的索引。

  3. arr可选。当前元素属于的数组对象。

javascript
    let arr = [20, 40, 90, 100, 130, 300, 140, 80];
    let newArr = arr.filter((item) => {
         return item < 100;
    });
    console.log(newArr);//[20, 40, 90, 80]
  1. forEach

  • forEach()方法类似于 map(),传入的函数不需要返回值,并将元素传递给回调函数。

  • forEach() 对于空数组是不会执行回调函数的。

  • forEach()不会返回新的数组,总是返回undefined.

传递给 forEach() 方法的回调函数接受 3 个参数:currentValueindexarray

  1. currentValue必须。当前元素的的值。

  2. index可选。当前元素的索引。

  3. arr可选。当前元素属于的数组对象。

javascript
    let arr = [20, 40, 90, 100];
    let newArr = arr.forEach((item,index) => {
        console.log(item,index);
    });
    //20 0
    //40 1
    //90 2
    //100 3
  1. sort

  • sort()方法用于对数组的元素进行排序。

  • sort()会修改原数组。

sort() 方法接受一个可选参数,用来规定排序顺序,必须是函数。如果没有传递参数, sort() 方法默认把所有元素先转换为 String 再排序 ,根据 ASCII 码进行排序。 如果想按照其他标准进行排序,就需要提供比较函数,该函数要比较两个值,然后返回一个用于说明这两个值的相对顺序的数字。比较函数应该具有两个参数 ab,其返回值如下:

  • a 小于 b,在排序后的数组中 a 应该出现在 b 之前,则返回一个小于 0 的值。

  • a 等于 b,则返回 0

  • a 大于 b,则返回一个大于 0 的值。

javascript
    //从小到大排序
    let arr = [10, 20, 1, 2];
    arr.sort(function (x, y) {
        if (x < y) {
            return -1;
        }
        if (x > y) {
            return 1;
        }
        return 0;
    });
    console.log(arr); // [1, 2, 10, 20]
      
    //从大到小排序
    let arr = [10, 20, 1, 2];
    arr.sort(function (x, y) {
        if (x < y) {
            return 1;
        }
        if (x > y) {
            return -1;
        }
        return 0;
    }); // [20, 10, 2, 1]
  1. some

  • some() 方法用于检测数组中的元素是否满足指定条件。

  • some() 方法会依次执行数组的每个元素。

  • 如果有一个元素满足条件,则表达式返回 true, 剩余的元素不会再执行检测。

  • 如果没有满足条件的元素,则返回 false

  • some()不会对空数组进行检测。

  • some()不会改变原始数组。

传递给 some() 方法的回调函数接受 3 个参数:currentValueindexarray

  1. currentValue必须。当前元素的的值。

  2. index可选。当前元素的索引。

  3. arr可选。当前元素属于的数组对象。

javascript
    let arr = [10, 20, 1, 2];
    let result = arr.some((item) => {
        return item > 10;
    });
    console.log(result);//true
  1. every

  • every() 方法用于检测数组所有元素是否都符合指定条件。

  • every() 方法会依次执行数组的每个元素。

  • 如果数组中检测到有一个元素不满足,则整个表达式返回 false ,且剩余的元素不会再进行检测。

  • 如果所有元素都满足条件,则返回 true

  • every()不会对空数组进行检测。

  • every()不会改变原始数组。

传递给 every() 方法的回调函数接受 3 个参数:currentValueindexarray

  1. currentValue必须。当前元素的的值。

  2. index可选。当前元素的索引。

  3. arr可选。当前元素属于的数组对象。

javascript
    let arr = [11, 20, 51, 82];
    let result = arr.every((item) => {
       return item > 10;
    });
    console.log(result);//true
  1. reduce

  • reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。

  • reduce()对于空数组是不会执行回调函数的。

reduce 方法接收两个参数: 回调函数一个可选的 initialValue (初始值)

如果不传第二个参数 initialValue,则函数的第一次执行会将数组中的第一个元素作为 prev 参数返回。

传递给 reduce() 方法的回调函数接受 4 个参数:prev, current, currentIndex, arr

  1. prev必须。函数传进来的初始值或上一次回调的返回值。

  2. current必须。数组中当前处理的元素值。

  3. currentIndex可选。当前元素索引。

  4. arr可选。当前元素所属的数组本身。

javascript
    let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    let sum = arr.reduce((prev, current) => {
       return prev + current;
    }, 0);
    console.log(sum); //55
  1. reduceRight

reduceRight() 方法的功能和 reduce() 功能是一样的,不同的是 reduceRight() 从数组的末尾向前将数组中的数组项做累加。 9. #### find

  • find()方法用于查找符合条件的第一个元素,如果找到了,返回这个元素,否则,返回undefined

  • find()不会对空数组进行检测。

  • find()不会改变原始数组。

传递给 find() 方法的回调函数接受 3 个参数:currentValueindexarray

  1. currentValue必须。当前元素的的值。

  2. index可选。当前元素的索引。

  3. arr可选。当前元素属于的数组对象。

javascript
     let arr = [11, 20, 51, 82];
     let result = arr.find((item) => {
       return item > 50;
     }, 0);
     console.log(result);//51
  1. findIndex

findindex()find() 类似,也是查找符合条件的第一个元素,不同之处在于 findindex() 会返回这个元素的索引,如果没有找到,返回 -1

javascript
let arr = [11, 20, 51, 82];
let result = arr.findIndex((item) => {
  return item > 50;
}, 0);
console.log(result);// 2

6-22

如何解决vue初始化页面闪动问题

使用 vue 开发时,在 vue 初始化之前,由于 div 是不归 vue 管的,所以我们写的代码在还没有解析的情况下会容易出现花屏现象,看到类似于 的字样,虽然一般情况下这个时间很短暂,但是我们还是有必要让解决这个问题的。

首先:在 css 里加上 [v-cloak] { display: none; } 。如果没有彻底解决问题,则在根元素加上 style="display: none;" :style="{display: block }"

6-21

求正序增长的正整数数组中,其和为 N 的两个数

javascript
//获取其中某一种组合
function twoSum(arr, target) {
  let first
  let second
  arr.forEach(element => {
    if (arr.includes(target - element)) {
      first = element
    }
  })
  second = arr.find(ele => ele === target - first)

  if (!first || !second) return null

  return [first, second]
}

//获取所有的组合
function twoSum(arr, target) {
  let firstArr = []
  let secondArr = []
  let result = []

  arr.forEach(ele => {
    if (arr.includes(target - ele)) {
      firstArr.push(ele)
    }
  })

  firstArr.forEach(ele => {
    secondArr.push(target - ele)
  })

  firstArr.forEach((firstEle, i) => {
    secondArr.forEach((secondEle, j) => {
      if (i === j) {
        result.push([firstEle, secondEle])
      }
    })
  })

  return result.length > 0 ? result : null
}

6-20

实现二进制与十进制的互相转化的两个函数

javascript
function integerToBin (num) {
  // 64
  const result = []
  while (num / 2) {
    next = num % 2
    num = Math.floor(num / 2)
    result.unshift(next)
  }
  return result
}

function fractionalToBin (num) {
  const result = []
  let i = 0
  while (num !== 0 && i < 54) {
    num = num * 2
    
    next = num >= 1 ? 1 : 0
    num = num % 1
    i++
    result.push(next)
  }
  return result
}

function decToBinary (num) {
  // 1.5
  const [int, fraction] = String(num).split(/(?=\.)/).map((x, i) => {
    return i === 0 ? integerToBin(x) : fractionalToBin(x)
  })
  return [int, fraction]
}

function binToDec (num) {
  const [_int, _fraction] = String(num).split('.')
  const int = _int.split('').reduce((acc, x, i, arr) => {
    return acc + Number(x) * 2 ** (arr.length - 1 - i)
  }, 0)
  const fraction = _fraction ? _fraction.split('').reduce((acc, x, i) => {
    return acc + x * 2 ** -(i + 1)
  }, 0) : 0
  return `${int}${fraction ? '.' + fraction.toString().slice(2) : ''}`
}

6-19

请输出 100 以内的菲波那切数列

js
function fib (n) {
  let a = 0, b = 1;
  let r = [0]
  while (b < n) {
    r.push(b);
    [a, b] = [b, a + b];
  }
  return r
}

6-18

统计字符串中出现次数最多的字符及次数

javascript
function getFrequentChar (str) {
  const dict = {}
  let maxChar = ['', 0]
  for (const char of str) {
    dict[char] = (dict[char] || 0) + 1
    if (dict[char] > maxChar[1]) {
      maxChar = [char, dict[char]]
    }
  }
  return maxChar
}

6-17

Vue 如何实现按需加载

方法一:vue-router配置resolve + require加载

这种方式就是下一个组件生成一个JS文件

路由配置如下:

javascript
{
    path: '/demo',
    name: 'Demo',
    component: resolve => require(['../components/Demo'], resolve)
}

方法二:ES6提案的import()方法

javascript
// 下面2行代码,没有指定 webpackChunkName,每个组件打包成一个js文件。
const ImportFuncDemo1 = () => import('../components/ImportFuncDemo1')
const ImportFuncDemo2 = () => import('../components/ImportFuncDemo2')

// 下面2行代码,指定了相同的 webpackChunkName,会合并打包成一个js文件。
const ImportFuncDemo1 = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '../components/ImportFuncDemo1')
const ImportFuncDemo2 = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '../components/ImportFuncDemo2')

export default new Router({
    routes: [
        {
            path: '/importfuncdemo1',
            name: 'ImportFuncDemo1',
            component: ImportFuncDemo1
        },
        {
            path: '/importfuncdemo2',
            name: 'ImportFuncDemo2',
            component: ImportFuncDemo2
        }
    ]
})

方法三:webpack提供的resolve + require.ensure()

vue-router 配置路由,使用 webpack 的 require.ensure 技术,也可以实现按需加载。

这种情况下,多个路由指定相同的 chunkName,会合并打包成一个js文件。

  • 常规不按需加载的引入方式:
javascript
import home from '../../common/home.vue'
  • require.ensure() 引入方式:
javascript
{
    path: '/promisedemo',
    name: 'PromiseDemo',
    component: resolve => require.ensure([], () => resolve(require('../components/PromiseDemo')), 'demo')
},
{
    path: '/hello',
    name: 'Hello',
    component: resolve => require.ensure([], () => resolve(require('../components/Hello')), 'demo')
}
// 这两个路由最终会打包成一个demo.js文件

6-16

Vue 模板渲染的原理

Vue 中通过 v-on 或其语法糖 @ 指令来给元素绑定事件并且提供了事件修饰符,基本流程是进行模板编译生成 AST(抽象语法树),生成 render 函数后并执行得到VNode,VNode 生成真实 DOM 节点或者组件时候使用 addEventListener 方法进行事件绑定。

6-15

Vue 插槽 slot 是什么?作用?原理?

slot 又名插槽,是 Vue 的内容分发机制,组件内部的模板引擎使用slot 元素作为承载分发内容的出口。插槽 slot 是子组件的一个模板标签元素,而这一个标签元素是否显示,以及怎么显示是由父组件决定的。

slot 又分三类:默认插槽,具名插槽和作用域插槽

  • 默认插槽:又名匿名插槽,当 slot 没有指定 name 属性值的时候一个默认显示插槽,一个组件内只能有一个匿名插槽。
  • 具名插槽:带有具体名字的插槽,也就是带有 name 属性的 slot,一个组件可以出现多个具名插槽。
  • 作用域插槽 slot-scope:默认插槽、具名插槽的一个变体,可以是匿名插槽,也可以是具名插槽,该插槽的不同点是在子组件渲染作用域插槽时,可以将子组件内部的数据传递给父组件,让父组件根据子组件的传递过来的数据决定如何渲染该插槽。

使用步骤

子组件中定义插槽

父组件使用子组件时往插槽写入代码。

实现原理

当子组件vm实例化时,获取到父组件传入的 slot 标签的内容,存放在vm.$slot 中,默认插槽为 vm.$slot.default,具名插槽为 vm.$slot.xxx,xxx 为插槽名,当组件执行渲染函数时候,遇到 slot 标签,使用 $slot 中的内容进行替换,此时可以为插槽传递数据,若存在数据,则可称该插槽为作用域插槽。

6-14

Vue-Router中使用active-class

方法一:直接在路由JS文件中配置linkActiveClass

javascript
export default new Router({
  linkActiveClass: 'active'
})
vue
<router-link to="/home" class="menu-home" active-class="active">首页</router-link>

如果使用第二种方法添加 active-class,跳转到my页面后,两个router-link始终都会有选中样式,代码如下:

vue
<div class="menu-btn">
  <router-link to="/" class="menu-home" active-class="active">
    首页
  </router-link>
</div>
<div class="menu-btn">
  <router-link to="/my" class="menu-my" active-class="active">
    我的
  </router-link>
</div>

原因:可能是因为 to="/" 引起的,active-class 选择样式时根据路由中的路径去匹配,然后显示。例如在my页面中,路由为 localhost:8080/#/my,那么to="/" 和 to="/my" 都可以匹配到,所以都会激活选中样式。

解决方案:要解决问题也有两种方式,都是通过加入一个 exact 属性

  • 直接在路由js文件中配置 linkExactActiveClass
javascript
export default new Router({
  linkExactActiveClass: 'active',
})
  • <router-link> 中写入 exact
vue
<router-link to="/" class="menu-home" active-class="active" exact>首页</router-link>
  • 在路由中加入重定向
vue
<router-link to="/home" class="menu-home" active-class="active" exact>首页</router-link>
// 路由js中加入重定向
{
  path: '/',
  redirect: '/home'
}

6-13

Vuex的属性及其作用

Vuex 属性有五种,分别是 State、 Getter、Mutation、Action、 Module

State

Vuex 就是一个仓库,仓库里面放了很多对象。其中 state 就是数据源存放地,对应于一般 Vue 对象里面的 data。

state 里面存放的数据是响应式的,Vue 组件从 store 中读取数据,若是store 中的数据发生改变,依赖这个数据的组件也会发生更新。

可以通过 mapState 把全局 state 和 getters 映射到当前组件的 computed 计算属性中使用。

Getter

getters 可以对 State 进行计算操作,它就是 Store 的计算属性。

虽然在组件内也可以做计算属性,但是 getters 可以在多组件之间复用,如果一个状态只在一个组件内使用,可以不用 getters。

Mutation

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。

Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数。

Mutation 就是提供存储设置 state 数据的方法。

Action

Action 类似于 mutation。

不同在于:Action 提交的是 mutation,而不是直接变更状态;Action 可以包含任意异步操作。

Module

当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

为了解决这个问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块 module ——从上至下进行同样方式的分割。

6-12

Vuex是什么?使用场景?

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

使用场景:单页应用中,组件之间的状态,音乐播放、登录状态、加入购物车等。

6-11

JS脚本加载问题

正常加载模式

在这种情况下JS会阻塞浏览器,浏览器必须等待前面所有的JS加载和执行完毕才能去做其他事情。

javascript
<script src="index.js"></script>

async(异步)模式

async模式下,JS不会阻塞浏览器做任何其他的事情。它的加载是异步的,当它加载结束,JS脚本会立即执行。

javascript
<script async src="index.js"></script>

defer(延缓)模式

defer模式下,JS的加载是异步的,执行是被推迟的。等整个文档解析完成、DOMContentLoaded事件即将被触发时,被标记了defer的JS文件才会开始依次执行。

javascript
<script defer src="index.js"></script>

从应用的角度来说,一般我们的脚本与DOM元素和其他脚本之间的依赖关系不强时,我们会选用async;当脚本依赖于DOM元素和其他脚本的执行结果时,我们会选用defer

6-10

AOT 编译模式和 JIT 编译模式异同?

相较于传统的 JIT 编译模式,AOT 避免了 JIT 预热等各方面的开销,可以提高 JAVA 程序的启动速度并减少占用, 还由于 AOT 编译后的代码不容易被反编译或修改,能增强 JAVA 程序的安全性。

AOT 的主要优势在于启动时间、内存占用和打包体积。JIT 的主要优势在于具备更高的极限处理能力,可以降低请求的最大延迟。

但是由于 AOT 静态编译的特点,它无法支持 JAVA 的一些动态特性,如反射、动态代理等。

6-9

什么是 AOT 编译模式?

AOT 编译模式(Ahead-of-Time Compilation)自 JDK9 引入,它会在程序被执行前就将其编译成机器码,属于静态编译, C、RUST、c++ 等语言都是静态编译。

6-8

为什么说 JAVA 语言解释与编译并存?

一般来讲,高级语言的执行方式有两种:解释执行和编译执行。

  • 编译型:编译型语言会通过编译器将源代码一次性翻译成可被该平台执行的机器码。一般情况下,编译语言的执行速度比较快,开发效率比较低。常见的编译性语言有 C、C++、Go、Rust 等等。
  • 解释型:解释型语言会通过解释器一句一句的将代码解释(interpret)为机器代码后再执行。解释型语言开发效率比较快,执行速度比较慢。常见的解释性语言有 Python、JavaScript、PHP 等等。

Java 语言既具有编译型语言的特征,也具有解释型语言的特征。因为 Java 程序要经过先编译,后解释两个步骤,由 Java 编写的程序需要先经过编译步骤,生成字节码(.class 文件),这种字节码必须由 Java 解释器来解释执行。

6-7

JAVA 采用字节码的好处?

在 JAVA 中,字节码表示 JVM可以理解的代码,拓展名为 .class。它不面向任何特定的处理器,只面向虚拟机。 JAVA 语言通过字节码的方式在一定程度上解决了传统解释型语言效率低的问题,同时又保留了解释型语言可移植的特点。 所以 JAVA 程序运行时相对较为高效。使用字节码还有跨平台等好处。

6-6

什么是僵尸进程?

一个子进程的进程描述符在子进程退出时不会释放,只有当父进程通过 wait() 或 waitpid() 获取了子进程信息后才会释放。如果子进程退出,而父进程并没有调用 wait() 或 waitpid(),那么子进程的进程描述符仍然保存在系统中,这种进程称之为僵尸进程。僵尸进程通过 ps 命令显示出来的状态为 Z(zombie)。系统所能使用的进程号是有限的,如果产生大量僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程。要消灭系统中大量的僵尸进程,只需要将其父进程杀死,此时僵尸进程就会变成孤儿进程,从而被 init 所收养,这样 init 就会释放所有的僵尸进程所占有的资源,从而结束僵尸进程。

6-5

什么是孤儿进程?

如果父进程退出,但是它的一个或多个子进程还在运行,那么这些子进程将成为孤儿进程。 孤儿进程会被 init 进程收养(接管),并将它们放入一个进程组中,并由 init 进程对它们完成状态收集工作。 由于孤儿进程会被 init 进程收养,所以孤儿进程不会对系统造成危害。

6-4

什么是 CSRF 攻击?

CSRF (Cross-site request forgery),跨站请求伪造,又称为 one-click attack,是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。

  1. 使用 JSON API。当进行 CSRF 攻击时,请求体通过 <form> 构建,请求头为 application/www-form-urlencoded。它难以发送 JSON 数据被服务器所理解。
  2. CSRF Token。生成一个随机的 token,切勿放在 cookie 中,每次请求手动携带该 token 进行校验。
  3. SameSite Cookie。设置为 Lax 或者 Strict,禁止发送第三方 Cookie。

6-3

简述 http 的缓存机制

缓存过程

浏览器与服务器通信的方式为应答模式,即是:浏览器发起 HTTP 请求 – 服务器响应该请求。那么浏览器第一次向服务器发起该请求后拿到请求结果,会根据响应报文中HTTP头的缓存标识,决定是否缓存结果,是则将请求结果和缓存标识存入浏览器缓存中。

img

Http 缓存分为以下两种,两者都是通过 HTTP 响应头控制缓存

  1. 强制缓存
  2. 协商缓存

强制缓存

强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程,强制缓存的情况主要有三种(暂不分析协商缓存过程);

1、不存在该缓存结果和缓存标识,强制缓存失效,则直接向服务器发起请求(跟第一次发起请求一致)

img

2、存在该缓存结果和缓存标识,但该结果已失效,强制缓存失效,则使用协商缓存(暂不分析)

img

3、存在该缓存结果和缓存标识,且该结果尚未失效,强制缓存生效,直接返回该结果

img

与之相关的 Response Headers 有以下几个:

Expires

Expires是HTTP/1.0控制网页缓存的字段,其值为服务器返回该请求结果缓存的到期时间,即再次发起该请求时,如果客户端的时间小于Expires的值时,直接使用缓存结果。

Expires是HTTP/1.0的字段,但是现在浏览器默认使用的是HTTP/1.1,那么在HTTP/1.1中网页缓存还是否由Expires控制?

到了HTTP/1.1,Expire已经被Cache-Control替代,原因在于Expires控制缓存的原理是使用客户端的时间与服务端返回的时间做对比,那么如果客户端与服务端的时间因为某些原因(例如时区不同;客户端和服务端有一方的时间不准确)发生误差,那么强制缓存则会直接失效,这样的话强制缓存的存在则毫无意义,那么Cache-Control又是如何控制的呢?

Cache-Control

在HTTP/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存,主要取值为:

  • public:所有内容都将被缓存(客户端和代理服务器都可缓存)
  • private:所有内容只有客户端可以缓存,Cache-Control的默认取值
  • no-cache:客户端缓存内容,但是是否使用缓存则需要经过协商缓存来验证决定
  • no-store:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存
  • max-age=xxx (xxx is numeric):缓存内容将在xxx秒后失效

HTTP响应报文中expires的时间值,是一个绝对值。

HTTP响应报文中Cache-Control为max-age=600,是相对值。

由于Cache-Control的优先级比expires,那么直接根据Cache-Control的值进行缓存,意思就是说在600秒内再次发起该请求,则会直接使用缓存结果,强制缓存生效。

协商缓存

协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:

1、协商缓存生效,返回304

img

2、协商缓存失效,返回200和请求结果结果

img

与之相关的 Request/Response Headers 有以下几个

Last-Modified / If-Modified-Since

Last-Modified是服务器响应请求时,返回该资源文件在服务器最后被修改的时间;

If-Modified-Since则是客户端再次发起该请求时,携带上次请求返回的Last-Modified值,通过此字段值告诉服务器该资源上次请求返回的最后被修改时间。服务器收到该请求,发现请求头含有If-Modified-Since字段,则会根据If-Modified-Since的字段值与该资源在服务器的最后被修改时间做对比,若服务器的资源最后被修改时间大于If-Modified-Since的字段值,则重新返回资源,状态码为200;否则则返回304,代表资源无更新,可继续使用缓存文件。

Etag / If-None-Match

Etag是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成);

If-None-Match是客户端再次发起该请求时,携带上次请求返回的唯一标识Etag值,通过此字段值告诉服务器该资源上次请求返回的唯一标识值。服务器收到该请求后,发现该请求头中含有If-None-Match,则会根据If-None-Match的字段值与该资源在服务器的Etag值做对比,一致则返回304,代表资源无更新,继续使用缓存文件;不一致则重新返回资源文件,状态码为200

总结

强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,重新获取请求结果,再存入浏览器缓存中;生效则返回304,继续使用缓存,主要过程如下:

img

6-2

http 常见的状态码

  • 1XX 表示消息
  • 2XX 表示成功
  • 3XX 表示重定向
  • 4XX 表示客户端错误
  • 5XX 表示服务端错误

200:最喜欢见到的状态码,表示请求成功

301:永久重定向

302:临时重定向

304:自上次请求,未修改的文件

http 状态码中 301,302和307有什么区别

  • 301,Moved Permanently。永久重定向,该操作比较危险,需要谨慎操作:如果设置了301,但是一段时间后又想取消,但是浏览器中已经有了缓存,还是会重定向。
  • 302,Found。临时重定向,但是会在重定向的时候改变 method: 把 POST 改成 GET,于是有了 307
  • 307,Temporary Redirect。临时重定向,在重定向时不会改变 method

400:错误的请求

401:未被授权,需要身份验证,例如token信息等等

403:请求被拒绝

404:资源缺失,接口不存在或请求的文件不存在等等

405:请求方法不对,需要POST这条资源而你却使用了GET

500:服务器端的未知错误

502:网关错误

503:服务暂时无法使用

504:网关超时,上游应用层迟迟未响应

6-1

浏览器中如何实现剪切板复制内容的功能?

方法1:第三方库 clipboard-copy(opens in a new tab)

使用 Clipboard API 进行实现是目前最为推荐的方式

javascript
navigator.clipboard.writeText(text);

而对于一些不支持 Clipboard API 的浏览器,使用以下 API 进行复制

方法2:选中: Selection API/Range API

Selection API

Selection 对象表示用户选择的文本范围或插入符号的当前位置。它代表页面中的文本选区,可能横跨多个元素。文本选区由用户拖拽鼠标经过文字而产生。要获取用于检查或修改的 Selection 对象,请调用 window.getSelection()

Selection 对象所对应的是用户所选择的 ranges(区域),俗称拖蓝。默认情况下,该函数只针对一个区域。

方法

getRangeAt:返回选区包含的指定区域(Range)的引用

collapse:将当前的选区折叠为一个点。

extend:将选区的焦点移动到一个特定的位置。

modify:修改当前的选区。

collapseToStart:将当前的选区折叠到起始点。

collapseToEnd:将当前的选区折叠到最末尾的一个点。

selectAllChildren:将某一指定节点的子节点框入选区。

addRange:一个区域(Range)对象将被加入选区。

removeRange:从选区中移除一个区域。

removeAllRanges:将所有的区域都从选区中移除。

deleteFromDocument:从页面中删除选区中的内容。

selectionLanguageChange

当键盘的朝向发生改变后修改指针的 Bidi 优先级。

toString:返回当前选区的纯文本内容。

containsNode:判断某一个 Node 是否为当前选区的一部分。

选中

javascript
const selection = window.getSelection();
const range = document.createRange();
 
// RangeAPI: 制造区域
range.selectNodeContents(element);
 
// Selection: 选中区域
selection.addRange(range);
 
selectedText = selection.toString();

取消选中

javascript
window.getSelection().removeAllRanges();

方法3:复制execCommand

js
document.execCommand("copy");