0%

架构图

  • Introduction
  • Core Thinking
    • Data-Driven
    • Pub and Sub
  • Architecture Design
    • Config
    • Base Layout
      • Header (Toolbar)
      • Left Sidebar(Set of widgets)
      • Right Siderbar(Area of widgets controllers)
      • Body(Area of central scene)
      • Popup(Area of system popup)
    • Core Abstract
      • Item
      • Widget
      • Editor
    • Widget System(Why not named component ?)
      • Item
      • Widget
      • Editor
      • Attrs
  • Core FEATURES
    • Flexiably Draggable
    • Joyful UI
    • Excellent Performance(Not that exactly)
    • Less Code For Developers
    • Abundant Opeations & Interactions
      • Dpr Support
      • Layer Support
      • Delete Widget
      • Indicator
      • Magnetic Attraction
      • Undo And Redo
      • Rotate And Resize
      • Realtime Interactions
    • Easy-to-use Data Structure
  • ROADMAP
    • Pull Away Engine
    • Make A GitBook
    • Add More Features
      • Ruler
      • Popover
      • Grid Lines
      • Zoom
      • Lock Layer
      • Alignment
      • Copy And Paste
      • Group And Break-up
      • Keyboard Hot Key
      • Page Guideline
      • Customizable Themes
      • i18n
      • Mobile Simulation
      • Portrait And Landscape
  • Core Libraries
    • react
    • react-dom
    • mobx
    • mobx-react
    • react-dnd
    • react-dnd-html5-backend
    • antd
    • react-color
    • lodash

  • 基本用法
  • 基本原理
    • 概述
    • 绑定与注册
    • 触发与dispatch
  • 合成事件(SyntheticEvent)
    • 概念
    • 优势
  • 事件池概念

基本用法

class DemoCom extends React.Component {
    handleClick(e) {
        alert('click')
    }
    render() {
        return <div onClick={this.handleClick}></div>
    }
}

注意点

  • 驼峰格式属性名
  • 值为事件处理器而非字符串
  • v14以后版本必须使用e.preventDefault()显示阻止浏览器默认事件而非return false

基本原理

概述

通过事件委托合成事件配合事件池批处理队列机制实现Event System

绑定与注册

所谓绑定,即建立事件处理器与实际Dom的关联。 所谓注册,即push事件处理器到关联Event Type的执行(callback)队列中等待派发。 而在React中,绑定与注册过程需要经过如下步骤,分别为:

  1. React Component在mountComponent以及updateComponent阶段都需要执行_updateDOMProperties处理JSX属性,然后遍历其属性并识别出事件属性
  2. (由于所有事件绑定在document上)通过ReactEventListener类的enqueuePutListener原型方法将事件进行注册,由Event Type与React对象进行了二维划分
  3. 通过EventPluginHub类的putListener原型方法储存事件等待派发

触发与dispatch

所谓dispatch,即依次执行对应Event Type相关的回调方法 而React自身实现一套捕获/冒泡机制并使用批处理的方式进行事件dispatch,关键步骤如下:

  1. 用户触发绑定在document上的TopLevel Event,执行ReactEventListener类dispatchEvent原型方法,将“bookKeeping”(可以理解为申请执处理)方法push到React内置的批处理队列中等待执行
  2. 当批处理tick到达,执行ReactBrowserEventEmitter.handleTopLevelImpl原型方法,该方法获取Dom与React对象,并且找到该React对象的祖先对象生成数组,从当前React对象向祖先遍历从而实现冒泡,并执行相同事件类型callback(也就是事件处理器)
  3. 执行ReactBrowserEventEmitter.handleTopLevel,通过合成事件构造器构造或者被析构后的事件对象重用两种方式生成合成事件(不同事件类型合成事件不一样),然后进入批处理队列等待执行
  4. 当批处理tick到达,遍历events,顺序执行event,然后遍历event相关的listeners,for循环依次执行,如果遇到stopPropagation则break,否则执行事件处理函数

SyntheticEvent

概念

对浏览器原生事件作一层封装的增强型事件对象,称为合成事件。 注意

  • 合成事件被Proxy封装
  • 原则上不建议和原生事件混用

