- 📙 Focusing on JavaScript & Rust
- 🔭 I'm currently working on JavaScript App
- 🍀 I'm currently learning and sharing on my blog, welcome~
physicshi / just-code Goto Github PK
View Code? Open in Web Editor NEW日拱一卒,功不唐捐。
日拱一卒,功不唐捐。
我们不能含糊地记说:“中序遍历是先访问左子树,再访问根节点,再访问右子树”。这么描述是不准确的,容易产生误导。
事实上,无论是前、中、后序遍历,都是先访问根节点,再访问它的左子树,再访问它的右子树
我们都会对每一个节点「做一些事情」,区别在于做一些事情的时机不同。
比如中序遍历,它是将处理当前节点放在了访问完它的左子树之后。比方说,打印一下节点值,就会产生「左 根 右」的打印顺序。
前、中、后序遍历都是基于DFS,节点的访问顺序如上图所示,每个节点有三个不同的驻留阶段,即每个节点会被经过三次:
在递归它的左子树之前。
在递归完它的左子树之后,在递归它的右子树之前。
在递归完它的右子树之后。
我们将处理当前节点,放在这三个时间点之一,就分别对应:前、中、后序遍历。
在递归它的左子树之前,处理当前节点 ---- 前序遍历
在递归完它的左子树之后,在递归它的右子树之前,处理当前节点 ---- 中序遍历
在递归完它的右子树之后,处理当前节点 ---- 后序遍历
二叉树的前序遍历和中序遍历思路是一致的,对于迭代的方式来讲,区别就是前序遍历是入栈的顺序(在stack
进行push
的时候,结果数组push
),中序遍历是出栈的顺序(在stack
进行pop
的时候,结果数组push
)。
对于后序遍历,需要先遍历右子树,再遍历左子树, 和前中序遍历对比就是反着来,在stack
进行push
的时候,队列unshift
的顺序就是后序遍历的顺序。
var preorderTraversal = function(root) {
const res = [];
const stack = [];
while(root || stack.length){
while(root){
res.push(root.val);
stack.push(root);
root=root.left
}
root=stack.pop()
root=root.right
}
return res
};
const inorderTraversal = (root) => {
if(!root) return [];
const res = [];
const stack = [];
while(root || stack.length){
while(root){
stack.push(root)
root = root.left;
}
root = stack.pop();
res.push(root.val);
root = root.right;
}
return res;
};
var postorderTraversal = function(root) {
// 初始化数据
const res =[];
const stack = [];
while (root || stack.length){
while(root){
stack.push(root);
res.unshift(root.val);
root = root.right;
}
root = stack.pop();
root = root.left;
}
return res;
};
所有基于cookie
的操作都是document.cookie
。
如果要新增cookie
,就可以document.cookie="cookiename=cookievalue"
(更新cookie
只需要document.cookie="cookiename=newCookievalue"
就会进行覆盖)
注意设置为HttpOnly
的cookie
我们是没办法用js
拿到的
function get_cookie(Name) {
var search = Name + "="//查询检索的值
var returnvalue = "";//返回值
if (document.cookie.length > 0) {
sd = document.cookie.indexOf(search);
if (sd!= -1) {
sd += search.length;
end = document.cookie.indexOf(";", sd);
if (end == -1)
end = document.cookie.length;
//unescape() 函数可对通过 escape() 编码的字符串进行解码。
returnvalue=unescape(document.cookie.substring(sd, end))
}
}
return returnvalue;
}
//使用方式:
get_cookie("popped");
//获取当前时间
var date=new Date();
var expiresDays=10;
//将date设置为10天以后的时间
date.setTime(date.getTime()+expiresDays*24*3600*1000);
//将userId和userName两个cookie设置为10天后过期
document.cookie="userId=828; userName=hulk; expires="+date.toGMTString();
删除一个cookie只需要给cookie设置一个过去的时间;
//获取当前时间
var date=new Date();
//将date设置为过去的时间
date.setTime(date.getTime()-10000);
//将userId这个cookie删除
document.cookie="userId=828; expires="+date.toGMTString();
其中GMT_String
是以GMT
格式表示的时间字符串,这条语句就是将userId
这个cookie
设置为GMT_String
表示的过期时间,超过这个时间,cookie
将消失,不可访问。
var cookie = {
set:function(key,val,time){//设置cookie方法
var date=new Date(); //获取当前时间
var expiresDays=time; //将date设置为n天以后的时间
date.setTime(date.getTime()+expiresDays*24*3600*1000); //格式化为cookie识别的时间
document.cookie=key + "=" + val +";expires="+date.toGMTString(); //设置cookie
},
get:function(key){//获取cookie方法
/*获取cookie参数*/
var getCookie = document.cookie.replace(/[ ]/g,""); //获取cookie,并且将获得的cookie格式化,去掉空格字符
var arrCookie = getCookie.split(";") //将获得的cookie以"分号"为标识 将cookie保存到arrCookie的数组中
var tips; //声明变量tips
for(var i=0;i<arrCookie.length;i++){ //使用for循环查找cookie中的tips变量
var arr=arrCookie[i].split("="); //将单条cookie用"等号"为标识,将单条cookie保存为arr数组
if(key==arr[0]){ //匹配变量名称,其中arr[0]是指的cookie名称,如果该条变量为tips则执行判断语句中的赋值操作
tips=arr[1]; //将cookie的值赋给变量tips
break; //终止for循环遍历
}
}
return tips;
},
delete:function(key){ //删除cookie方法
var date = new Date(); //获取当前时间
date.setTime(date.getTime()-10000); //将date设置为过去的时间
document.cookie = key + "=v; expires =" +date.toGMTString();//设置cookie
}
}
子串连续。子序列可以不连续
给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
这个题和最长回文子序列有一些像,就是看我们的重点都是扩展。
布尔类型的dp[i][j]
:表示区间范围[i,j]
(注意是左闭右闭)的子串是否是回文子串,如果是dp[i][j]
为true
,否则为false
。
当
s[i]
与s[j]
不相等,那没啥好说的了,dp[i][j]
一定是false
。
当s[i]
与s[j]
相等时,这就复杂一些了,有如下三种情况
情况一:下标i
与j
相同,同一个字符例如a
,当然是回文子串
情况二:下标i
与j
相差为1,例如aa
,也是文子串
情况三:下标:i
与j
相差大于1的时候,例如cabac
,此时s[i]
与s[j]
已经相同了,我们看i
到j
区间是不是回文子串就看aba
是不是回文就可以了,那么aba
的区间就是i+1
与j-1
区间,这个区间是不是回文就看dp[i + 1][j - 1]
是否为true
。
var countSubstrings = function (s) {
const dp = new Array(s.length);
let count = 0
for (let i = s.length-1; i >=0; i--) {
dp[i] = new Array(s.length).fill(false)
for (let j = i; j < s.length; j++) {
if (i === j) { dp[i][j] = true; count++ }
else if (j - i === 1 && s[j] === s[i]) {
dp[i][j] = true;
count++
} else if (j - i > 1 && s[j] === s[i] && dp[i + 1][j - 1]) {
dp[i][j] = true
count++
}
}
}
return count
}
OAuth规定授权流程,Token授权中的一环作为信息的载体
OAuth
就是一种授权机制,核心就是向第三方应用颁发令牌,同意第三方应用进入系统获取数据。
认证服务器同意授权第三方应用进入系统获取数据,系统会产生一个短期令牌,用来代替密码,供第三方使用。
OAuth 2.0 规定了四种获得令牌的流程。
主要说一下授权码,授权码主要指的是第三方应用先申请一个授权码,然后再用该码获取令牌。
请求授权码->返回授权码->请求令牌->返回令牌
其实很多微信授权登陆就是用的授权码的方式。
A 网站提供一个链接,用户点击后就会跳转到 B 网站,授权用户数据给 A 网站使用
用户跳转后,B 网站会要求用户登录,然后询问是否同意给予 A 网站授权。用户表示同意,这时 B 网站就会跳回redirect_uri
参数指定的网址。跳转时,会传回一个授权码
A 网站拿到授权码以后,就可以在后端,向 B 网站请求令牌
B 网站收到请求以后,就会颁发令牌。具体做法是向redirect_uri
指定的网址,发送一段 JSON 数据
access_token
字段就是令牌,A 网站在后端拿到两种token
,access token
和refresh token
使用双令牌实现无感知登录
Refresh Token
过期时间设置较长,用于更新 Access Token
Access Token
过期时间设置较短服务端生成
调API时所需要的资源凭证(请求api
时也要携带token
)
简单 token 的组成: uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,token 的前几位以哈希算法压缩成的一定长度的十六进制字符串)
使用:加上一个Authorization
字段,令牌就放在这个字段里面。
令牌到期前,用户使用 refresh token 发一个请求,去更新令牌
https://b.com/oauth/token? grant_type=refresh_token& client_id=CLIENT_ID& client_secret=CLIENT_SECRET& refresh_token=REFRESH_TOKEN
上面 URL 中,grant_type
参数为refresh_token
表示要求更新令牌,client_id
参数和client_secret
参数用于确认身份,refresh_token
参数就是用于更新令牌的令牌。
B 网站验证通过以后,就会颁发新的令牌
Session
Session
的唯一标识信息 SessionID
返回给浏览器SessionID
信息后,会将此信息存入到 Cookie
中,同时 Cookie
记录此 SessionID
属于哪个域名Cookie
信息也发送给服务端,服务端会从 Cookie
中获取 SessionID
,再根据 SessionID
查找对应的 Session
信息,如果没有找到说明用户没有登录或者登录失效,如果找到 Session
证明用户已经登录可执行后面操作。根据以上流程可知,SessionID 是连接 Cookie 和 Session 的一道桥梁,大部分系统也是根据此原理来验证用户登录状态。
cookie
并不是只存在浏览器里,实际上,Cookies
相关的内容还可以存在本地文件里,就比如说 Mac 下的 Chrome,存放目录就是 ~/Library/Application Support/Google/Chrome/Default
,里面会有一个名为 Cookies
的数据库文件。
存放在本地的好处就在于即使你关闭了浏览器,Cookie 依然可以生效。
因为http是无状态的,所以服务端会通过session机制来进行身份验证:
用户输入其登录信息
服务器验证信息是否正确,并创建一个session
,然后将其存储在数据库中
服务器为用户生成一个sessionId
(随机字符串),在响应头里面添加一个 Set-Cookie
字段
浏览器收到响应后保存下 Cookie
之后对该服务器每一次请求中都通过 Cookie
字段将 Cookie
信息发送给服务器,服务器会根据数据库验证sessionId
,如果有效,则接受请求
但是对于用户量大的场景,大量的sessionId
对服务器来说也是很大的压力。
JSON web token简称JWT,是目前最流行的跨域认证解决方案。
用户输入其登录信息
服务器验证信息是否正确,并返回已签名的token
token
储在客户端,例如存在localStorage
或cookie
中
之后的HTTP
请求都将token
添加到请求头里Authorization
这个字段
服务器解码JWT,并且如果令牌有效,则接受请求
一旦用户注销,令牌将在客户端被销毁,不需要与服务器进行交互一个关键是,令牌是无状态的。后端服务器不需要保存令牌或当前session的记录。`
- JWT 的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源。比如用在用户登录上。
- 可以使用 HMAC 算法或者是 RSA 的公/私秘钥对 JWT 进行签名
- 因为 JWT 是内部包含了一些会话信息,因此减少了需要查询数据库的需要
- 因为 JWT 并不使用 Cookie 的,所以你可以使用任何域名提供你的 API 服务而不需要担心跨域资源共享问题(CORS)
- 因为用户的状态不再存储在服务端的内存中,所以这是一种无状态的认证机制
- 一旦签发,到期前始终有效,除非存在额外逻辑
相同点是,它们都是存储用户信息;然而,Session是在服务器端的,而JWT是在客户端的。
Session方式存储用户信息的最大问题在于要占用大量服务器内存,增加服务器的开销。
而JWT方式将用户状态分散到了客户端中,可以明显减轻服务端的内存压力。
Session的状态是存储在服务器端,客户端只有session id;而Token的状态是存储在客户端。
Domain 指定了 Cookie 可以送达的主机名(domain就是cookie绑定的域)。假如没有指定,那么默认值为当前文档访问地址中的主机部分(但是不包含子域名)。
像淘宝首页设置的 Domain 就是 .taobao.com,这样无论是 a.taobao.com 还是 b.taobao.com 都可以使用 Cookie。
在这里注意的是,不能跨域设置 Cookie,比如阿里域名下的页面把 Domain 设置成百度是无效的:
Set-Cookie: qwerty=219ffwef9w0f; Domain=baidu.com; Path=/; Expires=Wed, 30 Aug 2020 00:00:00 GMT
Path 指定了一个 URL 路径,这个路径必须出现在要请求的资源的路径中才可以发送 Cookie 首部。比如设置 Path=/docs
,/docs/Web/
下的资源会带 Cookie 首部,/test
则不会携带 Cookie 首部。
Domain 和 Path 标识共同定义了 Cookie 的作用域:即 Cookie 应该发送给哪些 URL。
设置 HTTPOnly
属性可以防止客户端脚本通过 document.cookie
等方式访问 Cookie
,有助于避免 XSS
攻击。
SameSite
是最近非常值得一提的内容,因为 2 月份发布的 Chrome80 版本中默认屏蔽了第三方的 Cookie,这会导致阿里系的很多应用都产生问题,为此还专门成立了问题小组,推动各 BU 进行改造。
我们先来看看这个属性的作用:
SameSite 属性可以让 Cookie 在跨站请求时不会被发送,从而可以阻止跨站请求伪造攻击(CSRF)。
第三方Cookie:由当前a.com页面发起的请求的 URL 不一定也是 a.com 上的,可能有 b.com 的,也可能有 c.com 的。我们把发送给 a.com 上的请求叫做第一方请求(first-party request),发送给 b.com 和 c.com 等的请求叫做第三方请求(third-party request),第三方请求和第一方请求一样,都会带上各自域名下的 cookie,所以就有了第一方cookie(first-party cookie)和第三方cookie(third-party cookie)的区别。上面提到的 CSRF 攻击,就是利用了第三方 cookie可以携带发送的特点 。
比如百度的网页上有一张淘宝的图片,请求这个图片就是第三方请求。
只有当前网页的 URL 与请求目标一致,才是第一方cookie
SameSite
可以有下面三种值:
之前默认是 None 的,Chrome80 后默认是 Lax。
首先要理解的一点就是跨站和跨域是不同的。同站(same-site)/跨站(cross-site)」和第一方(first-party)/第三方(third-party)是等价的。但是与浏览器同源策略(SOP)中的「同源(same-origin)/跨域(cross-origin)」是完全不同的概念。
同源策略的同源是指两个 URL 的协议/主机名/端口一致。例如,www.taobao.com/pages/...,它的协议是 https,主机名是 www.taobao.com,端口是 443。
同源策略作为浏览器的安全基石,其「同源」判断是比较严格的,相对而言,Cookie中的「同站」判断就比较宽松:只要两个 URL 的 eTLD+1 相同即可,不需要考虑协议和端口。其中,eTLD 表示有效顶级域名,注册于 Mozilla 维护的公共后缀列表(Public Suffix List)中,例如,.com、.co.uk、.github.io 等。eTLD+1 则表示,有效顶级域名+二级域名,例如 taobao.com 等。
举几个例子,www.taobao.com 和 www.baidu.com 是跨站,www.a.taobao.com 和 www.b.taobao.com 是同站,a.github.io 和 b.github.io 是跨站(注意是跨站)。
一个事件被触发,会经历三个阶段,事件捕获、目标阶段、事件冒泡。
捕获的传播路径就是从document
(可以看做整个html
文档)出发,document
->html
->body
->...->e.target
的父元素;
目标阶段就是事件目标元素上发生;
冒泡阶段:e.target的父元素
->...->body
->html
->document
事件绑定有DOM0级(on+type)和DOM2级(addEventListener)
在DOM0级事件只支持在冒泡阶段回调;
在DOM2级事件中规定的事件流同时支持了事件捕获阶段和事件冒泡阶段;作为开发者,我们可以选择事件处理函数在哪一个阶段被调用
addEventListener(eventType, function, useCapture)
其中第三个参数表示事件处理函数在哪个阶段被回调:默认是false,在冒泡阶段被回调。
addEventListener
可以绑定多个事件,不会覆盖
chrome91
之后是按照先捕获再冒泡的顺序执行的,之前是按照按代码顺序可以支持事件的冒泡和捕获:通过传入的第三个参数控制,默认为false
表示事件冒泡;true
表示事件捕获
onclick
只支持冒泡阶段
一次只能绑定一个事件处理,会覆盖
e.currentTarget
e.preventDefault()
e.stopPropagation()
、e.cancelBubble=true
e.target
给你一个字符串 s
,找到 s
中最长的回文子串。
重点也是扩展,dp[i][j]
表示 s
中从 i
到 j
(包括 i
和 j
)是否可以形成回文
对于回文类的题,都是扩展的思路;对于子串,
dp[i][j]
是区间[i,j]
是否可以形成回文串;对于子序列,dp[i][j]
是从i
到j
的最长子序列
var longestPalindrome = function (s) {
if (s.length === 0 || !s) { return "" }
const dp = []
let res = s[0]
for (let i = s.length - 1; i >= 0; i--) {
dp[i] = []
for (let j = i; j < s.length; j++) {
if (i === j) dp[i][j] = true
else if (j-i===1 && s[i]===s[j]) {
dp[i][j] = true
} else if(s[i] === s[j] && dp[i + 1][j - 1]){
dp[i][j] = true
}
if (dp[i][j] && j - i + 1 > res.length) {
res = s.slice(i, j + 1)
}
}
}
return res
};
给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
dp[i][j]
表示从i
到j
的最长回文子序列;
重点是扩展,如果一个字符串是回文串,左右加一个一样的,也是回文串:dp[i][j]=dp[i+1][j-1]+2
;
如果左右加了不一样的,那么dp[i][j]
要么是dp[i+1][j]
要么是dp[i][j-1]
const longestPalindromeSubseq = (s) => {
const dp = []
for(let i= s.length-1;i>=0;i--){
dp[i]=new Array(s.length).fill(0)
for(let j=i;j<s.length;j++){
if(i===j) {dp[i][j]=1}
else if(s[i]===s[j]){
dp[i][j]=dp[i+1][j-1]+2
} else{
dp[i][j] = Math.max(dp[i+1][j],dp[i][j-1])
}
}
}
return dp[0][s.length-1]
}
本身现在用react
或者vue
构建的都是单页面富应用,在使用一个html
的基础上,页面切换实质是组件的切换。
react-router
基于history
库。
history
库提供了两种创建路由模式的api
( createBrowserHistory
或者 createHashHistory
创建出不同的 history
对象),以及监听路由变化(popstate
、onhashchange
)、改变路由(push
、replace
)
react-router
包提供了Route
、Switch
、Redirect
react-router-dom
包提供了NavLink
、Link
、BrowserRouter
、HashRouter
核心思路:利用Provider传递上下文
/* 模拟网络请求 */
function getRootPermission(){
return new Promise((resolve)=>{
resolve({
code:200, /* 数据模拟只有编写文档,和编写标签模块有权限,文档列表没有权限 */
data:[ '/config/index' , '/config/writeTag' ]
})
})
}
/* 路由根部组件 rootRoute.tsx */
const Permission = React.createContext([])
export default function Index(){
const [ rootPermission , setRootPermission ] = React.useState([])
React.useEffect(()=>{
/* 获取权限列表 */
getRootPermission().then(res=>{
console.log(res,setRootPermission)
const { code , data } = res as any
code === 200 && setRootPermission(data)
})
},[])
return <Permission.Provider value={rootPermission} >
<RootRouter/>
</Permission.Provider>
}
/* 路由权限组件 */
export function PermissionRouter(props){
const permissionList = useContext(Permission) /* 消费权限列表 */
const isMatch = permissionList.indexOf(props.path) >= 0 /* 判断当前页面是否有权限 */
return isMatch ? <Route {...props} /> : <Redirect to={'/config/NoPermission'} />
}
<Switch>
<PermissionRouter path={'/config/index'} component={WriteDoc} />
<PermissionRouter path={'/config/docList'} component={DocList} />
<PermissionRouter path={'/config/writeTag'} component={WriteTag} />
<PermissionRouter path={'/config/tagList'} component={TagList} />
<Route path={'/config/NoPermission'} component={NoPermission} />
</Switch>
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
双指针:一个链表遍历结束,移到另一个链表重新遍历,这样两个链表走的长度肯定是一样的,就会在第二次遍历时相遇,得到相交结点
var getIntersectionNode = function(headA, headB) {
let a=headA,b=headB
while(a!==b){
a=a===null?headB:a.next;
b=b===null?headA:b.next;
}
return a
};
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.