Giter VIP home page Giter VIP logo

blog's People

Contributors

liang520 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

blog's Issues

nodejs事件循环机制

对于高并发的处理


一般而言,高并发都是多线程模型,服务器为每个客户端发来的请求都分配一个线程来处理,使用同步I/O,系统通过切换线程来弥补同步I/O调动的时间开销。比如 Apache 就是这种策略,由于 I/O 一般都是耗时操作,因此这种策略很难实现高性能,但非常简单,可以实现复杂的交互逻辑。
而事实上,大多数网站的服务器端都不会做太多的计算,它们接收到请求以后,把请求交给其它服务来处理(比如读取数据库),然后等着结果返回,最后再把结果发给客户端。因此,Node.js 针对这一事实采用了单线程模型来处理,它不会为每个接入请求分配一个线程,而是用一个主线程处理所有的请求,然后对 I/O 操作进行异步处理,避开了创建、销毁线程以及在线程间切换所需的开销和复杂性。

事件循环


Node.js 在主线程里维护了一个事件队列,当接到请求后,就将该请求作为一个事件放入这个队列中,然后继续接收其他请求。当主线程空闲时(没有请求接入时),就开始循环事件队列,检查队列中是否有要处理的事件,这时要分两种情况:如果是非 I/O 任务,就亲自处理,并通过回调函数返回到上层调用;如果是 I/O 任务,就从 线程池 中拿出一个线程来处理这个事件,并指定回调函数,然后继续循环队列中的其他事件。

当线程中的 I/O 任务完成以后,就执行指定的回调函数,并把这个完成的事件放到事件队列的尾部,等待事件循环,当主线程再次循环到该事件时,就直接处理并返回给上层调用。 这个过程就叫 事件循环 (Event Loop),其运行原理如下图所示:
image
这个图是整个 Node.js 的运行原理,从左到右,从上到下,Node.js 被分为了四层,分别是 应用层V8引擎层Node API层LIBUV层

应用层: 即 JavaScript 交互层,常见的就是 Node.js 的模块,比如 http,fs
V8引擎层: 即利用 V8 引擎来解析JavaScript 语法,进而和下层 API 交互
NodeAPI层: 为上层模块提供系统调用,一般是由 C 语言来实现,和操作系统进行交互 。
LIBUV层: 是跨平台的底层封装,实现了 事件循环、文件操作等,是 Node.js 实现异步的核心

无论是 Linux 平台还是 Windows 平台,Node.js 内部都是通过 线程池 来完成异步 I/O 操作的,而 LIBUV 针对不同平台的差异性实现了统一调用。因此,Node.js 的单线程仅仅是指 JavaScript 运行在单线程中,而并非 Node.js 是单线程。

工作原理


Node.js 实现异步的核心是事件,也就是说,它把每一个任务都当成 事件 来处理,然后通过 Event Loop 模拟了异步的效果,为了更具体、更清晰的理解和接受这个事实,下面我们用伪代码来描述一下其工作原理 。

  • 【1】定义事件队列
    既然是队列,那就是一个先进先出 (FIFO) 的数据结构,我们用JS数组来描述,如下:
/**
 * 定义事件队列
 * 入队:push()
 * 出队:shift()
 * 空队列:length == 0
 */
globalEventQueue: []

利用数组来模拟队列结构:数组的第一个元素是队列的头部,数组的最后一个元素是队列的尾部,push() 就是在队列尾部插入一个元素,shift() 就是从队列头部弹出一个元素。这样就实现了一个简单的事件队列。

  • 【2】定义接收请求入口
    每一个请求都会被拦截并进入处理函数,如下所示:
/**
 * 接收用户请求
 * 每一个请求都会进入到该函数
 * 传递参数request和response
 */
processHttpRequest:function(request,response){
     
    // 定义一个事件对象
    var event = createEvent({
        params:request.params, // 传递请求参数
        result:null, // 存放请求结果
        callback:function(){} // 指定回调函数
    });
 
    // 在队列的尾部添加该事件  
    globalEventQueue.push(event);
}

这个函数很简单,就是把用户的请求包装成事件,放到队列里,然后继续接收其他请求。

  • 【3】定义 Event Loop
    当主线程处于空闲时就开始循环事件队列,所以我们还要定义一个函数来循环事件队列:
/**
 * 事件循环主体,主线程择机执行
 * 循环遍历事件队列
 * 处理非IO任务
 * 处理IO任务
 * 执行回调,返回给上层
 */
eventLoop:function(){
    // 如果队列不为空,就继续循环
    while(this.globalEventQueue.length > 0){
         
        // 从队列的头部拿出一个事件
        var event = this.globalEventQueue.shift();
         
        // 如果是耗时任务
        if(isIOTask(event)){
            // 从线程池里拿出一个线程
            var thread = getThreadFromThreadPool();
            // 交给线程处理
            thread.handleIOTask(event)
        }else {
            // 非耗时任务处理后,直接返回结果
            var result = handleEvent(event);
            // 最终通过回调函数返回给V8,再由V8返回给应用程序
            event.callback.call(null,result);
        }
    }
}

主线程不停的检测事件队列,对于 I/O 任务,就交给线程池来处理,非 I/O 任务就自己处理并返回。

  • 【4】处理 I/O 任务
    线程池接到任务以后,直接处理IO操作,比如读取数据库:
/**
 * 处理IO任务
 * 完成后将事件添加到队列尾部
 * 释放线程
 */
handleIOTask:function(event){
    //当前线程
    var curThread = this;
 
    // 操作数据库
    var optDatabase = function(params,callback){
        var result = readDataFromDb(params);
        callback.call(null,result)
    };
     
    // 执行IO任务
    optDatabase(event.params,function(result){
        // 返回结果存入事件对象中
        event.result = result;
 
        // IO完成后,将不再是耗时任务
        event.isIOTask = false;
         
        // 将该事件重新添加到队列的尾部
        this.globalEventQueue.push(event);
         
        // 释放当前线程
        releaseThread(curThread)
    })
}

当 I/O 任务完成以后就执行回调,把请求结果存入事件中,并将该事件重新放入队列中,等待循环,最后释放当前线程,当主线程再次循环到该事件时,就直接处理了。

总结以上过程我们发现,Node.js 只用了一个主线程来接收请求,但它接收请求以后并没有直接做处理,而是放到了事件队列中,然后又去接收其他请求了,空闲的时候,再通过 Event Loop 来处理这些事件,从而实现了异步效果,当然对于IO类任务还需要依赖于系统层面的线程池来处理。

因此,我们可以简单的理解为:Node.js 本身是一个多线程平台,而它对 JavaScript 层面的任务处理是单线程的。

VUE原理代码

编译原理部分:

<!DOCTYPE html>
<head>
  <title>myVue</title>
</head>
<style>
  #app {
    text-align: center;
  }
</style>
<body>
  <div id="app">
    <form>
      <input type="text"  v-model="number">
      <button type="button" v-click="increment">增加</button>
    </form>
    <h3 v-bind="number"></h3>
    <form>
      <input type="text"  v-model="count">
      <button type="button" v-click="incre">增加</button>
    </form>
    <h3 v-bind="count"></h3>
  </div>
</body>

