oauth 手写_GitHub - MrCrazyLeo/daily-life: 这是Leo日常学习、工作记录

2021-01-27

Vue nextTick原理与作用

Vue异步执行Dom更新。只要观察到数据变化(data、watch、computed),Vue将开启一个队列,并缓冲在同一时间循环中发生的所有数据改变。如果同一个watcher被多次触发,只会被推入队列中一次(比如同一个周期内疯狂点击按钮触发的事件,此时只会被认为是同一个)。这种在缓冲时去除重复数据对于避免不必要的计算和DOM操作上非常重要。然后,在下一个时间循环“tick”中,Vue刷新队列并执行实际(已去重)工作。Vue在内部尝试对异步队列使用原生的Promise.then和MessageChannel,如果执行环境不支持,会采用setTimeOut(cb,0)代替

Vue组件生命周期

Vue3

lifecycle.png

Vue2

lifecycleVue2.png

三路快排

function fastSort(arr){

const left = []

const right = []

const same = []

const len = arr.length

// 注意这里要返回数组!!

// 或者写成 if (len<=1) {return arr;}

if(!len) return []

const pivot = arr[len >> 1]

for(let i=0;i

arr[i] > pivot ? right.push(arr[i]) : arr[i] === pivot ? same.push(arr[i]) : left.push(arr[i])

}

return fastSort(left).concat(same, fastSort(right))

}

归并排序

