Coder Social home page Coder Social logo

shared's People

Contributors

dependabot[bot] avatar ycshill avatar

Stargazers

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

Watchers

 avatar  avatar

Forkers

abigdonkey

shared's Issues

读书笔记-《深入浅出React和Redux》

第1章 React新的前端思维方式

  • React 不支持 IE8
  • 环境搭建:
    • npm install --global create-react-app
  • chuang 创建项目
    • create-react-app frist_react_app
  • JSX
    • React 判断一个元素是HTML元素还是React组件,原则上就是看第一个字母是否是大写
    • onClick ???????

      即使现在,我们还是要说在HTML中直接使用onclick很不专业,原因如下:
      □ onclick添加的事件处理函数是在全局环境下执行的,这污染了全局环境,很容易产生意料不到的后果;
      □ 给很多DOM元素添加onclick事件,可能会影响网页的性能,毕竟,网页需要的事件处理函数越多,性能就会越低;
      □ 对于使用onclick的DOM元素,如果要动态地从DOM树中删掉的话,需要把对应的时间处理器注销,假如忘了注销,就可能造成内存泄露,这样的bug很难被发现。
      上面说的这些问题,在JSX中都不存在。
      首先,onClick挂载的每个函数,都可以控制在组件范围内,不会污染全局空间。
      我们在JSX中看到一个组件使用了onClick,但并没有产生直接使用onclick(注意是onclick不是onClick)的HTML,而是使用了事件委托(event delegation)的方式处理点击事件,无论有多少个onClick出现,其实最后都只在DOM树上添加了一个事件处理函数,挂在最顶层的DOM节点上。所有的点击事件都被这个事件处理函数捕获,然后根据具体组件分配给特定函数,使用事件委托的性能当然要比为每个onClick都挂载一个事件处理函数要高。
      因为React控制了组件的生命周期,在unmount的时候自然能够清除相关的所有事件处理函数,内存泄露也不再是一个问题。

  • React 工作原理
    • 响应式编程: UI = render(data);
    • Virtual Dom ???????

第2章 设计高质量的React组件

  • 组件
    • 一个组件只做一件事情

    • prop

      • 组件对外的接口
      • propTypes ??????(需要扩展)

      开发者在代码中定义propTypes,在开发过程中避免犯错,但是在发布产品代码时,用一种自动的方式将propTypes去掉,这样最终部署到产品环境的代码就会更优。现有的babel-react-optimize就具有这个功能,可以通过npm安装,但是应该确保只在发布产品代码时使用它

    • state

      • 改变state的状态的时候用 this.setState,原因如下:

      直接修改this.state的值,虽然事实上改变了组件的内部状态,但只是野蛮地修改了state,却没有驱动组件进行重新渲染,既然组件没有重新渲染,当然不会反应this.state值的变化;而this.setState()函数所做的事情,首先是改变this.state的值,然后驱动组件经历更新过程,这样才有机会让this.state里新的值出现在界面上。

    • 组件不应该改变prop的值,而state的存在的目的就是让组件来改变的;

    • 组件的生命周期

      • 分为【装载过程】、【更新过程】、【卸载过程】

      • 装载过程

        • 执行的顺序:
          • constructor
          • getInitialState (es6中用不到)
          • getDefaultProps (es6中用不到)
          • componentWillMount
          • render
          • componentDidMount
        • constructor
          出现原因多是:
          - 初始化state
          - 绑定this
        • render
          - 是一个纯函数,不能改变state和props的状态
          - render函数不做实际的渲染动作,只是返回一个JSX的描述结果,最终的React来渲染;
        • componentWillMount和componentDidMount

        在装载过程中,componentWillMount会在调用render函数之前被调用,componentDidMount会在调用render函数之后被调用,这两个函数就像是render函数的前哨和后卫,一前一后,把render函数夹住,正好分别做render前后必要的工作。
        不过,我们通常不用定义componentWillMount函数,顾名思义,componentWillMount发生在“将要装载”的时候,这个时候没有任何渲染出来的结果,即使调用this.setState修改状态,也不会引发重新绘制,一切都迟了。换句话说,所有可以在这个componentWillMount中做的事情,都可以提前到constructor中间去做,可以认为这个函数存在的主要目的就是为了和componentDidMount对称。
        而componentWillMount的这个兄弟componentDidMount作用就大了。
        需要注意的是,render函数被调用完之后,componentDidMount函数并不是会被立刻调用,componentDidMount被调用的时候,render函数返回的东西已经引发了渲染,组件已经被“装载”到了DOM树上
        我们还是以ControlPanel为例,在ControlPanel中有三个Counter组件,我们稍微修改Counter的代码,让装载过程中所有生命周期函数都用console.log输出函数名和caption的值,比如,componentWillMount函数的内容如下:
        componentWillMount() {
 console.log('enter componentWillMount ' + this.props.caption);
}

上面修改并没有添加任何功能,只是通过console.log输出一些内容,然后我们刷新网页,在浏览器的console里我们能够看见:
enter constructor: First enter componentWillMount First enter render: First enter constructor: Second enter componentWillMount Second enter render: Second enter constructor: Third enter componentWillMount Third enter render: Third enter componentDidMount First enter componentDidMount Second enter componentDidMount Third
> 可以清楚地看到,虽然componentWillMount都是紧贴着自己组件的render函数之前被调用,componentDidMount可不是紧跟着render函数被调用,当所有三个组件的render函数都被调用之后,三个组件的componentDidMount才连在一起被调用。
之所以会有上面的现象,是因为render函数本身并不往DOM树上渲染或者装载内容,它只是返回一个JSX表示的对象,然后由React库来根据返回对象决定如何渲染。而React库肯定是要把所有组件返回的结果综合起来,才能知道该如何产生对应的DOM修改。所以,只有React库调用三个Counter组件的render函数之后,才有可能完成装载,这时候才会依次调用各个组件的componentDidMount函数作为装载过程的收尾。
componentWillMount和componentDidMount这对兄弟函数还有一个区别,就是componentWillMount可以在服务器端被调用,也可以在浏览器端被调用;而componentDidMount只能在浏览器端被调用,在服务器端使用React的时候不会被调用。
到目前为止,我们构造的React应用例子都只在浏览器端使用React,所以看不出区别,但后面第12章关于“同构”应用的介绍时,我们会探讨在服务器端使用React的情况。
至于为什么只有componentDidMount仅在浏览器端执行,这是一个实现上的决定,而不是设计时刻意为之。不过,如果非要有个解释的话,可以这么说,既然“装载”是一个创建组件并放到DOM树上的过程,那么,真正的“装载”是不可能在服务器端完成的,因为服务器端渲染并不会产生DOM树,通过React组件产生的只是一个纯粹的字符串而已。
不管怎样,componentDidMount只在浏览器端执行,倒是给了我们开发者一个很好的位置去做只有浏览器端才做的逻辑,比如通过AJAX获取数据来填充组件的内容
在componentDidMount被调用的时候,组件已经被装载到DOM树上了,可以放心获取渲染出来的任何DOM。
在实际开发过程中,可能会需要让React和其他UI库配合使用,比如,因为项目前期已经用jQuery开发了很多功能,需要继续使用这些基于jQuery的代码,有时候其他的UI库做某些功能比React更合适,比如d3.js已经支持了丰富的绘制图表的功能,在这些情况下,我们不得不考虑如何让React和其他UI库和平共处。
以和jQuery配合为例,我们知道,React是用来取代jQuery的,但如果真的要让React和jQuery配合,就需要是利用componentDidMount函数,当componentDidMount被执行时,React组件对应的DOM已经存在,所有的事件处理函数也已经设置好,这时候就可以调用jQuery的代码,让jQuery代码在已经绘制的DOM基础上增强新的功能。
在componentDidMount中调用jQuery代码只处理了装载过程,要和jQuery完全结合,又要考虑React的更新过程,就需要使用下面要讲的componentDidUpdate函数。
- 更新过程
- 执行顺序:
- componentWillReceiveProps
- shouldComponentUpdate
- render
- componentDidUpdate
- componentWillReceiveProps
只要父组件的render函数被调用,在render函数里面被渲染的子组件就会经历更新的过程,不管父组件传给子组件的props有没有改变;

第3章 从Flux到Redux

  • Redux

    • redux的三个基本原则: 唯一数据源;保持状态只读;数据改变只能通过纯函数完成;

    • 唯一数据源:
      只有一个Store,如何设计Store上的状态结构是要考虑的重点,原则是避免冗余的数据

    • 保持状态只读:
      修改状态,只能够通过action修改,但是这个改变不是去修改状态上的值,而是创建一个新的状态对象返回给Redux。

    • 数据改变只能通过纯函数
      reducer(state, action)
      例如:

      reducer(state, action) => {
        switch(action.type) {
          case ActionTypes.type:
            return {...state, count: count + 1}  
        }
      }
      

      说明,Redux的reducer只负责计算状态,不负责存储状态;

  • 聪明组件和傻瓜组件

    • 聪明组件:用来存储state和redux交互;
    • 傻瓜组件:通过props传入状态,只是用来渲染;
  • Context ??? (后期有时间了解一下)
    Context 这个功能提供了一个全局可以访问的对象,一般避免使用

  • React-Redux
    做了两个事情: 提供一个connect;提供一个Provider

    • connect:
      相当于聪明组件,用mapStateToProps 把聪明组件的状态传给傻瓜组件,通过mapDispatchToProps把用傻瓜组件的用户行为转化为派发给Store的动作。

第4章 模块化React和Redux应用

  • 模块化应用要点:
    • 代码文件的组织方式:
      • 按角色分类
        此处输入图片的描述
      • 按功能分类
        此处输入图片的描述
    • 模块的接口: 负责内部的联系和外部的输出
  • 工具:
    • redux-immutable-state-invariant ????

第5章 React组件性能优化

  • 性能优化
    • 使用react-redux 中的connect,原理是shouldUpadateComponent(),要诀就是避免传递给其他组件的prop值是一个不同的对象;
    • Key
      可以提高性能,要满足两点:
      • 唯一性
      • 稳定性
    • 使用reselect
      工作原理:只要相关的状态没有改变,那就使用上一次缓存的结果;
    • 范式化状态数设计
      要扁平化,范式化,所谓范式化,就是按照关系型数据库的设计原则,减少冗余数据;

第6章 React高级组件

  • 高级组件:???????
    • 定义:

      简单的说,一个高级组件就是一个函数,这个函数接受一个组件作为输入,然后返回一个新的组件作为结果,而且返回新的组件具有新的组件不具有的功能;

    • 使用意义
      • 重用代码
      • 不改变原有组件的情况下修改现有React组件的行为。
  • 以函数为子组件:???????