优势

  • 兼容性。抹除IE与W3C标准差异,并提供与原生相同的API
  • 便捷性。提供onCopy等组合后事件,另外提供onClickCapture在捕获阶段执行事件处理器
  • 效率性。事件载体复用。

其他

Event System通过事件池与批处理机制对事件执行进行了性能优化,但并不见得比原生Event System性能好

事件池

概念

用于存放被析构的event栈。

如何形成

每次event对象使用完毕后(未使用event.persist()情况下)在release阶段生成并push至event栈。

如何利用

React内部在处理事件返回值时,分两种情况。

  1. 使用event.persist(),强制使用nativeEvent,此时不会使用事件池来优化。
  2. 不调用时,React返回一个合成事件。先判断事件池中是否存在可用的event实例对象,如果有则重用该实例并通过EventConstructor构造出新的合成事件后返回给事件处理器,反之则使用new EventConstructor()构造全新合成事件,并在使用完毕即进入release阶段,该阶段React将析构该合成事件后把event壳的引用push到事件池中等待复用。

  • 工作原理
    • SSR优劣势
    • 详细构建过程
    • 运行流程概述
  • 实践
    • 工程目录结构
    • 同构中的差异
    • 踩坑

工作原理

SSR优劣势

  • 优势
    • 首屏渲染加快(相当于省去client端fetch + load bundle并且初始化vue的时间)
    • 支持SEO

构建过程

  • 入口文件二:entry-server。需要依次作如下逻辑处理
    • 新建vue、vue-router、vuex实例(注意不能复用实例)
    • app挂载router和store插件
    • router根据当前请求即req.url,根据路由匹配到Components,如果存在asyncData则异步获取所有数据后resolve app实例
    • registerModule并存在state时,将state挂载到context.state下,并由renderer将其序列化
    • renderer可以在HTML中收集并内联组件css,并自动注入首屏关键css(critical css)
    • renderer通过app实例render为HTML string,并返回给client端
  • 入口文件一:entry-client。需要依次作如下逻辑处理
    • 同样的,初始化app实例并挂载router,store插件
    • 当路由onReady后,挂载<div id="app" data-server-rendered="true">...</div>真实DOM,由于此时通过data-server-rendered已知晓由服务端渲染完毕,因此客户端走激活(hydrate)流程而不是重新渲染替换节点#app
    • 另外数据预取逻辑和服务端略有不同,我用的是按需加载策略也就是通过mixin混入beforeMount生命周期去预取数据。为了防止double-fetch数据,这里需要作出调整即在挂载app之后再混入。
  • 然后webpack通过不同入口分别打包client-manifest.json以及server-bundle.json,应用于renderer
  • 其中client-manifest主要用于标记程序依赖chunk,server-bundle中则存储并映射打包后所有的bundle用于渲染。renderer具有程序的所有构建信息,因此它可以自动推断和注入资源prefetch与preload指令,以及css、javascript标签到HTML中。与此同时,为避免client瀑布式请求(waterfull request)以及达到最快的TTI(Time-To Interactive),只有应用程序首屏依赖的chunk会以preload形式注入,暂未使用的异步chunk通过prefetch形式注入。

运行流程概述

  • server端负责渲染首屏的工作比如预取数据,全局state初始化(不包括actions,mutations以及getters),直出首屏完整DOM
  • client端负责hydrate(激活)SPA,比如store replaceState,注册store模块

实践

工程依赖的核心库版本概览

"vue": "^2.5.16",
"vue-router": "^3.0.1",
"vue-server-renderer": "^2.5.16",
"vuex": "^3.0.1"
"vue-loader": "^15.2.4",
"vue-style-loader": "^4.1.0",

工程目录结构

请移步我的github查看(欢迎star)

可执行命令

  • npm run dev
  • npm run serve
  • npm run serve:watch
  • npm run build:client-standalone
  • npm run build:client-analyze

同构中的差异

  • server端和client端渲染不同页面时在各自的生命周期中(server是beforeCreated and created,client更关注mounted and more life cycles)可能需要处理相同的事情,比如网页title和通用头部管理等

用全局Mixin或者Class继承来做。个人更倾向于全局Mixin,因为开发时少写冗余代码,但因此损失一点运行时性能比如内存和计算力

踩坑

  • TypeError: Cannot read property ‘render’ of undefined

