GithubHelp home page GithubHelp logo

ly525 / blog Goto Github PK

View Code? Open in Web Editor NEW
46.0 46.0 4.0 209 KB

My personal blog / 个人博客(小小鲁班的技术口袋)

Home Page: https://github.com/ly525/blog/issues

License: MIT License

Shell 53.94% JavaScript 46.06%

blog's Introduction

Hi there 👋

  • 🔭 I'm currently working on the crm web and low code in YMM.
  • 🌱 I’m currently learning Node/Python/UE(user experience).

Ly525's GitHub stats

You can also read my articles in blog

blog's People

Contributors

ly525 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

Watchers

 avatar  avatar  avatar  avatar

blog's Issues

17. nginx 转发请求, django如何获得真正的用户IP?

Question

nginx 转发请求, django 如何获得真正的用户 IP?

  1. nginx 配置文件如下, 这样的话, 由于后端代码得到的请求都是本地 nginx 转发的, 因此其获取的 IP 地址也就是127.0.0.1, 是 nginx 所在的 IP 地址
  2. 现在要获得用户请求时候的原始 IP 地址(外网地址), 比如 a.b.c.d
server {
	listen 80;
	server_name www.test.com;
	default_type text/html;
	location / {
	      proxy_pass http://127.0.0.1:8000;
	}
	location ~ ^/static/ {
	    root /opt/test-project;
	}
}
  1. 场景, 后端需要对 IP 地址进行过滤, 但是前端请求都是通过 nginx 转发到本地的后端服务端口,比如 80-> 8000.
  2. 如果通过 django 的 get_ip(request)模块, 得到的 IP 只是 127.0.0.1, 得不到真正的用户 IP 地址

Answer

真正的 IP 地址 nginx 可以取得, 然后转发给后端

方法: nginx 转发真正的 IP 地址给后端, 添加

location / {
	proxy_set_header X-Real-IP $remote_addr; #加上这句话就可以了
	proxy_pass http://127.0.0.1:8000;
}

1. HTTP Provisional headers are shown

如图所示, 大大的感叹号内的Provisional headers are shown

Provisional headers are shown

  1. 意思是请求没有发出去
  2. Provisional headers are shown-> 显示的是临时请求头
  3. 我这边出现的问题是请求内网数据, 但是同时挂了VPN导致的, 将VPN关了之后就好了.另外开Lantern倒是没有影响

[前端部署篇-第1期] Nginx Location 与前端路由优先级,以及刷新404 原由

问答

  1. :前端路由和 nginx location 哪个优先级更高?
  2. :当然是 nginx location 优先级更高

因为 nginx location 优先级更高,所以会导致 history 模式的SPA(单页应用)在刷新的时候,会有 404 的问题

场景描述

  1. 访问www.abc.com/dashboard,点击logout退出系统,前端路由处理,跳转到www.abc.com/login
  2. 但是登录页面刷新之后,就显示 nginx 404
  3. 奇怪的地方就在于,为何退出的时候,重定向到/login的时候,没有报404, 为何一刷新就 404 了呢?
  4. 前端路由重定向到/login逻辑:this.$router.push({name: 'login'});
  5. 贴下前端路由配置
 routes: [
 {
      name: 'home',
      path: '/',
      component: Home,
      meta: { requiresAuth: true },
  },
  {
      path: '/login',
      name: 'login',
      component: Login,
  },
  {
      path: '*',
      component: NotFound,
  }
  ]
  1. 现有nginx配置

    location / {
        root /var/data/static;
    }

解决方案

1. 为何点击退出可以正常显示登录页面?
  1. 因为点击退出,前端路由this.$router.push({name: 'login'});来实现跳转的,这时候已经有index.html 和相关的js了(也就是说这时候页面跳转的逻辑已经由 Vue-Router 或 React Router 接管了),可以直接使用前端路由跳转到/login路由对应的组件
2. 为何刷新的时候显示: `nginx/404` ?
  1. 因为刷新的时候,会先向服务器请求www.abc.com/login
    > 2. 这时候服务器的nginx配置中没有关于/login路径的配置(即没有 location /login {}),因此 nginx 会直接报错nginx/404
3. 如何解决? 
按照如下方式配置nginx
 location / {
     root /var/data/static;
     # 意思是说,在nginx 没有找到 /login 路径的时候,不要报错,要把 index.html 返回给客户端
     # 这样客户端(浏览器)就能加载到路由相关的js 文件了
     # 这时候,路由由前端接管,前端路由会查看路由表里面是否由 /login 的配置,有的话,跳转即可
     try_file /index.html;
 }
4. 更多解释
  1. nginx进行匹配路径的时候,发现没有`/login`的路径,便会转到`/`路径处理。
  2. 发现`root`路径下(是静态文件的root目录,不是linux的root目录)没有`login`文件,就会`try_file`规则,返回index.html, 
  3. 这样浏览器拿到index.html 之后,开始加载其中的前端路由部分
  4. 这时候/login就会在前端路由中找到匹配规则。

5. 同理`/404`也可以写在前端路由中了

38. JS sdk 涉及点

  1. 通过JS动态引入的方式加载的CSS文件总是异步的,所以不会阻塞其他文件的风险
  2. 重复加载问题: 提供给publisher的代码,不必再引入多个脚本引入多个脚本来渲染组件。但是很多时候大家都用复制粘贴来解决问题时,publisher还是会这么干,因此脚本有可能会重复执行。如果不希望二次执行,可以在脚本执行时创建一个标识符,比如window[xxxx] = 1从而让代码在二次执行的时候提前退出。判断页面上广告位数量>0的时候才去加载数据。
  3. 使用throttle 解决滚动时候页面抖动问题
  4. 判断元素是否出现在滚动区域
  5. 如果元素出现在滚动区域,判断图片是否有lazy_src属性,使用图片懒加载。

正则系列

  1. trim => return str.replace(/^[\s\xa0]+|[\s\xa0]+$/g, "")

14. JavaScript获得network面板的所有请求域名 / use JavaScript to get all hosts of requests in chrome network panel

  1. 到network面板, copy all as har

image

  1. 在console面板中, 将copy的内容赋值给networkRequests变量
  2. 执行下面的代码