第7章 Redux和服务器通信

  • Redux和服务端通信:
    • fetch: 用fetch来访问服务器数据资源,fetch函数返回的结果是Promise对象,虽然被fetch广为接受,大有取代其他网络访问方式的架势,但是它有一个特性一直被人诟病,那就是fetch认为只要服务器返回一个合法的HTTP响应就算成功,就会调用then提供的回调函数,即使这个HTTP响应的状态码是表示出错了的400或者500。正因为fetch的这个特点,所以我们在then中,要做的第一件事就是检查传入参数response的status字段,只有status是代表成功的200的时候才继续,否则以错误处理。

      fetch(apiUrl).then((response) => {
      if (response.status !== 200) {
      throw new Error('Fail to get response with status ' + response.status);
      }
      
    • 访问服务器数据的时机和步骤

      • 访问服务器API是一个异步操作。因为JavaScript是单线程的语言,不可能让唯一的线程一直等待网络请求的结果,所以所有对服务器的数据请求必定是异步请求。
        但是,React组件的渲染又是同步的,当开始渲染过程之后,不可能让Weather组件一边渲染一边等待服务器的返回结果。
        总之,当Weather组件进入装载过程的时候,即使此时Weather立刻通过fetch函数发起对服务器的请求,也没法等待服务器的返回数据来进行渲染。因为React组件的装载过程和更新过程中生命周期函数是同步执行的,没有任何机会等待一个异步操作。
        所以,可行的方法只能是这样,分两个步骤完成:
        步骤1,在装载过程中,因为Weather组件并没有获得服务器结果,就不显示结果。或者显示一个“正在装载”之类的提示信息,但Weather组件这时候要发出对服务器的请求。
        步骤2,当对服务器的请求终于获得结果的时候,要引发Weather组件的一次更新过程,让Weather重新绘制自己的内容,这时候就可以根据API返回结果绘制天气信息了。
    • Redux 访问服务器

      • redux-thunk 中间件: 解决Redux访问服务器异步的问题;?????

第8章 单元测试

  • 单元测试 ????

第9章 扩展Redux

  • 中间件:
    • 中间件用来增强Redux Store的dispatch方法。

    • 中间件模板:每个中间件都是一个函数,返回一个接收next参数的函数,而接收next参数的函数又返回一个接收actions参数的函数。next参数本身也是一个函数,中间件调用这个next函数通知Redux自己的处理工作已经结束。例如以下的代码:

          function doNothingMiddleware({dispatch, getState}) {  //这两个参数是非必须的
            return function(next) {
              return function(action) {
                // 这里面可以做很多事情,比如调用dispatch派发出一个action对象;tedia调用getState获取当前的Redux Store上的状态;访问action对象上action上的所有数据;
                return next(action);
              }
            }
          }
      

      上面的代码就等同于:

       ({dispatch, getState}) => next => action => next(action)
      
    • 中间件的使用:
      以同时使用 redux-thunk 和 redux-devtools 增强功能为例:

       import {createStore, applyMiddleware, compose} from 'redux';
       import thunkMiddleware from 'redux-thunk';
       const win = window;
       const storeEnhancers = compose( //redux提供的compose函数,可以把多个store Enhancer c串联成为一个函数,因为createStore 只能够接受一个Store Enhancer 的参数;
         applyMiddleware(...middleware),  // **一定要作为第一个**
         (win && win.devToolsExrension) ? win.devToolsExension : f=> f;
      )
      // createStore 接受三个参数,第一个参数是reducer,第二个如果是对象,被认为是创建Store的初始化状态,第三个参数是增强器
      const store = createStore(reducer, storeEnhancers);
      
    • 中间件的开发?????????

第10章 动画

  • 动画
    • ReactCSSTransitionGroup
      • 主要用来解决装载过程和卸载过程中的动画;
      • transitionName
      • transitionAppear
    • React-Motion

第11章 多页面应用

  • 单页应用
    • 这种看起来是多页面但是其实只有一个页面的应用,就是单页应用;
  • React-Router
    • Link
    • react-router-redux
  • 代码分片?????
    对javascript进行分片打包,然后按需加载;

第12章 同构

  • 同构????????

以上画 ?号的都要再读一遍!!!!

javascript 高级程序设计 6-10

  • 理解对象-属性类型
  • 数据属性
    • 定义:普通定义;
    • 特性:
      • configurable: 1. 是否通过 delete 可以删除 2. 是否可以改为访问其属性 3. 是否可以修改属性特性;
      • enumerable: 同下
      • writable: 是否可以修改属性的值;
      • value: 这个属性的值;
  • 访问器属性
    • 定义:必须使用 Object.defineProperty(对象,属性名,特性组成的对象)
    • 常见的使用方式: 设置一个属性的值,会导致其他属性的变化
    • 特性
      • configurable : 1.是否能通过 delete 删除;2.是否能修改特性;3.是否能把属性修改为数据属性;
      • enumerable: 是否能通过for-in循环返回属性;
      • set: 写入属性时候调用, 默认值为undefined如果定义的时候没有写,则不能写入
      • get:读取的时候调用,默认为undefined, 定义时没有,不能独缺
  • 例子
var person = {
  name: 'lily';  // 数据属性
  _weight: 50;
}

// height就是访问器属性
Object.defineProperty(person, 'height', {
get: function() {
  return 168;
},
set: function(newVal) {
  newVal > 180 ? this._weight = 60 : 50;
}
})
  • 三个方法

    • Object.defineProperty(对象,属性名, 特性组成对象) 可以 1. 修改数据属性特性; 2.可以定义访问器属性;
    • Object.defineProperties(对象,属性的字面量对象) 可以同一时间,创建多个属性;
     Object.defineProperties(person, {
       name: {
          writable: true,
          value: 'lily',
       },
       height: {
         get: function() { return 168 }
       }
     })
    
    • 读取属性特性 Object.getOwnPropertyDescriptor(对象,属性名)

技术分享-HTTP扫盲

HTTP 扫盲

标签(空格分隔): HTTP


前言

本篇博客是读完《图解HTTP》之后,摘录和总结其中个人感觉需要知道的知识点,因为这部分知识点比较枯燥乏味,所以本篇博客力求简单,明了,有趣。

  • WEB 及其网络的基础
  • 确保Web安全的HTTPS
  • 确认访问用户身份的认证
  • 基于HTTP追加协议

WEB 及其网络的基础

TCP/IP 分层管理

ISO制定的OSI参考模型的过于庞大、复杂招致了许多批评。与此对照,由技术人员自己开发的TCP/IP协议栈获得了更为广泛的应用。
此处输入图片的描述

接下来的图,主要说明各个层的作用和各层涉及到协议:
此处输入图片的描述

常用协议:IP、TCP、DNS

####IP协议(网路层)

  • 概念:IP协议是在源地址要和目的地址之间传送一种称为数据包的东西
    此处输入图片的描述

####TCP 协议(传输层)
TCP 协议是提供可靠的字节流服务;

  • 可靠:确保数据能够精确可靠的传给对方(三次握手);
  • 字节流服务:为了传输方便,将大块数据分割为以报文段为单位的数据包进行管理;
    通过三次握手建立连接:
    此处输入图片的描述
    四次挥手关闭连接:
    此处输入图片的描述
    ####DNS 协议
    提供域名和IP地址之间的解析服务
    此处输入图片的描述

TCP/IP 通信传输流

此处输入图片的描述

URL 和 URI 格式

URI 是统一资源标识符,而 URL 是统一资源定位符
此处输入图片的描述

web客户端访问资源的流程

此处输入图片的描述

HTTP协议简介

定义

HTTP-超文本传输协议,是位于计算机网络中的应用层,HTTP是建立在TCP协议之上,所以HTTP协议的瓶颈及其优化技巧都是基于TCP协议本身的特性,例如tcp建立连接的3次握手和断开连接的4次挥手以及每次建立连接带来的RTT延迟时间;

发展历史

HTTP 发展起来是因为在互联网的黎明时期,CERN(欧洲核子研究组织)的蒂姆.博纳斯-李 博士提出了一种让远隔两地的研究者们共享知识的设想。最初的设想的基本理念是:借助多文档之间互相关联行程超文本,连成互相参阅的WWW(万维网)。后来提出了三项技术:作为页面的文本标记语言-HTML(超文本标记语言);作为文档传输协议的-HTTP(超文本传输协议);指定文档所在地址的URL(统一资源定位符);

  • 1991 HTTP/0.9 (没多久就废弃了)
  • 1996 HTTP/1.0 (需要使用keep-alive参数来告知服务器端要建立一个长连接)
  • 1999 HTTP/1.1 (默认支持长连接)
  • 2015 HTTP/2 (主要是性能的提高)

确保Web安全的HTTPS

HTTP的缺点

  • 通信使用明文(不加密),可能会被窃听;
  • 不验证通信方的身份,可能遭到伪装;
  • 无法证明报文的完整性,可能已遭篡改;

1. 通信使用明文(不加密),可能会被窃听

  • HTTP 本身不具备加密的功能,所以无法做到对通信整体进行加密;
  • TCP/IP 协议族的工作机制,通信内容在所有的通信线路上都有可能遭到窥视;

2. 不验证通信方的身份,可能遭到伪装

  • 在HTTP协议通信时,由于不存在确认通信方的处理步骤,任何人都可以发起请求;

3. 无法证明报文的完整性,可能已遭篡改

  • 由于HTTP 无法证明通信报文的完整性,说以即使请求或者响应的内容遭到了篡改(中间人攻击),也没有办法获悉。

HTTPS的诞生

由于 HTTP 的以上缺点,为了确保安全就诞生了HTTPS。简单的说 ** HTTPS = HTTP + 加密 + 认证 + 完整性保护**。HTTPS并不是一种新的协议,只是HTTP通信接口部分用SSL和TLS协议代替而已。例如,HTTP直接和TCP通信,使用了SSL时候,就会演变为先和SSL通信,再由SSL和TCP通信。
此处输入图片的描述

常用的加密技术

  • 共享密钥加密/对称密钥加密
  • 公开密钥加密/非对称加密
  • 混合加密机制

共享密钥加密/对称密钥加密

  • 概念:信息的发送方和信息的接收方使用 同一套密码 进行加密和解密;
  • 共享密钥加密可以理解为服务器为了数据传输安全,送给你了一把钥匙,这把钥匙可以把数据在盒子里,也可以打开已经锁上的盒子。当数据发送到服务器时,服务器会用同样的钥匙打开盒子。
    此处输入图片的描述
  • 缺点:密钥有很大的风险可能会被黑客拦截;
  • 优点:处理速度快;
  • 总结:这个例子可以简单的理解为:小红和小花是朋友,小花家的门有一个钥匙,有一天为了方便,配了个钥匙给小红,从此小红就可以随便的进出小花家了,但是钥匙得好好保存,万一被小偷得到,就也可以随便进出小花家了。

公开密钥加密/非对称加密

  • 概念:公开密钥有 一对非对称的密钥,一把公开密钥,一把私有密钥,公开密钥可以发送给任何发送请求方,而私有密钥 只有一方有,一对中的一把密钥加密后,只能用另外一个进行解密,即使是自己也不可以解密
  • 公开密钥加密可以理解为,服务器首先给发送请求的客户端发送了一个公开密钥**(公钥可以发给任何请求方)**,客户端收到好,对报文进行加密后,发送给服务器,服务器再用独有的私钥进行解密。
    此处输入图片的描述
  • 优点:防止在传输过程中被获取密钥;
  • 缺点:处理速度慢;
  • 公钥用来进行加密,私钥用来数字签名

混合加密机制

  • 概念:混合使用公开密钥加密和共享密钥加密,在交换密钥的环节采用公开密钥加密,之后建立的通信报文阶段则使用共享密钥加密,这样既可以避免密钥被盗取,又可以提高通信的效率;
    此处输入图片的描述
  • HTTPS 采用的混合加密机制;

数字签名和证书

数字签名

  • 概念
    一封信中,文末的签名是为了证明这封信是签名者写的,同理,数字签名也是为了证明某个消息是特定的某个人发的(有效性);同时,数字签名还能证明消息没有被篡改(完整性);
  • 作用
    • 有效性;
    • 完整性;
  • 如何生成数字签名
    一般不对消息直接进行签名,而是对消息的哈希值进行签名。
    • 对消息进行哈希计算,得到哈希值;
    • 利用私钥对哈希值进行加密,生成数字签名;
    • 将签名附件在消息后面,一起发送过去;
  • 验证签名
    • 收到消息后提取消息中的签名;
    • 用公钥对签名进行解密,得到哈希值1;
    • 对消息中的正文进行计算得到哈希值2;
    • 比较哈希值1和哈希值2,如果相同,则验证成功;

证书

  • 概念:
    证书其实就是对公开加密方法中的公钥进行加密,是对公钥的合法性提供证明的技术;
  • 作用:
    • 认证服务器的公开密钥是真实有效的数字证书认证机构;
    • 服务器的公开密钥是值得信赖的;
  • 认证机构
    可以使用数字认证机构(CA)和其他相关的机关颁发公开的密钥证书(就像是银行);
  • 证书种类
    • EV SSL 证书
      EV SSL证书可以证明作为通信一方的服务器是否规范,同时也可以确认服务器背后运营的企业是否是真实存在的。
    • 客户端证书
      证明服务器正在通信的对方始终是预料之内的客户端;
    • 自签名证书
      • 概念:由自认证机构(独立构建的认证机构)颁发的证书称为自签名证书;
      • 浏览器访问该服务器的时候,会显示“无法确认连接安全性”或“该网站的安全可能存在问题”等警告;
      • 由自认证机构颁发的服务器证书之素哟所以不起作用,是因为它无法消除伪装的可能性(就像是跑路的p2p公司);
  • 生成证书
    • 服务器会将公钥A给CA(公钥是服务器的);
    • CA用自己的私钥B对A进行加密,生成数字签名C;
    • CA把公钥A,数字签名C,和附加服务器的一些信息生成证书,发给服务器;
  • 验证证书
    多数浏览器会将开发商发布版本的时候,会事先在内部植入常用的认证机关的公开密钥;
    • 客户端得到证书;
    • 客户端得到证书公钥D;
    • 客户端用公钥D对证书中的数字签名进行解密,得到哈希值1;
    • 客户端对公钥A进行哈希值计算,得到哈希值2;
    • 如果哈希值1和哈希值2相等,则证书是合法的;
  • 证书作废
    当用户私钥丢失或者被盗,认证机构需要对证书进行作废;

总结

https的通信流程(阮大神的一篇博文):http://www.ruanyifeng.com/blog/2011/08/what_is_a_digital_signature.html

确认访问用户身份的认证

何为认证

  • 概念:
    就像是你买的保险箱只想某些人可以看一样,网页上的web页面也有可能只想特定的人浏览,这就少不了认证功能了,简单的说,认证就是判断访问服务器者的身份
  • 核对信息通常有以下几点:
    • 密码:只有本人才会知道的字符串信息;
    • 动态令牌:仅限本人持有的设备内显示的一次性密码;
    • 数字证书:仅限本人(终端)持有的信息;
    • 生物认证:指纹和虹膜等本人的生理信息;
    • IC 卡等: 仅限本人持有的信息;
  • HTTP 使用的认证方式
    • BASIC 认证 (基本认证,不常用)
    • DIGEST 认证 (摘要认证,不常用)
    • SSL 客户端认证 (付费)
    • FormBase 认证 (基于表单的认证)

SSL 客户端认证

  • 概念:SSL 客户端认证是借由HTTPS的客户端证书完成认证的方式,要完成这种认证,需事先将客户端证书分发给客户端,且客户端必须安装此证书
  • 认证步骤
    • 接收到需要认证资源的请求,服务器会发送 Certificate(证书) Request,要求客户端提供客户端证书;
    • 用户选择将发送的客户端证书后,客户端会把客户端证书信息以Client Certificate 报方式发送给服务器;
    • 服务器验证客户端证书验证通过后方可领取证书内客户端的公开密钥,然后开始HTTPS加密通信;
  • SSL 客户端认证需要费用
    一般这部分费用是:
    • 认证机构购买客户端证书的费用;
    • 服务器运营者为保证自己搭建的认证机构安全运营所产生的费用;
    • 自己搭建的认证机构,为维持安全运行产生的费用;

基于表单的认证

  • 概念:
    客户端会向服务器上的Web应用程序发送登录信息,按登录信息的验证结果认证;
  • session 管理及Cookie应用
    因为HTTP是无状态协议,所以一般使用 Cookie 来管理 Session;
  • 步骤
    • 客户端把用户ID和密码等登录信息放入报文的实体部分,通常是以POST方法把请求发送给服务器;
    • 服务器在Set-Cooki中写入Session ID (区分不同用户的等位号,为了防止被盗,session id 应该使用难以推测的字符串,且服务器也需要进行有效期的管理,为了保证安全建议在Cookie内写httponly)
    • 客户端收到从服务器发来的Session ID 后,会将其作为Cookie保存在本地,下次向服务器发送请求时,浏览器会自动发送Cookie;

双因素认证

SSL客户端认证和表单认证结合,SSL客户端认证来认证客户端的计算机,而表单认证来确定用户本人的行为;

基于HTTP追加协议

HTTP 通信的瓶颈

  • HTTP 协议的最大弊端就是每个 TCP 连接只能对应一个 HTTP 请求,即每个 HTTP 连接只请求一个资源,浏览器只能通过建立多个连接来解决;
  • 请求只能从客户端开始,客户端不可以接受响应以外的指令;
  • 请求/响应首部未经压缩就发送,首部信息越多越延迟
  • 发送冗长的首部,每次互相发送相同的首部造成浪费较大;
  • 可任意的选择数据压缩格式,非强制的压缩发送;
    例如论坛上,成千上万的用户发布内容的时候,web网站需要及时的更新,而使用HTTP协议探知服务器上是否有内容更新,就必须频繁地从客户端到服务器端进行确认。如果服务器上没有内容更新,就会产生徒劳的通信。

追加的几种协议:

  • Ajax
  • Comet
  • SPDY(已经被废弃)
  • WebSocket
  • HTTP/2.0

WebSocket

HTTP/2.0

支持HTTP2.0的条件

  1. 需要浏览器的支持,目前最新版的 Chrome、Opera、 FireFox、 IE11、 edge 都已经支持了;
  2. 需要 WEB 服务器的支持,比如 Nginx , H20;
    如何浏览器或者服务器有一方不支持,就会自动变为HTTP/1.1

HTTP/2.0 改进点

  • 二进制分帧
  • 压缩头部
  • 多路复用
  • 请求优先级
  • 服务器推送
  • 强制 SSL
HTTP/2.0 性能增强的核心:二进制分帧

在应用层和传输层之间增加了一个二进制分帧层,以达到“在不改动HTTP语义,HTTP方法、状态码、URI机及首部字段的情况下,突破HTTP/1.1 的性能限制,改进传输性能,实现低延迟和高吞吐量”;
此处输入图片的描述

在二进制分帧层上,HTTP2.0 会将所有的信息分割为更小的消息和,并对它们采用二进制格式的编码,其中HTTP1.X 的首部信息会被封装到Headers帧,而request body则封装到Data帧里

HTTP2.0 通信都在一个连接上完成,这个连接可以承载任意数量的双向数据流。每个数据流以消息的形式发送,而消息又是由一个或者多个组成,这些帧可以乱序发送,然后再根据每个帧首部的流标识符重新组装;

压缩头部

HTTP/2.0规定了在客户端和服务器端会**使用并且维护「首部表」**来跟踪和存储之前发送的键值对,对于相同的头部,不必再通过请求发送,只需发送一次

如果请求中不包含首部,那么首部的开销就是零字节,此时所有的首部都自动使用之前请求发送的首部;如果首部发变化了,那么只需要发送变化了数据在Headers帧里面,新增或者修改的首部帧会被追加到“首部表”。首部表在HTTP2.0连接存续4qi期内始终的存在,由客户端和服务器共同渐进地更新。

多路复用

HTTP2.0所有的通信都是在一个TCP连接上完成的。HTTP2.0 把 HTTP 协议通信的基本单位缩小为一个个的帧,这些帧对应着逻辑流中的消息。**这些帧并行地同在一个TCP连接上双向交换消息。**也就是说同一链接上有多个不同方向的数据流在传输。客户端可以一边乱序发送streams,也可以一边接收服务器的响应,而服务器端同理。

  • 优点
    HTTP性能的关键在于低延迟,而不是高带宽。大多数的HTTP连接的时间短,而且是突发性的,但是TCP只在长时间连接传输大块数据时效率才最高。HTTP2.0 通过让所有的数据流公用一个连接,可以更有效地使用TCP连接,让高带宽也能真正服务于HTTP的性能。
    同时,单链接多资源的方式,有如下好处:

  • 可以减少服务器的压力,内训占用少,连接的吞吐量大;

  • 由于 TCP 连接减少,使得网络阻塞的情况得以改观;

  • 慢启动时间减少,拥塞和丢包恢复速度更快。

    因此, “资源合并减少请求”的优化手段对于HTTP2.0来说是没有效果的。

请求优先级

既然所有资源都是并行发送,那么就需要“优先级”的概念了,这样就可以对重要的文件进行先传输,加速页面的渲染。

服务器推送

在 HTTP2.0中,服务器推送是指客户端请求之前发送数据的机制。

强制 SSL

虽然 HTTP2.0 协议并没有声明一定要使用SSL,但是Google Chrome 等浏览器强制要使用 HTTP2.0 必须使用SSL。

学习笔记-Dart基础语法

Dart 基础

  1. 列表项

标签(空格分隔): Flutter


常量和变量

变量

  • var
    如何不赋值的话,就是 null,和js中的差不多;
  • final
    声明只能赋值一次的变量;

常量

  • const
var a;
print(a);  // null

a = 10;
print(a); // 10

a = 'Hello Dart';
print(a);  // Hello Dart;

//  总结 var 和js 一样,  未初始化的时候是 null,(变量)

// 声明一个只赋值一次的变量
final b = 20;
print(b);  // 20;

// 声明一个常量(编译时)
const c = 30;
print(c); // 30

数据类型

内置数据类型

  • Number
  • String
  • Boolean
  • List(列表)
  • Map(键值对)

Number

  • int

  • double
    注意定义了double类型字段的时候,可以赋值为整数的,但是打印的时候还是浮点的

    num a = 10;
    print(a); // 10
    a = 10.5;
    print(a); // 10.5
    
    int b = 10;
    // b = 10.5;  不可以
    print(b);
    
    double c = 10.5;
    print(c);  // 10.5
    c = 2;
    print(c); // 2.0
    
  • 数值型操作

    • 运算符+,-,*,/,%,~/
      • '+' : 有一个是double,就会自动转化为double
      • '/': 返回的是double;
      • '~/': 返回的是取正;
      • '%': 返回的是取余的;
      print(a + b);  // 有一个是浮点型的就是浮点型的 20.5
      print(b + d);  // 21
      print(d - a); // 0.5
      print(b * d); // 110;
      print(d/a); // 1.0476190476190477
      print(d ~/a); // 1 取整
      print(d%a); // 0.5 取余
    
    • 常用属性

      • isNaN
      • isEven:是否是偶数
      • isOdd:是否是奇数
       int a = 2;
       print(a.isNaN); // false
       print(a.isEven); // true
       print(a.isOdd); // false
      
    • 常用方法

      • abs()
      • round() 四舍五入
      • floor() 向下取整
      • ceil() 向上取整
      • toInt()
      • toDouble()
      int a = -100;
      print(a.abs());
      
      double b = 10.5;
      print(b.round()); // 四舍五入 11
      print(b.floor()); // 向下取整 10
      print(b.ceil()); // 向上取整 11
      
      print(b.toInt()); // 10
      print(a.toDouble());
      

String

  • 创建字符串
    • 使用单引号或者双引号创建字符串;
    • 使用三个单引号或者双引号创建字符串;
    • 使用r创建raw(原生-不对特殊字符进行转换)的字符串;
    String a = 'hello string'; // "" 使用单引号,双引号创建字符串
    print(a);  // hello string
    
    String b = ''' hello
    multi line string'''; // """  使用三个单引号,双引号创建多行字符串
    print(b);
    // hello
    // multi line string
    
    String c = 'hello  \n not raw'; // \n 会被转换为换行符
    print(c);
    // hello
    // not raw
    
    String d = r'hello \n raw'; // 使用r创建原始的raw字符串, 其中的\n不会被转义
    print(d); // hello \n raw
    
  • 字符串的操作
    • '+' : 连接;
    • '*': *4 拼接四个一样的字符串;
    • '==': 判断是否相等;
    • '[]': [2]返回字符串第二个位置的元素;
    • 插值表达式:${表达式}
    • 插值变量:$a
    • 常用属性:
      • length
      • isEmpty
      • isNotEmpty;
    • 常用方法:
      • contains(str) 是否包含某个字符串;
      • subString(start, end) 截取字符串,掐头去尾;
      • startWith(str) 是否是以**开头;
      • endsWith(str) 是否是以**结尾;
      • indexOf(str) 所含第一字符所在的开始位置;
      • lastIndexOf(str) 所含最后一个字符所在的位置;
      • toLowerCase() 转为小写;
      • toUpperCase() 转为大写;
      • trim() 去掉左右两边的空格;
      • trimLeft() 去掉左边的空格;
      • trimRight() 去掉右边的空格;
      • split() 以拆分为数组;
      • replaceAll(pattern, str) 替换所有匹配的;
      • replaceFirst(pattern, str) 替换第一个匹配的;
      • replaceRange(start, end, str) 替换指定范围的;
      // 常用的操作
      String a = 'i am a girl';
      print(a + 'i love life'); // i am a girli love life
      print(a*3); // i am a girli am a girli am a girl
      print(a[2]); // a
      
      int b = 10;
      int c = 10;
      print('b is $b'); // b is 10
      print('a + b = ${b + c}'); // a + b = 20
      
      // 常用的属性
      print(a.length); // 11
      print(a.isEmpty); // false
      print(a.isNotEmpty); // true
      
      // 常用的方法
      String d = 'this is a sun day';
      print(d.contains('is')); // true;
      print(d.substring(2,4)); // 掐头去尾 is
      print(d.startsWith('a')); // false
      print(d.endsWith('ay')); // true
      print(d.indexOf('is'));  // 2
      print(d.lastIndexOf('is')); // 5
      print(d.toLowerCase()); // this is a sun day
      print(d.toUpperCase()); // THIS IS A SUN DAY
      print(d.split(' ')); // [this, is, a, sun, day]
      print(d.replaceAll('s', 'w')); // thiw iw a wun day
      print(d.replaceFirst('s', 'w')); // thiw is a sun day
      print(d.replaceRange(0, 5, 'r')); // ris a sun day
      String e = '   this is a sun day   ';
      print(e.trim()); // this is a sun day
      print(e.trimLeft());
      print(e.trimRight()); //    this is a sun day
      

Boolean

  • true
  • false
 bool a = true;
 print(a); // true;

列表List(数组)

list和js中的数组很相似

  • 创建List

    • 直接声明
    • 创建不可变的List
    • 构造创建
    // 创建List
    List a = [1, 3, 4, true, 'hello'];
    print(a); // [1, 3, 4, true, hello]
    List b = const [1, 2, 4];
    //  b[1] = 'eh'; // Cannot modify an unmodifiable list
    List c = new List();
    print(c); // []
    
  • 常用操作

    • [] 获取i的值,或者修改i位置的值;
    • length 获取数组的长度;
    • add(dynamic value) 在末尾添加元素
    • addAll(Iterable iterable) 添加可遍历的元素;
    • insert(int index, dynamic ele) 把ele添加到指定的位置;
    • remove(ele) 删除指定元素
    • removeLast() 删除最后一个
    • removeAt(index) 删除指定位置的元素
    • removeRange(start, end) 删除几个元素,掐头去尾
    • clear() 删除;
    • indexOf(ele) 返回第一个匹配元素的位置,没有则返回-1
    • lastIndexOf(ele) 返回最后一个匹配字符的位置,没有则返回-1;
    • sort([int compare(E a, E b)]) 排序里面可以传入哪种方式进行比较;
    • sublist(start) 返回一个截取的数组;
    • shuffle()
    • asMap() 将List 转换为Map
    • forEach(void f(E element)) 循环遍历 传入一个函数
      // List常用的操作
      List d = [1, true, 'hello'];
      print(d); // [1, true, hello]
      print(d[0]); // 1
      d[0] = 4;
      print(d); // [4, true, hello]
      print(d.length); // 3
      d.add('world');
      print(d); // [4, true, hello, world]
      d.insert(2, false); // 将ele插入数组的指定位置
      print(d); // [4, true, false, hello, world]
      d.remove('hello');
      print(d); // [4, true, false, world]
      d.removeLast(); // 删除最后一个
      print(d); // [4, true, false]
      d.removeAt(1); // 删除指定位置
      print(d); // [4, false]
      d.addAll([5, 6, 0]);
      print(d); // [4, false, 5, 6, 0]
      d.removeRange(0, 1);
      print(d); // [false, 5, 6, 0] 掐头去尾
      print(d.indexOf(6)); // 2
      d.addAll([5, 6, 0]);
      print(d); // [false, 5, 6, 0, 5, 6, 0]
      print(d.lastIndexOf(0)); // 6
      d.remove(false);
      print(d); // [5, 6, 0, 5, 6, 0]
      d.sort();
      print(d); // [0, 0, 5, 5, 6, 6]
      print(d.sublist(2)); // [5, 5, 6, 6]
      d.shuffle();
      print(d); // [6, 5, 0, 6, 5, 0]
      d.forEach(print);  
      /**
       *
        flutter: 0
        flutter: 5
        flutter: 6
        flutter: 0
        flutter: 5
        flutter: 6
       */
       print(d.asMap()); // {0: 5, 1: 5, 2: 0, 3: 6, 4: 6, 5: 0}
    

Map

键值对的形式,有点像对象

  • 创建Map
    • 直接构建 var a = {'first': 'a', 'second': 'b'};
    • 创建不可变的Map var a = const {'first': 'a', 'second': 'b'};
    • 构造创建var a= new Map();
// 创建Map
Map a = {
'name': 'lily',
'age': '26',
};
print(a); // {name: lily, age: 26}
// 创建一个不可变的map
Map b = const {
'name': 'lily',
'age': '26',
};
// b['name'] = 'xiaoming'; // Cannot set value in unmodifiable Map
print(b);
Map c = new Map();
print(c); // {}
  • 常用的操作
    • [] 获取和改变;
    • length
    • isEmpty()
    • isNotEmpty();
    • keys() 获取key;
    • values();
    • containsKey(key);
    • containsValue(val);
    • remove(key) 删除指定key的元素;
    • forEach(void f(K key, V value));
    void main() {
      runApp(MaterialApp(
        title: 'dart 的基础语法',
        home: FirstPage(),
      ));
    
      // 创建Map
      Map a = {
        'name': 'lily',
        'age': '26',
      };
      print(a); // {name: lily, age: 26}
      // 创建一个不可变的map
      Map b = const {
        'name': 'lily',
        'age': '26',
      };
      // b['name'] = 'xiaoming'; // Cannot set value in unmodifiable Map
      print(b);
      Map c = new Map();
      print(c); // {}
    
      // 常用的操作
      Map d = {
        'name': 'lily',
        'age': '26',
      };
      print(d['name']); // lily
      d['name'] = 'xiao';
      print(d); // {name: xiao, age: 26}
      print(d.length); // 2
      print(d.isNotEmpty); // true
      print(d.isEmpty); // false
      print(d.keys); // (name, age)
      print(d.values);  // (xiao, 26)
      print(d.containsKey('name')); // true
      print(d.containsValue(3)); // false
      d.remove('name');
      print(d); // {age: 26}
      d.forEach(getKeyValue); //key-age, value-26
    }
    
    void getKeyValue(key, value) {
      print('key-$key, value-$value');
    }
    

dynamic

可变数据类型;

dynamic a = 0;
print(a); // 0
a = 'hello';
print(a); // hello

List arr = new List<dynamic>();
arr.add(1);
arr.add('hello');
print(arr); // [1, hello]

运算符

算数运算符

  • '+'
  • '-'
  • '*'
  • '/' : 除
  • '~/':取整
  • '%':取余
int a = 10;
int b = 11;

print(a + b); // 21
print(a - b); // -1
print(a * b); // 110
print(b / a); // 1.1
print(b ~/ a); // 1
print(b % a); // 1

关系运算符

  • '==' : 判断内容是否相等;
  • '!='
  • '>'
  • '<'
  • '>='
  • '<='
  • 前++(先++,在取值),后++(先取值做运算,再++);
  • 前--,后--
int a = 10;
int b = 11;

print(a == b); // false
print(a != b); // true
print(a > b); // false
print(a < b); // true
print(a >= b); // false
print(a <= b); // true
print(a++); // 10
print(++a); // 12 上面的表达式使用a的值的时候,就++啦
print(a--); // 12
print(--a); // 10

String strA = '123';
String strB = '123';
print(strA == strB); // true

逻辑运算符

  • '!' :取反
  • '&&'
  • '||'
bool a = true;
bool b = false;
print(!a); // false
print(a && b); // false
print(a || b); // true

赋值运算符

  • 基础运算符
    • '=';
    • '??=' : b ??= a如果b有值的话,就取b的值,如果b没值的话,就取=后面的值;
  • 复合运算符
    将进行操作后的值赋值给前面的值;
    • '+='
    • '-='
    • '*='
    • '/='
    • '%='
    • '~/='
int a = 10; // =
print(a); // 10

int b;
b ??= a;
print(b); // 10; 如果b没有值,就会读取 = 后面的值

int c = 5;
c ??= a;
print(c); // 5 如果c有值,就会读取c本身的值

print(a += b); // 20
print(a -= b); // 10
print(a *= b); // 100
// print(a /= b); //  value of type 'double' can't be assigned to a variable of type 'int'
print(a ~/= b); // 10
print(a %= b); // 0

double d = 11.0;
print(d /= b); // 1.1

条件表达式

  • 三目运算符 ? = :
  • ??运算符 如果前面的值为null,则取后面的值,否则就是前面的值
bool isMan = false;
print(isMan ? '$isMan, he is a man' : '$isMan, he is not a man'); // false, he is not a man
isMan = true;
print(isMan ? '$isMan, he is a man' : '$isMan, he is not a man'); // true, he is a man

String strA;
String strB = 'hello';
print(strA ?? strB); // hello, 如果前面是null的话,就读取后面的
print(strB ?? strA); // hello, 如果前面不是null的话,就读取前面的值

控制语句

if语句

  • if
  • if else
  • if... else if... else...

for语句

  • for
  • for in 语句
List arr = [1, 2, 3, 4];
for(int i = 0; i < arr.length; i++){
print('$i--${arr[i]}');
/***
  flutter: 0--1
  flutter: 1--2
  flutter: 2--3
  flutter: 3--4
 */
}

for(int item in arr){
print(item);
/**
  flutter: 1
  flutter: 2
  flutter: 3
  flutter: 4
 */
}

while 语句

  • while 语句
  • do...while 语句
int count = 0;
  while(count < 5) {
    print(count++);
  }
  /**
    flutter: 0
    flutter: 1
    flutter: 2
    flutter: 3
    flutter: 4
  */
  print('+++$count+++'); // 5

  do {
    print(count--);
  } while (count > 0 && count < 5);
  /**
   *
  flutter: 5
  flutter: 4
  flutter: 3
  flutter: 2
  flutter: 1
   */

break 和 continue 语句

  • break
    break 跳出本次最近的循环;

  • continue
    continue 进入跳过本次循环,进入下一次循环;

      List arr = [1, 2, 3, 4];
      for (int item in arr) {
        if (item == 2) {
          break; // 跳出按着的循环
        }
        print(item); // 1
      }
      print('----------');
      for (int item in arr) {
        if (item == 2) {
          continue;  // 跳出本次循环
        }
        print(item); // 1 3 4
      }
      print('-------');
      List inner = ['a', 'b', 'c'];
      for(int item in arr) {
        for(String i in inner) {
          print(i);
          break;  // 跳出临近的本次循环
          /**
           * flutter: a
            flutter: a
            flutter: a
            flutter: a
           */
        }
      }
    

switch-case 语句

和其他的语句不同的是可以加入continue flag,会跳转到flag的地方执行语句;

String language = 'java';
  // break;
  switch(language) {
    case 'js':
      print('js');
      break;
    case 'java':
      print('java');
      break;
    case 'dart':
      print('dart');
      break;
    default:
      print('none');
  }
  // 使用 continue
  String language1 = 'dart';
  switch(language1) {
  Tag:
   case 'js':
      print('js');
      break;
    case 'java':
      print('java');
      break;
    case 'dart':
      print('dart');
      continue Tag;
    default:
      print('none');
  }
  /**
   * flutter: java
     flutter: dart
     flutter: js
   */

方法

方法特性

  • 方法也是对象,并且有具体类型Function
  • 返回值类型、参数类型都可以省略;
  • 可以使用箭头函数;
  • 方法都有返回值,如果没有指定,默认return null

可选参数

可选参数要放在后面

  • 可选命名参数:{param1, param2,...};
  • 可选位置参数:[param1, param2,...];
void main() {
  runApp(MaterialApp(
    title: 'dart 的基础语法',
    home: FirstPage(),
  ));

  print(getPerson('lily', grade: '一年级')); // name-lily,age-null,grade-一年级
  print(getPersonInfo('lily',10,)); // 使用位置可选参数:name-lily,age-10,grade-null
}

// 使用命名可选参数
String getPerson(String name, {int age, String grade}) {
  return 'name-$name,age-$age,grade-$grade';
}

// 使用位置可选参数
String getPersonInfo(String name, [int age, String grade]) {
  return '使用位置可选参数:name-$name,age-$age,grade-$grade';
}

默认参数

void main() {
  runApp(MaterialApp(
    title: 'dart 的基础语法',
    home: FirstPage(),
  ));

  print(getPerson('lily', grade: '一年级')); // name-lily,age-10,grade-一年级
}

// 使用命名可选参数
String getPerson(String name, {int age = 10, String grade = "大学生"}) {
  return 'name-$name,age-$age,grade-$grade';
}

方法对象

  • 作为参数,传递给变量
void main() {
  runApp(MaterialApp(
    title: 'dart 的基础语法',
    home: FirstPage(),
  ));

  Function fun = printHello;  // 作为一个参数,传递给变量
  fun(); // hello

}

void printHello() {
  print('hello');
}

  • 作为函数的参数,传递给变量
void main() {
  runApp(MaterialApp(
    title: 'dart 的基础语法',
    home: FirstPage(),
  ));

  Function fun = printHello;  // 作为一个参数,传递给变量
  fun(); // hello

  List hello = ['h', 'e', 'l', 'l', 'o'];
  print(ListTimes(hello, times));  // [hhh, eee, lll, lll, ooo]

}

void printHello() {
  print('hello');
}

// 返回三次该字符串
String times(str) {
  return str * 3;
}

// 根据List 返回一个新的List
List ListTimes(List list, String times(str)) {  // 作为一个函数的参数传入
  for(int i = 0; i< list.length; i++) {
    list[i] = times(list[i]);
  }

  return list;
}

匿名方法

  • 可以赋值给变量,通过变量进行调用;
Function fun = (str) {
print('hello---$str');
};

fun('1'); // hello---1
  • 可在其他的方法中直接调用,或者传递给其他方法;
  • 格式
(参数1,参数2){
    方法体
    return 返回值;
}
void main() {
  runApp(MaterialApp(
    title: 'dart 的基础语法',
    home: FirstPage(),
  ));

  // 匿名方法
  Function fun = (str) {
    print('hello---$str');
  };

  fun('1'); // hello---1

  // 自身直接调用
  ((){
    print('自己直接调用');
  })();

  List hello = ['h', 'e', 'l', 'l', 'o'];
  // 匿名函数作为参数传递
  // print(listTimes(hello, (str){ return str * 3; }));  // [hhh, eee, lll, lll, ooo]
  print(listTimes1(hello)); // [hhh, eee, lll, lll, ooo]

}

// 将自身调用三次
List listTimes(List list, String times(str)) {
  for (int i = 0; i < list.length; i++) {
    list[i] = times(list[i]);
  }

  return list;
}

// 匿名函数在方法中使用
List listTimes1(List list){
  Function fun = (str) { return str * 3; };
   for (int i = 0; i < list.length; i++) {
    list[i] = fun(list[i]);
  }

  return list;
}

闭包

  • 闭包是一个方法(对象);
  • 闭包定义在其他方法的内部;
  • 闭包能够访问外部方法中的局部变量,并且持有状态;
void main() {
  runApp(MaterialApp(
    title: 'dart 的基础语法',
    home: FirstPage(),
  ));

  Function fun = printCount();
  fun(); // 0
  fun(); // 1
 }

printCount() {
  int count = 0;

  // 一般都使用匿方法返回闭包函数
  return (){
    print(count++);
  };
}

异常处理

通过 throw 抛出异常,通过 try-catch(如果不知道是哪种类型) 或者 try-on(用于精确匹配) 捕获异常。

void main() {
  runApp(MaterialApp(
    title: 'dart 的基础语法',
    home: FirstPage(),
  ));

  // 使用 try-on try-catch 进行捕获
  try{
    divide(10, 0);
  }on IntegerDivisionByZeroException {  // on 是知道是哪种异常的时候,精确匹配
    print('Division by zero.');
  } catch(e) {  // 对于不确定类型的异常,可以用catch处理
    print(e);
  } finally { // 都会执行
    print('无论什么样都执行!');
  }
}

// 异常处理
divide(int a, int b) {
  if (b == 0) {
    // 通过throw 抛出异常
    throw new IntegerDivisionByZeroException();  // 抛出的是自带的异常
    // throw new Exception('Divide by zero'); // 抛出字符串异常
  } else {
    return a / b;
  }
}

定义

  • 简单的类
//一个简单的类
class Point {
  num x;
  num y;
}
void main() {
  var point = Point();
  point.x = 4; // Use the setter method for x.
  assert(point.x == 4); // Use the getter method for x.
  assert(point.y == null); // Values default to null.
}
  • 有构造函数
class Cat {
    String name;
    int age;
    Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
void main() {
  Cat Tom = new Cat("Tom", 2);
    print(Tom.name);
}
  • 使用语法糖实现构造函数
class Cat {
    String name;
    int age;
    Cat(this.name ,this.age) 
}
void main() {
  Cat Tom = new Cat("Tom", 2);
    print(Tom.name);
}
  • 命名构造函数
class Cat {
    String name;
    int age;
    Cat(this.name ,this.age) 
    Cat.newBorn(){
        name = "Cherry";
        age = 0;
    }
}
void main() {
  Cat Cherry = new Cat.newBorn();
    print(Cherry.name);
}

类的组成

类的实例是由 方法实例变量 组成的;

  • 使用类的成员;
    通过 . 引用实例的变量或者方法,可以使用 ?.

    var p = new Person('lily');
    print(p.name);
    p?.name = 'wanghogn';  // 如果p不是null,把值设置为wanghogn
    

继承

import 'package:flutter/material.dart';


class FirstPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Dart 的基础语法练习'),
      ),
      body: Center(
        child: Text('1212'),
      ),
    );
  }
}


