Code Monkey home page Code Monkey logo

blog's Introduction

不畏将来,不念过往,遵从内心 😄

NsNe's GitHub stats

🔭 个人简介

  • 2015年毕业于西安科技大学,电子商务(理)专业(一个既学习计算机又学习管理的专业 😄)
  • 曾就职于绿盟,网易,依图。现就职于快手。
  • 6年工作中,前端技术栈主要使用 react,了解 vueangularjs
  • 了解 pythonnodelinux (曾经在绿盟写过一段时间 shell
  • 个人blog (早期用 hexo
  • 学习与解决问题能力强,代码强迫症患者
  • 喜欢打篮球,不过近几年久坐,导致小肚子越来越大,要减肥了

🌱 正在做的事

  • 正在学习 nodereact 源码,前端其他
  • 减肥

📫 联系我

[email protected]

🚏 Languages

Top Langs

blog's People

Contributors

nsne avatar

Stargazers

 avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

blog's Issues

lerna 初体验

在产生一个解决方案时,不可避免的会产生多个 npm 包,以适配不同的用户群体,而这些包有可能互相之间又有依赖,管理起来,比较复杂,lerna 可以很容易的帮助我们管理多包

lerna 优劣

优势:

  • 不需要在意各个子模块之间的依赖,lerna 会帮你管理&发包
  • 开发子模块不用在意开发环境的依赖,外层统一管理, 开发更便捷
  • 不需要切换子模块来开发, 所有的子模块在同一项目
  • 子模块之间开发相对来说也较为独立
  • 自动修改依赖,提交,发布,并创建 tag

劣势:

  • 仓库体积增长迅速,随着子模块的增多,仓库的体积会变得十分庞大

lerna 初始化

lerna init

修改 lerna.json

version 为 independent 表示各个子包独立管理版本

{
  "packages": [
    "packages/*"
  ],
  "version": "independent",
  "command": {
    "bootstrap": {
      "npmClientArgs": ["--hoist"]
    }
  }
}

可以使用 lerna link convert 将依赖提取到根,类似 yarn workspace

安装依赖

$ npm i    # 安装项目全局依赖
$ lerna bootstrap    # 安装所有子模块的依赖
$ lerna add {module} --scope={package}    # 给某个模块添加一个依赖
$ lerna run start --scope={package}    # 运行某个模块

举例

$ # 运行所有子模块
$ lerna run start
$ # 运行 antd 模块,注意 scope 后为包名,而非文件名
$ lerna run start --scope antd

$ # 添加 --stream 参数,在终端打印运行日志信息
$ lerna run start --scope antd --stream

卸载依赖

lerna exec -- <command> [..args] # 在所有包中运行该命令
//例子
lerna exec --scope=antd npm uninstall webpack # 将 antd 包下的 webpack 卸载

删除安装依赖

lerna clean

查看包是否发生了变化

lerna updated/diff

查看本地所有包列表

lerna list

发布前查看哪些包发生了变化

lerna changed

lerna 发包

lerna publish
lerna version  # 只把包提交到 git , 并不发布到 npm

lerna 重新发包

lerna publish from-git
lerna publish from-package

React组件中的事件处理函数

在react中实现事件处理,有多种写法,那那种写法相对更优,更利于React的渲染性能呢?

React组件中添加事件处理的几种方式

constructor函数中bind

class ReactEvent extends Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    console.log('Click');
  }

  render() {
    return <button onClick={this.handleClick}>Click Me</button>;
  }
}

使用箭头函数(实验语法,尚未标准化)

render中使用箭头函数

class ReactEvent extends Component {

  handleClick() {
    console.log('Click');
  }

  render() {
    return <button onClick={() => this.handleClick()}>Click Me</button>;
  }
}

使用class fields语法(https://babeljs.io/docs/en/babel-plugin-proposal-class-properties)

class ReactEvent extends Component {

  //此函数会被绑定到ReactEvent类的实例
  handleClick = () => {
    console.log('Click');
  }

  render() {
    return <button onClick={this.handleClick}>Click Me</button>;
  }
}

在render中使用bind

class ReactEvent extends Component {

  handleClick() {
    console.log('Click');
  }

  render() {
    return <button onClick={this.handleClick.bind(this)}>Click Me</button>;
  }
}

几种方式比较

影响 constructor函数中bind 使用class fields语法 render中使用箭头函数 在render中使用bind
render时生成新函数
性能 无影响 无影响 有影响 有影响
可直接携带参数
简洁性 不好

  上表中我们看到,在render中直接bind或者箭头函数都会影响性能,原因在于,在render 中的bind和箭头函数在每次render时都会创建新的函数,导致子组件的props发生改变,这在PureComponent中会影响性能,除非自己在shouldComponentUpdate中进行优化。

bind和箭头函数相关请参考mdn:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_functions

//仅作为示例代码,不遵循常用代码规范
//子组件
class Button extends React.PureComponent {

  render() {
    console.log('================')
    return (
      <button onClick={this.props.handleClick}>hahaha</button>
    )
  }

}


//父组件
class ButtonList extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      index: -1,
      list: [1, 2, 3, 4]
    };
    this.handleClick = this.handleClick.bind(this);
  }
  
  handleClick() {
    console.log('Click');
  }
  
  onStateChange = () => {
    this.setState({
      index: 1
    });
  }
  
  render() {
    return (
      <div>
        <button onClick={this.onStateChange}>stateChange</button>   
        {
          this.state.list.map(item => <Button handleClick={this.handleClick}/>)
        }
      </div>
    )
  }
}


ReactDOM.render(
    <ButtonList />, document.getElementById('root')
);

事件处理中传参

  在开发当中,经常遇到对一个列表做操作,可能包含删除,修改,查看。这时候绑定事件就需要传参,通常为id。

直接传递参数

  //render中使用箭头函数
  {
    this.state.list.map(item => (
      <Button onClick={() => this.handleClick(item.id)}/>
    ))
  }
  //render中使用bind
  {
    this.state.list.map(item => (
      <Button onClick={this.handleClick.bind(this, item.id)}/>
    ))
  }