function UrlRegEx(url) {
    //如果加上/g参数,那么只返回$0匹配。也就是说arr.length = 0
    var re = /(\w+):\/\/([^\:|\/]+)(\:\d*)?(.*\/)([^#|\?|\n]+)?(#.*)?(\?.*)?/i;
    //re.exec(url);
    var arr = url.match(re);
    return arr;

}

function getHost(url) {
    return UrlRegEx(url)[2];
}
var requests = networkRequest.log.entries
var all_urls = []
requests.forEach(item => {
    all_urls.push(getHost(item.request.url))
})

var finalUrls = {}
all_urls.forEach(item=>{
    finalUrls[item] = ''
})
JSON.stringify(Object.keys(finalUrls))

39. 题目

CSS

  1. 左侧固定,右侧自适应(侧边栏导航)
  2. 实现三角形(tooltip,dropdown)、叉号
  3. 动画渐变(height 属性是否可以有效,实现loading效果)
  4. data-set 应用(HTML5 新特性)
  5. less 常用函数,mixin等等(简历有提)
  6. Bootstrap中为何使用了 row: margin:-15px,作用是什么?
  7. css sprit 和 style loader limit 作用?(优化)
  8. 清除浮动,以及为何要清除浮动,浮动会带来哪些影响?
  9. flexbox 布局
  10. display:none与visibility:hidden的区别?以及display height 能否取到,应用到动画中
  • display:none 不显示对应的元素,在文档布局中不再分配空间(回流+重绘)
  • visibility:hidden 隐藏对应元素,在文档布局中仍保留原来的空间(重绘)
  1. box-sizing 应用场景(项目)
  2. 清除图片底部一像素(广告位代码中常见)
  3. 元素的height 百分比(基础)
  4. 横向菜单导航条li之间有空白怎么解决?font size 0。(布局基础)
  5. display:inline-block 什么时候会显示间隙?(导航条设计)
  • 有空格时候会有间隙 解决:移除空格
  • margin正值的时候 解决:margin使用负值
    • 使用font-size时候 解决:font-size:0、letter-spacing、word-spacing
  1. css 选择器,以及匹配顺序(基础)
  2. 移动端布局,rem 计算以及 em(微信、H5 等移动端布局)
  3. 滚动条导致的页面闪动如何解决?
    -calc
  4. 50 道 CSS 基础面试题及答案 http://mp.weixin.qq.com/s/XPYy8gxKkzwkqGBxh91JNQ

JavaScript

  1. 函数节流/Debounce 函数,懒加载函数(交互、性能)
  2. deepclone
  3. splice 与 slice 区别?是否改变原来的数组呢?
  4. 1>2>3 的结果?(考察基础运算符)
  5. ![] 对数组的理解,如何判断一个值是数组?
  6. setTimeout(fn, 0)
  7. 增删className
  8. 0.1 + 0.2 === 0.3,保留两位小数的实现?
  9. 给li 元素动态添加点击时间,并要以后动态生成的li元素同样具有该事件
  10. 举一个实际工作中使用到闭包的例子
    Let self = this
  11. 遍历节点数组
  12. 浏览器渲染过程,? 如果一个css文件 遇到JS文件 底下又一个css文件,这块浏览器的处理 是怎样的? 还是原理问题
  13. https://sourcegraph.com/github.com/front-thinking/FE-Summary@83e33d8d3b0edbae748788ae89ea5aa05dfd5ab8/-/blob/3.javascript.md

Vue

  1. 在vue.js 如何使输入框获得焦点(nextTick 使用)
  2. 数组如何侦测变化(后台操作较多,需要实际开发经验)
  3. Vue 双向绑定原理,如何实现
  4. props、data、computed、watcher 的初始化顺序(源码)
  5. keep-alive (交互体验)
  6. 在页面中直接引入js 和vue 文件有区别嘛?或者说 vue-loader 做了什么事?或者说 .vue文件最终被转换成了什么?
  7. Scoped
  8. watcher immediate
  9. Created 与 mounted 区别,什么时间操作dom
  10. Render 函数。render: (h) => h(App) 做了什么?

前后端

  1. cookie session
  2. 错误处理、http status code
  3. 前后端分离
    • 配合 webpack 进行反向代理
    • js 操作 cookie

工程化

  1. babel 配置的stage1,stage0 有何区别?(考察es6)
  2. let 和 var 的区别,变量提升。var 为何可以重复定义,而let 却不可以,考察作用域与生命周期
  3. exports、module.exports 与 export default 区别?
  4. 前端部署
    • 是否开启gzip 压缩
  5. 项目中你碰到过的最难的问题,以及怎么解决的?
  6. 前后端分离的原理及意义?
  7. 常见页面优化
  8. node 作为服务器,有考虑 ssr吗?
  9. 一个页面从输入 URL 到页面加载完的过程中都发生了什么事情
  10. eslint

代码段

 for (let i = 0; i < 100000; i ++) {
        let $body = $('body');
        $body.text();
    }
  1. box-size 是什么和应用场景?

  2. 输入123456789,输出123,456,789,其实就是千分位

    function getThousands(number) {
     return String(number).replace(/(^|\s)\d+/g, m =>
         m.replace(/(?=(?!\b)(\d{3})+$)/g, ","))
     }
    

40. 一片空白

  1. charset的标签写成html5的这种比较简洁的写法就行了,不需要写成html4这种长长的:<meta http-equiv="Content-Type" content="text/html; charset=utf-8" 一般charset是怎么写的<meta charset="UTF-8">
  2. http-equiv 是什么意思?
  3. < © 这种被称为unicode特殊符号,需使用对应的实体编码

  4. 里面的rel什么意思?浏览器如何理解这个rel

34. download csv 方案: data schema VS Blob

  1. Stack Overflow max-data-uri-size-in-javascript
  2. data_URIs与base64???
  3. 利用Blob和data schema 前端下载csv, 来自iview
 _getDownloadUrl (text) {
        const BOM = '\uFEFF';
        // Add BOM to text for open in excel correctly
        if (window.Blob && window.URL && window.URL.createObjectURL && !has('Safari')) {
            const csvData = new Blob([BOM + text], { type: 'text/csv' });
            return URL.createObjectURL(csvData);
        } else {
            return 'data:attachment/csv;charset=utf-8,' + BOM + encodeURIComponent(text);
        }
    },

    download (filename, text) {
        if (has('ie') && has('ie') < 10) {
            // has module unable identify ie11 and Edge
            const oWin = window.top.open('about:blank', '_blank');
            oWin.document.charset = 'utf-8';
            oWin.document.write(text);
            oWin.document.close();
            oWin.document.execCommand('SaveAs', filename);
            oWin.close();
        } else if (has('ie') === 10 || this._isIE11() || this._isEdge()) {
            const BOM = '\uFEFF';
            const csvData = new Blob([BOM + text], { type: 'text/csv' });
            navigator.msSaveBlob(csvData, filename);
        } else {
            const link = document.createElement('a');
            link.download = filename;
            link.href = this._getDownloadUrl(text);
            link.target = '_blank';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        }
    }

30. CSS padding overrides overflow?

http://stackoverflow.com/questions/19051411/css-padding-overrides-overflow/19051546#19051546

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        body {
            border: 1px solid green;
        }
        .placeholder {
            width: 50%;
            padding-bottom: 50%;
            background: rebeccapurple;
            height: 0;
            overflow: hidden;
        }
    </style>
</head>
<body>
    <div class="placeholder">
        text
    </div>
</body>
</html>

2. Vue.js 生命周期和vue-router的切换钩子函数

关键字: created(), vue-router , transition, route

  1. Bug描述

    1. 多个路由使用了相同组件, 也就是页面的结构相同, 但是根据路由的不同,加载相同结构的不同数据(也就是api相同,但是参数根据路由进行变化)
    2. 比如/v1/videos?country=china和/v1/videos?country=us 这里不同的api相同, 但是参数不同
    3. 问题: 切换不同的路由的时候, 发现只有路由没有点击过才会加载数据, 但是点击了被点击过的路由便不会加载数据
    4. 图中的收件箱的新邮件已发邮件可以用相同组件, 简单用这个举个栗子吧
      image
  2. 修复

    1. Vue.js组件的生命周期中, created()在组件被创建之后, 如果判断组件可以重用, 便不会再执行
    2. 如果切换不同的路由, 需要加载不同的数据, 可以把数据加载的操作放在 route 的data()中执行

32. http状态码 应用场景

Question

  1. 302 和 301在实际项目应用的区别?
  2. 链接的重定向与302, 301关系?
  3. 502 bad gateway
  4. 400 bad request
  5. 403 forbidden

Answer

  1. 502 bad gateway

    nginx开启了,但是反向代理的后端服务没有开启

  2. 400 bad request

    1. Django settings allow hosts 没有允许该IP的访问
    2. 常见于本地调试使用localhost:8000 访问后端服务器,但是后端服务允许的ip是127.0.0.1。这样后端接收到前端请求头中的host是localhost,返回400。这时候就需要将localhost:8000 换成127.0.0.1
  3. 302

    1. Django中的login_required中是使用了302跳转来完成重定向操作

    2. login_required('/login') => user_passes_test => redirect_to_login => HttpResponseRedirect

    3. 之前疑惑为什么后端可以使得前端页面跳转,后来明白了。浏览器在接收到302的status_code 之后,会自动跳转到新的url

      # https://github.com/daoluan/decode-Django/blob/master/Django-1.5.1/django/http/response.py#L426
      class HttpResponseRedirectBase(HttpResponse):
          allowed_schemes = ['http', 'https', 'ftp']
      
          def __init__(self, redirect_to, *args, **kwargs):
              parsed = urlparse(redirect_to)
              if parsed.scheme and parsed.scheme not in self.allowed_schemes:
                  raise SuspiciousOperation("Unsafe redirect to URL with protocol '%s'" % parsed.scheme)
              super(HttpResponseRedirectBase, self).__init__(*args, **kwargs)
              # 关键部分,可以用来做链接重定向的检测
              self['Location'] = iri_to_uri(redirect_to)
      
      
      class HttpResponseRedirect(HttpResponseRedirectBase):
          status_code = 302
  4. 403

    1. 很常见的场景是NGINX没有权限访问文件/文件夹

45. mac 找出占用端口号的进程,并kill

Find Out Which Process Is Listening Upon a Port
https://stackoverflow.com/questions/3855127/find-and-kill-process-locking-port-3000-on-mac

netstat -vanp tcp | grep 8081
tcp6       0      0  ::1.8081               ::1.54270              ESTABLISHED 407251 146808  88305      0
tcp6       0      0  ::1.54270              ::1.8081               ESTABLISHED 358716 146808  57219      0
tcp46      0      0  *.8081                 *.*                    LISTEN      131072 131072  88305      0
tcp6       0      0  ::1.8081               ::1.54294              TIME_WAIT   407182 146808  88305      0
tcp6       0      0  ::1.8081               ::1.54265              TIME_WAIT   405456 458104  88305      0
tcp6       0      0  ::1.8081               ::1.54292              TIME_WAIT   407150 146808  88305      0
tcp6       0      0  ::1.8081               ::1.54293              TIME_WAIT   407197 146808  88305      0
kill -9 88305
所以,你可以把这个命令封装一下
alias portgrep='netstat -vanp tcp | grep'
以后这样用就OK了:
portgrep 8080

41. [译] The Node.js Event Loop, Timers, and process.nextTick()

The Node.js Event Loop, Timers, and process.nextTick()

Node.js 事件循环, 定时器 和 process.nextTick()

What is the Event Loop?

事件循环是什么?

The event loop is what allows Node.js to perform non-blocking I/O operations
事件循环是允许Node.js 执行非阻塞I/O的关键

— despite the fact that JavaScript is single-threaded
尽管事实上JavaScript是单线程的

— by offloading operations to the system kernel whenever possible.
通过尽可能地卸载操作到操作系统内核

Since most modern kernels are multi-threaded, they can handle multiple operations executing in the background.
因为多数现代内核是多线程的, 它们可以在后台同时处理多个操作

When one of these operations completes, the kernel tells Node.js so that the appropriate callback
may be added to the poll queue to eventually be executed.
当其中一个操作完成时, 内核会通知Node.js, 这样, 合适的回调函数会被添加到poll队列中来保证最终执行

We'll explain this in further detail later in this topic.
接下来我们更详细解释这一点

Event Loop Explained

解释事件循环

When Node.js starts, it initializes the event loop, processes the provided input script (or drops into
the REPL, which is not covered in this document),
当Node.js启动时,它初始化事件循环,processes 处理入口脚本(或者REPL的输入)这个在此没有提及

which may make async API calls, schedule timers, or call process.nextTick(), then begins processing the event loop.
这个脚本可能会调用异步API,设置定时任务 或者 调用process.nextTick() 然后开始处理事件循环

The following diagram shows a simplified overview of the event loop's
order of operations.
下面的流程图展示了简化版的事件循环操作

   ┌───────────────────────┐
┌─>│        timers         │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     I/O callbacks     │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     idle(休眠), prepare(准备)     │
│  └──────────┬────────────┘      ┌───────────────┐
│  ┌──────────┴────────────┐      │   incoming:   │
│  │         poll          │<─────┤  connections, │
│  └──────────┬────────────┘      │   data, etc.  │
│  ┌──────────┴────────────┐      └───────────────┘
│  │        check          │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
└──┤    close callbacks    │
   └───────────────────────┘

note: each box will be referred to as a "phase" of the event loop.
*注: 每一个盒子都是事件循环的一个阶段

Each phase has a FIFO queue of callbacks to execute.
每一个阶段都会执行一个先进先出的回调函数队列(FIFO: first in, first out)

While each phase is special in its own way,
尽管每一个阶段的方式都很特殊,

generally, when the event loop enters a given phase, it will perform any operations specific to that phase,
通常,当事件循环进入一个阶段的之后,它会执行该阶段的任何操作

then execute callbacks in that phase's queue until the queue has been exhausted or the maximum number of callbacks has executed.
然后执行这个阶段的回调函数, 直到这回调函数队列为空 或者 已经执行了回调函数的最大次数

When the queue has been exhausted or the callback limit is reached, the event loop will move to the next phase, and so on.
当队列里面没有回调函数,或者到达了callbakcks的最大的执行次数,事件队列将会进入下一个阶段,以此类推

Since any of these operations may schedule more operations and new
events processed in the poll phase are queued by the kernel, poll
events can be queued while polling events are being processed. As a
result, long running callbacks can allow the poll phase to run much
longer than a timer's threshold. See the timers and
poll sections for more details.

NOTE: There is a slight discrepancy between the Windows and the
Unix/Linux implementation, but that's not important for this
demonstration. The most important parts are here. There are actually
seven or eight steps, but the ones we care about — ones that Node.js
actually uses - are those above.

Phases Overview

  • timers: this phase executes callbacks scheduled by setTimeout()
    and setInterval().
  • I/O callbacks: executes almost all callbacks with the exception of
    close callbacks, the ones scheduled by timers, and setImmediate().
  • idle, prepare: only used internally.
  • poll: retrieve new I/O events; node will block here when appropriate.
  • check: setImmediate() callbacks are invoked here.
  • close callbacks: e.g. socket.on('close', ...).

Between each run of the event loop, Node.js checks if it is waiting for
any asynchronous I/O or timers and shuts down cleanly if there are not
any.

Phases in Detail

timers

A timer specifies the threshold after which a provided callback
may be executed rather than the exact time a person wants it to
be executed
. Timers callbacks will run as early as they can be
scheduled after the specified amount of time has passed; however,
Operating System scheduling or the running of other callbacks may delay
them.

Note: Technically, the poll phase controls when timers
are executed.

For example, say you schedule a timeout to execute after a 100 ms
threshold, then your script starts asynchronously reading a file which
takes 95 ms:

const fs = require('fs');

function someAsyncOperation(callback) {
  // Assume this takes 95ms to complete
  fs.readFile('/path/to/file', callback);
}

const timeoutScheduled = Date.now();

setTimeout(() => {
  const delay = Date.now() - timeoutScheduled;

  console.log(`${delay}ms have passed since I was scheduled`);
}, 100);


// do someAsyncOperation which takes 95 ms to complete
someAsyncOperation(() => {
  const startCallback = Date.now();

  // do something that will take 10ms...
  while (Date.now() - startCallback < 10) {
    // do nothing
  }
});

When the event loop enters the poll phase, it has an empty queue
(fs.readFile() has not completed), so it will wait for the number of ms
remaining until the soonest timer's threshold is reached. While it is
waiting 95 ms pass, fs.readFile() finishes reading the file and its
callback which takes 10 ms to complete is added to the poll queue and
executed. When the callback finishes, there are no more callbacks in the
queue, so the event loop will see that the threshold of the soonest
timer has been reached then wrap back to the timers phase to execute
the timer's callback. In this example, you will see that the total delay
between the timer being scheduled and its callback being executed will
be 105ms.

Note: To prevent the poll phase from starving the event loop, libuv
(the C library that implements the Node.js
event loop and all of the asynchronous behaviors of the platform)
also has a hard maximum (system dependent) before it stops polling for
more events.

I/O callbacks

This phase executes callbacks for some system operations such as types
of TCP errors. For example if a TCP socket receives ECONNREFUSED when
attempting to connect, some *nix systems want to wait to report the
error. This will be queued to execute in the I/O callbacks phase.

poll

The poll phase has two main functions:

  1. Executing scripts for timers whose threshold has elapsed, then
  2. Processing events in the poll queue.

When the event loop enters the poll phase and there are no timers
scheduled
, one of two things will happen:

  • If the poll queue is not empty, the event loop will iterate
    through its queue of callbacks executing them synchronously until
    either the queue has been exhausted, or the system-dependent hard limit
    is reached.

  • If the poll queue is empty, one of two more things will
    happen:

    • If scripts have been scheduled by setImmediate(), the event loop
      will end the poll phase and continue to the check phase to
      execute those scheduled scripts.

    • If scripts have not been scheduled by setImmediate(), the
      event loop will wait for callbacks to be added to the queue, then
      execute them immediately.

Once the poll queue is empty the event loop will check for timers
whose time thresholds have been reached. If one or more timers are
ready, the event loop will wrap back to the timers phase to execute
those timers' callbacks.

check

This phase allows a person to execute callbacks immediately after the
poll phase has completed. If the poll phase becomes idle and
scripts have been queued with setImmediate(), the event loop may
continue to the check phase rather than waiting.

setImmediate() is actually a special timer that runs in a separate
phase of the event loop. It uses a libuv API that schedules callbacks to
execute after the poll phase has completed.

Generally, as the code is executed, the event loop will eventually hit
the poll phase where it will wait for an incoming connection, request,
etc. However, if a callback has been scheduled with setImmediate()
and the poll phase becomes idle, it will end and continue to the
check phase rather than waiting for poll events.

close callbacks

If a socket or handle is closed abruptly (e.g. socket.destroy()), the
'close' event will be emitted in this phase. Otherwise it will be
emitted via process.nextTick().

setImmediate() vs setTimeout()

setImmediate and setTimeout() are similar, but behave in different
ways depending on when they are called.

  • setImmediate() is designed to execute a script once the current
    poll phase completes.
  • setTimeout() schedules a script to be run after a minimum threshold
    in ms has elapsed.

The order in which the timers are executed will vary depending on the
context in which they are called. If both are called from within the
main module, then timing will be bound by the performance of the process
(which can be impacted by other applications running on the machine).

For example, if we run the following script which is not within an I/O
cycle (i.e. the main module), the order in which the two timers are
executed is non-deterministic, as it is bound by the performance of the
process:

// timeout_vs_immediate.js
setTimeout(() => {
  console.log('timeout');
}, 0);

setImmediate(() => {
  console.log('immediate');
});
$ node timeout_vs_immediate.js
timeout
immediate

$ node timeout_vs_immediate.js
immediate
timeout

However, if you move the two calls within an I/O cycle, the immediate
callback is always executed first:

// timeout_vs_immediate.js
const fs = require('fs');

fs.readFile(__filename, () => {
  setTimeout(() => {
    console.log('timeout');
  }, 0);
  setImmediate(() => {
    console.log('immediate');
  });
});
$ node timeout_vs_immediate.js
immediate
timeout

$ node timeout_vs_immediate.js
immediate
timeout

The main advantage to using setImmediate() over setTimeout() is
setImmediate() will always be executed before any timers if scheduled
within an I/O cycle, independently of how many timers are present.

process.nextTick()

Understanding process.nextTick()

You may have noticed that process.nextTick() was not displayed in the
diagram, even though it's a part of the asynchronous API. This is because
process.nextTick() is not technically part of the event loop. Instead,
the nextTickQueue will be processed after the current operation
completes, regardless of the current phase of the event loop.

Looking back at our diagram, any time you call process.nextTick() in a
given phase, all callbacks passed to process.nextTick() will be
resolved before the event loop continues. This can create some bad
situations because it allows you to "starve" your I/O by making
recursive process.nextTick() calls
, which prevents the event loop
from reaching the poll phase.

Why would that be allowed?

Why would something like this be included in Node.js? Part of it is a
design philosophy where an API should always be asynchronous even where
it doesn't have to be. Take this code snippet for example:

function apiCall(arg, callback) {
  if (typeof arg !== 'string')
    return process.nextTick(callback,
                            new TypeError('argument should be string'));
}

The snippet does an argument check and if it's not correct, it will pass
the error to the callback. The API updated fairly recently to allow
passing arguments to process.nextTick() allowing it to take any
arguments passed after the callback to be propagated as the arguments to
the callback so you don't have to nest functions.

What we're doing is passing an error back to the user but only after
we have allowed the rest of the user's code to execute. By using
process.nextTick() we guarantee that apiCall() always runs its
callback after the rest of the user's code and before the event loop
is allowed to proceed. To achieve this, the JS call stack is allowed to
unwind then immediately execute the provided callback which allows a
person to make recursive calls to process.nextTick() without reaching a
RangeError: Maximum call stack size exceeded from v8.

This philosophy can lead to some potentially problematic situations.
Take this snippet for example:

let bar;

// this has an asynchronous signature, but calls callback synchronously
function someAsyncApiCall(callback) { callback(); }

// the callback is called before `someAsyncApiCall` completes.
someAsyncApiCall(() => {
  // since someAsyncApiCall has completed, bar hasn't been assigned any value
  console.log('bar', bar); // undefined
});

bar = 1;

The user defines someAsyncApiCall() to have an asynchronous signature,
but it actually operates synchronously. When it is called, the callback
provided to someAsyncApiCall() is called in the same phase of the
event loop because someAsyncApiCall() doesn't actually do anything
asynchronously. As a result, the callback tries to reference bar even
though it may not have that variable in scope yet, because the script has not
been able to run to completion.

By placing the callback in a process.nextTick(), the script still has the
ability to run to completion, allowing all the variables, functions,
etc., to be initialized prior to the callback being called. It also has
the advantage of not allowing the event loop to continue. It may be
useful for the user to be alerted to an error before the event loop is
allowed to continue. Here is the previous example using process.nextTick():

let bar;

function someAsyncApiCall(callback) {
  process.nextTick(callback);
}

someAsyncApiCall(() => {
  console.log('bar', bar); // 1
});

bar = 1;

Here's another real world example:

const server = net.createServer(() => {}).listen(8080);

server.on('listening', () => {});

When only a port is passed the port is bound immediately. So the
'listening' callback could be called immediately. Problem is that the
.on('listening') will not have been set by that time.

To get around this the 'listening' event is queued in a nextTick()
to allow the script to run to completion. Which allows the user to set
any event handlers they want.

process.nextTick() vs setImmediate()

We have two calls that are similar as far as users are concerned, but
their names are confusing.

  • process.nextTick() fires immediately on the same phase
  • setImmediate() fires on the following iteration or 'tick' of the
    event loop

In essence, the names should be swapped. process.nextTick() fires more
immediately than setImmediate() but this is an artifact of the past
which is unlikely to change. Making this switch would break a large
percentage of the packages on npm. Every day more new modules are being
added, which mean every day we wait, more potential breakages occur.
While they are confusing, the names themselves won't change.

We recommend developers use setImmediate() in all cases because it's
easier to reason about (and it leads to code that's compatible with a
wider variety of environments, like browser JS.)

Why use process.nextTick()?

There are two main reasons:

  1. Allow users to handle errors, cleanup any then unneeded resources, or
    perhaps try the request again before the event loop continues.

  2. At times it's necessary to allow a callback to run after the call
    stack has unwound but before the event loop continues.

One example is to match the user's expectations. Simple example:

const server = net.createServer();
server.on('connection', (conn) => { });

server.listen(8080);
server.on('listening', () => { });

Say that listen() is run at the beginning of the event loop, but the
listening callback is placed in a setImmediate(). Now, unless a
hostname is passed binding to the port will happen immediately. Now for
the event loop to proceed it must hit the poll phase, which means
there is a non-zero chance that a connection could have been received
allowing the connection event to be fired before the listening event.

Another example is running a function constructor that was to, say,
inherit from EventEmitter and it wanted to call an event within the
constructor:

const EventEmitter = require('events');
const util = require('util');

function MyEmitter() {
  EventEmitter.call(this);
  this.emit('event');
}
util.inherits(MyEmitter, EventEmitter);

const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
  console.log('an event occurred!');
});

