Git Product home page Git Product logo

learningrecord's Introduction

learningrecord's People

Contributors

rashomon511 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

learningrecord's Issues

聊聊 Vue 的双向数据绑定,Model 如何改变 View,View 又是如何改变 Model 的

  • viewmodel将el指向的模板转换成(一个东西) | string-loader 将引入到模块中html变成字符串
  • 利用模板引擎将数据渲染上去,如果有指令,对指令进行处理,如@click就会给指定的按钮绑点击事件
  • 渲染之后的那个东西转成字符串放入到页面中

结论: 视图产生用户操作,viewmodel就能马上得知, 因为viewmodel将自己作用范围的视图做了编译/rerender等处理,并且根据指令来操作了dom
所以被重新渲染到页面中的视图已经与viewmodel做了某些程度的绑定

详解

['1', '2', '3'].map(parseInt) what & why ?

首先让我们回顾一下,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 是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。
它的核心就在于它认为自任何站点装载的信赖内容是不安全的。当被浏览器半信半疑的脚本运行在沙箱时,它们应该只被允许访问来自同一站点的资源,而不是那些来自其它站点可能怀有恶意的资源。
所谓同源是指:域名、协议、端口相同。

同源策略又分为以下两种:

  • DOM 同源策略:禁止对不同源页面 DOM 进行操作。这里主要场景是 iframe 跨域的情况,不同域名的 iframe 是限制互相访问的。
  • XMLHttpRequest 同源策略:禁止使用 XHR 对象向不同源的服务器地址发起 HTTP 请求。

没有同源策略会怎样:

  • 如果没有 DOM 同源策略,也就是说不同域的 iframe 之间可以相互访问,那么黑客可以这样进行攻击:
  • 如果 XMLHttpRequest 同源策略,那么黑客可以进行 CSRF(跨站请求伪造) 攻击:

扩展:跨域第方式有哪些

  • CORS(跨域资源共享)
  • JSONP 跨域
  • 图像 Ping 跨域
  • 服务器代理
  • document.domain 跨域
  • window.name 跨域
  • location.hash 跨域
  • postMessage 跨域

详解

介绍下重绘和回流(Repaint & Reflow),以及如何进行优化

当元素的样式发生变化时,浏览器需要触发更新,重新绘制元素。这个过程中,有两种类型的操作,即重绘与回流。
重绘(repaint): 当元素样式的改变不影响布局时,浏览器将使用重绘对元素进行更新,此时由于只需要UI层面的重新像素绘制,因此 损耗较少

回流(reflow): 当元素的尺寸、结构或触发某些属性时,浏览器会重新渲染页面,称为回流。此时,浏览器需要重新经过计算,计算后还需要重新页面布局,因此是较重的操作。会触发回流的操作:

  • 页面初次渲染
  • 浏览器窗口大小改变
  • 元素尺寸、位置、内容发生改变
  • 元素字体大小变化
  • 添加或者删除可见的 dom 元素
  • 激活 CSS 伪类(例如::hover)
  • 查询某些属性或调用某些方法
    • clientWidth、clientHeight、clientTop、clientLeft
  • offsetWidth、offsetHeight、offsetTop、offsetLeft
  • scrollWidth、scrollHeight、scrollTop、scrollLeft
  • getComputedStyle()
  • getBoundingClientRect()
  • scrollTo()

回流必定触发重绘,重绘不一定触发回流。重绘的开销较小,回流的代价较高。

实践:

css

  • 避免使用table布局
  • 将动画效果应用到position属性为absolute或fixed的元素上

javascript

  • 避免频繁操作样式,可汇总后统一 一次修改
  • 尽量使用class进行样式修改
  • 减少dom的增删次数,可使用 字符串 或者 documentFragment 一次性插入
  • 极限优化时,修改样式可将其display: none后修改
  • 避免多次触发上面提到的那些会触发回流的方法,可以的话尽量用 变量存住

扩展: style标签写在body后与body前有什么区别
写在head标签中利于浏览器逐步渲染(resources downloading->CSSOM+DOM->RenderTree(composite)->Layout->paint)
写在body标签后由于浏览器以逐行方式对html文档进行解析,当解析到写在尾部的样式表(外联或写在style标签)会导致浏览器停止之前的渲染,等待加载且解析样式表完成之后重新渲染,在windows的IE下可能会出现FOUC现象(即样式失效导致的页面闪烁问题)

