loading

【打卡起点】1月完结

打卡起点月,当身边发生了这样的案例,你就知道学习的重要性了。

没有什么比别人的刺激能让自己进步更快了

# 1.31

# 给女朋友写了半天PPT

# 和高中同学打了2把LOL(全输)

# 自己打了三把排位(全赢)

# 1.30

# 加班一天

# 1.29 年会

前面抽中了京东卡(100元),气的要死,到后面。en,京东卡真香!

没学什么~亏了一天

# 1.28

# HTTP 的 OPTIONS 方法

MDN: 用于获取目的资源所支持的通信选项。客户端可以对特定的 URL 使用 OPTIONS 方法,也可以对整站(通过将 URL 设置为“*”)使用该方法。

规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。

# 自动触发(options 下的简单请求和非简单请求):

  1. 使用这里任一HTTP 方法:PUT/DELETE/CONNECT/OPTIONS/TRACE/PATCH
  2. 人为设置了以下集合之外首部字段(有一些自定义header): Accept/Accept-Language/Content-Language/Content-Type/DPR/Downlink/Save-Data/Viewport-Width/Width
  3. Content-Type 的值不属于下列之一(项目中正常是:application/json): application/x-www-form-urlencoded、multipart/form-data、text/plain

# 优化OPTIONS请求:

  1. 避免触发
  2. Access-Control-Max-Age 响应首部表示 preflight request (预检请求)的返回结果可以被缓存的最长时间,单位是秒,如果值为 -1,则表示禁用缓存

# 使用Promise实现可以控制并发个数的函数

思路:

  • 从传入的promiseArr第1个元素开始,初始化promise对象,同时用一个executing数组保存正在执行的promise
  • 不断初始化promise,直到达到poolLimt(设置的最大并发数)
  • 使用Promise.race,获得executing中promise的执行情况,当有一个promise执行完毕,继续初始化promise并放入executing中
  • 所有promise都执行完了,调用Promise.all返回

# 1.27

# flex-basis

  • content -> width -> flex-basis
  • 指定了 flex-basis 后,width 属性被忽略、不再起作用
  • Flex 项目的最终尺寸会受到 min-width 和 max-width 属性影响
  • 所有项目的flex-basis相加 > content ,每个item会被压缩,设置flex-shrink: 0,不许收缩
  • 所有项目的flex-basis相加 < content ,每个item就是设置的宽度,设置flex-grow: 1,可以填充满

# promise.allSettled 实现

根据传入的promise个数声明一个存储结果的数组 注意一点,请求是并发的。拿到promise都去执行一遍,执行的顺序,就是每一个promise结果在数组中的顺序。通过一个变量控制要不要resolve出去

let p1 = new Promise((resolve, reject) => {
	setTimeout(() => {
		resolve('p1');
	},3000)
})
let p2 = new Promise((resolve, reject) => {
	setTimeout(() => {
		resolve('p2');
	},2000)
})

let p3 = new Promise((resolve, reject) => {
	setTimeout(() => {
		resolve('p3');
	},2000)
})
Promise.allSettled = function(promises) {
    return new Promise(function(resolve, reject) {
        if (!Array.isArray(promises)) {
            return reject(
                new TypeError("arguments must be an array")
            );
        }
        let resolvedCounter = 0;
        const promiseNum = promises.length;
        // 统计所有的promise结果并最后返回
        const resolvedResults = new Array(promiseNum);
        for (let i = 0; i < promiseNum; i++) {
            Promise.resolve(promises[i]).then(
                function(value) {
                    resolvedCounter++;
                    resolvedResults[i] = value;
                    if (resolvedCounter == promiseNum) {
                        return resolve(resolvedResults);
                    }
                },
                // 错误结果
                function(reason) {
                    resolvedCounter++;
                    resolvedResults[i] = reason;
                    if (resolvedCounter == promiseNum) {
                        return resolve(reason);
                    }
                }
            );
        }
    });
};