You can't emit an event from the constructor immediately
because the script will not have processed to the point where the user
assigns a callback to that event. So, within the constructor itself,
you can use process.nextTick() to set a callback to emit the event
after the constructor has finished, which provides the expected results:

const EventEmitter = require('events');
const util = require('util');

function MyEmitter() {
  EventEmitter.call(this);

  // use nextTick to emit the event once a handler is assigned
  process.nextTick(() => {
    this.emit('event');
  });
}
util.inherits(MyEmitter, EventEmitter);

const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
  console.log('an event occurred!');
});

16. 单页应用中如何下载文件呢, 比如csv文件

  1. 场景: 生成报表
  2. 后端Python使用HttpResponse 返回给前端二进制文件流, 前端如何在不刷新页面的情况下, 得到文件呢?
  3. 后端代码
  4. 疑惑是使用form.submit()提交表单的时候, 如果收到了response, 这时候页面在等待后端生成文件的过程中, 前端显示状态有点像刷新中, 那么在后端出现问题, 就会进行页面的跳转,那么如何后端出错的情况下, 不进行页面的跳转, 还留在原来的页面呢(指的是url不变化), 因为使用form.submit()是向与当前URL1不同的URL2 POST数据的, 因为当后端失败之后, 也会跳转对应的URL2 , 问题是如何在失败的时候(1) 提示用户失败信息:如用户密码不正确 (2) 页面不跳转, 仍然在此页面