使用data属性

  //handleClick中通过e.target.dataset.id获取
  {
    this.state.list.map(item => (
      <Button data-id={item.id} onClick={this.handleClick}/>
    ))
  }

总结

  这里不强制推荐使用哪一种,对于各个团队来说,可以根据项目,选择自己团队的事件绑定方式。

  因为箭头函数的简洁性,在公司项目中,我们团队通常使用class fields 定义箭头函数来绑定事件。
  当需要传参的时,单个参数传递使用data属性传参。
  多个参数传递时,采用拆分子组件的方式回调传参。

//子组件
class Button extends React.PureComponent {

  handleClick = () => {
    this.props.handleClick(this.props.item);
  }

  render() {
    return (
      <button onClick={this.handleClick}>hahaha</button>
    )
  }
}


//父组件
class ButtonList extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      list: [1, 2, 3, 4]
    };
  }
  
  handleClick = (item) => {
    console.log('Click', item);
  }
  
  render() {
    const { list=[] } = this.state;

    return (
      <div>
        {
          list.map(item => <Button handleClick={this.handleClick} item={item}/>)
        }
      </div>
    )
  }
}

ReactDOM.render(
    <ButtonList />, document.getElementById('root')
);

结语

  前端发展巨快,各种新特性,新框架,新UI层出不穷。需要我们不断保持学习,深挖技术底层,这样遇到任何新的技术,才能够以一法破万法。

React组件生命周期

  React是很火的前端框架,其最大的特点便是组件化。为了迎娶白富美,当上CEO,走上人生巅峰,对这么火的东东也有了一定的了解,但是对其生命周期一知半解,于是去官方网站瞅了几眼,结合本地实例,和大家共同学习下React组件的生命周期。如果英文比较好的同学可以直接到https://facebook.github.io/react/docs/react-component.html 查看。

创建一个React组件

官网说了,React中有一个React.Component类,这是一个抽象类,我们很少会直接用到它,通常我们写一个子类去继承它,并且在我们的类中至少定义一个render()方法。

class Greeting extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

上面这个是ES6的写法,当然如果没有用ES6,那么可以向下面这样去写:

var Greeting = React.createClass({
  render: function() {
    return <h1>Hello, {this.props.name}</h1>;
  }
});

组件生命周期

每个组件都有几个生命周期函数,以will为前缀的函数是在发生某些事之前调用,以did为前缀的是在发生某些事之后调用

Mounting

如下这些方法在组件实例被创建和被插入到dom中时被调用。

  • constructor()
      constructor在组件被mounted之前调用,我们的组件继承自React.Component,constructor函数中我们在其他操作前应该先调用super(props),否则this.props将会是undefined。
      constructor初始化state的好地方。如果我们不需要初始化state,也不需要bind任何方法,那么在我们的组件中不需要实现constructor函数。
      注意下面这种情况,很容易产生bug,我们通常的做法是提升state到父组件,而不是使劲的同步state和props。
constructor(props) {
  super(props);
  this.state = {
    color: props.initialColor
  };
}  
  • componentWillMount()
      此方法在mounting之前被立即调用,它在render()之前调用,因此在此方法中setState不会触发重新渲染。此方法是服务器渲染中调用的唯一的生命周期钩子,通常我们建议使用constructor()。
  • render()
      render()方法是react组件必须的,它检查this.props和this.state并且返回一个React元素,我们也可以返回null或false,代表我们不想有任何的渲染。
      render()方法应该是一个纯方法,即它不会修改组件的state,在每一次调用时返回同样的结果。它不直接和浏览器交互,如果我们想要交互,应该在componentDidMount()或者其他的生命周期函数里面。
  • componentDidMount()
      此方法在组件被mounted之后立即被调用,初始化dom节点应该在此方法中,如果需要从远端健在数据,这里是实例化网络请求的好地方,此方法中setState会触发组件重新渲染。

Updating

props和state的改变产生更新。在重新渲染组建时,如下的方法被调用

  • componentWillReceiveProps()
      一个已经mounted的组件接收一个新的props之前componentWillReceiveProps()被调用,如果我们需要更新state来响应prop的更改,我们可以在此方法中比较this.props和nextProps并使用this.setState来更改state。
      注意,即使props没有改变,React也可以调用这个方法,因此如果你只想处理改变,请确保比较当前值和下一个值。当父组件导致你的组件重新渲染时,可能会发生这种情况。
      React在组件mounting期间不会调用此方法,只有在一些组件的props可能被更新的时候才会调用。调用this.setState通常不会触发componentWillReceiveProps。
  • shouldComponentUpdate()
      使用此方法让React知道组件的输出是否不受当前state或props更改的影响。默认行为是在每次state更改时重新渲染组件,在大多数情况下,我们应该默认改行为。
      当接收到新的props或state时,shouldComponentUpdate()在渲染之前被调用。默认返回true,对于初始渲染或使用forceUpdate()时,不调用此方法。返回false不会阻止子组件的state更改时,该子组件重新渲染。
      如果shouldComponentUpdate()返回false,那么componentWillUpdate(),render()和componentDidUpdate()将不会被调用。在将来,React可能将shouldComponentUpdate()作为提示而不是strict指令,返回仍然可能导致组件重新渲染。
  • componentWillUpdate()
      当接收新的props或state时,componentWillUpdate()在组件渲染之前被立即调用。使用此函数作为在更新发生之前执行准备的机会。初始渲染不会调用此方法。
    注意:这里不能调用this.setState()(如果调用会怎么样?好奇心很重呀,试了一下,会产生死循环,一直更新。。。)。如果我们需要更新state以响应props的更改,我们应该使用componentWillReceiveProps()
  • render()
      render()方法是react组件必须的,它检查this.props和this.state并且返回一个React元素,我们也可以返回null或false,代表我们不想有任何的渲染。
      render()方法应该是一个纯方法,即它不会修改组件的state,在每一次调用时返回同样的结果。它不直接和浏览器交互,如果我们想要交互,应该在componentDidMount()或者其他的生命周期函数里面。
  • componentDidUpdate()
      此函数在更新后立即被调用。初始渲染不调用此方法。
      当组件已经更新时,使用此操作作为DOM操作的机会。这也是一个好的地方做网络请求,只要你比较当前的props和以前的props(例如:如果props没有改变,可能不需要网络请求)。

