<body>
<button id="counter-btn">
</button>
<script>
let counterBtn = document.getElementById('counter-btn');
let number = 0;
counterBtn.addEventListener('click',function(){
    counterBtn.innerHTML = ++number;
});
</script>
</body>
<body>
<div id="counter-app"></div>
<script src="index.js"></script>
<script>
let counterApp = document.getElementById('counter-app');
counterApp.innerHTML = new Counter().render();
</script>
</body>
class Counter{
  render(){
      return (
          `
          <button id="counter-btn">
            0
          </button>
          `
      )
  }
}
<body>
<div id="counter-app"></div>
<script src="index.js"></script>
<script>
let counterApp = document.getElementById('counter-app');
counterApp.appendChild(new Counter().render());
</script>
</body>
class Counter{
  constructor(){
      this.state = {number:0};
  }  
  createDOMFromString(domString){
    const div = document.createElement('div');
    div.innerHTML = domString;
    return div.children[0];
  }
  increment (){
    this.state = {number:this.state.number+1};
    let oldElement = this.domElement;
    let newElement = this.render();
    oldElement.parentElement.replaceChild(newElement,oldElement);
  }
  render(){
     this.domElement = this.createDOMFromString(`
     <button id="counter-btn">
        ${this.state.number}
     </button>
     `);
     this.domElement.addEventListener('click',this.increment.bind(this));
     return this.domElement;
  }
}
<body>
<div id="counter-app"></div>
<script src="index.js"></script>
<script>
let counterApp = document.getElementById('counter-app');
new Counter({name:'珠峰架构'}).mount(counterApp);
</script>
</body>
window.trigger = function(event,name){
    let component = event.target.component;
    component[name].call(component,event);
}
class Component{
    constructor(props){
        this.props = props;
    }
    createDOMFromString(domString){
        const div = document.createElement('div');
        div.innerHTML = domString;
        return div.children[0];
    }
    setState(partialState){
        this.state = Object.assign(this.state,partialState);
        let oldElement = this.domElement;
        let newElement = this.renderElement();
        oldElement.parentElement.replaceChild(newElement,oldElement);
    }
    renderElement(){
        let renderString = this.render();
        this.domElement = this.createDOMFromString(renderString);
        this.domElement.component = this;
        return this.domElement;
    }
    mount(container){
        container.appendChild(this.renderElement());
    }
}
class Counter extends Component{
  constructor(props){
      super(props);
      this.state = {number:0};
  }  
  increment(){
   this.setState({number:this.state.number+1});
   console.log(this.state);
   this.setState({number:this.state.number+1});
   console.log(this.state);
   setTimeout(()=>{
    this.setState({number:this.state.number+1});
    console.log(this.state);
    this.setState({number:this.state.number+1});
    console.log(this.state);
   },1000);
  }
  render(){
      return (
        `
        <button id="counter-btn" onclick="trigger(event,'increment')">
         ${this.props.name}:${this.state.number}
        </button>
        `
      )
  }
}