void main() {
  runApp(MaterialApp(
    title: 'dart 的基础语法',
    home: FirstPage(),
  ));

  Person p = new Person.born();
  print(p.name); // lily 这样的好处感觉在定义的时候不用初始化,在内部已经写好了,不错
  Person p1 = new Person.middle();
  print(p1.age); // 16

  Man man1 = new Man.small('honhong');
  print(man1.age);

  Man man2 = new Man.born();
  print('${man2.name}, ${man2.age}'); // man 0
}

class Person {
  String name;
  int age;

  Person(this.name, this.age);

  Person.born(){
    name = 'baby';
    age = 0;
  }

  Person.middle() {
    name = 'junior';
    age = 16;
  }

}

// 实现继承,实现继承,在里面不用定义变量,父类命名的构造函数不会被子类继承,要使用:调用
class Man extends Person {
  Man(String name, int age):super(name, age);

  // 调用自身的构造函数,自身又调用父类的构造函数
  Man.small(String name):this(name, 0);

  Man.old(String name):this(name, 45);

  Man.born():super.born(){
    name= 'man';
  }
}

初始实例变量

void main() {
  runApp(MaterialApp(
    title: 'dart 的基础语法',
    home: FirstPage(),
  ));

  Point dot = new Point(2, 4);
  print(dot.sum); // 6
}