最终发现在webpack.base.conf.js中output.filename后面带了hash导致可能在生成server-bundle.json的时候匹配不到该chunk导致json文件构建失败,从而导致vm为undefined,最终抛出以上错误信息。解决方案是把该配置项放在webpack.client.conf.js与webpack.server.conf.js中分别配置

  • loader执行顺序从后到前

也就是说不管是字符串style-loader!css-loader!stylus-loader还是数组形式['style-loader', 'css-loader', 'stylus-loader'],执行顺序都是从stylus-loader -> css-loader -> style-loader

  • style-loader中依赖window,导致ssr失败

用vue-style-loader代替

  • 服务端渲染开发时做hmr有坑

  • 当在ssr工程中引入standalone时需要做一些处理比如

如果是standalone模式则挂载前mixin预取数据的生命周期钩子用于client fetch asyncData,如果是ssr-client模式则采用挂载后再mixin此钩子的策略,为防止client hydrating出现double-fetch的情况

  • 核心工作原理
    • 模块解析过程(Module Resolve)
    • 模块对象(Module Object)
    • 模块包装(Module Wrapper)
    • 缓存(Cache)
    • 循环引用(Cycle)

核心工作原理

======

模块解析过程(Module Resolve)

1
require('x')
  1. x是node核心模块(如http,zlib等)则返回,否则继续
  2. 根据Module对象的paths属性一直递归找node_modules文件夹下是否存在该模块,直到根目录,否则抛出Error(‘MODULE_NOT_FOUND’)
  3. x是路径(如/path/to/file)
    3.1 尝试LOAD_AS_FILE(如.js,.json,.node),没有则继续
    3.2 尝试LOAD_AS_DIR(如文件夹下package.json),没有则抛出Error(‘MODULE_NOT_FOUND’)

LOAD_AS_FILE:

  1. .js
  2. .json
  3. .node文件(编译好的二进制node插件)

LOAD_AS_DIR:

  1. X/package.json中的main字段作为模块入口文件
  2. index.js
  3. index.json
  4. index.node

模块对象


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Module {
id: '.',
exports: {},
parent: null,
filename: '/Users/wl/Sites/myapp/node-learning/src/module/index.js',
loaded: false,
children:
[
Module
{
id: '/Users/wl/Sites/myapp/node-learning/src/module/a.js',
exports: [Object],
parent: [Circular],
filename: '/Users/wl/Sites/myapp/node-learning/src/module/a.js',
loaded: true,
children: [Array],
paths: [Array]
}
],
paths:
[
'/Users/wl/Sites/myapp/node-learning/src/module/node_modules',
'/Users/wl/Sites/myapp/node-learning/src/node_modules',
'/Users/wl/Sites/myapp/node-learning/node_modules',
'/Users/wl/Sites/myapp/node_modules',
'/Users/wl/Sites/node_modules',
'/Users/wl/node_modules',
'/Users/node_modules',
'/node_modules'
]
}
  • id 模块id。通常为模块文件绝对路径,主模块通常为.
  • exports 模块导出对象。这里的exports指的是module.exports
  • parent 父模块。即被依赖模块
  • filename 模块文件名。通常与id相同
  • loaded 模块加载状态。是否已执行完成
  • children 子模块。即依赖模块
  • paths 模块查找路径数组。

模块包装(Module Wrapper)

主要由以下两点考虑

  • 使模块内定义的顶层变量限制在方法(也就是wrapper或者说模块内部)级作用域中,防止污染global环境

注:建议启用’use strict’模式,防止定义全局变量

  • 传入module, require有利于实现node模块化机制
1
2
3
(function(exports, require, module, __filename, __dirname) {
// Module code
});

缓存(Cache)

在一个node上下文环境中,两次require同一个文件,通常情况下返回完全相同的两个对象引用。除非用高阶函数返回工厂函数。

循环引用(Cycle)

由于node包相互依赖,则较大可能会形成循环引用,node利用其缓存机制避免无限循环。比如 index.js

1
2
3
4
5
const prefix = '主模块:'
const a = require('./a.js')
console.log(prefix, a) // {a:2}
console.log(prefix, require.main === module)
console.log(module)

a.js