Unmounting

当从dom中移除组件时,这个方法会被调用

  • componentWillUnmount()
      此函数在组件被卸载和销毁之前被立即调用。在此方法中执行一些必要的清理。例如清除计时器,取消网络请求或者清理在componentDidMount中创建的任何DOM元素。

上手试试

官网说组件生命周期是这样的,但是谁知道对不对呢(当然是对的,但是怀疑的态度还是要有的嘛,主要还是为了加深理解,更好的写React组件)。
https://codepen.io/NsNe/pen/oBVKWM

可以在http://codepen.io(此处不是打广告,只是自己在用这款而已,当然你也可以用其的,比如:jsfiddle等,总有一款适合你,也可以直接f12(chrome),打开console)查看上述代码的console结果。
第一次渲染组件:LifeCycle组件第一次渲染时,打印出(生命周期函数执行):

"constructor"
"componentWillMount"
"render"
"componentDidMount"

当组件的props发生改变:当我们点击“改变LifeCycle的props”按钮(组件的props发生改变)时,按理说,这时LifeCycle会调用updating阶段的钩子函数。

"componentWillReceiveProps"
"shouldComponentUpdate"
"componentWillUpdate"
"render"
"componentDidUpdate"

当组件的state发生改变:我们在组件内部,点击“改变LifeCycle的state”按钮时(组件的state发生改变),打印如下

"shouldComponentUpdate"
"componentWillUpdate"
"render"
"componentDidUpdate"

当父组件导致子组件重新渲染:当点击“父组件重新渲染,子组件re-render”按钮时(在父组件中改变与子组件无关的state),打印结果如下

"componentWillReceiveProps"
"shouldComponentUpdate"
"componentWillUpdate"
"render"
"componentDidUpdate"

当组件被卸载和销毁:打印结果如下:

"componentWillUnmount"

结语

  至此,我们已经很清楚的知道了React组件的生命周期,等到我们写组件的时候就会很清楚的知道代码该往哪里写,哪些地方不能setState等等。大家可以在上述代码中按自己的意愿来修改,加深理解。
  文中难免有疏漏,望大家共同交流,批评指正。

防抖和节流啥区别,实现一个防抖和节流

防抖

在一定时间内执行一次,如果在此时间内再次触发,则重新计时

const debounce = (func, timeout, immediate = false) => {
  let timer = null;
  return function (...args) {
    if (!timer && immediate) {
      func.apply(this, args);
    }
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      func.apply(this, args);
    }, timeout);
  }
}

节流

在一定时间内执行一次,如果在此时间内再次触发,则会拦截不执行

const throttle = (func, timeout) => {
  let timer = null;
  return function (...args) {
    if (!timer) {
      timer = setTimeout(() => {
        timer = null;
        func.apply(this, args);
      }, timeout);
    }
  }
}

React 小知识

React.StrictMode

  • 识别具有不安全生命周期的组件
  • 有关旧式字符串ref用法的警告
  • 关于已弃用的findDOMNode用法的警告
  • 检测意外的副作用
  • 检测遗留 context API
    示例:
import React from 'react';

function ExampleApplication() {
  return (
    <div>
      <Header />
      <React.StrictMode>
        <div>
          <ComponentOne />
          <ComponentTwo />
        </div>
      </React.StrictMode>
      <Footer />
    </div>
  );
}

React.Lazy

允许我们定义动态加载的组件
示例:

// This component is loaded dynamically
const SomeComponent = React.lazy(() => import('./SomeComponent'));

React.Suspense

延迟加载组件,当组件未准备好时,显示 loading

// This component is loaded dynamically
const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    // Displays <Spinner> until OtherComponent loads
    <React.Suspense fallback={<Spinner />}>
      <div>
        <OtherComponent />
      </div>
    </React.Suspense>
  );
}

ReactDOM.createPortal

将组件挂载在任意节点

ReactDOM.createPortal(child, container)

Profiler

测量渲染一个 React 应用多久渲染一次以及渲染一次的“代价”

render(
  <App>
    <Profiler id="Navigation" onRender={callback}>
      <Navigation {...props} />
    </Profiler>
    <Main {...props} />
  </App>
);
function onRenderCallback(
  id, // the "id" prop of the Profiler tree that has just committed
  phase, // either "mount" (if the tree just mounted) or "update" (if it re-rendered)
  actualDuration, // time spent rendering the committed update
  baseDuration, // estimated time to render the entire subtree without memoization
  startTime, // when React began rendering this update
  commitTime, // when React committed this update
  interactions // the Set of interactions belonging to this update
) {
  // Aggregate or log render timings...
}

npm link

npm link 方法,可在项目开发中使用 npm 包,用于排查问题,或方便的检测 npm 包在项目中的使用

  • npm 包
cd [module]
npm link
  • 项目内
cd example
npm link [module]

排序算法

排序算法

本文讲解冒泡排序,快速排序,归并排序 三种排序算法的思路和编码

冒泡排序

思路:依次比较相邻的两个数,将比较小的数放在前面,比较大的数放在后面。
直到跑完一次,这时候,最大的数就放在最后一个位置了,在了它排序后应该在的位置了。依此类推,再把第二大的数冒泡到倒数第二个位置, 跑 i 次,把 i 个数都放到它指定的位置,排序完成。

function bubbleSort(arr) {
  const len = arr.length;
  for (let i = 0; i < len; i++) {
    // 每次把最大值冒泡到最后
    for (let j = 1; j < len - i; j++) {
      if (arr[j - 1] > arr[j]) {
        [arr[j - 1], arr[j]] = [arr[j], arr[j - 1]];
      }
    }
  }
  return arr;
}

快速排序

思路:设定一个分界值,通过该分界值将数组分成左右两部分,把小于它的,放在左边,大于它的,放在右边。
  跑完一次,那么分界值就在了它自己应该在的位置(因为左边的都小于它,右边的都大于它),分割的两端继续按照这个逻辑去跑(递归)。

