-- 创造无限可能

场景:

在前端业务的开发中,我们可能会封装很多公共组件。大部分时候因为忙于业务开发,并不会为这些公共组件进行文档编写、测试。在进行复用的时候,需要在项目中查看源码去查看该组件的作用、参数和事件。这种组件开发的方式在前端项目规模不大的时候还能接受,但当项目到达一定规模后,一般会存在以下问题:

  • 重复造轮子:因为没有统一的组件展示,其他开发者会不清楚组件已经在项目中实现,出现重复造轮子的现象。
  • 组件通用性不强:组件和项目逻辑强耦合,导致重复利用率不高。
  • 不知道如何使用:没有组件文档的存在,其他开发者需要去查看源码弄明白组件有哪些 event 和 props。增加了组件的使用难度

    storybook/vue

场景

获取当前年份、月份

实现

let date = new Date()
let currentYear = date.getFullYear() //获取完整的年份(4位)
let currentMonth = date.getMonth() + 1 //获取当前月份(1-12,1代表1月)
currentMonth = currentMonth >= 10 ? currentMonth : "0" + currentMonth

场景

java springboot 项目允许跨域访问

解决方案

package com.test.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@Configuration
public class CorsConfig {
    @Bean
    public CorsFilter corsFilter() {
        System.out.println("=======================允许跨域访问==============");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration corsConfiguration = new CorsConfiguration();

        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", corsConfiguration);

        return new CorsFilter(source);
    }

}

问题

使用SpringBoot开发的时候,录入数据里面包含了事件字段,可能会出现这样的问题,直接打开数据表查看事件,会发现数据表中存储的事件会比录入的时间少8个小时,

问题分析

少八个时区很容易想到,我们处于东八区,java存储的时间默认是以0时区为准,默认情况下,会自动转换成0时区的时间

解决办法

方法一:
在实体类中,标注时间的时区timezone

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")

该方法必须每个字段都设置,很不方便
方法二
在数据库配置中设置时区

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/数据库名称?serverTimezone=Asia/Shanghai

推荐使用

场景

大数字显示的时候,为了方便阅读,需要对数字用千分号分割

知识点:Number.toLocaleString方法

格式化数字
1、使用各国语言显示数字
2、千分号格式化数字
3、添加货币符号
4、添加百分号

解决方案

var num = 12345678
num.toLocaleString() // "12,345,678"

其他用途

  1. 添加货币符号

    var num = 1234
    num.toLocaleString('zh',{style:'currency' , currency:'CNY' }) // "¥1,234"
    
  2. 添加百分号

    var num = 1234
    num.toLocaleString('zh',{style:'percent'}) // "¥1,234"
    

练习1: 编写一个函数来查找字符串数组中的最长公共前缀。

思路:
1.取第一个数组的第一个元素与后面的元素作比较,找出公共的前缀
2.将该前缀作为第一个元素,继续与后面的元素作比较,找出公共的前缀
遍历字符串,找出相同的前缀
代码:
var longestCommonPrefix = function(strs) {
// 判断字符串是否为空
if(strs == null || strs.length===0){
return “”
}
let result = strs[0];
// 从数组的二个元素开始遍历
for(let i = 1;i < strs.length;i++){
let len = Math.min(result.length, strs[i].length);
let index = 0;
while(index < len && result.charAt(index) == strs[i].charAt(index)){
index++;
}
if(index == 0){
return “”
}
result = result.substr(0,index);
}
return result;
};

练习2: 编写一个函数来查找字符串数组中的最长公共前缀。

解题思路:使用栈和遍历
栈:后进先出
示例代码:
// 有效的括号:括号要左右配对顺序相同,相当于栈

// 字符串长度必须是偶数
// 1.遍历字符串
// 2.遇到左括号依次入栈,根据先进后出,栈顶即为最里面的左括号
// 3.遇到右括号,判断此时栈顶元素是否和Map里右括号对应的值相同,且栈不为空
// 4.若栈顶元素与右括号对应的值不匹配,则返回false
// 5.否则出栈
// 6.最后若栈为空,则是有效字符串,返回true
var isValid = function(s) {
const len = s.length;
if(len % 2 !=0){
return false;
}
const pairs =new Map([
[‘)’,’(‘],
[‘}’,’{‘],
[‘]’,’[‘],
])
let stk = [];
for(let ch of s){
if(pairs.has(ch)){
if(!stk.length || stk[stk.length-1] != pairs.get(ch)){
return false;
}
stk.pop();
}else{
stk.push(ch);
}
}
return !stk.length;
};

练习3:删除有序数组中的重复项。

解题思路:
1.使用双指针法的快慢指针,定义slow和fast做为指针。
2.初始化指针slow指向数组的起始位置(nums[0]),指针fast指向指针slow的后一个位置(nums[1])。
3.指针fast不断向后移动,将指针fast指向的元素与指针slow指向的元素进行比较。
代码:
var removeDuplicates = function(nums) {
if(nums.length == 0){
return 0;
}
// 初始化时指针slow指向数组的起始位置(nums[0]),指针fast指向指针slow的后一个位置(nums[1])。
let slow = 0,fast = 1;
while(fast<nums.length){
if(nums[fast]!=nums[slow]){
slow = slow + 1;
nums[slow] = nums[slow];
}
fast = fast + 1;
}
return slow + 1;
};

练习4:移除元素。

解题思路:
1.使用双指针法的快慢指针,定义slow和fast做为指针。
2.快指针:寻找新数组元素,新数组就是不含有目标元素的数组
慢指针:指向更新新数组下标的位置
代码:
var removeElement = function(nums, val) {
if(nums.length == 0){
return 0;
}
var slow = 0;
var fast;
for(fast = 0;fast<nums.length; fast++){
if(nums[fast] == val){
continue;
}else{
nums[slow] = nums[fast];
slow++;
}
}
return slow;
};

练习4:搜索插入位置。

解题思路:
1.利用二分法查找左边边界。
代码:
var searchInsert = function(nums, target) {
let left = 0;
right = nums.length - 1;
ans = nums.length;
while(left<=right){
let mid = parseInt((right + left)/2);
if(target <= nums[mid]){
ans = mid;
right = mid - 1;
}else{
left = mid + 1;
}
}
return ans;

};