<script>
  function myVue(options) {
    this._init(options);
  }

  myVue.prototype._init = function (options) {
    this.$options = options;
    this.$el = document.querySelector(options.el);
    this.$data = options.data;
    this.$methods = options.methods;

    this._binding = {};
    this._obverse(this.$data);
    this._complie(this.$el);
  }
 
  myVue.prototype._obverse = function (obj) {
    var _this = this;
    Object.keys(obj).forEach(function (key) {
      if (obj.hasOwnProperty(key)) {
        _this._binding[key] = {                                                                                                                                                          
          _directives: []
        };
        console.log(_this._binding[key])
        var value = obj[key];
        if (typeof value === 'object') {
          _this._obverse(value);
        }
        var binding = _this._binding[key];
        Object.defineProperty(_this.$data, key, {
          enumerable: true,
          configurable: true,
          get: function () {
            console.log(`${key}获取${value}`);
            return value;
          },
          set: function (newVal) {
            console.log(`${key}更新${newVal}`);
            if (value !== newVal) {
              value = newVal;
              binding._directives.forEach(function (item) {
                item.update();
              })
            }
          }
        })
      }
    })
  }

  myVue.prototype._complie = function (root) {
    var _this = this;
    var nodes = root.children;
    for (var i = 0; i < nodes.length; i++) {
      var node = nodes[i];
      if (node.children.length) {
        this._complie(node);
      }

      if (node.hasAttribute('v-click')) {
        node.onclick = (function () {
          var attrVal = nodes[i].getAttribute('v-click');
          return _this.$methods[attrVal].bind(_this.$data);
        })();
      }

      if (node.hasAttribute('v-model') && (node.tagName = 'INPUT' || node.tagName == 'TEXTAREA')) {
        node.addEventListener('input', (function(key) {
          var attrVal = node.getAttribute('v-model');
          _this._binding[attrVal]._directives.push(new Watcher(
            'input',
            node,
            _this,
            attrVal,
            'value'
          ))

          return function() {
            _this.$data[attrVal] =  nodes[key].value;
          }
        })(i));
      } 

      if (node.hasAttribute('v-bind')) {
        var attrVal = node.getAttribute('v-bind');
        _this._binding[attrVal]._directives.push(new Watcher(
          'text',
          node,
          _this,
          attrVal,
          'innerHTML'
        ))
      }
    }
  }

  function Watcher(name, el, vm, exp, attr) {
    this.name = name;         //指令名称,例如文本节点,该值设为"text"
    this.el = el;             //指令对应的DOM元素
    this.vm = vm;             //指令所属myVue实例
    this.exp = exp;           //指令对应的值,本例如"number"
    this.attr = attr;         //绑定的属性值,本例为"innerHTML"

    this.update();
  }

  Watcher.prototype.update = function () {
    this.el[this.attr] = this.vm.$data[this.exp];
  }

  window.onload = function() {
    var app = new myVue({
      el:'#app',
      data: {
        number: 0,
        count: 0,
      },
      methods: {
        increment: function() {
          this.number ++;
        },
        incre: function() {
          this.count ++;
        }
      }
    })
  }
</script>

proxy优化部分

 class Watcher {
    constructor(proxy, key, cb) {
        this.cb = cb;
        Dep.target = this;
        this.value = proxy[key];
    }
}

class Dep {
    constructor(){
        this.subs = []
    }
    addSub(sub){
        this.subs.push(sub);
    }
    notify(newVal){
        this.subs.forEach(sub => {
            sub.cb(newVal, sub.value);
            sub.value = newVal;
        })
    }
}

const observe = (obj) => {
    const deps = {};
    return new Proxy(obj, {
        get: function (target, key, receiver) {
            const dep = (deps[key] = deps[key] || new Dep);
            Dep.target && dep.addSub(Dep.target)
            Dep.target = null;
            return Reflect.get(target, key, receiver);
        },
        set: function (target, key, value, receiver) {
            const dep = (deps[key] = deps[key] || new Dep);
            Promise.resolve().then(() => {
                dep.notify(value);
            })
            return Reflect.set(target, key, value, receiver);
        }
    });
}

var state = observe({x:0})
new Watcher(state, 'x', function(n, o){
    console.log(n, o)
});
new Watcher(state, 'y', function(n, o){
    console.log(n, o)
});
state.x = 3;
state.y = 3;

Promise实现

/**
 * 关于promise的实现,需要实现以下功能
 *
 * function fn1(resolve,reject){
 *        setTimeout(function(){
 *             console.log('步骤一')
 *             resolve(1)
 *         },500)
 * }
 * function fn2(resolve,reject){
 *         setTimeout(function(){
 *             console.log('步骤二')
 *             resolve(2)
 *         },200)
 * }
 *
 * new Promise(fn1).then(function(val){
 *     console.log(val)
 *     return new Promise(fn2)
 * }).then(function(val){
 *     console.log(val)
 *     return 33;
 * }).then(function(val){
 *     console.log(val)
 * })
 *
 */

//第一版基础实现

// /**
//  * 实现promise类
//  * @param {function} fn 传给promise的构造函数
//  */
// function Promise(fn) {
//   //定义回调方法变量
//   var callback;

//   //写then方法的
//   this.then = function(done) {
//     callback = done;
//   };
//   /**
//    * resolve之后,执行回调的成功的值
//    */
//   function resolve() {
//     callback();
//   }
//   fn(resolve);
// }

//第二版基础实现
//一个promise的then可以有多个

// function Promise(fn) {
//   var promise = this;
//   value = null;
//   promise._resolve = []; //回调的队里

//   this.then = function(onFulfilled) {
//     //将回调一步一步塞入队里里面
//     promise._resolve.push(onFulfilled);
//     return this;
//     //then之后需要链式调用
//   };

//   function resolve(value) {
//     //将resolve执行改成异步过程
//     setTimeout(function() {
//       promise._resolve.forEach(function(callback) {
//         //将resolve的值,传入每个回调里面
//         callback(value);
//       });
//     }, 0);
//   }
//   fn(resolve);
// }

/**
 * 此处执行
 * promise(fn(resolve)=>{
 *  resolve()
 * }).then((val)=>{
 *  console.log(val)
 * })
 *
 * 按照上面的写法,先执行resolve,此时是队列then注入的方法为空,
 * 需要对resolve做一次异步处理
 */

//引入promise状态

// function Promise(fn) {
//   var promise = this;
//   promise._resolve = [];
//   value = null;
//   promise._status = "PENDING";
//   this.then = function(onFulfilled) {
//     //状态为pending的时候,插入到队里
//     if (promise._status === "PENDING") {
//       promise._resolve.push(onFulfilled);
//       return this;
//     }
//     //否者直接执行
//     onFulfilled(value);
//     return this;
//   };
//   function resolve(value) {
//     setTimeout(function() {
//       //resolve之后,状态修改
//       promise._status = "FULFILLED";
//       promise._resolve.forEach(function(callback) {
//         value = callback(value);
//         //接受then之后执行的回调返回的数据
//       });
//     }, 0);
//   }
//   fn(resolve);
// }

/**
 * promise需要串行
 * 在当前promise状态为fufilled之后,then后会返回一个新的promise;
 * 需要串行调用
 */

