Coder Social home page Coder Social logo

tech_post's Introduction

tech_post's People

Contributors

alexis374 avatar bryant1410 avatar

Stargazers

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

Watchers

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

tech_post's Issues

git cheatsheet

  • git diff somefile 比较working dir和暂存区域快照的差异。
  • git diff --cached somefile 比较暂存区域的不同(add 之后)和 已经commit之后(即快照)的不同
  • 若要删除版本库的文件,首先rm file此时处于not staged状态,然后git rm file(类似添加文件的git add),表示暂存了删除的动作,最后再git commit -m保存到快照
  • git rm --cached file从快照中删除该文件,保留当前工作区的文件,使其成为untracked的状态。
  • 'git reset HEAD filename',将filename从暂存区中恢复成修改状态。此时文件是已经修改的,但是没有add
  • git checkout filename,撤销修改,还原成未修改的样子。是个危险的动作。
  • git commit --amend 将两次提交合并成一次。如编辑了a,b两个文件,第一次只add和commit了a,现在想在这一次commit里同时提交b文件,先git add b,然后git commit --amend,修改提交信息。
  • git remote,列出远程仓库的名,git remote -v列出详细的地址。git remote add name url 添加一个名为name,指定url的远程仓库
  • git fetch remoteName [branchName]拉取远程仓库的内容(默认master),成功后可以通过remoteName/branchName访问。只会拉取数据,但不会手动合并。

关于网站的防爬

请问我应该如何判断网站是根据什么来判断阻止爬取的?又改如何针对解决?
例如我爬淘宝的详情图片的时候第一个详情页77张大图,每次都74就主动拒绝了。换了ip睡几分钟一抓还是立刻拒绝掉。

JSX in Depth

JSX in Depth

本文为我读《ReactJS by Example》英文书第二章的总结

  • why jsx?
    • jsx 代码可读性更高,更简单
  • 在jsx中,原生html标签为小写,自定义的React组件开头要大写
  • JSX是XML-like的,这意味着必须要有结束标签,不管是<App />还是<App></App>
  • js表达式,用{}包裹起来的表达式,大体上分为两类,一类是普通的js语句,用于求值,比较等;另一类用在JSX语句的内部,通常值是多个React元素组成的列表
render(){
    return (<tbody>
        {
            {/* 下面表达式返回的是由Row元素组成的数组。这种表达式中插入注释也和普通的表达式不同  */}   
            this.props.changeSets.map((changeSet,index)=>{
                return <Row changeSet={changeSet} key={index} />
            })
        }        
    </tbody>)
    }
  • {this.props.children}的用法:
    {this.props.children}可以把一个元素包装成另一个元素而不改变其功能。比如,
    原来的元素是
class App extends Component{
    render(){
        return (
            <table>
                <A a={this.props.a} />
                <B b={this.props.b} />
            </table>
        )
    }
}
//将 table元素包装成自定义元素 RecentChangesTable:
class RecentChangesTable extends Component{
    render(){
        return (<table>
            {this.props.children}
        </table>
    }
}
class App extends Component{
    render(){
        return (
            <RecentChangesTabl>
                <A a={this.props.a} />
                <B b={this.props.b} />
            </RecentChangesTable>  

        )
    }
}
  • spread attibute:
var props={
    a:'123',
    b:'456'
}
//传递给组件时:
ReactDOM.render(<App {...props}/>,document.getElementById('app'))
  • style: style,class需要设置在真实的DOM元素上。注意是className
    • <table style={{backgroundColor:'#fff',}}>
    • <table className = 'recentChangesTable'>
  • 设置不在html规范上的属性最终不会渲染,data-除外。
  • 转义特殊字符串:
    <div>you &amp; me </div> // 直接写在元素里的不会转义 ,输出you & me
    var str = 'you &amp; me' // 输出 you &amp; me 
    <div>{str} </div> //当做变量传进来的会转义

    // 避免转义的方法:
    1. 用一个数组 <div> {['first', <span>&amp;</span>, 'second']} </div>
    2. <div dangerouslySetInnerHTML={{__html: 'Mike &amp; Shawn'}} />

vuex 文档总结

半翻译,半总结

读vuex文档


vuex是为了方便大型项目中多组件之间的状态管理而引入的插件,借鉴了Flux和Redux的架构模式。
整个app的状态都集中于store,store与简单的js对象相比有两点不同:

  1. 它是响应式的,store更改会自动反映到关联的组件,更新其显示
  2. 不能显式设定store的状态,而需要通过分发mutations。这使得每次更改都可以被追踪记录。

store的构成: state对象和mutations对象

import Vuex from 'vuex'

const state = {
  count: 0
}

const mutations = {
  INCREMENT (state) {
    state.count++
  }
}

export default new Vuex.Store({
  state,
  mutations
})

vuex的工作流:

用户交互触发action,action dispatch 相应类型的mutaion,
mutation修改state。getter函数抽取state当中需要的部分,组件调用相应的getter展示需要的数据。

action函数接受一个store对象作为第一个参数,但是也可以传入对象,vuex会自动包装。

export const incrementCounter = function ({ dispatch, state }) {
  dispatch('INCREMENT', 1)
}

mutations