练习1: 编写一个函数来查找字符串数组中的最长公共前缀。

思路:
1.取第一个数组的第一个元素与后面的元素作比较,找出公共的前缀
2.将该前缀作为第一个元素,继续与后面的元素作比较,找出公共的前缀
遍历字符串,找出相同的前缀
代码:
var longestCommonPrefix = function(strs) {
// 判断字符串是否为空
if(strs == null || strs.length===0){
return “”
}
let result = strs[0];
// 从数组的二个元素开始遍历
for(let i = 1;i < strs.length;i++){
let len = Math.min(result.length, strs[i].length);
let index = 0;
while(index < len && result.charAt(index) == strs[i].charAt(index)){
index++;
}
if(index == 0){
return “”
}
result = result.substr(0,index);
}
return result;
};

练习2: 编写一个函数来查找字符串数组中的最长公共前缀。

解题思路:使用栈和遍历
栈:后进先出
示例代码:
// 有效的括号:括号要左右配对顺序相同,相当于栈

// 字符串长度必须是偶数
// 1.遍历字符串
// 2.遇到左括号依次入栈,根据先进后出,栈顶即为最里面的左括号
// 3.遇到右括号,判断此时栈顶元素是否和Map里右括号对应的值相同,且栈不为空
// 4.若栈顶元素与右括号对应的值不匹配,则返回false
// 5.否则出栈
// 6.最后若栈为空,则是有效字符串,返回true
var isValid = function(s) {
const len = s.length;
if(len % 2 !=0){
return false;
}
const pairs =new Map([
[‘)’,’(‘],
[‘}’,’{‘],
[‘]’,’[‘],
])
let stk = [];
for(let ch of s){
if(pairs.has(ch)){
if(!stk.length || stk[stk.length-1] != pairs.get(ch)){
return false;
}
stk.pop();
}else{
stk.push(ch);
}
}
return !stk.length;
};

练习3:删除有序数组中的重复项。

解题思路:
1.使用双指针法的快慢指针,定义slow和fast做为指针。
2.初始化指针slow指向数组的起始位置(nums[0]),指针fast指向指针slow的后一个位置(nums[1])。
3.指针fast不断向后移动,将指针fast指向的元素与指针slow指向的元素进行比较。
代码:
var removeDuplicates = function(nums) {
if(nums.length == 0){
return 0;
}
// 初始化时指针slow指向数组的起始位置(nums[0]),指针fast指向指针slow的后一个位置(nums[1])。
let slow = 0,fast = 1;
while(fast<nums.length){
if(nums[fast]!=nums[slow]){
slow = slow + 1;
nums[slow] = nums[slow];
}
fast = fast + 1;
}
return slow + 1;
};

练习4:移除元素。

解题思路:
1.使用双指针法的快慢指针,定义slow和fast做为指针。
2.快指针:寻找新数组元素,新数组就是不含有目标元素的数组
慢指针:指向更新新数组下标的位置
代码:
var removeElement = function(nums, val) {
if(nums.length == 0){
return 0;
}
var slow = 0;
var fast;
for(fast = 0;fast<nums.length; fast++){
if(nums[fast] == val){
continue;
}else{
nums[slow] = nums[fast];
slow++;
}
}
return slow;
};

练习4:搜索插入位置。

解题思路:
1.利用二分法查找左边边界。
代码:
var searchInsert = function(nums, target) {
let left = 0;
right = nums.length - 1;
ans = nums.length;
while(left<=right){
let mid = parseInt((right + left)/2);
if(target <= nums[mid]){
ans = mid;
right = mid - 1;
}else{
left = mid + 1;
}
}
return ans;

};

练习1: 编写一个函数来查找字符串数组中的最长公共前缀。

思路:
1.取第一个数组的第一个元素与后面的元素作比较,找出公共的前缀
2.将该前缀作为第一个元素,继续与后面的元素作比较,找出公共的前缀
遍历字符串,找出相同的前缀
代码:
var longestCommonPrefix = function(strs) {
// 判断字符串是否为空
if(strs == null || strs.length===0){
return “”
}
let result = strs[0];
// 从数组的二个元素开始遍历
for(let i = 1;i < strs.length;i++){
let len = Math.min(result.length, strs[i].length);
let index = 0;
while(index < len && result.charAt(index) == strs[i].charAt(index)){
index++;
}
if(index == 0){
return “”
}
result = result.substr(0,index);
}
return result;
};

练习2: 编写一个函数来查找字符串数组中的最长公共前缀。

解题思路:使用栈和遍历
栈:后进先出
示例代码:
// 有效的括号:括号要左右配对顺序相同,相当于栈

// 字符串长度必须是偶数
// 1.遍历字符串
// 2.遇到左括号依次入栈,根据先进后出,栈顶即为最里面的左括号
// 3.遇到右括号,判断此时栈顶元素是否和Map里右括号对应的值相同,且栈不为空
// 4.若栈顶元素与右括号对应的值不匹配,则返回false
// 5.否则出栈
// 6.最后若栈为空,则是有效字符串,返回true
var isValid = function(s) {
const len = s.length;
if(len % 2 !=0){
return false;
}
const pairs =new Map([
[‘)’,’(‘],
[‘}’,’{‘],
[‘]’,’[‘],
])
let stk = [];
for(let ch of s){
if(pairs.has(ch)){
if(!stk.length || stk[stk.length-1] != pairs.get(ch)){
return false;
}
stk.pop();
}else{
stk.push(ch);
}
}
return !stk.length;
};

练习3:删除有序数组中的重复项。

解题思路:
1.使用双指针法的快慢指针,定义slow和fast做为指针。
2.初始化指针slow指向数组的起始位置(nums[0]),指针fast指向指针slow的后一个位置(nums[1])。
3.指针fast不断向后移动,将指针fast指向的元素与指针slow指向的元素进行比较。
代码:
var removeDuplicates = function(nums) {
if(nums.length == 0){
return 0;
}
// 初始化时指针slow指向数组的起始位置(nums[0]),指针fast指向指针slow的后一个位置(nums[1])。
let slow = 0,fast = 1;
while(fast<nums.length){
if(nums[fast]!=nums[slow]){
slow = slow + 1;
nums[slow] = nums[slow];
}
fast = fast + 1;
}
return slow + 1;
};