聊聊 Redux 和 Vuex 的设计**

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。

详解

如何实现一个 new

function _new(fn, ...arg) {
    const obj = Object.create(fn.prototype);
    const ret = fn.apply(obj, arg);
    return ret instanceof Object ? ret : obj;
}

介绍下观察者模式和订阅-发布模式的区别,各自适用于什么场景

  • 发布-订阅模式就好像报社, 邮局和个人的关系,报纸的订阅和分发是由邮局来完成的。报社只负责将报纸发送给邮局。
  • 观察者模式就好像 个体奶农和个人的关系。奶农负责统计有多少人订了产品,所以个人都会有一个相同拿牛奶的方法。奶农有新奶了就负责调用这个方法。

观察者模式中主体和观察者是互相感知的,发布-订阅模式是借助第三方来实现调度的,发布者和订阅者之间互不感知

区别与适用场景
总的来说,发布-订阅模式适合更复杂的场景。

在「一对多」的场景下,发布者的某次更新只想通知它的部分订阅者?

在「多对一」或者「多对多」场景下。一个订阅者依赖于多个发布者,某个发布者更新后是否需要通知订阅者?还是等所有发布者都更新完毕再通知订阅者?

react生命周期

组件生命周期

react16前的生命周期

在react16的之前生命周期其实主要分为四个阶段:组件初始化、组件挂载、组件更新、组件卸载。
image.png

组件初始化阶段

constructor

在该阶段组件中的构造方法 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初始值
  }
}

组件挂载阶段

componentWillMount

在组件将要挂载到 DOM 前调用,只会被调用一次,在该方法中修改 state 的值,并不会引起组件重新渲染。(数据请求等异步操作不建议写在该方法内,异步操作可能阻塞 UI)。

componentWillMount(){}

render()

该函数会创建一个虚拟 DOM,用来表示组件的输出。只能通过 this.props 和 this.state 访问数据,且不能在里面执行 this.setState 更该组件状态。在 render 中可以返回 null、false 或者任何 React 组件,只能出现一个顶级组件,不能返回一组元素(在 react16 中有所改善,可以返回一组元素或单个字符串)。

Render(){
	return (
  		// react组件
  )
}

componentDidMount

组件挂载到 Dom 后调用,且只调用一次。此时组件已经生成对应的 DOM 结构,可以在该函数中通过ReactDOM.findDOMNode()访问到真实的 DOM 或者通过 this.refs.[refName] 属性获取真实 DOM 。(数据请求等异步操作建议写在该方法内)

componentDidMount() {
  // 进行异步数据请求或者获取dom
}

组件更新阶段

componentWillReceiveProps

该函数接受一个参数 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。

shouldComponentUpdate(nextProps,nextState)

该函数是唯一可以控制组件渲染的生命周期。如果 props 和 state 的改变不需要重新渲染组件。则可以在该函数内返回 false,阻止组件的重新渲染。为了优化组件性能,减少组件的不必要渲染。

shouldComponentUpdate(nextProps, nextState){
  // return true 更新组件
  // return false 则不更新组件
}

componentWillUpdate(nextProps,nextState)

shouldComponentUpdate 方法返回 true 后,在组件即将进行重新渲染前调用该函数(注意不要里面去更新 props 或者 state,会导致组件进入死循环),在这之后会调用 render 方法进行重新渲染。

componentWillUpdate(nextProps,nextState) {
  // 不要在此处更新props或state
}

componentDidUpdate(prevProps,prevState)

组件被重新渲染后该方法会被调用,可以拿到更新前的 props 和 state 。除了首次渲染时调用的componentDidMount,之后每次渲染都会调用该函数。和 componentDidMount 类似的是可以在这里操作更新后的DOM。

componentDidUpdate(prevProps,prevState) {}

组件卸载阶段

componentWillUnmount

该函数在组件卸载前被调用,可以在执行一些清理工作,比如清除组件中使用的定时器或者事件监听器,以避免引起内存泄漏。

componentWillUnmount() {
  // 清除定时器或事件监听器
}

react16的生命周期