1
2
3
4
5
6
7
'use strict'
const prefix = 'A模块:'
module.exports = {a:1}
const b = require('./b.js')
console.log(prefix, b) // {b:1}
module.exports = {a:2}
console.log(prefix, require.main === module)

b.js

1
2
3
4
5
const prefix = 'b模块:'
module.exports = {b:1}
const a = require('./a.js')
console.log(prefix, a) // {a:1}
console.log(prefix, require.main === module)

如上。当b.js引用a.js时,为避免无限循环,a.js未完成的副本(我认为是require(‘./b.js’)之前的所有代码,但是在官方文档中未得到特别确切的表述)导出的对象被b.js引用

  • 基本用法
  • GraphQL概述
    • GraphQL基本语法特性
    • GraphQL类型系统
    • GraphQL类型系统内置基础类型
    • GraphQL类型系统内置修饰符
    • GraphQL工作原理
    • GraphQL执行过程
  • Vue工程接入GraphQL

基本用法(如何去用)

package.json

"dependencies": {
    "apollo-server-koa": "^1.3.6",
    "graphql": "^0.13.2",
    "graphql-import": "^0.6.0",
    "graphql-tools": "^3.0.2",
    "koa": "^2.5.1",
    "koa-bodyparser": "^4.2.1",
    "koa-router": "^7.4.0",
    "koa-websocket": "^5.0.1"
},
"devDependencies": {
    "babel-cli": "^6.26.0",
    "babel-preset-env": "^1.7.0"
}

server.js

import koa from 'koa'
import koaRouter from 'koa-router'
import koaBody from 'koa-bodyparser'
import websocketify from 'koa-websocket'
import { graphqlKoa, graphiqlKoa } from 'apollo-server-koa'
import { makeExecutableSchema } from 'graphql-tools'

const app = websocketify(new koa())
const router = new koaRouter()
const PORT = 3000

// fake data
const moments = [
  {
    user: {
      id: 1000,
      name: '锐雯',
      avatar: 'http://imgsrc.baidu.com/imgad/pic/item/42a98226cffc1e17d31927154090f603738de974.jpg'
    },
    main: {
      content: '这是一条朋友圈',
      pics: [
        'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1529219875063&di=bc0bcc78ae800c1c21c198f52697f515&imgtype=0&src=http%3A%2F%2Fimgsrc.baidu.com%2Fimgad%2Fpic%2Fitem%2F4a36acaf2edda3ccd53548ea0be93901203f9223.jpg',
        'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1529219893624&di=8d9e418df27e1fdb6afb1d993801a980&imgtype=0&src=http%3A%2F%2Fimgsrc.baidu.com%2Fimgad%2Fpic%2Fitem%2F3801213fb80e7beca9004ec5252eb9389b506b38.jpg'
      ]
    },
    comments: [
      {
        user: {
          id: 233122,
          name: '亚索'
        },
        reply: '面对疾风吧'
      }
    ]
  }
]

const typeDefs = `
  type Query {
    moments: [Moment]
  }
  type Mutation {
    addComment(
      entity: Add_Comment
    ) : Comment
  }
  type Moment {
    user: User
    main: Main
    comments: [Comment]
  }
  type User {
    id: Int
    name: String
    avatar: String
  }
  type Comment {
    user: User
    reply: String
  }
  type Main {
    content: String
    pics: [String]
  }
  input Add_User {
    id: Int
    name: String
  }
  input Add_Comment {
    user: Add_User
    reply: String
  }

  # 定义graphqlf服务哪个是RootQuery以及RootMutation
  schema {
    query: Query
    mutation: Mutation
  }
`

const resolvers = {
  Query: {
    moments () {
      return moments
    }
  },
  Mutation: {
    addComment (_, { entity }, unknown, context) {
      console.log(entity)
      moments[0].comments.push(entity)
      return entity
    }
  }
}

const schema = makeExecutableSchema({
  typeDefs,
  resolvers
})

// koaBody is needed just for POST.
router.post('/graphql', koaBody(), graphqlKoa({ schema: schema }))
// router.get('/graphql', graphqlKoa({ schema: schema }))

router.get('/graphiql', graphiqlKoa({ endpointURL: '/graphql' }))