class Point {
  final num x;
  final num y;

  final num sum;

  // 可以在构造函数主体运行之前初始化实例变量,初始值设定项用逗号分开
  // 这个挺好的,能够通过其他的变量来初始化其他的变量
  Point(x, y)
    : x = x,
      y = y,
      sum = x + y;

}

类中的函数

定义函数

void main() {
  runApp(MaterialApp(
    title: 'dart 的基础语法',
    home: FirstPage(),
  ));

  Person xiaoming = new Person.born();
  xiaoming.run(); //  i can run
}

class Person {
  String name;
  int age;

  Person(this.name, this.age);
  Person.born(){
    name = 'baby';
    age  = 0;
  }

  // 定义方法
  run() {
    print('i can run');
  }
}

函数的重写

void main() {
  runApp(MaterialApp(
    title: 'dart 的基础语法',
    home: FirstPage(),
  ));

 Man xiaoming = new Man('xiaoming', 24);
 xiaoming.run(); // i am a man, i can run
}

class Person {
  String name;
  int age;

  Person(this.name, this.age);
  Person.born(){
    name = 'baby';
    age  = 0;
  }

  // 定义方法
  run() {
    print('i can run');
  }
}

class Man extends Person{
  Man(String name, int age):super(name, age);

  // 重写方法
  @override
  run() {
    print('i am a man, i can run');
  }
}

getter setter

dart中通过使用getset关键字来实现gettersetter,get 在定义的时没有括号没有参数

void main() {
  runApp(MaterialApp(
    title: 'dart 的基础语法',
    home: FirstPage(),
  ));

  offsetPoint dot = offsetPoint(1, 2, 10);
  print(dot); // offsetPoint{x=11, y=12}
  print(dot.x); // 11 ,调用get
  print(dot.y); // 12
  dot.x = 4;  // 调用set
  print(dot); // offsetPoint{x=14, y=12}
}

class offsetPoint{
  int _x;
  int _y;
  int offset;

  offsetPoint(int x, int y, int offset)
    : _x = x,
      _y = y,
      offset = offset {
        print('初始化实例变量');
      }

  // 定义一个getter
  int get x => _x + offset;
  // getter 不能有参数,连括号都要删掉
  int get y {
    return _y + offset;
  }

  // 定义setter
  void set x(int x) => _x = x;
  void set y(int y) => _y = y;

  @override
  String toString() {
    return 'offsetPoint{x=$x, y=$y}';
  }
}

抽象方法和抽象类

  • 抽象类使用 abstract 关键字定义,抽象方法只能存在于抽象类中
  • 除非定义了工厂构造函数,抽象类不能被实例化
  • 抽象方法以(;)结尾,没有函数体
void main() {
  runApp(MaterialApp(
    title: 'dart 的基础语法',
    home: FirstPage(),
  ));

  Man p1 = new Man();
  p1.eat(); // i eat meat;
}

// 定义一个抽象类
abstract class Person {
  // 定义一个抽象方法
  void eat();
}

class Man extends Person {
  void eat() {
    print('i eat meat');
  }
}

隐式接口(暂时没有找到一个比较好的例子)

之所以叫隐式接口,是因为**没有关键字 interface **,一个类可以实现多个接口 class Point implements Comparable, Location {...}

静态方法

通过在方法前面添加 static 来实现;静态方法要通过直接调用;

mixins 为类添加功能

  • 定义
    使用 mixin 关键字创建一个没有构造函数的扩展类;
  • 使用
    使用 with 关键字后跟多个类名来使用mixin
  • 和实现的区别,里面 不用实现方法,实例可以直接调用;
void main() {
  runApp(MaterialApp(
    title: 'dart 的基础语法',
    home: FirstPage(),
  ));

  Man p1 = new Man('lily');
  p1.eat();  // i eat
}

mixin Person {
  String name;
  eat() {
    print('i eat');
  }
}

class Man with Person {
  String name;
  Man(this.name);
}

泛型

  • 类型变量,具有单字母名称,如 ETSKV

为什么使用泛型

  • 指定泛型类型,可以生成更好的代码;
  // 使用泛型,可以更好的生成代码;
  var names = List<String>();
  names.add('kathy'); // 只能添加字符串
  • 减少代码重复;
    泛型允许在许多的类型之间共享同一个接口或和实现;例如有一个拥有用于存放int类型的数据,又有一个用于存放double类型的数据,就可以只定义一个接口,用泛型做一个定位符;
void main() {
  runApp(MaterialApp(
    title: 'dart 的基础语法',
    home: FirstPage(),
  ));

  // 使用泛型,可以更好的生成代码;
  var names = List<String>();
  names.add('kathy'); // 只能添加字符串

  DataHolder<String> data1 = new DataHolder('string 类型的');
  print(data1.getData());

  DataHolder<int> data2 = new DataHolder(1);
  print(data2.getData());
}

// 这样写可以减少代码的重复
class DataHolder<T> {
  T data;

  DataHolder(this.data);

  getData() {
    return data;
  }

  setData(data) {
    this.data = data;
  }
}

使用字面量集合

  // 使用字面量集合
  List names = <String>['a', 'b', 'c'];
  Map person = <String, String>{
    'name': 'lily',
    'age': '10',
  };

构造函数的参数化类型,将类型放在后面的尖括号中;

var names = List<String>();

限制参数化类型

class Eeat<T extends Person> {
  String toString();
}

异步处理

Future

异步库里有个概念叫做 Future,就像是 Promise

  • 引入库
import 'dart:async';
  • 使用 Future 创建一个异步
void main() {
  runApp(MaterialApp(
    title: 'dart 的基础语法',
    home: FirstPage(),
  ));

  getAjoke().then((val) {
    print(val);  // his is a joke
  }).catchError((err) {
    print(err);  // 'no joke for you!1'
  });
}

// 使用Futter 实现异步
Future<String> getAjoke() {
  return new Future<String>(() {
    throw new Exception('no joke for you!1');
    return 'this is a joke';
  });
}
  • 使用 Future 模拟一个延时的方法
void main() {
  runApp(MaterialApp(
    title: 'dart 的基础语法',
    home: FirstPage(),
  ));

  getAjoke().then((val) {
    print(val);  // his is a joke
  }).catchError((err) {
    print(err);  // 'no joke for you!1'
  });
}

// 使用Future 创建一个比较费时的例子
Future<String> getAjoke() {
  return new Future<String>.delayed(new Duration(milliseconds: 2000), () {
    return 'this is a joke';
  });
}
  • async/await
    使用 async 方法和 await 表达式来配合实现异步,一个异步的函数可以包含多个 await 表达式;
void main() async{
  runApp(MaterialApp(
    title: 'dart 的基础语法',
    home: FirstPage(),
  ));

  try {
    String result = await getAjoke();
    print(result);
  } catch(e) {
    print(e);
  }
  print('现在可以执行');
}

// 使用Future 创建一个比较费时的例子
Future<String> getAjoke() {
  return new Future<String>.delayed(new Duration(milliseconds: 2000), () {
    return 'this is a joke';
  });
}

读书笔记-《图解HTTP》

第一章 了解web及网络基础

1.1 使用 HTTP 协议访问web

  • 客户端:
    通过发送请求获取服务器资源的web浏览器等;
  • web:
    使用一种名为 HTTP 的协议作为规范,完成从客户端到服务端等一系列运作流程,简单的说 Web 就是建立在 HTTP 协议上通信的;

1.2 HTTP 的诞生

  • www/web 的三项构建技术:
    HTML, HTTP, URL

1.3 网络基础 TCP/IP

  • 协议:
    计算机和网络设备之间相互通信所要遵循的规则;
  • TCP/IP 协议族
    互联网相关的各类协议族的总称;
  • TCP/IP 分层管理
    此处输入图片的描述
  • TCP/IP 通信传输流
    此处输入图片的描述

1.4 与HTTP 关系密切的协议:IP、TCP 和 DNS & 1.5 负责域名解析的DNS服务 & 1.6 各种协议与HTTP协议的关系

  • IP 协议:
    IP 协议是在源地址和目的地址之间传送一种称为数据包的东西;

    此处输入图片的描述

  • TCP 协议
    TCP 协议是提供可靠的字节流服务;

    • 可靠:确保数据能够精确可靠的传给对方(三次握手);
    • 字节流服务:为了传输方便,将大块数据分割为以报文段为单位的数据包进行管理;

    此处输入图片的描述

  • DNS 服务
    提供域名到IP地址之间的解析服务;

    此处输入图片的描述

  • 在使用 HTTP 协议通信的过程中,IP 协议、TCP 协议 和 DNS 服务 起到的作用:

此处输入图片的描述

1.7 URI 和 URL

  • url 的格式:
    此处输入图片的描述

第一章总结

此处输入图片的描述

第二章 简单的HTTP协议

POST/GET

  • POST:主要用来传输实体
  • GET:获取资源