react16的生命周期新引入了三个新的生命周期函数:getDerivedStateFromProps,getSnapshotBeforeUpdate,componentDidCatch,弃用的三个生命周期函数:componentWillMount、componentWillReceivePorps,componentWillUpdate。其他的生命周期功能与前面介绍的相同。

getDerivedStateFromProps(props, state)

该函数在组件挂载阶段和后续更新阶段调用,根据 props 和 state 两个参数,计算出预期的状态改变,返回一个对象表示新的 state进行更新;如果不需要更新,返回 null 即可。该函数用来替代 componentWillReceiveProps。

static getDerivedStateFromProps(props, state) {
  //根据props和state计算出预期的状态改变,返回结果会被送给setState
}

getSnapshotBeforeUpdate(prevProps, prevState)

该函数在render之后被调用,可以读取但无法使用DOM的时候。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。返回值将作为componentDidUpdate的第三个参数。该函数配合componentDidUpdate, 可以替代componentWillUpdate。

  getSnapshotBeforeUpdate(prevProps, prevState) {
    console.log('getSnapshotBeforeUpdate');
    return 'react16';
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log('snapshot = ', snapshot);
  }

static getDerivedStateFromError()

此生命周期会在后代组件抛出错误后被调用。 它将抛出的错误作为参数,并返回一个值以更新 state。

  static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染可以显降级 UI
    return { hasError: true };
  }

componentDidCatch(error,info)

任何一处的javascript会触发该函数。

componentDidCatch(error, info) {
  // 获取到javascript错误
}

总结

react16更新后的生命周期可以总结为:

组件挂载阶段
  • constructor
  • getDerivedStateFromProps
  • render
  • componentDidMount
组件更新阶段
  • getDerivedStateFromProps
  • shouldComponentUpdate
  • render
  • getSnapshotBeforeUpdate
  • componentDidUpdate
组件卸载阶段
  • componentWillUnmount

image.pngimage

参考链接:

ref和DOM操作

ref和DOM操作

在上一篇我们了解到可以在 componentDidMount 或 componentDidUpdate 函数中通过ref属性来访问已经挂载的 DOM 节点或组件实例。ref 属性既可以在组件上使用,也可以在 DOM 节点上使用。

ref的三种用法。

  • 回调函数
  • React.createRef()
  • 字符串(已废弃)

回调函数用法

回调函数就是在 DOM 节点或组件上挂载函数,从而获取到 DOM 节点或组件实例。回调函数在组件被加载或卸载时会立即执行,加载时入参是 DOM 节点或组件实例,卸载时入参时 null。

  1. 在 HTML 元素上设置 ref 属性
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>
    );
  }
}
  1. 在组件上设置 ref 属性,可以获取组件实例。
class AutoFocusTextInput extends React.Component {
  constructor(props) {
    super(props);
  }

  componentDidMount() {
    // 通过this.inputForm 获取改组件实例
    this.inputForm.focusTextInput();
  }

  render() {
    return (
      <InputForm ref={(inputForm) => this.inputForm = inputForm} />
    );
  }
}
  1. 在组件传递回调形式的 refs,在父组件把 refs回调函数 inputFormref 传递给子组件,子组件从 props 中取到相同的回调函数传递给 input。此时父组件中的 inputFormref 取到的是 input 元素的 DOM 节点。 
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.creatRef()用法

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

字符串用法(不推荐使用)

  1. 在 HTML 元素上上设置ref属性,可以获取真实DOM节点
 <input type="text" ref='textInput' />      // this.refs.textInput访问DOM节点
  1. 在组件上设置ref属性,可以获取组件实例
 <InputForm ref='inputForm' />     // this.refs.refInputForm 访问该组件实例

官方提示:勿过度使用refs,避免使用 refs 来做任何可以通过声明式实现来完成的事情

JS 异步解决方案的发展历程以及优缺点。

  • 回调函数
    缺点:回调地狱,嵌套过多影响代码结构不能用 try catch 捕获错误,不能 return,缺乏顺序性 和可信任性
    优点:解决了同步的问题
  • promise
    缺点:无法取消 Promise ,错误需要通过回调函数来捕获
    优点:解决了回调的回调地狱,多重嵌套,promise采用链式调用,可以利用try,catch捕获错误
  • generator
    优点:可以控制函数的执行,可以配合 co 函数库使用
  • Async/Await
    优点: 代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题
    缺点: await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低。
