Code Monkey home page Code Monkey logo

zchange_blog's People

Contributors

zchanges avatar

Watchers

 avatar  avatar

zchange_blog's Issues

发布订阅模式ES6实现

发布订阅模式ES6实现

举个例子:微信订阅一个公众号,当公众号发布文章时,只要公众号发布文章,订阅者会第一时间接受到消息。(一对多的关系,对象发生变化,所有订阅者都会被通知到)
subscriber

思路:

  1. 定义一个缓存Map对象存储所有订阅对象(_subscribers)
  2. 定义_index下标,可订阅同一个主题多次,而不会被覆盖
  3. on方法存储订阅者和回调至缓存列表中(可多次订阅一个主体),返回订阅者名称和对应key值
  4. emit发布消息,执行对应订阅者回调,通过Map.get获取到
  5. destroy销毁指定订阅者,通过Map.delete直接销毁订阅者
  6. remove删除,清空所有缓存订阅者信息。

采用es6中Map更方便的处理订阅者,代码的减少以及可读性。

项目中哪里可以用到

  1. 全局广播消息
  2. 框架内跨组件之间的通讯

优点

可以用于异步编程(其中执行回调这一阶段和Promise的实现一致)
完全解耦出两者的关系,发布者只需要关注何时发布消息,订阅者只需订阅即可

缺点

缺点:如果消息一直没有发生,订阅者会一直存在内存中。

具体实现

class Event {
  constructor() {
    this._subscribers = new Map();
    this.__index = 0;
  }

  /**
   * 将订阅者信息存入list
   * @param {String} eventName 事件名称
   * @param {fn} callback 订阅回调
   * 通过Map来存取对应的订阅者
   * 监听同一个主体,下一次的不会覆盖上一次的监听
   * 返回订阅者名称,和对应的下标,可供后面销毁
   */
  subscribe(eventName, callback) {
    if (typeof eventName !== 'string' || typeof callback !== 'function') {
      throw new Error('parameter error')
    }
    if (!this._subscribers.has(eventName)) {
      this._subscribers.set(eventName,new Map());
    }
    // 订阅同一个主题通过_index不会覆盖上一次。
    this._subscribers.get(eventName).set(++this._index,callback);
    return [eventName, this._index]
  }


  on(eventName, callback) {
    return this.subscribe(eventName, callback);
  }

  /**
   * 发布信息
   * @param {String} eventName 订阅者名称
   * @param {any} args 参数
   */
  emit(eventName, ...args) {
    if(this._subscribers.has(eventName)){
      const eveMap = this._subscribers.get(eventName);
      eveMap.forEach((map) =>map(...args));
    }else{
      throw new Error(`The subscription parameter ${eventName} does not exist`)
    }

  }

  /**
   * 销毁对应订阅者
   * @param {String|Object} event 
   */
  destroy(event) {
    if (typeof event === 'string') {
      // 直接销毁对应订阅者
      if (this._subscribers.has(event)) {
        this._subscribers.delete(event)
      }
    } else if (typeof event === 'object') {
      // 通过订阅者名词和下标,销毁其中某一个订阅者
      const [eventName, key] = event;
      this._subscribers.get(eventName).delete(key);
    }
  }

  /**
   * 清除所有订阅者
   */
  remove() {
    this._subscribers.clear();
  }

}

const $event = new Event();
const ev1 = $event.on('aa', (...agrs) => {
  console.log(...agrs);
  console.log(111);
})
const ev2 = $event.on('aa', (...agrs) => {
  console.log(...agrs);
  console.log(222);
})
setTimeout(() => {
  $event.emit('aa', '1', '2');
  $event.destroy();
  $event.remove();
}, 500)

webpack入门

webpack入门

基础配置

  • entry 入口
  • output 出口,输出的文件
  • module 模块,处理模块
  • plugins 插件

入口和出口文件(entry | output)

  • entry 默认为 ./src/index.js
  • output默认为 ./dist/main.js // 必须是绝对路径
  • mode设置模式production(js压缩)development(不压缩) 生产环境和开发环境
  "scripts": {
    "dev": "webpack --mode development ./src/main.js --output ./dist/index.js",
    "build": "webpack --mode production ./src/main.js --output ./dist/index.js"
  },

entry 接受这三种形式的值[字符串、对象、数组]

  • 字符串:正常的入口路径 等同于entry: { main: '入口路径' }
  • 数组:打包数组中的文件可以是插件
  • 对象:key:value key打包后的名称,也可以是路径 、 value字符串:路径或插件名称 数组:[入口文件,插件名称]
const path = require('path')

const ROOT_PATH = path.resolve(__dirname)
const APP_PATH = path.resolve(ROOT_PATH, 'src');
const BUILD_PATH = path.resolve(ROOT_PATH, 'build')

const webpackConfig = {
  entry: APP_PATH,
  // entry: {
  //   index: APP_PATH,
  //   vendors: [APP_PATH,jquery]
  // },
  // entry: [APP_PATH, 'jquery'],
  output: {
    path: BUILD_PATH,
    filename: '[name]-bundle.js'
  }
}

module.exports = webpackConfig

module

设置模块中文件的处理

js设置兼容

  • 安装yarn add babel-core babel-preset-env babel-loader -D
  • babel-core babel的核心库
  • babel-preset-env 将你设置的运行环境下转译代码(env下2015、2016、2017)或设置browsers
  // webpack.config.js
  const path = require('path')
  const ROOT_PATH = path.resolve(__dirname)
  const APP_PATH = path.resolve(ROOT_PATH, 'src');
  const BUILD_PATH = path.resolve(ROOT_PATH, 'build')
  const webpackConfig = {
    entry: ["babel-polyfill", APP_PATH ],
    output: {
      path: BUILD_PATH,
      filename: 'bundle.js'
    },
    module: {
      rules: [{
        test: /\.js$/,
        exclude: /node_modulse/,
        use: {
          loader: "babel-loader"
        }
      }]
    }
  }

  module.exports = webpackConfig

  // .babelrc
  {
   "presets": [
      "env"
   ]
  }

  //package.json
  ...
  "scripts": {
    "dev": "webpack --mode development --module-bind js=babel-loader",
    "build": "webpack --mode production --module-bind js=babel-loader"
  },
  ...

处理css

webpack不会把css提取到单独文件

  • 安装yarn add style-loader css-loader -D
  • 安装yarn add mini-css-extract-plugin -D(4.4.1版本)之前采用extract-text-webpack-plugin(单独提取处css)
<link rel="stylesheet" href="main.css">
<body style='border: 1px solid #000'>
</body>
</html>
<script src="bundle.js"></script>
module: {
    rules: [
      ...
      {
        test: /\.css$/,
        // use: [ 'style-loader', 'css-loader' ]//处理css
        use: [ MiniCssExtractPlugin.loader, 'css-loader']//处理css
      }
      ...
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].css",
      chunkFilename: "[id].css"
    })
  ]

less

  • 安装yarn add less-loader less -D
@import './style.css';
@import './common.css';
@padding: 20px;

body{
  padding-top: @padding;
  div{
    padding-left: @padding * 2;
  }
}
  // index.js
  import './less.less';
  const div = document.createElement('div');
  div.innerHTML = 'webpack';
  document.body.appendChild(div)

  // webpack.config.js
  module: {
    rules: [
      {
        test: /\.css|.less$/,
        use: [ MiniCssExtractPlugin.loader, 'css-loader','less-loader']
      },
    ]
  },

html

生成html,自动引入js css 不需要手动添加

  • 安装yarn add html-webpack-plugin html-loader -D
const path = require('path')
const HtmlWebPackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const ROOT_PATH = path.resolve(__dirname)
const APP_PATH = path.resolve(ROOT_PATH, 'src');
const BUILD_PATH = path.resolve(ROOT_PATH, 'build')

const webpackConfig = {
 
  module: {
    rules: [
      ...
      {
        test: /\.html$/,
        use: [
          {
            loader: "html-loader",
            options: { minimize: true } // 压缩
          }
        ]
      }
      ...
    ]
  },
  plugins: [
    new HtmlWebPackPlugin({
      // title:'webpack-demo5',
      // filename: "./index.html",
      template: "./src/index.html",
      filename: "./index.html"
    }),
  ]
}

module.exports = webpackConfig

开启devServer

  • 安装yarn webpack-dev-server -D
  • --hot 开启热更新

index.html中引入的script路径不是配置出口的地址,是内存路径,不在物理硬盘上,默认是/,也就是<script src="main.js"></script>

<!DOCTYPE html>
<html lang="en">
<head>
  ...
</head>
<body>
  
</body>
</html>
<script src="main.js"></script>
  "scripts": {
    "dev": "webpack-dev-server --mode development"
  },
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')