13. JS没有块级作用域 - for循环和if语句中的执行环境

  1. 在JavaScript 中,if语句和for循环中的变量声明会将变量添加到当前的执行环境(作用域)
  2. JavaScript中没有块级作用域
  3. var声明的变量会自动被添加到最接近的环境中

Demo1

var myAlerts = [];
for (var i = 0; i < 5; i++) {
    myAlerts.push(
        function inner(i) {
            alert(i); // 此时的i将会添加到inner函数自己的执行环境的变量对象上
        }
    );
}
myAlerts[0](); 
myAlerts[1](); 
myAlerts[2](); 
myAlerts[3](); 
myAlerts[4](); 

Demo2

// 全局作用域global context
var myAlerts = [];
for (var i = 0; i < 5; i++) {
    myAlerts.push(
        function inner() {
            alert(i); //此时的i会被添加到 global context的环境对象上
        }
    );
}
myAlerts[0](); 
myAlerts[1](); 
myAlerts[2](); 
myAlerts[3](); 
myAlerts[4](); 

参照链接

4. JavaScript高级程序设计读书笔记

Chapter3

  1. typeof操作符的操作数可以是变量, 也可以是字面量typeof name,typeof 10
  2. typeof null -> object 因为特殊值null被认为是一个空对象的引用(null值表示一个空对象指针)
  3. 变量没声明的话, 直接用报错xxx is not defined
  4. var name; // name没有进行初始化 typeof name -> undefined
  5. typeof age -> undefined //age 没有声明
  6. null更多用来初始化对象

类型转换

  1. !!"" -> false
  2. null 和 undefined没有toString()方法, 其他类型都有toString()字符串的toString()方法返回一个字符串的副本
  3. toString(进制数), 默认为10
  4. null和undefined没有toString()方法, undefined.toString()报错Uncaught TypeError: Cannot read property 'toString' of undefined
  5. String()可以转换所有类型数据为字符串; String(null) => "null"; String(undefined) => undefined;
  6. Boolean()转换

    在if(condition)语句中,condition可以是任意表达式,
    而且这个表达式的求值结果并不一定是布尔值,ECMAScript会自动调用Boolean()
    转换函数将表达式结果转换为布尔值

  7. break直接跳出整个循环; continue跳出本次循环forEach如何跳出循环???"

###类型转换(解释即可)

解释了下面的每一结果的原因,

  1. ['a','b','c'] + ['2', '3','4']
  2. 3<4<2
  3. toString() 和 toValue()

值传递和应用传递

  1. 按值访问和按引用访问
  2. 参数传递:按值传递
  3. 函数的参数传递和变量复制一样
  4. 所谓对象的引用就是栈内存中指向堆内存对象的一个指针值
  5. 参见文章
  6. ES中函数的参数传递都是值传递

类型判断

  1. typeof基本类型检测: undefined, boolean, string, number, function
  2. instanceof 引用类型检测
  1. 所有引用类型值都是Object的实例
  2. instanceof 检测基本类型 return false, 因为基本类型不是变量
  1. typeof /12/ => object 正则表达式也是对象
  2. /12/ instanceof RegExp => true

正则表达式

  1. typeof /12/ => object 正则表达式也是对象
  2. /12/ instanceof RegExp => true

第五章 引用类型

5.1 Object类型

  1. 在最后一个属性后添加,会在IE7以及opera中导致错误
  2. 在使用对象字面量语法时,属性名也可以使用字符串.数值属性名会自动转换为字符串
  3. var person = {} <=> var person = new Object() 在通过对象字面量定义对象时,实际上不会调用Object构造函数
  4. 方括号语法访问对象属性值的有点是: 可以通过变量来访问属性
    JavaScript var propertyName = 'name' person[propertyName] //在for循环中根据key值作为变量, 访问对象属性值 // 如果属性名中包含会导致语法错误的字符,或者属性名使用的是关键字或保留字,也可以使用方括号表示法。例如:person["first name"] = "Nicholas"; 由于"first name"中包含一个空格,所以不能使用点表示法来访问它。然而,属性名中是可以包含非字母非数字的,这时候就可以使用方括号表示法来访问它们。通常,除非必须使用变量来访问属性,否则我们建议使用点表示法

5.2 Array类型

  1. 利用length属性也可以方便地在数组末尾添加新项,如下所示:
var colors = ["red", "blue", "green"];      
colors[colors.length] = "black";
colors[colors.length] = "brown";  
//由于数组最后一项的索引始终是length-1,因此下一个新项的位置就是length。每当在数组末尾添加一项后,其length属性都会自动更新以反应这一变化。
  1. ES5 中检测数组的方法: Array.isArray()
  2. 转换

toString()将数组转换为字符串, [1,2,3,4,5].toString()会调用数组中每一项的toString()方法,之后将其拼接为一个字符串, 中间以逗号分隔

  1. 改写toString()方法和验证上一步骤:
 var person1 = {
   toLocaleString: function() {
       return 'john'
   },
   toString: function() {
       return 'john2'
   }
 }
 var person2 = {
   toLocaleString: function() {
     return 'zhangsan'
   },
   toString: function() {
     return 'zhangsan2'
   }
 }
 var people = [person1, person2]
 console.log(people)
 console.log(people.toString())
 console.log(people.toLocaleString())
  1. 如果数组中的某一项的值是null或者undefined,那么该值在join()、toLocaleString()、toString()和valueOf()方法返回的结果中以空字符串表示
  2. join()
 var colors = ["red", "green", "blue"];
 colors.join('=')  //  "red=green=blue="
 colors //  ["red", "green", "blue"];
 var colors = ["red", "green", "blue", undefined];
 colors.toString() // "red,green,blue,"
  1. sort
    1. 默认情况下,sort()方法按升序排列数组项——即最小的值位于最前面,最大的值排在最后面。
    为了实现排序,sort()方法会调用每个数组项的toString()转型方法,然后比较得到的字符串,以确定如何排序。
    即使数组中的每一项都是数值,sort()方法比较的也是字符串,如下所示

    var values = [0, 1, 5, 10, 15]; 
    values.sort();
    alert(values);     //0,1,10,15,5 
2. 自定义排序函数
> 比较函数接收两个参数,
> 如果第一个参数应该位于第二个之前则返回一个负数,
> 如果两个参数相等则返回0,
> 如果第一个参数应该位于第二个之后则返回一个正数。
> 以下就是一个简单的比较函数:

```JavaScript
  function compare(value1, value2){
    if (value1 < value2) { return -1}
    else if (value1 > value2 ) {return 1}
    else {return 0}
  }
  var values = [0, 1, 5, 10, 15]; 
  values.sort(compare) // values => [0, 1, 5, 10, 15];
  // 注意: 排序之后, 数组的值与之前不同
  // reverse()和sort()方法的返回值是经过排序之后的数组
```