async function responseMiddleware(ctx, next) {
  ctx.set('Access-Control-Allow-Origin', 'http://localhost:8080')
  ctx.set('Access-Control-Allow-Methods', 'POST,OPTIONS')
  ctx.set('Access-Control-Allow-Headers', 'authorization,content-type')
  // ctx.set('Access-Control-Allow-Credentials', 'true')
  await next()
}

app.use(responseMiddleware)
app.use(router.routes())
app.use(router.allowedMethods())

app.ws.use(responseMiddleware)
app.ws.use(router.routes())
app.ws.use(router.allowedMethods())

app.listen(PORT)

GraphQL概述

GraphQL基本语法特性

包括有fields,alias,arguments,fragments,variables,directives,inline fragments

  • field

GraphQL类型系统

主要由RootQuery + RootMutation两种入口类型(操作)加上RootValue(resolvers)构成GraphQL Schema。(此处用graphql-tools是为了将所有的类型定义在一个字符串中,后续会移到一个.graphql文件中,然后用graphql-import导入)

GraphQL类型系统内置基础类型

  • 标量类型(Scalar Types)
    • Int: 有符号的32位整数
    • Float: 有符号双精度浮点值
    • String: UTF-8字符序列
    • Boolean: true or false
    • ID:ID 标量类型表示一个唯一标识符(类似一种UUID),通常用以重新获取对象或者作为缓存中的键。ID 类型使用和 String 一样的方式序列化。
  • 枚举类型(Enumeration Types)

是一种特殊的标量类型

enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}
  • 数组类型(Array Types)

用方括号[]标记列表

  • 接口类型(Interface Types)

是一种抽象类型,与java的interface机制类似。

  • 联合类型(Union Types)

    {
    search(text: “an”) {

    ... on Human {
      name
      height
    }
    ... on Droid {
      name
      primaryFunction
    }
    ... on Starship {
      name
      length
    }

    }
    }

  • 输入类型(Input Types)

与之前提到的所有Types对立,这是一种也是唯一一种输入类型,其主要用于mutations时传递整个对象的case,它没有参数。

内置修饰符

  • !: 表示非空。如下

    query DroidById($id: ID!) {
    droid(id: $id) {

    name

    }
    }

GraphQL工作原理

GraphQL中每个查询字段是返回子类型的父类型函数。每个类型的字段对应由一个resolver函数支持,当字段被执行时,响应的resolver被调用并return结果。 如果该字段为标量类型值,比如字符串或数字,则执行完成。否则递归执行对应解析器直至结果为标量类型值。

GraphQL基本数据流

每个GraphQL服务端应用的顶层必定会有一个入口点,通常为Root或者Query类型,接着执行该字段预设的解析器(同步或异步),而每个字段被解析的结果被放置在键值映射中,字段名(或别名)作为键,解析器的值作为值,这个过程从查询字段的底部叶子节点开始返回,直到Query类型的起始节点,最后生成镜像查询结果返回给客户端

Vue工程接入GraphQL

安装vue-cli3.x

npm i -g @vue/cli

初始化工程

vue create [project-name]

引入apollo插件

cd [project-name]
vue add apollo

FriendCircle.vue

<template>
  <div>
    <div v-for="(item, index) in moments" :key="index">
      { {item}}
    </div>
    <input type="text" v-model="comment.reply" placeholder="请输入要回复的内容">
    <button @click="addComment">回复</button>
  </div>
</template>

<script>
import gql from 'graphql-tag'

const QUERY_LIST = gql`
  query {
    moments {
      user {
        id
        name
        avatar
      }
      main {
        content
        pics
      }
      comments {
        user {
          id
          name
        }
        reply
      }
    }
  }
`

export default {
  data () {
    return {
      moments: [],
      comment: {
        user: {
          id: (Math.random() * 10000).toFixed(0),
          name: '费德提克'
        },
        reply: ''
      }
    }
  },
  apollo: {
    moments: {
      query: QUERY_LIST
    }
  },
  methods: {
    addComment () {
      this.$apollo.mutate({
        mutation: gql`
          mutation addComment($comment: Add_Comment) {
            addComment(entity: $comment) {
              user {
                id
                name
              }
              reply
            }
          }
        `,
        variables: {
          comment: this.comment
        },
        update: (store, { data: { addComment } }) => {
          // Read the data from our cache for this query.
          const data = store.readQuery({ query: QUERY_LIST })
          // set first moment's comment
          data.moments[0].comments.push(addComment)
          // Write our data back to the cache.
          store.writeQuery({ query: QUERY_LIST, data })
        }
      })
    }
  }
}
</script>