function merge(left,right){

var result = []

while(left.length && right.length) {

if(left[0]

else result.push(right.shift())

}

return result.concat(left,right)

}

function mergeSort(arr){

if(arr.length < 2) return arr

const middle = arr.length >> 1

const left = arr.slice(0,middle)

const right = arr.slice(middle)

return merge(mergeSort(left),mergeSort(right))

}

var a = [1,2,4,5,1,11,8]

console.log(mergeSort(a)) // [1,1,2,4,5,8,11]

3505216999-58da612e4591c_articlex.png

2021-01-26

大前端

核心是跨平台技术

Vue组件是如何渲染和更新组件的

初次渲染过程

解析模板为render函数(或在开发环境已完成,vue-loader)

触发响应式,监听data 属性getter、setter

执行render函数,生成vnode,patch(elem, vnode)

更新过程

修改data,触发setter(此前getter已被监听)

重新执行render函数,生成newVnode

patch(vnode, newVnode)

2021-01-25

红绿灯交替闪烁

function red(){console.log('红灯亮了')}

function green(){console.log('绿灯亮了')}

function yellow(){console.log('黄灯亮了')}

const light = function (cb,timer) {

return new Promise(resolve => {

setTimeout(() => {

cb()

resolve()

}, timer)

})

}

async function step(){

await light(red,1000)

await light(green,1000)

await light(yellow,1000)

}

step()

2021-01-24

git cherry-pick

是将指定提交应用于其他分支

git cherry-pick

git cherry-pick命令的参数,不一定是提交的哈希值,分支名也是可以的,表示转移该分支的最新提交。

git cherry-pick feature

也可以一次性转移多个提交

git cherry-pick # 将 A 和 B 两个提交应用到当前分支

git cherry-pick A..B # 转移一系列的连续提交

2021-01-23

Set、WeakSet、Map、WeakMap

Set

向 Set 加入值的时候,不会发生类型转换,所以5和"5"是两个不同的值。Set 内部判断两个值是否不同,使用的算法叫做**“Same-value-zero equality”,它类似于精确相等运算符(===),主要的区别是NaN等于自身,而精确相等运算符认为NaN不等于自身。**

有方法:set、has、delete、clear

WeakSet

允许你将弱引用对象储存在一个集合里边

跟Set的区别:

WeakSet只能存储对象引用,不能存放值,而Set对象可以

WeakSet对象中存储的对象值都是被弱引用的,即垃圾回收机制不考虑WeakSet对该对象的引用,如果没有其他变量或者属性引用这个对象值,则这个对象将会被垃圾回收掉(不考虑该对象还存在WeakSet中)。所以WeakSet对象里有多少个成员元素,取决于GC有没有运行,运行前后成员元素个数可能不一致,遍历结束之后有的成员被回收了所以不见了。WeakSet对象是无法被遍历的(ES6规定)。也没办法拿到它包含的所有元素

const arr = [[1,2],[3,4]]

const weakset = new WeakSet(arr)

console.log(weakset) // WeakSet {Array(2), Array(2)}[[Entries]]0: Array(2)1: Array(2)__proto__: WeakSet

有add(value)、has(value)、delete(value)方法。clear方法被废弃了

遍历方法:keys()、values()、entries()返回一个包含Set对象中所有元素的键值对迭代器、forEach(callbackFn, thisArg):用于对集合成员执行callbackFn操作,如果提供了 thisArg 参数,回调中的this会是这个参数,没有返回值。遍历顺序是插入顺序。

Map

字典,可以储存不重复的值。与上边集合的区别:集合以[value1, value2]储存元素,字典以[key,value]形式储存

只有对同一个对象引用,Map结构才会将其视为同一个键。如下:

const map = new Map()

map.set(['a'], 5)

map.get(['a']) // undefinded,因为键是数组,上下不是对应同一内存地址

有操作方法:set(key,value)、get(key)、has(key)、delete(key)、clear()

有遍历方法:Keys()、values()、entries()返回所有成员的迭代器、forEach()

WeakMap:

WeakMap的键名是弱引用,键值可以说任何正常的值。在没有其他引用和该键引用同一对象,这个对象会被垃圾回收(相应的key变成无效)。所以WeakMap是不可枚举的。方法有set、get、has、delete。不能遍历!

优化webpack构建打包速度

开发环境用

自动刷新

热更新

DLLPlugin

生产环境用

优化babal-loader(缓存、规范打包范围include / exclude)

IgnorePlugin

noParse

happyPack 多进程

ParallelUglifyPlugin

2021-01-22

module、chunk、bunble的区别

webpack一切皆module,每个源文件都是module

chunk就是多模块(文件)集合

bunble,最终输出文件

Gzip原理

gzip 使用deflate算法进行压缩。gzip 对于要压缩的文件,首先使用LZ77算法的一个变种进行压缩,对得到的结果再使用Huffman编码的方法

2021-01-20

三种常见的浏览器渲染流程

其实就是layout和Paint都是可以避免的

1158202-35dabdd33093e413.webp

2021-01-19

CSS优先级

不同级别:!important > 行内样式 > ID选择器 > 类选择器 > 标签 > 通配符(*) > 继承 > 浏览器默认属性

同级别: 后写覆盖先写

2021-01-17

webpack工作原理

初始化参数:

从配置文件(默认webpack.config.js)和shell语句中读取并合并参数,得出最终的参数

开始编译:

用上一步得到的参数数值化Compiler对象,加载所有配置的插件,通过执行对象的run方法开始执行编译

确定入口:

根据配置中的entry找出所有的入口文件

编译模块:

从入口文件触发,调用所有配置的loader对模块进行编译,再找出该模块的依赖模块,再递归本步骤直到所有入口依赖文件都经过处理

完成编译模板:

经过第四步之后,得到每个模块被翻译之后的最终内容以及他们的依赖关系

输出资源:

根据入口和模块之间的依赖关系,组装成一个个包含多个模块的chunk,再讲每个chunk转换成一个单的文件加入输出列表中,这是可以修改输出内容的最后机会

输出完成:在确定好输出内容后,根据配置(webpack.config.js && shell)确定输出的路径和文件名,将文件的内容写入文件系统中(fs)

(在以上过程中,webpack会在特定的时间点广播特定的事件,插件监听事件并执行相应的逻辑,并且插件可以调用webpack提供的api改变webpack的运行结果)

babel工作原理

用户输入ES6代码

babelon 将代码转成AST树

babel使用babel-travese遍历ES6的AST语法树生成新的ES5的AST树

使用babel-generator生成ES5代码

2021-01-16

H5

更加语义化:aside、header、article、nav、section、details、summary(在没有css样式的情况下能有更好的布局)

前端存储机制:localStorage、SessionStorage、IndexDB

地理位置相关的API

支持音频、视频

postMeassage

canvas和svg

WebSocket

WebWorker

增强型表单:标签属性up up,date、e-mail、range等

2021-01-15

webpack的loader和plugin的区别

loader: webpack自身只支持js、JSON两种格式的文件,对于其他文件(比如css)需要通过loader将其转换成commonJS规范的文件之后,webpack才能解析

plugin:用在webpack的打包编译过程里,在对应的时间节点里执行自定义操作,比如资源管理、bundle文件优化、代码混淆等

76778096-39507500-67e4-11ea-9d0a-56e12b36d6dc.png

富途的一道考察任务队列的算法题

function testAsync() {

setTimeout(()=>{

return new Promise((resolve,reject) => resolve(111)).then(res => console.log(res))

},0)

console.log('Hi ~')

const test = () => {

setTimeout(() => {console.log('test')},0)

return 'Out async'

}

const hello = function() {

console.log('In async')

const res = await test()

console.log(res)

}

hello()

console.log('Hello ~')

}

testAsync()

// Hi ~

// In async

// Hello ~

// Out async

// 111

// test

[].slice.call()

[].slice.call(arguments)能将具有length属性的对象转成数组:

千分位

将 1000000000变成 1.000.000.000

// 德国以 . 分割金钱, 转到德国当地格式化方案即可

// 因为数字后面接第一个.是会被认为是小数点的,所以就变成了10000000. 之后连接一个toLocaleString('de-DE') ,接第二个点才被认为是对这个数字字面量进行操作。

10000000000..toLocaleString('de-DE')

// 寻找字符空隙加 .

'10000000000'.replace(/\B(?=(\d{3})+(?!\d))/g, '.')

// 寻找数字并在其后面加 .

'10000000000'.replace(/(\d)(?=(\d{3})+\b)/g, '$1.')

// 改进版:防止对后续小数点超过三位的数也处理,如不处理,下边例子会输出10.000.000.000.0.123

'10000000000.0123'.replace(/\B(?=(\d{3})+(?!\d)$.)/g, '.')

// 使用xxx.toLocaleString().replace(/,/g,'.') -> 貌似有问题,时区不确定

2021-01-24

手写深拷贝

// 没有解决循环引用的版本

function myDeepCopy(obj){

if(typeof obj === 'object') {

let clone = Array.isArray(obj) ? [] : {}

for(const key in obj){

clone[key] = myDeepCopy(obj[key])

}

return clone

} else return obj // 基本类型直接返回即可

}

var obj = {

a:1,

b:null,

c:true,

d:{},

e: {

f: {

g: 1

}

},

h:[]

}

console.log(myDeepCopy(obj))

// 上边会导致循环引用的问题

// 比如加入这段话

obj.obj = obj

// 那么如何解决循环引用的问题呢,那就是使用hash

// JS里边就用weekMap做弱引用就好(方便垃圾回收)

// 直接用map会因为强引用而部分内存没法释放

function myDeepCopy2(obj, map = new WeakMap()){

if(typeof obj === 'object'){

const isArray = Array.isArray(obj)

let clone = isArray ? [] : {}

if(map.get(obj)){

return map.get(obj)

}

map.set(obj, clone)

for(const key in obj){

clone[key] = myDeepCopy2(obj[key], map)

}

return clone

}

else return obj

}

Object.defineProperty

有writable、enumerable、configurable、value、get、set这些key值

WX20210114-102944@2x.png

2021-01-13

一道面试题

[] == ![] // true 后者为false,前者也是false

{} == !{} // false

'a' + + 'b' // 'ab'

1+ '2' // '12'

1+ [] // '1'

1 + [0] // '10'

1+ [1,2,3] // '11,2,3'

1+ {} // '1[object Object]'

true + true // 2

HTTPS

HTTP+SSL/TLS,通过 SSL证书来验证服务器的身份,并为浏览器和服务器之间的通信进行加密。

默认端口443

v2-a994fbf3094d737814fe01c2b919477b_r.jpg

上边这张图是错的,HTTPS使用非对称加密。私钥只存在于服务器上,服务器下发的内容不可能被伪造,因为别人都没有私钥,所以无法加密。 所有人都有公钥,但私钥只有服务器有,所以服务器才能看到被加密的内容。

首先客户端通过URL访问服务器建立SSL链接

服务端收到客户端请求后,会将网络支持的证书信息(证书中包含公钥)传送一份给客户端

客户端与服务器开始协商SSL连接的安全等级,也就是信息加密等级

客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后将会话密钥加密,并传送给服务端

服务端利用自己的私钥解密出会话密钥

服务器利用会话密钥加密与客户端之间的通信

HTTPS缺点:

费钱买ca证书

由于连接繁琐,所以降低了通讯效率

SSL涉及到的安全算法会消耗 CPU 资源,对服务器资源消耗较大。

对称加密、非对称机密

对称加密:加、解密使用同一串密钥。常见的对称加密算法:DES,AES等。

非对称加密:指的是加、解密使用不同的密钥,一把作为公开的公钥,另一把作为私钥。公钥加密的信息,只有私钥才能解密。反之,私钥加密的信息,只有公钥才能解密。最常用的非对称加密算法:RSA

对称加密优缺点:对称加密相比非对称加密算法来说,加解密的效率要高得多、加密速度快。但是缺陷在于对于密钥的管理和分发上比较困难,不是非常安全,密钥管理负担很重。

非对称加密优缺点:安全性更高,公钥是公开的,密钥是自己保存的,不需要将私钥给别人。缺点:加密和解密花费时间长、速度慢,只适合对少量数据进行加密。

Vue父子组件挂载顺序

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

加载渲染过程

父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted

更新过程

父beforeUpdate->子beforeUpdate->子updated->父updated

销毁过程

父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

new的实质

新建一个对象f

f.__proto__ = F.prototype ,将新建实例的__proto__指向构造函数的prototype

将this指向新创建的对象实例

返回新对象,如果原构造函数返回类型是基本类型(null、undefined、boolean、number、string),那么返回新对象;如果是对象类型,那么返回该对象类型

function myNew(fn,...args){

let obj = Object.create(fn.prototype)

let res = fn.apply(obj,args)

return res instanceof Object ? res : obj

}

新建一个对象f

f的隐式原型指向构造函数的prototype

构造函数

JS提供一个构造函数模式,用来在创建对象时初始化对象,构造函数其实就是普通的函数。只不过有如下特点:

首字母大写

内部使用this

使用new生成实例

通过构造函数添加属性和方法实际上也就是通过this添加属性和方法。因为this总是指向当前对象,所以通过this添加的属性和方法是会是之前构造函数对象和方法的拷贝,这就造成了内存的浪费

function Cat(name,color){

this.name = name;

this.color = color;

this.eat = function(){

alert('吃老鼠')

}

}

var cat1 = new Cat('tom','red')

原型链

解决上边内存浪费的问题,引入了新问题:后边派生的方法没法继承到

function Cat(name,color){

this.name = name;

this.color = color;

}

Cat.prototype.type = "猫科动物";

Cat.prototype.eat = function(){alert("吃老鼠")};

var cat1 = new Cat("大毛","黄色");

var cat2 = new Cat("二毛","黑色");

cat1.eat === cat2.eat // true,指向同一内存

继承

类式继承

如果父类的构造函数中有引用类型,就会在子类中被所有实例共用,那么一个子类的实例如果更改了这个引用类型,就会影响到其他子类的实例。

构造函数继承

导致内存浪费

组合式继承

避免了内存浪费,又使得每个实例化的子类互不影响

function Super(name, age) {

this.name = name;

this.age = age;

}

function Sub(name, age, sex) {

Super.call(this, name, age);

this.sex = sex;

}

// 原型继承

Sub.prototype = new Super();

// 构造函数指向

Sub.prototype.constructor = Sub;

寄生组合继承

解决组合式继承父类构造函数被创建两次的问题(call一次、new一次)

先给父类的原型创建一个副本,然后修改子类constructor属性,最后在设置子类的原型就可以了

function Super() {}

function Sub() {

Super.call(this)

}

Sub.prototype = new Super();

Sub.constructor = Sub;

严格模式

禁止删除变量

禁止未声明变量直接使用

重名错误

函数不能重名

对象不能有重名的属性

禁止八进制表示法

不允许使用一些保留字为变量明明

包括implements, interface, let, package, private, protected, public, static,yield

arguments对象的限制

不允许对arguments赋值

arguments不在跟踪参数的变化

禁止使用arguments.callee

函数声明必须在顶层

"use strict";

if (true) {

function f() { } // 语法错误

}

for (var i = 0; i < 5; i++) {

function f2() { } // 语法错误

}

其他不表。

WechatIMG44.jpeg

JS旋转二维数组

var rotate = function(matrix){

//逆时针旋转 90 度

//列 = 行

//行 = n - 1 - 列(j); n表示总行数

var temp = [];

var len = matrix.length;

for(var i = 0; i < len; i++){

for(var j = 0; j < len; j++){

var k = len - 1 -j;

// 注意初始化,不然报错

if(!temp[k]){

temp[k] = [];

}

temp[k][i] = matrix[i][j];

}

}

return temp;

};

var arr = [

[1,2,3],

[4,5,6],

[7,8,9]

];

console.log(rotate(arr));

console.log(arr);

2021-01-11

CSS3新特性

clip-path

选择器世界?

Border-image

Canvas与Svg的区别

D3与Echarts的区别

WX20210113-140238@2x.png

2021-01-10

常见前端鉴权方案

Session + Cookie

创建会话 -> 服务器创建sessionId,setCookie -> 客户端在请求的时候在请求头字段必须携带这个token

a7308nqc59.svg

如果某个用户一直在操作,同一个 sessionID 可能会长期有效,如果相关 cookie 泄露,可能导致比较大的风险,可以在生成 sessionID 的同时生成一个 refreshID,在 sessionID 过期之后使用 refreshID 请求服务端生成新的 sessionID(这个方案需要前端判断 sessionID 失效,并携带 refreshID 发请求)。

Session是服务端保存的一个数据结构,用于跟踪用户的状态,这个数据可以保存在集群、数据库、文件中;

Cookie是客户端保存用户信息的一个机制,用来记录用户的一些信息,也是实现Session的一种方式

JWT

e7ro2w5c82.svg

OAuth

第三方授权登录

SSO

单点登录

HTTP Auth Authentication

一般多被用在内部安全性要求不高的的系统上,如路由器网页管理接口

浏览器的事件循环和node.js事件循环的区别

Node端,microTask在事件循环的各个阶段之间执行

浏览器端,microTask在事件循环的macroTask执行完之后再执行

由于node版本更新到11,Event Loop运行原理发生了变化,一旦执行一个阶段里的一个宏任务(setTimeout,setInterval和setImmediate)就立刻执行微任务队列,这点就跟浏览器端一致

常见的 macro-task 比如:setTimeout、setInterval、script(整体代码)、 I/O 操作、UI 渲染、postMessage、MessaeChannel、SetImmediate(Node)等。

常见的 micro-task 比如: new Promise().then catch finally、MutationObserver(html5新特性) 、process.nextTick(Node)等。

68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031392f312f31322f313638343162616431636461373431663f696d616765736c696d

2021-01-09

单链表反转

function ReverseList(pHead)

{

if(pHead === null) return null

let pre = null

while(pHead){

// 先把当前节点的下一个节点保存起来

let temp = pHead.next

// 然后重新赋值当前节点的下一个节点

pHead.next = pre

// 之后因为要过渡到下一个节点,所以把pre前任指针指向当前节点

pre = pHead

// 把当前节点指针指向下一个节点

pHead = temp

}

// 记得是pre

return pre

}

WX20210109-212830@2x.png

(上边图示有个错误,应该是pre先移动到cur的位置,然后cur移动到下一个节点上(也就是temp上))

斐波那契数列

int fib(int n) {

if (n == 2 || n == 1)

return 1;

int prev = 1, curr = 1;

for (int i = 3; i <= n; i++) {

int sum = prev + curr;

prev = curr;

curr = sum;

}

return curr;

}

超过10000个列表项的长列表渲染

虚拟列表:在首屏加载的时候,只加载可视区域内需要的列表项,当滚动发生时,动态计算可视区域内的列表项,并将非可视区的列表项删除

增加上下缓冲区,避免滑动过快导致出现白屏

1590852748945-dd1059a8-7e54-49de-92d7-a20c820fcc08.png

这里我们定义两个缓冲区:需要渲染的列表总项目=视窗内(原来渲染的数量)+视窗上方缓冲区+视窗下方缓冲区。并且缓冲区的大小支持props配置。

2021-01-08

把数组排成最小的数

WX20210108-163333@2x.png

一行代码搞定:借助JS自带的sort函数(底层用了快排)

function PrintMinNumber(numbers)

{

// write code here

return numbers.sort((a,b) => `${a}${b}` - `${b}${a}`).join('')

}

实现16:9的效果

div::before{

content: "";

display: block;

padding-bottom: 56.25%;

width: 0;

height: 0;

}

div#main {

width: auto;

height: auto;

line-height: 100%;

background-color: #ff5000;

position: relative

}