function quickSort(arr) {
  const sort = (L, R) => {
    if (L >= R) return;
    let l = L;
    let r = R;
    // 选择一个基准值,把小于它的,放在左边,大于它的,放在右边
    // 拆分后的两端继续这样做
    const pivot = arr[l];
    while (l < r) {
      while (l < r && arr[r] > pivot) r--;
      if (l < r) {
        arr[l] = arr[r];
      }
      while (l < r && arr[l] < pivot) l++;
      if (l < r) {
        arr[r] = arr[l];
      }
    }
    arr[l] = pivot;
    sort(L, r - 1);
    sort(r + 1, R);
  };
  sort(0, arr.length - 1);
  return arr;
}

归并排序

思路:将数组拆分为两部分,分别对两个子数组进行递归拆分。直到无法再分,这时候进行两两合并(归并),合并后的子序列是有序的,也就是合并两个有序的子序列。合并完成即为最后排序后的结果。

function mergeSort(arr) {
  const merge = (sortV1, sortV2) => {
    let sortValue = [];
    let i = 0,
      j = 0;
    while (i < sortV1.length && j < sortV2.length) {
      if (sortV1[i] > sortV2[j]) {
        sortValue.push(sortV2[j]);
        j++;
      } else {
        sortValue.push(sortV1[i]);
        i++;
      }
    }
    if (i < sortV1.length) {
      sortValue = sortValue.concat(sortV1.slice(i, sortV1.length));
    } else if (j < sortV2.length) {
      sortValue = sortValue.concat(sortV2.slice(j, sortV2.length));
    }
    return sortValue;
  };

  const sort = (arr) => {
    if (arr.length === 1) return arr;
    let mid = Math.floor(arr.length / 2);
    let v1 = arr.slice(0, mid);
    let v2 = arr.slice(mid, arr.length);
    const sortV1 = sort(v1);
    const sortV2 = sort(v2);
    return merge(sortV1, sortV2);
  };

  return sort(arr);
}

手动实现 Promise

Promise 代表了一个异步操作的最终完成或者失败

定义状态

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

构造函数定义

function MyPromise(excutor){
  // 初始状态为 pending
  this.status = PENDING;
  // 成功的值
  this.value = undefined;
  // 失败的原因
  this.reason = undefined;
  // then 的回调函数集合,后面会用到
  this.fulfilledCallback = [];
  this.rejectedCallback = [];

  const resolve = (value) => {
    if (this.status === PENDING) {
      this.status = FULFILLED;
      this.value = value;
      // 状态变更后执行 then 的回调函数
      this.fulfilledCallback.forEach(fn => fn(value))
    }
  }

  const reject = (reason) => {
    if (this.status === PENDING) {
      this.status = REJECTED;
      this.reason = reason;
      this.rejectedCallback.forEach(fn => fn(reason))
    }
  }

  try {
    excutor(resolve, reject);
  } catch (error) {
    reject(error);
  }
}

原型方法

MyPromise.prototype.then = function(onResolve, onReject){

  onResolve = onResolve === undefined ? value => value : onResolve,
  onReject = onReject === undefined ? error => { throw error } : onReject;

  return new MyPromise((resolve, reject) => {

    function handle(func, value){
      try {
        const result = func(value);
        // 如果返回一个 Promise
        if (result instanceof MyPromise) {
            result.then(resolve, reject);
        } else {
          resolve(result);
        }
      } catch (err) {
          reject(err);
      }
    }

    if (this.status === FULFILLED) {
      queueMicrotask(() => {
        handle(onResolve, this.value);
      });
    }

    if (this.status === REJECTED) {
      queueMicrotask(() => {
        handle(onReject, this.reason);
      });
    }

     // 状态为 pending,将回调函数保存到前面定义的函数集合中
    if (this.status === PENDING) {
      // 保存到 fulfilledCallback
      this.fulfilledCallback.push(value => {
        queueMicrotask(() => {
          handle(onResolve, value);
        });
      });

      // 保存到 rejectedCallback
      this.rejectedCallback.push(reason => {
        queueMicrotask(() => {
          handle(onReject, reason)
        });
      });
    }
  })
};

  MyPromise.prototype.catch = function (onReject) {
    return this.then(undefined, onReject);
  };

  MyPromise.prototype.finally = function (cb) {
    return this.then(
      (value) => MyPromise.resolve(cb()).then(() => value),
      (reason) =>
        MyPromise.resolve(cb()).then(() => {
          throw reason;
        })
    );
  };

静态方法

  MyPromise.resolve = function (value) {
    // 如果参数是MyPromise实例,直接返回这个实例
    if (value instanceof MyPromise) return value;
    return new MyPromise((resolve) => resolve(value));
  };

  MyPromise.reject = function (value) {
    // 如果参数是MyPromise实例,直接返回这个实例
    if (value instanceof MyPromise) return value;
    return new MyPromise((resolve, reject) => reject(value));
  };

  MyPromise.race = function (promises) {
    return new Promise((resolve, reject) => {
      promises.forEach((p) => {
        MyPromise.resolve(p).then(resolve, reject);
      });
    });
  };

  MyPromise.all = function (promises) {
    let result = [];
    const len = promises.length;
    let count = 0;
    return new MyPromise((resolve, reject) => {
      promises.forEach((p, index) => {
        MyPromise.resolve(p).then(
          (res) => {
            result[index] = res;
            count++;
            if (count === len) {
              resolve(result);
            }
          },
          (error) => {
            reject(error);
          }
        );
      });
    });
  };

  MyPromise.allSettled = function (promises) {
    let result = [];
    const len = promises.length;
    let count = 0;
    return new MyPromise((resolve, reject) => {
      promises.forEach((p, index) => {
        MyPromise.resolve(p).then(
          (value) => {
            result[index] = {
              status: FULFILLED,
              value,
            };
            count++;
            if (count === len) {
              resolve(result);
            }
          },
          (reason) => {
            result[index] = {
              status: REJECTED,
              reason,
            };
            count++;
            if (count === len) {
              resolve(result);
            }
          }
        );
      });
    });
  };

最后的最后