const webpackConfig = {
  ...
  devServer: {
    inline: true, // 打包后加入一个websocket客户端
    hot: true, // 热更新
    contentBase: BUILD_PATH, // 开发服务运行时的文件根目录
    host: 'localhost',
    port: 9090,
    compress: true // 开启启动gzip压缩
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: './index.html',
    }),
    new webpack.HotModuleReplacementPlugin()
  ],
  ...
}

module.exports = webpackConfig

删除文件

  • 安装yarn add clean-webpack-plugin -D
  ...
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: './index.html',
    }),
    new webpack.HotModuleReplacementPlugin(),
    new CleanWebpackPlugin(['dist']) // 删除dist文件
  ],
  ...

Vue配置

公共部分

  • 入口出口
  • 配置一些loader
  • 定义扩展名
// webpack.base.config.js


const path = require('path')

const ROOT_PATH = path.resolve(__dirname, '../')
const SRC_PATH = path.resolve(ROOT_PATH, 'src')
const DIST_PATH = path.resolve(ROOT_PATH, 'dist')
const MAIN_PATH = path.resolve(SRC_PATH, 'main.js')
const { VueLoaderPlugin } = require('vue-loader');

module.exports = {
  entry: {
    app: MAIN_PATH // 入口地址
  },
  output: { // 出口
    path: DIST_PATH,
    filename: '[name].js'
  },
  resolve: {
    extensions: ['.js', '.vue'], // 自动解析已确定的扩展文件名称
    alias: {  // 设置别名
      '@': SRC_PATH
    }
  },
  module: { // 定义的loader
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        include: [SRC_PATH]
      },  
      {
        test: /\.css|.less$/,
        use: ['vue-style-loader','css-loader','less-loader'],
      }
    ]
  },
  plugins:[
        new VueLoaderPlugin()
        // vue-loader的使用都是需要伴生 VueLoaderPlugin
  ]
}

开发环境

  • 启动服务
  • 自动生成index.html
  • 开发环境的devtool 具体参考
const baseWebpackConfig = require('./webpack.base.config');
const HtmlWebpackPlugin = require('html-webpack-plugin')
const merge = require('webpack-merge');
const devWebPackConfig = merge(baseWebpackConfig,{
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',  
  // http://webpack.css88.com/configuration/devtool.html
  devServer: {
		inline: true,
		progress: true,
    host: 'localhost',
		port: 5050,
    compress: true // 开启启动gzip压缩
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'index.html',
      inject: true
    })
  ]
})


module.exports = devWebPackConfig

生产环境

  • 提取css 可预先加载css
  • 自动生成html 并压缩删除注释等
  • 出口文件添加hash值清理缓存
  • 设置devtool:source-map 生成一个单独的map文件,并在出口文件添加注释 (用来调试)
// webpack.prod.config.js
const path = require('path')
const baseWebpackConfig = require('./webpack.base.config');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin')

const merge = require('webpack-merge');

const prodWebPackConfig = merge(baseWebpackConfig, {
  mode: 'production',
  devtool: 'source-map',
  output: {
    filename: '[name].[chunkhash].js'
  },
  module: {
    rules: [
      {
        test: /\.css|.less$/,
        use: [ MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']//处理css
      },
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].[chunkhash].css",
      chunkFilename: "[id].[chunkhash].css"
    }),
    new HtmlWebpackPlugin({
      filename: path.resolve(__dirname, '../dist/index.html'),
      template: 'index.html',
      inject: true, // script标签位于html文件的 body 底部
      minify: {
        removeComments: true, // 去除注释
        collapseWhitespace: true, // 去空格
        removeAttributeQuotes: true // 移除属性的引号
      },
    }),
  ]
})

module.exports = prodWebPackConfig;

单元测试

单元测试

TDD、BDD的理解

TDD(测试驱动开发)
在开发人员开发功能之前,先编写单元测试用例代码,来推动开发的进行。
目的:不是为了写出大覆盖率的测试代码,而是测试函数逻辑的各种可能性,辅助提高代码的质量.帮助发现错误.

BDD(行为驱动开发)
开发人员和测试产品客户等进行讨论,使用自然的语言去描述,取得预期的软件行为.
目的:让产品运行结果符合预期行为,避免表达不一致带来的问题(整合开发和产品测试)

工具

  • Mocha(测试框架)
  • should.js(断言库)、或采用node自带的assert
  • Karma(自动化单元测试框架)

测试用例

全局安装mocha npm install mocha -g
采用node自带的assert

equal

equal:判断是否相等

  describe('#All', function () {
    it('should return -1 when the value is not present', function () {
      // 判断两个值是否相等
      assert.equal(-1, [1, 2, 3].indexOf(4))
    })
  });

deepEqual || deepStrictEqual

deepEqual废弃采用deepStrictEqual代替:判断深度是否相等(会递归判断子对象是否相等)

  describe('#All', function () {
    it('a和b应当深度相等', function () {
        var a = {
          c: {
            e: 1
          }
        }
        var b = {
          c: {
            e: 1
          }
        }
      assert.deepStrictEqual(a, b)
    })
  });

StrictEqual || notStrictEqual

是否全等 是否全不等

  describe('#All', function () {
    it('is equality', function(done) {
      assert.strictEqual(1, 1);
      assert.notStrictEqual(1, '1');
    });
  });

throws

捕获错误

describe('assert', function () {
  it('可以捕获并验证函数fn的错误', function () {
    function fn() {
      xxx;
    }
    assert.throws(
      fn,
      (err) => {
        if (/xxx is not defined/.test(err)) {
          return true;
        }
      },
      '未捕获到错误'
    );

  })
})

ifError

判断value是否为false,如果为false则通过,如果为ture则抛出信息为value的错误。

describe('assert', function () {
  it('ifError', function () {
    assert.fail();
    // 抛出 AssertionError [ERR_ASSERTION]: Failed
    assert.fail('失败');
    // 抛出 AssertionError [ERR_ASSERTION]: 失败
    assert.fail(new TypeError('失败'));
    // 抛出 TypeError: 失败
  })
})

done()

测试异步代码(Mocha api)
mocha支持Promise,不需要done直接返回Promise即可

  describe('#All', function () {
    it('should save without error', function(done) {
      var user = new User('Luna');
      user.save(function() {
        done();
      });
    });
  });

  it('异步请求应该返回一个对象', function() {
    return fetch('xxxxxx')
      .then(function(res) {
        return res.json();
      });
  });

钩子

mocha提供了钩子函数

  before(function() {
    // 在本区块的所有测试用例之前执行
  });

  after(function() {
    //在本区块的所有测试用例之后执行
  });

  beforeEach(function() {
    // 在本区块的每个测试用例之前执行
  });

  afterEach(function() {
    // 在本区块的每个测试用例之后执行
  });
});

Karma

  • 安装:

npm install karma karma-chrome-launcher --save-dev

  • 初始化
    karma init