练习4:移除元素。

解题思路:
1.使用双指针法的快慢指针,定义slow和fast做为指针。
2.快指针:寻找新数组元素,新数组就是不含有目标元素的数组
慢指针:指向更新新数组下标的位置
代码:
var removeElement = function(nums, val) {
if(nums.length == 0){
return 0;
}
var slow = 0;
var fast;
for(fast = 0;fast<nums.length; fast++){
if(nums[fast] == val){
continue;
}else{
nums[slow] = nums[fast];
slow++;
}
}
return slow;
};

练习4:搜索插入位置。

解题思路:
1.利用二分法查找左边边界。
代码:
var searchInsert = function(nums, target) {
let left = 0;
right = nums.length - 1;
ans = nums.length;
while(left<=right){
let mid = parseInt((right + left)/2);
if(target <= nums[mid]){
ans = mid;
right = mid - 1;
}else{
left = mid + 1;
}
}
return ans;

};

练习1: 两个数之和,同一个元素在答案里不能重复出现

代码:
let num1 = 0;
let num2 = 0;
for(let i = 0;i<nums.length;i++){
num1= nums[i]
for(let j = i+1;j<nums.length;j++){
num2= nums[j]
if(num1 + num2 == target){
return [i,j]
}
}
}
总结:使用for循环,循环两次,第二次循环把i改为j,j=i+1

练习2: 回文数

解题思路:既然回文数是数字反转以后还和之前的数字一样,那么可以判断反转过的数字是否和之前的一样,
是的话就是回文数,不是的话就不是回文数先把数字转化为字符串,解析成数组反转过以后再转化字符串,
然后把字符串在重新转化为数字。
代码:
var isPalindrome = function(x) {
// 数字转字符串
if(x<0){
return false
}
// 反转字符串
var x2=parseInt(x.toString().split(‘’).reverse().join(‘’))
// 判断反转后的字符串是否和原始字符串相等
return x2 === x ? true : false
};
isPalindrome(121)

练习3: 罗马数字转整数

解题思路:
1.左边的罗马数字 > 右边的罗马数字时,罗马数 == 左边罗马数字对应的阿拉伯数字 + 右边罗马数字对应的阿拉伯数字
2.左边的罗马数字 < 右边的罗马数字时,罗马数 == 左边罗马数字对应的阿拉伯数字的反数即负数 + 右边罗马数字对应的阿拉伯数字
代码:
var romanToInt = function(s) {
let obj={
I:1,
V:5,
X:10,
L:50,
C:100,
D:500,
M:1000,
}
let num=0
for(let i = 0; i < s.length; i++){
if(obj[s[i]] < obj[s[i+1]]){
num -= obj[s[i]]
}else{
num += obj[s[i]]
}
}
return num
};
romanToInt (‘III’)
赋值运算符:-=、+=
x+=y,相当于x=x+y
x-=y,相当于x=x-y

练习1: 两个数之和,同一个元素在答案里不能重复出现

代码:
let num1 = 0;
let num2 = 0;
for(let i = 0;i<nums.length;i++){
num1= nums[i]
for(let j = i+1;j<nums.length;j++){
num2= nums[j]
if(num1 + num2 == target){
return [i,j]
}
}
}
总结:使用for循环,循环两次,第二次循环把i改为j,j=i+1

练习2: 回文数

解题思路:既然回文数是数字反转以后还和之前的数字一样,那么可以判断反转过的数字是否和之前的一样,
是的话就是回文数,不是的话就不是回文数先把数字转化为字符串,解析成数组反转过以后再转化字符串,
然后把字符串在重新转化为数字。
代码:
var isPalindrome = function(x) {
// 数字转字符串
if(x<0){
return false
}
// 反转字符串
var x2=parseInt(x.toString().split(‘’).reverse().join(‘’))
// 判断反转后的字符串是否和原始字符串相等
return x2 === x ? true : false
};
isPalindrome(121)

练习3: 罗马数字转整数

解题思路:
1.左边的罗马数字 > 右边的罗马数字时,罗马数 == 左边罗马数字对应的阿拉伯数字 + 右边罗马数字对应的阿拉伯数字
2.左边的罗马数字 < 右边的罗马数字时,罗马数 == 左边罗马数字对应的阿拉伯数字的反数即负数 + 右边罗马数字对应的阿拉伯数字
代码:
var romanToInt = function(s) {
let obj={
I:1,
V:5,
X:10,
L:50,
C:100,
D:500,
M:1000,
}
let num=0
for(let i = 0; i < s.length; i++){
if(obj[s[i]] < obj[s[i+1]]){
num -= obj[s[i]]
}else{
num += obj[s[i]]
}
}
return num
};
romanToInt (‘III’)
赋值运算符:-=、+=
x+=y,相当于x=x+y
x-=y,相当于x=x-y

练习1: 两个数之和,同一个元素在答案里不能重复出现

代码:
let num1 = 0;
let num2 = 0;
for(let i = 0;i<nums.length;i++){
num1= nums[i]
for(let j = i+1;j<nums.length;j++){
num2= nums[j]
if(num1 + num2 == target){
return [i,j]
}
}
}
总结:使用for循环,循环两次,第二次循环把i改为j,j=i+1

练习2: 回文数

解题思路:既然回文数是数字反转以后还和之前的数字一样,那么可以判断反转过的数字是否和之前的一样,
是的话就是回文数,不是的话就不是回文数先把数字转化为字符串,解析成数组反转过以后再转化字符串,
然后把字符串在重新转化为数字。
代码:
var isPalindrome = function(x) {
// 数字转字符串
if(x<0){
return false
}
// 反转字符串
var x2=parseInt(x.toString().split(‘’).reverse().join(‘’))
// 判断反转后的字符串是否和原始字符串相等
return x2 === x ? true : false
};
isPalindrome(121)

练习3: 罗马数字转整数

