GithubHelp home page GithubHelp logo

blog's Introduction

blog's People

Contributors

ybfacc avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar

blog's Issues

事件机制

事件机制

事件流阶段

  1. 事件捕获:从上级节点,向目标元素传播(html>body>div>ui>li)

  2. 目标阶段

  3. 事件冒泡:从目标元素,向上级节点传播(li>ui>div>body>html)(默认)

不是所有的事件都能冒泡,如:blur、focus、load、unload都不能

优点

  1. 有大量子组件需要绑定同一个事件时,可以绑定在父组件上。方便管理,优化性能。
  2. 对动态生成的子组件,可以统一在父组件上进行管理。

事件绑定

事件绑定的2种形式:

DOM 0级

动态绑定事件,把一个函数赋值给事件处理程序。

只能冒泡形式触发。

box1.onclick = function () {
      //......
}

注意:DOM 0级会覆盖

前函数会被后函数覆盖,前函数消失。

  <script>
    box1.onclick = function () {
      console.log('box1');
    }
    box1.onclick = function () {
      console.log('box1 two');
    }
  </script>
  
//box1 two

DOM 2级

通过事件监听的方式绑定事件。执行顺序与添加顺序相关。

    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 位))的结果:

image-20210806175441115

Chrome(92.0.4515.107)的结果:

image-20210806175612535

可以看到Chrome的事件机制与Firefox不同。

chrome严格遵守先捕获再冒泡,Firefox是按当前元素的上绑定元素的先后顺序来决定触发顺序。

DOM 0级覆盖之后的执行顺序

  <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 位))的结果:

image-20210806180952

Chrome(92.0.4515.107)的结果:

image-20210806181120654

2个浏览器执行结果相同:都是取代onclick出现的第一个位置。

事件绑定的this

  <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>

image-20210806181836690

IE6、7、8 使用attachEvent监听时,this指向window。

stopPropagation()和preventDefault()

event.stopPropagation()

阻止捕获和冒泡阶段中当前事件的进一步传播。但是,它不能防止任何默认行为的发生。

  <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

event.preventDefault()

取消默认行为

在IE下:设置window.event.returnValue = false

参考

JS事件流和事件委托

理解Javascript中的事件绑定与事件委托

详解JS事件冒泡、事件捕获原型 stopPropagation()和preventDefault()作用

[event.stopPropagation](

less踩坑

解决less中无法正确计算calc的问题

在less中使用calc函数进行不同长度单位的计算时,后面值的单位会被忽略,例如:

height: calc(100vh - 50px);

结果等于50vh

解决方法:在表达式前添加"~"

height: calc(~"100vh - 50px");

https://www.cnblogs.com/neeter/p/13424737.html

AVL树

AVL树

能够保持平衡的BST

分析

旋转

根据左右子树的高度差(大于2),来解决旋转的类型。

旋转类似链表的操作(断链插入)

删除

将目标元素的值与比目标元素大一位的元素进行替换=>更新目标:删除目标元素大一位的元素

大小顺序: a < b < c < d < f < g

矩形的高度代表了树的高度

红色=>蓝色:代表了节点的变化

LL

转化前:

未命名文件

转化后:

后

RR

转化前:

前

转化后:

后

LR

转化1前:

前

转化1后:

中

转化2前:

中 (1)

转化2后:

后

RL

转化1前:

前

转化1后:

中

转化2前:

中 (1)

转化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
}

参考

ES6的JavaScript数据结构实现之树(二叉搜索树、AVL树、红黑树)

JS 二分递归 + AVL树

原型

原型

JavaScript 继承通过原型链来实现--每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法

BB0FB8AA-62D5-4F10-90FD-571EF7B03780

此图出自《JavaScript忍者秘籍(第2版)》

函数独有prototype

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__

.__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运算符

instanceof 运算符用来检测 constructor.prototype 是否存在于参数 object 的原型链上MDN

function apple() {
  this.name = 'apple'
}

let a = new apple()

console.log(a instanceof apple)//true

isPrototypeOf()

方法用于测试一个对象是否存在于另一个对象的原型链上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

使用new关键字

function apple() {
  this.name = 'apple'
}

let a = new apple()

console.log(a instanceof apple)//true
console.log(a instanceof Object)//true

使用new关键字生成的对象,继承自function.prototype原型对象

New一个对象的过程

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

嗯?没有!!难道原型出错了?🤨

我们去查查MDN

字符串字面量 (通过单引号或双引号定义) 和 直接调用 String 方法(没有通过 new 生成字符串对象实例)的字符串都是基本字符串。JavaScript会自动将基本字符串转换为字符串对象,只有将基本字符串转化为字符串对象之后才可以使用字符串对象的方法。当基本字符串需要调用一个字符串对象才有的方法或者查询值的时候(基本字符串是没有这些方法的),JavaScript 会自动将基本字符串转化为字符串对象并且调用相应的方法或者执行查询。MDN

原来字面量在使用API时,会自动转化为对象。没翻车😘

参考

JS基础-函数、对象和原型、原型链的关系

JS原型链与继承别再被问倒了

MDN

puppeteer小练习

puppeteer小练习

使用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的参数。

大致思路

  • page.setUserAgent设置UA信息
  • page.goto打开页面
  • page.waitForSelector等待dom节点加载
  • page.$eval相当于document.querySelector,找到匹配的dom节点
  • page.evaluateHandle等待promis执行完成
  • browser.close()关闭浏览器
  • 返回所有img的数组
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标签

<a href="javascript:ShowNext();"><img src=""></a>

第一次看见这样的a标签。可以在点击a标签时,直接触发js函数。

德布鲁因序列

德布鲁因序列(De Bruijn sequence)

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

箭头函数

箭头函数

箭头函数的介绍

箭头函数是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的指向

箭头函数的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()

这样就显示正确了。

不可以使用new构造对象

箭头函数本事就依靠声明时的this,还怎么new一个对象。😢

不可以使用arguments类数组

可以使用rest参数(...变量名)

不可以当作Generator函数

参考

ECMAScript 6 入门

《JavaScript忍者秘籍(第2版)》

Form

表单

我的代码

常见的表单提交 Header:

  • multipart/form-data
  • application/x-www-form-urlencoded
  • text/plain
  • application/json

GET方式提交表单

原生表单会以key=value的方式拼接在url后面。

例如以下代码:

  <form action="/get" method="GET" >
    <input type="text" name="name1" id="id1" value="">
    <button type="submit">GET</button>
  </form>

点击提交后,跳转到新页面并看到url变化。

1

application/x-www-form-urlencoded

