zchanges / zchange_blog Goto Github PK
View Code? Open in Web Editor NEWzChange blog
zChange blog
举个例子:微信订阅一个公众号,当公众号发布文章时,只要公众号发布文章,订阅者会第一时间接受到消息。(一对多的关系,对象发生变化,所有订阅者都会被通知到)
采用es6中Map更方便的处理订阅者,代码的减少以及可读性。
- 全局广播消息
- 框架内跨组件之间的通讯
可以用于异步编程(其中执行回调这一阶段和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)
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
设置模块中文件的处理
yarn add babel-core babel-preset-env babel-loader -D
babel-core
babel的核心库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"
},
...
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"
})
]
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,自动引入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文件
],
...
- 入口出口
- 配置一些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(行为驱动开发)
开发人员和测试产品客户等进行讨论,使用自然的语言去描述,取得预期的软件行为.
目的:让产品运行结果符合预期行为,避免表达不一致带来的问题(整合开发和产品测试)
Mocha
(测试框架)should.js
(断言库)、或采用node自带的assert
Karma
(自动化单元测试框架)全局安装
mocha
npm install mocha -g
采用node
自带的assert
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
代替:判断深度是否相等(会递归判断子对象是否相等)
describe('#All', function () {
it('a和b应当深度相等', function () {
var a = {
c: {
e: 1
}
}
var b = {
c: {
e: 1
}
}
assert.deepStrictEqual(a, b)
})
});
是否全等 是否全不等
describe('#All', function () {
it('is equality', function(done) {
assert.strictEqual(1, 1);
assert.notStrictEqual(1, '1');
});
});
捕获错误
describe('assert', function () {
it('可以捕获并验证函数fn的错误', function () {
function fn() {
xxx;
}
assert.throws(
fn,
(err) => {
if (/xxx is not defined/.test(err)) {
return true;
}
},
'未捕获到错误'
);
})
})
判断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: 失败
})
})
测试异步代码(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() {
// 在本区块的每个测试用例之后执行
});
});
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 是在软件开发领域中的一个在线的,分布式的持续集成服务,用来构建及测试在GitHub托管的代码。
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"
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
安装
npm install karma-chrome-launcher --save-dev
使用Karma
的customlaunchers
插件,您可以将其添加
....
browsers: ['Chrome','Chrome_without_security'],
customLaunchers: {
Chrome_without_security: {
base: 'Chrome',
flags: ['--disable-web-security']
}
}
...
-
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);
...
tags: React
在一个文件中进行操作dom,逻辑判断,获取数据然后重新渲染页面。
MVC: M(数据层) V(控制层) C(视图层)
把整个系统分层,M数据层、V视图层、C控制层。每个层相互独立,互不影响,每一层都向外开放一些接口(interface),供其他层去调用,就实现了模块化。
view(视图层)、Active(动作)、Dispatcher(派发器)、Store(数据层)。
用户访问view,触发事件,发出active,Dispatcher接收到后,要求store对应更新,发出change事件,view接受到change事件触发更新。
// 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;
getDefaultProps or getInitialState: 设置组件属性默认值 or 默认状态
constructor
componentWillMount: 组件挂载时执行,只执行一次,可以用setState
render(): 渲染
componentDidMount: 组件(子组件)加载完成后执行,可操作DOM,使用后refs
componentWillReceiveProps: 父组件render后子组件就会调用此生命周期(不管props有没有更新,父子组件数据有没有交换)
shouldComponentUpdate:组件挂在后,每次调用setState setProps后出发,可以通过nextProps和和props来判断是否需要重新render
componentWillUpdate:在shouldComponentUpdate 返回true后触发,不可以使用setState操作;fasle则直接返回不做渲染
render() 渲染
componentDidUpdate: 和componentDidMount声明周期对应(挂载时候只执行一次),每次更新渲染之后会被调用'
componentWillUnmount 最后组件销毁
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
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;
}
connect(mapStateToProps, mapDispatchToProps) (Commpoent);
来实现,内置判断了是否需要重新渲染react-redex
中对比两个props是通过===
来判断的,在两个对象完全一样也可能引用不一样,所以要尽可能避免两个看上去一样但是引用不一样的进行比较。// 错误
// 每次render都回产生新的对象和函数
render{
<Header style={color:red} onClick={()=>{……}}/>
}
// 正确
// 保证初始化只执行一次
let headerStyle = {color:red};
clickFn(){……}
render{
<Header style={headerStyle} onClick={clickFn}/>
}
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方式
redux方式
mergeProps(connect第三个参数不写有默认)
合并props传给组件,还做了当数据发送改变组件是否重新渲染(connect建立了react和redux的连接)react-redux
connect(,)()
createStore()
combineReducers({})
合并reducrbindActionCreators({})
Reselect的createSelector
通过selector 缓存数据提高性能Router
Router
下只允许一个节点所以用div
包裹下
<HashRouter>
<div>
<Route path="/" component={Login}/>
<Route path="/Chat" component={Chat}/>
</div>
</HashRouter>
BrowserRouter
Or HashRouter
BrowserRouter
需要和后端配合,重定向只能到首页,刷新就到404,兼容性低,可采用HashRouter
PropTypes
设置类型判断报错
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());
}
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
} = require("tapable");
所有暴露出来的HOOK都继承与
Hook
(存储订阅中的回调)和HookCodeFactory
(用来自动生成发布函数的工厂)
new SyncHook(['name'])
所有的Hook构造函数都有一个可选的参数,是一个字符串数组
.tap(name, callback)
:订阅(监听)
name:
名称
callback:
回调函数
.call(...params)
: 发布
params:
传入的参数和构造函数传入的长度保持一致
sync
图
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
工厂类的方法- 看下
HookCodeFactory
的create
- 先把
taps
args
type
存到基类的options
中方便后面直接使用,
- 再把
agrs
入参分割成数组,在后面生成函数时使用,生成入参
- 进行判断走进
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
同样他也是继承与
Hook
和HookCodeFactory
和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);
问题:
- 子类中无法继承父类实例中的属性
- 会将父类的实例属性扩展到子类原型上
- 子类的实例的构造函数会指向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');
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);
'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);
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;
}();
....
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();
// 实现继承
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,} 可能出现啊任意次数,或者不出现
惰性匹配:尽可能的少匹配
/\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));
});
});
- 年:四位数字 [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")
每个函数都有一个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_
属性值就是对应原型对象,用来继承原型中的属性和方法
实例中访问属性和方法都会先在自身查找,如果没有就会去原型中查找,而原型也有他自己的原型,这就形成了链式,直到返回null为结束.(几乎所有对象都有)
// 原型链 _proto_
_proto_ 属性会指向该对象的原型。
foo._proto_ === Foo.prototype // true
构造函数就是初始化一个实例对象。每个原型上都有一个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_指向的原型中有。
终极图
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.