  1. mutations 作为store的属性,是一个对象,是name和handler function的集合。handler接收state作为第一个参数
  2. name一般为大写,handler函数不可直接调用,只能通过store.dispatch(name,...restArgs)调用
  3. handler可以接收多个参数
  4. object-style dispath: store.dispatch({type:name,args}),对象作为handler的第二个参数
  5. mutation遵循vue的响应规则,推荐初始化时设置所有的值,当给对象添加属性时,应该用Vue.set(obj, 'newProp', 123)
    或使用es6语法state.obj = { ...state.obj, newProp: 123 }
  6. 推荐使用常量,作为mutation的name。在声明handler时可以用es6语法
mutations: {
    // we can use the ES2015 computed property name feature
    // to use a constant as the function name
    [SOME_MUTATION] (state) {
      // mutate state
    }
  1. mutation handler中的内容必须是同步的,因为异步函数无法确保函数何时调用,导致追踪状态困难。
  2. 业务中有异步的操作,应该写到action中,这也是为什么需要在把修改state的操作分成两个action和mutation。

actions

  1. action也是函数,接收store作为第一个参数,使用es6的argument destructuring,可以写成这样:
function incrementBy ({ dispatch }, amount) {
  dispatch('INCREMENT', amount)
}
  1. action的作用仅仅是dispatch mutation,不能返回值或者给action传递回调

  2. 在组件中使用时,可以在声明组件配置项中指定action,这样可以作为vm实例的方法,也可以应用在模板中。并且这样
    不用显式传递state对象。

    import { incrementBy } from './actions'
    
    const vm = new Vue({
      vuex: {
        getters: { ... },
        actions: {
          plus: incrementBy // bind using a different name
        }
      }
    })
    //可以应用为 vm.plus
    
    //bind all
    import * as actions from './actions'
    
    const vm = new Vue({
      vuex: {
        getters: { ... },
        actions // bind all actions
      }
    })

html post方式提交数据编码问题

最近项目中遇到了这方面的问题,算是比较基础的知识吧。
前端向服务端发起请求,服务端返回一长串数据,数据是类似a=12&b=34c=56这样的(当然真实的数据远远比这个长且复杂),前端需要分隔出来每个数据然后再组成表单,post到另一个server上。

之前理解的post表单其实是把每个表单的name和value用=&符号连接成字符串发送到server,那是否可以偷懒地传递,即name为a,然后值为12&b=34&c=56...这样的形式联结起来呢?

结果当然是不可以的。

构造一个html,内容如下

<html>
    <head>
        <meta charset="utf-8">
    </head>
    <body>
        <form action="http://baidu.com" method="POST">
            <input type="text" name="a">
            <input type="text" name="b">
            <input type="submit" value="submit">
        </form>

         <form action="http://baidu.com" method="POST">
            <input type="text" name="a">
            <input type="submit" value="submit">
        </form>
    </body>

</html>

通过抓包分析,第一个表单中分别填入12,34,http的body为a=12&b=34,若把=12&b=34填入第二个表单,则http的body为a=12%26b%3D34,可知&被转义为 %26,=被转义为 %3D。这种编码方式被成为 百分号编码,或url编码,用于编码url和表单数据。其实从表单的默认enctype=application/x-www-form-urlencoded 也可以看出用的是这种编码。虽然以前知道表单的enctype,但是看来还是需要融会贯通啊

年会祝福+抽奖程序前端设计

贵司2016年年会抽奖的任务分派给我了,需求如下:

  • 现场会有人从内部聊天工具发送祝福语,服务端提供http接口,前端取得数据进行展示,类似人人墙的效果。
  • 给定参会人员抽奖名单,从中抽取一等奖,二等奖,三等奖等奖项,并展示效果。

需求看起来简单,但是经不住需求人员反反复复变更的需求啊,直到上线前一天,还否定了之前的设计,重新找人设计了图,更不用说抽奖规则之类的了。。。
接到需求之后,我认真研究了一下,具体需要注意以下几点:

  1. 时效性短,应用范围窄。仅需一台电脑打开网页,展示页面,然后投放到大屏幕上,不用考虑浏览器兼容性问题;
  2. 需要考虑安全性问题。后台的接口是不做任何过滤的,如果有人恶意地注入js脚本,比如弹个框什么的,阻塞页面展示,直接GG。
  3. 抽奖工作在浏览器端做,应该在页面加载的时候就一次性做好,而不应等到点击按钮时再做,否则会出现卡顿的情况。
  4. 对抽奖人员名单要做好去重处理,已经中过一次奖的人不能再次中奖。

我列出的技术方案如下:
1. 用Vue.js做前端MVVM框架,展示页面
2. 用lodash做列表筛选,随机选择指定的人数,作为中奖人员,且进行去重处理。
3. 用xss.js做xss过滤
4. 抽奖页面和显示中奖结果页面是两个页面,抽奖的计算是在抽奖页面,在这个页面计算后,存入localStorage里面,中奖结果页面从中取出结果并显示。
5. 祝福页面,需要定期清理已经展示过的祝福语,减少浏览器性能损耗。不过用了Vue之后,将要显示的数据和Model层双向绑定,清除过期数据只需更改代表数据的数组即可。

具体实施过程以及部分注意事项:

  1. 表情包解析:
  • 所发祝福里面可以含有表情,类似“/:snbye”,这种形式,对应一个文件名,需要解析出文件名,拼接出地址,请求出图片。有个表情和图片名称对应的关系,是xml格式的。前台js不方便解析xml,我首先用python将这种对应关系转成json形式的。主要用了BeautifulSoup解析xml,再写入json文件。
  • 页面加载时,在初始化Vue对象时在生命周期的compile阶段将这些json文件的内容请求并缓存为改model的emotion_list对象。
  • 对于每一条信息,遍历emotion_list,截取出表情,转成文件名,并拼接出img标签。这一步写成了filter形式,可以直接在模板中调用。
  • 需要注意的是虽然Vue默认会过滤html标签,但因为img标签是动态生成的,必须动态插入,会留下安全隐患。因此,这一步可以借助xss.js的白名单机制,只允许img标签的src属性,生成过滤规则,应用在动态生成的img上。
  • 获取祝福语并展示
  • 因为页面上只展示3,4条数据,展示过的数据需要删除,而一次请求20条数据。我设置了两个数组,people数组一个与页面上的模板绑定,用于显示数据,另一个是tmp数据,作为取得数据的缓冲。
  • 当tmp内有数据时,将tmp的数据插入到people头部,但当people的长度大于4时,则将后面的people[4]后的所有数据清除。 当tmp的length小于40时,向服务器获得新数据
  • 以上策略需要两秒钟展示一次新数据,所以需要循环2s执行。但setInterval长时间执行后,间隔就会越来越失真,所以我利用setTimeout改写如下:
        function interval(lastId){

        var flag = setTimeout(function(){
            clearTimeout(flag);
            //长度大于40 直接展示页面
            if(vm.tmp.length>40){
                callback.call(vm,2);
            }
            //请求数据 展示页面
            else{
                request(lastId,callback,function(err){console.log(err);}) 
            }
        },2000)
        }

然后在callback里再次调用interval函数,以实现间隔调用

  • 为防止系统出现错误刷新后又从第一条开始显示,我将每次请求的lastId保存在localStorage里,以保证下次请求从这个id开始。
  • callback里做的事情主要是判断tmp长度,大于40直接展示,或者不足40时,根据请求到的数据,往tmp里添加
  • 在实践过程中,我首先用了Vue的vue-resource库发送jsonp请求,但在请求次数多了以后,vue-resource虽然能发送成功数据,但是无法调用回调,一直处于pending状态,可能是实现上有bug。不得已换用jquery的ajax,没有出过问题
  • 动画效果
    动画效果前期折磨了我好长时间。如果直接用jquery操作DOM的话,那么写动画效果很简单的。但是现在模型层和模板绑定,people的第一个元素就是模板li的
    第一个子元素,而且由上可知,新数据是由tmp插入people的第一个元素,然后原本是第一个的数据现在变成第二个,以此类推。而再向其应用动画效果,很容易
    导致错乱。经过多次尝试,我利用了Vue的过渡系统,在过渡开始的时候找到元素,及其兄弟元素,应用jquery在1s内改变其top值。刚好能达到效果。但这次算是
    误打误撞地成功了,因为改变时间间隔,又会导致动画错位。若因为动画的原因不能成功,那么只能放弃使用Vue,而是老老实实直接操作DOM
  • 抽奖
    抽奖完成的操作主要是筛选,去重等。lodash封装了各种对象,数组操作的方法,很容易就实现了所需的功能。如_.sampleSize,_.differenceWith等。

总结:
这个项目是我将Vue应用于实战的第一个项目,并没有用到高大上的组件等功能。从这次项目中我体会到了只看文档是永远停留在纸上谈兵的阶段,唯有真刀真枪应用于实战,才能提高自己的水平。
同时,没有哪种技术是万能的,要根据自己的需求选择合适的技术。这就需要涉猎各种技术的同时,能够取其精华,分辨其应用场景。
ps: 因为做了这个,给了个正激励200,啊哈哈。。。

关于crawl wechat article

好像现在使用这篇文章介绍的方法已经不能获取文章的json了
我自己试过好像不行了,有谁能行的可以告知否

关于微信公众号抓取疑问

你好:

我正在做微信公众号抓取,但是抓取过程中那个key一直获取不到,看到你贴的文章,其中有写不明白的地方 “继续寻找,这个span是a标签的子元素,看这个a标签的href,” 这段话下面的网页是怎么获取到的?

希望能给解答一下。如果方便希望加下联系方式,QQ1281272115

npm 最佳实践

npm最佳实践


在Node.js大规模应用系列中,我们写了一系列的文章,着重关注大规模应用Node的公司以及已经了解基本Node的开发者的需求。

第一章是你将学习到的是npm的最佳实践以及一些技巧,这些技巧可以帮助你在日常开发中节省很大一部分时间。

npm install是最常用的命令。但是npm提供了更多的命令。这篇文章你会了解到npm在你整个应用开发周期中是怎么帮助你的,从新建一个项目到开发再到部署。

0 了解你的npm

在深入了解主题之前,让我们了解一些你正在使用的npm的版本信息和可用的命令。

npm 版本

运行npm --version获取npm版本。运行npm verion可以得到更多的信息,包括当前包的版本,node.js的版本和openssl和v8的版本

$ npm version
{ bleak: '1.0.4',
  npm: '2.15.0',
  ares: '1.10.1-DEV',
  http_parser: '2.5.2',
  icu: '56.1',
  modules: '46',
  node: '4.4.2',
  openssl: '1.0.2g',
  uv: '1.8.0',
  v8: '4.5.103.35',
  zlib: '1.2.8' }
npm help

像多数命令行工具一样,npm内建了一个help功能。经常能得到细节描述和摘要信息。这些基本上是man-pages的信息。

$ npm help test
NAME  
       npm-test - Test a package

SYNOPSIS  
           npm test [-- <args>]

           aliases: t, tst

DESCRIPTION  
       This runs a package's "test" script, if one was provided.

       To run tests as a condition of installation, set the npat config to true.

用npm init 初始化新项目

当初始化新项目时,npm init可以帮助你交互式地创建package.json,它会提问一些例如项目名称或描述的问题。但是,通过npm init --yes可以更快地创建。
如果你使用了它,命令行不会提示任何信息,仅仅是创建了一个package.json,填充一些默认的信息。你可以使用以下命令设置默认信息:

npm config set init.author.name YOUR_NAME  
npm config set init.author.email YOUR_EMAIL  

2 寻找npm包

找到合适的npm包是具有挑战性的。成百上千的包供你挑选。举例来说,让我们选一个包来帮助我们发送http请求。
npms.io是个很好的网站。它展示了诸如质量,流行度和维护性等指标。这些是通过模块是否有过期的依赖,是否有linter配置,是否有测试或最近提交的时间等指标衡量的。

3 调查npm包

一旦我们选择了模块(这个例子是request),我们需要看看文档,打开的issues等来了解我们究竟要在项目中引入什么。不要忘了你用越多的包,你就会越有漏洞或恶意代码的风险。如果你想要读更多的关于
npm安全相关的风险,可以阅读我们相关的指南。
如果你想打开模块的主页,你可以输入

$ npm home request

输入开启的issues或公开的路线图,你可以输入$ npm bugs request,或者你可以打开它的git仓库地址npm repo request

4 保存依赖

一旦你找到需要的包,你需要安装并保存。常用命令是npm install request,如果你想自动保存到package.json,你可以npm install request --save,npm会默认用^保存依赖。这意味着下一次运行npm install时,主版本相同的最新包会被安装。
要改变默认行为,你可以输入npm config set save-prefix='~',如果你需要确定的版本,你可以输入$ npm config set save-exact true

5 锁定依赖

即使你用特定版本安装你的依赖,你需要注意多数npm模块的作者不会这样做。这是很正常的,他们这样做是为了自动得到补丁和新的功能。

但这样的情况会在生产部署的时候产生问题:可能会使生产环境的版本和本地的不一致。如果间接依赖的包有更新,且新版本的依赖有了bug,就会产生问题,影响到生产系统。

为了解决这个问题,你可以用npm shrinkwrap。它会产生npm-shrinkwrap.json,它不仅包含直接依赖的确切版本,也包含间接依赖的确切版本。一旦你有了这个文件,npm install就会用它来生成依赖树。

6 检查过期的依赖

可以用内建的npm outdated检测过期的依赖。在你项目的文件夹运行此命令

Package        Current  Wanted  Latest  Location
ava             0.16.0  0.16.0  0.17.0  duimenger
gulp-imagemin    2.4.0   2.4.0   3.1.1  duimenger
gulp-jsdoc3      0.3.0   0.3.0   1.0.1  duimenger
gulp-uglify      1.5.4   1.5.4   2.0.0  duimenger
helmet           2.3.0   2.3.0   3.1.0  duimenger
lodash          4.17.1  4.17.2  4.17.2  duimenger
log4js          0.6.38  0.6.38   1.0.1  duimenger
request         2.78.0  2.79.0  2.79.0  duimenger
serve-favicon    2.3.0   2.3.2   2.3.2  duimenger

一旦你管理更多的项目,保持你项目的所有依赖都是最新的是个巨大的工作。可以使用Greenkeeper,一旦依赖过期它会自动地发送pr到你的仓库。

7 生产环境不要有devDepenendencies

被称为开发依赖(devDepenendencies )是有原因的。你不需要在生产环境引用它们。它会使部署占的空间更小更安全,因为生产环境模块越少产生安全问题的模块就越少。用npm install --production NODE_ENV=production npm install只安装生产依赖

8 保证你项目和口令的安全

如果你登录了npm,你的npm token会存在.npmrc文件中。随着多数开发者在github上存.开头的文件,这些token会被公开。在github上可以搜到很多这样的文件,其中很多都包含token。如果仓库中包含.npmrc,请检查是否包含口令!

另一类可能的安全问题是不经意地发布一些文件到npm.一般情况下npm遵循.gitignore文件的规则,满足这些规则的不会发布。但是如果你添加了.npmignore文件,它会复写.gitignore的内容,它们两者不会合并。

9 开发包

当在本地开发时,你经常会试着在你的项目中引用你开发的包,这些开发的包还没有发布到npm,这时 npm link可以起作用。

这个命令创建了一个执行这个命令的文件夹到全局文件夹的符号链接。
你从另一个地方可以执行npm link package-name,创建一个从全局安装的package-name包到当前目录下的node_modules文件夹的链接。

# create a symlink to the global folder
/projects/request $ npm link

# link request to the current node_modules
/projects/my-server $ npm link request

# after running this project, the require('request') 
# will include the module from projects/request

node 可写流

Writable Streams

Writable streams are an abstraction for a destination to which data is written.

可写流是对数据被写入(的地方)的抽象

Examples of Writable streams include:

  • HTTP requests, on the client
  • HTTP responses, on the server
  • fs write streams
  • zlib streams
  • crypto streams
  • TCP sockets
  • child process stdin
  • process.stdout, process.stderr

可写流的实例有如上几个。上面列出的可写流有些是Duplex流,实现了Writable接口。

All Writable streams implement the interface defined by the stream.Writable class.

所有的可写流都实现了 stream.Writable 类定义的接口

While specific instances of Writable streams may differ in various ways, all Writable streams follow the same fundamental usage pattern as illustrated in the example below:

尽管具体的可写流实例千差万别,所有的实例都遵循如下所示的相同的基础使用模式:

const myStream = getWritableStreamSomehow(); 
myStream.write('some data'); 
myStream.write('some more data'); 
myStream.end('done writing data'); 

Class: stream.Writable

close事件

The 'close' event is emitted when the stream and any of its underlying resources (a file descriptor, for example) have been closed. The event indicates that no more events will be emitted, and no further computation will occur.

当流和流底层的任意资源(如文件描述符)关闭时,close事件会被触发。close事件意味着不会有其他的事件被触发,也不会有其他的计算发生了。

Not all Writable streams will emit the 'close' event.

不是所有的可写流都触发close事件

drain事件

If a call to stream.write(chunk) returns false, the 'drain' event will be emitted when it is appropriate to resume writing data to the stream.

如果调用stream.write(chunk)返回假,当可以向流写数据的时候,drain事件就会被触发

// Write the data to the supplied writable stream one million times. 
// Be attentive to back-pressure. 
function writeOneMillionTimes(writer, data, encoding, callback) { 
let i = 1000000; 
write(); 
function write() { 
var ok = true; 
do { 
i--; 
if (i === 0) { 
// last time! 
writer.write(data, encoding, callback); 
} else { 
// see if we should continue, or wait 
// don't pass the callback, because we're not done yet. 
ok = writer.write(data, encoding); 
} 
} while (i > 0 && ok); 
if (i > 0) { 
// had to stop early! 
// write some more once it drains 
writer.once('drain', write); 
} 
} 
} 
error事件

The 'error' event is emitted if an error occurred while writing or piping data. The listener callback is passed a single Error argument when called.

error事件会在写数据或导流数据出错时触发。回调函数在调用的时候会接收一个Error参数

Note: The stream is not closed when the 'error' event is emitted.

注意 error触发的时候流并没有关闭

finish事件

finish事件在stream.end()调用后触发,所有数据都被冲到底层的系统中 (即流的缓冲区中不再有数据)。

const writer = getWritableStreamSomehow(); 
for (var i = 0; i < 100; i ++) { 
writer.write('hello, #${i}!\n'); 
} 
writer.end('This is the end\n'); 
writer.on('finish', () => { 
console.error('All writes are now complete.'); 
}); 
pipe 事件

The 'pipe' event is emitted when the stream.pipe() method is called on a readable stream, adding this writable to its set of destinations.

pipe 事件在一个可读流调用pipe方法的时候被触发,把这个可写流作为可读流数据的目的地。

  • src <stream.Readable> source stream that is piping to this writable

回调函数中的src参数指的是导流的那个可读流。

const writer = getWritableStreamSomehow(); 
const reader = getReadableStreamSomehow(); 
writer.on('pipe', (src) => { 
console.error('something is piping into the writer'); 
assert.equal(src, reader); 
}); 
reader.pipe(writer); 
unpipe 事件

The 'unpipe' event is emitted when the stream.unpipe() method is called on a Readable stream, removing this Writable from its set of destinations.

unpipe 事件在一个可读流调用unpipe方法的时候被触发,将这个流从可读流的目标移除。

const writer = getWritableStreamSomehow(); 
const reader = getReadableStreamSomehow(); 
writer.on('unpipe', (src) => { 
console.error('Something has stopped piping into the writer.'); 
assert.equal(src, reader); 
}); 
reader.pipe(writer); 
reader.unpipe(writer); 
writable.cork()

The writable.cork() method forces all written data to be buffered in memory. The buffered data will be flushed when either the stream.uncork() or stream.end() methods are called.

writable.cork() 强制所有写入的数据都存在内存的缓冲区中。缓存的数据会在调用 stream.uncork() 或 stream.end() 调用的时候写入底层并清除缓冲区。

The primary intent of writable.cork() is to avoid a situation where writing many small chunks of data to a stream do not cause a backup in the internal buffer that would have an adverse impact on performance. In such situations, implementations that implement the writable._writev() method can perform buffered writes in a more optimized manner.

writable.cork()的最初目的是避免这样一种情景:写入许多小块数据到流中不会在流的内部缓冲区中备份,这会影响性能。在这种情况下,实现了writable._writev()方法的可以以一种更优化的方式执行写入缓存操作。

writable.end([chunk][, encoding][, callback])
  • chunk <String> | <Buffer> | <any> Optional data to write. For streams not operating in object mode, chunk must be a string or a Buffer. For object mode streams, chunk may be any JavaScript value other than null.
  • encoding <String> The encoding, if chunk is a String
  • callback <Function> Optional callback for when the stream is finished

chunk 在非对象模式时必须是是Buffer或String类型的。在对象模式下可以是任意类型。
encoding 在chunk是String类型时可以指定编码格式
callback 流结束的时候调用的回调函数

Calling the writable.end() method signals that no more data will be written to the Writable. The optional chunk and encoding arguments allow one final additional chunk of data to be written immediately before closing the stream. If provided, the optional callback function is attached as a listener for the 'finish' event.

调用end()方法意味着不会再有更多的数据写入流对象了。chucnk和encoding参数允许在流关闭前写入最后一部分数据。如果提供了callback参数,则将它当做是finish事件的回调。

Calling the stream.write() method after calling stream.end() will raise an error.

在end()之后再调用stream.write()方法会报错。

writable.setDefaultEncoding(encoding)

The writable.setDefaultEncoding() method sets the default encoding for a Writable stream.

这个方法设置了默认的编码方式.函数返回的是stream本身。

writable.uncork()

The writable.uncork() method flushes all data buffered since stream.cork() was called.

writable.uncork() 将自从writable.cork()调用后缓存的所有数据清洗掉(指清空缓冲区,写入底层)

When using writable.cork() and writable.uncork() to manage the buffering of writes to a stream, it is recommended that calls to writable.uncork() be deferred using process.nextTick(). Doing so allows batching of all writable.write() calls that occur within a given Node.js event loop phase.

当用cork和uncork来管理写入到流的缓冲数据时,推荐在process.nextTick中uncork。这样做可以在一次事件循环中批处理stream.write()的调用。

stream.cork(); 
stream.write('some '); 
stream.write('data '); 
process.nextTick(() => stream.uncork()); 

If the writable.cork() method is called multiple times on a stream, the same number of calls to writable.uncork() must be called to flush the buffered data.

uncork 和cork调用的次数应该相等

stream.cork(); 
stream.write('some '); 
stream.cork(); 
stream.write('data '); 
process.nextTick(() => { 
stream.uncork(); 
// The data will not be flushed until uncork() is called a second time. 
stream.uncork(); 
}); 
writable.write(chunk[, encoding][, callback])
  • callback <Function> Callback for when this chunk of data is flushed
  • Returns: <Boolean> false if the stream wishes for the calling code to wait for the 'drain' event to be emitted before continuing to write additional data; otherwise true.

callback指定的函数在数据被冲洗(写入底层)的时候调用。write函数返回布尔值,如果stream希望在drain事件被触发后再写入更多数据,则返回false,否则返回true。

私货:如上所示,callback在写入底层后才会调用。如下所示 在5秒以后write的callback才调用。

var stream = require('stream'), 
fs = require('fs'); 

writer = fs.createWriteStream('corkwrite.txt') 

writer.cork() 

writer.write("fuckyou",function(){ 
console.error('write callback') 
}) 

setTimeout(function(){ 
console.log('uncork called') 
writer.uncork(); 
},5000) 

The writable.write() method writes some data to the stream, and calls the supplied callback once the data has been fully handled. If an error occurs, the callback may or may not be called with the error as its first argument. To reliably detect write errors, add a listener for the 'error' event.

write()函数写数据到stream,一旦数据被处理后,调用回调。如果错误发生,回调函数的第一个参数不一定是error对象。更可靠的方法是监听error事件。

The return value is true if the internal buffer is less than the highWaterMark configured when the stream was created after admitting chunk. If false is returned, further attempts to write data to the stream should stop until the 'drain' event is emitted.

在接收过数据后,如果内部的缓冲区小于 初始化时设定的highWaterMark返回true。如果返回了false,之后尝试写数据的操作应该在drain事件被触发后再执行。

While a stream is not draining, calls to write() will buffer chunk, and return false. Once all currently buffered chunks are drained (accepted for delivery by the operating system), the 'drain' event will be emitted. It is recommended that once write() returns false, no more chunks be written until the 'drain' event is emitted. While calling write() on a stream that is not draining is allowed, Node.js will buffer all written chunks until maximum memory usage occurs, at which point it will abort unconditionally. Even before it aborts, high memory usage will cause poor garbage collector performance and high RSS (which is not typically released back to the system, even after the memory is no longer required). Since TCP sockets may never drain if the remote peer does not read the data, writing a socket that is not draining may lead to a remotely exploitable vulnerability.

当流没有耗尽时,调用write()会缓存数据,然后返回false。一旦所有缓存的部分都被底层接受,drain事件会被触发。推荐在write返回false的时候,不再像流中写数据,知道drain被触发为止。尽管在流未耗尽的时候调用write()是允许的,Node会缓存所有的数据,直到超过内存的最大值。超过内存最大值的时候,会无条件地终止程序运行。即使没有终止运行,太高的内存占用会导致垃圾回收的性能降低以及高的 RSS(即使在内存不使用的情况下依然不贵返回给系统)。因为如果远程连接的一方不读取数据,TCP socket可能永远不会耗尽,所以向一个没有耗尽的socket写数据会导致远程漏洞利用。

Writing data while the stream is not draining is particularly problematic for a Transform, because the Transform streams are paused by default until they are piped or an 'data' or 'readable' event handler is added.

对于Transform流,如果在流没有耗尽的情况下写数据会产生问题。因为Transform流默认会暂停,直到它们调用了pipe(),或者readable,data事件处理程雪被添加了。

If the data to be written can be generated or fetched on demand, it is recommended to encapsulate the logic into a Readable and use stream.pipe(). However, if calling write() is preferred, it is possible to respect backpressure and avoid memory issues using the the 'drain' event:

如果要写入的数据可以按需产生或获取,推荐把数据封装成可读流,并用stream.pipe().但如果更想用可写流的write()方法的话,尽可能遵循背压(backpressure)(的原则),并用drain事件避免内存占用过高的问题。

function write (data, cb) { 
if (!stream.write(data)) { 
stream.once('drain', cb) 
} else { 
process.nextTick(cb) 
} 
} 

// Wait for cb to be called before doing any other write. 
write('hello', () => { 
console.log('write completed, do more writes now') 
}) 

A Writable stream in object mode will always ignore the encoding argument.

在对象模式下,可写流会忽略encoding参数

vuex基础

vuex基础,教程和解释


注: 本文写成后,Vuex的api发生了巨大的改变,但是基本的理念并没有改变。本文阐述了vuex为何重要,如何工作的,以及它怎样使开发维护更容易

Vuex是Vue.js的作者写的一个原型库,旨在帮助开发者用更可维护的方式开发大型web应用。它遵循与Flux,Redux相似的原则。

与其直接介绍如何使用vuex,不如先解释一下它优于其他方式的基本原理以及它怎样帮助你

我们在做什么?

一个简单的app,仅有一个按钮和一个计数器。按下按钮后,计数器叠加。尽管很简单,但目的是理解底层的概念。

这个app有两个组件:

  1. 按钮(事件源)
  2. 计数器(须基于事件更新数值)

这两个组件彼此不能感知到对方,并且不能相互通信。web应用里通常是这个模式,即使是很小的app。在大型应用中,成百上千的组件相互通信,
并且需要不断检查其他组件。例如一个todo list的应用:

本文的目的

探索四种方式解决相同的问题:

  1. 利用组件间的事件传播
  2. 利用共享的状态对象
  3. 利用vuex

读完本文后,希望可以理解:

  1. 在项目中利用vuex的工作流
  2. vuex 解决了什么问题
  3. vuex为何比其他方法好,尽管它显得更啰嗦和严格

建立起点

创建项目

$ npm install -g vue-cli
$ vue init webpack vuex-tutorial
$ cd vuex-tutorial
$ npm install
$ npm install --save vuex
$ npm run dev

首先,创建IncrementButton组件

<template>
  <button @click.prevent="activate">+1</button>
</template>

<script>
export default {
  methods: {
    activate () {
      console.log('+1 Pressed')
    }
  }
}
</script>

<style>
</style>

CounterDisplay组件://filename:src/components/CounterDisplay.vue

<template>
  Count is {{ count }}
</template>

<script>
export default {
  data () {
    return {
      count: 0
    }
  }
}
</script>
<style>
</style>

App.vue:

<template>
  <div id="app">
    <h3>Increment:</h3>
    <increment></increment>
    <h3>Counter:</h3>
    <counter></counter>
  </div>
</template>

<script>
import Counter from './components/CounterDisplay.vue'
import Increment from './components/IncrementButton.vue'
export default {
  components: {
    Counter,
    Increment
  }
}
</script>

<style>
</style>

app的原型已经基本出来了,点击按钮只会在控制台打印。
方法1:事件广播
修改 IncrementButton.vue,

export default {
  methods: {
    activate () {
      // Send an event upwards to be picked up by App
      this.$dispatch('button-pressed')
    }
  }
}

app.vue中接受“button-pressed”事件,并向CounterDisplay广播:

export default {
  components: {
    Counter,
    Increment
  },
  events: {
    'button-pressed': function () {
      // Send a message to all children
      this.$broadcast('increment')
    }
  }
}

CounterDisplay中,处理来自父元素的事件:

export default {
  data () {
    return {
      count: 0
    }
  },
  events: {
    increment () {
      this.count ++
    }
  }
}

此方法的缺点:

这种写法在技术上虽然没有错误,你也可以把你所有的逻辑都写在一个文件中。但是这并不容易后期维护:

  1. 对每一个动作,都需要链接到父元素,并且分发事件到相应的组件
  2. 当应用规模逐渐扩大后,很难理解每个事件的来源究竟是哪个组件
  3. 没有清晰地方来存放的业务逻辑.this.count++CounterDisplay中,但是业务逻辑可能在任何地方,这使得围护变得困难。

让我来举个例子来说明这种方法可能引起的bug:

  1. 你招聘了两个实习生,A,B。你让A写了另一个计数器组件。让B写了一个归零按钮。
  2. A写了一个FormattedCounterDisplay组件,这个组件订阅了increment事件,A愉快地提交了代码。
  3. B写了归零组件,向app发射reset事件,app接到事件后重新分发它。他在CounterDisplay实现了响应的代码(使计数器归零),但B不知道A的组件也应订阅了归零事件。
  4. 你按+1 这没问题,但是按归零后,只有原来的计数器组件归零了。

这是个很简单的例子,但是却表明了状态和业务逻辑的分散性,基于事件分发的方法可能会导致错误。

解法2: 共享状态

重新开始。新建一个store.js

export default {
  state: {
    counter: 0
  }
}

首先修改CounterDisplay.vue:

<template>
  Count is {{ sharedState.counter }}
</template>

<script>
import store from '../store'

export default {
  data () {
    return {
      sharedState: store.state
    }
  }
}
</script>

我们做了几点有趣的事:

  1. 获得的store对象是个常量对象,定义在其他文件中
  2. 自己组件的data属性指向store.state
  3. 因为data是组件的一部分,vue使它成为响应式的,即store.state发生改变后,vue会自动更新shredState

修改IncrementButton.vue

import store from '../store'

export default {
  data () {
    return {
      sharedState: store.state
    }
  },
  methods: {
    activate () {
      this.sharedState.counter += 1
    }
  }
}

这里,导入了store,并且订阅了响应式的state,就像上面的一样。当activate函数调用时,sharedState更改导致store.state更改,使计数器加一。所有订阅了counter的组件和计算属性会更新。

比方法一好在哪里?

仍考虑2个实习生的问题。

  1. A写了FormattedComponentDisplay组件,订阅了共享的状态,计数器会一直显示最新的值。
  2. B的重置组件设置共享状态计数器到0.会影响CounterDisplay和A写的FormattedComponentDisplay
  3. reset 会像人们期望的一样工作

这种方法的不足之处

  1. 在他们实习期间,AB会写各式各样的计数器展示组件和重置组件,以及递增组件。它们都修改同一个共享对象,很不错
  2. 一旦他们离开,你需要维护
  3. 新来的产品狗C说,不想要计数器超过100

你会怎么做?

  1. 你会去这些组件中找到更新值的地方,很无聊
  2. 你会去找展示这些值的地方,增加一个过滤器或格式化器,同样很无聊

问题就在这,业务逻辑分散在应用的各个地方,原则上是很简单的事情,但是很难维护和调试。

稍微好一点的方法

store.js

var store = {
  state: {
    counter: 0
  },
  increment: function () {
    if (store.state.counter < 100) {
      store.state.counter += 1;
    }
  },
  reset: function () {
    store.state.counter = 0;
  }
}

export default store

这使得代码更干净了,因为你显式调用了increment方法,你所有的业务逻辑都在store里。但是,新实习生不知道这背后的道理,并且发现写store.state.counter更简单,于是会在项目的其他地方修改,于是代码变得更难维护了。

于是你制定了更严格的规定,指南,代码审查来保证没人会在store.js外用其他方式修改state,一旦有人违反,则重罚。

方法3:vuex

原则上 vuex有点像在方法2中做的事情。下面是比较繁杂的原理图

首先创建store.js

import Vuex from 'vuex'
import Vue from 'vue'

Vue.use(Vuex)

var store = new Vuex.Store({
  state: {
    counter: 0
  },
  mutations: {
    INCREMENT (state) {
      state.counter ++
    }
  }
})

export default store

这段代码做了什么?

  1. 导入vuex,并使其作为vue的插件
  2. store不再是一个简单的对象,而是Vuex.Stored 实例
  3. 设置state的counter为0
  4. 设置了Mutations对象,拥有INCREMENT方法,接受一个state参数,并改变该state

这段代码做了以下有趣的事:
1.所有 require或import store from './store.js'都会应用同一个store实例,
2. 我们从来不会修改store.state.counter,但我们得到state的一个备份,在这个备份中修改和更新state,这很重要

IncrementButton.vue 这样写

import store from '../store'

export default {
  methods: {
    activate () {
      store.dispatch('INCREMENT')
    }
  }
}

这个组件没有data,但是点击时直接调用 store.dispatch('INCREMENT'),稍后再说
CounterDisplay.vue文件如下

<template>
  Count is {{ counter }}
</template>

<script>
import store from '../store'

export default {
  computed: {
    counter () {
      return store.state.counter
    }
  }
}
</script>

令人兴奋的是,我们不再订阅任何共享对象了,相反,我们应用vue的计算属性来获取store里的counter值。
Vue会智能地识别出计算属性counter依赖于store.state.counter,所以当store更新后,会自动更新相关的组件,就这么简单。

下面是顺序发生的事情:

  1. vue的事件处理器调用active方法,这个函数调用了store.dispatch('INCREMENT')
  2. INCREMENT是个action的id,代表了改变state的类型,可以向dispatch函数传递额外的参数。
  3. vue会找出对应的mutator,来响应这个dispatch,这里仅仅有一个,当然可以有多个。
  4. mutator接受state对象的备份,vue会保存旧的state备份,可以用作更高级的用法
  5. 当state更新后,vue自动更新所有依赖次state的组件。
  6. 这使得代码更加可测试

比方法2更好地地方

  1. 若开发过程中所有的state都被保存,开发者可以做出“时间旅行调试器”,除了听起来像个超级英雄,它可以让你插销动作,改变逻辑,开发更快。
  2. 你可以制作中间件,记录所有state的改变。如,制作一个记录器,记录用户所有的动作,如果找到一个bug,你可以找到那条日志,重放所有的动作,有效地重现bug
  3. 强制所有的action写在用一个地方,团队里的所有成员就有了一个很好的参考,就可以用所有的改变state的方法了

还有很长的路要走

本文只是介绍了vuex能力的冰山一角。vuex自身仍然在开发,但我确定若干年后它会逐渐成熟,成为开发规范

题外话: 处理实习生的代码

你把vuex用到你的项目里,但是你的实习生仍然觉得直接修改store.state.counter更方便。你可以在你的store.js里加上一行,

var store = new Vuex.Store({
  state: {
    counter: 0
  },
  mutations: {
    INCREMENT (state) {
      state.counter ++
    }
  },
  strict: true // Vuex's patent pending anti-intern device
})

这样直接修改store.state便不会生效。注意这样会使你的代码运行更慢,生产环境可以移除它。

前端常见bug

  1. 动态创建的form表单提交:动态创建form表单提交时一定要将form添加到document内部作为子元素,否则在ff和ie上并不能完成提交功能。

mongo cheatsheet

  • MongoDB stores documents in collections. similar to table
  • mongoimport --db test --collection restaurants --drop --file ~/downloads/primer-dataset.json
  • If you attempt to add documents to a collection that does not exist, MongoDB will create the collection for you
  • equality query: top filed: db.restaurants.find( { "borough": "Manhattan" } ); embeded filed: db.restaurants.find( { "address.zipcode": "10075" } )
db.restaurants.find(
   { $or: [ { "cuisine": "Italian" }, { "address.zipcode": "10075" },{'grades.grade':{'$gt':20}} ] }
)
  • update:
db.restaurants.update(
    { "name" : "Juni" },  // find the first that matches this condition
    {
      $set: { "cuisine": "American (New)" },
      $currentDate: { "lastModified": true }
    },
     //{ 
         multi: true,
         upsert:true // if no matches ,insert a new document

         }
)

//replacement
db.restaurants.update(
   { "restaurant_id" : "41704620" },
   {
     "name" : "Vella 2",
     "address" : {
              "coord" : [ -73.9557413, 40.7720266 ],
              "building" : "1480",
              "street" : "2 Avenue",
              "zipcode" : "10075"
     }
   }
)

//multi write interleave,  use $isolated operator


db.restaurants.remove()  //remove all documents
db.restaurants.drop() // remove all documents and indexes
  • 多个文档构成集合,多个集合组成数据库。
  • 格式相同的文档应放在同一个集合(对管理,查询,索引都有好处)。
  • 一个mongo实例可承载多个数据库。数据库名对应磁盘上同名文件,每个数据库有独立的权限。
  • shell 用法
show collections
show users
show profile
  • mongo 有32位整数 64位整数 64位浮点数,而shell只有64位浮点数一种形式。
  • _id的默认类型是ObjectId,12字节,每个字节是2个十六进制数,故是24位长的字符串。ObjectId是由时间戳,机器标识符,PID, 计数器组成,可以保证同一秒中不同机器不同进程产生的ID是唯一的,同一秒钟相同机器相同进程可以产生2^24个不同的ObjectId,
    _id是由客户端驱动程序生成的,节省开销,同时可以方便地知道自己生成的Id
  • 插入:db.foo.insert({'bar':'baz'}),也可以传入个数组批量加入。插入原理:数据转成bson,然后插入,避免注入攻击。
  • 删除:db.foo.remove({'bar':'baz'}),删除符合条件的,若不指定条件,将collection内容全部删除,collection保留,索引保留。
    db.drop_collection('foo'),索引被删除。
  • 更新:分为文档替换和文档。
//文档替换
//foo = {name:'foo',age:1}
var foo = db.foo.findOne({name:'foo'});
foo.age+=1
db.foo.update({name:'foo'},foo)
//文档更新
db.foo.update({'name':'foo'},{$inc:{age:1}})

常用修改器:
$set: 可以修改键和内嵌文档的键
$unset:删除键

db.update({name:'foo'},{$set:{'favorate book':'js',},$unset:{'age':1}})

Object.create(nulll) 和 {} 一样么?

Object.create(nulll){} 一样么?

在翻看qs模块的源码的时候,看到其utils.js中有:

exports.arrayToObject = function (source, options) {
    var obj = options.plainObjects ? Object.create(null) : {};
    for (var i = 0, il = source.length; i < il; ++i) {
        if (typeof source[i] !== 'undefined') {

            obj[i] = source[i];
        }
    }

    return obj;
};

看到函数体内第一行,才发现两者竟然不一样。经过google发现有人已经问过.
两者区别如下:{}指定其原型为Object.prototype会从Object.prototype继承toString之类的方法,在chrome控制台下声明a={},然后打出a.,浏览器会自动补全;而Object.create(null)则不指定原型,在控制台下输入b=Object.create(null)然后输入b. 不会自动补全。
所以 {}Object.create(Object.prototype)等价的。

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.