这是原生表单的默认的提交格式。

  <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的数据格式。

2

解析即可。

multipart/form-data

可以用来上传文件。

  <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: 的值有些不一样。

3

指明 multipart/form-data 很正常。但是后面的 boundary=---WebKitFormBoundaryCkL35Iut5A3hdvGD 就比较奇怪。

在MDN中解释到:

boundary:用于封装消息的多个部分的边界

4

可以看到我们上传的格式相对于 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('只能上传图片文件'); 
    }
  });
}

参考

Node.js HTTP服务器中不依赖第三方模块的文件、图片上传

玩转Koa -- koa-bodyparser原理解析

tcp的学习

tcp的学习

在本地或者服务器上使用node创建http服务器,在本地发起请求并使用wireshark来进行抓包分析。

tcp报文首部格式

以下两幅中、英文图对比观看,对抓到的数据可以轻松理解。

1

2

以下是使用wireshark抓包一次连接。

12-11-1

Seq、Ack这些值不是从0开始,实际上是由算法产生的。只是软件为了更加直观帮我们从0算起。

Flags

全部的Flags标示:

  • URG:是紧急数据,应尽快传送
  • ACK:确认号有效
  • PUSH:尽快交付,不需要等待缓存填满
  • RST:连接出现严重差错,释放连接再重新建立
  • SYN:连接请求或连接接受
  • FIN:数据发送完毕,要求释放连接

这些标示是由8个位来控制的。Flags代表这个tcp包的作用。

4

3次握手

5

客户端第1次发送请求时,seq根据自身算法得到。SYN表示请求连接

服务端第1次发送请求时,seq根据自身算法得到,ack=客户端seq+1。SYN表示接受请求,ACK表示确认号有效。

客户端第2次发送请求时,seq=服务端ack,ack=服务端seq+1。ACK表示确认号有效。

PS:虽然2个seq都显示为0,但是其真实值并不相同。

5-1

5-2

4次挥手🙋‍♂️

6

a端第1次,传输结束,要求释放连接。ACK表示确认号有效。FIN表示数据传送完毕,要求释放连接。

b端第1次,数据进行传输。ACK表示确认号有效。

b第2次,传输结束,已准备断开。ACK表示确认号有效。FIN数据传送完毕,要求释放连接。

a第2次,确认断开。ACK表示确认号有效。

7

PS:抓包时发现:首先发送FIN的不一定是客户端,服务端也可以。

3次挥手

10

假设客户端发起FIN。服务端也没有数据需要传输,服务端可以直接返回FIN,将2次回复变成1次。

参考

实战!我用 Wireshark 让你“看见“ TCP

wireshark分析TCP连接和关闭

图1、5、6 来自《第5 版计算机网络原理》课件

图2 https://www.cnblogs.com/lshs/p/6038458.html

394-字符串解码

我的思路

  1. 找出没有【】嵌套的字段
  2. 使用string.replace()对目标字段进行替换
  3. 检测是否完成。是=>结束,否=>回到1

有点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)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

正则的可视化

QQ20200528-175054

正则工具

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
}

Script标签async和defer

Script标签async和defer

async :可选。表示应该立即下载脚本,但不应妨碍页面中的其他操作,比如下载其他资源或等待加载其他脚本。只对外部脚本文件有效。
defer :可选。表示脚本可以延迟到文档完全被解析和显示之后再执行。只对外部脚本文件有效。IE7及更早版本对嵌入脚本也支持这个属性。

引用来自《JavaScript高级程序设计(第3版) 》

原图链接https://www.growingwiththeweb.com/2014/02/async-vs-defer-attributes.html

bVWhRl

放在head中不使用async和defer

会等待所有资源下载完毕,并按照脚本的顺序进行执行

不使用

放在body底部

常见的方式

放在body底部

放在head中使用async

会异步下载脚本,不会影响页面的解析。并且执行时,不会按照脚本的顺序而是谁先下载完谁先执行。

async

放在head中使用defer

异步下载脚本,在文档渲染完之后,在 DOMContentLoaded时间触发前,按照脚本的顺序进行执行

defer

放在head中同时使用async和defer

表现与只使用async相同。

同时使用

参考

defer和async的区别

浅谈script标签的defer和async

238-除自身以外数组的乘积

238-除自身以外数组的乘积

我的思路

从左扫一遍,累乘得到一个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
}

使用防抖

使用防抖

对一个频繁触发的请求使用防抖。

方式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官方文档

Generator

Generator

介绍

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大致的执行流程:

  • 执行Iteratornext()

    • 函数压入调用栈,代码从头或者上次中断的地方开始执行
    • 遇到 yield 关键字,返回一个对象{ value: value, done: false }
      • value:紧跟在 yield 关键字后的表达式计算值
      • donegenerator函数未执行完毕
    • 函数保留当前环境,挂起弹出调用栈
  • 执行return时,返回一个对象{ value: value, done: true }

    • valuereturn将要返回的值
    • donegenerator函数执行完毕

下面使用一个例子:

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

  1. yield关键字只能在 generator 函数中使用。不可以跨函数。
function* fruit() {
  let arr = ['apple', 'pear', 'ending']
  arr.forEach(item=>{
    yield item
  })
  return 
}

这样改变函数将会报错。

  1. yield是”懒执行“,只会在运行需要执行时才执行。
function* fruit() {
  yield 'pear'
  yield console.log('执行')
  return 'ending'
}

var _fruit = fruit()

_fruit.next()
_fruit.next()
_fruit.next()
  1. yield 放在包含在另一个表达式中需要()
function* fruit() {
  yield 1 + (yield 2)
}

var _fruit = fruit()

_fruit.next()
_fruit.next(2)
_fruit.next()

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不同。当使用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}

return

在外部提前结束generator函数。

使用return函数返回一个对象。valuereturn中的参数,donetrue

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 }

参考

Generator 函数的语法

跨域JSONP

跨域JSONP

跨域限制

什么叫跨域

当一个资源加载另一个资源时,协议、域名、端口有一项或一项以上不同

为什么有跨域

提高浏览器的安全性

代码实现思路

客户端

  1. 定义获取数据后,需要执行的函数
  2. 动态生成对服务器JS引用的代码
    1. 设置url地址,并指定callback参数
    2. 创建script标签,并设置src属性
    3. 把script标签加入head,此时调用开始
<!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>

服务端

  1. 将客户端发送的callback参数作为函数名来包裹住JSON数据,返回数据至客户端
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攻击

参考

7种跨域方案操练起来

类型转化

类型转化

在日常使用if等判断语句时,你会碰到隐式转化。例如以下的例子👇