TCP的短连接,持久化链接,管线化

  • HTTP 协议是无状态的:
    HTTP 协议是无状态的,简单说就是打开一个服务器上的网页和之前打开的网页之间没有任何的联系,比如,你登录了一个网站,跳转到其他的页面,登录的这些信息就不存在了。
  • TCP 短连接:
    • 概念:浏览器和服务器每进行一次HTTP操作,就建立一个连接,但是任务中断就结束连接
    • HTTP/1.0中,默认使用的是短连接
    • 形象描述:...建立HTTP连接-->HTTP请求/响应-->断开TCP连接-->建立HTTP连接-->HTTP请求/ian响应-->断开TCP连接...
    • TCP 持久化链接:
      • 概念:当浏览器和服务器建立了连接后,只要是任意一方没有明确的提出断开连接,则保持TCP连接状态
    • HTTP/1.1 起,默认使用长连接,如果是长连接,可以在浏览器中的request headers 中看到 contention: keep-alive 字段;
      • 形象描述:建立HTTP连接-->...HTTP请求/响应-->HTTP请求/响应...断开TCP连接
      • 如何开启长连接:需要在http头加入contention: Keep-Alive
      • 如何关闭长连接:需要在httptou头加入Connection: close
    • TCP 管线化:
      • 概念:简单的理解就是批量请求的长连接,不需要等待响应;
      • 形象描述:``请求1--> 请求2--> 响应1-- > 响应2

Cookie

HTTP 协议是无状态的,为了想保留某些状态,引入了cookie;
本来想写点,但是看了一篇博客,感觉写得非常好,就把地址粘在这里吧https://segmentfault.com/a/1190000004556040

第四章 返回结果的 HTTP 状态码

状态码 场景描述
200 客户端发来的请求在服务端已正常处理
204 请求已经处理,但是没有资源可以返回
206 对资源的某一个部分进行请求
301 永久性重定向,资源的URI已经更新
302 临时重定向,资源的URI已临时定位其他资源
303 临时重定向,资源的URI已临时定位其他资源,要使用GET
304 资源已经找到,但是未符合条件
307 临时重定向,资源的URI已临时定位其他资源
400 请求报文中存在语法错误
401 需要认证
403 资源不允许访问
404 没有找到资源
500 服务器内部资源出故障
503 服务器处于超负载或者停机维护

第五章 与HTTP协作的web服务

代理

是客户端和服务器端的中间人,是一种有转发功能的应用程序;代理服务器的基本行为就是接收客户端发送的请求后转发(不会改变URI)给其他的服务器;
此处输入图片的描述
- 缓存代理
代理转发响应的时候,会预先将资源的副本保存到代理服务器上,当代理再次接收相同的资源请求时,就可以将之前缓存的资源作为响应返回。

网关

网关是转发其他服务器通信数据的服务器。当接收到客户端的请求的时候,它就像是自己拥有其他服务器的资源一样来处理。网关能使通信线路上的协议提供非HTTP协议服务。
此处输入图片的描述

隧道

隧道可按要求建立起一条与其他服务器通信线路。

第五章 HTTP 首部

概念

HTTP 首部可以在客户端和服务器端传递额外重要的信息;

HTTP/1.1 通用首部字段

  • Cache-Control
    可以操作缓存服务器的缓存机制;

    参数名称 作用
    public 表明其他的与欧诺个户用户也可以利用的缓存
    private 缓存只是给指定的用户用
    no-cache 不缓存过期的资源(do not serve from cahce without reavalidation)
    no-store 不进行缓存,暗示请求和响应中包含有机密信息
    max-age=600800(秒) 缓存的最长时间,如果有expires的时候,会忽略掉这个字段
    min-fresh=60(秒) 过了60秒后缓存就无法作为响应返回了
    only-if-cached 仅在缓存服务器本地缓存目标资源的情况下才会返回
    must-revalidate 代理会向源服务器再次验证即将返回的响应
    no-transform 缓存不能改变主体的媒体类型
  • Connection

    参数名称 作用
    close 首部字段不再转发给代理
    Keep-Alibe 缓存只是给指定的用户用
  • Date
    HTTP 报文的日期和时间

  • Transfer-Encoding
    采用哪种编码方式进行发送

    参数名称 作用
    chunked 采用分块的方式
  • Via
    报文经过的传输路径(网关或者代理)

  • Warning
    通知用户一些和缓存相关的问题警告;

    警告码 警告内容 说明
    110 响应已过期 代理返回已过期的资源
    111 再验证失败 代理再次验证资源的有效性的时失败(服务器无法到达等原因)
    112 断开连接操作 代理与互联网的连接故意被切断
    113 试探性过期 响应超过最大限定时间
    119 杂项警告 任意的警告内容
    214 使用了转换 代理对内容编码或者媒体类型执行了某些操作
    299 持久杂项警告 任意的警告内容

请求首部字段

  • Accept
    • 通知服务器端,用户代理能够处理的媒体类型和媒体类型的相对优先级
    • 格式: Accept: text/html; q=0.3; text/plian
  • Accept-Charset
    • 通知服务器,用户代理支持的字符集以及字符集的相对优先顺序;
    • Accept-Charset: iso-8859-5, unicode-1-1; q=0.8
  • Accept-Encoding
    • 通知服务器,用户代理支持的内容编码以内容编码的优先级顺序;

    • Accept-Encoding: gzip

      名称 说明
      gzip 由文件压缩程序gzip生成的编码格式
      identity 不执行压缩或不会变化的默认格式
  • Accept-Language
    • 告诉服务器用户代理能够处理的自然语言集
    • Accept-Language: zh-cn
  • Host
    • 告诉服务器,请求资源所处的互联网主机名和端口号
    • Host: www.hackr.jp
  • if-modified-since
    如果在if-modified-since字段指定的日期时间后,资源发生了更新,服务器会接受请求;
  • max-forwards
    最大的转发次数
  • range
    • 告知服务器,获取指定范围的资源
    • range: bytes=5001-10000 请求获取从第5001字节到10000字节的资源;
  • user-agent
    用户传达浏览器的种类;

响应首部字段

  • Age
    告诉客户端,源服务器在多久前创建了响应。zi'duan'z字段值的单位为秒;

  • Etag
    可将资源以字符串的形式做唯一性标识的方式,资源的URI虽然没有变,但是当资源更新后,ETAG值会改变;

  • Server
    告诉客户端当前服务器上安装的HTTP服务器应用程序的信息。

  • 实体首部字段

  • Allow
    告诉客户端能够支持的所有HTTP方法;

  • Content-Length
    实体主体部分的大小,** 对实体主体进行内容编码传输的时候,不能再使用content-length;

  • 为cookie 服务的首部字段

    字段名 说明 首部类型
    set-cookie kai'shi开始状态管理所使用的cookie信息 响应首部字段
    cookie 服务器接收到的cookie信息 请求首部字段
  • set-cookie 字段的属性

    属性 说明
    name=value 给cookie的名称和值
    expires=date cookie的有效期
    path=Path 限定cookie 的发送范围的文件目录
    domain=域名 作为cookie适用对象的域名
    secure 仅在HTTP安全通信时才会发送cookie
    HttpOnly 加以限制,使Cookie不能被javascript脚本访问

第七章 确保Web安全的HTTPS

HTTP 的缺点:

通信使用明文(不加密),可能会被窃听

  • HTTP 本身不具备加密的功能,所以无法做到对通信整体进行加密;
  • TCP/IP 是可能被窃听的网络
    TCP/IP 协议族的工作机制,通信内容在所有的通信线路上都有可能遭到窥视;
  • 加密处理
    • 通信的加密
      • HTTP 协议没有加密机制,可以通过和 SSL (安全套阶层) 或 TLS (安全层传输协议)的组合使用,加密HTTP的通信内容;
      • 与SSL组合使用的HTTP被称为HTTPS
    • 内容加密
      • 对HTTP协议传输的内容进行加密,这种加密,要求客户端和服务端同时具备加密和解密的机制

不验证通信方的身份,可能遭到伪装

  • 在HTTP协议通信时,由于不存在确认通信方的处理步骤,任何人都可以发起请求;
  • 查明对手的证书
    • 技术:SSL。SSL不仅提供加密处理,而且还使用了一种称为证书(证书是由值得信任的第三方机构颁发,用以证明服务器和客户端是实际存在)的手段,用于确定方。
      此处输入图片的描述

无法证明报文的完整性,可能已遭篡改

  • 接收到的内容可能有误
    由于HTTP 无法证明通信报文的完整性,说以即使请求或者响应的内容遭到了篡改(中间人攻击),也没有办法获悉。
  • 如何防止被篡改
    • MD5算法:利用MD5算法生成散列值;
    • PGP(完美隐私):利用PGP创建数字签名;
      虽然有以上的方法,但是这些方法无法百分百的保证确认结果,因为PGP或MD5也可能被改写,为了防止这些弊端,有必要使用HTTPS。
  • HTTPS = HTTP + 加密 + 认证 + 完整性保护
  • HTTPS 并不是一种新的协议,只是HTTP通信接口部分用SSL和TLS协议代替而已。例如,HTTP直接和TCP通信,使用了SSL时候,就会演变为先和SSL通信,再由SSL和TCP通信。
    此处输入图片的描述

加密技术

共享密钥加密/对称密钥加密

  • 概念:信息的发送方和信息的接收方使用 同一套密码 进行加密和解密;
  • 共享密钥加密可以理解为服务器为了数据传输安全,送给你了一把钥匙,这把钥匙可以把数据在盒子里,也可以打开已经锁上的盒子。当数据发送到服务器时,服务器会用同样的钥匙打开盒子。
    此处输入图片的描述
  • 缺点:密钥有很大的风险可能会被黑客拦截;
  • 优点:处理速度快;
  • 总结:这个例子可以简单的理解为:小红和小花是朋友,小花家的门有一个钥匙,有一天为了方便,配了个钥匙给小红,从此小红就可以随便的进出小花家了,但是钥匙得好好保存,万一被小偷得到,就也可以随便进出小花家了。

公开密钥加密/非对称加密

  • 概念:公开密钥有 一对非对称的密钥,一把公开密钥,一把私有密钥,公开密钥可以发送给任何发送请求方,而私有密钥 只有一方有,一对中的一把密钥加密后,只能用另外一个进行解密,即使是自己也不可以解密
  • 公开密钥加密可以理解为,服务器首先给发送请求的客户端发送了一个公开密钥**(公钥可以发给任何请求方)**,客户端收到好,对报文进行加密后,发送给服务器,服务器再用独有的私钥进行解密。
    此处输入图片的描述
  • 优点:防止在传输过程中被获取密钥;
  • 缺点:处理速度慢;
  • 公钥用来进行加密,私钥用来数字签名

混合加密机制

  • 概念:混合使用公开密钥加密和共享密钥加密,在交换密钥的环节采用公开密钥加密,之后建立的通信报文阶段则使用共享密钥加密,这样既可以避免密钥被盗取,又可以提高通信的效率;
    此处输入图片的描述
  • HTTPS 采用的混合加密机制;

数字签名

  • 概念
    一封信中,文末的签名是为了证明这封信是签名者写的,同理,数字签名也是为了证明某个消息是特定的某个人发的(有效性);同时,数字签名还能证明消息没有被篡改(完整性);
  • 作用
    • 有效性;
    • 完整性;
  • 如何生成数字签名
    一般不对消息直接进行签名,而是对消息的哈希值进行签名。
    • 对消息进行哈希计算,得到哈希值;
    • 利用私钥对哈希值进行加密,生成数字签名;
    • 将签名附件在消息后面,一起发送过去;
  • 验证签名
    • 收到消息后提取消息中的签名;
    • 用公钥对签名进行解密,得到哈希值1;
    • 对消息中的正文进行计算得到哈希值2;
    • 比较哈希值1和哈希值2,如果相同,则验证成功;

证书

  • 概念:
    证书其实就是对公开加密方法中的公钥进行加密,是对公钥的合法性提供证明的技术;
  • 作用:
    • 认证服务器的公开密钥是真实有效的数字证书认证机构;
    • 服务器的公开密钥是值得信赖的;
  • 认证机构
    可以使用数字认证机构(CA)和其他相关的机关颁发公开的密钥证书(就像是银行);
  • 证书种类
    • EV SSL 证书
      EV SSL证书可以证明作为通信一方的服务器是否规范,同时也可以确认服务器背后运营的企业是否是真实存在的。
    • 客户端证书
      证明服务器正在通信的对方始终是预料之内的客户端;
    • 自签名证书
      • 概念:由自认证机构(独立构建的认证机构)颁发的证书称为自签名证书;
      • 浏览器访问该服务器的时候,会显示“无法确认连接安全性”或“该网站的安全可能存在问题”等警告;
      • 由自认证机构颁发的服务器证书之素哟所以不起作用,是因为它无法消除伪装的可能性(就像是跑路的p2p公司);
  • 生成证书
    • 服务器会将公钥A给CA(公钥是服务器的);
    • CA用自己的私钥B对A进行加密,生成数字签名C;
    • CA把公钥A,数字签名C,和附加服务器的一些信息生成证书,发给服务器;
  • 验证证书
    多数浏览器会将开发商发布版本的时候,会事先在内部植入常用的认证机关的公开密钥;
    • 客户端得到证书;
    • 客户端得到证书公钥D;
    • 客户端用公钥D对证书中的数字签名进行解密,得到哈希值1;
    • 客户端对公钥A进行哈希值计算,得到哈希值2;
    • 如果哈希值1和哈希值2相等,则证书是合法的;
  • 证书作废
    当用户私钥丢失或者被盗,认证机构需要对证书进行作废;

总结

https的通信流程(阮大神的一篇博文):http://www.ruanyifeng.com/blog/2011/08/what_is_a_digital_signature.html

第八章 确认访问用户身份的认证

何为认证

  • 概念:
    就像是你买的保险箱只想某些人可以看一样,网页上的web页面也有可能只想特定的人浏览,这就少不了认证功能了,简单的说,认证就是判断访问服务器者的身份
  • 核对信息通常有以下几点:
    • 密码:只有本人才会知道的字符串信息;
    • 动态令牌:仅限本人持有的设备内显示的一次性密码;
    • 数字证书:仅限本人(终端)持有的信息;
    • 生物认证:指纹和虹膜等本人的生理信息;
    • IC 卡等: 仅限本人持有的信息;
  • HTTP 使用的认证方式
    • BASIC 认证 (基本认证,不常用)
    • DIGEST 认证 (摘要认证,不常用)
    • SSL 客户端认证 (付费)
    • FormBase 认证 (基于表单的认证)

BASIC 认证步骤

此处输入图片的描述

  • 缺点:
    • 不够灵活;
    • 没有办法达到Web网站的安全性等级;

DIGEST 认证

此处输入图片的描述

  • 缺点:
    • 不够灵活;
    • 防止了密码被窃取的风险,但是没有防止用户伪装的机制;

SSL 客户端认证

  • 概念:SSL 客户端认证是借由HTTPS的客户端证书完成认证的方式,要完成这种认证,需事先将客户端证书分发给客户端,且客户端必须安装此证书
  • 认证步骤
    • 接收到需要认证资源的请求,服务器会发送 Certificate(证书) Request,要求客户端提供客户端证书;
    • 用户选择将发送的客户端证书后,客户端会把客户端证书信息以Client Certificate 报方式发送给服务器;
    • 服务器验证客户端证书验证通过后方可领取证书内客户端的公开密钥,然后开始HTTPS加密通信;
  • SSL 客户端认证需要费用
    一般这部分费用是:
    • 认证机构购买客户端证书的费用;
    • 服务器运营者为保证自己搭建的认证机构安全运营所产生的费用;
    • 自己搭建的认证机构,为维持安全运行产生的费用;

表单认证

  • 概念:
    客户端会向服务器上的Web应用程序发送登录信息,按登录信息的验证结果认证;
  • session 管理及Cookie应用
    因为HTTP是无状态协议,所以一般使用 Cookie 来管理 Session;
  • 步骤
    • 客户端把用户ID和密码等登录信息放入报文的实体部分,通常是以POST方法把请求发送给服务器;
    • 服务器在Set-Cooki中写入Session ID (区分不同用户的等位号,为了防止被盗,session id 应该使用难以推测的字符串,且服务器也需要进行有效期的管理,为了保证安全建议在Cookie内写httponly)
    • 客户端收到从服务器发来的Session ID 后,会将其作为Cookie保存在本地,下次向服务器发送请求时,浏览器会自动发送Cookie;