Promise.allSettled([p3,p1,p2]).then(res => {
	console.log(res);
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

# 1.26

# symbol 使用场景

const PROP_NAME = Symbol()
const PROP_AGE = Symbol()

let obj = {
  [PROP_NAME]: "有什么用?"
}
obj[PROP_AGE] = 18

obj[PROP_NAME] // '有什么用?'
obj[PROP_AGE] // 18

Object.keys(obj) // []
1
2
3
4
5
6
7
8
9
10
11
12

为了防止重复键名覆盖?不太理解,用这种方式属性也变得不可枚举,用于实现私有变量?

# this

深入理解call、apply、bind(改变函数中的this指向) (opens new window)

很少用到这些,该知道的还是得知道

  • 自动执行机制:bind与call、apply方法的区别是,call和apply方法会对目标函数进行自动执行,会返回一个新的函数。call和apply无法在事件绑定函数中使用。而bind弥补了这个缺陷,在实现改变函数 this 的同时又不会自动执行目标函数
  • bind: 返回一个原函数的拷贝,并拥有指定的 this 值和初始参数(MDN) 。 新函数中的this会永久绑定bind传入的第一个参数,this不可再次更改

零碎的知识点得好好看看

# vue3 修改后的proxy如何兼容IE11

一句话介绍:IE 11 将受到支持,但将会是另外构建一个版本 (build) 的形式支持,不过这个版本会存在与 Vue 2.x 响应式机制所存在的同样的局限。

新的代码库目前只针对主流浏览器,而且我们假定他们都支持 ES2015。但是,哎,我们也知道在可预见的未来还有很多用户仍然需要支持 IE11。除了 Proxy 外,大多数 ES2015 的特性都可以用转译或者垫片的方式在 IE11 中使用。我们的计划是另外单独实现一个具有同样 API 的替代性 observer,不过是基于老式的 Object.defineProperty API;然后再通过单独构建一个使用这个实现的 Vue 3.x 版本 (build) 进行发布,不过这个单独的版本还是会有 Vue 2.x 在变动探测方面所存在的问题,所以它其实并不是一个完全兼容 3.x 的一个版本。我们也意识到这会给第三方库的作者们带来某些不便,因为他们需要考虑两个不同版本之间的兼容性问题,不过当我们真的推进到那个阶段时,那时我们肯定会确保提供一份清晰的指导。 摘自知乎:预计今年发布的Vue3.0到底有什么不一样的地方? (opens new window)

目前也可以用babel-polyfill,但是无法完全实现proxy的完整功能,毕竟proxy是JS引擎提供的东西。具体有没有坑,还没实践过

# form表单提交会产生跨域吗?

浏览器遵从同源策略,限制ajax跨域的原因在于ajax网络请求是可以携带cookie的(通过设置withCredentials为true),比如用户打开了浏览器,登录了weibo.com,然后又打开了百度首页,这时候百度首页内的js,向weibo.com用withCredentials为true的ajax方式提交一个post请求,是会携带浏览器和weibo.com之间的cookie的,所以浏览器就默认禁止了ajax跨域,服务端必须设置CORS才可以。 而form提交是不会携带cookie的,你也没办法设置一个hidden的表单项,然后通过js拿到其他domain的cookie,因为cookie是基于域的,无法访问其他域的cookie,所以浏览器认为form提交到某个域,是无法利用浏览器和这个域之间建立的cookie和cookie中的session的,故而,浏览器没有限制表单提交的跨域问题。

# Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:

  • 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数
  • 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译
  • 确定入口:根据配置中的 entry 找出所有的入口文件
  • 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
  • 完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系
  • 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会
  • 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统

Plugin ,基于事件流框架 Tapable,插件可以扩展 Webpack 的功能,在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

# 1.25

# vuex原理

  • store注入 vue的实例组件的方式,是通过vue的 mixin机制,借助vue组件的生命周期 钩子 beforeCreate 完成的。即 每个vue组件实例化过程中,会在 beforeCreate 钩子前调用 vuexInit 方法

# vue父子组件生命周期

父beforeCreate-> 父create -> 子beforeCreate-> 子created -> 子mounted -> 父mounted
1

# 1.24

# react复习

还得搞项目练练才行

# vue2.x原理文章输出

学到的东西得记下来,输出出去

# 1.23

# input type=file 同一个文件二次上传无效的问题

e.target.value = '';
1

# Array.prototype.map的第二个参数

map方法可以接受两个参数:第一个参数callback是一个函数,这个参数是必需的。callback中有三个可选参数,分别是当前的正在处理的数组元素、当前处理元素的索引、调用map方法的数组;

第二个参数是可选的,它的作用是用来定义执行callback时的this指向

# 斐波那契数列求和

var fib = function(n) {
    if(n === 0 || n === 1) return n;
    let res1 = 0;
    let res2 = 1;
    for(let i = 1; i < n; i++) {
        let t = res1;
        res1 = res2;
        res2 = (t + res2) % 1000000007
    }
    return res2
};
1
2
3
4
5
6
7
8
9
10
11

# promise 乞丐版·

class Promise {
    callbacks = [];
    constructor(fn) {
        fn(this._resolve.bind(this));
    }
    then(onFulfilled) {
        this.callbacks.push(onFulfilled);
    }
    _resolve(value) {
        this.callbacks.forEach(fn => fn(value));
    }
}

//Promise应用
let p = new Promise(resolve => {
    setTimeout(() => {
        console.log('done');
        resolve('5秒');
    }, 5000);
}).then((tip) => {
    console.log(tip);
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# react 复习一下

# 1.22

# Node.js 事件循环,定时器和 process.nextTick()

官网解释 (opens new window)

# vue 2.x 原理总结

去年看过一次,再看一遍又是新的感觉

# 1.21

# markdown 点击展开语法

点击展开 alexwjj is perfect

# vue2.x相关原理

  • object.definePropertry 优缺点
  • vue 底层对数组方法的改写
  • vue 响应式原理 dep和watcher
  • vue 底层如何把this.data.xxx 转换this.data
  • vue diff算法如何进行vnode比较的,patch更新策略
  • vue 源码-判断对象是否相等函数

# 1.20

# vue源码中判断两个对象是否相等

diff 算法思想和这个类似,层层对比,只比对相同层级

export function looseEqual (a: any, b: any): boolean {
  // 地址相同,一定是一个对象
  if (a === b) return true
  const isObjectA = isObject(a)
  const isObjectB = isObject(b)
  if (isObjectA && isObjectB) {
    try {
      const isArrayA = Array.isArray(a)
      const isArrayB = Array.isArray(b)
      if (isArrayA && isArrayB) {
        // 数组,对比length和数组的每一项
        return a.length === b.length && a.every((e, i) => {
          return looseEqual(e, b[i])
        })
      } else if (a instanceof Date && b instanceof Date) {
        // 日期类型单独处理
        return a.getTime() === b.getTime()
      } else if (!isArrayA && !isArrayB) {
        const keysA = Object.keys(a)
        const keysB = Object.keys(b)
        // 判断keys的长度 和 每一个key拿出来进行对比
        return keysA.length === keysB.length && keysA.every(key => {
          return looseEqual(a[key], b[key])
        })
      } else {
        return false
      }
    } catch (e) {
      return false
    }
  } else if (!isObjectA && !isObjectB) {
    return String(a) === String(b)
  } else {
    return false
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# 非对称加密

有两把密钥,通常一把叫做公钥、一把叫做私钥,用公钥加密的内容必须用私钥才能解开,同样,私钥加密的内容只有公钥能解开

非对称加密算法非常耗时,所以https放弃了使用两组公钥私钥方案

https 非对称加密(加密对称加密的秘钥)+对称加密

  • 某网站拥有用于非对称加密的公钥A、私钥A’。
  • 浏览器像网站服务器请求,服务器把公钥A明文给传输浏览器。
  • 浏览器随机生成一个用于对称加密的密钥X,用公钥A加密后传给服务器。
  • 服务器拿到后用私钥A’解密得到密钥X。
  • 这样双方就都拥有密钥X了,且别人无法知道它。之后双方所有数据都用密钥X加密解密。

中间人攻击 

  • 拦截公钥私钥,然后给浏览器发送一个自己的私钥,得到浏览器的内容后,用拦截到的公钥传输给服务器

加入SSL证书校验

# new实现

function objectFactory() {
  // 创建一个空对象 
    var obj = new Object(),

    Constructor = [].shift.call(arguments);
  // 绑定原型
    obj.__proto__ = Constructor.prototype;
  // 修改this
    var ret = Constructor.apply(obj, arguments);
  // 返回结果
    return typeof ret === 'object' ? ret : obj;

};
1
2
3
4
5
6
7
8
9
10
11
12
13

# vue生命周期图

结合vue原理看生命周期会理解的更深

# Vue 响应式原理

  1. 从 new Vue 开始,首先通过 get、set 监听 Data 中的数据变化,同时创建 Dep 用来搜集使用该 Data 的 Watcher。
  2. 编译模板,创建 Watcher,并将 Dep.target 标识为当前 Watcher。
  3. 编译模板时,如果使用到了 Data 中的数据,就会触发 Data 的 get 方法,然后调用 Dep.addSub 将 Watcher 搜集起来。
  4. 数据更新时,会触发 Data 的 set 方法,然后调用 Dep.notify 通知所有使用到该 Data 的 Watcher 去更新 DOM。

参考文章:图解 Vue 响应式原理 (opens new window)

# 1.19

# vue2.x源码分析

老师讲的挺好,先带着实现一个vue,然后再带着看源码,目前看了一小半

B站链接 (opens new window)

# 复习手写

# 整理资料

各团队技术分享 - 资料整理,后续自己学一波再发出来

# 业务较忙

多需求并行还有一些团队内的事情,空不出时间来搞其他的

# 1.18

# React Fiber

  • 链表树遍历算法: 通过 节点保存与映射,便能够随时地进行 停止和重启,这样便能达到实现任务分割的基本前提
  • 任务分割,分割时机: 1.vdom 的数据对比 2.Commit 阶段
  • 分散执行: 任务分割后,就可以把小任务单元分散到浏览器的空闲期间去排队执行,而实现的关键是两个新API: requestIdleCallback(处理低优先级) 与 requestAnimationFrame(处理高优先级)

# 函数柯里化

在一个函数中,首先填充几个参数,然后再返回一个新的函数的技术,称为函数的柯里化。通常可用于在不侵入函数的前提下,为函数 预置通用参数,供多次重复调用

const add = function add(x) {
	return function (y) {
		return x + y
	}
}

const add1 = add(1)

add1(2) === 3
add1(20) === 21

1
2
3
4
5
6
7
8
9
10
11

# 手写防抖、节流、发布订阅、观察者

前端基础 - 手写系列 (opens new window)

# 数组乱序

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
arr.sort(function () {
    return Math.random() - 0.5;
});
1
2
3
4

# 正则表达式replace

// replace,第二个是回调函数,
// 第一个参数是正则表达式匹配到的值,第n个参数代表第n组(正则表达式中用()代表每一组),
// 最终str被替换的是函数return的值
str.replace(reg, function(matchItem, group) {
  return xxx
})
1
2
3
4
5
6

# 1.17

# Webpack 的运行流程

一个串行的过程,从启动到结束会依次执行以下流程:

  • 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数;
  • 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译;
  • 确定入口:根据配置中的 entry 找出所有的入口文件;
  • 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
  • 完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
  • 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
  • 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统

在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果

# CSS Modules

简单说:模块化管理css,webpack编译后可以形成局部css和全局css;一般都是react在用,了解一下

参考:CSS Modules 用法教程 (opens new window)

import React from 'react';
import style from './App.css';

export default () => {
  return (
    <h1 className={style.title}>
      Hello World
    </h1>
  );
};
1
2
3
4
5
6
7
8
9
10

App.css

.title {
  color: red;
}
1
2
3

构建工具会将类名style.title编译成一个哈希字符串

<h1 class="_3zyde4l1yATCOkgn-DBWEL">
  Hello World
</h1>
1
2
3

App.css也会同时被编译。

._3zyde4l1yATCOkgn-DBWEL {
  color: red;
}
1
2
3

全局css(不会编译成hash)

:global(.title) {
  color: green;
}
1
2
3

# import 和 require 导入的区别

(1)require 是 AMD规范引入方式;import是es6的一个语法标准,如果要兼容浏览器的话必须转化成es5的语法

(2)require是运行时调用,所以require理论上可以运用在代码的任何地方;import是编译时调用,所以必须放在文件开头

(3)本质上,require是赋值过程,其实require的结果就是对象、数字、字符串、函数等,再把require的结果赋值给某个变量;而import是解构过程,但是目前所有的引擎都还没有实现import,我们在node中使用babel支持ES6,也仅仅是将ES6转码为ES5再执行,import语***被转码为require

# web worker

在HTML页面中,如果在执行脚本时,页面的状态是不可相应的,直到脚本执行完成后,页面才变成可相应。web worker是运行在后台的js,独立于其他脚本,不会影响页面你的性能。并且通过postMessage将结果回传到主线程。这样在进行复杂操作的时候,就不会阻塞主线程了。

# webpack和gulp区别(模块化与流的区别)

  • gulp强调的是前端开发的工作流程,我们可以通过配置一系列的task,定义task处理的事务(例如文件压缩合并、雪碧图、启动server、版本控制等),然后定义执行顺序,来让gulp执行这些task,从而构建项目的整个前端开发流程。

  • webpack是一个前端模块化方案,更侧重模块打包,我们可以把开发中的所有资源(图片、js文件、css文件等)都看成模块,通过loader(加载器)和plugins(插件)对资源进行处理,打包成符合生产环境部署的前端资源。

# 过滤数组null,undefined等无效项目

arr.filter(item => true)
1

# 字符串处理

  1. 写一个名为toCamelCase的方法,实现把类似'abc-def-ghi'的字符转换成'abcDefGhi'。

  2. 写一个名为toDashJoin的方法,实现把驼峰形式字符串'abcDefGhi'转换成'abc-def-ghi'。

function toCamelCase(str){
    return str.replace(/-(.)/g,function($0,$1){
        return $1.toUpperCase()
    })
}    
console.log(toCamelCase("abc-def-ghi"));
function toDashJoin(str){
    return str.replace(/[A-Z]/g,function($0){
        return '-'+$0.toLowerCase()
    })
}
console.log(toDashJoin("abcDefGhi"));
1
2
3
4
5
6
7
8
9
10
11
12

# 通过某一项进行排序

<!-- 正常来说 -->
let a = [{age:88,name:'JJ88'},{age:66,name: 'JJ66'},{age: 77,name: 'JJ77'}]
let b = a.sort((a,b) => a.age - b.age)
结果:
0: {age: 66, name: "JJ66"}
1: {age: 77, name: "JJ77"}
2: {age: 88, name: "JJ88"}
<!-- 变形题目 -->
let data = [{
    userId: '001',
    userName: '张三(zhangsan)'
}, {
    userId: '002',
    userName: '李四(lisi)'
}, {
    userId: '003',
    userName: '王二(wanger)'
}, {
    userId: '004',
    userName: '麻子(mazi)'
}];

// 请实现数组按照数组项中userName中的姓名拼音排序。

data = data.sort((objA, objB) => {
  return objA.userName.split('(')[1] > objB.userName.split('(')[1] ? 1 : -1
});

console.log(data);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 最长公共前缀

看面经是拼多多一面算法题,没看题解写了一会 leetcode: javascript 排序对比法 - 好理解 (opens new window)

var longestCommonPrefix = function (strs) {
	if (strs.length === 0) return ''
	// 寻找数组中最短的字符串,可通过length排序找到
	let arrMap = strs.map(v => {
		return {
			length: v.length,
			value: v
		}
	})
	arrMap.sort((a, b) => a.length - b.length);

	// 每次对最短字符串删除最后一位,然后和strs的每一项开头就行比对
	// 注意循环次数是arrMap中长度最长的,排过序的取最后一个就行
	let miniStr = arrMap[0].value;
	let miniArr = miniStr.split('');
	for (let i = 0; i < arrMap[arrMap.length - 1].length; i++) {
		if (strs.every(v => v.startsWith(miniStr))) {
			return miniStr
		} else if (miniArr.length) {
			miniArr.pop()
			miniStr = miniArr.join('');
		} else {
			miniStr = ''
		}
	}
	return miniStr
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 1.16

周六团建去了【杭州大明山滑雪】 --- 偷懒

第一次滑雪⛷,一跤没摔,值得骄傲一下,hhh

# 1.15

# let 输出

let 有块级作用域

const test = () => {
  let a = 1;
  if(true) {
    let a = 2;
  }
  console.log(a)
}
test() // 1
1
2
3
4
5
6
7
8

# LocalStorage如何突破5M

localforage的逻辑是这样的:优先使用IndexedDB存储数据,如果浏览器不支持,使用WebSQL,浏览器再不支持,使用localStorage

import localforage from 'localforage';
 
localforage.setItem('name', 'jim').then(() => {
  console.log('名字设置成功');
 
  localforage.getItem('name').then(value => {
    console.log('name', value)
  })
})

1
2
3
4
5
6
7
8
9
10

# ES7 / ES8 新特性

  • ES7 Array.prototype.includes() 方法
  • ES8 引入了跟 Object.keys 配套的 Object.values 和 Object.entries
const obj = { foo: 'bar', baz: 42 };
Object.entries(obj) // [ ["foo", "bar"], ["baz", 42] ]
1
2
  • ES10 flat / flatMap
let a = [1,[2,[3,[4,[5,[7,9]]]]]]
a.flat(Infinity); // [1, 2, 3, 4, 5, 7, 9]

1
2
3
  • String.trimStart 和 String.trimEnd

# 实现深拷贝(基础版)

完整版太难了~,优化的话可以用weakMap,还需要考虑其他数据类型,function,null等

function clone(target, map = new Map()) {
    if (typeof target === 'object') {
        let cloneTarget = Array.isArray(target) ? [] : {};
        if (map.get(target)) {
            return map.get(target);
        }
        map.set(target, cloneTarget);
        for (const key in target) {
            cloneTarget[key] = clone(target[key], map);
        }
        return cloneTarget;
    } else {
        return target;
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# JavaScript 求两个数组的差集,交集,并集

let a = [1, 2, 3],b = [2, 4, 5]
//(a-b 差集:属于a但不属于b的集合)a-b=[1,3],b-a=[4,5]
let difference = a.concat(b).filter(v => !a.includes(v) ) // [4,5]
// 交集
let intersection = a.filter(v => b.includes(v)) // [2]
// 并集
let union = Array.from(new Set(a.concat(b)))
1
2
3
4
5
6
7

# 1.14

# interface 和 type 的区别

能用 interface 实现,就用 interface , 如果不能就用 type

  • interface可以重复声明,会合并,type重复声明会报错

# Promise中的then第二个参数和catch有什么区别?

then第二个参数可以是一个函数,接收reject错误,写了第二个参数,catch就无法接收到reject

# npm install

  • 运行时需要用到的包使用––save,否则使用––save-dev
  • npm install会先检查,node_modules目录之中是否已经存在指定模块。如果存在,就不再重新安装了,即使远程仓库已经有了一个新版本,也是如此。若不存在,npm 向 registry 查询模块压缩包的网址,下载压缩包,存放在根目录下的.npm目录里,解压压缩包到当前项目的node_modules目录

# 树结构生成

思路: 借助map数据结构。以当前遍历项的pid,去map对象中找到索引的id。如果找到索引,那么说明此项不在顶级当中,那么需要把此项添加到,他对应的父级中。如果没有在map中找到对应的索引ID,那么直接把 当前的item添加到 val结果集中,作为顶级

var data = [
{ id: 1, name: "用户管理", pid: 0 },
{ id: 2, name: "菜单申请", pid: 1 },
{ id: 3, name: "信息申请", pid: 1 },
{ id: 4, name: "模块记录", pid: 2 },
{ id: 5, name: "系统设置", pid: 0 },
{ id: 6, name: "权限管理", pid: 5 },
{ id: 7, name: "用户角色", pid: 6 },
{ id: 8, name: "菜单设置", pid: 6 },
];
function toTree (data) {
  // 删除 所有 children,以防止多次调用
  data.forEach(function (item) {
    delete item.children;
  });

  // 将数据存储为 以 id 为 KEY 的 map 索引数据列
  var map = {};
  data.forEach(function (item) {
    map[item.id] = item;
  });
  // console.log(map, 'map');
  var val = [];
  data.forEach(function (item) {
    // 以当前遍历项的pid,去map对象中找到索引的id
    var parent = map[item.pid];
    // 如果找到索引,那么说明此项不在顶级当中,那么需要把此项添加到,他对应的父级中
    if (parent) {
      (parent.children || (parent.children = [])).push(item);
    } else {
      // 如果没有在map中找到对应的索引ID,那么直接把 当前的item添加到 val结果集中,作为顶级
      val.push(item);
    }
  });
  return val;
}
console.log(toTree(data))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 股票最大利益 III (hard)

每周四-团队算法题,很难~动态规划方程的转换搞不懂,看下肖大佬写的思路,稍微理解下。【动态规划】的题目目前还只会简单的套公式,其他的当场死亡~

/*
算法第 37 题- 买卖股票的最佳时机 III

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

输入:prices = [3,3,5,0,0,3,1,4]
输出:6
解释:在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。
     随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。
示例 2:

输入:prices = [1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。   
     注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。   
     因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:

输入:prices = [7,6,4,3,1] 
输出:0 
解释:在这个情况下, 没有交易完成, 所以最大利润为 0。
示例 4:

输入:prices = [1]
输出:0
 

提示:

1 <= prices.length <= 105
0 <= prices[i] <= 105

最多只能交易两次, 求最大利润,也可以不交易
首先我们进行状态分析,每天的状态   1. 一次都没买   2. 买入 1 次,,3. 买入一次,卖出一次  4。 交易完成一次, 买入  5.  交易完成两次
然后推导出,我们交易两次后,或者不交易的最大利润

*/

/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function(prices) {
  // 最多只能交易两次, 最大利润
  // 每天的状态   1. 一次都没买   2. 买入 1 次,,3. 买入一次,卖出一次  4。 交易完成一次, 买入  5.  交易完成两次
  // 定义   买一次是   buy1 = -price  sell1 = price - buy1    buy2 = -price + sell1  sell2 = price - buy2 + sell1
  let buy1 = -prices[0]
  let sell1 = 0
  let buy2 = -prices[0]
  let sell2 = 0
  for (let i = 1; i < prices.length; i++) {
      console.log(buy1, sell1, buy2, sell2)
    // 当前是否要买入,判断的条件是取值,之前买入一次的价格和当前的哪个价格低
    buy1 = Math.max(buy1, -prices[i])
    // 第一次卖出的价格,判断条件是,当前价格减去第一次买入的价格和之前的 sell1 的最大值
    sell1 = Math.max(sell1, prices[i] + buy1)
    // 第二次是否买入,是当前的价格和上次的利润
    buy2 = Math.max(buy2, sell1 - prices[i])
    // 第二次卖出
    sell2 = Math.max(sell2, buy2 + prices[i])
  }
    return sell2;
};

console.log(
    maxProfit([3,3,5,0,0,3,1,4]), 6
)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

# 1.13

# 搭建UI组件库

  • 正常编写一个组件,只是最后需要install一下导出的组件
  • umd规范打包产物 package配置vue-cli-service build --target lib --name tag-textarea --dest lib src/index.js或者打包入口配置libraryTarget: 'umd'
  • import { Alert, Button } from 'ui-library' 这种按需引入方式,还需要借助第三方的 babel 插件来,Element-ui 的 babel-plugin-component,Ant Design 的 babel-plugin-import
  • npm包发布,npm login / npm puslish
  • 参考:前端 UI 组件库搭建指南 (opens new window)

# OAuth 2.0

OAuth 2.0 是目前最流行的授权机制,用来授权第三方应用,获取用户数据。

较多的做法是:客户端发起请求,通过client_id和redirect_uri跳转到授权页,拿到授权code后回来,与服务端换取后续鉴权的token

参考: 理解OAuth 2.0 阮一峰 (opens new window)

# HTTP请求的限制

HTTP/1.1 存在一个问题:单个 TCP 连接在同一时刻只能处理一个请求,只能在同一连接上顺序处理多个请求。 虽然 HTTP/1.1 规范中规定了 Pipelining 来试图解决这个问题,但是这个功能在浏览器中默认是关闭的。

HTTP2 提供了 Multiplexing 多路传输特性,可以在一个 TCP 连接中同时完成多个 HTTP 请求

Chrome 最多允许对同一个 Host 建立6个 TCP 连接,不同的浏览器有一些区别

参考:你知道一个TCP连接上能发起多少个HTTP请求吗? (opens new window)

# TCP 三次握手和四次挥手,同事大佬总结的

# 三次握手

目的:为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误

过程:

  • 第一次握手:建立连接时,客户端发送syn(握手信号)包(syn=j)到服务器,并进入SYN_SENT(请求连接)状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
  • 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV(服务端被动打开后,接收到了客户端的SYN并且发送了ACK时的状态)状态;
  • 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1)(确认字符),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。完成三次握手,客户端与服务器开始传送数据

在三次握手协议中,服务器维护一个未连接队列,该队列为每个客户端的SYN包(syn=j)开设一个条目,该条目表明服务器已收到SYN包,并向客户发出确认,正在等待客户的确认包。这些条目所标识的连接在服务器处于 Syn_RECV状态,当服务器收到客户的确认包时,删除该条目,服务器进入ESTABLISHED状态。

# 四次挥手

目的:TCP协议是一种面向连接的、可靠的、基于字节流的运输层通信协议。TCP是全双工模式,双方都要发送FIN包来确认断开连接。这就意味着,当主机1发出FIN报文段时,只是表示主机1已经没有数据要发送了,主机1告诉主机2,它的数据已经全部发送完毕了;但是,这个时候主机1还是可以接受来自主机2的数据;当主机2返回ACK报文段时,表示它已经知道主机1没有数据发送了,但是主机2还是可以发送数据到主机1的;当主机2也发送了FIN报文段时,这个时候就表示主机2也没有数据要发送了,就会告诉主机1,我也没有数据要发送了,之后彼此就会愉快的中断这次TCP连接。

过程:

  • 第一步,当主机A的应用程序通知TCP数据已经发送完毕时,TCP向主机B发送一个带有FIN附加标记的报文段(FIN表示英文finish)。
  • 第二步,主机B收到这个FIN报文段之后,并不立即用FIN报文段回复主机A,而是先向主机A发送一个确认序号ACK,同时通知自己相应的应用程序:对方要求关闭连接(先发送ACK的目的是为了防止在这段时间内,对方重传FIN报文段)。
  • 第三步,主机B的应用程序告诉TCP:我要彻底的关闭连接,TCP向主机A送一个FIN报文段。
  • 第四步,主机A收到这个FIN报文段后,向主机B发送一个ACK表示连接彻底释放。

# js 驼峰命名和下划线互换

// 下划线转换驼峰
function toHump(name) {
    return name.replace(/\_(\w)/g, function(all, letter){
        return letter.toUpperCase();
    });
}
// 驼峰转换下划线
function toLine(name) {
  return name.replace(/([A-Z])/g,"_$1").toLowerCase();
}


// 测试
let a = 'a_b2_345_c2345';
console.log(toHump(a));

let b = 'aBdaNf';
console.log(toLine(b));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 1.12

# node返回一个html

response.writeHead(200,{'Content-Type':'text/html'})
fs.readFile('./practice/login.html','utf-8',function(err,data){
if(err){
throw err ;
}
response.end(data);
});
1
2
3
4
5
6
7

egg中参考:egg 模板渲染 (opens new window)

注意配置静态文件存放地址:一般放在app/view中

# Object.defineProperty() 不能监听数组长度变化以及对象新属性的变化

通过下标修改数组长度,数组的length属性被初始化configurable false,所以想要通过get/set方法来监听length属性是不可行的。

vue中通过重写了七个能改变原数组的方法来进行数据监听

对象还是使用Object.defineProperty()添加get和set来监听

参考

# 实现一个拖拽组件

document.onmousedown = function(e) {
	//获取目标元素
	let dropDom = e.target; 
	//算出鼠标相对元素的位置
	let disX = e.clientX - dropDom.offsetLeft;
	let disY = e.clientY - dropDom.offsetTop;
	//鼠标按下并移动的事件
	document.onmousemove = (e) => {
		//用鼠标的位置减去鼠标相对元素的位置,得到元素的位置
		let left = e.clientX - disX;
		let top = e.clientY - disY;

		//绑定元素位置到positionX和positionY上面,貌似没啥用
		this.positionX = top;
		this.positionY = left;

		//移动当前元素
		dropDom.style.left = left + "px";
		dropDom.style.top = top + "px";
	};
	document.onmouseup = (e) => {
		document.onmousemove = null;
		document.onmouseup = null;
	};
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 不知道父元素宽高,水平垂直居中兼容性较好的方案

position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
margin: auto;
1
2
3
4
5
6

# 输入字符串 '1,5,3,2,10,8,7' 输出字符串 '1~3,5,7~8,10'

const str = '1,5,3,2,10,8,7';
const test = (str) => {
	// 分割(注意分割后的每一项字符串)
	// sort排序,a-b从小,b-a从大
	const arr = str.split(',').sort((a, b) => +a - +b)
  let res = [];
  let pre = arr[0];
  let next = arr[0];
  let j = 0;
  arr.forEach((v, i) => {
    if (+v + 1 === +arr[i + 1]) {
      next = arr[i + 1];
    } else {
      res[j] = (pre === next ? arr[i] : `${pre}~${next}`);
      pre = next = arr[i + 1];
      j++;
    }
  });
  return res.join(',');
};
console.log(test(str));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
最近更新时间: 2021/05/01 17:06:28
最近更新
01
2023/02/08 00:00:00
02
2023/01/03 00:00:00
03
2022/12/21 00:00:00