ybfacc / blog Goto Github PK
View Code? Open in Web Editor NEW仅记录个人学习使用
仅记录个人学习使用
当 web 缓存发现请求的资源已经被存储,它会拦截请求,返回该资源的拷贝,而不会去源服务器重新下载。这样带来的好处有:缓解服务器端压力,提升性能(获取资源的耗时更短了)。对于网站来说,缓存是达到高性能的重要组成部分。MDN
Service Worker(不涉及)
Memory cache
Disk cache
Push Cache(不涉及)
no-cache:强制要求缓存把请求提交给原始服务器进行验证(协商缓存验证)
no-store:不使用任何缓存。
private:只能被单个用户缓存,不能作为共享缓存。
public:表明响应可以被任何对象缓存。
max-age:设置缓存存储的最大周期(秒)。
MDN建议:只在需要兼容 HTTP/1.0 客户端的场合下应用 Pragma 首部
声明资源的过期时间。有max-age的情况下,max-age优先。
给我的图片加上Cache-control
。
ctx.res.setHeader('Cache-control', 'public,max-age=86400')
请求页面后,我们看到服务器日志中,资源的请求都是正常的。
我们多次刷新页面。
可以看到图片已经缓存在内存中了,然后我们查看日志。
可以看到使用了缓存之后,服务器再没有收到请求。
let exp = new Date()
exp.setTime(exp.getTime() + 60000)//毫秒
ctx.res.setHeader('Expires', exp.toUTCString())
ctx.res.setHeader('Cache-control', 'public,max-age=86400')//秒
我们同时设置这两个属性。
等1分钟后刷新,还是强缓存状态。 Cache-control优先级更高。
这里我简单的做个测试发现:
这一块没有找到好的分享,以上的结论并不完全对。
箭头函数是es6的新特性。它对函数表达式进行缩写,可以使我们更优雅的使用函数进行编程。
以下是我们不使用箭头函数的例子:
let arr = [2, 3, 4, 1, 8]
arr.sort(function (a, b) {
return a - b
})
下面我们将它改写:
let arr = [2, 3, 4, 1, 8]
arr.sort((a, b) => a - b)
代码不仅短了,而且行数也减少了。
如果使用一个剪头函数时,你不需要参数,可以使用 ()
代替。
let f = () => console.log(1)
f()
使用单个参数时,可以省略括号。
let a = 1
let f = a => console.log(a)
f(a)
使用多个参数,参数需要在在括号内。
let a = 1,
b = 2
let f = (a, b) => console.log(a + b)
f(a, b)
在函数体中只有一条语句时,隐式的使用return:
let a = 1,
b = 2
let f = (a, b) => a + b
//等价于以下代码
let g = (a, b) =>{return a + b}
console.log(g(a, b))
console.log(f(a, b))
如果你不需要隐式的使用return,可以使用void返回undefeated。
let a = 1,
b = 2
let g = (a, b) => void (a + b)
let f = (a, b) => a + b
console.log(g(a, b), f(a, b))
需要你显式的调用return,不然返回的就是undefined。
let a = 1,
b = 2
let g = (a, b) => {
let c = 4
return a + b + c
}
console.log(g(a, b))
箭头函数的this与声明所在的上下文相同。
在浏览器环境下执行以下代码:
var id = 'apple'
var obj = {
id: 'obj',
a: function () {
console.log(this.id)
},
b: () => {
console.log(this.id)
return
}
}
obj.a()
obj.b()
在箭头函数声明时,上下文环境是window。(用字面量创建的对象,环境参考上一级)
此代码在node环境下,表现不同。无法打印'apple'
。
原因:node当前文件的环境是 exports,需要微调下代码。
exports.id = 'apple'
var obj = {
id: 'obj',
a: function () {
console.log(this.id)
},
b: () => {
console.log(this.id)
return
}
}
obj.a()
obj.b()
这样就显示正确了。
箭头函数本事就依靠声明时的this,还怎么new一个对象。
可以使用rest参数(...变量名)
《JavaScript忍者秘籍(第2版)》
二分查找细节是魔鬼。
取决于你的区间是闭区间[0,len-1],还是左闭右开 [0,len)
[0,len)分成[left,mid),[mid+1,right)
[0,len-1]分成[left,mid-1],[mid+1,right]
function binarySearch(nums: number[], target: number) {
const Len = nums.length
let left = 0, right = Len - 1
while (left <= right) {
const mid = left + ((right - left) >> 1);
if (nums[mid] == target) {
return mid
} else if (nums[mid] < target) {
left = mid + 1
} else if (nums[mid] > target) {
right = mid - 1
}
}
return -1
}
if (nums[mid] === target) right = mid - 1
向左收缩
function left_binarySearch(nums: number[], target: number) {
const Len = nums.length
let left = 0, right = Len - 1
while (left <= right) {
const mid = left + ((right - left) >> 1);
if (nums[mid] === target) {
right = mid - 1
} else if (nums[mid] < target) {
left = mid + 1
} else if (nums[mid] > target) {
right = mid - 1
}
}
if (nums[left] !== target || left === Len) return -1
return left
}
if (nums[mid] === target) left = mid + 1
向右收缩
function right_binarySearch(nums: number[], target: number) {
const Len = nums.length
let left = 0, right = Len - 1
while (left <= right) {
const mid = left + ((right - left) >> 1);
if (nums[mid] === target) {
left = mid + 1
} else if (nums[mid] < target) {
left = mid + 1
} else if (nums[mid] > target) {
right = mid - 1
}
}
if (nums[right] !== target || right === Len) return -1
return right
}
代码和思路都参考二分查找细节详解,顺便赋诗一首
块格式化上下文(Block Formatting Context,BFC) 是Web页面的可视CSS渲染的一部分,是块盒子的布局过程发生的区域,也是浮动元素与其他元素交互的区域。
以下设置会产生BFC
<html>
)float
不是 none
)position
为 absolute
或 fixed
)display
为 inline-block
)overflow
值不为 visible
的块元素display
为 flex
或 inline-flex
元素的直接子元素)display
为 grid
或 inline-grid
元素的直接子元素)column-count
或 column-width
不为 auto
,包括 column-coun
为 1
)以上引用来自MDN
我们可以知道根元素<html>
就是一个 BFC。当我们在页面中开启新的BFC,这就像在在旧 Html 中,开启了一个新的 Html 。这两个 Html 不能相互影响。
<div id="div_1" class="BFC">
<div id="div_2">
<div id="div_3"></div>
</div>
<div id="div_4">
<div id="div_5"></div>
</div>
<div id="div_6" class="BFC">
<div id="div_7"></div>
<div id="div_8"></div>
</div>
</div>
<style>
.box {
border: 10px solid red;
}
.left {
width: 50px;
height: 50px;
background-color: aqua;
float: left;
}
</style>
<body>
<div class="box">
<div class="left"></div>
</div>
</body>
经典的高度塌陷。想解决它,利用上面的属性1。我们给 .box
添加上 overflow: hidden;
来开启BFC
<style>
.box1 {
height: 100px;
width: 100px;
float: left;
background: lightblue;
}
.box2 {
width: 200px;
height: 200px;
background: #eee;
}
</style>
<body>
<div class="box1">左浮动</div>
<div class="box2">awecscrsc
dadaedaed
adaedaedadaedewdrgvr
vcercrcwcnciwenciwen
wcwinciweicmweomcowe
</div>
</body>
可以看到这里的 box1
出现了环绕的效果。这里清除前一个浮动的影响,可以使 box2
开启 BFC。
<style>
.box1 {
height: 100px;
width: 100px;
background: lightblue;
margin: 50px;
}
.box2 {
width: 100px;
height: 100px;
background: #eee;
margin: 50px;
}
</style>
<body>
<div class="box1"></div>
<div class="box2"></div>
</body>
可以看到这里上下边距出现了重叠。解决此方法可以给 box2
套一层 wrap。
<style>
.box1 {
height: 100px;
width: 100px;
background: lightblue;
margin: 50px;
}
.box2 {
width: 100px;
height: 100px;
background: #eee;
margin: 50px;
}
.wrap {
overflow: hidden;
}
</style>
<body>
<div class="box1"></div>
<div class="wrap">
<div class="box2"></div>
</div>
</body>
在日常使用if等判断语句时,你会碰到隐式转化。例如以下的例子
if(""){
console.log('true')
}
所以了解类型转化也成了我们的必备技能。
所有基本类型的值都是不可改变的。但需要注意的是,基本类型本身和一个赋值为基本类型的变量的区别。变量会被赋予一个新值,而原值不能像数组、对象以及函数那样被改变。
这是我在第一次使用js碰到的问题。
let str = 'abcdefg'
for (let i = 0; i < str.length; i++) {
str[i] = 'A'
}
console.log(str)//abcdefg
从 str
不可变,可以验证基本类型的值不可变。
falsy
值 (虚值) 是在 Boolean
上下文中认定为 false 的值
js中只有以下7个Falsy值:
原始值到布尔值。
除了 Falsy
其他的都转化为 true
。
原始值转化为字符串。
相当于 原始值+“”
原始值转化为数字。
Boolean
:true=>1、 false=>0
String
:转化为数字或者 NaN 。
null
和 undefined
使用 new Object()
转化为对象,将会创建并返回一个空对象。Boolean
、String
、Number
、BigInt
、Symbol
。这里有3个函数可能会影响结果。
Symbol.toPrimitive
toString()
valueOf()
一个对象可被转换为原始值。该函数被调用时,会被传递一个字符串参数 hint
,表示要转换到的原始值的预期类型。 hint
参数的取值是 "number"
、"string"
和 "default"
中的任意一个
举个栗子
注意
返回值为该对象的原始值。
注意valueOf
在不同类型中表现不同。(数组、函数等都会重写 valueOf
方法)
必须使用 Number.isNaN()
或 isNaN()
函数。
===:进行比较时,不会类型转化。
==:进行比较时,会类型转化。
如果 x 或 y 中有一个为 NaN
,则返回 false
;
如果 x 与 y 皆为 null
或 undefined
中的一种类型,则返回 true
(null == undefined // true);否则返回 false
(null == 0 // false);
如果 x,y 类型不一致,且 x,y 为 String
、Number
、Boolean
中的某一类型,则将 x,y 使用 toNumber
函数转化为 Number
类型再进行比较;
如果 x,y 中有一个为 Object
,则首先使用 ToPrimitive
函数将其转化为原始类型,再进行比较。
此处引用来自 JavaScript 运算符规则与隐式类型转换详解
let a={name:'a'}
let b={name:'a'}
a==b//false
a==a//true
2个都是对象时,我猜测是:比较地址引用。
发现有趣的现象[6,2,3]>[9,6]
的结果为 true
可以看到 >
可以对数组进行比较。(按字典序返回结果)
PS.如果字典序排列中,你缺少了一位,缺少的这一位比比0小
在本地或者服务器上使用node创建http服务器,在本地发起请求并使用wireshark来进行抓包分析。
以下两幅中、英文图对比观看,对抓到的数据可以轻松理解。
以下是使用wireshark抓包一次连接。
Seq、Ack这些值不是从0开始,实际上是由算法产生的。只是软件为了更加直观帮我们从0算起。
全部的Flags标示:
这些标示是由8个位来控制的。Flags代表这个tcp包的作用。
客户端第1次发送请求时,seq根据自身算法得到。SYN表示请求连接
服务端第1次发送请求时,seq根据自身算法得到,ack=客户端seq+1。SYN表示接受请求,ACK表示确认号有效。
客户端第2次发送请求时,seq=服务端ack,ack=服务端seq+1。ACK表示确认号有效。
PS:虽然2个seq都显示为0,但是其真实值并不相同。
a端第1次,传输结束,要求释放连接。ACK表示确认号有效。FIN表示数据传送完毕,要求释放连接。
b端第1次,数据进行传输。ACK表示确认号有效。
b第2次,传输结束,已准备断开。ACK表示确认号有效。FIN数据传送完毕,要求释放连接。
a第2次,确认断开。ACK表示确认号有效。
PS:抓包时发现:首先发送FIN的不一定是客户端,服务端也可以。
假设客户端发起FIN。服务端也没有数据需要传输,服务端可以直接返回FIN,将2次回复变成1次。
图1、5、6 来自《第5 版计算机网络原理》课件
JavaScript 继承通过原型链来实现--每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法
此图出自《JavaScript忍者秘籍(第2版)》
function.prototype是个对象,用来保存需要共享的属性
function name(params) {}
console.log(typeof name.prototype)//object
每个对象都需要使用某个函数,我们总不可能给每个对象都加个函数。这也太麻烦了。
我们可以给他们的原型对象上挂一个公用属性。
let a = { name: 'A' }
let b = { name: 'B' }
Object.prototype.say = function () {
console.log(this.name)
}
a.say() //A
b.say() //B
规则:如果试图引用对象(obj)的某个属性,会首先在对象内部寻找该属性,直至找不到,然后才在该对象的原型(obj.__proto__)里去找这个属性
.__proto__([[prototype]])是串联起原型链的关键,指向原型对象
function apple() {
this.name = 'apple'
}
let a = new apple()
console.log(a.__proto__ === apple.prototype)//true
来自MDN的警告:该特性已经从 Web 标准中删除,虽然一些浏览器目前仍然支持它,但也许会在未来的某个时间停止支持,请尽量不要使用该特性。
function apple() {
this.name = 'apple'
}
let a = new apple()
//读
console.log(Object.getPrototypeOf(a) === a.__proto__)
//写
a.__proto__ = Object.prototype
Object.setPrototypeOf(a, apple.prototype)
//创建
let b = Object.create(apple.prototype)
instanceof
运算符用来检测 constructor.prototype
是否存在于参数 object
的原型链上MDN
function apple() {
this.name = 'apple'
}
let a = new apple()
console.log(a instanceof apple)//true
方法用于测试一个对象是否存在于另一个对象的原型链上MDN
function apple() {
this.name = 'apple'
}
let a = new apple()
console.log(apple.prototype.isPrototypeOf(a)) //true
let a = {}
console.log(a instanceof Object)//true
直接由字面量赋值的对象,继承全局函数Object
function apple() {
this.name = 'apple'
}
let a = new apple()
console.log(a instanceof apple)//true
console.log(a instanceof Object)//true
使用new关键字生成的对象,继承自function.prototype原型对象
1.创建一个全新的对象
2.这个新对象的原型(Object.getPrototypeOf(target))指向构造函数的prototype对象
3.该函数的this会绑定在新创建的对象上
4.如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
var obj = {}//1
obj.__proto__ = F.prototype//2
F.call(obj)//3
return obj//4
console.log(Object.prototype.__proto__)//null
所有继承了 Object
的对象都会继承到 hasOwnProperty
方法。这个方法可以用来检测一个对象是否含有特定的自身属性;和 in
运算符不同,该方法会忽略掉那些从原型链上继承到的属性。MDN
let a = { name: 'A' }
let b = { name: 'B' }
Object.prototype.say = function () {
console.log(this.name)
}
console.log(a.hasOwnProperty('name'))
console.log(a.hasOwnProperty('say'))
Object.prototype.bar = 1;
var foo = {
moo: 2
};
for(var i in foo) {
console.log(i);
}
此题引用来自这些前端基础题你能答对几道?(测试你的基础掌握,附答案解析)。
自己做这题时,像不起来 in
的特性了。
这里有个问题=>字符串字面量为什么可以调用split()API?
let num = '1234'
let arr = num.split('')
字面量继承了String.prototype?
console.log(num instanceof String)//false
嗯?没有!!难道原型出错了?
字符串字面量 (通过单引号或双引号定义) 和 直接调用 String 方法(没有通过 new 生成字符串对象实例)的字符串都是基本字符串。JavaScript会自动将基本字符串转换为字符串对象,只有将基本字符串转化为字符串对象之后才可以使用字符串对象的方法。当基本字符串需要调用一个字符串对象才有的方法或者查询值的时候(基本字符串是没有这些方法的),JavaScript 会自动将基本字符串转化为字符串对象并且调用相应的方法或者执行查询。MDN
可以只使用O(1)空间完成树的遍历
核心就是子树的最右侧的节点的右指针指向父节点。
var Morris_pre = function (root) {
if (!root) return
let curr = root
while (curr) {
if (curr.left) {
let proNode = curr.left
while (proNode.right && proNode.right !== curr) {
proNode = proNode.right
}
if (!proNode.right) {
console.log(curr.val)
proNode.right = curr
curr = curr.left
} else {
curr = curr.right
proNode.right = null
}
} else {
console.log(curr.val)
curr = curr.right
}
}
}
var Morris_mid = function (root) {
if (!root) return
let curr = root
while (curr) {
if (curr.left) {
let proNode = curr.left
while (proNode.right && proNode.right !== curr) {
proNode = proNode.right
}
if (!proNode.right) {
proNode.right = curr
curr = curr.left
} else {
console.log(curr.val)
curr = curr.right
proNode.right = null
}
} else {
console.log(curr.val)
curr = curr.right
}
}
}
有一个反转输出的过程
var Morris_post = function (root) {
if (!root) return
let curr = root
while (curr) {
if (curr.left) {
let proNode = curr.left
while (proNode.right && proNode.right !== curr) {
proNode = proNode.right
}
if (!proNode.right) {
proNode.right = curr
curr = curr.left
continue
} else {
proNode.right = null
print(curr.left)
}
}
curr = curr.right
}
print(root)
}
function print(node) {
const tail = reverse(node)
let temp = tail
while (temp) {
console.log(temp.val)
temp = temp.right
}
reverse(tail)
}
function reverse(node) {
let pre, next
while (node) {
;[next, node.right] = [node.right, pre]
;[pre, node] = [node, next]
}
return pre
}
注意
提起 变量提升
就要提起 预编译
。分为以下4步:
if (false) {
var a = 'ybf'
}
console.log(a)//undefined
var a
function a() {}
console.log(a)//function...
function a(a1, b1) {
function a1() {}
console.log(a1)
}
a('a传参', 'b传参')//function a1() { … }
function name() {
var a = b = 'ybf'
}
name()
// console.log(a)//is not defined
console.log(b)//"ybf"
b 会直接挂载到全局变量上。
function a(a1, b1) {
console.log(a1)//function
var a1 = 'var'
console.log(a1)//var
function a1() {}
console.log(a1)//var
}
a('a传参', 'b传参')
可以看到形参直接没有出现,我们可以从预编译的过程中找到答案。
函数声明在最后声明,覆盖了形参。
a1的赋值,覆盖了函数。
函数声明优先级会高于值声明。
变量提升忽略 块级作用域
如果一个值没有声明就使用,不会变量提升。但是当执行时,会直接挂载到全局环境上。
在我们的日常使用中 undefined 被翻译为未定义,null 则表达“无”。
举个
var a //undfined
可以看到变量被隐式声明为 undefined ,而如果我们要声明变量为 null,需要显式声明。
var a = null
这2个属性在平时使用中,区别感觉并不明显。都可以表达“空”。
undefined
是全局对象的一个属性。全局的 window.undefined
不可被改写。例如下图。
局部使用 undefined
的危险
如果一定需要使用 undefined
时可以使用 viod
。
void 运算符通常只用于获取
undefined
的原始值,一般使用void(0)
(等同于void 0
)。在上述情况中,也可以使用全局变量undefined
来代替(假定其仍是默认值)。
引用来自MDN
null
是一个字面量。null
感觉更像是一个没有创建的对象。(原型链的终点就是 null
)上次在手写call等文章中看到了,支持柯里化,所以简单的了解下(只做了解)
只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。
function curry(fn) {
var args = Array.prototype.slice.call(arguments, 1)
return function () {
var innerArgs = Array.prototype.slice.call(arguments)
var finalArgs = args.concat(innerArgs)
return fn.apply(null, finalArgs)
}
}
function add(num1, num2) {
return num1 + num2
}
var curriedAdd = curry(add, 5)
console.log(curriedAdd(3)) //8
《JavaScript高级程序设计(第3版)》
要去:O(log n)时间复杂度和 O(1)空间复杂度
基础思路:二分
如果mid左右都不存在与mid重复的数,则找到目标数
mid左找到与mid重复的数
mid右找到与mid重复的数
如果最后left=right,则找到目标数
var singleNonDuplicate = function (nums) {
if (nums.length === 0) return null
let left = 0
let right = nums.length - 1
while (left <= right) {
if (left === right) return nums[left]
let mid = (left + right) >> 1
if (nums[mid] === nums[mid - 1]) {
if ((mid - left - 1) % 2 === 1) {
right = mid - 2
} else {
left = mid + 1
}
} else if (nums[mid] === nums[mid + 1]) {
if ((right - mid - 1) % 2 === 1) {
left = mid + 2
} else {
right = mid - 1
}
} else {
return nums[mid]
}
}
}
Function.prototype.call.bind() 今天第一次在掘金上看到了这样的使用方法。
这样的用法看的我一脸蒙蔽。
这里我们找2个垫片,方便我们调试。
Function.prototype.mybind = function () {
var slice = Array.prototype.slice
var thatFunc = this,
thatArg = arguments[0]
var args = slice.call(arguments, 1)
if (typeof thatFunc !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError(
'Function.prototype.bind - ' +
'what is trying to be bound is not callable'
)
}
return function () {
var funcArgs = args.concat(slice.call(arguments))
return thatFunc.apply(thatArg, funcArgs)
}
}
Function.prototype.mycall = function (context, ...args) {
if (!context) {
console.log('不存在')
context = global
}
if (typeof context !== 'object') {
console.log('不是对象')
context = global
}
let say = Symbol() // 防止覆盖掉原有属性
context[say] = this // 这里的this为需要执行的方法
const res = context[say](...args)
delete context[say]
return res
}
先看一个简单的:
var fun = Function.prototype.mycall(Object.prototype.toString)
Object.prototype.toString.Function.prototype()
Function.prototype是函数的原型对象,所有函数都要继承它,相当于执行了一个空函数。
接下来是升级版:
var toStr1 = Function.prototype.mycall.mycall(Object.prototype.toString)
Object.prototype.toString.mycall()
Object.prototype.toString.call(global)
终极版:
var toStr1 = Function.prototype.mycall.mybind(Object.prototype.toString)
Function.prototype.mycall.myapply(Object.prototype.toString, [[123]])
这与其他函数不同,bind会返回一个绑定好this的闭包函数。
js Function.prototype.call.bind(Array.prototype.slice),为啥要这么写?
原型
this的使用
call()
提供新的 this 值给当前调用的函数/方法。你可以使用 call
来实现继承:写一个方法,然后让另外一个新的对象来继承它(而不是在新对象中再写一次这个方法)。来自MDN
在非严格模式下,第一个参数如果为null或者undefined时,会自动替换为指向全局对象。
let apple = {
name: 'apple',
test: function (price, time) {
console.log(time, this.name, price)
}
}
let pear = {
name: 'pear'
}
//在原型链上绑定方法
Function.prototype._call = function (context, ...args) {
if (!context || typeof context !== 'object') {
context = global
}
let say = Symbol() // 防止覆盖掉原有属性
context[say] = this // 这里的this为需要执行的方法
const res = context[say](...args)
delete context[say]
return res
}
apple.test._call(pear, 10, '2020-5-29')//2020-5-29 pear 10
call()方法的作用和 apply() 方法类似,区别就是call()
方法接受的是参数列表,而apply()
方法接受的是一个参数数组。来自MDN
let apple = {
name: 'apple',
test: function (price, time) {
console.log(time, this.name, price)
}
}
let pear = {
name: 'pear'
}
global.name = 'window'//环境为node
apple.test.apply(null, [10, '2020-5-29'])//2020-5-29 window 10
Function.prototype._apply = function (context, args) {
if (!context || typeof context !== 'object') {
context = global
}
const key = global.Symbol() // 防止覆盖掉原有属性
context[key] = this // 这里的this为需要执行的方法
const result = context[key](...args)
delete context[key]
return result
}
apple.test._apply(null, [10, '2020-5-29'])//2020-5-29 window 10
apple.test._apply(pear, [10, '2020-5-29']) //2020-5-29 pear 10
返回一个原函数的拷贝,并拥有指定的 this
值和初始参数。来自MDN
let apple = {
name: 'apple',
test: function (price, time) {
console.log(time, this.name, price)
}
}
let pear = {
name: 'pear'
}
//借助call
Function.prototype._bind = function (context) {
if (!context || typeof context !== 'object') {
context = global
}
return (...args) => {
this.call(context, ...args)
}
}
let fun1 = apple.test._bind(pear)
fun1(10, '2020-5-29') //2020-5-29 pear 10
let apple = {
name: 'apple',
test: function (price, time) {
console.log(time, this.name, price)
}
}
let pear = {
name: 'pear'
}
Function.prototype._call = function (context, ...args) {
if (!context || typeof context !== 'object') {
context = global
}
let say = Symbol() // 防止覆盖掉原有属性
context[say] = this // 这里的this为需要执行的方法
const res = context[say](...args)
delete context[say]
return res
}
//不使用原生call
Function.prototype._bind_ = function (context) {
if (!context || typeof context !== 'object') {
context = global
}
return (...args) => {
this._call(context, ...args)
}
}
let fun2 = apple.test._bind_(pear)
fun2(10, '2020-5-29') //2020-5-29 pear 10
通过Object.defineProperty劫持属性。
通过订阅-发布模式来修改dom节点。
初始化流程:
运行流程:
代码参考来自vue 的双向绑定原理及实现
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>self-vue</title>
</head>
<style>
#name {
text-align: center;
}
</style>
<body>
<h1 id="name">{{name}}</h1>
</body>
<script>
function SelfVue(data, el, exp) {
this.data = data
observe(data)
el.innerHTML = this.data[exp] // 初始化模板数据的值
new Watcher(this, exp, function (value) {
el.innerHTML = value
})
return this
}
</script>
<script>function Observer(data) {
this.data = data
this.walk(data)
}
Observer.prototype = {
walk: function (data) {
var self = this
Object.keys(data).forEach(function (key) {
self.defineReactive(data, key, data[key])
})
},
defineReactive: function (data, key, val) {
var dep = new Dep()
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
if (Dep.target) {
dep.addSub(Dep.target)
}
return val
},
set: function (newVal) {
if (newVal === val) {
return
}
val = newVal
dep.notify()
}
})
}
}
function observe(value, vm) {
if (!value || typeof value !== 'object') {
return
}
return new Observer(value)
}
function Dep() {
this.subs = []
}
Dep.prototype = {
addSub: function (sub) {
this.subs.push(sub)
},
notify: function () {
this.subs.forEach(function (sub) {
sub.update()
})
}
}
Dep.target = null
</script>
<script>
function Watcher(vm, exp, cb) {
this.cb = cb;
this.vm = vm;
this.exp = exp;
this.value = this.get(); // 将自己添加到订阅器的操作
}
Watcher.prototype = {
update: function () {
this.run();
},
run: function () {
var value = this.vm.data[this.exp];
var oldVal = this.value;
if (value !== oldVal) {
this.value = value;
this.cb.call(this.vm, value, oldVal);
}
},
get: function () {
Dep.target = this; // 缓存自己
var value = this.vm.data[this.exp] // 强制执行监听器里的get函数
Dep.target = null; // 释放自己
return value;
}
};
</script>
<script>
var ele = document.querySelector('#name');
var selfVue = new SelfVue({
name: 'hello world'
}, ele, 'name');
window.setTimeout(function () {
console.log('name值改变了');
selfVue.data.name = 'canfoo';
}, 2000);
</script>
</html>
对一个频繁触发的请求使用防抖。
方式1:
<script>
import debounce from '@/utils/debounce.js'
export default {
data() {
return {
query: ''
}
},
created() {
this.$watch(
'query',
debounce(newQuery => {
this.$emit('query', newQuery)
}, 400)
)
}
}
</script>
方式2:
<script>
import debounce from '@/utils/debounce.js'
export default {
data() {
return {
query: ''
}
},
methods: {
_debounce_: debounce(function(newQuery) {
this.$emit('query', newQuery)
}, 500),
},
watch: {
query(newQuery) {
this._debounce(newQuery)
}
},
}
</script>
debounce函数中不要使用箭头函数,因为箭头函数不能使用call。
注意,不应该使用箭头函数来定义 method 函数 (例如
plus: () => this.a++
)。理由是箭头函数绑定了父级作用域的上下文,所以this
将不会按照期望指向 Vue 实例,this.a
将是 undefined。
debounce文件:
export default function debounce(fun, delay) {
let timer
return function(...args) {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fun.apply(this, args)
}, delay)
}
}
两种方式都是使用闭包保存定时器,来实现防抖的目的。
Vue官方文档
有点BFS味道
/**
* @param {string} s
* @return {string}
*/
var decodeString = function (s) {
//得到数字和字符串
let reg = /(\d+)\[([a-zA-Z]+)\]/g
//检测是否完成
let all = /\[.*\]/
//循环直到完成编码
while (all.test(s)) {
s = s.replace(reg, (match, p1, p2) => p2.repeat(~~p1))
}
return s
}
作者:ybf20181212
链接:https://leetcode-cn.com/problems/decode-string/solution/jszheng-ze-by-ybf20181212/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
正则的可视化
API详细可以查看MDN
栈的思路参考自leetcode
其实也是套娃,层层计算
var decodeString = s => {
let stack_num = []
let stack_str = []
let num = 0
let res = ''
for (const char of s) {
if (!isNaN(char)) {
num = num * 10 + ~~char
} else if (char === '[') {
stack_num.push(num)
stack_str.push(res)
res = ''
num = 0
} else if (char === ']') {
res = stack_str.pop() + res.repeat(stack_num.pop())
} else {
res += char
}
}
return res
}
async :可选。表示应该立即下载脚本,但不应妨碍页面中的其他操作,比如下载其他资源或等待加载其他脚本。只对外部脚本文件有效。
defer :可选。表示脚本可以延迟到文档完全被解析和显示之后再执行。只对外部脚本文件有效。IE7及更早版本对嵌入脚本也支持这个属性。
引用来自《JavaScript高级程序设计(第3版) 》
原图链接https://www.growingwiththeweb.com/2014/02/async-vs-defer-attributes.html
会等待所有资源下载完毕,并按照脚本的顺序进行执行
常见的方式
会异步下载脚本,不会影响页面的解析。并且执行时,不会按照脚本的顺序而是谁先下载完谁先执行。
异步下载脚本,在文档渲染完之后,在 DOMContentLoaded时间触发前,按照脚本的顺序进行执行
表现与只使用async相同。
能够保持平衡的BST
根据左右子树的高度差(大于2),来解决旋转的类型。
旋转类似链表的操作(断链插入)
将目标元素的值与比目标元素大一位的元素进行替换=>更新目标:删除目标元素大一位的元素
大小顺序: a < b < c < d < f < g
矩形的高度代表了树的高度
红色=>蓝色:代表了节点的变化
转化前:
转化后:
转化前:
转化后:
转化1前:
转化1后:
转化2前:
转化2后:
转化1前:
转化1后:
转化2前:
转化2后:
参考修改代码:
class AVLNode {
constructor(val) {
this.val = val
this.left = null
this.right = null
this.height = 1
}
getKey() {}
}
const BLANCE_STATE = {
UNBALANCE_LEFT: 2,
UNBALANCE_RIGHT: -2,
SLIGHT_UNBALANCE_LEFT: 1,
SLIGHT_UNBALANCE_RIGHT: -1,
BALANCE: 0
}
class AVLTree {
constructor() {
this.root = null
}
//RR
_leftRotate(node) {
const res = node.right
;[res.left, node.right] = [node, res.left]
this._updateHeigh(node)
return res
}
//LL
_rightRotate(node) {
const res = node.left
;[res.right, node.left] = [node, res.right]
this._updateHeigh(node)
return res
}
//LR
_leftRightRotate(node) {
node.left = this._updateHeigh(this._leftRotate(node.left))
return this._rightRotate(node)
}
//RL
_rightLeftRotate(node) {
node.right = this._updateHeigh(this._rightRotate(node.right))
return this._leftRotate(node)
}
_getHeight(node) {
return node !== null ? node.height : 0
}
_updateHeigh(node) {
node.height =
Math.max(this._getHeight(node.left), this._getHeight(node.right)) + 1
return node
}
insert(val) {
this.root = this._insertNode(this.root, val)
}
_insertNode(node, val) {
if (node === null) return new AVLNode(val)
if (node.val === val) return node
if (node.val < val) {
node.right = this._insertNode(node.right, val)
} else if (node.val > val) {
node.left = this._insertNode(node.left, val)
}
return this._toBalance(node)
}
_toBalance(node) {
if (node === null) return null
const BalanceState = this._getBalanceState(node)
if (BalanceState === BLANCE_STATE.UNBALANCE_LEFT) {
const leftNodeBalanceState = this._getBalanceState(node.left)
if (leftNodeBalanceState == BLANCE_STATE.SLIGHT_UNBALANCE_RIGHT) {
return this._updateHeigh(this._leftRightRotate(node))
} else {
return this._updateHeigh(this._rightRotate(node))
}
} else if (BalanceState === BLANCE_STATE.UNBALANCE_RIGHT) {
const rightNodeBalanceState = this._getBalanceState(node.right)
if (rightNodeBalanceState == BLANCE_STATE.SLIGHT_UNBALANCE_LEFT) {
return this._updateHeigh(this._rightLeftRotate(node))
} else {
return this._updateHeigh(this._leftRotate(node))
}
}
return this._updateHeigh(node)
}
_getBalanceState(node) {
return this._getHeight(node.left) - this._getHeight(node.right)
}
remove(val) {
if (this.search(val) === null) return false
this.root = this._removeNode(this.root, val)
return true
}
_removeNode(node, val) {
if (node.val > val) node.left = this._removeNode(node.left, val)
else if (node.val < val) node.right = this._removeNode(node.right, val)
else if (node.val == val) {
if (node.left == null) return node.right
else if (node.right == null) return node.left
else {
let next = this.getNext(val)
node.val = next.val
// 转为删除next节点
node.right = this._removeNode(node.right, node.val)
}
}
return this._doBalance(node)
}
search(val) {
let node = this.root
while (node) {
if (node.val === val) return node
else if (node.val > val) node = node.right
else node = node.left
}
return null
}
getNext(val) {
let res = null,
node = this.root
while (node) {
if (node.val <= val) node = node.right
else if (node.val > val) {
res = node
node = node.left
}
}
return res
}
}
function arrayToBBST(nums) {
let avlTree = new AVLTree()
for (let i = 0; i < nums.length; i++) {
avlTree.insert(nums[i])
}
return avlTree
}
Array.prototype._reduce = function (fn, init) {
//类型检测
let sum = init === undefined ? 0 : init
const list = this
for (let i = 0; i < list.length; i++) {
sum = fn.call(this, sum, list[i], i, list)
}
return sum
}
console.log(
[1, 2, 3]._reduce(function (pre, curr) {
return pre + curr
})
)
关于forEach能不能提前结束循环:不能。
我对此的理解:执行的回调函数中,不能控制父级函数的执行。只能结束当前的函数调用栈,然后将执行权交给父级。
let a = [1, 2, '11']
Array.prototype._forEach = function (fn, thisArg) {
//类型判断
const list = this
for (let i = 0; i < list.length; i++) {
fn.call(this, list[i], i, arr)
}
}
function n() {
a._forEach((v, i) => {
console.log(v)
return
})
}
n()
Cookie 使基于无状态的HTTP协议记录稳定的状态信息
Cookie 主要用于以下三个方面MDN:
总结一句话:保存用户信息,方便发送到服务器
每个域名下的 Cookie 的大小最大为 4KB(我们可以写个代码测试一哈
function setcookie(name, value, time, path, domain) {
let exp = new Date();
exp.setTime(exp.getTime() + time);
document.cookie = name + "=" + escape(value) + ";expires=" + exp.toUTCString() + ";path=" + path + ";domain=" + domain;
}
let str = ""
for (let i = 0; i < 4000; i++) {
str += i
setcookie(i, str, 86400000, '/', "ybf.com")
}
具体实测数据:
每个域名下的 Cookie 数量最多为???个。(也自己写个代码测试一哈)
function setCookie(name, value) {
document.cookie = name + "=" + value
}
for (let i = 1; i <= 300; i++) {
setCookie(i + 'cookie', "value" + i);
}
console.log(document.cookie.split(" ").length)
Chrome、Edge =>(大该都在 150 - 180 )
Firefox => 300 个
Safair => 300 个
cookie的基本格式为key=value
,设置这些属性时,属性之间由一个分号
和一个空格
隔开。
字符串中逗号、分号、空格
被当做了特殊符号(相当于代码中的保留字)
实测分号
必须转码,会带来一些列问题。逗号、空格
好像没啥影响
(1)escape() 与 unescape()
被废弃,MDN已不推荐使用
(2)encodeURIComponent() 与 decodeURIComponent()
使用encodeURIComponent()对value进行转码
console.log(encodeURIComponent("yyy,y;=y y"));//yyy%2Cy%3B%3Dy%20y
console.log(escape("yyy,y;=y y"));//yyy%2Cy%3B%3Dy%20y
console.log(decodeURIComponent("yyy%2Cy%3B%3Dy%20y"));//yyy,y;=y y
console.log(unescape('yyy%2Cy%3B%3Dy%20y'));//yyy,y;=y y
maxAge
标示在 Cookie 失效之前需要经过的时间。(主推)
expires
标示 Cookie 的最长有效时间。(兼容)
expires
和maxAge
决定了 Cookie 存活时间
expires
和maxAge
的单位都是毫秒
设置时间都是 GMT 时间,不是当地时间
Domain
标识指定了哪些主机可以接受 Cookie。
Path
标识指定了主机下的哪些路径可以接受 Cookie。
Domain
和 Path
组合决定了 Cookie 应该发送给哪些URL。
标记为 Secure
的 Cookie 只通过被 HTTPS 协议加密过的请求发送给服务端
标记为 httpOnly
的 Cookie 不能被客户端 JavaScript 脚本调用
Secure
和HttpOnly
加强了 Cookie 的安全性
SameSite
属性用来限制第三方 Cookie。
可以使用封装好的库(例如:js-cookie )。也可以自己手动实现一下代码改自js-cookie.js的使用
function setcookie(name, value, time, path, domain) {
let exp = new Date();
exp.setTime(exp.getTime() + time);
document.cookie = name + "=" + escape(value) + ";expires=" + exp.getUTCDate() + ";path=" + path + ";domain=" + domain;
}
setcookie("age", "18", 6400, '/', "ybf.com")
使用 Koa 设置 Cookie 。会在 Header 中添加 Set-Cookie
ctx.cookies.set('id', 'apply', {
httpOnly: false
})
ctx.cookies.set('name', 'xiaomin', {
httpOnly: true
})
Cookie 挂载在document.cookie
上,你可以在控制台打印下试试。( Cookie没有开启httpOnly,就能拿到id=value)
我现在有2个 Cookie,一个开启 httpOnly,另一个不开启。
可以看到我们只能拿到没有开启 httpOnly 的 Cookie
你本地存在 Cookie 时,在服务端设置获得
ctx.cookies.get('age')
浏览器自动帮我们添加上 Cookie 这个 Header
答案:不能。
W3C的 xhr 标准中做了限制,规定客户端无法获取 response 中的 Set-Cookie
、Set-Cookie2
这2个字段,无论是同域还是跨域请求。
和创建 Cookie 步骤一致,可以改变 value 和 maxAge / expires。
直接重新设置即可。可以改变 value,maxAge / expires,httpOnly,secure
expires 设置为过期即可
直接重新设置即可,maxAge 设置为-1,expires 设置为过期时间
需要保持 path 和 domain 一致。
如果改变 path。原 Cookie 修改失败,会创建一个新 Cookie。
对一个已经存在对 Cookie,你不可以设置成其他二级域名
浏览器关闭之后它会被自动删除,也就是说它仅在会话期内有效。
会话期 Cookie 不需要指定过期时间(Expires
)或者有效期(Max-Age
)。
指定Max-Age
为 0 时,也是会话 Cookie
默认为当前文档的主机(不包含子域名)。如果指定了Domain
,则一般包含子域名。
path默认为\
。
第一条为指定域名,第二条不指定域名
在二级域名 ybf.com
设置 Cookie,去子域名 test.ybf.com
获取 Cookie
子域名将无法获取父域名设置的cookie
先在 ybf.com
设置 Cookie。
; (function (name, value, time, path, domain) {
let exp = new Date();
exp.setTime(exp.getTime() + time);
document.cookie = name + "=" + value + ";expires=" + exp.toUTCString() + ";path=" + path + ";domain=" + domain;
})("ybf", "ybf", 86400000, '/', "ybf.com")
; (function (name, value, time, path, domain) {
let exp = new Date();
exp.setTime(exp.getTime() + time);
document.cookie = name + "=" + value + ";expires=" + exp.toUTCString() + ";path=" + path + ";domain=" + domain;
})("acc", "acc", 86400000, '/', "acc.com")
域名相同毫无疑问可以设置上,我们去 acc.com
看看是否设置上了
答案:不可以。
ctx.Cookies.set('ybf', 'ybf', {
maxAge: 86400000,
domain: 'ybf.com',
httpOnly: false
})
ctx.Cookies.set('acc', 'acc', {
maxAge: 86400000,
domain: 'acc.com',
httpOnly: false
})
答案:不可以。
你设置时会出警告,设置不上。This Set-Cookie was blocked because its Domain attribute was invalid with regards to the current host url。
我们执行以下代码,在test.ybf.com
设置ybf.com
。
; (function (name, value, time, path, domain) {
let exp = new Date();
exp.setTime(exp.getTime() + time);
document.Cookie = name + "=" + value + ";expires=" + exp.toUTCString() + ";path=" + path + ";domain=" + domain;
})("ybf", "ybf", 86400000, '/', "ybf.com")
答案:可以。而且设置完 test.ybf.com
可以直接收到 Cookie。
我们执行以下代码,在 ybf.com
设置 test.ybf.com
; (function (name, value, time, path, domain) {
let exp = new Date();
exp.setTime(exp.getTime() + time);
document.cookie = name + "=" + value + ";expires=" + exp.toUTCString() + ";path=" + path + ";domain=" + domain;
})("ybf", "ybf", 86400000, '/', "test.ybf.com")
答案:不可以
这里需要你频繁的改host文件,推荐使用SwitchHosts!
我使用的使用浏览器版本
Chrome 版本 83.0.4103.61
Safair 版本13.1.1
Edge 版本 81.0.416.72
Firefox 77.0
原文:https://www.cnblogs.com/zhloong/p/installohmyzsh.html
原作者:zhloong
安装zsh
sudo yum update
yum install zsh
安装git
yum install git
切换默认shell
chsh -s /bin/zsh
clone from GitHub
git clone git://github.com/robbyrussell/oh-my-zsh.git ~/.oh-my-zsh
复制默认.zshrc
cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting
zsh-syntax-highlighting
git clone git://github.com/zsh-users/zsh-autosuggestions $ZSH_CUSTOM/plugins/zsh-autosuggestions
zsh-autosuggestions
在浏览器中是window
在node中是global
全局作用域中挂载着一切预设的内置对象
var a = { name: '1' }
console.log(a)//已经挂载在全局变量上
function name(params) {
var a = { name: '1' }
b = a
}
name()
console.log(b)//已经挂载在全局变量上
每一个函数执行时,都会产生一个函数作用域
外部不能直接访问函数作用域中的变量(可以实现私有变量)
function A() {
var a = '函数内部'
console.log(a)//'函数内部'
}
A()
console.log(a)//报错
什么是块级作用域?
由{}封闭的代码块,
if (true){
int a1=11;
}
System.out.print((a1));//报错
老前辈var就不会形成块级作用域(因为var会变量提升)
经典老例子
for(var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i); // 5 5 5 5 5
}, 200);
};
es6中的let和const声明时就会绑定在最近的作用域上(包含块级)
for (let i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i) // 0 1 2 3 4
}, 200)
}
静态作用域:函数的作用域在函数定义的时候就决定了
动态作用域:函数的作用域是在函数调用的时候才决定
function foo(){
console.log(a);
}
function bar(){
var a = 3;
foo();
}
var a = 2;
bar();//答案是?
如果这里输出的是2,则是静态作用域。输出3就是动态作用域
js是静态作用域,所以输出2(bash 是动态作用域)
我们可以看到函数声明时,作用域已经确定
对引用类型(Object)进行拷贝时,不会重新开辟新的地址而是直接复制地址。
let obj = {
a: 'a',
b: 'b',
c: {
c1: 'c1',
c2: 'c2'
}
}
function copy(object) {
let _obj = {}
for (const key of Object.keys(obj)) {
_obj[key] = object[key]
}
return _obj
}
let temp = copy(obj)
temp.c.c1 = 'test'
console.log(obj.c.c1)//test
console.log(temp.c.c1)//test
我们拷贝的对象中的属性改变,原对象中的属性也会改变。
此方法可以实现深拷贝,但是有缺点:
function A() {
this.a = 'A'
}
A.prototype.YBF = function (params) {}
let a = new A()
let aa = JSON.parse(JSON.stringify(a))
let obj1 = { name: 'obj1' }
let obj2 = { name: 'obj2' }
obj1.a = obj2
obj2.a = obj1
let obj3 = {
b: obj1
}
JSON.parse(JSON.stringify(obj3))
let list = [1, 1, 1, 1]
let set = new Set(list)
let rex = new RegExp('adb')
let map = new Map([{ 1: 1 }, { 2: 2 }])
let a = {
list: list,
set: set,
rex: rex,
map: map
}
let aa = JSON.parse(JSON.stringify(a))
试了下就数组可以
isType
:来判断参数的的类型
hasClone来存储已经创建的地址,避免循环
使用递归来层层进行拷贝,完成深拷贝
const isType = (obj, type) => {
if (typeof obj !== 'object') return false
const typeString = Object.prototype.toString.call(obj)
let flag
switch (type) {
case 'Array':
flag = typeString === '[object Array]'
break
case 'Set':
flag = typeString === '[object Set]'
break
case 'Map':
flag = typeString === '[object Map]'
break
default:
flag = false
}
return flag
}
const deepClone = parent => {
const hasClone = new Set()
const _deepClone = parent => {
if (parent === null) return null
if (hasClone.has(parent)) {
return parent
}
if (typeof parent !== 'object') return parent
let child
hasClone.add(parent)
if (isType(parent, 'Array')) {
child = []
for (const iterator of parent) {
child.push(_deepClone(iterator))
}
} else if (isType(parent, 'Map')) {
child = new Map()
for (const [key, value] of parent) {
child.set(key, _deepClone(value))
}
} else if (isType(parent, 'Set')) {
child = new Set()
for (const iterator of parent) {
child.add(_deepClone(iterator))
}
} else {
proto = Object.getPrototypeOf(parent)
child = Object.create(proto)
for (const key in parent) {
child[key] = _deepClone(parent[key])
}
}
return child
}
return _deepClone(parent)
}
在less中使用calc函数进行不同长度单位的计算时,后面值的单位会被忽略,例如:
height: calc(100vh - 50px);
结果等于50vh
解决方法:在表达式前添加"~"
height: calc(~"100vh - 50px");
常见的表单提交 Header:
原生表单会以key=value的方式拼接在url后面。
例如以下代码:
<form action="/get" method="GET" >
<input type="text" name="name1" id="id1" value="">
<button type="submit">GET</button>
</form>
点击提交后,跳转到新页面并看到url变化。
这是原生表单的默认的提交格式。
<form action="/post" method="POST">
<input type="text" name="name1" value="">
<input type="text" name="name2" value="">
<input type="text" name="name3" value="">
<button type="submit">POST</button>
</form>
我们可以在后端拿到类似url的数据格式。
解析即可。
可以用来上传文件。
<form action="/post" method="POST" enctype="multipart/form-data">
<input type="text" name="name1" value="">
<input type="text" name="name2" value="">
<input type="text" name="name3" value="">
<button type="submit">POST</button>
</form>
点击按钮上传时,会发现 Content-Type: 的值有些不一样。
指明 multipart/form-data
很正常。但是后面的 boundary=---WebKitFormBoundaryCkL35Iut5A3hdvGD
就比较奇怪。
在MDN中解释到:
boundary:用于封装消息的多个部分的边界
可以看到我们上传的格式相对于 x-www-form-urlencoded
有所改变。
<form action="/post" method="POST" enctype="multipart/form-data">
<input type="file" name="img">
<button type="submit">POST</button>
</form>
我选择 一张图片进行上传。
注意接收时,选择二进制流而不是UTF-8。
以下代码来自Node.js HTTP服务器中不依赖第三方模块的文件、图片上传
function parseFile (req, res) {
req.setEncoding('binary');
var body = ''; // 文件数据
var fileName = ''; // 文件名
// 边界字符串
var boundary = req.headers['content-type'].split('; ')[1].replace('boundary=','');
req.on('data', function(chunk){
body += chunk;
});
req.on('end', function() {
var file = querystring.parse(body, '\r\n', ':')
// 只处理图片文件
if (file['Content-Type'].indexOf("image") !== -1)
{
//获取文件名
var fileInfo = file['Content-Disposition'].split('; ');
for (value in fileInfo){
if (fileInfo[value].indexOf("filename=") != -1){
fileName = fileInfo[value].substring(10, fileInfo[value].length-1);
if (fileName.indexOf('\\') != -1){
fileName = fileName.substring(fileName.lastIndexOf('\\')+1);
}
console.log("文件名: " + fileName);
}
}
// 获取图片类型(如:image/gif 或 image/png))
var entireData = body.toString();
var contentTypeRegex = /Content-Type: image\/.*/;
contentType = file['Content-Type'].substring(1);
//获取文件二进制数据开始位置,即contentType的结尾
var upperBoundary = entireData.indexOf(contentType) + contentType.length;
var shorterData = entireData.substring(upperBoundary);
// 替换开始位置的空格
var binaryDataAlmost = shorterData.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
// 去除数据末尾的额外数据,即: "--"+ boundary + "--"
var binaryData = binaryDataAlmost.substring(0, binaryDataAlmost.indexOf('--'+boundary+'--'));
// 保存文件
fs.writeFile(fileName, binaryData, 'binary', function(err) {
es.end('图片上传完成');
});
} else {
res.end('只能上传图片文件');
}
});
}
从左扫一遍,累乘得到一个left数组
从右扫一遍,累乘得到一个right数组
把nums [ i ] 的left [ i -1 ] * right [ i +1 ]得到答案数组
var productExceptSelf = function (nums) {
let left = [1]
for (let i = 0; i < nums.length; i++) {
left[i + 1] = left[i] * nums[i]
}
left.push(1)
let right = [1]
right[nums.length + 1] = 1
for (let i = nums.length; i > 0; i--) {
right[i] = right[i + 1] * nums[i - 1]
}
let res = []
for (let i = 0; i < left.length - 2; i++) {
res[i] = left[i] * right[i + 2]
}
return res
}
使用puppeteer来爬取漫画
每章漫画的地址http://xxx.com/m12942/
img的地址为http://xxx.com/1/64/12942/6_4535.jpg?cid=12942&key=8321fe3a9daf63df94cd1cc9503a870c&uk=
img的地址暴露,没有在header中进行校验,使用该地址可以直接拿到图片。
我们可以使用puppeteer来进行自动化操作,存贮一章漫画的所有img链接。
有了img链接,就可以自己写个node脚本请求下载
省去了我们自己分析http的参数。
const puppeteer = require('puppeteer')
async function getUrl() {
const browser = await puppeteer.launch({
executablePath: '/Applications/Chromium.app/Contents/MacOS/Chromium',
headless: true,
defaultViewport: { width: 1440, height: 1000 }
})
const page = await browser.newPage()
page.setUserAgent(
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36'
)
await page.goto(`http://xxx.com/m12942/`)
//存储所有img链接
const url_list = []
await page.waitForSelector('.bottom-page2')
const str = await page.$eval('.bottom-page2', item => item.textContent)
//获取img的数量
let index = parseInt(str.match(/\-(\d+)/)[1], 10)
console.log(index)
while (index > 0) {
await page.waitForSelector('img[id=cp_image]')
await page.waitForSelector('.bottom-right :nth-child(4)')
const url_item = await page.$eval('img[id=cp_image]', item => item.src)
url_list.push(url_item)
//跳转页面
await page.evaluateHandle(() => Promise.resolve(window.ShowNext()))
index--
}
await browser.close()
return url_list
}
module.exports = getUrl
<a href="javascript:ShowNext();"><img src=""></a>
第一次看见这样的a标签。可以在点击a标签时,直接触发js函数。
强缓存:状态码是200,服务器并不会收到请求,浏览器直接使用缓存。
协议缓存:服务器收到请求并进行比较处理,如果文件没有改变就返回状态码 304,否则加上 Haeder 标记、状态码 200 、并重新返回资源。
强缓存的优先级高于协议缓存
The Last-Modified
是一个响应首部,其中包含源头服务器认定的资源做出修改的日期及时间。 它通常被用作一个验证器来判断接收到的或者存储的资源是否彼此一致。由于精确度比 ETag
要低,所以这是一个备用机制。包含有 If-Modified-Since
或 If-Unmodified-Since
首部的条件请求会使用这个字段。
语法
Last-Modified: <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT
缺陷
ETag
HTTP响应头是资源的特定版本的标识符。这可以让缓存更高效,并节省带宽,因为如果内容没有改变,Web服务器不需要发送完整的响应。而如果内容发生了变化,使用ETag有助于防止资源的同时更新相互覆盖(“空中碰撞”)。
语法
ETag: W/"<etag_value>"
ETag: "<etag_value>"
if-modified-since
if-modified-since
与最后一次时间进行比较。如果没有改变就返回 304 ,如果不一致则返回 200 并加上 last-modified
告诉浏览器这个文件最后一次修改时间。ctx.res.setHeader('Cache-control', 'no-cache')
const ifModifiedSince = ctx.req.headers['if-modified-since']
let stats = fs.statSync('./cache/png.png')
let change_time = stats.mtime.toUTCString()
if (change_time === ifModifiedSince) {
ctx.res.writeHead(304)
} else {
ctx.res.setHeader('last-modified', change_time)
ctx.type = 'image/png'
ctx.res.writeHead(200)
let img = fs.readFileSync('./cache/png.png')
ctx.res.write(img, 'binary')
ctx.res.end()
}
与上次的步骤基本一致。
ctx.res.setHeader('Cache-control', 'no-cache')
const ifNoneMatch = ctx.req.headers['if-none-match']
const hash = crypto.createHash('md5')
let css = fs.readFileSync('./cache/test.css', 'utf-8')
hash.update(css)
const etag = `"${hash.digest('hex')}"`
if (ifNoneMatch === etag) {
ctx.res.writeHead(304)
} else {
ctx.res.setHeader('etag', etag)
ctx.type = 'text/css'
ctx.res.writeHead(200)
ctx.res.write(css, 'binary')
ctx.res.end()
}
ctx.res.setHeader('Cache-control', 'no-cache')
const ifNoneMatch = ctx.req.headers['if-none-match']
let stats = fs.statSync('./cache/png1.png')
var mtime = stats.mtime.getTime().toString(16)
var size = stats.size.toString(16)
let change_time = 'W/' + `"${size}-${mtime}"`
if (change_time === ifNoneMatch) {
ctx.res.writeHead(304)
} else {
ctx.res.setHeader('etag', change_time)
ctx.type = 'image/png'
ctx.res.writeHead(200)
let img = fs.readFileSync('./cache/png1.png')
ctx.res.write(img, 'binary')
ctx.res.end()
}
用户行为 | 强缓存 | 协议缓存 |
---|---|---|
普通刷新 | Yes | Yes |
强制刷新 | No | No |
前进后退 | ?? | ?? |
页面跳转 | Yes | Yes |
新标签页输入网址 | Yes | Yes |
页面的所有资源都被浏览器缓存了,协议缓存并没有给服务器发送请求,没有开启强缓存的资源也被缓存。
当你指定 Cache-control:no-store
时,每次还会请求资源。
可以实现大顶堆或者小顶堆。
可以便利的解决一类 topK 问题。
以小顶堆为例:
根节点小于所有子节点
用列表来模拟树的结构
插入:节点放在数组末尾
插入后与父节点进行比较
重复该操作,直到父节点比子节点小
弹出根节点
将根节点与数组最后一位进行交换
然后pop()弹出当前堆的最小值
进行重建
左右子节点选出最小数
与根节点进行比较
根节点>最小数,交换节点,递归重建
根节点<最小数,结束
function swap(arr, i, j) {
;[arr[i], arr[j]] = [arr[j], arr[i]]
}
class MinHeap {
constructor(arr = []) {
this.container = []
if (Array.isArray(arr)) {
arr.forEach(this.insert.bind(this))
}
}
insert(data) {
const { container } = this
container.push(data)
let index = container.length - 1
while (index) {
let parent = Math.floor((index - 1) / 2)
if (container[index] >= container[parent]) {
break
}
swap(container, index, parent)
index = parent
}
}
extract() {
const { container } = this
if (!container.length) {
return null
}
swap(container, 0, container.length - 1)
const res = container.pop()
const length = container.length
let index = 0,
exchange = index * 2 + 1
while (exchange < length) {
let right = index * 2 + 2
if (right < length && container[right] < container[exchange]) {
exchange = right
}
if (container[exchange] >= container[index]) {
break
}
swap(container, exchange, index)
index = exchange
exchange = index * 2 + 1
}
return res
}
top() {
if (this.container.length) return this.container[0]
return null
}
}
function getMin(arr) {
const heap = new MinHeap(arr.slice())
for (let i = 0; i < arr.length; ++i) {
console.log(heap.extract())
}
}
getMin([4, 5, 8, 2, 1, 10, 11, 5, 7, 9, 5, 7])
使用npm安装npm i puppeteer
。
执行以下代码
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
await page.screenshot({path: 'example.png'});
await browser.close();
})();
报错 Error: Could not find browser revision 768783. Run "npm install" or "yarn install" to download a browser binary.
需要手动安装Chromium
下载地址:https://download-chromium.appspot.com/
然后更改代码指向Chromium.app路径
const puppeteer = require('puppeteer')
;(async () => {
const browser = await puppeteer.launch({
executablePath: '/Applications/Chromium.app',
headless: false
})
const page = await browser.newPage()
await page.goto('https://example.com')
await page.screenshot({ path: 'example.png' })
await browser.close()
})()
报错 UnhandledPromiseRejectionWarning: Error: Failed to launch the browser process! spawn /Applications/Chromium.app/ EACCES
更改路径 executablePath: '/Applications/Chromium.app/Contents/MacOS/Chromium'
const puppeteer = require('puppeteer')
;(async () => {
const browser = await puppeteer.launch({
executablePath: '/Applications/Chromium.app/Contents/MacOS/Chromium',
headless: false
})
const page = await browser.newPage()
await page.goto('https://example.com')
await page.screenshot({ path: 'example.png' })
await browser.close()
})()
跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。
引用来自MDN
两者的区别在于:是否需要发送预检请求。
满足以下条件就可以不需要发送预检请求
使用axios库来简化ajax操作。
当我们像平常一样发送 GET 请求时,会出现跨域报错。
解决的方法就是加上Access-Control-Allow-Origin:*
这个 Header。
可以看到我们的跨域请求正常收到了。
其实你可以通过服务端的日志看到,其实服务端是正常返回的。浏览器帮你拦截了这个请求。
axios 需要打开 withCredentials: true (默认为false)
const api = axios.create({
timeout: 1000,
withCredentials: true
});
可以看到报错了。这里需要我们将Access-Control-Allow-Origin:
指定明确的域名。
还是报错这里需要我们加上另一个 Header Access-Control-Allow-Credentials
为 true。
可以看到请求正常了。
你在服务端设置 Header
ctx.res.setHeader('X-Custom-Header',"TestCors")
然后客户端打印下可以拿到的 Header
可以看到确实没有拿到我们需要的 Haeder。我们需要指定 Access-Control-Expose-Headers
。
ctx.res.setHeader('Access-Control-Expose-Headers', 'X-Custom-Header')
客户端再次打印可以拿到的 Header。
可以看到我们需要的 Header已经正常了。
复杂请求需要在正式请求前加一个 Haeder。
我们可以看看一个服务端的日志。
发送了2次请求:一次预检、一次正式请求。
我们把客户端和服务端都设置成 PUT。
我们需要将 Access-Control-Allow-Methods
设置成 PUT
。(这里最好返回所有支持的方法,以免多次预检)
我们可以在 Network 中看到 OPTIONS 请求。
可以看到 PUT 请求请求正常。
我们可以设置 Access-Control-Max-Age
来设置预检的有效期。
ctx.res.setHeader('Access-Control-Max-Age', '30')
打印服务端的日志
当超过设置的时间后,再次请求。可以看到又请求了。
同上
我们在客户端设置 Header,然后在服务端获取。
const api = axios.create({
timeout: 1000,
headers: { 'X-Custom-Header': 'foobar' }
});
我们可以看到报错了。
这里我们需要指定 Access-Control-Allow-Headers
ctx.res.setHeader('Access-Control-Allow-Headers', 'X-Custom-Header')
//省略一些代码
ctx.req.headers['x-custom-header']
可以看到我们获取了客户端设置的 Header。
引用MDN来自:请求头 **Access-Control-Request-Headers
**出现于 preflight request(预检请求)中,用于通知服务器在真正的请求中会采用哪些请求头。
没试出作用
引用来自我知道的跨域与安全
geneartor
函数的特征:function
与函数名之间会有个*号,函数内部有 yield
表达式。
执行 Generator
函数会返回一个迭代器。
当然我们可以使用 for...of...
或者扩展运算符
进行操作。
function* fruit() {
yield 'apple'
yield 'pear'
return 'ending'
}
for (const iterator of fruit()) {
console.log(iterator) //apper pear
}
let a = [...fruit()]
generator
大致的执行流程:
执行Iterator
的next()
yield
关键字,返回一个对象{ value: value, done: false }
value
:紧跟在 yield
关键字后的表达式计算值done
:generator
函数未执行完毕执行return
时,返回一个对象{ value: value, done: true }
value
:return
将要返回的值done
:generator
函数执行完毕下面使用一个例子:
function* fruit() {
yield 'apple'
yield 'pear'
return 'ending'
}
var _fruit = fruit()
_fruit.next()//{ value: 'apple', done: false }
_fruit.next()//{ value: 'pear', done: false }
_fruit.next()//{ value: 'ending', done: true }
yield
关键字只能在 generator
函数中使用。不可以跨函数。function* fruit() {
let arr = ['apple', 'pear', 'ending']
arr.forEach(item=>{
yield item
})
return
}
这样改变函数将会报错。
yield
是”懒执行“,只会在运行需要执行时才执行。function* fruit() {
yield 'pear'
yield console.log('执行')
return 'ending'
}
var _fruit = fruit()
_fruit.next()
_fruit.next()
_fruit.next()
yield
放在包含在另一个表达式中需要()
function* fruit() {
yield 1 + (yield 2)
}
var _fruit = fruit()
_fruit.next()
_fruit.next(2)
_fruit.next()
next
可以启动或者继续执行generator
函数。
next
可以携带返回值,作为上次执行 yield
的返回值。
function* fruit() {
let a = yield 1
let b = yield 2
return a + b
}
var _fruit = fruit()
_fruit.next()
_fruit.next('a')
_fruit.next('b')
throw
用于在状态机内部抛出错误。这个是挂载在原型链上的throw
方法与throw
不同。当使用throw
后进入执行状态,可以继续往下执行。
function* fruit() {
try {
let a = yield 1
let b = yield 2
} catch (error) {
console.log('error=>' + error)
} finally {
return 'end'
}
}
var _fruit = fruit()
_fruit.next() //{ value: 1, done: false }
_fruit.throw('a') //{ value: 'end', done: true }
_fruit.next('b') //{ value: undefined, done: true }
如果状态机函数内部没有try
,则会在外部捕获。如果外部也没有try
,则程序报错终止。
function* fruit() {
let a = yield 1
let b = yield 2
let c = yield 3
return 'end'
}
var _fruit = fruit()
try {
_fruit.next()//Object {value: 1, done: false}
_fruit.next('b')//Object {value: 2, done: false}
_fruit.throw('a')
} catch (error) {
console.log('error=>' + error)
}
在开始时就抛出错误,这时generator
函数还未开始执行,直接在外部捕获。如果外部也没有try
,则程序报错终止。
function* fruit() {
try {
let a = yield 1
let b = yield 2
let c = yield 3
return 'end'
} catch (error) {
console.log('内部' + error)
}
}
var _fruit = fruit()
try {
console.log(_fruit.throw('a')) //外部a
console.log(_fruit.next())
console.log(_fruit.next('b'))
} catch (error) {
console.log('外部' + error)
}
如果没有在内部进行捕获,就提前结束generator
函数。
function* fruit() {
let a = yield 1
let b = yield 2
let c = yield 3
return 'end'
}
var _fruit = fruit()
try {
console.log(_fruit.next()) //Object {value: 1, done: false}
console.log(_fruit.throw('a'))
} catch (error) {
console.log('外部' + error)
}
console.log(_fruit.next('b')) //Object {value: undefined, done: true}
在外部提前结束generator
函数。
使用return
函数返回一个对象。value
为return
中的参数,done
为true
。
function* fruit() {
let a = yield 1
let b = yield 2
let c = yield 3
return 'end'
}
var _fruit = fruit()
console.log(_fruit.next()) //{ value: 1, done: false }
console.log(_fruit.return('a')) //{ value: 'a', done: true }
console.log(_fruit.next('b')) //{ value: undefined, done: true }
会执行finally
语句中的内容。
function* fruit() {
try {
let a = yield 1
let b = yield 2
} finally {
let c = yield 3
}
}
var _fruit = fruit()
_fruit.next() //{ value: 1, done: false }
_fruit.return('a') //{ value: 3, done: false }
_fruit.next('b') //{ value: 'a', done: true }
_fruit.next() //{ value: undefined, done: true }
如果finally
中有return
,忽略参数
function* fruit() {
try {
let a = yield 1
let b = yield 2
} finally {
let c = yield 3
return 'end'
}
}
var _fruit = fruit()
_fruit.next() //{ value: 1, done: false }
_fruit.return('a') //{ value: 3, done: false }
_fruit.next('b') //{ value: 'end', done: true }
_fruit.next() //{ value: undefined, done: true }
当一个资源加载另一个资源时,协议、域名、端口有一项或一项以上不同
提高浏览器的安全性
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script>
var handle = function (data) {//=>1
alert(data)
}
var url = 'http://127.0.0.1:3000/jsonp?callback=handle&&city=nanjin'//=>2.1
var script = document.createElement('script')//=>2.2
script.setAttribute('src', url)//=>2.2
document.getElementsByTagName('head')[0].appendChild(script)//=>2.3
</script>
</head>
<body></body>
</html>
const Koa = require('koa')
const app = new Koa()
const Router = require('koa-router')
let page = new Router()
page.get('/jsonp', (ctx, next) => {
let city = ctx.request.query.city
let handle = ctx.request.query.callback
let num = 18
ctx.body = `${handle}('${city}:温度是${num}')`
})
app.use(page.routes()).use(page.allowedMethods())
app.listen(3000)
不需要设置header,解除CORS的限制
支持老浏览器
只能发起get请求
不可以存放敏感数据
不安全容易受到xss攻击
CommonJs
是对值的浅拷贝
ES Modules
是对值对引用
参照参考文章中的方法使用webpack进行打包
Npm 下载[email protected]和webpack-cli@bate
按照webpack官网的配置Mode:none,不使用任何优化措施
module.exports = {
mode: 'none',
performance: {
hints: false
},
optimization: {
flagIncludedChunks: false,
concatenateModules: false,
splitChunks: {
hidePathInfo: false,
minSize: 10000,
maxAsyncRequests: Infinity,
maxInitialRequests: Infinity
},
noEmitOnErrors: false,
checkWasmTypes: false,
minimize: false
},
plugins: []
}
我们使用CommonJS来进行打包
在index.js中写入
const { obj, child, change } = require('./obj')
console.log(obj, child)
change()
console.log(obj, child)
在obj.js中写入
let obj = {
id: 'obj',
child: {
id: 'child'
}
}
function change() {
obj.child = {}
}
module.exports = { obj, child: obj.child, change }
然后我们执行得到以下结果
可以对象中的子对象发生了改变,证明了CommonJs是对其进行浅拷贝
使用webpack对文件进行打包,大概可以简化为以下代码
;(() => {
// webpackBootstrap
/******/ var __webpack_modules__ = [
,
/* 0 */ /* 1 */
/***/ module => {
let obj = {
id: 'obj',
child: {
id: 'child'
}
}
function change() {
obj.child = {}
}
module.exports = { obj, child: obj.child, change }
/***/
}
]
// The module cache
var __webpack_module_cache__ = {}
// The require function
function __webpack_require__(moduleId) {
// Check if module is in cache
if (__webpack_module_cache__[moduleId]) {
return __webpack_module_cache__[moduleId].exports
}
// Create a new module (and put it into the cache)
var module = (__webpack_module_cache__[moduleId] = {
// no module.id needed
// no module.loaded needed
exports: {}
})
// Execute the module function
__webpack_modules__[moduleId](module, module.exports, __webpack_require__)
// Return the exports of the module
return module.exports
}
;(() => {
const { obj, child, change } = __webpack_require__(1)
console.log(obj, child)
change()
console.log(obj, child)
})()
/******/
})()
webpack_module_cache 存储已经导出的结果,防止二次倒出
__webpack_require__将__webpack_module_cache__中的数据返回
__webpack_modules__将原来的模块文件中的代码,使用箭头函数进行包裹。(第一项为空)
PS:Js中是词法作用域,在声明时就确定了作用域
看到module.exports只是对值进行浅拷贝
我们使用CommonJS来进行打包
在index.js中写入
import { obj, change, child } from './obj.js'
console.log(obj, child)
change()
console.log(obj, child)
在obj.js中写入
export let child = {
id: 'child'
}
export let obj = {
id: 'obj',
child
}
export function change() {
child = {}
}
然后我们执行得到以下结果
使用webpack对文件进行打包,大概可以简化为以下代码
/******/ ;(() => {
// webpackBootstrap
/******/ 'use strict'
/******/ var __webpack_modules__ = [
/* 0 */
/***/ (
__unused_webpack_module,
__webpack_exports__,
__webpack_require__
) => {
__webpack_require__.r(__webpack_exports__)
/* harmony import */ var _obj_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
1
)
console.log(
_obj_js__WEBPACK_IMPORTED_MODULE_0__.obj,
_obj_js__WEBPACK_IMPORTED_MODULE_0__.child
)
Object(_obj_js__WEBPACK_IMPORTED_MODULE_0__.change)()
console.log(
_obj_js__WEBPACK_IMPORTED_MODULE_0__.obj,
_obj_js__WEBPACK_IMPORTED_MODULE_0__.child
)
/***/
},
/* 1 */
/***/ (
__unused_webpack_module,
__webpack_exports__,
__webpack_require__
) => {
__webpack_require__.r(__webpack_exports__)
__webpack_require__.d(__webpack_exports__, {
child: () => /* binding */ child,
obj: () => /* binding */ obj,
change: () => /* binding */ change
})
let child = {
id: 'child'
}
let obj = {
id: 'obj',
child
}
function change() {
child = {}
}
/***/
}
]
// The module cache
var __webpack_module_cache__ = {}
// The require function
function __webpack_require__(moduleId) {
// Check if module is in cache
if (__webpack_module_cache__[moduleId]) {
return __webpack_module_cache__[moduleId].exports
}
// Create a new module (and put it into the cache)
var module = (__webpack_module_cache__[moduleId] = {
// no module.id needed
// no module.loaded needed
exports: {}
})
// Execute the module function
__webpack_modules__[moduleId](module, module.exports, __webpack_require__)
// Return the exports of the module
return module.exports
}
/* webpack/runtime/define property getters */
;(() => {
// define getter functions for harmony exports
__webpack_require__.d = (exports, definition) => {
for (var key in definition) {
if (
__webpack_require__.o(definition, key) &&
!__webpack_require__.o(exports, key)
) {
Object.defineProperty(exports, key, {
enumerable: true,
get: definition[key]
})
}
}
}
})()
/* webpack/runtime/hasOwnProperty shorthand */
;(() => {
__webpack_require__.o = (obj, prop) =>
Object.prototype.hasOwnProperty.call(obj, prop)
})()
/* webpack/runtime/make namespace object */
;(() => {
// define __esModule on exports
__webpack_require__.r = exports => {
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
}
Object.defineProperty(exports, '__esModule', { value: true })
}
})()
// startup
// Load entry module
__webpack_require__(0)
// This entry module used 'exports' so it can't be inlined
})()
webpack_require.o确定属性是否已存在
webpack_require.d将definition上的属性挂载到exports上
关键代码:
__webpack_require__.d(__webpack_exports__, {
child: () => /* binding */ child,
obj: () => /* binding */ obj,
change: () => /* binding */ change
})
用箭头函数动态的返回相应的值。
注意:这里虽然关键代码在模块代码执行前声明,但是没有违背let的“暂时性死区“原则。因为这里的函数会在exports使用属性时触发getter时使用。
1723. 完成所有工作的最短时间
来自这道题的部分题解。
NumberOfTrailingZeros使用了德布鲁因序列来快速求二进制数末尾0的个数。
const NumberOfTrailingZeros = number => {
const num = parseInt(number).toString(2)
const multiply_De_Bruijn_position = [
0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23,
21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
]
return multiply_De_Bruijn_position[(((num & -num) * 0x077cb531) >> 27) & 31]
}
德布鲁因序列, B(k, n),是k元素构成的循环序列。所有长度为n的k元素构成序列都在它的子序列(以环状形式)中,出现并且仅出现一次。
0x077cb531(00000111011111001011010100110001)是一个B(2,6)的德布鲁因序列
000001110111110010110101001100010000
00000
00001
00011
00111
01110
11101
11011
10111
01111
11111
11110
11100
11001
10010
00101
01011
10110
01101
11010
10101
01010
10100
01001
10011
00110
01100
11000
10001
0001|0
001|00
01|000
1|0000
可以保证(x* 0x077cb531) >> 27有唯一值,这里的x代表32位数中只有1位是1。
再根据唯一值从multiply_De_Bruijn_position得到目标值。& 31 :应该是防止超过数组长度上限。
这里演示下8和16求末尾0 的个数
let i = 8 * 0x077cb531,
j = 16 * 0x077cb531
console.log(i.toString(2), j.toString(2))
const multiply_De_Bruijn_position = [
0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21,
19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
]
//8: 00111011111001011010100110001000 =>
// 00111=>multiply_De_Bruijn_position[7]:3
//16: 01110111110010110101001100010000 =>
// 01110=>multiply_De_Bruijn_position[14]:4
https://graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightMultLookup
此图引用MDN
父容器设置
子容器设置
flex-grow
, flex-shrink
与 flex-basis
space-evenly:均分空白。
space-around:首尾空白减半,其他均分。
space-between:首尾对齐,均分空白。
stretch:伸展
flex-start、center、flex-end
space-evenly
space-around
space-between
row :默认。从左到右排列
row-reverse:从右到左排列
column:从上到下排列
column-reverse:从下到上排列
column-reverse排列
center
flex-start
flex-end
baseline
stretch
center
flex-strat
flex-end
last baseline
center、flex-start、flex-end
space-between、space-around、space-evenly
stretch
baseline
center
space-between
可以看到弹性容器被分成上下2 个子项。空间平均分配,子项行高占满。
align-items以当前行高进行渲染。
可以看到 行高被压缩,每行以当前行内最高的元素为基础。
align-items以当前行高进行渲染。
不设置默认为0,数字越大,优先级越低。
<div class="father1">
<div class="son1" style="order: 4;">1</div>
<div class="son1" style="order: 3;">2</div>
<div class="son1" style="order: 2;">3</div>
<div class="son1" style="order: 1;">4</div>
</div>
默认设置为0。
它指定了flex容器中剩余空间的多少应该分配给项目
剩余的空间是flex容器的大小减去所有flex项的大小加起来的大小。如果所有的兄弟项目都有相同的flex-grow系数,那么所有的项目将获得相同的剩余空间,否则将根据不同的flex-grow系数定义的比例进行分配。
第一项flex-grow:1
第一项flex-grow:0.6
默认设置为0。为0时不参与收缩。
flex 元素仅在默认宽度之和大于容器的时候才会发生收缩,其收缩的大小是依据 flex-shrink 的值。
总宽度300px,子项宽度为100px,指定2个flex-shrink: 1
默认设置为auto。(含义是 "参照我的width
和height
属性")
指定了 flex 元素在主轴方向上的初始大小。
如果不使用 box-sizing
改变盒模型的话,那么这个属性就决定了 flex 元素的内容盒(content-box)的尺寸。
单值语法: 值必须为以下其中之一:
- 一个无单位数
它会被当作
的值。- 一个有效的**宽度(
width
)**值: 它会被当作<flex-basis>的值。
- 关键字
none
,auto
或initial
.双值语法: 第一个值必须为一个无单位数,并且它会被当作
<flex-grow>
的值。第二个值必须为以下之一:
- 一个无单位数:它会被当作
<flex-shrink>
的值。- 一个有效的宽度值: 它会被当作
<flex-basis>
的值。三值语法:
- 第一个值必须为一个无单位数,并且它会被当作
<flex-grow>
的值。- 第二个值必须为一个无单位数,并且它会被当作
<flex-shrink>
的值。- 第三个值必须为一个有效的宽度值, 并且它会被当作
<flex-basis>
的值。
指定align-content为center,第一项alidn-self为start。
[使用 CSS 弹性盒子](
为了实现O(1)时间复杂度,此题需要使用双向链表。
需要使用map来快速获取双向链表的值
需要使用frequentMap来获取同频率的所有节点。
需要维护一个变量minFreq来指向frequentMap中频率最低
更新minFreq的时机:节点已经从原链表中删除,节点插入到新链表之后
此图作者:liweiwei1419
class linkedTwo {
pre: linkedTwo | undefined
next: linkedTwo | undefined
key: number
val: number
times: number
constructor(key: number, val: number) {
this.key = key
this.val = val
this.pre
this.next
this.times = 1
}
delete(): void {
const pre = this.pre as linkedTwo
const next = this.next as linkedTwo
pre.next = next
next.pre = pre
}
}
class LFUCache {
getLinked: Map<number, linkedTwo>
frequentMap: Map<number, LFUitem>
max: number
cache_num: number
minFreq: number
constructor(capacity: number) {
this.max = capacity
this.frequentMap = new Map()
this.getLinked = new Map()
this.cache_num = 0
this.minFreq = 1
}
get(key: number): number {
if (this.getLinked.has(key)) {
const linked = this.getLinked.get(key) as linkedTwo
this.move(linked)
return linked.val
}
return -1
}
put(key: number, value: number): void {
const frequentMap = this.frequentMap
if (this.getLinked.has(key)) {
const linked = this.getLinked.get(key) as linkedTwo
linked.val = value
this.move(linked)
} else {
if (this.cache_num >= this.max && this.max >= 1) {
let item = frequentMap.get(this.minFreq) as LFUitem
const temp = item.delete_Tail()
this.getLinked.set(key, this.insert_linked(key, value))
this.getLinked.delete(temp)
} else if (this.cache_num < this.max) {
this.getLinked.set(key, this.insert_linked(key, value))
this.cache_num++
}
}
}
move(linked: linkedTwo): void {
const frequentMap = this.frequentMap
linked.delete()
linked.times++
this.add_frequentMap(linked)
this.updata_minFreq(frequentMap.get(linked.times - 1) as LFUitem, linked.times - 1)
}
insert_linked(key: number, value: number): linkedTwo {
const target = new linkedTwo(key, value)
this.minFreq = 1
this.add_frequentMap(target)
return target
}
add_frequentMap(linked: linkedTwo): void {
const frequentMap = this.frequentMap
const index = linked.times
if (!frequentMap.has(index)) {
frequentMap.set(index, new LFUitem())
}
const item = frequentMap.get(index) as LFUitem
item.insert_tump(linked)
}
updata_minFreq(item: LFUitem | undefined, index: number): void {
if (!item) return
const frequentMap = this.frequentMap
if (item.nothing()) {
frequentMap.delete(index)
if (frequentMap.size === 0) {
this.minFreq = 1
return
}
while (!frequentMap.has(this.minFreq)) {
this.minFreq++
}
}
}
}
class LFUitem {
tump: linkedTwo
tail: linkedTwo
constructor() {
this.tump = new linkedTwo(NaN, NaN)
this.tail = new linkedTwo(NaN, NaN)
this.tump.next = this.tail
this.tail.pre = this.tump
}
insert_tump(linked: linkedTwo): void {
const temp = this.tump.next as linkedTwo
linked.pre = this.tump
this.tump.next = linked
linked.next = temp
temp.pre = linked
}
delete_Tail(): number {
const target = this.tail.pre as linkedTwo
const pre = target.pre as linkedTwo
pre.next = this.tail
this.tail.pre = pre
return target.key
}
nothing(): boolean {
const tump = this.tump
const tail = this.tail
if (tump.next === tail) {
return true
}
return false
}
}
使用双向链表代替frequentMap,可以省去minFreq变量。
原本更新minFreq的时间复杂度在最坏的情况下,不是O(1)
class linked_list {
tump: linked_item
tail: linked_item
constructor(frequent: frequentItem) {
this.tump = new linked_item(NaN, NaN, frequent)
this.tail = new linked_item(NaN, NaN, frequent)
this.tump.next = this.tail
this.tail.pre = this.tump
}
insert_tump(linked: linked_item): void {
const temp = this.tump.next as linked_item
linked.pre = this.tump
this.tump.next = linked
linked.next = temp
temp.pre = linked
}
delete_Tail(): number {
const target = this.tail.pre as linked_item
const pre = target.pre as linked_item
pre.next = this.tail
this.tail.pre = pre
return target.key
}
nothing(): boolean {
const tump = this.tump
const tail = this.tail
if (tump.next === tail) {
return true
}
return false
}
}
class linked_item {
pre: linked_item | undefined
next: linked_item | undefined
key: number
val: number
frequent: frequentItem | undefined
constructor(key: number, val: number, frequent?: frequentItem) {
this.key = key
this.val = val
this.pre
this.next
this.frequent = frequent
}
delete(): void {
const pre = this.pre as linked_item
const next = this.next as linked_item
pre.next = next
next.pre = pre
}
}
class frequent_list {
start: frequentItem
end: frequentItem
constructor() {
this.start = new frequentItem(NaN)
this.end = new frequentItem(NaN)
this.start.next = this.end
this.end.pre = this.start
}
insert(linked: linked_item) {
if (this.start?.next?.key !== 1) {
this.start.add_frequentItem(1)
}
const one = this.start.next as frequentItem
linked.frequent = one
one.item.insert_tump(linked)
}
delete(): number {
const target = this.start.next as frequentItem
if (target.key === NaN) return -1
const key = target.item.delete_Tail()
target.delete_frequentItem()
return key
}
}
class frequentItem {
pre: frequentItem | undefined
next: frequentItem | undefined
item: linked_list
key: number
constructor(key: number) {
this.pre
this.next
this.item = new linked_list(this)
this.key = key
}
next_item(linked: linked_item) {
if (this.next?.key !== this.key + 1) {
this.add_frequentItem(this.key + 1)
}
const next_linked_list = this.next?.item!
linked.frequent = this.next!
next_linked_list.insert_tump(linked)
this.delete_frequentItem()
}
add_frequentItem(key: number) {
const next = this.next as frequentItem
const new_item = new frequentItem(key)
this.next = new_item
new_item.pre = this
new_item.next = next
next.pre = new_item
}
delete_frequentItem() {
const linked_list = this.item as linked_list
if (linked_list.nothing()) {
const pre = this.pre as frequentItem
const next = this.next as frequentItem
pre.next = next
next.pre = pre
}
}
}
class LFUCache {
linked_item: Map<number, linked_item>
frequent_list: frequent_list
capacity: number
cache_num: number
constructor(capacity: number) {
this.cache_num = 0
this.capacity = capacity
this.linked_item = new Map()
this.frequent_list = new frequent_list()
}
get(key: number): number {
const map = this.linked_item
if (map.has(key)) {
const target = map.get(key) as linked_item
this.move(target)
return target.val
}
return -1
}
put(key: number, value: number): void {
const map = this.linked_item
if (this.capacity === 0) return
if (map.has(key)) {
const target = map.get(key) as linked_item
target.val = value
this.move(target)
} else {
const frequent_list = this.frequent_list
if (this.cache_num === this.capacity) {
const key = frequent_list.delete()
map.delete(key)
} else {
this.cache_num++
}
const target = new linked_item(key, value)
map.set(key, target)
frequent_list.insert(target)
}
}
move(linked: linked_item) {
const frequent = linked.frequent as frequentItem
linked.delete()
frequent.next_item(linked)
}
}
我在写这题(123. 买卖股票的最佳时机 III )时产生的问题 :
var maxProfit = function (prices) {
if (prices.length === 0) return 0
const Len = prices.length
const dp = Array.from({ length: Len }, () => 0)
let res = 0
for (let i = 1; i < Len; i++) {
dp[i] = dp[i - 1]
for (let j = 1; j <= i; j++) {
const curr = prices[i] - prices[j - 1]
res = Math.max(res, curr)
dp[i] = Math.max(dp[i], curr)
if (j - 2 >= 0) {
res = Math.max(res, curr + dp[j - 2])
}
}
}
return res
}
时间复杂度N^2,空间N^1(官方加了测试用例N^2已经不能通过)
var maxProfit = function (prices) {
if (prices.length === 0) return 0
const Len = prices.length
const dp = Array.from({ length: Len }, () =>
Array.from({ length: 3 }, () => Array.from({ length: 2 }, () => 0))
)
dp[0][0][1] = -prices[0]
dp[0][1][1] = -prices[0]
for (let i = 1; i < Len; ++i) {
dp[i][0][1] = Math.max(dp[i - 1][0][1], dp[i - 1][0][0] - prices[i])
dp[i][1][0] = Math.max(dp[i - 1][1][0], dp[i - 1][0][1] + prices[i])
dp[i][1][1] = Math.max(dp[i - 1][1][1], dp[i - 1][1][0] - prices[i])
dp[i][2][0] = Math.max(dp[i - 1][2][0], dp[i - 1][1][1] + prices[i])
}
return Math.max(
dp[Len - 1][0][1],
dp[Len - 1][1][0],
dp[Len - 1][1][1],
dp[Len - 1][2][0]
)
}
时间复杂度N^1,空间N^1
当时真的百思不得其解,因为这题的数据也足够大,这时间差距应该有显著差距。
直到我再次优化将3维数组变成常数空间。
var maxProfit = function (prices) {
if (prices.length === 0) return 0
const Len = prices.length
let dp0 = 0
let dp1 = -prices[0]
let dp2 = 0
let dp3 = -prices[0]
let dp4 = 0
for (let i = 1; i < Len; i++) {
dp1 = Math.max(dp1, dp0 - prices[i])
dp2 = Math.max(dp2, dp1 + prices[i])
dp3 = Math.max(dp3, dp2 - prices[i])
dp4 = Math.max(dp4, dp3 + prices[i])
}
return Math.max(dp0, dp1, dp2, dp3, dp4)
}
这才是N^1的算法才有的速度。
观察这2个算法,差别就在于创建数组。
const dp = Array.from({ length: Len }, () => 0)
const dp = Array.from({ length: Len }, () =>
Array.from({ length: 3 }, () => 0)
)
const dp = Array.from({ length: Len }, () =>
Array.from({ length: 3 }, () => Array.from({ length: 2 }, () => 0))
)
乖乖!可以看到创建数组的耗时远比算法本身的运行时间长太多。
相同的算法,java的耗时就没有这么夸张。
java3维耗时
java2维耗时
java常数耗时
js在创建多维数组上性能很差,拿来写算法避免创建多维数组
利用js中的map特性=>Map
对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者原始值) 都可以作为一个键或一个值。MDN
map可以保存原始的插入顺序,降低了题目难度
var LRUCache = function (capacity) {
this.capacity = capacity
this.map = new Map()
}
LRUCache.prototype.get = function (key) {
if (this.map.has(key)) {
let value = this.map.get(key)
this.map.delete(key) //删除重新设置,会更新位置
this.map.set(key, value)
return this.map.get(key)
}
return -1
}
LRUCache.prototype.put = function (key, value) {
if (this.map.has(key)) {
//存在则删除
this.map.delete(key)
}
if (this.map.size == this.capacity) {
//如果到达临界值就需要删除第一个
const { value: key, done } = this.map.keys().next()
this.map.delete(key)
}
this.map.set(key, value)
}
忘记copy谁的了
不使用map,改用对象基础key-value,和双向链表方便查找断链
亚头节点和伪节点方便链操作一般化
class ListNode {
constructor(key, value) {
this.key = key
this.value = value
this.next = null
this.pre = null
}
}
class LRUCache {
constructor(cache) {
this.cache = cache
this.count = 0
this.map = {}
this.dummyHead = new ListNode()
this.dummyTail = new ListNode()
this.dummyHead.next = this.dummyTail
this.dummyTail.prev = this.dummyHead
}
get(key) {
let node = this.map[key]
if (!node) return -1
this.moveToHead(node)
return node.value
}
put(key, value) {
let node = this.map[key]
if (!node) {
let newNode = new ListNode(key, value)
this.map[key] = newNode
this.addToHead(newNode)
this.count++
if (this.count > this.cache) {
this.removeLater()
}
} else {
node.value = value
this.moveToHead(node)
}
}
moveToHead(node) {
this.removeFromList(node)
this.addToHead(node)
}
removeFromList(node) {
let temp_pre = node.pre
let temp_next = node.next
temp_pre.next = temp_next
temp_next.pre = temp_pre
}
addToHead(node) {
node.pre = this.dummyHead
node.next = this.dummyHead.next
this.dummyHead.next.pre = node
this.dummyHead.next = node
}
removeLater() {
let later = this.dummyTail.pre
this.removeFromList(later)
delete this.map[later.key]
this.count--
}
}
这是一题类岛屿问题,分析情况:
这题可以去重,可以避免走重复的路径
function updateBoard(board: string[][], click: number[]): string[][] {
const x = click[0]
const y = click[1]
if (board[x][y] === 'M') {
board[x][y] = 'X'
return board
}
const rowLimit = board.length
const colLimit = board[0].length
const visited = new Set()
const ways = [
[-1, -1],
[-1, 0],
[-1, 1],
[0, -1],
[0, 1],
[1, -1],
[1, 0],
[1, 1]
]
const dfs = function (x: number, y: number): number {
if (board[x][y] === 'M' || board[x][y] === 'X') return 1
const str = `${x},${y}`
if (board[x][y] !== 'E' || visited.has(str)) return 0
visited.add(str)
let count = 0
for (const way of ways) {
const r = way[0] + x
const c = way[1] + y
if (r < 0 || r >= rowLimit || c < 0 || c >= colLimit) continue
if (board[r][c] === 'M' || board[r][c] === 'X') count++
}
if (count === 0) {
for (const way of ways) {
const r = way[0] + x
const c = way[1] + y
if (
r < 0 ||
r >= rowLimit ||
c < 0 ||
c >= colLimit ||
visited.has(`${r},${c}`)
)
continue
dfs(r, c)
}
board[x][y] = 'B'
} else {
board[x][y] = count + ''
}
return 0
}
dfs(x, y)
return board
}
在我理解中浏览器相当于一个 Cookie仓库,当你发出请求时,都会从自己这个仓库中去搜寻目标域名的cookie,如果满足发送的条件,则会自动的将 Cookie 放到请求的Header中。
第一方 Cookie:我们的域名是 ybf.com
,访问我们的服务时,会自动设置 ybf.com
域名下的 Cookie。
第三方 Cookie:我们的域名是 ybf.com
,我的网页访问时,使用 acc.com
这个域下的资源, acc.com
这个域会设置一个属于 acc.com
的 Cookie。
Cookie 的SameSite
属性用来限制第三方 Cookie,从而减少安全风险。
请求类型 | 以前 | Strict | Lax | None |
---|---|---|---|---|
链接 | 发送 | NO | 发送 | 发送 |
预加载 | 发送 | NO | 发送 | 发送 |
get表单 | 发送 | NO | 发送 | 发送 |
post表单 | 发送 | NO | NO | 发送 |
Iframe | 发送 | NO | NO | 发送 |
AJAX | 发送 | NO | NO | 发送 |
image | 发送 | NO | NO | 发送 |
此表格来自SameSite小识
如果你的 Chrome 的设置为限制第三方 Cookie ,就是将 SameSite 安全级提高到 Strict,限制一切第三方 Cookie。所以测试时关闭 Chrome 的限制第三方 Cookie 。
我们在ybf.com
中加载了acc.com
的资源,看到了第三方 Cookie 的警告。
这里不指定 SameSite 的值,看到 Cookie 工作为 None 的特性。
可以看到有警告:需要将 Secure 设置为 True。
这就要求我们需要将 http 升级为 https。
我们先给acc.com
设置上 Cookie imge=imge
(属性中包含 sameSite: 'lax'
) 然后关闭网页。
然后我们在ybf.com
中添加一个a标签 <a href="http://acc.com:4000">跳转去acc页面</a>
,
和一个img标签 <img src="http://acc.com:4000/imge">
。
我们访问ybf.com
然后我们点击查看 Nerwork查看img标签的网络请求。
可以看到加载imge时不会加载 Cookie。然后我们点击a标签进行跳转。
跳转时添加上了 Cookie。
事件捕获:从上级节点,向目标元素传播(html>body>div>ui>li)
目标阶段
事件冒泡:从目标元素,向上级节点传播(li>ui>div>body>html)(默认)
不是所有的事件都能冒泡,如:blur、focus、load、unload都不能
事件绑定的2种形式:
动态绑定事件,把一个函数赋值给事件处理程序。
只能冒泡形式触发。
box1.onclick = function () {
//......
}
前函数会被后函数覆盖,前函数消失。
<script>
box1.onclick = function () {
console.log('box1');
}
box1.onclick = function () {
console.log('box1 two');
}
</script>
//box1 two
通过事件监听的方式绑定事件。执行顺序与添加顺序相关。
box1.addEventListener('click', function () {
//......
}, true);
removeEventListener可以移除监听。移除后,按原顺序执行。
IE中 使用attachEvent()和detachEvent(),IE8和更老的只支持冒泡。
<div id="box1">
<div id="box2">
<div id="box3"></div>
</div>
</div>
<script>
box1.onclick = function () {
console.log('box1 51561');
}
box2.onclick = function () {
console.log('box2');
}
box3.onclick = function () {
console.log('box3');
}
box1.onclick = function () {
console.log('box1');
}
box1.addEventListener('click', function () {
console.log('box1 捕获阶段');
}, true);
box2.addEventListener('click', function () {
console.log('box2 捕获阶段');
}, true);
box1.addEventListener('click', function () {
console.log('box1 冒泡阶段');
}, false);
box2.addEventListener('click', function () {
console.log('box2 冒泡阶段');
}, false);
box3.addEventListener('click', function () {
console.log('box3 冒泡阶段');
}, false);
box3.addEventListener('click', function () {
console.log('box3 捕获阶段');
}, true);
</script>
Firefox(90.0.2 (64 位))的结果:
Chrome(92.0.4515.107)的结果:
可以看到Chrome的事件机制与Firefox不同。
chrome严格遵守先捕获再冒泡,Firefox是按当前元素的上绑定元素的先后顺序来决定触发顺序。
<div id="box1">
<div id="box2">
<div id="box3"></div>
</div>
</div>
<script>
box3.onclick = function () {
console.log('box3');
}
box3.onclick = function () {
console.log('box3 覆盖1');
}
box3.addEventListener('click', function () {
console.log('box3 冒泡阶段');
}, false);
box3.onclick = function () {
console.log('box3 覆盖2');
}
box3.addEventListener('click', function () {
console.log('box3 捕获阶段');
}, true);
box3.onclick = function () {
console.log('box3 覆盖3');
}
</script>
Firefox(90.0.2 (64 位))的结果:
Chrome(92.0.4515.107)的结果:
2个浏览器执行结果相同:都是取代onclick出现的第一个位置。
<div id="box1">
<div id="box2">
<div id="box3"></div>
</div>
</div>
<script>
box1.onclick = function () {
console.log('onclick', this);
}
box1.addEventListener('click', function () {
console.log('addEventListener', this);
}, false)
</script>
IE6、7、8 使用attachEvent监听时,this指向window。
阻止捕获和冒泡阶段中当前事件的进一步传播。但是,它不能防止任何默认行为的发生。
<div id="box1">
<div id="box2">
<div id="box3"></div>
</div>
</div>
<script>
box1.addEventListener('click', function () {
console.log('box1 捕获阶段');
}, true);
box1.addEventListener('click', function () {
console.log('box1 冒泡阶段');
}, false);
box2.addEventListener('click', function (e) {
console.log('box2 捕获阶段');
}, true);
box2.addEventListener('click', function () {
console.log('box2 冒泡阶段');
}, false);
box3.addEventListener('click', function (e) {
e.stopPropagation()
console.log('box3 冒泡阶段');
}, false);
box3.addEventListener('click', function (e) {
console.log('box3 捕获阶段');
}, true);
</script>
当节点上设置stopPropagation时,当前节点的后续监听继续执行。
在IE下:设置cancelBubble = true
取消默认行为
在IE下:设置window.event.returnValue = false
详解JS事件冒泡、事件捕获原型 stopPropagation()和preventDefault()作用
[event.stopPropagation](
遍历器(
Iterator
)就是一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator
接口,就可以完成遍历操作
平常使用的(...)扩展运算符
和 for...of...
都在隐式的调用的 Iterator
。
原生具备 Iterator
接口。
- Array
- Map
- Set
- String
- TypedArray
- 函数的 arguments 对象
- NodeList 对象
已经存在的Symbol.iterator
具有以下特性
属性 | True/False |
---|---|
writable | false |
enumerable | false |
configurable | false |
let obj = {
data: [1, 2, 3],
[Symbol.iterator]: function () {
let context = this
let index = 0
let length = this.data.length
return {
next: function () {
if (index < length) {
return { value: context.data[index++], done: false }
} else {
return { value: void 0, done: true }
}
}
}
}
}
for (const item of obj) {
console.log(item)//1 2 3
}
原本你不可以直接在对象上使用for...of...
。现在我们在对象上定义[Symbol.iterator]
,使得for...of...
可以找到 iterator
属性进行遍历。
for...of...
可以约等于以下代码:
let iterator = obj[Symbol.iterator]()
let result = iterator.next()
while (!result.done) {
console.log(result.value)
//....
result = iterator.next()
}
我们不可以更改对象原型链上的 Symbol.iterator
,只能在当前对象上定义新的 Symbol.iterator
。
例如以下例子:
let a = [1, 2, 3]
a[Symbol.iterator] = function () {
let conext = this
let index = 0
return {
next() {
if (Number.isInteger(conext[index])) {
return { value: conext[index++] + 1, done: false }
} else {
return { value: void 0, done: true }
}
}
}
}
for (const item of a) {
console.log(item)//2 3 4
}
扩展运算符也是调用 Symbol.iterator
。
let a = [1, 2, 3]
a[Symbol.iterator] = function () {
let conext = this
let index = 0
return {
next() {
if (Number.isInteger(conext[index])) {
return { value: conext[index++] + 1, done: false }
} else {
return { value: void 0, done: true }
}
}
}
}
let b = [...a] //[2,3,4]
return()
用于循环提前结束的情况。
return
方法必须返回一个对象,这是 Generator 规格决定的。
let a = [1, 2, 3]
a[Symbol.iterator] = function () {
let conext = this
let index = 0
return {
next() {
if (Number.isInteger(conext[index])) {
return { value: conext[index++] + 1, done: false }
} else {
return { value: void 0, done: true }
}
},
return() {
console.log('提前结束')
return { value: void 0, done: true }
}
}
}
for (const item of a) {
console.log(item)
break
}
for (const item of a) {
console.log(item)
throw new Error()
}
throw()
用于配合 Generator 函数
使用。
var g = function* () {
try {
yield 123
} catch (e) {
console.log('内部捕获', e)
}
}
var i = g()
console.log(i.next())
try {
i.throw('a')
i.throw('b')
} catch (e) {
console.log('外部捕获', e)
}
let a = 'abc'
for (const item of a) {
console.log(item)
}
//a b c
这里的string是字面量,本身没有Symbol.iterator
,这里应该是隐式字面量转化为String包装类,包装类上就有Symbol.iterator
,符合预期。
let a = 'abc'
let A = new String(a)
for (const item of A) {
console.log(item)
}
//a b c
引用和部分代码都来自:ECMAScript 6 入门
递归的次数太多了,超过了程序承载的能力。
我通过一个简易的代码,来测试下函数调用栈的能力。
function f(n) {
try {
return f(n + 1)
} catch (error) {
console.log(n)
}
}
f(1)
我的node环境上限大概是10000左右。
实际看一下调用栈。在浏览器中执行以下代码。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
test
</body>
<script>
function factorial(n, total = 1) {
console.trace()
if (n === 1) return total
return factorial(n - 1, n + total)
}
console.log(factorial(30000))
</script>
</html>
可以看到函数调用栈里的数量逐渐增加。
现在对尾递归的支持并不好。浏览器只有Safari支持,而且你必须开启严格模式。
你可以使用这个网站来查看支持性。
特征
我们在Safari 中执行以下代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
test
</body>
<script>
'use strict'
function factorial(n, total = 1) {
console.trace()
if (n === 1) return total
return factorial(n - 1, n + total)
}
console.log(factorial(30000))
</script>
</html>
可以看到成功运行了,并且函数调用栈道数量一直没有发生变化。
以下代码copy来自尾调用优化
function tailFactorial(n, total) {
if (n === 1) return total;
return tailFactorial(n - 1, n * total);
}
function factorial(n) {
return tailFactorial(n, 1);
}
factorial(5) // 120
function currying(fn, n) {
return function (m) {
return fn.call(this, m, n);
};
}
function tailFactorial(n, total) {
if (n === 1) return total;
return tailFactorial(n - 1, n * total);
}
const factorial = currying(tailFactorial, 1);
factorial(5) // 120
function factorial(n, total = 1) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}
factorial(5) // 120
节流:一段时间内,只能触发一次函数。
防抖:某段时间内,不管函数触发多少次,我只生效最后一次。(触发函数时,时间重置)
在我们滚动页面时,大量触发滚动事件。
以下代码修改来自前端性能优化原理与实践。
<script>
// fn是我们需要包装的事件回调, interval是时间间隔的阈值
function throttle(fn, interval) {
// last为上一次触发回调的时间
let last = 0
// 将throttle处理结果当作函数返回
return function (args) {
// 保留调用时的this上下文
let context = this
// 记录本次触发回调的时间
let now = +new Date()
// 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
if (now - last >= interval) {
// 如果时间间隔大于我们设定的时间间隔阈值,则执行回调
last = now;
fn.apply(context, args);
}
}
}
// 用throttle来包装scroll的回调
const better_scroll = throttle(() => console.log('触发了滚动事件'), 1000)
document.addEventListener('scroll', better_scroll)
</script>
通过闭包来记录上一次执行的时间。如果2次触发的时间间隔小于你的设定值,则不触发。
可以看到使用了节流,触发函数的次数明显减少。
以下代码来自7分钟理解JS的节流、防抖及使用场景
<script>
//模拟一段ajax请求
function ajax(content) {
console.log('ajax request ' + content)
}
function debounce(fun, delay) {
return function (args) {
let that = this
clearTimeout(fun.id)
fun.id = setTimeout(function () {
fun.call(that, args)
}, delay)
}
}
let inputb = document.getElementById('debounce')
let debounceAjax = debounce(ajax, 500)
inputb.addEventListener('keyup', function (e) {
debounceAjax(e.target.value)
})
</script>
可以看到,当我停下输入一段时间后才执行函数。
掘金小册前端性能优化原理与实践
在本地使用portainer来管理远程服务器上的docker。
安装docker参考上面
portainer直接使用docker拉取镜像,映射端口即可。
具体流程:
systemctl status docker
找到docker配置文件的位置
vim /usr/lib/systemd/system/docker.service
编辑这个文件
ExecStart的后面加上 -H 0.0.0.0:2376
开启远程访问(注意打开防火墙)
重启docker
systemctl daemon-reload
systemctl restart docker
Portainer配置
完成配置后,你就能正常访问。
但是存在安全问题,你的docker谁都可以访问,添加tls提高安全性。
参考文章中配置TLS的操作
原文链接:Docker配置TLS认证开启远程访问
原文作者:张巍的烂笔头
1、2、3步推荐在本地创建一个文件夹进行存放
cd ~/
mkdir ssl
cd ./ssl
生成CA公钥和私钥(本地)
// 1. 生成 ca key 需要设置一个密码,密码后面的配置会要求输入。
openssl genrsa -aes256 -out ca-key.pem 4096
// 2. 创建 ca 公钥
openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -out ca.pem
创建client证书(本地)
// 客户端证书
openssl genrsa -out key.pem 4096
// 客户端签名请求文件
openssl req -subj '/CN=client' -new -key key.pem -out client.csr
echo extendedKeyUsage = clientAuth > extfile-client.cnf
// CA 签名
openssl x509 -req -days 365 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out cert.pem -extfile extfile-client.cnf
创建server端证书(本地)
//创建 server key
openssl genrsa -out server-key.pem 4096
//证书签名请求文件 /CN后的填使用正式的ip或者域名
openssl req -subj "/CN=你的ip地址" -sha256 -new -key server-key.pem -out server.csr
//使用 CA 证书签名文件
// extfile.cnf IP:XXXX 允许访问的ip
echo subjectAltName = DNS:你的DNS,IP:你的IP地址,IP:127.0.0.1 >> extfile.cnf
echo extendedKeyUsage = serverAuth >> extfile.cnf
//生成签名证书
openssl x509 -req -days 365 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile extfile.cnf
上传
将本地的ca.pem
、server-cert.pem
、 server-key.pem
这3个文件上传到服务器中。
推荐将这3个文件放在一个文件夹中
cd /root
mkdir ./tls
上传文件可以使用scp命令,也可以使用vscode的远程模式。只需要装个插件Visual Studio Code Remote - SSH,对不熟悉vim操作的同学比较友好。
server端配置(远程)
vim /usr/lib/systemd/system/docker.service
注意替换为自己的路径,在ExecStart中写入tls的配置
portainer配置
选择你对应的文件并update endpoint
,大功告成。
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.