作用域
作用域作用域可以理解为变量存储的环境,你只能获取到当前作用域和上级作用域的变量
当前作用域找不到的变量会往上级作用域找,到头了还找不到就抛出变量未定义的错误
ES5在 ES6 提出新的词法环境概念前,只有两种作用域:
函数作用域:
在函数体内使用 var 声明变量,就会形成一个函数作用域
1234function foo() { // 这里面就形成了一个作用域 var bar = 'bar';}
全局作用域:
在除函数体的任意位置使用 var 声明变量,这个变量属于全局作用域
12345var a = 'a'; // 属于全局作用域function foo() { // 这里面就形成了一个作用域 var bar = 'bar';}
函数内部可以获得全局(上级)作用域的变量,声明在函数中的变量在外部获取不到:
1234567891011var foo = 'foo';{ var block = 'block';}f ...
对象拷贝
本文参考了:2.6 万字 JS 干货分享,带你领略前端魅力!- 第二节
对象拷贝分成浅拷贝和深拷贝
浅拷贝即只拷贝对象第一层的属性值,如果属性是引用类型,拷贝的是其引用:
12345let origin = { list: [1, 2, 3], foo: 'foo' };let cp = Object.assign({}, origin);// 是同一个 Array 对象origin.list === cp.list; // true
实现除了上面的 Object.assign 方法,还有许多 API 和方法来实现浅拷贝:
遍历实现:
1234let cp = {};for (const key in origin) { cp[key] = origin[key];}
ES6 展开运算符:
1let cp = { ...origin };
迭代器:
1let cp = Object.fromEntries(Object.entries(origin)); ...
比较运算符
本文参考了:我对 JS 中相等和全等操作符转化过程一直很迷惑,直到有了这份算法
比较运算符可以简单分为两类:
严格比较
非严格比较
严格比较===,!== 都是严格比较,!== 是 === 的结果取反,=== 规则如下:
如果两个操作数有不同的类型,它们不是严格相等的
如果两个操作数都为 null,则它们是严格相等的
如果两个操作数都为 undefined,它们是严格相等的
如果一个或两个操作数都是 NaN,它们就不是严格相等的
如果两个操作数都为 true 或都为 false,它们是严格相等的
如果两个操作数都是 number 类型并且具有相同的值,则它们是严格相等的
如果两个操作数都是 string 类型并且具有相同的值,则它们是严格相等的
如果两个操作数都引用相同的对象或函数,则它们是严格相等的
以上所有其他情况下操作数都不是严格相等的
非严格比较==,!=,>,<,>=,<= 都是非严格比较,== 比较步骤如下:
如果操作数具有相同的类型,使用上面的严格比较验证它们是否严格相等
如果操作数有不同的类型:
如果一个操 ...
TCP协议
本文参考了:TCP协议灵魂之问
简介TCP 是一个面向连接的、可靠的、基于字节流的传输层协议
面向连接:指的是客户端和服务器的连接,在双方互相通信之前,TCP 需要三次握手建立连接
可靠:
有状态:TCP 会精准记录哪些数据发送了,哪些数据被对方接收了,哪些没有被接收到,而且保证数据包按序到达,不允许半点差错
可控制:当意识到丢包了或者网络环境不佳,TCP 会根据具体情况调整自己的行为,控制自己的发送速度或者重发
基于字节流:TCP 为了维护状态,将一个个 IP 包变成了字节流
建立连接老生常谈的 TCP 建立连接的三次握手
第一次握手客户端向服务端发问,询问是否准备好建立连接
客户端、服务端状态 CLOSED
服务端状态监听某个端口,状态变为 LISTEN
客户端发送 SYN 和序列号 seq = x,状态变为 SYN-SENT
第二次握手服务端回答客户端准备好建立连接了,并询问是否做好了准备建立连接
服务端接收到 SYN 和序列号 seq = x
服务端发送 ACK 和确认号 ack = x + 1 以回应客户端的 SYN;同时发送 SYN 和序列 ...
原型链
前言最近在恶补 JS 基础,什么闭包、作用域链、词法环境、事件循环 balabala…人都补傻了…
大概这就是半路出家学前端的代价吧 QAQ。
今天先来把最近刚刚啃透的原型链知识梳理一下。
几个概念这几个概念是理解原型链的基础,非!常!重!要!
万物皆为对象在 JS 中,除了 null、undefined 之外的所有数据类型都是对象。
没错, NaN 也是对象,NaN 本质上是 number。
普通对象就是对象(废话)。
普通对象都会拥有 __proto__ 属性。
{} 是普通对象。
var foo = new Foo() ,foo 是普通对象。
"abc" 是普通对象。
123 是普通对象。
NaN 也是普通对象。
函数对象就是函数。
函数对象都会拥有 prototype 属性,函数对象同时也是普通对象,所以也拥有 __proto__ 属性。
function A() {} 是一个函数对象。
String、Number、Object、Function 等都是函数对象。
普通对象是函数对象的实例下面这几个例子看一 ...
双向绑定
发生神魔事了在 受控表单 中,可以发现其封装还能进行更高层次的抽象封装
没错,就是双向绑定的封装
分析受控表单 的封装其实就是一个写死变量名称的双向绑定
所以需要能够自定义变量名
传递对象模型 model 理解成本还能降低
去掉 model 参数,让用户自己定义内部变量
可能会有多个需要实现双向绑定的属性
支持 .sync 修饰符语法糖
为此我们可以封装两个混入 vModel.js, sync.js
sync.js双向绑定语法糖可以从 .sync 二次封装而来,所以我们先实现 sync.js
自定义变量名propName 定义外部传递的属性名,valueName 定义内部的属性名
默认值分别为 "value", "data"
title12345678910import { immutable } from '@/utils';const sync = ( { propName = 'value', valueName = 'data' ...
加载状态
发生甚么事了什么都没发生,纯纯的总结下搬砖技巧罢了
业务中的加载状态需求一般如下:
状态粒度细分到组件
同时能够拥有一个较为宏观的加载状态
使用方便,理解简单,弱代码入侵
这么一看,害挺简单,转换成逻辑就是这样:
能通过一个 name 设置其 name 对应的加载状态
能够自定义聚合加载状态的粒度
使用者需要用到的 api 越少越好
撸码个人经过仔细思考,决定通过两个混入的组合,来实现上述需求
匿名加载栈说人话就是维护一个队列,加载时推入数据,加载结束取出,队列长度不为 0 即是加载中
这里肯定有人会问,你为啥不直接做成一个计数器?纯纯的浪费性能!
其实这边设计成队列是有超前的考虑,我们如果在结束请求前组件销毁了,那么请求会自动撤销吗?不会,所以可能会出现内存泄漏的问题
为了解决这个问题,一开始是打算在请求工具方法上挂一个撤销请求的方法,然后在 beforeDestroy 遍历撤销请求的,但是因为业务需求不停的来,加上我们公司使用了微前端的同时维护公共工具的方法是发布到私有 npm 上,所以统一更新很麻烦,就不了了之了 (以后必补上!)
并且这个东西只要有思路就 ...
受控表单
受控表单
B 端开发中打交道最多的组件 - 表单
原文: 点我跳转
示例代码: 点我跳转
发生甚么事了最近在掘金摸鱼时,刷到了好几篇关于在 vue2 中封装表单的文章,什么提效 200% 叭叭叭的说的天花乱坠
点进去一看,其实就是把表单封装成一个可配置的巨型组件,模板语法里一大堆 v-if v-else-if
我个人认为,这种封装方式不太可取,原因有以下几点:
它完全是把写模板的代码量转移到了配置上,实质上只是换了一种写法,工作量并没有减少多少,简直是为了封装而复用,本末倒置
不仅要理解封装后组件的使用方法,还需要熟悉原始表单组件的使用方法
并且如果稍微增加、变动几个需求或者换个表单组件,一座没法维护的 💩 山就形成了
这边总结一下较高层抽象的表单通用封装需求:
可以替换不同的组件库
可以灵活应对业务逻辑的变化
使用成本低
这么一看,可以抽离封装的东西其实不多,只有表单数据的逻辑
只对数据进行抽象封装,模板中的组件消费这些数据
撸码首先整一个简单的表单出来
title1234567891011121314151617181920212223242526 ...
060-排列序列
原题:排列序列
思路:找到规律,拆解独立步骤,递归
思路观察题目我们可以看出是按照数字从小到大的顺序排列组合的
所以不妨拆分成按照位置求每一位
举个例子:
1234567# 当 n = 3 时123132213231312321
稍微观察就可以找到一个规律,只看第一位,第 k 个数为当前未使用数的第 (k ÷ 其余位组合种类) 个数比如 k = 4,那么就是 4 ÷ 2(两位数的组合数) = 2(未使用数中的第二个),因为没有任何数字被使用过,所以第一位数是 2
1. 初始化我们求组合数会用到阶乘,同时还需要跳过已经使用过的数字
从而需要使用两张哈希表 calMap、resultMap
1234// 缓存阶乘结果const calMap = {};// 缓存已经填入的数字const resultMap = {};
同时准备一个能缓存结果的阶乘函数 calNum
12345678910111213function calNum(n) { // 如果有缓存就直接返回 if (calMap[n] !== undefined ...
015-三数之和
原题:三数之和
思路:排序后头尾双指针向中间移动
这题是我永远滴痛,在没任何算法基础的时候有一家心仪的公司就挂在这题的变体!!
思路这种找边界组合的问题,一般通过排序 + 双指针就能解决,这题就是非常经典的一道题
这题要求的是三数和为 0 即 a + b + c = 0,稍微理解下技能明白,求 a 固定时 b 和 c 的不同组合,就能解决这道题
1. 排序从小到大排序,这边为了方便我们直接用库函数就行
1nums = nums.sort((a, b) => a - b);
2. 开始遍历每一个数字
若当前数字大于 0,因为是升序,右边的数只会更大,结果必定大于 0,直接返回结果
1if (nums[i] > 0) return result;
如果当前下标大于 0 且当前数与上一数相同,跳过,避免重复解 i > 0 && nums[i] === nums[i - 1]
1if (i > 0 && nums[i] === nums[i - 1]) continue;
定义指针 L 指向下一个数 i + 1,指针 ...