前端开发工程师 @ buxuku
- 🔭 目前在成都工作
- 🌱 目前在学习算法与设计模式
- ✍️ 我喜欢在我的博客上面写一些文章 林晓东的个人博客
- 🖥 我曾是一名草根站长,因此涉足过前端,后端,数据库,服务器,推广,SEO等等
- 😄 我喜欢阅读与写作
📝 最近博客更新:
centos6.5最小化安装/AWS centos
cmake-3.1.0-rc2
mysql-5.7.4-m14.tar.gz
wget http://downloads.mysql.com/archives/get/file/mysql-5.7.4-m14.tar.gz
wget http://www.cmake.org/files/v3.1/cmake-3.1.0-rc2.tar.gz
tar -zxvf mysql-5.7.4-m14.tar.gz
tar -zxvf cmake-3.1.0-rc2.tar.gz
因为从mysql 5.5形如,需要使用cmake方便进行安装了,所以我们首先安装cmake
cd cmake-3.1.0-rc2
./bootstrap
gmake
gmake install
groupadd mysql #添加组
useradd mysql -g mysql -s /sbin/nologin #添加新用户,禁止登录shell
mkdir /usr/local/mysql #创建安装目录
mkdir /var/mysql
mkdir /var/mysql/data #创建数据目录
chown -R mysql:mysql /usr/local/mysql/
chown -R mysql:mysql /var/mysql/data #予数据存放目录权限
cd mysql-5.7.4
cmake -DCMAKE_INSTALL_PREFIX=/usr/local/mysql/ -DMYSQL_UNIX_ADDR=/tmp/mysqld.sock -DMYSQL_USER=mysql -DDEFAULT_CHARSET=utf8 -DDEFAULT_COLLATION=utf8_general_ci -DWITH_INNOBASE_STORAGE_ENGINE=1 -DWITH_ARCHIVE_STORAGE_ENGINE=1 -DWITH_BLACKHOLE_STORAGE_ENGINE=1 -DWITH_PERFSCHEMA_STORAGE_ENGINE=1 -DWITH_READLINE=1 -DWITH_DATADIR=/var/mysql/data/ -DWITH-TCP_PORT=3306 -DENABLE_DOWNLOADS=1
make && make install
chmod +x scripts/mysql_install_db
scripts/mysql_install_db --basedir=/usr/local/mysql --datadir=/var/mysql/data --user=mysql
cp support-files/my-medium.cnf /usr/local/mysql/my.cnf
修改my.cnf参数,没有则加入如下:
basedir = /usr/local/mysql #(不配置的话默认为$PREFIX_DIR)
datadir = /var/mysql/data #(不配置的话默认为$PREFIX_DIR/data)
log-error = /usr/local/mysql/mysql_error.log #(不配置的话默认为$PREFIX_DIR/data/$hostname.err)
pid-file = /usr/local/mysql/mysql.pid #(不配置的话默认为$PREFIX_DIR/data/$hostname.pid)
user = mysql
tmpdir = /tmp #(不配置的话默认为/tmp)
全局安装webpack
npm i webpack -g
mkdir webpack-step //新建一个目录并初始化它
cd webpack-step
npm init -y //加y参数全部使用默认值快速初始化
npm i [email protected] -D //项目中再安装一次webpack,webpack现在已经有2.0版本了,有些配置文件修改了,还是先安装1.0以上的版本吧
touch test.js //新建一个文件
vi test.js //编辑文件 写入console.log("hello world");并保存
webpack test.js bundle.js //运行webpack进行打包
执行到这一步,就会在项目中打包生成一个bundle.js的打包文件
这就是webpack的最简使用方法了。
touch webpack.config.js
vi webpack.config.js
输入以下内容
var webpack = require('webpack');//载入webpack模块
module.exports = {
entry :['./test.js'],//设置打包入口文件
output :{
path:__dirname,//设置打包的输出文件夹
filename:'bundle.js'//打包后的文件
}
}
这个时候再直接执行webpack
命令就可以直接打包了。
webpack有很多内置的插件及npm安装众多的插件,插件使用在webpack中的plugins配置项中,它是一个数组项,可以配置多个
var webpack = require('webpack');//载入webpack模块
module.exports = {
entry :['./test.js'],//设置打包入口文件
output :{
path:__dirname,//设置打包的输出文件夹
filename:'bundle.js'//打包后的文件
},
plugins :[
new Webpack.BannerPlugin("打包后文件的头部注释")//打包后文件的头部注释..
]
}
执行打包后就会在bundle.js文件的头部生成一条注释信息
比如我们要编译es6到es5,我们就需要用到babel这个工具,首先需要至少安装它的插件
npm i babel-core -D //babel核心库
npm i babel-loader -D //babel用于loader
npm i babel-preset-es2015 -D //babel转码规则
var webpack = require('webpack');
module.exports = {
entry :['./test.js'],
output :{
path:__dirname,
filename:'bundle.js'
},
module :{
loaders :[{
test:/\.js$/, //匹配js文件
loader:'babel-loader',//使用bable进行转码
query:{
presets:['es2015'] //转换成es5
}
}
]
},
plugins :[
new webpack.BannerPlugin("打包后文件的头部注释")//打包后文件的头部注释..
]
}
修改一下test.js文件,写入一句es6的语法
let a = "hello world!";
console.log(a);
运行webpack打包之后,打开bundle.js就会发现代码会转换成es5中的var
方法了。
这里的配置里面的'query'部分也可以提取出来,把它写入package.json中也可以。
{
"name": "webpack-step",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"babel-core": "^6.22.1",
"babel-loader": "^6.2.10",
"babel-preset-es2015": "^6.22.0",
"webpack": "^1.14.0"
},
"babel": {
"presets": [
"es2015"
],
"plugins": []
}
}
这样和新建一个.babelrc
文件效果是一致的
{
"presets": [
"es2015"
],
"plugins": []
}
接下来就采用最后一种独立文件的方式吧,来得直观一些。
以上所有的运行我们都是使用webpack来运行的,接下来我们把它配置进npm的脚本中,以方便直接使用以及添加参数等
在package.json中的scripts节点中新增脚本:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},
接下来运行npm run build
就可以执行打包文件了
比如我们在配置中添加-w
参数,就可以让webpack自动监听文件的修改并重新打包了
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack -w"
},
webpack-dev-server也升级到2.0以上了,安装2.0以上版本在package.json中不能使用脚本启动,所以还是先安装1.0以上的版本
npm i [email protected]
然后在package.json中添加启动脚本
"server":"webpack-dev-server"
运行npm run server
就会默认启动一个本地localhost:8080的服务
让浏览器实现热加载,只需要给webpack-dev-server添加--inline
参数就可以了
是时候让我们新建一个html文件并引入bundle.js文件在这个index.html了,这个时候,打开控制台,修改test.js文件,就能看到自动刷新的效果。
默认情况下,webpack-dev-server会采用webpack.config.js这个配置文件的,所以当文件修改之后,它会自动调用这个文件进行打包。
webpack-dev-server可以以指定文件夹来运行服务,比如我们新建一个build文件夹,然后把index.html放进去,并删除bundle.js文件,在启动脚本中新增参数--content-base build/
,变成如下:
"server": "webpack-dev-server --inline --content-base build/"
重新运行服务,会发现现在是从build目录启动服务了,这个时候并没有bundle.js文件,因为webpack-dev-server是把它打包在内存当中的。
TODO
我们修改之后,发现浏览器是全部刷新一次的,这个时候可以通过--hot
参数来实现热加载功能。
首先安装React包
npm i react --save
npm i react-dom --dave
安装babel插件
npm i babel-preset-react -D
npm i babel-preset-react-hmre -D
在.babelrc中添加react转码
{
"presets": [
"es2015",
"react"
],
"plugins": []
}
修改test.js文件,写一个最简单的无状态组件
import React from 'react';
import ReactDOM from 'react-dom';
function HelloComponent(props) {
return <div>Hello {props.name}</div>
}
ReactDOM.render(<HelloComponent name="12345" />, document.body)
运行一下,OK,我们的React也跑起来了。
webpack最强大的地方就是loaders,我们再来添加一个css-loader以支持在js中import css文件
npm i css-loader -D
npm i style-loader -D
安装好这两个插件之后,再修改webpack.config.js配置文件
module :{
loaders :[{
test:/\.js$/,
loader:'babel-loader'
},
{
test: /\.css$/,
loader: 'style-loader!css-loader'
}
]
},
这个时候我们新建一个app.css的文件,里面写入一句背景颜色的设置
body{
background-color: red;
}
在test.js中添加
import "./app.css";
运行一下,我们的css文件已效了。
继续修改一个index.html文件,添加一个容器和输入框,容器用来放我们的react组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="container"></div>
<input type="text">
</body>
<script src="bundle.js"></script>
</html>
稍微修改一下test.js里面的组件渲染位置
import React from 'react';
import ReactDOM from 'react-dom';
import "./app.css";
function HelloComponent(props) {
return <div>Hello {props.name}</div>
}
ReactDOM.render(<HelloComponent name="world" />, document.getElementById("container"))
运行之后,我们在输入框里面随便输入内容,然后修改一个app.css文件,发现热加载功能也生效了。
tools-->new snippet会自动打开一个新建片段的文档
<snippet>
<content><![CDATA[
Hello, ${1:this} is a ${2:snippet}.
]]></content>
<!-- Optional: Set a tabTrigger to define how to trigger the snippet -->
<!-- <tabTrigger>hello</tabTrigger> -->
<!-- Optional: Set a scope to limit where the snippet will trigger -->
<!-- <scope>source.python</scope> -->
</snippet>
content代码片段内容
tabTrigger代码的快捷键,在文档中输入该代码后,按Tab就可以输出content中的代码内容了。
scope中定义该代码在哪种类型文档中可以生效。
例如我常常要写一个jquery开源cdn库的缩写。
<snippet>
<content><![CDATA[
<script type="text/javascript" src="http://libs.useso.com/js/jquery/1.9.1/jquery.min.js"></script>
]]></content>
<!-- Optional: Set a tabTrigger to define how to trigger the snippet -->
<tabTrigger>jq</tabTrigger>
<!-- Optional: Set a scope to limit where the snippet will trigger -->
<!-- <scope>source.python</scope> -->
</snippet>
编辑完之后保存为 C:\Users[用户]\AppData\Roaming\Sublime Text 2\Packages\User\jq.sublime-snippet (Win7下) 默认的保存路径就行。后缀必须是.sublime-snippet。
重启后打开,在代码中输入jq,然后按tab就可以直接插入jquery源了。
当然,我们还可以在代码中使用${1},1代码序号,使用了之后,在输出代码的时候会自动定位的${1}位置,按tab后会跳到第二个位置,如果数字相同,则是同时选中的效果。
如果下面一个代码片段:
<snippet>
<content><![CDATA[
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>${1}</title>
</head>
<body>
<h1>${1}</h1>
Hello, ${2:this} is a ${3:snippet}.
</body>
</html>
]]></content>
<!-- Optional: Set a tabTrigger to define how to trigger the snippet -->
<tabTrigger>html5</tabTrigger>
<!-- Optional: Set a scope to limit where the snippet will trigger -->
<!-- <scope>source.python</scope> -->
</snippet>
保存完重启Sublime text 2,新建文件:输入html5,tab会出现如下效果:
${1}出现了两次,所以光标同时编辑图中两处。
${2:this},所以在2处出现this默认值。${1}处编辑完按tab就到${2}处。
在javascript的五种基本数据类型中,有两和数据类型可能会经常给我们带来一些困惑,他们就是undefined
和null
了.
我们先来看一种情况:
var a;
console.log(a);//"undefined"
console.log(b);//报错
然后有
var a;
console.log(typeof a);//"undefined"
console.log(typeof b);//"undefined"
这里我们可以得出结论就是:未定义的变量,未初始化的变量,使用typeof的时候,他们的结果都是'undefined';而对于未定义的变量,他也只能使用typeof操作符,执行其它操作都将报错.(调用delete也不会报错,但没有任何意义);
从上面也可以看出,未初始化变量的默认值就是undefined
,所以console.log(a == undefined)
将会打印出true
.
因此,良好的编程习惯应该这样:没必要显式地声明a = undefined
;而对于任何变量,我们都应该显式的初始化一个除undefined
外我们想要的值,这样当我们在应用typeof的时候,我们将可以很容易地知道undefined
结果代表的是变量未声明.
我们也是先看一种情况
var a=null;
console.log(typeof a); //object;
这里我们可以看出,null
是以一种空对象的形式保存的.所以对于任何我们即将要以对象的形式保存的变量,我们都可以使用null
来进行初始化;
有一点不好理解的就是,当conslole.log(null == undefined)
的时候,结果竟然返回的是true
,其中的原因就是undefined
是派生至null
的.那么console.log(null === undefined)
的结果肯定是false
,因为他们是两种数据类型.
因为我们也就能够理解下面这段代码了:
console.log(Boolean(null));//false
console.log(Boolean(undefined));//false
对于undefined
和null
它们将存在于以下区别
1.在使用Boolean进行转换的时候,undefined
和null
都为false
,而null
所赋于的变量,拥有任何对象后都将会是true
值.
2.在使用Number进行转换的时候,Number(null)
的值为0,而Number(undefined)
为NaN
.
3.在合作String进行转换的时候,String(null)
的结果是"null"
,而String(undefined)
的结果是"undefined"
.
我们还需要注意的一点就是,typeof
操作符返回的结果是字符串.所以console.log(typeof undefined == undefined);
的结果是false
,所以typeof undefined
返回的"undefined"
非此undefined
.
使用场景来源于曾经的一次代码合并,代码合并之后,控制台报错,而且不是显性的错误,很难从代码层面查找到问题,测试了几次都没办法定位到问题的所在位置。无奈,只好进行回退对比了,这一次合并一共有十来次提交,决定回退看看是哪一次提交产生了对比,然后做修改对比。
当然,很自然的就是采用二分法的回退,先回退到中间的那一个版本,看问题是否存在,这样每回退一次,都可以减少一半的提交记录,十来次的提交,很快就可以定位到错误的那一次提交了。
而对于强大的git,在这一次的经历中,确实是没有想到git还有git bisect
这么方便的一个命令,虽然它和git flow
工具一样,并不是什么强大的命令,但在这关键的时候,确实能减少我们的工作量,十多次的提交还好,手动回退也很方便,但如果次数很多,再自己去记录,计算中间那一次的提交,就显得不是那么方便了。
于是,顺便安利一下这个命令,当工作中再次遇到类似需要定位错误的提交时,就可以更加得心应手了。
这里我创建了一个项目 [email protected]:buxuku/git-bisect.git
,并进行了10次的提交,运行git log
可以看到10次的提交记录:
commit 9551544db6aec178b064eeaba33389ea878d5979
Author: 林晓东 <[email protected]>
Date: Tue Oct 10 15:43:21 2017 +0800
v10
commit 4c8d52d249c16908c1ce7aaef00592385cb5e0a2
Author: 林晓东 <[email protected]>
Date: Tue Oct 10 15:42:44 2017 +0800
v9
commit fe97e12f750bfd427b815fb062fddb8895e66232
Author: 林晓东 <[email protected]>
Date: Tue Oct 10 15:42:28 2017 +0800
v8
commit 734216f2df46550948e1f5ff1161d5d6b6860b10
Author: 林晓东 <[email protected]>
Date: Tue Oct 10 15:42:09 2017 +0800
v7
commit 9b715e9f8f3775242c0abd0cb5fd6f65b6d6626c
Author: 林晓东 <[email protected]>
Date: Tue Oct 10 15:41:52 2017 +0800
v6
commit ed63a6a71bb28939e498f75cbdf27fb22c01ae72
Author: 林晓东 <[email protected]>
Date: Tue Oct 10 15:40:06 2017 +0800
v5
commit c025e1de4c12cca9b245ed33844ebaa040fe6e27
Author: 林晓东 <[email protected]>
Date: Tue Oct 10 15:39:45 2017 +0800
v4
commit 1d6abe13c044653ac4e59fce77159f70c1c5b06e
Author: 林晓东 <[email protected]>
Date: Tue Oct 10 15:39:27 2017 +0800
v3
commit c3041bd4987b39feaf98d5efaa0690ed35c31c23
Author: 林晓东 <[email protected]>
Date: Tue Oct 10 15:39:02 2017 +0800
v2
commit e7bb6dd85abe705972ac4e5ed1626c9893a97627
Author: 林晓东 <[email protected]>
Date: Tue Oct 10 15:38:15 2017 +0800
v1
假设在v1
版本我们确认是没问题的版本,在v10
版本是有问题的版本,
git bisect start #开始执行查找
git bisect good e7bb6dd85abe705972ac4e5ed1626c9893a97627 #我们可以确认没问题的最新的一次提交
git bisect bad 9551544db6aec178b064eeaba33389ea878d5979 #我们可以确认的有问题的一次提交
执行后可以看到
Bisecting: 4 revisions left to test after this (roughly 2 steps)
[ed63a6a71bb28939e498f75cbdf27fb22c01ae72] v5
现在head指向了v5这个版本,我们进行测试,发现这个版本是正常的,我们给这个版本打上bisect结果
git bisect good ed63a6a71bb28939e498f75cbdf27fb22c01ae72
这个时候版本指向了v7
这一次的提交,
Bisecting: 2 revisions left to test after this (roughly 1 step)
[734216f2df46550948e1f5ff1161d5d6b6860b10] v7
我们进行测试,发现这一次提交是有问题的,
git bisect bad 734216f2df46550948e1f5ff1161d5d6b6860b10
这个时候版本指向了v6
,继续测试,发现v6
也是有问题的一次提交
git bisect bad 9b715e9f8f3775242c0abd0cb5fd6f65b6d6626c
执行到这一步, 我们已经定位到的错误的那一次提交了,同时git也给出了我们结果:
9b715e9f8f3775242c0abd0cb5fd6f65b6d6626c is the first bad commit
commit 9b715e9f8f3775242c0abd0cb5fd6f65b6d6626c
Author: 林晓东 <[email protected]>
Date: Tue Oct 10 15:41:52 2017 +0800
v6
:100644 100644 901b26927b75f2a338d8004b3953cc4abe1d6a82 39e1bf998f9a8945cbc08b293d4a759aec15b628 M README.md
表明这是一次有问题的提交。
当然,我们不应该在这一版本中进行修改,我们应该找到问题所在,然后在最新的版本中进行该问题的修复。
git bisect reset #退出二分查找
在使用这个命令的时候,我们还可以通过git bisect log > filename
来把我们整个查找的过程输出到一个日志文件中,方便我们查看整个过程,当然,它还有一个更好的用处,就是配合git bisect replay
来修正我们多次查找当中的错误标记。
比如我们有几十次的提交中,在经过多次二分查找之后,突然不小心把一个错误的提交标记成了正确的提交,而又不希望从头再来,那么就可以把这个过程输出到日志文件中,然后编辑日志文件,再进行replay
即可。
重复上面的二分过程,最后输出日志文件:
git bisect log > log.txt
打开这个日志文件,可以看到我们的记录:
git bisect start
# good: [e7bb6dd85abe705972ac4e5ed1626c9893a97627] v1
git bisect good e7bb6dd85abe705972ac4e5ed1626c9893a97627
# bad: [9551544db6aec178b064eeaba33389ea878d5979] v10
git bisect bad 9551544db6aec178b064eeaba33389ea878d5979
# good: [ed63a6a71bb28939e498f75cbdf27fb22c01ae72] v5
git bisect good ed63a6a71bb28939e498f75cbdf27fb22c01ae72
# bad: [734216f2df46550948e1f5ff1161d5d6b6860b10] v7
git bisect bad 734216f2df46550948e1f5ff1161d5d6b6860b10
# bad: [9b715e9f8f3775242c0abd0cb5fd6f65b6d6626c] v6
git bisect bad 9b715e9f8f3775242c0abd0cb5fd6f65b6d6626c
# first bad commit: [9b715e9f8f3775242c0abd0cb5fd6f65b6d6626c] v6
假如我们在v6
那一次标记是出错了,应该是一次正确的提交,我们删除v6开始的代码,保存之后,
git bisect replay log.txt
Bisecting: 2 revisions left to test after this (roughly 1 step)
[734216f2df46550948e1f5ff1161d5d6b6860b10] v7
这个时候自动重播到了v7
这一次的二分步骤。接下来就可以继续进行标记查找了。
来点恶作剧?既然我们可以自己编辑这个log文件,那么如果我这样编辑它:
git bisect start
# good: [e7bb6dd85abe705972ac4e5ed1626c9893a97627] v1
git bisect good e7bb6dd85abe705972ac4e5ed1626c9893a97627
# bad: [9551544db6aec178b064eeaba33389ea878d5979] v10
git bisect bad 9551544db6aec178b064eeaba33389ea878d5979
# bad: [ed63a6a71bb28939e498f75cbdf27fb22c01ae72] v5
git bisect bad ed63a6a71bb28939e498f75cbdf27fb22c01ae72
# good: [734216f2df46550948e1f5ff1161d5d6b6860b10] v7
git bisect good 734216f2df46550948e1f5ff1161d5d6b6860b10
我认为第一次提交是有问题的,第五次提交是有问题的,第七次提交是正常的,第十次提交又是有问题的,然后我们replay
会怎样:
git bisect replay log.txt
Some good revs are not ancestor of the bad rev.
git bisect cannot work properly in this case.
Maybe you mistook good and bad revs?
显然,它认为这个是有问题的...
更多命名可以参考 git-bisect
微信的流行,让我们经常会在微信中打开一些网页,但因为微信的webview是内置的浏览器,没有给我们直接进行浏览器设置的地方,比如清理缓存之类的。
经常在做微信网页开发的时候,在调试阶段,这种缓存是非常令人头痛的。
还有就是浏览过一些网页之后,希望能够清理掉cookie,比如登录信息之类的。
这些尝试过在应用设置里面清理微信的缓存,用安全工具清理掉缓存,都办法清理掉。
一个解决办法就是我们自己做开发的,可以给静态资源添加一个时间戳,可以解决一部分问题。
一个就是因为andorid版本的微信是内置的QQ浏览器x5的内核,我们可以通过在微信中打开QQ浏览器的调试页面:http://debugx5.qq.com
,在里面找到清理的选项,如下图,然后勾选我们要清理的内容,点清理就可以了。
centos6.5最小化安装/AWS centos
apr-1.5.1
apr-util-1.5.4
httpd-2.4.10
pcre-8.36
在centos最小化安装情况下,首先我们需要先安装以下几个包
yum install wget
yum install gcc
yum install gcc-c++
wget http://mirrors.cnnic.cn/apache/httpd/httpd-2.4.10.tar.gz
wget http://mirrors.cnnic.cn/apache//apr/apr-1.5.1.tar.gz
wget http://mirrors.cnnic.cn/apache//apr/apr-util-1.5.4.tar.gz
wget ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-8.36.tar.gz
tar -zxvf httpd-2.4.10.tar.gz
tar -zxvf apr-1.5.1.tar.gz
tar -zxvf apr-util-1.5.4.tar.gz
tar -zxvf pcre-8.36.tar.gz
首先把pcre安装上
cd pcre-8.36
./configure
make && make install
然后把apr移动到httpd安装包里面
cd ../
mv apr-1.5.1 httpd-2.4.10/srclib/apr
mv apr-util-1.5.4 httpd-2.4.10/srclib/apr-util
cd httpd-2.4.10
配置httpd编译参数
./configure --with-included-apr --enable-nonportable-atomics=yes --with-z
出奇的简单,因为默认就有 --enable-mods-shared=most ,模块化安装,以后自行到 httpd.conf 中决定是否开启模块,所以什么 --enable-deflate --enable-rewrite --enable-blablabla 等就完全不必要了。
默认安装的是 event mpm,如果要用 worker ,就需要--with-mpm=worker,或者干脆 --enable-mpms-shared=all,这样event、worker、prefork就会以模块化的方式安装,要用哪个就在 httpd.conf 里配置就好了
编译安装httpd
make && make install
软件已经默认安装到/usr/local/apache2里面了,对应的配置文件是conf/httpd.conf
cp /usr/local/apache2/bin/apachectl /etc/init.d/httpd
vi /etc/init.d/httpd
在首行 #!/bin/sh 下面加入两行:
# chkconfig: 35 85 15
# description: Activates/Deactivates Apache 2.4.10
加入开机自启
chkconfig --add httpd
chkconfig httpd on
接下来我们便可以启动apache了
service httpd start
打开浏览器,我们应该能够看到大大的It works!
几个字,表明我们已经正常安装了。
这是因为在默认情况,ceontos的防火墙关闭了80端口,开启方法如下
/sbin/iptables -I INPUT -p tcp --dport 80 -j ACCEPT #允许80端口
/etc/rc.d/init.d/iptables save #保存
/etc/init.d/iptables restart #重启防火墙
其实做为开发人员,我们都应该尊重别人的劳动成果的,但互联网的免费性,开源性,分享性,而更多的是码农的穷逼性,不知是被逼使用一些盗版软件,破解一些东西什么的。
以前手头上微信框架用的是某X力的,虽然知道也是破解的微擎的,但人家便宜呀,模块多呀。
不过真的是一分钱一分货,bug太多,更新不行,以至后来我升级到他们的新版本后根本就不能更新了。
最后想了想,还是和微擎吧,毕竟是官方的,而且免费更新框架,至少框架这一块是稳定了。
但用免费版的微擎有一个比较大的问题就是,很多模块都是要收费的,这样可免费用的模块就比较少了。
网上会有很多微擎的模块盗版出来的,但安装会有一个问题就是,微擎的模块都是在线安装的,就算我们拿到模块进行本地安装时,也会通过模块名进行云检测,如果商城中有这个模块,就会提示盗版,而站点就可能会被记录黑名单,这样就不能再使用微擎的一切云服务了,比如安装微擎商城中的模块,更新框架等。
网上主流的解决方法就是屏蔽云服务,但是这样所有模块就只能本地安装了,框架也不能更新了。
我希望能够得到完美的解决方案就是,我能够正常使用微擎的免费服务,包括更新框架,安装商城免费的模块,更新商城的免费模块,同时,我也可以安装自己盗版的模块。
通过分析微擎云服务文件,弄清楚了每个函数的用途,最后终于完美解决了我的问题,达到了我上面想要的效果。当然,具体解决方法就不公布了,毕竟这是一个不友好的行为。在此只是记录一下解决思路:让云服务不知道你安装了哪些盗版的模块。
人人商城肯定是盗版的,而微商城是正常从商城里面安装的,后期也是可以免费升级的。
分享一份自己备注的微擎cloud.mod.php函数
<?php
/**
* [WeEngine System] Copyright (c) 2014 WE7.CC
* WeEngine is NOT a free software, it under the license terms, visited http://www.we7.cc/ for more details.
*/
defined('IN_IA') or exit('Access Denied');
function cloud_client_define() {
return array(
'/framework/function/communication.func.php',
'/framework/model/cloud.mod.php',
'/web/source/cloud/upgrade.ctrl.php',
'/web/source/cloud/process.ctrl.php',
'/web/source/cloud/dock.ctrl.php',
'/web/themes/default/cloud/upgrade.html',
'/web/themes/default/cloud/process.html'
);
}
function cloud_prepare() {
global $_W;
setting_load();
if(empty($_W['setting']['site']['key']) || empty($_W['setting']['site']['token'])) {
return error('-1', "您的程序需要在微擎云服务平台注册你的站点资料, 来接入云平台服务后才能使用相应功能.");
}
return true;
}
function cloud_m_prepare($name) {//模块验证 传入模块名字,通过云平台验证是否受版权保护 仅用于模块安装 批量安装模块的时候使用
$pars['method'] = 'module.check';
$pars['module'] = $name;
$dat = cloud_request('http://v2.addons.we7.cc/gateway.php', $pars);
if (is_error($dat)) {
return $dat;
}
if ($dat['content'] == 'install-module-protect') {
return error('-1', '此模块已设置版权保护,您只能通过云平台来安装。');
}
return true;
}
function _cloud_build_params() {//云服务变量数组
global $_W;
$pars = array();
$pars['host'] = $_SERVER['HTTP_HOST'];
$pars['family'] = IMS_FAMILY;
$pars['version'] = IMS_VERSION;
$pars['release'] = IMS_RELEASE_DATE;
$pars['key'] = $_W['setting']['site']['key'];
$pars['password'] = md5($_W['setting']['site']['key'] . $_W['setting']['site']['token']);
$clients = cloud_client_define();
$string = '';
foreach($clients as $cli) {
$string .= md5_file(IA_ROOT . $cli);
}
$pars['client'] = md5($string);
return $pars;
}
function cloud_m_build($modulename, $type = '') {//云平台安装 卸载模块 更新模块
$type = in_array($type, array('uninstall')) ? $type : '';
$sql = 'SELECT * FROM ' . tablename('modules') . ' WHERE `name`=:name';
$module = pdo_fetch($sql, array(':name' => $modulename));
$pars = _cloud_build_params();
$pars['method'] = 'module.build';
$pars['module'] = $modulename;
$pars['type'] = $type;
if (!empty($module)) {
$pars['module_version'] = $module['version'];
}
$dat = cloud_request('http://v2.addons.we7.cc/gateway.php', $pars);
$file = IA_ROOT . '/data/module.build';
$ret = _cloud_shipping_parse($dat, $file);//云服务结果处理
if (!is_error($ret)) {
$dir = IA_ROOT . '/addons/' . $modulename;
$files = array();
if (!empty($ret['files'])) {
foreach ($ret['files'] as $file) {
$entry = $dir . $file['path'];
if (!is_file($entry) || md5_file($entry) != $file['checksum']) {
$files[] = '/' . $modulename . $file['path'];
}
}
}
$ret['files'] = $files;
$schemas = array();
if (!empty($ret['schemas'])) {
load()->func('db');
foreach ($ret['schemas'] as $remote) {
$name = substr($remote['tablename'], 4);
$local = db_table_schema(pdo(), $name);
unset($remote['increment']);
unset($local['increment']);
if (empty($local)) {
$schemas[] = $remote;
} else {
$diffs = db_table_fix_sql($local, $remote);
if (!empty($diffs)) {
$schemas[] = $remote;
}
}
}
}
$ret['upgrade'] = true;
$ret['type'] = 'module';
$ret['schemas'] = $schemas;
if (empty($module)) {
$ret['install'] = 1;
}
}
return $ret;
}
function cloud_m_query() {//非系统模块发送到服务端 a=module&do=check 已经安装模块 检查更新 安装模块 检查已经购买但没有安装的
$pars = _cloud_build_params();
$pars['method'] = 'module.query';
$pars['module'] = cloud_extra_module();//获取非系统模块
$dat = cloud_request('http://v2.addons.we7.cc/gateway.php', $pars);
$file = IA_ROOT . '/data/module.query';
$ret = _cloud_shipping_parse($dat, $file);
return $ret;
}
function cloud_m_info($name) {//获取指定模块信息 安装模块 更新模块时进行检查
$pars = _cloud_build_params();
$pars['method'] = 'module.info';
$pars['module'] = $name;
$dat = cloud_request('http://v2.addons.we7.cc/gateway.php', $pars);
$file = IA_ROOT . '/data/module.info';
$ret = _cloud_shipping_parse($dat, $file);
return $ret;
}
function cloud_m_upgradeinfo($name) {//在线检测模块更新 仅用于module.ctrl.php 469 $do == 'upgrade' 参数 传入模块名
$module = pdo_fetch("SELECT name, version FROM ".tablename('modules')." WHERE name = '{$name}'");
$pars = _cloud_build_params();
$pars['method'] = 'module.info';
$pars['module'] = $name;
$pars['curversion'] = $module['version'];
$pars['isupgrade'] = 1;
$dat = cloud_request('http://v2.addons.we7.cc/gateway.php', $pars);
$file = IA_ROOT . '/data/module.info';
$ret = _cloud_shipping_parse($dat, $file);
return $ret;
}
function cloud_t_prepare($name) {//模板云检测
$pars['method'] = 'theme.check';
$pars['theme'] = $name;
$dat = cloud_request('http://v2.addons.we7.cc/gateway.php', $pars);
if (is_error($dat)) {
return $dat;
}
if ($dat['content'] == 'install-theme-protect') {
return error('-1', '此模板已设置版权保护,您只能通过云平台来安装。');
}
return true;
}
function cloud_t_query() {//获取在线安装的模板
$pars = _cloud_build_params();
$pars['method'] = 'theme.query';
$pars['theme'] = cloud_extra_theme();
$dat = cloud_request('http://v2.addons.we7.cc/gateway.php', $pars);
$file = IA_ROOT . '/data/theme.query';
$ret = _cloud_shipping_parse($dat, $file);
return $ret;
}
function cloud_t_info($name) {//在线获取指定模板信息
$pars = _cloud_build_params();
$pars['method'] = 'theme.info';
$pars['theme'] = $name;
$dat = cloud_request('http://v2.addons.we7.cc/gateway.php', $pars);
$file = IA_ROOT . '/data/theme.info';
$ret = _cloud_shipping_parse($dat, $file);
return $ret;
}
function cloud_t_build($name) {//在线安装模板
$sql = 'SELECT * FROM ' . tablename('site_templates') . ' WHERE `name`=:name';
$theme = pdo_fetch($sql, array(':name' => $name));
$pars = _cloud_build_params();
$pars['method'] = 'theme.build';
$pars['theme'] = $name;
if(!empty($theme)) {
$pars['themeversion'] = $theme['version'];
}
$dat = cloud_request('http://v2.addons.we7.cc/gateway.php', $pars);
$file = IA_ROOT . '/data/theme.build';
$ret = _cloud_shipping_parse($dat, $file);
if(!is_error($ret)) {
$dir = IA_ROOT . '/app/themes/' . $name;
$files = array();
if(!empty($ret['files'])) {
foreach($ret['files'] as $file) {
$entry = $dir . $file['path'];
if(!is_file($entry) || md5_file($entry) != $file['checksum']) {
$files[] = '/'. $name . $file['path'];
}
}
}
$ret['files'] = $files;
$ret['upgrade'] = true;
$ret['type'] = 'theme';
if(empty($theme)) {
$ret['install'] = 1;
}
}
return $ret;
}
function cloud_t_upgradeinfo($name) {//在线获取模板更新
$sql = 'SELECT `name`, `version` FROM ' . tablename('site_templates') . ' WHERE `name` = :name';
$theme = pdo_fetch($sql, array(':name' => $name));
$pars = _cloud_build_params();
$pars['method'] = 'theme.upgrade';
$pars['theme'] = $theme['name'];
$pars['version'] = $theme['version'];
$pars['isupgrade'] = 1;
$dat = cloud_request('http://v2.addons.we7.cc/gateway.php', $pars);
$file = IA_ROOT . '/data/module.info';
$ret = _cloud_shipping_parse($dat, $file);
return $ret;
}
function cloud_sms_send($mobile, $content) {//在线短信接口
global $_W;
$log = array(
'mobile' => $mobile,
'content' => $content,
'result' => '',
'uniacid' => $_W['uniacid'],
'createtime' => TIMESTAMP
);
$row = pdo_get('uni_settings' , array('uniacid' => $_W['uniacid']), array('notify'));
$row['notify'] = @iunserializer($row['notify']);
if(!empty($row['notify']) && !empty($row['notify']['sms'])) {
$config = $row['notify']['sms'];
$balance = intval($config['balance']);
if($balance <= 0) {
$log['result'] = '发送短信失败, 请联系系统管理人员. 错误详情: 短信余额不足';
pdo_insert('core_sendsms_log', $log);
return error(-1, $log['result']);
}
$sign = $config['signature'];
if(empty($sign) && IMS_FAMILY == 'x') {
$sign = $_W['setting']['copyright']['sitename'];
}
if(empty($sign)) {
$sign = '微擎';
}
$log['content'] = $content . " 【{$sign}】";
$pars = _cloud_build_params();
$pars['method'] = 'sms.send';
$pars['mobile'] = $mobile;
$pars['content'] = $content . " 【{$sign}】";
$dat = cloud_request('http://v2.addons.we7.cc/gateway.php', $pars);
$file = IA_ROOT . '/data/sms.send';
$ret = _cloud_shipping_parse($dat, $file);
if (is_error($ret)) {
$log['result'] = $ret['message'];
pdo_insert('core_sendsms_log', $log);
return error($ret['errno'], $ret['message']);
}
if ($ret == 'success') {
return true;
} else {
$log['result'] = $ret['message'];
pdo_insert('core_sendsms_log', $log);
return error(-1, $ret);
}
}
pdo_insert('core_sendsms_log', $log);
return error(-1, '发送短信失败, 请联系系统管理人员. 错误详情: 没有设置短信配额或参数');
}
function cloud_build() {//程序更新入口
$pars = _cloud_build_params();
$pars['method'] = 'application.build';
$pars['extra'] = cloud_extra_account();
$dat = cloud_request('http://v2.addons.we7.cc/gateway.php', $pars);
$file = IA_ROOT . '/data/application.build';
$ret = _cloud_shipping_parse($dat, $file);
if(!is_error($ret)) {
if($ret['state'] == 'warning') {
$ret['files'] = cloud_client_define();
unset($ret['schemas']);
unset($ret['scripts']);
} else {
$files = array();
if(!empty($ret['files'])) {
foreach($ret['files'] as $file) {
$entry = IA_ROOT . $file['path'];
if(!is_file($entry) || md5_file($entry) != $file['checksum']) {
$files[] = $file['path'];
}
}
}
$ret['files'] = $files;
$schemas = array();
if(!empty($ret['schemas'])) {
load()->func('db');
foreach($ret['schemas'] as $remote) {
$name = substr($remote['tablename'], 4);
$local = db_table_schema(pdo(), $name);
unset($remote['increment']);
unset($local['increment']);
if(empty($local)) {
$schemas[] = $remote;
} else {
$sqls = db_table_fix_sql($local, $remote);
if(!empty($sqls)) {
$schemas[] = $remote;
}
}
}
}
$ret['schemas'] = $schemas;
}
if($ret['family'] == 'x' && IMS_FAMILY == 'v') {
load()->model('setting');
setting_upgrade_version('x', IMS_VERSION, IMS_RELEASE_DATE);
message('您已经购买了商业授权版本, 系统将转换为商业版, 并重新运行自动更新程序.', 'refresh');
}
$ret['upgrade'] = false;
if(!empty($ret['files']) || !empty($ret['schemas']) || !empty($ret['scripts'])) {
$ret['upgrade'] = true;
}
$upgrade = array();
$upgrade['upgrade'] = $ret['upgrade'];
$upgrade['lastupdate'] = TIMESTAMP;
cache_write('upgrade', $upgrade);
}
return $ret;
}
function cloud_schema() {//数据库整理
$pars = _cloud_build_params();
$pars['method'] = 'application.schema';
$dat = cloud_request('http://v2.addons.we7.cc/gateway.php', $pars);
$file = IA_ROOT . '/data/application.schema';
$ret = _cloud_shipping_parse($dat, $file);
if(!is_error($ret)) {
$schemas = array();
if(!empty($ret['schemas'])) {
load()->func('db');
foreach($ret['schemas'] as $remote) {
$name = substr($remote['tablename'], 4);
$local = db_table_schema(pdo(), $name);
unset($remote['increment']);
unset($local['increment']);
if(empty($local)) {
$schemas[] = $remote;
} else {
$diffs = db_schema_compare($local, $remote);
if(!empty($diffs)) {
$schemas[] = $remote;
}
}
}
}
$ret['schemas'] = $schemas;
}
return $ret;
}
function cloud_download($path, $type = '') {//下载文件
$pars = _cloud_build_params();
$pars['method'] = 'application.shipping';
$pars['path'] = $path;
$pars['type'] = $type;
$pars['gz'] = function_exists('gzcompress') && function_exists('gzuncompress') ? 'true' : 'false';
$headers = array('content-type' => 'application/x-www-form-urlencoded');
$dat = cloud_request('http://v2.addons.we7.cc/gateway.php', $pars, $headers, 300);
if(is_error($dat)) {
return error(-1, '网络存在错误, 请稍后重试。' . $dat['message']);
}
if($dat['content'] == 'success') {
return true;
}
$ret = @json_decode($dat['content'], true);
if(is_error($ret)) {
return $ret;
} else {
return error(-1, '不能下载文件, 请稍后重试。');
}
}
function _cloud_shipping_parse($dat, $file) {//云服务结果处理
if (is_error($dat)) {
return error(-1, '网络传输错误, 请检查您的cURL是否可用, 或者服务器网络是否正常. ' . $dat['message']);
}
$tmp = unserialize($dat['content']);
if (is_array($tmp) && is_error($tmp)) {
if ($tmp['errno'] == '-2') {
$data = file_get_contents(IA_ROOT . '/framework/version.inc.php');
file_put_contents(IA_ROOT . '/framework/version.inc.php', str_replace("'x'", "'v'", $data));
}
return $tmp;
}
if ($dat['content'] == 'patching') {
return error(-1, '补丁程序正在更新中,请稍后再试!');
}
if ($dat['content'] == 'blacklist') {
return error(-1, '抱歉,您的站点已被列入云服务黑名单,云服务一切业务已被禁止,请联系微擎客服!');
}
if (strlen($dat['content']) != 32) {
return error(-1, '云服务平台向您的服务器传输数据过程中出现错误, 这个错误可能是由于您的通信密钥和云服务不一致, 请尝试诊断云服务参数(重置站点ID和通信密钥). 传输原始数据:' . $dat['meta']);
}
$data = @file_get_contents($file);
if (empty($data)) {
return error(-1, '没有接收到服务器的传输的数据.');
}
@unlink($file);
$ret = @iunserializer($data);
if (empty($data) || empty($ret) || $dat['content'] != $ret['secret']) {
return error(-1, '云服务平台向您的服务器传输的数据校验失败, 可能是因为您的网络不稳定, 或网络不安全, 请稍后重试.');
}
$ret = iunserializer($ret['data']);
if (is_array($ret) && is_error($ret)) {
if ($ret['errno'] == '-2') {
$data = file_get_contents(IA_ROOT . '/framework/version.inc.php');
file_put_contents(IA_ROOT . '/framework/version.inc.php', str_replace("'x'", "'v'", $data));
}
}
if (!is_error($ret) && is_array($ret) && !empty($ret)) {
if ($ret['state'] == 'fatal') {
return error($ret['errorno'], '发生错误: ' . $ret['message']);
}
return $ret;
} else {
return error($ret['errno'], "发生错误: {$ret['message']}");
}
}
function cloud_request($url, $post = '', $extra = array(), $timeout = 60) {//发送云服务请求
global $_W;
load()->func('communication');
if (!empty($_W['setting']['cloudip']['ip']) && empty($extra['ip'])) {
$extra['ip'] = $_W['setting']['cloudip']['ip'];
}
return ihttp_request($url, $post, $extra, $timeout);
}
function cloud_extra_account() {//获取添加公众号信息
$data = array();
$data['accounts'] = pdo_fetchall("SELECT name, account, original FROM ".tablename('account_wechats') . " GROUP BY account");
return serialize($data);
}
function cloud_extra_module() {//获取非系统模块 只用于cloud_m_query()方法
$sql = 'SELECT `name` FROM ' . tablename('modules') . ' WHERE `type` <> :type';
$modules = pdo_fetchall($sql, array(':type' => 'system'), 'name');
if (!empty($modules)) {
return base64_encode(iserializer(array_keys($modules)));
} else {
return '';
}
}
function cloud_extra_theme() {//获取非默认模板
$sql = 'SELECT `name` FROM ' . tablename('site_templates') . ' WHERE `name` <> :name';
$themes = pdo_fetchall($sql, array(':name' => 'default'), 'name');
if (!empty($themes)) {
return base64_encode(iserializer(array_keys($themes)));
} else {
return '';
}
}
function cloud_cron_create($cron) {//添加定时任务
$pars = _cloud_build_params();
$pars['method'] = 'cron.create';
$pars['cron'] = base64_encode(iserializer($cron));
$result = cloud_request('http://v2.addons.we7.cc/gateway.php', $pars);
return _cloud_cron_parse($result);
}
function cloud_cron_update($cron) {//更新定时任务
$pars = _cloud_build_params();
$pars['method'] = 'cron.update';
$pars['cron'] = base64_encode(iserializer($cron));
$result = cloud_request('http://v2.addons.we7.cc/gateway.php', $pars);
return _cloud_cron_parse($result);
}
function cloud_cron_get($cron_id) {//获取定时任务
$pars = _cloud_build_params();
$pars['method'] = 'cron.get';
$pars['cron_id'] = $cron_id;
$result = cloud_request('http://v2.addons.we7.cc/gateway.php', $pars);
return _cloud_cron_parse($result);
}
function cloud_cron_change_status($cron_id, $status) {//更改定时任务状态
$pars = _cloud_build_params();
$pars['method'] = 'cron.status';
$pars['cron_id'] = $cron_id;
$pars['status'] = $status;
$result = cloud_request('http://v2.addons.we7.cc/gateway.php', $pars);
return _cloud_cron_parse($result);
}
function cloud_cron_remove($cron_id) {//删除定时任务
$pars = _cloud_build_params();
$pars['method'] = 'cron.remove';
$pars['cron_id'] = $cron_id;
$result = cloud_request('http://v2.addons.we7.cc/gateway.php', $pars);
return _cloud_cron_parse($result);
}
function _cloud_cron_parse($result) {//定时任务返回结果处理
if (empty($result)) {
return error(-1, '没有接收到服务器的传输的数据');
}
if ($result['content'] == 'blacklist') {
return error(-1, '抱歉,您的站点已被列入云服务黑名单,云服务一切业务已被禁止,请联系微擎客服!');
}
$result = json_decode($result['content'], true);
if (null === $result) {
return error(-1, '云服务通讯发生错误,请稍后重新尝试!');
}
$result = $result['message'];
if (is_error($result)) {
return error(-1, $result['message']);
}
return $result;
}
function cloud_auth_url($forward, $data = array()){//云端oauth权限
global $_W;
$auth = array();
$auth['key'] = '';
$auth['password'] = '';
$auth['url'] = rtrim($_W['siteroot'], '/');
$auth['referrer'] = intval($_W['config']['setting']['referrer']);
$auth['version'] = IMS_VERSION;
$auth['forward'] = $forward;
if(!empty($_W['setting']['site']['key']) && !empty($_W['setting']['site']['token'])) {
$auth['key'] = $_W['setting']['site']['key'];
$auth['password'] = md5($_W['setting']['site']['key'] . $_W['setting']['site']['token']);
}
if ($data && is_array($data)) {
$auth = array_merge($auth, $data);
}
$query = base64_encode(json_encode($auth));
$auth_url = 'http://v2.addons.we7.cc/web/index.php?c=auth&a=passwort&__auth=' . $query;
return $auth_url;
}
function cloud_module_setting_prepare($module, $binding) {//模块设置
global $_W;
$auth = _cloud_build_params();
$auth['arguments'] = array(
'binding' => $binding,
'acid' => $_W['uniacid'],
'type' => 'module',
'module' => $module,
);
$iframe_auth_url = cloud_auth_url('module', $auth);
return $iframe_auth_url;
}
function cloud_resource_to_local($uniacid, $type, $url){
global $_W;
load()->func('file');
$setting = $_W['setting']['upload'][$type];
$pathinfo = pathinfo($url);
$extension = !empty($pathinfo['extension']) ? $pathinfo['extension'] : 'jpg';
$originname = $pathinfo['basename'];
$setting['folder'] = "{$type}s/{$uniacid}/".date('Y/m/');
$originname = pathinfo($url, PATHINFO_BASENAME);
$filename = file_random_name(ATTACHMENT_ROOT .'/'. $setting['folder'], $extension);
$pathname = $setting['folder'] . $filename;
$fullname = ATTACHMENT_ROOT . $pathname;
mkdirs(dirname($fullname));
if (file_put_contents($fullname, file_get_contents($url)) == false) {
return error(1, '提取文件失败');
}
if (!empty($_W['setting']['remote']['type'])) {
$remotestatus = file_remote_upload($pathname);
if (is_error($remotestatus)) {
return error(1, '远程附件上传失败,请检查配置并重新上传');
} else {
file_delete($pathname);
}
}
$data = array(
'uniacid' => $uniacid,
'uid' => intval($_W['uid']),
'filename' => $originname,
'attachment' => $pathname,
'type' => $type == 'image' ? 1 : 2,
'createtime' => TIMESTAMP,
);
pdo_insert('core_attachment', $data);
$data['url'] = tomedia($pathname);
$data['id'] = pdo_insertid();
return $data;
}
我们在使用hexo安装了主题之后,后续主题作者可能会更新了主题,比如修复了bug,增加了功能等。那么这个时候我们就要想办法更新主题,最粗暴的方法就是直接删除主题,重新安装最新的,但这种方式不是用hexo写博客的精神。如果主题是托管在git上面的,那么就非常好办了。我们通过简单的git命令就可以完成更新操作了。
这里我们以目前非常流行的NexT
主题为例,我们安装的时候都是通过git的方式安装的,那么我们通过git在hexo目录下面,定位到next
目录,
在git中直接运行git fetch origin master
先把远程分支摘取下来,如下图
我们来对比看一下远程和本地的差异,运行git diff master origin/master
,如下图
接下来我们就可以进行合并操作了,运行git merge origin/master
,如下图
简单的三条命令我们就完成了主题的更新操作。
需要注意的是:
我们一般都要对主题的配置文件进行修改的,所以在合并的时候一定要注意远程的修改时间和本地的修改时间,如果远程的修改时间比本地的新,比如配置文件中可能增加了新的功能,那么一定要记得备份本地的修改文件,要不然合并下来可能会被覆盖掉的。
当然,我们也可以通过更加方便的方式使用git stash
或者git rebase
方式来更新合并。
当然,更好的办法还是应该是把原作者的github进行fork过来,然后在自己的本地修改git的远程分支为自己fork过来的地址,并添加upstream分支为原作者的分支。
这样,我们保留master分支上的代码一直是和upstream以及origin上的代码是同步并一致的,而自己对主题的修改刚新建的一个分支,比如叫building
分支,这样,当主分支有了更新之后,我们也能够安全地进行合并,如果我们自己想对主题进行比较好的修改,那么我们可以在主分支上面,再新建的一个dev
分支,添加功能之后,还可以向原作者进行pull request,这样也可以贡献我们自己的代码了。
比如我的实际操作,在github上fork完毕原作者的主题之后:
因为我之前对主题文件的配置进行了修改,所以我先把配置文件保存出来,因为我不需要合并,我需要把master分支保证和源分支一致。
首先,丢弃我对主题的修改
git reset --hard HEAD
修改远程地支的地址为自己fork之后的,
git remote origin set-url [email protected]:buxuku/hexo-theme-next.git
当然,这一步我们也可以先删除远程分支再添加的方式
git remote rm origin
git remote add origin [email protected]:buxuku/hexo-theme-next.git
或者直接修改config配置文件
然后添加原作者的远程分支
git remote add upstream [email protected]:iissnan/hexo-theme-next.git
同步原作者的分支
git fetch upstream
合并到master分支
git merge upstream/master
push到自己的github上面
git push origin master
添加自己写博客需要的一个分支
git checkout -b building
然后尽情地修改自己的配置文件,主题什么的,尽情享受写作的乐趣吧
修改完毕之后,git commit之后,我们再git push origin building推送到自己的远程目录。
当我们换电脑之后,全新安装hexo,进入theme目录,克隆我们的远程仓库
git clone [email protected]:buxuku/hexo-theme-next.git next
和上面一样,添加upstream分友
下载我们的building分支
git checkout -b building origin/building
git checkout master
检查更新,合并更新,合并更新到building分支
git fetch upstream
git merge upstream/master
git push origin master
git checkout building
git merge master
git push origin building
Syncing a fork :https://help.github.com/articles/syncing-a-fork/
Configuring a remote for a fork :https://help.github.com/articles/configuring-a-remote-for-a-fork/
在javascript中的函数参数传值中,对于基本数字类型,我们都能够很好理解,它是按值传递的.比如下面这样:
var num = 10;
function add(num){
return num+1;
}
console.log(add(num));//11
console.log(num);//10
而对于对象的传递,我们可能就不太好理解了,因为如果是同基本类型一样,按值传递,但其实际表现又像引用类型一样.比如下面这样
情形一:
var a=[],
b={},
c={};
function change(a,b,c){
a.push(1);
b.name = "my name";
c.age = 23;
}
change(a,b,c);
console.log(a);//[1]
console.log(b);// Object { name="my name"}
console.log(c.age);//23
如果是按值传递,为什么函数把外面的值给改变了呢?我们又继续看下面这个例子:
情形二:
var a=[],
b={},
c={};
function change(a,b,c){
a = [1];
b = [2];
c = {age:3}
}
change(a,b,c);
console.log(a);//[]
console.log(b);//object
console.log(c.age);//undefined
同样是进行操作,为什么这次又 没有任何变化呢?
其实深入理解,我们必须相信的是,在javascript中,函数的参数确实是按值传递的.对于以上现象,我们可以这样理解:
变量a保存的是一个对象,而我们又知道,对象类型的变化保存的其实是对象引用的一个地址,当把变化a传递的函数的参数a的时候,函数参数a复制了变量a,实际上也就是复制了变量a引用对象的地址,因为是同一个地址,所以指向的是同一个变量.所以对函数中变量a进行的操作,都会反映到这个地址引用的对象上面.这就是情形1的执行结果.
而在函数中,当我们把变量a赋值给另外一个对象后,它保存的就是另外一个对象的引用地址,所以对它进行任何操作,都不会反映到最开始变量a的对象上面.
通过以上两种情形,我们就能够很好地理解下面这种情况了:
var person ={
name : "my name"
}
function change(person){
person.name = "your name";
person = {
name : "her name"
}
}
change(person);
console.log(person.name);//your name
所以,当有一天,我们能够改变var person
里面的引用地址的时候,我们才敢说是按照引用传值的.
test
欢迎通过issue提交更好的口诀或者补充未提到的正则表达式
从前有个傻大三,
哇塞,日子过得特别难
\w\s\r\z\d\t\b\n
人傻人穷只有捡破烂
做梦都想变成大富豪,就不用再出来捡破烂
\W\S\D\B
每天哼着歌儿出去捡破烂
两手叉着腰,左手捡了右手又捡
a|b
你问他捡到钱没
?
左手回答得还行,右手被问得头冒金星
+
*
只有装进袋子把数字标
{n}
关上门来把金银财宝往屋里搬
[abc]
脑袋儿被门夹了不知道啥东西要选
[^abc]
钢镚儿一碗,铁镚儿一碗
(abc)
没问题的打个标记,好知道有好多钱
(?<name>)
有问题的放一边,挤破脑袋也还要向前再看一眼
(?=exp)
(?!exp)
(?<=)
(?<!)
剩下的铜钱儿用线串
[a-zA-Z0-9]
一不小心遇到地头蛇,一顿拷问被吓得赶紧溜回家
+?
*?
尖尖代表^
符号,钱代表$
符号
傻大三这个人一张嘴说话,句句都是以钱收尾
所以我们get到了
正则表达式 | 描述 | 示例 |
---|---|---|
^ |
匹配字符串的开始 | ^acb 匹配以abc开头的字符串 |
$ |
匹配字符串结尾 | abc$ 匹配以abc结尾的字符串 |
在正则里面有许多的元字符,这里收集了比较常用的一些,取元字符的来当声母,用来助记这些元字符,当然,我们更明白元字符所对应的英文单词,就无须死背元字符所对应的的内容了.
所以我们get到了一大串的元字符
代码 | 描述 | 对应文字 | 对应单词 |
---|---|---|---|
\w |
匹配一个单词的组成部分,字符,数字,下划线.(是否匹配中文视操作系统和应用环境而定) | 哇 | Word |
\s |
匹配空白符 | 塞 | Space |
\r |
回车 | 日 | Enter |
\z |
字符串结尾(类似$,但不受处理多行选项的影响) | 子 | ? |
过 | 过 | ||
\d |
数字 | 得 | Digital |
\t |
tab制表符 | 特 | Tabulator key |
\b |
边界,单词分界位置 | 别 | Boundary |
\n |
换行符 | 难 | Line feed |
. |
点号(相当于句号),在一个段落中,以它结尾,它包括了前的各种符号,但不匹配换行符.因为换行就是新段落了 | . |
变成大富豪,表示元字符变成了大写字母
就不用再出来破烂,表示不再匹配,相当于对以上小写的元字符取反
所以我们get到了
代码 | 描述 |
---|---|
\W |
匹配一个非单词的组成部分,字符,数字,下划线.(是否匹配中文视操作系统和应用环境而定) |
\S |
匹配非空白符 |
\D |
非数字 |
\B |
非边界,单词分界位置 |
这一表示正则里面的分枝匹配,形如a|b
表示两边或的匹配关系,即可以匹配a
也可以匹配b
傻大三捡垃圾,左边有就左手捡到,右边有,就右手捡起来.
问到捡到钱没,这是一个问句,回答应该是是与否,代表0或者1,所以有正则?
匹配0次或者1次
因为他左手刚刚捡到了一块垃圾,所以他傻傻地也知道左手捡到有东西,在数学里面就是用正号+
表示,至少有一个.
被问得头冒金星,他也不知道右手到底有没有捡到有东西了.金星用*
表示,可能没有捡到有,也可能捡到很多
只好数一下装进袋子里面,用数字标上有多少.袋子用{}
表示,数字用n
表示有多少个.得到完整的正则表达式{n}
即表示前面的匹配n
次
于是我们get到的正则的匹配模式
代码 | 描述 |
---|---|
+ |
匹配至少一次 |
* |
匹配0次或者多次 |
? |
匹配零次或者一次 |
{n} |
匹配n次 |
{n,m} |
匹配n到m次 |
{n,} |
匹配n次或者更多次,没有m代表无穷尽 |
关上门表示[]
符号,关在里面的金银财宝,都可以往屋里搬,所以表达式[abc]
表示匹配abc
任意一个.
结果傻大三的头^
不小心被门给夹住了,不知道要这些金银财宝了.所以表达式[^abc]
表示匹配不是abc
这些字符的.
碗用()
表示,傻大三还是知道对这些垃圾进行分类,也就是正则里面的分组,分组之后,不仅可以用于前面的匹配模式,也可以用于后面的反向引用和零宽匹配及更多的功能.
比如(abc){2}
表示匹配连续的两个abc
即abcabc
傻大三对碗打个标记,以免有疑问?
时才好知道有好多钱,所以有正则表达式形式(?<name>exp)
,打好标记之后,在后续就可以通过这个标记来识别引用了.
比如\b(?<Word>\w+)\b\s+\k<Word>\b
表示匹配连续重复的单词,比如go go
.如果不打标记,默认使用数字来识别,每个分组会自动拥有一个组号,规则是:从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。所以这个表达式不打标记的写法就是\b(\w+)\b\s+\1\b
有问题?
的,有可能是真钢镚,即等号=
表示,有可能不是真钢镚,即非!
表示
放一边,表示只匹配了,但不参与捕获.和前面的^
,$
,\b
一样,是属于零宽断言,它们 本身不匹配任何字符,只是对 "字符串的两头" 或者 "字符之间的缝隙" 附加了一个条件.
于是有正预测先行断言(?=exp)
,表示断言自身出现的位置后面能匹配表达式,比如\b\w+(?=ed\b)
表示匹配以ed
结尾的单词,但不包含ed
;
同理有负预测先行断言(?!exp)
,和上面的意思相反,即不能满足表达式.比如\b\w*e(?!d)\w*\b
表示匹配包含e
的单词,但同时e
后面不能跟着字符d
.它和\b\w*e[^d]\w*\b
的区别是,后者的[^d]
会参与一次匹配,比如它会匹配上e,abc
,因为里面的,
参与了[^d]
的匹配.
挤破脑袋向前<
,前面说的是先行断言,即先匹配上前面的表达式.相反的,挤破脑袋往前面去就变成了后发断言.即有
正预测后发断言(?<=exp)
表示断言自身出现的位置前端能匹配表达式exp
负预测后发断言(?<!exp)
表示断言自身出现的位置前面不匹配表达式exp
零宽断言是先满足匹配其自身表达式后,再获取断言里面的表达式是否满足. 比如(?<=\d{4})\d+(?=\d{4})
去匹配1234567890
,先匹配上\d+
,再判断(?<=\d{4}
和(?=d{4})
,于是最终的匹配结果是56
.
综合以上三条,我们对于正则分组,可以再总结一下
代码 | 描述 |
---|---|
(exp) |
分组匹配,后续可以通过\n 进行反向引用 |
(?<word>exp) |
分组匹配命名,后续可以通过\k<word> 进行反向引用 |
(?:exp) |
只分组,不生成分组编号,也不捕获.相当于只看一眼: 而已 |
(?=exp) |
匹配exp 前面的位置 |
(?!exp) |
匹配后面不是跟的exp 的位置 |
(?<=exp) |
匹配exp 后面的后面 |
(?<!exp) |
匹配前面不是跟的exp 的位置 |
用线-
串,表示把铜钱儿都串起来了,就是正则里面的[a-zA-Z0-9]
这种表示方法,表示a
到z
的全部大小写字母和0
到9
的全部数字.
遇到地头蛇拷问?
,溜回家不再捡垃圾,就是正则里面的遇到?
由默认的贪婪匹配变成了非贪婪(惰性)匹配.
比如对于字符串aabaab
用正则a.*b
会匹配上整个字符串,因为它是贪婪匹配的,里面的.*
会尽可能长的进行匹配.
而a.*?b
则只会匹配aab
.因为对于.*
遇到了?
号,就被变成了非贪婪匹配了.
于是结合前面的匹配模式,我们可以get到
代码 | 描述 |
---|---|
+? |
匹配至少一次,但尽可能少地匹配 |
*? |
匹配0次或者多次,但尽可能少地匹配 |
?? |
匹配零次或者一次,但尽可能少地匹配 |
{n,m}? |
匹配n到m次,但尽可能少地匹配 |
{n,}? |
匹配n次或者更多次,没有m代表无穷尽,但尽可能少地匹配 |
在对echsop进行二开的时候,我很简单地写了一句;
$shop_style = empty(intval($_POST['shop_style'])) ? 1:intval($_POST['shop_style']);
结果一打开页面就报错:
Fatal error: Can't use function return value in write context in ...index.php on line 503
怎么看这代码都没有问题呀,上网搜索才发现,对于empty()
函数,有如下描述
Note: empty() only checks variables as anything else will result in a parse error. In other words, the following will not work: empty(trim($name)).
empty() 只检测变量,检测任何非变量的东西都将导致解析错误!
所以说上面那句正确的写法应该是
$shop_style = empty($_POST['shop_style']) ? 1:intval($_POST['shop_style']);
在javascript中的一个重大的难点就是对this的理解,因为javascript虽然是一种词法作用域的语言,但里面的this却似乎总是在变化中的,我们很难有一个既定的规则来描述在表示的是什么。同时它也未非像它的字面意思那样,表示指向当前函数。
比如
var a = 1;
function test(){
var a = 2;
console.log(this.a);
}
test();//1
从字面上来理解,也许this.a表示test中的a更容易让我们授受,反而结果却并不是这样的。这也就是为什么在ES6中会出现=>
符号的原因了。
借力,也许是最轻松的办法,在这里,也许我们通过借力js中的call方法能够让我们更轻松地弄清楚js中的this在各种情况之下的指向。
一.call的本来面目
我们知道,js中的call就是改变一个对象的引用,它可以修改函数中的this引用。这点就可以让我们很轻松地明白了,你call了什么,this就是代表的什么了。
function show(b){
console.log("a:"+this.a+" b:"+b);
}
var a = 1;
var obj1 = {
a:2
};
var obj2 = {
a:3
};
show.call(obj1,4);//a:2 b:4
show.call(obj2,4);//a:3 b:4
二.没有call,call依然存在
function show(b){
console.log("a:"+this.a+" b:"+b);
}
var a = 1;
var obj1 = {
a:2
};
var obj2 = {
a:3
};
show(4);//a:1 b:4
show.call(window,4);//a:1 b:4
换一个思路来理解这段代码,其实show(4)
也就是相当于show.call(window,4)
;
也就是说,直接执行函数的时候,其实就是call了一个window对象进去。只是我们把它给简写了而已。
三.隐式调用
function show(b){
console.log("a:"+this.a+" b:"+b);
}
var a = 1;
var obj = {
a:2,
show:show
}
obj.show(3);//a:2 b:3
show.call(obj,3);//a:2 b:3
看得出,在对象的执行上下方下中的代码,其obj.show(3)
也就相当于show.call(obj.3)
四.当有了new
var a = 1;
function show(a){
this.a = a
}
var b = new show(2);
console.log(b.a);//2
我们知道,new操作符是新生成一个实例对象出来,这里的操作其实也就是有点类似于var b = {}
,当new了之后,其执行上下文也就是在b中了,所以它的指向也就是b了,和上面第三点的隐似调用非常类似。
我们始终记住一句话:除了加性操作符之外,其它操作符中遇上字符的时候,javascript都会尝试在后台采用Number对字符串进行转换.于是我们可以看到如下的结果:
console.log("15"+5);//"155"
console.log("15"-5);//10
console.log("abc"+5);//"abc5"
console.log("abc"-5);//NaN
console.log("15"*5);//75
console.log("15"/5);3
console.log("5"==5);//ture
console.log("5abc"==5);//false
console.log("23"<"3");//true 没有数字,比较第一个字符"2"和"2"
console.log("23"<33);//true "23"转换为23
console.log("a"<33);//false "a"转换为NaN
从react-router v3到react-router v4确实不能算是升级,而是称得到是迁移了。API的变化发生了翻天覆地的变化了。
所有引入的react-route
都要换成react-route-dom
了
需要引入BrowserRouter
来实现
官方对此讨论也很激烈,remix-run/react-router#4410
官方给出的解决方案就是使用第三方库query-string
来解决
path="goods/(:id)"
需要写成path="goods/:id?"
this.props.params.id
需要改成this.props.match.params.id
https://reacttraining.com/react-router/web/example/auth-workflow
https://github.com/lincenying/mmf-blog-react-v2
扩展阅读
【React Router 从v3升级到v4踩坑之旅】(http://www.jianshu.com/p/e2277aaa53f1)
【react-router v4 使用 history 控制路由跳转】(https://segmentfault.com/a/1190000011137828)
非常喜欢用markdown来写文章,但相信大多数在使用markdown的时候会有和我一样的困扰,那就是插入表格是非常不方便的.markdown本来就是让我们专注于写作的,而表格的书写方式确实是反人类的.今天要写一篇文章,不得不插入一些表格,于是寻找了一些解决方法,能够方便地在markdown里面插入表格.
这是这笨的方法,也是最无奈的方法,就是把表格转换成图片,当然,这也是最不想用 方法了,体积大,语义不强等.
就是借用markdown编辑器自带的插入表格功能.有一部分markdown已经带有表格插入功能了,比如MarkDownPad,但它需要pro版才带有这个功能的,还有比如小书匠,免费的,带有插入表格的功能.
因为我习惯于直接使用平时写代码的sublime text来写,所以对于markdown编辑器没有多试用,所以其它编辑器大家可以自行查看是否支持表格的插入.
如果你像我一样在使用其它编辑器来写markdown,而编辑器又不支持插入表格,同时也不想再安装一个编辑器来单独写markdown,那么可以尝试在线转换的方式.这里推荐几个网站:
PS:我用markdown表格写的一篇博文:《javascript中数据类型转换那点事 》
整理一下自己在项目用使用mockjs时的一些小技巧
@float(1, 100,2,2)
=> 69.32
@image(50x50,@color,@cword)
=>http://dummyimage.com/50x50/f27979&text=量
这个可以巧妙地使用mockjs的function功能
function() {
return new Date().getTime()
}
=> 1507804275240
这个可以巧妙地使用mockjs的正则功能
/PO\d{19}/
=>PO5478965874589658741
在上篇日志里《记录2012年走过的点点滴滴》,我的梦想1是坚持写作,而转眼一到就到了四月份,自己却还没有写下一点东西,今天下午的时候的逼迫下写了一篇连自己都不知所然的《营销,像风一般拂过》,之所以是时间的逼迫是因为想参加一个会议而提交的一份写作,前天收到的写作要求邮件,因为自己对这次写作框架里面的东西理解确实还比较肤浅,实在不知道写些什么,所以一直拖到今天,朋友鼓励说随便写些什么,据内部消息说只要提交了作品都可以参加会议的。所以在今天下午还是逼了一下自己,完成了一篇一千三百多字的写作。
上次我在会议上大胆地做了一次公众承诺,我说从四月份我会每个月至少完成一本书的阅读,然后我会在我的空间里每个月写下一篇日志。在这里,我想我也应该花点时间来先写点东西了,要不然以后写什么东西都会让今天下午一样,写不出一点东西,或者写的全是自己都读不懂的东西。所以在这里决定还是把上个月走过的感悟做一次小小的记录吧。
三月份最大的收获就是学习了林伟贤的《money& you》,这也是改变我最多的一堂课。第一个就是“成功人士走的第一步就与别人不一样“,在这里分享到了一个关于乔吉拉德的故事,他的名字是被列入了世界吉尼斯记录里面的,他从1963到到1977年的十五年时间里从事汽车销售,总共卖了13001辆汽车,而他最辉煌的一年是1973年,他总共卖了一千四百多辆汽车,按照每周五个工作日来算,平均每天要买六部车,而这六部车则是从他早上起床的那一刻起就开始了,他给自己买了一个特大号的闹钟,早上闹钟一响,他就立马跳下床,告诉自己,今天一定会有人为我在别人还在睡懒觉的时候就已经起床而付出代价的。这就是他与大多数人不一样的地方,我们大家早上都爱睡懒觉,我们习惯性的动作就是早上闹钟一响,我们关上闹钟,盖上被子,继续等待闹钟第二次,第三次响起。我也是一直就这样的习惯,早上总是要等两三次闹钟才能慢慢地起来。但这个故事却深深地改变我这的一个坏毛病,从三月份以前,每天早上闹钟一响,我就告诉我自己”成都人士走的第一步就与别人不一样“,于是便立马起床了。就这么一句简单的话,就给了我无穷的动力,让我轻而易举地改变了这个坏毛病,我认为这是我三月份中取得的最大进步。诚然,许多老板,当他们赚了许多钱以后,首先换的是自己的房子,自己的车子,甚至自己的老婆,而不曾想过这一切都将有可失去,只是换掉自己的脑袋,不断去学习,才能不断地进步,不断地创造更大的财富。
有一次,和同事们玩了一个游戏,大家分别写出自己心中认为最重要的十件东西,然后通过游戏不断地一件件地划去,游戏中,大家都写出了许多自己认为最重要的东西,亲情,健康,事业,友谊等等,而我也一直认为健康和快乐是我们人生最重要的东西,然而,我们每个人每天花八个小时甚至更多的时间努力工作,却是为了什么呢?我们高尚地说我们不是为了金钱,我们是为了给家人更好的生活,给未来孩子更好的教育,当我们辛苦地沉浸于工作之中,我们透支着健康,我们透支着与父母交流的机会,我们透支着关爱孩子的时间,一切一切,当我们最终拥有无数的财富时,我们真的是在为着那一个高尚的目标吗?我们的身子垮了,我们都不知道父母早已满脸皱纹了,我们都不知道我们的孩子叛逆成什么样子了,这就是我们所想要的吗?然而,金钱对我们而言也很重要,我们需要为父母,给孩子,给自己一个更美好的生活,我们可以有很美好的梦想,我们要挣下多少钱,我们要买下什么车,我们要买下多大的房子,朋友,这一切都只是我们努力工作奋斗的一个目标,但请不要忘了,我们的目的是什么,我们的目的是为了能够过上更好的生活,能够拥有一个健康的身体,能够拥有一帮很好的朋友,在我们为着目标奋斗时,也请不要忘了我们的目的是什么,目标永远不是一条单一的线,目标是为着目的而服务的,我们脚踏实地的朝着目标前进时,同时也让我们的双手紧紧握住我们的目的。工作累了,放一放,活动一下身子,放假的时候,多出去走动走动一下,常常给家长打打电话,问声温暖,有时间尽量回去多陪陪他们,当我们有了孩子的时候,永远要记得挤也得挤出时间来陪陪他们。记得曾经在一次特训营的活动中,一个小孩子想对他们的父母说“爸爸妈妈,我不希望你们能够挣好多钱,我只希望你们能多花时间来陪陪我“,我们的这一生中,工作不是我们唯一的目的,金钱也不是我们唯一的目标,我们还有着太多太多更重要的时间,我们可以花更多的时间在工作上,但请让我们花更多的心在我们追求的人生上面。记得曾看到过一句非常有感触的话”家不是放钱的地方,家是放心的地方,只有把心放在家里,我们的家庭才会幸福美好“。我们工作再累再辛苦,工资再高,我们挣的不是钱,而是一个幸福美好的家庭。这也是我在三月份学习到感触最深的一点,关于目标与目标的关系。
3月份开始报了驾照,本来打算在外面找陪教的,但后来恰好副总把她以前在驾校报的名额让给我了,所以我也只好在驾校去学校了,不过因为教练和我一同事关系特别好,还没有报名就开始让我练了一次车,谢谢副总,也谢谢小燕,我也开始为着驾照的梦想行进了。
小悦悦的未来储蓄计划,考虑了很多,定期存款,基金,黄金等等,最终还是选定了基金定投,从三月份开始了为期20年的基金定投,也希望未来能够带来不错的收益吧。
三月份还有一件事情,就是特别想摆地摊,也和朋友们去过好几个地方考察,结果都没有找到可以摆地摊的地方,这事后来也就松懈下来了,不知道接下来有没有机会找到摆地摊的地方。
现象:使用文章营销插件之后,分享者把链接分享出去之后,被分享者点击链接之后,能够正常记录阅读数,但记录不到分享记录,同时也导致给分享者设置的奖励无效。
原因分析:在文章营销首页php文件addons/ewei_shop/plugin/article/core/mobile/index.php
中第52行开始代码如下
$myid = m('member')->getMid();
$shareid = intval($_GPC['shareid']);
echo $doShare = $this->model->doShare($article, $shareid, $myid);
在调用doShare
方法之前,获取到的$myid
是为空的,继续分析member
文件中的getMid()
方法可以发现,这里获取到的myid
是读取数据库ewei_shop_member
里面的信息,而如果用户之前没有进入过公众号人人商城,那么也就不会存在这个记录值的,所以返回结果是为空的。
解析办法:在这段代码之前添加方法m('member')->checkMember();
,代码如下:
m('member')->checkMember();
$myid = m('member')->getMid();
$shareid = intval($_GPC['shareid']);
echo $doShare = $this->model->doShare($article, $shareid, $myid);
现象:文章分享出去,别人点击之后,在后台查看分享记录里面的点击者,只能显示未更新用户信息
原因分析:在文件/addons/ewei_shop/core/model/member.php
中第219左右行如下:
public function checkMember($openid = '')
{
global $_W, $_GPC;
if (strexists($_SERVER['REQUEST_URI'], '/web/')) {
return;
}
if (empty($openid)) {
$openid = m('user')->getOpenid();
}
if (empty($openid)) {
return;
}
$member = m('member')->getMember($openid);
$userinfo = m('user')->getInfo($openid);
$followed = m('user')->followed($openid);
其中的$userinfo = m('user')->getInfo($openid);
写法有误,分析getInfo()
方法可以发现,这个方法只会返回openid
这一条信息的,不会返回其它如昵称之类的。
解决办法:使用$userinfo = m('member')->getInfo($openid);
方法,代码如下:
public function checkMember($openid = '')
{
global $_W, $_GPC;
if (strexists($_SERVER['REQUEST_URI'], '/web/')) {
return;
}
if (empty($openid)) {
$openid = m('user')->getOpenid();
}
if (empty($openid)) {
return;
}
$member = m('member')->getMember($openid);
$userinfo = m('member')->getInfo($openid);
$followed = m('user')->followed($openid);
现象:这个也不能说算是bug,只是程序写法中,区别上面一条的是,上面就算用户关注了公众号的,也是没办法获取到点击者信息的,而如果用户未关注公众号,在修复了上面那个问题之后,依然是获致不到用户信息的。
原因分析:在ewei_shop/core/model/member.php
中的getInfo()
方法,这个方法只会读取ewei_shop_member
表和系统用户表里面的用户信息,而如果用户没有关注的话,是没有这些信息的。
解析方法:在这个方法的原来判断代码中,如下
public function getInfo($openid = '')
{
global $_W;
$uid = intval($openid);
if ($uid == 0) {
$info = pdo_fetch('select * from ' . tablename('ewei_shop_member') . ' where openid=:openid and uniacid=:uniacid limit 1', array(
':uniacid' => $_W['uniacid'],
':openid' => $openid
));
} else {
$info = pdo_fetch('select * from ' . tablename('ewei_shop_member') . ' where id=:id and uniacid=:uniacid limit 1', array(
':uniacid' => $_W['uniacid'],
':id' => $uid
));
}
if (!empty($info['uid'])) {
echo "fda";
load()->model('mc');
这里面的if (!empty($info['uid']))
后面添加else
代码块
else{
$info = m('user')-> oauth_info();
$info['avatar'] = $info['headimgurl'];
}
通过完整的OAuth方式获取用户信息。
实例应用:汽车点修网违章查询
应用地址:http://www.car91.cn
实现效果:对接聚合数据,实现违章查询功能,并对查询记录通过cookie保存,方便下次直接点击查询
define(["jquery", 'config', 'jquery.cookie'], function($, config) {
return {
wzInit: function() {
var history = $.cookie("queryHistory");
if (history) {
var historyJson = eval("(" + history + ")");
if (historyJson.length > 0) {//因为是采用前置插入的方式,所以在插入前先判断是否已经插入过,避免多次运行重复生成的情况
if ($(".quick-wz").length > 0) {
$(".quick-wz").remove();
}
var list = "<div class='quick-wz'><p class='quick-wz__tit'>快速查询</p><ul class='quick-wz__list clearfix'>";
for (var i = 0; i < historyJson.length; i++) {
list = list + "<li class='query' data-index='" + i + "'>" + decodeURIComponent(historyJson[i].hphm) + " " + decodeURIComponent(historyJson[i].cityName) + "</li>";
}
list = list + "</ul></div>"
$("#wzForm").before(list);
$(".query").on("click", function() {
var index = $(this).data("index");
$.ajax({
type: "POST",
dataType: "jsonp",
url: config.wzQuery,
data: {
"city": historyJson[index].city,
"hpzl": historyJson[index].hpzl,
"car_province": historyJson[index].car_province,
"hphm2": historyJson[index].hphm2,
"engineno": historyJson[index].engineno,
"classno": historyJson[index].classno,
"registno": historyJson[index].registno,
"hphm": historyJson[index].hphm
},
success: function(e) {
//var e=eval("("+e+")");
var resultcode = e.resultcode;
var info = e.reason;
if (resultcode == '200') {
var html = '<table width="98%" border="1" cellspacing="0" cellpadding="0" style="border:1px solid #f2f2f2">';
html += '<tr>' + '<td height="40"> 时间</td>' + '<td> 地点</td>' + '<td> 违章事项</td>' + '<td> 违章代码</td>' + '<td> 扣分</td>' + '<td> 罚款</td>' + '<td> 是否处理</td>' + '</tr>';
var list = e.result.lists;
if (list.length > 0) {
for (var i in list) {
html += '<tr>' + '<td height="40" width="120"> ' + list[i].date + '</td>' + '<td width="150"> ' + list[i].area + '</td>' + '<td width="280"> ' + list[i].act + '</td>' + '<td width="60"> ' + list[i].code + '</td>' + '<td width="30"> ' + list[i].fen + '</td>' + '<td width="30"> ' + list[i].money + '</td>' + '<td width="30"> ' + list[i].handled + '</td>' + '</tr>';
}
} else {
html += '<tr><td colspan=7 height="40">查询不到该车辆的违章记录</td></tr>';
}
html += '</table>';
$(".wz-result").html(html);
} else if (resultcode == '210') {
alert(resultcode + ":" + info);
} else {
alert(resultcode + ":" + info);
}
$(".wz-btn").val('违章查询').removeAttr("disabled");
}
})
})
}
}
var queryHistory = function() {
var city = $("select[name='city']").val();
var hpzl = $("select[name='hpzl']").val();
var car_province = $("select[name='car_province']").val();
var hphm2 = $("input[name='hphm2']").val();
var engineno = $("input[name='engineno']").val();
var classno = $("input[name='classno']").val();
var registno = $("input[name='registno']").val();
var hphm = $("#hphm").val();
var cityName = $(".selectCitys option:selected").text();
cityName = encodeURIComponent(cityName);
var len = 0;
var canAdd = true;
if (history) {
len = historyJson.length;
$(historyJson).each(function() {
if (this.city == city && this.hphm == hphm) {//判断相同车牌号及相同城市的查询
canAdd = false;
return false;
}
})
}
if (canAdd == true) {
var newCookie = "[";
var start = 0;
if (len > 2) {//最多记录三条历史记录
start = 1;
}
for (var i = start; i < len; i++) {
newCookie = newCookie + "{\"city\":\"" + historyJson[i].city + "\",\"hpzl\":\"" + historyJson[i].hpzl + "\",\"car_province\":\"" + historyJson[i].car_province + "\",\"hphm2\":\"" + historyJson[i].hphm2 + "\",\"engineno\":\"" + historyJson[i].engineno + "\",\"classno\":\"" + historyJson[i].classno + "\",\"registno\":\"" + historyJson[i].registno + "\",\"hphm\":\"" + historyJson[i].hphm + "\",\"cityName\":\"" + historyJson[i].cityName + "\"},";
}
newCookie = newCookie + "{\"city\":\"" + city + "\",\"hpzl\":\"" + hpzl + "\",\"car_province\":\"" + car_province + "\",\"hphm2\":\"" + hphm2 + "\",\"engineno\":\"" + engineno + "\",\"classno\":\"" + classno + "\",\"registno\":\"" + registno + "\",\"hphm\":\"" + hphm + "\",\"cityName\":\"" + cityName + "\"}]";
$.cookie("queryHistory", newCookie, {
expires: 1,
path: "/"
});
}
}
$.ajax({
type: "POST",
dataType: "jsonp",
url: config.wzCity,
data: "",
success: function(e) {
$(".selectProvince").empty();
var html = '<option value="">请选择城市</option>';
var ev = e['result'];
for (i in ev) {
html += "<option value='" + ev[i].province_code + "'>" + ev[i].province + "</option>";
//alert(e[i].city_name);
//$(".selectCitys").append("<option value='"+e[i].city_code+"'>"+e[i].city_name+"</option>");
}
$(".selectProvince").append(html);
}
})
var evc;
$("#selectProvince").change(function() {
var province = $(".selectProvince").val();
$(".selectCitys").empty().append("<option>loading...</option>");
$.ajax({
type: "POST",
dataType: "jsonp",
url: config.wzCity,
data: "province=" + encodeURIComponent(province),
success: function(e) {
$(".selectCitys").empty();
var html = '<option value="">请选择城市</option>';
ev = e['result'];
$.each(ev, function(k, v) {
evc=v;
$.each(v['citys'], function(kk, vv) {
html += "<option value='" + vv.city_code + "' engine='"+vv.engine+"' engineno='"+vv.engineno+"' abbr='" + vv.abbr + "' eclass='" + vv.class + "' eclassno='" + vv.classno + "'>" + vv.city_name + "</option>";
})
$(".selectCitys").append(html);
})
}
})
})
$(".selectCitys").change(function() {
var province = $(".selectProvince").val();
var index=$(this).get(0).selectedIndex-1;
console.log(evc['citys'][index]);
var qinfo=evc['citys'][index];
console.log(qinfo.abbr);
var city = $(this).val();
var abbr = $(".selectCitys").find("option:selected").attr("abbr");
var engine = $(".selectCitys").find("option:selected").attr("engine");
var engineno = $(".selectCitys").find("option:selected").attr("engineno");
var eclass = $(".selectCitys").find("option:selected").attr("eclass");
var eclassno = $(".selectCitys").find("option:selected").attr("eclassno");
if (typeof(abbr) != "undefined") {
$("#car_province").val(abbr);
}
if (engine == '1') {
if (engineno == '0') {
var engineinfo = '全部发动机号';
} else {
var engineinfo = '发动机号后' + engineno + '位';
}
$("input[name=engineno]").attr("placeholder", engineinfo);
$("#engineno").css({
display: ""
});
} else {
$("#engineno").css({
display: "none"
});
}
if (eclass == '1') {
if (eclassno == '0') {
var classinfo = '全部车架号';
} else {
var classinfo = '车架号后' + eclassno + '位';
}
$("input[name=classno]").attr("placeholder", classinfo);
$("#classno").css({
display: ""
});
} else {
$("#classno").css({
display: "none"
});
}
})
$(".wz-btn").click(function() {
$(".wz-btn").val('查询中...').attr("disabled", "disabled");
$(".wz-result").html('正在查询中....');
var hphm = $("#car_province").val() + $(".onlyhm").val();
hphm = encodeURIComponent(hphm);
$("#hphm").val(hphm);
queryHistory();
$.ajax({
type: "POST",
//用POST方式传输
dataType: "jsonp",
//数据格式:JSON
url: config.wzQuery,
//目标地址
data: {
"city": $("select[name='city']").val(),
"hpzl": $("select[name='hpzl']").val(),
"car_province": $("select[name='car_province']").val(),
"hphm2": $("input[name='hphm2']").val(),
"engineno": $("input[name='engineno']").val(),
"classno": $("input[name='classno']").val(),
"registno": $("input[name='registno']").val(),
"hphm": hphm
},
success: function(e) {
//var e=eval("("+e+")");
console.log("ok");
var resultcode = e.resultcode;
var info = e.reason;
if (resultcode == '200') {
var html = '<table width="98%" border="1" cellspacing="0" cellpadding="0" style="border:1px solid #f2f2f2">';
html += '<tr>' + '<td height="40"> 时间</td>' + '<td> 地点</td>' + '<td> 违章事项</td>' + '<td> 违章代码</td>' + '<td> 扣分</td>' + '<td> 罚款</td>' + '<td> 是否处理</td>' + '</tr>';
var list = e.result.lists;
if (list.length > 0) {
for (var i in list) {
html += '<tr>' + '<td height="40" width="120"> ' + list[i].date + '</td>' + '<td width="150"> ' + list[i].area + '</td>' + '<td width="280"> ' + list[i].act + '</td>' + '<td width="60"> ' + list[i].code + '</td>' + '<td width="30"> ' + list[i].fen + '</td>' + '<td width="30"> ' + list[i].money + '</td>' + '<td width="30"> ' + list[i].handled + '</td>' + '</tr>';
}
} else {
html += '<tr><td colspan=6 height="40">查询不到该车辆的违章记录</td></tr>';
}
html += '</table>';
$(".wz-result").html(html);
} else if (resultcode == '210') {
alert(resultcode + ":" + info);
} else {
alert(resultcode + ":" + info);
}
$(".wz-btn").val('违章查询').removeAttr("disabled");
}
})
})
}
}
})
1.该模块加载了jquery.cookie插件,主要是为了方便把json数据保存在cookie中。模块加载进来后,首先判断是存在历史记录,存在的话就进行相应的显示,在最开始书写的时间,生成历史记录图标,我是把所有需要的参数都通过data标签生成在DOM元素上的,例如
list = list + "<li class='query' data-city='"+historyJson[i].city+"'>" + decodeURIComponent(historyJson[i].hphm) + " " + decodeURIComponent(historyJson[i].cityName) + "</li>";
然后在接下来的ajax发送数据的时候,则通过$(this).data('city')
来获取数据,后来发现这样标签内容非常多,而我们在点击时候,其实只需要知道是点击的第一个标签,然后从原始的json数据中去获取对应的值就可以了,于是就产生了上面的data-index的写法,在获取数据时,采用historyJson[index].city就可以了。
1.在$("#selectProvince").change(function()
函数中,通过ajax获取到应用城市的json数据,数据为多层,示例如下:
jQuery19103159100429620594_1428636651525({
"resultcode": "200",
"reason": "成功的返回",
"result": {
"QH": {
"province": "青海",
"province_code": "QH",
"citys": [
{
"city_name": "西宁",
"city_code": "QH_XN",
"abbr": "青",
"engine": "0",
"engineno": "0",
"classa": "1",
"class": "1",
"classno": "0",
"regist": "0",
"registno": "0"
},
{
"city_name": "海东",
"city_code": "QH_HAIDONG",
"abbr": "青",
"engine": "1",
"engineno": "6",
"classa": "1",
"class": "1",
"classno": "0",
"regist": "0",
"registno": "0"
},
{
"city_name": "海西",
"city_code": "QH_HAIXI",
"abbr": "青",
"engine": "1",
"engineno": "6",
"classa": "1",
"class": "1",
"classno": "0",
"regist": "0",
"registno": "0"
},
{
"city_name": "海南",
"city_code": "QH_HAINAN",
"abbr": "青",
"engine": "1",
"engineno": "6",
"classa": "1",
"class": "1",
"classno": "0",
"regist": "0",
"registno": "0"
},
{
"city_name": "玉树",
"city_code": "QH_YUSHU",
"abbr": "青",
"engine": "1",
"engineno": "6",
"classa": "1",
"class": "1",
"classno": "0",
"regist": "0",
"registno": "0"
},
{
"city_name": "黄南",
"city_code": "QH_HUANGNAN",
"abbr": "青",
"engine": "1",
"engineno": "6",
"classa": "1",
"class": "1",
"classno": "0",
"regist": "0",
"registno": "0"
},
{
"city_name": "海北",
"city_code": "QH_HAIBEI",
"abbr": "青",
"engine": "1",
"engineno": "6",
"classa": "1",
"class": "1",
"classno": "0",
"regist": "0",
"registno": "0"
},
{
"city_name": "果洛",
"city_code": "QH_GUOLUO",
"abbr": "青",
"engine": "1",
"engineno": "6",
"classa": "1",
"class": "1",
"classno": "0",
"regist": "0",
"registno": "0"
}
]
}
},
"error_code": 0
})
我需要获取到最底层每个城市的信息,原来的写法是通过$.each
访求循环来获取,后来研究json格式时,明白了大括号表示对象,中括号表示数组,对象可以通过点的方式如obj.result或者key的方法如obj[result]的方法来获取,而数据则是通过下标来获取,如obj[0],上面的json中,每次选择一个省份后,只是返回一个该省份的数据,即result下面的对象只有一个,而且对象名可以通过select的值来获取,因此,不需要通过循环,直接通过下标就可以获取到值了。
ev = e['result'];
var evc=ev[province];
2.继续刚刚$.each里面的循环部分,我还是把返回的json数据写入到了dom标签中,这一步可以继续优化,因为我们可以通过$(this).get(0).selectedIndex
获取到当前选中项的索引值,因为我们可以通过该索引值再去读取json中对应的值就可以了。于是有了上面代码中的初步测试部分
var index=$(this).get(0).selectedIndex-1;
console.log(evc['citys'][index]);
var qinfo=evc['citys'][index];
console.log(qinfo.abbr);
3.再来优化,上面第2条中的返回json数据我是通过定义一个全局变量evc
来方便下面的$(".selectCitys").change(function()
方法访问,我们来继续优化减少全局变量,把$(".selectCitys").change(function()
封装成一个函数,把json返回值通过传值的方式来传递,于是便有了如下写法:
function selectCitys(evc){
$(".selectCitys").change(function() {
var province = $(".selectProvince").val();
var city = $(this).val();
var index=$(this).get(0).selectedIndex-1;
var cityInfo=evc['citys'][index];
var abbr = cityInfo.abbr;
var engine = cityInfo.engine;
var engineno = cityInfo.engineno;
var eclass = cityInfo.class;
var eclassno = cityInfo.classno;
if (typeof(abbr) != "undefined") {
$("#car_province").val(abbr);
}
if (engine == '1') {
if (engineno == '0') {
var engineinfo = '全部发动机号';
} else {
var engineinfo = '发动机号后' + engineno + '位';
}
$("input[name=engineno]").attr("placeholder", engineinfo);
$("#engineno").css({
display: ""
});
} else {
$("#engineno").css({
display: "none"
});
}
if (eclass == '1') {
if (eclassno == '0') {
var classinfo = '全部车架号';
} else {
var classinfo = '车架号后' + eclassno + '位';
}
$("input[name=classno]").attr("placeholder", classinfo);
$("#classno").css({
display: ""
});
} else {
$("#classno").css({
display: "none"
});
}
})
}
4.优化继续,我们发现在上面的代码中,记录cookie及查询的时候,两次手动读取了一样的数据,于是我们可以把这部分先独立出来,接下来传值即可。如果优化如下:
$(".wz-btn").click(function() {
$(".wz-btn").val('查询中...').attr("disabled", "disabled");
$(".wz-result").html('正在查询中....');
var hphm = $("#car_province").val() + $(".onlyhm").val();
hphm = encodeURIComponent(hphm);
$("#hphm").val(hphm);
var carInfo={
"city": $("select[name='city']").val(),
"hpzl": $("select[name='hpzl']").val(),
"car_province": $("select[name='car_province']").val(),
"hphm2": $("input[name='hphm2']").val(),
"engineno": $("input[name='engineno']").val(),
"classno": $("input[name='classno']").val(),
"registno": $("input[name='registno']").val(),
"hphm": hphm
};
queryHistory(carInfo);
$.ajax({
type: "POST",
dataType: "jsonp",
url: config.wzQuery,
data: carInfo,
success: function(e) {
var resultcode = e.resultcode;
var info = e.reason;
if (resultcode == '200') {
var html = '<table width="98%" border="1" cellspacing="0" cellpadding="0" style="border:1px solid #f2f2f2">';
html += '<tr>' + '<td height="40"> 时间</td>' + '<td> 地点</td>' + '<td> 违章事项</td>' + '<td> 违章代码</td>' + '<td> 扣分</td>' + '<td> 罚款</td>' + '<td> 是否处理</td>' + '</tr>';
var list = e.result.lists;
if (list.length > 0) {
for (var i in list) {
html += '<tr>' + '<td height="40" width="120"> ' + list[i].date + '</td>' + '<td width="150"> ' + list[i].area + '</td>' + '<td width="280"> ' + list[i].act + '</td>' + '<td width="60"> ' + list[i].code + '</td>' + '<td width="30"> ' + list[i].fen + '</td>' + '<td width="30"> ' + list[i].money + '</td>' + '<td width="30"> ' + list[i].handled + '</td>' + '</tr>';
}
} else {
html += '<tr><td colspan=6 height="40">查询不到该车辆的违章记录</td></tr>';
}
html += '</table>';
$(".wz-result").html(html);
} else if (resultcode == '210') {
alert(resultcode + ":" + info);
} else {
alert(resultcode + ":" + info);
}
$(".wz-btn").val('违章查询').removeAttr("disabled");
}
})
})
因此,最终优化完毕的全部代码如下:
define(["jquery", 'config', 'jquery.cookie'], function($, config) {
return {
wzInit: function() {
var history = $.cookie("queryHistory");
if (history) {
var historyJson = eval("(" + history + ")");
if (historyJson.length > 0) {
if ($(".quick-wz").length > 0) {
$(".quick-wz").remove();
}
var list = "<div class='quick-wz'><p class='quick-wz__tit'>快速查询</p><ul class='quick-wz__list clearfix'>";
for (var i = 0; i < historyJson.length; i++) {
list = list + "<li class='query' data-index='" + i + "'>" + decodeURIComponent(historyJson[i].hphm) + " " + decodeURIComponent(historyJson[i].cityName) + "</li>";
}
list = list + "</ul></div>"
$("#wzForm").before(list);
$(".query").on("click", function() {
var index = $(this).data("index");
$.ajax({
type: "POST",
dataType: "jsonp",
url: config.wzQuery,
data: {
"city": historyJson[index].city,
"hpzl": historyJson[index].hpzl,
"car_province": historyJson[index].car_province,
"hphm2": historyJson[index].hphm2,
"engineno": historyJson[index].engineno,
"classno": historyJson[index].classno,
"registno": historyJson[index].registno,
"hphm": historyJson[index].hphm
},
success: function(e) {
var resultcode = e.resultcode;
var info = e.reason;
if (resultcode == '200') {
var html = '<table width="98%" border="1" cellspacing="0" cellpadding="0" style="border:1px solid #f2f2f2">';
html += '<tr>' + '<td height="40"> 时间</td>' + '<td> 地点</td>' + '<td> 违章事项</td>' + '<td> 违章代码</td>' + '<td> 扣分</td>' + '<td> 罚款</td>' + '<td> 是否处理</td>' + '</tr>';
var list = e.result.lists;
if (list.length > 0) {
for (var i in list) {
html += '<tr>' + '<td height="40" width="120"> ' + list[i].date + '</td>' + '<td width="150"> ' + list[i].area + '</td>' + '<td width="280"> ' + list[i].act + '</td>' + '<td width="60"> ' + list[i].code + '</td>' + '<td width="30"> ' + list[i].fen + '</td>' + '<td width="30"> ' + list[i].money + '</td>' + '<td width="30"> ' + list[i].handled + '</td>' + '</tr>';
}
} else {
html += '<tr><td colspan=7 height="40">查询不到该车辆的违章记录</td></tr>';
}
html += '</table>';
$(".wz-result").html(html);
} else if (resultcode == '210') {
alert(resultcode + ":" + info);
} else {
alert(resultcode + ":" + info);
}
$(".wz-btn").val('违章查询').removeAttr("disabled");
}
})
})
}
}
var queryHistory = function(carInfo) {
var city = carInfo.city;
var hpzl = carInfo.hpzl;
var car_province = carInfo.car_province;
var hphm2 = carInfo.hphm2;
var engineno = carInfo.engineno;
var classno = carInfo.classno;
var registno = carInfo.registno;
var hphm = $("#hphm").val();
var cityName = $(".selectCitys option:selected").text();
cityName = encodeURIComponent(cityName);
var len = 0;
var canAdd = true;
if (history) {
len = historyJson.length;
$(historyJson).each(function() {
if (this.city == city && this.hphm == hphm) {
canAdd = false;
return false;
}
})
}
if (canAdd == true) {
var newCookie = "[";
var start = 0;
if (len > 2) {
start = 1;
}
for (var i = start; i < len; i++) {
newCookie = newCookie + "{\"city\":\"" + historyJson[i].city + "\",\"hpzl\":\"" + historyJson[i].hpzl + "\",\"car_province\":\"" + historyJson[i].car_province + "\",\"hphm2\":\"" + historyJson[i].hphm2 + "\",\"engineno\":\"" + historyJson[i].engineno + "\",\"classno\":\"" + historyJson[i].classno + "\",\"registno\":\"" + historyJson[i].registno + "\",\"hphm\":\"" + historyJson[i].hphm + "\",\"cityName\":\"" + historyJson[i].cityName + "\"},";
}
newCookie = newCookie + "{\"city\":\"" + city + "\",\"hpzl\":\"" + hpzl + "\",\"car_province\":\"" + car_province + "\",\"hphm2\":\"" + hphm2 + "\",\"engineno\":\"" + engineno + "\",\"classno\":\"" + classno + "\",\"registno\":\"" + registno + "\",\"hphm\":\"" + hphm + "\",\"cityName\":\"" + cityName + "\"}]";
$.cookie("queryHistory", newCookie, {
expires: 1,
path: "/"
});
}
}
$.ajax({
type: "POST",
dataType: "jsonp",
url: config.wzCity,
data: "",
success: function(e) {
$(".selectProvince").empty();
var html = '<option value="">请选择城市</option>';
var ev = e['result'];
for (i in ev) {
html += "<option value='" + ev[i].province_code + "'>" + ev[i].province + "</option>";
}
$(".selectProvince").append(html);
}
})
$("#selectProvince").change(function() {
var province = $(".selectProvince").val();
$(".selectCitys").empty().append("<option>loading...</option>");
$.ajax({
type: "POST",
dataType: "jsonp",
url: config.wzCity,
data: {"province" : encodeURIComponent(province)},
success: function(e) {
$(".selectCitys").empty();
var html = '<option value="">请选择城市</option>';
ev = e['result'];
var evc=ev[province];
$.each(evc['citys'], function(kk, vv) {
html += "<option value='" + vv.city_code + "'>" + vv.city_name + "</option>";
})
$(".selectCitys").append(html);
selectCitys(evc);
}
})
})
function selectCitys(evc){
$(".selectCitys").change(function() {
var province = $(".selectProvince").val();
var city = $(this).val();
var index=$(this).get(0).selectedIndex-1;
var cityInfo=evc['citys'][index];
var abbr = cityInfo.abbr;
var engine = cityInfo.engine;
var engineno = cityInfo.engineno;
var eclass = cityInfo.class;
var eclassno = cityInfo.classno;
if (typeof(abbr) != "undefined") {
$("#car_province").val(abbr);
}
if (engine == '1') {
if (engineno == '0') {
var engineinfo = '全部发动机号';
} else {
var engineinfo = '发动机号后' + engineno + '位';
}
$("input[name=engineno]").attr("placeholder", engineinfo);
$("#engineno").css({
display: ""
});
} else {
$("#engineno").css({
display: "none"
});
}
if (eclass == '1') {
if (eclassno == '0') {
var classinfo = '全部车架号';
} else {
var classinfo = '车架号后' + eclassno + '位';
}
$("input[name=classno]").attr("placeholder", classinfo);
$("#classno").css({
display: ""
});
} else {
$("#classno").css({
display: "none"
});
}
})
}
$(".wz-btn").click(function() {
$(".wz-btn").val('查询中...').attr("disabled", "disabled");
$(".wz-result").html('正在查询中....');
var hphm = $("#car_province").val() + $(".onlyhm").val();
hphm = encodeURIComponent(hphm);
$("#hphm").val(hphm);
var carInfo={
"city": $("select[name='city']").val(),
"hpzl": $("select[name='hpzl']").val(),
"car_province": $("select[name='car_province']").val(),
"hphm2": $("input[name='hphm2']").val(),
"engineno": $("input[name='engineno']").val(),
"classno": $("input[name='classno']").val(),
"registno": $("input[name='registno']").val(),
"hphm": hphm
};
queryHistory(carInfo);
$.ajax({
type: "POST",
dataType: "jsonp",
url: config.wzQuery,
data: carInfo,
success: function(e) {
var resultcode = e.resultcode;
var info = e.reason;
if (resultcode == '200') {
var html = '<table width="98%" border="1" cellspacing="0" cellpadding="0" style="border:1px solid #f2f2f2">';
html += '<tr>' + '<td height="40"> 时间</td>' + '<td> 地点</td>' + '<td> 违章事项</td>' + '<td> 违章代码</td>' + '<td> 扣分</td>' + '<td> 罚款</td>' + '<td> 是否处理</td>' + '</tr>';
var list = e.result.lists;
if (list.length > 0) {
for (var i in list) {
html += '<tr>' + '<td height="40" width="120"> ' + list[i].date + '</td>' + '<td width="150"> ' + list[i].area + '</td>' + '<td width="280"> ' + list[i].act + '</td>' + '<td width="60"> ' + list[i].code + '</td>' + '<td width="30"> ' + list[i].fen + '</td>' + '<td width="30"> ' + list[i].money + '</td>' + '<td width="30"> ' + list[i].handled + '</td>' + '</tr>';
}
} else {
html += '<tr><td colspan=6 height="40">查询不到该车辆的违章记录</td></tr>';
}
html += '</table>';
$(".wz-result").html(html);
} else if (resultcode == '210') {
alert(resultcode + ":" + info);
} else {
alert(resultcode + ":" + info);
}
$(".wz-btn").val('违章查询').removeAttr("disabled");
}
})
})
}
}
})
一个客户的项目中,在表中有个字段card_no
是保存的会员编号,客户采用的是四位数的方式来记录的,0001~9999这样的,字段采用的字符串的方式来保存的。
现在客户有一个这样的需求,在一个搜索应用中,中需要输入会员的后面的编号,比如0001
号的,只需要输入1
就可以直接搜索出这个会员。
当然,我们可以在采用sql搜索之前通过程序进行这个值的判断补全。
而更简单的方法就是通过mysql的LPAD函数。
LPAD(str,len,padstr)
使用这个函数的结果就是对str字符串判断len长度,如果超过就从左边开始截取指定len的长度,如果不足len长度就使用padstr在str前面进行补全。
对于客户这需求,我们就可以写出这样的sql语句了。
SELECT * FROM ecs_users WHERE card_no = LPAD(88,4,0);//88为传入的数值
同时还有一个函数RPAD,使用方法也是一样的。这个只是进行后补全的。
我们使用git很多时候都是需要配合github同步使用的,当我们在本地使用了git管理代码,并进行修改,提交之后,接下来我们就想把代码部署到github上面去,整个流程也是非常简单的。
首先,我们登录github之后,点击右上角的new repository
,来新建一个分支,接下来就要求我们级分支起一个名字,这里比如我起一个mytest
的名字,写上分支的描述,如下图
接下来就是我最喜欢github的一点了,因为他已经非常直观地告诉了我们要怎么做了,如下图
echo "# mytest" >> README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin https://github.com/buxuku/mytest.git
git push -u origin master
这里教我们在本地创建一个新的仓库,并建立一个README.md的文件,然后提交并推送到github上面
git remote add origin https://github.com/buxuku/mytest.git
git push -u origin master
这里就是教我们在本地已经存在的仓库里面把这个远程地址添加进来,然后把代码提交上去。
整个过程就是这么的简单。比如我提交的一份代码执行图:
从图中也看到了,github有时候可能会出现网络不稳定或者被墙的情况。
我们在使用git进行远程仓库操作的时候,如果使用https的方式的话,总是会在每次操作的时候都要求输入用户名密码,如果我们固定会在某台电脑上面使用的话,就可以选择用SSH的方式来避免每次输入用户名和密码。
SSH是一个身份认证系统,它是用来识别你的身份,而不是用来识别客户端的,
因此在电脑上面,我们要用我们自己的身份来生成一份公私的密钥对。
打开git bash,运行ssh-keygen -t rsa -C '[email protected]'
,这里面的[email protected]
就是你自己的邮箱,如下图:
之后就会在我们的用户目录下面生成一个.ssh
的文件夹,里面会出现两个文件,id_rsa
和id_rsa.pub
,后面这个就是我们的公钥文件,我们需要把这个公钥文件设置到github上面。
github上面有两个设置的地方,一个是账户设置里面里面的SSH Keys
,这里面添加话,则可以通过私钥来控制整个账户下面的项目。
另一个就是具体项目设置里面的Deploy keys
,在这里面添加key则只能控制这一个项目,如果勾选里面的Allow write access
则可以对项目进行PUSH操作。
设置了key之后,我们打开本地项目中的.git
文件夹,修改里面的config
文件,把[remote "origin"]
里面的url修改成我们项目的SSH地址就可以了。接下来我们就可以轻松地使用SSH进行操作了。
需要注意的是,假如账户[email protected]在github上面注册了,并创建了仓库,我们在本地为账户[email protected]创建了SSH,而账户B是没有注册github的,我们把B的公钥添加到账户A的账户级的SSH Keys
中,那么账户B同样能够对A的所有仓库进行PUSH的操作的。而项目级的Deploy key
中,则同样对该项目具有PUSH操作权限。
一个公钥只能应用于一处,即要么是账户级,要么是项目级,项目级也只能应用于一个项目中,不能多个项目使用同个公钥。
有一个例子可以很好地展示javascript中的语法作用域的特性:
function foo(){
console.log(a);
}
function bar(){
var a = 3;
foo();
}
var a = 2;
bar();//2
同大多数语言一样,javascript也是采用的词法作用域.
作用域分为两种,一种就是词法作用域(Lexical Scope),一种是动态作用域(Dynamic Scope).
词法作用域是属静态作用域模型,也就是变量,函数的声明,引用都是在编译阶段就完成了,它们不会受到执行上下文,调用方式的影响.比如上面定义的函数foo,它里面对a的RHS查询,是基于它本身内部变量定义,函数参数以及全局变量中进行查询的,所以就算在bar中进行执行,它的静态模型也是不会被改变的.因此是正常地输出2.
相反的,如果是动态作用域,则RHS查询是在当前的执行环境中进行查询,所以它是动态改变的,它会受到执行上下文,调用方式的改变,如上面这个例子,如果采用动态作用域的方式来执行的话,在bar内部执行foo,foo对a进行RHS查询时,自己内部作用域中进行查询,没有查询到,继续向当前执行环境中进行查询,于是查询到了bar中定义的a,所以最终输出了结果3.
总结下来就是:
{% cq %} 遇到既不是形参也不是函数内部定义的局部变量的变量时,词法作用域的函数去函数定义时的环境中查询,动态域的函数到函数调用时的环境中查询。 {% endcq %}
function foo(str){
eval(str);
console.log(a);
}
function bar(){
foo("var a = 3");
}
var a = 2;
bar();//3
我们知道,javascript中分词阶段,只会对变量进行声明.在对foo进行分词时,首先会忽略eval内部的执行代码,这个时候对a的静态引用还是基于函数内部定义,参数和全局变量的,但这个时候没有进行"a = 2"这个赋值操作,因为它应该是在执行环节进行的.
接下来在执行环节就会出现问题了,因为eval是立即执行内部代码的,我们在"a = 2"赋值操作之前,执行了"var a = 3"这句操作,它覆盖了原来的"var a = 2"操作,所以完成了一次词法作用域的欺骗,最终输出结果3.
var a = 10;
function go(obj) {
with(obj) {
a = 2;
}
}
var foo = {
a: 1
},
bar = {
b: 1
};
go(foo);
console.log(foo.a); // 2
console.log(a); //10
go(bar);
console.log(bar.a); // undefined
console.log(a); // 2(对头,a变成了一枚金闪闪的全局变量)
我们常常不喜欢with的原因就是这样的:当bar对象中没有a这个属性的时候,with不是给它新增一个a的属性,而是在全局变量中新增了一个属性,也正因为这样的特性,导致它改变了原来定义的"var a = 10"这个值.
和之前的一篇文章《不使用var就不是声明变量》一样,这里不是声明一个变量,而是给window新增加了一个属性.通过代码可以测试出.
function go(obj) {
with(obj) {
a = 2;
}
}
var bar = {
b: 1
};
go(bar);
console.log(bar.a); // undefined
console.log(a); //2
console.log(delete a); //true
备注:本篇文章是五一节的时候在家写的,当时保存在家里的电脑上面,家里的电脑没联网了,所以一直没有发布,直到端午节的时候回家再次打开一看,虽然已经有点不堪入目,本想重新修改,但想下还是算了吧,毕竟也是那个时候最真实的感悟,便还是发上来吧,以表纪念。
四月是成长的一月,也是思维上产生了巨大转变的一个的月。
曾经天天经常说我是“三分热情”,因为以前不管我产生一个什么想法,最开始是充满了无限的热情,可是过不了一段时间就放弃了。现在我发现之所以是这样的,那是因为改变自己的决定不够。当自己下定决定要去改变自己的时候,当这份决心越强烈,就越能坚持着去行动。
我相信我现在做出一份决定时,我就必将要产生强烈的一份愿望。而且我也将把它转变成一份强大的执行力,成为生活中的一部分,而逐渐成为一种习惯。四月份我看完了两本富爸爸的书,以及每天练习钢笔字。与其说是坚持下来,准确地说应该是习惯下来了。当自己决定每个月至少看完一本书,要练习钢笔字的,我便很自然地把它变成了每天晚上的一部分。正如前几天所发的微博一样,不管晚上因为其它什么原因弄得很晚,我都依然会坚持看一会儿书,然后写一页字。哪怕量可以少一点,但我会每天坚持必须去执行。当这一切逐渐形成习惯之后,每天没看书写字,反倒会觉得心中少了点什么。
我们总是感慨时间过得真快,却真没有想到时间就是在我们不经意间悄悄从指缝中流过了。当我决定每个月至少看一本书时,一个月执行下来,却是发现我是很轻松地就看完了两本书。当每天晚上我不再一吃完饭就打开电脑,无聊地刷下微博,刷下空间时,静下心来,打开台灯,翻阅几十页书纸,写上一两篇字,却是发现这短短的一两个小时,却是无限收获,无限充实的两个小时。好好地抓住一两个小时时间,我们就可以惊奇地发现,一天中就因为我们多了这短短的两个小时,却是多做了许多事情。其实说起来很简单,大家都明白我们的时间都是在无聊中慢慢地流失掉了。关键问题是我们不明白我们除了工作,我们还可以做什么。似乎工作就成了我们唯一的生活,下班后,我们可能就只能是无聊地看电视,上网,打游戏。正如网上一名老太太说的一样,现在的年轻人做着他们八十岁都还会做的事,那我们的生活色彩,我们的追求又在哪里呢?也许你可以感受一份书香墨气的清香,也许你可以收获朋友畅谈的一份轻快,也许你可以获取城市夜灯下的繁荣......时间之所以无聊,只是因为我们自己本身太无聊。当给以自己生活足够的方式时,人生也不再寂寞,成长也不再止步。
不知道是生活经历多了,还是因为慢慢在书香墨气中熏陶着一份宁静,我发现我对生活,对事情开始变得从容淡定了。很多时候,我们之所以急燥,之所以发脾气,往往都是在以这样一种无用的方式来回避而已。快乐其实很简单,就如一同事给我们分享的一个故事那样,他掉了钱,并不会觉得伤心悲痛,他会想,也许拾得这钱的人正需要这笔钱,也许他正准备犯法去偷钱去抢钱,而我这钱可能就救了他一命。我觉得这就是一种心境,一种淡定。曾经天天不小心被别人偷了几百元,她急得恨不得把那个人碎石万断,而我在电话里却是很淡定地安慰她掉了就掉了吧,钱又不是挣不来的,何必还要让自己去伤心呢。自己的工作接触的事情是比较繁多而杂乱的,以前我在内心老是犯急燥,因为我最怕杂乱了,尤其是我正在做一件事时突然又冒出另外一件事来,而现在我开始学会淡定去面对了,自己把所有的事情整理好,一件一件地去完成,我发现我会有很充足的空间时间去收获那份属于我自己的快乐。前几天和几个兄弟们一起聊天,一兄弟说我现在不敢破釜沉舟了,就是在没的找好工作前,我是不敢裸辞,我说我敢,我是真的敢,这一是一份激情,不是一份狂热,只是一份淡定,因为我相信,裸辞之后,面对接下来可能出现的各种状态,我都有足够强大的心态去面对与应付,曾经离开德阳,我是裸辞,当年在温州,天天睡地铺吃泡菜,面对雅安地震,我还活着,面对那些成功人士,我还年轻,面对那些高层管理,我还愿意去学习,我还有什么不能去面对的呢?不淡定者是因为我们在内心一直告诉自己“我可不行,我可办不到”,而我们应该告诉自己的是“我应该怎么去做?”
关于金钱的观念是我这个月思维转变最大的一次,虽然以前我也明白通过理财可以让自己的财富倍增,但我没有理解到金钱的深层次运作机制。两本富爸爸的书,以及现金流游戏深深地改变了我对金钱的看清。虽然我目前的状况是已经跳出了老鼠赛跑,但这个月的学习则是让我站在了整个人生的高度来看待我们的金钱。
我们都知道,我们金钱分为收入和支出,而我们收入其实应该是分为劳动收入和被动收入,这是很重要的一个概念,因为衡量我们是否是获得了财务自由,就是看我们的被动收入能否超过我们的支出,只有当我们的被动收入已经超出了我们的支出,我们才能称得上实现了财务自由。被动收入就是我们工作之外的收入,可以不用自己去工作就能源源不断地产生的收入,这就是一个完全自动的印钞机,比如我们的存款利息,股票,期货,版权,房租等等都是我们的被动收入。
这里讨论财务自由由我们的工资没有关系,只和我们的被动收入及支出有关,有些人也许工资很高,但他可能没有一分钱的被动收入,这是一个很容易想象的现象,万一那天公司裁员或者倒闭了,自己就将面临着没有任何收入来源了。我们又不得不马上顶都会一份压力立马去寻找其它工作,找到工作后,我们不得不小心翼翼地拼命把握住这份工作,因为一旦失去工作,我们又将失去收入来源,又不得不再去寻找下一份工作,周而复始,我们就这样不断地被工作牵着团团转了。
相反,当我们实现财务自由之后,我们则是另外一种生活状态,因为我们已经有了被动收入,而这个被动收入不是依附于自己的工作,所以哪怕自己美美地睡上一天觉,也是依然有稳定的收入流进来,而且这份收入是可以满足自己的支出需要的。这个时候,我们就不再是工作的奴隶了,因为我们有了可以选择工作的权利,我们可以很轻松去去选择自己喜欢的工作,也可以很大胆地放弃自己不喜欢的工作。
既然财务自由与我们的工作收入没有关系,那我们的工作收入代表着什么呢?工作收入就是我们的一份储蓄,市场的变化,物价的上涨,支出结构的变化等等因素都可能导致我们的支出总额不断地增多,而我们要不断地维持财务自由,我们就需要不断地提高自己的被动收入。提高自己的被动收入一方面是加大以前项目的投资额度,别一方面是增加其它被动收入来源,这就是需要寻找与把握机会,只有当我们拥有充足的储蓄时,我们才会拥有更多的机会把把握住能够产生被动收入的机会。我们不断地提高我们的工作收入,也就是在不断地储备被动收入的能力。
在前端发过迁移React-router v4的踩坑记录:《react-route从v3迁移到v4(折腾+踩坑)》,整个升级需要有很多坑,但我觉得都还比较好,可以一步一步地去填这些坑,不外乎就是一个工作量的问题。
但在最后解决我之前项目中处理404页面的时候,遇到了一个头痛的问题:
在我的项目中,所有页面共用了一套Layout,除了404页面是完全独立的一套Layout,这个实现在v3中是很方便的,比如用下面这套route配置就可以完成了
<Route path="/" component={Layouts} >
<IndexRoute component={Home}/>
<Route path="/goods/add" component={GoodsAdd}/>
</Route>
<Route path="*" component={NotFoundPage} />
但到了react-router v4就行不通了,因为在v4中,要实现这样的Layout,path="/"的匹配规则就不能添加exec
了,而对于404的那个链接,刚会首先进入path="/"这个里面去,从而导致Layouts的渲染。不管是否使用Switch
均无法解决这个问题。
一直在想办法解决404页面的问题,刚网上搜索出来的解决方法均一样:
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
<Route component={NoMatch}/>
</Switch>
这个确实是一个实现方式,但却不能实现我需要的Layout功能了,Home和About是完全独立的两个页面当然是没问题的。
后来转念一想,我的需求不再于404页面的问题,而在于如何实现多套Layout的问题,如何通过简单的方式来实现多套Layout,那么404页面做为完全独立的一个页面,也就能够简单的实现的。
因为我大多数页面都会是同一个Layout,而极个别的可能会是另外的一套,所以我还是想通过v3的方式那样,通过根路由来渲染这个主要的Layout,这样我不用再去处理那大部分的,而只需要去处理那及个别的即可。
后来通过阅读react-router v4的文档,发现有一个render的api,通过render的方式,我们就有了更多的自定义渲染控制权了,包括怎么渲染,额外传递一些参数等等。
有了这个api,问题就好解决了,我们可以自己再封装一个实现router v4中onEnter功能的高组组件类似的一个组件,通过这个组件来决定利用哪个Layout来渲染这个页面,这样就可以实现任意多套的Layout了。
最终我封装了一个ARoute
的组件,这个组件实现onEnter的功能,同时加载Layout.
import React from 'react'
import Route from 'react-router-dom/Route'
import Redirect from 'react-router-dom/Redirect'
import {Layout} from 'antd'
import Layouts from './Layout'
import SideBar from './SideBar'
import './style/layout.less';
const {Content} = Layout;
const SideBarLayout = ({component: Component, ...rest}) => {
const {layout} = rest;
return (
<Route {...rest} render={matchProps => (
!layout || layout=="main"
?<Layouts>
<Layout className={!layout?" ant-layout-has-sider":""}>
{!layout?<SideBar />:null}
<Content
style={{
minHeight: 680,
overflow:"visible",
width:!layout?"1028px":"1200px"
}}
>
<Component {...matchProps} />
</Content></Layout>
</Layouts>
:<Component {...matchProps} />
)} />
)
};
export default ({ component: Component, ...rest }) => {
const {path}=rest;
const status=sessionStorage.getItem("suppliers");
const out=(!status || JSON.parse(status).status<6);
const inEnter=path && path.indexOf("enter")>-1;
const entered=status && JSON.parse(status).status==7;
return (!out && !inEnter) || (out && inEnter)? <SideBarLayout {...rest} component={Component} /> :entered && inEnter?<Redirect to={{ pathname: '/' }} />: <Redirect to={{ pathname: '/enter/write/fullPage' }} />
}
我的路由就可以写成这样的
<BrowserRouter>
<Switch>
<ARoute exact path="/" component={Home} />
<ARoute exact path="/goods/add" component={GoodsAdd} />
<ARoute layout="404" component={NotFoundPage} />
</Switch>
</BrowserRouter>
我原本是想把这里面的所有ARoute封装在一起,但发现这样路由会去匹配里面的所有ARoute.
都是一些正在看和准备看的前端书籍,偶然在一位大牛的博客里找到整理出来的。
1 JavaScript权威指南(第6版):号称javascript圣经,前端必备;前端程序员学习核心JavaScript语言和由Web浏览器定义的JavaScript API的指南和综合参考手册;
2 JavaScript高级程序设计(第3版) :前端必备书,如果你想真正进入前端世界,这是一本不可多得的进阶书,没什么好说的,必须细细品读;
3 JavaScript语言精粹 :这是一本值得任何正在或准备从事JavaScript开发的人阅读,并且需要反复阅读的js书籍;
4 基于MVC的JavaScript Web富应用开发 :这不是一本适合初学者看的书籍,更适合具有一定前端开发经验的从业人员看的框架书,如果你想构建一个复杂的前端应用,你会如获至宝;
5 JavaScript DOM编程艺术(第2版):好书主要是dom操作和兼容方面的知识,值得一看;
6 JavaScript经典实例 :可以看看,主要是有关javascript一些典型小工具,有些剖析的并不深入,总体来说可以看看;
7 JavaScript设计模式 :写js不难。如何编写优美、结构化和可维护的代码呢?反复的揣摩这本书吧;
8 JavaScript编程精解 : 了解一下可以;
9 JavaScript模式 : 又一本设计模式的好书,如果你想让自身的Javascript技巧提高到一个新层次,成为专业的开发人员和程序员,反复阅读吧;
10 JavaScript高效图形编程: 是一本具有很强实操性的JavaScript图书,主要涉及JavaScript性能优化、高级UI设计、Web游戏开发、面向移动设备的开发、图形编程知识等
11 JavaScript RIA开发实战——最佳实践、性能、表现:主要介绍如何采用最合理的方式为RIA编写可靠的、易于维护的HTML、CSS和JavaScript代码,以及如何使用Ajax技术在后台实现浏览器与Web服务器的动态通信。可以细细读一下;
12 高性能JavaScript : 又一本好书,涵盖了当今JavaScript开发者需要了解的所有性能问题,毫无疑问,它已加入我的性能最佳实践列表;值得细细品读并实践;
13 悟透JavaScript(美绘本): 这是一本可以让你轻松加愉快的阅读的一本好书,读完你可能会领悟:哦原还可以这样;
14 Ajax权威指南 : 详细的展示了ajax技术的发展以及应用,对于了解ajax技术很不错的一本详尽书籍;
15 Node.js开发指南 : Node.js是一种新兴的开源技术,它将JavaScript从Web浏览器移植到常规的服务器端,使用Chrome的V8虚拟机来解释和执行JavaScript代码,能用于构建高性能、高可扩展的服务器和客户端应用,以实现真正“实时的Web应用”;
16 Node Web开发 : 雅虎架构师精准解读最炙手可热的Web开发技术;
17 jQuery Mobile权威指南:是系统学习jQuery Mobile的权威参考书;
18 数据可视化实战:使用D3设计交互式图表 :web矢量图类库d3.js的工具书;
19 jQuery权威指南 : 学习jquery的入门书籍;
20 精彩绝伦的jQuery : 名字起的不错,能忽悠不少人,入门级书籍;
21 锋利的jQuery : 不错的一本jquery应用书籍;
1 HTML 5与CSS 3权威指南 :html5和css3入级好书;详尽讲解了HTML5与CSS 3的所有功能和特性;
2 HTML5移动Web开发指南:介绍了一下移动端的web开发技术,以及一些移动端框架:QueryMobile、Sencha Touch,以及PhoneGap;
3 响应式Web设计:HTML5和CSS3实战 :有关响应式设计的知识并不是很多,大量篇幅写了html5和css3,这本书比较一般;
4 HTML5程序设计(第2版) : 很全面的介绍了一下html5技术,前端人员都应该看一看;
5 编写高质量代码:Web前端开发修炼之道 : 不可多得的一本前端开发规范书,前端开发人员的必读书;
6 精通CSS:高级Web标准解决方案 :css兼容性解决方案汇总,好书;
7 CSS禅意花园 :主要的Web设计原则以及它们运用的CSS布局技巧;
8 CSS权威指南 : css经典工具书;
9 高性能网站建设进阶指南:Web开发者性能优化最佳实践 : 好书啊!网站性能优化,浏览器加载渲染详细解析;
10 网站重构——应用Web标准进行设计 : 前端开发人员必读书,一本可以帮助网页设计师快速了解和掌握web标准设计的书;
11 变幻之美 DIV+CSS网页布局揭秘 : 详细的介绍了从效果图到web布局实现的整个过程;
12 HTML5 Canvas基础教程 : canvas入门书籍;
很简单的一个拖拽效果,没有判断窗口溢出的现象,主要还是加深this的应用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
#drag{
width: 40px;
height: 40px;
position: absolute;
background-color: red;
cursor:pointer;;
}
</style>
</head>
<body>
<div id="drag">
</div>
</body>
<script>
var drag = document.getElementById("drag");
drag.onmousedown = function(ev){
var ev = ev || window.event;
var x = ev.clientX-this.offsetLeft;
var y = ev.clientY-this.offsetTop;
document.onmousemove = function(ev){
var ev = ev || window.event;
var nowx= ev.clientX-x;
var nowy= ev.clientY-y;
drag.style.left = nowx + 'px';
drag.style.top = nowy + 'px';
}
document.onmouseup = function(){
document.onmousemove = null;
document.onmouseup = null;
}
}
</script>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
#drag{
width: 40px;
height: 40px;
position: absolute;
background-color: red;
cursor:pointer;
}
</style>
</head>
<body>
<div id="drag">
</div>
</body>
<script>
function Drag(name){
this.drag = document.getElementById(name);
this.x=0;
this.y=0;
}
Drag.prototype.init = function(){
var This = this;
this.drag.onmousedown = function(ev){
var ev = ev || window.event;
This.fnDown(ev);
return false;
}
}
Drag.prototype.fnDown = function(ev){
var This = this;
this.x = ev.clientX-this.drag.offsetLeft;
this.y = ev.clientY-this.drag.offsetTop;
document.onmousemove = function(ev){
var ev = ev || window.event;
This.fnMove(ev);
};
document.onmouseup = this.fnUp;
}
Drag.prototype.fnMove = function(ev){
var nowx= ev.clientX-this.x;
var nowy= ev.clientY-this.y;
this.drag.style.left = nowx + 'px';
this.drag.style.top = nowy + 'px';
}
Drag.prototype.fnUp = function(){
document.onmousemove = null;
document.onmouseup = null;
}
var drags = new Drag('drag');
drags.init();
</script>
</html>
和创建对象的几个方式类似,javascript也产生了几种不同的实现继承的方式.
<script>
function SuperType(){
this.property = true;
this.color = ["yellow","red"];
this.name = "xiaodong";
}
SuperType.prototype.getSuperTypeValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
/* SubType.prototype.getSubValue = function(){ //添加属性和方法必须在替换原型之后
return this.subproperty;
}*/
SubType.prototype = new SuperType();
var instance = new SubType();
console.log(instance.getSuperTypeValue());//true
console.log(instance instanceof Object);//true 原型和实例的关系
console.log(instance instanceof SubType);//true
console.log(instance instanceof SuperType);//true
console.log(Object.prototype.isPrototypeOf(instance));//true
console.log(SuperType.prototype.isPrototypeOf(instance));//true
console.log(SubType.prototype.isPrototypeOf(instance));//true
/*console.log(SubType.getSubValue());//TypeError: SubType.getSubValue is not a function*/
SubType.prototype.getSubValue = function(){ //添加属性和方法必须在替换原型之后
return this.subproperty;
};
console.log(instance.getSubValue());//false
/* SubType.prototype = { //不能再使用对象字面量的方式来重写原型,这样会断开最初的原型链
getSubValue : function(){
return this.subproperty;
}
}
var instance2 = new SubType();
console.log(instance2.getSuperTypeValue());//TypeError: instance2.getSuperTypeValue is not a function*/
//原型链继承的问题 :包含超级类的属性值 并且对实例中引用类型的值的修改,会反映到超级类的所有实例上面 并且子类的实例没有办法向超级类传递参数 即使通过子类的参数间接传递也不行
var instance2 = new SubType();
instance.name = "tiantian";
//instance.color = ["black"];
instance.color.push("black");//如果使用instance.color=["black"],就不会影响到其它实例,因为改变了对象的引用
console.log(instance2.color);// ["yellow", "red", "black"]
console.log(instance2.name);//xiaodong
</script>
<script>
function SuperType(name){
this.color = ["yellow","green"];
this.name = name;
this.getName = function(){
return this.name;
};
}
function SubType(name){
SuperType.call(this,name);
}
var instance = new SubType("xiaodong");
instance.color.push("black");
var instance2 = new SubType();
console.log(instance.color);//["yellow", "green", "black"]
console.log(instance.name);//xiaodong
console.log(instance2.color);// ["yellow", "green"]
console.log(instance.getName == instance2.getName);//false 方法没有达到共用
//借用构造函数解决了属性值共享的问题,以及实例向超级类传递参数的问题. 缺点 方法也是在函数中定义 无法解决函数共用的问题
</script>
<script>
//最常用的继承模式
function SuperType(name){
this.name = name;
this.color = ["yellow","green"];
}
SuperType.prototype.getName = function(){
return this.name;
};
function SubType (name,age){
SuperType.call(this,name);
}
SubType.prototype = new SubType();
SubType.prototype.constructor = SubType;
SubType.prototype.getAge = function (){
return this.age;
};
var instance = new SubType("xiaodong",22);
instance.color.push("black");
var instance2 = new SubType("tiantian",11);
console.log(instance.color);// ["yellow", "green", "black"]
console.log(instance2.color);// ["yellow", "green"]
console.log(instance.getName == instance2.getName);//true 实现了方法共享
console.log(instance.getAge == instance2.getAge);//true 实现了方法共享
</script>
<script>
//基本模型 缺点:引用类型值共享
function object(o){//o传入一个基本对象
function F(){}
F.prototype = Object(o);
return new F();
}
var person = {
name:"xiaodong",
color:["yellow","green"],
getName:function(){
return this.name;
}
};
var person1 = object(person);
person1.name = "tiantian";
person1.color.push("black");
var person2 = object(person);
console.log(person1.name);//tiantian
console.log(person2.name);//xiaodong
console.log(person1.color);// ["yellow", "green", "black"]
console.log(person2.color);// ["yellow", "green", "black"]
console.log(person1.getName == person2.getName);//true 实现了函数共享
//ES5中新增了Object.create()方法来更加规范地实现这一类继承
var newperson = {
name:"xiaodong",
color:["yellow","green"],
getName:function(){
return this.name;
}
};
var newperson1 = Object.create(newperson);
newperson1.name = "tiantian";
newperson1.color.push("black");
var newperson2 = Object.create(newperson);
console.log(newperson1.name);//tiantian
console.log(newperson2.name);//xiaodong
console.log(newperson1.color);// ["yellow", "green", "black"]
console.log(newperson2.color);// ["yellow", "green", "black"]
console.log(newperson1.getName == newperson2.getName);//true 实现了函数共享
</script>
<script>
function object(o){
var F = function(){};
F.prototype = o;
return new F();
}
function creatAnother (origin) {
var clone = object(origin);
clone.sayHi = function(){
return "hi";
};
return clone;
}
var origin = {
name:"xiaodong",
color:["yellow","green"],
getName:function(){
return this.name;
}
};
var other = creatAnother(origin);
other.name = "tian";
other.color.push("black");
var other2 = creatAnother(origin);
console.log(other.name);//tian
console.log(other.color);// ["yellow", "green", "black"] 引用类型共享
console.log(other2.name);// xiaodong
console.log(other2.color);// ["yellow", "green", "black"]
console.log(other.getName == other2.getName);//true
console.log(other.sayHi == other2.sayHi);//false 使用寄生式继承来为对象添加函数,函数不能共享而降低效率
</script>
<script>
/*//组合式继承的缺点:超类会调用两次
function SuperType(name){
this.name = name;
this.color = ["yellow","green"];
this.age = 23;
};
SuperType.prototype.getName = function(){
return this.name;
}
function SubType(name,age){
SuperType.call(this,name);//第二次调用超类
this.age= age;
}
SubType.prototype = new SuperType();//第一次调用超类
SubType.constructor = SubType;
SubType.prototype.getAge = function(){
return this.age;
}
var instance = new SubType("xiaodong",23);
console.log(instance.getName());//xiaodong*/
//寄生组合式继承 只调用一次SuerType构造函数,同时避免了在SubType.prototype上面创建不必要的,多余的属性.同时原型链保值不变.
function inheritPrototype(subType,superType){
var prototype = Object(superType.prototype);//创建对象
prototype.constructor = SubType;//增强对象
subType.prototype = prototype;//指定对象
}
function SuperType(name){
this.name = name;
this.color = ["yellow","green"];
this.age = 23;
}
SuperType.prototype.getName = function(){
return this.name;
};
function SubType(name,age){
SuperType.call(this,name);
this.age = age;
}
inheritPrototype(SubType,SuperType);
SubType.prototype.getAge = function(){
return this.age;
};
var instance = new SubType("xiaodong",23);
console.log(instance.getName());//xiaodong
</script>
centos6.5最小化安装/AWS centos
php-5.5.5
yum install libxml2-devel gd-devel libmcrypt-devel libcurl-devel openssl-devel
cd /usr/local/src
wget http://us3.php.net/get/php-5.5.5.tar.gz/from/cn2.php.net/mirror
tar -xvf php-5.5.5.tar.gz
cd php-5.5.5
./configure --with-apxs2=/usr/local/apache2/bin/apxs --disable-cli --enable-shared --with-libxml-dir --with-gd --with-openssl --enable-mbstring --with-mcrypt --with-mysqli --with-mysql --enable-opcache --enable-mysqlnd --enable-zip --with-zlib-dir --with-pdo-mysql --with-jpeg-dir --with-freetype-dir --with-curl --without-pdo-sqlite --without-sqlite3
make && make install
我已经尽量的在参数上做了精简,用以上参数编译安装好的 php 运行 wordpress, joomla, ip board 等常见的博客、论坛程序都是没有问题的,因为有了 --disable-cli,所以就没法 make test 了,安装好以后也没法 php -v 了。安装吧:
cp php.ini-production /usr/local/lib/php.ini
vi /usr/local/apache2/conf/httpd.conf
在httpd.conf里面添加如下内容
AddType application/x-httpd-php .php
AddType application/x-httpd-php-source .phps
#应该将以上两句添加在其他AddType之后。
LoadModule php5_module modules/libphp5.so
#上面那行可能在编译安装 php 的过程中已经由系统自动添加了
<FilesMatch \.php$>
SetHandler application/x-httpd-php
</FilesMatch>
接下来可以重启php查看是否安装成功了。
我们可以在apache默认目录下面新建一个php文件测试一下
vi /usr/local/apache2/htdocs/php.php
<?php
echo "php is OK";
?>
在js中,作用域的问题算还是比较容易理解的,之前也写过一篇博文《javascript中的词法作用域》。但js的作用域规则在遇到闭包的时候可能就会出现一些问题了。最经典的就是for变量声明的问题了。
首先我们有html和js的代码片段
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<div id="showClick">
<p>产品一</p>
<p>产品二</p>
<p>产品三</p>
<p>产品四</p>
</div>
</body>
</html>
var showClick = document.getElementsByTagName('p');
for(var i=0;i<showClick.length;i++){
showClick[i].onclick=function(){
console.log(i);
}
}
view code;
这个就是很经典的一个问题,当我们点击的时候,控制台输出的全部是4。出现这个问题的最根本的原因就是:
我们从词法作用域来分析这一句话,这个闭包中的i,他通过他的执行环境来获取,也就是for那一层,即全局变量,全局变量中的i是一个静态变量,在for执行完毕之后,他的值为4,也就是接下来我们执行onclick操作时的最后一个值。
之所以利用词法作用域来理解我认为有助于让我们更容易来理解解决这个问题的几种方案。
var showClick = document.getElementsByTagName('p');
for(var i=0;i<showClick.length;i++){
(function (m){
showClick[m].onclick=function(){
console.log(m);}
})(i);
}
从词法作用域来看,闭包中的m来源于匿名函数的实参,而我们知道,函数的参数是按值传递而非按照引用传递的,所以这里得到的m就是每次循环的i的实际值。
同理
var showClick = document.getElementsByTagName('p');
for(var i=0;i<showClick.length;i++){
showClick[i].onclick=(function (m){
return function(){
console.log(m);
}
})(i);
}
或者可以不传参数
var showClick = document.getElementsByTagName('p');
for(var i=0;i<showClick.length;i++){
(function (){
var tem = i;
showClick[i].onclick=function(){
console.log(tem);}
})();
}
var showClick = document.getElementsByTagName('p');
for(var i=0;i<showClick.length;i++){
showClick[i].onclick=(function (){
var tem = i;
return function(){
console.log(tem);
}
})();
}
原理都是一样的,强制让闭包在匿名函数中去找值。
var showClick = document.getElementsByTagName('p');
for(var i=0;i<showClick.length;i++){
showClick[i].i = i;
showClick[i].onclick=function(){
console.log(this.i);
}
}
这里的showClick是对象,所以我们是可以给为增加一个属性的,而对象是引用类型的,所以在增加的属性的时候,他的值已经被固定了。
var showClick = document.getElementsByTagName('p');
function returnShow(i){
return function(){
console.log(i);
}
}
for(var i=0;i<showClick.length;i++){
showClick[i].i = i;
showClick[i].onclick=returnShow(i);
}
就这相当于避开了闭包,思路同1一样的,函数的参数是按值传递的。
var showClick = document.getElementsByTagName('p');
for(let i=0;i<showClick.length;i++){
showClick[i].onclick=function(){
console.log(i);
}
}
于是我们思考一下,既然函数参数是按值传递,加上作用域链,我们可不可以这样直接通过参数来缓存变量呢?
var showClick = document.getElementsByTagName('p');
for(var i=0;i<showClick.length;i++){
showClick[i].onclick=function(i){
console.log(i);
}
}
答案是否定的,因为onclick事件传递的参数是event事件,所以打印的实际上是event事件,而不是i的值。
那么对于setTimeOut我们则可以通过这种方式来进行解决
for (var i = 0; i < 5; i++) {
setTimeout(function(){
console.log(i)
},10)
}
不出所料,以上代码会全部打印出5出来。
我们可以采用上面的匿名函数的方法来解决:
for (var i = 0; i < 5; i++) {
(function(i){
setTimeout(function(){
console.log(i)
},10)
})(i)
}
for (var i = 0; i < 5; i++) {
(function(){
var tem = i;
setTimeout(function(){
console.log(tem)
},10)
})()
}
以及我们可以直接通过函数传参的方式来实现:
function a(a){
return function(){
console.log(a);
}
}
for (var i = 0; i < 5; i++) {
setTimeout(a(i),10)
}
yum install git
因为我们需要通过ssh的方式来连接git,这里我是用zijidelu来安装的控制面板,通过在php中执行echo shell_exec('whoami')
能够看到php脚本是以zijidelu用户来执行的,所以网站的文件权限和用户组我设置的是zijidelu:zijideluGroup
,我需要用zijidelu
这个用户来执行git以避免文件权限的变化。运行git的时候。会从当前用户目录中去查找ssh key;
cd /home/
mkdir zijidelu_home
cd zijidelu_home
mkdir .ssh
cd ../
cd ../
chown -R zijidelu:zijideluGroup zijidelu_home
sudo -u zijidelu ssh-keygen -t rsa
ssh保存的路径就是我们上一步创建的目录
接下来把这个目录设置成zijidelu
这个用户的主目录。vi /etc/passwd
修改zijidelu
用户为:
zijidelu:x:1520:1520::/home/zijidelu_home:/sbin/nologin
这个是用于gitee
的脚本,其它如coding也类似,只是取得的参数可能有变化。
<?php
$access_token = '******';
$access_ip = array('*.*.*.*');
/* get user token and ip address */
$client_ip = $_SERVER['REMOTE_ADDR'];
/* create open log */
$fs = fopen('./webhook.log', 'a');
fwrite($fs, 'Request on ['.date("Y-m-d H:i:s").'] from ['.$client_ip.']'.PHP_EOL);
/* test token */
/* test ip */
// if ( ! in_array($client_ip, $access_ip))
// {
// echo "error 503";
// fwrite($fs, "Invalid ip [{$client_ip}]".PHP_EOL);
// exit(0);
// }
/* get json data */
$json = file_get_contents('php://input');
$data = json_decode($json, true);
$client_token = $data['password'];
if ($client_token !== $access_token)
{
echo "error 403";
fwrite($fs, "Invalid token [{$client_token}]".PHP_EOL);
exit(0);
}
/* get branch */
$branch = $data["ref"];
fwrite($fs, '======================================================================='.PHP_EOL);
/* if you need get full json input */
//fwrite($fs, 'DATA: '.print_r($data, true).PHP_EOL);
/* branch filter */
if ($branch === 'refs/heads/master')
{
/* if master branch*/
fwrite($fs, 'BRANCH: '.print_r($branch, true).PHP_EOL);
fwrite($fs, '======================================================================='.PHP_EOL);
$fs and fclose($fs);
/* then pull master */
exec("/home/deploy/deploy.sh");
}
// else
// {
// /* if devel branch */
// fwrite($fs, 'BRANCH: '.print_r($branch, true).PHP_EOL);
// fwrite($fs, '======================================================================='.PHP_EOL);
// $fs and fclose($fs);
// /* pull devel branch */
// exec("/home/deploy/devel_deploy.sh");
// }
?>
这一步通过#echo $USER >> /home/deploy/deploy.log
通过查到sh脚本是以zijidelu
这个用户来执行的
#!/bin/bash
cd /home/wwwroot/518zst
#echo $USER >> /home/deploy/deploy.log
#sudo -u zijidelu echo "OKOK4" >> /home/deploy/deploy.log
git reset --hard HEAD >> /home/deploy/deploy.log
git pull origin master >> /home/deploy/deploy.log
#chmod 777 data/order_print_seller.html
#chmod 777 data/order_print_vendor.html
这个脚本这最近一次的部署中是正常运行的,因为他是以zijidelu
这个用户来执行的。
但在我以前的编写中是加了一条sudo -u zijidelu
,像:sudo -u zijidelu git reset --hard HEAD
,同时vi /etc/soduers
在root用户下面添加了这一句:
daemon ALL=(ALL) NOPASSWD:ALL
表示deamon用户可以在非终端模式下执行命令,这样在以前的部署中正常执行的。
反而在这一次这样设置后没能正常执行,sudo -u
这条命令没有任何的结果
经测试,这是因为php执行的模式的问题,当php以FastCGI
执行的时候,我们可以按照上面的脚本来写即可。但如果以非FastCGI
模式来运行的时候,echo $USER >
只是一行空白,这个时候执行的命令前面就需要加上sudo -u
,并且在sudoers中添加deamon
这个用户的脚本执行。
第一次最好在服务器端通过sudo -u zijidelu git pull origin master
来运行一次,因为首次通过ssh方式来连接git的话,在know_host里面没有记录,会弹出确认信息,如下
The authenticity of host 'git.coding.net (123.59.83.79)' can't be established.
RSA key fingerprint is 98:ab:2b:30:60:00:82:86:bb:85:db:87:22:c4:4f:b1.
Are you sure you want to continue connecting (yes/no)?
我们输入yes之后,以后就不用再确认了,这个时候通过exec来执行git也能成功执行了。
否则如果我们直接通过exec来执行的话,git pull这一步是不会执行的,也不会抛出任何信息的。
测试可以分步测试,在php文件中通过写入文件来查看php是否正常执行,然后通过sh脚本的输出看sh脚本是否正常执行。
同时也可以在终端下测试sh执行是否正确。
无聊想到写的一个小demo,整个js代码只有几行,关键思路就是乘数置前的处理手段。
我们正常的思维逻辑按照我们背的方式,一列一列的来生成:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
</body>
<script>
for(i=1;i<10;i++){
for(j=i;j<10;j++){
console.log(i+"*"+j+"="+i*j);
}
}
</script>
</html>
如果要在页面上展示出来,我们一行一行地进行处理,刚不是向上面那个一列一列地处理,可能更容易展示,于是我们就想到在循环中把乘数放在外层是更容易处理的。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<table id="table"></table>
</body>
<script>
for(var i=1,htmlStr='';i<10;i++){
htmlStr += "<tr>"
for(var j=1;j<=i;j++){
htmlStr += "<td>"+j+"*"+i+"="+i*j+"</td>"
}
htmlStr += "</tr>"
}
document.getElementById("table").innerHTML=htmlStr;
</script>
</html>
这个软件不用说了,xbeta专门用一个网站来推荐他的一个神一般的资源管理器,因为我与电脑文件交道打得太频繁了,详细使用方法在Xbeta上面已经有了.
当年用DW做网站的时候,那个时候正好经历了从表格到DIV+CSS转变的时候,当把DIV+CSS学得差不多时候,发现DW只用得上代码模式了,其它的功能基本上都用不上了,而就为了这一点点却上用上一个庞大的DW,一直希望能够有一个记事本般小巧,但能够有代码提示功能的,直到我遇到了SB,最爱他的皮肤,他的模糊匹配,他的丰富插件,他的...
在firefox里面有一个很好用的插件xthunder,这个下载管理器非常方便,后来经常用chrome了,却一直找不到一个满意的下载管理器,直到我遇到IDM,才发现chrome不需要什么下载管理器了,它和chrome完美兼容,静默下载,多线程下载,小巧得我可以忽略它的存在,再也不用忍受每次启动都要等个十多二十秒的迅雷或者旋风,下载,就这么简单就好.
可惜它是收费的,网上比较优秀的破解版应该是Yanu的吧.附上一链接:http://www.ccav1.com/idm.html
每个月只有1G的流量,和现在动不动就几十T的网盘比起来,我却还是只用它,只因为它同步特别稳定,从来没有遇到过同步出过问题的,增量同步,多文件夹同步,方便极了.
刚刚才接触它,却立马爱上他了,以前一直用everything,同样是一款神一样的软件,秒秒搜索全盘软件,而Listary除了上搜索上同样厉害之外,还增强了许多功能,比如快捷键唤出搜索,和文件管理器结合一体,还与Totalcommander整合,增强的打开保存功能.
说实话,我用它真的是太浪费它了,因为我只用它来打开常用软件,常用文件,每天win+q打开QQ的感觉真的很爽,用上它,用上totalcommander,listray这些软件,真的是可以越来越少使用鼠标了.
我不得说,做为一个十多年的资深网民,很多下载页面我也是要找半天才能找到真实的下载地址,浏览器有很多过虑广告的插件,比如Adblock Plus,和它同样优秀的这个奶牛,优点是可以过滤到不仅仅浏览器,软件的广告也都可以过滤到.整个互联网终于清静了.看视频也不用等广告了.
以前用过CLCL,呼出加快捷选择很方便,后来用上ditto,同样非常快捷方便的剪贴板管理工具,天天和代码,文档打交道,这个是必不可少的.再也不用来回重新复制了.
现在用得比较少了,但笔记本还是一直让它自启的,因为一直登录自己的账号,远程控制无须本机确认,桌面管理,文件传输,流畅的速度,远程管理一直用的就是它了.
以前也用过一些虚拟桌面软件,但都记不得名字了,最近又看到一个虚拟桌面软件,不是virgo,虽然实际使用频率特别少,但只有7K的体积让我屈服了.不得不说算得上是超级经典的软件了.
以前一直用的是KMplayer,PotPlayer是KMplayer的新作,后来的射手同样也是做的非常优秀,我比较喜欢那些小巧得惊人,但功能却一点都不差不的软件,而且相当专注,没有太多臃肿的功能.
每次分区,修复分区表,找回误删的文件,查看丢失分区里面文件...最顺手,最信任的可能就是它了.
同样是因为小巧,但清理得却是非常彻底的.不需要动不动就是什么助手,卫士的.
我一直认为我用不上它的,因为我不是邮件达人,直到我发现它还支持RSS,每天再也不用不断去看那几个喜欢的软件有没有更新了,直接在它里面全部浏览无余.而且它收取gmail邮件比我以前用QQ代收要来得快得多.
以前用7z,因为它免费,一般我尽量找能够替代收费软件的替代品,毕竟用破解版总感觉对作者不尊重似的,后来用上bandizip主要是因为同样也是免费的,另外支持直接查看压缩包里面的图片.
折腾系统,网银不兼容,测试,测试,测试,....还是测试,而且现在VMware还非常良心地为我们推出了免费版的VMware Workstation Player,基本上我们要用的功能免费版上面都已经有了。
应该是免费VPN里面最好的了吧.
曾经研究过一段时间,什么换IP,用虚拟机,都没有它来得防查水表的强大.
有些时候,我们往往有这样的需求:
针对这些需求,我们使用在线的代码片段测试工具,也许来得更加简单和方便了。
针对前端的在线代码片段工具很多,比较常见的有jsbin和jsfiddle以及codepen.
而我最喜欢的就是jsbin了,它有着更多的特性给我带来了极大的方便:
任意控制要展示的窗口
点击这些标签,就可以控制对应的窗口的显示与隐藏,让我们获得更大的编辑区域,减少不需要的窗口的干扰。
支持console面板,因为我调试代码习惯于用console面板来调试,所以它的这个面板一下子就吸引了我。
直接支持在线用书写sublime text的快捷键来书写代码,而且是支持emmet的哦。不需要在自己的编辑器里面写好,再复制过去了,直接在线流畅地进行书写,
开启方法:Account->Editor settings->Addons 里面,Key bindings勾选Sublime就可以了。
我们在书写了代码之后,可以通过File->Save snapshot来保存当前的代码片段,但默认保存的很难让我们区分出这段代码是干什么用的。就像下面这样:
这里面的信息我们是不能编辑的,我们可以在编辑窗口,点击File->Add description来添加描述。
这下子就非常清晰了。
园子里已经有不少介绍HTTP的的好文章。对HTTP的一些细节介绍的比较好,所以本篇文章不会对HTTP的细节进行深究,而是从够高和更结构化的角度将HTTP协议的元素进行分类讲解。
在一个网络中。传输数据需要面临三个问题:
1.客户端如何知道所求内容的位置?
2.当客户端知道所求内容的位置后,如何获取所求内容?
3.所求内容以何种形式组织以便被客户端所识别?
对于WEB来说,回答上面三种问题分别采用三种不同的技术,分别为:统一资源定位符(URIs),超文本传输协议(HTTP)和超文本标记语言(HTML)。对于大多数WEB开发人员来说URI和HTML都是非常的熟悉。而HTTP协议在很多WEB技术中都被封装的过多使得HTTP反而最不被熟悉。
HTTP作为一种传输协议,也是像HTML一样随着时间不断演进的,目前流行的HTTP1.1是HTTP协议的第三个版本。
HTTP 0.9作为HTTP协议的第一个版本。是非常弱的。请求(Request)只有一行,比如:
GET www.cnblogs.com
从如此简单的请求体,没有POST方法,没有HTTP 头可以看出,那个时代的HTTP客户端只能接收一种类型:纯文本。并且,如果得不到所求的信息,也没有404 500等错误出现。
虽然HTTP 0.9看起来如此弱,但已经能满足那个时代的需求了。
随着1996年后,WEB程序的需求,HTTP 0.9已经不能满足需求。HTTP1.0最大的改变是引入了POST方法,使得客户端通过HTML表单向服务器发送数据成为可能,这也是WEB应用程序的一个基础。另一个巨大的改变是引入了HTTP头,使得HTTP不仅能返回错误代码,并且HTTP协议所传输的内容不仅限于纯文本,还可以是图片,动画等一系列格式。
除此之外,还允许保持连接,既一次TCP连接后,可以多次通信,虽然HTTP1.0 默认是传输一次数据后就关闭。
2000年5月,HTTP1.1确立。HTTP1.1并不像HTTP1.0对于HTTP0.9那样的革命性。但是也有很多增强。
首先,增加了Host头,比如访问我的博客:
GET /Careyson HTTP/1.1
Host: www.cnblogs.com
Get后面仅仅需要相对路径即可。这看起来虽然仅仅类似语法糖的感觉,但实际上,这个提升使得在Web上的一台主机可以存在多个域。否则多个域名指向同一个IP会产生混淆。
此外,还引入了Range头,使得客户端通过HTTP下载时只下载内容的一部分,这使得多线程下载也成为可能。
还有值得一提的是HTTP1.1 默认连接是一直保持的,这个概念我会在下文中具体阐述。
在Internet中所有的传输都是通过TCP/IP进行的。HTTP协议作为TCP/IP模型中应用层的协议也不例外。HTTP在网络中的层次如图1所示。
图1.HTTP在TCP/IP中的层次
可以看出,HTTP是基于传输层的TCP协议,而TCP是一个端到端的面向连接的协议。所谓的端到端可以理解为进程到进程之间的通信。所以HTTP在开始传输之前,首先需要建立TCP连接,而TCP连接的过程需要所谓的“三次握手”。概念如图2所示。
图2.TCP连接的三次握手
在TCP三次握手之后,建立了TCP连接,此时HTTP就可以进行传输了。一个重要的概念是面向连接,既HTTP在传输完成之间并不断开TCP连接。在HTTP1.1中(通过Connection头设置)这是默认行为。所谓的HTTP传输完成我们通过一个具体的例子来看。
比如访问我的博客,使用Fiddler来截取对应的请求和响应。如图3所示。
图3.用fiddler抓取请求和相应
可以看出,虽然仅仅访问了我的博客,但锁获取的不仅仅是一个HTML而已,而是浏览器对HTML解析的过程中,如果发现需要获取的内容,会再次发起HTTP请求去服务器获取,比如图2中的那个common2.css。这上面19个HTTP请求,只依靠一个TCP连接就够了,这就是所谓的持久连接。也是所谓的一次HTTP请求完成。
所谓的HTTP请求,也就是Web客户端向Web服务器发送信息,这个信息由如下三部分组成:
1.请求行
2.HTTP头
3.内容
一个典型的请求行比如:
GET www.cnblogs.com HTTP/1.1
请求行写法是固定的,由三部分组成,第一部分是请求方法,第二部分是请求网址,第三部分是HTTP版本。
第二部分HTTP头在HTTP请求可以是3种HTTP头:1.请求头(request header) 2.普通头(general header) 3.实体头(entity header)
通常来说,由于Get请求往往不包含内容实体,因此也不会有实体头。
第三部分内容只在POST请求中存在,因为GET请求并不包含任何实体。
我们截取一个具体的Post请求来看这三部分,我在一个普通的aspx页面放一个BUTTON,当提交后会产生一个Post请求,如图4所示。
图4.HTTP请求由三部分组成
虽然我们所常见的只有Get和Post方法,但实际上HTTP请求方法还有很多,比如: PUT方法,DELETE方法,HEAD方法,CONNECT方法,TRACE方法。这里我就不细说了,自行Bing。
这里重点说一下Get和Post方法,网上关于Get和Post的区别满天飞。但很多没有说到点子上。Get和Post最大的区别就是Post有上面所说的第三部分:内容。而Get不存在这个内容。因此就像Get和Post其名称所示那样,Get用于从服务器上取内容,虽然可以通过QueryString向服务器发信息,但这违背了Get的本意,QueryString中的信息在HTTP看来仅仅是获取所取得内容的一个参数而已。而Post是由客户端向服务器端发送内容的方式。因此具有请求的第三部分:内容。
当Web服务器收到HTTP请求后,会根据请求的信息做某些处理(这些处理可能仅仅是静态的返回页,或是包含Asp.net,PHP,Jsp等语言进行处理后返回),相应的返回一个HTTP响应。HTTP响应在结构上很类似于HTTP请求,也是由三部分组成,分别为:
1.状态行
2.HTTP头
3.返回内容
首先来看状态行,一个典型的HTTP状态如下:
HTTP/1.1 200 OK
第一部分是HTTP版本,第二部分是响应状态码,第三部分是状态码的描述,因此也可以把第二和第三部分看成一个部分。
对于HTTP版本没有什么好说的,而状态码值得说一下,网上对于每个具体的HTTP状态码所代表的含义都有解释,这里我说一下分类。
信息类 (100-199)
响应成功 (200-299)
重定向类 (300-399)
客户端错误类 (400-499)
服务端错误类 (500-599)
HTTP响应中包含的头包括1.响应头(response header) 2.普通头(general header) 3.实体头(entity header)。
第三部分HTTP响应内容就是HTTP请求所请求的信息。这个信息可以是一个HTML,也可以是一个图片。比如我访问百度,HTTP Response如图5所示。
图5.一个典型的HTTP响应
图5中的响应是一个HTML,当然还可以是其它类型,比如图片,如图6所示。
图6.HTTP响应内容是图片
这里会有一个疑问,既然HTTP响应的内容不仅仅是HTML,还可以是其它类型,那么浏览器如何正确对接收到的信息进行处理?
这是通过媒体类型确定的(Media Type),具体来说对应Content-Type这个HTTP头,比如图5中是text/html,图6是image/jpeg。
媒体类型的格式为:大类/小类 比如图5中的html是小类,而text是大类。
IANA(The Internet Assigned Numbers Authority,互联网数字分配机构)定义了8个大类的媒体类型,分别是:
application— (比如: application/vnd.ms-excel.)
audio (比如: audio/mpeg.)
image (比如: image/png.)
message (比如,:message/http.)
model(比如:model/vrml.)
multipart (比如:multipart/form-data.)
text(比如:text/html.)
video(比如:video/quicktime.)
HTTP头
HTTP头仅仅是一个标签而已,比如我在Aspx中加入代码:
Response.AddHeader("测试头","测试值");
对应的我们可以在fiddler抓到的信息如图7所示。
图7.HTTP头
不难看出,HTTP头并不是严格要求的,仅仅是一个标签,如果浏览器可以解析就会按照某些标准(比如浏览器自身标准,W3C的标准)去解释这个头,否则不识别的头就会被浏览器无视。对服务器也是同理。假如你编写一个浏览器,你可以将上面的头解释成任何你想要的效果微笑
下面我们说的HTTP头都是W3C标准的头,我不会对每个头的作用进行详细说明,关于HTTP头作用的文章在网上已经很多了,请自行Bing。HTTP头按照其不同的作用,可以分为四大类。
通用头即可以包含在HTTP请求中,也可以包含在HTTP响应中。通用头的作用是描述HTTP协议本身。比如描述HTTP是否持久连接的Connection头,HTTP发送日期的Date头,描述HTTP所在TCP连接时间的Keep-Alive头,用于缓存控制的Cache-Control头等。
实体头是那些描述HTTP信息的头。既可以出现在HTTP POST方法的请求中,也可以出现在HTTP响应中。比如图5和图6中的Content-Type和Content-length都是描述实体的类型和大小的头都属于实体头。其它还有用于描述实体的Content-Language,Content-MD5,Content-Encoding以及控制实体缓存的Expires和Last-Modifies头等。
请求头是那些由客户端发往服务端以便帮助服务端更好的满足客户端请求的头。请求头只能出现在HTTP请求中。比如告诉服务器只接收某种响应内容的Accept头,发送Cookies的Cookie头,显示请求主机域的HOST头,用于缓存的If-Match,If-Match-Since,If-None-Match头,用于只取HTTP响应信息中部分信息的Range头,用于附属HTML相关请求引用的Referer头等。
HTTP响应头是那些描述HTTP响应本身的头,这里面并不包含描述HTTP响应中第三部分也就是HTTP信息的头(这部分由实体头负责)。比如说定时刷新的Refresh头,当遇到503错误时自动重试的Retry-After头,显示服务器信息的Server头,设置COOKIE的Set-Cookie头,告诉客户端可以部分请求的Accept-Ranges头等。
还有一点值得注意的是,HTTP协议是无状态的,这意味着对于接收HTTP请求的服务器来说,并不知道每一次请求来自同一个客户端还是不同客户端,每一次请求对于服务器来说都是一样的。因此需要一些额外的手段来使得服务器在接收某个请求时知道这个请求来自于某个客户端。如图8所示。
图8.服务器并不知道请求1和请求2来自同一个客户端
为了解决这个问题,HTTP协议通过Cookies来保持状态,对于图8中的请求,如果使用Cookies进行状态控制,则变成了如图9所示。
图9.通过Cookies,服务器就可以清楚的知道请求2和请求1来自同一个客户端
除了Cookies之外,还可以使用表单变量来保持状态,比如Asp.net就通过一个叫ViewState的Input=“hidden”的框来保持状态,比如:
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKMjA0OTM4MTAwNGRkXUfhlDv1Cs7/qhBlyZROCzlvf5U=" />
这个原理和Cookies大同小异,只是每次请求和响应所附带的信息变成了表单变量。
这个原理和上述两种状态保持方法原理是一样的,QueryString通过将信息保存在所请求地址的末尾来向服务器传送信息,通常和表单结合使用,一个典型的QueryString比如:
www.xxx.com/xxx.aspx?var1=value&var2=value2
本文从一个比较高的视角来看HTTP协议,对于HTTP协议中的细节并没有深挖,但对于HTTP大框架有了比较系统的介绍,更多关于HTTP的细节信息,请去Bing或参看相关书籍:-)
文章来源:http://www.cnblogs.com/CareySon/archive/2012/04/27/HTTP-Protocol.html
在前端面试题中,一般会遇到一道关于array去重的面试题。简单点来说 ,就是找出不一样的,或者说排队一样的。那么我们就可以通过生活中的一些情景来思考这道题的解法。
假如我们要表演一场千手观音的节目,我们需要高矮不一样的人来表演,于是在参选人员中,我们让他们按照高矮次序排队,一样高的就排除掉后面的那一个。
于是我们就得到了解法,先对数组进行排序,然后循环比较当前值和其后面那个值的是否相等,如果不相等,刚后面那个值认为是有效的。
var arr = [2,3,2,5,4,3];
var new_arr = [];
arr = arr.sort();
for (var i=0;i<arr.length;i++){
if(arr[i]!=arr[i+1]){
new_arr.push(arr[i])
}
}
console.log(new_arr);
假如有一大排五颜六色的球,我们要从中每个颜色选择一个球出来,于是我们就可以这样选:准备一个口袋,每看到一个球,看一下这个球的颜色在袋子里面是不是已经装得有了,如果有,就不装了,如果没有,就把它装进去。
于是在js中得到解法,使用js中的indexOf来判断数组中的每一个元素是否已经在输出结果的新数组中:
var arr = [2,3,2,5,4,3];
var new_arr = [];
for(var i=0;i<arr.length;i++){
if(new_arr.indexOf(arr[i])==-1){
new_arr.push(arr[i]);
}
}
console.log(new_arr);
相当于我们上下班打卡一样,对于同一个人在某一个时间段内重复打卡,我们会只记录他的一次打卡记录。
于是在js,我们可以把相同的值认为是同一个值在多次出现,而我们可以把值转化为一个对象的属性,这样,某一个值一旦出现过,我们就为其完成“打卡”操作。
var arr = [2,3,2,5,4,3];
var new_arr = [];
var has = {};
for(var i=0;i<arr.length;i++){
if(has[arr[i]]!== 1){
has[arr[i]]=1;
new_arr.push(arr[i]);
}
}
console.log(new_arr);
有一份名单,上面的名字可能因为在制作表的时候造成了很多名字的重复,老师为了避免重复点名,他只好每点一次名,就看下这个名字是不是在之前出现过,如果没有,就点名,否时就不点了。
于是js的解决思路还是要用到indexOf来判断当前值是否在之前的位置出现过。
var arr = [2,3,2,5,4,3];
var new_arr = [];
for(var i=0;i<arr.length;i++){
if(arr.indexOf(arr[i])==i){
new_arr.push(arr[i]);
}
}
console.log(new_arr);
假如我把数据从左往右按照次序看,前面出现过的相同的数字认为是同一只小鸟,而那个数字只是小鸟之前停留过的位置而已,所以我们只需要找到每一只小鸟最后停留的位置即可了。
解决方法来源于国外的一个人写的,参考链接.
这个函通过双重循环,顶级中的一次循环,都通过次级的循环判断后面是否还有相同的值,如果有,次级继续循环,直到找到后面没有重复值的下一个顶级i的值,接下来顶级在新的i值基础上继续循环。
Array.prototype.unique = function() {
var a = [], l = this.length;
for(var i=0; i<l; i++) {
for(var j=i+1; j<l; j++)
if (this[i] === this[j]) j = ++i;
a.push(this[i]);
}
return a;
};
根据网上的代码优化而来,可以实现
快捷打开TotalCommander
如果已经打开TotalCommander则激活TotalCommander
如果是未注册版本就自动点击数字1,2,3
未注册窗口设置为透明
实现的代码如下
#t::
IfWinExist ahk_class TTOTAL_CMD
{
WinActivate
}
else
{
Run "c:\totalcmd\TOTALCMD64.EXE" ;设置为自己TC所在位置
WinWait,ahk_class TNASTYNAGSCREEN,,1 ;探测NagPage,若机器慢,1可改为3、4、5
If ErrorLevel=0 ;如果有NagPage,需要模拟发送1、2、3
{
WinSet,Transparent,0,ahk_class TNASTYNAGSCREEN ;设置NagPage为透明
WinActivate,ahk_class TNASTYNAGSCREEN ;抢焦点
WinGetText,NagTextStr ;获取NagPage信息并处理
StringMid,NagSendChar,NagTextStr,1,1
WinActivate,ahk_class TNASTYNAGSCREEN ;再抢焦点
Send,%NagSendChar% ;模拟发送1、2、3
}
WinActivate,ahk_class TTOTAL_CMD
}
return
注:代码中第15行 StringMid,NagSendChar,NagTextStr,1,1
网上的代码最后数字全部设置的为10,1,经过测试,发现都无法运行,最终获取到的是全部的窗口内容,只有设置为1,1才能正确截取到相应的数字。不知道是不是和我的运行环境有关?win7 x64+TotalCommander 8.51a x64
varsu@DESKTOP-V7HEGUG MINGW64 /d/temp/npm
$ npm -v
2.15.1
varsu@DESKTOP-V7HEGUG MINGW64 /d/temp/npm
$ npm install npm --global
C:\Users\varsu\AppData\Roaming\npm\npm -> C:\Users\varsu\AppData\Roaming\npm\node_modules\npm\bin\npm-cli.js
[email protected] C:\Users\varsu\AppData\Roaming\npm\node_modules\npm
varsu@DESKTOP-V7HEGUG MINGW64 /d/temp/npm
$ npm -v
3.10.9
varsu@DESKTOP-V7HEGUG MINGW64 /d/temp/npm
$ npm install forever -g
varsu@DESKTOP-V7HEGUG MINGW64 /d/temp/npm
$ npm uninstall forever -g
D:\temp\npm>npm install underscore
D:\temp\npm
`-- [email protected]
npm WARN enoent ENOENT: no such file or directory, open 'D:\temp\npm\package.json'
npm WARN npm No description
npm WARN npm No repository field.
npm WARN npm No README data
npm WARN npm No license field.
D:\temp\npm>tree
文件夹 PATH 列表
卷序列号为 000000B9 8841:2A63
D:.
└─node_modules
└─underscore
D:\temp\npm>npm uninstall underscore
- [email protected] node_modules\underscore
npm WARN enoent ENOENT: no such file or directory, open 'D:\temp\npm\package.json'
npm WARN npm No description
npm WARN npm No repository field.
npm WARN npm No README data
npm WARN npm No license field.
D:\temp\npm>npm ls
D:\temp\npm
`-- [email protected]
或者加上参数-g
查看全局范围安装的包
D:\temp\npm>npm info underscore
{ name: 'underscore',
description: 'JavaScript\'s functional programming helper library.',
'dist-tags': { latest: '1.8.3', stable: '1.8.3' },
versions:
[ '1.0.3',
'1.0.4',
'1.1.0',
'1.1.1',
'1.1.2',
'1.1.3',
'1.1.4',
'1.1.5',
'1.1.6',
'1.1.7',
'1.2.0',
'1.2.1',
'1.2.2',
'1.2.3',
'1.2.4',
'1.3.0',
'1.3.1',
'1.3.2',
'1.3.3',
'1.4.0',
'1.4.1',
'1.4.2',
'1.4.3',
'1.4.4',
'1.5.0',
'1.5.1',
'1.5.2',
'1.6.0',
.............
D:\temp\npm>npm install [email protected]
D:\temp\npm
`-- [email protected]
初始化一个项目,生成package.json 项目名不能有空格
D:\temp\npm>npm init
{
"name": "my_npm",
"version": "1.0.0",
"description": "",
"main": "index.js",
"dependencies": {},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "buxuku",
"license": "ISC"
}
在package.json中安装npm包
D:\temp\npm>npm install underscore --save
[email protected] D:\temp\npm
`-- [email protected]
D:\temp\npm>npm install babel-cli --save-dev
[email protected] D:\temp\npm
`-- [email protected]
卸载package.json中的包
D:\temp\npm>npm uninstall underscore --save
- [email protected] node_modules\underscore
D:\temp\npm>npm install [email protected] --save-dev
检查更新
D:\temp\npm>npm outdated
Package Current Wanted Latest Location
gulp 2.0.0 2.7.0 3.9.1 my_npm
Wanted表示可以更新到的版本号,但它最新的却是3.9.1,打开package.json,我们可以看到
"devDependencies": {
"babel-cli": "^6.16.0",
"gulp": "^2.0.0"
},
包后面的^
表示只更新第二位数据的版本号,如果改成~
则表示只更新最后一位的版本号,如果版本号全部改成*
,刚表示更新全部的版本号.
改成~
D:\temp\npm>npm outdated
Package Current Wanted Latest Location
gulp 2.0.0 2.0.1 3.9.1 my_npm
改成*
D:\temp\npm>npm outdated
Package Current Wanted Latest Location
gulp 2.0.0 3.9.1 3.9.1 my_npm
注意,一般在开发项目中最好不要改成*
,因为大的版本号的更新可能会导致原来的功能不能正常使用。
cnpm的使用参见淘宝的cnpm,这里主要使用nrm来管理切换npm使用的源
安装
D:\temp\npm>npm install nrm -g
查看可以使用的源
D:\temp\npm>nrm ls
* npm ---- https://registry.npmjs.org/
cnpm --- http://r.cnpmjs.org/
taobao - https://registry.npm.taobao.org/
nj ----- https://registry.nodejitsu.com/
rednpm - http://registry.mirror.cqupt.edu.cn/
npmMirror https://skimdb.npmjs.com/registry/
edunpm - http://registry.enpmjs.org/
测试各个源的连接速度
D:\temp\npm>nrm test
* npm ---- 1520ms
cnpm --- 300ms
taobao - 445ms
nj ----- Fetch Error
rednpm - Fetch Error
npmMirror 12046ms
edunpm - Fetch Error
切换npm的源
D:\temp\npm>nrm use cnpm
Registry has been set to: http://r.cnpmjs.org/
D:\temp\npm>nrm ls
npm ---- https://registry.npmjs.org/
* cnpm --- http://r.cnpmjs.org/
taobao - https://registry.npm.taobao.org/
nj ----- https://registry.nodejitsu.com/
rednpm - http://registry.mirror.cqupt.edu.cn/
npmMirror https://skimdb.npmjs.com/registry/
edunpm - http://registry.enpmjs.org/
react已经发布了v16的正式版,同时也把license从BSD
修改为MIT
了,在折腾了router4之后,也来继续折腾react v16了,react的升级不像router那样变化之大,网上说的只要react v15在控制台里面没有什么wraning之类的,就可以直接升级到v16了,但在实际升级过程中,可能因为实际项目的差异,可能还需要做一些调整的地方。
react
和react-dom
如果你只是升级了react的话,肯定去运行出一大堆错误出来的,必须同时把react-dom
也升级到v16版本。
比如下面这个错误:
Cannot find module "react/lib/ReactComponentTreeHook"
contextTypes
因为在我之前的代码的,路由的跳转用的是下面这种方式
static contextTypes = {
router: React.PropTypes.object
}
升级之后,将不再支持React.PropTypes.object
了,而我的项目也使用了router v4了,所以全部移除这部分代码,改为history的方式
一些奇怪的错误,可能是第三方包没有兼容react v16而引起的,我们尽可能把第三方包升级到最新版。以及删除之前所有的模块再来重新包装一些包,因为单独升级一个包,不一定能同时升级与它相关的依赖包。
rm -rf node_modules
npm cache clean
npm i
Stateless function components cannot be given refs. Attempts to access this ref will fail.null
目前看来应该是UI库antd的一个兼容问题 ant-design/ant-design#7784
我们都知道,在js中闭包会一直保持对着包含函数中的变量的引用,哪怕是在其它地方返回了闭包函数,其外部函数中的变量也不会销毁的。根据这个特性,我们引用一个面试题来深入观察一下:
如何定义一个count函数,每次调用的时候,都返回这个函数被调用的次数,除了count外,不能再有其它全局变量。
这道题主要也就是考察闭包中变量引用及作用域链的问题,根据闭包的特性,于是我们可以得到这样的一个函数:
var count=(function(){
var i=0;
return function(){
console.log("这是你第"+ ++i + "次调用我");
}
})();
count();
count();
我们通过定义匿名函数来减少一次函数命名,闭包中存在对i的调用,所以函数虽然是返回了闭包函数,但其对i的引用一直存在,所以外包函数中的变量一直存在。
当时, 如果不考虑函数命名的问题,我们也可以这样写:
function count_fun(){
var i=0;
return function(){
console.log("这是你第"+ ++i + "次调用我");
}
};
var count = count_fun();
count();
count();
扩展一下,我们都知道函数也有是属性的,我们在做动画和定时器的时候,可能会经常用到这种函数属性来方法来避免定时器干扰的问题。针对这个问题,我们也同样可以通过只使用一个变量,然后通过增加属性的方法来实现。
function count(){
console.log("这是你第"+ ++count.i +"次调用");
}
count.i=0;
count();
count();
或者直接通过对象的属性来实现也可以
var count = {
i:0,
add:function(){
console.log("这是您第"+ ++this.i +"次调用");
}
}
count.add();
count.add();
count.add();
这个不用提了,每次必装的插件,写html.css代码如飞一般。
没办法,被逼的,只有用来它支持下中文了。
最后才装上的一个插件,支持HTML,CSS,JS,JSON的格式化插件,装上它后发现太强大了。以前一直用的tag,发现很多时候都格式化不了,这个插件不仅可以格式化html,还可以格式化css,js,json,打开压缩后的min js文件,点右键,或者ctrl+shift+h
就可以格式化了,再也不用上网找工具了,json文件也是如此。
代码成对高亮插件,找凑合标签非常方便,但不知道为什么今天装的时候在package install 里面找不到这个插件了。
PS:刚把电脑上面原来64位的卸载掉了,然后重新安装32位的,可以正常安装了,估计应该是不支持64位吧。
自定义页面模板的功能,有时候再写一套网站的时候,很多页面的模板基本上都是一样的,虽然通过复制粘贴可以实现,但还是没有用这个插件来得简洁,快捷。
插件的详细使用说明可以参考http://www.fantxi.com/blog/archives/sublime-template-engine-sublimetmpl/
自动补完文件名字,这个在插入图片文件时候非常方便的。
配合chrome F12工具中的LiveStyle插件一起使用,边浏览边在chorme里面就可以修改Css文件了。
最开始发现宽带问题是用手机下载APP的时候,经常会先下载一个360手机助手,第二次下载的时候才会下载到正常的APP,后来发现我在使用京东,淘宝,亚马逊,包括百度的时候,都会自动变成联盟网站的链接,而且平时看电视基本上不卡的,而且是基本上都是卡的,最开始也不解,以为是应用商店的问题,因为在应用商店里面也有人评价说下载成了其它的APP,后来在电脑上下载APP的时候,在APP的官网上下载也会出现这样的问题,这下肯定不是应用商店的问题,估计应该是DNS挟持的问题吧,更改DNS为谷歌的,阿里的,问题依然如故。后来发现这下肯定是被运营商给TCP挟持了,这种情况下再怎么改DNS也没有办法的。在网上找到解决办法就是安装一个smart header的插件,用这个插件把连接请求中的user-agent删除掉就可以了,网上的说法是运营商的TCP挟持没有了user-agent就没办法起作用了,安装了这个插件之后,确实好像没有被挟持了,但在浏览过程中会出现问题,比如京东,淘宝没办法下单等,而且也没办法解决手机下载APP的问题。
于是决定还是最终极的办法,就是投诉,先打电话给长宽的客户,一直问我是不是要弹出长宽的广告页面,我一直解释说不是弹出页面,是TCP挟持,会变成联盟链接,下载APP的问题等,后来客服如网上的说话类似,说以前确实没有接到类似的投诉,帮我记录一下。
看来客服不好沟通,打开长宽的网页,在意见反馈里面说明了我的情况,要求在48小时内给我解决结果。
然后又打开他们的在线客户,说明情况,说让技术联系我,
最开始技术说不会做这些处理,我也懒得给他们解释,再给他们一个台阶下,让他们检查一下,否则就投诉到工信部,相信他们会心知肚明的,他们马上说稍等一下,结果一个晚上就再也没有回复了。
第二天,我继续联系客服,
看到没,直接给我说没办法处理,一下子就生气了,那我就直接说投诉到工信部了,看来他们还是怕工信部的。
后来技术给我打电话,我说了我的问题,他还在狡辩说什么京东这些网站都是走的他们的资源,什么是联盟链接说不知道,后来我说让你去找我们的主管或者懂这方面的技术的。后来他就说昨天晚上已经把我的商品更新了一下,让我再看一下。
晚上回家,测试,已经没有进行TCP挟持了,同时我还发现我的IP段前三位是固定的了,不管怎么断线重连,只有最后一位会变化。同时看电视也还没有出现卡的情况了。
javascript中创建对象有几种方式,工厂模式,构造函数模式,原型模式,构造函数+原型混合模式,动态原型模式,寄生构造函数模式,稳妥构造函数模式.
针对前几种,记录一下自己的书写笔记,有时间再来整理一下文字版.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>对象</title>
<script>
//工厂方法 缺点:对象识别问题.
function CreatPerson(name,age){
var obj = new Object;
obj.name = name;
obj.age = age;
obj.fav = ["苹果","葡萄"];
obj.run = function (){
return this.name+this.age+"正在运行中...";
};
return obj;
}
/*
var person1 = CreatPerson("lxd",23);
person1.fav.push("橙子");
var person2 = CreatPerson("tian",14);
person1.name = "xiaodong";
console.log(person1.run());//xiaodong23正在运行中...
console.log(person1.fav);// ["苹果", "葡萄", "橙子"] 引用对象不共享
console.log(person2.run());//tian14正在运行中...
console.log(person2.fav);// ["苹果", "葡萄"]
console.log(person1 instanceof Object);//true
console.log(person1 instanceof CreatPerson);//false 不能获得对象标识
console.log(person1.run == person2.run);//false
*/
//构造函数模式 缺点:里面相同任务的function也会被实例化
function Person(name,age){
this.name = name;
this.age = age;
this.fav = ["苹果","葡萄"];
this.run = function(){ //这里的方法也会被实例化,等同于this.run = new Function("return this.name+this.age+'正在运行中...';")
return this.name+this.age+"正在运行中...";
}
}
var person1 = new Person("lxd",23);
person1.fav.push("橙子");
var person2 = new Person("tian",14);
person1.name = "xiaodong";
console.log(person1.run());//xiaodong23正在运行中...
console.log(person1.fav);// ["苹果", "葡萄", "橙子"] 引用对象不共享
console.log(person2.run());//tian14正在运行中...
console.log(person2.fav);// ["苹果", "葡萄"]
console.log(person1 instanceof Object);//true
console.log(person1 instanceof Person);//true 可以获得对象标识
console.log(person1.run == person2.run);//false 实例化对象,里面的方法也会实例化
Person("window",100);//当作普通函数使用 添加到window对象中
console.log(window.run());//window100正在运行中...
var o = new Object;
Person.call(o,"oooo",99);//在另外一个对象的作用域中调用
console.log(o.run());//oooo99正在运行中...
//针对构造函数的缺点,进行改造,把函数提取出来 缺点:暴躁在全局变量中的方法,却只能在对象中使用,而且如果方法很多,会创建很多这样的全局方法.
function NewPerson(name,age){
this.name = name;
this.age = age;
this.fav = ["苹果","葡萄"];
this.run = run;
}
function run(){
return this.name+this.age+"正在运行中...";
}
var newperson1 = new NewPerson("xiaodong",13);
var newperson2 = new NewPerson("tian",22);
console.log(newperson1.run == newperson2.run);//true 访问的是同一个方法
</script>
</head>
<body>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>prototype</title>
<script>
//原型模式
function Person() {};
Person.prototype.name = "xiaodong";
Person.prototype.age = 23;
Person.prototype.job = "do software";
Person.prototype.sayName = function (){
return this.name;
}
var person1 = new Person();
var person2 = new Person();
console.log(person1.sayName());//"xiaodong"
console.log(person2.sayName());//"xiaodong"
console.log(person1.sayName === person2.sayName);//访问同一个对象指针
console.log(Person.prototype.isPrototypeOf(person1));//true person1中包含有指向Person.prototype的指针
console.log(Object.getPrototypeOf(person1) == Person.prototype);//true page 149 返回prototype的值
console.log(Object.getPrototypeOf(person1).name);//xiaodong
person1.name = "tiantian"; //只能添加到实例中,不能改变原型的值
console.log(person1.name);//"tiantian" 来自实例而非原型
console.log(person2.name);//"xiaodong" 来自原型而非实例
delete person1.name;
console.log(person1.name);//"xiaodong" 来自原型
/*
console.log(person1.hasOwnProperty("name"));//false 检测是实例属性还是原型属性
person1.name = "tian";
console.log(person1.hasOwnProperty("name"));//ture 实例中包含name属性
*/
console.log("name" in person1);//true 可枚举原型中的属性 来自原型
person1.name = "okok";
console.log("name" in person1);//true //来自实例
//利用hasOwnProperty和in可以判断属性是来自实例还是原型例如
function hasPrototypeProperty(object,name){
return !object.hasOwnProperty(name) && (name in object);
}
delete person1.name;
console.log(hasPrototypeProperty(person1,"name"));//ture
person1.name = "test";
console.log(hasPrototypeProperty(person1,"name"));//false
for (var i in person1){
if (i == "age"){
console.log("1234");//"1234"
}
}
var keys = Object.keys(Person.prototype);
console.log(keys);//["name", "age", "job", "sayName"]
var keys = Object.keys(person1);
console.log(keys);// ["name"] 实例属性
Person.prototype.toString = function(){
return "ok";
}
var keys = Object.getOwnPropertyNames(Person.prototype);
console.log(keys);// ["constructor", "name", "age", "job", "sayName", "toString"] 包含不可枚举的 注意还有tostring;
var keys = Object.getOwnPropertyNames(person1);
console.log(keys);// ["name"] 证明只有构造函数才有constructor等方法
//person1.prototype.name = "dfsa"; //TypeError: person1.prototype is undefined
function NewPerson(){};
NewPerson.prototype = {//采用字面量的方式书写;
name:"xiaodong",
age: 23,
sayName: function(){
return this.name;
}
};
var newperson = new NewPerson();
console.log(newperson instanceof Object);//ture
console.log(newperson instanceof NewPerson);//ture
console.log(newperson.constructor == NewPerson);//false 重写了默认的prototype,所以其constructor属性变成了新对象的constructor属性(指向Object);
console.log(newperson.constructor == Object);//ture
function NewPerson2(){};
NewPerson2.prototype = {//采用字面量的方式书写;
constructor:NewPerson2,//显式指定
name:"xiaodong",
age: 23,
sayName: function(){
return this.name;
}
};
var newperson2 = new NewPerson2();
console.log(newperson2.constructor == NewPerson2);//ture;
NewPerson2.prototype.name = "tiantian";
console.log(newperson2.name);//原型的动态性 对其修改会在所有的对象实例中反映出来
NewPerson2.prototype = {//但如果重写的话,就会切断构造函数与最初原型的关系 page156 实例中的指针仅指向原型,而不是构造函数
name : "other"
};
console.log(newperson2.name);//tiantian
console.log(newperson2.constructor === NewPerson2)//true
//原型模式的缺点:所有的属性都是共享的.
function Friend(){};
Friend.prototype={
name:"jack",
fav:["苹果","葡萄"],
run:function(){
return this.fav;
}
};
var friend1 = new Friend();
var friend2 = new Friend();
friend1.fav.push("梨子");//引用类型
console.log(friend1.fav);// ["苹果", "葡萄", "梨子"] 引用类型
console.log(friend2.fav);// ["苹果", "葡萄", "梨子"] 引用类型
//组合使用构造函数模式和原型模式 目前最常用的方式
function Base(name,age){
this.name = name;
this.age = age;
};
Base.prototype.run = function(){
return this.name;
};
var base1 = new Base("xiaodong",23);
var base2 = new Base("tiantian",22);
console.log(base1.run());//"xiaodong"
console.log(base2.run());//"tiantian"
console.log(base1.run === base2.run);//true
</script>
</head>
<body>
</body>
</html>
在linux+Apache环境下开启gzip的方法:
LoadModule deflate_module modules/mod_deflate.so
<ifmodule mod_deflate.c>
DeflateCompressionLevel 6
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE text/javascript
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/atom_xml
AddOutputFilterByType DEFLATE application/x-javascript
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-httpd-php
AddOutputFilterByType DEFLATE image/svg+xml
AddOutputFilterByType DEFLATE application/json
</ifmodule>
查看header信息中request header
Accept:image/webp,*/*;q=0.8
Accept-Encoding:gzip, deflate, sdch
Accept-Language:zh-CN,zh;q=0.8
Cache-Control:no-cache
Connection:keep-alive
Cookie:__cfduid=d5cc4c6799d5826ebe40e22a6ce4c1ff61427275063; tq_current_visit_time=1427423496918; tq_current_source_page_url=http://we.car91.cn/exposure/exposure/index; JSESSIONID=48D7BC57B2BD6868DEDB2AFC646BEB84.jvm1
Host:www.car91.cn
Pragma:no-cache
Referer:http://www.car91.cn/default/index
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36
Accept-Encoding就表示客户端支持的压缩格式
查看header信息中response header
Accept-Ranges:bytes
Cache-Control:max-age=5356000
Connection:Keep-Alive
Content-Encoding:gzip
Content-Length:32775
Content-Type:application/javascript
Date:Fri, 27 Mar 2015 06:32:19 GMT
ETag:"169d5-5111212ad8b00-gzip"
Keep-Alive:timeout=5, max=94
Last-Modified:Thu, 12 Mar 2015 07:10:04 GMT
Server:Apache/2.4.10 (Unix)
Vary:Accept-Encoding
中看到Content-Encoding:gzip
则表示已经启用了压缩
LoadModule headers_module modules/mod_headers.so
###在httpd.conf中添加需要缓存的文件类以及设置时间
<FilesMatch ".(flv|gif|jpg|jpeg|png|ico|swf|js|css|pdf|json)$">
Header set Cache-Control "max-age=5356000"
</FilesMatch>
设置成功后,如上在response header中就可以看到Cache-Control:max-age=5356000
的效果了。
对于静态资源,我们不需要在每次请求中带上cookie信息,对于静态资源避免带上cookie的一个比较简单的方法就是单独用一个域名来保存静态资源,因为不同域的请求不会带上其它域的cookie,比如网站域名是www.abc.com,则可以另外单独用一个static.def.com的域名来保存静态资源。或者单独分配一个static.abc.com来保存,但对于这种二级域名要避免在主域名中存在.abc.com这样作用域的cookie,否则还是会把cookie请求上谨言该主域下面的所有请求上。
要降低首字节的响应时间,一个是检查dns解析时间,如果域名的dns解析时间过长,则需要考虑更换域名的dns以提高解析速度。另一个则是优化页面的响应时间了。
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.