function Promise(fn) {
  var value = null;
  promise = this;
  promise._resolve = [];
  promise._state = "PENDING";

  this.then = function(onFulfilled, onRejected) {
    //then方法也会返回一个promise
    return new Promise(function(resolve) {
      //重写处理函数
      function handle(value) {
        //判断是否是函数,then后面传入的
        var ret = isFunction(onFulfilled) ? onFulfilled(value) : value;
        //此处要考虑then里面返回的是一个promise对象
        if (ret && typeof ret["then"] == "function") {
          //执行返回的promise,取到值后,执行resolve
          ret.then(function(value) {
            resolve(value);
          });
        } else {
          //当前这个promise异步执行完成,执行resolve
          resolve(ret);
        }
      }

      function errorCallback(reason) {
        reason = (isFunction(onRejected) && onRejected(reason)) || reason;
        reject(reason);
      }

      if (promise._state == "PENDING") {
        promise._resolve.push(handle);
        promise._reject.push(errorCallback);
      } else if (promise._state == "FULFILLED") {
        //promise内部resolve(value)的值;在then后面调用then里面传入方法
        handle(value);
      } else if (promise._state == "REJECTED") {
        errorCallback(promise._reason);
      }
    });
  };

  function resolve(value) {
    setTimeout(function(value) {
      promise._state = "FULFILLED";
      promise._resolve.forEach(function(callback) {
        value = callback(value);
      });
    }, 0);
  }

  function reject(value) {
    setTimeout(function(value) {
      promise._state = "REJECTED";
      promise._reject.forEach(function(callback) {
        promise._reason = callback(value);
      });
    }, 0);
  }

  fn(resolve);

}

/**
 * Promise.resolve方法
 */
Promise.resolve = function(val) {
  return new Promise(function(resolve, reject) {
    resolve(val);
  });
};

/**
 * Promise.reject方法
 */
Promise.reject = function(val) {
  return new Promise(function(resolve, reject) {
    reject(val);
  });
};

Promise.all = function(promises) {
  if (Array.isArray(promises)) {
    throw new TypeError("yuo must pass an array to all");
  }
  //返回一个promise实例
  return new Promise(function(resolve, reject) {
    var result = [],
      len = promises.length,
      count = len;
    /**
     * 每一个promise执行成功之后,返回一个函数,接受promise之后的值
     * @param {number} index 数字
     */
    function resolver(index) {
      //返回一个函数,接受promise执行返回的值
      return function(value) {
        resolveAll(index, value);
      };
    }

    /**
     * 错误处理拒绝函数
     * @param {Object} reason 错误信息
     */
    function rejecter(reason) {
      reject(reason);
    }

    /**
     * 把执行成功的值,放到数组中
     * @param {number} index 返回值序号
     * @param {any} value 每一次的返回值
     */
    function resolveAll(index, value) {
      result[index] = value;
      if (--count == 0) {
        //当所有promise执行成功之后,resolve
        resolve(result);
      }
    }

    //依次执行每个promise
    for (var i = 0; i < len; i++) {
      promises[i].then(resolver(i), rejecter);
    }

  });
};

Promise.race = function(promises) {
  if (Array.isArray(promises)) {
    throw new TypeError("yuo must pass an array to all");
  }

  return new Promise(function(resolve, reject) {
    var len = promises.length;
    function resolver(value) {
      resolve(value);
    }
    function rejecter(reason) {
      reject(reason);
    }
    for (var i = 0; i < len; i++) {
      promises[i].then(resolver, rejecter);
    }
  });
};

算法总结与学习

知识点

  1. 哈希表:在一般的多重循环中,可以通过建立哈希表,通过以空间换取速度的方式,我们可以将查找时间从 O(n)降低到 O(1)。
    比如:
  • 列1

题目:
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
示例:
给定 nums = [2, 7, 11, 15], target = 9,因为 nums[0] + nums[1] = 2 + 7 = 9,所以返回 [0, 1]
解题:
一般的方式是多重循环暴力得出,比较好的方式是通过建立数字与索引位置的哈希表,一层循环迭代出来。最优的方法是迭代一遍,先把需要的结果计算出来,去哈希表中查找。如果没有,就把当前迭代的数字和键存起来,下一次迭代的时候,再如此循环。这样就是循环后面的值的时候,计算需要的数字,直接在哈希表中查找前面所存下来的值。

css 3D原理解析

概论
css3D并非真的3D,而是通过视觉处理,在2D的空间里面,绘制出3D视觉效果。

基础

  1. 3D三要素

摄影镜头:主要定义了观看者的角度
立体空间:具备了X、Y、Z 三个座标轴的空间
立体物件: 是在这个空间里头的物件。

  1. css当中模拟3D架构
    css中没有这些摄像头之类的这些东西,所有要用dome元素来替代,在对应的元素上面加上style属性,来进行模拟,上面所有的概念中,我们需要三层div模型来替代,最外层是摄影镜头,第二层为立体空间,第三层则是立体空间内的立体元素。
<div class="camera">
    <div class="space">
        <div class="box"></div>
    </div>
</div>

设定camera
把最外层的div (以下通称camera )设定为摄影镜头,设定的方法为添加perspective-origin以及perspective这两个属性。

perspective-origin:指定了观察者的位置(摄影镜头)
perspective:指定了观察者与z=0平面的距离,使具有三维位置变换的元素产生透视效果。z>0的三维元素比正常大,而z<0时则比正常小,大小程度由该属性的值决定。
作为摄影镜头的camera 的三个维度, perspective-origin代表了X和Y轴,perspective代表了Z轴

.camera{
    width:200px;
    height:200px;
    perspective-origin:center center;
    -moz-perspective-origin:center center;
    -webkit-perspective-origin:center center;
    perspective:500px;
    -moz-perspective:500px;
    -webkit-perspective:500px;
}

设定space
摄影机完成后,就是要设定一个立体空间space,这个空间设定的方式很简单,只要设定一个属性:transform-style,这个属性预设为flat,也就是只要是这个div内的子元素,一律都是以扁平( flat )的方式呈现,所属的变换transform也一律都是用flat的方式变换,换句话说就是没有Z轴的存在,为了让内容元素都是立体元素,所以我们要将transform-style设为3D,如此一来内容元素就全部都可以用3D进行变换,为了方便区隔,下面我将space外围多一个boder做识别。

.space{
    width:100%;
    height:100%;
    border:1px dashed #000;
    transform-style:3d;
    -moz-transform-style:3d;
    -webkit-transform-style:3d;
}

设定box
最后就是内容元素Box了,我们可以添加一个100px*100px的box进去,在 没有设定box的traslateZ、rotate的情形下,不论我们如何去修改camera的perspective-origin和perspective,box的大小和位置都不会有变化 ,为什么?因为在没有设定box的translateZ或rotate,让Z的深度有所变化,摄影机透过perspective看出去的位置都是相同的,也造成不论怎么去看这个box都是一样的大小。

.box{
    width:100px;
    height:100px;
    background:#069;
    transform:translateX(50px) translateY(50px);
    -moz-transform:translateX(50px) translateY(50px);
    -webkit-transform:translateX(50px) translateY(50px);
}

image
不过当我们给box改变Z轴的深度之后(这里我先把translateZ设定为150px ),再去改变camera的perspective-origin和perspective,一切仿佛就有了变化

.box{
    width:100px;
    height:100px;
    background:#069;
    transform:translateX(50px) translateY(50px) translateZ(150px);
    -moz-transform:translateX(50px) translateY(50px) translateZ(150px);
    -webkit-transform:translateX(50px) translateY(50px) translateZ(150px);
}

image
大概了解之后,来把box旋转一下角度,看得应该就会更清楚,当摄影机的变成广角,也就是perspective变短,整个旋转后变形也会更加明显,大家可以用开发者工具修改camera的perspective就会明白

.box{
    width:100px;
    height:100px;
    background:#069;
    transform:translateX(50px) translateY(50px) rotateY(60deg);
    -moz-transform:translateX(50px) translateY(50px) rotateY(60deg);
    -webkit-transform:translateX(50px) translateY(50px) rotateY(60deg);
}

image
改变一下perspective-origin 也会很有意思。
image