span {

display: block;

position: absolute;

width: 100%;

text-align: center;

top: 50%;

}

16:9

WX20210108-150527.png

2021-01-07

手写Promise

then收集依赖 -> 异步触发resolve -> resolve执行依赖

async/await 自动执行、返回Promise的resolve/reject值

2021-01-06

ES6

170de4be1318de5e.png

ES2015新特性

let、const、dead zone静态死区

箭头函数

for...of

Promise

Generator

Class

Module

Set()

Map()

Proxy

Reflect

Symbol

ES2016新特性

数值拓展: ** 相当于Math.pow()

数组扩展:includes()

ES2017新特性

async/await

原理:将Generator函数和自动执行器spawn包装在一个函数里

形式:将Generator函数的*替换成async,将yield替换成await

函数参数尾逗号

共享内存和原子操作:由全局对象SharedArrayBuffer和Atomics实现,将数据存储在一块共享内存空间中,这些数据可在JS主线程和web-worker线程之间共享

字符串扩展

padStart()、padEnd()

对象扩展

Object.getOwnPropertyDescriptors()

Object.values()

Object.entries()

ES2018新特性

字符串拓展 放松对标签模板里字符串转义的限制:遇到不合法的字符串转义返回undefined,并且从raw上可获取原字符串

对象拓展