3. 对于数值类型或者其valueOf()方法会返回数值类型的对象类型,可以使用一个更简单的比较函数。这个函数只要用第二个值减第一个值即可(正序)
```JavaScript
function compare(value1, value2){ 
  return value1 - value2; 
}
```
  1. 操作方法
    1. concat 连接两个数组, arr.concat() => arr保持不变, 返回的是arr的副本
    2. slice 不改变原数组
    3. splice 改变原数组
  2. 位置方法
    1. indexOf() 查找使用 ===
    2. lastIndexOf 同上
    3. 使用indexOf()和lastIndexOf()方法查找特定项在数组中的位置非常简单,支持它们的浏览器包括IE9+、Firefox 2+、Safari 3+、Opera 9.5+和Chrome。
  3. 归并(求和)操作
    1. reduce()
    2. reduceRight()
    3. 在对对象数组的某一个属性求和的时候注意:
    4. 参考链接
    5. 这两个方法都接收两个参数:一个在每一项上调用的函数和(可选的)作为归并基础的初始值- <JS高程 5.2.9节 归并方法>
    6. // A cleaner way to accomplish this is by providing an initial value:
    JavaScript var arr = [{x:1}, {x:2}, {x:4}]; arr.reduce(function (prev, cur) { return prev + cur.x; }, 0); // 7
  1. The first time the anonymous function is called, it gets called with (0, {x: 1}) and returns 0 + 1 = 1. 函数第一次调用的时候, 传入(prev, cur)的值是(0, {x: 1})
  2. The next time, it gets called with (1, {x: 2}) and returns `1 + 2 = 3 .
  3. It's then called with (3, {x: 4}), finally returning
  4. 如果不传入最后的0, 函数第一次调用,传入(prev, cur)的值是({x:1}, {x: 2}), 返回值就是"[object Object]2".
  5. 如果有一个操作数是对象、数值或布尔值,则调用它们的toString()方法取得相应的字符串值,然后再应用前面关于字符串的规则。对于undefined和null,则分别调用String()函数并取得字符串"undefined"和"null - <JS高程 3.5.5节 加性操作符>"

5.5 Function类型

  1. 解析器在向执行环境中加载数据时,对函数声明和函数表达式并非一视同仁。解析器会率先读取函数声明,并使其在执行任何代码之前可用(可以访问);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行
  2. 在代码开始执行之前,解析器就已经通过一个名为函数声明提升(function declaration hoisting)的过程,读取并将函数声明添加到执行环境中。对代码求值时,JavaScript引擎在第一遍会声明函数并将它们放到源代码树的顶部。所以,即使声明函数的代码在调用它的代码后面,JavaScript引擎也能把函数声明提升到顶部
  3. 函数声明
  alert(sum(10,10));
  function sum(num1, num2){     return num1 + num2; }
  1. “unexpected identifier”(意外标识符)错误 => 变量未声明,就直接调用
  2. 因为ECMAScript中的函数名本身就是变量,所以函数也可以作为值来使用。也就是说,不仅可以像传递参数一样把一个函数传递给另一个函数,而且可以将一个函数作为另一个函数的结果返回
  3. arguments.callee 表示函数本身
  function factorial(num){
    if (num <=1) { return 1; }
    else return num * arguments.callee(num-1)
  }
  1. arguments.callee.caller 表示函数的调用者
function outer(){
    inner(); 
  } 
  function inner(){
    alert(arguments.callee.caller);
  }  
  outer();
  1. 函数的名字仅仅是一个包含指针的变量而已。因此,即使是在不同的环境中执行,全局的sayColor()函数与o.sayColor()指向的仍然是同一个函数
  2. IE、Firefox、Chrome和Safari的所有版本以及Opera 9.6都支持caller属性。当函数在严格模式下运行时,访问arguments.callee会导致错误。ECMAScript 5还定义了arguments.caller属性,但在严格模式下访问它也会导致错误,而在非严格模式下这个属性始终是undefined。定义这个属性是为了分清arguments.caller和函数的caller属性。以上变化都是为了加强这门语言的安全性,这样第三方代码就不能在相同的环境里窥视其他代码了。严格模式还有一个限制:不能为函数的caller属性赋值,否则会导致错误
  3. 函数的length表示函数希望接受的参数个数
  4. call 与 apply扩充函数作用域的最大好处: 对象不需要与方法有任何耦合关系
  5. call, apply, bind
  6. new Number('10') 与 Number('25')不一样. 后者是类型转换函数, 参见第三章类型转换
  1. typeof new Number('10') => 'object'
  2. typeof Number('10') => 'number'

5.6.3 String类型

  1. 位置

    1. charAt
    2. charCodeAt 获得字符编码
    3. 可以使用方括号加数字索引来访问字符串中的特定字符, 'abcd'[1], IE8及以上支持
  2. 操作

    1. concat => +
    2. slice() , 参数[开始位置, 结束位置)
    3. substring(), 参数[开始位置, 结束位置) 在对字符串操作时候, 与上述相同
    4. substr(), 参数[开始位置, 返回字符串个数]
    5. slice()、substr()和substring() 指定一个参数时候(非负数), 返回值都是从指定位置到字符串最后一个字符(包含最后一个)的子字符串, 传递一个参数是负数时, var stringValue = "hello world"
    1. stringValue.substr(-3) => stringValue.substr(11-3) => stringValue.substr(8) IE的JavaScript实现在处理向substr()方法传递负值的情况时存在问题,它会返回原始的字符串。IE9修复了这个问题。
    2. stringValue.slice(-3) => stringValue.slice(11-3) => stringValue.slice(8)
    3. stringValue.substring(-3) => stringValue.substring(0) => 返回值就是整个字符串了
    1. 传递第二个参数是负数时, var stringValue = "hello world"
    1. stringValue.slice(3, -4) => slice(3,7)
    2. stringValue.substring(3, -4) => stringValue.substring(3, 0) => stringValue.substring(0,3) substring()方法会把第二个参数转换为0,使调用变成了substring(3,0),而由于这个方法会将较小的数作为开始位置,将较大的数作为结束位置,因此最终相当于调用了substring(0,3)
    3. substr()也会将第二个参数转换为0,这也就意味着返回包含零个字符的字符串,也就是一个空字符串. 因为substr第二个参数代表的是返回字符串的个数,既然第二参数变为0了, 因此也就返回0个字符, 也就是空字符串
    1. 利用indexOf 查询字符串中某元素的所有位置
    var stringValue = "Lorem ipsum dolor sit amet, consectetur adipisicing elit"; 
    var positions = new Array();
    var pos = stringValue.indexOf("e");
    while(pos > -1){ 
        positions.push(pos); 
        pos = stringValue.indexOf("e", pos + 1);
    }
    alert(positions);    //"3,24,32,35,52" 
    1. trim()去除字符串两端空格实现:`IE9+、Firefox 3.5+、Safari 5+、Opera 10.5+和Chrome支持
    String.prototype.trim = function() {
      return this.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
    }

5.7 单体内置对象

  1. Math
    1. Math.floor()
    2. Math.ceil()
    3. Math.round()
    4. Math.min.apply(Math, 数组)
    5. Math.max.apply(Math, 数组)
    > 这个技巧的关键是把Math对象作为apply()的第一个参数,从而正确地设置this值。然后,可以将任何数组作为第二个参数
    6. Math.max(11,22,33,44,55). Math.max()的参数不能是数组,因此如果比较数组的话, 需要把数组作为apply的参数传入.
    7. Math.random() 返回值: [0, 1) 与Math.floor配合可以得到[2, 10] 或 其它区间
```JavaScript
function selectFrom(lowerValue, upperValue) {
  var choices = upperValue - lowerValue + 1;
  return Math.floor(Math.random() * choices + lowerValue); 
}
var num = selectFrom(2, 10);
alert(num);   // 介于2和10之间(包括2和10)的一个数值
var colors = ["red", "green", "blue", "yellow", "black", "purple", "brown"]; 
var color = colors[selectFrom(0, colors.length-1)];
alert(color);  // 可能是数组中包含的任何一个字符串
```

第23章 离线应用与客户端存储

  1. 23.3 数据存储
  2. Cookie
    1. cookie的key 和value都必须是URL编码的???
    2. cookie与session如何进行配合
    3. 怎么验证该多个请求是同一个回话中发出的呢?和token进行比较呢??

  1. 变量赋值

这说明JavaScript实际上只有一个全局作用域。任何变量(函数也视为变量),如果没有在当前函数作用域中找到,就会继续往上查找,最后如果在全局作用域中也没有找到,则报ReferenceError错误。

'use strict';
var old_alert = window.alert;
window.alert = function(){ }
  1. 变量提升: 变量提升时,函数声明的变量名字和值都会提升,而函数表达式只提升名字。
demo();
test();
function demo(){
console.log('demo');
}
var test = function(){
   console.log('test');
};
/*结果: VM2970:3 Uncaught TypeError: test is not a function(…)*/

[about] Resume

基本信息

个人概况

熟练掌握原⽣ JS 并能⼿写代码, 对 JS 闭包、 DOM有深刻地理解并在实际项目中运⽤
熟悉前端 MVVM 框架 Vue .js, 研究过其实现原理, 并了解其它框架如 React.js
掌握 ⼀ 些常⻅的设计模式, 追求开发高质量、 高可维护性的代码
熟悉后端语⾔如 Python、 Node.js
具有较强的学习能⼒、 团队合作精神
有技术总结的意识, 推动建⽴团队技术博客。 有⾃⼰的独⽴博客
热爱开源和构建复⽤性强的组件

工作经历

2020.06 ~ 至今

  • 工作职责:
    带领团队主要负责

    • CRM、呼叫中心(外呼千万级)等中台业务线,并产出多项集团通用标准方案
    • 隐私号、新车/二手车、IM等前台C端业务线
  • 成果

    1. 中后台组件库:
      带领业务前端团队0~1建设中后台组件业务组件库,并成功落地 & 演进成为大前端通用中后台组件库
      – 组件: 40+PC端、H5端20+
      – 覆盖项目 100+

    2. 动态表单:
      引入动态表单框架,统一团队表单/查询表格场景最佳实践,大规模提高中后台迭代效率

    • 支撑5+团队
    • 覆盖基础表单、客服工单、销售电销小结、企效工单、问卷调研等多种场景
    • 相同协议,支持双端 PC、H5
    • 周边生态 15+ 组件
    • 年提效约200人天
    • 支撑中后台场景表单提交量 >千万次/年
    1. 制定团队前后端公约,并以统一请求库落地
    • 请求库现成为中后台场景默认统一请求库
    • 团队
      PC和H5统一请求库,覆盖页面400+
    • 支持请求相关问题快速定位,时间成本由前端 5~10min/次 -> 前端无须介入
    1. 带领团队沉淀流程画布引擎、规则选择引擎、Enum降低魔法数字等工具
  • 团队管理

    1. 三年团队管理经验

      1. 带领团队多人成功晋升L6
      2. 输出一人至Web基础组
      3. 输出一人为虚线TL
    2. 大前端校招生训练营负责人

      1. 负责制定并跟进校招生保温计划
      2. 负责并组织大前端校招训练营,包含但不限课程、练习、大作业制订等内容,协助推动训练营顺利结营

2016.06 - 2020.06: 南京⽹觉软件有限公司 (前端⼯程师)

项目经历
2020 - ⾄今: 调研、 开发⼴告动态下发、 渲染⽅案
项目背景: 改造原有Native ⼴告渲染流程, 采⽤ JSBridge + JSRender实现⼴告下发之后 动态渲染
主要模块: JSBridge、 JSRender
主要成果:

  • 确认与客户端通信⽅案与规范, 开发 JSBridge 前端模块, 并编写单元测试, 降低维护 成本
  • 研发 JSRender 实现⼴告动态渲染

