欢迎关注我的微信公众号,微信扫下面二维码或搜索“无畏前端” ,学习中分享,分享中学习!希望与您一同进步
rashomon511 / learningrecord Goto Github PK
View Code? Open in Web Editor NEW学习资料汇总、阅读记录,持续学习,每天进步一点点.🏫🏫
学习资料汇总、阅读记录,持续学习,每天进步一点点.🏫🏫
首先让我们回顾一下,map函数的第一个参数callback:
var new_array = arr.map(function callback(currentValue[, index[, array]]) { // Return element for new_array }[, thisArg])
这个callback一共可以接收三个参数,其中第一个参数代表当前被处理的元素,而第二个参数代表该元素的索引。
而parseInt则是用来解析字符串的,使字符串成为指定基数的整数。
parseInt(string, radix)
接收两个参数,第一个表示被处理的值(字符串),第二个表示为解析时的基数。
了解这两个函数后,我们可以模拟一下运行情况
parseInt('1', 0) //radix为0时,且string参数不以“0x”和“0”开头时,按照10为基数处理。这个时候返回1
parseInt('2', 1) //基数为1(1进制)表示的数中,最大值小于2,所以无法解析,返回NaN
parseInt('3', 2) //基数为2(2进制)表示的数中,最大值小于3,所以无法解析,返回NaN
map函数返回的是一个数组,所以最后结果为[1, NaN, NaN]
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
var maxSubArray = function(nums) {
let res = nums[0];
let sum = 0;
for (let num of nums) {
if (sum > 0){
sum += num;
}
else {
sum = num;
}
res = Math.max(res, sum);
}
return res;
};
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说 Web 是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。
它的核心就在于它认为自任何站点装载的信赖内容是不安全的。当被浏览器半信半疑的脚本运行在沙箱时,它们应该只被允许访问来自同一站点的资源,而不是那些来自其它站点可能怀有恶意的资源。
所谓同源是指:域名、协议、端口相同。
同源策略又分为以下两种:
没有同源策略会怎样:
扩展:跨域第方式有哪些
const arr = [3, 15, 8, 29, 102, 22]
arr.sort((a,b) => a-b) // 3,8,15,22,29,102
arr.sort((a,b) => b-a) //102, 29, 22, 15, 8, 3
当元素的样式发生变化时,浏览器需要触发更新,重新绘制元素。这个过程中,有两种类型的操作,即重绘与回流。
重绘(repaint): 当元素样式的改变不影响布局时,浏览器将使用重绘对元素进行更新,此时由于只需要UI层面的重新像素绘制,因此 损耗较少
回流(reflow): 当元素的尺寸、结构或触发某些属性时,浏览器会重新渲染页面,称为回流。此时,浏览器需要重新经过计算,计算后还需要重新页面布局,因此是较重的操作。会触发回流的操作:
回流必定触发重绘,重绘不一定触发回流。重绘的开销较小,回流的代价较高。
css
javascript
扩展: style标签写在body后与body前有什么区别
写在head标签中利于浏览器逐步渲染(resources downloading->CSSOM+DOM->RenderTree(composite)->Layout->paint)
写在body标签后由于浏览器以逐行方式对html文档进行解析,当解析到写在尾部的样式表(外联或写在style标签)会导致浏览器停止之前的渲染,等待加载且解析样式表完成之后重新渲染,在windows的IE下可能会出现FOUC现象(即样式失效导致的页面闪烁问题)
Redux vs VUEX 对比分析
store和state是最基本的概念,VUEX没有做出改变。其实VUEX对整个框架**并没有任何改变,只是某些内容变化了名称或者叫法,通过改名,以图在一些细节概念上有所区分。
VUEX弱化了dispatch的存在感。VUEX认为状态变更的触发是一次“提交”而已,而调用方式则是框架提供一个提交的commit API接口。
VUEX取消了Redux中Action的概念。不同于Redux认为状态变更必须是由一次"行为"触发,VUEX仅仅认为在任何时候触发状态变化只需要进行mutation即可。Redux的Action必须是一个对象,而VUEX认为只要传递必要的参数即可,形式不做要求。
VUEX也弱化了Redux中的reducer的概念。reducer在计算机领域语义应该是"规约",在这里意思应该是根据旧的state和Action的传入参数,"规约"出新的state。在VUEX中,对应的是mutation,即"转变",只是根据入参对旧state进行"转变"而已。
总的来说,VUEX通过弱化概念,在任何东西都没做实质性削减的基础上,使得整套框架更易于理解了。
另外VUEX支持getter,运行中是带缓存的,算是对提升性能方面做了些优化工作,言外之意也是鼓励大家多使用getter。
function _new(fn, ...arg) {
const obj = Object.create(fn.prototype);
const ret = fn.apply(obj, arg);
return ret instanceof Object ? ret : obj;
}
观察者模式中主体和观察者是互相感知的,发布-订阅模式是借助第三方来实现调度的,发布者和订阅者之间互不感知
区别与适用场景
总的来说,发布-订阅模式适合更复杂的场景。
在「一对多」的场景下,发布者的某次更新只想通知它的部分订阅者?
在「多对一」或者「多对多」场景下。一个订阅者依赖于多个发布者,某个发布者更新后是否需要通知订阅者?还是等所有发布者都更新完毕再通知订阅者?
在react16的之前生命周期其实主要分为四个阶段:组件初始化、组件挂载、组件更新、组件卸载。
在该阶段组件中的构造方法 constructor() 接受 props 接收父组件传下来的 props。还可以在 constructor() 内部定义定义this.state 的初始内容。注意:在组件中写了 constructor 方法就必须在里面使用 super(),并且应在其他语句之前前调用 super(props)。否则,this.props 在构造函数中可能会出现未定义的 bug。
constructor(props) {
super(props)
console.log(this.props) // 在内部可以使用props
this.state = {
//定义state初始值
}
}
在组件将要挂载到 DOM 前调用,只会被调用一次,在该方法中修改 state 的值,并不会引起组件重新渲染。(数据请求等异步操作不建议写在该方法内,异步操作可能阻塞 UI)。
componentWillMount(){}
该函数会创建一个虚拟 DOM,用来表示组件的输出。只能通过 this.props 和 this.state 访问数据,且不能在里面执行 this.setState 更该组件状态。在 render 中可以返回 null、false 或者任何 React 组件,只能出现一个顶级组件,不能返回一组元素(在 react16 中有所改善,可以返回一组元素或单个字符串)。
Render(){
return (
// react组件
)
}
组件挂载到 Dom 后调用,且只调用一次。此时组件已经生成对应的 DOM 结构,可以在该函数中通过ReactDOM.findDOMNode()访问到真实的 DOM 或者通过 this.refs.[refName] 属性获取真实 DOM 。(数据请求等异步操作建议写在该方法内)
componentDidMount() {
// 进行异步数据请求或者获取dom
}
该函数接受一个参数 nextProps,当父组件重传props时会调用。拿到新的 props 与旧的 props 来比较是否变化,若变化可以通过 this.setState 更新 state。当然也可以不比较新旧 props 值直接更新 state。
componentWillReceiveProps(nextProps) {
// 示例
if (nextProps.state !== this.props.state) {
this.setState({
state: nextProps.state
});
}
}
官方提示:在componentWillReceiveProps中调用 this.setState() 将不会引起第二次渲染。
由于每次子组件接收到新的props,都会重新渲染一次,除非你使用 shouldComponentUpdate 来阻止重新渲染,但是你可以 componentWillReceiveProps 中根据新的 props 更新 state,虽然更新state也会触发一次重新渲染,但并不会触发额外的render。
该函数是唯一可以控制组件渲染的生命周期。如果 props 和 state 的改变不需要重新渲染组件。则可以在该函数内返回 false,阻止组件的重新渲染。为了优化组件性能,减少组件的不必要渲染。
shouldComponentUpdate(nextProps, nextState){
// return true 更新组件
// return false 则不更新组件
}
shouldComponentUpdate 方法返回 true 后,在组件即将进行重新渲染前调用该函数(注意不要里面去更新 props 或者 state,会导致组件进入死循环),在这之后会调用 render 方法进行重新渲染。
componentWillUpdate(nextProps,nextState) {
// 不要在此处更新props或state
}
组件被重新渲染后该方法会被调用,可以拿到更新前的 props 和 state 。除了首次渲染时调用的componentDidMount,之后每次渲染都会调用该函数。和 componentDidMount 类似的是可以在这里操作更新后的DOM。
componentDidUpdate(prevProps,prevState) {}
该函数在组件卸载前被调用,可以在执行一些清理工作,比如清除组件中使用的定时器或者事件监听器,以避免引起内存泄漏。
componentWillUnmount() {
// 清除定时器或事件监听器
}
react16的生命周期新引入了三个新的生命周期函数:getDerivedStateFromProps,getSnapshotBeforeUpdate,componentDidCatch,弃用的三个生命周期函数:componentWillMount、componentWillReceivePorps,componentWillUpdate。其他的生命周期功能与前面介绍的相同。
该函数在组件挂载阶段和后续更新阶段调用,根据 props 和 state 两个参数,计算出预期的状态改变,返回一个对象表示新的 state进行更新;如果不需要更新,返回 null 即可。该函数用来替代 componentWillReceiveProps。
static getDerivedStateFromProps(props, state) {
//根据props和state计算出预期的状态改变,返回结果会被送给setState
}
该函数在render之后被调用,可以读取但无法使用DOM的时候。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。返回值将作为componentDidUpdate的第三个参数。该函数配合componentDidUpdate, 可以替代componentWillUpdate。
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('getSnapshotBeforeUpdate');
return 'react16';
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('snapshot = ', snapshot);
}
此生命周期会在后代组件抛出错误后被调用。 它将抛出的错误作为参数,并返回一个值以更新 state。
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染可以显降级 UI
return { hasError: true };
}
任何一处的javascript会触发该函数。
componentDidCatch(error, info) {
// 获取到javascript错误
}
参考链接:
在上一篇我们了解到可以在 componentDidMount 或 componentDidUpdate 函数中通过ref属性来访问已经挂载的 DOM 节点或组件实例。ref 属性既可以在组件上使用,也可以在 DOM 节点上使用。
回调函数就是在 DOM 节点或组件上挂载函数,从而获取到 DOM 节点或组件实例。回调函数在组件被加载或卸载时会立即执行,加载时入参是 DOM 节点或组件实例,卸载时入参时 null。
class InputForm extends React.Component {
focusTextInput = () => {
// 通过this.textInput 访问DOM节点
this.textInput.focus();
}
render() {
return (
<div>
<input type="text" ref={(textInput) => this.textInput = textInput} />
<button onClick={this.focusTextInput}>focus</button>
</div>
);
}
}
class AutoFocusTextInput extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
// 通过this.inputForm 获取改组件实例
this.inputForm.focusTextInput();
}
render() {
return (
<InputForm ref={(inputForm) => this.inputForm = inputForm} />
);
}
}
function InputForm(props) {
return (
<div>
<input ref={props.inputFormref} />
</div>
);
}
class AutoFocusInput extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
// 通过this.inputForm 获取input元素的DOM节点
this.inputForm.focus();
}
render() {
return (
<InputForm inputFormref={(inputForm) => this.inputForm = inputForm} />
);
}
}
使用 React.createRef() 来创建Refs,并通过 ref 属性关联到具体的 DOM 节点。React 会在组件挂载时给 current 属性传入 DOM 元素,并在组件卸载时传入 null 值。最终通过 ref 的 current 属性来访问 DOM 节点。
1.在 HTML 元素上使用
class InputForm extends React.Component {
constructor(props) {
super(props);
// 创建一个 ref 来存储 textInput 的 DOM 元素
this.textInput = React.createRef();
}
focusTextInput = () => {
// 通过 "current" 来访问 DOM 节点
this.textInput.current.focus();
}
render() {
return (
<div>
<input type="text" ref={this.textInput}/>
<button onClick={this.focusTextInput}>focus</button>
</div>
);
}
}
2.在组件上使用时通过 ref 的 current 属性获取的是组件实例。(不能在函数组件上使用,因为它们没有实例)
class AutoFocusInput extends React.Component {
constructor(props) {
super(props);
this.inputForm = React.createRef();
}
componentDidMount() {
// 通过 "current" 来访问组件实例
this.inputForm.current.focusTextInput();
}
render() {
return (
<InputForm ref={this.inputForm} />
);
}
}
<input type="text" ref='textInput' /> // this.refs.textInput访问DOM节点
<InputForm ref='inputForm' /> // this.refs.refInputForm 访问该组件实例
官方提示:勿过度使用refs,避免使用 refs 来做任何可以通过声明式实现来完成的事情
async function test() {
// 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式
// 如果有依赖性的话,其实就是解决回调地狱的例子了
await fetch('XXX1')
await fetch('XXX2')
await fetch('XXX3')
}
参考:阮一峰异步编程
CA(Certificate Authority)是负责管理和签发证书的第三方权威机构,是所有行业和公众都信任的、认可的。
CA证书,就是CA颁发的证书,可用于验证网站是否可信(针对HTTPS)、验证某文件是否可信(是否被篡改)等,也可以用一个证书来证明另一个证书是真实可信,最顶级的证书称为根证书。除了根证书(自己证明自己是可靠),其它证书都要依靠上一级的证书,来证明自己。
TCP的连接建立是一个三次握手过程,目的是为了通信双方确认开始序号
- 连接开始时,连接建立方(Client)发送SYN包,并包含了自己的初始序号a;
- 连接接受方(Server)收到SYN包以后会回复一个SYN包,其中包含了对上一个a包的回应信息ACK,回应的序号为下一个希望收到包的序号,即a+1,然后还包含
- 连接建立方(Client)收到回应的SYN包以后,回复一个ACK包做响应,其中包含了下一个希望收到包的序号即b+1
- 首先进行关闭的一方(即发送第一个FIN)将执行主动关闭,而另一方(收到这个FIN)执行被动关闭
- 当服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号
- 同时TCP服务器还向应用程序(即丢弃服务器)传送一个文件结束符。接着这个服务器程序就关闭它的连接,导致它的TCP端发送一个FIN
- 客户必须发回一个确认,并将确认序号设置为收到序号加1
两种遍历算法:
深度优先遍历
广度优先遍历
深度优先遍历(DFS)
深度优先遍历(Depth-First-Search),是搜索算法的一种,它沿着树的深度遍历树的节点,尽可能深地搜索树的分支。当节点v的所有边都已被探寻过,将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已探寻源节点到其他所有节点为止,如果还有未被发现的节点,则选择其中一个未被发现的节点为源节点并重复以上操作,直到所有节点都被探寻完成。
简单的说,DFS就是从图中的一个节点开始追溯,直到最后一个节点,然后回溯,继续追溯下一条路径,直到到达所有的节点,如此往复,直到没有路径为止。
DFS 可以产生相应图的拓扑排序表,利用拓扑排序表可以解决很多问题,例如最大路径问题。一般用堆数据结构来辅助实现DFS算法。
注意:深度DFS属于盲目搜索,无法保证搜索到的路径为最短路径,也不是在搜索特定的路径,而是通过搜索来查看图中有哪些路径可以选择。
步骤:
访问顶点v
依次从v的未被访问的邻接点出发,对图进行深度优先遍历;直至图中和v有路径相通的顶点都被访问
若此时途中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到所有顶点均被访问过为止
实现:
Graph.prototype.dfs = function() {
var marked = []
for (var i=0; i<this.vertices.length; i++) {
if (!marked[this.vertices[i]]) {
dfsVisit(this.vertices[i])
}
}
function dfsVisit(u) {
let edges = this.edges
marked[u] = true
console.log(u)
var neighbors = edges.get(u)
for (var i=0; i<neighbors.length; i++) {
var w = neighbors[i]
if (!marked[w]) {
dfsVisit(w)
}
}
}
}
测试:
graph.dfs()
// 1
// 4
// 3
// 2
// 5
测试成功
广度优先遍历(BFS)
广度优先遍历(Breadth-First-Search)是从根节点开始,沿着图的宽度遍历节点,如果所有节点均被访问过,则算法终止,BFS 同样属于盲目搜索,一般用队列数据结构来辅助实现BFS
BFS从一个节点开始,尝试访问尽可能靠近它的目标节点。本质上这种遍历在图上是逐层移动的,首先检查最靠近第一个节点的层,再逐渐向下移动到离起始节点最远的层
步骤:
创建一个队列,并将开始节点放入队列中
若队列非空,则从队列中取出第一个节点,并检测它是否为目标节点
若是目标节点,则结束搜寻,并返回结果
若不是,则将它所有没有被检测过的字节点都加入队列中
若队列为空,表示图中并没有目标节点,则结束遍历
实现:
Graph.prototype.bfs = function(v) {
var queue = [], marked = []
marked[v] = true
queue.push(v) // 添加到队尾
while(queue.length > 0) {
var s = queue.shift() // 从队首移除
if (this.edges.has(s)) {
console.log('visited vertex: ', s)
}
let neighbors = this.edges.get(s)
for(let i=0;i<neighbors.length;i++) {
var w = neighbors[i]
if (!marked[w]) {
marked[w] = true
queue.push(w)
}
}
}
}
测试:
graph.bfs(1)
// visited vertex: 1
// visited vertex: 4
// visited vertex: 3
// visited vertex: 2
// visited vertex: 5
// 工具函数
let _toString = Object.prototype.toString
let map = {
array: 'Array',
object: 'Object',
function: 'Function',
string: 'String',
null: 'Null',
undefined: 'Undefined',
boolean: 'Boolean',
number: 'Number'
}
let getType = (item) => {
return _toString.call(item).slice(8, -1)
}
let isTypeOf = (item, type) => {
return map[type] && map[type] === getType(item)
}
深度复制
let DFSdeepClone = (obj, visitedArr = []) => {
let _obj = {}
if (isTypeOf(obj, 'array') || isTypeOf(obj, 'object')) {
let index = visitedArr.indexOf(obj)
_obj = isTypeOf(obj, 'array') ? [] : {}
if (~index) { // 判断环状数据
_obj = visitedArr[index]
} else {
visitedArr.push(obj)
for (let item in obj) {
_obj[item] = DFSdeepClone(obj[item], visitedArr)
}
}
} else if (isTypeOf(obj, 'function')) {
_obj = eval('(' + obj.toString() + ')');
} else {
_obj = obj
}
return _obj
}
广度复制
let BFSdeepClone = (obj) => {
let origin = [obj],
copyObj = {},
copy = [copyObj]
// 去除环状数据
let visitedQueue = [],
visitedCopyQueue = []
while (origin.length > 0) {
let items = origin.shift(),
_obj = copy.shift()
visitedQueue.push(items)
visitedCopyQueue.push(_obj)
if (isTypeOf(items, 'object') || isTypeOf(items, 'array')) {
for (let item in items) {
let val = items[item]
if (isTypeOf(val, 'object')) {
let index = visitedQueue.indexOf(val)
if (!~index) {
_obj[item] = {}
//下次while循环使用给空对象提供数据
origin.push(val)
// 推入引用对象
copy.push(_obj[item])
visitedQueue.push(val)
} else {
_obj[item] = visitedCopyQueue[index]
}
} else if (isTypeOf(val, 'array')) {
// 数组类型在这里创建了一个空数组
_obj[item] = []
origin.push(val)
copy.push(_obj[item])
} else if (isTypeOf(val, 'function')) {
_obj[item] = eval('(' + val.toString() + ')');
} else {
_obj[item] = val
}
}
} else if (isTypeOf(items, 'function')) {
copyObj = eval('(' + items.toString() + ')');
} else {
copyObj = obj
}
}
return copyObj
}
测试
/**测试数据 */
// 输入 字符串String
// 预期输出String
let str = 'String'
var strCopy = DFSdeepClone(str)
var strCopy1 = BFSdeepClone(str)
console.log(strCopy, strCopy1) // String String 测试通过
// 输入 数字 -1980
// 预期输出数字 -1980
let num = -1980
var numCopy = DFSdeepClone(num)
var numCopy1 = BFSdeepClone(num)
console.log(numCopy, numCopy1) // -1980 -1980 测试通过
// 输入bool类型
// 预期输出bool类型
let bool = false
var boolCopy = DFSdeepClone(bool)
var boolCopy1 = BFSdeepClone(bool)
console.log(boolCopy, boolCopy1) //false false 测试通过
// 输入 null
// 预期输出 null
let nul = null
var nulCopy = DFSdeepClone(nul)
var nulCopy1 = BFSdeepClone(nul)
console.log(nulCopy, nulCopy1) //null null 测试通过
// 输入undefined
// 预期输出undefined
let und = undefined
var undCopy = DFSdeepClone(und)
var undCopy1 = BFSdeepClone(und)
console.log(undCopy, undCopy1) //undefined undefined 测试通过
//输入引用类型obj
let obj = {
a: 1,
b: () => console.log(1),
c: {
d: 3,
e: 4
},
f: [1, 2],
und: undefined,
nul: null
}
var objCopy = DFSdeepClone(obj)
var objCopy1 = BFSdeepClone(obj)
console.log(objCopy === objCopy1) // 对象类型判断 false 测试通过
console.log(obj.c === objCopy.c) // 对象类型判断 false 测试通过
console.log(obj.c === objCopy1.c) // 对象类型判断 false 测试通过
console.log(obj.b === objCopy1.b) // 函数类型判断 false 测试通过
console.log(obj.b === objCopy.b) // 函数类型判断 false 测试通过
console.log(obj.f === objCopy.f) // 数组类型判断 false 测试通过
console.log(obj.f === objCopy1.f) // 数组类型判断 false 测试通过
console.log(obj.nul, obj.und) // 输出null,undefined 测试通过
// 输入环状数据
// 预期不爆栈且深度复制
let circleObj = {
foo: {
name: function() {
console.log(1)
},
bar: {
name: 'bar',
baz: {
name: 'baz',
aChild: null //待会让它指向obj.foo
}
}
}
}
circleObj.foo.bar.baz.aChild = circleObj.foo
var circleObjCopy = DFSdeepClone(circleObj)
var circleObjCopy1 = BFSdeepClone(circleObj)
console.log(circleObjCopy, circleObjCopy1) // 测试通过?
扩展:深拷贝与浅拷贝
基本类型--名值存储在栈内存中,
引用数据类型--名存在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值
因此在浅拷贝引用类型进行复制时,复制对值指向对是同一个地址
function deepCopy(obj) {
let newObj = Array.isArray(obj) ? [] : {};
if(obj && typeof obj === 'object'){
for(key in obj){
if(obj.hasOwnProperty(key)){
//判断ojb子元素是否为对象,如果是,递归复制
if(obj[key]&&typeof obj[key] ==="object"){
newObj[key] = deepCopy(obj[key]);
}else{
//如果不是,简单复制
newObj[key] = obj[key];
}
}
}
}
return newObj
}
deep表示是否深拷贝,为true为深拷贝,为false,则为浅拷贝
target Object类型 目标对象,其他对象的成员属性将被附加到该对象上。
object1 objectN可选。 Object类型 第一个以及第N个被合并的对象。
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
答案:
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
合理的title、description、keywords:搜索对着三项的权重逐个减小,title值强调重点即可,重要关键词出现不要超过2次,而且要靠前,不同页面title要有所不同;description把页面内容高度概括,长度合适,不可过分堆砌关键词,不同页面description有所不同;keywords列举出重要关键词即可
语义化的HTML代码,符合W3C规范:语义化代码让搜索引擎容易理解网页
重要内容HTML代码放在最前:搜索引擎抓取HTML顺序是从上到下,有的搜索引擎对抓取长度有限制,保证重要内容一定会被抓取
重要内容不要用js输出:爬虫不会执行js获取内容
少用iframe:搜索引擎不会抓取iframe中的内容
非装饰性图片必须加alt
提高网站速度:网站速度是搜索引擎排序的一个重要指标
Set
成员唯一、无序且不重复
[value, value],键值与键名是一致的(或者说只有键值,没有键名)
可以遍历,方法有:add、delete、has
WeakSet
成员都是对象
成员都是弱引用,可以被垃圾回收机制回收,可以用来保存DOM节点,不容易造成内存泄漏
不能遍历,方法有add、delete、has
Map
本质上是键值对的集合,类似集合
可以遍历,方法很多可以跟各种数据格式转换
WeakMap
只接受对象最为键名(null除外),不接受其他类型的值作为键名
键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的
不能遍历,方法有get、set、has、delete
1.Object 与 Set
const properties1 = {
'width': 1,
'height': 1
}
console.log(properties1['width']? true: false)
// Set
const properties2 = new Set()
properties2.add('width')
properties2.add('height')
console.log(properties2.has('width'))
2.Object 与 Map
JS 中的对象(Object),本质上是键值对的集合(hash 结构)
const data = {};
const element = document.getElementsByClassName('App');
data[element] = 'metadata';
console.log(data['[object HTMLCollection]']) // "metadata"
但当以一个DOM节点作为对象 data 的键,对象会被自动转化为字符串[Object HTMLCollection],所以说,Object 结构提供了 字符串-值 对应,Map则提供了 值-值 的对应
给定一个仅包含大小写字母和空格 ' ' 的字符串,返回其最后一个单词的长度。
如果不存在最后一个单词,请返回 0 。
说明:一个单词是指由字母组成,但不包含任何空格的字符串。
function lengthOfLastWord(s) {
let arr = s.split(' ').filter(i => i)
if(arr.length) {
return arr[arr.length-1].length
}
else {
return 0;
}
};
主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制
下图可以形象说明
一般来说,有以下四种会放入异步任务队列:
性能方面:Array.isArray()性能最好,instanceof次之,Object.prototype.toString.call()第三
功能方面:
Object.prototype.toString.call()所有的类型都可以判断
instanceof只能判断对象原型,原始类型不可以,
[] instanceof Object // true
Array.isArray 优于 instanceof ,因为 Array.isArray 可以检测出 iframes,Array.isArray()是ES5新增的方法,当不存在 Array.isArray() ,可以用 Object.prototype.toString.call() 实现
while实现
function sleep(ms){
var start=Date.now(),expire=start+ms;
while(Date.now()<expire);
console.log('1111');
return;
}
promise实现
const sleep = (timer) => {
return new Promise(resolve => setTimeout(resolve, timer))
}
sleeo(1000).then()
generate实现
function* sleep(ms){
yield new Promise(function(resolve,reject){
console.log(111);
setTimeout(resolve,ms);
})
}
sleep(500).next().value.then(function(){console.log(2222)})
async实现
const sleep = (time) => {
return new Promise(resolve => setTimeout(resolve, time))
}
async function sleepAsync() {
await sleep(1000)
}
sleepAsync()
1攻击者通过xss拿到用户的cookie然后就可以伪造cookie了。
2或者通过csrf在同个浏览器下面通过浏览器会自动带上cookie的特性
在通过 用户网站-攻击者网站-攻击者请求用户网站的方式 浏览器会自动带上cookie
但是token
1 不会被浏览器带上 问题2 解决
2 token是放在jwt里面下发给客户端的 而且不一定存储在哪里 不能通过document.cookie直接拿到,通过jwt+ip的方式 可以防止 被劫持 即使被劫持 也是无效的jwt
apply()和 call()这两个方法的用途都是在特定的作 用域中调用函数,实际上等于设置函数体内 this 对象的值。bind()方法会创建一个函数的实例,其 this 值会被绑定到传给 bind()函数的值。总的来说是用来改变函数运行时this的指向。
call方法将需要参数按顺序传递进去
function sum(num1, num2){
return num1 + num2;
}
function callSum(num1, num2){
return sum.call(this, num1, num2);
}
alert(callSum(10,10)); //20
Function.prototype.call2 = function (context) {
context = context || window
context.fn = this
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
var result = eval('context.fn(' + args +')');
delete context.fn
return result;
}
apply()方法接收两个参数:一个 是在其中运行函数的作用域,另一个是参数数组,第二个参数可以是 Array 的实例,也可以是 arguments 对象。
function sum(num1, num2){
return num1 + num2;
}
function callSum1(num1, num2){
return sum.apply(this, arguments);
}
function callSum2(num1, num2){
return sum.apply(this, [num1, num2]);
}
alert(callSum1(10,10)); //20
alert(callSum2(10,10)); //20
其中 this 是你想指定的上下文,他可以是任何一个 JavaScript 对象(JavaScript 中一切皆对象)
apply()和 call()的用法,接受参数的方式不一样,使用 call()(或 apply())来扩充作用域的最大好处,就是对象不需要与方法有任何耦合关系,如果你传的 context 就 null 或者 undefined,那么 window 对象就是默认的 context(严格模式下默认 context 是 undefined)。
Function.prototype.apply = function (context, arr) {
context = Object(context) || window;
context.fn = this;
var result;
if (!arr) {
result = context.fn();
}
else {
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push('arr[' + i + ']');
}
result = eval('context.fn(' + args + ')')
}
delete context.fn
return result;
}
bind()用法:第一个参数是this的指向,从第二个参数开始是接收的参数列表
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
var objectSayColor = sayColor.bind(o);
objectSayColor(); //blue
Function.prototype.bind = function (oThis) {
if (typeof this !== "function") {
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function () {},
fBound = function () {
// 当作为构造函数时,this 指向实例,此时结果为 true,将绑定函数的 this 指向该实例,可以让实例获得来自绑定函数的值
// 以上面的是 demo 为例,如果改成 `this instanceof fBound ? null : context`,实例只是一个空对象,将 null 改成 this ,实例会具有 habit 属性
// 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
return fToBind.apply(
this instanceof fNOP && oThis ? this : oThis || window,
aArgs.concat(Array.prototype.slice.call(arguments))
);
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
call、apply和bind函数存在的区别:
需要注意的一点的是:
如何使用promise只调用一次接口,但是让 promise 执行完不直接 resolve,而是把 resolve 存起来,请求回来后一起调把。
思路:利用asyns awit堵塞请求
function ajax(params) {
console.log("发起请求")
return new Promise(resolve => {
setTimeout(() => {
resolve(1)
console.log('resolve')
}, 1000);
})
}
let request = false;
var getData = new Promise(async resolve => {
console.log('立即执行')
if (!request) {
request = true;
const res = await ajax();
console.log("请求成功")
resolve(res);
}
})
getData.then(res => {
console.log(res,'1')
})
getData.then(res => {
console.log(res, '2')
})
回调思路
let qu = [];
let request = false;
function getData(callback){
qu.push(callback)
if(!request){
request = true;
ajax()
.then((res) => {
console.log("请求成功")
qu.map((cb) => {
cb(res);
})
})
}
}
getData((res) => {
console.log(res, '11')
})
getData((res) => {
console.log(res, '22')
})
getData((res) => {
console.log(res, '33')
})
class Example extends React.Component {
constructor() {
super();
this.state = {
val: 0
};
}
componentDidMount() {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 1 次 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 2 次 log
setTimeout(() => {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 3 次 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 4 次 log
}, 0);
}
render() {
return null;
}
};
0 0 2 3
1、第一次和第二次都是在 react 自身生命周期内,触发时 isBatchingUpdates 为 true,所以并不会直接执行更新 state,而是加入了 dirtyComponents,所以打印时获取的都是更新前的状态 0。
2、两次 setState 时,获取到 this.state.val 都是 0,所以执行时都是将 0 设置成 1,在 react 内部会被合并掉,只执行一次。设置完成后 state.val 值为 1。
3、setTimeout 中的代码,触发时 isBatchingUpdates 为 false,所以能够直接进行更新,所以连着输出 2,3。
es6实现的继承代码:
class Parent{
constructor(name){
this.name = name;
}
static sayHello(){
console.log('hello');
}
sayName(){
console.log('my name is ' + this.name);
return this.name;
}
}
class Child extends Parent{
constructor(name, age){
super(name);
this.age = age;
}
sayAge(){
console.log('my age is ' + this.age);
return this.age;
}
}
let parent = new Parent('Parent');
let child = new Child('Child', 18);
代码里有两条原型链
// 1、构造器原型链
Child.__proto__ === Parent; // true
Parent.__proto__ === Function.prototype; // true
Function.prototype.__proto__ === Object.prototype; // true
Object.prototype.__proto__ === null; // true
// 2、实例原型链
child.__proto__ === Child.prototype; // true
Child.prototype.__proto__ === Parent.prototype; // true
Parent.prototype.__proto__ === Object.prototype; // true
Object.prototype.__proto__ === null; // true
ES6 extends 继承,主要就是
new
、Object.create
和`Object.setPrototypeOf来实现,那么我们可以来实现es5的继承// ES5 实现ES6 extends的例子
function Parent(name){
this.name = name;
}
Parent.sayHello = function(){
console.log('hello');
}
Parent.prototype.sayName = function(){
console.log('my name is ' + this.name);
return this.name;
}
function Child(name, age){
// 相当于super
Parent.call(this, name);
this.age = age;
}
// new
function object(){
function F() {}
F.prototype = proto;
return new F();
}
function _inherits(Child, Parent){
// Object.create
Child.prototype = Object.create(Parent.prototype);
// __proto__
// Child.prototype.__proto__ = Parent.prototype;
Child.prototype.constructor = Child;
// ES6
// Object.setPrototypeOf(Child, Parent);
// __proto__
Child.__proto__ = Parent;
}
_inherits(Child, Parent);
Child.prototype.sayAge = function(){
console.log('my age is ' + this.age);
return this.age;
}
var parent = new Parent('Parent');
var child = new Child('Child', 18);
上述两中实现继承的方式都是通过 [[Prototype]] 机制来实现的,我们可以使用对象关联的风格来实现上述功能
Parent = {
init: function(value){
this.name = value
},
sayHello: function(){
console.log('hello');
},
sayName: function(){
console.log('my name is ' + this.name);
return this.name;
}
}
Child = Object.create( Parent );
Child.sayAge = function(){
console.log('my age is ' + this.age);
return this.age;
}
var child1 = Object.create( Child );
child1.init( "tom" );
var child2 = Object.create( Child );
child2.init('lili');
child1.sayHello();
child2.sayName();
从上面代码可以看出对象关联风格的代码显然更加简洁,因为这种代码只关注一件事:对象之间的关联关系,
使用类构造函数的话,你需要在同一个步骤中实现构造和初始化。然而,在许多情况下把这两步分开(就像对象关联代码一样)更灵活。
对象关联除了能让代码看起来更简洁(并且更具扩展性)外还可以通过行为委托模式简化代码结构
typeof null === "object"; // true
var a;
a //undefined
b //ReferenceError: b is not defined undeclared
typeof a; // "undefined"
typeof b; // "undefined"
var a = [ ];
a["13"] = 42; //字符串键值被强制类型转换为十进制数字
a.length; // 14
0.1 + 0.2 === 0.3; // false 二进制浮点数中的 0.1 和 0.2 并不是十分精确
void 0、void 1 和 undefined 之间并没有实质上的区别
var a = 2 / "foo"; // NaN
typeof a === "number"; // true
a == NaN; // false
a === NaN; // false
NaN === NaN //falsew
零值
var a = 0 / -3;
a.toString(); // "0"
a + ""; // "0"
String( a ); // "0"
// JSON也如此,很奇怪
JSON.stringify( a );// "0"
+"-0"; // -0
Number( "-0" ); // -0
JSON.parse( "-0" ); // -0
-0 == 0;// true
a === b;// true
-0 === 0;// true
0 > -0; // false
a > b; // false
var a = new Boolean(false) // b值为真
强制类型转换
var a = new String( "abc" );
var b = a + ""; // b的值为"abc"
typeof a; // "object"
typeof b; // "string"
Array 构造函数只带一个数字参数的时候,该参数会被作为数组的预设长度(length),而 非只充当数组中的一个元素
var a = new Array( 3 );
var b = [ undefined, undefined, undefined ];
var c = [];
c.length = 3;
console.log(a); //[empty × 3]
console.log(b) //[ undefined, undefined, undefined ]
console.log(c) //[empty × 3]
已知如下数组:
var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];
编写一个程序将数组扁平化去并除其中重复部分数据,最终得到一个升序且不重复的数组
一解
function getArr(arr) {
let arrs = [];
arr.map(item => {
if(Array.isArray(item)){
arrs.push(...getArr(item))
} else {
arrs.push(item)
}
})
return arrs
}
let newArrs = [...(new Set(getArr(arr)))].sort((a,b) => a-b)
二解
function getArr(arr) {
let newArr = [...(new Set(arr.join(',').split(',').map(item => Number(item))))].sort((a, b) => a-b)
return newArr
}
三解
function getArr(arr) {
let arrB =[...arr]
let newArr = [];
while (arrB.length){
let item = arrB.shift()
if(Array.isArray(item)){
arrB.unshift(...item)
}else {
newArr.push(item)
}
}
return [...(new Set(newArr))].sort((a,b) => a-b)
}
第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
1.建立服务器443端口连接
2.SSL握手:随机数,证书,密钥,加密算法
3.发送加密请求
4.发送加密响应
5.关闭SSL
6.关闭TCP
1.客户端发起HTTPS请求
2.服务端的配置
采用HTTPS协议的服务器必须要有一套数字证书,可以是自己制作或者CA证书。区别就是自己颁发的证书需要客户端验证通过,才可以继续访问,而使用CA证书则不会弹出提示页面。这套证书其实就是一对公钥和私钥。公钥给别人加密使用,私钥给自己解密使用。
3.传送证书
这个证书其实就是公钥,只是包含了很多信息,如证书的颁发机构,过期时间等。
4.客户端解析证书
这部分工作是有客户端的TLS来完成的,首先会验证公钥是否有效,比如颁发机构,过期时间等,如果发现异常,则会弹出一个警告框,提示证书存在问题。如果证书没有问题,那么就生成一个随即值,然后用证书对该随机值进行加密。
5.传送加密信息
这部分传送的是用证书加密后的随机值,目的就是让服务端得到这个随机值,以后客户端和服务端的通信就可以通过这个随机值来进行加密解密了。
6.服务段解密信息
服务端用私钥解密后,得到了客户端传过来的随机值(私钥),然后把内容通过该值进行对称加密。所谓对称加密就是,将信息和私钥通过某种算法混合在一起,这样除非知道私钥,不然无法获取内容,而正好客户端和服务端都知道这个私钥,所以只要加密算法够彪悍,私钥够复杂,数据就够安全。
7.传输加密后的信息
这部分信息是服务段用私钥加密后的信息,可以在客户端被还原。
8.客户端解密信息
客户端用之前生成的私钥解密服务段传过来的信息,于是获取了解密后的内容。
PS: 整个握手过程第三方即使监听到了数据,也束手无策。
在HTTPS握手的第四步中,如果站点的证书是不受信任的,会显示出现下面确认界面,确认了网站的真实性。另外第六和八步,使用客户端私钥加密解密,保证了数据传输的安全。
HTTPS一般使用的加密与HASH算法如下:
非对称加密算法:RSA,DSA/DSS
对称加密算法:AES,RC4,3DES
HASH算法:MD5,SHA1,SHA256
注意https加密是在传输层
https报文在被包装成tcp报文的时候完成加密的过程,无论是https的header域也好,body域也罢都是会被加密的。
当使用tcpdump或者wireshark之类的tcp层工具抓包,获取是加密的内容,而如果用应用层抓包,使用Charels(Mac)、Fildder(Windows)抓包工具,那当然看到是明文的。
https协议需要到ca申请证书或自制证书。
http的信息是明文传输,https则是具有安全性的ssl加密。
http是直接与TCP进行数据传输,而https是经过一层SSL(OSI表示层),用的端口也不一样,前者是80(需要国内备案),后者是443。
http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
react和vue都是采用diff算法,在组件中写入key值,在新老节点对比时,根据·可以值不同,可以准确找到需要渲染的节点,这样可以减少渲染的时间,优化性能。
vue和react都是采用diff算法来对比新旧虚拟节点,从而更新节点。在vue的diff函数中。可以先了解一下diff算法。
在交叉对比的时候,当新节点跟旧节点头尾交叉对比没有结果的时候,会根据新节点的key去对比旧节点数组中的key,从而找到相应旧节点(这里对应的是一个key => index 的map映射)。如果没找到就认为是一个新增节点。而如果没有key,那么就会采用一种遍历查找的方式去找到对应的旧节点。一种一个map映射,另一种是遍历查找。相比而言。map映射的速度更快。
vue部分源码如下:
// vue项目 src/core/vdom/patch.js -488行
// oldCh 是一个旧虚拟节点数组,
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
创建map函数
function createKeyToOldIdx (children, beginIdx, endIdx) {
let i, key
const map = {}
for (i = beginIdx; i <= endIdx; ++i) {
key = children[i].key
if (isDef(key)) map[key] = i
}
return map
}
遍历寻找
// sameVnode 是对比新旧节点是否相同的函数
function findIdxInOld (node, oldCh, start, end) {
for (let i = start; i < end; i++) {
const c = oldCh[i]
if (isDef(c) && sameVnode(node, c)) return i
}
}
知其然知其所以然,我需要看源码,需要更加深入
客户端会有一个有效证书串,一般的浏览器都会内置很多常见服务器的这个证书,特殊的服务器就需要前期通过手工将证书添加到客户端。证明对方是否持有证书的对应的私钥,客户端通过比对来确认证书的有效性
this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式,this的绑定方式有四种,要判断this的绑定,只需要找到函数的调用位置,根据规则判断即可判断this
的指定对象
独立函数调用,在全局模式下,全局对象将无法使用默认绑定
function foo() {
console.log( this.a ); // this指向window
}
var a = 2;
foo(); // 2 调用位置为全局
在本例中,this.a被解析成全局变量,因为函数调用时应用了this的默认绑定,因此指向全局对象
调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含,对象属性引用链中只有最顶层或者说最后一层会影响调用位置
function foo(){
console.log(this.a)
}
var obj={
a:2,
foo:foo
}
obj.foo();//2
上面调用foo()时this被绑定到obj,因此this.a和obj.a是一样的,因为隐式绑定规则会把函数调用中的this绑定到这个上下文对象
利用call,apply方法可以直接指定 this 的绑定对象
function foo(){
console.log(this.a)
}
var obj={
a:2,
}
foo.call(obj); //2
通过foo.call(…)我们可以把调用foo时强制把他的this绑定到obj上,在显示绑定的基础上还有硬绑定,这是显示绑定的变种
function foo() {
console.log( this.a );
}
var obj = { a:2};
var bar = function() {
foo.call( obj );
};
bar(); // 2
setTimeout( bar, 100 ); // 2
// 硬绑定的 bar 不可能再修改它的this
bar.call( window ); // 2
创建了bar函数,在内部强制把this绑定到obj上面,之后无论如何调用bar,都不能改变this指向。apply和bind同样可以实现硬绑定
function foo(el) {
console.log( el, this.id );
}
var obj = {
id: "awesome"
};
// 调用 foo(..) 时把 this 绑定到 obj [1, 2, 3].forEach( foo, obj );
// 1 awesome 2 awesome 3 awesome
相对于硬绑定来说,在实现硬绑定效果的同时,可以保留隐式绑定或者显式绑定修改 this 的能力
使用 new 来调用 foo(..) 时,我们会构造一个新对象并把它绑定到 foo(..) 调用中的 this 上。new 是最后一种可以影响函数调用时 this 绑定行为的方法,我们称之为 new 绑定
function foo(){
this.a=a
}
var bar=new foo(2);
console.log(bar.a)//2
发生函数构造时或者使用new来调用函数,会自动执行下面的操作:
new绑定>显示绑定>隐式绑定>默认绑定
特例:箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)作用域来决定this。箭头函数会继承外层函数调用的 this 绑定(无论 this 绑定到什么)。这 其实和 ES6 之前代码中的 self = this 机制一样
HTTP2采用二进制格式传输,取代了HTTP1.x的文本格式,二进制格式解析更高效。
多路复用代替了HTTP1.x的序列和阻塞机制,所有的相同域名请求都通过同一个TCP连接并发完成。在HTTP1.x中,并发多个请求需要多个TCP连接,浏览器为了控制资源会有6-8个TCP连接都限制。
HTTP2中
同域名下所有通信都在单个连接上完成,消除了因多个 TCP 连接而带来的延时和内存消耗。
单个连接上可以并行交错的请求和响应,之间互不干扰
防抖:高频事件触发n秒后才执行,如果n秒内重新触发,则重新计算时间,形象类比于坐电梯,实际可运用在输入框实时搜索
var btn = document.getElementById('button')
function _debounce(fn,wait){
var timer = null;
return function(){
clearTimeout(timer)
console.log('1123')
timer = setTimeout(()=>{
fn()
},wait)
}
}
btn.onclick = _debounce(_log,5000)
function _log(){
console.log(1)
}
节流:高频时间触发n秒内只执行一次,执行后只能等n秒后再执行,形象类比于坐公交,实际可运用于拖拽
function throttle(fn, gapTime) {
let _lastTime = null;
return function () {
let _nowTime = + new Date()
if (_nowTime - _lastTime > gapTime || !_lastTime) {
fn();
_lastTime = _nowTime
}
}
}
let fn = ()=>{
console.log('boom')
}
setInterval(throttle(fn,1000),10)
防抖
触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间
思路:
每次触发事件时都取消之前的延时调用方法
function debounce(fn) {
let timeout = null; // 创建一个标记用来存放定时器的返回值
return function () {
clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
timeout = setTimeout(() => { // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
fn.apply(this, arguments);
}, 500);
};
}
function sayHi() {
console.log('防抖成功');
}
var inp = document.getElementById('inp');
inp.addEventListener('input', debounce(sayHi)); // 防抖
节流
高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率
思路:
每次触发事件时都判断当前是否有等待执行的延时函数
function throttle(fn) {
let canRun = true; // 通过闭包保存一个标记
return function () {
if (!canRun) return; // 在函数开头判断标记是否为true,不为true则return
canRun = false; // 立即设置为false
setTimeout(() => { // 将外部传入的函数的执行放在setTimeout中
fn.apply(this, arguments);
// 最后在setTimeout执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。当定时器没有执行的时候标记永远是false,在开头被return掉
canRun = true;
}, 500);
};
}
function sayHi(e) {
console.log(e.target.innerWidth, e.target.innerHeight);
}
window.addEventListener('resize', throttle(sayHi));
思考:需要结合项目中实际需求来理解这两个概念
function Foo() {
getName = function() { alert(1); }
return this
}
Foo.getName = function() { alert(2); }
Foo.prototype.getName = function() { alert(3); }
var getName = function () { alert(4); }
function getName() { alert(5); }
// 输出值
Foo.getName(); //2
getName(); //4
Foo().getName(); // 1
getName(); // 1
new Foo.getName() // 2
new Foo().getName() //3
new new Foo().getName() //3
实际执行
Foo.getName(); // 2
getName(); // 4
Foo();
getName(); // 1
getName(); // 1
Foo.getName(); // 2
(new Foo()).getName(); // 3
(new Foo()).getName(); // 3
console.log(1)
let promise = new Promise(function(resolve, reject) {
console.log(3);
resolve()
}).then(function(){
console.log(4)
}).then(function(){
console.log(9)
});
console.log(5)
1,3,5,4,9
答案:Promise 构造函数是同步执行,then方法是异步执行
扩展:
console.log(1)
setTimeout(function(){
console.log(2);
let promise = new Promise(function(resolve, reject) {
console.log(7);
resolve()
}).then(function(){
console.log(8)
});
},1000);
setTimeout(function(){
console.log(10);
let promise = new Promise(function(resolve, reject) {
console.log(11);
resolve()
}).then(function(){
console.log(12)
});
},0);
let promise = new Promise(function(resolve, reject) {
console.log(3);
resolve()
}).then(function(){
console.log(4)
}).then(function(){
console.log(9)
});
console.log(5)
/// 1,3,5,4,9,10,11,12,2,7,8
思考:
async function timeout(ms) {
await new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint('hello world', 50);
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
// script start, async1 start,async2,promise1,script end, async1 end,promise2,setTimeout,hello world,
- 服务器和客户建立连接后,若服务器主机崩溃,有两种可能:
- 服务器不重启,客户继续工作,就会发现对方没有回应(ETIMEOUT),路由器聪明的话,则是目的地不可达(EHOSTUNREACH)。
- 服务器重启后,客户继续工作,然而服务器已丢失客户信息,收到客户数据后响应RST。
A处于syn_sent状态
es6继承
class A extends B{}
A.__proto__ === B; //继承属性
A.prototype.__proto__ == B.prototype;//继承方法
es5继承
function Super() {}
function Sub() {}
Sub.prototype = new Super();
Sub.prototype.constructor = Sub;
var sub = new Sub();
Sub.__proto__ === Function.prototype;
ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。
ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。
扩展:ES6中的class和ES5的类有什么区别?
1.ES6 class 内部所有定义的方法都是不可枚举的;
2.ES6 class 必须使用 new 调用;
3.ES6 class 不存在变量提升;
4.ES6 class 默认即是严格模式;
5.ES6 class 子类必须在父类的构造函数中调用super(),这样才有this对象;ES5中类继承的关系是相反的,先有子类的this,然后用父类的方法应用在this上。
原理:
async function test() {
// 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式
// 如果有依赖性的话,其实就是解决回调地狱的例子了
await fetch('XXX1')
await fetch('XXX2')
await fetch('XXX3')
}
var a = 10;
(function () {
console.log(a)
a = 5
console.log(window.a)
var a = 20;
console.log(a)
})()
输出:
// undefind 10 20
- 1.setTimeout它会开启一个定时器线程,并不会影响后续的代码执行,这个定时器线程会在事件队列后面添加一个任务,
当js线程在主线程执行其他线程代码完毕后,就会取出事件队列中的事件进行执行,- 2.定时器中的this存在隐式丢失的情况
var a = 0;
function foo(){
console.log(this.a);
};
var obj = {
a : 2,
foo:foo
}
setTimeout(obj.foo,100);//0
若想获得obj对象中的a属性值,可以将obj.foo函数放置在定时器中的匿名函数中进行隐式绑定
var a = 0;
function foo(){
console.log(this.a);
};
var obj = {
a : 2,
foo:foo
}
setTimeout(function(){
obj.foo();
},100);//2
或者使用bind方法将foo()方法的this绑定到obj上,call,apply方法均可
var a = 0;
function foo(){
console.log(this.a);
};
var obj = {
a : 2,
foo:foo
}
setTimeout(obj.foo.bind(obj),100);//2
promise就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数
Promise 新建后立即执行,promise提供promise.all,promise.race,promise.resolve,promise.rejecr等方法
async/await
then和settimeout执行顺序,即setTimeout(fn, 0)在下一轮“事件循环”开始时执行,Promise.then()在本轮“事件循环”结束时执行。因此then 函数先输出,settimeout后输出。
例子:
var p1 = new Promise(function(resolve, reject){
resolve(1);
})
setTimeout(function(){
console.log("will be executed at the top of the next Event Loop");
},0)
p1.then(function(value){
console.log("p1 fulfilled");
})
setTimeout(function(){
console.log("will be executed at the bottom of the next Event Loop");
},0)
p1 fulfilled
will be executed at the top of the next Event Loop
will be executed at the bottom of the next Event Loop
原因:
JavaScript将异步任务分为MacroTask和MicroTask,
- MacroTask包含MacroTask Queue(宏任务队列)主要包括setTimeout,setInterval, setImmediate, requestAnimationFrame, NodeJS中的I/O等。
- MicroTask包含独立回调microTask:如Promise,其成功/失败回调函数相互独立;复合回调microTask:如 Object.observe, MutationObserver 和NodeJs中的 process.nextTick ,不同状态回调在同一函数体;
- 依次执行同步代码直至执行完毕;
- 检查MacroTask 队列,若有触发的异步任务,则取第一个并调用其事件处理函数,然后跳至第三步,若没有需处理的异步任务,则直接跳至第三步;
- 检查MicroTask队列,然后执行所有已触发的异步任务,依次执行事件处理函数,直至执行完毕,然后跳至第二步,若没有需处理的异步任务中,则直接返回第二步,依次>执行后续步骤;
- 最后返回第二步,继续检查MacroTask队列,依次执行后续步骤;
- 如此往复,若所有异步任务处理完成,则结束;
promise与asyns/await的不同:
asyns函数前面多了一个aync关键字。await关键字只能用在aync定义的函数内。async函数会隐式地返回一个promise,该promise的reosolve值就是函数return的值。
任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行
相同点:
async函数的返回值是 Promise 对象,可以使用then方法添加回调函数,这一点与promise类似
希望多个请求并发执行,可以使用Promise.all方法
promise和setTimeout都会被放入任务队列
在React中,如果是由React引发的事件处理(比如通过onClick引发的事件处理),调用setState不会同步更新this.state,除此之外的setState调用会同步执行this.state。所谓“除此之外”,指的是绕过React通过addEventListener直接添加的事件处理函数,还有通过setTimeout/setInterval产生的异步调用。
简单来说就是当setState方法调用的时候React就会重新调用render方法来重新渲染组件;setState通过一个队列来更新state,当调用setState方法的时候会将需要更新的state放入这个状态队列中,这个队列会高效的批量更新state;
function cloneDeep(x) {
const root = {};
// 栈
const loopList = [
{
parent: root,
key: undefined,
data: x,
}
];
while(loopList.length) {
// 广度优先
const node = loopList.pop();
const parent = node.parent;
const key = node.key;
const data = node.data;
// 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素
let res = parent;
if (typeof key !== 'undefined') {
res = parent[key] = {};
}
for(let k in data) {
if (data.hasOwnProperty(k)) {
if (typeof data[k] === 'object') {
// 下一次循环
loopList.push({
parent: res,
key: k,
data: data[k],
});
} else {
res[k] = data[k];
}
}
}
}
return root;
}
扩展:深拷贝和浅拷贝的区别
浅拷贝只是新开一个别名来引用这个内存区。即拷贝原对象的引用
深拷贝会重新开辟一个内存区,并把之前内存区的值复制进来,这样两个内存区初始值是一样的,但接下去的操作互不影响
深拷贝终极探索
请把俩个数组 [A1, A2, B1, B2, C1, C2, D1, D2] 和 [A, B, C, D],合并为 [A1, A2, A, B1, B2, B, C1, C2, C, D1, D2, D]。
let arrA = ['A1', 'A2', 'B1', 'B2', 'C1', 'C2', 'D1', 'D2'];
let arrB = ['A', 'B', 'C', 'D',];
function combine(a, b) {
while (b.length){
let str = b.shift();
let indexNum = 0;
a.forEach((item,index) => {
if(item.indexOf(str) !== -1){
indexNum = index
}
})
a.splice(indexNum + 1, 0, str)
}
return a;
}
主要的区别在于浏览器的event loop 和nodejs的event loop 在处理异步事件的顺序是不同的,nodejs中有micro event;其中Promise属于micro event 该异步事件的处理顺序就和浏览器不同.nodejs V11.0以上 这两者之间的顺序就相同了.
浏览器下事件循环(Event Loop):
微任务 microtask(jobs): promise / ajax / Object.observe(该方法已废弃)
宏任务 macrotask(task): setTimout / script / IO / UI Rendering
Node 的 Event Loop
在ES5中,全局变量直接挂载到全局对象的属性上,所以能在window上看到var声明的变量
在ES6中,全局对象的属性和全局变量脱钩,但是为了保持兼容性,旧的不变,所以var、function声明的全局变量依然可以在window对象上看到,而let、const声明的全局变量在window对象上看不到
let a = 1;
var b = 2
let Object = {
a: 3,
d: function () {
console.log(this.b)
console.log(a)
console.log(this.a)
},
}
let ed = Object.d;
ed() // 2, 1, undefined
此时this指向window,通过window.a访问为undefined,由于const 和 let 声明的变量不在 window 上,所有通过直接访问即可
扩展: let、const 以及 var 的区别是什么?
let/const 也存在变量声明提升,只是没有初始化分配内存。 一个变量有三个操作,声明(提到作用域顶部),初始化(赋默认值),赋值(继续赋值)
let 是一开始变量声明提升,然后没有初始化分配内存,代码执行到那行初始化,之后对变量继续操作是赋值。因为没有初始化分配内存,所以会报错,这是暂时性死区
实现一个 Object.assign 大致思路如下:
1、判断原生 Object 是否支持该函数,如果不存在的话创建一个函数 assign,并使用 Object.defineProperty 将该函数绑定到 Object 上。
2、判断参数是否正确(目标对象不能为空,我们可以直接设置{}传递进去,但必须设置值)。
3、使用 Object() 转成对象,并保存为 to,最后返回这个对象 to。
4、使用 for..in 循环遍历出所有可枚举的自有属性。并复制给新的目标对象(使用 hasOwnProperty 获取自有属性,即非原型链上的属性)。
实现代码如下,这里为了验证方便,使用 assign2 代替 assign。注意此模拟实现不支持 symbol 属性,因为ES5 中根本没有 symbol 。
if (typeof Object.assign2 != 'function') {
// Attention 1
Object.defineProperty(Object, "assign2", {
value: function (target) {
'use strict';
if (target == null) { // Attention 2
throw new TypeError('Cannot convert undefined or null to object');
}
// Attention 3
var to = Object(target);
for (var index = 1; index < arguments.length; index++) {
var nextSource = arguments[index];
if (nextSource != null) { // Attention 2
// Attention 4
for (var nextKey in nextSource) {
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
},
writable: true,
configurable: true
});
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.