解题思路:
1.左边的罗马数字 > 右边的罗马数字时,罗马数 == 左边罗马数字对应的阿拉伯数字 + 右边罗马数字对应的阿拉伯数字
2.左边的罗马数字 < 右边的罗马数字时,罗马数 == 左边罗马数字对应的阿拉伯数字的反数即负数 + 右边罗马数字对应的阿拉伯数字
代码:
var romanToInt = function(s) {
let obj={
I:1,
V:5,
X:10,
L:50,
C:100,
D:500,
M:1000,
}
let num=0
for(let i = 0; i < s.length; i++){
if(obj[s[i]] < obj[s[i+1]]){
num -= obj[s[i]]
}else{
num += obj[s[i]]
}
}
return num
};
romanToInt (‘III’)

流程

  1. 定义一个变量,存储英文字符串
  2. 获取用户输入字符,和当前需要对比的字符匹配
  3. 修改已匹配的文字颜色

    js 代码

    定义一个变量,存储英文字符串
     var str="......"
    
    获取字符串排序
     var currentIndex=0
    
    让字符串为空
     let strShow=""
    
    循环
     for(let i = 0;i < str.length; i ++){
     strShow += "<span id='chat_" + i +"'>" + str[i] + "</span>"
     }
    
    显示字符串长度
     document.setElementById("content").innerHTML=strShow
    
    获取字符串长度
     document.setElementById("total").innerHTML=(str.length)
    
    用户输入为空
     var userInput=""
    
    获取用户输入
     document.onkeydown = function(envent){
         var e = event || windown.event;
         //获取用户输入字符
         let value = e.key
         //和当前的字符匹配是否相等
         userInput = userInput + str[currentIndex]
         //显示用户完整输入
         document.getElementById("userInput").innerHTMl=userInput
         //用户输入字符,字符串改变颜色
         document.getElementById("chat_" + currentIndex).classList.add('red')
         //自增,设置下一个需要匹配的索引
         currentIndex ++
     } 
    

场景

js向下取整

方法

Math.floor

案例

Math.floor(12.5); // 12

使用场景:元素大小不一,但需要设置元素的间距相同

解决方案

  • flex-wrap:规定flex容器是单行或者多行,同时横轴的方向决定了新行堆叠的方向。。
  • justify-content:用于弹性盒子元素的布局方式

    实际案例

    ul{
    display:flex;
    flex-wrap:wrap;
    justify-content:space-between;
    }
    li{
    width:30%;
    height:100px;
    }
    

场景

  1. 数字相减

    111.00-222.20
    // -111.19999999999999
    
  2. 数字相加

    1.10+2.20
    // 3.3000000000000003
    
  3. 数字相乘

    0.3*3
    // 0.8999999999999999
    
  4. 数字相除

    0.3/3
    // 0.09999999999999999
    

原因分析

JavaScript中的数字运算遵循IEEE 754标准的浮点数算法,这种算法使用二进制来表示实数,以及对实数进行加、减、乘、除等运算。然而,由于二进制不能准确地表示所有的十进制小数,因此,进行数字运算时可能会出现无限小数。

例如,0.1在二进制中是一个无限循环的小数,用64位浮点数表示时就会出现精度误差。如果对0.1和0.2进行加法运算,期望结果是0.3,但是实际结果可能是0.30000000000000004,这是由于二进制表示的精度问题造成的。

解决方案

  1. 先乘以10000转成整数计算后再除以10000,(这里的10000可以自己根据需要调整)
    例如:1.1+2.2

    Math.round(1.1 * 10000 + 2.2 * 10000)/10000
    // 3.3
    
  2. 使用字符串或数组等数据结构来模拟数学运算中的处理过程
    具体来说,当计算两个数字相加时,高精度计算库会将数字的每一位分别相加,并且在需要的时候进行进位操作;当计算乘法时,当前位的计算结果是将当前数字的每一位乘以另一个数对应位的数字后相加得到的。
    该方法比较复杂,别人已经写有类似的库,我们可以借鉴

以下是一些常用的第三方库:

  • BigNumber.js:这是一个功能强大的库,支持高精度整数、小数和有理数运算。它提供了类似于JavaScript内置数字类型的API,例如加、减、乘、除等运算,以及比较、取余、幂等运算等。

  • decimal.js:这是一个基于JavaScript的高精度计算库,支持任意精度的小数运算。它提供了诸如加、减、乘、除等基本运算,还支持一些高级运算,例如求正弦、余弦、幂运算等。

  • bignumber.js:这也是一个流行的高精度计算库,支持任意精度的整数和小数计算。它提供了相对较小、内存占用低的API,适用于较小的高精度计算场景。

场景

  1. 数字相减

    111.00-222.20
    // -111.19999999999999
    
  2. 数字相加

    1.10+2.20
    // 3.3000000000000003
    
  3. 数字相乘

    0.3*3
    // 0.8999999999999999
    
  4. 数字相除

    0.3/3
    // 0.09999999999999999
    

原因分析

JavaScript中的数字运算遵循IEEE 754标准的浮点数算法,这种算法使用二进制来表示实数,以及对实数进行加、减、乘、除等运算。然而,由于二进制不能准确地表示所有的十进制小数,因此,进行数字运算时可能会出现无限小数。

例如,0.1在二进制中是一个无限循环的小数,用64位浮点数表示时就会出现精度误差。如果对0.1和0.2进行加法运算,期望结果是0.3,但是实际结果可能是0.30000000000000004,这是由于二进制表示的精度问题造成的。

解决方案

  1. 先乘以10000转成整数计算后再除以10000,(这里的10000可以自己根据需要调整)
    例如:1.1+2.2

    Math.round(1.1 * 10000 + 2.2 * 10000)/10000
    // 3.3
    
  2. 使用字符串或数组等数据结构来模拟数学运算中的处理过程
    具体来说,当计算两个数字相加时,高精度计算库会将数字的每一位分别相加,并且在需要的时候进行进位操作;当计算乘法时,当前位的计算结果是将当前数字的每一位乘以另一个数对应位的数字后相加得到的。
    该方法比较复杂,别人已经写有类似的库,我们可以借鉴

以下是一些常用的第三方库:

  • BigNumber.js:这是一个功能强大的库,支持高精度整数、小数和有理数运算。它提供了类似于JavaScript内置数字类型的API,例如加、减、乘、除等运算,以及比较、取余、幂等运算等。

  • decimal.js:这是一个基于JavaScript的高精度计算库,支持任意精度的小数运算。它提供了诸如加、减、乘、除等基本运算,还支持一些高级运算,例如求正弦、余弦、幂运算等。

  • bignumber.js:这也是一个流行的高精度计算库,支持任意精度的整数和小数计算。它提供了相对较小、内存占用低的API,适用于较小的高精度计算场景。