2019.07 - 2020-01: 改造 DSP 投放平台, ADX 平台数据报表 (前端⼯程师)
项⽬背景: 重启 DSP + ADX, 改进 DSP 现有投放后台和流程, 增强 ADX 数据统计报表
主要模块: DSP 投放模块, ADX 数据报表
相关技术: Vue + Vuetify
主要成果:
- 改进现有 DSP 投放平台以及流程, 使之能够与 ⼀ 线 DSP 平台相媲美 完善 DSP 数据报表, 让运营能够查看更多纬度的数据
- 改进 ADX 平台, 增加数据对比报表, ⽀持分⼩时环比和同比, 协助运营快速查找、 定 位问题并决策
- 改进 ADX 平台, 提供⽀持多纬度查看、 数据下钻的报表可视化模块, 协助运营10分钟 内定位、 分析问题, 将其模块抽象为通⽤解决⽅案

2019.05 - 2018.07: 基于Marketing API 的⼴告投放平台 (前端⼯程师)
项⽬背景:对接 Google Ad words、 Facebook、 巨量引擎的Marketing-API, 提高⼴告优化师投放的效 率, 整合多平台数据报表
主要模块: Facebook 投放⻚⾯、 巨量引擎投放⻚⾯、 素材库、
相关技术: React、 Redux、 Ant Design
主要成果:

  • 系统性掌握并在实际项⽬中使⽤ React 技术栈, 扩展了技术⾯
  • 参照 Facebook 、 巨量引擎⼴告投放⻚⾯, 集合实际业务, 实现投放⻚⾯。 让运营能够 在 ⼀ 平台进⾏投放, 减少多账号管理的困难。
  • 调研Facebook、 巨量引擎提供的Marketing API, 与后端配合, 减少投放过程中的重复 劳动, 提高优化师的投放效率
  • 构建通⽤素材库, ⽅便素材设计师上传素材, ⼴告优化师使⽤素材, 减少双⽅沟通成 本。
  • 结合素材库和统计数据, 寻找高质量的素材, 提高⼴告平台的优质素材积累
  • 总结接⼊巨量引擎Marketing API 投放过程中遇到的问题, 形成⽂档, 为⼩组做批量操 作的同事提供参考

2017.08 - 2018. 12: 国内⼴告平台 (前端⼯程师)
项⽬背景: 构建国内版⼴告平台, 下发⼴告至移动端⽹⻚, 展示、 统计⼴告 主要模块: ⼴告SDK、 统计平台
相关技术: 原⽣JS、 iView
主要成果:
- 研发⼴告下发、 打点 SDK, 书写⽂档:
- 移动端⽹⻚通过简单的集成即可加载、 展示⼴告、 上报⼴告点击、 展示数据 研发⼴告投放平台:
- 让运营能够快速进⾏⼴告投放, 形成: 投放+下发+统计的闭环
- 与后端配合, 调研 Graf ana, 实现上报数据的实时监控、 报警机制, 降低⻛险。

2016.6 - 2018.7: SSP⼴告统计平台 (前端⼯程师)
项⽬背景: 为SSP⼴告系统, 搭建⼴告统计平台, ⽅便运营查看统计数据
主要模块: 报表统计、 多渠道趋势对比、 对账模块
相关技术: Vue .js & iView UI & Echarts
主要成果:

  • 开发报表模块, ⽅便运营从多维度查看统计数据
  • 使⽤ Echarts 开发多渠道数据对比模块, ⽅便运营快速定位数据异常 改进之前的邮件对账流程, 将对账数据归纳⾄对账模块, 提升对账效率
  • 调研 PWA ⽅案, 上线 PWA 和钉钉版本 SSP, ⽅便运营同学在移动端、 ⼿机钉钉上查 看数据, 提升⽤户体验。 整理相关⽂档, 输出⽂档和代码, 供其它部⻔参考。

28. vagrant 操作 => mysql、防火墙、端口映射

原文链接: #28
应用场景: 本地开发机器是mac或者Linux、windows,需要模拟发布到centos服务器的流程

注意执行该命令位置(pwd) /home/someone/

  1. 已有box, 位置: /home/someone/centos.box

  2. vagrant box add --name centos_for_dev /home/someone/centos.box

  3. vagrant init centos_for_dev

  4. 会发现在 /home/someone/有一个文件 VagrantFile

  5. vagrant up && vagrant ssh 登录centos_for_dev 机器

  6. 自动登录, 用户名和密码都是 vagrant

  7. 端口映射

    1. 场景: 需要在vagrant中开启mysql,供host机器访问
    2. config.vm.network "forwarded_port", guest: 8000, host: 3306 其中host为宿主机(比如widdows或者mac机器),guest为vagrant内的虚拟机。
    3. 这样在本地访问执行mysql -u root -p的时候,访问本地3306端口的mysql,实际上访问的是vagrant中的端口为8000的mysql
    4. vagrant reload 重新加载配置文件
    5. vagrant up 启动机器,vagrant ssh登陆机器之后执行下面操作
    6. sudo vim /etc/sysconfig/iptables 编辑防火墙,开启8000端口,供外部访问
    7. -A INPUT -p tcp -m tcp --dport 3306 -m state --state NEW -j ACCEPT
    8. 重启防火墙 service iptables restart
  8. 文件映射

    1. 场景: dev环境: Mac 或 windows, production环境: centos。需要针对服务器制定一些脚本或者目录,方便部署。因此需要将dev代码目录映射到vagrant目录,实现dev改了代码之后,和production环境(vagrant,生产环境)代码保持一致,方便调试。不用再使用scp或rsync更新代码了。
    2. config.vm.synced_folder "/home/someone/code/", "/opt/code"
    3. 这样宿主机的/home/someone/code/目录下的代码修改了之后,vagrant虚拟机的/opt/code目录下的代码会同步更新,或者说二者指向同一份文件
    4. vagrant reload 重新加载配置文件
  9. scp 同步文件

    vagrant plugin install vagrant-scp
    scp -P 2222 your_file [email protected]:/home/vagrant/
  10. vagrnat up 开机

  11. vagrant halt 关机

  12. 创建新用户centos

    1. 在Vagrant机器中 useradd centos 创建centos用户。
    2. 在Vagrantfile中,添加配置config.ssh.username = "centos"

更多参见官网文档 Get Started

Vagrant 集群配置

  1. https://kiwenlau.com/2016/07/03/vagrant-vm-cluster/
  2. hashicorp/vagrant#6796
  3. 关键配置
    1. 其中需要主要的是网卡相关配置
    1. node.vm.network "public_network", ip: "10.10.30.#{200+i}", bridge: "en0: Wi-Fi (AirPort)"
    2. ip 与 ifconfig 中主要的实际使用使用的网络在同一个网段即可,200+是因为200以后的 IP 多数没有被占用
    3. bridge 使用 networksetup -listallhardwareports 查看;关键字List All Network Hardware from the Command Line in OS X
    1. 设置虚拟的box node.vm.box = "/Users/someone/workspace/vagrant-boxs/centos7_test.box"
    1. 可以是 虚拟机 box 的绝对路径
    2. vagrant 市场中的名称
    3. 和 Vagrantfile 在同一个文件夹中 node.vm.box = "centos7_test.box"

44. JS 应用场景(Promise => 图片上传)

Promise

  1. Promise.reject()

element || iview 上传图片尺寸校验

  1. 在上传图片之前,验证图片大小、尺寸等信息,可以返回一个Promise。当验证成功的时候,可以return file。 这样,在upload中可以拿到该文件对象
  2. 当验证失败的时候,需要使用reject,因此需要使用return Promise.reject() (无法直接返回false的原因是return 的内容需要是一个Promise)
// element ui 代码
// https://github.com/ElemeFE/element/blob/dev/packages/upload/src/upload.vue#L77
upload(rawFile, file) {
  this.$refs.input.value = null;
  if (!this.beforeUpload) {
    return this.post(rawFile);
  }
  const before = this.beforeUpload(rawFile);
  if (before && before.then) {
    before.then(processedFile => {
      if (Object.prototype.toString.call(processedFile) === '[object File]') {
        this.post(processedFile);
      } else {
        this.post(rawFile);
      }
    }, () => {
      this.onRemove(null, rawFile);
    });
  } else if (before !== false) {
    this.post(rawFile);
  } else {
    this.onRemove(null, rawFile);
  }
},
// 组件关键实现
checkWidthHeight(file) {
    return new Promise((resolve, reject) => {
        // 取限定尺寸
        let width = this.limit.width
        let height = this.limit.height
        var _URL = window.URL || window.webkitURL;
        var img = new Image()
        img.onload = function() {
            // this => img
            let valid = width === this.width  && height === this.height
            valid ? resolve() : reject(this)
        }
        img.src = _URL.createObjectURL(file)
    })
},
handleBeforeUpload(file) {
    return this.checkWidthHeight(file).then(async () => {
        let filename = `qiniu-filename-prefix-`
        file.name.replace(/.(jpg|jpeg|png|gif)$/, (prefix, suffix) => {
            // suffix => 取文件后缀
            // 文件名添加时间戳随机数防止重复
            filename += `${+new Date()}${suffix}`
        })
        this.fileName = filename
        this.form.key = filename
        this.form.token = await api.qiniuUploadToken() // ajax 取七牛上传 token
        return file // 被element upload 中的 before.then(processedFile =>  {} ) 调用
    }, (img) => {
        this.$Notice.warning({
            title: `文件尺寸不正确`,
            desc: `文件尺寸 width: ${img.width}, height: ${img.height}`
        });
        // 返回 reject
        // 从而调用 element upload 方法中reject 的 this.onRemove(null, rawFile);
        return Promise.reject()
    })
},

31. 最近css整理

最近css整理

  1. 【?】 水平ul>li, 可以设置display:inline, 但是对于较为复杂的水平列表, 如果浮动列表项, 然后使用margin将列表项分开, 会更灵活
    1. 灵活体现在: inline 或者 line-block会导致li之间存在空白间隙, 解决方案: 给ul设置font-size=0,再给每个li设置对应的font-size
    2. 间隙产生的原因是: 写代码的时候格式化产生的,也就是 </li结尾>空白/换行导致的的空白间隙<li开始> 可以排在一起就不会有间隙了