扩展运算符(...)**:转换对象为用逗号分隔的参数序列({ ...obj },相当于rest/spread参数的逆运算)

克隆对象:const obj = { __proto__: Object.getPrototypeOf(obj1), ...obj1 }

合并对象:const obj = { ...obj1, ...obj2 }

转换字符串为对象:{ ..."hello" }

转换数组为对象:{ ...[1, 2] }

与对象解构赋值结合:const { x, ...rest/spread } = { x: 1, y: 2, z: 3 }(不能复制继承自原型对象的属性)

修改现有对象部分属性:`const obj = { x: 1, ...{ x: 2 } }

正则拓展

s修饰符:dotAll模式修饰符,使.匹配任意单个字符(dotAll模式)

dotAll:是否设置s修饰符

后行断言:x只有在y后才匹配

后行否定断言:x只有不在y后才匹配

Unicode属性转义:匹配符合Unicode某种属性的所有字符

正向匹配:\p{PropRule}

反向匹配:\P{PropRule}

限制:\p{...}和\P{...}只对Unicode字符有效,使用时需加上u修饰符

具名组匹配:为每组匹配指定名字(?)

形式:str.exec().groups.GroupName

解构赋值替换

声明:const time = "2017-09-11"、const regexp = /(?\d{4})-(?\d{2})-(?\d{2})/u

匹配:time.replace(regexp, "$/$/$")

Promise.finally

Async 异步迭代器 for await of :循环等待每个Promise对象变为resolved状态才进行下一步

ES2019新特性

字符串扩展:直接输入U+2028和U+2029、JSON.stringify()改造、trimStart()、trimEnd()

对象扩展:Object.fromEntries()

数组扩展:flat()、flatMap()、sort稳定性

函数扩展:toString()改造(返回函数原始代码(与编码一致))、catch()参数可省略

Symbol.description 返回Symbol值的描述

ES2020新特性

globalThis:顶层对象

BigInt

链判断操作符(?.):是否存在对象属性(不存在返回undefined且不再往下执行)

空判断操作符(??):是否值为undefined或null,是则使用默认值

import():动态导入(返回Promise)

for-in遍历顺序:不同的引擎已就如何迭代属性达成一致,从而使行为标准化

Promise.allSettled():该方法返回一个Promise对象,等所有Promise都已敲定(fulfilled或者rejected)才返回这么一个Promise。并带一个对象数组,每个对象对应每个Promise的结果。

2021-01-05

JS大数问题

最大整数问题

因为JS的Number类型是遵循IEEE 754规范表示,这就意味着JavaScript能精确表示的数字是优先的,可以精确到个位的最大整数是9007 1992 5474 0992(9千万亿多),也就是2^53,超过这个范围精度就会丢失,所以出现下边现象:

Math.pow(2, 53); // 9007199254740992

Math.pow(2, 53) === Math.pow(2, 53) + 1; // true

9007199254740992 === 9007199254740992 + 1; // true

解决方案:

最大浮点数问题

前面讲到,在JavaScript中,使用浮点数标准IEEE 754表示数字的,在表示小数的时候,在转化二进制的时候有些数是不能完整转化的,比如0.3,转化成二进制是一个很长的循环的数,是超过了JavaScript能表示的范围的,所以近似等于0.30000000000000004。这个是二进制浮点数最大的问题(不仅 JavaScript,所有遵循 IEEE 754 规范的语言都是如此)。所以要判断两个值是否相等,可以用ES6引入的极小常量Number.EPSILON。Number.EPSILON是JS实际能达到的最小精度,比2^(-52)大

function IsEqual(num1,num2){

let EPSILON = Number.EPSILON ? Number.EPSILON : Math.pow(2,-52)

return Math.abs(num1 - num2) < EPSILON

}

BigInt

为解决大数(大于2^53-1)问题,JS内置了一种新的数字基本类型BigInt,可以表示任意精度整数。(注意,是整数)

可以用在一个整数字面量后面加 n 的方式定义一个 BigInt ,如:10n,或者调用函数BigInt()

它在某些方面类似于 Number ,但是也有几个关键的不同点:不能用于 Math 对象中的方法;不能和任何 Number 实例混合运算,两者必须转换成同一种类型。在两种类型来回转换时要小心,因为 BigInt 变量在转换成 Number 变量时可能会丢失精度。

以下操作符可以和 BigInt 一起使用: +、*、-、**、% 。除 >>> (无符号右移)之外的 位操作 也可以支持。因为 BigInt 都是有符号的, >>> (无符号右移)不能用于 BigInt。为了兼容 asm.js,BigInt 不支持单目 (+) 运算符。

/ 操作符对于整数的运算也没问题。可是因为这些变量是 BigInt 而不是 BigDecimal ,该操作符结果会向零取整,也就是说不会返回小数部分。

const rounded = 5n / 2n;

// ↪ 2n, not 2.5n

BigInt 和 Number 不是严格相等的,但是宽松相等的。Number 和 BigInt 可以进行比较。

注意被 Object 包装的 BigInts 使用 object 的比较规则进行比较,只用同一个对象在比较时才会相等。

0n === Object(0n); // false

Object(0n) === Object(0n); // false

const o = Object(0n);

o === o // true

对任何 BigInt 值使用 JSON.stringify() 都会引发 TypeError,因为默认情况下 BigInt 值不会在 JSON 中序列化。但是,如果需要,可以实现 toJSON 方法:

JSON.stringify(BigInt(1)); // Uncaught TypeError: Do not know how to serialize a BigInt

// 重新实现toJSON方法

BigInt.prototype.toJSON = function() { return this.toString(); }

JSON.stringify(BigInt(1));

// '"1"'

BigInt(10).toString // '10' 会转成数值再转成字符串,所以依旧有精度损失问题

instanceof原理

实际就是考察原型链的问题

instanceof 检测一个对象A是不是另一个对象B的实例的原理:

查看对象B的prototype指向的对象是否在对象A的__proto__链上。如果在,则返回true,如果不在则返回false。不过有一个特殊的情况,当对象B的prototype为null将会报错(类似于空指针异常)。

function _instanceof(A, B) {

var O = B.prototype;// 取B的显示原型

A = A.__proto__;// 取A的隐式原型

while (true) {

//Object.prototype.__proto__ === null

if (A === null)

return false;

if (O === A)// 这里重点:当 O 严格等于 A 时,返回 true

return true;

A = A.__proto__;

}

}

var Person = function() {};

var student = new Person();

console.log(student instanceof Person); // true

_instanceof(student,Person) // true

2021-01-04

no-cache 和 no-store的区别

no-cache和no-store都是HTTP协议头Cache-Control的值

no-store 彻底禁用缓存,所有内容都不会被缓存(无论是本地还是服务器),每次都从服务器获取

no-cache 可以本地缓存,可以代理服务器缓存,在浏览器使用缓存前,会往返对比Etag,如果Etag没变,返回304,则使用缓存

除了no-cache和no-store,Cache-Control头的取值还有:

public 所有内容都被缓存(客户端、代理服务器都缓存)

private 客户端缓存、代理服务器不缓存

max-age 常见

缓存的内容将在 xxx 秒后失效,这个选项只在 HTTP1.1 可用,并如果和 Last-Modified 一起使用时,优先级较高。

webpack loader加载顺序

loader加载顺序从右往左

require("!style!css!less!bootstrap/less/bootstrap.less");

//=> the file "bootstrap.less" in the folder "less" in the "bootstrap"

//module (that is installed from github to "node_modules") is

//transformed by the "less-loader". The result is transformed by the

//"css-loader" and then by the "style-loader".

//If configuration has some transforms bound to the file, they will not be applied.

Vue响应式原理

十二字真言:数据劫持 收集依赖 派发更新

JS精度问题

加法:

0.1 + 0.2 = 0.30000000000000004

减法:

0.3 - 0.2 = 0.09999999999999998

乘法:

19.9 * 100 = 1989.9999999999998

9.96 * 10 = 99.60000000000001

除法:

0.3 / 0.2 = 1.4999999999999998

处理大数也会有问题!

一道面试题:对于CORS,GET、POST的区别

GET是简单请求,不会触发Option

POST在请求头传入格式为formdata时是简单请求,传入json的话是复杂请求,会进行option验证

for of 、for in 区别

for in循环的是key(对象的属性),for of循环的是value(对象的值)

var s = [1,2,3,4]

for(let item of s ){console.log(item)} // 1 2 3 4

for(let item in s ){console.log(item)} // 0 1 2 3

for of是ES6引入的特性,修复了ES5引入的for in的不足

let aArray = ['a',123,{a:'1',b:'2'}]

for(let index in aArray){

console.log(`${aArray[index]}`);

} // a 123 [object Object]

// 此时给aArray增加一个name的属性

aArray.name ='xxx'

for(let index in aArray){

console.log(`${aArray[index]}`);

} // a 123 [object Object]

// 此时aArray已经是object了...

// 如果实在想用for...of来遍历普通对象的属性的话,可以通过和Object.keys()搭配使用,先获取对象的所有key的数组,然后遍历

for(var key of Object.keys(aArray)){

//使用Object.keys()方法获取对象key的数组

console.log(key+": "+aArray[key]);

}

// 0: a

// 1: 123

// 2: [object Object]

for...of不能循环普通的对象,需要通过和Object.keys()搭配使用 。for of更适合遍历数组,for in适合遍历对象。for in也可以遍历数组,但是会有如下问题:

index索引为字符串型数字,不能直接进行几何运算;

遍历顺序有可能不是按照实际数组的内部顺序;

使用for in会遍历数组所有的可枚举属性,包括原型。

设计模式在前端的应用

迭代器模式

// async/await就是借助Promise与Generator生成器

双向绑定

Echarts与D3的区别

兼容性:Echarts兼容到ie6以上的主流浏览器,D3兼容到i9以上的主流浏览器

依赖:D3依赖于一个个Svg标签,直接操作DOM,所以比较耗费性能;Echarts依赖canvas,则没有这个问题

学习成本:D3比较底层,API众多,学习成本比较高;Echarts使用Options API,上手比较容易

灵活性:D3偏函数式编程,可以做非常丰富的定制;echarts封装成一个一个Option配置项,灵活性较差

(共性:都是数据可视化工具,都免费开源)

浏览器缓存

强缓存:

Expires 资源过期日期

Expires: Wed, 11 May 2018 07:20:00 GMT

Cache-Control

public, max-age=31536000

协商缓存:

Last-Modified、If-Modified-Since

Last-Modified 表示本地文件最后修改日期,浏览器会在request header加上If-Modified-Since(上次返回的Last-Modified的值),询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来

但是如果在本地打开缓存文件,就会造成 Last-Modified 被修改,所以在 HTTP / 1.1 出现了 ETag

Last-Modified仅支持秒级更新,所以当文件频繁更换时导致结果不准确,也需要Etag

ETag、If-None-Match

ETag的优先级比Last-Modified更高

使用ETag的原因:

一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新GET;

某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since能检查到的粒度是s级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒);

某些服务器不能精确的得到文件的最后修改时间。

第一次请求资源:

606026927-59315bafce189_articlex.png

之后请求资源:

3768101591-59312984bf500_articlex.png

2021-01-03

用ES5实现私有变量

核心:基于闭包

function Person(name){

const _name = name

this.getName = function(){

return _name

}

}

const p = new Person('xxx')

console.log(p._name) // undefinded

console.log(p.getName()) // xxx

2021-01-02

一道算法题: 乱序排序(洗牌。)

const arr = [1,2,3,4,5,6,7]

// Fisher-Yates洗牌算法,实现十分简单,并且它可以保证均匀性,即元素的各种排列顺序出现的概率都相等

function shuffle(arr){

for(let i=arr.length - 1;i>0;i--){

const j = Math.floor(Math.random() * (i+1))

const temp = arr[i]

arr[i] = arr[j]

arr[j] = temp

}

}

shuffle(arr)

console.log(arr)

顺便提一嘴,一开始我想的是下边这种解决方式,这种洗牌方式出来的结果是不等概率

const arr = [1,2,3,4,5,6,7]

function shuffle(arr){

const len = arr.length

for(let i=0;i

const j = Math.floor(Math.random() * len)

const temp = arr[i]

arr[i] = arr[j]

arr[j] = temp

}

}

shuffle(arr)

console.log(arr)

其原理是,在第 i 次循环中,从所有元素中等可能地选一个元素,与第 i 个元素交换。这种算法的错误可以如下证明:对于一个长度为 68747470733a2f2f7777772e7a686968752e636f6d2f6571756174696f6e3f7465783d2b6e 的数组,算法创造了 68747470733a2f2f7777772e7a686968752e636f6d2f6571756174696f6e3f7465783d6e2535456e 个等可能的基本事件,这些事件对应于 68747470733a2f2f7777772e7a686968752e636f6d2f6571756174696f6e3f7465783d6e253231 种排列顺序。在非平凡情况下, 68747470733a2f2f7777772e7a686968752e636f6d2f6571756174696f6e3f7465783d6e2535456e 不能被 68747470733a2f2f7777772e7a686968752e636f6d2f6571756174696f6e3f7465783d6e253231 整除,所以各种排列顺序不可能等概率。

一道面试题: 水平垂直居中

行内元素水平居中

text-align:center;

块状元素水平居中

margin: 0 auto

flex

{

display: flex;

justify-content: center; /*使子项目水平居中*/

align-items: center; /*使子项目垂直居中*/

}

已知高度宽度元素的水平垂直居中

绝对定位与负边距实现

#container{

position:relative;

}

#center{

width:100px;

height:100px;

position:absolute;

top:50%;

left:50%;

margin:-50px 0 0 -50px;

}

%E7%BB%9D%E5%AF%B9%E5%AE%9A%E4%BD%8D%E4%B8%8E%E8%B4%9F%E8%BE%B9%E8%B7%9D.jpg

绝对定位与margin

#container{

position:relative;

}

#center{

position:absolute;

margin:auto;

top:0;

bottom:0;

left:0;

right:0;

}

未知高度和宽度元素的水平垂直居中

当要被居中的元素是inline或者inline-block元素

#container{

display:table-cell;

text-align:center;

vertical-align:middle;

}

#center{}

Css3的transform

#container{

position:relative;

}

#center{

position: absolute;

top: 50%;

left: 50%;

transform: translate(-50%, -50%);

}

一道面试题:去重

常规方法

var arr=[1,2,3,3,4,5,5,6,6]

var res = []

for(let i=0;i

if(res.indexOf(arr[i]) === -1) {

res.push(arr[i])

}

}

console.log('去重之后的数组 ',res)

ES6

const arr=[1,2,3,3,4,5,5,6,6]

const t = new Set(arr)

[...t]

// 或

const arr=[1,2,3,3,4,5,5,6,6]

Array.from(new Set(arr))

// 或使用正则表达式

// (arr+',').replace(/(\d+,)\1+/ig,'$1') -> "1,2,3,4,5,6,"

// (arr+',').replace(/(\d+,)\1+/ig,'$1').split(',') -> ["1", "2", "3", "4", "5", "6", ""]

(arr+',').replace(/(\d+,)\1+/ig,'$1').split(',').slice(0,-1) // ["1", "2", "3", "4", "5", "6"]

2021-01-01

前端安全常见问题

CSRF 跨站请求伪造

针对客户,利用客户身份干坏事。CSRF是攻击者借助受害者cookie骗取服务器新人,可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受供给服务器,从而在并未授权的情况下执行权限保护之内的操作

XSS 跨站脚本攻击

XSS是恶意攻击者往网页嵌入恶意脚本代码,当用户浏览网页的时候,脚本执行,达到恶意攻击用户的目的

2020-12-31

babel实现的原理

解析

通过解析器babylon将代码解析成抽象语法树AST

转换

通过babel-traverse plugin对抽象树进行深度优先遍历,遇到需要转换的,就直接在AST对象上对节点进行添加、更新及移除操作,比如遇到箭头函数,就转换成普通函数,最后得到新的AST树

生成

通过babel-generator将AST树生成es5代码

2020-12-30

从URL到页面渲染经历了什么?

1.DNS查询

2.TCP连接

3.HTTP请求

4.服务器响应

5.客户端渲染

这个是启用现代浏览器的dns预解析。对于要重定向的域名有效。

最优的方案应该是:通过js初始化一个iframe异步加载一个页面,而这个页面里包含本站所有的需要手动dns prefetching的域名。

预连接

尽管 dns-prefetch 仅执行 DNS查找,但preconnect 会建立与服务器的连接。如果站点是通过HTTPS服务的,则此过程包括DNS解析,建立TCP连接以及执行TLS握手。将两者结合起来可提供进一步减少跨域请求的感知延迟的机会。您可以安全地将它们一起使用,如下所示:

一道链式调用的面试题(来自三七互娱):Hero("37er")

编写代码,满足以下条件:

(1)Hero("37er");执行结果为

Hi! This is 37er

(2)Hero("37er").kill(1).recover(30);执行结果为

Hi! This is 37er

Kill 1 bug

Recover 30 bloods

(3)Hero("37er").sleep(10).kill(2)执行结果为

Hi! This is 37er

//等待10s后

Kill 2 bugs //注意为bugs

(双斜线后的为提示信息,不需要打印)

// 使用构造函数的形式

function Hero(name) {

this.name = name;

console.log(name);

return this;

}

Hero.prototype.kill = function () {

console.log('Kill');

return this

}

Hero.prototype.sleep = function (time) {

var start = new Date().getTime();

console.log('Timeout');

while((start + time * 1000) > new Date().getTime()) {

}

return this;

}

// 创建一个实例

var hero = function(name) {

return new Hero(name);

}

hero("37er").sleep(10).kill(2)

// 使用对象的形式

var Hero = function(text) {

console.log('Hi! This is ' + text)

var obj = {

kill: function(num) {

if (num == 1) {

console.log(`Kills ${num} bug`);

} else {

console.log(`Kills ${num} bugs`);

}

return this;

},

sleep: function(time) {

var start = new Date().getTime();

while((start + time*1000) > new Date().getTime()) {

}

console.log('')

return this;

},

recover: function(num) {

console.log('Recover ' + num + 'bloods');

return this;

}

}

return obj;

}

2020-12-29

HTTP请求头与响应头

浏览器请求头

Accept(text/html、*/*)

Accept-Encoding

Accept-Language

Connection(keep-alive、close)

Host(发送请求时,该报头域是必需的)

Referer

User-Agent

Cache-Control(默认为private)

Cache-Control:private 默认为private 响应只能够作为私有的缓存,不能在用户间共享

Cache-Control:public响应会被缓存,并且在多用户间共享。正常情况, 如果要求HTTP认证,响应会自动设置为 private.

Cache-Control:must-revalidate 响应在特定条件下会被重用,以满足接下来的请求,但是它必须到服务器端去验证它是不是仍然是最新的。

Cache-Control:no-cache 响应不会被缓存,而是实时向服务器端请求资源。

Cache-Control:max-age=10 设置缓存最大的有效时间,但是这个参数定义的是时间大小(比如:60)而不是确定的时间点。单位是[秒 seconds]。

**Cache-Control:no-store **在任何条件下,响应都不会被缓存,并且不会被写入到客户端的磁盘里,这也是基于安全考虑的某些敏感的响应才会使用这个。

Range 断点续传

Range:bytes=0-5 指定第一个字节的位置和最后一个字节的位置。用于告诉服务器自己想取对象的哪部分。

服务端响应头

Cache-Control

Content-Type

Content-Type:text/html;charset=UTF-8 告诉客户端,资源文件的类型,还有字符编码,客户端通过utf-8对资源进行解码,然后对资源进行html解析。通常我们会看到有些网站是乱码的,往往就是服务器端没有返回正确的编码。

Content-Encoding

Content-Encoding:gzip 告诉客户端,服务端发送的资源是采用gzip编码的,客户端看到这个信息后,应该采用gzip对资源进行解码

Date 服务器时间

Date: Tue, 03 Apr 2018 03:52:28 GMT 这个是服务端发送资源时的服务器时间,GMT是格林尼治所在地的标准时间。http协议中发送的时间都是GMT的,这主要是解决在互联网上,不同时区在相互请求资源的时候,时间混乱问题。

Server 服务器和对应版本

Transfer-Encoding

Transfer-Encoding:chunked 这个响应头告诉客户端,服务器发送的资源的方式是分块发送的。一般分块发送的资源都是服务器动态生成的,在发送时还不知道发送资源的大小,所以采用分块发送,每一块都是独立的,独立的块都能标示自己的长度,最后一块是0长度的,当客户端读到这个0长度的块时,就可以确定资源已经传输完了。

Expires 过期时间

Expires:

Sun, 1 Jan 2000 01:00:00 GMT 这个响应头也是跟缓存有关的,告诉客户端在这个时间前,可以直接访问缓存副本,很显然这个值会存在问题,因为客户端和服务器的时间不一定会都是相同的,如果时间不同就会导致问题。所以这个响应头是没有Cache-Control:max-age=*这个响应头准确的,因为max-age=date中的date是个相对时间,不仅更好理解,也更准确。

Last-Modified 最后修改时间

Last-Modified: Dec, 26 Dec 2015 17:30:00 GMT 所请求的对象的最后修改日期(按照 RFC 7231 中定义的“超文本传输协议日期”格式来表示)

Connection

Etag

ETag: "737060cd8c284d8af7ad3082f209582d" 就是一个对象(比如URL)的标志值,就一个对象而言,比如一个html文件,如果被修改了,其Etag也会别修改,所以,ETag的作用跟Last-Modified的作用差不多,主要供WEB服务器判断一个对象是否改变了。比如前一次请求某个html文件时,获得了其 ETag,当这次又请求这个文件时,浏览器就会把先前获得ETag值发送给WEB服务器,然后WEB服务器会把这个ETag跟该文件的当前ETag进行对比,然后就知道这个文件有没有改变了。

Refresh 刷新时间

Access-Control-Allow-Origin 允许跨域的域名

Access-Control-Allow-Methods 允许访问的方法

Access-Control-Allow-Credentials

Access-Control-Allow-Credentials: true 是否允许发送cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。如果access-control-allow-origin为*,当前字段就不能为true

Content-Range

单例模式

普通单例模式:

核心:调用创建单例函数式才占用内存

// 普通单例

var Singleton = function(name) {

this.name = name;

// 已经占用内存了...

this.instance = null;

};

Singleton.getInstance = function(name) {

if (!this.instance) {

this.instance = new Singleton(name);

}

return this.instance;

};

var a = Singleton.getInstance('sven1');

var b = Singleton.getInstance('sven2');

alert(a === b); // true

// 惰性单例

var Singleton = function(name) {

this.name = name;

};

Singleton.getInstance = (function() {

// 调用时才占用内存空间

var instance = null;

return function(name) {

if (!instance) {

instance = new Singleton(name);

}

return instance;

}

})();

var a = Singleton.getInstance( 'sven1' );

var b = Singleton.getInstance( 'sven2' );

alert ( a === b ); // true

透明的单例模式

// 所谓透明,就是可以直接new,而不用像上边一样记住Singleton.getInstance写法

const CreateDiv = (function(){

let instance;

const _CreateDiv = function(html){

if(instance) {

return instance

}

this.html=html

this.init()

return instance = this

}

_CreateDiv.prototype.init = function() {

const div=document.createElement('div')

div.innerHTML = this.html

document.body.appendChild(div)

}

return _CreateDiv

})()

var a = new CreateDiv('sven1');

var b = new CreateDiv('sven2');

alert(a === b); // => true

代理实现单例模式

// 普通的创建 div 的类:

const CreateDiv = function(html) {

this.html = html;

this.init();

};

CreateDiv.prototype.init = function() {

var div = document.createElement('div');

div.innerHTML = this.html;

document.body.appendChild(div);

};

// 代理

const ProxySingletonCreateDiv = (function() {

let instance;

return function(html) {

if (!instance) {

instance = new CreateDiv(html);

}

return instance;

}

})();

const a = new ProxySingletonCreateDiv('sven1');

const b = new ProxySingletonCreateDiv('sven2');

alert(a === b);

思考题:

问题描述:如果想要实现一个高阶函数,高阶函数接受一个函数作为参数,返回一个新的函数,这个新的函数和原函数功能一样,但是多了单例的效果,如何实现?

const Singleton = function(fn){

const instance;

return function() {

return instance || instance = fn.apply(this, arguments)

}

}

2020-12-27

设计模式的核心是方便代码的维护和项目重构?

%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20201227131555.png

一道面试题 —— 获取一张图片,若获取失败则报错

const imgAddress = "xxxxx"

const imgPromise = (url) => {

return new Promise((resolve,reject) => {

const img = new Image();

img.src = url

img.onload =() => {

resolve(img)

}

img.onerror = () => {

reject(new Error('图片加载出错'))

}

})

}

imgPromise(imgAddress).then(img => {

document.body.appendChild(img)

}).catch(err => {

document.body.innerHTML = err

})

一道面试题 —— 柯里化函数

// 时间监听,ie是只支持attchEvent

const whichEvent = (function() {

if(window.addEventListener) {

// element绑定事件的元素、type监听类型、listener执行的回调函数、useCapture是捕获还是冒泡

return function(element, type, listener, useCapture) {

element.addEventListener(type, function(e){

listener.call(element,e)

}, useCapture)

}

} else if(window.attachEvent){

// ie只支持冒泡

return function(element, type, handler) {

element.attachEvent('on'+type, function(e){

handler.call(element, e)

})

}

}

})()

// 实现 add(1)(2)(3) -> 6 , add(1,2)(3),add(1)(2,3)

const fixCurry = (fn, totalArg) => {

const length = totalArg || fn.length;

return function resultFn (...args) {

return args.length < length ? resultFn.bind(null, ...args) : fn(...args);

};

};

const add = fixCurry((a, b, c) => a + b + c, 3);

console.log(add(1)(2)(3));

console.log(add(1,2,3));

console.log(add(1)(2,3));

2020-12-26

前端是否需要写自动测试的代码

if(你写的是一个utils类 || 你写的是一个公共component || 你写的是一个开源项目)

return 你需要写单元测试(UT)代码

WX20201226-231903@2x.png

WX20201226-232056@2x.png

2020-12-25

代码评审(Code Review)的作用

降低低级bug产出,提高代码产出质量

学习别人更好的代码风格和解决方案

代码评审可以更好地评估工时,因为业务有时候是相似的,通过回顾以往代码,可以了解该模块的业务背景及开发手段,从而优化更好的方案,对于评估做类似模块的工时也会更为准确

代码评审让团队成员不再只是涉及自;己开发的模块,而是对别人模块也有所了解,这样其他成员可以在后续工期接手前人的代码

代码评审有助于指导培养新工程师

需求评审的作用

明白需求的背景和目标

统一需求实现的过程、方案以及相关功能点

让参会成员清楚各种任务以及完成时间

确认研发和测试公式,产品经理按照需求上线时间判断是否需要拆分任务、增加资源

2020-12-16

Typescript的优势到底在哪?

类型推断,减少“不知是字符串还是数字所以要转成数字”类似的兜底情况

一边写代码一边产出半成品文档(因为注释较多) —— 这是个“成也萧何败也萧何”的问题

杜绝了手误导致的变量名错误(找不到该变量名就会报错提示)

静态类型有利于构建大型应用,静态类型检查可以做到early fail,即你编写的代码即使没有被执行到,一旦你编写代码时发生类型不匹配,语言在编译阶段(解释执行也一样,可以在运行前)即可发现。针对大型应用,测试调试分支覆盖困难,很多代码并不一定能够在所有条件下执行到。而假如你的代码简单到任何改动都可以从UI体现出来,这确实跟大型应用搭不上关系,那么静态类型检查确实没什么作用。

The main benefit of TypeScript is that it can highlight unexpected behavior in your code, lowering the chance of bugs.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值