总结
由上述三个属性,很容易绘制出3D图形,这当中还有一个最重要的规律在里面,这个规律就是tramsform里头是有顺序的,因为CSS 3D完全是藉由2D演算而来,并不是真的像3D软体是真的有3D的空间,所以就变成会「按照顺序」进行演算,而且又因为transform会造成物体的整个座标轴变换,在顺序的编排上就格外重要。

perspective相对于变换元素距离是变换之后的距离
image

javascript引擎工作原理与函数执行机制

探索JS引擎工作原理

几个概念

  • 执行环境栈( Execution Context Stack) EC Stack
  • 全局对象(Global Object)
  • 执行环境(Execution Context ) EC
  • 变量对象(Varibale Object) VO
  • 活动对象(Activation Object) AO
  • 作用域 (Scope)
  • 作用域链 (Scope Chain)

通过一段代码来演示

var x = 1;  //定义一个全局变量 x
function A(y){
   var x = 2;  //定义一个局部变量 x
   function B(z){ //定义一个内部函数 B
       console.log(x+y+z);
   }
   return B; //返回函数B的引用
}
var C = A(1); //执行A,返回B
C(1); //执行函数B,输出 4

下面我们将分全局初始化执行函数A执行函数B 三个阶段来分析JS引擎的工作机制

全局初始化

JS引擎在进入一段可执行的代码时,需要完成以下三个初始化工作:

首先,创建一个全局对象(Global Object) , 这个对象全局只存在一份,它的属性在任何地方都可以访问,它的存在伴随着应用程序的整个生命周期。全局对象在创建时,将Math,String,Date,document 等常用的JS对象作为其属性。由于这个全局对象不能通过名字直接访问,因此还有另外一个属性window,并将window指向了自身,这样就可以通过window访问这个全局对象了。用伪代码模拟全局对象的大体结构如下:

//创建一个全局对象
var globalObject = {
    Math:{},
    String:{},
    Date:{},
    document:{}, //DOM操作
    ...
    window:this //让window属性指向了自身
}

然后,JS引擎需要构建一个执行环境栈( Execution Context Stack) ,与此同时,也要创建一个全局执行环境(Execution Context)EC ,并将这个全局执行环境EC压入执行环境栈中。执行环境栈的作用是为了保证程序能够按照正确的顺序被执行。在javascript中,每个函数都有自己的执行环境,当执行一个函数时,该函数的执行环境就会被推入执行环境栈的顶部并获取执行权。当这个函数执行完毕,它的执行环境又从这个栈的顶部被删除,并把执行权并还给之前执行环境。我们用伪代码来模拟执行环境栈和EC的关系:

var ECStack = []; //定义一个执行环境栈,类似于数组
 
var EC = {};   //创建一个执行空间,
//ECMA-262规范并没有对EC的数据结构做明确的定义,你可以理解为在内存中分配的一块空间
 
ECStack.push(EC); //进入函数,压入执行环境
ECStack.pop(EC);  //函数返回后,删除执行环境

最后,JS引擎还要创建一个与EC关联的全局变量对象(Varibale Object) VO, 并把VO指向全局对象,VO中不仅包含了全局对象的原有属性,还包括在全局定义的变量x 和函数 A,与此同时,在定义函数A的时候,还为 A 添加了一个内部属性scope,并将scope指向了VO。每个函数在定义的时候,都会创建一个与之关联的scope属性,scope总是指向定义函数时所在的环境。此时的ECStack结构如下:

ECStack = [   //执行环境栈
    EC(G) = {   //全局执行环境
        VO(G):{ //定义全局变量对象
            ... //包含全局对象原有的属性
            x = 1; //定义变量x
            A = function(){...}; //定义函数A
            A[[scope]] = this; //定义A的scope,并赋值为VO本身
        }
    }
];

执行函数A

当执行进入A(1) 时,JS引擎需要完成以下工作:

首先,JS引擎会创建函数A的执行环境EC,然后EC推入执行环境栈的顶部并获取执行权。此时执行环境栈中有两个执行环境,分别是全局执行环境和函数A执行环境,A的执行环境在栈顶,全局执行环境在栈的底部。然后,创建函数A的作用域链(Scope Chain) ,在javascript中,每个执行环境都有自己的作用域链,用于标识符解析,当执行环境被创建时,它的作用域链就初始化为当前运行函数的scope所包含的对象。

接着,JS引擎会创建一个当前函数的活动对象(Activation Object) AO,这里的活动对象扮演着变量对象的角色,只是在函数中的叫法不同而已(你可以认为变量对象是一个总的概念,而活动对象是它的一个分支), AO中包含了函数的形参、arguments对象、this对象、以及局部变量和内部函数的定义,然后AO会被推入作用域链的顶端。需要注意的是,在定义函数B的时候,JS引擎同样也会为B添加了一个scope属性,并将scope指向了定义函数B时所在的环境,定义函数B的环境就是A的活动对象AO, 而AO位于链表的前端,由于链表具有首尾相连的特点,因此函数B的scope指向了A的整个作用域链。 我们再看看此时的ECStack结构:

ECStack = [   //执行环境栈
    EC(A) = {   //A的执行环境
        [scope]:VO(G), //VO是全局变量对象
        AO(A) : { //创建函数A的活动对象
            y:1,
            x:2,  //定义局部变量x
            B:function(){...}, //定义函数B
            B[[scope]] = this; //this指代AO本身,而AO位于scopeChain的顶端,因此B[[scope]]指向整个作用域链
            arguments:[],//平时我们在函数中访问的arguments就是AO中的arguments
            this:window  //函数中的this指向调用者window对象
        },
        scopeChain:<AO(A),A[[scope]]>  //链表初始化为A[[scope]],然后再把AO加入该作用域链的顶端,此时A的作用域链:AO(A)->VO(G)
    },
    EC(G) = {   //全局执行环境
        VO(G):{ //创建全局变量对象
            ... //包含全局对象原有的属性
            x = 1; //定义变量x
            A = function(){...}; //定义函数A
            A[[scope]] = this; //定义A的scope,A[[scope]] == VO(G)
        }
    }
];

执行函数B

函数A被执行以后,返回了B的引用,并赋值给了变量C,执行 C(1) 就相当于执行B(1),JS引擎需要完成以下工作:

首先,还和上面一样,创建函数B的执行环境EC,然后EC推入执行环境栈的顶部并获取执行权。 此时执行环境栈中有两个执行环境,分别是全局执行环境和函数B的执行环境,B的执行环境在栈顶,全局执行环境在栈的底部。(注意:当函数A返回后,A的执行环境就会从栈中被删除,只留下全局执行环境)然后,创建函数B的作用域链,并初始化为函数B的scope所包含的对象,即包含了A的作用域链。最后,创建函数B的活动对象AO,并将B的形参z, arguments对象 和 this对象作为AO的属性。此时ECStack将会变成这样:

ECStack = [   //执行环境栈
    EC(B) = {   //创建B的执行环境,并处于作用域链的顶端
        [scope]:AO(A), //指向函数A的作用域链,AO(A)->VO(G)
        var AO(B) = { //创建函数B的活动对象
            z:1,
            arguments:[],
            this:window
        }
        scopeChain:<AO(B),B[[scope]]>  //链表初始化为B[[scope]],再将AO(B)加入链表表头,此时B的作用域链:AO(B)->AO(A)-VO(G)
    },
    EC(A), //A的执行环境已经从栈顶被删除,
    EC(G) = {   //全局执行环境
        VO:{ //定义全局变量对象
            ... //包含全局对象原有的属性
            x = 1; //定义变量x
            A = function(){...}; //定义函数A
            A[[scope]] = this; //定义A的scope,A[[scope]] == VO(G)
        }
    }
];