涉及的知识点有Root_Query,Root_Mutation,variables以及store cache

cache

其核心机制包括以下两点

  1. 对所有(包括嵌套的)非标量类型递归进行缓存,往往通过类型id或_id以及__typename唯一组合标识,然后在一个扁平的数据结构中存储
  2. 可以设置不同缓存策略:cache-and-network,no-cache,network-only

update回调

this.$apollo.mutate(options) options中有一个update回调,在成功响应数据后触发,并且可以直接读取并操作由apollo-cache-inmemory生成的store。上述例子中使用此回调同步更新缓存以及UI

注:所有绑定的变量均不可直接修改,内部使用Object.freeze将对象冻结,无法直接增删。

nodejs - Non-Blocking I/O Model - Event Loop - Event-Driven - 基本架构 - 何为阻塞 - 代码执行时 - 阻止事件循环的几个维度 - Worker Pool - npm模块的风险

Non-Blocking I/O Model

non-blocking是指node.js进程中不同步等待执行非javascript操作(例如I/O)完成而继续执行下一块代码的特性。

注:CPU密集型属于javascript操作。

I/O通常指与磁盘网络的交互 非阻塞I/O模型使得nodejs支持高并发且非常适合于I/O密集型应用

Nodejs Event Loop and Worker Pool

共6个阶段

  • timers setTimeout与setInterval回调函数队列
  • pending callbacks 会在下一次loop中执行的系统级回调队列。如TCP ECONNREFUSED -idle,prepare 内部使用
  • poll 接收新的I/O事件。执行I/O相关回调。在这个阶段node进程可能会阻塞
  • check setImmediate回调会在这个阶段执行
  • close 一些关闭的回调。比如connect.on('close', () => {....})

注:process.nextTick不属于任何一个阶段,它是介于任意两个阶段之间,并且在阶段切换时执行nextTick回调

Event-Driven

基本架构

  • nodejs事件驱动架构中有两种线程:事件循环线程(Event Loop)以及工作线程池(worker pool)
  • Event Loop负责编排客户端请求而后调度Worker Pool处理CPU密集型任务

注:因此nodejs并不是纯粹的单线程语言!

何为阻塞

  • 如果Event Loop执行回调或worker执行任务需要很长时间,即为阻塞。当发生阻塞时,主要会有两点需要考虑:
    • 性能:如果某worker线程定期执行heavyweight任务,会影响服务吞吐量(请求/秒)
    • 安全性:假设某些输入会引起程序阻塞,则存在被恶意客户端利用并攻击的风险。即拒绝服务攻击。

代码执行时

  • 在Event Loop中同步执行常规的变量、方法的定义与调用,javascript所有回调以及非阻塞异步I/O如网络I/O
  • Worker Pool是libuv(线程池工作调度的c++库)在Worker Pool中异步执行“昂贵”繁重的任务。node提供非阻塞I/O(操作系统不提供)API,以及CPU密集的I/O API
    • I/O密集型API:
    • DNS: dns.lookup()
    • fs: fs.readFile(),除了那些显示说明同步的方法
    • CPU密集型API:
    • crypto: crypto.pbkdf2()
    • zlib: 除了那些显示说明同步的方法

Event Loop实质

抽象来说,Event Loop维护挂起事件的队列,Worker Pool维护挂起任务的队列。 实际上,Event Loop并不是维护一个队列。而是一个文件描述符的集合,这些文件描述符从系统级事件通知机制获取比如epoll(linux),kqueue(OSX),IOCP(Windows)。这些文件描述符对应于某些网络套接字以及node正在监视的文件等等。当某个描述符准备好时,Event Loop会将其转换为合适的事件并执行对应的回调。 另外,Worker Pool维护的是一个真正的队列。Worker会pop出队列的task并执行,完成后会触发Event Loop“至少一个事件已完成”的事件。