async function test() {
  // 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式
  // 如果有依赖性的话,其实就是解决回调地狱的例子了
  await fetch('XXX1')
  await fetch('XXX2')
  await fetch('XXX3')
}

参考:阮一峰异步编程

CA证书是什么?

CA(Certificate Authority)是负责管理和签发证书的第三方权威机构,是所有行业和公众都信任的、认可的。

CA证书,就是CA颁发的证书,可用于验证网站是否可信(针对HTTPS)、验证某文件是否可信(是否被篡改)等,也可以用一个证书来证明另一个证书是真实可信,最顶级的证书称为根证书。除了根证书(自己证明自己是可靠),其它证书都要依靠上一级的证书,来证明自己。

谈谈你对TCP三次握手和四次挥手的理解

  • 三次握手:


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
    }
  • JSON对象的parse和stringify也可以实现深拷贝
  • JQ的extend方法。
    $.extend( [deep ], target, object1 [, objectN ] )

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

前端需要注意哪些SEO

合理的title、description、keywords:搜索对着三项的权重逐个减小,title值强调重点即可,重要关键词出现不要超过2次,而且要靠前,不同页面title要有所不同;description把页面内容高度概括,长度合适,不可过分堆砌关键词,不同页面description有所不同;keywords列举出重要关键词即可
语义化的HTML代码,符合W3C规范:语义化代码让搜索引擎容易理解网页
重要内容HTML代码放在最前:搜索引擎抓取HTML顺序是从上到下,有的搜索引擎对抓取长度有限制,保证重要内容一定会被抓取
重要内容不要用js输出:爬虫不会执行js获取内容
少用iframe:搜索引擎不会抓取iframe中的内容
非装饰性图片必须加alt
提高网站速度:网站速度是搜索引擎排序的一个重要指标

Set、Map、WeakSet 和 WeakMap

  • Set
    成员唯一、无序且不重复
    [value, value],键值与键名是一致的(或者说只有键值,没有键名)
    可以遍历,方法有:add、delete、has

  • WeakSet
    成员都是对象
    成员都是弱引用,可以被垃圾回收机制回收,可以用来保存DOM节点,不容易造成内存泄漏
    不能遍历,方法有add、delete、has

  • Map
    本质上是键值对的集合,类似集合
    可以遍历,方法很多可以跟各种数据格式转换

  • WeakMap
    只接受对象最为键名(null除外),不接受其他类型的值作为键名
    键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的
    不能遍历,方法有get、set、has、delete

    • 扩展:Object与Set、Map

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

javascript的运行机制

  • 由于js是单线程,js将所有任务分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。
  • 同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
  • 异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
  • 在所有同步任务执行完之前,任何的异步任务是不会执行的
  • 异步执行的运行机制如下:
  • 1.所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
  • 2.主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
  • 3.一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
  • 4.主线程不断重复上面的第三步。
  • 主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制
    下图可以形象说明

  • 一般来说,有以下四种会放入异步任务队列:

  • setTimeout和setlnterval
  • DOM事件
  • ES6中的Promise
  • Ajax异步请求

有以下 3 个判断数组的方法,请分别介绍它们之间的区别和优劣

Object.prototype.toString.call() 、 instanceof 以及 Array.isArray()

性能方面: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() 实现

实现一个 sleep 函数,比如 sleep(1000) 意味着等待1000毫秒,可从 Promise、Generator、Async/Await 等角度实现

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

cookie 和 token 都存放在 header 中,为什么不会劫持 token?

1攻击者通过xss拿到用户的cookie然后就可以伪造cookie了。
2或者通过csrf在同个浏览器下面通过浏览器会自动带上cookie的特性
在通过 用户网站-攻击者网站-攻击者请求用户网站的方式 浏览器会自动带上cookie
但是token
1 不会被浏览器带上 问题2 解决
2 token是放在jwt里面下发给客户端的 而且不一定存储在哪里 不能通过document.cookie直接拿到,通过jwt+ip的方式 可以防止 被劫持 即使被劫持 也是无效的jwt

call,apply,bind的用法以及实现原理

前言

apply()和 call()这两个方法的用途都是在特定的作 用域中调用函数,实际上等于设置函数体内 this 对象的值。bind()方法会创建一个函数的实例,其 this 值会被绑定到传给 bind()函数的值。总的来说是用来改变函数运行时this的指向。