当函数B执行“x+y+z”时,需要对x、y、z 三个标识符进行一一解析,解析过程遵守变量查找规则:先查找自己的活动对象中是否存在该属性,如果存在,则停止查找并返回;如果不存在,继续沿着其作用域链从顶端依次查找,直到找到为止,如果整个作用域链上都未找到该变量,则返回“undefined”。从上面的分析可以看出函数B的作用域链是这样的:

AO(B)->AO(A)->VO(G)

因此,变量x会在AO(A)中被找到,而不会查找VO(G)中的x,变量y也会在AO(A)中被找到,变量z 在自身的AO(B)中就找到了。所以执行结果:2+1+1=4.

函数执行机制

后续抽时间继续总结;

git原理解析

理解

  1. Git 仓库中的提交记录保存的是你的目录下所有文件的“快照”,就像是把整个目录复制,然后再粘贴一样,但比复制粘贴优雅许多!
  2. 在你每次进行提交时,它并不会盲目地复制整个目录。条件允许的情况下,它会将当前版本与仓库中的上一个版本进行对比,并把所有的“差异“打包到一起作为一个提交记录。
  3. Git 还保存了提交的“历史记录”。这也是为什么大多数提交记录的上面都有”父节点“的原因

git commit
初始提交 C0 和其后可能包含某些有用修改的提交 C1。(*号表示当前所在分支)
image
执行git commit之后,基于C1修改保存成提交记录C2;
image

git Branch
分支非常轻量。它们只是简单地指向某个提交纪录--仅此而已。
因为即使创建再多分的支也不会造成储存或内存上的开销,并且按逻辑分解工作到不同的分支要比维护那些特别臃肿的分支简单多了。
只要记住使用分支其实就相当于在说:“我想基于这个提交以及它所有的父提交进行新的工作。”
如图创建新的newImage分支放入过程
image
执行git branch newImage
image
git checkout newImage;git commit切换到新分支,基于当前分支提交代码
image

创建新分支并切换到新分支git checkout -b <your-branch-name>

分支与合并
第一种方法:git merge ,合并两个分支时会产生一个特殊的提交记录,它有两个父节点;
把 bugFix 合并到 master 里
image
git merge bugFix
image
再把 master 分支合并到 bugFix:git checkout bugFix;git merge master
image
master 继承自 bugFix,Git 什么都不用做,只是简单地把 bugFix 移动到 master 所指向的那个提交记录。

git rebase
rebase也是一种合并方法,Rebase 实际上就是取出一系列的提交记录,“复制”它们,然后在另外一个地方逐个的放下去。
准备了两个分支;注意当前所在的分支是 bugFix(星号标识的是当前分支)
image
把 bugFix 分支里的工作直接移到 master 分支上。移动以后会使得两个分支的功能看起来像是按顺序开发,但实际上它们是并行开发的。
执行git rebase master
image
注意,提交记录 C3 依然存在(树上那个半透明的节点),而 C3' 是我们 Rebase 到 master 分支上的 C3 的副本。
再更新master,切换到master分支;
image
把它 rebase 到 bugFix 分支上
2018-11-30 1 10 13
由于 bugFix 继承自 master,所以 Git 只是简单的把 master 分支的引用向前移动了一下而已。

在提交树上面移动
HEAD: HEAD 是一个对当前检出记录的符号引用 —— 也就是指向你正在其基础上进行工作的提交记录。HEAD 总是指向当前分支上最近一次提交记录。
观察提交前后 HEAD 的位置。
image
执行git checkout C1;git checkout master;git commit;git checkout C2;
ko4fz-ta1lz
如果想看 HEAD 指向,可以通过 cat .git/HEAD 查看, 如果 HEAD 指向的是一个引用,还可以用 git symbolic-ref HEAD 查看它的指向。

分离HEAD
分离的 HEAD 就是让其指向了某个具体的提交记录而不是分支名;
如图,在命令执行之前的状态如下所示:
HEAD -> master -> C1
HEAD 指向 master, master 指向 C1
image
执行git checkout C1命令
image
现在变成了 HEAD -> C1

相对引用
通过指定提交记录哈希值的方式在 Git 中移动不太方便。在实际应用时,所以你就不得不用 git log 来查查看提交记录的哈希值。通过哈希值指定提交记录很不方便,所以 Git 引入了相对引用。
使用相对引用的话,你就可以从一个易于记忆的地方(比如 bugFix 分支或 HEAD)开始计算。
两个简单的用法:

  • 使用 ^ 向上移动 1 个提交记录
  • 使用 ~ 向上移动多个提交记录,如 ~3

首先看看操作符 (^)。把这个符号加在引用名称的后面,表示让 Git 寻找指定提交记录的父提交。
所以 master^ 相当于“master 的父节点”。
master^^ 是 master 的第二个父节点
image
执行git checkout master^
image

使用~ 一次后退四步,例如:
image
执行git checkout HEAD~4
image

强制修改分支位置
相对引用最多的就是移动分支。可以直接使用 -f 选项让分支指向另一个提交。
git branch -f master HEAD~3
image
上面的命令会将 master 分支强制指向 HEAD 的第 3 级父提交。
image
-f 则容许我们将分支强制移动到那个位置。

撤销变更
主要有两种方法用来撤销变更 —— 一是 git reset,还有就是 git revert。

git reset 通过把分支记录回退几个提交记录来实现撤销改动。
image
执行git reset HEAD~1
image
在reset后, C2 所做的变更还在,但是处于未加入暂存区状态,reset对于本地分支很方便,对于远程分支是无效的

使用git revert ,为了撤销更改并分享给别人;
image
执行git revert HEAD
image
C2' 的状态与 C1 是相同的。

整理提交记录
如果你想将一些提交复制到当前所在的位置(HEAD)下面的话, Cherry-pick 是最直接的方式了。

git cherry-pick <提交号>...
image
执行git cherry-pick C2 C4
image

例一种方法,git rebase -i HEAD~4弹出交互框,手动选择性的拖拽合并

关于React架构的一些底层知识

首先了解一些理念
diff算法
react使用 virtual dom 来表示 dom 树,而 diff 算法就是用于比较 virtual dom 树的区别,并更新界面需要更新的部分。diff 算法和 virtual dom 的完美结合的过程被称为 reconciler,有了 reconciler,开发者可以脱身操作真实的 dom 树
image
在 react16 之前的 reconciler 叫 stack reconciler,fiber 是 react 新的 reconciler,这次更新到 fiber 架构是一次重量级的核心架构的替换
Fiber相关

  • 为何出现?

diff的时候时间太长,会阻塞交互

  • 怎么处理

引入了异步渲染的概念,采用fiber架构

  • 和原来的对比

原先的 stack reconciler 像是一个递归执行的函数,从父组件调用子组件的 reconciler 过程就是一个递归执行的过程,这也是为什么被称为 stack reconciler 的原因。当我们调用 setState 的时候,react 从根节点开始遍历,找出所有的不同,而对于特别庞大的__ dom 树来说,这个递归遍历的过程会消耗特别长的时间。这个过程任何交互都会阻塞。

  • 具体优化的方案

fiber 的出现解决了这个问题,它把 reconciler 的过程拆分成了一个个的小任务,并在完成了小任务之后暂停执行 js 代码,然后检查是否有需要更新的内容和需要响应的事件,做出相应的处理后再继续执行 js 代码。

Fiber如何做到异步

  • 前提