双因素认证

SSL客户端认证和表单认证结合,SSL客户端认证来认证客户端的计算机,而表单认证来确定用户本人的行为;

第九章 基于HTTP 的功能追加协议

HTTP 通信的瓶颈

  • HTTP 协议的最大弊端就是每个 TCP 连接只能对应一个 HTTP 请求,即每个 HTTP 连接只请求一个资源,浏览器只能通过建立多个连接来解决;
  • 请求只能从客户端开始,客户端不可以接受响应以外的指令;
  • 请求/响应首部未经压缩就发送,首部信息越多越延迟
  • 发送冗长的首部,每次互相发送相同的首部造成浪费较大;
  • 可任意的选择数据压缩格式,非强制的压缩发送;
    例如论坛上,成千上万的用户发布内容的时候,web网站需要及时的更新,而使用HTTP协议探知服务器上是否有内容更新,就必须频繁地从客户端到服务器端进行确认。如果服务器上没有内容更新,就会产生徒劳的通信。

Ajax 解决办法

Ajax的核心技术是XMLHttpRequest的API,通过javascript的脚本的调用就能和服务器进行HTTP通信了,借由这种手段,就能从已经加载完毕web页面上发起请求,只更新局部页面。

Comet 的解决办法

使用延迟应答,模拟实现服务器向客户端进行推送的功能。
通常,服务器端接收到请求,在处理完毕后就会立即响应,但是为了实现推送功能,Comet会先将响应置于挂起的状态,等服务器端有内容更新的时候,再返回响应。

  • 缺点:内容上做到了实时更新,但是为了保留响应,一次连接的持续时间也会变长,为了维持连接,会消耗更多的资源。

SPDY

SPDY是Google 开发的基于传输控制协议 (TCP) 的应用层协议 ,开发组正在推动 SPDY 成为正式标准(现为互联网草案)。
此处输入图片的描述

  • 优点:
    • 多路复用,请求优化
      允许一个SPDY连接内可以有无限个并行请求,即允许多个并发HTTP请求公用一个TCP会话。这样这要建立一个TCP连接,就可以传送网页上所有资源,不仅可以减少消息交互往返的时间还可以避免创建新连接造成的延迟,使得 TCP 的效率更高。
      可以设置优先级,会选择性的先传输 CSS 这样更重要的资源,然后再传输网站图标之类不太重要的资源,可以避免让非关键资源占用网络通道的问题,提升 TCP 的性能。
    • 支持服务器推送技术
      服务器可以主动向客户端发起通信向客户端推送数据,这种预加载可以使用户一直保持一个快速的网络。
    • 压缩HTTP头部;
      舍弃掉了不必要的头信息,经过压缩之后可以节省多余数据传输所带来的等待时间和带宽。
    • 强制使用SSL传输协议;
  • Chrome 已经移除了SPDY, 后来SPDY变成了HTTP/2;

WebSocket

HTTP/2.0

支持HTTP2.0的条件

  1. 需要浏览器的支持,目前最新版的 Chrome、Opera、 FireFox、 IE11、 edge 都已经支持了;
  2. 需要 WEB 服务器的支持,比如 Nginx , H20;
    如何浏览器或者服务器有一方不支持,就会自动变为HTTP/1.1

HTTP/2.0 改进点

HTTP/2.0 性能增强的核心:二进制分帧

在应用层和传输层之间增加了一个二进制分帧层,以达到“在不改动HTTP语义,HTTP方法、状态码、URI机及首部字段的情况下,突破HTTP/1.1 的性能限制,改进传输性能,实现低延迟和高吞吐量”;
此处输入图片的描述

在二进制分帧层上,HTTP2.0 会将所有的信息分割为更小的消息和,并对它们采用二进制格式的编码,其中HTTP1.X 的首部信息会被封装到Headers帧,而request body则封装到Data帧里

HTTP2.0 通信都在一个连接上完成,这个连接可以承载任意数量的双向数据流。每个数据流以消息的形式发送,而消息又是由一个或者多个组成,这些帧可以乱序发送,然后再根据每个帧首部的流标识符重新组装;

压缩头部

HTTP/2.0规定了在客户端和服务器端会**使用并且维护「首部表」**来跟踪和存储之前发送的键值对,对于相同的头部,不必再通过请求发送,只需发送一次

如果请求中不包含首部,那么首部的开销就是零字节,此时所有的首部都自动使用之前请求发送的首部;如果首部发变化了,那么只需要发送变化了数据在Headers帧里面,新增或者修改的首部帧会被追加到“首部表”。首部表在HTTP2.0连接存续4qi期内始终的存在,由客户端和服务器共同渐进地更新。

多路复用

HTTP2.0所有的通信都是在一个TCP连接上完成的。HTTP2.0 把 HTTP 协议通信的基本单位缩小为一个个的帧,这些帧对应着逻辑流中的消息。**这些帧并行地同在一个TCP连接上双向交换消息。**也就是说同一链接上有多个不同方向的数据流在传输。客户端可以一边乱序发送streams,也可以一边接收服务器的响应,而服务器端同理。

  • 优点
    HTTP性能的关键在于低延迟,而不是高带宽。大多数的HTTP连接的时间短,而且是突发性的,但是TCP只在长时间连接传输大块数据时效率才最高。HTTP2.0 通过让所有的数据流公用一个连接,可以更有效地使用TCP连接,让高带宽也能真正服务于HTTP的性能。
    同时,单链接多资源的方式,有如下好处:

  • 可以减少服务器的压力,内训占用少,连接的吞吐量大;

  • 由于 TCP 连接减少,使得网络阻塞的情况得以改观;

  • 慢启动时间减少,拥塞和丢包恢复速度更快。

    因此, “资源合并减少请求”的优化手段对于HTTP2.0来说是没有效果的。

请求优先级

既然所有资源都是并行发送,那么就需要“优先级”的概念了,这样就可以对重要的文件进行先传输,加速页面的渲染。

服务器推送

在 HTTP2.0中,服务器推送是指客户端请求之前发送数据的机制。

强制 SSL

虽然 HTTP2.0 协议并没有声明一定要使用SSL,但是Google Chrome 等浏览器强制要使用 HTTP2.0 必须使用SSL。

第11章 Web的攻击技术

针对web的攻击技术

  • 客户端可篡改请求
    在HTTP 请求报文内加载攻击代码,也就是通过URL查询字段或表单、HTTP首部、Cookie等途径把攻击代码传入,如果这时候Web应用存在安全漏洞,那内部信息就会遭到窃取,或被攻击者拿到管理权限;
  • 针对 Web 应用的攻击模式
    • 主动攻击
      是通过直接访问Web应用,把攻击代码传入的攻击模式。代表性的是SQL注入攻击命令注入攻击
    • 被动攻击
      利用圈套策略执行攻击代码的攻击模式。
      • ① 攻击者诱使用户出发已经设置好的陷阱,而陷阱会启动发送已嵌入攻
        击代码的HTTP请求;
      • ② 当用户中招后,用户的浏览器或者客户端就会触发陷阱;
      • ③ 中招后的用户浏览器会把含有攻击代码的HTTP请求发送给攻击目标的Web应用,运行攻击代码;
      • ④ 执行完攻击代码,可能导致用户所持的Cookie等个人信息被窃取,登录
        状态中的用户权限遭恶意滥用都能后果;
    • 常见的模式
      • 跨站脚本攻击;
      • 跨站点请求伪造;

因输出值转义不完全引发的安全漏洞

  • 跨站脚本攻击
  • SQL注入攻击
  • OS 命令注入攻击
  • HTTP 首部注入攻击
  • 邮件首部注入攻击
  • 远程文件包含漏洞

因设置或者设计上的缺陷引发的安全漏洞

因回话管理忽略引发的安全漏洞

其他安全漏洞

  • DOS攻击
  • 点击劫持
  • 密码破解

存疑点

  • 如何实现管线化
  • web攻击

参考文章

读书笔记-javascript 高级程序设计

一、JavaScript 简介

JavaScript 诞生于 1995年,是一种专为与网页交互而设计的脚本语言。由三种不同部分组成:

  • ECMAScript,提供核心语言功能;
  • 文档对象模型(DOM),提供访问和操作网页内容的方法和接口;
  • 浏览器对象模型(BOM),提供和浏览器交互的方法和接口

1.1 ECMScript

ECMScript 是这本语言的基础,规定了这门语言的下列组成部分:

  • 语法
  • 类型
  • 语句
  • 关键字
  • 保留字
  • 操作符
  • 对象

1.2 文档对象模型(DOM)

文档对象模型(DOM, Document Object Model) 是针对XML但经过扩展用于HTML的应用程序编程接口。提供了以下方法和接口的定义:

  • DOM 视图;
  • DOM 事件;
  • DOM 样式;
  • DOM 遍历和方法;
  • SVG;
  • MathML;
  • SMIL;

1.3 浏览器对象模型(BOM)

浏览器对象模型(BOM, Browser Object Model) 用来访问和操作浏览器窗口,有以下的扩展:

  • 弹出新浏览器窗口的功能;
  • 移动、缩放和关闭浏览器窗口的功能;
  • 提供浏览器详细信息的 navigator 对象;
  • 提供浏览器所加载页面的详细信息的 location 对象;
  • 提供用户显示器分辨率详细信息的 screen 对象;
  • 对 cookies 的支持;
  • 像 XMLHttpRequest 和 IE 的 ActiveXObject 这样自定义对象;

二、在 HTML 中使用 JavaScript

2.1 内嵌脚本

在 HTML 内部直接写入 <script> 片断,这种写法要注意JavaScript 是单线程的,如果在初始化页面的时候,你的代码中存在大量同步运行的代码,导致 JS 线程一直处于繁忙状态,这时候用户在页面上进行交互时将不会得到任何反应,就像是卡死了一样。想了解具体原因,参考文章浏览器渲染原理

2.2 引入外部脚本

  <script src="example.js" src="defer"></script>
  • defer属性让脚本在文档完全呈现之后再执行
  • async属性表示当前文本不用等待其他文本,异步执行。

三、基本概念

3.1 变量

ESCAScript 的变量是 松散类型 的,就是说可以用来保存任何类型的数据。也就是说一个变量就是一个占位符。

  var a = 'hi';
  a = 10;

定义变量可以使用 var 操作符,定义多个变量,每个变量之间用 逗号 分开,如果 省略 var 操作符可以定义 全局变量,未经初始化的变量,会保存一个特殊的值—— undefined

  function show() {
    var _message = '局部变量',
        _name = 'lily';
    g_message = '全局变量';
  }
  show();
  console.log(g_message);  // 全局变量
  console.log(_message);  // _message is not defined

在 ESCScript 中变量会被自动 提升

  console.log(enhance); // 变量提升
  var enhance = "变量提升"; 

3.2 数据类型

ECMAScript 有6种数据类型,其中5种基本的数据类型: Undefined、Null、Boolean、Number和String,还有1种复杂的数据类型: Object。一般使用 typeof 检查5种基本的数据类型,使用 instanceof 检查是哪种复杂的数据类型。

  1. typeof
    常见的就不说了,说几个可能容易混或者错的。

    使用typeof操作符返回
    值未初始化 'undefined'
    值对象、null 'Object'
    值是函数 'function'
    // 未初始化的变量
    var uninital;
    console.log('未初始化的变量:---' +typeof uninital); // undefined
    
    // 未定义的变量
    console.log('未定义的变量:---' + typeof liy); // undefined
    
    // 值为null
    var obj = null;
    console.log('值是null的时候:---' + typeof obj); // object
    
    // 值未函数
    function add (a, b) {
      return a + b;
    }
    console.log('值是funciton:---' + typeof add); // function
    
  2. Boolean 类型
    Boolean 类型是 ECMAScript 中使用最多的一种类型,只有两个字面量值: true、false;区分大小写,也就是说 True 不是布尔值。

    流程控制语句(例如 if 语句)自动执行 转型函数 Boolean()。

    var message = "hello world";
    if (message) {
      console.log('转换为 true');  // 执行这句
    } else {
      console.log('转换为 false');
    } 
    

    下表给出了几种数据类型及其对应的转换规则

    数据类型 转换为true 值 转换为false值
    Boolean true false
    String 任何非空字符串 ''(空字符串)
    Number 任何非0数字(包括无穷大) 0、NaN
    Object 任何对象 null
    Undefined 不适用 undefined
  3. Number 类型

    • 浮点数值计算会产生舍入误差
    • isFinite(值) 用于判断一个数值是不是位于最小值和最大值之间
    var result = Number.MAX_VALUE + Number.MAX_VALUE;
    console.log('数值是否是有穷的:' + isFinite(result));  // false
    
    • isNan(值) 用于判断一个数值是不是非数值。isNaN 也适用于对象。在基于对象调用 isNaN() 函数时,会首先调用对象的 valueof() 方,然后确定返回值是否可以转换为数值。如果不能,则基于这个返回值再调用 toString() 方法,再测试返回值。
    console.log(isNaN(10));  // false
    console.log(isNaN('10')); // false
    console.log(NaN == NaN);  // false  
    
    • 将数值转换为数值:Number(值), parseInt(值,几进制),parseFloat(值,几进制)
    console.log(Number('hello'));  // NaN
    console.log(Number(''));  // 0
    
  4. String 类型

    • 转换为字符串有两种方式 toString([几进制])String(名)

      var num = 10;
      console.log(num.toString()); // '10'
      console.log(num.toString(2)); // '1010'
      console.log(String(10)); // '10'    
      
  5. Object 类型
    访问对象的属性有两种方式

    person['name']
    person.name
    

3.3 操作符

  1. 布尔操作符
    逻辑非和逻辑或都是 短路 操作符

    • 逻辑与
      如果第一个操作数是对象,则返回 第二个 操作数;
      如果第二个操作符是对象,则只有第一个操作数的求值结果是 true 的情况下才会返回该 对象

      var obj = {
        name: 'lily',
      }
      console.log(obj && true); // true
      console.log(true && obj); // {name: 'lily'}
      
    • 逻辑与
      如果第一个操作数是对象,则返回 第一个 操作数;
      如果两个操作数都是对象,则返回 第一个 操作数;

  2. 关系操作符

    • 如果两个操作数都是字符串,则比较两个字符串对应的 字符编码,所以字符串比较的时候一般都转换为大写(或者小写);

    • 任何操作数与 NaN 进行比较,结果都是 false;

    console.log('Brink'.toLowerCase() < 'alpha'.toLowerCase()); // false
    console.log(NaN < 3); // false
    

3.4 语句

  1. for 语句
    在 ECMAScript 中没有块级作用域的概念,因此循环内部定义的变量也可以在外部访问到。

    for (var i = 0;i < 10 ; i++) {}
    console.log(i);  //10
    

    其实以上的代码就相当于

    var j;
    for (j = 0; j < 10; j ++) {}
    console.log(j);
    
  2. for-in 语句
    for-in 语句用来遍历对象的属性,但此时的对象不能是null或者undefined。

  3. switch 语句
    switch 语句在比较值的时候使用的是全等
    看一个我觉得很新颖的语句

    var num = 18;
    switch (true) {
      case num < 0:
        alert('less than 0');
        break;
      case num > 0 && num < 10:$
        alert('between 0 and 10');
        breka;
      default:
        alert('more than 10');
    }
    
  4. 函数
    可以向 ECMAScript 函数传递任意数量的参数,并且可以通过arguments对象来访问这些参数。即便你定义的函数只接收两个参数,在调用这个函数也未必一定传递两个参数,可以传递一个、三个甚至不传递参数。如果没传递值的命名参数将自动被赋予undefined值。

    function add() {
      if (arguments.length == 1) {
        alert(arguments[0] + 10);
      } else {
        alert(arguments[0] + arguments[1]);
      }
    }
    add(10);  // 20
    