call

用法

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

用法

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

用法

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函数存在的区别:

  • bind()不会立即执行,而是返回一个改变了上下文 this 后的函数, 便于稍后调用;
  • apply, call则是立即调用.
  • call比apply的性能要好,平常可以多用call, call传入参数的格式正是内部所需要的格式

需要注意的一点的是:

  • 在 ES6 的箭头函数下, call 和 apply 将失效, 对于箭头函数来说箭头函数体内的 this 对象, 就是定义时所在的对象。
  • 箭头函数不可以当作构造函数,也就是说不可以使用 new 命令, 否则会抛出一个错误
  • 箭头函数不可以使用 arguments 对象,,该对象在函数体内不存在

promise的运用

如何使用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')
        })

React setState 笔试题,下面的代码输出什么?

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。

可参考19题

es6类的继承和对象关联

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 继承,主要就是

    1. 把子类构造函数(Child)的原型(proto)指向了父类构造函数(Parent)
    1. 把子类实例child的原型对象(Child.prototype) 的原型(proto)指向了父类parent的原型对象(Parent.prototype)。
    1. 子类构造函数Child继承了父类构造函数Preant的里的属性。使用super调用的(ES5则用call或者apply调用传参)。
      2和3小点,可以通过寄生组合式继承实现。设是__proto__可以通过newObject.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();

从上面代码可以看出对象关联风格的代码显然更加简洁,因为这种代码只关注一件事:对象之间的关联关系,
使用类构造函数的话,你需要在同一个步骤中实现构造和初始化。然而,在许多情况下把这两步分开(就像对象关联代码一样)更灵活。
对象关联除了能让代码看起来更简洁(并且更具扩展性)外还可以通过行为委托模式简化代码结构

js中容易忽略的小点

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

分别介绍HTTP与 HTTPS 握手过程

HTTP 握手过程

第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;

第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

HTTPS 握手过程

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握手的第四步中,如果站点的证书是不受信任的,会显示出现下面确认界面,确认了网站的真实性。另外第六和八步,使用客户端私钥加密解密,保证了数据传输的安全。

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和HTTP的区别
  1. https协议需要到ca申请证书或自制证书。

  2. http的信息是明文传输,https则是具有安全性的ssl加密。

  3. http是直接与TCP进行数据传输,而https是经过一层SSL(OSI表示层),用的端口也不一样,前者是80(需要国内备案),后者是443。

  4. http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

写 React / Vue 项目时为什么要在组件中写 key,其作用是什么

  • 自己的答案:

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
    }
  }
  • 思考:

知其然知其所以然,我需要看源码,需要更加深入

HTTPS 握手过程中,客户端如何验证证书的合法性

客户端会有一个有效证书串,一般的浏览器都会内置很多常见服务器的这个证书,特殊的服务器就需要前期通过手工将证书添加到客户端。证明对方是否持有证书的对应的私钥,客户端通过比对来确认证书的有效性

this指向

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上,在显示绑定的基础上还有硬绑定,这是显示绑定的变种

  • 1.硬绑定: 强制把 foo 的 this 绑定到了 obj,js内置bind(..) 会返回一个硬编码的新函数,它会把参数设置为 this 的上下文并调用原始函数
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同样可以实现硬绑定

  • API调用的“上下文”: 提供了一 个可选的参数,通常被称为“上下文”(context),其作用和 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绑定

使用 new 来调用 foo(..) 时,我们会构造一个新对象并把它绑定到 foo(..) 调用中的 this 上。new 是最后一种可以影响函数调用时 this 绑定行为的方法,我们称之为 new 绑定

function foo(){
this.a=a
}
var bar=new foo(2);
console.log(bar.a)//2

发生函数构造时或者使用new来调用函数,会自动执行下面的操作:

    1. 创建(或者说构造)一个全新的对象。
    1. 这个新对象会被执行[[原型]]连接。
    1. 这个新对象会绑定到函数调用的this。
    1. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。

优先级问题

new绑定>显示绑定>隐式绑定>默认绑定

特例:箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)作用域来决定this。箭头函数会继承外层函数调用的 this 绑定(无论 this 绑定到什么)。这 其实和 ES6 之前代码中的 self = this 机制一样

简单讲解一下http2的多路复用

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