if(""){
  console.log('true')
}

所以了解类型转化也成了我们的必备技能。

基本数据类型和引用数据类型

  • 基本类型
    • Boolean
    • String
    • Number
    • Null
    • Undefined
    • Symbol
    • Bigint
  • 引用类型
    • Object

所有基本类型的值都是不可改变的。但需要注意的是,基本类型本身和一个赋值为基本类型的变量的区别。变量会被赋予一个新值,而原值不能像数组、对象以及函数那样被改变。

举个例子

这是我在第一次使用js碰到的问题。

let str = 'abcdefg'
for (let i = 0; i < str.length; i++) {
  str[i] = 'A'
}
console.log(str)//abcdefg

str 不可变,可以验证基本类型的值不可变

Falsy

falsy 值 (虚值) 是在 Boolean 上下文中认定为 false 的值

js中只有以下7个Falsy值:

  • false
  • 0
  • 0n(BigInt)
  • ""、‘’、``(空字符串)
  • null
  • undefined
  • NaN

原始值转换到原始值

  1. 原始值到布尔值。

    除了 Falsy 其他的都转化为 true

  2. 原始值转化为字符串。

    相当于 原始值+“”

  3. 原始值转化为数字。

    Boolean:true=>1、 false=>0

    String:转化为数字或者 NaN 。

1_change

原始值转化到对象

  1. 如果是 nullundefined 使用 new Object() 转化为对象,将会创建并返回一个空对象。

3_change

  1. 使用对应的包装对象。BooleanStringNumberBigIntSymbol

对象转化到原始值

  1. 对象转化为布尔都为 true。
  2. 对象转化为字符串、数字。

这里有3个函数可能会影响结果。

Symbol.toPrimitive

toString()

valueOf()

Symbol.toPrimitive

一个对象可被转换为原始值。该函数被调用时,会被传递一个字符串参数 hint ,表示要转换到的原始值的预期类型。 hint 参数的取值是 "number""string""default" 中的任意一个

举个栗子

4_change

toString()

返回一个表示该对象的字符串。5_change

注意⚠️ toString 在不同类型中表现不同。(数组、函数等都会重写 toString 方法)

6_change

valueOf()

返回值为该对象的原始值。

注意⚠️ valueOf 在不同类型中表现不同。(数组、函数等都会重写 valueOf 方法)

8_change

测试

Symbol.toPrimitive优先

10_change

转为数字时,valueOf 优先。转为字符串时,toString 优先

11_change

判断 NaN

必须使用 Number.isNaN()isNaN() 函数。

12

===与==

===:进行比较时,不会类型转化。

==:进行比较时,会类型转化。

  • 如果 x 或 y 中有一个为 NaN,则返回 false

  • 如果 x 与 y 皆为 nullundefined 中的一种类型,则返回 true(null == undefined // true);否则返回 false(null == 0 // false);

  • 如果 x,y 类型不一致,且 x,y 为 StringNumberBoolean 中的某一类型,则将 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

432

可以看到 > 可以对数组进行比较。(按字典序返回结果)

PS.如果字典序排列中,你缺少了一位,缺少的这一位比比0小

999

参考

Falsy

MDN

JavaScript核心概念(1):类型转换

Symbol.toPrimitive

JavaScript 运算符规则与隐式类型转换详解

CORS

CORS

跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求

引用来自MDN

我的完整代码

简单请求和复杂请求

两者的区别在于:是否需要发送预检请求。

满足以下条件就可以不需要发送预检请求👇

  • 使用以下方法之一:
    • GET
    • POST
    • HEAD
  • Header头不超过以下几种:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type包含以下几种:
      • text/plain
      • multipart/form-data
      • application/x-www-form-urlencoded
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width

简单请求测试

使用axios库来简化ajax操作。

发送一个get请求

当我们像平常一样发送 GET 请求时,会出现跨域报错。

1

解决的方法就是加上Access-Control-Allow-Origin:*这个 Header。

2

可以看到我们的跨域请求正常收到了。

其实你可以通过服务端的日志看到,其实服务端是正常返回的。浏览器帮你拦截了这个请求。

带 Cookie 的 GET 请求

axios 需要打开 withCredentials: true (默认为false)

const api = axios.create({
  timeout: 1000,
  withCredentials: true
});

3

可以看到报错了。这里需要我们将Access-Control-Allow-Origin:指定明确的域名。

5

还是报错这里需要我们加上另一个 Header Access-Control-Allow-Credentials 为 true。

80

可以看到请求正常了。

客户端需要获取服务端设置的 Header

你在服务端设置 Header

ctx.res.setHeader('X-Custom-Header',"TestCors")

然后客户端打印下可以拿到的 Header

100

可以看到确实没有拿到我们需要的 Haeder。我们需要指定 Access-Control-Expose-Headers

ctx.res.setHeader('Access-Control-Expose-Headers', 'X-Custom-Header')

客户端再次打印可以拿到的 Header。

1

可以看到我们需要的 Header已经正常了。

复杂请求测试

复杂请求需要在正式请求前加一个 Haeder。

我们可以看看一个服务端的日志。

3

发送了2次请求:一次预检、一次正式请求。

我们发送一个 PUT 请求

我们把客户端和服务端都设置成 PUT。

5

我们需要将 Access-Control-Allow-Methods设置成 PUT。(这里最好返回所有支持的方法,以免多次预检)

1

我们可以在 Network 中看到 OPTIONS 请求。

2

可以看到 PUT 请求请求正常。

减少发送预检的次数

我们可以设置 Access-Control-Max-Age 来设置预检的有效期。

ctx.res.setHeader('Access-Control-Max-Age', '30')

打印服务端的日志👀来看清每次请求

3

当超过设置的时间后,再次请求。可以看到又请求了。

30

客户端需要获取服务端设置的 Header

同上

服务端获取客户端设置的 Header

我们在客户端设置 Header,然后在服务端获取。

const api = axios.create({
  timeout: 1000,
  headers: { 'X-Custom-Header': 'foobar' }
});

我们可以看到报错了。

1

这里我们需要指定 Access-Control-Allow-Headers

ctx.res.setHeader('Access-Control-Allow-Headers', 'X-Custom-Header')
//省略一些代码
ctx.req.headers['x-custom-header']

1

可以看到我们获取了客户端设置的 Header。

关于 Access-Control-Request-Headers

引用MDN来自:请求头 **Access-Control-Request-Headers **出现于 preflight request(预检请求)中,用于通知服务器在真正的请求中会采用哪些请求头。

没试出作用😰。

关于跨域的误区

  1. ✕ 动态请求就会有跨域的问题

✔ 跨域只存在于浏览器端,不存在于安卓/ios/Node.js/python/ java等其它环境

  1. ✕ 跨域就是请求发不出去了

✔ 跨域请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了

引用来自我知道的跨域与安全

参考

HTTP访问控制(CORS)

跨域资源共享 CORS 详解

二分查找

二分查找

二分查找细节是魔鬼。

while (left <= right)与while (left < right)

取决于你的区间是闭区间[0,len-1],还是左闭右开 [0,len)

left = mid + 1,right = mid与left = mid + 1,right = mid - 1

[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
}

参考

代码和思路都参考二分查找细节详解,顺便赋诗一首

540-有序数组中的单一元素

540-有序数组中的单一元素

思路

  • 要去:O(log n)时间复杂度和 O(1)空间复杂度

  • 基础思路:二分

  • 如果mid左右都不存在与mid重复的数,则找到目标数

  • mid左找到与mid重复的数

    • 判断mid左边数组的长度-1是不是奇数
    1. 是,则舍去右边。right=mid-2
    2. 否,则舍去左边。left=mid+1
  • 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]
    }
  }
}

