Fork me on GitHub
杨溢的博客

努力向上爬的小人物


  • 首页

  • 归档

爬虫溢关于Yeoman

发表于 2018-01-21

Yeoman官网教学案例:使用Yeoman构建WebApp

设置开发环境

与 Yeoman 的所有交互都是通过命令行。Mac 系统使用 terminal.app,Linux 系统使用 shell,windows 系统可以使用 cmder/PowerShell/cmd.exe。

安装条件

安装yeoman之前,你需要先安装如下内容

  • Nodejs v4 或者更高版本
  • npm
  • git

通过以下命令检查是否安装 Node 环境以及 npm 管理工具。

$ node -v && npm -v

npm 默认随 Node 一起安装。有些 Node 版本可能安装的是旧版本的 npm,你可以通过以下命令更新 npm

$ npm install -g npm@latest

通过以下命名检查是否安装git

$ git --version

安装yeoman工具箱

如果已经安装了 node 环境,可以通过以下命令安装 Yeoman

$ npm install -g yo

确认安装

首先确认 Yeoman 是否正确安装

$ yo --version

安装Yeoman生成器

在传统的 web开发中,你需要花大量时间为你的 webapp 设置模板代码、下载依赖包以及手动创建文件目录结构。Yeoman 的生成器会帮你搞定这一切。让我为 FountainJS 项目安装一个生成器。

安装生成器

你可以通过 npm 命令安装 Yeoman 生成器,目前可用的生成器超过了 3500 个,大多数都是开源社区贡献的。

通过以下命令安装 generator-fountain-webapp

$ npm install -g generator-fountain-webapp

该命令将安装生成器所需的node包。

和使用 npm install 一样,你可以通过 Yeoman 的交互菜单搜索 generators。

运行 yo 然后选择 Install a generator 来搜索发布的生成器。

使用生成器搭建我们的app

我们已经使用多次“脚手架”这个词,但是你可能还不知道它是什么意思。在 Yeoman的 语境中,脚手架材料表示通过一些配置为你的 webapp 生成文件。在这一步中,你会看到 Yeoman 如何为你喜欢的库及框架生成文件,以及使用如 webpack/babel/Sass 等一些额外的库的配置。

创建项目文件夹

创建 mytodo 文件夹

$ mkdir mytodo && cd mytodo

生成器生成的脚手架文件会放在这个文件夹中。

通过 Yeoman 菜单使用生成器

再次运行 yo

$ yo

如果你已经安装了多个 generator,你需要从中选择一个。选中 Fountain Webapp,按回车 enter 运行生成器。

配置生成器

为了加快开发环境的初始化设置,有些生成器也会提供选项来自定义你的app的基础开发库。

FountainJS 生成器提供一些选项来匹配你的喜好。

  • 框架(React,Angular2,Angular1)
  • 模块管理工具(Webpack,SystemJS,none with bower)
  • JavaScript预处理器(babel,TypeScript,none)
  • css 预处理器(Sass,Less,none)
  • 三个模板app(a landing page,hello world,TodoMVC)

在该案例中,我们会使用 React, Webpack, Babel, SASS,Redux TodoMVC 模板。


方向键选择,回车键确认。


Yeoman 会自动搭建你的 app,获取依赖包。几分钟之后我们将进行下一步。

查看Yeoman生产的app的目录结构

打开你的 mytodo

目录,看一下脚手架搭建了什么。应该如下图所示:

在 mytodo 文件夹中,我们有:

src: web应用的父目录

  • app:React+Redux的代码
  • index.html:基础html文件
  • index.js:TodoMVC app 的入口文件

conf:配置文件及第三方工具的父目录(Bowersync,Webpack,Gulp,karma)

gulp_tasks 和 gulpfile.js:构建任务

.babelrc,package.json,node_modules:配置以及所需依赖包

.gitattributes 和 .gitignore:git的配置

在浏览器中预览你的app

如果想要在你喜欢的浏览器上预览你的 web app,你无须在电脑上做任何事情来设置本地服务器。这些都是 Yeoman 所做的一部分。

打开服务器

运行 npm 脚本,创建在 localhost:3000 (或者127.0.0.1:3000) 上预览的基于 node 的本地 http 服务器。

$ npm run serve

在浏览器的新页面打开localhost:3000

停止服务器

如果你想停止服务器,按 Ctrl + C 停止当前CLI的进程

注意:你不能在同一端口运行多个http服务器(默认3000)

查看你的文件

打开你喜欢的文本编辑器开始做点改变。每一次改变都会强制浏览器刷新而不需要你亲自操作。这种方式叫做即时加载(live reloading),可以实时查看app状态。

即时加载的功能是通过配置 gulpfile.js 中的 gulp tasks 以及 gulp_tasks/browsersync.js 中的 Browsersync 实现的。它会监测你的文件的变化然后自动加载。

如下,我们编辑 src/app/components 路径下的 Header.js

修改立即生效

使用karma和jasmine测试

有些人可能不熟悉Karma,它是不依赖于框架的测试运行器。Fountainjs 生成器中已经包含 jasmine 测试框架。

运行测试单元

让我们返回命令行按 Ctrl+C 停止本地服务器。package.json 中已经有了运行测试单元的 npm 脚本。可以如下运行

$ npm test

每一个测试都应该通过

升级单元测试

你可以在 src 文件夹中找到单元测试脚本,打开src/app/reducers/todos.spec.js 。这是为 Todos reducers 编写的单元测试。举个例子,我们看一下验证初始状态的第一个测试。

it('should handle initial state', () => {
      expect(
        todos(undefined, {})
      ).toEqual([
    {
      text: 'Use Redux',
      completed: false,
      id: 0
       }
  ]);
});

按如下修改

it('should handle initial state', () => {
      expect(
        todos(undefined, {})
      ).toEqual([
        {
         text: 'Use Yeoman', // <=== here
         completed: false,
          id: 0
     }
    ]);
  });

重新运行 npm test,可以看到如下错误

if you want run test automatically on change you can use npm run test:auto instead

打开 src/app/reducers/todos.js

把初始状态改成

const initialState = [
      {
    text: 'Use Yeoman',
    completed: false,
       id: 0
     }
];

你成功修复了测试

如果你的 app 越来越大或者更多的开发者参与进来,编写单元测试可以更容易的发现 bug。

使用 Local Storage 永久保存 todos

让我们重新看一下当刷新浏览器时 React/Redux mytodo 不能保存的问题。

安装 npm 包

为了轻松解决这个问题,我们可以使用另一个Redux模块“redux-localstorage”,它可以快速执行local storage。

运行以下命令

$ npm install --save redux-localstorage@rc

使用redux-localstorage

Redux 需要配置才能使用,将 src/app/store/configureStore.js 修改如下

import {compose, createStore} from 'redux';
import rootReducer from '../reducers';

import persistState, {mergePersistedState} from 'redux-localstorage';
import adapter from 'redux-localstorage/lib/adapters/localStorage';

export default function configureStore(initialState) {
  const reducer = compose(
mergePersistedState()
  )(rootReducer, initialState);

  const storage = adapter(window.localStorage);

  const createPersistentStore = compose(
    persistState(storage, 'state')
  )(createStore);

  const store = createPersistentStore(reducer);
  if (module.hot) {
    // Enable Webpack hot module replacement for reducers
    module.hot.accept('../reducers', () => {
      const nextReducer = require('../reducers').default;
      store.replaceReducer(nextReducer);
    });
  }

  return store;
}

如果你浏览器中查看应用程序,你会在待办事项列表看到一项“Use Yeoman”。

应用程序初始化时,如果本地存储是空的,则列表中不会有事项。

继续前进,并添加一些项目到列表中:

现在当我们刷新浏览器列表项依然存在。万岁!

我们可以确认一下数据是否保存在本地存储中,打开chrome浏览器的检查工具,产看 Resources 面板,从左边栏选择 Local Storage

为生产做准备

准备好把你 todo 应用程序展示给世界了吗?让我们尝试建立一个准备生产的版本。

优化产品文件

为了创建应用程序的生产版本,我们需要

  • lint 代码
  • 合并和缩小我们的脚本及样式来拯救那些网络请求
  • 编译预处理器的输出结果
  • 使应用程序更精炼

哇!令人惊讶的是,所有运行都可以通过:

$ npm run build

建立及预览生产的应用程序

如果想在本地预览 app,可以运行下面的 npm 脚本

$ npm run serve:dist

它会创建你的项目并且启动本地服务器。

功能强大的 JQuery 图片查看器 https://github.com/nzbin/magnify

简化类名的轻量级 CSS 框架 https://github.com/nzbin/snack

与任意 UI 框架搭配使用的通用辅助类 https://github.com/nzbin/snack-helper

类似纸牌游戏的卡片抽奖插件 https://github.com/nzbin/CardShow

本文参考:Yeoman官网教学案例

爬虫溢关于svn

发表于 2018-01-21

上一篇文章简单介绍了git的使用,这篇文章主要介绍svn的使用。
svn官网:https://tortoisesvn.net/

什么是SVN

SVN是Subversion的简称,是一个开放源代码的版本控制系统,相较于RCS、CVS,它采用了分支管理系统,它的设计目标就是取代CVS。

SVN的下载安装

下载地址:https://tortoisesvn.net/downloads.zh.html

安装完不要忘记重启电脑

安装完成后,按下鼠标右键,会看到如下界面:

说明SVN已经安装成功了。

SVN的常用操作

迁出配置库内容(SVN Checkout)

  1. 新建或进入目录下(比如E盘),右键 →SVN Checkout
  2. URL of repository 填写仓库路径即可
  3. Revision处,“HEAD revision”是指最新版,也可以指定Revision为任意一个版本。
  4. 点击“OK”按钮后,在弹出的对话框中输入用户名和密码,验证成功后,项目文件开始从远程服务器下载到本地工作目录中:
  5. 点击“确定”按钮后,即可获取完成,出现如下下载界面:
  6. 下载完成后,服务器上所有内容会出现在本地文件夹下

更新文件(SVN Update)

1)当从配置库迁出相应目录后,他人对服务器上此目录内容进行了修改,则需要再次获取改动内容到本地目录的过程称为更新。更新可以针对一个文件、几个选中的文件或者整个文件目录。
选中要被更新的文件,右键选择“SVN Update”项,如下:


2)点击“SVN Update”后会弹出窗口显示更新的进度,如下:

若上述框中的有文件出现亮红,说明来自配置库的内容与你本地修改内容合并时出现了冲突