详解

Promise 构造函数是同步执行还是异步执行,那么 then 方法呢?

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,

A、B 机器正常连接后,B 机器突然重启,问 A 此时处于 TCP 什么状态

  • 服务器和客户建立连接后,若服务器主机崩溃,有两种可能:
  • 服务器不重启,客户继续工作,就会发现对方没有回应(ETIMEOUT),路由器聪明的话,则是目的地不可达(EHOSTUNREACH)。
  • 服务器重启后,客户继续工作,然而服务器已丢失客户信息,收到客户数据后响应RST。

A处于syn_sent状态

ES5/ES6 的继承除了写法以外还有什么区别?

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/Await 如何通过同步的方式实现异步

原理:

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

setTimeout、Promise、Async/Await 的区别

  • setTimeout方法用于在指定的毫秒数后调用函数或计算表达式。setTimeout() 只是将事件插入了“任务队列”,必须等当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是当前代码消耗时间很长,也有可能要等很久,所以并没办法保证回调函数一定会在 setTimeout() 指定的时间执行。所以, setTimeout() 的第二个参数表示的是最少时间,并非是确切时间,setTimeout() 的第二个参数的最小值不得小于4毫秒,如果低于这个值,则默认是4毫秒
  • 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

  • async/await是写异步代码的新方式,以前的方法有回调函数和Promise。
  • async/await是基于Promise实现的,它不能用于普通的回调函数。
  • async/await与Promise一样,是非阻塞的。
  • 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 ,不同状态回调在同一函数体;
  • js执行顺序
  • 依次执行同步代码直至执行完毕;
  • 检查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 中 setState 什么时候是同步的,什么时候是异步的?

在React中,如果是由React引发的事件处理(比如通过onClick引发的事件处理),调用setState不会同步更新this.state,除此之外的setState调用会同步执行this.state。所谓“除此之外”,指的是绕过React通过addEventListener直接添加的事件处理函数,还有通过setTimeout/setInterval产生的异步调用。

  • 原因:在React的setState函数实现中,会根据一个变量isBatchingUpdates判断是直接更新this.state还是放到队列中回头再说,而isBatchingUpdates默认是false,也就表示setState会同步更新this.state,但是,有一个函数batchedUpdates,这个函数会把isBatchingUpdates修改为true,而当React在调用事件处理函数之前就会调用这个batchedUpdates,造成的后果,就是由React控制的事件处理过程setState不会同步更新this.state。

简单来说就是当setState方法调用的时候React就会重新调用render方法来重新渲染组件;setState通过一个队列来更新state,当调用setState方法的时候会将需要更新的state放入这个状态队列中,这个队列会高效的批量更新state;
image

详解: 深入 setState 机制

深拷贝的实现

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

说说浏览器和 Node 事件循环的区别

主要的区别在于浏览器的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

  • timer 阶段: 执行到期的setTimeout / setInterval队列回调
  • I/O 阶段: 执行上轮循环残流的callback
  • idle, prepare
  • poll: 等待回调
    • 执行回调
    • 执行定时器
      • 到期的setTimeout / setInterval, 则返回 timer 阶段
      • 如有setImmediate,则前往 check 阶段
  • check
    • 执行setImmediate
  • close callbacks

浏览器与Node的事件循环(Event Loop)有何区别?

全局作用域中,用 const 和 let 声明的变量不在 window 上,那到底在哪里?如何去获取?。

在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 定义的变量不会出现变量提升,而 var 定义的变量会提升。
  • let 和 const 是JS中的块级作用域
  • let 和 const 不允许重复声明(会抛出错误)
  • let 和 const 定义的变量在定义语句之前,如果使用会抛出错误(形成了暂时性死区),而 var 不会。
  • const 声明一个只读的常量。一旦声明,常量的值就不能改变(如果声明是一个对象,那么不能改变的是对象的引用地址)

let/const 也存在变量声明提升,只是没有初始化分配内存。 一个变量有三个操作,声明(提到作用域顶部),初始化(赋默认值),赋值(继续赋值)
let 是一开始变量声明提升,然后没有初始化分配内存,代码执行到那行初始化,之后对变量继续操作是赋值。因为没有初始化分配内存,所以会报错,这是暂时性死区

Object.assign 模拟实现

实现一个 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
  });
}

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.