场景

一些需要预约的场景,一般需要显示最近10天的时间给用户选择

方案

  1. 获取到今天的时间戳(毫秒),let today = (new Date()).getTime()
  2. 循环10次,每日增加一天的秒数,let nextDay = new Date(today + 86400000)
  3. 格式化日期

    实例

function getDateList(num=15){
    // 获取当前日期
    const currentDate = new Date();
    let list = []
    // 循环生成接下来的 num 天日期
    for (let i = 0; i < num; i++) {
      // 生成日期所需的时间戳
      const timestamp = currentDate.getTime() + i * 24 * 60 * 60 * 1000;

      // 将时间戳转换成日期格式
      const date = new Date(timestamp);

      // 输出日期
      let key = formatDate(date,'MM-dd')
      let value = formatDate(date,'yyyy-MM-dd')
      list.push({key,value})
    }
    return list
}
//格式化时间戳
function formatDate(date, format) {
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, '0');
  const day = String(date.getDate()).padStart(2, '0');
  const hour = String(date.getHours()).padStart(2, '0');
  const minute = String(date.getMinutes()).padStart(2, '0');
  const second = String(date.getSeconds()).padStart(2, '0');

  let result = format.replace(/yyyy/g, year);
  result = result.replace(/MM/g, month);
  result = result.replace(/dd/g, day);
  result = result.replace(/HH/g, hour);
  result = result.replace(/mm/g, minute);
  result = result.replace(/ss/g, second);

  return result;
}

使用场景

有时候获取到一个对象,需要把改对象拼接成url请求参数发起get请求

解决方案

function buildGetParam(paramObj){
    for (const key in paramObj) {
        if(paramObj[key] != ''){
            if(paramObj[key] instanceof String){
                str += key +"="+ paramObj[key] + "&"
                console.log(paramObj[key])
            }else if(paramObj[key] instanceof Array){
                paramObj[key].forEach(item => {
                    str += key +"[]="+ item + "&"
                })
            }
        }
    }
}

Javascript刷新页面的几种方法:
1 history.go(0)
2 location.reload()
3 location=location
4 location.assign(location)
5 document.execCommand(‘Refresh’)
6 window.navigate(location)
7 location.replace(location)
8 document.URL=location.href

为什么需要命名规范

  1. 为了以后的自己能理解和维护自己以前写的代码
  2. 为了方便理解和维护同一个团队里面别人写的代码
  3. 为了方便同一个团队里面别人理解和维护自己写的代码

怎么定制命名规范

  1. 不同公司有不同的约定,具体根据自己公司的情况自己
  2. 简单
  3. 语义化

命名规范约定什么

  1. 变量
  2. 常量
  3. 方法和函数
  4. 类的定义
  5. 注释

以下是一套javascript开发命名规范,可以根据自己需要调整

  1. 变量
    1.1 小驼峰命名法:第一个单词首字母小写,其他单词首字母大写,例如 firstName
    1.2 布尔类型变量应使用 is, has, can 等前缀,例如 isCompleted
    1.3 由字母、数字、下划线、$符号组成,不能以数字开头,以下是不好的例子123name,#abc
    1.4 变量名区分大小写
    1.5 不能使用保留的关键字、保留字、truefalsenull

  2. 常量名
    使用全大写字母,单词之间使用下划线分隔,例如 MAX_NUMBER

  3. 函数和方法
    3.1 小驼峰命名法:第一个单词首字母小写,其他单词首字母大写,例如 getName
    3.2 函数和方法都是表示对什么东西进行操作,建议第一个单词是动词,名词在后面,例如 getUserList


  4. 4.1 类名使用大驼峰命名法,每个单词第一个字母大写,例如 Person

  5. 变量名应该避免音译命名,应该使用英文单词或者常用缩写来进行命名。例如使用 load 而不是 lode。

  6. 缩写应该当获取远离语境并且广为人知。例如 URL 是广为人知的缩写。

  7. 避免使用单字母命名,除非是一个已经被广泛接受的惯例,例如 i 用于循环中所索引的变量命名。

JS获取屏幕高度

网页可见区域宽:document.body.clientWidth
网页可见区域高:document.body.clientHeight
网页可见区域宽:document.body.offsetWidth (包括边线和滚动条的宽)
网页可见区域高:document.body.offsetHeight(包括边线的宽)
网页正文全文宽:document.body.scrollWidth
网页正文全文高:document.body.scrollHeight
网页被卷去的高:document.body.scrollTop
网页被卷去的左:document.body.scrollLeft
网页正文部分上:window.screenTop
网页正文部分左:window.screenLeft
屏幕分辨率的高:window.screen.height
屏幕分辨率的宽:window.screen.width
屏幕可用工作区高度:window.screen.availHeight
屏幕可用工作区宽度:window.screen.availWidth
你的屏幕设置是 window.screen.colorDepth +" 位彩色"

使用场景

在二开项目的时候,原有的组件会修改页面的dom,为了监听该dom的变化

常用dom变更事件

DOMSubtreeModified: 在DOM结构发生任何变化的时候。这个事件在其他事件触发后都会触发;
DOMNodeInserted: 当一个节点作为子节点被插入到另一个节点中时触发;
DOMNodeRemoved: 在节点从其父节点中移除时触发;
DOMNodeInsertedIntoDocument: 在一个节点被直接插入文档或通过子树间接插入文档之后触发。这个事件在 DOMNodeInserted 之后触发;
DOMNodeRemovedFromDocument: 在一个节点被直接从文档移除或通过子树间接从文档移除之前触发。这个事件在 DOMNodeRemoved 之后触发;
DOMAttrModified: 节点属性被修改之后触发;
DOMCharacterDataModified: 在文本节点的值发生变化时触发;

网站开发js技巧:使用正则搜索字符串

var str = 'abcdef';
var result = str.search(/abc/i);// i:不区分大小写

方法一

 "日一二三四五六".charAt(new Date().getDay());