四、变量、作用域和内存问题

4.1 变量

ECMAScript 变量包含两种不同类型的值:基本类型和引用类型值。

  1. 基本类型的值

    • 基本类型的值在内存中占据固定大小的空间,因此保存在 内存中

    • 如果从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把值复制到新变量的位置上。

      var num1 = 5;
      var num2 = num1;
      

      此处输入图片的描述

    • 传递参数的时候是按 传递的,和复制变量一样。

  2. 引用类型

    • 引用类型的值是对象,保存在 内存中;

    • 从一个变量向另一个变量复制引用类型的值时,同样也将存储在变量对象中的值复制一份到新分配的空间中。不同的是,这个值的副本实际上时一个指针

      var obj1 = new Object();
      var obj2 = obj1;
      

      此处输入图片的描述

      但是当为对象添加属性的时候,操作的是实际对象

      obj1.name = "lily";
      console.log(obj2.name);  // lily
      
    • 传递参数的时候是按 传递的,和引用类型复制一样。

4.2 执行环境和作用域

- 执行环境
    执行环境定义了变量或函数有权访问的其他数据,决定了他们的行为;
    执行环境可以分为**全局执行环境** 和 **每个函数自己的执行环境**; 

- 作用域链
    代码在执行环境中执行,会创建对象的一个 **作用域链**,这个作用域链可以保证对执行环境有权访问的所有变量和函数的有序访问。

4.3 内存管理

优化内存占用的最佳方式,就是为代码只保存必要的数据。一旦数据不再用了,最好通过将其值设置为 null 来释放引用。

function cretePerson(name) {
  var localPerson = new Object();
  localPerson.name = name;
  return localPerson;
}
var g_person = cretePerson('lily');
g_person = null;

五、引用类型

在 ECMAScript中,引用类型是一种数据结构,常用的有:Object、Date、RegExp、Function、基本包装类型、单体内置对象。

5.1 Array 类型

  1. 插入

    • 在末尾插入数据: arr[length] = 'add data from bottom';
    • 在末尾插入数据: arr.push('add data from bottom');
    • 在头部插入数据: arr.unshift('add data from head');
    • 在任意位置插入数据,返回删除数据组成的数组:splice(2,0,'red','green');,三个参数,第一个是位置,第二个是要删除的数据个数,第三个数据为插入的数据;
  2. 删除

    • 在末尾删除数据: arr[length] = arr.length - 1;
    • 在末尾删除数据,返回删除的数据: arr.pop();
    • 在头部删除数据,返回删除的数据: arr.shift();
    • 任意位置删除,返回删除数据的数组: arr.splice(0,1);,两个参数,第一个参数是位置,第二个参数是要删除的个数
  3. 替换

    • 替换任意位置数据,返回被替换的数据组成数组: arr.splice(2,1,'red')
  4. 检测数组
    检测是否是数组

    if(Array.isArray(value)){}
    
  5. 转换为字符串的方法

    • join
    • toString
    • tolocalString
    • valueOf
  6. 排序方法

    • reverse
    • sort(可以传入排序规则)
  7. 合并

    • concat
    var color1 = ['color1', 'color2'];
    var color2 = color1.concat('yellow');  // color1,color2,yellow
    
  8. 位置方法

    • indexof('要查找内容',起始位置),不包含返回 -1
    • lastIndexof
  9. 迭代

    • every(): 对数组的每一项运行给定函数,每一项都 true 的时候,返回 true;
    • some(): 对数组中的每一项运行给定函数,有任意一项返回 true 时候,才会返回 true;
    • filter(): 对数组中的每一项都运行给定函数,返回该函数返回 true 项组成的数组
    • forEach(): 对数组中的每一项运行给定的函数,无返回值
    • map(): 对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。

10.归并数组
- reduce
- reduceRight

5.1 Function 类型

函数分为函数声明和函数表达式,函数表达式可以随时访问,因为函数声明提升

  • 函数内部属性
    • arguments 对象
      arguments 对象主要用途是保存函数参数,包含一个特殊属性callee属性,是个指针,指向拥有这个arguments对象的函数

      function factorial (num) {
        if (num < 1) {
          return 1;
        } else {
          return num * arguments.callee(num - 1);
        }
      }
      
      console.log(factorial(4));  //24
      
    • this 对象
      this 引用的是函数执行环境对象

    window.color = 'red';
    var obj = {color: 'obj bule'};
    
    function showColor () {
      console.log(this.color);
    }
    
    showColor();  // red
    obj.sayColor = showColor;
    obj.sayColor(); // obj bule
    
  • 非继承的函数方法
    每个函数都包含两个方法:call() 和 apply()。这两个方法的用途都是在特定的作用域中调用函数,实际上就是设置函数体内this的值
    • apply(运行函数的作用域, 参数数组)

      window.color = 'window color red';
      var obj = {color: 'obj color blue'};
      
      function showColor(data) {
        console.log(this.color);  //window color red
        console.log(`传入数据${data}`);
      }
      
      showColor(); 
      showColor.apply(obj, ['我用了apply']);  
      // obj color blue  传入数据我用了apply
      
    • call(运行函数的作用域,其余参数)

      window.color = 'window color red';
      var obj = {color: 'obj color blue'};
      
      function showColor(data) {
        console.log(this.color);  //window color red
        console.log(`传入数据${data}`);
      }
      
      showColor.call(obj, '我用了call');  
      //obj color blue  
      //传入数据我用了call
      

5.1 String 类型

 1. length
 2. charAt(位置)
 3. charCodeAt(位置):返回字符编码
 4. substring(start, [end])
 5. substr(start, [end])
 6. slice(start, [end])
 7. indexof(要查找内容,位置)
 8. trim: 去除空格
 9. toUpperCase
 10. toLocaleUpperCase
 11. toLowerCase
 12. toLocalLowerCase
 13. split(切割符),返回数组

flat 和 flatMap

今天分享一个ECMAScript的continuous,文章最下面附了原文链接。
文章的题目:“Flatten Arrays in Vanilla JavaScript with flat() and flatMap()”很好的解释了flat()flatMap()的作用,就是用来 展开数组,并且是不用任何第三方库;直接上例子

flat()

  • 简单的例子
const animals = [['🐕', '🐶'], ['😺', '🐈']];

const flatAnimals = animals.flat();
// same as: const flatAnimals = animals.flat(1);

console.log(flatAnimals);

// ['🐕', '🐶', '😺', '🐈']
  • 当数组的总的深度大于flat()方法的深度的时候:
const animals = [['🐕', '🐶'], ['😺', '🐈', ['😿',['🦁'], '😻']]];

const flatAnimals = animals.flat(2);

console.log(flatAnimals);
// ['🐕', '🐶', '😺', '🐈', '😿',['🦁'], '😻']
  • 如果你想要展开任意的数组的话,可以给flat的参数设置为Infinity
const animals = [['🐕', '🐶'], ['😺', '🐈', ['😿',['🦁'], '😻']]];

const flatAnimals = animals.flat(Infinity);

console.log(flatAnimals);
// ['🐕', '🐶', '😺', '🐈', '😿', '🦁', '😻']

flatMap()

flatMap() 就是对数组的每个值先执行map方法,然后再对形成的数组执行flat(1)的方法;

const animals = ['🐕', '🐈', '🐑', '🐮'];
const noises = ['woof', 'meow', 'baa', 'mooo'];

const mappedOnly = animals.map((animal, index) => [animal, noises[index]]);
const mappedAndFlatten = animals.flatMap((animal, index) => [animal, noises[index]]);

console.log(mappedOnly);
// [['🐕', 'woof'], ['🐈', 'meow'], ['🐑', 'baa'], ['🐮', 'mooo']

console.log(mappedAndFlatten);
// ['🐕', 'woof', '🐈', 'meow', '🐑', 'baa', '🐮', 'mooo']

原文地址的链接

技术分享-MobX

背景

在 2013 年以前,对于数据流的控制一直以来是使用的 MVC(Model-View-Controller) 架构模式来进行数据的管理:

  • Model(模型) 负责管理数据;
  • View(视) 负责渲染用户界面;
  • Controller(控制器) 负责接受用户的输入,根据用户输入调用对应 Model 部分逻辑,把产生的数据结果交给 View 部分,让 View 渲染出必要的输出;

理想的MVC图1-理想的MVC

但是对于非常巨大的代码库和庞大的组织来说,MVC 很快就会变得非常复杂。每当工程师需要新增一个功能的时候,对代码的修改可能带来新的 bug,不同模块之间的依赖关系会变得“脆弱而且不可预测”。如图2:

现实的MVC图2 - 现实的MVC

基于以上的情况,Facebook 公司推出了 Flux 框架,用来管理数据,相比于 MVC ,它是一种更严格的数据流控制。

Flux 框架图3 - Flux框架

一个 Flux 包含四个部分,如下:

  • Dispatcher:处理分发动作,维持Store之间的依赖关系;
  • Store:负责存储数据和处理数据相关的逻辑;
  • Action:驱动 Dispatcher;
  • View:视图展示;

当用户请求一个动作,会触发 Action,之后 Action e驱动 Dispatcher 来进行分发 Action 操作,从而更新 Store 中的数据,Store 中数据改变后,就会更新 View 的展示。
Flux 虽然很好,也有不足之处,比如说 难以进行服务端渲染Store 混杂了逻辑和状态等,但是这种 单一数据流 的理念衍生出了像 Redux 和 MobX 框架的实现。本篇文章着重讲述 MobX。

简介

MobX 通过 响应式编程(在命令式编程环境中,a := b + c 表示将表达式的结果赋给 a,而之后改变 b 或者 c 的值不会影响 a 的值,但在响应式编程中, a 的值会随着 b 或 c 的值得改变而改变)的**来管理数据。MobX 也是支持单向数据流的,是通过 action 触发 state 的变化,进而触发 state 的衍生对象(Computed 和 Reactions)。所有的衍生默认都是同步更新的。

MobX 实现图4 - MobX实现

概念

装饰器

ESNext 中新增了 decorator 属性,所谓装饰器,可以简单的理解为 锦上添花;以钢铁侠为例,钢铁侠本质上是一个人,只是装饰了很多的武器以后才变得很 NB ,不过怎么装饰他还是一个人。

钢铁侠 实现图5 - 钢铁侠装饰器

function decorateArmour(target, key, descriptor) {
  const method = descriptor.value;
  let moreDef = 100;
  let ret;
  descriptor.value = (...args)=>{
    args[0] += moreDef;
    ret = method.apply(target, args);
    return ret;
  }
  return descriptor;
}

class Man{
  constructor(def = 2,atk = 3,hp = 3){
    this.init(def,atk,hp);
  }

  @decorateArmour   //这个就是使用了装饰器
  init(def,atk,hp){
    this.def = def; // 防御值
    this.atk = atk;  // 攻击力
    this.hp = hp;  // 血量
  }
  toString(){
    return `防御力:${this.def},攻击力:${this.atk},血量:${this.hp}`;
  }
}

var tony = new Man();

console.log(`当前状态 ===> ${tony}`);
// 输出:当前状态 ===> 防御力:102,攻击力:3,血量:3

tips:
ES7 中的 decorator 其实是一个语法糖,不过依赖于 Object.defineProperty(obj, prop, descriptor)

  • obj : 要在其上定义属性的对象;
  • prop: 要定义和修改的属性的名称;
  • descriptor: 将被定义或者修改的属性的描述符;

可观察数据

在 MobX 中, State 就对应业务的原始状态,可以通过 observable 或者 @observable 将这些状态变为可观察的,顾名思义,可观察数据就是 当数据变化的时候,可以被观察到

  • 哪些数据可以被观察
    一般来说,原始类型(String, Number, Boolean, Symbol),对象(objects),数组(arrays),maps 都可以被观察,其中 objects arrays mapsobservable 转换为可观察数据,而 原始数据observable.box 转化为可观察数据;
    • 普通object
      普通对象和非普通对象的划分就是看对象是否有原型,如果没有原型或者原型是Object.prototype 的对象,那么就是普通对象;

      const person = observable({
        name: 'lily',
        age: 26,
        address: {
          province: '天津',
        }
      });
      
      console.log('打印经过observable修饰后的对象---', person);
      //Proxy {Symbol(mobx administration): ObservableObjectAdministration$$1}
      
      // 默认第一次会执行一次
      autorun(() => {
        console.log(`name: ${person.name}-- age:${person.age}--address:${JSON.stringify(person.address)}`);
      });
      
      // 改变name的值
      person.name = '石丽丽';
      //name: 石丽丽-- age:26--address:{"province":"天津"}
      
      // 改变 address 的province的值, 说明会递归遍历整个对,即使属性还是个对象;
      person.address.province = '北京';
      //name: 石丽丽-- age:26--address:{"province":"北京"}
      
      // 新增的属性是不可以被观察的,可以使用 extendObservable
      person.obj = "web developer";
      // 没有打印的结果
      // name: 石丽丽-- age:26--address:{"province":"北京"}--obj:web developer--extendObj:undefined
      
      // 使用 extendObservable
      extendObservable(person, {
        extendObj: 'extend web developer'
      })
      // name: 石丽丽-- age:26--address:{"province":"北京"}--obj:web developer--extendObj:extend web developer
      

      tips: 对于新增的属性,不可以被观察,如果需要被观察需要用 extendObservable或者set;observable 会递归遍历整个对象,即使这个属性还是个对象;

    • 非普通对象
      observable 会返回一个特殊的boxed values 类型的可观测的对象,返回的 boxed values 对象并不会把非普通对象的属性转换为可观测的,而是保存一个指向对象的引用;这个引用是可观测的;对原对象的访问和修改可以通过 get()set() 方法操作。

      function  Person(name, age) {
        this.name = name;
        this.age = age;
      }
      const person = observable.box(new Person('lily', 26));
      
      autorun(() => {
        console.log(`name: ${person.get().name}, age: ${person.get().age}`);
      });
      
      // 改变属性
      person.get().name = 'wanghong';
      // 不会打印
      
      // 改变引用
      person.set(new Person('wanghong', 27));
      //name: wanghong, age: 27
      

      对于非普通的对象的属性,可以通过以下的方式将其变为可观察的:

      //将非普通对象的属性变为可观察的
      function Person(name, age) {
        // 使用 extendObservable 在构造函数中创建可观察的属性
        extendObservable(this, {
          name: name,
          age: age,
        })
      }
      
      const person = new Person('extendlily', 28);
      
      autorun(() => {
        console.log(`使用extendObservable创建可观察的属性--name:${person.name},age:${person.age}`);
      });
      
      // 改变对象的属性
      person.name = '王宏';
      // 使用extendObservable创建可观察的属性--name:王宏,age:28
      

      这种方式比较麻烦,所以推荐使用装饰器的方式,这种方式的好处还在于对于原始数据类型的数据的话,自己内部有判断,不用使用observable.box()如下:

      // 非常推荐的一种方式,使用 @observable 装饰器
      class Person {
        @observable name;
        @observable age;
      
        constructor(name, age) {
          this.name = name;
          this.age = age;
        }
      }
      
      const person = new Person('@lily', 20);
      
      autorun(() => {
        console.log(`使用装饰器修饰的--name:${person.name},age:${person.age}`);
      })
      
      // 改变可观察的属性
      person.name = 'wanghong';
      // 使用装饰器修饰的--name:wanghong,age:20
      
    • arrays

      const arr = observable([1,2,3]);
      console.log(`用observable修饰的数组`, arr);
      
      autorun(() => {console.log(`arr--, ${arr}`)})
      // 判断是不是一个数组
      arr.push(4);
      // arr--, 1,2,3,4
      

      tips: 判断是不是数数组的两种方式: Array.isArray(observable([]).slice())isArrayLike(arr)

    • maps

      const map = observable.map({ key: "value" });
      
      autorun(() => {
        console.log(`map:${map.get('key')}`);
      })
      
      // 改变key的值
      map.set("key", "new value");
      // map:new value
      

      tips: Map 对象的每个对象都是可观测的,而且向Map对象中添加删除元素的行为也是可以被观测的;

    • 原始数据类型
      对于原始数据的话,通过 get() 获取数据,通过 set() 设置数据;

        const cityName = observable.box('Vienna');
      
        console.log(cityName.get());  // Vienna
      