ul.pagination li {
	float: left;
	margin-left: 0.6em;
}
  1. 当元素(ul>li)浮动的时候, 它不在占据原来的位置。因此,父列表(ul元素)实际上是没有内容的,它就会收缩。 从而隐藏列表的背景色等等内容。有几种方法可以让父元素包含浮动子元素
    1. 添加一个进行清理的元素, 但这会在页面中增加不必要的标记, 尽可能避免
    2. 让父元素浮动,并且使用某个元素(比如站点页脚)对它进行清理以便换行
    3. 使用overflow:hidden;,创建一个BFC
ul {
	margin: 0; // 因为IE早期版本和Opera使用margin-left控制列表缩进, 而Safari和Firefox在内的大多数现代浏览器则选择使用padding-left。 因此需要去掉浏览器的默认行为
	padding: 0;
	list-style: none;
	width: 72em; // 
}
  1. ul>li>a

    1. display: block这样可以让链接占满父元素, 从而让整个li都可以点击, 否则就是只有<a>内容</a>内容区域长度可以点击
    2. 设置line-height 让链接文本竖直居中
  2. 纯css工具提示

绝对定位元素的定位相对于最近的已定位祖先元素(如果没有, 就相对于祖先元素)
下面的例子中, 已经定位了a, 因此span相对于a定位

<a href="xxxx" class="tooltip">Something <span>tooltip content</span></a>
a.tooltip {
	position: relative;
}
a.tooltip span {
	display: none;
}
a.tooltip:hover span {
	display: block;
	position: relative;
	top: 1em;
	left: 2em;
}

18. 同步文件 | 部署