1. Which testing framework do you want to use ? (mocha)  选择测试框架
2. Do you want to use Require.js ? (no)是否用Require
3. Do you want to capture any browsers automatically ? (Chrome) 选择浏览器
4. What is the location of your source and test files ? 设置测试文件和依赖文件(https://cdn.bootcss.com/jquery/2.2.4/jquery.js, node_modules/should/should.js, test/**.js)
5. Should any of the files included by the previous patterns be excluded ? 是否应该排除前一种模式所包含的任何文件 不太懂直接忽略吧
6. Do you want Karma to watch all the files and run the tests on change ? (yes) 是否监听文件是否变化,试试刷新
  • 运行测试
karma start

Travis CI

Travis CI 是在软件开发领域中的一个在线的,分布式的持续集成服务,用来构建及测试在GitHub托管的代码。

  1. 先用Github登录
  2. 进入profile页面,里面会同步你github上所有的项目,选择你需要自动化的项目
    travis
  3. 在项目根目录添加.travis.yml文件,添加配置,提交到github上就可以自动构建了
    travis1

配置Travis

language: node_js // 设置语言
node_js:          // 设置node版本
  - "9.3"
addons:           // 扩展 
 chrome: stable   // 扩展chrome,构建安装chrome(stable稳定版本也可以设定版本)
before_script:    // xvfb 模拟显示,运行界面测试 https://docs.travis-ci.com/user/gui-and-headless-browsers/#Using-xvfb-to-Run-Tests-That-Require-a-GUI
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
git:              // Git克隆深度
  depth: 1        // 如果一个构建在队列,那么在推送新提交时,Travis不会构建队列中的提交。
install:          // 自定义安装步骤
  - npm install -g karma-cli
  - npm install
cache:            // 缓存node_modules中的包
  directories:
    - "node_modules"

Travis CI上运行需要图形用户界面的测试需要使用xvfb

// https://docs.travis-ci.com/user/gui-and-headless-browsers/#Using-xvfb-to-Run-Tests-That-Require-a-GUI
addons:           // 扩展 
 chrome: stable   // 扩展chrome,构建安装chrome(stable稳定版本也可以设定版本)
before_script:    // xvfb 模拟显示,运行界面测试 
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start

在使用Travis跑Karma的时候设置chrome启动器

安装npm install karma-chrome-launcher --save-dev
使用Karmacustomlaunchers插件,您可以将其添加

 ....
  browsers: ['Chrome','Chrome_without_security'],
  customLaunchers: {
    Chrome_without_security: {
      base: 'Chrome',
      flags: ['--disable-web-security']
    }
  }
 ...

在使用Travis跑Karma的时候设置只运行一次

-package.json 设置"test": "karma start --browsers Chrome,Chrome_without_security --single-run"

或者在karma.conf.js中设置

....
  let configuration = {
    ...
  }
  if (process.env.TRAVIS) {
     configuration.singleRun = true;
  }
  config.set(configuration);
...

react

React

tags: React


flux

Jq模式

在一个文件中进行操作dom,逻辑判断,获取数据然后重新渲染页面。

  • 优点:简便的通过一些api去操作dom,方便快捷,易于理解
  • 缺点:都集中一个文件,当功能复杂下,代码杂乱不能快速定位问题,不易于维护,结构复杂。

MVC

MVC: M(数据层) V(控制层) C(视图层)
把整个系统分层,M数据层、V视图层、C控制层。每个层相互独立,互不影响,每一层都向外开放一些接口(interface),供其他层去调用,就实现了模块化。

  • 优点:结构明了,一层调用另外一层方法,不需要知道内部实现,只需要能得到结果,相互不影响,单一职责。(如一接口地址改变,只需要在M层中修改对应地址即可,其他层无需改变)
  • 缺点:当项目庞大时,难以扩展。视图和模型可能出现双向数据流,数据流错综复杂,不可预测性,难以调试维护

Flux模式

view(视图层)、Active(动作)、Dispatcher(派发器)、Store(数据层)。
用户访问view,触发事件,发出active,Dispatcher接收到后,要求store对应更新,发出change事件,view接受到change事件触发更新。

  • 限制View和Model之间直接回话,如要改变view必须通过active派发dispatcher,杜绝了数据混乱,单项数据流。

Flux例子

// ActionTypes.js
// 声明动作类型
export const INCREMENT = 'increment';
export const DECREMENT = 'decrement';

//AppDispatcher.js
import { Dispatcher } from 'flux';
export default new Dispatcher();

// Active.js
// 执行的动作,然后通过dispatcher去派发
import AppDispatcher from './AppDispatcher.js';
import * as ActionTypes from './ActionTypes';
export const increment = (counterCaption) => {
    AppDispatcher.dispatch({
        type: ActionTypes.INCREMENT,
        counterCaption: counterCaption
    })
}

export const decrement = (counterCaption) => {
    AppDispatcher.dispatch({
        type: ActionTypes.DECREMENT,
        counterCaption: counterCaption
    })
}
// CounterStore.js
import { EventEmitter } from "events";
import AppDispatcher from '../AppDispatcher';
import * as ActionTypes from '../ActionTypes';
// listener type值
const CHANGE_EVENT = 'changed';
// 初始化参数
const counterValues = {
  First: 0,
  Tow: 10
};

//通过Object.assign克隆EventEmitter原型上的方法,
//添加监听方法的,发送消息方法,删除监听方法,获取初始化值。
const CounterStore = Object.assign({}, EventEmitter.prototype, {
  getCounterValues: () => {
    return counterValues;
  },
  emitChange: function() {
    this.emit(CHANGE_EVENT);
  },
  addChangeListener: function(callback) {
    this.on(CHANGE_EVENT, callback);
  },
  removerChangeListener: function(callback) {
    this.removeListener(CHANGE_EVENT, callback);
  }
});

// 登记各种action回调,当dispatch收到ActionTypes动作后就执行相应的回调
CounterStore.dispatchToken = AppDispatcher.register((action) => {
    if(action.type === ActionTypes.INCREMENT){
        counterValues[action.counterCaption] ++;
        CounterStore.emitChange();
    }else if(action.type === ActionTypes.DECREMENT){    
        counterValues[action.counterCaption] --;
        CounterStore.emitChange();
    }
})
export default CounterStore;

1.初始化获取count的值CounterStore.getCounterValues()[this.props.caption]
2.渲染完成后添加监听和添加回调CounterStore.addChangeListener(this.onChange);
3.点击按钮执行动作,并派发Actions.increment(this.props.caption)
4.派发后进入dispatcher.register登记,执行对应的发送消息emitChage()
5.一开始监听的消息被触发,触发后执行回调onChange(),先从CounterStore中获取改变后的count,然后更新this.setState({count: newCount});
整体流程:点击按钮后执行Active => (dispather)派发 => Store对应更新数据(触发change) => 页面监听到更新render

//Counter.js
import React, { Component} from 'react';
import PropTypes from 'prop-types';
import * as Actions from '../flux/Actions';
import CounterStore from '../flux/stores/CounterStore';
class Counter extends Component {
    constructor(props) {
        console.log('constructor');
        super(props)
        this.state = {
            count : CounterStore.getCounterValues()[this.props.caption]
        }
    }

    onClickIncrementButton = () => {
        Actions.increment(this.props.caption)
        // this.setState({ count: this.state.count + 1})
    }

    onClickDecrementButton = () => {
        Actions.decrement(this.props.caption)
        // this.setState({ count: this.state.count - 1})
    }
    
    // 初始化渲染触发
    render() {
        const { caption } = this.props
        return (
         <div>
            <button style={buttonStyle} onClick={this.onClickIncrementButton}> + </button> 
            <button style={buttonStyle} onClick={this.onClickDecrementButton}> - </button> 
            {caption}   {this.state.count}
         </div> 
        )
    }
  
    // 初始化渲染
    componentDidMount() {
        CounterStore.addChangeListener(this.onChange);
    }

    onChange = () => {
        const newCount = CounterStore.getCounterValues()[this.props.caption];
        this.setState({count: newCount});
    }
  }
  
  export default Counter

问题:flux下有多个store,又需要相互依赖就不得不用到register必须要等dispatchToke返回

import { EventEmitter } from "events";
import AppDispatcher from "../AppDispatcher";
import * as ActionTypes from "../ActionTypes";
import CounterStore from "./CounterStore";

const CHANGE_EVENT = "changed";

function computeSummary(counterValues) {
  let summary = 0;
  for (const key in counterValues) {
    if (counterValues.hasOwnProperty(key)) {
      summary += counterValues[key];
    }
  }
  return summary;
}

const SummaryStore = Object.assign({}, EventEmitter.prototype, {
  getSynnary: () => {
    return computeSummary(CounterStore.getCounterValues());
  },

  emitChange: function() {
    this.emit(CHANGE_EVENT);
  },

  addChangeListener: function(callback) {
    this.on(CHANGE_EVENT, callback);
  },

  removerChangeListener: function(callback) {
    this.removeListener(CHANGE_EVENT, callback);
  }
});

SummaryStore.dispatchToken = AppDispatcher.register(action => {
  if (
    action.type === ActionTypes.INCREMENT ||
    action.type === ActionTypes.DECREMENT
  ) {
    // 计算数字总和依赖每个CounterStore的计算,等待dispatchToken后在计算总和。
    // 保证顺序
    AppDispatcher.waitFor([CounterStore.dispatchToken]);
    SummaryStore.emitChange();
  }
});

export default SummaryStore;

生命周期

初始化 首次render(Mounting)

  1. getDefaultProps or getInitialState: 设置组件属性默认值 or 默认状态
  2. constructor
  3. componentWillMount: 组件挂载时执行,只执行一次,可以用setState
  4. render(): 渲染
  5. componentDidMount: 组件(子组件)加载完成后执行,可操作DOM,使用后refs

更新阶段

  1. componentWillReceiveProps: 父组件render后子组件就会调用此生命周期(不管props有没有更新,父子组件数据有没有交换)
  2. shouldComponentUpdate:组件挂在后,每次调用setState setProps后出发,可以通过nextProps和和props来判断是否需要重新render
  3. componentWillUpdate:在shouldComponentUpdate 返回true后触发,不可以使用setState操作;fasle则直接返回不做渲染
  4. render() 渲染
  5. componentDidUpdate: 和componentDidMount声明周期对应(挂载时候只执行一次),每次更新渲染之后会被调用'

销毁阶段

  1. componentWillUnmount 最后组件销毁

react.png-53kB

Redux

redux:
1.唯一的数据源 -- 应用的状态数据保存在一个Store上。如果有多个store又有依赖关系就会增加复杂度容易带来新的问题

 // Actions.js
 // 只需要单纯的返回action,不需要执行dispatch
import * as ActionTypes from './ActionTypes';
export const increment = (counterCaption) => {
    return {
        type: ActionTypes.INCREMENT,
        counterCaption: counterCaption
    }
}
export const decrement = (counterCaption) => {
    return{
        type: ActionTypes.DECREMENT,
        counterCaption: counterCaption
    }
}

// Reducer.js
// 这里只负责返回操作store,不负责存储(存储交给了redux的createStore.js)
import * as ActionTypes from './ActionTypes';
export default (state, action) => {
    const {counterCaption} = action;
    switch (action.type) {
        case ActionTypes.INCREMENT:
            return {...state, [counterCaption]:state[counterCaption] + 1 };
        case ActionTypes.DECREMENT:
            return {...state, [counterCaption]:state[counterCaption] - 1 };
    }
} 

// counter.js
import React, { Component} from 'react';
import store from '../redux/Store';
import * as Actions from '../redux/Actions';

class Counter extends Component {
    constructor(props) {
        super(props)
        this.state  = this.getValue();
        this.onClickIncrementButton = this.onClickIncrementButton.bind(this);
        this.onClickDecrementButton = this.onClickDecrementButton.bind(this);  
        this.onChange = this.onChange.bind(this);    
    }

    onClickIncrementButton () {
        store.dispatch(Actions.increment(this.props.caption)) // redux
    }
    onClickDecrementButton () {
        store.dispatch(Actions.decrement(this.props.caption)) // redux
    }
    
    render() {
        const value = this.state.value;
        const { caption } = this.props;
        return (
         <div>
            <button style={buttonStyle} onClick={this.onClickIncrementButton}> + </button> 
            <button style={buttonStyle} onClick={this.onClickDecrementButton}> - </button> 
            {caption}  {value}
         </div> 
        )
    }
  
    componentDidMount() {
        store.subscribe(this.onChange)
    }

    onChange () {
        this.setState(this.getValue());
    }

    getValue () {
        return {
            value: store.getState()[this.props.caption]
        }
    }
  }
  export default Counter

redux源码探索

  1. createStore(reducer,initValues); // Store.js
    存储reducer、初始化State 第三个参数enhancer暂时不看
export default function createStore(reducer, preloadedState, enhancer){
   ....
    
    var _ref2;
    var currentReducer = reducer; // 获取到reducer
    var currentState = preloadedState; // 初始化state 没有则为undefined
    var currentListeners = []; // 创建一个监听数组
    var nextListeners = currentListeners; //  存储下一次的监听数组(后面会解释)
    var isDispatching = false;
    
    // 拷贝监听数组,解除对象引用
    function ensureCanMutateNextListeners() {
        if (nextListeners === currentListeners) {
          nextListeners = currentListeners.slice();
        }
    }
  ....
  
  return _ref2 = {
    dispatch: dispatch,
    subscribe: subscribe,
    getState: getState,
    replaceReducer: replaceReducer
  }, _ref2[$$observable] = observable, _ref2;
}

优化

  1. 定义key
  2. shouldComponentUpdate生命周期返回false 减少不必要的渲染
    可通过connect(mapStateToProps, mapDispatchToProps) (Commpoent); 来实现,内置判断了是否需要重新渲染
  3. react-redex中对比两个props是通过===来判断的,在两个对象完全一样也可能引用不一样,所以要尽可能避免两个看上去一样但是引用不一样的进行比较。
// 错误
// 每次render都回产生新的对象和函数
render{
       <Header style={color:red} onClick={()=>{……}}/>
}

// 正确
// 保证初始化只执行一次
let headerStyle = {color:red};

clickFn(){……}
render{
       <Header style={headerStyle} onClick={clickFn}/>
}
  1. reselect在state发生变化的时候,为了减少渲染的压力,reselect使用了缓存机制。(只要上一次的state没有发生变化就采用缓存的数据)
    createSelector接收两个参数,通过第一个参数来判断是否采用缓存数据(只要state参数不变就不会调用第二个参数),第二个参数就是计算过滤需要的数据。
// 当数据存储的数据量大时,state不变的情况下没没有必要重新过滤获取数据
// 通过判断传入的state的todos和filter来判断是否需要进入第二个参数中过滤出相应的数据。从而减少没必要的渲染。(在connect中会判断数据源不变的情况下是不会重新render的)

// selector.js
import {createSelector} from 'reselect';
import {FilterTypes} from '../../constants';
const getTodos = (state) => state.todos;
const getFilter = (state) => state.filter;
export const selectVisibleTodos = createSelector(
  [getTodos, getFilter],
  (todos, filter) => {
    switch (filter) {
      case FilterTypes.ALL:
        return todos;
      case FilterTypes.COMPLETED:
        return todos.filter(item => item.completed);
      case FilterTypes.UNCOMPLETED:
        return todos.filter(item => !item.completed);
      default:
        throw new Error('unsupported filter');
    }
  }
)
// todoList.js
const mapStateToProps = (state) => {
  return {
    todos: selectVisibleTodos(state)
  };
}
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);