上述代码用到了queueMicrotask()来执行微任务
mdn上也提供了queueMicrotask()的polyfill实现
它通过使用立即 resolve 的 promise 创建一个微任务(microtask)
如下:

if (typeof window.queueMicrotask !== "function") {
  window.queueMicrotask = function (callback) {
    Promise.resolve()
      .then(callback)
      .catch(e => setTimeout(() => { throw e; }));
  };
}

Vue代码分割懒加载

webpack > 2的时代,vue做代码分割懒加载更加的easy,不需要loader,不需要require.ensure。
import解决一切。

分割层级

Vue代码分割懒加载包含如下几个层级:

1、 组件层级分割懒加载
2、 router路由层级
3、 Vuex 模块

组件层级代码分割

//全局组件
Vue.component('AsyncComponent', () => import('./AsyncComponent'))

//局部注册组件
new Vue({
  // ...
  components: {
    'AsyncComponent': () => import('./AsyncComponent')
  }
})

// 如果不是default导出的模块
new Vue({
  // ...
  components: {
    'AsyncComponent': () => import('./AsyncComponent').then({ AsyncComponent }) => AsyncComponent
  }
})

路由层级代码分割

const AsyncComponent= () => import('./AsyncComponent')

new VueRouter({
  routes: [
    { path: '/test', component: AsyncComponent}
  ]
})

Vuex 模块代码分割,vuex中有动态注册模块方法,同时也是加上import

const store = new Vuex.Store()

import('./store/test').then(testModule => {
  store.registerModule('test', testModule)
})

总结

在一般项目中,我们按照router和components层面分割(或者只使用router分割)就足够了。大型项目可能三者都会用到,但用法都很简单,不是么?

webpack2配置react + antd开发生产环境

webpack.dev.config.js

const webpack = require('webpack');
const path = require('path');
const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ManifestPlugin = require('webpack-manifest-plugin');
const precss = require('precss');
const autoprefixer = require('autoprefixer');

module.exports = function(env) {
    return {

        resolve: {
            extensions: [    //扩展后缀,让你的import 'index.js' 可以写成 import 'index'
                ".js",
                ".json",
                ".jsx",
                ".css",
                '.less',
                '.sass',
                '.scss'
            ]
        },

        entry: {
            main: path.resolve(__dirname, '../src/index.js'),    //项目入口文件
            vendors: [          //公共第三方依赖
                "react",
                'react-dom',
                'react-router',
                'react-router-dom',
                'react-router-redux',
                'redux',
                'react-redux',
                'redux-thunk',
                'immutable',
                'recharts'
            ],
            
        },

        module: {
            rules: [    //处理css, js, 图片等。其中js添加了对antd的支持
                {
                    test: /\.css$/,
                    use: ['style-loader', 'css-loader', 'postcss-loader'],
                }, {
                    test: /\.s[a|c]ss$/,
                    use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader']
                }, {
                    test: /\.less$/,
                    use: ['style-loader', 'css-loader', 'postcss-loader', 'less-loader']
                }, {
                    test: /\.jsx?$/,
                    exclude: /(node_modules)/,
                    use: [
                        {
                            loader: 'babel-loader',
                            options: {
                                presets: [
                                    [
                                        "es2015", {
                                            "modules": false
                                        }
                                    ],
                                    "stage-2",
                                    "react"
                                ],
                                plugins: [
                                    "transform-runtime",
                                    [
                                        "import", {
                                            "libraryName": "antd",
                                            "style": true,
                                        }
                                    ]
                                ]
                            }
                        }
                    ]
                }, {
                    test: /\.(woff|woff2|eot|ttf|svg)$/,
                    use: {
                        loader: 'url-loader',
                        options: {
                            limit: 10000,
                            name: '[name]-[hash:8].[ext]',
                            outputPath: 'fonts/',
                            publicPath: '../fonts/'
                        }
                    }
                }, {
                    test: /\.(png|jpg)$/,
                    use: {
                        loader: 'url-loader',
                        options: {
                            limit: 8192,
                            name: 'img/[name]-[hash:8].[ext]'
                        }
                    }
                }
            ]
        },

        output: {       //打包输出路径
            filename: 'js/[name].[hash:8].js',
            path: path.resolve(__dirname, '../dist'),
            publicPath: '/'
        },

        devtool: 'cheap-module-eval-source-map',

        plugins: [
            new webpack.LoaderOptionsPlugin({        //使用postcss对css添加浏览器兼容支持
                options: {
                    postcss: function() {
                        return [precss, autoprefixer];
                    }
                }
            }),
            new webpack.optimize.CommonsChunkPlugin({           //配置公共js
                names: ['vendors', 'manifest'] // Specify the common bundle's name.
            }),
            new webpack.optimize.UglifyJsPlugin({sourceMap: true, beautify: true, compress: false}),    //压缩js
            new HtmlWebpackPlugin({            //生成index.html, 并将css和js注入
                title: "webpack",
                template: path.resolve(__dirname, '../src/index.html'),
                favicon: path.resolve(__dirname, '../src/favicon.ico'),
                inject: 'body'
            }),
            new webpack.DefinePlugin({             //定义全局环境标识
                'process.env': {
                    NODE_ENV: JSON.stringify('development')
                }
            }),
            new webpack.HotModuleReplacementPlugin(),         //热部署
            new webpack.NamedModulesPlugin(),                //输出热部署改变的文件的详细信息
            new ManifestPlugin({fileName: 'asset-manifest.json'})
        ],

        devServer: {          //配置开发服务器
            port: 8080,
            host: 'localhost',
            inline: true,
            hot: true,
            historyApiFallback: true,
            noInfo: false,
            stats: 'minimal',
            compress: true,
            contentBase: path.resolve(__dirname, '../dist'),
            // match the output path
            publicPath: '/',
            // match the output 'publicPath'
            // 开发过程中可设置代理
            proxy: {
               '/api/v1': {
                    target: 'http://10.246.75.12:8080',
                    pathRewrite: {"^/api/v1" : ""},
                    secure: false,
                    changeOrigin: true
               }
            }
        }
    };
};

webpack.prod.config.js

const webpack = require('webpack');
const path = require('path');
const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const precss = require('precss');
const autoprefixer = require('autoprefixer');