在做显示方面的工作时,经常会听到一个目标叫 60 帧,这表示的是画面的更新频率,也就是画面每秒钟更新 60 次。这是因为在 60 帧的更新频率下,页面在人眼中显得流畅,无明显卡顿。每秒钟更新 60 次也就是每 16ms 需要更新一次页面,如果更新页面消耗的时间不到 16ms,那么在下一次更新时机来到之前会剩下一点时间执行其他的任务,只要保证及时在 16ms 的间隔下更新界面就完全不会影响到页面的流畅程度。fiber 的核心正是利用了 60 帧原则,实现了一个基于优先级和 requestIdleCallback 的循环任务调度算法。
image

  • 关于requestIdleCallback

假如某一帧里面要执行的任务不多,在不到16ms(1000/60)的时间内就完成了上述任务的话,那么这一帧就会有一定的空闲时间,这段时间就恰好可以用来执行requestIdleCallback的回调,由于requestIdleCallback利用的是帧的空闲时间,所以就有可能出现浏览器一直处于繁忙状态,导致回调一直无法执行,那么这种情况我们就需要在调用requestIdleCallback的时候传入第二个配置参数timeout了

requestIdleCallback(myNonEssentialWork, { timeout: 2000 });
function myNonEssentialWork (deadline) {
  // 当回调函数是由于超时才得以执行的话,deadline.didTimeout为true
  while ((deadline.timeRemaining() > 0 || deadline.didTimeout) &&
         tasks.length > 0) {
       doWorkIfNeeded();
    }
  if (tasks.length > 0) {
    requestIdleCallback(myNonEssentialWork);
  }
}

image

  • fiber的具体实现

fiber 利用了这个参数,判断当前剩下的时间是否足够继续执行任务,如果足够则继续执行,否则暂停任务,并调用 requestIdleCallback 通知浏览器空闲的时候继续执行当前的任务。

function fiber(剩余时间) {
 if (剩余时间 > 任务所需时间) {
   做任务;
 } else {
   requestIdleCallback(fiber);
 }
}

fiber 还会为不同的任务设置不同的优先级,高优先级任务是需要马上展示到页面上的,

  { 
 Synchronous: 1, // 同步任务,优先级最高
 Task: 2, // 当前调度正执行的任务
 Animation 3, // 动画
 High: 4, // 高优先级
 Low: 5, // 低优先级
 Offscreen: 6, // 当前屏幕外的更新,优先级最低
}
  • fiber架构

在 fiber 架构中,有一种数据结构,它的名字就叫做 fiber,这也是为什么新的 reconciler 叫做 fiber 的原因。fiber 其实就是一个 js 对象,这个对象的属性中比较重要的有 stateNode、tag、return、child、sibling 和 alternate。

Fiber = {
 tag // 标记任务的进度
 return // 父节点
 child // 子节点
 sibling // 兄弟节点
 alternate // 变化记录
 .....
};

我们可以看出 fiber 基于链表结构,拥有一个个指针,指向它的父节点子节点和兄弟节点,在 diff 的过程中,依照节点连接的关系进行遍历。

目前可能的问题
在 fiber 中,更新是分阶段的,具体分为两个阶段,首先是 reconciliation 的阶段,这个阶段在计算前后 dom 树的差异,然后是 commit 的阶段,这个阶段将把更新渲染到页面上。第一个阶段是可以打断的,因为这个阶段耗时可能会很长,因此需要暂停下来去执行其他更高优先级的任务,第二个阶段则不会被打断,会一口气把更新渲染到页面上。
由于 reconciliation 的阶段会被打断,可能会导致 commit 前的这些生命周期函数多次执行。react 官方目前已经把 componentWillMountcomponentWillReceivePropscomponetWillUpdate 标记为 unsafe,并使用新的生命周期函数 getDerivedStateFromPropsgetSnapshotBeforeUpdate 进行替换。

async最新的v8处理

如下代码:

var p = Promise.resolve();
(async () => {
  await 1; 
  console.log('after:await');
})();
p.then(() => console.log('tick:a'))
 .then(() => console.log('tick:b'));

awiat后面如果是一个基础类型值,会通过Promise.resovle包装,awiat后面的值,会被包装到then里面。这个时候,then后面的函数就会直接放到微任务中。
如果后面是Promise,就不再包装,只是把await后面的值,放到then后面,then后面的任务什么时候放入到微任务中,需要根据await后面的promise的状态改变。

redux实现原理分析

自己的状态和reducer

let initState = {
    count: 0
  }

function reducer(state,action){
    //如果没有初始值,那么就初始化一次
    if(!state){
        state=initState;
    }
    switch(action.type){
        case 'INCREASE':
        return {
            ...state,
            count:state.count+1
        }
        case "DECREMENT":
        return {
            ...state,
            count:state.count-1
        }
        default:
        return state;
    }
}

自己创建createStore

const createStore=function(reducer,initState){
    let state=initState;
    let listeners=[];
    function subscribe(listener){
        listeners.push(listener);
        //退订
        return function unsubscribe(listener){
            const index=listeners.indexOf(listener);
            listeners.splice(index,1)
        }
    }

    function dispatch(action){
        state=reducer(state,action);
        for(let i=0;i<listeners.length;i++){
            const listener=listeners[i];
            listener();
        }
    }
    function getState(){
        return state;
    }

    /**
     * 替换reducer
     * @param {*} nextReDucer 要替换的reducer
     */
    function replaceReducer(nextReDucer){
        reducer=nextReDucer
        dispatch({type:Symbol()});//刷一遍state状态
    }
    /**
     * 所有reducer都不匹配,会初始化所有的state值
     */
    dispatch({type:Symbol()});
    return {
        subscribe,dispatch,getState
    }
}

合并reducer方法

 /**
 * 合并
 * @param {*} reducers 传入reducer数据结构类似于{
                                counter: counterReducer,
                                info: InfoReducer
                            }
 */
function combinReducers(reducers){
    const reducerKeys=Object.keys(reducers);
    return function combination(state={},action){//返回一个新的reducer ,对所有传入的state和action重新做处理
        const nextState={};//定义新的state
        //遍历所有reducer,每个reducer都会返回相关的数据,处理之后,返回新数据,没有相关联的,就返回之前的数据
        for(let i=0;i<reducerKeys.length;i++){
            const key=reducerKeys[i];//reducer name
            const reducer=reducers[key];//reducer
            const preiousStateForKey=state[key];//传入state的某个值
            const nextStateForKey=reducer(preiousStateForKey,action);
            nextState[key]=nextStateForKey;//新构造出来一个
        }
        return nextState;//返回新的状态
    }
}

重点内容:中间件

//中间件是对dispatch的扩展
/**
 * 比如记录日志
 * 记录修改前的state,为什么修改,修改后的state
 * 通过重写dispatch来实现
 * 
 * const store=createStore(reducer);
 * const next=store.dispatch;
 * 
 * store.dispatch=(action)=>{
 *  console.log('this state',store.getState())
 *  next(action)
 *  console.log('next state',store.getState())
 * }
 * 
 * store.dispatch({
 *  type:'INCREMENT'
 * })
 * 
 * 输出日志
 * 
 */


/**
 * 
 * 记录异常
 * 
 * const store = createStore(reducer);
 * const next = store.dispatch;
 * 
 * store.dispatch=(action)=>{
 * try{
 *      next(action)
 *  }catch(err){
 *      console.log(err)
 *  }
 * }
 * 
 */

 /**
  * 如果两个功能都需要处理呢
  */

 const store = createStore(reducer);
 const next = store.dispatch;
 
 //单独把日志提取出来
 const loggerMiddleware = (action) => {
   console.log('this state', store.getState());
   console.log('action', action);
   next(action);
   console.log('next state', store.getState());
 }
 
 store.dispatch = (action) => {
   try {
     loggerMiddleware(action);
   } catch (err) {
     console.error('错误报告: ', err)
   }
 }