思维导向

jq方式

  • 获取input的值插入到数组中,再页面中循环数组展现,点击某一条更改type值改变状态,切换状态,通过状态过滤对应数据展现数据。
    用户点击,过滤添加操作数据,然后渲染数据

redux方式

  • 用户点击添加,发出一个动作(active)为addtodo把input值传过去,派遣(dispatch)到Store,
  • Store通过active的type动作,通过reducer中处理后拿到对应的数据,Store再做存储,在通过connect把store传给组件渲染。
  • connect做了这些事:Provider把整个应用包裹住,connect获取到全局的state和dispatch,在用过mergeProps(connect第三个参数不写有默认)合并props传给组件,还做了当数据发送改变组件是否重新渲染(connect建立了react和redux的连接)

查看源码

react-redux

  1. connect(,)()
    connect方法就是吧state、dispath传入connect中然后吧connect包裹住组件,组件就可以通过props获取到connect中的state和dispach。
    Provider是共享全局,而connect是包裹某个组件,传递props;
  2. createStore()
  3. combineReducers({})合并reducr
  4. bindActionCreators({})
  5. Reselect的createSelector 通过selector 缓存数据提高性能

Router

Router下只允许一个节点所以用div包裹下

<HashRouter>
    <div>
        <Route path="/" component={Login}/>
        <Route path="/Chat" component={Chat}/>
    </div>
</HashRouter>

image_1bu0jnod71skc1v5l177d1huafvo9.png-32.7kB


BrowserRouter Or HashRouter

BrowserRouter需要和后端配合,重定向只能到首页,刷新就到404,兼容性低,可采用HashRouter

PropTypes设置类型判断报错

image_1bv4fi1taef115r96ro1hpo1sct25.png-31.4kB

error: TypeError: Cannot read property 'string' of undefined
版本问题:在react 16下PropTypes 独立成一个单独的包prop-types

// React -v 16
import React, { Component ,PropTypes} from 'react';
 Counter.PropTypes = {
    caption: PropTypes.string,
    count: PropTypes.number
 }

安装 year add prop-types -S || npm install prop-types -S
import PropTypes from 'prop-types';

TypeError: __webpack_require__(...) is not a function

const middlewares = [];
if (process.env.NODE_ENV !== 'production') {
  middlewares.push(require('redux-immutable-state-invariant')());
}

// 解决方案
// default();
const middlewares = [];
if (process.env.NODE_ENV !== 'production') {
  middlewares.push(require('redux-immutable-state-invariant').default());
}

tapable之Sync源码分析

tapable之Sync源码分析


Hook(钩子)

const {
    SyncHook,
    SyncBailHook,
    SyncWaterfallHook,
    SyncLoopHook,
} = require("tapable");

所有暴露出来的HOOK都继承与Hook(存储订阅中的回调)和HookCodeFactory(用来自动生成发布函数的工厂)

new SyncHook(['name'])
所有的Hook构造函数都有一个可选的参数,是一个字符串数组
.tap(name, callback) :订阅(监听)
name: 名称
callback:回调函数
.call(...params): 发布
params:传入的参数和构造函数传入的长度保持一致

sync

tapable


SyncHook
不关心监听的函数的返回值
SyncBailHook
监听函数中只有有一个返回不是null则跳过下面所有的监听
SyncWaterfallHook
上一个监听返回值,传至下一个监听函数中
SyncLoopHook
监听函数中如果返回true则重复执行这个监听函数,返回false则退出循环

let queue = new SyncHook(['name','name2']);
// // 订阅
queue.tap('1', function (name, name2) {
    console.log(name, name2, 1);
    return '1'
});

queue.tap('2', function (name) {
    console.log(name, 2);
});