module.exports = function(env) {

    var data = {

        resolve: {
            extensions: [".js", ".json", ".jsx", ".css", '.less', '.sass', '.scss']
        },

        entry: {
            main: path.resolve(__dirname, '../src/index.js'),
            vendors: [
                "react",
                'react-dom',
                'react-router',
                'react-router-dom',
                'react-router-redux',
                'redux',
                'react-redux',
                'redux-thunk',
                'immutable',
                'recharts'
            ]
        },

        module: {
            noParse: /node_modules\/(jquey|moment|chart\.js)/,
            rules: [{
                test: /\.css$/,
                use: ExtractTextWebpackPlugin.extract({
                    fallback: 'style-loader',
                    use: ['css-loader', 'postcss-loader']
                })
            }, {
                test: /\.s[a|c]ss$/,
                use: ExtractTextWebpackPlugin.extract({
                    fallback: 'style-loader',
                    use: ['css-loader', 'postcss-loader', 'sass-loader']
                })
            }, {
                test: /\.less$/,
                use: ExtractTextWebpackPlugin.extract({
                    fallback: 'style-loader',
                    use: ['css-loader', 'postcss-loader', 'less-loader']
                })
            }, {
                test: /\.jsx?$/,
                exclude: /(node_modules)/,
                use: [{
                    loader: 'babel-loader',
                    options: {
                        presets: [
                            ["es2015", {"modules": false}],
                            "stage-2",
                            "react"
                        ],
                        plugins: [
                            "transform-runtime",
                            [
                                "import", {
                                    "libraryName": "antd",
                                    "style": 'css',
                                }
                            ]
                        ]
                    }
                }]
            }, {
                test: /\.(woff|woff2|eot|ttf|svg)$/,
                use: {
                    loader: 'url-loader',
                    options: {
                        limit: 10000,
                        name: '[name]-[hash:8].[ext]',
                        outputPath: 'fonts/',
                        publicPath: '../fonts/'
                    }
                }
            }, {
                test: /\.(png|jpg)$/,
                use: {
                    loader: 'url-loader',
                    options: {
                        limit: 8192,
                        name: 'img/[name]-[hash:8].[ext]'
                    }
                }
            }]
        },

        output: {
            path: path.resolve(__dirname, '../dist'),
            filename: 'js/[name].[chunkhash:8].js'
        },

        devtool: 'cheap-module-source-map',

        plugins: [

            new webpack.LoaderOptionsPlugin({
                options: {
                    postcss: function() {
                        return [precss, autoprefixer];
                    }
                }
            }),
            new ExtractTextWebpackPlugin('css/bundle.css'),
            new webpack.optimize.CommonsChunkPlugin({
                names: ['vendors', 'manifest'] // Specify the common bundle's name.
            }),
            new webpack.optimize.UglifyJsPlugin({
                output: {
                    comments: false  // remove all comments
                },
                compress: {
                    warnings: false
                }
            }),
            new webpack.DefinePlugin({
                'process.env': {
                    NODE_ENV: JSON.stringify('production')
                }
            }),
            new HtmlWebpackPlugin({
                title: "webpack",
                filename: "../dist/index.html",
                template: path.resolve(__dirname, '../src/index.html'),
                favicon: path.resolve(__dirname, '../src/favicon.ico'),
                inject: 'body',
                minify: {
                    "removeAttributeQuotes": true,
                    "removeComments": true,
                    "removeEmptyAttributes": true
                }
            })

        ]

    };


    return data;
};


处理实时搜索 异步数据问题

最近在写项目的过程中,遇到一个问题。就是实时搜索,但是异步数据返回的时间不一致,导致,搜索的结果和文本其实并不能完全匹配

解法一

在请求成功后,判断参数是否和搜索条件一致,如果一致,才 setState。
这种解法

解法二

使用请求库的 cancel 方法

axios cancel: https://github.com/axios/axios#cancellation

umi-request cancel: https://github.com/umijs/umi-request#cancel-request

很明显,2 的解法要比 1 的解法高级很多

markdown 语法

今天写博客时,发现用Hexo + github page搭建的博客要用Markdown语法来更新文章,于是在网上查了其用法,放到自己的博客里,以便随时查询。

What is Markdown?

Markdown 是一种轻量级的「标记语言」,它的优点很多,目前也被越来越多的写作爱好者,撰稿者广泛使用。看到这里请不要被「标记」、「语言」所迷惑,Markdown 的语法十分简单。常用的标记符号也不超过十个,这种相对于更为复杂的 HTML 标记语言来说,Markdown 可谓是十分轻量的,学习成本也不需要太多,且一旦熟悉这种语法规则,会有一劳永逸的效果。

代码高亮

如果你只想高亮语句中的某个函数名或关键字,可以使用 function_name() 实现
通常编辑器根据代码片段适配合适的高亮方法,但你也可以用 ``` 包裹一段代码,并指定语言

```javascript
$(document).ready(function () {
    alert('hello world');
});
```

支持的语言:actionscript, apache, bash, clojure, cmake, coffeescript, cpp, cs, css, d, delphi, django, erlang, go, haskell, html, http, ini, java, javascript, json, lisp, lua, markdown, matlab, nginx, objectivec, perl, php, python, r, ruby, scala, smalltalk, sql, tex, vbscript, xml
也可以使用 4 空格缩进,再贴上代码,实现相同的的效果

    def g(x):
        yield from range(x, 0, -1)
    yield from range(x)

标题

文章内容较多时,可以用标题分段:

最高阶标题(h1)
======

第二阶标题(h2)
-----
# 一级标题(h1)
## 二级标题(h2)
###三级标题(h3)

粗斜体

*斜体文本*    _斜体文本_
**粗体文本**    __粗体文本__
***粗斜体文本***    ___粗斜体文本___

居中

使用一对中括号“[文字]”来居中一段文字

[### 居中的标题]

链接

常用链接方法

文字链接 [链接名称](http://链接网址)
网址链接 <http://链接网址>

高级链接技巧

这个链接用 1 作为网址变量 [Google][1].
这个链接用 yahoo 作为网址变量 [Yahoo!][yahoo].
然后在文档的结尾为变量赋值(网址)
    [1]: http://www.google.com/
    [yahoo]: http://www.yahoo.com/

列表

普通无序列表

- 列表文本前使用 [减号+空格]
+ 列表文本前使用 [加号+空格]
* 列表文本前使用 [星号+空格]

普通有序列表

1. 列表前使用 [数字+空格]
2. 我们会自动帮你添加数字
7. 不用担心数字不对,显示的时候我们会自动把这行的 7 纠正为 3

列表嵌套

1. 列出所有元素:
    - 无序列表元素 A
        1. 元素 A 的有序子列表
    - 前面加四个空格
2. 列表里的多段换行:
    前面必须加四个空格,
    这样换行,整体的格式不会乱
3. 列表里引用:

    > 前面空一行
    > 仍然需要在 >  前面加四个空格

4. 列表里代码段:

    ```
    前面四个空格,之后按代码语法 ``` 书写
    ```

        或者直接空八个,引入代码块