提交更新(SVN Commit)

1)本地文件修改后,若是需要更新到服务器上,则需要提交(Commit)最新的更新。
Commit的作用是将本地最新修改的文件同步到SVN服务端,供其他人来参考或者使用,当然使用之前,要先Update一下,来确保是最新的,在修改文件上击右键,出现菜单,选择“SVN Commit…”,如下:


2)然后填写关于本次更新的日志(log message),这是必填项,否则commit会失败,如下:


3)当出现下图所示提示框,则表明刚刚的修改已成功提交,并且当前的SVN版本号加1。

增加文件(Add)

1)将需要增加的新文件放入到本地迁出的文件夹TestManger目录的相应位置中,鼠标选中新文件右键选择“Tortoise SVN”的“Add”项,如下图所示:


2)鼠标选中TestManger文件夹右键选择“SVN Commit…”,将新文件上传配置库对应文件夹中(若只上传单个文件,只需点中单个文件上传即可)。

检查更新(Check for modifications)

此功能可以显示本地对文件所做的修改有哪些还没有提交。不光能看到对文件的修改变化,还包括增加文件或目录,删除文件或目录,移动文件或目录等。当他人提交了哪些文件的改动,也可通过此项来进行查询。

删除文件(Delete)

1)选中要被删除的文件,右键选择“Tortoise SVN”的“Delete”项,如下:

2)删除文件后,鼠标选中TestManger文件夹右键选择“SVN Commit…”项进行提交,提交方式同增加文件的提交方式,提交后则将新文件从配置库中删除。

撤销更改(Revert)

在修改了某些文件后(文件未上传到配置库),需要返回到修改前的状态,则选中文件夹右键选择“Tortoise SVN”的“Revert…”项进行撤销,本地硬盘上的文件将恢复到修改前的内容,修改的内容将被删除。

锁定和解锁(Get lock and Release lock)

当项目需要时可以在本地硬盘中将迁出的内容进行锁定,选中要被锁定的文件右键选择“Tortoise SVN”的“Get lock…”项进行锁定(锁定后他人将无法修改此文件),系统弹出锁定信息框。 当文本文件锁定后,需要通过解锁他人才能继续对文件进行修改。

选中被锁定的文件右键选择“Tortoise SVN”的“Release lock…”项进行解锁。

重命名文件(Rename)

修改文件名,选中需要重命名的文件或文件夹,然后右键“Tortoise SVN”的“Rename”,在弹出的对话框中输入新名称,点击“OK”按钮,并将修改文件名后的文件或文件夹“SVN Commit…”提交到SVN服务器上。

获取历史文件(Show log)

Show log顾名思义是显示日志的作用,主要是显示该文件或者该目录被执行的操作,是被谁修改了,以及修改的时间和日期。鼠标选中文件夹右键选择“Tortoise SVN”的“Show log”项,系统弹出此路径下的所有文件版本信息,如下:

本文参考:详情SVN使用

爬虫溢关于express

发表于 2018-01-20

Express框架:

在说express框架之前我们先简单介绍一下淘宝cnpm镜像:

淘宝做了一个npm的镜像,叫做cnpm。

官网: npm.taobao.org

特别简单复制下面的程序到CMD中按回车就行了:

npm install -g cnpm --registry=https://registry.npm.taobao.org

-g安装表示安装命令行程序,安装完毕之后,我们就能在CMD中使用cnpm了。

整体感知