queue.tap('3', function (name) {
    console.log(name, 3);
});

// 发布
queue.call('zz', 'bb');

// zz bb 1
// zz 2
// zz 3

SyncHook源码解析

"use strict";

const Hook = require("./Hook");
const HookCodeFactory = require("./HookCodeFactory");

class SyncHookCodeFactory extends HookCodeFactory {
  content({ onError, onResult, onDone, rethrowIfPossible }) {
    return this.callTapsSeries({
      onError: (i, err) => onError(err),
      onDone,
      rethrowIfPossible
    });
  }
}

const factory = new SyncHookCodeFactory();

class SyncHook extends Hook {
  tapAsync() {
    throw new Error("tapAsync is not supported on a SyncHook");
  }

  tapPromise() {
    throw new Error("tapPromise is not supported on a SyncHook");
  }

  compile(options) {
    factory.setup(this, options);
    return factory.create(options);
  }
}

module.exports = SyncHook;

SyncHook Class

  • 从上面看出tapable 1.x都是用es6进行编写的。
  • 这里先看SyncHook,他继承了hook这个基类。
  • 同时他是不支持tapAsync tapPromise方法,直接调用会抛出错误
  • compile重写了Hook基类中的compile方法。
  • compile中调用了HookCodeFactory基类工厂中的方法,这个后面再看

Hook Class

  • 首先先看tap方法,先把tap传入的入参整合成一个对象(名称,回调,类型:name,callback,sync)存到taps中(其中我精简了些)这个很简单只是把回调及名称进行存储
  • .call方法则是通过HookCodeFactory基类进行操作的,主要是用来自动生成存储在taps中回调的方法的一个函数,下面就是由HookCodeFactory自动生成的执行函数
// 其中this._x则是tasp中的存储的函数,这里自动生成执行这个自启动函数
// 进行依次调用tap挂载的方法
(function(name, name2
    /*``*/) {
    "use strict";
    var _context;
    var _x = this._x;
    var _fn0 = _x[0];
    _fn0(name, name2);
    var _fn1 = _x[1];
    _fn1(name, name2);
    var _fn2 = _x[2];
    _fn2(name, name2);
})
// Hook.js
// 为方便理解,暂时未涉及到的都删除了
"use strict";

class Hook {
  constructor(args) {
    if(!Array.isArray(args)) args = [];
    this._args = args;
    this.taps = [];
    this.interceptors = [];
    this.call = this._call = this._createCompileDelegate("call", "sync"); 
    this.promise = this._promise = this._createCompileDelegate("promise", "promise");
    this.callAsync = this._callAsync = this._createCompileDelegate("callAsync", "sync");
    this._x = undefined;
  }

  // 重载compile这个方法
  compile(options) {
    throw new Error("Abstract: should be overriden");
  }

  
  _createCall(type) {
    // 调用子类中的compile方法
    // 就是HookCodeFactory自动生成函数的工厂
    return this.compile({
      taps: this.taps,
      args: this._args,
      type: type
    });
  }

  _createCompileDelegate(name, type) {
    // 调用call时,去调用创建自动生成函数的工厂类,生成函数进行执行
    const lazyCompileHook = (...args) => {
      this[name] = this._createCall(type);
      return this[name](...args);
    };
    return lazyCompileHook;
  }


  tap(options, fn) {
    // 判断类型,组装入参set带taps中
    if(typeof options === "string")
      options = { name: options };
    if(typeof options !== "object" || options === null)
      throw new Error("Invalid arguments to tap(options: Object, fn: function)");
    options = Object.assign({ type: "sync", fn: fn }, options);
    if(typeof options.name !== "string" || options.name === "")
      throw new Error("Missing name for tap");
    options = this._runRegisterInterceptors(options); // 可忽略不看
    this._insert(options); // 插入到taps中
  }

  ......

  ......

  _resetCompilation() {
    this.call = this._call;
    this.callAsync = this._callAsync;
    this.promise = this._promise;
  }

  _insert(item) {
    this._resetCompilation(); // 重置下私有变量
    ....
    let i = this.taps.length;
    while(i > 0) {
      i--;
      const x = this.taps[i];
      this.taps[i+1] = x;
      ...
      i++;
      break;
    }
    this.taps[i] = item;
  }
}

module.exports = Hook;

HookCodeFactory

  • 在执行call方法时是调用SyncHook下的重载的compile方法,其实就是调用HookCodeFactory工厂类的方法
  • 看下HookCodeFactorycreate
    1. 先把taps args type存到基类的options中方便后面直接使用,
    1. 再把agrs入参分割成数组,在后面生成函数时使用,生成入参
    1. 进行判断走进sync下面就是开始生成代码

其中生成函数中this.args() this.header() 都很容易理解,生成入参,和生成所需要的变量。下面主要看this.content(...)
这里调用callTapsSeries()方法是用来循环调用this.callTap(...)来进行生成代码的
通过onResult方法返回值来进行判断onResult是否循环调用callTap函数生成代码

// SyncHook.js
class SyncHookCodeFactory extends HookCodeFactory {
  content({ onError, onResult, onDone, rethrowIfPossible }) {
    return this.callTapsSeries({
      onError: (i, err) => onError(err),
      onDone,
      rethrowIfPossible
    });
  }
}
const factory = new SyncHookCodeFactory();

class SyncHook extends Hook {
  
  compile(options) {
    // 在SyncHook类下把taps中的所有fn存储到_x变量中
    factory.setup(this, options);
    // 生成函数
    return factory.create(options);
  }
}

// HookCodeFactory.js
class HookCodeFactory {
  constructor(config) {
    this.config = config;
    this.options = undefined;
  }

  create(options) {
    // 把调用tap所有的入参存储到options中,并将构造函数中的入参存储到_args中
    this.init(options);
    // 开始生成代码
    // 主要有三部 
    // 1.生成入参参数 this.args()
    // 2.生成所需要的变量 this.header()
    // 3.循环生成调用tap中的回调的函数 this.content(...)
    switch(this.options.type) {
      case "sync":
        return new Function(this.args(), "\"use strict\";\n" + this.header() + this.content({
          onError: err => `throw ${err};\n`,
          onResult: result => `return ${result};\n`,
          onDone: () => "",
          rethrowIfPossible: true
        }));
      case "async":
        ...
      case "promise":
        ...
    }
  }

  setup(instance, options) {
    // 把tasp中的fn回调传放到到实例中
    instance._x = options.taps.map(t => t.fn);
  }

  init(options) {
    this.options = options;
    this._args = options.args.slice();
  }

  header() {
    // 生成所需的变量
    let code = "";
    if(this.needContext()) {
      code += "var _context = {};\n";
    } else {
      code += "var _context;\n";
    }
    code += "var _x = this._x;\n";
    ...
    return code;
  }

  needContext() {
    for(const tap of this.options.taps)
      if(tap.context) return true;
    return false;
  }

  callTap(tapIndex, { onError, onResult, onDone, rethrowIfPossible }) {
    let code = "";
    let hasTapCached = false;
    // 找到并生成对应的fn,方便后面调用 :var _fn0 = _x[0];
    code += `var _fn${tapIndex} = ${this.getTapFn(tapIndex)};\n`;
    const tap = this.options.taps[tapIndex];
    switch(tap.type) {
      case "sync":
        if(!rethrowIfPossible) {
          code += `var _hasError${tapIndex} = false;\n`;
          code += "try {\n";
        }

        // 判断是否有返回值,没有返回值就直接调用并传入入参
        // _fn0(name, name2);
        if(onResult) {
          code += `var _result${tapIndex} = _fn${tapIndex}(${this.args({
            before: tap.context ? "_context" : undefined
          })});\n`;
        } else {
          code += `_fn${tapIndex}(${this.args({
            before: tap.context ? "_context" : undefined
          })});\n`;
        }

        // 判断是否继续执行callTap方法继续生成代码
        if(onDone) {
          code += onDone();
        }
        if(!rethrowIfPossible) {
          code += "}\n";
        }
        break;
      case "async":
        ...
        break;
      case "promise":
        ...
        break;
    }
    return code;
  }

  callTapsSeries({ onError, onResult, onDone, rethrowIfPossible }) {
    if(this.options.taps.length === 0)
      return onDone();
    // 这里判断tasp中的type是否是sync,来设置rethrowIfPossible的值
    const firstAsync = this.options.taps.findIndex(t => t.type !== "sync");
    const next = i => {
      // 判断i是否大于taps的长度,不大于就继续执行next生成代码
      if(i >= this.options.taps.length) {
        return onDone();
      }
      // 用来循环调用this.callTap
      const done = () => next(i + 1);
      const doneBreak = (skipDone) => {
        if(skipDone) return "";
        return onDone();
      }
      return this.callTap(i, {
        onError: error => onError(i, error, done, doneBreak),
        onResult: onResult && ((result) => {
          return onResult(i, result, done, doneBreak);
        }),
        onDone: !onResult && (() => {
          return done();
        }),
        rethrowIfPossible: rethrowIfPossible && (firstAsync < 0 || i < firstAsync)
      });
    };
    return next(0);
  }
  // 生成入参
  args({ before, after } = {}) {
    let allArgs = this._args;
    if(before) allArgs = [before].concat(allArgs);
    if(after) allArgs = allArgs.concat(after);
    if(allArgs.length === 0) {
      return "";
    } else {
      return allArgs.join(", ");
    }
  }