对 observables 做出响应

MobX 中四种方式对 observables 做出响应,分别为 @computed autorun when reaction,接下来会分别介绍这四种方式的使用场景:

  • @computed

    Computed values are values that can be derived from the existing state or other computed values. Conceptually, they are very similar to formulas in spreadsheets. Computed values can't be underestimated, as they help you to make your actual modifiable state as small as possible. Besides that they are highly optimized, so use them wherever possible.
    计算值(computed values)是可以根据现有的状态或其它计算值衍生出的值。 概念上来说,它们与excel表格中的公式十分相似。 不要低估计算值,因为它们有助于使实际可修改的状态尽可能的小。 此外计算值还是高度优化过的,所以尽可能的多使用它们。

    以上的这句话是 MobX 官网的原话,这段话充分的说明了 MobX 的使用场景和重要性。Mobx 是纯函数,不能改变state的状态,computed value 采用的是延迟更新,computed values are automatically derived from your state if any value that affects them changes。如果一个计算值不再被观察了,例如使用它的UI不复存在了,MobX 可以自动地将其垃圾回收。

    class Squared{
      @observable length = 2;
    
      constructor(length) {
        this.length = length;
      }
    
      @computed get squared() {
        return this.length * this.length;
      }
    }
    
    const square = new Squared(2);
    
    // 改变长度
    square.length = 3;
    // Squared的面积:9
    
    autorun(() => {
      console.log(`Squared的面积:${square.squared}`)
    });
    
  • autorun
    顾名思义,就是自动执行,当使用 autorun 时,所提供的函数总是立即被触发一次,然后每次它的依赖关系改变时会再次被触发。相比于 computed,他不会产生一个新的值,它更像是发起请求这样的命令式副作用;它会返回一个清楚函数 disposer,当不需要观察相关的 state 变化的时,可以调用 disposer 函数清除副作用。

    const number = observable([1,2,3]);
    const sum = computed(() => number.reduce((a, b) => a + b), 0);
    
    const disposer = autorun(() => {console.log(sum.get())});
    
    number.push(4);
    // 10
    
    // 清除副作用
    disposer();
    
    number.push(5);
    // 不执行
    
  • when
    when(predicate: () => boolean, effect?: () => void, options?),predicate 会自动响应它使用的任何的state的变化,当predicate 返回ture 的时候,函数effect会执行,且执行一次。when 也返回一个 disposer 函数。when 非常的适合用在以影响式的方式执行取消或者清楚逻辑的场景;

componentDidMount() {
class showDetail{
@observable first = 2;
@observable second = 3;

  @computed get isVisible() {
    return (this.first * this.second) > 10
  }
  
  constructor() {
    when(
      () => this.isVisible,
      () => this.dispose(),
    )
  }

  dispose() {
    console.log('这里做清理工作');
  }
}

const demo = new showDetail();
demo.second = 10;
// 这里做清理工作
```
  • reation
    reaction(() => data, (data, reaction) => { sideEffect }, options?),它接收两个函数参数,第一个(数据函数)是用来追踪并返回数据作为第二个函数(效果函数)的输入,第二个函数 reaction 会返回一个清楚函数 disposer。第一个函数是返回需要被观察的数据,第二个函数接收这个需要被观察的数据,同时传入 reaction,当被观察的数据改变的时候,就会触发 reaction,这样就不像 autorun 似的,当一个状态改变的时候就会触发,从而建立和要被观察的数据和reaction之间的关系;总结来说,相较于autorun,reaction 可以对跟踪哪些对象有更多的控制;
    const todos = observable([
      {
        title: 'Java',
        done: true,
      },
      {
        title: 'javascript',
        done: false,
      }
    ]);
    
    // 对 length 做出反应
    const resLen = reaction(
      () => todos.length,
      length => console.log(`对长度做出反应:${todos.map(todo => todo.title).join(',')}`)
    )
    
    // 对 length 和 title 的变化作出反应
    const resTitle = reaction(
      () => todos.map(todo => todo.title),
      titles => console.log(`对标题做出反应:${titles.join(',')}`)
    );
    
    // autorun 对任何可观察数据做出反应
    const resAll = autorun(
      () => console.log(`autorun 对任何的变化做出反应: ${todos.map(todo => todo.title).join(',')}`)
    )
    
    // 改变todos的长度
    todos.push({
      title: 'C++',
      done: false,
    });
    /**
     * 对长度做出反应:Java,javascript,C++
     * Reaction.jsx:26 对标题做出反应:Java,javascript,C++
     * Reaction.jsx:31 autorun 对任何的变化做出反应: Java,javascript,C++ 
     * */
    
     // 改变 title
     todos[0].title = 'Make tea';
     /**
      * 对标题做出反应:Make tea,javascript,C++
      * Reaction.jsx:31 autorun 对任何的变化做出反应: Make tea,javascript,C++ 
      * */
    
    

改变observables

官方建议修改 observables 或者具有副作用的函数使用 @action,简单的说就是对于修改可观察的数据,建议使用 @action

  • @action 和 @action.bound

    action 装饰器/函数遵循 javascript 中标准的绑定规则。但是,action.bound 可以用来自动地将动作绑定到目标对象。 注意,与 action 不同的是,(@)action.bound 不需要一个name参数,名称将始终基于动作绑定的属性。

    class Ticker{
      @observable tick = 0;
    
      @computed get ifDispose() {
        return this.tick >= 10;
      }
    
      // 使用@action.bound 绑定的this永远是正确的
      @action.bound
      // 使用action的话就不会增加,因为此时的this是window
      // @action
      increment() {
        this.tick ++;  
      }
      
    }
    
    const ticker = new Ticker();
    
    const disposer = autorun(() => {console.log(`tick: ${ticker.tick}`)})
    
    when(
      () => ticker.ifDispose,
      () => disposer(),
    )
    
    setInterval(ticker.increment, 1000)
    
  • asny actions

action包装/装饰器只会对当前运行的函数作出反应,而不会对当前运行函数所调用的函数(不包含在当前函数之内)作出反应! 这意味着如果 action 中存在 setTimeout、promise 的 then 或 async 语句,并且在回调函数中某些状态改变了,那么这些回调函数也应该包装在 action 中。

  • 使用 action
    这里列出了比较常见的一种方式,就是使用 action 进行包装:

     ```
     class Store {
       @observable students = [];
       @observable loadState = 'pending';
       
       @action
       fetchData() {
         this.students = [];
         this.loadState = 'pending';
         rest.fetchActionData({}).then(
           // 内联创建动作
           action('fetchSuccess', response => {
             if (response.code === '0' && response.result) {
               this.students = [...response.result];
               this.state = 'done';
             }
           }),
           action('fetchError', error => {
             this.state = 'error';
           })
         )
       }
     }
     
     const store = new Store();
     
     autorun(() => {
       console.log(`store中的students: ${JSON.stringify(store.students)}`);
     })
    
     store.fetchData();
     ```
    
  • 使用 runInAction 工具函数
    这种模式的优点在于你可以不用到处的写action,而仅仅在整个过程结束的时候对状态进行修改:

    class Store {
      @observable students = [];
      @observable loadState = 'pending';
    
      @action
      fetchData() {
        this.students = [];
        this.loadState = 'pending';
        rest.fetchActionData({}).then(
          response => {
            if (response.code === '0' && response.result) {
              //  将最终的修改放在一个异步的操作中
              runInAction(() => {
                this.students = [...response.result];
                this.loadState = 'done';
              })
            }
          },
          error => {
            runInAction(() => {
              this.loadState = 'error';
            })
          }
        )
      }
    }
    
    const store = new Store();
    
    autorun(() => {
      console.log(`store中的students: ${JSON.stringify(store.students)}`);
    })
    
    store.fetchData();
    
    • 使用 flows

    However, a nicer approach is to use the built-in concept of flows. They use generators. Which might look scary in the beginning, but it works the same as async / await. Just use function * instead of async and yield instead of await. The advantage of flow is that it is syntactically very close to async / await (with different keywords), and no manually action wrapping is needed for async parts, resulting in very clean code.
    flow can be used only as function and not as decorator. flow integrates neatly with MobX development tools, so that it is easy to trace the process of the async function.
    然而,更好的方式是使用 flow 的内置概念。它们使用生成器。一开始可能看起来很不适应,但它的工作原理与 async / await 是一样的。只是使用 function * 来代替 async,使用 yield 代替 await 。 使用 flow 的优点是它在语法上基本与 async / await 是相同的 (只是关键字不同),并且不需要手动用 @action 来包装异步代码,这样代码更简洁。
    flow 只能作为函数使用,不能作为装饰器使用。 flow 可以很好的与 MobX 开发者工具集成,所以很容易追踪 async 函数的过程

     ```
     class Store {
       @observable students = [];
       @observable loadState = 'pending';
    
       fetchData = flow(function * () {  // 这是一个生成器函数
         this.students = [];
         this.loadState = 'pending';
         try {
           const response = yield rest.fetchActionData();  // 获取resolve 解析的值
    
           // 异步代码会被自动的包装成动作
           if (response.code === '0' && response.result) {
             this.students = [...response.result];
             this.state = 'done';
           }
    
         } catch (error) {
           this.state = 'error';
         }
       })
     }
    
     const store = new Store();
    
     autorun(() => {
       console.log(`store中的students: ${JSON.stringify(store.students)}`);
     })
    
     store.fetchData();
     ```
    

mobx-react

mobx-react 顾名思义,是联系 MobxReact 之间的桥梁,从而更方便的使用 MobxReact 中开发,经常的用到有:Provider inject observer/@observer:

  • Provider
    Provider 是一个 React 组件,利用 Reacxt 的 context 机制把应用所需的 state 传递给子组件。

  • inject
    inject 是个高阶组件,和 Provider 结合使用,用于从 Provider 提取所需的 state,作为 props 传递给目标组件。

  • observer/@observer

    observer 函数/装饰器可以用来将React组件转变为响应式组件。它用 mobx.autorun 包装了组件的render函数以确保任何组件渲染中使用的数据变化时都可以强制刷新组件。

    /**
    * App.jsx 文件
    **/
    import React, { Component } from 'react';
    import { Provider } from 'mobx-react';
    import TodoList from './pages/todoList/TodoList';
    import stores from './mobx/stores/index';
    
    class App extends Component {
      render() {
        return (
          <Provider {...stores}>
            <TodoList />
          </Provider>
        );
      }
    }
    
    export default App;
    
    /**
    *  TodoList.jsx 文件
    **/
    import React, { Component } from 'react';
    import { observer, inject } from 'mobx-react';
    import TodoFilterForm from './TodoFilterForm';
    import TodoMain from './TodoMain';
    import TodoFilter from './TodoFilter';
    import '../../styles/todoList.css';
    
    // Provider 配合 inject 引入 state
    // 无论有多少修饰器,@observer 永远在第一个
    @observer 
    @inject('todoStore')
    class TodoList extends Component {
     
      render () {
        const { todoStore } = this.props;
        return (
          <div className="todo-list">
            <TodoFilterForm todoStore = {todoStore}/>
            <TodoMain todoStore = {todoStore}/>
            <TodoFilter todoStore = {todoStore}/>
          </div>
        )
      }
    }
    
    export default TodoList;
    

手写一个 todoList

开发环境的搭建

MobX 中大量的使用了 ES.Next 中的装饰器语法,为了在新搭建的项目中支持这种语法,有两种实现方式:

  1. 使用create-react-app project-name --scripts-version custome-react-scripts 创建项目,这种方式创建的项目,支持 修饰器语法LessSass

  2. 仍然使用create-react-app project-name 创建项目,然后执行yarn run eject弹射出配置文件,然后安装 yarn add babel-plugin-transform-decorators-legacy -D 修改 webpack 的配置文件,添加

    "babel": {
        "plugins": [
            "transform-decorators-legacy"
        ],
        "presets": [
            "react-app"
        ]
    }
    

以上的内容配置好了以后,还要通过 yarn add mobx-react mobx -S 安装 mobx-react;

目录结构

目录结构图6-目录结构

代码

因为代码比较多,所以直接上github的地址:https://github.com/ycshill/shared/tree/master/mobx-share

MobX 常用工具函数和调试

  • toJS(value, options)
    递归地将一个observable对象转换为javascript结构。支持observable数组、对象、映射和原始数据类型

    const person = observable({
      name: 'lily',
      age: 26,
    });
    console.log(`没有时候用toJS转化时候的对象--`, person);
    // 没有时候用toJS转化时候的对象-- Proxy {Symbol(mobx administration): ObservableObjectAdministration$$1}
    console.log(`通过toJS转化后的对象---`, toJS(person));
    // 通过toJS转化后的对象:{"name":"lily","age":26}   
    
  • mobx-react-devtools
    mobx-react-devtools 是一个用来调试 MobX + React 项目的工具,可以追踪组件的渲染以及组件依赖的可观测数据。

    • 安装
      yarn add mobx-react-devtools -D

    • 代码

        renderDevTool() {
          // 在开发环境下,添加调试工具;
          if (process.env.NODE_ENV !== 'production') {
            const DevTools = require('mobx-react-devtools').default;
            return <DevTools />;
          }
        }
      

性能优化

  • 尽可能多地使用小组件
    @observer 组件会追踪render方法中所有的可观测的值,当任何一个值变化的时候,都会重新渲染,所以组件越小,重新渲染的变化就越小。

  • 在专用的组件中渲染列表

    /**
    * TodoMain.jsx
    **/
     <div>
        <ul>{todoList.map((todo) => {
          return <li className="todo-item" key={todo.id}>
            <TodoItem todo={todo} />
            <span className="delete" onClick={e => todoStore.removeTodo(todo)}>X</span>
          </li>
        })}</ul>
      </div>
    
    /**
    * TodoItem.jsx
    **/
    import React, { Component, Fragment } from 'react';
    import { observer } from 'mobx-react';
    
    @observer
    class TodoItem extends Component {
    
      handleCeckboxClick = () => {
        this.props.todo.toggle();
      }
    
      render () {
        const { todo } = this.props;
        return (
          <Fragment>
            <input
              type="checkbox"
              className="toggle"
              checked={todo.finished}
              onClick={this.handleCeckboxClick}
            />
            <span className={['title', todo.finished && 'finished'].join(' ')}>{todo.title}</span>
          </Fragment>
        )
      }
    }
    
    export default TodoItem;
    
  • 晚一点使用间接引用值

    使用 mobx-react 时,推荐尽可能晚的使用间接引用值。 这是因为当使用 observable 间接引用值时 MobX 会自动重新渲染组件。 如果间接引用值发生在组件树的层级越深,那么需要重新渲染的组件就越少

    快的: <DisplayName person={person} />
    慢的: <DisplayName name={person.name} />

Redux VS Mobx

此处输入图片的描述图8- redux&mobx

  • 社区观点
    • store
      • redux:单一store,通过拆分reducer来拆分应用逻辑,单一store可以方便不同组件之间进行的数据共享
      • mobx:多个store,把逻辑拆分到不同的store中,当维护多个组件之间的数据共享、相互之间的引用的时候会变得特别的麻烦。
    • 编程**
      • redux:是基于函数式的编程**;
      • mobx: 是面向对象的编程**;
    • state:
      • redux:state不可改变,每次状态的变化,都会创建一个新的state
      • mobx:state是可观测对象,并且state可以被直接的修改,state的变化会自动触发使用它的组件重新渲染。
  • 源码观点
    推荐一篇文章:我为什么从Redux迁移到了Mobx
  • 个人观点
    • mobx: 个人感觉可观察数据变化的时候,组件自动更新,只需要在组件上加上@observer修饰组件就可在修改数据的时候自动进行处理更新,同时免面向对象的编程的写法感觉如果熟悉面向对象编程的开发人员,会减少学习的成本;
    • redux:单一store对于数据的管理,更加容易的跟踪state,但是学些成本和负责的更改数据流程让人觉得不是很友好。

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.