阻止事件循环的几个维度

  1. 数据处理流程中是否包含计算复杂度高的任务,比如使用CPU密集型Node API比如crypto,fs,zlib,child-process(分区处理与offload to Worker Pool)
  2. ReDoS攻击,检查是否存在易受攻击的正则表达式(使用安全正则表达式库做安全校验
  3. 是否在主线程中使用JSON.parse以及JSON.stringify(潜在风险,因此也建议offload给Worker Pool)

Worker Pool

nodejs默认的Worker Pool专门用于处理I/O任务,维护自己的线程池可以使用cluster模块以及child_process模块做自定义线程池。 Node服务器的吞吐量取决于WorkerPool的吞吐量。有效降低逐个任务时间开销以及稳定任务时间开销的变化将最大程度提升服务器的吞吐量。最常见的方法就是复杂重复型任务(比如数组迭代)做分区处理。

注:由于调度Worker Pool会增加额外的通信开销,因为Worker Pool无法获取主线程的命名空间从而无法直接读取Javascript对象,所以需要序列化/反序列化导致增加通信成本。

npm模块的风险

npm生态系统中存在数十万个模块为开发者提供了极大的便利,然而社区中npm包良莠不齐,因为无法较为准确的估计其使用Event Loop或者Worker Pool的成本而导致一些程序隐患。

四个维度

  • 系统级(尽可能提高网站的可访问性以及可用性)
    • 服务器
    • 域名申请、备案以及解析
    • 全站HTTPS支持
    • 流量预估
    • 静态资源CDN加速
    • web server选型(nginx,apache,node)
    • BFF Or GraphQL
  • 工程级(提高工程框架易用性以及可伸缩性)
    • 前端框架选型(vue,react)
    • 工程中三方依赖库分析(包含UI库)
    • 工程化构建工具选型(webpack,parcel,rollup)
    • 工程复杂度分析,一般以页面数量来定义。如果足够复杂建议使用ts增强系统稳定性
    • 国际化支持
    • 图标库引入
  • 业务级(通过业务梳理、系统划分、共性抽象,提高系统完整性以及可拆分性)
    • 系统分析。根据业务拆分系统对应不同endpoint。
    • 页面分析。根据业务需求划分页面。
    • 前端路由管理,根级路由做redirect,一级路由做UI壳,二级路由做具体业务,三级路由做子业务。不超过三级。
    • 数据源管理。分析工程中依赖数据及操作,xhr工具类定义,以及数据获取方法定义。
    • 全局状态管理。先梳理业务逻辑所需数据结构,而后分析可能触发状态更改的action,然后分析可能同步到页面的getter。
    • 公共组件封装。分析工程中可重用结构,抽象后定义组件。
    • 公共样式提取。分析工程中可重用样式,抽象后定义公共样式(如mixin、原子类、基本变量)。
    • 公共行为混入。分析工程中可重用行为,抽象后定义行为。
    • 帮助类定义。分析工程中工具方法的使用场景,比如merge等等。
    • 过滤器定义。分析工程中过滤器的使用场景,比如状态、类型、时间戳等等的格式化。
  • 代码级
    • 代码规范工具选择(prettier,eslint)
    • 实现数据源
    • 实现全局状态管理
    • 实现公共组件,公共样式,公共混入,帮助类,过滤器
    • 实现页面

javascript - scope chain - context(this指向) - excution context

scope chain

即执行环境(execution environment) 即一条执行时才会建立的定义时已经决定了(liao)的链。

context(this指向)

this === context

var name = '亚索'
// 1-------------------------------------------------------
var obj = {
    name: '锐雯',
    readName: function() {
        console.log(this.name)
    }
}
obj.readName()
// bind用法
obj.readName.bind(this)  //invalid
var otherFn = obj.readName
otherFn.bind(this)
otherFn()
// call用法
obj.readName.call(this)
// apply用法
obj.readName.apply(this)
// 2--------------------------------------------------------
function readName() {
    (function ad() {
        console.log(this.name)
    })()
}
readName()
// 3-------------------------------------------------------
function Person(name) {
    this.name = name
}
console.log(new Person(name).name)
// 4-------------------------------------------------------
var obj2 = {
    readName: () => {
        console.log(this.name)
    }
}
obj2.readName()
obj2.readName.call(this)

结果请复制到控制台执行后查看结果

注:箭头函数其实没有自己的this指针,它只是一个语法糖

箭头函数
var bbb = {
    name: '锐萌萌',
    normalFn: function b () {
        return () => {
            console.log(this.name)
        }
    },
    arrowFn: function b () {
        let _this = this
        return function () {
            console.log(_this.name)
        }
    }
}

excution context

我们理解JS引擎时所了解的栈即为执行环境栈。比如我们用语言模拟一段脚本执行到function后发生的一系列动作。 创建阶段(creation phase)

  1. 创建一个变量对象(也称活动对象)常常由定义在当前执行环境中的内部变量、内部function以及参数组成,且仅能被解析器访问
  2. 初始化作用域链
  3. this指针被最终确定
  4. 推入堆栈并获取控制权

执行阶段(execution phase)

  1. 代码在当前执行环境中执行,执行完成后pop出当前执行环境,并将控制权移交给上一级执行环境

主题

  • 从请求头与响应头理解浏览器网络相关机制
  • 从渲染流程理解浏览器渲染相关机制

请求头与响应头

connection与proxy-connection(即将被http2机制取缔)

常用于双端。该指令指定tcp连接行为,关闭或建立长链接。connection: keep-alive(由于其他值如close, upgrade场景受限,不考虑),表示此http(tcp)链接需要keep,另外也说明该客户端支持长链接。服务端接收到该http请求之后,如果支持则会建立长链接,并设置超时时间,在响应头中会增加connection: keep-alive以及keep-alive: true两个键值对。

注:此超时关闭行为类似前端常用名词中的debounce行为,即仅当请求间隔超过timeout时才会close connection

cache

常用于双端。该指令指定浏览器的存取缓存行为。cache-control指令有很多用法,这里介绍几种常用的,比如no-store, no-cache, must-revalidate, public, max-age= 常用于ajax请求(响应头)

  • no-cache 禁止get缓存,并携带验证器向原始服务器发起请求
  • no-store禁止set缓存
  • must-revalidate 强制用户在使用缓存之前验证旧资源的状态,并且不可使用过期资源

常用于静态文件(响应头)

  • public 表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存。
  • max-age=<seconds> 设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒)