引用

普通引用

> 引用文本前使用 [大于号+空格]
> 折行可以不加,新起一行都要加上哦

引用里嵌套引用

> 最外层引用
> > 多一个 > 嵌套一层引用
> > > 可以嵌套很多层

引用里嵌套列表

> - 这是引用里嵌套的一个列表
> - 还可以有子列表
>     * 子列表需要从 - 之后延后四个空格开始

引用里嵌套代码块

>     同样的,在前面加四个空格形成代码块
>  
> ```
> 或者使用 ``` 形成代码块
> ```

图片

跟链接的方法区别在于前面加了个感叹号 !,这样是不是觉得好记多了呢?

![图片名称](http://图片网址)

当然,你也可以像网址那样对图片网址使用变量

这个链接用 1 作为网址变量 [Google][1].
然后在文档的结尾位变量赋值(网址)

 [1]: http://www.google.com/logo.png

也可以使用 HTML 的图片语法来自定义图片的宽高大小

<img src="htt://example.com/sample.png" width="400" height="100">

换行

如果另起一行,只需在当前行结尾加 2 个空格

在当前行的结尾加 2 个空格  
这行就会新起一行

如果是要起一个新段落,只需要空出一行即可。

分隔符

如果你有写分割线的习惯,可以新起一行输入三个星号*。

前面的段落

***

后面的段落

空格

    半方大的空白&ensp;或&#8194;
    全方大的空白&emsp;或&#8195;
    不断行的空白格&nbsp;或&#160;

在线编辑

https://codepen.io/NsNe/pen/OWaPRp

拼多多技术面试算法题

面试算法题

一面算法

经常会遇到后端传给我的是一个拍平的树结构,将这样的结构,转为树结构,可以用于类似cascader
例:

输入:
const data = [
  {
    parent: 3,
    id: 4,
    value: 4,
  },
  {
    parent: null,
    id: 1,
    value: 1,
  },
  {
    parent: 1,
    id: 2,
    value: 2,
  },
  {
    parent: 1,
    id: 3,
    value: 3,
  }
];

输出:
[
  {
    id: 1,
    value: 1,
    children: [
      {
        id: 2,
        value: 2,
      },
      {
        id: 3, 
        value:3,
        children: [
          {
            id: 4,
            value: 4,
          }
        ]
      }
    ]
  }
];

实现思路:

  • 先找到根节点
  • 再从根节点递归找其孩子

二面算法

在一个一维坐标,给出一个目标线段,例如(3, 8)。一组源线段,例如(1, 2),(3, 4), (5, 8), (3, 6)。判断源线段组成的合集是否能完全覆盖目标线段,返回 true 或 false

输入:
const data = [3, 8];
const source = [[1, 2], [3, 4], [5, 8], [3, 6]];
输出:
true

实现思路: 主要是如何将源线段合并

  • 先将源线段根据起始点排序
  • 再去遍历源线段,每次通过下一个线段的起点与当前终点比较,判断是否又交集,如果有,则合并,如果没有,说明是分开的线段
  • 遍历合并后的源线段,判断是否完全包含目标线段

三面算法

合并两个有序数组,并逆序。
作答: 参考leet code 88

总结

一面算法题,和三面算法题很快就写出来了,二面的算法题,也是让面试官给了些提示才堪堪写出来。
在面试过程中,遇到算法题,不要慌张。下面几步帮你搞定算法:

  1. 首先搞清楚题目的含义,确定输入输出。和面试官确认题目理解没有错
  2. 整理自己的思路,向面试官讲解一下,思路大体没有问题,再开始编码
  3. 即使没有思路,也不要心慌,或者就不做了,可以笑着让面试官给点提示
  4. 按照提示思考,再重复步骤2
  5. 编码与运行,总结分析时间复杂度与空间复杂度

Redux源码解析

Redux API 和 Redux 源码结构

Redux API

export {
    createStore,             //创建一个state用来存储状态树
    combineReducers,   //合并reducer
    bindActionCreators,  //将dispatch和action结合
    applyMiddleware,     //调度中间件来增强store,例如中间件redux-thunk等
    compose              //从右向左组合多个函数, compose(f, g, h)会返回(...args) => f(g(h(...args)))
}

源码结构

Redux源码结构和提供的API大体对应,如下:
utils——warning.js //console中打印warning信息要用到的
applyMiddleware.js
bindActionCreators.js
combineReducers.js
compose.js
createStore.js
index.js //export 上述定义的module

createStore

上面我们看到了redux的API和源码结构,看的出来,warning.js和index.js不用解析,都看得懂,关键时其余的几个module,那我们从最重要的createStore讲起。

    export var ActionTypes = {
        INIT: '@@redux/INIT'
    }
   //首先定义了一个action类型,我们知道更新state的唯一方法就是dispatch一个action,这个action是用
   // 来初始化state的,后面会用到它

现在来看下createStore的大体结构

//接收三个参数
//reducer为function。当dispatch一个action时,此函数接收action来更新state
//preloadState初始化State
//enhancer 为function。用来增强store, Redux 定义有applyMiddleware来增强store,后面会
//单独讲applyMiddleware

export default function createStore(reducer, preloadedState, enhancer) {
    if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
        //如果只传了两个参数,并且第二个参数为函数,第二个参数会被当作enhancer
        enhancer = preloadedState
        preloadedState = undefined
    }
  
    if (typeof enhancer !== 'undefined') {
        if (typeof enhancer !== 'function') {
            //校验enhancer是否为函数,如果不是函数则抛出异常
            throw new Error('Expected the enhancer to be a function.')
        }
        //如果enhancer存在且为函数,那么则返回如下调用,如果enhancer为applyMiddleware,那么调用则
        //是applyMiddleware(createStore)(reducer, preloadedState)。后面讲applyMiddleware再详细讲。
        return enhancer(createStore)(reducer, preloadedState)
    }

    if (typeof reducer !== 'function') {
        //校验reducer是否为函数
        throw new Error('Expected the reducer to be a function.')
    }

    var currentReducer = reducer
    //得到reducer

    var currentState = preloadedState
    //得到初始init,没有传递则为undefined

    var currentListeners = []
    //定义一个数组用来存放listeners。就是一个函数数组,当state发生改变时,会循环执行这个数组里面的函数

    var nextListeners = currentListeners
    //用来存储下一次的listeners数组。为什么要有这个listeners数组呢?因为当state发生改变时,我们根据
    //上面的currentListeners来循环执行函数,但是在这执行这些函数时,函数内部可能取消或者添加订阅
    //(state改变时,添加或者取消执行函数),这时如果直接操作currentListeners ,相当于在循环
    //内部修改循环条件,执行瞬间就乱套了,有没有啊,有没有

    var isDispatching = false
    //reducer函数是否正在执行的标识

    function ensureCanMutateNextListeners() {
        //拷贝currentListeners一份为nextListeners,这样nextListeners的改变不会引起currentListeners的改变
        //(上面解释过原因啦)
    }

    function dispatch() {
        //触发action去执行reducer,更新state
        .....
    }

    function subscribe() {
        //接收一个函数参数,订阅state的改变。当state改变时会执行这个函数
        ....
    }

    function getState() {
        //获取state树
        ....
    }

    function replaceReducer() {
        //替换reducer
        ....
    }

    function observable() {
        //没用,不解释(后面有解释)
        ......
    }


    dispatch({ type: ActionTypes.INIT })
    //执行dispatch函数,初始化state

    return {
        //真正的返回,执行createStore其实返回的就是这些东东
        dispatch,       //触发action去执行reducer,更新state
        subscribe,     //订阅state改变,state改变时会执行subscribe的参数(自己定义的一个函数)
        getState,      //获取state树
        replaceReducer,       //替换reducer
        [$$observable]: observable         
        //redux内部用的,对我们来说没用(非要深究它写这是干嘛的?咋跟
        //我一样死脑筋呢,都说了没用啦。算了,还是告诉你把,就是内部用的,在测试代码中会用到,感兴
        //趣的可以去test目录下查看)
    }
}

现在是不是感觉明朗(懵逼)了许多,懵逼就对了,接下来我们再来解析一下dispatch, subscribe等函数的具体实现,或许会让你明朗(更懵逼)起来

看了上面的大体结构,我们明白以下这些就够了。
createStore是一个函数,它定义了一些变量(currentState, currentListeners等)及函数(dispatch, subscribe等),并且调用了dispatch,最后返回一个对象,该对象包含的就是dispatch和subscribe等函数。接下来我们来解析这些函数。
createStore里面只调用了一个函数,那就是dispatch,那我们就从这里开始讲起。

dispatch

它是这样被调用的,有没有很熟悉。。。

dispatch({ type: ActionTypes.INIT })

来看看dispatch的源码

  function dispatch(action) {
    //校验action参数,必须为一个纯粹的对象。这也说明了,我们不能直接在redux做异步请求,而是需要
    //使用applyMiddleware去应用一些中间件。比如redux-thunk等
    if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects. ' +
        'Use custom middleware for async actions.'
      )
    }
    //同样是校验参数,不解释,相信都能看懂
    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
        'Have you misspelled a constant?'
      )
    }
    //判断是否正在执行reducers函数,如果正在执行,此action不会触发,那有人就问题了,那是不是我
    //的dispatch不会起作用,导致state没有更新,数据是错误的? 答案是state是不会有错。因为redux本
    //身整个更新state的过程是同步的,从dipatch——>reducers——>state。所以这段代码意在你定义的
    //reducers函数中不允许调用dispatch
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      //设置标识为,并且执行currentReducer,还记得吗?这个我们通过参数获取到的一个函数,往往是我
      //们调用combineReducers返回,combineReducers我们后面解析
      isDispatching = true
      //调用reducers函数
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }
    //调用所有订阅state改变的函数,这些函数就可以通过getState函数获取到最新的state值。订阅的函数
    //从哪里来呢,从subscribe中来, 我们后面来解析subscribe和getState
    var listeners = currentListeners = nextListeners
    for (var i = 0; i < listeners.length; i++) {
      listeners[i]()
    }

    return action
  }

实现一个如 f1(f2(f3(f4(x)))) 的 compose

假设有函数

[f1, f2, f3, f4]

f1(f2(f3(f4(x))))

function compose(...funcs) {
  if(funcs.length === 0) {
    return args => args;
  }
  return funcs.reduce((acc, current) => {
    return (...args) => acc(current(...args));
  });
}

f4(f3(f2(f1(x))))

function compose(...funcs) {
  if(funcs.length === 0) {
    return args => args;
  }
  return funcs.reduce((acc, current) => {
    return (...args) => current(acc(...args));
  });
}

经典题型测试

function add(a, b = 1) {
  return a + b;  
}
 
function square(a) {
  return a*a;
}
 
function plusOne(a) {
  return a + 1;
}
 
function compose(...funcs) {
  if(funcs.length === 0) {
    return args => args;
  }
  return funcs.reduce((acc, current) => {
    return (...args) => acc(current(...args));
  });
}
 
var addSquareAndPlusOne = compose(add, square, plusOne);
 
addSquareAndPlusOne(1, 2);

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.