  getTapFn(idx) {
    return `_x[${idx}]`;
  }

  getTap(idx) {
    return `_taps[${idx}]`;
  }
}

module.exports = HookCodeFactory;

简单的实现SyncHook

 //简单的实现就是如下
 class SyncHook {
   constructor() {
     this.taps = [];
   }
   tap(name, fn) {
     this.taps.push({
        name,
        type: "sync",
        fn
     })
   }
   call(...arg) {
     this.taps.forEach(hook => hook.fn(...arg))
   }
 }

SyncBailHook

监听函数中存在返回不为undefined的则跳过下面所有监听

let queue = new SyncBailHook(['name']);

queue.tap('1', function (name) {
    console.log(name, 1);
});
queue.tap('2', function (name) {
    console.log(name, 2);
    return 'zz'
}); {
    console.log(name, 3);
});

queue.call('aa');
// aa 1
// aa 2

源码解析

同样他也是继承与HookHookCodeFactory
SyncHook唯一不同之处就是
queue.tap('3', function (name)
onResult: (i, result, next) => if(${result} !== undefined) {\n${onResult(result)};\n} else {\n${next()}}\n
通过onResult生成返回结果代码。判断如果执行.call中的callback的返回值不为undefined就直接执行后返回,不进入下面的操作。反之则继续往下走剩下的监听回调。

"use strict";

const Hook = require("./Hook");
const HookCodeFactory = require("./HookCodeFactory");

class SyncBailHookCodeFactory extends HookCodeFactory {
  content({ onError, onResult, onDone, rethrowIfPossible }) {
    return this.callTapsSeries({
      onError: (i, err) => onError(err),
      // 唯一不同处:判断结果是否是undefined,是则继续往下走,反之则直接返回
      onResult: (i, result, next) => `if(${result} !== undefined) {\n${onResult(result)};\n} else {\n${next()}}\n`,
      onDone,
      rethrowIfPossible
    });
  }
}

const factory = new SyncBailHookCodeFactory();

class SyncBailHook extends Hook {
  tapAsync() {
    throw new Error("tapAsync is not supported on a SyncBailHook");
  }

  tapPromise() {
    throw new Error("tapPromise is not supported on a SyncBailHook");
  }

  compile(options) {
    factory.setup(this, options);
    return factory.create(options);
  }
}

module.exports = SyncBailHook;

HookCodeFactory自动生成的执行代码

(function (name) {
  "use strict";
  var _context;
  var _x = this._x;
  var _fn0 = _x[0];
  var _result0 = _fn0(name);
  if (_result0 !== undefined) {
    return _result0;
  } else {
    var _fn1 = _x[1];
    var _result1 = _fn1(name);
    if (_result1 !== undefined) {
      return _result1;
    } else {
      var _fn2 = _x[2];
      var _result2 = _fn2(name);
      if (_result2 !== undefined) {
        return _result2;
      } else {
      }
    }
  }
})

简单的实现

 //简单的实现就是如下
 class SyncBailHook {
   constructor() {
     this.taps = [];
   }
   tap(name, fn) {
     this.taps.push({
        name,
        type: "sync",
        fn
     })
   }
   call(...arg) {
     for(let i = 0; i < this.taps.length; i++){
       const result = this.taps[i].fn(...arg);
       if(result)
         break
     }
   }
 }

SyncWaterfallHook

上一个监听返回值,传至下一个监听函数中(监听中如没返回值则默认是构造函数中传入的name值))

let queue = new SyncWaterfallHook(['name']);
queue.tap('1', function (name) {
    console.log(name, 1);
    return 1;
});
queue.tap('2', function (data) {
    console.log(data, 2);
    return 2;
});
queue.tap('3', function (data) {
    console.log(data, 3);
});

queue.call('aa');
// aa 1
// 1 2
// 2 3

// 如果删除监听中所有的返回值,结果如下:
// aa 1
// aa 2
// aa 3

SyncWaterfallHook源码解析

还是一样唯一的变化也就是onResult
onResult生成的代码就是上一次监听所返回的值,如果上一次监听有返回值就传入到下一个监听回调中,如果没有则是默认是构造函数中传入的值

"use strict";

const Hook = require("./Hook");
const HookCodeFactory = require("./HookCodeFactory");

class SyncWaterfallHookCodeFactory extends HookCodeFactory {
  content({ onError, onResult, onDone, rethrowIfPossible }) {
    return this.callTapsSeries({
      onError: (i, err) => onError(err),
      onResult: (i, result, next) => {
        let code = "";
        code += `if(${result} !== undefined) {\n`;
        code += `${this._args[0]} = ${result};\n`;
        code += `}\n`;
        code += next();
        return code;
      },
      onDone: () => onResult(this._args[0]),
      rethrowIfPossible
    });
  }
}

const factory = new SyncWaterfallHookCodeFactory();

class SyncWaterfallHook extends Hook {
  constructor(args) {
    super(args);
    if(args.length < 1) throw new Error("Waterfall hooks must have at least one argument");
  }
  ....
  compile(options) {
    factory.setup(this, options);
    return factory.create(options);
  }
}

module.exports = SyncWaterfallHook;

自动生成的代码

(function (name) {
  var _context;
  var _x = this._x;
  var _fn0 = _x[0];
  var _result0 = _fn0(name);
  if (_result0 !== undefined) {
    name = _result0;
  }
  var _fn1 = _x[1];
  var _result1 = _fn1(name);
  if (_result1 !== undefined) {
    name = _result1;
  }
  var _fn2 = _x[2];
  var _result2 = _fn2(name);
  if (_result2 !== undefined) {
    name = _result2;
  }
  return name;
})

简单的实现

 //简单的实现就是如下
 class SyncWaterfallHook {
   constructor() {
     this.taps = [];
   }
   tap(name, fn) {
     this.taps.push({
        name,
        type: "sync",
        fn
     })
   }
   call(...arg) {
     const result = null;
     this.taps.forEach((hook, index) => {
        result = index === 0 ? hook.fn(...arg) :  hook.fn(result)
     });
   }
 }

SyncLoopHook

监听中如果函数返回true则会重复执行,返回undefined则退出循环

let queue = new SyncLoopHook(['name']);

let count = 3;
queue.tap('1', function (name) {
    console.log('count: ', count--);
    if (count > 0) {
        return true;
    }
    return;
});
queue.call('a');
// 3
// 2
// 1

SyncLoopHook源码解析

这里和其他sync不同的是生成代码使用了callTapsLooping方法
通过do while来实现的

// SyncLoopHook
const Hook = require("./Hook");
const HookCodeFactory = require("./HookCodeFactory");
class SyncLoopHookCodeFactory extends HookCodeFactory {
  content({ onError, onResult, onDone, rethrowIfPossible }) {
    return this.callTapsLooping({
      onError: (i, err) => onError(err),
      onDone,
      rethrowIfPossible
    });
  }
}

const factory = new SyncLoopHookCodeFactory();

class SyncLoopHook extends Hook {
  tapAsync() {
    throw new Error("tapAsync is not supported on a SyncLoopHook");
  }

  tapPromise() {
    throw new Error("tapPromise is not supported on a SyncLoopHook");
  }

  compile(options) {
    factory.setup(this, options);
    return factory.create(options);
  }
}

module.exports = SyncLoopHook;




// HookCodeFactory.js
callTapsLooping({ onError, onDone, rethrowIfPossible }) {
  if(this.options.taps.length === 0)
    return onDone();
  const syncOnly = this.options.taps.every(t => t.type === "sync");
  let code = "";
  if(!syncOnly) {
    code += "var _looper = () => {\n";
    code += "var _loopAsync = false;\n";
  }
  code += "var _loop;\n";
  code += "do {\n";
  code += "_loop = false;\n";
  for(let i = 0; i < this.options.interceptors.length; i++) {
    const interceptor = this.options.interceptors[i];
    if(interceptor.loop) {
      code += `${this.getInterceptor(i)}.loop(${this.args({
        before: interceptor.context ? "_context" : undefined
      })});\n`;
    }
  }
  code += this.callTapsSeries({
    onError,
    onResult: (i, result, next, doneBreak) => {
      let code = "";
      code += `if(${result} !== undefined) {\n`;
      code += "_loop = true;\n";
      if(!syncOnly)
        code += "if(_loopAsync) _looper();\n";
      code += doneBreak(true);
      code += `} else {\n`;
      code += next();
      code += `}\n`;
      return code;
    },
    onDone: onDone && (() => {
      let code = "";
      code += "if(!_loop) {\n";
      code += onDone();
      code += "}\n";
      return code;
    }),
    rethrowIfPossible: rethrowIfPossible && syncOnly
  })
  code += "} while(_loop);\n";
  if(!syncOnly) {
    code += "_loopAsync = true;\n";
    code += "};\n";
    code += "_looper();\n";
  }
  return code;
}

