-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontent.json
More file actions
1 lines (1 loc) · 197 KB
/
content.json
File metadata and controls
1 lines (1 loc) · 197 KB
1
{"meta":{"title":"码字仓颉","subtitle":"个人博客","description":"yancongwen'blog","author":"Yan Congwen","url":"http://blog.yancongwen.com"},"pages":[{"title":"About","date":"2018-04-20T03:52:26.000Z","updated":"2019-03-28T06:16:34.351Z","comments":true,"path":"about/index.html","permalink":"http://blog.yancongwen.com/about/index.html","excerpt":"","text":"欢迎来到我的个人博客"},{"title":"categories","date":"2018-01-31T09:21:14.000Z","updated":"2019-03-28T06:16:34.351Z","comments":true,"path":"categories/index.html","permalink":"http://blog.yancongwen.com/categories/index.html","excerpt":"","text":""},{"title":"Project","date":"2019-03-28T06:16:34.351Z","updated":"2019-03-28T06:16:34.351Z","comments":true,"path":"project/index.html","permalink":"http://blog.yancongwen.com/project/index.html","excerpt":"","text":""},{"title":"Tags","date":"2019-03-28T06:16:34.367Z","updated":"2019-03-28T06:16:34.367Z","comments":true,"path":"tags/index.html","permalink":"http://blog.yancongwen.com/tags/index.html","excerpt":"","text":""}],"posts":[{"title":"矢量切片工具:tippecanoe","slug":"矢量切片工具:tippecanoe","date":"2019-04-02T12:37:00.000Z","updated":"2019-04-16T12:43:54.330Z","comments":true,"path":"2019/04/02/矢量切片工具:tippecanoe/","link":"","permalink":"http://blog.yancongwen.com/2019/04/02/矢量切片工具:tippecanoe/","excerpt":"","text":"tippecanoe 本文翻译自 tippecanoe/README.md Tippecanoe 用于将 GeoJSON, Geobuf, 或者 CSV 格式的矢量要素转换为矢量瓦片。 目的Tippecanoe 的目的是将数据制作为比例独立的视图,以使在任何缩放级别下,你都可以看到数据的密度和细节,而不是将数据简化或聚合。如果你提供的是 OpenStreetMap 所有的数据,在小比例尺下,你应该看到类似于All Streets的地图,而不是州际道路地图。如果你提供的是洛杉矶的所有详细的建筑数据,并且将地图缩放到小比例尺下,绝大部分的单体建筑将不再可辨,但是你应该可以看到每个街区的范围和变化。如果你提供的是一年内 twitter 推文的定位数据集,你应该可以发现所有兴趣点之间的关联和热门的旅游路线。 安装 OSX 操作系统使用 Homebrew 安装: 1$ brew install tippecanoe Ubuntu 系统最简单的方式是从源码中构建: 1234$ git clone git@github.com:mapbox/tippecanoe.git$ cd tippecanoe$ make -j$ make install 如果编译中出现问题,可能是你的C++编译器需要升级,或者缺少必要的依赖包,详细请查看文档。 使用1$ tippecanoe -o file.mbtiles [options] [file.json file.json.gz file.geobuf ...] 如果没有指定文件,会从默认路径读取 GeoJSON 文件;如果指定了多个文件,每一个文件将会被当做一个图层。GeoJSON 要素不一定非得包含在 FeatureCollection 中。你可以将多个 GeoJSON 要素或者文件合并。 Try this first如果你不确定使用什么选项,请尝试一下命令:1$ tippecanoe -o out.mbtiles -zg --drop-densest-as-needed in.geojson 使用-zg 选项,Tippecanoe 将自动选择一个可以反映原始数据精度的最大级别(如果结果没有达到你想要的效果,你也可以使用-z 选项手动设置最大级别)。如果生产出的切片太大,可以使用 --drop-densest-as-needed选项,来让Tippecanoe自动删除各个级别下最不可见的要素。(如果它删除了太多的要素,你可以使用-x选项来删除不必要的属性字段) 选项tippecanoe 切片有很多选项,但是大部分情况下你并不想要使用它们,除了使用 -o output.mbtiles来定义输出瓦片文件名,或者再加上-f 选项来强制删除同名文件。 如果你不确定需要切片的最大级别,-zg 选项可以根据源数据自动计算出一个最大级别。 通常,在最大切片级别以下的级别,tippecanoe 会舍弃部分点要素,以防止瓦片过大。如果你的数据集本来就不大,你想要保留所有要素,可以使用-r1选项。如果你确实是想要简化数据,但是又不想简化得过于稀疏,可以使用 -B 选项设置一个小于最大级别的数值。 通过以上设置,如果你的切片仍然很大,你可以使用 --drop-densest-as-needed 选项来进一步简化要素。 如果你的要素包含很多属性,你可以使用-y选项来选择只保留你给定的字段。 如果你的GeoJSON 文件是格式化后的,使用-p可以加快文件读取。 输出选项 -o file.mbtiles 或 --output=file.mbtiles:指定输出文件名 -e directory 或 --output-to-directory=directory:指定输出文件路径 -f 或 --force:若存在同名文件则删除 -F 或 --allow-existing 瓦片集属性选项 -n name 或 --name=name: 给瓦片集设置一个易读的名字 -A text 或 --attribution=text: 瓦片集 -N description 或 --description=description: 瓦片集描述 输入文件和图层名 name.json 或 name.geojson:读取 GeoJSON 文件 name.json.gz 或 name.geojson.gz:读取 GeoJSON 压缩文件 name.geobuf:读取 Geobuf 文件 name.csv:读取 CSV 文件 -l name 或 --layer=name: 使用自定义图层名,而不是默认的输入文件名作为图层名,如果有多个输入文件,将合并为一个图层,除非使用-L选项来分别指定图层名。 -L name:file.json 或 --named-layer=name:file.json:定义每个文件的对应的图层名 -L{layer-json} 或 --named-layer={layer-json}: 通过 json 对象定义图层。示例: 1tippecanoe -z5 -o world.mbtiles -L'{\"file\":\"ne_10m_admin_0_countries.json\", \"layer\":\"countries\", \"description\":\"Natural Earth countries\"}' 坐标系 -s projection 或 --projection=projection: 给定输入文件的坐标系统。当前支持的坐标系有EPSG:4326(WGS84,默认值)、EPSG:3857(Web Mercator)。请尽量使用 WGS84 坐标系统的数据集。 切片级别 -z zoom 或 --maximum-zoom=zoom:切片的最大级别(默认为14) -zg 或 --maximum-zoom=g: 根据数据的密集程度自动计算一个最大级别 -Z zoom 或 --minimum-zoom=zoom: 切片的最小级别(默认0) -ae 或 --extend-zooms-if-still-dropping: 如果在大级别下瓦片仍然很大,它将自动增加最大级别,以使最大级别下没有要素被删除 -R zoom/x/y 或 --one-tile=zoom/x/y: 如果你知道你想要的切片的数据精度,那么你就可以根据以下表格来设置切片级别: 级别 精度 (英尺) 精度 (m) -z0 32000 ft 10000 m -z1 16000 ft 5000 m -z2 8000 ft 2500 m -z3 4000 ft 1250 m -z4 2000 ft 600 m -z5 1000 ft 300 m -z6 500 ft 150 m -z7 250 ft 80 m -z8 125 ft 40 m -z9 64 ft 20 m -z10 32 ft 10 m -z11 16 ft 5 m -z12 8 ft 2 m -z13 4 ft 1 m -z14 2 ft 0.5 m -z15 1 ft 0.25 m 属性筛选 -x name 或 --exclude=name: 指定切片中应剔除的字段。 -y name 或 --include=name: 指定切片中应包含的字段。 Cookbook线要素(全球铁路),在所有级别可见12345curl -L -O https://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_railroads.zipunzip ne_10m_railroads.zipogr2ogr -f GeoJSON ne_10m_railroads.geojson ne_10m_railroads.shptippecanoe -zg -o ne_10m_railroads.mbtiles --drop-densest-as-needed --extend-zooms-if-still-dropping ne_10m_railroads.geojson -zg: 自动选择最大级别; --drop-densest-as-needed: 如果在小级别下瓦片太大,该选项将自动简化要素; --extend-zooms-if-still-dropping: 如果在大级别下瓦片仍然很大,它将自动增加最大级别,以使最大级别下没有要素被删除; 不连续的面要素(美国罗德岛),在所有级别可见1234curl -L -O https://usbuildingdata.blob.core.windows.net/usbuildings-v1-1/RhodeIsland.zipunzip RhodeIsland.ziptippecanoe -zg -o RhodeIsland.mbtiles --drop-densest-as-needed --extend-zooms-if-still-dropping RhodeIsland.geojson -zg: 自动选择最大级别; --drop-densest-as-needed: 如果在小级别下瓦片太大,该选项将自动简化要素; --extend-zooms-if-still-dropping: 如果在大级别下瓦片仍然很大,它将自动增加最大级别,以使最大级别下没有要素被删除; 连续的面要素(行政区划),在所有级别可见12345curl -L -O https://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_admin_1_states_provinces.zipunzip -o ne_10m_admin_1_states_provinces.zipogr2ogr -f GeoJSON ne_10m_admin_1_states_provinces.geojson ne_10m_admin_1_states_provinces.shptippecanoe -zg -o ne_10m_admin_1_states_provinces.mbtiles --coalesce-densest-as-needed --extend-zooms-if-still-dropping ne_10m_admin_1_states_provinces.geojson -zg: 自动选择最大级别; --extend-zooms-if-still-dropping: 如果在大级别下瓦片仍然很大,它将自动增加最大级别,以使最大级别下没有要素被删除; --coalesce-densest-as-needed: 如果瓦片在低级别或中等级别下比较大,该选项将合并要素; 海量点数据(公交车GPS轨迹数据),可视化,在所有级别可见123curl -L -O ftp://avl-data.sfmta.com/avl_data/avl_raw/sfmtaAVLRawData01012013.csvsed 's/PREDICTABLE.*/PREDICTABLE/' sfmtaAVLRawData01012013.csv > sfmta.csvtippecanoe -zg -o sfmta.mbtiles --drop-densest-as-needed --extend-zooms-if-still-dropping sfmta.csv (sed 命令用于清除不必要的字段) -zg: 自动选择最大级别; --drop-densest-as-needed: 如果在小级别下瓦片太大,该选项将自动简化要素; --extend-zooms-if-still-dropping: 如果在大级别下瓦片仍然很大,它将自动增加最大级别,以使最大级别下没有要素被删除; 低级别显示国家边界,高级别显示州边界1234567891011curl -L -O https://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_admin_0_countries.zipunzip ne_10m_admin_0_countries.zipogr2ogr -f GeoJSON ne_10m_admin_0_countries.geojson ne_10m_admin_0_countries.shpcurl -L -O https://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_admin_1_states_provinces.zipunzip -o ne_10m_admin_1_states_provinces.zipogr2ogr -f GeoJSON ne_10m_admin_1_states_provinces.geojson ne_10m_admin_1_states_provinces.shptippecanoe -z3 -o countries-z3.mbtiles --coalesce-densest-as-needed ne_10m_admin_0_countries.geojsontippecanoe -zg -Z4 -o states-Z4.mbtiles --coalesce-densest-as-needed --extend-zooms-if-still-dropping ne_10m_admin_1_states_provinces.geojsontile-join -o states-countries.mbtiles countries-z3.mbtiles states-Z4.mbtiles Countries: -z3: 最大切片级别为3,即只切 0 - 3 级别的瓦片; --coalesce-densest-as-needed: 如果瓦片在低级别或中等级别下比较大,该选项将合并要素; States and Provinces: -Z4: 最小切片级别为4; -zg: 自动选择最大级别; --coalesce-densest-as-needed: 如果瓦片在低级别或中等级别下比较大,该选项将合并要素; --extend-zooms-if-still-dropping: 如果在大级别下瓦片仍然很大,它将自动增加最大级别,以使最大级别下没有要素被删除; 多个数据源切片为独立的图层123456789curl -L -O https://www2.census.gov/geo/tiger/TIGER2010/COUNTY/2010/tl_2010_17_county10.zipunzip tl_2010_17_county10.zipogr2ogr -f GeoJSON tl_2010_17_county10.geojson tl_2010_17_county10.shpcurl -L -O https://www2.census.gov/geo/tiger/TIGER2010/COUNTY/2010/tl_2010_18_county10.zipunzip tl_2010_18_county10.zipogr2ogr -f GeoJSON tl_2010_18_county10.geojson tl_2010_18_county10.shptippecanoe -zg -o counties-separate.mbtiles --coalesce-densest-as-needed --extend-zooms-if-still-dropping tl_2010_17_county10.geojson tl_2010_18_county10.geojson -zg: 自动选择最大级别; --coalesce-densest-as-needed: 如果瓦片在低级别或中等级别下比较大,该选项将合并要素; --extend-zooms-if-still-dropping: 如果在大级别下瓦片仍然很大,它将自动增加最大级别,以使最大级别下没有要素被删除; 多个数据源切片并合并为一个图层123456789curl -L -O https://www2.census.gov/geo/tiger/TIGER2010/COUNTY/2010/tl_2010_17_county10.zipunzip tl_2010_17_county10.zipogr2ogr -f GeoJSON tl_2010_17_county10.geojson tl_2010_17_county10.shpcurl -L -O https://www2.census.gov/geo/tiger/TIGER2010/COUNTY/2010/tl_2010_18_county10.zipunzip tl_2010_18_county10.zipogr2ogr -f GeoJSON tl_2010_18_county10.geojson tl_2010_18_county10.shptippecanoe -zg -o counties-merged.mbtiles -l counties --coalesce-densest-as-needed --extend-zooms-if-still-dropping tl_2010_17_county10.geojson tl_2010_18_county10.geojson -l counties: 图层名默认为文件名,也可以使用该选项自定义; tile-join用于合并或复制矢量瓦片。 tippecanoe-decode用于将矢量瓦片逆向转换为 GeoJSON。 tippecanoe-enumerate用于列举mbtiles中的矢量瓦片。","categories":[],"tags":[{"name":"gis","slug":"gis","permalink":"http://blog.yancongwen.com/tags/gis/"}]},{"title":" Mongodb 基本使用","slug":"Mongodb-基本使用","date":"2019-04-01T12:40:00.000Z","updated":"2019-04-16T12:43:29.929Z","comments":true,"path":"2019/04/01/Mongodb-基本使用/","link":"","permalink":"http://blog.yancongwen.com/2019/04/01/Mongodb-基本使用/","excerpt":"","text":"Mongodb 基本使用安装MongoDB 提供了 linux 各发行版本 64 位的安装包,你可以在官网下载安装包。下载地址:https://www.mongodb.com/download-center#community。下载完安装包,并解压 tgz(以下演示的是 64 位 Linux 上的安装)。 123curl -O https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-3.0.6.tgz # 下载tar -zxvf mongodb-linux-x86_64-3.0.6.tgz # 解压mv mongodb-linux-x86_64-3.0.6/ /usr/local/mongodb # 将解压包拷贝到指定目录 MongoDB 的可执行文件位于 bin 目录下,所以可以将其添加到 PATH 路径中: 1export PATH=<mongodb-install-directory>/bin:$PATH <mongodb-install-directory> 为你 MongoDB 的安装路径。如本文的 /usr/local/mongodb 。 配置新建并编辑文件 /etc/mongod.conf: 12345678910111213141516171819202122# Where and how to store data.storage: dbPath: /data/db journal: enabled: true# where to write logging data.systemLog: destination: file logAppend: true path: /data/db/log/mongodb.log# network interfacesnet: port: 27017 bindIp: 127.0.0.1# how the process runsprocessManagement: timeZoneInfo: /usr/share/zoneinfo # Enable a daemon mode that runs the mongos or mongod process in the background. fork: true 参考文档:配置文档 启动 配置文件启动mongod -f /etc/mongod.conf 或 mongod --config /etc/mongod.conf 自定义命令启动mongod --dbpath=/data/db --logpath=/data/logs --fork 停止 方式 1mongod --shutdown 方式 2如果不是后台启动的 mongodb,直接 Crtl+C 关闭 方式 3使用数据库命令关闭:输入 mongo 进入 mongodb 的命令行模式,依次输入use admin、 db.shutdownServer()命令 方式 4使用操作系统的进程命令或进程管理工具关闭 MongoDB GUI: Robo 3T","categories":[],"tags":[{"name":"mongodb","slug":"mongodb","permalink":"http://blog.yancongwen.com/tags/mongodb/"}]},{"title":"node 项目集成 CAS 单点登录","slug":"node-项目集成-CAS-单点登录","date":"2019-02-28T13:03:00.000Z","updated":"2019-04-16T13:04:29.903Z","comments":true,"path":"2019/02/28/node-项目集成-CAS-单点登录/","link":"","permalink":"http://blog.yancongwen.com/2019/02/28/node-项目集成-CAS-单点登录/","excerpt":"","text":"node 项目集成 CAS 单点登录 最近接到一个新的需求,将现有的 node + vue 前后端分离的项目和公司已有的 CAS 单点登录系统对接,即需要删除项目中已有的登录认证机制,和 CAS 集成。 一、 什么是单点登录(SSO)假设用户 X 需同时登录站点 A 和站点 B,这两个站点之间其实是有关联性的,但是如果用户认证数据不通用,那将需要注册或登录两次。单点登录系统(Single Sign On,简称 SSO)就是为了解决这种场景的问题,建立一种用户认证中心,只要经过这个中心注册或登录了某一站点服务的用户,总是能够认证登录这个中心所授权的其他所有服务。 登录 相比于单系统登录,SSO 需要一个独立的认证中心,只有认证中心能接受用户的用户名密码等安全信息,其他系统不提供登录入口,只接受认证中心的间接授权。间接授权通过令牌实现,sso 认证中心验证用户的用户名密码没问题,创建授权令牌,在接下来的跳转过程中,授权令牌作为参数发送给各个子系统,子系统拿到令牌,即得到了授权,可以借此创建局部会话,局部会话登录方式与单系统的登录方式相同。 注销 在一个子系统中注销,所有子系统的会话都将被销毁。 二、 什么是 CASCAS (Central Authentication Service) 是耶鲁 Yale 大学发起的一个 java 开源项目,旨在为 Web 应用系统提供一种可靠的单点登录解决方案( Web SSO ),github 地址。CAS 作为一种单点登录框架,后端可配置不同的用户数据库,支持自定义验证或加密逻辑,并提供不同的协议用于与业务 server(cas-client)间的通信。CAS 的源码是由 java 写的,因此对于 java 的 web 项目天生友好。当然只要实现 CAS 相关协议的 client,无论是哪种语言实现的,都能集成到 CAS 认证框架中。CAS 的部署请参考这里从结构上看, CAS 包含两个部分: CAS Server 和 CAS Client 。 CAS Server CAS Server 需要独立部署,主要负责对用户的认证工作, 它会处理用户名 / 密码等凭证 (Credentials) CAS Client CAS Client 部署在客户端, 负责处理 对本地 Web 应用(客户端)受保护资源的访问请求,并且当需要对请求方进行身份认证时,重定向到 CAS Server 进行认证 。CAS Client 负责部署在客户端(Web 应用),原则上, CAS Client 的部署意味着,当有对本地 Web 应用的受保护资源的访问请求,并且需要对请求方进行身份认证, Web 应用不再接受任何的用户名密码等类似的 Credentials ,而是重定向到 CAS Server 进行认证。 通常单点登陆都会涉及到对已有系统的改造。所以,client 端的侵入性就变的很重要。侵入性越小,越容易部署和测试。CAS 框架的优点之一就在于它的 client 端对应用系统的侵入性比较小。对于 Java 的 Web 项目来说,你只需要在 web.xml 里面添加一个 filter,拷贝 CAS client 的 jar 包到应用系统,然后改造登陆认证过程即可。 三、 node 项目集成 CAS首先介绍一下项目架构,项目前端采用 Vue 开发,服务端采用 node + express 开发。项目最终上线部署时,前端打包后放在服务端的 /public 目录下,即采用 express 静态服务功能。开始我还是固有的思维,考虑在前端实现单点登录的集承,用户登录不都是在前端做的吗,调用服务端接口,CAS 也提供了一些列的 Restful Api,然而当我去实现的时候却相当麻烦,还会有跨域的问题。然后想到了可否直接在服务端控制访问权限?于是网上搜索一番,终于找到了 connect-cas2 node 中间件,其实它就是 CAS Client 的 node 实现,正好符合我的项目场景。connect-cas2 的使用也比较简单,具体请看官方文档,直接抄文档示例就可以了。以下是主要代码: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859var express = require('express')var ConnectCas = require('connect-cas2')var bodyParser = require('body-parser')var session = require('express-session')var cookieParser = require('cookie-parser')var MemoryStore = require('session-memory-store')(session)var app = express()app.use(cookieParser())app.use( session({ name: 'NSESSIONID', secret: 'Hello I am a long long long secret', resave: true, saveUninitialized: true, store: new MemoryStore() }))var casClient = new ConnectCas({ debug: true, ignore: [/\\/ignore/], match: [], servicePrefix: 'http://localhost:3000', serverPath: 'http://your-cas-server.com', paths: { validate: 'cas/validate', serviceValidate: 'cas/serviceValidate', login: 'cas/login', logout: 'cas/logout', proxy: '', proxyCallback: '' }})app.use(casClient.core())app.use(bodyParser.json())app.use(bodyParser.urlencoded({ extended: true }))app.get('/logout', casClient.logout())app.get('/', function(req, res) { if (!req.session.cas.user) { return next() } const username = req.session.cas.user return res.send( '<p>You are logged in. Your username is ' + username + '. <a href=\"/logout\">Log Out</a></p>' )})app.get('/api', function(req, res) { return res.send('hi')})app.listen(3000) 参考 单点登录原理与简单实现 基于 CAS 单点登录的前后端分离架构说明 单点登录终极方案之 CAS 应用及原理 单点登录(一)-单点登录 SSO 的介绍和 CAS+选型 connect-cas2 文档 基于 node.js 的 sso(单点登录-客户端校验)","categories":[],"tags":[{"name":"Node.js","slug":"Node-js","permalink":"http://blog.yancongwen.com/tags/Node-js/"}]},{"title":"四大WebGIS地图引擎的对比选择","slug":"四大WebGIS地图引擎的对比选择","date":"2019-02-05T04:42:00.000Z","updated":"2019-04-16T13:02:54.461Z","comments":true,"path":"2019/02/05/四大WebGIS地图引擎的对比选择/","link":"","permalink":"http://blog.yancongwen.com/2019/02/05/四大WebGIS地图引擎的对比选择/","excerpt":"","text":"四大Webgis地图引擎的对比选择 选择的方式主要是根据业务需求,主要分为: 传统GIS业务(Leaflet),三维业务(Cesium),互联网展示型(MapboxGL),老IE浏览器,老业务维护(OpenLayers)。 1、Leafletleaflet是常规的的最适合常规gis开发的地图,因此核心功能就是“传统GIS”功能. 优点 主流投影坐标支持:几乎所有的主流投影坐标系都可以支持; 矢量表达:矢量专题图,矢量空间分析,矢量瓦片,矢量可视化等矢量表达; 全样式表达:可结合主流的互联网可视化技术,如D3,Echarts,Mapv,几乎主要的地图的可视化表达都可以实现; 功能全,操作友好:功能全,插件丰富,社区生态完善.出现bug几乎百度找到,对开发者友好; 跨平台:兼容大部分浏览器,跨平台强; 移动设备的支持:内部代码框架设计的时候考虑到移动设备的支持.针对移动设备天然支持; 缺点 没有使用webgl进行渲染,在可视化表达上差一点点; 没有使用硬件加速,在数据量上没有发挥硬件的最大效果; 2、Openlayersopenlayers强调的是老ie等浏览器的兼容性. 优点 主流投影坐标系支持:几乎所有的主流投影坐标系都可以支持; 脚本一体化:功能全并且集成到官方脚本; ogc协议:几乎是最遵循ogc协议的脚本了; 兼容性:兼容老的ie6789等疑难浏览器问题; 缺点 功能大而虚,很多功能有实现但是实际使用效果不理想; 可视化表达差劲; 内存释放与优化差; 3、Mapbox GLMapbox GL主要是构建世界上最漂亮的地图,因此核心功能就是一个“看”字.相关可视化库还有:Kepler-GL、Echarts-GL 优点 高效矢量瓦片:真正高效实用的矢量瓦片; 顶级可视化:真正顶级的可视化渲染,mapboxGL,echartGL,KeplerGl等; 高清矢量图形:真正顶级的高清矢量图形绘制SVG,Canvas; 顶级互联网技术加持:国内Baidu,国外Uber,Mapbox等顶级可视化巨头技术加持; 缺点 只支持web墨卡托投影(EPSG:3857); 三维表达局限于高程和基本高程无法支持浮空真三维模型,这就是mapbox的关于三维的设置项叫做fill-extrusion而不是model的原因; 4、CesiumCesium强调的是BIM三维模型,倾斜摄影的表达,重点在于三维建模与时态模拟.Cesium相关资料汇总 优点 倾斜摄影:支持倾斜摄影,地形,海洋环境等三维场景展现; BIM三维建模:支持BIM管网建模和3dx,gltf模型的展示; 时态表达:支持时态,时间播放,时间动画,时空聚类等时空展现; 缺点 没有类似unity的特殊光晕效果,虽然使用了webgl但效果平平; 自成体系的模型与几何绘制策略,需要重新学习; 代码过重,并且主视图必须获取顶级div,影响工程代码结构;","categories":[],"tags":[{"name":"gis","slug":"gis","permalink":"http://blog.yancongwen.com/tags/gis/"}]},{"title":"JavaScript 之 Array","slug":"JavaScript-之-Array","date":"2019-02-01T12:48:00.000Z","updated":"2019-04-16T12:51:37.729Z","comments":true,"path":"2019/02/01/JavaScript-之-Array/","link":"","permalink":"http://blog.yancongwen.com/2019/02/01/JavaScript-之-Array/","excerpt":"","text":"JavaScript 之 Array 本文内容大部分总结自 《Javascript 高级程序设计(第 3 版)》 1. 介绍本质:数组就是原型链中有 Array.prototype 的对象 新建 1234567//第一种方法:通过构造函数 Array //注:可以省略 new 关键字var colors = new Array()var colors = new Array(20)var colors = new Array()//第二种方法:数组字面量var colors = []var colors = ['blue', 'black', 'green'] 使用和赋值 1234var colors = ['blue', 'black', 'green']alert(colors[0]) //使用colors[2] = 'black' //赋值colors[3] = 'brown' //新增 length 属性\\length 属性不是只读的!通过设置此属性可以从数组的末尾添加或删除项,新增的项默认为 undefined。利用 length 属性可以方便的向数组的末尾添加项。 12345var colors = ['blue', 'black', 'green']colors[colors.length] = 'black'colors[colors.length] = 'brown'colors[99] = 'white'alert(colors.length) //100 2. 检测数组:Array.isArray()当检测 Array 实例时, Array.isArray() 优于 instanceof, 因为Array.isArray()能检测 iframes。使用instanceof操作符的问题在于它是假定只有一个全局执行环境。如果项目中使用了多个框架,就存在两个以上的全局执行环境,从而存在两个以上版本的 Array 构造函数。为了解决这个问题,ECMASsript5 新增了Array.isArray()。这个方法的目的是最终确定某个值到底是不是数组。 12var bool = value instanceof Arrayvar bool = Array.isArray(value) Polyfill: 12345if (!Array.isArray) { Array.isArray = function(arg) { return Object.prototype.toString.call(arg) === '[object Array]' }} 3. 转换方法:toLocaleString()、toString()、valueOf()、join() toString():返回由数组中每个值的字符串形式拼接而成的一个以逗号分隔的字符串,为了创建这个字符串,会调用每一项的 toString 方法; toLocaleString():返回由数组中每个值的字符串形式拼接而成的一个以逗号分隔的字符串,为了创建这个字符串,会调用每一项的 toLocaleString 方法; valueOf():返回的还是数组; join():可以使用不同的分隔符来构建数组字符串; 4. 添加删除:push()、pop()、shift()、unshift() push():末尾添加 pop():末尾删除 shift():前端移除 unshift():前端添加 栈:后进先出 (LIFO),栈中项的操作只发生在一个位置——栈的顶部。可使用 push、pop 实现。 队列:先进先出 (FIFO),末尾添加、前端移除。可使用 push、shift 实现。 5. 重排序方法:reverse()、sort() reverse():反转 sort():排序 123// 最简单的例子:var array = [1, 2, 3, 4, 5]array.reverse() //[5,4,3,2,1] 在上面的例子, reverse 方法直观明了,但不够灵活,不能满足我们自定义排序规则的需求。因此才有了 sort 方法。 sort 方法默认按照升序排列。为了实现排序,该方法会自动调用每个数组项的 toString 方法,比较字符串进行排序。如: 123var array = [0, 1, 5, 10, 15]var result = array.sort() //[0,1,10,15,5]//在这里会得到意外的结果,因为并不是按照数字比较的 sort 方法强大之处在于其可以接受一个 比较函数作为参数,从而实现我们自定义排序规则。比较函数接收两个参数,表示的是数组中的两项,暂用 a, b 表示,如果 a 应该位于 b 之前,则返回一个负数(表示不用交换位置);如果 a 应该位于 b 之后,则返回一个正数(表示需要交换位置);如果 a 等于 b,则返回 0。例: 12345678910111213141516function compare(a, b) { if (a < b) { return -1 } else if (a > b) { return 1 } else { return 0 }}//使用以上比较函数实现升序var array = [0, 10, 15, 1, 5]var result = array.sort(compare) //[0,1,5,10,15]// 上面的比较函数可以简化为:function compare(a, b) { return a - b} 通过以上形式,可以实现更复杂的排序效果。\\reverse 和 sort 方法操作数组本身,返回值是经过排序后的数组。 6. 操作方法:concat()、slice()、splice() concat():合并数组,不影响原数组\\concat()方法会先创建当前数组的一个副本,然后将接收到的参数添加到副本的末尾,最后返回新建的数组。 123var colors1 = ['red', 'green']var colors2 = colors1.concat('yellow', ['blue', 'black'])//colors2 = ['red','green','yellow','blue','black'] slice():剪切数组,不影响原数组\\slice 基于当前数组的一个或多项组建新数组。接受一个或者两个参数. 123456789var colors1 = ['red', 'green', 'yellow', 'blue', 'black']//若只有一个参数,则返回从该参数指定位置到结束位置之间项的数组var colors2 = colors1.slice(1) //['green','yellow','blue','black']//若有两个参数,则返回第一个参数指定位置项至第二个参数指定项(不包含)之间的项的数组var colors3 = colors1.slice(1, 4) //['green','yellow','blue']//若参数中有负数,则用数组长度加上该参数确定位置var colors4 = colors1.slice(-4, -1) //['green','yellow','blue']//若第二个参数小于第一个参数,则返回空数组var colors5 = colors1.slice(2, 1) //[] splice():拼接数组,对原数组操作\\可以实现对原数组的删除、插入、替换操作,始终返回一个数组,该数组中包含从原始数组中删除的项。 删除:接收两个参数:要删除的第一项位置和要删除的项数; 插入:接收三个以上参数:起始位置、0(要删除的项数)、要插入的项; 替换:接收三个以上参数:起始位置、要删除的项数、要插入的项; 12345678910var colors = ['red', 'green', 'yellow', 'blue', 'black']//删除var removed = colors.splice(0, 1) // 返回 ['red']//colors = ['green','yellow','blue','black']//插入var removed = colors.splice(1, 0, 'white', 'orange') // 返回 []//colors = ['green','white','orange','yellow','blue','black']//替换var removed = colors.splice(1, 1, 'red') // 返回 ['white']//colors = ['green','red','orange','yellow','blue','black'] 7. 位置方法:indexOf()、lastIndexOf()ECMAScript5 增加了两个位置方法。这两个方法都接收两个参数:要查找的项和开始查找的位置索引。返回值是要查找的项在数组中的位置,没找到返回-1.在查找的过程中使用全等操作符。 indexOf() 正序 lastIndexOf() 反序 12345var number = [1,2,3,4,5,4,3,2,1];alert number.indexOf(4); //3alert number.lastIndexOf(4); //5alert number.indexOf(4,4); //5alert number.lastIndexOf(4,4); //3 8. 迭代方法:every()、filter()、forEach、map()、some()5 个迭代方法,都接收两个参数:要在每一项上运行的函数和运行该函数的作用域对象(影响 this)。传入的函数接收三个参数:当前数组项的值,该项在数组中位置,数组本身。 every() :对数组中每一项运行给定函数,若结果全都是 true,则返回 true; some() :对数组中每一项运行给定函数,有一项结果为 true 就返回 true; filter() :对数组中每一项运行给定函数,返回结果为 true 的数组项组合的新数组; forEach() :对数组中每一项运行给定函数,没有返回值; map() :对数组中每一项运行给定函数,返回每一项的运行结果数组; 123456789101112131415161718192021var number = [1,2,3,4,5,4,3,2,1];var everyResult = number.every(function(item,index,array){ return (item>2);}); //falsevar someResult = number.some(functon(item,index,array){ return (item>2);}); //truevar filterResult = number.filter(function(item,index,array){ return (item>2);}) //[3,4,5,4,3]var mapResult = number.map(function(item,index,array){ return (item*2);}); //[2,4,6,8,10,8,6,4,2]//forEach方法没有返回值number.forEach(function(item,index,array){ doSomething()}); 9. 归并方法:reduce()、reduceRight()ECMAScript5 新增方法。这两个方法都会迭代数组所有项,构建一个最终返回值。接受两个参数:一个在每一项上调用的函数和作为归并基础的初始值。其中接受的第一个参数是函数,它接收 4 个参数:前一个值、当前值、项索引、数组对象;这个函数返回的任何值都会作为第一个参数自动传给下一项,两个函数执行方向不同,除此之外完全相同。 reduce() 正序 reduceRight() 反序 123456789//数组求和:var number = [1, 2, 3, 4, 5]var sum = number.reduce(function(prev, cur, index, array) { console.log(prev, cur, index, array) return prev + cur}) //15var sum = number.reduceRight(function(prev, cur, index, array) { return prev + cur}) //15 我们把 index 打印出来,你会发现,它的 index 是从 1 开始的。 10. ES6 中新增的方法1. Array.from()Array.from 方法用于将两类对象转为真正的数组:伪数组对象和可遍历(iterable)对象(包括 ES6 新增的数据结构 Set 和 Map )。 12345678910let arrayLike = { '0': 'a', '1': 'b', '2': 'c', length: 3}// ES5var arr1 = [].slice.call(arrayLike) // ['a', 'b', ''c]// ES6var arr2 = Array.from(arrayLike) // ['a', 'b', ''c] 只要是部署了Iterator接口的数据结构,Array.from 都能将其转为数组。 1234Array.from('hello') // ['h', 'e', 'l', 'l', 'o']// Setlet nameSet = new Set(['a', 'b'])Array.from(nameSet) // ['a', 'b'] Polyfill: 123const toArray = (() => { Array.from ? Array.from : obj => [].slice.call(obj)})() 2. Array.of()Array.of 方法用于将一组值转为数组。 1234Array.of() // []Array.of(3, 4, 5) // [3,4,5]Array.of(3) // [3]Array.of(3).length // 3 这个方法的目的主要是弥补数组构造函数Array()的不足。因为参数个数的不同会导致Array()的行为有差异。 123Array() // []Array(3) // [ , , ]Array(3, 4, 5) // [3,4,5] 上面代码中,Array 方法没有参数、有 1 个参数、或 3 个参数时,返回结果不一样。只有参数不少于 2 个时,Array() 才会返回由参数组成的新数组。参数只有 1 个时,实际上是指定数组长度。Array.of 基本上可以替代 Array() 或 new Array(),并且不存在由于参数不同而导致的重载。它的行为非常统一。 Polyfill: 123function ArrayOf() { return [].slice.call(arguments)} 3. find() 和 findIndex()查找 4. fill()填充 5. copyWithin()6. includes()11. 伪数组 什么是伪数组? 拥有一个 length 属性和若干索引属性的任意对象(有 0,1,2,3,4,5…n,length 这些 key 的对象); 原型链中没有 Array.prototype; 目前知道的伪数组有: 函数的arguments对象; document.querySelectorAll('div') 返回的节点的集合NodeList; 12. 数组去重 经典的一个面试题目:如何实现数组去重?: 假设有数组 array = [1,5,2,3,4,2,3,1,3,4] 你要写一个函数 unique,使得 unique(array) 的值为 [1,5,2,3,4]。要求:不要做多重循环,只能遍历一次;请给出两种方案,一种能在 ES 5 环境中运行,一种能在 ES 6 环境中运行。 答案 ES 5 1234567891011function unique(array) { var result = [] var obj = {} for (var i = 0; i < array.length; i++) { if (!obj[array[i]]) { result.push(array[i]) obj[array[i]] = true } } return result} ES 6 123function unique(array) { return Array.from(new Set(array))}","categories":[],"tags":[{"name":"Javascript","slug":"Javascript","permalink":"http://blog.yancongwen.com/tags/Javascript/"}]},{"title":"JavaScript 之 Function","slug":"JavaScript-之-Function","date":"2019-01-16T12:56:00.000Z","updated":"2019-04-16T13:03:12.926Z","comments":true,"path":"2019/01/16/JavaScript-之-Function/","link":"","permalink":"http://blog.yancongwen.com/2019/01/16/JavaScript-之-Function/","excerpt":"","text":"JavaScript 之 Function1、函数的五种声明方式123456789101112131415161718192021// 方式1:函数声明,存在变量提升function f1(x, y) { return x + y}f1.name === 'f1'// 方式2:函数表达式,不存在变量提升var f2 = function(x, y) { return x + y}f2.name === 'f2'// 方式3(不用)var f3 = function fff(x, y) { return x + y}f3.name === 'fff'// 方式4(不用)var f4 = new Function(x, y, 'return x+y')f4.name === 'anonymous' //匿名的// 方式5(ES6增)var f5 = (x, y) => x + yf5.name === 'f5' 2、函数内部属性:name、length、this、arguments、callee、caller name(函数名) length(函数形参长度) thisjs 中的 this 和 java、C# 中的类似,表示函数据以执行的的环境对象。此外,这里还要注意一点的是,普通函数和 ES6 中的箭头函数中 this 是有区别的。 - 普通函数中的 this 是不固定的,它会随着执行环境的改变而改变; - ES6 中的箭头函数没有自己的 this, 箭头函数函数体内的 this 就是定义时所在的对象,而不是使用时所在的对象,一旦它定义以后就不会再变; 123456789101112131415var color = 'red'var obj = { color: 'blue' }function sayColor() { console.log(this.color)}sayColor() // redobj.sayColor = sayColorobj.sayColor() // bluevar sayColor2 = () => { console.log(this.color)}sayColor2() // redobj.sayColor2 = sayColor2obj.sayColor2() // red arguments 与 calleearguments 是一个伪数组对象,包含着传入函数中的所有参数。虽然它的主要用途是保存函数参数,但这个对象还有一个 callee 属性,该属性是一个指针,指向拥有这个 arguments 对象的函数。下面是阶乘函数的定义,用到了递归,使用 callee 很好的避免了函数内部的耦合: 12345678function factorial(num) { if (num <= 1) { return 1 } else { return this.arguments.callee(num - 1) //等价于(且优于) return factorial(num-1) }} callerECMAScript 5 规范化了另一个函数对象属性:caller。这个属性保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,那么它的值为 null。 12345678fucntion outer(){ inner()}function inner(){ alert(arguments.callee.caller) // 等价于 alert(outer)}outer() 3、改变函数中 this 指向的三个方法:call、apply、bind每一个函数都会从它的构造函数 Function 的原型中继承得到 call、apply、bind 三个方法。它们的用途在于在特定的作用域中调用函数,实际上等于改变函数内部 this 指针的指向(ES6 的箭头函数当然没用啦)。call、apply 为立即调用函数而 bind 是返回函数。 apply该方法接收两个参数,函数作用域对象、参数数组(数组或者 arguments 伪数组) callcall 方法和 apply 方法作用完全相同,它们的区别仅在于接收的第二个参数形式不同。call方法要求传递给函数的参数必须逐个列举出来。至于是采用 call 还是 apply 完全取决于你采取哪种传参方式方便。 123456789function sum(num1, num2) { return sum1 + sum2}function callSum(num1, num2) { return sum.apply(this, arguments) // 等价于 return sum.applay(this, [num1,num2]) // 等价于 return sum.call(this, num1, num2)}callSum(1, 2) // 3 事实上,传递参数并非 applay()、call() 的真正用武之地,他们真正强大之处在于能够扩充函数赖以运行的环境。来看下面一个列子。 123456789window.color = 'red'var obj = { color: 'blue' }function sayColor() { console.log(this.color)}sayColor() // redsayColor.call(window) // redsayColor.call(this) // redsayColor.call(obj) // blue 使用 call() 或者 apply() 来扩充函数作用域的最大好处就是对象与方法的解耦。 bindbind 是 ES5 中定义的一个方法,bind 接受的参数跟 call 一致,执行bind()不会立即调用,它会生成一个新的函数,新函数的 this 就是 bind 方法穿进去的参数。例如: 1234567window.color = 'red'var obj = { color: 'blue' }function sayColor() { console.log(this.color)}var newSayColorFun = sayColor.bind(obj)newSayColorFun() // blue 4、什么是闭包,闭包的用途是什么? 定义:「函数」和「函数内部能访问到的变量」(也叫环境)的总和,就是一个闭包。JavaScript 有两种作用域:全局作用域和函数作用域。函数内部可以直接读取全局变量。但是,在函数外部无法读取函数内部声明的变量。换言之,如果一个函数,使用了它范围外的变量,那么‘这个函数+这个变量’就叫做闭包。 示例 12345678910function foo() { var local = 1 function bar() { local++ return local } return bar}var func = foo()func() 为什么要函数套函数呢?是因为需要局部变量,所以才把 local 放在一个函数里,如果不把 local 放在一个函数里,local 就是一个全局变量了,达不到使用闭包的目的——隐藏变量 用途 隐藏一个变量,外部无法直接访问这个变量 让这些变量始终保持在内存中 封装对象的私有属性和私有方法123456789101112function f1(n) { return function() { return n++ }}var a1 = f1(1)a1() // 1a1() // 2var a2 = f1(1)a2() // 1a2() // 2//这段代码中,a1 和 a2 是相互独立的,各自返回自己的私有变量。 5、什么是 call stackjs 是单线程的,也就是说同一时间只能执行一个方法。 参考 Javascript 高级程序设计(第 3 版)>) JS 中的闭包是什么?","categories":[],"tags":[{"name":"Javascript","slug":"Javascript","permalink":"http://blog.yancongwen.com/tags/Javascript/"}]},{"title":"浏览器存储","slug":"浏览器存储","date":"2019-01-12T13:01:00.000Z","updated":"2019-04-16T13:02:30.091Z","comments":true,"path":"2019/01/12/浏览器存储/","link":"","permalink":"http://blog.yancongwen.com/2019/01/12/浏览器存储/","excerpt":"","text":"浏览器存储随着 Web 应用程序的不断发展,产生了能够直接在客户端存储用户信息能力的要求。想法是很合乎逻辑,属于某个特定用户的信息(登录信息、偏好设置及其他数据)应该存储在该用户的机器上。本地存储的第一个解决方案是网景公司创造的 Cookie 。今天,Cookie 只是客户端存储数据的其中一种选项,Cookie 的性质和它的局限性使它并不能作为存储大量信息的理想手段,所以又出现了其他方法:Storage、sessionStorage、globalStorage(过时)、localStorage、IndexedDB。 Cookie Cookie 是 HTTP 协议中的内容 服务器通过 Set-Cookie 头给客户端一串字符串, 客户端每次访问相同域名的网页时,必须带上这段字符串 服务器读取 Cookie 就知道登录用户的信息(不要存重要信息) 客户端要在一段时间内保存这个 Cookie Cookie 默认在用户关闭页面后就失效,服务端可以任意设置 Cookie 的过期时间(expires) 大小大概在 4kb 以内 Cookie 遵守同源策略,不过跟 AJAX 的同源策略稍微有些不同,服务端可以对 Cookie 的路径做限制 Cookie 存在 Windows C 盘的一个文件里 一般用来记录不重要的用户信息,或者存储 SessionID,用于跟踪用户 Cookie 内容太多会影响请求性能,因此尽可能少在 Cookie 中存储信息 Cookie 的问题:用户可以随意篡改 Cookie Session Session 依赖于 Cookie 将 SessionID(随机数)通过 Cookie 发给客户端 客户端访问服务器时,服务器读取 SessionID 服务器有一块内存(哈希表)保存了所有 session 通过 SessionID 我们可以得到对应用户的隐私信息,如 id、email 这块内存(哈希表)就是服务器上的所有 session LocalStorage LocalStorage 跟 HTTP 无关 HTTP 不会带上 LocalStorage 的值 只有相同域名的页面才能互相读取 LocalStorage(没有同源那么严格) 每个域名 localStorage 最大存储量为 5Mb 左右(每个浏览器不一样) 常用场景:记录有没有提示过用户(没有用的信息,不能记录密码) LocalStorage 永久有效,除非用户清理缓存 SessionStorge(会话存储) 基本和 LocalStorage 一样,区别就是保存时间不同 SessionStorage 在用户关闭页面(会话结束)后就失效","categories":[],"tags":[{"name":"Javascript","slug":"Javascript","permalink":"http://blog.yancongwen.com/tags/Javascript/"}]},{"title":"前端测试基本知识","slug":"前端测试基本知识","date":"2018-12-16T13:05:00.000Z","updated":"2019-04-16T13:06:54.257Z","comments":true,"path":"2018/12/16/前端测试基本知识/","link":"","permalink":"http://blog.yancongwen.com/2018/12/16/前端测试基本知识/","excerpt":"","text":"测试知识前端常用测试库 KarmaKarma([ˈkɑrmə] 卡玛)是一个测试运行器,它可以呼起浏览器,加载测试脚本,然后运行测试用例 MochaMocha([ˈmoʊkə] 摩卡)是一个单元测试框架/库,它可以用来写测试用例 SinonSinon(西农)是一个 spy / stub / mock 库,用以辅助测试(使用后才能理解) 步骤 安装各种工具 1npm i -D karma karma-chrome-launcher karma-mocha karma-sinon-chai mocha sinon sinon-chai karma-chai karma-chai-spies 创建 karma 配置 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647// 新建 karma.conf.js,内容如下module.exports = function (config) { config.set({ // base path that will be used to resolve all patterns (eg. files, exclude) basePath: '', // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter frameworks: ['mocha', 'sinon-chai'], client: { chai: { includeStack: true } }, // list of files / patterns to load in the browser files: [ 'dist/**/*.test.js', 'dist/**/*.test.css' ], // list of files / patterns to exclude exclude: [], // preprocess matching files before serving them to the browser // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: {}, // test results reporter to use // possible values: 'dots', 'progress' // available reporters: https://npmjs.org/browse/keyword/karma-reporter reporters: ['progress'], // web server port port: 9876, // enable / disable colors in the output (reporters and logs) colors: true, // level of logging // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG logLevel: config.LOG_INFO, // enable / disable watching file and executing tests whenever any file changes autoWatch: true, // start these browsers // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher browsers: ['ChromeHeadless'], // Continuous Integration mode // if true, Karma captures browsers, runs the tests and exits singleRun: false, // Concurrency level // how many browser should be started simultaneous concurrency: Infinity }) }","categories":[],"tags":[{"name":"测试","slug":"测试","permalink":"http://blog.yancongwen.com/tags/测试/"}]},{"title":"CSS 实现多行文字截断","slug":"CSS-实现多行文字截断","date":"2018-11-16T12:45:00.000Z","updated":"2019-04-16T12:53:36.332Z","comments":true,"path":"2018/11/16/CSS-实现多行文字截断/","link":"","permalink":"http://blog.yancongwen.com/2018/11/16/CSS-实现多行文字截断/","excerpt":"","text":"CSS 实现多行文字截断 做响应式系统设计的时候遇到需要对标题进行多行文字截取的效果,但是并没有一个统一 CSS 属性实现标准,需要用到一些奇淫妙计来实现。下面是一些实现方法。 单行文字截断 text-overflow文本溢出我们经常用到的应该就是 text-overflow:ellipsis 了,只需轻松几行代码就可以实现单行文本截断。该属性浏览器原生支持,各大浏览器兼容性好,缺点就是只支持单行文本截断。如果是多行文字截取效果,实现起来就没有那么轻松。 12345div { white-space: nowrap; overflow: hidden; text-overflow: ellipsis;} 多行文字截断方法 1: -webkit-line-clamp 实现具体的方式如下: 123456div { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;} 它需要和 display、 -webkit-box-orient 和 overflow 结合使用: display:-webkit-box; 必须结合的属性,将对象作为弹性伸缩盒子模型显示。 -webkit-box-orient:vertical; 必须结合的属性,设置或检索伸缩盒对象的子元素的排列方式。 text-overflow:ellipsis; 可选属性,可以用来多行文本的情况下,用省略号“…”隐藏超出范围的文本。 -webkit-line-clamp: 2; 限制在一个块元素显示的文本的行数。 优点: 响应式截断,根据不同宽度做出调整。 文本超出范围才显示省略号,否则不显示省略号。 浏览器原生实现,所以省略号位置显示刚好。 缺点:-webkit-line-clamp 是一个不规范的属性,只有 webkit 内核的浏览器才支持这个属性,Firefox, IE 浏览器统统都不支持这个属性。 使用场景:多用于移动端页面,因为移动设备浏览器更多是基于 webkit 内核,除了兼容性不好,实现截断的效果不错。 方法 2:定位元素实现多行文本截断通过伪元素绝对定位到行尾并遮住文字,再通过 overflow: hidden 隐藏多余文字。 1234567891011121314151617181920p { position: relative; line-height: 18px; height: 36px; overflow: hidden;}p::after { content: '...'; font-weight: bold; position: absolute; bottom: 0; right: 0; padding: 0 20px 1px 45px; background: linear-gradient( to right, rgba(255, 255, 255, 0), white 50%, white );} 在 jsfiddle 查看演示。 优点: 兼容性好缺点: 无法识别文字的长短,省略号一直都在使用场景: 文字内容较多,确定文字内容一定会超过容器的,那么选择这种方式不错 方法 3:float 特性实现多行文本截断下面这个方法充分利用了元素浮动的特性。有个三个盒子 div,粉色盒子左浮动,浅蓝色盒子和黄色盒子右浮动, 当浅蓝色盒子的高度低于粉色盒子,黄色盒子仍会处于浅蓝色盒子右下方。 如果浅蓝色盒子文本过多,高度超过了粉色盒子,则黄色盒子不会停留在右下方,而是掉到了粉色盒子下。 以上是理论基础,具体的实现方法就是:将最后一个黄色的盒子看做省略号,当文本多余需要截断时,将已经浮动到左下侧的黄色盒子相对定位到浅蓝色盒子的右下侧。这样,当文本较少时,黄色盒子被定位到盒子以外不可见的区域,当文本超出时,就显示在文本右下角。具体实现请看代码: 1234567<div class=\"wrap\"> <div class=\"text\"> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dignissimos labore sit vel itaque delectus atque quos magnam assumenda quod architecto perspiciatis animi. </div></div> 12345678910111213141516171819202122232425262728293031323334353637383940.wrap { height: 40px; line-height: 20px; overflow: hidden;}.wrap .text { float: right; margin-left: -5px; width: 100%; word-break: break-all;}.wrap::before { float: left; width: 5px; content: ''; height: 40px;}.wrap::after { float: right; content: '...'; height: 20px; line-height: 20px; padding-right: 5px; text-align: right; /* 为三个省略号的宽度 */ width: 3em; /* 使盒子不占位置 */ margin-left: -3em; /* 移动省略号位置 */ position: relative; left: 100%; top: -20px; padding-right: 5px; background: linear-gradient( to right, rgba(255, 255, 255, 0), white 50%, white );} 在 jsfiddle 查看演示。 优点: 兼容性好;响应式截断,根据不同宽度做出调整;文本超出范围才显示省略号,否则不显示省略号;缺点: 增加了额外的 div 包裹元素和伪元素;使用场景: 这个方法应该是我看到最好的用纯 CSS 处理的方式了,推荐! 参考 本文摘录自:纯 CSS 实现多行文字截断","categories":[],"tags":[{"name":"CSS","slug":"CSS","permalink":"http://blog.yancongwen.com/tags/CSS/"}]},{"title":"CSS 设置滚动条样式","slug":"CSS-设置滚动条样式","date":"2018-11-12T12:47:00.000Z","updated":"2019-04-16T12:53:46.821Z","comments":true,"path":"2018/11/12/CSS-设置滚动条样式/","link":"","permalink":"http://blog.yancongwen.com/2018/11/12/CSS-设置滚动条样式/","excerpt":"","text":"CSS 设置滚动条样式!> ::-webkit-scrollbar只适用于 webkit 内核的浏览器 (谷歌 Chrome, 苹果 Safari、360、QQ、搜狗…),本文也仅讨论适用于 webkit 内核浏览器的设置方法。 定义滚动条样式就是设置伪元素和伪类样式。 1、滚动条伪元素选择器(7 个) 数字 属性 解释 1 ::-webkit-scrollbar 滚动条整体部分(可以设置纵向滚动条宽度、横向滚动条高度) 2 ::-webkit-scrollbar-button 滚动条两端的按钮(没有该属性默认无按钮 ) 3 ::-webkit-scrollbar-track 滚动条轨道 4 ::-webkit-scrollbar-track-piece 滚动条没有滑块的轨道部分 5 ::-webkit-scrollbar-thumb 滚动的滑块 6 ::-webkit-scrollbar-corner 当同时有垂直滚动条和水平滚动条时交汇的部分 7 ::-webkit-resizer 某些元素的 corner 部分的部分样式(例如 textarea 的可拖动按钮) 2、伪类伪类有点复杂,但是常用的只有前两个 1234567891011:horizontal //horizontal伪类适用于任何水平方向上的滚动条:vertical //vertical伪类适用于任何垂直方向的滚动条:decrement //decrement伪类适用于按钮和轨道碎片。表示递减的按钮或轨道碎片,例如可以使区域向上或者向右移动的区域和按钮:increment //increment伪类适用于按钮和轨道碎片。表示递增的按钮或轨道碎片,例如可以使区域向下或者向左移动的区域和按钮:start //start伪类适用于按钮和轨道碎片。表示对象(按钮 轨道碎片)是否放在滑块的前面:end //end伪类适用于按钮和轨道碎片。表示对象(按钮 轨道碎片)是否放在滑块的后面:double-button //double-button伪类适用于按钮和轨道碎片。判断轨道结束的位置是否是一对按钮。也就是轨道碎片紧挨着一对在一起的按钮。:single-button //single-button伪类适用于按钮和轨道碎片。判断轨道结束的位置是否是一个按钮。也就是轨道碎片紧挨着一个单独的按钮。:no-button //no-button伪类表示轨道结束的位置没有按钮。:corner-present //corner-present伪类表示滚动条的角落是否存在。:window-inactive //适用于所有滚动条,表示包含滚动条的区域,焦点不在该窗口的时候。 示例以下代码就是本页面的滚动条样式设置 123456789101112131415161718::-webkit-scrollbar { display: inline-block; width: 8px; height: 12px;}::-webkit-scrollbar-track { background: transparent;}::-webkit-scrollbar-thumb { background-color: transparent; border-radius: 4px;}:hover::-webkit-scrollbar-thumb { background-color: rgba(28, 70, 100, 1);}::-webkit-scrollbar-thumb:horizontal { border-radius: 0;} 参考 MDN 文档 css 设置滚动条样式 CSS3 自定义滚动条样式 -webkit-scrollbar","categories":[],"tags":[{"name":"CSS","slug":"CSS","permalink":"http://blog.yancongwen.com/tags/CSS/"}]},{"title":"HTTP 网络请求参数中带有特殊符号相关问题","slug":"http网络请求参数中带有特殊符号相关问题","date":"2018-10-21T05:21:33.000Z","updated":"2019-03-28T06:16:34.351Z","comments":true,"path":"2018/10/21/http网络请求参数中带有特殊符号相关问题/","link":"","permalink":"http://blog.yancongwen.com/2018/10/21/http网络请求参数中带有特殊符号相关问题/","excerpt":"","text":"HTTP 网络请求参数中带有特殊符号相关问题1、GET 请求参数中带有空格请求参数中带有空格会被处理为+号。这是HTML4标准中定义的,请看这里Form content types。在HTTP请求头中,首部字段Content-type用于指示资源的MIME类型,规定了提交表单元素时对数据的处理方式。下面是几个常见的值:1.text/html2.text/plain3.text/css4.text/javascript5.application/x-www-form-urlencoded6.multipart/form-data7.application/json8.application/xml…其中application/x-www-form-urlencoded是默认值,使用该值时,提交表单时内容会按照如下规则编码:空格转换为+号;非法字符转换为类似于%E0的两位16进制表示的ASCII码;换行符被转换为CR LF;数据项名称和数据值以=号分割,数据项与数据项之间以&分割;…….按照以上规则,在GET请求中,我们的请求参数会按照以上编码规则进行编码,然后拼接到请求URL后面。例如:12345678$.ajax({ type: 'GET', url: \"http://ip:port/count\", data: { app: '互联网 举证' }, success: function(response){ console.log(response) }}) 当我们发起以上请求时,请求参数会进行转码后拼接在 url 后面,空格被转换为了+号,真正的请求 URI 为 http://ip:port/count?app=app=%E4%BA%92%E8%81%94%E7%BD%91+%E4%B8%BE%E8%AF%81当我们直接在浏览器地址栏中输入请求地址,如下图(%20就是输入的空格),浏览器就会去请求你输入的地址,而不会再将特殊字符转码。但是会将中文转码。(其实相当于执行了JS中的encodeURI) 2、GET 请求参数中带有 + 号+ 号和汉字一样,会被转码,转码后为 %2B。例如:12345678$.ajax({ type: 'GET', url: \"http://ip:port/count\", data: { app: '互联网+举证' }, success: function(response){ console.log(response) }}) 当我们发起以上请求时,请求参数会进行转码后拼接在 url 后面,+号被转为%2B,真正的请求 URI 为 http://ip:port/count?app=%E4%BA%92%E8%81%94%E7%BD%91%2B%E4%B8%BE%E8%AF%81直接在浏览器地址栏中输入请求地址,参数中带有+号,真实请求中就会保留+号。 请细细品味这这几个示例的区别。 3、JavaScript中的 URI 编码解码方法 encodeURI 该方法用于将一个完整的URI编码,该方法不会对那些保留的并且在URI中有特殊意思的字符进行编码; decodeURI 该方法解码一个由encodeURI先前创建的统一资源标识符(URI); encodeURIComponent 对统一资源标识符(URI)的组成部分进行编码的方法。转义除了字母、数字、(、)、.、!、~、*、'、-和_之外的所有字符。 对于 application/x-www-form-urlencoded (POST) 这种数据方式,空格需要被替换成 ‘+’,所以通常使用 encodeURIComponent 的时候还会把 “%20” 替换为 “+”。参考 decodeURIComponent 用于解码由encodeURIComponent或者其它类似方法编码的部分统一资源标识符(URI)。","categories":[],"tags":[]},{"title":"理解 jQuery","slug":"jquery","date":"2018-10-21T05:20:10.000Z","updated":"2019-03-28T06:16:34.351Z","comments":true,"path":"2018/10/21/jquery/","link":"","permalink":"http://blog.yancongwen.com/2018/10/21/jquery/","excerpt":"","text":"理解 jQuery1、还有必要学习 jQuery 吗首先必须肯定的回答:有必要。虽然目前 MVVM 框架很流行,但 jQuery 依然占据一定地位。某些特定场景的项目 jQuery 依然是最好的选择,jQuery帮助我们解决了太多的兼容性问题,而且对于有一定JS基础的人来说学习 jQuery 的成本很低,没必要去掌握全部API,只要会查文档就可以。虽然新项目中不一定会使用 jQuery ,但是学习 jQuery ,尤其是去阅读 jQuery 源码,理解其设计思想、设计模式,你将会颇有收获。 2、jQuery DOM 操作设计思想jQuery 的基本设计思想和主要用法,就是”选择某个网页元素,然后对其进行某种操作”。使用 jQuery 的第一步,往往就是将一个选择表达式,放进构造函数 jQuery()(简写为$),得到被选中的元素,选中的元素可能是一个,也可能是多个。第二步就是对这些元素进行一系列操作,例如添加class、移除class、取值和赋值、移动等。 jQuery的一大特点就是支持链式操作,即类似于这样$('div').find('h3').eq(2).html('Hello');,将一系列操作连接在一起。它的原理在于每一步的jQuery操作,返回的都是一个jQuery对象,所以不同操作可以连在一起。 3、自己实现一个简单的 jQuery123456789101112131415161718192021222324252627282930313233window.jQuery = function(nodeOrSelector) { var nodes = {}; if (typeof nodeOrSelector === 'string') { var nodeList = document.querySelectorAll(nodeOrSelector); nodeList.forEach(function(item,index){ nodes[index] = item; }); nodes.length = nodeList.length; } else if (nodeOrSelector instanceof Node) { nodes = { '1': nodeOrSelector, lenght: 1 }; } nodes.addClass = function(classNames){ for (var i=0; i<nodes.length; i++) { classNames.forEach(function(item){ nodes[i].classList.add(item); }); } }; nodes.setText = function(text) { for (var i=0; i<nodes.length; i++) { nodeList[i].innerHTML = text; } }; return nodes;};// aliaswindow.$ = jQuery// 使用$('ul>li').addClass(['red','blue']);$('ul>li').setText('Hello jQuery'); 以上是本人实现的一个简单的jQuery对象。该对象接收一个参数,可以是一个已经获取到的DOM对象,也可以是一个选择器字符串。jQuery方法返回的是一个自定义的节点对象,该对象上定义了addClass、setText等一系列操作方法。 4、jQuery获取DOM和JS选择器获取的DOM的区别与联系例如:1<div id='x'></div> 12var div = document.getElementById('x')var $div = $('#x') div 是由原生API获取的元素节点对象, div.__proto__ === HTMLDivElement.prototype div.__proto__.__proto__ === HTMLElement .prototype $div 是jQuery对象实例,它包含了从jQuery继承过来的很多方法和属性 $div.__proto__ === jQuery.prototype $div.__proto__ .__proto__ === Object.prototype div 变成 $div: $(div) $div 变成 div: $div[0] === div","categories":[],"tags":[]},{"title":"缓存(cache)相关知识点总结","slug":"缓存(cache)相关知识点总结","date":"2018-10-16T13:07:00.000Z","updated":"2019-04-16T13:08:01.508Z","comments":true,"path":"2018/10/16/缓存(cache)相关知识点总结/","link":"","permalink":"http://blog.yancongwen.com/2018/10/16/缓存(cache)相关知识点总结/","excerpt":"","text":"缓存(cache)相关知识点总结Cache-ControlCache-Control: max-age=1000 ,设置的是 1000 s 后缓存失效;在缓存有效期内不再请求数据;一般设置很长很长一段时间,如一年,设置十年;每次版本迭代之后,修改文件名就行了,这样缓存就失效了,会请求新数据,一般不回缓存 html 文件。 Expire设置缓存过期的时间点,超过这个时间点就请求,一般推荐使用 Cache-Control; ETag通过文件的 MD5 值来判断文件是否改变,改变就返回数据,不改变就不返回;不管数据变不变,都要发送请求;任然要发送请求,只是不返回数据而已,所以效率不如 Cache-Control, Cache-Control 直接就不请求数据。 MD5 是什么 缓存与 304 的区别 缓存没有请求。 304 有请求,有响应,但是响应没有第四部分。 参考 一文读懂前端缓存","categories":[],"tags":[{"name":"Javascript","slug":"Javascript","permalink":"http://blog.yancongwen.com/tags/Javascript/"},{"name":"计算机网络","slug":"计算机网络","permalink":"http://blog.yancongwen.com/tags/计算机网络/"}]},{"title":"git提交信息规范化","slug":"git提交信息规范化","date":"2018-10-12T06:37:00.000Z","updated":"2019-03-28T06:16:34.351Z","comments":true,"path":"2018/10/12/git提交信息规范化/","link":"","permalink":"http://blog.yancongwen.com/2018/10/12/git提交信息规范化/","excerpt":"","text":"git commit 信息规范化1、前言  Git 每次提交代码,都要写 Commit message(提交说明),否则就不允许提交。1$ git commit -m "hello world"   上面代码的-m参数,就是用来指定 commit mesage 的。如果一行不够,可以只执行git commit,就会跳出文本编辑器,让你写多行。  git 并没有规定你提交信息的内容和格式,但是,一个好的项目,一定要有一个自己的统一的提交格式,以便于后期回顾代码。目前,社区有多种 Commit message 的写法规范。本文介绍Angular 规范,这是目前使用最广的写法,比较合理和系统化,并且有配套的工具。 2、规范  每次提交,Commit message 都包括三个部分:Header(必需),Body(可选) 和 Footer(可选)。12345<type>(<scope>): <subject>// 空一行<body>// 空一行<footer> Header 部分只有一行,包括三个字段: type(必需) 用于说明 commit 的类别,只允许使用下面7个标识。 feat:新功能(feature) fix:修补bug docs:文档(documentation) style: 格式(不影响代码运行的变动) refactor:重构(即不是新增功能,也不是修改bug的代码变动) test:增加测试 chore:构建过程或辅助工具的变动 scope(可选)用于说明 commit 影响的范围,比如数据层、控制层、视图层等等,视项目不同而不同。 subject(必需)是 commit 目的的简短描述,不超过50个字符。 Body 部分是对本次 commit 的详细描述 Footer 部分只用于两种情况(详细请看阮一峰博客) 不兼容变动 关闭 Issue 3、设置 git commit 模板  如果你只是个人的项目, 或者想尝试一下这样的规范格式, 那么你可以为 git 设置 commit template, 每次 git commit 的时候在 vim 中带出, 以时刻提醒自己提交规范。修改 ~/.gitconfig, 添加:12[commit]template = ~/.gitmessage   新建 ~/.gitmessage 内容可以如下:1234567891011121314# head: (): # - type: feat, fix, docs, style, refactor, test, chore# - scope: can be empty (eg. if the change is a global or difficult to assign to a single component)# - subject: start with verb (such as 'change'), 50-character line## body: 72-character wrapped. This should answer:# * Why was this change necessary?# * How does it address the problem?# * Are there any side effects?## footer: # - Include a link to the ticket, if any.# - BREAKING CHANGE#   按照以上方式设置以后,每次执行 git commit 命令提交时进入 vim 编辑器,就会出现提交规范提示信息。 4、工具:Commitizen  Commitizen 是一个帮助我们撰写合格 Commit message 的工具。它提供一个交互式的命令行工具 commitizen/cz-cli 帮助我们生成符合规范的 commit message。除此之外, 我们还需要为 commitizen 指定一个 Adapter 比如: cz-conventional-changelog (一个符合 Angular团队规范的 preset). 使得 commitizen 按照我们指定的规范帮助我们生成 commit message. 4.1 全局安装和使用 全局安装12npm install -g commitizen cz-conventional-changelogecho '{ "path": "cz-conventional-changelog" }' > ~/.czrc 全局模式下, 需要 ~/.czrc 配置文件, 为 commitizen 指定 Adapter 使用  如果全局安装过 commitizen, 那么在对应的项目中执行 git cz 或者 npm run commit 来替代 git commit 就可以了。执行命令后会进入一个交互式的命令环境,按照提示填写内容就可以了。如图: 提示:如果你是在Windows中使用 Git Bash 执行命令,交互提示符并不工作。你必须通过 winpty git cz 启动这个命令。 4.2 项目级安装和使用1npm install -D commitizen cz-conventional-changelog package.json中配置:12345678"script":{ "commit":"git-cz"},"config":{ "commitizen":{ "path":"node_modules/cz-conventional-changelog" }} 4.3 自定义 Adapter也许 Angular 的那套规范我们不习惯, 那么可以通过指定 Adapter cz-customizable 指定一套符合自己团队的规范。我本人采用的就是使用最广泛的 Angular 规范,关于自定义规范,这里不再重复描述,具体请看这里。 参考 阮一峰:Commit message 和 Change log 编写指南 优雅的提交你的 Git Commit Message https://github.com/commitizen/cz-cli git commit 规范指南","categories":[],"tags":[{"name":"git","slug":"git","permalink":"http://blog.yancongwen.com/tags/git/"}]},{"title":"一个网页的加载过程","slug":"浏览器渲染过程","date":"2018-10-11T13:08:00.000Z","updated":"2019-04-16T13:10:57.895Z","comments":true,"path":"2018/10/11/浏览器渲染过程/","link":"","permalink":"http://blog.yancongwen.com/2018/10/11/浏览器渲染过程/","excerpt":"","text":"一个网页的加载过程 一个经典的面试题:从输入 URL 到页面加载显示完成,这个过程中都发生了什么?对于这个问题,其实也并没有标准答案,简单点,可能几句话就能回答,复杂点,这个过程的每一个细节都可以长篇大论。每一名开发人员,随着其工作年限的增长,对这个过程都会有更深入的理解。作为一个新人,本人也是仅知道皮毛,在查阅各路大神的回答和总结之后,在此记录梳理一番。 首先来看一下大致流程: DNS 解析 建立 TCP 连接(三次握手) 发送 HTTP 请求 服务器处理请求并返回 HTTP 报文 浏览器解析渲染页面 关闭 TCP 连接(四次挥手) 1、DNS 解析DNS 域名解析,即根据域名寻找服务器主机 IP。DNS 解析是一个递归查询的过程: 浏览器 DNS 缓存。当用户通过浏览器访问某域名时,浏览器首先会在自己的缓存中查找是否有该域名对应的 IP 地址。(谷歌浏览器查看自身 DNS 缓存:chrome://net-internals/#dns) 操作系统缓存。当浏览器缓存中无域名对应 IP 则会自动检查用户计算机系统 Hosts 文件 DNS 缓存是否有该域名对应 IP。(windows 中 ipconfig/displaydns 来查看 DNS 缓存内容,ipconfig/flushdns 来清空 DNS 缓存内容,linx 系统缓存主要存在/etc/hosts) 路由器缓存。当浏览器及系统缓存中均无域名对应 IP 则进入路由器缓存中检查,以上三步均为客服端的 DNS 缓存。 ISP(互联网服务提供商)DNS 缓存。当在用户客服端查找不到域名对应 IP 地址,则将进入 ISP DNS 缓存中进行查询。比如你用的是电信的网络,则会进入电信的 DNS 缓存服务器中进行查找。 根域名服务器。当以上均未完成,则进入根服务器进行查询。全球仅有 13 台根域名服务器,1 个主根域名服务器,其余 12 为辅根域名服务器。根域名收到请求后会查看区域文件记录,若无则将其管辖范围内顶级域名(如.com)服务器 IP 告诉本地 DNS 服务器。 顶级域名服务器。顶级域名服务器收到请求后查看区域文件记录,若无则将其管辖范围内主域名服务器的 IP 地址告诉本地 DNS 服务器。 主域名服务器。主域名服务器接受到请求后查询自己的缓存,如果没有则进入下一级域名服务器进行查找,并重复该步骤直至找到正确纪录。 其实真实的互联网世界背后存在成千上百台服务器,大型的网站甚至更多。但是在用户的眼中,它需要的只是处理他的请求,哪台机器处理请求并不重要。DNS 可以返回一个合适的机器的 IP 给用户,例如可以根据每台机器的负载量,该机器离用户地理位置的距离等等,这种过程就是 DNS 负载均衡,又叫做 DNS 重定向。大家耳熟能详的 CDN(Content Delivery Network)就是利用 DNS 的重定向技术,DNS 服务器会返回一个跟用户最接近的点的 IP 地址给用户,CDN 节点的服务器负责响应用户的请求,提供所需的内容。 2、建立 TCP 连接(三次握手)TCP 位于传输层,提供可靠的字节流服务,将大块数据分割成以报文段为单位的数据包进行管理。浏览器根据上一步获取到的 IP 地址向服务器发起 TCP 连接,进行 TCP 三次握手: 客户端首先发送一个带 SYN 标志的数据包给对方(我能和你建立连接吗?); 服务器收到后,返回一个带有 SYN/ACK 标志的数据包以示传达确认信息(当然可以,收到请回复); 最后,客户端再回传一个带 ACK 标志的数据包,完成三次握手,客户端与服务器开始传送数据(收到了)。 3、发起 HTTP 请求HTTP 请求的过程就是构建 HTTP 请求报文,请求报文会被 TCP 协议分割成报文段,然后发送到服务器指定端口。 HTTP 请求报文由两部分组成: 请求头、请求体。请求头又包括请求行、实体头、头部结束标志三部分;其中请求行是请求方法(GET、POST、OPTION、PUT…)、请求地址、协议版本(HTTP/1.0 HTTP/1.1)的声明,实体头是各种首部字段的值;请求体即传送的数据。 详细请查看:图解 HTTP 学习笔记(四):HTTP 报文。 4、服务器处理请求并返回 HTTP 报文服务器对应端口接收到 HTTP 请求报文后,相应的服务端程序开始对请求作出一系列处理,并组织响应数据,通过 HTTP 协议构建响应报文,响应报文同样由两部分组成:响应头、响应体。响应头中包含各种状态码(200、403…)表示响应状态。 5、浏览器解析渲染页面浏览器在收到 HTML、CSS、JS 文件后,它是如何把页面呈现到屏幕上的? 下图对应的就是 WebKit 渲染的过程。 浏览器是一个边解析边渲染的过程。首先浏览器解析 HTML 文件构建 DOM 树,然后解析 CSS 文件构建 渲染树,等到渲染树构建完成后,浏览器开始布局渲染树并将其绘制到屏幕上。这个过程比较复杂,涉及到两个概念: reflow(回流)和 repain(重绘)。DOM 节点中的各个元素都是以盒模型的形式存在,这些都需要浏览器去计算其位置和大小等,这个过程称为 relow;当盒模型的位置、大小以及其他属性,如颜色、字体、等确定下来之后,浏览器便开始绘制内容,这个过程称为 repain。页面在首次加载时必然会经历 reflow 和 repain。reflow 和 repain 过程是非常消耗性能的,尤其是在移动设备上,它会破坏用户体验,有时会造成页面卡顿。所以我们应该尽可能少的减少 reflow 和 repain。 JS 的解析是由浏览器中的 JS 解析引擎完成的。浏览器在解析过程中,如果遇到请求外部资源时,如图像,iconfont,JS 等,浏览器将请求并加载该资源。请求过程是异步的,并不会影响 HTML 文档进行加载,但是当文档加载过程中遇到 JS 文件,HTML 文档会挂起渲染过程,不仅要等到文档中 JS 文件加载完毕还要等待解析执行完毕,才会继续 HTML 的渲染过程。原因是因为 JS 有可能修改 DOM 结构,这就意味着 JS 执行完成前,后续所有资源的下载是没有必要的,这就是 JS 阻塞后续资源下载的根本原因。CSS 文件的加载不影响 JS 文件的加载,但是却影响 JS 文件的执行。JS 代码执行前浏览器必须保证 CSS 文件已经下载并加载完毕。 6、关闭 TCP 连接(四次挥手)通过四次挥手关闭 TCP 连接。 主机向服务器发送一个断开连接的请求(我要走了); 服务器接到请求后发送确认收到请求的信号(知道了); 服务器向主机发送断开通知(我也要走了); 主机接到断开通知后断开连接并反馈一个确认信号(好的),服务器收到确认信号后断开连接; 7、后记上面部分主要介绍了一次完整的请求对应的过程,了解该过程有助于对 Web 开发的整体把控以及优化。如何尽快的加载资源?答案就是能不从网络中加载的资源就不从网络中加载,合理使用缓存,将资源放在浏览器端,这是最快的方式。如果资源必须从网络中加载,则要考虑缩短连接时间,即 DNS 优化部分;减少响应内容大小,即对内容进行压缩。另一方面,如果加载的资源数比较少的话,也可以快速的响应用户。当资源到达浏览器之后,浏览器开始进行解析渲染,浏览器中最耗时的部分就是 reflow,所以围绕这一部分就是考虑如何减少 reflow 的次数。 参考 前端经典面试题: 从输入 URL 到页面加载发生了什么? 浏览器与 DNS 解析过程 DNS 原理及其解析过程 图解 HTTP 学习笔记(一):网络基础 图解 HTTP 学习笔记(四):HTTP 报文 雅虎 34 条军规 从 URL 输入到页面展现到底发生什么 前端必读:浏览器内部工作原理 浏览器发送 http 请求过程分析","categories":[],"tags":[{"name":"总结","slug":"总结","permalink":"http://blog.yancongwen.com/tags/总结/"}]},{"title":"原型与原型链","slug":"原型与原型链","date":"2018-09-18T08:09:00.000Z","updated":"2019-04-16T13:01:06.332Z","comments":true,"path":"2018/09/18/原型与原型链/","link":"","permalink":"http://blog.yancongwen.com/2018/09/18/原型与原型链/","excerpt":"","text":"原型与原型链 大多数编程语言是基于类的语言,而 JS 是一种基于原型继承的语言。 1、为什么会有原型和原型链?  首先,先来看一下为什么会有原型和原型链,原型和原型链能带来什么好处。在面向对象编程中,创建对象的方式有很多种,最最简单的就是工厂模式和构造函数模式,然而,这些方式创建的对象,不能共享属性和方法,每一个对象会重复创建相同的属性和方法,造成内存资源的浪费。最简单的,每个变量都会有一个 toString 方法,那么是每个变量都有一个自己的方法吗,显然不是的。原型的作用就是帮助我们存放公用的属性和方法。 2、理解原型对象  我们创建的每一个函数都有一个prototype属性,该属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法,这个对象就是该函数的原型对象。原型对象自动获得一个constructor属性,这个属性又指向了函数本身。 3、理解原型链  简单来讲,构造函数、原型、实例有如下关系:每一个构造函数都有一个原型对象prototype,原型对象都包含一个指向构造函数的指针constructor,而实例都包含一个指向原型对象的内部指针__proto__。  基于以上关系,我们让一个构造函数的原型等于另一个类型的实例,此时,该原型对象就包含了一个指向另一个原型的指针,相应的,另一个原型中也包含着一个指向另一个构造函数的指针。加入另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是原型链的基本概念。\\一个例子: 123456789101112131415161718192021function Person(name, age) { this.name = name this.age = age}Person.prototype.alertName = function() { alert(this, name)}var p = new Person('zhangsan', 20)p.alertAge = function() { alert(this.age)}p.alertAge() // 自身属性p.alertName() // p._proto_(Person.prototype)中找p.toString() // p._proto_._proto_(Object.prototype中)中找//循环对象自身的属性for (var item in p) { //高级浏览器已经屏蔽了来自原型的属性,但是这个为了保证兼容性和健壮性建议还是添加这个筛选 if (p.hasOwnProperty(item)) { console.log(item) }} 4、总结 1、 prototype是函数的原型对象,它是一个对象,这个对象又包含了一个constructor属性指向了该函数(prototype是函数的属性,而且我们一般讨论的是构造函数); 2、 对象的__proto__指向它构造函数的prototype(__proto__是实例的属性); 3、 所有的构造函数的原型链最后都会指向Object构造函数的原型,即可以理解Object构造函数的原型是所有原型链的最底层,即Object.prototype.__proto__===null; 4、 要寻找一个函数的prototype,就先看它是从谁继承来的; 5、 要寻找一个对象的__proto__,就先看它是谁的实例,找它的构造函数; 5、举例记住上一小节中的几句话,我们再来看下面的题目,就比较简单了。以下等式恒成立: 第一组: (几个原生对象的原型关系) 123456789Object.__proto__ === Function.prototype // 将Object视为Function的实例Object.prototype.__proto__ === null // 将Object视为构造函数Function.__proto__ === Function.prototype // 将Function视为Function的实例Function.prototype.__proto__ === Object.prototype //将Function视为构造函数,它的原型是Object的一个实例Array.__proto__ === Function.prototype // 将Array视为Function的实例Array.prototype.__proto__ === Object.prototype //将Array视为构造函数,它的原型是Object的一个实例//类推 Boolean、 String、 Number...... 第二组: 123456789101112131415var obj = {}obj.__proto__ === Object.prototype // obj是Object的实例obj.prototype === undefined // obj不是函数,所以没有原型对象属性var arr = []arr.__proto__ === Array.prototype // arr是Array的实例var fn = function() {}fn.__proto__ === Function.prototype // 将fn视为Function的实例fn.prototype.__proto__ === Object.prototype // 将fn视为函数,它的原型是Object的一个实例function Test() {}var test = new Test()Test.prototype.__proto__ === Object.prototype // 将Test视为构造函数,它的原型是Object的一个实例test.__proto__ === Test.prototype // test是Test的实例 参考 Javascript 高级程序设计(第 3 版)>) 三句话给你解释清楚原型和原型链","categories":[],"tags":[{"name":"Javascript","slug":"Javascript","permalink":"http://blog.yancongwen.com/tags/Javascript/"}]},{"title":"JS数据类型转换","slug":"JS数据类型转换","date":"2018-09-18T08:07:00.000Z","updated":"2019-04-16T12:59:37.926Z","comments":true,"path":"2018/09/18/JS数据类型转换/","link":"","permalink":"http://blog.yancongwen.com/2018/09/18/JS数据类型转换/","excerpt":"","text":"JS 数据类型转换任意类型转字符串 String(x) x.toString() x + ‘’ 任意类型转布尔 六个 falsy 值:false、0、NaN、null、undefined、''(其实还有一个document.all),除了以上六个值被转为false外,其他值都转为 true; 所有的对象都被转换为 true(数组、函数、空数组、空对象) Boolean(x) !!x 任意类型转数字 Number(x) parseInt(x, 10) parseFloat(x) x - 0 +x","categories":[],"tags":[{"name":"Javascript","slug":"Javascript","permalink":"http://blog.yancongwen.com/tags/Javascript/"}]},{"title":"JS里的数据类型","slug":"JS里的数据类型","date":"2018-09-17T13:27:00.000Z","updated":"2019-03-28T06:16:34.335Z","comments":true,"path":"2018/09/17/JS里的数据类型/","link":"","permalink":"http://blog.yancongwen.com/2018/09/17/JS里的数据类型/","excerpt":"","text":"JS中的数据类型七种:number、string、 boolean、 undefined、 null、 object、 symbol没有 array 噢 number 整数和小数:1、 1.1、 .1 科学记数法:1.23e2 二进制:0b11 八进制:011(后来 ES5 添加了 0o11 语法) 十六进制:0x11 string 空字符串: '' 多行字符串: 12 var s = `1234567890` // 含回车符号 Base64 Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法;Base64编码是从二进制到字符的过程,可用于在HTTP环境下传递较长的标识信息。 编码方法:window.btoa("test");//"dGVzdA==" 解码方法:window.atob("dGVzdA==");//"test" booleantrue or false undefined 和 null都可以表示“没有”,含义非常相似 (规范)如果一个变量没有被赋值,那么这个变量的值就是 undefiend (习俗)如果你想表示一个还没赋值的对象,就用 null。如果你想表示一个还没赋值的字符串/数字/布尔/symbol,就用 undefined(但是实际上你直接 var xxx 一下就行了,不用写 var xxx = undefined) null是一个表示“空”的对象,转为数值时为0;undefined是一个表示”此处无定义”的原始值,转为数值时为NaN undefined == null //true object object 就是几种基本类型(无序地)组合在一起,可以无限嵌套 object 的 key 一律是字符串,不存在其他类型的 key(ES6中也可以是 Symbol类型的) object[''] 是合法的 object['key'] 可以写作 object.key object.key 与 object[key] 不同 delete object['key'] 'key' in object 用于判断是否存在这个 key symbolES6中新增的一个类型,它是 JavaScript 语言的第七种数据类型,表示独一无二的值。Symbol 值通过Symbol函数生成。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。 typeof 操作符 类型 string number boolean symbol undefined null object function typeof的值 ‘string’ ‘number’ ‘boolean’ ‘symbol’ ‘undefined’ ‘object’ ‘object’ ‘function’ 1、注意 function 并不是一个类型;2、null的类型是object,这是由于历史原因造成的。1995年的 JavaScript 语言第一版,只设计了五种数据类型(对象、整数、浮点数、字符串和布尔值),没考虑null,只把它当作object的一种特殊值。后来null独立出来,作为一种单独的数据类型,为了兼容以前的代码,typeof null返回object就没法改变了。","categories":[],"tags":[{"name":"Javascript","slug":"Javascript","permalink":"http://blog.yancongwen.com/tags/Javascript/"}]},{"title":"JavaScript 运行机制及 Event Loop","slug":"JavaScript-运行机制及-Event-Loop","date":"2018-09-11T12:54:00.000Z","updated":"2019-04-16T12:55:43.853Z","comments":true,"path":"2018/09/11/JavaScript-运行机制及-Event-Loop/","link":"","permalink":"http://blog.yancongwen.com/2018/09/11/JavaScript-运行机制及-Event-Loop/","excerpt":"","text":"JavaScript 运行机制及 Event Loop 本文主要摘自阮一峰老师文章《JavaScript 运行机制详解:再谈 Event Loop》 1. JavaScript 是单线程JavaScript 语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。为了利用多核 CPU 的计算能力,HTML5 提出 WebWorker 标准,允许 JavaScript 脚本创建多个线程,但是子线程完全受主线程控制,且不得操作 DOM 。所以,这个新标准并没有改变 JavaScript 单线程的本质。 2. 任务队列单线程:所有任务需要排队,前一个任务结束,才会执行后一个任务。如果排队是因为计算量大,CPU 忙不过来,倒也算了,但是很多时候 CPU 是闲着的,因为 IO 设备(输入输出设备)很慢(比如 Ajax 操作从网络读取数据),不得不等着结果出来,再往下执行。于是,所有任务可以分成两种,一种是 同步任务(synchronous),另一种是 异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入“任务队列”(task queue)的任务,只有“任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。具体来说,异步执行的运行机制如下。(同步执行也是如此,因为它可以被视为没有异步任务的异步执行。) 1、所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。 2、主线程之外,还存在一个”任务队列”(task queue)。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。 3、一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。 4、主线程不断重复上面的第三步。只要主线程空了,就会去读取”任务队列”,这就是 JavaScript 的运行机制。这个过程会不断重复。 3. 事件和回调函数 “任务队列”是一个事件的队列,IO 设备完成一项任务,就在”任务队列”中添加一个事件,表示相关的异步任务可以进入”执行栈”了。主线程读取”任务队列”,就是读取里面有哪些事件。 “任务队列”中的事件,除了 IO 设备的事件以外,还包括一些用户产生的事件(比如鼠标点击、页面滚动等等)。只要指定过回调函数,这些事件发生时就会进入”任务队列”,等待主线程读取。 所谓”回调函数”(callback),就是那些会被主线程挂起来的代码,回调函数放在任务队列中。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。 “任务队列”是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上是自动的,只要执行栈一清空,”任务队列”上第一位的事件就自动进入主线程。但是,由于存在后文提到的”定时器”功能,主线程首先要检查一下执行时间,某些事件只有到了规定的时间,才能返回主线程。 如果执行栈没有执行完的话,是永远不会触发 callback 的,任务队列也不会被执行。 4. Event Loop主线程从”任务队列”中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为 Event Loop(事件循环)。为了更好地理解 Event Loop,请看下图: 上图中,主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部 API,它们在”任务队列”中加入各种事件(click,load,done)。只要栈中的代码执行完毕,主线程就会去读取”任务队列”,依次执行那些事件所对应的回调函数。\\执行栈中的代码(同步任务),总是在读取”任务队列”(异步任务)之前执行。请看下面这个例子。 12345var req = new XMLHttpRequest()req.open('GET', url)req.onload = function a() {}req.onerror = function b() {}req.send() 上面代码中的 req.send 方法是 Ajax 操作向服务器发送数据,它是一个异步任务,意味着只有当前脚本的所有代码执行完,系统才会去读取”任务队列”。所以,它与下面的写法等价。 12345var req = new XMLHttpRequest()req.open('GET', url)req.send()req.onload = function a() {}req.onerror = function b() {} 也就是说,指定回调函数的部分(onload 和 onerror),在 send()方法的前面或后面无关紧要,因为它们属于执行栈的一部分,系统总是执行完它们,才会去读取”任务队列”。function a,b 就是存放在任务队列中,当事件触发,且执行栈为空,就会去任务队列中读取 a,b 执行。 5. 定时器除了放置异步任务的事件,”任务队列”还可以放置定时事件,即指定某些代码在多少时间之后执行。定时器功能主要由 setTimeout 和 setInterval 这两个函数来完成,它们的内部运行机制完全一样,区别在于前者指定的代码是一次性执行,后者则为反复执行。以下主要讨论setTimeout。 12345console.log(1)setTimeout(function() { console.log(2)}, 1000)console.log(3) 上面代码的执行结果是 1,3,2,因为setTimeout将第二行推迟到 1000 毫秒之后执行。如果将setTimeout的第二个参数设为 0,就表示当前代码执行完(执行栈清空)以后,立即执行(0 毫秒间隔)指定的回调函数。 1234setTimeout(function() { console.log(1)}, 0)console.log(2) 上面代码的执行结果总是 2,1,因为只有在执行完第二行以后,系统才会去执行”任务队列”中的回调函数。\\需要注意的是,setTimeout只是将事件插入了”任务队列”,必须等到当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是当前代码耗时很长,有可能要等很久,所以并没有办法保证,回调函数一定会在setTimeout指定的时间执行。 6. 一道题目123456789101112setTimeout(() => console.log(1))new Promise((resolve, reject) => { console.log(2) for (var i = 0; i < 10000; i++) { i == 9999 && resolve() } console.log(3)}).then(() => { console.log(4)})console.log(5)// 为什么输出结果为 2 3 5 4 1,而不是 2 3 5 1 4 这个题目我纠结的一点是,为什么 Promise 的异步任务要比 setTimeout 的异步先执行,仅仅靠以上知识点是无法回答这个问题的。原来异步任务之间也是存在差异的,可分为微任务和宏任务。 macro-task(宏任务):包括整体 script 代码,setInterval,setTimeout micro-task(微任务):promise ,process.nexttrick(nodejs 的内容) 不同类型的任务会进入对应的 event queue,比如 setInterval,setTimeout 会进入相同的 event queue。事件循环的顺序决定 js 代码的执行顺序。进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。分析一下以上代码中的代码执行顺序: (1) 这段代码作为宏任务进入主线程,先遇到 settimeout,那么将其回调函数分发到宏任务的 event queue 上; (2) 接下来遇到 promise,new promise 立即执行,then 函数分发到微任务的 event queue 中; (3) 然后,整体 script 代码作为第一个宏任务执行结束,看看有哪些微任务,我们发现 then 在微任务 event queue 里,则执行; (4) 第一轮循环事件结束,开始第二轮循环,当然是从宏任务的 event queue 开始,我们发现了宏任务 event queue 中的 settimeout 对应的回调函数,则立即执行 7. 总结 Javascript 是单线程的,所有的同步任务都会在主线程中执行; 当主线程中的任务,都执行完之后,系统会 “依次” 读取任务队列里的事件。与之相对应的异步任务进入主线程,开始执行; 异步任务之间,会存在差异,所以它们执行的优先级也会有区别。大致分为微任务(micro task,如:Promise、MutaionObserver 等)和宏任务(macro task,如:setTimeout、setInterval、I/O 等); Promise 执行器中的代码会被同步调用,但是回调是基于微任务的; 宏任务的优先级高于微任务; 每一个宏任务执行完毕都必须将当前的微任务队列清空; 第一个 script 标签的代码是第一个宏任务; 主线程会不断重复上面的步骤,直到执行完所有任务; 参考: JavaScript 运行机制详解:再谈 Event Loop 从 setTimeout 谈 JavaScript 运行机制 JavaScript 是如何工作的:引擎,运行时和调用堆栈的概述! js 基础进阶–promise 和 setTimeout 执行顺序的问题","categories":[],"tags":[{"name":"Javascript","slug":"Javascript","permalink":"http://blog.yancongwen.com/tags/Javascript/"}]},{"title":"在WSL中开发Node.js","slug":"在WSL中开发Node-js","date":"2018-08-28T09:19:00.000Z","updated":"2019-03-28T06:16:34.351Z","comments":true,"path":"2018/08/28/在WSL中开发Node-js/","link":"","permalink":"http://blog.yancongwen.com/2018/08/28/在WSL中开发Node-js/","excerpt":"","text":"WSL 介绍 WSL(Windows Subsystem for Linux) 适用于Linux的Windows子系统,有了它,不要再安装臃肿的Vmware和VirtualBox等虚机机系统,就可以直接在Windows上体验原生的Linux应用了,甚至还有图形界面! WSL是Win10提供的功能,默认关闭,需要去功能管理中开启WSL功能并重启计算机;然后在 Microsoft Store 中搜索 linux 或者 wsl ,会出现几个版本的linux系统供选择,直接安装想要的linux 发型版本即可。我这里安装的是 Ubuntu 18.04.1 LTS,大小约600MB,安装是一键安装的,安装完成直接点击图标即进入linux命令窗口。 WSL 安装的Linux子系统,拥有独立的目录系统,其目录在C盘中:C:\\Users\\username\\AppData\\Local\\Packages\\CanonicalGroupLimited.UbuntuonWindows_79rhkp1fndgsc\\LocalState\\rootfs 。虽然我们能找到这个目录,但是请不要在Windos中去操作这里的文件! 个人理解,WSL安装的linux系统,其实仅仅是给了我们一个linux环境,其任然和Winddows系统共享计算机硬件,共享很多东西,比如磁盘、网络端口,linux中的localhost和windows中的localhost一致的。 基础环境安装安装的Ubuntu自带git、node,但是版本较低,需要升级。具体方法请自行百度。 git git 需要做一些简单的配置,想要以SSH方式连接远程仓库还要生成SSH Key并将公钥配置到远程仓库。这里要注意的是用户权限问题,在linux中不同用户创建的SSH Key并不相同。请看另一片文章。 node yarn npm 开发模式开始,我一直疑虑在WSL中应该以怎样的方式进行node开发。使用vim写代码对于我这种菜鸟还是太费力了,难道我还要在linux中再安装一下图形界面,然后再安装VSCode?不不不。还有一种方式,就是直接使用Windows中的VSCode打开Linux目录下的node项目,也就是直接打开上文提到的藏的很深的那个C盘目录下的项目,但是这种方式也很不优雅,一方面,我们使用window的编辑器去编辑改变linux中的文件,另一方面,我们要在linux中去执行node、yarn、git等命令,并且不能使用VScode去执行命令,不能使用VSCode去调试程序。 建立软连接如上文提及,我们使用VSCode去编辑Linux目录中项目,这个目录藏在C盘很深的位置,不仅占用C盘空间,还不方便管理,可以使用建立软连接的方式来解决。首选我们来看一下什么是软连接、硬连接: 软连接: 也叫符号连接(Symbolic Link),软链接文件类似于Windows的快捷方式,它实际上是一个特殊的文件。在符号连接中,文件实际上是一个文本文件,其中包含的文本就是连接的另一文件的位置。 硬连接: 硬连接指通过索引节点来进行连接。在Linux的文件系统中,保存在磁盘分区中的文件不管是什么类型都给它分配一个编号,称为索引节点号(Inode Index)。在Linux中,多个文件名指向同一索引节点是存在的。一般这种连接就是硬连接。硬连接的作用是允许一个文件拥有多个有效路径名,这样用户就可以建立硬连接到重要文件,以防止“误删”的功能。其原因如上所述,因为对应该目录的索引节点有一个以上的连接。只删除一个连接并不影响索引节点本身和其它的连接,只有当最后一个连接被删除后,文件的数据块及目录的连接才会被释放。也就是说,文件真正删除的条件是与之相关的所有硬连接文件均被删除。 我们可以以建立软连接的方式,将F盘的开发目录连接到linux的目录下,这样既可以方便管理开发项目,也可以节省C盘空间占用。 Linux ln 命令创建连接 命令形式如下: ln (选项) [源文件] [文件连接],创建软连接:ln -s \\mnt\\f\\wsl-dev \\home\\yancongwen\\develop 。以上命令就创建了一个软连接,将F盘的目录 wsl-dev 连接到了linux用户目录下的develop,这样我们就可以从linux中的develop访问F盘的开发目录,在linux中,develop目录就形同是一个文件夹,cd 命令可以进入访问。关于ln 命令的更多说明可以看这里。 这里也记录一下我踩过的坑: 1、ln 命令前一个参数是真实的数据存储所在,后一个参数是连接文件,不要搞反了;刚开始我就是在F盘创建的连接,而目录在linux目录下,显然是不合理的,也没什么用处; 2、源文件目录和连接文件都一定要采用绝对路径,不要采用~\\develop这样的相对路径;我一开始没注意到 ~\\就是相对路径,所以创建的连接文件一直进不去,把我郁闷坏了。 windows mklink 命令创建连接windows中同样支持创建软连接和硬连接,使用的是mklink命令。一开始我使用的就是mklink创建的软连接:mklink /D C:\\Users\\username\\AppData\\Local\\Packages\\CanonicalGroupLimited.UbuntuonWindows_79rhkp1fndgsc\\LocalState\\rootfs\\home\\yancongwen\\develop F:\\wsl-dev (注意,这里前一个路径是连接,后一个连接是源文件),然而,才linux系统中,根本就识别不了这个连接,cd 命令进不去。所以不要使用windows的连接命令去连接linux目录。 【参考】 Win+Linux单系统解决方案——WSL(入门篇) 理解 Linux 的硬链接与软链接 linux 创建连接命令 ln -s 软链接","categories":[],"tags":[{"name":"linux","slug":"linux","permalink":"http://blog.yancongwen.com/tags/linux/"}]},{"title":"Error: Permission denied (publickey)","slug":"Error-Permission-denied-publickey","date":"2018-08-23T11:37:00.000Z","updated":"2019-03-28T06:16:34.335Z","comments":true,"path":"2018/08/23/Error-Permission-denied-publickey/","link":"","permalink":"http://blog.yancongwen.com/2018/08/23/Error-Permission-denied-publickey/","excerpt":"","text":"Git Error: Permission denied (publickey) 最近接触到一个nodejs后端服务项目,项目需要运行在linux环境中,经leader推荐,决定使用Win10的WSL功能,安装了Ubuntu 18.04。在Ubuntu中配置git中遇到了以下问题。 问题描述Ubuntu中自带了git,但是使用之前需要做一些配置。首先要做的事情就是设置你的名字和邮件地址,此外,使用ssh连接github或者gitlab这些远程仓库需要配置公钥。我按照如下命令生成了SSH Key,生成的密钥在 ~/.ssh 文件夹下,将公钥复制到github中即可。1ssh-keygen -t rsa -b 4096 -C "your_email@example.com" 然而,当我执行 sudo git clone git@gitlab.com:***.git 时,就失败了,显示如下信息: git@gitlab.com: Permission denied (publickey). fatal: Could not read from remote repository. 问题解决上面的信息显示应该是公钥出了问题。反复确认,我的公钥确实没有配置错误啊,而且执行ssh git@gitlab.com的时候确实显示SSH链接已经建立了呀。然而,当我执行sudo ssh git@gitlab.com的时候,同样抛出了Permission denied (publickey),说明可能和sudo 有关。反反复复查资料,发现也有其他人出现了这个问题,但是按照里面的方法我并没有解决问题。最后,还是在github的帮助文档找到了问题所在。原来,在git命令前加上sudo权限后,使用的密匙就应该是 sudo权限下生成的 SSH Key,这个SSH Key 在\\root\\.ssh目录下,而我们配置的是~/.ssh 目录下的SSH Key。所以我所做的,就是使用sudo权限重新生成了SSH Key,然后将\\root\\.ssh目录下的公钥配置到github中,这样再执行sudo git这样的命令就OK了 参考 Generating a new SSH key and adding it to the ssh-agent Error: Permission denied (publickey)","categories":[],"tags":[{"name":"git","slug":"git","permalink":"http://blog.yancongwen.com/tags/git/"},{"name":"linux","slug":"linux","permalink":"http://blog.yancongwen.com/tags/linux/"}]},{"title":"数据结构基础","slug":"数据结构基础","date":"2018-07-29T11:46:00.000Z","updated":"2019-03-28T06:16:34.351Z","comments":true,"path":"2018/07/29/数据结构基础/","link":"","permalink":"http://blog.yancongwen.com/2018/07/29/数据结构基础/","excerpt":"","text":"哈希表(Hash Table) 计数排序中的桶(复杂度 O(n+max),比快排还快 桶排序 与计数排序的区别 基数排序 与计数排序的区别 队列(Queue) 先进先出 可以用数组实现 举例:排队 栈(Stack) 先进后出 可以用数组实现 举例:盗梦空间 链表(Linked List) 数组无法直接删除中间的一项,链表可以 用哈希(JS里面用对象表示哈希)实现链表 head、node 概念 树(tree) 举例:层级结构、DOM 概念:层数、深度、节点个数 二叉树 满二叉树 完全二叉树 完全二叉树和满二叉树可以用数组实现 其他树可以用哈希(对象)实现 操作:增删改查 堆排序用到了 tree 其他:B树、红黑树、AVL树","categories":[],"tags":[{"name":"计算机基础","slug":"计算机基础","permalink":"http://blog.yancongwen.com/tags/计算机基础/"},{"name":"数据结构","slug":"数据结构","permalink":"http://blog.yancongwen.com/tags/数据结构/"}]},{"title":"CSS总结","slug":"CSS技巧","date":"2018-07-28T07:41:00.000Z","updated":"2019-04-17T12:10:54.441Z","comments":true,"path":"2018/07/28/CSS技巧/","link":"","permalink":"http://blog.yancongwen.com/2018/07/28/CSS技巧/","excerpt":"","text":"本文记录一些CSS中的一些常识和技巧CSS 的学习不需要死记硬背,就是经验和熟练度,要会用工具。比如,要会搜索 “css generator” 一、知识点1、文档流 行内元素 块级元素 2、盒模型盒模型的组成大家肯定都懂,由里向外content,padding,border,margin。盒模型是有两种标准的,一个是标准模型,一个是IE模型。 标准盒模型元素宽高 = content 宽高 IE 盒模型元素宽高 = content宽高 + padding + border 12345/* 标准模型,默认值 */box-sizing:content-box; /*IE模型,推荐*/box-sizing:border-box; 3、定位 position static默认,没有定位,元素出现在正常的流中(忽略 top, bottom, left, right 或者 z-index 声明)。 relative相对定位,元素先放置在未添加定位时的位置,再在不改变页面布局的前提下调整元素位置(因此会在此元素原始位置留下空白) absolute绝对定位,相对于 static 定位以外的第一个父元素进行定位。 fixed固定定位,相对于浏览器窗口进行定位。 sticky粘性定位,基于用户滚动的位置,它的行为就像 position:relative; 而当页面滚动超出目标区域时,它的表现就像 position:fixed;,它会固定在目标位置。 二、技巧 调试技巧:给元素加border; 浮动,给父元素清除浮动; .clearfix{ content:’’; display:block; clear:both; } 不要上来就给div加高度和宽度,要让内容撑开它; div 的高度是由其文档流的高度决定的 height 和 width 是 bug 的来源 脱离文档流 三、常用工具网站 CSS技巧大全 CSS Tricks CSS 代码生成器 CSS3 Generator CSS 动画生成器 Animate.css 特殊符号大全 浏览器测试 BROWSERHACKS 获取渐变色 Ultimate CSS Gradient Generator 设计社区 dribbble","categories":[],"tags":[{"name":"CSS","slug":"CSS","permalink":"http://blog.yancongwen.com/tags/CSS/"}]},{"title":"命令行技巧","slug":"命令行技巧","date":"2018-07-22T12:36:00.000Z","updated":"2019-03-28T06:16:34.351Z","comments":true,"path":"2018/07/22/命令行技巧/","link":"","permalink":"http://blog.yancongwen.com/2018/07/22/命令行技巧/","excerpt":"","text":"技巧一:~/.bashrc自动运行1、首先 touch ~/.bashrc 创建一下这个文件2、start ~/.bashrc选用编辑器编辑这个文件,内容为 echo 'Hi'3、你也可以用命令行编辑文件 echo "echo 'hi'" >> ~/.bashrc4、关闭退出 Git Bash,然后打开 Git Bash,是不是看到了 Hi,这说明每次进入 Git Bash,就会优先运行 ~/.bashrc里面的命令5、重新编辑~/.bashrc,内容改为cd ~/Desktop,重启 Git Bash,有没有发现默认就进入桌面目录了?你可以用 ~/.bashrc 在进入 Git Bash 前执行任何命令,十分方便。 alias 别名1、 在 ~/.bashrc 里新增一行 alias f="echo 'frank is awesome'",等于号两边不能有空格,你最好一个字都不要错。2、 运行 source ~/.bashrc,作用是执行 ~/.bashrc3、 运行 f,就会看到 frank is awesome4、 也就是说,现在 f 就是 echo 'frank is awesome' 的缩写了,利用这个技巧,我们可以把很多常见的命令缩写一下,比如1234567alias la='ls -a'alias ll='ls -l'alias gst='git status -sb'alias ga='git add'alias ga.='git add .'alias gc='git commit'alias gc.='git commit .' 保存退出,然后运行 source ~/.bashrc5、这样一来,你的 Git 操作就会简单很多:12345ga 1.txtga .gc 1.txtgc.gst","categories":[],"tags":[]},{"title":"Shadowsocks配置","slug":"shadowsocks","date":"2018-04-21T17:27:00.000Z","updated":"2019-03-28T06:16:34.351Z","comments":true,"path":"2018/04/22/shadowsocks/","link":"","permalink":"http://blog.yancongwen.com/2018/04/22/shadowsocks/","excerpt":"","text":"记录一下Shadowsocks的配置过程 1. 通过SSH连接主机2. 安装Shadowsocks1234# apt-get update // 更新源中包列表# apt-get install python-pip // 安装pip# pip install --upgrade pip // 更新pip# pip install shadowsocks // 安装Shadowsocks 3. 配置Shadowsocks 编辑配置文件 1# vi /etc/shadowsocks.json 在文件中输入以下内容,保存 1234567891011{ "server":"0.0.0.0", "local_port":1080, "timeout":600, "method":"aes-256-cfb", "port-password":{ // 设置端口号和对应的密码,自定义即可 "9001":"666666666", "9002":"666666666" }} 配置完成以上内容,就可以运行ss了 1# ssserver -c /etc/shadowsocks.json -d start 4. 设置开机启动 编辑以下文件 1# vi /etc/rc.local 在文件中添加以下内容保存 1ssserver -c /etc/shadowsocks.json -d start","categories":[],"tags":[{"name":"工具","slug":"工具","permalink":"http://blog.yancongwen.com/tags/工具/"}]},{"title":"Chorme插件推荐","slug":"chorme-plugs","date":"2018-04-21T03:49:00.000Z","updated":"2019-03-28T06:16:34.335Z","comments":true,"path":"2018/04/21/chorme-plugs/","link":"","permalink":"http://blog.yancongwen.com/2018/04/21/chorme-plugs/","excerpt":"","text":"本文简单汇总下本人常用的 Chorme 插件,主要是 Web 前端开发中常用开发利器。 1. 日常使用 Proxy SwitchyOmega翻\\墙必备,控制代理模式,帮助我们合适高效翻\\墙,可以自定义翻\\墙规则 谷歌访问助手没有 VPN、Shadowsocks 等翻\\墙账号时可以暂时使用这个工具,但是只能满足部分需求,谷歌是可以的,youtube、twitter 这些就不行 Adblock Plus广告屏蔽插件 Google学术搜索查论文 CNKI E-Study配合 CNKI E-Study客户端使用,方便导入论文 OneTab当打开的标签页太多时,不仅自己不爽,还占电脑内存,此工具可以一键杀死谷歌浏览器中所有标签页,并将杀死的标签页保留,你可以随时恢复 Save to Pocket轻松保存文章、视频方便以后查看 有道词典Chrome划词插件网页鼠标划词翻译 Marinara番茄工作法助理 极简图床我目前使用的图床工具,配合七牛云使用,可以很方便地复制、拖拽、采集页面的图片到七牛云存储 新浪微博图床使用微博作为图床,本人之前使用的图床,偶尔会出现图片丢失的情况,现在已经全部转移到七牛云存储(配合极简图床) 2. Web 开发 Allow-Control-Allow-Origin: *该插件可谓是前端开发利器,帮助我们解决跨域问题。该插件实现跨域的机制是利用Chrome浏览器开发接口对请求头响应头做了修改。该插件在每次发起请求时,在请求头中添加了请求头 origin:http://evil.com/。详细信息可以参考这里 Vue.js devtoolsVue 开发必备 JSON ViewerJson 格式化工具 LiveReload配合 Sublime Text3 中 LiveReload 插件一起使用,修改代码后会自动刷新浏览器页面 Octotree在 GitHub 网页展示文件结构 Tampermonkey浏览器脚本管理工具,非常强大!其中有非常多开源脚本,脚本功能五花八门,只有你想不到的,没有它做不到,有兴趣可以试一下。 PostmanHTTP 请求工具,可以测试开发中后台接口 ng-inspect for AngularJSAngularJS 开发工具 AngularJS BatarangAngularJS 开发工具","categories":[],"tags":[{"name":"工具","slug":"工具","permalink":"http://blog.yancongwen.com/tags/工具/"}]},{"title":"常见排序算法","slug":"算法初级","date":"2018-04-20T07:29:00.000Z","updated":"2019-03-28T06:16:34.351Z","comments":true,"path":"2018/04/20/算法初级/","link":"","permalink":"http://blog.yancongwen.com/2018/04/20/算法初级/","excerpt":"","text":"排序是开发中十分常见且核心的操作,虽说实际项目中很小几率会需要我们手动实现,但是了解这些精妙的思想对我们还是大有裨益的。本文简单总结下最基础的几类算法。这里首先推荐一个数据结构和算法动态可视化网站:https://visualgo.net/zh 0. 概述 十种常见排序算法可以分为两大类: 非线性时间比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此称为非线性时间比较类排序。 线性时间非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此称为线性时间非比较类排序。 算法复杂度 相关概念 时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。 空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。 稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。 不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。 1. 冒泡排序(Bubble Sort) 思想 对相邻的元素进行两两比较,顺序相反则进行交换,这样,每一趟会将最小或最大的元素“浮”到顶端,最终达到完全有序。 在冒泡排序的过程中,如果某一趟执行完毕,没有做任何一次交换操作,比如数组[5,4,1,2,3],执行了两次冒泡之后变为[1,2,3,4,5]。此时,再执行第三次循环后,一次交换都没有做,这就说明剩下的序列已经是有序的,排序提前完成。 算法分析 时间复杂度:Ο(n^2) 空间复杂度:Ο(1) JS实现 123456789101112131415161718192021222324function bubbleSort(arr) { var len = arr.length; var i, j, temp, bSwap; for(i=0; i<len-1; i++){ bSwap = false; for(j=0; j<len-i-1; j++){ if(arr[j]>arr[j+1]){ temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; bSwap = true; } } // 用于判断此轮循环有没有做交换,如果没有交换,说明数组已经是有序的了,排序完成 if (!bSwap){ break; } } return arr;}/* 交换两个数值的简便方式,接下来的代码就采用此方式: [a,b] = [b,a]; */ 2. 快速排序(Quick Sort) 思想 通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。 快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下: 1、从数列中挑出一个元素,称为 “基准”; 2、重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置,这个称为分区操作; 3、对每个分区不断重复第一步和第二步,直到所有子集只剩下一个元素为止。 3. 插入排序4. 希尔排序5. 选择排序6. 归并排序7. 堆排序8. 计数排序(Count Sort) 简介 计数排序不是基于比较的排序算法。它的优势在于在对一定范围内的整数排序时,快于任何比较排序算法。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。 思想 1、找出待排序的数组中最大和最小的元素; 2、统计数组中每个值为i的元素出现的次数,存入数组C的第i项; 3、对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加); 4、反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。 算法分析 时间复杂度:Ο(n+k),k为待排序数的范围,n为数组长度 空间复杂度:Ο(k) 稳定性:稳定 要求:数值较小的整数数组。计数排序是一种以空间换时间的排序算法,并且只适用于待排序列中所有的数较为集中时。当数组较为分散时,比如一组序列中的数据为0 1 2 3 4 999就得开辟1000个辅助空间,就很不划算。 JS实现 1234567891011121314151617181920212223242526272829function countingSort(arr){ var len = arr.length, Result = [], Count = [], min = max = arr[0]; console.time('countingSort waste time:'); /*查找最大最小值,并将arr数置入Count数组中,统计出现次数*/ for(var i = 0;i<len;i++){ Count[arr[i]] = Count[arr[i]] ? Count[arr[i]] + 1 : 1; min = min <= arr[i] ? min : arr[i]; max = max >= arr[i] ? max : arr[i]; } console.log(Count) /*从最小值->最大值,将计数逐项相加*/ for(var j = min;j<max;j++){ Count[j+1] = (Count[j+1]||0)+(Count[j]||0); } /*Count中,下标为arr数值,数据为arr数值出现次数;反向填充数据进入Result数据*/ for(var k = len - 1;k>=0;k--){ /*Result[位置] = arr数据*/ Result[Count[arr[k]] - 1] = arr[k]; /*减少Count数组中保存的计数*/ Count[arr[k]]--; /*显示Result数组每一步详情*/ console.log(Result); } console.timeEnd(\"countingSort waste time:\"); return Result;}","categories":[{"name":"技术","slug":"技术","permalink":"http://blog.yancongwen.com/categories/技术/"}],"tags":[{"name":"计算机基础","slug":"计算机基础","permalink":"http://blog.yancongwen.com/tags/计算机基础/"},{"name":"算法","slug":"算法","permalink":"http://blog.yancongwen.com/tags/算法/"}]},{"title":"Centering in CSS","slug":"css-centering","date":"2018-04-18T17:29:00.000Z","updated":"2019-03-28T06:16:34.335Z","comments":true,"path":"2018/04/19/css-centering/","link":"","permalink":"http://blog.yancongwen.com/2018/04/19/css-centering/","excerpt":"","text":"本文翻译自 CSS-Tricks 中的一篇文章—— Centering in CSS: A Complete Guide,CSS居中完全指南,其中有部分删改。   Centering things in CSS is the poster child of CSS complaining. Why does it have to be so hard? They jeer. I think the issue isn’t that it’s difficult to do, but in that there so many different ways of doing it, depending on the situation, it’s hard to know which to reach for.  居中是一种很常见的布局方式。有人抱怨CSS居中布局很难,其实不然,在我看来,说居中布局难,不是因为居中真的有多么难实现,而是因为实现居中的方法有太多太多,以至于新手总是纠结去用哪个。我们需要根据不同的场景,去选择合适的方法。  本文将CSS居中布局方法进行归类,以便于理解和简化居中问题。 1.水平居中1.1 行内元素行内元素直接为其父元素设置文本居中即可。123.center-children { text-align: center;} https://codepen.io/chriscoyier/pen/HulzB 1.2 块级元素块级元素直接设置左右边距为 auto 即可,前提是该块级元素必需有 width ,而且必需处于标准文档流中,浮动、绝对定位、固定定位了的元素就不能。 123.center-me { margin: 0 auto;} https://codepen.io/chriscoyier/pen/eszon 1.3 多个块级元素 多个块元素在一行内居中 方法一:父元素设置 text-align: center ,子元素设置 display: inline-block; 方法二:使用 flex 布局,父元素设置 display: flex; justify-content: center; 多个块元素在一列内居中如果只是需要让多个块级元素整体水平居中,并且按默认的方式纵向排列,那直接设置左右边距为 auto 即可。 2. 垂直居中2.1 行内元素 单行居中 设置行高与元素的高度相同https://codepen.io/chriscoyier/pen/LxHmK 为行内元素/文本元素设置相等的上下内边距https://codepen.io/chriscoyier/pen/ldcwq 多行居中 设置相等的上下内边距 vertical-align 属性来实现垂直居中 https://codepen.io/chriscoyier/pen/ekoFx flexboxhttps://codepen.io/chriscoyier/pen/uHygv123456.flex-center-vertically { display: flex; justify-content: center; flex-direction: column; height: 400px;} 2.2 块级元素 元素高度已知 123456789.parent { position: relative;}.child { position: absolute; top: 50%; height: 100px; margin-top: -50px; /* account for padding and border if not using box-sizing: border-box; */} https://codepen.io/chriscoyier/pen/HiydJ 元素高度未知(最常见的一种场景) 方法一:先将元素相对于其原始位置向下移动父元素高度的一半距离,再将该元素相对其本身的高度向上移动一半,这样就能实现垂直居中的效果了。 12345678.parent { position: relative;}.child { position: absolute; top: 50%; transform: translateY(-50%);} https://codepen.io/chriscoyier/pen/lpema 方法二:flexbox 12345.parent { display: flex; flex-direction: column; justify-content: center;} https://codepen.io/chriscoyier/pen/FqDyi 3. 垂直和水平都居中当然,我们可以结合以上给出的方法来实现垂直和水平方向都居中的布局。这里我们再进行一下分类总结。 3.1 宽高固定将元素相对于其父元素的宽度/高度值向右并向下移动一半的距离,然后再通过设置负边距值的方法,将元素相对于其自身的宽度/高度值向左并向上移动一半的距离,就可实现水平垂直均居中的效果了。并且这种方法的浏览器兼容性是很好的。123456789101112131415.parent { position: relative;}.child { width: 300px; height: 100px; padding: 20px; position: absolute; top: 50%; left: 50%; margin: -70px 0 0 -170px;} https://codepen.io/chriscoyier/pen/JGofm 3.2 宽高不固定如果元素的宽度或者高度未知,则在将元素相对于父元素的宽高往向右并向下移动一半距离后,再用 transform 属性来将其向左并向上移动自身宽度及高度值一半的距离即可。123456789.parent { position: relative;}.child { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);} https://codepen.io/chriscoyier/pen/lgFiq 3.3 使用 flexbox12345.parent { display: flex; justify-content: center; align-items: center;} https://codepen.io/chriscoyier/pen/msItD 3.4 使用 grid这只是一个小技巧,只适用于一个元素的情况。 4. 结束语CSS 还是很伟大的,能够实现的布局多种多样,实现的方法也多种多样,重要的是找到合适的方法!这就需要多写多练多总结!","categories":[{"name":"技术","slug":"技术","permalink":"http://blog.yancongwen.com/categories/技术/"}],"tags":[{"name":"CSS","slug":"CSS","permalink":"http://blog.yancongwen.com/tags/CSS/"},{"name":"译文","slug":"译文","permalink":"http://blog.yancongwen.com/tags/译文/"}]},{"title":"节流与防抖","slug":"节流与防抖","date":"2018-04-16T13:00:00.000Z","updated":"2019-04-16T13:00:38.148Z","comments":true,"path":"2018/04/16/节流与防抖/","link":"","permalink":"http://blog.yancongwen.com/2018/04/16/节流与防抖/","excerpt":"","text":"节流与防抖防抖与节流函数是一种最常用的高频触发优化方式,能对性能有较大的帮助。 节流函数 每隔一段时间后执行一次,也就是降低频率,将高频操作优化成低频操作,通常使用场景: scrooll 事件或者 resize 事件,通常每隔 100~500ms 执行一次即可。 12345678910111213function throttle(fn, wait) { let timer = null return function() { let context = this let args = arguments if (!timer) { timer = setTimeout(() => { fn.apply(context, args) timer = null }, wait) } }} 防抖函数 将多次高频操作优化为只在最后一次执行,通常使用的场景是:用户输入,只需再输入完成后做一次输入校验即可。 1234567891011function debounce(fn, wait) { let timer = null return function() { let context = this let args = arguments timer && clearTimeout(timer) timer = setTimeout(() => { fn.apply(context, args) }, wait) }}","categories":[],"tags":[{"name":"Javascript","slug":"Javascript","permalink":"http://blog.yancongwen.com/tags/Javascript/"}]},{"title":"寻找最长回文字符串","slug":"寻找最长回文字符串","date":"2018-04-16T12:39:00.000Z","updated":"2019-04-16T12:44:02.433Z","comments":true,"path":"2018/04/16/寻找最长回文字符串/","link":"","permalink":"http://blog.yancongwen.com/2018/04/16/寻找最长回文字符串/","excerpt":"","text":"寻找最长回文字符串1. 问题描述给定一个字符串 s,找到 s 中最长的回文子串。示例 1: 123输入: "babad"输出: "bab"注意: "aba"也是一个有效答案。 示例 2: 12输入: "cbbd"输出: "bb" 2. 解决方法2.1 暴力解法该方法直接使用两次循环,找出所有可能的字符串组合,再去判断每个字符串是否是回文字符串。 时间复杂度:O(n^3) 空间复杂度:O(1) 1234567891011121314151617181920212223/** * @param {string} s * @return {string} */let longestPalindrome = function(s) { let len = s.length let result = s.slice(0, 1) let maxLen = 1 for (let i = 0; i < len; i++) { for (let j = len - 1; j > i + maxLen - 1; j--) { let str = s.slice(i, j + 1) let reverse = str .split('') .reverse() .join('') if (str === reverse && str.length > maxLen) { result = str maxLen = result.length } } } return result} 2.2 动态规划算法2.3 中心扩展算法","categories":[],"tags":[{"name":"算法","slug":"算法","permalink":"http://blog.yancongwen.com/tags/算法/"}]},{"title":"解决 WFS 矢量要素重复加载问题","slug":"解决-WFS-矢量要素重复加载问题","date":"2018-04-16T12:38:00.000Z","updated":"2019-04-16T12:43:42.398Z","comments":true,"path":"2018/04/16/解决-WFS-矢量要素重复加载问题/","link":"","permalink":"http://blog.yancongwen.com/2018/04/16/解决-WFS-矢量要素重复加载问题/","excerpt":"","text":"解决WFS矢量要素重复加载问题 技术选型:Openlayer + Arcgis Server WFS 服务 问题描述使用的是Arcgis server 服务,在做图层矢量要素编辑的功能时,选中要素时,要素会有一定高亮效果,但是总是出现奇怪现象,高亮效果不消失。或在执行删除矢量要素操作时,发现明明已经执行了删除操作,但是图层上仍然显示该要素。 查找原因 当时图进行拖动或缩放,会自动向服务器请求该视图区域下要素,如果统计当前图层的要素数量,会发现其大于图层原本的要素数量。 因服务发布原因,可能发布的服务中返回的 objectIdFieldName值为空,该字段的值是指 objectID 字段的名称,而该字段对于wfs要素加载至关重要,缺少该字段,会导致wfs同一要素在地图上重复加载。 解决若请求返回中缺少该字段,可从所有字段中找到字段类型为esriFieldTypeOID的字段,将该字段设置为objectIdFieldName。筛选要素可以使用 where参数。","categories":[],"tags":[{"name":"gis","slug":"gis","permalink":"http://blog.yancongwen.com/tags/gis/"}]},{"title":"Git基础","slug":"Git基础","date":"2018-04-15T13:37:00.000Z","updated":"2019-03-28T06:16:34.335Z","comments":true,"path":"2018/04/15/Git基础/","link":"","permalink":"http://blog.yancongwen.com/2018/04/15/Git基础/","excerpt":"","text":"1、工作区、暂存区、版本库、远程库 工作区(Working Directory) 就是你在电脑里能看到的目录 版本库(Repository) 工作区有一个隐藏目录.git就是版本库。 暂存区 Git 的版本库里存了很多东西,其中最重要的就是称为 stage(或者叫index)的暂存区,还有 Git 为我们自动创建的第一个分支 master,以及指向 maste r的一个指针叫HEAD。 远程库 指 github 或码云等 git 服务器上的版本库,也可以自己搭建 git 服务器。 2、常用命令 git init:初始化一个Git仓库 git add:把文件修改添加到暂存区,可反复多次使用,添加多个文件 git add -A // 添加所有改动 git add * //加新建文件和修改,但是不包括删除 git add . //加新建文件和修改,但是不包括删除 git add -u //加修改和删除,但是不包括新建文件 git commit:提交更改,把暂存区的所有内容提交到当前分支 git status:查看仓库当前的状态 -s:以简洁的形式显示 -b:也显示分支的状态 git diff:查看改变 git log:查看提交历史 git reflog:查看命令历史 –pretty=oneline 一行显示 git reset:回退到指定版本。HEAD指向的版本就是当前版本,因此,Git允许我们在版本的历史之间穿梭,使用命令git reset –hard commit_id git clone :克隆远程库 12345678910// SSH加密git clone git@github.com:yancongwen/仓库名.git// HTTPS加密,采用此协议的话以后每次推送都要登陆,比较繁琐git clone https://github.com/yancongwen/仓库名.git// 克隆分支: git clone -b <branch name> [remote repository address].git// 或者:git clone <respository-name>,先克隆库git checkout <branchname>,检出分支git pull 取回远程主机某个分支的更新,再与本地的指定分支合并 git pull: 取回远程主机某个分支的更新,再与本地的指定分支合并 1234$ git pull --rebase <远程主机名> <远程分支名>:<本地分支名>// 例:$ git pull origin next:master // 取回origin主机的next分支,与本地的master分支合并 git push:将本地分支的更新,推送到远程主机 1$ git push <远程主机名> <本地分支名>:<远程分支名> 3、命令","categories":[],"tags":[{"name":"git","slug":"git","permalink":"http://blog.yancongwen.com/tags/git/"}]},{"title":"HTTP总结(前端开发中的常识性问题)","slug":"HTTP总结--前端开发中的常识性问题","date":"2018-04-11T05:33:00.000Z","updated":"2019-03-28T06:16:34.335Z","comments":true,"path":"2018/04/11/HTTP总结--前端开发中的常识性问题/","link":"","permalink":"http://blog.yancongwen.com/2018/04/11/HTTP总结--前端开发中的常识性问题/","excerpt":"","text":"1. TCP/IP 简介 首先应该理解 TCP/IP 协议族中的四个分层:应用层、传输层、网络层、数据链路层,详细请看另一篇博客。 HTTP 位于应用层,负责生成或解析报文 TCP、UDP 位于传输层,负责将报文方便、可靠得传输 IP 位于网络层,负责搜索地址、路由中转、传输 数据链路层是硬件设备 2. HTTP HyperText Transfer Protocol,超文本转移协议,是TCP/IP 协议族的子集,用于客户端和 服务器之间的通信。详细请看其他文章:HTTP基础、HTTP报文、HTTP状态码、HTTP首部。 请求报文 响应报文 Chrome开发者工具查看 HTTP 报文 ① 打开 Network ② 地址栏输入网址 ③ 在 Network 点击,查看 Request/Response Headers,点击「view source」 3. TCP、UDP TCP(三次握手,四次挥手) 可靠:三次握手、四次挥手; 字节流服务:为了方便传输,将大块数据分割成以报文段为单位的数据包进行管理; 可靠、面向连接、相对 UDP 较慢; UDP 不可靠,不面向连接、相对 TCP 较快 4. curl 命令的使用 在Linux中curl是一个利用URL规则在命令行下工作的文件传输工具,可以说是一款很强大的http命令行工具。 语法: 1# curl [option] [url] 常见参数 -s :silent,安静模式,不显示过程或错误信息 -v :verbose,显示请求和响应报文 -I :head,仅仅显示响应报文,不显示请求返回的内容 -i :include,显示响应报文 -H :header,增加请求时的请求头信息 -X :request,自定义请求方法,默认为GET -d :data,定义POST请求中发送的数据 -A :agent,自定义User-Agent用户代理 -o : output,保存到指定文件 示例 123456789101112131415// 最简单用法,显示请求过程和请求结果$ curl www.baidu.com // 最常用用法,显示请求报文、响应报文、请求结果$ curl -s -v www.baidu.com // 自定义用POST方法进行HTTP通信,并添加自定义请求头$ curl -X POST -s -v -H "Frank: xxx" -- "https://www.baidu.com" // 发送数据$ curl -X POST -d "1234567890" -s -v -H "Frank: xxx" -- "https://www.baidu.com" // 下载文件保存到本地指定文件$ curl http://www.baidu.com > index.html$ curl -o index.html http://www.baidu.com","categories":[],"tags":[{"name":"计算机网络","slug":"计算机网络","permalink":"http://blog.yancongwen.com/tags/计算机网络/"}]},{"title":"Geoserver 中 WFS 服务的使用","slug":"Geoserver-中-WFS-服务的使用","date":"2018-04-01T12:35:00.000Z","updated":"2019-04-16T12:44:14.019Z","comments":true,"path":"2018/04/01/Geoserver-中-WFS-服务的使用/","link":"","permalink":"http://blog.yancongwen.com/2018/04/01/Geoserver-中-WFS-服务的使用/","excerpt":"","text":"Geoserver 中 WFS 服务的使用解决跨域问题 为了保证数据安全,geoserver 本身发布的矢量要素服务(wfs)不允许跨域访问。 解决跨域的方式有很多,主要分为三大类:服务端解决、前端解决、代理转发。 1、服务端解决:修改GeoServer某些配置来解决跨域。 1、下载跨域jar包jetty-servlets.jar\\下载geoserver使用的对应jetty版本,并将jar包放到 <Geoserver>\\webapps\\geoserver\\WEB-INF\\lib文件夹下。 2、设置跨域配置。\\打开<Geoserver>\\webapps\\geoserver\\web.xml文件,找到文件中平级的位置,添加如下内容。12345678910111213141516<filter> <filter-name>cross-origin</filter-name> <filter-class>org.eclipse.jetty.servlets.CrossOriginFilter</filter-class> <init-param> <param-name>allowedOrigins</param-name> <param-value>*</param-value> </init-param> <init-param> <param-name>allowedMethods</param-name> <param-value>GET,POST</param-value> </init-param> <init-param> <param-name>allowedHeaders</param-name> <param-value>x-requested-with,content-type</param-value> </init-param> </filter> 找到文件中平级的位置,添加如下内容:1234<filter-mapping> <filter-name>cross-origin</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> 3、重启GeoServer服务 参考链接:[1] http://blog.csdn.net/mengdong_zy/article/details/51784781 2、前端:Jsonp跨域参考:跨域及常见解决方案 3、使用代理服务器参考:跨域及常见解决方案 4、chorm 浏览器插件参考:跨域及常见解决方案 WFS 矢量查询待更… 几个名词 gwc: GeoWebCache 缓存 ows: OGC Web Service","categories":[],"tags":[{"name":"gis","slug":"gis","permalink":"http://blog.yancongwen.com/tags/gis/"}]},{"title":"命令行基础","slug":"命令行基础","date":"2018-03-30T17:00:00.000Z","updated":"2019-03-28T06:16:34.351Z","comments":true,"path":"2018/03/31/命令行基础/","link":"","permalink":"http://blog.yancongwen.com/2018/03/31/命令行基础/","excerpt":"","text":"首先,推荐一个网站explainshell.com,超级强大的Linux命令解释工具,在搜索框里任意输入Linux命令,系统会将命令解析。 1.常用单词及缩写 英文 翻译 directory 目录、文件夹 file 文件 make 新建 remove 删除 move 移动 copy 复制 list 罗列 link 链接 find 查找 echo 发出回音、重复 touch 触摸 change 改变 缩写 命令 全写 缩写 创建目录 make directory mkdir 删除 remove rm 移动 / 重命名 move mv 复制 copy cp 罗列 list ls 改变目录 change directory cd 2.常见的自带命令 操作 命令 进入目录 cd 显示当前目录 pwd 创建目录 mkdir 目录名 创建目录 mkdir -p 目录路径 显示用户名 whoami 查看路径 ls 路径 查看路径 ls -a 路径 查看路径 ls -l 路径 查看路径 ls -al 路径 创建文件 echo ‘1’ > 文件路径 强制创建文件 echo ‘1’ >! 文件路径 追加文件内容 echo ‘1’ >> 文件路径 创建文件 touch 文件名 改变文件更新时间 touch 文件名 复制文件 cp 源路径 目标路径 复制目录 cp -r 源路径 目标路径 移动节点 mv 源路径 目标路径 删除文件 rm 文件路径 强制删除文件 rm -f 文件路径 删除目录 rm -r 目录路径 强制删除目录 rm -rf 目录路径 查看目录结构 tree 建立软链接 ln -s 真实文件 链接 下载文件 curl -L https://www.baidu.com > baidu.html 拷贝网页 wget -p -H -e robots=off https://www.baidu.com 磁盘占用 df -kh 当前目录大小 du -sh 各文件大小 du -h 显示当前用户 whoami 显示命令路径 where name 查找命令路径 which name","categories":[],"tags":[{"name":"计算机基础","slug":"计算机基础","permalink":"http://blog.yancongwen.com/tags/计算机基础/"}]},{"title":"DOM 事件基础","slug":"DOM-事件基础","date":"2018-01-22T12:51:00.000Z","updated":"2019-04-16T13:16:46.943Z","comments":true,"path":"2018/01/22/DOM-事件基础/","link":"","permalink":"http://blog.yancongwen.com/2018/01/22/DOM-事件基础/","excerpt":"","text":"DOM 事件基础1、DOM0、DOM2、DOM3 标准 DOM0 直接通过 onclick 写在 html 里面的事件; 缺点:html 和 js 耦合在一起,修改不方便; DOM2 通过 addEventListener 绑定事件; 通过 removeEvementListener 解绑事件; IE 下通过 attachEvent 绑定,detachEvent 解绑; DOM3 DOM3 是增加了一些新的事件; 2、事件捕获和事件冒泡 DOM2 级的事件规定了事件流包含三个阶段包括: 事件捕获:事件开始由顶层对象触发,然后逐级向下传播,直到目标元素; 处于目标阶段:处在绑定事件的元素上; 事件冒泡阶段:事件由具体的元素先接收,然后逐级向上传播,直到不具体的元素; 可以看一下这个 示例、源码在此 一般来说事件冒泡机制,用的更多一些,所以在 IE8 以及之前,IE 只支持事件冒泡。IE9/FF/Chrome 这 2 种模型都支持,可以通过 addEventListener((type, listener, useCapture)的useCapture参数来设定,useCapture=false(默认)代表着事件冒泡,useCapture=true 代表着采用事件捕获。 e.stopPropagation() 阻止冒泡; e.preventDefault() 阻止事件默认行为; 3、事件代理1234567<div id=\"div1\"> <a href=\"#\">a1</a> <a href=\"#\">a2</a> <a href=\"#\">a3</a> <a href=\"#\">a4</a> ......</div> 1234567var div1 = document.getElementById('div1')div1.addEventListener('click', function(e) { var target = e.target if (target.nodeName === 'A') { alert(target.innerHTML) }}) 4、几个不常用的事件1234567891011121314151617181920// 禁止显示右键选项document.oncontextmenu = function(e) { return false}// 禁止选择document.onselectstart = function(e) { return false}// 禁止粘贴document.onpaste = function(e) { return false}// 禁止复制document.oncopy = function(e) { return false}// 禁止剪切document.oncut = function(e) { return false} 参考 [解惑]JavaScript 事件机制 DOM0,DOM2,DOM3 事件,事件基础知识入门 JavaScript 标准参考教程–阮一峰","categories":[],"tags":[{"name":"Javascript","slug":"Javascript","permalink":"http://blog.yancongwen.com/tags/Javascript/"}]},{"title":"图解HTTP学习笔记(七):HTTPS","slug":"图解HTTP学习笔记七--HTTPS","date":"2018-01-12T12:00:00.000Z","updated":"2019-03-28T06:16:34.351Z","comments":true,"path":"2018/01/12/图解HTTP学习笔记七--HTTPS/","link":"","permalink":"http://blog.yancongwen.com/2018/01/12/图解HTTP学习笔记七--HTTPS/","excerpt":"","text":"1. HTTP 的缺点 通信使用明文(不加密),内容可能会被窃听; 不验证通信方的身份,因此有可能遭遇伪装( DoS 攻击,拒绝服务攻击); 无法证明报文的完整性,所以有可能已遭篡改(中间人攻击,MITM); 2. HTTPS 是身披 SSL 外壳的 HTTP HTTPS = HTTP + 加密 + 认证 + 完整性保护   HTTPS 并非是应用层的一种新协议。只是 HTTP 通信接口部分用 SSL(Secure Socket Layer)和 TLS(Transport Layer Security)协议代替而已。通常,HTTP 直接和 TCP 通信。当使用 SSL 时,则演变成先和 SSL 通信,再由 SSL 和 TCP 通信了。简言之,所谓 HTTPS,其实就是身披 SSL 协议这层外壳的 HTTP。  SSL 是独立于 HTTP 的协议,所以不光是 HTTP 协议,其他运行在应用层的 SMTP 和 Telnet 等协议均可配合 SSL 协议使用。可以说 SSL 是 当今世界上应用最为广泛的网络安全技术。 3. 相互交换密钥的公开密钥加密技术 共享密钥加密 使用两把密钥的公开密钥加密 HTTPS 采用混合加密机制 4. 证明公开密钥正确性的证书  遗憾的是,公开密钥加密方式还是存在一些问题的。那就是无法证明公开密钥本身就是货真价实的公开密钥。为了解决上述问题,可以使用由数字证书认证机构(CA,Certificate Authority)和其相关机关颁发的公开密钥证书。 5. HTTPS 的安全通信机制 6. HTTPS 缺点 消耗更多 CPU 及内存等资源,导致处理速度变慢; 和HTTP相比,SSL 通信使通信量会增加; HTTPS 比 HTTP 要慢 2 到 100 倍; 证书必须向认证机构(CA)购买;","categories":[],"tags":[{"name":"计算机网络","slug":"计算机网络","permalink":"http://blog.yancongwen.com/tags/计算机网络/"}]},{"title":"图解HTTP学习笔记(六):HTTP首部","slug":"图解HTTP学习笔记六--HTTP首部","date":"2018-01-12T10:00:00.000Z","updated":"2019-03-28T06:16:34.351Z","comments":true,"path":"2018/01/12/图解HTTP学习笔记六--HTTP首部/","link":"","permalink":"http://blog.yancongwen.com/2018/01/12/图解HTTP学习笔记六--HTTP首部/","excerpt":"","text":"HTTP 协议的请求和响应报文中必定包含 HTTP 首部。首部内容为客户端和服务器分别处理请求和响应提供所需要的信息。报文结构: 一、首部字段HTTP/1.1 规范定义了 47 种首部字段。HTTP 首部字段根据实际用途被分为以下 4 种类型: 通用首部字段:请求报文和响应报文双方都会使用的首部 首部字段名 说明 Cache-Control 控制缓存的行为 Connection 逐跳首部、连接的管理 Date 创建报文的日期时间 Program 报文指令 Trailer 报文末端的首部一览 Transfer-Encoding 指定报文主体的传输编码方式 Upgrade 升级为其他协议 Via 代理服务器的相关信息 Warning 错误通知 请求首部字段:请求报文中,用于补充请求的附加信息、客户端信息、对响应内容相关的优先级等。 首部字段名 说明 Accept 用户代理可处理的媒体类型 Accept-Charset 优先的字符集 Accept-Encoding 优先的内容编码 Accept-Language 优先的语言(自然语言) Authorization Web认证信息 Expect 期待服务器的特定行为 From 用户的电子邮箱地址 Host 请求资源所在的服务器 If-Match 比较实体标记(ETag) If-Modified-Since 比较资源的更新时间 If-None-Match 比较实体标记(与If-Match相反) If-Range 资源未更新时发送实体Byte的范围请求 If-Unmodified-Since 比较资源的更新时间(与If-Modified-Since相反) Max-Forwards 最大传输逐跳数 Proxy-Authorization 代理服务器要求客户端的认证信息 Range 实体的字节范围请求 Referer 对请求中的URI的原始获取方 TE 传输编码的优先级 User-Agent HTTP客户端程序的信息 响应首部字段:响应报文中, 用于补充响应的附加信息、服务器信息,以及对客户端的附加要求等信息 首部字段名 说明 Accept-Ranges 是否接受字节范围请求 Age 推算资源创建经过时间 Content-Disposition 可以控制返回的资源是下载还是预览(图片) ETag 资源的匹配信息 Location 令客户端重定向至指定URI Proxy-Authenticate 代理服务器对客户端的认证信息 Retry-After 对再次发起请求的时机要求 Server HTTP服务器的安装信息 Vary 代理服务器缓存的管理信息 WWW-Authenticate 服务器对客户端的认证信息 实体首部字段:包含在请求报文和响应报文中的实体部分所使用的首部,用于补充内容的更新时间等与实体相关的信息 首部字段名 说明 Allow 资源可支持的HTTP方法 Content-Encoding 实体主体使用的编码方式 Content-Language 实体主体的自然语言 Content-Length 实体主体的大小(单位:字节) Content-Location 替换对应资源的URI Content-MD5 实体主体的报文摘要 Content-Range 实体主体的位置范围 Content-Type 实体主体的媒体类型 Expires 实体主体过期的日期时间 Last-Modified 资源的最后修改日期时间 二、通用首部字段 1、Cache-Control请求指令:响应指令: 2、Connection 控制不再转发给代理的首部字段 管理持久连接 HTTP/1.1 版本的默认连接都是持久连接。为此,客户端会在持久连接上连续发送请求。当服务器端想明确断开连接时,则指定 Connection 首部字段的值为 Close。 HTTP/1.1 之前的 HTTP 版本的默认连接都是非持久连接。为此,如果想在旧版本的 HTTP 协议上维持持续连接,则需要指定 Connection 首部字段的值为 Keep-Alive。 3、Data 4、Pragma 5、Trailer 6、Transfer-Encoding 7、Upgrade 8、Via 报文经过代理或网关时,会先在首部字段 Via 中附加该服务器的信息,然后再进行转发。 9、Warning 该首部通常会告知用户一些与缓存相关的问题的警告 三、请求首部字段 1、Accept Accept 首部字段可通知服务器,用户代理能够处理的媒体类型及媒体类型的相对优先级。 2、Accept-Charset 用来通知服务器用户代理支持的字符集及字符集的相对优先顺序。另外,可一次性指定多种字符集。与首部字段 Accept 相同的是可用权重 q 值来表示相对优先级。 3、Accept-Encoding 用来告知服务器用户代理支持的内容编码及 内容编码的优先级顺序 4、Accept-Language 告知服务器用户代理能够处理的自然语言集(指中文或英文等),以及自然语言集的相对优先级。 5、Authorization 是用来告知服务器,用户代理的认证信息(证书值)。通常,想要通过服务器认证的用户代理会在接收到返回的 401 状态码响应后,把首部字段 Authorization 加入请求中。 6、Expect 告知服务器,期望出现的某种特定行为 7、From 用来告知服务器使用用户代理的用户的电子邮件地址。通常,其使用目的就是为了显示搜索引擎等用户代理的负责人的 电子邮件联系方式。 8、Host 告知服务器,请求的资源所处的互联网主机名和端口号。Host 首部字段在 HTTP/1.1 规范内是唯一一个必须被包含在请求内的首部字段。 9、If-Match 形如 If-xxx 这种样式的请求首部字段,都可称为条件请求。服务器接收到附带条件的请求后,只有判断指定条件为真时,才会执行请求 只有当 If-Match 的字段值跟 ETag 值匹配一致时,服务器才会接受请求 10、If-None-Match 只有在 If-None-Match 的字段值与 ETag 值不一致时,可处理该请求 11、If-Modified-Since 如果在 If-Modified-Since 字段指定的日期时间后,资源发生了更新,服务器会接受请求 12、If-Unmodified-Since 如果在 If-Unmodified-Since 字段指定的日期时间后,资源没有发生更新,服务器才会接受请求 13、If-Range 它告知服务器若指定的 IfRange 字段值(ETag 值或者时间)和请求资源的 ETag 值或时间相一 致时,则作为范围请求处理。反之,则返回全体资源。 14、Max-Forwards 通过 TRACE 方法或 OPTIONS 方法,发送包含首部字段 MaxForwards 的请求时,该字段以十进制整数形式指定可经过的服务器最大数目。服务器在往下一个服务器转发请求之前,Max-Forwards 的 值减 1 后重新赋值。当服务器接收到Max-Forwards 值为 0 的请求 时,则不再进行转发,而是直接返回响应。 15、Proxy-Authorization 接收到从代理服务器发来的认证质询时,客户端会发送包含首部字段 Proxy-Authorization 的请求,以告知服务器认证所需要的信息。 16、Range 可告知服务器资源的指定范围。接收到附带 Range 首部字段请求的服务器,会在处理请求之后返回状 态码为 206 Partial Content 的响应。无法处理该范围请求时,则会返回状态码 200 OK 的响应及全部资源。 17、Referer 告知服务器请求的原始资源的 URI Referer 的正确的拼写应该是 Referrer,但不知为何,大家一直沿用这个错误的拼写。 18、TE 告知服务器客户端能够处理响应的传输编码方式及相对优先级。它和首部字段 Accept-Encoding 的功能很相像,但是用于传输编码。 19、User-Agent 将创建请求的浏览器和用户代理名称等信息传达给服务器。 四、响应首部字段 1、Accept-Ranges 用来告知客户端服务器是否能处理范围请求,以指定获取服务器端某个部分的资源。可指定的字段值有两种,可处理范围请求时指定其为 bytes,反之则指定其为 none。 2、Age 告知客户端,源服务器在多久前创建了响应 3、ETag 告知客户端实体标识。例如,当使用中文版的浏览器访问 http://www.google.com/ 时,就会返回中文版对应的资源,而使用英文版的浏览器访问时,则会返回英文版对应的资源。两者的 URI 是相同的,所以仅凭 URI 指定缓存的资源是相当困难的。若在下 载过程中出现连接中断、再连接的情况,都会依照 ETag 值来指定资源。 4、Location 将响应接收方引导至某个与请求 URI 位置不同的资源。基本上,该字段会配合 3xx :Redirection 的响应,提供重定向的 URI。 几乎所有的浏览器在接收到包含首部字段 Location 的响应后,都会强制性地尝试对已提示的重定向资源的访问。 5、Proxy-Authenticate 把由代理服务器所要求的认证信息发送给客户端。 6、Retry-After 告知客户端应该在多久之后再次发送请求。主要配合状态码 503 Service Unavailable 响应,或 3xx Redirect 响应一起使用。 7、Server 告知客户端当前服务器上安装的 HTTP 服务器应用程序的信息 8、Vary 可对缓存进行控制。 9、WWW-Authenticate 用于 HTTP 访问认证。它会告知客户端适用于访问请求 URI 所指定资源的认证方案(Basic 或是 Digest)和带参数提示的质询(challenge)。状态码 401 Unauthorized 响应中, 肯定带有首部字段 WWW-Authenticate。 五、实体首部字段 1、Allow 用于通知客户端能够支持 Request-URI 指定资源的所 有 HTTP 方法。当服务器接收到不支持的 HTTP 方法时,会以状态码 405 Method Not Allowed 作为响应返回。与此同时,还会把所有能支持的 HTTP 方法写入首部字段 Allow 后返回。 2、Content-Encoding 告知客户端服务器对实体的主体部分选 用的内容编码方式。 主要采用以下 4 种内容编码的方式: gzip、compress、deflate、identity 3、Content-Language 实体主体使用的自然语言 4、Content-Length 实体主体部分的大小。 5、Content-Location 给出与报文主体部分相对应的 URI。和首部字段 Location 不同,Content-Location 表示的是报文主体返回资源对应的 URI。 6、Content-MD5 是一串由 MD5 算法生成的值,其目的在于检查报文主体在传输过程中是否保持完整,> 以及确认传输到达 7、Content-Range 针对范围请求,告知客户端作为响应返回的实体的哪个部分符合范围请求 8、Content-Type 了实体主体内对象的媒体类型 9、Expires 将资源失效的日期告知客户端 10、Last-Modified 资源最终修改的时间 六、为 Cookie 服务的首部字段 首部字段名 说明 首部类型 Set-Cookie 开始状态管理所使用的Cookie信息 响应首部字段 Cookie 服务器接收到的Cookie信息 请求首部字段 1、Set-Cookie 属性表: 属性 说明 NAME=VALUE 赋予Cookie的名称和其值(必需项) expires=DATE Cookie的有效期(若不明确指定则默认为浏览器关闭前为止) path=PATH 将服务器上的文件目录作为Cookie的适用对象(若不指定则默认为文档所在的文件目录) domain=域名 作为Cookie适用对象的域名(若不指定则默认为创建Cookie的服务器的域名) Secure 仅在HTTPS完全通信时才会发送Cookie HttpOnly 加以限制,使Cookie不能被JavaScript脚本访问 2、Cookie 告知服务器,当客户端想获得 HTTP 状态管理支持时,就会在请求中包含从服务器接收到的 Cookie。接收到多个 Cookie 时,同样可以以多个 Cookie 形式发送。 七、其他首部字段HTTP 首部字段是可以自行扩展的。所以在 Web 服务器和浏览器的应用上,会出现各种非标准的首部字段。如: 1、X-Frame-Options 2、X-XSS-Protection 3、DNT 4、P3P","categories":[],"tags":[{"name":"计算机网络","slug":"计算机网络","permalink":"http://blog.yancongwen.com/tags/计算机网络/"}]},{"title":"图解HTTP学习笔记(五):HTTP状态码","slug":"图解HTTP学习笔记五--HTTP状态码","date":"2018-01-11T01:00:00.000Z","updated":"2019-03-28T06:16:34.351Z","comments":true,"path":"2018/01/11/图解HTTP学习笔记五--HTTP状态码/","link":"","permalink":"http://blog.yancongwen.com/2018/01/11/图解HTTP学习笔记五--HTTP状态码/","excerpt":"","text":"HTTP 状态码负责表示客户端 HTTP 请求的返回结果、标记服务器端的处理是否正常、通知出现的错误等工作。 状态码 类别 原因短语 1XX Informational(信息性状态码) 接受的请求正在处理 2XX Success(成功) 请求正常处理 3XX Redirecton(重定向) 需要进行附加操作 4XX Client Error(客户端错误) 服务器无法完成请求 5XX Server Error(服务端错误) 服务器处理请求出错 1. 2XX 成功 200 OK 请求在服务器端被正常处理 204 No Content 返回的响应报文中不含实体的主体部分 206 Partial Content 该状态码表示客户端进行了范围请求,而服务器成功执行了这部分的 GET 请求。响应报文中包含由 Content-Range 指定范围的实体内容。 2. 3XX 重定向 301 Moved Permanently 永久性重定向。该状态码表示请求的资源已被分配了新的 URI,以后应使用资源现在所指的 URI。 302 Found 临时性重定向。该状态码表示请求的资源已被分配了新的 URI,希望 用户(本次)能使用新的 URI 访问。 和 301 状态码相似,但 302 状态码代表的资源不是被永久移动,只是临时性质的。 303 See Other 该状态码表示由于请求对应的资源存在着另一个 URI,应使用 GET 方法定向获取请求的资源。 304 Not Modified 该状态码表示客户端发送附带条件的请求时,服务器端允许请求访问资源,但未满足条件的情况。304 状态码返回时,不包含任何响应的主体部分。304 虽然被划分在 3XX 类别中,但是和重定向没有关系。 307 Temporary Redirect 临时重定向。该状态码与 302 Found 有着相同的含义。 3. 4XX 客户端错误 400 Bad Request 该状态码表示请求报文中存在语法错误。当错误发生时,需修改请求的内容后再次发送请求。另外,浏览器会像 200 OK 一样对待该状态码。 401 Unauthorized 该状态码表示发送的请求需要有通过 HTTP 认证的认证信息。若之前已进行过 1 次请求,则表示认证失败。 403 Forbidden 该状态码表明对请求资源的访问被服务器拒绝了。服务器端没有必要 给出拒绝的详细理由。未获得文件系统的访问授权,访问权限出现某些问题(从未授权的发送源 IP 地址试图访问)等列举的情况都可能是发生 403 的原因。 404 Not Found 该状态码表明服务器上无法找到请求的资源。除此之外,也可以在服务器端拒绝请求且不想说明理由时使用。 4. 5XX 服务器错误 500 Internal Server Error 该状态码表明服务器端在执行请求时发生了错误。也有可能是 Web 应用存在的 bug 或某些临时的故障。 503 Service Unavailable 该状态码表明服务器暂时处于超负载或正在进行停机维护,现在无法 处理请求。","categories":[],"tags":[{"name":"计算机网络","slug":"计算机网络","permalink":"http://blog.yancongwen.com/tags/计算机网络/"}]},{"title":"图解HTTP学习笔记(四):HTTP报文","slug":"图解HTTP学习笔记四--HTTP报文","date":"2018-01-10T01:00:00.000Z","updated":"2019-03-28T06:16:34.351Z","comments":true,"path":"2018/01/10/图解HTTP学习笔记四--HTTP报文/","link":"","permalink":"http://blog.yancongwen.com/2018/01/10/图解HTTP学习笔记四--HTTP报文/","excerpt":"","text":"  用于 HTTP 协议交互的信息被称为 HTTP 报文。请求端(客户端)的 HTTP 报文叫做请求报文,响应端(服务器端)的叫做响应报文。  HTTP 报文大致可分为报文首部和报文主体两块。 HTTP报文结构: HTTP报文实例:","categories":[],"tags":[{"name":"计算机网络","slug":"计算机网络","permalink":"http://blog.yancongwen.com/tags/计算机网络/"}]},{"title":"图解HTTP学习笔记(三):HTTP基础","slug":"图解HTTP学习笔记三--HTTP基础","date":"2018-01-09T14:00:00.000Z","updated":"2019-03-28T06:16:34.351Z","comments":true,"path":"2018/01/09/图解HTTP学习笔记三--HTTP基础/","link":"","permalink":"http://blog.yancongwen.com/2018/01/09/图解HTTP学习笔记三--HTTP基础/","excerpt":"","text":"HTTP,即 超文本传输协议 (HyperText Transfer Protocol)。 1. HTTP 方法 下面列出HTTP1.1中的请求方法: 方法 说明 GET 获取资源 POST 传输数据实体 PUT 传输文件 HEAD 获取报文首部 DELETE 删除文件 OPTIONS 询问支持的方法 TARACK 追踪路径 CONNECT 要求用隧道协议连接代理 2. 持久连接  持久连接旨在建立 1 次 TCP 连接后进行多次请求和响应的交互。在 HTTP/1.1 中,所有的连接默认都是持久连接,但在 HTTP/1.0 内并 未标准化。 3. 管线化  持久连接使得多数请求以管线化(pipelining)方式发送成为可能。从前发送请求后需等待并收到响应,才能发送下一个请求。管线化技术出现后,不用等待响应亦可直接发送下一个请求。这样就能够做到同时并行发送多个请求,而不需要一个接一个地等待响应了。 4. 使用 Cookie 的状态管理  HTTP 是无状态协议,它不对之前发生过的请求和响应的状态进行管理。Cookie 技术通过在请求和响应报文中写入 Cookie 信息来控制客户端的状态。  Cookie 会根据从服务器端发送的响应报文内的一个叫做 Set-Cookie 的首部字段信息,通知客户端保存 Cookie。当下次客户端再往该服务器发送请求时,客户端会自动在请求报文中加入 Cookie 值后发送出去。","categories":[],"tags":[{"name":"计算机网络","slug":"计算机网络","permalink":"http://blog.yancongwen.com/tags/计算机网络/"}]},{"title":"图解HTTP学习笔记(二):URI和URL","slug":"图解HTTP学习笔记二--URI和URL","date":"2018-01-08T14:00:00.000Z","updated":"2019-03-28T06:16:34.351Z","comments":true,"path":"2018/01/08/图解HTTP学习笔记二--URI和URL/","link":"","permalink":"http://blog.yancongwen.com/2018/01/08/图解HTTP学习笔记二--URI和URL/","excerpt":"","text":"1. 统一资源标识符 URI1.1 统一资源标识符 URI (Uniform Resource Identifier)URI 就是标识某一互联网资源的字符串。URI可被视为定位符(URL),名称(URN)或两者兼备。 1.2 统一资源定位符 URL (Universal Resource Locator)URL是URI的最常见形式,它标识一个互联网资源,也被称为 Web 地址。 URL 是 URI 的子集。例如: 12345https://developer.mozilla.orghttps://developer.mozilla.org/en-US/docs/Learn/https://developer.mozilla.org/en-US/search?q=URLftp://ftp.is.co.za/rfc/rfc1808.txtmailto:John.Doe@example.com 1.3 统一资源名称 URN (Universal Resource Name)通过特定命名空间中的唯一名称来标识资源。例如: 12345urn:isbn:9780141036144urn:ietf:rfc:7230上面两个 URN 分别标识了下面的资源: - 乔治·奥威尔所著的《1984》 - IETF规范7230,超文本传输协议 (HTTP/1.1):Message Syntax and Routing. 1.4 三者关系RL和URN都是URI的子集。统一资源名(URN)如同一个人的名称,而统一资源定位符(URL)代表一个人的住址。在 知乎 上有人这么回答:原来URI包括URL和URN,后来URN没流行起来,导致几乎目前所有的URI都是URL。 2. URI 格式 方案或协议:http:// 告诉浏览器使用何种协议,对于大部分 Web 资源,通常使用 HTTP 协议或其安全版本,HTTPS 协议。还有:ftp、data、file、mailto、tel等协议。 服务器地址: IPv4、IPV6地址或域名。 端口号:若使用默认端口号可以省略,默认端口(HTTP为80,HTTPS为443)。 路径:资源的路径。 查询字符串:是提供给 Web 服务器的额外参数,这些参数是用 & 符号分隔的键/值对列表。 片段标识符:是资源本身的某一部分的一个锚点。锚点代表资源内的一种“书签”,它给予浏览器显示位于该“加书签”点的内容的指示。 例如,在HTML文档上,浏览器将滚动到定义锚点的那个点上;在视频或音频文档上,浏览器将转到锚点代表的那个时间。值得注意的是 # 号后面的部分,也称为片段标识符,永远不会与请求一起发送到服务器。 参考: Mozilla中统一资源标识符的语法 URL和URI的区别","categories":[],"tags":[{"name":"计算机网络","slug":"计算机网络","permalink":"http://blog.yancongwen.com/tags/计算机网络/"}]},{"title":"图解HTTP学习笔记(一):网络基础","slug":"图解HTTP学习笔记一--网络基础","date":"2018-01-08T01:00:00.000Z","updated":"2019-03-28T06:16:34.351Z","comments":true,"path":"2018/01/08/图解HTTP学习笔记一--网络基础/","link":"","permalink":"http://blog.yancongwen.com/2018/01/08/图解HTTP学习笔记一--网络基础/","excerpt":"","text":"计算机与网络设备要相互通信,双方就必须基于相同的方法。比如,如何探测到通信目标、由哪一边先发起通信、使用哪种语言进行通信、怎样结束通信等规则都需要事先确定。不同的硬件、操作系统之间的通信,所有的这一切都需要一种规则。而我们就把这种规则称为协议(Protocol)。 1. TCP/IP 协议族 TCP/IP 是互联网相关的各类协议族的总称也有说法 认为,TCP/IP 是指 TCP 和 IP 这两种协议。还有一种说法认为,TCP/ IP 是在 IP 协议的通信过程中,使用到的协议族的统称。 2. TCP/IP 的分层管理TCP/IP 协议族里重要的一点就是分层。TCP/IP 协议族按层次分别分为以下 4 层: 应用层 应用层决定了向用户提供应用服务时通信的活动。 包括:FTP、DNS、HTTP 传输层 传输层对上层应用层,提供处于网络连接中的两台计算机之间的数据传输。 包括:TCP、UDP 网络层 网络层用来处理在网络上流动的数据包。数据包是网络传输的最小数 据单位。该层规定了通过怎样的路径(所谓的传输路线)到达对方计算机,并把数据包传送给对方。 与对方计算机之间通过多台计算机或网络设备进行传输时,网络层所起的作用就是在众多的选项内选择一条传输路线。 包括:IP、 数据链路层 用来处理连接网络的硬件部分。包括控制操作系统、硬件的设备驱动、NIC(Network Interface Card,网络适配器,即网卡),及光纤等物理可见部分(还包括连接器等一切传输媒介)。硬件上的范畴均在 链路层的作用范围之内。 3. TCP/IP 通信传输流   用 HTTP 举例来说明: 首先作为发送端的客户端在应用层 (HTTP 协议)发出一个想看某个 Web 页面的 HTTP 请求。 接着,为了传输方便,在传输层(TCP协议)把从应用层处收到的数据(HTTP请求报文)进行分割,并在各个报文上打上标记序号及端口号后转发给网络层。 在网络层(IP 协议),增加作为通信目的地的 MAC 地址后转发给链路层。 这样一来,发往网络的通信请求就准备齐全了。 接收端的服务器在链路层接收到数据,按序往上层发送,一直到应用层。当传输到应用层,才能算真正接收到由客户端发送过来的 HTTP 请求。 发送端在层与层之间传输数据时,每经过一层时必定会被打上一个该层所属的首部信息。反之,接收端在层与层传输数据时,每经过一层 时会把对应的首部消去。 4. 与 HTTP 关系密切的协议 : IP、TCP 和 DNS4.1 负责传输的 IP 协议  按层次分,IP(Internet Protocol)网际协议位于网络层。Internet Protocol 这个名称可能听起来有点夸张,但事实正是如此,因为几乎所有使用网络的系统都会用到 IP 协议。TCP/IP协议族中的IP指的就是网际协议,协议名称中占据了一半位置,其重要性可见一斑。可能 有人会把“IP”和“IP 地址”搞混,“IP”是协议的名称。  IP 协议的作用是把各种数据包传送给对方。而要保证确实传送到对方那里,则需要满足各类条件。其中两个重要的条件是 IP 地址和 MAC 地址(Media Access Control Address)。IP 地址指明了节点被分配到的地址,MAC 地址是指网卡所属的固定地址。IP 地址可以和 MAC 地址进行配对。IP 地址可变,但 MAC 地址基本不会。  IP 间的通信依赖MAC地址。在网络上,通信的双方在同一局域网(LAN)内的情况是很少的,通常是经过多台计算机和网络设备中转才能连接到对方。而在进行中转时,会利用下一站中转设备的 MAC地址来搜索下一个中转目标。这时,会采用 ARP 协议(Address Resolution Protocol)。ARP是一种用以解析地址的协议,根据通信方的 IP 地址就可以反查出对应的 MAC 地址。  在到达通信目标前的中转过程中,那些计算机和路由器等网设备只能获悉很粗略的传输路线。这种机制称为路由选择(routing),有点像快递公司的送货过程。想要寄快递的人,只要将自己的货物送到集散中心,就可以知道快递公司是否肯收件发货,该快递公司的集散中心检查货物的送达地址,明确下站该送往哪个区域的集散中心。接着,那个区域的集散中心自会判断是否能送到对方的家中。 4.2 确保可靠性的 TCP 协议  按层次分,TCP 位于传输层,提供可靠的字节流服务。  字节流服务:为了方便传输,将大块数据分割成以报文段为单位的数据包进行管理。  可靠:TCP 协议能够确认数据最终是否送达到对方。为了准确无误地将数据送达目标处,TCP 协议建立连接时采用三次握手 (three-way handshaking)策略。发送端首先发送一个带 SYN 标志的数据包给对方。接收端收到后, 回传一个带有 SYN/ACK 标志的数据包以示传达确认信息。最后,发送端再回传一个带 ACK 标志的数据包,完成三次握手,客户端与服务器开始传送数据。  数据传输完成后,采用四次挥手策略关闭TCP连接。第一步,当主机A的应用程序通知TCP数据已经发送完毕时,TCP向主机B发送一个带有FIN附加标记的报文段(FIN表示英文finish)。第二步,主机B收到这个FIN报文段之后,并不立即用FIN报文段回复主机A,而是先向主机A发送一个确认序号ACK,同时通知自己相应的应用程序:对方要求关闭连接(先发送ACK的目的是为了防止在这段时间内,对方重传FIN报文段)。第三步,主机B的应用程序告诉TCP:我要彻底的关闭连接,TCP向主机A送一个FIN报文段。第四步,主机A收到这个FIN报文段后,向主机B发送一个ACK表示连接彻底释放。 4.3 负责域名解析的 DNS 服务  DNS(Domain Name System)服务是和 HTTP 协议一样位于应用层的协议。它提供域名到 IP 地址之间的解析服务。DNS 协议提供通过域名查找 IP 地址,或逆向从 IP 地址反查域名的服务。 5. 各种协议与 HTTP 协议的关系","categories":[],"tags":[{"name":"计算机网络","slug":"计算机网络","permalink":"http://blog.yancongwen.com/tags/计算机网络/"}]},{"title":"理解 jQuery","slug":"理解-jQuery","date":"2017-12-26T12:57:00.000Z","updated":"2019-04-16T13:17:19.983Z","comments":true,"path":"2017/12/26/理解-jQuery/","link":"","permalink":"http://blog.yancongwen.com/2017/12/26/理解-jQuery/","excerpt":"","text":"理解 jQuery1、还有必要学习 jQuery 吗首先必须肯定的回答:有必要。虽然目前 MVVM 框架很流行,但 jQuery 依然占据一定地位。某些特定场景的项目 jQuery 依然是最好的选择,jQuery 帮助我们解决了太多的兼容性问题,而且对于有一定 JS 基础的人来说学习 jQuery 的成本很低,没必要去掌握全部 API,只要会查文档就可以。虽然新项目中不一定会使用 jQuery ,但是学习 jQuery ,尤其是去阅读 jQuery 源码,理解其设计思想、设计模式,你将会颇有收获。 2、jQuery DOM 操作设计思想jQuery 的基本设计思想和主要用法,就是”选择某个网页元素,然后对其进行某种操作”。使用 jQuery 的第一步,往往就是将一个选择表达式,放进构造函数 jQuery()(简写为\\$),得到被选中的元素,选中的元素可能是一个,也可能是多个。第二步就是对这些元素进行一系列操作,例如添加 class、移除 class、取值和赋值、移动等。 jQuery 的一大特点就是支持链式操作,即类似于这样$('div').find('h3').eq(2).html('Hello');,将一系列操作连接在一起。它的原理在于每一步的 jQuery 操作,返回的都是一个 jQuery 对象,所以不同操作可以连在一起。 3、自己实现一个简单的 jQuery123456789101112131415161718192021222324252627282930313233window.jQuery = function(nodeOrSelector) { var nodes = {} if (typeof nodeOrSelector === 'string') { var nodeList = document.querySelectorAll(nodeOrSelector) nodeList.forEach(function(item, index) { nodes[index] = item }) nodes.length = nodeList.length } else if (nodeOrSelector instanceof Node) { nodes = { '1': nodeOrSelector, lenght: 1 } } nodes.addClass = function(classNames) { for (var i = 0; i < nodes.length; i++) { classNames.forEach(function(item) { nodes[i].classList.add(item) }) } } nodes.setText = function(text) { for (var i = 0; i < nodes.length; i++) { nodeList[i].innerHTML = text } } return nodes}// aliaswindow.$ = jQuery// 使用$('ul>li').addClass(['red', 'blue'])$('ul>li').setText('Hello jQuery') 以上是本人实现的一个简单的 jQuery 对象。该对象接收一个参数,可以是一个已经获取到的 DOM 对象,也可以是一个选择器字符串。jQuery 方法返回的是一个自定义的节点对象,该对象上定义了 addClass、setText 等一系列操作方法。 4、jQuery 获取 DOM 和 JS 选择器获取的 DOM 的区别与联系例如: 1<div id=\"x\"></div> 12var div = document.getElementById('x')var $div = $('#x') div 是由原生 API 获取的元素节点对象, div.__proto__ === HTMLDivElement.prototype div.__proto__.__proto__ === HTMLElement .prototype \\$div 是 jQuery 对象实例,它包含了从 jQuery 继承过来的很多方法和属性$div.__proto__ === jQuery.prototype $div.__proto__ .__proto__ === Object.prototype div 变成 \\$div:$(div) \\$div 变成 div:$div[0] === div","categories":[],"tags":[{"name":"Javascript","slug":"Javascript","permalink":"http://blog.yancongwen.com/tags/Javascript/"}]},{"title":"跨域及常见解决方案","slug":"跨域及常见解决方案","date":"2017-10-16T13:12:00.000Z","updated":"2019-04-16T13:13:19.205Z","comments":true,"path":"2017/10/16/跨域及常见解决方案/","link":"","permalink":"http://blog.yancongwen.com/2017/10/16/跨域及常见解决方案/","excerpt":"","text":"跨域及常见解决方案 本文主要讲解什么是跨域以及跨域的解决方案 一、什么是跨域 同源策略为了安全起见,浏览器必须保证只有 协议+域名+端口 一模一样才允许发 AJAX 请求。 二、跨域的解决方案跨域的解决方案很多,最常见的解决方案有:CORS、JSONP、代理转发、WebSocket。 1、CORSCORS 是当前适用场景最广泛,也是最新、最受推崇的一种跨域解决方案。主要操作就是在服务端的响应头中设置 Access-Control-Allow-Origin。 2、JSONPJSOP 是一种传统的跨域解决方案,有一定局限性(仅支持 GET 请求),目前很少使用。 首先,有些 html 标签(form、a、img、link、script)可以实现 get 请求,例如: 123456789101112button.addEventListener('click', e => { let image = document.createElement('img') image.src = '/pay' image.onload = function() { // 状态码是 200~299 则表示成功 alert('成功') } image.onerror = function() { // 状态码大于等于 400 则表示失败 alert('失败') }}) JSONP 基本原理: 请求方创建 script,src 指向响应方,同时传一个查询参数 ?callbackName=yyy 响应方根据查询参数 callbackName,构造形如这样的响应 “yyy.call(undefined, data)” “yyy(data)” 浏览器接收到响应,就会执行 yyy.call(undefined, data) 那么请求方就知道了他要的数据 下面是实现代码: 1234567891011121314151617181920212223242526272829button.addEventListener('click', e => { let functionName = 'frank' + parseInt(Math.random() * 10000000, 10) window[functionName] = function() { // 每次请求之前搞出一个随机的函数 amount.innerText = amount.innerText - 0 - 1 } let script = document.createElement('script') script.src = '/pay?callback=' + functionName document.body.appendChild(script) script.onload = function(e) { // 状态码是 200~299 则表示成功 e.currentTarget.remove() delete window[functionName] // 请求完了就干掉这个随机函数 } script.onerror = function(e) { // 状态码大于等于 400 则表示失败 e.currentTarget.remove() delete window[functionName] // 请求完了就干掉这个随机函数 }})//后端代码if (path === '/pay') { let callbackName = query.callback response.setHeader('Content-Type', 'application/javascript') response.write(` ${callbackName}.call(undefined, 'success') `) response.end()} 3、使用 Nginx(或其它服务器)转发 使用 Nginx(或其它服务器)将请求代理转发可以很方便解决跨域问题。它的原理也是基于CORS的。首先,我们应该明白,出现跨域问题是浏览器的安全机制引起的,浏览器拒绝请求非同源 URL。对于服务端程序是没有这个限制的。所以我们可以通过一个轻便的服务器(常用 Nginx)对非同源请求进行转发,转发的同时设置响应头 Access-Control-Allow-Origin 的值为 * 或者目标地址,这样浏览器直接请求 Nginx 代理转发后的 URL 地址就不会出现跨域报错。 使用这种方式解决跨域问题的一个优势就是不需要更改服务端代码,缺点就是增加了网络请求的复杂度和流程,会影响性能,也增加了系统部署复杂度。 下面给出 Nginx 反向代理的一个简单配置。 12345678910111213141516171819202122server { listen 8091; server_name localhost; location / { # 没有配置OPTIONS的话,浏览器如果是自动识别协议(http or https),那么浏览器的自动OPTIONS请求会返回不能跨域 if ( $request_method = OPTIONS ) { add_header 'Access-Control-Allow-Origin' "$http_origin"; add_header 'Access-Control-Allow-Methods' "POST, GET, PUT, OPTIONS, DELETE"; add_header 'Access-Control-Max-Age' "3600"; add_header 'Access-Control-Allow-Headers' "Origin, X-Requested-With, Content-Type, Accept, Authorization"; add_header 'Access-Control-Allow-Credentials' "true"; add_header 'Content-Length' 0; add_header 'Content-Type' text/plain; return 200; } add_header 'Access-Control-Allow-Origin' '$http_origin'; add_header 'Access-Control-Allow-Credentials' 'true'; add_header 'Access-Control-Allow-Methods' 'GET, PUT, POST, DELETE, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'Content-Type,*'; proxy_pass http://127.0.0.1:8888; }} 上面配置中,$http_origin 是 nginx 中的一个默认变量,其值为请求方的地址。上述配置中,实际的服务地址为 http://127.0.0.1:8888,转发后的地址为 http://127.0.0.1:8091,前端直接请求 http://127.0.0.1:8091 即可。下面为请求结果: 4、WebSocket5、Chrome 插件:Allow-Control-Allow-Origin: *该插件可谓是前端开发利器,可帮助我们在开发中临时解决跨域问题。该插件实现跨域的机制是:利用 CHrome 浏览器开发接口,拦截了每次的 HTTP 请求,并修改了请求头和响应头信息,主要是添加了 Access-Control-Allow-Origin: *。基本原理是 欺骗浏览器:在 Option 预请求中,将响应头中的Access-Control-Allow-Origin篡改为你发请求的客户端地址,这样就可以通过浏览器的校验; 欺骗服务器:在真实请求中,将请求头中的 Origin 修改为服务器允许的地址,这样就可以通过服务端的校验; 关于该插件的详细机制和原理可以查看这里。类似改功能的插件还有几个,可以自己在谷歌应用点查找。 参考 浏览器同源政策及其规避方法 —— 阮一峰 跨域资源共享 CORS 详解 —— 阮一峰 enable cross-origin resource sharing","categories":[],"tags":[{"name":"Javascript","slug":"Javascript","permalink":"http://blog.yancongwen.com/tags/Javascript/"},{"name":"总结","slug":"总结","permalink":"http://blog.yancongwen.com/tags/总结/"}]},{"title":"CSS-Sticky-Footer","slug":"CSS-Sticky-Footer","date":"2017-07-28T11:21:00.000Z","updated":"2019-03-28T06:16:34.335Z","comments":true,"path":"2017/07/28/CSS-Sticky-Footer/","link":"","permalink":"http://blog.yancongwen.com/2017/07/28/CSS-Sticky-Footer/","excerpt":"","text":"什么是 Sticky Footer  Sticky Footer 即绝对底部。具体效果就是:当页面内容超出屏幕,页脚模块会像正常页面一样,被推到内容下方,需要拖动滚动条才能看到。而当页面内容小于屏幕高度,页脚模块会固定在屏幕底部,就像是底边距为零的固定定位。 常见解决方法 经典思路 HTML: 12345678<div class="wrap"> <div class="content"> <p>内容</p> </div></div><div class="footer"> <p>页脚</p></div> CSS: 12345678910111213141516html,body { height: 100%;}body > .wrap { min-height: 100%;}.content { /* padding-bottom 等于 footer 的高度 */ padding-bottom: 60px;}.footer { width: 100%; height: 60px; /* margin-top 为 footer 高度的负值 */ margin-top: -60px;} Flexbox解决方案 HTML: 123456<div class="content"> <p>内容</p></div><div class="footer"> <p>页脚</p></div> CSS: 12345678html, body { display: flex; height: 100%; flex-direction: column;}body .content { flex: 1;} 固定高度的解决方案(不推荐) HTML: 1234<body> <div class="content"></div> <div class="footer"></div></body> CSS: 1234.content{ min-height:calc(100vh-footer的高度); box-sizing:border-box;}","categories":[],"tags":[{"name":"CSS","slug":"CSS","permalink":"http://blog.yancongwen.com/tags/CSS/"}]},{"title":"CSS定位","slug":"CSS定位-1","date":"2017-07-28T11:15:00.000Z","updated":"2019-03-28T06:16:34.335Z","comments":true,"path":"2017/07/28/CSS定位-1/","link":"","permalink":"http://blog.yancongwen.com/2017/07/28/CSS定位-1/","excerpt":"","text":"CSS定位1. 默认值 (static) 没有定位,元素出现在正常的流中 会忽略 top, bottom, left, right 或者 z-index 声明 2. 相对定位(relative) 相对定位,就是微调元素位置的。让元素相对自己原来的位置,进行位置调整 相对定位不脱标,真实位置是在老家,只不过影子出去了,可以到处飘 使用场景: 微调元素 做绝对定位的参考,子绝父相 3. 绝对定位 (absolute) 绝对定位比相对定位更灵活 脱离标准文档流,所有标准文档流的性质,绝对定位之后都不遵守了(绝对定位之后,标签就不区分所谓的行内元素、块级元素了,不需要 display:block; 就可以设置宽、高了) 参考点 绝对定位的参考点,如果用top描述,那么定位参考点就是页面的左上角,而不是浏览器的左上角 如果用bottom描述,那么就是浏览器首屏窗口尺寸,对应的页面的左下角 以盒子为参考点 一个绝对定位的元素,如果父辈元素中出现了也定位了的元素(static除外),那么将以父辈这个元素,为参考点。 要听最近的已经定位的祖先元素的,不一定是父亲,可能是爷爷。 不一定是相对定位,任何定位,都可以作为参考点。子绝父绝、子绝父相、子绝父固,都是可以给儿子定位的。但是,工程上子绝父绝,没有一个盒子在标准流里面了,所以页面就不稳固,没有任何实战用途。工程上,“子绝父相”才有意义,父亲没有脱标,儿子脱标在父亲的范围里面移动。 绝对定位的儿子,无视参考的那个盒子的padding。 绝对定位的盒子居中问题 绝对定位之后,所有标准文档流的规则,都不适用了。所以margin:0 auto; 失效。可使用以下方式使之居中。 12left: 50%;margin-left: 负的盒子宽度的一半。 4. 固定定位 (fixed) 固定定位,就是相对浏览器窗口定位。无论页面如何滚动,这个盒子显示的位置都不变。 脱标 5. 粘性定位 (sticky) 基于用户滚动的位置定位 它的行为就像 position:relative;,而当页面滚动超出目标区域时,它的表现就像 position:fixed;,它会固定在目标位置 目前还是实验属性 常见的一种使用场景是绝对底部(Sticky Footer)","categories":[],"tags":[{"name":"CSS","slug":"CSS","permalink":"http://blog.yancongwen.com/tags/CSS/"}]},{"title":"Hexo搭建博客","slug":"Hexo搭建博客-2","date":"2017-07-16T03:39:00.000Z","updated":"2019-03-28T06:16:34.335Z","comments":true,"path":"2017/07/16/Hexo搭建博客-2/","link":"","permalink":"http://blog.yancongwen.com/2017/07/16/Hexo搭建博客-2/","excerpt":"","text":"本文主要记录一下本人使用 hexo + github 搭建个人博客的流程及hexo使用技巧和相关辅助工具。 1.Hexo搭建博客1.1 Hexo介绍Hexo 是一个快速、简洁且高效的博客框架。Hexo 使用 Markdown(或其他渲染引擎)解析文章,可利用靓丽的主题生成静态网页。Hexo可以托管到github等代码托管平台上,开启page功能就可以访问。 1.2 第一篇博客 安装hexo 1$ npm install -g hexo-cli 新建项目 123$ mkdir filename && cd filename // 创建并进入一个目录$ hexo init // 初始化$ hexo install // 安装依赖包 生成页面 1$ hexo generate 部署 1$ hexo server 第一篇博客就搭建完成了。访问http://localhost:4000 就可看到你的博客 1.3 github部署将生成的博客页面部署到github上就可以让别人访问了。 新建github仓库,仓库名要和用户名一样 修改配置文件_config.yml,如下,设置github仓库的ssh地址(可以同时部署到码云gitee等其他平台上) 123456deploy: type: git repo: github: git@github.com:username/username.github.io.git gitee: git@gitee.com:username/username.git branch: master 访问 http://username.github.io.就是你的博客了 还可以设置自定义域名 1.4 主题设置hexo提供了很多主题,更换主题也比较简单 第一步,克隆主题到本地 themes目录下 第二步,修改配置文件_config.yml下的them项就好了 1.5 使用流程hexo写博客的一般流程是下面这样的:1234hexo new title // 新建文章 hexo g // 文章写好之后生成页面hexo s // 开启本地服务器来预览页面hexo d // 部署 2 开启HTTPS近年来Google一直在力推HTTPS加密协议,替换HTTP协议,Chrome浏览器68版本更是直接将所有的HTTP页面都标上“不安全”标签。虽然个人博客不会涉及账号密码登录泄露等问题,但为了获取浏览器的信任,开启HTTPS还是很有必要的。HTTPS协议要求整个页面所引用的和加载的资源必须也为HTTPS协议方可。 Github开启HTTPS直接在博客仓库下的设置项中找到 Enforce HTTPS选项打钩即可 申请免费SSL证书如果绑定了个人域名,需要申请SSL证书。腾讯云里可以申请免费的证书。 七牛云开启HTTPS七牛云用于存储博客的图片、视频等资源。将申请的SSL证书导入七牛云,然后开启HTTPS服务。 3 Hexo-admin管理博客hexo-admin是一款帮助我们管理文章的插件,比较实用。如下所示,只要我们跑起来server,就可以访问http://localhost:4000/admin/了,这个页面就是我们管理文章的页面了。不过deploy部署命令有点问题。123npm install --save hexo-adminhexo server -dopen http://localhost:4000/admin/ 4 多终端使用有时候可能想在不同的电脑写文章,所以我们需要将博客的生成代码做一下备份。「你的用户名.github.io」上保存的只是你的博客,并没有保存「生成博客的程序代码」,你需要再创建一个名为 blog-generator 的空仓库,用来保存「生成博客的程序代码」。以后每次 hexo deploy 完之后,博客就会更新;然后你还要要 add / commit /push 一下「生成博客的程序代码」,将源文件也更新一下。这样你在另一台电脑上就可以clone这份源码,同时部署相同的环境(node、git、hexo、hexo-admin),就可以写代码了。","categories":[],"tags":[{"name":"工具","slug":"工具","permalink":"http://blog.yancongwen.com/tags/工具/"}]},{"title":"HTML标签","slug":"HTML标签","date":"2017-04-13T11:46:00.000Z","updated":"2019-03-28T06:16:34.335Z","comments":true,"path":"2017/04/13/HTML标签/","link":"","permalink":"http://blog.yancongwen.com/2017/04/13/HTML标签/","excerpt":"","text":"1、iframe 标签iframe 元素会创建包含另外一个文档的内联框架,相当于是一个独立的新窗口; src 属性,窗口对应的链接地址,支持相对路径; frameborder 属性,一般会设置 frameborder=0,表示没有边框; name 属性,框架名称,一般配合 a 标签使用;12<iframe name=\"xxx\" src=\"http://www.baidu.com\" frameborder=\"0\"></iframe><a href=\"http://www.qq.com\" target=\"xxx\">在iframe中打开QQ页面</a> 2、a 标签定义超链接 href 属性 ,指示链接的目标 常规:href="http://www.qq.com" 省略协议,就会使用当前协议:href="www.qq.com" 相对路径:href="/index.html" 空值:href=""(会请求当前页面,极少使用) 查询参数:href="?name=xxx"(会触发请求) 锚点:href="#top"(只有锚点的话不会触发请求) href="#",空锚点,会指向页面顶部; 关于锚点定位,更多知识可以看张鑫旭文章《URL锚点HTML定位技术机制、应用与问题》 javascript伪协议:href="javascript: js代码段" href="javascript: ;",这种方法是很多网站最常用的方法,也是最周全的方法,既不会做请求、不会跳转页面,也不会改变当前页面位置,还能达到想要的效果; href="javascript: void(0);",和上面这种用法类似,类似的还有一些,请看这里; 在浏览器地址栏输入 “javascript: alert(‘hello’)” 同样会执行js代码; W3C标准不推荐在href里面执行javascript语句; target 属性,指定窗口 _blank:新窗口 _self:当前,默认值 _parent:父框架 _top:最顶层,即在整个窗口中打开被链接文档 framename:指定 iframe 内联框架名称 download 属性,规定被下载的超链接目标 href 属性中就是下载内容的地址 download 属性值就是下载文件的文件名1<a href="http://img.yancongwen.cn/18-4-12/34996969.jpg" download="ycw.jpg">下载图片</a> 3、from 标签表示了文档中的一个区域,这个区域包含有交互控制元件,用来向服务器提交信息。;表单能够包含 input 元素,比如文本字段、复选框、单选框、提交按钮等; action 属性,URL值,绝对路径或相对路径,规定当提交表单时向何处发送表单数据 method 属性,规定用于发送 form-data 的 HTTP 方法,只能是 GET 或者 POST,其它方法无效(a 标签只能用 GET) POST (最常用)POST 请求会将数据包含在 HTTP 请求体的第四部分中 GET (一般不用)GET 请求会将数据以参数的形式附在请求URL后面 target 属性,和 a 标签中用法相同 name 属性, 其他注意点: form 标签中 input 标签必需要有 name 属性值,否则对应数据不会被提交; form 标签中必须要有一个 submit 按钮才能被提交,正是因为有 submit 按钮,才能按 回车 提交表单; 如果一个 form 标签中没有 submit 按钮 <input type='submit'>, 而有 <button></button>,则 button 会默认为 submit 按钮,若写成这样 <button type='button'>button</button>” 则不会成为 submit; form 提交会刷新页面; 4、input\\button 标签 label 的两种用法,推荐第二种 12<label id=\"name\">名字:</label><input type=\"text\" name=\"xxx\" for=\"name\"><label>名字:<input type=\"text\" name=\"xxx\" for=\"name\"></label> button type 属性 submit, 提交表单数据,表单中未指定时,此值为默认值; reset,重置所有组件为初始值; button,没有默认行为; menu,打开一个由指定 <menu\\> 元素进行定义的弹出菜单; input type 属性 text,默认 button、reset、submit checkbox、radio date、datetime、datetime-local、month、time、week color file number password email range serch url tel hidden name 属性,必须有 详细细节请看 MDN 文档 input 标签的 button、reset、submit 按钮和 button 标签区别主要在于 input 标签只支持文本内容,不能再包含其他标签,但是 button 标签可以。button 比 input 更容易使用样式。 5、table 标签 过去 table 常用语页面布局,现在不推荐,专业的事情交给专业的人来做; 应当使用 CSS 定制 table 样式的样式,不推荐使用table标签中的属性设置(诸如:align、border、bgcolor、cellpadding、cellspacing 等不在推荐使用); thead tbody tfoot不写浏览器也不会报错,而会自动补全; colgroup 用于定义数据列的样式1234567891011121314151617181920212223242526272829<table border=\"1\"> <colgroup> <col width=\"100\"> <col bgcolor=\"red\" width=\"200\"> <col width=\"100\"> <col width=\"100\"> </colgroup> <thead> <tr> <th>姓名</th><th>数学</th><th>语文</th><th>总分</th> </tr> </thead> <tbody> <tr> <th>小明</th><td>100</td><td>100</td><td>200</td> </tr> <tr> <th>小白</th><td>100</td><td>99</td><td>199</td> </tr> <tr> <th>小黑</th><td>99</td><td>99</td><td>198</td> </tr> </tbody> <tfoot> <tr> <th>平均分</th><td>100</td><td>99</td><td>199</td> </tr> </tfoot></table>","categories":[],"tags":[{"name":"HTML","slug":"HTML","permalink":"http://blog.yancongwen.com/tags/HTML/"}]},{"title":"计算机字符编码及JavaScript编码方式","slug":"计算机字符编码及JavaScript编码方式","date":"2017-03-30T17:00:00.000Z","updated":"2019-03-28T06:16:34.351Z","comments":true,"path":"2017/03/31/计算机字符编码及JavaScript编码方式/","link":"","permalink":"http://blog.yancongwen.com/2017/03/31/计算机字符编码及JavaScript编码方式/","excerpt":"","text":"一、 计算机存储1. 位(bit)计算机内部,所有信息最终都是一个二进制值。每一个二进制位(bit)有0和1两种状态.bit是计算机内存数据存储的最小单位,也称为比特,可简写为b。计算机中的CPU位数指的是CPU一次能处理的最大位数。 2. 字节(byte)八个二进制位称为一个字节,即 1byte = 8bit,1字节最多可以表示 2^8 = 256 个字符。 3. 字符计算机中使用的字母、数字、汉字、符号等。在计算机中,采用不同的字符集、不同的编码方式,一个字符占用的存储空间是不同的。 在 ASCII 编码中,一个英文字母字符存储需要1个字节; 在 GB2312 编码或 GBK 编码中,一个汉字字符存储需要2个字节; 在 UTF-8 编码中,一个英文字母字符存储需要1个字节,一个汉字字符储存需要3到4个字节; 在 UTF-16 编码中,一个英文字母字符或一个汉字字符存储都需要2个字节(Unicode扩展区的一些汉字存储需要4个字节); 在UTF-32编码中,世界上任何字符的存储都需要4个字节。 二、 字符编码1. ASCII American Standard Code for Information Interchange 美国字符集,主要用于显示现代英语和其他西欧语言,共128个字符; 最通用的单字节编码系统; 1字节 2. GB2312、GBK GB2312是中国国标字符集,规定了6763个中文、682个拉丁、希腊字母等, 微软在GB基础上拓展出了GBK; 2字节 3. Unicode 全世界所有的字符集合,目前,Unicode的最新版本是7.0版,一共收入了109449个符号; 包含17个平面: 1个基本面(2^16=65536个字符,范围为 U+0000 ~ U+FFFF),16个辅助平面(U+010000 ~ U+10FFFF); 三、编码方法Unicode只规定了每个字符的码点,到底用什么样的字节序表示这个码点,就涉及到编码方法。 1. UTF-32 4个字节表示一个字符; 与Unicode编码一一对应,比如,字母a为0x00000061; Pro:查找效率高,时间复杂度o(1) Con:浪费空间,比相同ASCII编码大四倍 2. UTF-8 变长编码方法,1~4个字节,越是常用的字符,字节越短,最前面的128个字符,只使用1个字节表示,与ASCII码完全相同; 节省存储空间; 由于它节省空间的特性,使用最多; 编号范围 字节 0x0000 - 0x007F 1 0x0080 - 0x07FF 2 0x0800 - 0xFFFF 3 0x010000 - 0x1FFFF 4 3. UTF-16 变长编码,2或4个字节; 基本平面的字符占用2个字节(U+0000 ~ U+FFFF),辅助平面的字符占用4个字节(U+010000 ~ U+10FFFF) 四、JavaScript采用哪种? Unicode字符集 UCS-2编码 2个字节,且字符不全; JS被发明的时候只有UCS-2,没有UTF; 后来UTF-16取代了UCS-2,或者说UCS-2整合进了UTF-16,所以,现在只有UTF-16,没有UCS-2; 所以说JS采用的是UTF-16编码方式; 带来的问题: 由于JavaScript只能处理UCS-2编码,造成所有字符在这门语言中都是2个字节,如果是4个字节的字符,会当作两个双字节的字符处理。JavaScript的字符函数都受到这一点的影响,无法返回正确结果; ES6中增强了Unicode支持,基本解决了上述问题; 参考: 阮一峰:字符编码笔记:ASCII,Unicode和UTF-8 阮一峰:Unicode与JavaScript详解","categories":[],"tags":[{"name":"计算机基础","slug":"计算机基础","permalink":"http://blog.yancongwen.com/tags/计算机基础/"}]},{"title":"Hello World","slug":"hello-world","date":"2017-01-14T17:00:00.000Z","updated":"2019-03-28T06:16:34.351Z","comments":true,"path":"2017/01/15/hello-world/","link":"","permalink":"http://blog.yancongwen.com/2017/01/15/hello-world/","excerpt":"","text":"Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub. Quick StartCreate a new post1$ hexo new \"My New Post\" More info: Writing Run server1$ hexo server More info: Server Generate static files1$ hexo generate More info: Generating Deploy to remote sites1$ hexo deploy More info: Deployment","categories":[],"tags":[]}]}