做http服务的时候,不方便:

  • 匹配URL很不方便 if(//.test()){}
  • 使用静态页面不方便 fs.readFile(function(err,data){res.end(data)})
  • 不能静态化一个文件夹,我们想将一个文件夹中的所有文件自动拥有路由,实现不了
  • ……

Express简化了HTTP应用程序的开发。

安装依赖:

cnpm intsall --save express

express官网:http://www.expressjs.com.cn/

创建app和app的监听

我们引入express之后,这个express是一个函数,这个函数可以调用创建出一个app对象。
今后所有的操作都是用app对象来完成,需要注意的是,一个程序中只有一个app。
也就是说express不能多次调用。
express程序的基本结构:

var express = require("express");
var app = express();

中间件
中间件
中间件
中间件

app.listen(3000);

中间件

动词

中间件的语法:

app.动词("地址" , function(req,res){

});

我们先说动词,它是26种HTTP请求,必须是小写字母:

动词表示当用户用这种请求访问这个页面的时候做的事情。
我们现在就可以区分出用GET请求访问首页和POST请求访问首页做不同的事情:

var express = require("express");
var app = express();

app.get("/" , function(req,res){
    console.log("A");
});

app.post("/" , function(req,res){
    console.log("B");
});

app.listen(3000);

中间件的路径是自动比对主干部分

路径已经自动被url.parse()了,也就是说express会用用户输入的URL的主干部分来进行比对。
也就是说我的中间件如果是:

app.get("/xinwen" , function(req,res){
    res.send("<h1>新闻频道</h1>");
});

下面的URL都是合法的能够进入这个频道的:

http://127.0.0.1:3000/xinwen
http://127.0.0.1:3000/xinwen/
http://127.0.0.1:3000/xinwen?id=234234
http://127.0.0.1:3000/xinwen?id=234234#234234345435234

中间件可以有通配符

用:来表示画一个通配,要注意这里没有正则表达式的,在程序中可以通过req.params.***得到它。

app.get("/:banji/:xuehao" , function(req,res){
    var banji = req.params.banji;
    var xuehao = req.params.xuehao;

    res.send(banji + "班" + xuehao + "号");
});

今后的编程就不用写match()和test()方法了。
还有一种*的通配符,不常用,自己看手册。

中间件的顺序很关键

比如我们输入班级、学号查询学生信息,但是不能查询3班8号。此时一定要注意中间件的顺序。
中间件一旦匹配上的路由,此时不再进行其他匹配。有一种“拦截”的感觉。

app.get("/3/8" , function(req,res){
    res.send("<h1>校长的儿子你也敢查!</h1>")
});

app.get("/:banji/:xuehao" , function(req,res){
    var banji = req.params.banji;
    var xuehao = req.params.xuehao;

    res.send(banji + "班" + xuehao + "号");
});

用next()放行拦截

当一个中间件已经匹配了路径,但是自己不希望单独处理这次请求,可以用next来放行。

我们做一个业务能够查询学生或者老师的信息,不管查询什么都要增加计数器的数量。此时可以单独用一个中间件写计数器,放行请求即可:

app.get("/chaxun/*" , function(req,res,next){
    count++;
    next();
});

app.get("/chaxun/xuesheng/:banji/:xuehao" , function(req,res){
    res.send("查询" + req.params.banji + "班" + req.params.xuehao + "号" + "<br /> 共查询了" + count + "次");
});

app.get("/chaxun/laoshi/:gonghao" , function(req,res){
    res.send("查询" + req.params.gonghao + "工号的老师" + "<br /> 共查询了" + count + "次");
});

输出

  • 输出可以用res.send()做输出,会自动加上utf-8。

    app.get("/" , function(req,res){
        res.send("中文");
    });
    
  • 如果输出的内容是一个JSON,此时要用res.json()来进行输出。

    app.get("/" , function(req,res){
        res.json({"a":1,"b":2,"c":[1,2,3,4,{"m":4}]});
    });
    
  • 如果输出的内容是一个JSONP,此时要用res.jsonp()来输出,此时它会自动检测callback的GET请求,并且加上圆括号的调用。

    app.get("/" , function(req,res){
        res.jsonp({"a":1,"b":2,"c":[1,2,3,4,{"m":4}]});
    });
    

    复习一下jQuery中的jsonp跨域:

    $.ajax({
        "url" : "/?callback=?",
        "dataType" : "JSONP",
        "success" : function(data){
    
        }
    });
    
    //机理
    jQuery帮我们创建了一个<script>标签,src是这个url,用随机的字符串替换了?
    用随机的字符串为名字创建了一个全局的函数,将success指向它。
    

  • 如果输出的是一个外置页面,此时要用sendFile()这个API,注意这里必须要用绝对路径,此时我们用__dirname来进行一个拼合。

    app.get("/",function(req,res){
        //_dirname表示当前文件所在的目录
        res.sendFile(_dirname+"/public/a.html");
    });
    
  • 如果想要跳转页面,用res.redirect()即可

    app.get("/",function(req,res){
        res.redirect("http://www.163.com");
    });
    

    复习:

    res.send();
    res.sendFile();
    res.json();
    res.jsonp();
    res.redirect();
    

    静态化一个文件夹
    如果我们想让某文件夹中的所有文件自动拥有路由,此时非常简单,一句话即可(最好记下来):

    app.use(express.static("public"));

此时将把public文件进行静态化。

我们在app.js中静态化public文件夹:

var express = require("express");
var app = express();

app.use(express.static("public"));

app.listen(3000);

此时:


更进一步,如果我们不希望静态的文件夹出现在底层,而是在URL中体现public的名字,此时:

app.use("/public",express.static("public"));

Express中的GET请求和POST请求参数的获得

GET请求参数的获得


GET请求参数的识别实际上就是URL地址的解析。URL解析使用内置的url模块的parse方法即可。

var url = require("url");
app.get("/tijiao" , function(req,res){
    var query = url.parse(req.url , true).query;
    console.log("服务器收到了前端交来的数据" , query);
});

POST请求参数的获得

POST请求的参数携带在上行报文的报文体中。

我们使用npm包formidable来识别这样的上行报文。

API:https://www.npmjs.com/package/formidable

安装依赖:

cnpm install formidable --save

后台app.js识别POST请求需要使用formidable这个包。

var formidable = require('formidable');
app.post("/tijiao" , function(req,res){
    var form = new formidable.IncomingForm();

    form.parse(req , function(err , fields , files){
        res.json({"result" : 1})
    });
});

总结一下两种请求后台怎么得到参数(伪代码):

GET请求 POST请求
var url = require(“url”); var query = url.parse(req.url , true).query; var formidable = require(“formidable”); app.post(“/tijiao” , function(req,res){ var form = new formidable.IncomingForm(); form.parse(req , function(err , fields , files){ console.log(fields); }); });

其他请求

一共有26种请求,注意只有GET请求是通过URL缀?参数来传递参数的。其他的25种请求,都是通过上行报文来传参数的。formidable能够识别其他25种请求的参数。

先说一下jQuery如何发出DELETE请求:

$("#btn3").click(function(){
    $.ajax({
        "url" : "/tijiao" ,
        "type" : "DELETE" ,
        "data" : {
            "id" : 10086
        },
        "success" : function(data){
            alert(data.result);
        }
    });
});

我们的Express这样识别它:(你会发现和POST请求的处理方法是一样的,都是formidable):

//识别DELETE请求
app.delete("/tijiao" , function(req,res){
    var form = new formidable.IncomingForm();
    form.parse(req , (err , fields , files) => {
        console.log("服务器收到DELETE请求参数" , fields);
        res.json({"result" : 1});
    });
});

总结一下:

GET请求 其他请求
var url = require(“url”); var query = url.parse(req.url , true).query; var formidable = require(“formidable”); app.动词(“/tijiao” , function(req,res){ var form = new formidable.IncomingForm(); form.parse(req , function(err , fields , files){ console.log(fields); }); });

RESTful风格路由

注意这个单词的写法:RESTful。REST是Representational State Transfer。

RESTful风格的路由很简单,指的是用URL表示操作的资源,用HTTP动词表示何种操作。

RESTful风格的路由

事儿 处理这个事儿的URL
增加一个学生 http://127.0.0.1/student (POST)
删除一个学号为10086的学生 http://127.0.0.1/student/10086 (DELETE)
修改一个学号为10086的学生的性别 http://127.0.0.1/student/10086 (PATCH)
列出所有学生 http://127.0.0.1/student (GET)

爬虫溢关于git

发表于 2018-01-18

git使用的普遍想必在前端行业工作过的都知道,今天我粗略总结一下git基本使用,方便我以及各位在今后的使用。(本文参考:廖雪峰官网的git教程)

首先是安装git

在Linux上安装Git

首先,你可以试着输入git,看看系统有没有安装Git:

$ git

The program ‘git’ is currently not installed. You can install it by typing:
sudo apt-get install git

像上面的命令,有很多Linux会友好地告诉你Git没有安装,还会告诉你如何安装Git。

如果你碰巧用Debian或Ubuntu Linux,通过一条sudo apt-get install git就可以直接完成Git的安装,非常简单。

创建版本库

创建一个版本库非常简单,首先,选择一个合适的地方,创建一个空目录:

$ mkdir learngit
$ cd learngit
$ pwd
/Users/yangyia/learngit

pwd命令用于显示当前目录。在我的Mac上,这个仓库位于/Users/yangyia/learngit。

如果你使用Windows系统,为了避免遇到各种莫名其妙的问题,请确保目录名(包括父目录)不包含中文。

第二步,通过git init命令把这个目录变成Git可以管理的仓库:

$ git init

瞬间Git就把仓库建好了,而且是一个空的仓库(empty Git repository),你可以发现当前目录下多了一个.git的目录,这个目录是Git来跟踪管理版本库的,千万不要手动修改这个目录里面的文件,不然改乱了,就把Git仓库给破坏了。

如果你没有看到.git目录,那是因为这个目录默认是隐藏的,用ls -ah命令就可以看见。

把文件添加到版本库

编写一个readme.txt文件,内容如下:

Git is a version control system.
Git is free software.
一定要放到learngit目录下(子目录也行),因为这是一个Git仓库,放到其他地方Git再厉害也找不到这个文件。

把一个文件放到Git仓库只需要两步。

第一步,用命令git add告诉Git,把文件添加到仓库:

$ git add readme.txt

执行上面的命令,没有任何显示,说明添加成功。

第二步,用命令git commit告诉Git,把文件提交到仓库:

$ git commit -m "wrote a readme file"

-m后面输入的是本次提交的说明,可以输入任意内容,当然最好是有意义的,这样你就能从历史记录里方便地找到改动记录。

git status //命令可以让我们时刻掌握仓库当前的状态
git diff  //看看具体修改了什么内容

版本回退

git log  //命令查看历史记录

如果嫌输出信息太多,可以加上--pretty=oneline参数

$ git log --pretty=oneline

在Git中,用HEAD表示当前版本,上一个版本就是HEAD^,上上一个版本就是HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成HEAD~100。

$ git reset --hard HEAD^

查看文件内容:

$ cat readme.txt //查看内容

Git提供了一个命令git reflog用来记录你的每一次命令:

$ git reflog

git checkout -- file可以丢弃工作区的修改:

$ git checkout -- readme.txt

如果你已经git add了,那么如果你想撤销删除,执行下面:

$ git reset HEAD readme.txt

直接在文件管理器中把没用的文件删了,或者用rm命令删了:

$ rm test.txt

现在你有两个选择,一是确实要从版本库中删除该文件,那就用命令git rm删掉,并且git commit:

$ git rm test.txt    

现在,文件就从版本库中被删除了。

另一种情况是删错了,因为版本库里还有呢,所以可以很轻松地把误删的文件恢复到最新版本:

git checkout -- test.txt

远程仓库

第1步:创建SSH Key。在用户主目录下,看看有没有.ssh目录,如果有,再看看这个目录下有没有id_rsa和id_rsa.pub这两个文件,如果已经有了,可直接跳到下一步。如果没有,打开Shell(Windows下打开Git Bash),创建SSH Key:

$ ssh-keygen -t rsa -C "youremail@example.com"

把邮件地址换成你自己的邮件地址,然后一路回车,使用默认值即可,无需设置密码。

如果一切顺利的话,可以在用户主目录里找到.ssh目录,里面有id_rsa和id_rsa.pub两个文件,这两个就是SSH Key的秘钥对,id_rsa是私钥,不能泄露出去,id_rsa.pub是公钥,可以放心地告诉任何人。

第2步:登陆GitHub,打开“Account settings”,“SSH Keys”页面:

然后,点“Add SSH Key”,填上任意Title,在Key文本框里粘贴id_rsa.pub文件的内容,点“Add Key”,你就应该看到已经添加的Key。

为什么GitHub需要SSH Key呢?

因为GitHub需要识别出你推送的提交确实是你推送的,而不是别人冒充的,而Git支持SSH协议,所以,GitHub只要知道了你的公钥,就可以确认只有你自己才能推送。

添加远程库

首先,登陆GitHub,然后,在右上角找到“Create a new repo”按钮,创建一个新的仓库,在Repository name填入learngit,其他保持默认设置,点击“Create repository”按钮,就成功地创建了一个新的Git仓库,目前,在GitHub上的这个learngit仓库还是空的,GitHub告诉我们,可以从这个仓库克隆出新的仓库,也可以把一个已有的本地仓库与之关联,然后,把本地仓库的内容推送到GitHub仓库。

现在,我们根据GitHub的提示,在本地的learngit仓库下运行命令:

$ git remote add origin git@github.com:yourgithub/learngit.git

请千万注意,把上面的yourgithub替换成你自己的GitHub账户名

添加后,远程库的名字就是origin,这是Git默认的叫法,也可以改成别的,但是origin这个名字一看就知道是远程库。

下一步,就可以把本地库的所有内容推送到远程库上:

$ git push -u origin master

由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。

推送成功后,可以立刻在GitHub页面中看到远程库的内容已经和本地一模一样,从现在起,只要本地作了提交,就可以通过命令:

$ git push origin master

从远程库克隆

$ git clone git@github.com:yourgithub/gitskills.git

切换到gitskills下:

$ cd gitskills
$ ls        //查看当前文件夹下的文件

创建与合并分支

首先,我们创建dev分支,然后切换到dev分支:

$ git checkout -b dev
Switched to a new branch 'dev'

git checkout命令加上-b参数表示创建并切换,相当于以下两条命令:

$ git branch dev
$ git checkout dev
Switched to branch 'dev'

然后,用git branch命令查看当前分支:

$ git branch
* dev
  master

git branch命令会列出所有分支,当前分支前面会标一个*号。

然后,我们就可以在dev分支上正常提交,比如对readme.txt做个修改,加上一行:

Creating a new branch is quick.

然后提交:

$ git add readme.txt 
$ git commit -m "branch test"

现在,dev分支的工作完成,我们就可以切换回master分支:

$ git checkout master
Switched to branch 'master'

切换回master分支后,再查看一个readme.txt文件,刚才添加的内容不见了!因为那个提交是在dev分支上,而master分支此刻的提交点并没有变,现在,我们把dev分支的工作成果合并到master分支上:

$ git merge dev

git merge命令用于合并指定分支到当前分支。合并后,再查看readme.txt的内容,就可以看到,和dev分支的最新提交是完全一样的。

注意到上面的Fast-forward信息,Git告诉我们,这次合并是“快进模式”,也就是直接把master指向dev的当前提交,所以合并速度非常快。

当然,也不是每次合并都能Fast-forward,我们后面会讲其他方式的合并。

合并完成后,就可以放心地删除dev分支了:

$ git branch -d dev

删除后,查看branch,就只剩下master分支了:

$ git branch

查看分支:git branch

创建分支:git branch <name>

切换分支:git checkout <name>

创建+切换分支:git checkout -b <name>

合并某分支到当前分支:git merge <name>

删除分支:git branch -d <name>

解决冲突

用带参数的git log也可以看到分支的合并情况:

$ git log --graph --pretty=oneline --abbrev-commit

用git log --graph命令可以看到分支合并图。

Git还提供了一个stash功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作:

$ git stash

工作现场还在,Git把stash内容存在某个地方了,但是需要恢复一下,有两个办法:

一是用git stash apply恢复,但是恢复后,stash内容并不删除,你需要用git stash drop来删除;

另一种方式是用git stash pop,恢复的同时把stash内容也删了:

$ git stash pop

多人协作

当你从远程仓库克隆时,实际上Git自动把本地的master分支和远程的master分支对应起来了,并且,远程仓库的默认名称是origin。

要查看远程库的信息,用git remote:

$ git remote

或者,用git remote -v显示更详细的信息:

$ git remote -v

上面显示了可以抓取和推送的origin的地址。如果没有推送权限,就看不到push的地址。

推送分支

推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git就会把该分支推送到远程库对应的远程分支上:

$ git push origin master

如果要推送其他分支,比如dev,就改成:

$ git push origin dev

但是,并不是一定要把本地分支往远程推送,那么,哪些分支需要推送,哪些不需要呢?

  • master分支是主分支,因此要时刻与远程同步;
  • dev分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;
  • bug分支只用于在本地修复bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug;
  • feature分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。

抓取分支

多人协作时,大家都会往master和dev分支上推送各自的修改。

现在,模拟一个你的小伙伴,可以在另一台电脑(注意要把SSH Key添加到GitHub)或者同一台电脑的另一个目录下克隆:

$ git clone git@github.com:yangyia/learngit.git

当你的小伙伴从远程库clone时,默认情况下,你的小伙伴只能看到本地的master分支。不信可以用git branch命令看看:

$ git branch

现在,你的小伙伴要在dev分支上开发,就必须创建远程origin的dev分支到本地,于是他用这个命令创建本地dev分支:

$ git checkout -b dev origin/dev

现在,他就可以在dev上继续修改,然后,时不时地把dev分支push到远程:

$ git commit -m "add /usr/bin/env"

你的小伙伴已经向origin/dev分支推送了他的提交,而碰巧你也对同样的文件作了修改,并试图推送:

$ git add hello.py 
$ git commit -m "add coding: utf-8"
$ git push origin dev

推送失败,因为你的小伙伴的最新提交和你试图推送的提交有冲突,解决办法也很简单,Git已经提示我们,先用git pull把最新的提交从origin/dev抓下来,然后,在本地合并,解决冲突,再推送:

$ git pull

git pull也失败了,原因是没有指定本地dev分支与远程origin/dev分支的链接,根据提示,设置dev和origin/dev的链接:

$ git branch

再pull:

$ git pull

这回git pull成功,但是合并有冲突,需要手动解决,解决的方法和分支管理中的解决冲突完全一样。解决后,提交,再push:

$ git commit -m "merge & fix hello.py"
$ git push origin dev

因此,多人协作的工作模式通常是这样:

  1. 首先,可以试图用git push origin branch-name推送自己的修改;
  2. 如果推送失败,则因为远程分支比你的本地更新,需要先用git pull试图合并;
  3. 如果合并有冲突,则解决冲突,并在本地提交;
  4. 没有冲突或者解决掉冲突后,再用git push origin branch-name推送就能成功!

如果git pull提示“no tracking information”,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream branch-name origin/branch-name。

这就是多人协作的工作模式,一旦熟悉了,就非常简单。

查看远程库信息,使用git remote -v;

本地新建的分支如果不推送到远程,对其他人就是不可见的;

从本地推送分支,使用git push origin branch-name,如果推送失败,先用git pull抓取远程的新提交;

在本地创建和远程分支对应的分支,使用git checkout -b branch-name origin/branch-name,本地和远程分支的名称最好一致;

建立本地分支和远程分支的关联,使用git branch --set-upstream branch-name origin/branch-name;

从远程抓取分支,使用git pull,如果有冲突,要先处理冲突。

标签管理

创建标签

在Git中打标签非常简单,首先,切换到需要打标签的分支上:

$ git branch
$ git checkout master
Switched to branch 'master'

然后,敲命令git tag <name>就可以打一个新标签:

$ git tag v1.0

可以用命令git tag查看所有标签:

$ git tag
v1.0

默认标签是打在最新提交的commit上的。有时候,如果忘了打标签,比如,现在已经是周五了,但应该在周一打的标签没有打,怎么办?

方法是找到历史提交的commit id,然后打上就可以了:

$ git log --pretty=oneline --abbrev-commit

比方说要对add merge这次提交打标签,它对应的commit id是6224937,敲入命令:
$ git tag v0.9 6224937
再用命令git tag查看标签:

$ git tag
v0.9
v1.0

注意,标签不是按时间顺序列出,而是按字母排序的。可以用git show <tagname>查看标签信息:
$ git show v0.9
可以看到,v0.9确实打在add merge这次提交上。

还可以创建带有说明的标签,用-a指定标签名,-m指定说明文字:

$ git tag -a v0.1 -m "version 0.1 released" 3628164

用命令git show <tagname>可以看到说明文字:

$ git show v0.1

还可以通过-s用私钥签名一个标签:

$ git tag -s v0.2 -m "signed version 0.2 released" fec145a

签名采用PGP签名,因此,必须首先安装gpg(GnuPG),如果没有找到gpg,或者没有gpg密钥对,就会报错:

gpg: signing failed: secret key not available
error: gpg failed to sign the data
error: unable to sign the tag

如果报错,请参考GnuPG帮助文档配置Key。

用命令git show <tagname>可以看到PGP签名信息:

$ git show v0.2

用PGP签名的标签是不可伪造的,因为可以验证PGP签名。验证签名的方法比较复杂,这里就不介绍了。

  • 命令git tag <name>用于新建一个标签,默认为HEAD,也可以指定一个commit id;
  • git tag -a <tagname> -m "blablabla..."可以指定标签信息;
  • git tag -s <tagname> -m "blablabla..."可以用PGP签名标签;
  • 命令git tag可以查看所有标签。

操作标签

如果标签打错了,也可以删除:

$ git tag -d v0.1
Deleted tag 'v0.1' (was e078af9)

因为创建的标签都只存储在本地,不会自动推送到远程。所以,打错的标签可以在本地安全删除。

如果要推送某个标签到远程,使用命令git push origin <tagname>:

$ git push origin v1.0

或者,一次性推送全部尚未推送到远程的本地标签:

$ git push origin --tags

如果标签已经推送到远程,要删除远程标签就麻烦一点,先从本地删除:

$ git tag -d v0.9

然后,从远程删除。删除命令也是push,但是格式如下:

$ git push origin :refs/tags/v0.9

要看看是否真的从远程库删除了标签,可以登陆GitHub查看。

  • 命令git push origin <tagname>可以推送一个本地标签;
  • 命令git push origin --tags可以推送全部未推送过的本地标签;
  • 命令git tag -d <tagname>可以删除一个本地标签;
  • 命令git push origin :refs/tags/<tagname>可以删除一个远程标签。

使用GitHub

如何参与一个开源项目呢?比如人气极高的bootstrap项目,这是一个非常强大的CSS框架,你可以访问它的项目主页https://github.com/twbs/bootstrap,点“Fork”就在自己的账号下克隆了一个bootstrap仓库,然后,从自己的账号下clone:

git clone git@github.com:michaelliao/bootstrap.git

一定要从自己的账号下clone仓库,这样你才能推送修改。如果从bootstrap的作者的仓库地址git@github.com:twbs/bootstrap.git克隆,因为没有权限,你将不能推送修改。

Bootstrap的官方仓库twbs/bootstrap、你在GitHub上克隆的仓库my/bootstrap,以及你自己克隆到本地电脑的仓库,他们的关系就像下图显示的那样:


如果你想修复bootstrap的一个bug,或者新增一个功能,立刻就可以开始干活,干完后,往自己的仓库推送。

如果你希望bootstrap的官方库能接受你的修改,你就可以在GitHub上发起一个pull request。当然,对方是否接受你的pull request就不一定了。

如果你没能力修改bootstrap,但又想要试一把pull request,那就Fork一下我的仓库:https://github.com/michaelliao/learngit,创建一个your-github-id.txt的文本文件,写点自己学习Git的心得,然后推送一个pull request给我,我会视心情而定是否接受。

  • 在GitHub上,可以任意Fork开源仓库;
  • 自己拥有Fork后的仓库的读写权限;
  • 可以推送pull request给官方仓库来贡献代码。

自定义Git

在安装Git一节中,我们已经配置了user.name和user.email,实际上,Git还有很多可配置项。

比如,让Git显示颜色,会让命令输出看起来更醒目:

$ git config --global color.ui true

这样,Git会适当地显示不同的颜色,比如git status命令:


文件名就会标上颜色。

我们在后面还会介绍如何更好地配置Git,以便让你的工作更高效。

搭建Git服务器

第一步,安装git:

$ sudo apt-get install git

第二步,创建一个git用户,用来运行git服务:

$ sudo adduser git

第三步,创建证书登录:

收集所有需要登录的用户的公钥,就是他们自己的id_rsa.pub文件,把所有公钥导入到/home/git/.ssh/authorized_keys文件里,一行一个。

第四步,初始化Git仓库:

先选定一个目录作为Git仓库,假定是/srv/sample.git,在/srv目录下输入命令:

$ sudo git init --bare sample.git

Git就会创建一个裸仓库,裸仓库没有工作区,因为服务器上的Git仓库纯粹是为了共享,所以不让用户直接登录到服务器上去改工作区,并且服务器上的Git仓库通常都以.git结尾。然后,把owner改为git:

$ sudo chown -R git:git sample.git

第五步,禁用shell登录:

出于安全考虑,第二步创建的git用户不允许登录shell,这可以通过编辑/etc/passwd文件完成。找到类似下面的一行:

git:x:1001:1001:,,,:/home/git:/bin/bash

改为:

git:x:1001:1001:,,,:/home/git:/usr/bin/git-shell

这样,git用户可以正常通过ssh使用git,但无法登录shell,因为我们为git用户指定的git-shell每次一登录就自动退出。

第六步,克隆远程仓库:

现在,可以通过git clone命令克隆远程仓库了,在各自的电脑上运行:

$ git clone git@server:/srv/sample.git

剩下的推送就简单了。

管理公钥

如果团队很小,把每个人的公钥收集起来放到服务器的/home/git/.ssh/authorized_keys文件里就是可行的。如果团队有几百号人,就没法这么玩了,这时,可以用Gitosis来管理公钥。

这里我们不介绍怎么玩Gitosis了,几百号人的团队基本都在500强了,相信找个高水平的Linux管理员问题不大。

管理权限

有很多不但视源代码如生命,而且视员工为窃贼的公司,会在版本控制系统里设置一套完善的权限控制,每个人是否有读写权限会精确到每个分支甚至每个目录下。因为Git是为Linux源代码托管而开发的,所以Git也继承了开源社区的精神,不支持权限控制。不过,因为Git支持钩子(hook),所以,可以在服务器端编写一系列脚本来控制提交等操作,达到权限控制的目的。Gitolite就是这个工具。

这里我们也不介绍Gitolite了,不要把有限的生命浪费到权限斗争中。

  • 搭建Git服务器非常简单,通常10分钟即可完成;
  • 要方便管理公钥,用Gitosis;
  • 要像SVN那样变态地控制权限,用Gitolite。

git官网:https://git-scm.com/

爬虫溢关于html5的个人总结(二)

发表于 2018-01-17

接上一篇,本篇主要是关于css3新特性。

整体感知:

<style type="text/css">
    div{
        width: 200px;
        height: 200px;
        background-color:orange;
        transition:all 2s cubic-bezier(0, 0.84, 0, 0.98) 0s;
    }
    div.cur{
        transform:rotateX(110deg) rotateY(360deg) translateZ(180px);
        width:600px;
    }
    section{
        margin:100px;
        perspective:300px;
        width: 200px;
        height: 200px;
        border: 1px solid #000;
        animation:zhuan 1s 0s infinite;
    }
    @-webkit-keyframes zhuan{
        from{
            transform:rotate(0deg);
        }
        to{
            transform:rotate(360deg);
        }
    }
</style>
 <input type="button" value="按我" id="btn" />
<section>
    <div id="box"></div>
</section>
<script type="text/javascript">
var btn = document.getElementById("btn");
var box = document.getElementById("box");
btn.onclick = function(){
    box.className = "cur";
}
</script>

Css3和HTML5没有任何关系。

Css3没有颠覆传统的css的写法。

Css有一个优点:浏览器遇见自己不认识的选择器、属性、会静默,不会报错!

Css3主要的改变

选择器、伪类

伪元素

Border 的变化

Background 的变化; 渐变、尺寸、裁切

Transition 过渡

Transform 变形

Animation 动画

新的盒子模型flex

响应式

属性选择器

p:first-of-type

p:last-of-type

p:nth-of-type(3)

p:nth-of-type(3n+2)

p:nth-last-of-type(2n+3)

以上的全部选择器。JQ全都支持! IE6兼容性。

关系选择器

“>”表示儿子关系

“+”表示下一个亲兄弟

“~”表示后面的所有的兄弟

伪类

:hover 表示鼠标触碰的样子

:focus 得到焦点的控件

:enabled 有效的控件

:disabled 无效的控件

:empty 空标签

伪元素

有两个冒号。 ::before 和::after

功能即使替代了css的“钩子”,可以减少小标签的使用;

注意: ::before和::after 都是行内元素,必须转块后设置宽度高度。必须写content属性。可以值为空,但不能不写。

清除浮动:

div::before{
       content:'';
    clear:both;
    display:block;
}

::selection 反选的内容

::first-line 首行

::first-letter 首字

圆角边框

border-radius 值可以用px为单位,还可以百分比。

阴影

盒子阴影:

box-shadow:inset 1px 2px 3px 4px red;

参数:

0.内阴影

1.向右的偏移

2.向下的偏移

3.模糊的程度

(右、下、模)→(肉加馍)

4.延伸(可省略)

5.颜色

文字阴影:

text-shadow 1px 2px 10px red;

参数:右、下、模、色; 没有inset和延展,可以用多阴影。

background系列的属性

Css2.1中

background-color:red;
background-image:url(../images/123.jpg);
background-repeat:no-repeat;
background-position:100px 60px;
background-attachment:fixed;

Css3又曾加一系列的属性

background-origin

背景起源,第一张背景图的渲染位置。

注意:渲染的位置是在内容的盒子里,而不是padding

background-origin:content-box;

background-clip

背景裁切。裁切的是padding区域的背景

background-clip:content-box;

两个结合:

background-origin:content-box;
background-clip:content-box;
等价于:
background:url(../images/wyf.png) content-box;

background-size(背景尺寸)

现在我们可以设置背景尺寸;

两个参数:宽度、高度。若自动的按比例设置,写auto;可以写百分比,表示容器的百分之多少。

background-size:100px 100px;

覆盖:

Background-size:cover 

容纳:

Background-size:contain

Background-size:cover尽可能的铺满,不用管图片是否显示完整。
在PS中按住shift健缩放图片,在铺满情况下,直到宽度或高度一边被卡住;

Background-size:contain尽可能的容纳整体图片,不管图片是否是重复显示。在PS中按住shift健缩放图片,直到图片高度边或宽度边被卡住,先卡哪个边就是哪个边;

背景精灵

承载背景图片的div尺寸要放大;

背景尺寸要放大

背景定位的尺寸放大

渐变

background-image:-webkit-linear-gradient(top,red,green);

Linear 线性

gradient 渐变

-webkit-  chrome  safari 必须加上前缀
-moz- 火狐的前缀
-ms-  高级版本IE edge 前缀 
-o- 欧朋

兼容性:

background-image:-webkit-linear-gradient(top,red,green); background-image:-moz-linear-gradient(top,red,green); background-image:-ms-linear-gradient(top,red,green); background-image:-o-linear-gradient(top,red,green); background-image:linear-gradient(180deg,red,green); //W3C规范的;

渐变的第一个参数是“方向”下面是从右上开始渲染

background-image:-webkit-linear-gradient(top right,red,green);

可以deg度数;

background-image:-webkit-linear-gradient(274deg,red,green); 等价: background-image:linear-gradient(176deg,red,green);

方向后面的参数是颜色的罗列;用逗号隔开;可以用百分比表示颜色的范围;

background-image:-webkit-linear-gradient(top,#ff0000 0%,#ff00ff 15%,#0000ff 33%,#00ffff 49%,#00ff00 67%,#ffff00 84%,#ff0000 100%);

径向渐变
第一个参数是圆心位置;

background-image:-webkit-radial-gradient(250px 250px,#ff0000,#ff00ff);

多背景

一个盒子可以携带多个背景图

background: url('../images/wyf.png') content-box,url('../images/ly.jpg');

过渡动画

transition 属性

transition :all 1s ease 0s;

参数:

第一个参数:transition-property属性,表示什么东西参与过渡,若想要所有的属性都参加过渡,写all;

第二个参数:transition-duration属性,表示动画的持续时间,单位s,不是毫秒;

第三个参数:transition-timing-function属性,表示缓冲,常见有ease(两头慢,中间快)和linear(匀速)。

实际上,还可以写贝塞尔曲线的值;

第四个参数: transition-delay,表示延时时间,单位s。

什么属性可以参加动画;

所有属性可以参加动画。

background-color背景颜色可以过渡。

background-position也是可以过渡。

background-image 背景图片可以过渡;

transform变形都能参加过渡。

继承:不继承;

变形

transform Rotate旋转

transform:rotate(30deg);

Deg表示度数。单位不能少,只有deg单位。

Skew 斜切

transform:skew(10deg,20deg);

Scale: 缩放

大于1:放大,0-1:缩小;

transform:scale(0.5);

没有变形:

Transform:none;

Transform多属性书写:

Transform:rotate(30deg) skew(20deg,10deg) scale(2)

爬虫溢关于html5的一些总结(一)

发表于 2018-01-17

本文只是个人的一些总结,可能不够完善,欢迎补充发邮件给我,侧边栏有的我email。

简介:
2014年10月29日,HTML5标准规范终于最终制定完成了,并已公开发布。html5不仅仅颁布了超文本语言,而是一整套API的集合!

新的语义标签:(丰富了表单)

header、section、footer、nav、aside、article、address等等;

设备兼容特性,html5提供了移动设备的支持,设备的设置的所有的兼容API方案,浏览器获取GPS、摄像头、陀螺仪、蜂鸣器等,还加入全屏的API和拖放API;

连接的特性:
HTTP是无连接的,你的浏览器和服务器之间没有长连接,HTML5中提出了websocket特性,可以让浏览器和服务器实时的连接,下面简单介绍一下websocket:

websocket是html5规范中的一个部分,它借鉴了socket(了解socket)这种思想,为web应用程序客户端和服务端之间(注意是客户端服务端)提供一种全双工通信机制,同时,它又是一种新的应用层协议,通常表示为:ws://echo.websocket.org/?encoding=text HTTP/1.1,可以看到除了前面的协议名和http不同之外,它的表示地址就是传统的url地址。可以看到websocket并不是简单地将socket这一概念在浏览器环境中的移植,websocket本身虽然也是一种新的应用层协议,但是它也不能够脱离http而单独存在。我们在客户端构建一个websocket实例,并且为它绑定一个需要连接到的服务器地址,当客户端连接服务端的时候,会向服务端发送一个类似下面的http报文

可以看到,这是一个http get请求报文,注意该报文中有一个upgrade首部,它的作用是告诉服务端需要将通信协议切换到websocket,如果服务端支持websocket协议,那么他就会将自己的通信协议切换到websocket,同时发给客户端类似于以下的一个响应报文头:

返回的状态码为101,表示同意客户端协议转换请求,并将它转换为websocket协议,以上过程都是利用http通信完成的,称之为websocket协议握手,讲过这握手之后,客户端和服务端就建立了websocket连接,以后的通信走的都是websocket协议了,所以总结为websocket握手需要借助于http协议,建立连接后通信过程使用websocket协议。同时需要了解的是,该websocket连接还是基于我们刚才发起http连接的那个TCP连接,一旦建立连接之后,我们就可以进行数据传输了, websocket提供两种数据传输:文本数据和二进制数据。
基于以上分析,我们可以看到,websocket能够提供低延迟,高性能的客户端与服务端的双向数据通信,它颠覆了之前web开发的请求处理响应模式,并提供了一种真正意义上的客户端请求,服务器推送数据的模式,特别适合实时的数据交互应用开发。

详情请看:认识HTML5的WebSocket

新的音频和视频:

HTML5提出了新的video和audio标签,可以让你的网页轻松,不通过安装flash(Adobe)。

autoplay 自动播放

Width 视频的宽度

Control 显示控件

Loop 循环

新的画布canvas和svg(矢量)

详情请见:canvas、webGL、SVG

性能与集成的特性

XMLHTTPResquest对象; HTML5中有了获取进度的API。

新的语言规定

HTML5中自封闭的标签不用写结尾的反斜杠了

<meta charset="UTF-8" >
<img src="" alt="">
<link rel="stylesheet" type="text/css" href="">
<br>
<hr>

属性可以没有引号,非常合法

<img src=../images/tt.jpg alt=唐嫣 title=唐嫣>

标签名不一定是小写的

<DIV></DIV>

所有的需要些type的地方,丢可以不写type了;

<script></script>

但个人建议必须写! 为了保持风骨!

新的大纲标签

<header></header> 头部的语义
<footer></footer> 尾部的语义
<article></article> 文章语义
<section></section> 区域语义
<nav></nav> 导航语义
<aside></aside> 侧边内容
<main></main> 主要内容

新的小语义标签

Address 地址

<address>
北京市昌平区 点击查看百度地图
</address>

数据和数据标题:
Figure和 figcaption
数据就是图片,表格等等。

<figure>
    <img src="../images/tt.jpg" alt="">
    <figcaption>这个是我最喜欢的明星</figcaption>
</figure>

保留格式,pre(原型输出)

<pre>
    独立寒秋, 湘江北去, 橘子洲头。
    看万山红遍, 层林尽染; 漫江碧透, 百舸争流。
    鹰击长空, 鱼翔浅底, 万类霜天竞自由。
    怅寥廓, 问苍茫大地, 谁主沉浮?
    携来百侣曾游, 忆往昔峥嵘岁月稠。
    恰同学少年, 风华正茂; 书生意气, 挥斥方遒。
    指点江山, 激扬文字, 粪土当年万户侯。
    曾记否, 到中流击水, 浪遏飞舟?
</pre>

缩写词 abbr

<p>咱们出去玩的时候,记得带有<abbr title="GPS是英文Global Positioning System(全球定位系统)">GPS</abbr>的手机哈!</p>

引用 cite

<p>在没有得到任何证据的情况下是不能进行推理的,那样的话,只能是误入歧途<cite style="color:red">福尔摩斯探案集1888年</cite></p>

程序:code

<code>
    for(var i =0 ; i <100 ; i++){
        if( 1%2){
            while(){

            }
        }
    }
</code>

定义:dfn

<p><dfn>什么是浮云</dfn>一般暗指游子,浮云游子意,落日故人情。</p>

键盘按键 kbd

<p>在PS中,羽化的快捷键 是<kbd>Ctrl</kbd>+<kbd>shift</kbd>+<kbd>i</kbd></p>

高亮 mark

<p>明天天气很热,注意<mark>保暖</mark></p>

引用语句 q

<p>我们学生必须<q cite="http://www.360doc.com/content/15/1030/21/868418_509532510.shtml">好好学习,天天向上</q></p>

汉语拼音 ruby 、rp、rt

ruby 元素由一个或多个字符(需要一个解释/发音)和一个提供该信息的 rt 元素组成,还包括可选的 rp 元素,定义当浏览器不支持 “ruby” 元素时显示的内容。

<ruby>
    汉<rp>(</rp><rt>hàn</rt><rp>)</rp>
    子<rp>(</rp><rt>zǐ</rt><rp>)</rp>
</ruby>

举个例子: samp

<p>
    有钱人基本长得丑 <samp>马云</samp>
    不过张的丑的不一定是有钱人<samp>我</samp>
</p>

小文字 small

<p>极品雷事利驱使,淡定继续做<small>猎奴</small></p>

时间 time

<p>每天<time datetime="2017年4月14日17:22:22">9点整</time>上课</p>

变量语义 var

<p>二元一次方程:<var>x</var> = <var>y</var>+ 2</p>

换行机会 wbr

细节 details, summary

对话框语义 dialog

菜单语义: menu

模版 template

表单

<form action="" id="f0">
    <p>
        请输入年龄:
        <input type="number" name="" class="w400" id="" min="2" max="140" autofocus placeholder="请输入年龄,在2-140之间" />
    </p>
    <p>
        您最喜欢的颜色
        <input type="color" name="" id="" />
    </p>
    <p>
    出生日期:
    <input type="date" name="" id="" />
    </p>
    <p>
        出生星期:
        <input type="week" name="" id="" />
    </p>
    <p>
        起床时间:
        <input type="time" name="" id="" />
    </p>
    <p>
        您的邮箱:
        <input type="email" name="" id="" />
    </p>
    <p>
        您的身高:
        <input type="range" name="" id="range" min="60" max="250" /><span id="shuzhi"></span>
    </p>
    <p>
        查询:
        <input type="search" name="" id="" />
    </p>
    <p>
        电话:
        <input type="tel" name="" id="" />
    </p>
    <p>
        博客地址:
        <input type="url" name="" id="" />
    </p>
    <p>
        你的技能:
        <input type="text" name="" list="tip" />
        <datalist id="tip">
            <option value="internet">网上冲浪</option>
            <option value="skating">滑雪</option>
            <option value="swimming">游泳</option>
        </datalist>
    </p>
    <p>
        QQ号:
        <input type="text" name="" id="" pattern="^[\d]{5,10}$"/>
    </p>
    <p>
        爱好:
        <label><input type="checkbox" name="" id="" />抽烟</label>
        <label><input type="checkbox" name="" id="" />喝酒</label>
        <label><input type="checkbox" name="" id="" />烫头</label>
    </p>
    <p>
        提交:
        <input type="submit" value="提示" />
    </p>
</form>
 <p>
    <input type="text" name="xingming" form="f0" />
</p>

本地存储

HTML5提供了新的API。让我们可以在客户机的电脑里,存储数据。LocalStorage和SessionStorage。本地存储和会话存储。 统称叫“本地存储”

localStorage.setItem 持久数据

localStorage.setItem("dahuang",data);

localStorage.getItem 提取数据

var str = localStorage.getItem("dahuang");

本地存储会自动的为每一个网站(IP地址)建立一个独立的表格,在同一个网站(IP地址)下数据是可共享的。
localStorage存储东西,是永远不丢失。
sessionStorage,它是浏览器窗口一关,数据就丢失啦!

服务器给你的 Response Header 中带有一个 Set-Cookie字段,可以设置过期时间等等,今后再访问这个服务器,都会在Request Header 带着Cookie字段上去。这样服务器就能识别浏览器。这个就是cookie技术。
前端开发工程师,利用的是cookie是本地存储的小文件这个特点:

Docuemnt.cookie

长时间以来,cookie是唯一的在客户端中持久数据的途径。但是实际上cookie作为数据的持久方案。是对cookie的“奇技淫巧”。Cookie本身不是用来做持久数据的。

使用JSON来持久数据

本地存储是以字符串的方式存储的,我们一般情况是存JSON格式。 但是数据库中只能存字符串,所以要进行一段事情:

把JSON变为字符串 → 存储 → 读取 → 把JSON变为对象

// 要存储的东西
 var data = {"a":1,"b":2};
// 转成字符串
var dataStr = JSON.stringify(data);
// 持久
localStorage.setItem("dahuang",dataStr);
// 读取
var str = localStorage.getItem("dahuang");
// // 变为对象
var dataObj = JSON.parse(str);

console.log(dataObj.a)

多次保存同一个key,会覆盖,之前的key就没了。所以我们要“制作追加”的方案。

读取 → 修改 → 保存

// 要存储的东西
 var data = {"a":1,"b":2};
// 转成字符串
var dataStr = JSON.stringify(data);
// 持久
localStorage.setItem("dahuang",dataStr);
// 读取
var str = localStorage.getItem("dahuang");
// // 变为对象 (改变)
var dataObj = JSON.parse(str); 
//以上都是原数据
//追加
dataObj.c = 3;

var dataStrOnce = JSON.stringify(dataObj);
// 持久
localStorage.setItem("dahuang",dataStrOnce);
// 读取
var str = localStorage.getItem("dahuang");
// // 变为对象 (改变)
var dataObj1 = JSON.parse(str);
console.log(dataObj1.c)

JSON.parse(); 把字符串变为对象

JSON.stringify() 把对象变成字符串

本篇主要是html5的新特性,下一篇css新特性。

爬虫溢前端面试题及个人总结(二)

发表于 2018-01-15

接上一篇。(问题的答案全是个人理解,有可能错误,如发现错误问题,请发邮件给我,我的邮箱在侧边栏有,谢谢!)

function内路由跳转

Angular、vue、react都可实现,以下依次是实现方法。

Angular:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="angular1.4.6.min.js"></script>
  <script src="angular-route.js"></script>
</head>
<body ng-app="MyApp" >
<a href="#/" rel="external nofollow" >CHINA</a><br/>
<a href="#/1" rel="external nofollow" >CHINA1</a><br/>
<a href="#/2" rel="external nofollow" >CHINA2</a><br/>
<a href="#/3" rel="external nofollow" >CHINA3</a><br/>
<div ng-view>
11111
</div>
<script>
  var app = angular.module("MyApp", ["ngRoute"]);
  app.config(function ( $routeProvider) {
$routeProvider
  .when("/", {template: "<h1>123</h1>"})
  .when("/1", {template: "<h1>111</h1>"})
  .when("/2", {template: "<h1>222</h1>"})
  .when("/3", {template: "<h1>333</h1>"});
  });
</script>
</body>
</html>

vue:

比如html中这样写:

<span @click="clickFn">点我</span>

js中这样写:

methods:{
clickFn:function(){
    this.$router.go('/login');//其中login是你定义的一个路由模块
}

React:

import { browserHistory } from 'react-router'  //引入路由函数

browserHistory.push('/some/path')   //js方式跳转

或者使用 hashHistory 代替 browserHistory,看个人爱好。

关于react路由带#号问题

Router组件有一个参数history,它的值hashHistory表示,路由的切换由URL的hash变化决定,即URL的#部分发生变化。举例来说,用户访问http://www.example.com/,实际会看到的是http://www.example.com/#/

详情请见:阮一峰的博客

react组件参数验证

propTypes是React提供的一种验证机制,该对象中定义了一系列的验证方法,可对props进行验证。组件初始化时,如果传递的props属性和propTypes不匹配,则会打印一个console.warn日志。

详情请见:验证对象propTypes

react取input值

给input加入一个ref属性,<input ref="Dom" value="111"/> ,通过this.ref.dom就能获取到这个input,this.ref.Dom.value就能获取input中的值。

或者:

无约束组件。直接给定组件一个defaultValue值

<input type="text" defaultValue="Hello World!" ref="helloTo" />
//通过ref获取input的值
var inputValue = this.ref.hello.getDOMNode().value;

约束组件

<input type="text" defaultValue={this.state.hello} />
//{}来获取input的值, var inputValue = this.state.hello;

Promise对象

Promise对象标准语法:

var p = new Promise(function (resolve, reject) {
   setTimeout(function () {
   var ra = Math.random()
   ra < 0.5 ? resolve() : reject()  
}, 1000)
})

p.then(function () {
  console.log('成功')
}, function () {
  console.log('失败')
})

new Promise() 构造函数建立一个Promise对象

其最常见的就是then方法,then方法可接收3个函数,一般传递2个,最常见传递1个。

分别为 成功回调、失败回调、完成回调

react中应用了Promise对象,很好解决了不优雅的金字塔结构代码。
下面是常看到用ajax请求回来数据会有这样一段代码。

componentDidMount() {
this.props.promise.then(
  value => this.setState({loading: false, data: value}),
  error => this.setState({loading: false, error: error}));
  }

value 是代表 promise 完成后的 resolve 值,需要通过 value 传递。

详情了解promise对象:点击此处

babel是干什么的

Babel并非React的一部分,实际上,Babel的主要用途并非一个JSX语句转换器。Babel主要是一个JavaScript转换器,它可以转换各种ES*代码为浏览器可识别的ES代码。就目前来说,Babel主要会转换ES6和ES7语句为ES5语句,转换JSX看起来倒像是其的一个附加功能。

有了Babel,我们可以放心的在React中使用最新的ES语句了。

eslint

ESLint 是用来检查我们写的 JavaScript 代码是否满足指定规则的静态代码检查工具。或者说ESLint是一个用来识别 ECMAScript 并且按照规则给出报告的代码检测工具,使用它可以避免低级错误和统一代码的风格。

详细了解:ESlint官网

代理接口

主要通过代理服务器站点实现,代理网络用户获取信息,主要提高访问的速度。

什么是Reducer

下面这个函数就是reducer,这里就是所谓的“predictable”可以被预测的含义。所有对state的操作,都要罗列出来。

reducer函数必须符合的形态:接受之前的state和action,返回新的state:

(previousState,action) => newState

reducer:

function counter(state = {"v" : 100} , action){
if(action.type == "ZENGJIA"){
    return {"v" : state.v + 1};
}else if(action.type == "JIANSHAO"){
    return {"v" : state.v - 1};
}
return state;
}

在reducer中是绝对不能更改state的,而是应该返回新的state。

发送action

按钮监听不是直接改变state的值,而是调用store的dispatch方法发送命令给仓库,比对reducer中的if语句,执行相应的改变。action必须有一个type属性,这个type属性表示这个action是干嘛的。

document.getElementById("jiaBtn").onclick = function(){
 store.dispatch({"type" : "ZENGJIA"});
}

nodejs、npm、cnpm

Node.js是一个平台不是一个语言,语言仍然是JavaScript。此时Node.js平台可以让我们用JavaScript语言来开发服务器程序,Node.js特别像java虚拟机,大家只需要写一份语言,就可以运行在windows、mac、linux上。安装nodejs后会有npm命令,npm 可以安装node插件,cnpm使用的是淘宝网的镜像http://npm.taobao.org,安装命令提示符执行:npm install cnpm -g --registry=https://registry.npm.taobao.org

git命令拉取项目

新建一个文件夹,在文件夹里执行gitbash,输入
$git clone ssh://(你要拉取项目的ssh码)

爬虫溢前端面试题及个人总结(一)

发表于 2018-01-14

本人只是前端大行业中最渺小的一只,曾经一度处于阴霾迷失方向,现总结北京一些公司的前端题分享给或许也同样迷茫的你(持续更新),希望能帮助你们也是我自己慢慢拨开迷雾,这次更新的面试题是公司原题主要考察react框架,其实面试题答得好只是能通过初选,所以不要满足于面试题,一起加油。

react官网:https://reactjs.org/ 学习react我个人是建议看一遍官网的api的,会有一些感悟。

react最基础也是必须记住的就是它的生命周期(三阶段10个):

一、上树阶段(Mounting阶段)

生命周期 做了什么
constructor() 构造函数,写state什么的
componentWillMount() 将要上树,这里发出Ajax请求
render() 呈递视图
componentDidMount() 上树之后,可以操作DOM

本阶段需要注意的是,在
componentDidMount()生命周期函数中,就能操作DOM了,DOM就已经上树了;本阶段的4个函数都没有参数。
二、更新阶段(Updating阶段)
props的改变或者state的改变将触发本阶段。

生命周期 做了什么
componentWillReceiveProps(nextProps) 当父亲传给组件的prop改变的时候发生。在这个函数中,nextProps参数表示新的父亲传入的props对象
shouldComponentUpdate(nextProps, nextState) 门神 当父亲传入的props改变或者自己state改变发生。return true此时才会继续执行后面的声明周期;return false不执行后面的生命周期了。如果没有写门神函数,默认return true。
render()
componentDidUpdate(prevProps , prevState) 更新之后做的事情

三、下树阶段(Unmounting阶段)

生命周期 做了什么
componentWillUnmount() 将要下树

React中state与props介绍与比较

  • state
    1.state的作用
      state是React中组件的一个对象.React把用户界面当做是状态机,想象它有不同的状态然后渲染这些状态,可以轻松让用户界面与数据保持一致.
      React中,更新组件的state,会导致重新渲染用户界面(不要操作DOM).简单来说,就是用户界面会随着state变化而变化.
    2.state工作原理
      常用的通知React数据变化的方法是调用setState(data,callback).这个方法会合并data到this.state,并重新渲染组件.渲染完成后,调用可选的
      callback回调.大部分情况不需要提供callback,因为React会负责吧界面更新到最新状态.
    3.那些组件应该有state?
      大部分组件的工作应该是从props里取数据并渲染出来.但是,有时需要对用户输入,服务器请求或者时间变化等作出响应,这时才需要state.
      组件应该尽可能的无状态化,这样能隔离state,把它放到最合理的地方(Redux做的就是这个事情?),也能减少冗余并易于解释程序运作过程.
      常用的模式就是创建多个只负责渲染数据的无状态(stateless)组件,在他们的上层创建一个有状态(stateful)组件并把它的状态通过props
      传给子级.有状态的组件封装了所有的用户交互逻辑,而这些无状态组件只负责声明式地渲染数据.
    4.哪些应该作为state?
      state应该包括那些可能被组件的事件处理器改变并触发用户界面更新的数据.这中数据一般很小且能被JSON序列化.当创建一个状态化的组件
      的时候,应该保持数据的精简,然后存入this.state.在render()中在根据state来计算需要的其他数据.因为如果在state里添加冗余数据或计算
      所得数据,经常需要手动保持数据同步.
    5.那些不应该作为state?
      this.state应该仅包括能表示用户界面状态所需要的最少数据.因此,不应该包括:
      计算所得数据:
      React组件:在render()里使用props和state来创建它.
      基于props的重复数据:尽可能保持用props来做作为唯一的数据来源.把props保存到state中的有效的场景是需要知道它以前的值得时候,
      因为未来的props可能会变化.
  • props
    1.props的作用
      组件中的props是一种父级向子级传递数据的方式.
    2.复合组件

react销毁阶段

1.销毁阶段可以使用的函数:
componentWillUnmount:在删除组件之前进行清理操作,比如计时器和事件监听器。因为这些函数都是开发者手动加上去的,react不知道,必须进行手动清理。

2.实例
第一种方式:在render中,把之前已有的页面去掉,反映到页面中,就是把它删掉。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试触发顺序,不输入不会触发五个函数,只会触发render</title>
</head>
<body>
<script type="text/javascript" src="http://cdn.bootcss.com/react/0.13.2/react.min.js"></script>
<script type="text/javascript" src="http://cdn.bootcss.com/react/0.13.2/JSXTransformer.js"></script>
<script type="text/jsx">
    var style={
       color:"red",
       border:"1px solid #f99",
       width:"200px",
       height:"50px"
    };
    var HelloWorld= React.createClass({
        render:function(){
            console.log("render,4");
            return <p>Hello,{this.props.name ? this.props.name : "World"}</p>;
        },
        componentWillUnmount:function(){
            console.log("BOOM");
        },
    });
    var HelloUniverse=React.createClass({
        getInitialState:function(){
            return {name:""};
        },
        handleChange:function(event){
            //用来响应input的输入事件
            this.setState({name:event.target.value});
        },
        render:function(){
            if(this.state.name == "123"){
               return <div>123</div>
            }
            return <div>
            <HelloWorld name={this.state.name
                //这里引用了HelloWorld的组件,所以HelloUniverse是他的子组件
            }></HelloWorld>
            <br />
            <input type="text" onChange={this.handleChange} />  
            </div>
        },
    });
    React.render(<div style={style}><HelloUniverse></HelloUniverse></div>,document.body)
    // 写为React.render(<div style={style}><HelloWord></HelloWorld></div>,document.body)看看效果
</script>
</body>
</html>

输入别的不会触发

当输入123的时候

第二种:就是使用react提供的一个函数unmountComponentAtNode

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<script type="text/javascript" src="http://cdn.bootcss.com/react/0.13.2/react.min.js"></script>
<script type="text/javascript" src="http://cdn.bootcss.com/react/0.13.2/JSXTransformer.js"></script>
<script type="text/jsx">
    var style={
       color:"red",
       border:"1px solid #f99",
       width:"200px",
       height:"50px"
    };
    var HelloWorld= React.createClass({
        render:function(){
            console.log("render,4");
            return <p>Hello,{this.props.name ? this.props.name : "World"}</p>;
        },
        componentWillUnmount:function(){
            console.log("BOOM");
        },
    });
    var HelloUniverse=React.createClass({
        getInitialState:function(){
            return {name:""};
        },
        handleChange:function(event){
            //判断的是input里面的值,如果是123,我们就使用unmountComponentAtNode来删除
            //使用unmountComponentAtNode时,传入的必须是装载时候的节点。


            if(event.target.value == "123"){
              React.unmountComponentAtNode(document.getElementsByTagName("body")[0]);
              return;
            }
            this.setState({name:event.target.value});
        },
        render:function(){
            return <div>
            <HelloWorld name={this.state.name
                //这里引用了HelloWorld的组件,所以HelloUniverse是他的子组件
            }></HelloWorld>
            <br />
            <input type="text" onChange={this.handleChange} />  
            </div>
        },
    });
    React.render(<div style={style}><HelloUniverse></HelloUniverse></div>,document.body)
    // 写为React.render(<div style={style}><HelloWord></HelloWorld></div>,document.body)看看效果
</script>
</body>
</html>

react-router配置

路由库React-Router。它是官方维护的,事实上也是唯一可选的路由库。它通过管理 URL,实现组件的切换和状态的变化,开发复杂的应用几乎肯定会用到。
使用详情请参考阮一峰的博客。

react的render中,return里面是不是只能有一个最外层DIV

最外层只能一个组件,但不一定是div。
可以是一个react Component,如<A></A>;也可以是html标签,如<div></div'/'<form></form>;如果返回的是多个平级的react Component,需要用html标签包裹起来,如'<div><A/><B/></div>'。

render内的html class名

react使用JSX语法,JSX语法最终是要被转换为纯Javascript的,所以要和在Javascript DOM中一样,用className和htmlFor。

react行内样式

react的行内样式本质上是一个对象,比如官方文档的这个

var divStyle = {
  color: 'white',
  backgroundImage: 'url(' + imgUrl + ')',
  WebkitTransition: 'all', // 注意这里的首字母'W'是大写
  msTransition: 'all' // 'ms'是唯一一个首字母需要小写的浏览器前缀
};

React.render(<div style={divStyle}>Hello World!</div>, mountNode);

如何在React中使用数据动态生成DOM标签

我们的数据是从真实的DOM中获取来 通过事件 构建一个数组 每次点击或其他事件来把数据push到数组中 这段的代码中最重要的是我们要调用父组件中 修改数据的方法 这样数据就传递过去了 我们就可以使用上述的方法来动态生成 DOM元素了

参考详情请见:点击此处

关于Virtual DOM

React采用了Virtual DOM虚拟DOM和DIFF算法,在内存中快速比较setState变化前后的差异,做最小量更新!

比如下图,我们点击按钮之后,state影响界面从左侧的形式变为了右侧的形式:

方案1:删除4个li,插入5个li;

方案2:增加1个li,改变5个li的innerHTML;

很明显,方案2更加节约内存,效率高。

  • React中有DIFF算法,就是可以快速比较两个DOM结构的最小化差异。
  • React会将DOM结构编码(类似于数据结构中的树的结构),存储在内存中,称为Virtual DOM,虚拟DOM。

当组件有组件套组件的情况下,Virtual DOM还会自动计算子孙组件的最小化差异:

前端面试不可忽视的安全问题

发表于 2018-01-13

准备前端面试的时候,你绝对不会忘记准备html、css、js基础的知识,并且性能也在你的考虑范围之内,框架方面,angular/react/vue你也信心满满,然而你是否忽略了安全问题,而感觉被面试官套路了呢?
下面我们就来说一说前端面试中常见的安全问题

一、SQL注入攻击

什么是sql注入:
所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。具体来说,它是利用现有应用程序,将(恶意的)SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句

这一段是度娘的,网上有更深的解析文章,相信你能找到更准确的表述去回答面试官的问题

解法:

  1. 检查变量数据类型和格式
  2. 过滤特殊符号
基本关键词: and、or、order
增的关键词: insert、into
删的关键词: delete
改的关键词: replace、update
查的关键词: union、select、
文件处理的关键词: load_file、outfile

引号使用 反斜杠转义
  1. 绑定变量使用预编译语句(例如使用query)(最佳方式)

二、跨站脚本攻击 - XSS

什么是XSS:

XSS又称CSS,全称Cross SiteScript,跨站脚本攻击,是Web程序中常见的漏洞,XSS属于被动式且用于客户端的攻击方式,所以容易被忽略其危害性。其原理是攻击者向有XSS漏洞的网站中输入(传入)恶意的HTML代码,当其它用户浏览该网站时,这段HTML代码会自动执行,从而达到攻击的目的。如,盗取用户Cookie、破坏页面结构、重定向到其它网站等。

解法:

  1. 不要在不允许位置插入不可信数据
  2. html encode
字符 编码字符
less-than character (<) <
greater-than character (>) >
ampersand character (&) &
double-quote character (“) “
space character( )
Any ASCII code character whose code is greater-than or equal to 0x80 &#, where is the ASCII character value.

三、跨站伪造请求攻击 - CSRF

什么是CSRF:

CSRF(Cross Site Request Forgery, 跨站域请求伪造)是一种网络的攻击方式,该攻击可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击站点,从而在并未授权的情况下执行在权限保护之下的操作,有很大的危害性。

解法:

  1. 验证 HTTP Referer 字段(低版本浏览器可能会被篡改,用户也可以通过浏览器设置不发送此字段)
  2. token验证(通过native发请求,根据公钥生成token)

四、dns劫持

什么是dns劫持:

DNS劫持又称域名劫持,是指在劫持的网络范围内拦截域名解析的请求,分析请求的域名,把审查范围以外的请求放行,否则返回假的IP地址或者什么都不做使请求失去响应,其效果就是对特定的网络不能反应或访问的是假网址。

dns劫持跟dns污染有什么区别?
dns劫持是直接劫持了dns服务器,进行恶意的解析;而dns污染是伪装了dns服务器,进行dns解析,例如,dns服务器在国外,而在经过国内的某一个网络节点的时候,该网络节点伪装成dns服务器返回了恶意的解析结果

解法:

  1. 使用安全可靠的DNS服务器管理自己的域名,并且注意跟进DNS的相关漏洞信息,更新最新补丁,加固服务器;
  2. 保护自己的重要机密信息安全,避免域名管理权限被窃取;
  3. 提高服务器安全级别,更新系统及第三方软件漏洞,避免遭受攻击;
  4. 如果允许的话,用ip地址访问服务器(当然啦,一般情况下是不行的)

其它可了解的

  1. 分布式拒绝服务攻击 - DDOS
  2. 文件上传漏洞攻击

Vue-loader 的巧妙玩法

发表于 2018-01-13

声明:可以这么玩,不代表应该这么玩

一、Vue-loader 干了啥?

Vue-loader 是一个 webpack 加载器,它可以把形如:

的 Vue 组件转化为 Js 模块,这其中最最值得关注的是,它生成了 render function code, 详解 render function code 中,已经对它进行了细致的介绍:

render function code 是从模板编译而来(可以并且应该预编译)的组件核心渲染方法,在每一次组件的 Render 过程中,通过注入的数据执行可生成虚拟 Dom

既然 Vue-loader 预编译生成了 render function code,那么我们就可以通过改造 Vue-loader 来改写 render function code 的生成结果,从而全局的影响组件的每一次渲染结果

二、如何改造?

找到目标代码

Vue loader 并不普通,需要通过语法树分析的方式最终生成 render function code (并且不限于此),如果通篇阅读如此复杂的代码可能会让你——沮丧。幸运的是,要完成改写render function code 的小目标,我们并不需要读得太多,找到生成结果那一小段代码,在返回之前改写即可。那么新的问题又来了,这小段代码又如何定位?

一是靠猜:打开 Vue-loader 的目录结构,见名知义,显然 template-compiler 模板编译的意思更为接近

二是搜索:在 template-compiler 目录中搜索 render , 有没有发现有一段代码很有嫌疑

var code
  if (compiled.errors && compiled.errors.length) {
this.emitError(
  `\n  Error compiling template:\n${pad(html)}\n` +
  compiled.errors.map(e => `  - ${e}`).join('\n') + '\n'
)
code = 'module.exports={render:function(){},staticRenderFns:[]}'
  } else {
var bubleOptions = options.buble
// 这段代码太可疑了
code = transpile('module.exports={' +
  'render:' + toFunction(compiled.render) + ',' +
  'staticRenderFns: [' + compiled.staticRenderFns.map(toFunction).join(',') + ']' +
'}', bubleOptions)

// mark with stripped (this enables Vue to use correct runtime proxy detection)
if (!isProduction && (
  !bubleOptions ||
  !bubleOptions.transforms ||
  bubleOptions.transforms.stripWith !== false
)) {
  code += `\nmodule.exports.render._withStripped = true`
}
  }

三是调试确认:打印 toFunction(compiled.render), 查看输出结果,如果跟 render function code 的表现一致的话,那就是它了

with(this) {
return _c('div', {
    attrs: {
        "id": "app"
    },
    staticStyle: {
      "width": "100px"
    },
    style: styleObj
},
[_c('p', [_v("普通属性:" + _s(message))]), _v(" "), _c('p', [_v(_s(msg()))]), _v(" "), _c('p', [_v(_s(ct))]), _v(" "), _c('input', {
    directives: [{
        name: "model",
        rawName: "v-model",
        value: (message),
        expression: "message"
    }],
    domProps: {
        "value": (message)
    },
    on: {
        "input": function($event) {
            if ($event.target.composing) return;
            message = $event.target.value
        }
    }
}), _v(" "), _l((items),
function(item) {
    return _c('div', [_v("\n\t\t  " + _s(item.text) + "\n\t   ")])
}), _v(" "), _c('button', {
    on: {
        "click": bindClick
    }
},
[_v("点我出奇迹抓同伟")])], 2)
}

如何改造?

假如我们想把所有组件的所有静态样式(staticStyle)的像素值乘二(虽然有点搞破坏),那么我们需要对上述 toFunction(compiled.render)进行正则替换

var renderCode = toFunction(compiled.render)
renderCode = renderCode.replace(/(staticStyle:)(\s*{)([^}]*)(})/g, function (m, n1, n2, n3, n4) {
n3 = n3.replace(/(".*")(\s*:\s*")(\d+px)(")/g, function (m, n31, n32, n33, n34) {
  return n31 + n32 + parseInt(n33)*2 + 'px' + n34
})
return n1 + n2 + n3 + n4
  })

如果是改造动态样式(style),由于在 render function code 中,动态 style 以变量的形式出现,我们不能直接替换模板,那么我们可以通过传入一个方法,在运行时执行转换。不要企图写一个普通的方法,通过方法名的引用在render function code 中执行,因为 render function code 执行时的作用域,不是在Vue-loader 阶段可以确认的,所以我们需要写一个立即执行函数:

var convertCode = "(function(styleObj){styleObj = (...此处省略N行代码)})(##style##)"

立即执行函数的入参用 ##style##占位,替换的过程中用具体的变量代替,上述 render function code 替换结果为:

with(this) {
return _c('div', {
    attrs: {
        "id": "app"
    },
    staticStyle: {
      "width": "100px"
    },
    // 重点在这里 在这里
    style: (function(styleObj){styleObj = (...此处省略N行代码)})(styleObj)
},
...
)
}

三、有何使用场景?

例如,当你一开始使用了 px 作为布局单位,却需要改造为 rem 布局单位的时候(业务代码很多很繁杂,不方便一个个去改,并且由于动态样式的存在,难以全局替换)

对于插入立即执行函数去处理动态变量的方式,每一次 Re-render 都会执行一遍转换函数,显然,这对渲染性能有影响

所以,虽然可以这么玩,但是不代表应该这么玩,还需三思而行

其它的使用场景暂时也还没想到,即便如此,这种瞎折腾也不是没有意义的,没准在还无法预见的场景,这是一种绝佳的解决方案呢?如果你刚好遇到了,也同步给我吧~~

12
YangYi

YangYi

技术和生活,我融为一体。

14 日志
RSS
GitHub E-Mail Coding.NET Weibo 简书 Instagram
© 2018 YangYi
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
博客全站共23.1k字