//把错误处理提取出来
//  const exceptionMiddleware = (action) => {
//     try {
//       /*next(action)*/
//       loggerMiddleware(action);
//     } catch (err) {
//       console.error('错误报告: ', err)
//     } 
//   }
//   store.dispatch = exceptionMiddleware;

  /**
   * 错误处理里面写定了日志,不可配,提取出来
   */

   const exceptionMiddleware=(next)=>(action)=>{
        try{
            next(action);//任意dispatch
        }catch(err){
            console.log(err);
        }
   }

  store.dispatch=exceptionMiddleware(loggerMiddleware);

 /**
* 扩展loggerMiddleware
* 
* const loggerMiddleware=(next)=>(action)=>{
*   console.log('this state',store.getState())
*   console.log('action',action)
*   next(action)
*   console.log('next state',state.getState())
* }
*/






/**
 * 两个中间件合作模式
 * 
 * 
 *  const store = createStore(reducer);
    const next = store.dispatch;
    const loggerMiddleware = (next) => (action) => {
        console.log('this state', store.getState());
        console.log('action', action);
        next(action);
        console.log('next state', store.getState());
    }
    const exceptionMiddleware = (next) => (action) => {
        try {
            next(action);
        } catch (err) {
            console.error('错误报告: ', err)
        }
    }
    store.dispatch = exceptionMiddleware(loggerMiddleware(next));
 * 
 */

/**
 * 单独把中间件拆分出来 ,里面包含了store,需要提取出来
 * 
 * 
 * const store=creatStore(reducer)
 * const next=store.dispatch;
 * 
 * const loggerMiddleware=(store)=>(next)=>(action)=>{
 *     console.log('this state',store.getState());
 *     console.log('action',action)
 * 
 *     next(action)
 *     console.log('next state',store.getState)
 * }
 * 
 * const exceptMiddleware=(store)=>(next)=>(action)=>{
 *     try{
 *         next(action)
 *     }catch(err){
 *           console.log(err)
 *     }
 * }
 * 
 * const logger=loggerMiddleware(store);
 * const exception=exceptionMiddleware(store);
 * store.dispatch=exception(logger(next))
 */

/**
 * 我们只需要构建出相关的中间件,其他的内部细节,通过creatStore扩展开来
 * 
 * 期望实现 
 * const newCreatStore=applyMiddleware(exceptionMiddleware,timeMiddleware,loggerMiddleware)(creatStore)
 * const store=newCreatStore(reducer);//return 新的store
 * 
 * 实现 applyMiddleware
 * 
 * const applyMiddleware=function(...middelwares){
 *    return function rewriteCreateStore(oldCreateStore){
 *        return function newCreatStore(reducer,initState){
 *            const store=oldCreateStore(reducer,initState);
 * 
 *            const chain=middelwares.map((middleware)=>middleware(store))
 * 
 *            let dispatch=store.dispatch;
 *            
 *            //实现 exception(time(logger(dispatch)))
 *            chian.reverse().map((middleware)=>{
 *                //更改dispatch
 *                dispatch=middleware(dispatch)
 *            })
 *            //重写
 *            store.dispatch=dispatch;
 * 
 *            return store
 *        }
 *    }
 * }
 */


/**
* 现在会有两种createStore
* 
* 没有中间件的creatStore
* import { createStore } from './redux';
* const store = createStore(reducer, initState);
* 
* 
* 有中间件的createStore
* const rewriteCreateStoreFunc = applyMiddleware(exceptionMiddleware, timeMiddleware, loggerMiddleware);
* const newCreateStore = rewriteCreateStoreFunc(createStore);
* const store = newCreateStore(reducer, initState);
* 
* 
* 为了统一起来,修改createStore方法
* 
* const createStore=(reducer,initState,rewriteCreateStoreFunc)=>{
*   if(rewriteCreateStoreFunc){
*       const newCreateStore=rewriteCreateStoreFunc(createStore);
*       return newCreateStore(reducer, initState)
*   }
*   否则按照正常流程走
* }
* 
* 
*/

/**
 * 最终用法
 * 
 * const rewriteCreateStoreFunc = applyMiddleware(exceptionMiddleware, timeMiddleware, loggerMiddleware);
 * const store = createStore(reducer, initState, rewriteCreateStoreFunc);
 */

一些优化的地方

 /**
  * 目前中间件能够拿到store,通过store修改我们的东西,保持最小开放策略
  * 传递给中间件的变成当前一个快照state
  * 之前const chain = middlewares.map(middleware => middleware(store));
  * 
  * 修改之后
  * const simpleStore = { getState: store.getState };
  * const chain = middlewares.map(middleware => middleware(simpleStore));
  * 
  */


  /**
   * compose
   * applyMiddleware中,把 [A, B, C] 转换成 A(B(C(next))),是这样实现的
   * const chain = [A, B, C];
   * let dispatch = store.dispatch;
   * chain.reverse().map(middleware => {
   *    dispatch = middleware(dispatch);
   * });
   * 
   * redux提供了一个方法compose
   * const chain = [A, B, C];
   * dispatch = compose(...chain)(store.dispatch)
   * 
   * 内部实现
   * export default function compose(...funcs) {
   *    if (funcs.length === 1) {
   *        return funcs[0]
   *    }
   *    return funcs.reduce((a, b) => (...args) => a(b(...args)))
   * }
   */

 /**
    * 省略initState
    * 有时候没有传initState,redux允许我们这么写
    * const store = createStore(reducer, rewriteCreateStoreFunc);
    * 内部实现
    * const createStore=(reducer,initState,rewriteCreateStoreFunc)=>{
    *   if(typeof initState ==="function"){
    *       rewriteCreateStoreFunc=initState;
    *       initState=undefined;
    *   }
    *   ...
    * }
    */


 /**
     * bindActionCreators
     * 
     * 这只有在react-redux的connect实现中用到,
     * 他通过闭包,把dispatch和actionCreator封装起来,让其他地方感觉不到redux的存在
     * 
     * 通过普通方式来隐藏dispatch和actionCreator,注意最后两行代码
     * 
     * const reducer=combineReducers({
     *      counter:counterRedcuer,
     *      info:infoReducer
     * });
     * const store=createStore(reducer);
     * 
     * 
     * //返回action的函数叫做actionCreator
     * function increment(){
     *  return {
     *      type:'INCREMENT'
     *  }
     * }
     * 
     * function setName(name){
     *      return {
     *          type:"SET_NAME",
     *          name:name
     *      }
     * }
     * 
     * const acrions={
     *      increment:function(){
     *             return store.dispatch(increment.apply(this,arguments))
     *      },
     *      setName:function(){
     *              return store.dispatch(setName.apply(this,arguments))
     *      }
     * }
     * 
     * ⚠️注意:我们可以把actions传到任何地方去,
     * 其他地方实现加减的时候,根本不需要知道其他细节
     * 
     * actions.increment();
     * actions.setName('123')
     * 发现有好多公共代码,在actions生成的过程中
     * 希望可以直接调用:const actions=bindActionCreators({increment,setName},store.dispatch);
     * 
     * 实现,通过闭包隐藏actionCreator和dispatch
     * 
     * fucntion bindActionCreator(actionCreator,dispatch){
     *      return function(){
     *          return dispatch(actionCreator.apply(this,argument))
     *      }
     * }
     * 
     * export default function buindActionCreators(actionCreators,dispatch){
     *      if (typeof actionCreators === 'function') {
     *          return bindActionCreator(actionCreators, dispatch)
     *      }
     *      if (typeof actionCreators !== 'object' || actionCreators === null) {
     *          throw new Error()
     *      }
     *      const keys = Object.keys(actionCreators)
     *      const boundActionCreators = {}
     *      for (let i = 0; i < keys.length; i++) {
     *          const key = keys[i]
     *          const actionCreator = actionCreators[key]
     *          if (typeof actionCreator === 'function') {
     *              boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
     *          }
     *      }
     *      return boundActionCreators 
     * }
     */