自动生成的代码

(function (name) {
  var _context;
  var _x = this._x;
  var _loop;
  do {
    _loop = false;
    var _fn0 = _x[0];
    var _result0 = _fn0(name);
    if (_result0 !== undefined) {
      _loop = true;
    } else {
      if (!_loop) {
      }
    }
  } while (_loop);
})

简单的实现

 //简单的实现就是如下
 class SyncWaterfallHook {
   constructor() {
     this.taps = [];
   }
   tap(name, fn) {
     this.taps.push({
        name,
        type: "sync",
        fn
     })
   }
   call(...arg) {
    let result;
    do {
        result = this.taps.fn(...arg);
    } while (result)
   }
 }

继承

继承

原型链继承

function Parent (name) {
  this.name = name;
}
Parent.prototype.getName = function () {
  console.log(this.name);
}

function Child (num) {
  this.num = num;
}

Child.prototype = new Parent('zzz');

Child.prototype.getNum = function () {
    console.log(getNum);
}

var child = new Child('26');

console.log(child);

问题:

  1. 子类中无法继承父类实例中的属性
  2. 会将父类的实例属性扩展到子类原型上
  3. 子类的实例的构造函数会指向Paren,应该指向Child.

解决第一个问题(通过构造函数继承来解决):组合继承

子类中无法继承父类实例中的属性
: 通过call把父类的this指向子类,这样子类就继承了父类的实例方法

function Child(name,num) {
  Parent.call(this,name)
  this.num = num ;
}


// 具体方法
function Parent(name) {
  this.name = name;
}
Parent.prototype.getName = function () {
  console.log(this.name);
}

function Child(name, num) {
  Parent.call(this, name)
  this.num = num;
}

Child.prototype = new Parent();

Child.prototype.getNum = function () {
  console.log(getNum);
}

var child = new Child('zzzz','26');

解决第二个问题:寄生组合式继承

会将父类的实例属性扩展到子类原型上

// Child.prototype = new Parent();
Child.prototype = Parent.prototype;
// 可以直接子原型直接改为父的原型
// 但是会出现另外一个问题,如果在子原型上添加一些方法,在父原型上也会被修改


// 可以通过创建一个临时构造函数

function F () {};
F.prototype = Parent.prototype;
Child.prototype = new F();

// 具体方法

function Parent(name) {
  this.name = name;
}
Parent.prototype.getName = function () {
  console.log(this.name);
}

function Child(name, num) {
  Parent.call(this, name)
  this.num = num;
}

function F() {};
F.prototype = Parent.prototype;

Child.prototype = new F();

Child.prototype.getNum = function () {
  console.log(getNum);
}

var child = new Child('zzzz','26');

解决第三个问题

直接改变Child构造函数的指向为Child

Child.protptype.constructor = Child;

优化

上面通过一个临时函数取代了Parent构造函数,但是还是需要new操作,只要执行new操作就等于执行了一遍,这样肯定对系统有性能影响

// 在es5中可以通过Object.create();

function Parent(name) {
  this.name = name;
  console.log(1)
}
Parent.prototype.getName = function () {
  console.log(this.name);
}

function Child(name, num) {
  Parent.call(this, name)
  this.num = num;
}

// function F() {};
// F.prototype = Parent.prototype;
// Child.prototype = new F();
// Child.prototype.constructor = Child;

// 修改为
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;


Child.prototype.getNum = function () {
  console.log(getNum);
}

var child = new Child('zzzz','26');

ES6 继承

class Parent {
  constructor(name){
    this.name = name;
  }
  getName() {
    console.log(this.name);
  }
}

class Child extends Parent {
  constructor(name,num){
    super(name);
    this.num = num;
  }
  getNum (){
    console.log(this.num);
  }
}
const child = new Child('Parent',26);

解读ES5是如何实现Class

Bable Class

'use strict';

var _createClass = function () {
  function defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) { 
      var descriptor = props[i];
      descriptor.enumerable = descriptor.enumerable || false;
      descriptor.configurable = true;
      if ("value" in descriptor) 
      descriptor.writable = true; 
      Object.defineProperty(target, descriptor.key, descriptor); 
    }
  } 
  return function (Constructor, protoProps, staticProps) {
    if (protoProps)
    defineProperties(Constructor.prototype, protoProps);
    if (staticProps) 
    defineProperties(Constructor, staticProps); 
    return Constructor; 
  };
}();

function _classCallCheck(instance, Constructor) { 
  if (!(instance instanceof Constructor)) { 
    throw new TypeError("Cannot call a class as a function");
  } 
}

function _possibleConstructorReturn(self, call) { 
  if (!self) { 
    throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  } 
  return call && (typeof call === "object" || typeof call === "function") ? call : self; 
}

function _inherits(subClass, superClass) { 
  if (typeof superClass !== "function" && superClass !== null) { 
    throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
  } 
  subClass.prototype = Object.create(
    superClass && superClass.prototype, 
    { constructor: 
      {
        value: subClass,
        enumerable: false, 
        writable: true, 
        configurable: true 
      }
    }
  ); 
  if (superClass) 
    Object.setPrototypeOf
      ? Object.setPrototypeOf(subClass, superClass)
      : subClass.__proto__ = superClass;
}

var Parent = function () {
  function Parent(name) {
    _classCallCheck(this, Parent);
    this.name = name;
  }

  _createClass(Parent, [{
    key: 'getName',
    value: function getName() {
      console.log(this.name);
    }
  }]);

  return Parent;
}();

var Child = function (_Parent) {
  _inherits(Child, _Parent);
  function Child(num) {
    _classCallCheck(this, Child);
    var _this = _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, 'zz'));
    _this.num = num;
    return _this;
  }

  _createClass(Child, [{
    key: 'getNum',
    value: function getNum() {
      console.log(this.num);
    }
  }]);

  return Child;
}(Parent);

var child = new Child();

console.log(child);

我们分解一下先看Person

class Person {
    constructor(name) {
        this.name = name;
        this.type = 'person'
    }

    hello() {
        console.log('hello ' + this.name);
    }
}

// 编译后
'use strict';
// 保证能正常调用
// 直接 调用Person()是会抛错的
function _classCallCheck(instance, Constructor) { 
  if (!(instance instanceof Constructor)) { 
    throw new TypeError("Cannot call a class as a function"); 
  } 
}

var _createClass = function () {
  // 把props上的属性都扩展到target上
  function defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) { 
      var descriptor = props[i];
      descriptor.enumerable = descriptor.enumerable || false;
      descriptor.configurable = true;
      if ("value" in descriptor) 
      descriptor.writable = true; 
      // 定义或修改一个属性
      // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
      Object.defineProperty(target, descriptor.key, descriptor); 
    }
  } 
  return function (Constructor, protoProps, staticProps) {
    if (protoProps)
    // 在目标原型上扩展方法及方法
    defineProperties(Constructor.prototype, protoProps);
    if (staticProps) 
    // 在目标属性扩展属性及方法
    defineProperties(Constructor, staticProps); 
    return Constructor; 
  };
}();


var Parent = function () {
  function Parent(name) {
    // 保证能正常调用
    _classCallCheck(this, Parent);
    this.name = name;
  }

  // 为Parent添加原型方法
  _createClass(Parent, [{
    key: 'getName',
    value: function getName() {
      console.log(this.name);
    }
  }]);

  return Parent;
}();

下面我来看Child

....

class Child extends Parent {
  constructor(name,num){
    super(name);
    this.num = num;
  }
  getNum (){
    console.log(this.num);
  }
}

var child = new Child('parent',26);

...

// 编译后

// 实现继承的方法
function _inherits(subClass, superClass) { 
  if (typeof superClass !== "function" && superClass !== null) { 
    throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
  }
  // 让子类的原型继承父类原型上的方法
  // 创建一个新的对象 指定原型,和构造函数,返回一个新对象并有父类的原型方法
  // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create
  subClass.prototype = Object.create(
    superClass && superClass.prototype,
    { constructor: 
      {
        value: subClass,
        enumerable: false, 
        writable: true, 
        configurable: true 
      }
    }
  );
  // 通过 setPrototypeOf或__proto__来实现继承
  // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf
  if (superClass) 
    Object.setPrototypeOf
      ? Object.setPrototypeOf(subClass, superClass)
      : subClass.__proto__ = superClass;
}