方法一

var arr = new Array("日", "一", "二", "三", "四", "五", "六");
var week = new Date().getDay();
var str = "今天是星期"+ arr[week];

1.箭头函数与function定义函数的写法:

//function
function fn(a, b){
    return a + b;
}
//箭头函数
var foo = (a, b)=>{ return a + b };

2.this的指向:

使用function定义的函数,this的指向随着调用环境的变化而变化的,而箭头函数中的this指向是固定不变的,一直指向的是定义函数的环境。

//使用function定义的函数
function foo(){
    console.log(this);
}
var obj = { aa: foo };
foo(); //Window
obj.aa() //obj { aa: foo }

使用function定义的函数中this指向是随着调用环境的变化而变化的

//使用箭头函数定义函数

var foo = () => { console.log(this) };
var obj = { aa:foo };
foo(); //Window
obj.aa(); //Window

明显使用箭头函数的时候,this的指向是没有发生变化的。

3.构造函数

//使用function方法定义构造函数
function Person(name, age){
    this.name = name;
    this.age = age;
}
var lenhart =  new Person(lenhart, 25);
console.log(lenhart); //{name: 'lenhart', age: 25}
//尝试使用箭头函数
var Person = (name, age) =>{
    this.name = name;
    this.age = age;
};
var lenhart = new Person('lenhart', 25); //Uncaught TypeError: Person is not a constructor

    function是可以定义构造函数的,而箭头函数是不行的。

4.变量提升

由于js的内存机制,function的级别最高,而用箭头函数定义函数的时候,需要var(let const定义的时候更不必说)关键词,而var所定义的变量不能得到变量提升,故箭头函数一定要定义于调用之前!

foo(); //123
function foo(){
    console.log('123');
}

arrowFn(); //Uncaught TypeError: arrowFn is not a function
var arrowFn = () => {
    console.log('456');
};

1.获取时间

// 获取指定时间
var time = new Date("2017-11-15 22:22:22");
// 获取当前时间
var time = (new Date()).getTime()

2.时间转时间戳

function timetrans(date){
    var date = new Date(date*1000);//如果date为13位不需要乘1000
    var Y = date.getFullYear() + '-';
    var M = (date.getMonth()+1 < 10 ? '0'+(date.getMonth()+1) : date.getMonth()+1) + '-';
    var D = (date.getDate() < 10 ? '0' + (date.getDate()) : date.getDate()) + ' ';
    var h = (date.getHours() < 10 ? '0' + date.getHours() : date.getHours()) + ':';
    var m = (date.getMinutes() <10 ? '0' + date.getMinutes() : date.getMinutes()) + ':';
    var s = (date.getSeconds() <10 ? '0' + date.getSeconds() : date.getSeconds());
    return Y+M+D+h+m+s;
}

方法: 数组元素交换位置

function swapArray(arr, index1, index2) {
   arr[index1] = arr.splice(index2, 1, arr[index1])[0];
    return arr;
}

方法:上移,将当前数组index索引与后面一个元素互换位置,向数组后面移动一位

function zIndexUp(arr,index,length){
   if(index+1 != length){
       swapArray(arr, index, index+1);
   }else{
      alert('已经处于置顶,无法上移');
  }
}
}

方法:下移 将当前数组index索引与前面一个元素互换位置,向数组前面移动一位

function zIndexDown(arr,index,length){
   if(index!= 0){
       swapArray(arr, index, index-1);
   }else{
      alert('已经处于置底,无法下移');
  }
}

方法:置顶,即将当前元素移到数组的最后一位

function zIndexTop(arr,index,length){
   if(index+1 != length){

     //首先判断当前元素需要上移几个位置,置底移动到数组的第一位
      var moveNum = length - 1 - index;

      //循环出需要一个一个上移的次数
       for (var i = 0; i<moveNum; i++) {

         swapArray(arr, index, index + 1);

          index++;

     }
   }else{
      alert('已经处于置顶');
  }
}

置底,即将当前元素移到数组的第一位

//
function zIndexBottom(arr,index,length){
   if(index!=0){

      //首先判断当前元素需要上移几个位置,置底移动到数组的第一位
      var moveNum = index - 0;

      //循环出需要一个一个上移的次数
       for (var i = 0; i<moveNum; i++) {

         swapArray(arr, index, index - 1);

          index--;

     }
   }else{
      alert('已经处于置底');
  }
}

参考:https://www.cnblogs.com/dearxinli/p/6802151.html

方法一:函数

var function_name = "test";
//判断是否是函数名
if(function_name && function_name in window){
window[function_name]("参数");
}

function test(paras) {
    alert(paras);
}

方法二:eval

var fun_name = "fun1";
eval(fun_name+"()");
function fun1(){
    alert("eval执行")
}
  1. 数字数组转化为字符串数组
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.map(String);  //结果: ['1', '2', '3', '4', '5', '6', '7', '8', '9']
  1. 字符串数组转化为数字数组
    var a = ['1', '2', '3', '4', '5', '6', '7', '8', '9']
    a.map(Number);  //结果:[1, 2, 3, 4, 5, 6, 7, 8, 9]
    

方法一:使用jQuery的inArray

$.inarray(value,array)

方法二:函数实现inArray

function inArray(value,arr) {
  var i = arr.length;
  while (i--) {
    if (arr[i] === value) {
      return true;
    }
  }
  return false;
}

方法三:给Array增加一个函数contains
contains:包含

Array.prototype.contains = function (value) {
  var i = this.length;
  while (i--) {
    if (this[i] === value) {
      return true;
    }
  }
  return false;
}

参考:https://www.jb51.net/article/81762.htm

数据拼接成字符串:array.join()

join:参加; 加入; 连接; 联合

let arr = ['a','b','c']
arr.join(); // abc
arr.join('-'); // a-b-c

js技巧:获取文件后缀名方法

方法一:按.号切割字符串,获取最有一个元素

getSuffixName(file_path = ''){
    if(!file_path){
        return '';
    }
    let arr = file_path.split('.');
    return arr.length > 1?arr[arr.length - 1]:'';
}

方法二:按.号切割字符串,获取最有一个元素

getSuffixName(file_path = ''){
    if(!file_path){
        return '';
    }
    return file_path.split('.').pop();
}

