===
move contents to issues of this repo just because of the convenience of comments and labels!!!
本repo主要记录编程以及学习时的想法,欢迎与我交流。
record the technique and thinking when I am coding and learning
===
move contents to issues of this repo just because of the convenience of comments and labels!!!
本repo主要记录编程以及学习时的想法,欢迎与我交流。
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 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睡几分钟一抓还是立刻拒绝掉。
本文为我读《ReactJS by Example》英文书第二章的总结
<App />
还是<App></App>
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>
)
}
}
var props={
a:'123',
b:'456'
}
//传递给组件时:
ReactDOM.render(<App {...props}/>,document.getElementById('app'))
className
<table style={{backgroundColor:'#fff',}}>
<table className = 'recentChangesTable'>
data-
除外。 <div>you & me </div> // 直接写在元素里的不会转义 ,输出you & me
var str = 'you & me' // 输出 you & me
<div>{str} </div> //当做变量传进来的会转义
// 避免转义的方法:
1. 用一个数组 <div> {['first', <span>&</span>, 'second']} </div>
2. <div dangerouslySetInnerHTML={{__html: 'Mike & Shawn'}} />
半翻译,半总结
vuex是为了方便大型项目中多组件之间的状态管理而引入的插件,借鉴了Flux和Redux的架构模式。
整个app的状态都集中于store,store与简单的js对象相比有两点不同:
store的构成: state对象和mutations对象
import Vuex from 'vuex'
const state = {
count: 0
}
const mutations = {
INCREMENT (state) {
state.count++
}
}
export default new Vuex.Store({
state,
mutations
})
用户交互触发action,action dispatch 相应类型的mutaion,
mutation修改state。getter函数抽取state当中需要的部分,组件调用相应的getter展示需要的数据。
action函数接受一个store对象作为第一个参数,但是也可以传入对象,vuex会自动包装。
export const incrementCounter = function ({ dispatch, state }) {
dispatch('INCREMENT', 1)
}
store.dispatch(name,...restArgs)
调用store.dispatch({type:name,args})
,对象作为handler的第二个参数Vue.set(obj, 'newProp', 123)
state.obj = { ...state.obj, newProp: 123 }
mutations: {
// we can use the ES2015 computed property name feature
// to use a constant as the function name
[SOME_MUTATION] (state) {
// mutate state
}
argument destructuring
,可以写成这样:function incrementBy ({ dispatch }, amount) {
dispatch('INCREMENT', amount)
}
action的作用仅仅是dispatch mutation,不能返回值或者给action传递回调
在组件中使用时,可以在声明组件配置项中指定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
}
})
最近项目中遇到了这方面的问题,算是比较基础的知识吧。
前端向服务端发起请求,服务端返回一长串数据,数据是类似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年年会抽奖的任务分派给我了,需求如下:
需求看起来简单,但是经不住需求人员反反复复变更的需求啊,直到上线前一天,还否定了之前的设计,重新找人设计了图,更不用说抽奖规则之类的了。。。
接到需求之后,我认真研究了一下,具体需要注意以下几点:
我列出的技术方案如下:
1. 用Vue.js做前端MVVM框架,展示页面
2. 用lodash做列表筛选,随机选择指定的人数,作为中奖人员,且进行去重处理。
3. 用xss.js做xss过滤
4. 抽奖页面和显示中奖结果页面是两个页面,抽奖的计算是在抽奖页面,在这个页面计算后,存入localStorage里面,中奖结果页面从中取出结果并显示。
5. 祝福页面,需要定期清理已经展示过的祝福语,减少浏览器性能损耗。不过用了Vue之后,将要显示的数据和Model层双向绑定,清除过期数据只需更改代表数据的数组即可。
具体实施过程以及部分注意事项:
people[4]
后的所有数据清除。 当tmp的length小于40时,向服务器获得新数据 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函数,以实现间隔调用
总结:
这个项目是我将Vue应用于实战的第一个项目,并没有用到高大上的组件等功能。从这次项目中我体会到了只看文档是永远停留在纸上谈兵的阶段,唯有真刀真枪应用于实战,才能提高自己的水平。
同时,没有哪种技术是万能的,要根据自己的需求选择合适的技术。这就需要涉猎各种技术的同时,能够取其精华,分辨其应用场景。
ps: 因为做了这个,给了个正激励200,啊哈哈。。。
好像现在使用这篇文章介绍的方法已经不能获取文章的json了
我自己试过好像不行了,有谁能行的可以告知否
你好:
我正在做微信公众号抓取,但是抓取过程中那个key一直获取不到,看到你贴的文章,其中有写不明白的地方 “继续寻找,这个span是a标签的子元素,看这个a标签的href,” 这段话下面的网页是怎么获取到的?
希望能给解答一下。如果方便希望加下联系方式,QQ1281272115
在Node.js大规模应用系列中,我们写了一系列的文章,着重关注大规模应用Node的公司以及已经了解基本Node的开发者的需求。
第一章是你将学习到的是npm的最佳实践以及一些技巧,这些技巧可以帮助你在日常开发中节省很大一部分时间。
npm install
是最常用的命令。但是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功能。经常能得到细节描述和摘要信息。这些基本上是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
可以帮助你交互式地创建package.json,它会提问一些例如项目名称或描述的问题。但是,通过npm init --yes
可以更快地创建。
如果你使用了它,命令行不会提示任何信息,仅仅是创建了一个package.json,填充一些默认的信息。你可以使用以下命令设置默认信息:
npm config set init.author.name YOUR_NAME
npm config set init.author.email YOUR_EMAIL
找到合适的npm包是具有挑战性的。成百上千的包供你挑选。举例来说,让我们选一个包来帮助我们发送http请求。
npms.io是个很好的网站。它展示了诸如质量,流行度和维护性等指标。这些是通过模块是否有过期的依赖,是否有linter配置,是否有测试或最近提交的时间等指标衡量的。
一旦我们选择了模块(这个例子是request),我们需要看看文档,打开的issues等来了解我们究竟要在项目中引入什么。不要忘了你用越多的包,你就会越有漏洞或恶意代码的风险。如果你想要读更多的关于
npm安全相关的风险,可以阅读我们相关的指南。
如果你想打开模块的主页,你可以输入
$ npm home request
输入开启的issues或公开的路线图,你可以输入$ npm bugs request
,或者你可以打开它的git仓库地址npm repo request
一旦你找到需要的包,你需要安装并保存。常用命令是npm install request
,如果你想自动保存到package.json,你可以npm install request --save
,npm会默认用^
保存依赖。这意味着下一次运行npm install
时,主版本相同的最新包会被安装。
要改变默认行为,你可以输入npm config set save-prefix='~'
,如果你需要确定的版本,你可以输入$ npm config set save-exact true
即使你用特定版本安装你的依赖,你需要注意多数npm模块的作者不会这样做。这是很正常的,他们这样做是为了自动得到补丁和新的功能。
但这样的情况会在生产部署的时候产生问题:可能会使生产环境的版本和本地的不一致。如果间接依赖的包有更新,且新版本的依赖有了bug,就会产生问题,影响到生产系统。
为了解决这个问题,你可以用npm shrinkwrap
。它会产生npm-shrinkwrap.json
,它不仅包含直接依赖的确切版本,也包含间接依赖的确切版本。一旦你有了这个文件,npm install
就会用它来生成依赖树。
可以用内建的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到你的仓库。
被称为开发依赖(devDepenendencies )是有原因的。你不需要在生产环境引用它们。它会使部署占的空间更小更安全,因为生产环境模块越少产生安全问题的模块就越少。用npm install --production
或 NODE_ENV=production npm install
只安装生产依赖
如果你登录了npm,你的npm token会存在.npmrc文件中。随着多数开发者在github上存.开头的文件,这些token会被公开。在github上可以搜到很多这样的文件,其中很多都包含token。如果仓库中包含.npmrc,请检查是否包含口令!
另一类可能的安全问题是不经意地发布一些文件到npm.一般情况下npm遵循.gitignore文件的规则,满足这些规则的不会发布。但是如果你添加了.npmignore文件,它会复写.gitignore的内容,它们两者不会合并。
当在本地开发时,你经常会试着在你的项目中引用你开发的包,这些开发的包还没有发布到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
Writable streams are an abstraction for a destination to which data is written.
可写流是对数据被写入(的地方)的抽象
Examples of Writable streams include:
可写流的实例有如上几个。上面列出的可写流有些是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
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事件
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);
}
}
}
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事件在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.');
});
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);
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);
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()方法的可以以一种更优化的方式执行写入缓存操作。
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()方法会报错。
The writable.setDefaultEncoding() method sets the default encoding for a Writable stream.
这个方法设置了默认的编码方式.函数返回的是stream本身。
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();
});
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的api发生了巨大的改变,但是基本的理念并没有改变。本文阐述了vuex为何重要,如何工作的,以及它怎样使开发维护更容易
Vuex是Vue.js的作者写的一个原型库,旨在帮助开发者用更可维护的方式开发大型web应用。它遵循与Flux,Redux相似的原则。
与其直接介绍如何使用vuex,不如先解释一下它优于其他方式的基本原理以及它怎样帮助你
一个简单的app,仅有一个按钮和一个计数器。按下按钮后,计数器叠加。尽管很简单,但目的是理解底层的概念。
这个app有两个组件:
这两个组件彼此不能感知到对方,并且不能相互通信。web应用里通常是这个模式,即使是很小的app。在大型应用中,成百上千的组件相互通信,
并且需要不断检查其他组件。例如一个todo list的应用:
探索四种方式解决相同的问题:
读完本文后,希望可以理解:
创建项目
$ 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 ++
}
}
}
这种写法在技术上虽然没有错误,你也可以把你所有的逻辑都写在一个文件中。但是这并不容易后期维护:
this.count++
在CounterDisplay
中,但是业务逻辑可能在任何地方,这使得围护变得困难。让我来举个例子来说明这种方法可能引起的bug:
FormattedCounterDisplay
组件,这个组件订阅了increment
事件,A愉快地提交了代码。reset
事件,app接到事件后重新分发它。他在CounterDisplay
实现了响应的代码(使计数器归零),但B不知道A的组件也应订阅了归零事件。这是个很简单的例子,但是却表明了状态和业务逻辑的分散性,基于事件分发的方法可能会导致错误。
重新开始。新建一个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>
我们做了几点有趣的事:
store.state
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个实习生的问题。
FormattedComponentDisplay
组件,订阅了共享的状态,计数器会一直显示最新的值。CounterDisplay
和A写的FormattedComponentDisplay
你会怎么做?
问题就在这,业务逻辑分散在应用的各个地方,原则上是很简单的事情,但是很难维护和调试。
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,一旦有人违反,则重罚。
原则上 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
这段代码做了什么?
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更新后,会自动更新相关的组件,就这么简单。
下面是顺序发生的事情:
store.dispatch('INCREMENT')
本文只是介绍了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便不会生效。注意这样会使你的代码运行更慢,生产环境可以移除它。
我在浏览器中并不能打开历史消息记录啊,急问啊
table
mongoimport --db test --collection restaurants --drop --file ~/downloads/primer-dataset.json
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}} ] }
)
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
show collections
show users
show profile
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)
和 {}
一样么?在翻看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)
等价的。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.