<body>
<div id="counter-app"></div>
<script src="index.js"></script>
<script>
let counterApp = document.getElementById('counter-app');
new Counter({name:'珠峰架构'}).mount(counterApp);
</script>
</body>
let batchingStrategy = {
    isBatchingUpdates:false,
    updaters:[],
    batchedUpdates(){
        this.updaters.forEach(updater => {
            updater.component.updateComponent();
        });
    }
}
class Updater{
    constructor(component){
        this.component = component;
        this.pendingStates = [];
    }
    addState(particalState){
        this.pendingStates.push(particalState);
        batchingStrategy.isBatchingUpdates?batchingStrategy.updaters.push(this):this.component.updateComponent();
    }
}
let transaction = new Transaction({
    initialize() {
        batchingStrategy.isBatchingUpdates = true;
    },
    close() {
        batchingStrategy.isBatchingUpdates = false;
        batchingStrategy.batchedUpdates();
    }
});
window.trigger = function(event,name){
    batchingStrategy.isBatchingUpdates = true;
    let component = event.target.component;
    component[name].bind(component,event);
    batchingStrategy.isBatchingUpdates = false;
    batchingStrategy.batchedUpdates();
}
class Component{
    constructor(props){
        this.props = props;
        this.$updater = new Updater(this);
    }
    createDOMFromString(domString){
        const div = document.createElement('div');
        div.innerHTML = domString;
        return div.children[0];
    }
    setState(particalState){
        this.$updater.addState(particalState);
    }
    updateComponent(){
        let pendingStates = this.$updater.pendingStates;
        pendingStates.forEach(particalState=>Object.assign(this.state,particalState));
        this.$updater.pendingStates.length = 0;
        let oldElement = this.domElement;
        let newElement = this.renderElement();
        oldElement.parentElement.replaceChild(newElement,oldElement);
    }
    renderElement(){
        let renderString = this.render();
        this.domElement = this.createDOMFromString(renderString);
        this.domElement.component = this;
        return this.domElement;
    }
    mount(container){
        container.appendChild(this.renderElement());
    }
}
class Counter extends Component{
  constructor(props){
      super(props);
      this.state = {number:0};
  }  
  increment(){
   this.setState({number:this.state.number+1});
   console.log(this.state);
   this.setState({number:this.state.number+1});
   console.log(this.state);
   setTimeout(()=>{
    this.setState({number:this.state.number+1});
    console.log(this.state);
    this.setState({number:this.state.number+1});
    console.log(this.state);
   },1000);
  }
  render(){
      return (
        `
        <button id="counter-btn" onclick="trigger(event,'increment')">
         ${this.props.name}:${this.state.number}
        </button>
        `
      )
  }
}
一个所谓的 Transaction 就是将需要执行的 method 使用 wrapper 封装起来,再通过 Transaction 提供的 perform 方法执行
* wrappers (injected at creation time) * + + * | | * +-----------------|--------|--------------+ * | v | | * | +---------------+ | | * | +--| wrapper1 |---|----+ | * | | +---------------+ v | | * | | +-------------+ | | * | | +----| wrapper2 |--------+ | * | | | +-------------+ | | | * | | | | | | * | v v v v | wrapper * | +---+ +---+ +---------+ +---+ +---+ | invariants * perform(anyMethod) | | | | | | | | | | | | maintained * +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|--------> * | | | | | | | | | | | | * | | | | | | | | | | | | * | | | | | | | | | | | | * | +---+ +---+ +---------+ +---+ +---+ | * | initialize close | * +-----------------------------------------+ *
function setState() {
    console.log('setState')
}
class Transaction {
    constructor(wrappers) {
        this.wrappers = wrappers;
    }
    perform(func) {
        this.wrappers.forEach(wrapper=>wrapper.initialize())
        func.call();
        this.wrappers.forEach(wrapper=>wrapper.close())
    }
}
let transaction = new Transaction([
    {
        initialize() {
            console.log('before1');
        },
        close() {
            console.log('after1');
        }
    },
    {
        initialize() {
            console.log('before2');
        },
        close() {
            console.log('after2');
        }
    }
]);
transaction.perform(setState);
class Transaction {
    constructor(wrapper){
        this.wrapper = wrapper;
    }
    perform(func){
        this.wrapper.initialize();
        func.call();
        this.wrapper.close();
    }
}
let batchingStrategy = {
    isBatchingUpdates:false,
    updaters:[],
    batchedUpdates(){
        this.updaters.forEach(updater => {
            updater.component.updateComponent();
        });
    }
}
class Updater{
    constructor(component){
        this.component = component;
        this.pendingStates = [];
    }
    addState(particalState){
        this.pendingStates.push(particalState);
        batchingStrategy.isBatchingUpdates?batchingStrategy.updaters.push(this):this.component.updateComponent();
    }
}
let transaction = new Transaction({
    initialize() {
        batchingStrategy.isBatchingUpdates = true;
    },
    close() {
        batchingStrategy.isBatchingUpdates = false;
        batchingStrategy.batchedUpdates();
    }
});
window.trigger = function(event,name){
    let component = event.target.component;
    transaction.perform(component[name].bind(component,event));
}
class Component{
    constructor(props){
        this.props = props;
        this.$updater = new Updater(this);
    }
    createDOMFromString(domString){
        const div = document.createElement('div');
        div.innerHTML = domString;
        return div.children[0];
    }
    setState(particalState){
        this.$updater.addState(particalState);
    }
    updateComponent(){
        let pendingStates = this.$updater.pendingStates;
        pendingStates.forEach(particalState=>Object.assign(this.state,particalState));
        this.$updater.pendingStates.length = 0;
        let oldElement = this.domElement;
        let newElement = this.renderElement();
        oldElement.parentElement.replaceChild(newElement,oldElement);
    }
    renderElement(){
        let renderString = this.render();
        this.domElement = this.createDOMFromString(renderString);
        this.domElement.component = this;
        return this.domElement;
    }
    mount(container){
        container.appendChild(this.renderElement());
    }
}
class Counter extends Component{
  constructor(props){
      super(props);
      this.state = {number:0};
  }  
  increment(){
   this.setState({number:this.state.number+1});
   console.log(this.state);
   this.setState({number:this.state.number+1});
   console.log(this.state);
   setTimeout(()=>{
    this.setState({number:this.state.number+1});
    console.log(this.state);
    this.setState({number:this.state.number+1});
    console.log(this.state);
   },1000);
  }
  render(){
      return (
        `
        <button id="counter-btn" onclick="trigger(event,'increment')">
         ${this.props.name}:${this.state.number}
        </button>
        `
      )
  }
}