方法三:获取最后一个.号位置,截取该位置最后的字符串

getSuffixName(file_path = ''){
    if(!file_path){
        return '';
    }
    return file_path.substring(file_path.lastIndexOf('.') + 1);
}

前端问题:伪类first-child 有时候不起作用

<style>
article:first-child {
    color: red;//不生效
}​
</style>
<h1>logo</h1>
<article>article1</article>
<article>article2</article>
<article>article3</article>

改进:

<style>
article:first-child {
    color: red;//不生效
}​
</style>
<h1>logo</h1>
<div>
    <article>article1</article>
    <article>article2</article>
    <article>article3</article>
</div>

项目命名

全部采用小写方式, 以下划线分隔。
示例:my_project_name

目录

全部采用小写方式, 以下划线分隔。
有复数结构时,要采用复数命名法。
示例:scripts, styles, images

一、变量

命名方法: 小驼峰式命名法
命名规范:前缀为形容词 (函数前缀为动词, 以此来区分函数和变量)

  • 好的命名方式
    let maxCount = 10;
    let tableTitle = '标题';
    
  • 不好的命名方式
    let setConut = 10; //不要使用动词
    

二、常量

命名方法:名词全部大写
命名规范:使用大写字母和下划线来组合命名,下划线用来分割单词。

const MAX_COUNT = 10;
const URL = 'http://www.tooi.cn';

三、函数 & 方法

命名方法: 小驼峰式命名法
命名规范: 前缀应该为动词
常用动词约定

动词 含义
can 判断是否可执行某个动作
has 判断是否含义某个值
get 获取某个值
set 设置某个值
load 加载某些数据
// 是否可阅读
function canRead() {}
// 获取名称
function getName() {}

四、类 & 构造函数

命名方法:大写驼峰式命名法,首字母大写。
命名规范:前缀为名称。

class Persion {
  constructor(name) {
   ...
  }
}
let person = new Person('张三');

五、类的成员

类的成员包括:

  • 公共属性和方法: 跟变量和函数命名一样。
  • 私有属性和方法:前缀为下划线_, 后面跟公共属性和方法一样的命名方式。
class Person {
  // 私有属性 
  _name: string;
  constructor() { }

  // 公共方法
  getName() {
    return this._name;
  }
  // 公共方法
  setName(name) {
    this._name = name;
  }
}

六、注释规范

格式化插件推荐prettier

  1. 单行注释
// 设置标题
setTitle()
  1. 多行注释
    /*
    * 代码执行到这里后会调用setTitle()函数
    * setTitle():设置title的值
    */
    setTitle();
    

6.3 函数 & 方法注释

/**
 * 函数说明
 * @关键字
 **/

参考

https://www.cnblogs.com/Hsong/p/9016950.html
https://blog.csdn.net/Jiang216/article/details/121414732

一、变量

命名方法: 小驼峰式命名法
命名规范:前缀为形容词 (函数前缀为动词, 以此来区分函数和变量)

  • 好的命名方式
    let maxCount = 10;
    let tableTitle = '标题';
    
  • 不好的命名方式
    let setConut = 10; //不要使用动词
    

二、常量

命名方法:名词全部大写
命名规范:使用大写字母和下划线来组合命名,下划线用来分割单词。

const MAX_COUNT = 10;
const URL = 'http://www.tooi.cn';

三、函数 & 方法

命名方法: 小驼峰式命名法
命名规范: 前缀应该为动词
常用动词约定

动词 含义
can 判断是否可执行某个动作
has 判断是否含义某个值
get 获取某个值
set 设置某个值
load 加载某些数据
// 是否可阅读
function canRead() {}
// 获取名称
function getName() {}

四、类 & 构造函数

命名方法:大写驼峰式命名法,首字母大写。
命名规范:前缀为名称。

class Persion {
  constructor(name) {
   ...
  }
}
let person = new Person('张三');

五、类的成员

类的成员包括:

  • 公共属性和方法: 跟变量和函数命名一样。
  • 私有属性和方法:前缀为下划线_, 后面跟公共属性和方法一样的命名方式。
class Person {
  // 私有属性 
  _name: string;
  constructor() { }

  // 公共方法
  getName() {
    return this._name;
  }
  // 公共方法
  setName(name) {
    this._name = name;
  }
}

六、注释规范

格式化插件推荐prettier

  1. 单行注释
// 设置标题
setTitle()
  1. 多行注释
    /*
    * 代码执行到这里后会调用setTitle()函数
    * setTitle():设置title的值
    */
    setTitle();
    

6.3 函数 & 方法注释

/**
 * 函数说明
 * @关键字
 **/

常用关键字注释
注释名 语法 含义 示例
@param @param {参数类型} 描述信息 描述参数 @param {String} name 传入名称
@return @return {参数类型} 描述信息 描述返回值 @retun {Boolean} true: 可执行; false: 不可执行
@author @author 描述信息 描述作者 @author 某某某 2018/04/24
@example @example 示例代码 演示函数的使用 @example setTitle(‘啦啦啦’);

方法一:indexOf

var arr=[2,8,5,0,5,2,6,7,2];
function array_unique(arr){
  var result = [];
  for (var i = 0; i < arr.length; i++) {
     if(hash.indexOf(arr[i])==-1){
      result.push(arr[i]);
     }
  }
  return result;
}

方法二:filter

var  arr = [1, 2, 3, 1, 2, 3, 4, 5, 5];
var  resultArr;
resultArr = arr.filter( function  (item, index, self) {
   return  self.indexOf(item) == index;

});

基础单词

mutations:改变、变异
store:仓库
state:状态
Action:事件

基础实例

// 如果在模块化构建系统中,请确保在开头调用了 Vue.use(Vuex)
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
        //可以添加其他代码
        state.count++
    }
  }
})
// 改变状态
// 通过提交 mutation 的方式,而非直接改变 store.state.count,是因为我们想要更明确地追踪到状态的变化
store.commit('increment')
//获取状态值
console.log(store.state.count) // -> 1

全局注册

const app = new Vue({
  el: '#app',
  // 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件
  store,
  components: { Counter },
  template: `
    <div class="app">
      <counter></counter>
    </div>
  `
})
// 子组件使用
const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return this.$store.state.count
    }
  }
}