Flex

Flex

flexbox

此图引用MDN

属性

  • 父容器设置

    • `justify-content:分配弹性容器主轴的元素之间及其周围的空间
    • flex-direction:主轴的方向
    • flex-warp:是否换行
    • flex-flow: flex-direction 和 flex-wrap 的简写
    • align-items:当前行上,弹性项目沿侧轴默认如何排布
    • align-content:在侧轴上的对齐方式。(生效条件:允许换行)
  • 子容器设置

    • order:决定子容器的排列顺序
    • flex-grow:伸缩-扩展基数
    • flex-shrink:收缩规则
    • flex-basis: 在主轴方向上的初始大小
    • flex:简写 flex-grow, flex-shrinkflex-basis
    • align-self:覆盖父容器的align-items。

测试

justify-content

  • space-evenly:均分空白。

  • space-around:首尾空白减半,其他均分。

  • space-between:首尾对齐,均分空白。

  • stretch:伸展

  • flex-start、center、flex-end

    ​ space-evenly

    ​ space-around

    ​ space-between

3

flex-direction

  • row :默认。从左到右排列

  • row-reverse:从右到左排列

  • column:从上到下排列

  • column-reverse:从下到上排列

    ​ column-reverse排列

55

flex-wrap

  • nowrap:默认不换行
  • wrap:允许换行
  • wrap-reverse:允许换行,但是 cross-startcross-end 互换。

2

align-items

  • center

  • flex-start

  • flex-end

  • baseline

    • first baseline
    • last baseline
  • stretch

    ​ center

4

​ flex-strat

6

​ flex-end

7

​ last baseline

8

align-content

  • center、flex-start、flex-end

  • space-between、space-around、space-evenly

  • stretch

  • baseline

    ​ center

10

​ space-between

11

当出现换行时,align-content和align-items的行为

  1. align-content: normal或者不设置

可以看到弹性容器被分成上下2 个子项。空间平均分配,子项行高占满。

100

  1. 再设置align-items:center

102

align-items以当前行高进行渲染。

  1. align-content: space-around;

    101

可以看到 行高被压缩,每行以当前行内最高的元素为基础。

  1. 再设置align-items:center

103

align-items以当前行高进行渲染。

order

不设置默认为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>

1

flex-grow

默认设置为0。

它指定了flex容器中剩余空间的多少应该分配给项目

剩余的空间是flex容器的大小减去所有flex项的大小加起来的大小。如果所有的兄弟项目都有相同的flex-grow系数,那么所有的项目将获得相同的剩余空间,否则将根据不同的flex-grow系数定义的比例进行分配。

​ 第一项flex-grow:1

2

​ 第一项flex-grow:0.6

5

flex-shrink

默认设置为0。为0时不参与收缩。

flex 元素仅在默认宽度之和大于容器的时候才会发生收缩,其收缩的大小是依据 flex-shrink 的值。

​ 总宽度300px,子项宽度为100px,指定2个flex-shrink: 1

6

flex-basis

默认设置为auto。(含义是 "参照我的widthheight属性")

指定了 flex 元素在主轴方向上的初始大小。

如果不使用 box-sizing 改变盒模型的话,那么这个属性就决定了 flex 元素的内容盒(content-box)的尺寸。

flex

单值语法: 值必须为以下其中之一:

  • 一个无单位数 它会被当作的值。
  • 一个有效的**宽度(width)**值: 它会被当作 <flex-basis>的值。
  • 关键字noneautoinitial.

双值语法: 第一个值必须为一个无单位数,并且它会被当作 <flex-grow> 的值。第二个值必须为以下之一:

  • 一个无单位数:它会被当作 <flex-shrink> 的值。
  • 一个有效的宽度值: 它会被当作 <flex-basis> 的值。

三值语法:

  • 第一个值必须为一个无单位数,并且它会被当作 <flex-grow> 的值。
  • 第二个值必须为一个无单位数,并且它会被当作 <flex-shrink> 的值。
  • 第三个值必须为一个有效的宽度值, 并且它会被当作 <flex-basis> 的值。
  • auto===flex: 1 1 auto:完全弹性
  • initial===flex: 0 1 auto:不能伸长
  • none===flex: 0 0 auto :不弹性

align-self

​ 指定align-content为center,第一项alidn-self为start。

33

参考

CSS 常见布局方式

[使用 CSS 弹性盒子](

CommonJS和ES Modules

CommonJS和ES Modules

导出的差异

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 }

然后我们执行得到以下结果

123

可以对象中的子对象发生了改变,证明了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 = {}
}

然后我们执行得到以下结果

444

使用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时使用。

参考

利用 webpack 理解 CommonJS 和 ES Modules 的差异

146. LRU缓存机制

146. LRU缓存机制

思路1

利用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谁的了😰

思路2

不使用map,改用对象基础key-value,和双向链表方便查找断链

dummyHead 和 dummyTail

亚头节点和伪节点方便链操作一般化

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--
  }
}

参考

❤双向链表的有序性 与 哈希表的快速查询 结合

变量提升

变量提升

注意⚠️ 变量提升不适用于 let 、const。一般提及都是var、function。

提起 变量提升 就要提起 预编译。分为以下4步:

  1. 创建当前的活动对象。创建对应的执行上下文。
  2. 寻找函数形参,寻找var声明。设定为 undefined。
  3. 将实参与形参相互统一。
  4. 寻找函数声明挂载在当前活动对象上。函数名为 key,函数值为 value。

具体测试

变量提升忽略块级作用域

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 会直接挂载到全局变量上。

函数、形参、var声明重名

function a(a1, b1) {
  console.log(a1)//function

  var a1 = 'var'
  console.log(a1)//var

  function a1() {}
  console.log(a1)//var
}

a('a传参', 'b传参') 

可以看到形参直接没有出现,我们可以从预编译的过程中找到答案。

  1. 函数声明在最后声明,覆盖了形参。

  2. a1的赋值,覆盖了函数。

总结

函数声明优先级会高于值声明。

变量提升忽略 块级作用域

如果一个值没有声明就使用,不会变量提升。但是当执行时,会直接挂载到全局环境上。

协议缓存

协议缓存

我的完整代码

与强缓存的对比

强缓存:状态码是200,服务器并不会收到请求,浏览器直接使用缓存。

协议缓存:服务器收到请求并进行比较处理,如果文件没有改变就返回状态码 304,否则加上 Haeder 标记、状态码 200 、并重新返回资源。

强缓存的优先级高于协议缓存

Header 一览

请求头 if-modified-since 和响应头 last-modified

The Last-Modified是一个响应首部,其中包含源头服务器认定的资源做出修改的日期及时间。 它通常被用作一个验证器来判断接收到的或者存储的资源是否彼此一致。由于精确度比 ETag 要低,所以这是一个备用机制。包含有 If-Modified-SinceIf-Unmodified-Since 首部的条件请求会使用这个字段。

语法

Last-Modified: <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT

引用MDN

缺陷

  • 最小单位为秒,如果资源更新的速度是秒以下单位,就会产生 bug 。()
  • 如果文件是通过服务器动态生成的,那么该方法的更新时间永远是生成的时间,尽管文件可能没有变化,所以起不到缓存的作用。

请求头 if-none-match 和响应头 etag

ETagHTTP响应头是资源的特定版本的标识符。这可以让缓存更高效,并节省带宽,因为如果内容没有改变,Web服务器不需要发送完整的响应。而如果内容发生了变化,使用ETag有助于防止资源的同时更新相互覆盖(“空中碰撞”)。

语法

ETag: W/"<etag_value>"
ETag: "<etag_value>"

引用MDN

测试

原生使用if-modified-since 和 last-modified

  1. 以防万一我们先关闭强缓存 Cache-control:no-cache。
  2. 我们获取 Header 中的 if-modified-since
  3. 通过 fs 模块获取文件最后一次修改时间。
  4. 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()
}

原生使用请求头 if-none-match 和响应头 etag

与上次的步骤基本一致。

  1. 强校验不一样的就是通过 crypto 模块获得文件的 md5 值,每次比较的是 md5 值而不是时间。
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()
}
  1. 弱检验:根据文件的最后修改时间和文件大小计算合并得到。(参考express里的实现)
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 时,每次还会请求资源。

参考

一文读懂前端缓存

MDN

通过 koa2 服务器实践探究浏览器HTTP缓存机制

手写实现call, apply,bind

本文章的基础

  1. 原型

  2. this的使用

call

介绍

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

apply

介绍

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

bind

介绍

返回一个原函数的拷贝,并拥有指定的 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

参考

MDN

手写JS函数的call、apply、bind实现

手写call、apply、bind实现及详解

强缓存

强缓存

我的完整代码

使用好处

当 web 缓存发现请求的资源已经被存储,它会拦截请求,返回该资源的拷贝,而不会去源服务器重新下载。这样带来的好处有:缓解服务器端压力,提升性能(获取资源的耗时更短了)。对于网站来说,缓存是达到高性能的重要组成部分。MDN

按存储位置划分

  1. Service Worker(不涉及)

  2. Memory cache

  3. Disk cache

  4. Push Cache(不涉及)

属性一览

Cache-control(主流)

  • no-cache:强制要求缓存把请求提交给原始服务器进行验证(协商缓存验证)

  • no-store:不使用任何缓存。

  • private:只能被单个用户缓存,不能作为共享缓存。

  • public:表明响应可以被任何对象缓存。

  • max-age:设置缓存存储的最大周期(秒)。

Pragma(兼容)

  • no-cache:与 Cache-Control: no-cache 效果一致

MDN建议:只在需要兼容 HTTP/1.0 客户端的场合下应用 Pragma 首部

Expires(兼容)

声明资源的过期时间。有max-age的情况下,max-age优先。

测试

设置上Cache-control

给我的图片加上Cache-control

ctx.res.setHeader('Cache-control', 'public,max-age=86400')

请求页面后,我们看到服务器日志中,资源的请求都是正常的。

1

我们多次刷新页面。

300

可以看到图片已经缓存在内存中了,然后我们查看日志。

2

可以看到使用了缓存之后,服务器再没有收到请求。

测试 Expires 和 Cache-control 的优先级

  let exp = new Date()
  exp.setTime(exp.getTime() + 60000)//毫秒
  ctx.res.setHeader('Expires', exp.toUTCString())
  ctx.res.setHeader('Cache-control', 'public,max-age=86400')//秒

我们同时设置这两个属性。

3

等1分钟后刷新,还是强缓存状态。 Cache-control优先级更高。

决定使用 Memory cache 还是 Disk cache ?

这里我简单的做个测试发现:

  • 如果你短时间多次刷新(猛刷),Disk cache 会变成 Memory cache。
  • 对不同的文件有不同策略,css就容易进Disk cache。

这一块没有找到好的分享,以上的结论并不完全对。

参考

第7题-浏览器缓存命中策略

Cache-Control

二叉堆

二叉堆

可以实现大顶堆或者小顶堆。

可以便利的解决一类 topK 问题。

215. 数组中的第K个最大元素

可视化工具

以小顶堆为例:

  • 根节点小于所有子节点

  • 用列表来模拟树的结构

    • 得知子节点的序号index,求父节点Math.floor((index - 1) / 2)
    • 得知父节点的序号index,求左子节点index*2+1,求右节点index*2+2
  • 插入:节点放在数组末尾

    • 插入后与父节点进行比较

      • 若父节点小于子节点,进行交换
    • 重复该操作,直到父节点比子节点小

  • 弹出根节点

    • 将根节点与数组最后一位进行交换

    • 然后pop()弹出当前堆的最小值

    • 进行重建

      • 左右子节点选出最小数

      • 与根节点进行比较

        • 根节点>最小数,交换节点,递归重建

        • 根节点<最小数,结束

插入

11

12

弹出

444

555

666

777

代码

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])

安装puppeteer踩坑

安装puppeteer踩坑

使用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()
})()

参考的解决方案

puppeteer新手遇到的坑

null 与 undefined

null 与 undefined

使用

在我们的日常使用中 undefined 被翻译为未定义,null 则表达“无”。

举个🌰,我们随手声明个变量。

var a //undfined

可以看到变量被隐式声明为 undefined ,而如果我们要声明变量为 null,需要显式声明。

var a = null

这2个属性在平时使用中,区别感觉并不明显。都可以表达“空”。

差别

undefined

  • undefined全局对象的一个属性。
  • 变量被声明了,但没有赋值时,就等于 undefined。
  • 调用函数时,应该提供的参数没有提供,该参数等于 undefined。
  • 对象没有赋值的属性,该属性的值为 undefined。
  • 函数没有返回值时,默认返回 undefined。

全局的 window.undefined 不可被改写。例如下图。

55_change

局部使用 undefined 的危险

66_change

如果一定需要使用 undefined 时可以使用 viod

void 运算符通常只用于获取 undefined的原始值,一般使用void(0)(等同于void 0)。在上述情况中,也可以使用全局变量undefined 来代替(假定其仍是默认值)。

引用来自MDN

77_change

null

  • null 是一个字面量。
  • 理解 null 感觉更像是一个没有创建的对象。(原型链的终点就是 null

30_change

参考

undefined与null的区别

undefined

JavaScript深入理解之undefined与null

js创建数组耗时的思考

js创建数组耗时的思考

我在写这题(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已经不能通过)

image-20200823230639771

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

123

为什么N^2 和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)
}

222

这才是N^1的算法才有的速度。

对比

观察这2个算法,差别就在于创建数组

我在常数空间算法中,使他创建1维数组

333

  const dp = Array.from({ length: Len }, () => 0

我在常数空间算法中,使他创建2维数组

444

 const dp = Array.from({ length: Len }, () =>
    Array.from({ length: 3 }, () => 0)
  )  

我在常数空间算法中,使他创建3维数组

555

  const dp = Array.from({ length: Len }, () =>
    Array.from({ length: 3 }, () => Array.from({ length: 2 }, () => 0))
  )

乖乖!可以看到创建数组的耗时远比算法本身的运行时间长太多。

和java进行对比

相同的算法,java的耗时就没有这么夸张。

java3维耗时

java3

java2维耗时

java2

java常数耗时

java1

总结

js在创建多维数组上性能很差,拿来写算法避免创建多维数组

奇怪的Function.prototype.call.bind()

奇怪的Function.prototype.call.bind()

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
}

Function.prototype.call()

先看一个简单的:

var fun = Function.prototype.mycall(Object.prototype.toString)

Object.prototype.toString.Function.prototype()

Function.prototype是函数的原型对象,所有函数都要继承它,相当于执行了一个空函数。

Function.prototype.mycall.mycall()

接下来是升级版:

var toStr1 = Function.prototype.mycall.mycall(Object.prototype.toString)

Object.prototype.toString.mycall()

Object.prototype.toString.call(global)

Function.prototype.mycall.mybind()

终极版:

var toStr1 = Function.prototype.mycall.mybind(Object.prototype.toString)

Function.prototype.mycall.myapply(Object.prototype.toString, [[123]])

这与其他函数不同,bind会返回一个绑定好this的闭包函数。

  1. 返回的是一个可执行的函数。它不会像上面两个例子一样不停往前执行。
  2. 相当于一个柯里化函数,可以再次向它传参数。

参考

原型

手写实现call,apply,bind

一句有趣的JS代码

js Function.prototype.call.bind(Array.prototype.slice),为啥要这么写?

在JavaScript中借用方法

JS魔法堂:再次认识Function.prototype.call

关于Function.prototype.call.bind()的疑惑?

深拷贝

深拷贝

深拷贝的原因

对引用类型(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

我们拷贝的对象中的属性改变,原对象中的属性也会改变。

实现深拷贝

JSON.parse(JSON.stringify())

此方法可以实现深拷贝,但是有缺点:

  • 丢失原型,会直接指向Object
  • 循环引用会报错
  • 深拷贝的对象有限
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:来判断参数的的类型

    • const typeString = Object.prototype.toString.call(obj)能够有效的判断类型,好用。
  • 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)
}

参考

面试官:请你实现一个深克隆

JS深拷贝总结

LFU缓存

LFU缓存

为了实现O(1)时间复杂度,此题需要使用双向链表

需要使用map来快速获取双向链表的值

需要使用frequentMap来获取同频率的所有节点。

需要维护一个变量minFreq来指向frequentMap中频率最低

更新minFreq的时机:节点已经从原链表中删除,节点插入到新链表之后

  • get:看map中是否有key?
    • 有=>从x频率中删除,放到x+1频率的首部。(更新minFreq)
    • 无=>返回-1
  • put:看map中是否有key?
    • 有=>从x频率中删除,放到x+1频率的首部,更新value。(更新minFreq)
    • 无=>是否超出容量上限
      • 是=>删除使用频率最少,时间最久远的
      • 无=>容量+1
      • finally=>new 一个新节点,将其放入map中和频率为1的链表首部。(minFreq=1)

9e1fd010642e306d4616e6580d0ac75ee4fd1ecca7a3351ae1be415c35d10f5a-01

此图作者:liweiwei1419

链接:https://leetcode-cn.com/problems/lfu-cache/solution/ha-xi-biao-shuang-xiang-lian-biao-java-by-liweiwei/

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

使用双向链表代替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)
  }
}

Centos7服务器配置

原文:https://www.cnblogs.com/zhloong/p/installohmyzsh.html

原作者:zhloong

安装zsh 和 oh-my-zsh

  1. 安装zsh

    sudo yum update

    yum install zsh

  2. 安装git
    yum install git

  3. 切换默认shell
    chsh -s /bin/zsh

  4. clone from GitHub
    git clone git://github.com/robbyrussell/oh-my-zsh.git ~/.oh-my-zsh

  5. 复制默认.zshrc
    cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc

安装插件

语法高亮插件

  1. 安装
    git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting
  2. 配置
    在~/.zshrc的plugins中加入zsh-syntax-highlighting

自动补全插件

  1. 安装
    git clone git://github.com/zsh-users/zsh-autosuggestions $ZSH_CUSTOM/plugins/zsh-autosuggestions
  2. 配置
    在~/.zshrc的plugins中加入zsh-autosuggestions

Portainer远程连接docker

在本地使用portainer来管理远程服务器上的docker。

安装docker参考上面👆的教程

portainer直接使用docker拉取镜像,映射端口即可。

具体流程:

  1. systemctl status docker 找到docker配置文件的位置

  2. vim /usr/lib/systemd/system/docker.service 编辑这个文件

    ExecStart的后面加上 -H 0.0.0.0:2376 开启远程访问(注意打开防火墙)

  3. 重启docker

    systemctl daemon-reload

    systemctl restart docker

  4. Portainer配置

    33

    完成配置后,你就能正常访问。

    但是存在安全问题,你的docker谁都可以访问,添加tls提高安全性。

增加tls增加安全性

参考文章中配置TLS的操作

原文链接:Docker配置TLS认证开启远程访问

原文作者:张巍的烂笔头

1、2、3步推荐在本地创建一个文件夹进行存放

cd ~/
mkdir ssl
cd ./ssl
  1. 生成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
  2. 创建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
  3. 创建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
  4. 上传

    将本地的ca.pemserver-cert.pemserver-key.pem这3个文件上传到服务器中。

    推荐将这3个文件放在一个文件夹中

    cd /root
    mkdir ./tls

    上传文件可以使用scp命令,也可以使用vscode的远程模式。只需要装个插件Visual Studio Code Remote - SSH,对不熟悉vim操作的同学比较友好。

  5. server端配置(远程)

    vim  /usr/lib/systemd/system/docker.service 

    注意替换为自己的路径,在ExecStart中写入tls的配置

    7777

  6. portainer配置

    11

选择你对应的文件并update endpoint,大功告成。

节流与防抖

节流与防抖

节流:一段时间内,只能触发一次函数。

防抖:某段时间内,不管函数触发多少次,我只生效最后一次。(触发函数时,时间重置)

测试

不使用节流防抖

在我们滚动页面时,大量触发滚动事件。

1

节流

以下代码修改来自前端性能优化原理与实践

<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次触发的时间间隔小于你的设定值,则不触发。

scroll

可以看到使用了节流,触发函数的次数明显减少。

防抖

以下代码来自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>

可以看到,当我停下输入一段时间后才执行函数。

ajax

节流结合图片懒加载

1

参考

7分钟理解JS的节流、防抖及使用场景

掘金小册前端性能优化原理与实践

Cookie-SameSite

Cookie-SameSite

在我理解中浏览器相当于一个 Cookie仓库,当你发出请求时,都会从自己这个仓库中去搜寻目标域名的cookie,如果满足发送的条件,则会自动的将 Cookie 放到请求的Header中。

什么是第三方 Cookie

第一方 Cookie:我们的域名是 ybf.com,访问我们的服务时,会自动设置 ybf.com 域名下的 Cookie。

第三方 Cookie:我们的域名是 ybf.com,我的网页访问时,使用 acc.com 这个域下的资源, acc.com 这个域会设置一个属于 acc.com 的 Cookie。

SameSite属性介绍

Cookie 的SameSite属性用来限制第三方 Cookie,从而减少安全风险。

  • Strict:只会在第一方上下文中发送,不会与第三方网站发起的请求一起发送
  • Lax:Cookies允许与顶级一起发送,并将与第三方网站发起的GET请求一起发送。这是浏览器中的默认值
  • None:将在所有上下文中发送,即允许跨域发送
请求类型 以前 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 的警告。

30

这里不指定 SameSite 的值,看到 Cookie 工作为 None 的特性。

90

设置为 None

可以看到有警告:需要将 Secure 设置为 True。

40

这就要求我们需要将 http 升级为 https。

设置为 Lax

我们先给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标签的网络请求。

100

可以看到加载imge时不会加载 Cookie。然后我们点击a标签进行跳转。

200

跳转时添加上了 Cookie。

参考

SameSite cookies

Cookie 的 SameSite 属性

SameSite小识

excel 相关问题

这里使用 exceljs

特定列设置预设值

sheet.dataValidations.model['A2:A9999'] = {
        type: 'list',
        allowBlank: true,
        formulae: ['"male,female,other"']
};

Iterator

遍历器

遍历器(Iterator)就是一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作

平常使用的(...)扩展运算符for...of... 都在隐式的调用的 Iterator

原生具备 Iterator 接口。

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的 arguments 对象
  • NodeList 对象

已经存在的Symbol.iterator具有以下特性

属性 True/False
writable false
enumerable false
configurable false

自己实现一个Iterator

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...

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 ,只能在当前对象上定义新的 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()和 throw()

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)
}

关于String的补充

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 入门

简单了解下柯里化

柯里化

前言

上次在手写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版)》

作用域分类

作用域分类

全局作用域

在浏览器中是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)//报错

块级作用域

什么是块级作用域?

由{}封闭的代码块,👇拿java举个例子

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 是动态作用域)

[[scope]]属性

我们可以看到函数声明时,作用域已经确定

参考

《JavaScript高级程序设计》

谈谈 JavaScript 的作用域

《JavaScript忍者秘籍(第2版)》

JavaScript深入之词法作用域和动态作用域

Morris遍历树

Morris遍历

可以只使用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
}

参考

JS中的二叉树遍历

529-扫雷游戏

529-扫雷游戏

这是一题类岛屿问题,分析情况:

  • 踩到未挖出的雷,改为‘X'直接结束游戏
  • 踩到空白位置先验证附近8格是否有雷
    • 如果没雷就改为‘B’,继续揭示其他8格
    • 如果有雷就改为雷数,结束

这题可以去重,可以避免走重复的路径

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
}

vue双向绑定

双向绑定

通过Object.defineProperty劫持属性。

通过订阅-发布模式来修改dom节点。

初始化流程:

23

运行流程:

24

代码参考来自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>

参考

vue 的双向绑定原理及实现

reduce、forEach手写

reduce、foEach手写

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()

BFC

BFC

介绍

块格式化上下文(Block Formatting Context,BFC) 是Web页面的可视CSS渲染的一部分,是块盒子的布局过程发生的区域,也是浮动元素与其他元素交互的区域。

以下设置会产生BFC

  • 根元素(<html>)
  • 浮动元素(元素的 float 不是 none
  • 绝对定位元素(元素的 positionabsolutefixed
  • 行内块元素(元素的 displayinline-block
  • overflow 值不为 visible 的块元素
  • 弹性元素(displayflexinline-flex元素的直接子元素)
  • 网格元素(displaygridinline-grid 元素的直接子元素)
  • 多列容器(元素的 column-countcolumn-width 不为 auto,包括 column-coun1

以上引用来自MDN

我的理解

我们可以知道根元素<html>就是一个 BFC。当我们在页面中开启新的BFC,这就像在在旧 Html 中,开启了一个新的 Html 。这两个 Html 不能相互影响。

BFC的范围

  <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>
  • 这里有2个BFC:
    • div_1 包括 div_2、div_3、div_4、div_5、div_6
    • div_6 包括 div_7、div_8

使用

  1. 浮动定位和清除浮动时只会应用于同一个BFC内的元素。
  2. 浮动不会影响其它BFC中元素的布局,而清除浮动只能清除同一BFC中在它前面的元素的浮动。(不会重叠浮动元素)
  3. 外边距折叠也只会发生在属于同一BFC的块级元素之间。

测试

高度塌陷

  <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_change

经典的高度塌陷。想解决它,利用上面的属性1。我们给 .box 添加上 overflow: hidden; 来开启BFC

2_change

清除前一个浮动的影响

  <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>

5_change

可以看到这里的 box1 出现了环绕的效果。这里清除前一个浮动的影响,可以使 box2 开启 BFC。

6_change

外边距折叠

  <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>

10_change

可以看到这里上下边距出现了重叠。解决此方法可以给 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>

11_change

参考

块格式化上下文

深入理解BFC

学习 BFC (Block Formatting Context)

vue-router 刷新后无法再次命中 中文 路由

主要是中文在路径中会转义

解决方法:alias 重命名下转义后的中文

const sRouters = createRouter({
    history: createWebHashHistory('/src/scene/'),
    routes: [
        {
            path: '/路由',
            alias: encodeURI('/路由'),
            component: Blank
        },
    ],
});

Cookie

Cookie

我的完整代码

使用场景

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")
    }

    具体实测数据:

    • Chrome、Edge => 4034 B
    • Firefox => 4096 B
    • Safair => 4.0 KB
  • 每个域名下的 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 和 expires

maxAge标示在 Cookie 失效之前需要经过的时间。(主推)

expires标示 Cookie 的最长有效时间。(兼容)

expiresmaxAge决定了 Cookie 存活时间

注意

  • expiresmaxAge的单位都是毫秒

  • 设置时间都是 GMT 时间,不是当地时间

domain 和 path

Domain 标识指定了哪些主机可以接受 Cookie。

Path 标识指定了主机下的哪些路径可以接受 Cookie。

DomainPath 组合决定了 Cookie 应该发送给哪些URL。

secure 和 httpOnly

标记为 Secure 的 Cookie 只通过被 HTTPS 协议加密过的请求发送给服务端

标记为 httpOnly 的 Cookie 不能被客户端 JavaScript 脚本调用

SecureHttpOnly 加强了 Cookie 的安全性

SameSite

SameSite属性用来限制第三方 Cookie。

创建 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

客户端

Cookie 挂载在document.cookie上,你可以在控制台打印下试试。( Cookie没有开启httpOnly,就能拿到id=value)

我现在有2个 Cookie,一个开启 httpOnly,另一个不开启。

1

可以看到我们只能拿到没有开启 httpOnly 的 Cookie

2

服务端

你本地存在 Cookie 时,在服务端设置获得

ctx.cookies.get('age')

浏览器自动帮我们添加上 Cookie 这个 Header

4

我能不能通过 Set-Cookie 这个 header 拿到cookie ?

答案:不能。

👉引用来自你真的完全理解XMLHttpRequest吗?

W3C的 xhr 标准中做了限制,规定客户端无法获取 response 中的 Set-CookieSet-Cookie2这2个字段,无论是同域还是跨域请求。

修改 Cookie

客户端

和创建 Cookie 步骤一致,可以改变 value 和 maxAge / expires。

服务端

直接重新设置即可。可以改变 value,maxAge / expires,httpOnly,secure

删除 Cookie

客户端

expires 设置为过期即可

服务端

直接重新设置即可,maxAge 设置为-1,expires 设置为过期时间

修改和删除注意

需要保持 path 和 domain 一致。

如果改变 path。原 Cookie 修改失败,会创建一个新 Cookie。

对一个已经存在对 Cookie,你不可以设置成其他二级域名

实际测试

会话 Cookie

浏览器关闭之后它会被自动删除,也就是说它仅在会话期内有效。

  • 会话期 Cookie 不需要指定过期时间(Expires)或者有效期(Max-Age)。

  • 指定Max-Age0 时,也是会话 Cookie

不指定domain和path

默认为当前文档的主机不包含子域名)。如果指定了Domain,则一般包含子域名。

path默认为\

第一条为指定域名,第二条不指定域名

6

子域名测试

在二级域名 ybf.com 设置 Cookie,去子域名 test.ybf.com 获取 Cookie

  1. 指定域名的情况下

11

  1. 不指定域名的情况下

子域名将无法获取父域名设置的cookie

13

开启 secure 和 httpOnly

👉这里需要你准备一份证书

7

客户端 js 能否设置第三方域名

先在 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 看看是否设置上了😏。

12

答案:不可以。

一个服务端能否设置2个域名的 Cookie

  ctx.Cookies.set('ybf', 'ybf', {
    maxAge: 86400000,
    domain: 'ybf.com',
    httpOnly: false
  })
  ctx.Cookies.set('acc', 'acc', {
    maxAge: 86400000,
    domain: 'acc.com',
    httpOnly: false
  })

答案:不可以。

15

你设置时会出警告,设置不上。This Set-Cookie was blocked because its Domain attribute was invalid with regards to the current host url。

在 3 级域名设置 2 级域名

我们执行以下代码,在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")

18

答案:可以。而且设置完 test.ybf.com 可以直接收到 Cookie。

在 2 级域名设置 3 级域名

我们执行以下代码,在 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")

20

答案:不可以

测试平台

这里需要你频繁的改host文件,推荐使用SwitchHosts!

我使用的使用浏览器版本

  • Chrome 版本 83.0.4103.61

  • Safair 版本13.1.1

  • Edge 版本 81.0.416.72

  • Firefox 77.0

参考

MDN

Set-Cookie

聊一聊 cookie

js-cookie.js的使用

COOKIE长度限制总结

Cookie 的 SameSite 属性

尾递归

尾递归(tail call optimisation)

尾递归解决的问题

递归的次数太多了,超过了程序承载的能力。

我通过一个简易的代码,来测试下函数调用栈的能力。

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>

628-2_change

可以看到函数调用栈里的数量逐渐增加。

使用尾递归

现在对尾递归的支持并不好。浏览器只有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>

628-5_change

可以看到成功运行了,并且函数调用栈道数量一直没有发生变化。

例子

以下代码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

参考

尾递归的后续探究

尾调用优化

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.