// 继承父类实例
function _possibleConstructorReturn(self, call) { 
  if (!self) { 
    throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  } 
  return call && (typeof call === "object" || typeof call === "function") ? call : self; 
}


var Child = function (_Parent) {
  // 实现继承
  _inherits(Child, _Parent);
  function Child(num) {
    _classCallCheck(this, Child);
    // 通过call改变this来继承父类的实例方法
    var _this = _possibleConstructorReturn(
      this, 
      (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, 'zz')
      );
    _this.num = num;
    return _this;
  }

  // 同Parent
  _createClass(Child, [{
    key: 'getNum',
    value: function getNum() {
      console.log(this.num);
    }
  }]);

  return Child;
}(Parent);

var child = new Child();

Typescript Class

// 实现继承
var __extends = (this && this.__extends) || (function () {
  // 可以通过以下方法
  // 1. Object.setPrototypeOf 
  // 2. __proto__
  // 3. for in 把原型上的方法扩展到父类上
  var extendStatics =
    Object.setPrototypeOf ||
    ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
    function (d, b) {
      for (var p in b)
        if (b.hasOwnProperty(p))
        d[p] = b[p];
    };
  return function (d, b) {
    // 将父类的静态方法扩展子类上
    extendStatics(d, b);
    function __() {
      this.constructor = d;
    }
    // 重新赋值子类的原型
    // 如果b=null:就直接创建一个新对象,并继承了父类的原型方法
    // 反之:将父类的原型赋给__函数的原型,并且new一个实例
    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
  };
})();
var Parent = /** @class */ (function () {
  function Parent(name) {
      this.name = name;
  }
  Parent.prototype.getName = function () {
      console.log(this.name);
  };
  return Parent;
}());
var Child = /** @class */ (function (_super) {
  // 实现继承
  __extends(Child, _super);
  function Child(name, num) {
      // 通过call重新指向this来让子类继承父类的实例方法
      var _this = _super.call(this, name) || this;
      _this.num = num;
      return _this;
  }
  Child.prototype.getNum = function () {
      console.log(this.num);
  };
  return Child;
}(Parent));
var child = new Child('parent', 26);
console.log(child);

正则基础学习

正则

横向模糊匹配

{m,n}:连续出现 最少m次,最多n

// 让b最少出现2次,最多出现4次,后面为c
var regex =/ab{2,4}c/g;
var string = "abc abbc abbbc abbbbc abbbbbc abbbbbbc";
string.match(regex)
// =>  ["abbc", "abbbc", "abbbbc"]

模糊匹配

[asd]:表示可匹配asd中任意字符
[a-z]:-为范围匹配a到z的任意字母
要匹配a - z三个字符可以-az az- a\-z

var regex =/ab[456]c/g;
var regex2 =/ab[2-8]c/g;
var string = "ab5c ab3c ab9c";
string.match(regex)
// => ["ab5c"]
// => ["ab5c", "ab3c"]

排除字符

[^abc]表示除了abc这三个字符

var regex =/[^ab]/g;
var string = "ab5c";
string.match(regex)
// =>  ["5", "c"]

一些简写的形式

\d // => [0-9] 表示是位数字  d:digit(数字)
\D // => [^0-9] 表示除数字外

\w // => [0-9a-zA-Z_] 表示数字、小写大写字母、下划线  w:word(单词字符)
\W // => 除单词字符

\s // => [ \t\v\n\r\f] 表示空白符,空白符、制表符、换行、回车、换页 s: space character
\S // 非龙百福

. // => [^\n\r\u2028\u2029] 通配符,表示任意字符,除了换行符、回车、行分隔符、段分隔符

需要匹配任意字符 [\d\D] [\w\W] [\s\S] [^]

var regex =/[\d\D]/g;
var string = "ab5c";
string.match(regex)
// (4) ["a", "b", "5", "c"]

var regex =/[\w\W]/g;
var string = "ab5c";
string.match(regex)
// (4) ["a", "b", "5", "c"]
var regex =/[\s\S]/g;
var string = "ab5c";
string.match(regex)
// (4) ["a", "b", "5", "c"]

var regex =/[^]/g;
var string = "ab5c";
string.match(regex)
// (4) ["a", "b", "5", "c"]

量词

{m,n}:连续出现 最少m次,最多n
简写模式

{m,} // 表示至少出现m次
{m} // 表示出现m次
? // {0,1} 可能出现或没有
+ // {1,} 表示至少出现一次
* // {0,} 可能出现啊任意次数,或者不出现

贪婪匹配or惰性匹配

惰性匹配:尽可能的少匹配
/\d{2,5}/ 会匹配数字最少2位,最多5位的数字
贪婪:尽可能的多匹配,最多5位他就按照最多的匹配
惰性:尽可能少匹配, 最少2位他就按照最少的匹配

// 惰性匹配
var regex = /\d{2,5}?/g;
var string = "123 1234 12345 123456";
string.match(regex)
// (8) ["12", "12", "34", "12", "34", "12", "34", "56"]

多分支 (或者|)

可以匹配多个模式其中的一个

var regex = /a|b/g;
var string = "a";
string.match(regex)
// ["a"]

var regex = /a|b/g;
var string = "b";
string.match(regex)
//  ["b"]

var regex = /a|b/g;
var string = "c";
string.match(regex)
// null

例子

手机号码

  • 11位数
  • 前三位为固定
  • 移动:134(0-8)、135、136、137、138、139、147、150、151、152、157、158、159、178、182、183、184、187、188、198
  • 联通:130、131、132、145、155、156、175、176、185、186、166
  • 133、153、173、177、180、181、189、199
var regex = /^(13[0-9]|(14[5,7,9])|(15[0-3,5-9])|166|17[0,3,5-8]|18[0-9]|19[8,9]\d{8})/;

describe('正则表达式', () => {

  it('匹配手机号码', () => {
    [
      '13800000000',
      '14712341234',
      '15012341234',
      '14914914914',
      '19919999199',
      '16616616616',
      '19819819819'
    ].forEach((mobile) => {
      assert.ok(RegExps.mobile.test(mobile));
    });
    [
      '23800000000',
      '147000000000',
      '150-123-41234',
      '150-1234-1234',
      '1441441441441',
      '1971971971971',
      '1611611616161',
      '1411411411411',
      'asd516516asd1'
    ].forEach((mobile) => {
      assert.ok(!RegExps.mobile.test(mobile));
    });
  });

日期 YYYY-MM-DD

  • 年:四位数字 [0-9]{4}
  • 月:01-09 10-12 月份 (0[1-9]|1[0-2])
  • 日:最大31天 (0[1-9]|[12][0-9]|3[01])
var regex = /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;
 regex.test("2018-07-07")

原型 or 原型链

原型(prototype):

每个函数都有一个prototype属性,他是以对象形式存在的,可以在这个对象上添加属性和方法,通过这个函数创建的实例都会继承这些属性和方法。(函数才会有)

 // 原型 prototype
 function Foo () {} // 通过构造函数创建的实例foo
 Foo.prototype.name = 'zz';
 var foo = new Foo();
 foo.name // zz;
 Object.getPrototypeOf(foo) === Foo.protptype // true   ES5
 // foo通过Foo创建的实例都继承Foo.prototype的属性和方法

原型链(proto):

几乎所有对象都有一个与之对应的原型对象,_proto_属性值就是对应原型对象,用来继承原型中的属性和方法
实例中访问属性和方法都会先在自身查找,如果没有就会去原型中查找,而原型也有他自己的原型,这就形成了链式,直到返回null为结束.(几乎所有对象都有)

// 原型链 _proto_
_proto_ 属性会指向该对象的原型。
foo._proto_ === Foo.prototype  // true

构造函数(constructor):

构造函数就是初始化一个实例对象。每个原型上都有一个constructor属性,指向关联的构造函数
判别一个是构造函数,可以通过这个函数是否实例化。

 // 构造函数Foo
 function Foo () {
 }
 Foo.prototype.name = 'zz';
 // 通过构造函数创建的实例foo
 var foo = new Foo();
 foo.name // zz;
 // foo继承构造函数Foo原型上的属性和方法

Foo.prototype // { constructor: Foo()  }
Foo === Foo.prototype.constructor  // true

foo.constructor === Foo.prototype.constructor // true
// foo本身是没有constructor属性的,但是foo._proto_指向的原型中有。

来张图解释下
prototype.png-19.2kB

终极图

jsobj.jpg-99.1kB

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.