注意

  • 每个应用将仅仅包含一个 store 实例
  • 由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性 中返回某个状态

JavaScript不同类型的循环

  • while - 当指定条件为 true 时循环一段代码块
  • do/while - 当指定条件为 true 时循环一段代码块
  • map: 一定遍历全部数据,不能通过return结束,常用于转换数据结构
  • forEach: 遍历全部数据,不能通过return结束循环,用于不转换数据的全部遍历。
  • filter:遍历全部,返回数组,过滤成新的数组。常用于:过滤不符合项,数组去重,过滤空字符串、undefined、null等
  • for循环:通过累加数组索引,来输出数组中的值,一般只用于循环数组。有下标,通过下标取值,可通过return退出循环。
  • for of :遍历时获得其中的每一项(属性值),可以通过return结束循环,但是循环的时候没有下标
  • for in: 不但可以遍历数组,还可以遍历对象,数组遍历下标,对象遍历属性。

1、slice截取起止下标位置字符串

str.slice(start,end);
  • start:要抽取的片断的起始下标。如果是负数,则该参数规定的是从字符串的尾部开始算起的位置。也就是说,-1 指字符串的最后一个字符,-2 指倒数第二个字符,以此类推。
  • end:紧接着要抽取的片段的结尾的下标。若未指定此参数,则要提取的子串包括 start 到原字符串结尾的字符串。如果该参数是负数,那么它规定的是从字符串的尾部开始算起的位置。

2、substring截取起止下标位置字符串

str.substring(start,end);
  • start:必需。一个非负的整数,规定要提取的子串的第一个字符在 string Object 中的位置。
  • end:可选。一个非负的整数,比要提取的子串的最后一个字符在 string Object 中的位置多 1。如果省略该参数,那么返回的子串会一直到字符串的结尾。

3、substr不建议使用

str.substr(start,length);
  • start是要截取字符串的开始下标,
  • length是要截取的长度

区别

  • slice() 比 substring() 要灵活一些,因为它允许使用负数作为参数。
  • slice() 与 substr() 有所不同,因为它用两个字符的位置来指定子串,而 substr() 则用字符位置和长度来指定子串。

1、string.split():根据字符分割字符串

// split:分裂
var str = 'abc#edf#ghi';
var arr = str.split('#')

2、String.substring(start,end):根据给定的开始位置和结束位置获取子字符串

//substring:子字符串
var str = 'abcedfghi';
var arr = str.substring(2,4); // ce

3、String.substr(start,length):根据给定的开始位置和截取长度获取子字符串

//substr:子字符串
var str = 'abcedfghi';
var arr = str.substring(2,4); // bced

4、String.slice(start,end):根据给定的开始位置和结束位置获取子字符串

//substring:子字符串
var str = 'abcedfghi';
var arr = str.substring(2,4); // ce

总结:

slice,substring,substr 都是用来截取字符串,但是只有slice支持数组;三者如果不传参数,则都返回全部内容;

参数为正数时,只有substring会自动调换顺序,slice在第一参数大于第二参数时会无效返回空,而substr无所谓,除非给定的第一参数超出了源数据长度才会返回空;

参数为负数时,只有substring会永远无效,即不要给substring使用负值!slice可认为从尾部倒数,或者直接用源数据长度加上这个负值换算为正数,然后结论依然遵循第2条所述;而substr,则只适用第一参数为负数,换算方法同slice,其第二参数代表截取的个数,是不能为负数的;

据w3cshool所提示 (链接: http://www.w3school.com.cn/jsref/jsref_substr.asp) ,“ECMAscript 没有substr方法进行标准化,因此反对使用它”。

自己的理解:
slice 唯独它顺序有严格要求,两个参数都支持从尾部倒数,或者数据长度加上这个负值
substring 唯独它可以自动换顺序;
substr 唯独它第二个参数是个数的意思; 第一个参数为负数的时候支持从尾部倒数,或者数据长度加上这个负值

1、“+”:加号拼接

var str = 'abc' + 'efg';//'abcefg'

2、数组拼接成字符串

var arr = ['abc','def','ghi']
var str = arr.join('');// 'abcdefghi'

3、反引号:模版字符串

var str_1 = '你好'
var str_2 = `${str_1} 世界`;// 'abcdefghi'

4、concat():合并数组,合并字符串

var str_1 = '你好'
var str_2 = '世界';
var str_3 = str_1.concat(str_2)

javascript 替换,replace

replace() 方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。
语法:str.replace(regexp|substr, newSubStr|function)
参数1:必需,正则表达式或者字符串
参数2:必需,替换文本或生成替换文本的函数

案例一:只替换一个

var str = '你好,世界!你好,这个世界!'
var new_str = str.replace('世界','地球') //'你好,地球!你好,这个世界!'
var new_str = str.replace(/世界/,'地球') //'你好,地球!你好,这个世界!'

案例二:全局替换

var str = '你好,世界!你好,这个世界!'

var new_str_2 = str.replace(/世界/g,'地球') //'你好,地球!你好,这个地球!'

案例三:字符串首字母替换成大写

var str = 'hello world!'
var new_str = str.replace(/\b\w+\b/g,function(word){
  return word.substring(0,1).toUpperCase()+word.substring(1);
}) //'Hello World'
// 创建并挂载到 #app (会替换 #app)
new MyComponent().$mount('#app');

// 手动挂载
new myAppendTo().$mount().$appendTo('#app');//appendTo

问题分析

npm install 安装关联库的时候,可能有些库安装失败,一个主要原因是,npm默认使用国外仓库

问题解决

  1. 设置国内镜像
    npm install -g mirror-config-china --registry=http://registry.npm.taobao.org
  2. 重新安装node-sass
    npm install node-sass
```js function getQueryString(name) { var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)" , "i" ); var r = window.location.search.substr(1).match(reg); if(r != null ){ return unescape(r[2]); //如果有中文需要使用decodeURI(r[2]) }else{ return null; } } ``` 常见问题:获取到的值是乱码的 原因 : 浏览器会将url中的中文参数进行encodeURI编码,所以要通过js使用decodeURI进行解码 解决办法:将解码方式unscape换为decodeURI