互联网协议底层分析

模型组成
image

  • 实体层:把电路链接起来,传输0和1的电信号
  • 链路层:确定电信号的分组方式,以太网是这一层的协议,并规定:一组电信号构成一个数据包,“帧”(包含标头和数据)
    • 标头包含:发送、接受者,以及数据类型。数据就是需要传输的具体类型
      image
    • MAC地址: 标志发送和接受者的标记是设备的MAC地址
    • 广播:发送之前怎么知道接收者的网卡呢(ARP协议,后面解释)?知道之后,怎么才能发送到接受方。以太网直接向所有本网络内所有计算机发送,每台计算机自己接受到之后,再判断。这种方式叫做“广播”
  • 网络层:需要判断那些MAC地址同网络,采用广播。哪些不是,采用“路由”。这就需要网络层的网络地址来判断了。
    • IP协议:规定网络地址的协议叫做“IP协议”,所定义的地址叫做“IP地址”。目前采用IPv4,由四个字节(32个二进制组成),一般用十进制表示IP,“0.0.0.0”。一个IP地址,前部分代表网络,后部分代表主机。
    • 子网掩码:要判断是否处于同网之下,需要判断IP地址的网络部分是否相同,但是多少位代表网络地址,这就需要定义一个参数——子网掩码。如“11111111.11111111.11111111.00000000”,然后与IP进行AND运算,再做对比。
    • IP数据包:根据IP协议发送的数据。"标头"部分主要包括版本、长度、IP地址等信息,"数据"部分则是IP数据包的具体内容。它放进以太网数据包后,以太网数据包就变成了下面这样。
      image
    • ARP协议:一边情况我们需要知道对方的IP、MAC才能发送,IP通过DNS解析或者网关是已知的,需要通过IP来得到MAC地址,不同网络,数据发送到网关。同网络之下,发送一个数据包,包含了要查询主机IP,MAC地址填的“FF.FF.FF.FF.FF.FF”。广播之后,收到数据包与自身IP地址做对比,相同就填入MAC地址,并回复
  • 传输层:有了链路层和网络层,两台机子就可以建立连接了,但是一般一台机子由多个应用在运行,网络数据需要传输到特定的应用。这个时候就需要“端口”。不同的程序运行起来,连接不同的端口。因此,Unix系统就把主机+端口,叫做"套接字"(socket)。
    • UDP协议:加入端口信息和数据,就需要UDP协议,定义怎么组成。"标头"部分主要定义了发出端口和接收端口,"数据"部分就是具体的内容。然后,把整个UDP数据包放入IP数据包的"数据"部分,而前面说过,IP数据包又是放在以太网数据包之中的,所以整个以太网数据包现在变成了下面这样:
      image
    • TCP协议:UDP协议发出之后,不会再管是否接收到,TCP会确认是否收到,TCP数据包和UDP数据包一样,都是内嵌在IP数据包的"数据"部分。TCP数据包没有长度限制,理论上可以无限长,但是为了保证网络的效率,通常TCP数据包的长度不会超过IP数据包的长度,以确保单个TCP数据包不必再分割。
  • 应用层:规定应用数据的数据格式,这是最高的一层,直接面对用户。它的数据就放在TCP数据包的"数据"部分。因此,现在的以太网的数据包就变成下面这样。
    image

每一层都为了完成一种功能,都要遵守一些协议,这些协议就叫做互联网协议

发送的过程
而通过上面可以发现,网络通信的本质其实就是数据交换。而数据包的结构基本如下面所示:
image
而发送这个数据包需要知道两个地址:

对方的MAC地址
对方的IP地址(网络地址)

但是MAC地址有局限性,如果两个电脑不在同一个子网络,就不知道对方的MAC地址,就需要通过网关做一次转发。
image
1号电脑要向4号电脑发送一个数据包。它先判断4号电脑是否在同一个子网络,结果发现不是,于是就把这个数据包发到网关A。网关A通过路由协议,发现4号电脑位于子网络B,又把数据包发给网关B,网关B再转发到4号电脑。
1号电脑把数据包发到网关A,必须知道网关A的MAC地址。所以,数据包的目标地址,实际上分成两种情况:

场景 数据包地址
同一个子网络 对方的MAC地址,对方的IP地址
非同一个子网络 网关的MAC地址,对方的IP地址

发送数据包之前,电脑必须判断对方是否在同一个子网络,然后选择相应的MAC地址。

用户上网的设置

  • 静态IP地址
    一台新的电脑,如果需要接入网络,需要知道四个参数:

1.本机的IP地址
2.子网掩码
3.网关的IP地址
4.DNS的IP地址

Window的设置:
image
用户每次开机之后给定的IP地址,每次都会分到同样的,这叫做“静态IP地址上网”。
这样设置的话,其他的电脑就用不了这个IP地址,不够灵活。

  • 动态IP地址
    动态IP,会自动分配一个IP地址,不用人设,他所使用的协议是“DHCP协议”。
    这个协议中,每一个子网络有一个专门管理本网所有IP地址的计算器,他叫做“DHCP服务器”,新加入的计算器必须向“DHCP服务器”发送一个请求,申请IP地址和相应的网络参数。
    如果两个计算机在同一个网络,必须知道对方的MAC地址和IP地址,才能发送数据包,但是新加入的怎么处理?

DHCP协议:它是一个应用层协议,建立在UDP之上,数据包如图:
image
1). 最前面的以太网标头,设置本机的MAC地址和接收方MAC地址,接受方“FF-FF-FF-FF-FF-FF”。
2).IP标头,设置接受和发送方的IP,两者本机都是不知道的,所以发送方设置为“0.0.0.0”,接收方设置为“255.255.255.255”。
3).UDP标头,设置发出方的端口和接受方的端口,DHCP已经规定好了,发出为68,接受为67端口。
————
数据构造好之后,通过以太网广播发送,接受到了之后看到接受和发送的IP地址,DHCP服务器就知道是他的,分配好IP之后,返回数据信息,新加入的计算器就知道了自己的IP地址、子网掩码、网关地址、DNS服务器等。

所以不管是静态IP还是动态IP都需要知道四个参数:
1.本机IP地址
2.子网掩码
3.网关的IP地址
4.DNS的IP地址

访问一个网页的完整过程
用户设置了以上四个参数,打开访问“google.com”。

  1. DNS协议:已知DNS服务器,通过把地址发送给DNS知道IP地址
  2. 子网掩码:知道子网掩码判断IP是否是同一个子网络
  3. http 协议:把数据包裹起来,放入TCP数据包当中
  4. TCP协议:接收方默认端口知道,设置发送方端口,再把数据嵌入到IP数据包当中
  5. IP协议:设置接收方IP地址,发送发IP地址,数据嵌入到以太网数据包当中。
  6. 以太网协议:设置接受方和发送方的MAC地址,接收方为网关地址,通过网关IP和ARP协议可以获得MAC然后把IP数据包分割。
  7. 服务端相应:通过多个网关转发,接收方接收到数据,处理之后再相应回来

以上就是互联网协议的大致过层

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.