仅用于客户端。该指令指定客户端部分身份信息。可通过withCredentials控制浏览器是否携带cookie字段。

content-encoding与accept-encoding

仅用于服务端。content-encoding应用于响应头,与请求头accept-encoding配套使用。该指令指定传输内容编码方式。 常用于所有类型请求

  • gzip 使用(服务端)或支持(客户端)gzip压缩
  • deflate 使用(服务端)或支持(客户端)zlib压缩与deflate压缩

用于特殊请求

  • identity 未经修改或压缩

content-length

常用于双端。该指令指定传输内容body长度。

content-type

常用于双端。该指令指定body MIME类型

accept

仅用于客户端。该指令指定客户端支持的MIME类型

accept-language

仅用于客户端。该指令指定客户端支持的语言。

  • zh-CN,zh;q=0.9,en;q=0.8

q表明接受语言的权重,不写默认为q=1。

host

仅用于客户端。该指令指定客户端主机。如:www.google.com

origin

仅用于客户端。该指令指定客户端源。如:http://www.google.com

referer

仅用于客户端。该指令指定客户端origin + path。常用于防盗链,因为浏览器不允许js修改(排除某些低级浏览器)。如:http://www.google.com/document

user-agent

仅用于客户端。该指令指定客户端代理描述符。

PWA

what?

  • Progressive Web App
  • 匹敌Native APP的沉浸式全屏web应用,支持offline、push、硬件感知等功能
  • Chrome团队希望用一个新的buzzword(流行词)来影响、改变用户的期待

why?

  • 更方便的安装方式
  • 更小的应用体积
  • 更快的开启速度

problem?

  • 国内iphone使用者多,不支持PWA
  • 国内Android被各大手机厂商定制化,支持PWA对收益关系不大
  • 移动端Chrome使用人数较少
  • 依赖GCM的通知推送
  • 须各大移动端浏览器支持

how?

  • Service Workers 配合 Cache API实现离线应用
  • PUSH API实现推送通知
  • Add to HomeScreen
  • Hardware API实现硬件感知
  • Web Manifest辅助实现离线应用
  • HTTPS是数据安全保证

不废话了!!来干货

AMP