1. 使用rsync 同步文件

  1. 检测文件变动
 rsync -e "ssh -i ~/.ssh/abc.pem" -atzvhncP --exclude ".git" --exclude "node_modules" --exclude "*.log" ./* [email protected]:/opt/code
  1. 更新变动的文件
 rsync -e "ssh -i ~/.ssh/abc.pem" -atzvhcP --exclude ".git" --exclude "node_modules" --exclude "*.log" ./* [email protected]:/opt/code
  1. 服务器首先需要安装rsync
  2. 使用秘钥更新文件

2. 使用fab 同步文件,更新服务

46. HTML 类数组属性及其操作

array like �attributes and it's operation

  1. element.attributes
  2. docuement.getElementsByClassName || getElementsByTagName || getElementsByName || querySelectorAll
let el = document.getElementById('app')
let attrs = el.attributes
Array.isArray(attrs)  // => false
// iterate
[...attrs].map(attrs, attr => {
     return {
           name: attr.name,
           value: attr.value
     }
})

// 参见vuejs源码
[].forEach.map(attrs, attr => {
     return {
           name: attr.name,
           value: attr.value
     }
})

11. Python importError: No module named djcelery

  1. 类似问题

  2. 场景: 使用gunicorn运行项目的时候(在manage.py所在的目录, 运行gunicorn loga.wsgi:application) 报错

    1. importError: No module named djcelery
    2. 但是使用pip 已经安装过了,因为djcelery的依赖包是django-celery
    3. 使用 pip 重新安装django-celery, 得到结果是satisfied, 表明已经是重新安装了,仍然不理解
    4. 运行python manage.py runserver 没有报错
  3. 后来在这个Issue的启发下, 发现了问题,并且解决了

    1. 因为我的gunicorn是通过pip在virtualenv环境下安装的, 因此运行gunicorn命令需要带上gunicorn的所在的虚拟环境中的绝对路径
    2. 即: /home/someone/venv/bin/gunicorn loga.wsgi:application
  4. 同理, 运行一个python脚本 foo.py的时候, 如果这个脚本中有安装在virtualenv中, 运行这个python脚本时候,需要加上虚拟环境的路径: /home/someone/venv/bin/python a.py

10. 证书权限 Permissions 0644 for 'XXX.pem' are too open.

Q

  1. 使用ssh -i ~/.ssh./abc.pem [email protected] 登陆服务器的时候报错:Permissions 0644 for 'XXX.pem' are too open.
  2. 场景: 证书多是由同事的邮件发来的,下载下来的证书的权限多是644 权限,按照Linux中的权限说明也就是rw-r--r--.其中r=4;w=2;x=1分别是读、写、执行。

A

sudo chmod 400 ~/.ssh./abc.pem

600 也可以
原因参见man ssh, 就是因为证书是用来登陆的,因此里面包含了敏感信息,因此应该只能被当前用户读取(600就可以编辑了,但是很少编辑,都是自动生成的),不能被其他用户编辑和读取。

(.pem) Contains the private key for authentication. 
[These files contain sensitive data and should be readable by the user
but not accessible by others (read/write/execute).  
ssh will simply ignore a private key file if it is  accessible by others.]  
It is possible to specify a
passphrase when generating the key which will be used to encrypt the sensitive 
part of this file using 3DES.

48. Vue.js 源码涉及点

源码执行步骤

new Vue(options)
  => this._init(options)
    =>mergeOptions
    =>initLifecycle(vm)
    =>initEvents(vm)
    =>initRender(vm)
    =>callHook(vm, 'beforeCreate')
    =>initInjections(vm)
    =>initState(vm) => 
       => initProps()
       => initMethod() // 将method 挂载到 vm 上
       => initData() => observe(data, true) // 数据代理
       => initComputed()
       => initWatch()
    => vm.$mount(vm.$options.el) // 🌟🌟🌟🌟
    => Vue.prototype.$mount 「src/platforms/web/entry-runtime-with-compiler.js」
      => { render, staticRenderFns } = compileToFunctions(template, ...)
        => { compile, compileToFunctions } = createCompiler(baseOptions)
      	  => parse(template, options)
      	  => optimize(ast, options)
      	  => {render, staticRenderFns} = generate(ast, options)
      		  => code = genElement(ast, state) // code demo: _c('div',{attrs:{"id":"app"}},[_c('p',{on:{"click":show}},[_v(_s(text))])])
      			  => genData
      				  => data += `${genHandlers(el.events, false, state.warn)},`
    => mount.call()
    	=> Vue.prototype.$mount 「src/platforms/web/runtime/index.js」
    	=> mountComponent
    		=> updateComponent = function() { vm._update(vm._render(), hydrating) }
    		=> new Watcher(vm, updateComponent)
    			=> this.getter = updateComponent
    	      => this.get()
    			  => pushTarget
    			  => value = this.getter.call(vm, vm)
              => vm._update(vm._render(), hydrating)
              => vm._update(Vue.prototype._render())
              => Vue.prototype._update(Vue.prototype._render()) 「src/core/instance/lifecycle.js」
              => vm.__patch__

Vanilla JavaScript

  1. delegate 事件委托
    1. e.target <=> e.currentTarget
    2. typeof e.target
    3. element.webkitMatchesSelector
    4. 递归添加处理函数
  2. DOM 操作
    1. childNodes
    2. nodeType

Vue.js

  1. @click.native 与 @click
  2. v-if 与 v-show
  3. keep-alive
    1. keep-alive 在首页和detail页面的具体应用
        // 需要注意的是: 如果要匹配多个路径,include 传入为正则对象,不可是字符串
        // include 正则匹配的是组件的名称,不是路由的名称。
         <keep-alive :include=/xxx$/>
             <router-view></router-view>
         </keep-alive>
    1. 疑问:缓存到 cache 中的是 vnode 还是 真是的 dom 节点呢?如果是vdom,回退的时候,为何页面可以保持之前的状态,比如图标也是保持之前的形状呢?

ES6

  1. import 命令的提升效果,优先执行。在调试 vue 单文件的时候,console.log 打点如果在 import 前面,注意:console 会在 import 执行之后执行。

49. echarts 相关

为何需要指定 图表类 设计规范?

  1. 从下面的这图开始说起,A 部门在 Echarts 上面踩了很多坑,做了很多定制化的样式,这些经验+样式 存在哪里呢?git 仓库?前端部门的博客吗?其实都没有,最终没有任何积累。

  2. B 部门又有一个后台项目。做的时候。因为没有统一的设计规范或者视觉规范,产品和B部门的前端说,你按照A部门的样式来做,但是中间说不定又会改一些其它的内容,最终每个部门的图表都长的不一样。

  3. 如下图所示,比如去掉Y轴上面的 - 符号,这个坑A部门踩了,产品觉得A部门的这个OK,照抄,如果A部门的人没有做积累,或者说离职了呢,代码中也没有针对这个添加注释,该怎么办呢?

  4. 不过产品不在乎这种积累,直接把A部门页面直接贴过去给B部门的前端就可以了,既然A部门能做出来,B你做不出来,你能力不行 🐶

怎么办呢 :B 部门的人,只能又花 几天的时间重走A部门的趟过的坑!!

image

Y轴相关配置

image

x 与 y 轴同一个起点

image

不显示x 与 y轴

image

四线三格配置

  1. 在 yAxis 中配置
  2. 修改颜色:
yAxis: {
splitLine: {
        lineStyle: {
             // 使用深浅的间隔色
             color: ['#aaa', '#ddd'],
        }
    }
}

image
image

Tooltip 显示不全 / 自定义 tooltip function

option: {
  tooltip: {
    trigger: 'axis',
    position: function(point, params, dom, rect, size) {
      // 鼠标在左侧时 tooltip 显示到右侧,鼠标在右侧时 tooltip 显示到左侧。
      var obj = {};
      // 鼠标是否在当前图标的中线的左侧
      let inLeft = point[0] < size.viewSize[0] / 2
      // 左侧的tooltip 的left就是pointX 的值,再右侧,则是图标宽度-pointX
      let val = inLeft ? point[0] : size.viewSize[0] - point[0]
      // 在左侧时候,为 obj.left = pointX; 在右侧时候为 obj.right = widthOfChart - pointX
      // 效果就是 tooltip不会超出图表范围
      obj[['left', 'right'][+!inLeft]] = val
      return obj;
    } // position function end
  } // tootip end
} // option end

    

echarts配置

可以代码直接放在demo中运行

option = {
    // 调色盘颜色列表。如果系列没有设置颜色,则会依次循环从该列表中取颜色作为系列颜色。
    // 使用场景: 一个图表由很多条折线图构成,需要自定义这些颜色,可以写在这里
    colors: ['blue', 'orange'],
    title: {
        text: '未来一周气温变化',
        subtext: '纯属虚构'
    },
    // 工具提示,在鼠标移动到图表上时,显示该点的详细数据
    // 图:https://ws1.sinaimg.cn/large/006tKfTcgy1flizeduk3gj31380j6aak.jpg
    tooltip: {
        trigger: 'axis'
    },
    legend: {
        // 指示器,用于触发折线是否显示
        // https://ws4.sinaimg.cn/large/006tKfTcgy1flizizs6dqj314o0z0aao.jpg
        data:['最高气温','最低气温'] //这里要和下面series里面的每一个item的名字做对应
    },
    // 图 https://ws4.sinaimg.cn/large/006tKfTcgy1flizizs6dqj314o0z0aao.jpg
    toolbox: {
        show: true,
        right: 30, // 控制位置
        iconStyle: {
            // 正常展示时候的样式
            normal: {
                borderColor: "blue" // 控制样式
            },
            // emphasis(强调)=> 鼠标悬浮和点击时候的样式
            emphasis: {
                borderColor: "#2486f0"
            }
        },
        // 支持哪些特性,其实就是有哪些功能
        feature: {
            dataZoom: { // 图表选取扩大,还原
                yAxisIndex: 'none'
            },
            dataView: {readOnly: false},// 将图形转换为数据视图
            magicType: {type: ['line', 'bar']},
            restore: {},// 重置
            saveAsImage: {} // 将当前图表保存为图片
        }
    },
    xAxis:  {
        type: 'category',
        boundaryGap: false,
        // x轴的数据
        data: ['周一','周二','周三','周四','周五','周六','周日'],
        axisLine:{
            // // x轴数据的相关样式
            lineStyle:{
                color:'purple', 
                width:10, //x轴的宽度, 在这个图中, x轴位于0度处
            }
        },
    },
    yAxis: {
        type: 'value',
        name: '度',
        // 修饰y轴顶部的名称
        nameTextStyle: {
            color: ['red'],
            fontSize: 14,
            fontWeight: 600

        },
        // 坐标轴刻度标签的相关设置
        axisLabel: {
            formatter: '{value} °C',
            color: 'orange'
        },
        // 坐标轴轴线相关设置
        axisLine:{
            show: true, // false 不显示
            lineStyle:{
                color:'blue',
                // opacity: 1 // opacity 透明度为0则不显示
            }
        },
    },
    // 每一条折线的数据
    // 其实对应是legend
    series: [
        {
            name:'最高气温',
            type:'bar',
            // 柱状图的一个柱子的底部宽度 占据x轴一个刻度宽度 的比例
            barWidth: '19%',
            // 折线图或者柱状图的数值
            data:[3, 4, 3, 2, 2, 3, 7],
            // 特殊标记点
            markPoint: {
                data: [
                    {type: 'max', name: '最大值'},
                    {type: 'min', name: '最小值'}
                ]
            },
            markLine: {
                data: [
                    {type: 'average', name: '平均值'}
                ]
            }
        },
        {
            name:'最低气温',
            type:'line',
            data:[1, -2, 2, 5, 3, 2, 0],
            markPoint: {
                data: [
                    {name: '周最低', value: -2, xAxis: 1, yAxis: -1.5}
                ]
            },
            markLine: {
                data: [
                    {type: 'average', name: '平均值'},
                    [{
                        symbol: 'none',
                        x: '90%',
                        yAxis: 'max'
                    }, {
                        symbol: 'circle',
                        label: {
                            normal: {
                                position: 'start',
                                formatter: '最大值'
                            }
                        },
                        type: 'max',
                        name: '最高点'
                    }]
                ]
            }
        }
    ]
};

定制每条线上小圆点(symbol)大小(symbolSize

export function genSeries(name, data, color) {
  let currentUTCHour = new Date().getUTCHours()
  let item = {
    name: name,
    type: 'line',
    showSymbol: true,
    data: data, // 每一条线上面的数据
    //   symbolSize: 3,

    symbolSize: function (value, params) {
        return 0
      //   if (params.dataIndex % 3 === 0) {
      //       return 3
      //   } else {
      //       return 0
      //   }
    },
    lineStyle: {
        normal: {
            // width: 1,
            // type: 'solid' // solid or dotted
            color: color,
        }
    },
    itemStyle: {
        normal: {
            color: color
        }
    },
  }
  return item
}

markLine

自定义X轴

33. Eslint 配置

module.exports = {
  root: true,
  parserOptions: {
    parser: 'babel-eslint',
    sourceType: 'module'
  },
  env: {
    browser: true,
    node: true,
    es6: true,
  },
  extends: ['plugin:vue/recommended', 'eslint:recommended'],

  // add your custom rules here
  //it is base on https://github.com/vuejs/eslint-config-vue
  rules: {
    "vue/max-attributes-per-line": [2, {
      "singleline": 10,
      "multiline": {
        "max": 1,
        "allowFirstLine": false
      }
    }],
    "vue/singleline-html-element-content-newline": "off",
    "vue/multiline-html-element-content-newline":"off",
    "vue/name-property-casing": ["error", "PascalCase"],
    "vue/no-v-html": "off",
    'accessor-pairs': 2,
    'arrow-spacing': [2, {
      'before': true,
      'after': true
    }],
    'block-spacing': [2, 'always'],
    'brace-style': [2, '1tbs', {
      'allowSingleLine': true
    }],
    'camelcase': [0, {
      'properties': 'always'
    }],
    // 'comma-dangle': [2, 'never'],
    // 'comma-spacing': [2, {
    //   'before': false,
    //   'after': true
    // }],
    // 'comma-style': [2, 'last'],
    'constructor-super': 2,
    'curly': [2, 'multi-line'],
    'dot-location': [2, 'property'],
    'eol-last': 2,
    'eqeqeq': ["error", "always", {"null": "ignore"}],
    'generator-star-spacing': [2, {
      'before': true,
      'after': true
    }],
    'handle-callback-err': [2, '^(err|error)$'],
    'indent': [2, 2, {
      'SwitchCase': 1
    }],
    'jsx-quotes': [2, 'prefer-single'],
    'key-spacing': [2, {
      'beforeColon': false,
      'afterColon': true
    }],
    'keyword-spacing': [2, {
      'before': true,
      'after': true
    }],
    'new-cap': [2, {
      'newIsCap': true,
      'capIsNew': false
    }],
    'new-parens': 2,
    'no-array-constructor': 2,
    'no-caller': 2,
    'no-console': 'off',
    'no-class-assign': 2,
    'no-cond-assign': 2,
    'no-const-assign': 2,
    'no-control-regex': 0,
    'no-delete-var': 2,
    'no-dupe-args': 2,
    'no-dupe-class-members': 2,
    'no-dupe-keys': 2,
    'no-duplicate-case': 2,
    'no-empty-character-class': 2,
    'no-empty-pattern': 2,
    'no-eval': 2,
    'no-ex-assign': 2,
    'no-extend-native': 2,
    'no-extra-bind': 2,
    'no-extra-boolean-cast': 2,
    'no-extra-parens': [2, 'functions'],
    'no-fallthrough': 2,
    'no-floating-decimal': 2,
    'no-func-assign': 2,
    'no-implied-eval': 2,
    'no-inner-declarations': [2, 'functions'],
    'no-invalid-regexp': 2,
    'no-irregular-whitespace': 2,
    'no-iterator': 2,
    'no-label-var': 2,
    'no-labels': [2, {
      'allowLoop': false,
      'allowSwitch': false
    }],
    'no-lone-blocks': 2,
    'no-mixed-spaces-and-tabs': 2,
    'no-multi-spaces': 2,
    'no-multi-str': 2,
    'no-multiple-empty-lines': [2, {
      'max': 1
    }],
    'no-native-reassign': 2,
    'no-negated-in-lhs': 2,
    'no-new-object': 2,
    'no-new-require': 2,
    'no-new-symbol': 2,
    'no-new-wrappers': 2,
    'no-obj-calls': 2,
    'no-octal': 2,
    'no-octal-escape': 2,
    'no-path-concat': 2,
    'no-proto': 2,
    'no-redeclare': 2,
    'no-regex-spaces': 2,
    'no-return-assign': [2, 'except-parens'],
    'no-self-assign': 2,
    'no-self-compare': 2,
    'no-sequences': 2,
    'no-shadow-restricted-names': 2,
    'no-spaced-func': 2,
    'no-sparse-arrays': 2,
    'no-this-before-super': 2,
    'no-throw-literal': 2,
    'no-trailing-spaces': 2,
    'no-undef': 2,
    'no-undef-init': 2,
    'no-unexpected-multiline': 2,
    'no-unmodified-loop-condition': 2,
    'no-unneeded-ternary': [2, {
      'defaultAssignment': false
    }],
    'no-unreachable': 2,
    'no-unsafe-finally': 2,
    // 'no-unused-vars': [2, {
    //   'vars': 'all',
    //   'args': 'none'
    // }],
    'no-unused-vars': 'off',
    'no-useless-call': 2,
    'no-useless-computed-key': 2,
    'no-useless-constructor': 2,
    'no-useless-escape': 0,
    'no-whitespace-before-property': 2,
    'no-with': 2,
    'one-var': [2, {
      'initialized': 'never'
    }],
    'operator-linebreak': [2, 'after', {
      'overrides': {
        '?': 'before',
        ':': 'before'
      }
    }],
    'padded-blocks': [2, 'never'],
    'quotes': [2, 'single', {
      'avoidEscape': true,
      'allowTemplateLiterals': true
    }],
    'semi': [2, 'never'],
    'semi-spacing': [2, {
      'before': false,
      'after': true
    }],
    'space-before-blocks': [2, 'always'],
    'space-before-function-paren': [2, 'never'],
    'space-in-parens': [2, 'never'],
    'space-infix-ops': 2,
    'space-unary-ops': [2, {
      'words': true,
      'nonwords': false
    }],
    'spaced-comment': [2, 'always', {
      'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
    }],
    'template-curly-spacing': [2, 'never'],
    'use-isnan': 2,
    'valid-typeof': 2,
    'wrap-iife': [2, 'any'],
    'yield-star-spacing': [2, 'both'],
    'yoda': [2, 'never'],
    'prefer-const': 2,
    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
    'object-curly-spacing': [2, 'always', {
      objectsInObjects: false
    }],
    'array-bracket-spacing': [2, 'never'],
    "comma-dangle": ["error", {
      "arrays": "never",
      "objects": "always",
      "imports": "never",
      "exports": "always",
      "functions": "never"
    }]
  }
}

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.