liaoyf的博客

一分耕耘一分收获


  • 首页

  • 标签

  • 归档

  • 关于

  • 搜索

CSSDOM简介

发表于 2018-12-13 | 阅读次数

什么是CSSOM

CSS对象模型是一组允许从JavaScript处理CSS的API。它很像DOM,但对于CSS而不是HTML。它允许用户动态地读取和修改CSS样式。

通过element.style获取和设置内联样式

使用JavaScript操作或访问CSS属性和值的最基本方法是通过元素的style属性进行操作:

1
2
3
document.body.style.background = 'lightblue';
document.body.style.width = '888px';
document.body.style.cssFloat = 'left';

这是使用JavaScript操作或获取CSS属性和值的简单方法。但是style以这种方式使用属性需要注意:这只适用于元素的内联样式。

如果元素没有定义内联样式,即时有一个外部样式文件,获取不存在的样式属性时将得不到任何东西:

1
console.log(document.body.cssFloat) // ""

这显然有一些显着的局限性,所以让我们看一些使用JavaScript阅读和操作样式的更有用的技术。

获取计算样式

可以使用以下window.getComputedStyle()方法读取元素上任何CSS属性的计算CSS值:

1
2
3
4
widnow.getComputedStyle(document.body).background
// "rgba(0, 0, 0, 0) none repeat scroll 0% 0% / auto padding-box border-box"
getComputedStyle(document.body).width
// "134px"

计算属性将会得到最终的样式结果,包括浏览器默认的样式,这可能会得到太多东西了,(⊙o⊙)…!

有几种不同的方法可以使用window.getComputedStyle():

1
2
3
4
5
6
7
8
// dot notation, same as above
window.getComputedStyle(el).backgroundColor;

// square bracket notation
window.getComputedStyle(el)['background-color'];

// using getPropertyValue()
window.getComputedStyle(el).getPropertyValue('background-color');

获取伪元素的计算样式

一个鲜为人知的小问题window.getComputedStyle()是,它允许您检索伪元素的样式信息。你会经常看到这样的window.getComputedStyle()声明:

1
window.getComputedStyle(document.body, ":before").width;

第二个可选参数允许我指定我正在访问伪元素的计算CSS。

CSSStyleDeclaration

不管是通过style还是window.getComputedStyle,它返回的都是CSSStyleDeclaration对象,后者是只读的。

setProperty(), getPropertyValue() 和 item()

考虑如下代码:

1
2
3
4
5
6
let box = document.querySelector('.box');

box.style.setProperty('color', 'orange');
box.style.setProperty('font-family', 'Georgia, serif');
op.innerHTML = box.style.getPropertyValue('color');
op2.innerHTML = `${box.style.item(0)}, ${box.style.item(1)}`;
  • setProperty(css属性名,css属性值)

  • getPropertyValue(css属性名)

  • item(index):上图item(0)为color、item(1)为font-family,item(2)如果没有的话返回””

removeProperty()

1
2
3
4
5
box.style.setProperty('font-size', '1.5em');
box.style.item(0) // "font-size"

document.body.style.removeProperty('font-size');
document.body.style.item(0); // ""

获取和设置属性的优先级

1
2
3
4
5
box.style.setProperty('font-family', 'Georgia, serif', 'important');
box.style.setProperty('font-size', '1.5em');

box.style.getPropertyPriority('font-family'); // important
op2.innerHTML = box.style.getPropertyPriority('font-size'); // ""

当setProperty增加第三个参数important时,font-family将会有!important权重。

我要强调的是,这些方法可以与已经直接放在HTML元素style属性上的任何内联样式一起使用。所以,如果我有以下HTML:

1
<div class="box" style="border: solid 1px red !important;">

其他子属性也将会有!important权重。

1
2
3
4
5
6
// These all return "important"
box.style.getPropertyPriority('border'));
box.style.getPropertyPriority('border-top-width'));
box.style.getPropertyPriority('border-bottom-width'));
box.style.getPropertyPriority('border-color'));
box.style.getPropertyPriority('border-style'));

CSSStyleSheet接口

从样式表访问信息的最简单方法是使用document开放的styleSheets属性,例如,下面的行使用该length属性来查看当前页面具有多少样式表:

1
document.styleSheets.length; // 1

我可以使用从零开始的索引来引用任何文档的样式表:

1
document.styleSheets[0];

它将返回CSSStyleSheet对象,里面最有用的是cssRules属性,此属性提供该样式表中包含的所有CSS规则(包括声明块,规则,​​媒体规则等)的列表。在下面的部分,我们将详细介绍该API的操作。

cssRules(CSSRuleList) -> (CSSStyleRule) -> type, cssText, selectorText, style(CSSStyleDeclaration),…

使用样式表对象

下面使用样式表对象遍历选择器:

1
2
3
4
5
6
7
8
let myRules = document.styleSheets[0].cssRules,
p = document.querySelector('p');

for (i of myRules) {
if (i.type === 1) {
p.innerHTML += `<c​ode>${i.selectorText}</c​ode><br>`;
}
}

注意:cssRules是对象。

type类型:

  • 1: STYLE_RULE
  • 3: IMPORT_RULE
  • 4: MEDIA_RULE
  • 5: KEYFRAMES_RULE

完整类型请参考:https://developer.mozilla.org/en-US/docs/Web/API/CSSRule#Type_constants

其中selectorText是可写属性,所以我们可以动态修改:

1
2
3
4
5
6
7
for (i of myRules) {
if (i.type === 1) {
if (i.selectorText === 'a:hover') {
i.selectorText = 'a:hover, a:active';
}
}
}

使用CSSOM访问@media规则

假如有如下属性:

1
2
3
4
5
6
7
8
9
10
@media (max-width: 800px) {
body {
line-height: 1.2;
}

.component {
float: none;
margin: 0;
}
}

可以通过conditionText获取:

1
2
3
4
5
6
7
8
9
let myRules = document.styleSheets[0].cssRules,
p = document.querySelector('.output');

for (i of myRules) {
if (i.type === 4) {
p.innerHTML += `<c​ode>${i.conditionText}</c​ode><br>`;
// (max-width: 800px)
}
}

使用CSSOM访问@keyframes规则

假如有如下属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
@keyframes exampleAnimation {
from {
color: blue;
}

20% {
color: orange;
}

to {
color: green;
}
}

可以通过keyText获取:

1
2
3
4
5
6
7
8
9
for (i of myRules) {
if (i.type === 7) {
for (j of i.cssRules) {
p.innerHTML += `<code>${j.keyText}</code><br>`;
}
}
}

// 0% 20% 100%

您会注意到我的原始CSS使用from并to作为第一个和最后一个关键帧,但keyText属性计算这些0%和100%。keyText也可以设置值。在我的示例样式表中,我可以像这样硬编码:

1
2
3
4
5
6
7
8
// Read the current value (0%)
document.styleSheets[0].cssRules[6].cssRules[0].keyText;

// Change the value to 10%
document.styleSheets[0].cssRules[6].cssRules[0].keyText = '10%'

// Read the new value (10%)
document.styleSheets[0].cssRules[6].cssRules[0].keyText;

访问@keyframes规则时可用的另一个属性是name:

1
2
3
4
5
6
7
8
9
10
let myRules = document.styleSheets[0].cssRules,
p = document.querySelector('.output');

for (i of myRules) {
if (i.type === 7) {
p.innerHTML += `<c​ode>${i.name}</c​ode><br>`;
}
}

// exampleAnimation

我在这里要提到的最后一件事是能够获取单个关键帧内的特定样式。这是一些带有演示的示例代码:

1
2
3
4
5
6
7
8
9
10
let myRules = document.styleSheets[0].cssRules,
p = document.querySelector('.output');

for (i of myRules) {
if (i.type === 7) {
for (j of i.cssRules) {
p.innerHTML += `<c​ode>${j.style.color}</c​ode><br>`;
}
}
}

增加或删除CSSDeclarations规则

通过insertRule()增加规则:

1
2
3
4
5
let myStylesheet = document.styleSheets[0];
console.log(myStylesheet.cssRules.length); // 8

document.styleSheets[0].insertRule('article { line-height: 1.5; font-size: 1.5em; }', myStylesheet.cssRules.length);
console.log(document.styleSheets[0].cssRules.length); // 9

insertRule()第一个参数是样式字符串,第二个参数是表示您希望插入规则的位置或索引。如果未包括此项,则默认为0(意味着规则将插入规则集合的开头)。如果索引恰好大于rules对象的长度,则会抛出错误。

通过deleteRule()删除规则:

1
2
3
4
5
let myStylesheet = document.styleSheets[0];
console.log(myStylesheet.cssRules.length); // 8

myStylesheet.deleteRule(3);
console.log(myStylesheet.cssRules.length); // 7

该方法接受一个参数,该参数表示我要删除的规则的索引,参数传入的索引值必须小于cssRules对象的长度,否则将引发错误。

重新访问CSSStyleDeclaration API

记住,CSSRule下的style属性返回CSSStyleDeclaration属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Grab the style rules for the body and main elements
let myBodyRule = document.styleSheets[0].cssRules[1].style,
myMainRule = document.styleSheets[0].cssRules[2].style;

// Set the bg color on the body
myBodyRule.setProperty('background-color', 'peachpuff');

// Get the font size of the body
myBodyRule.getPropertyValue('font-size');

// Get the 5th item in the body's style rule
myBodyRule.item(5);

// Log the current length of the body style rule (8)
myBodyRule.length;

// Remove the line height
myBodyRule.removeProperty('line-height');

// log the length again (7)
myBodyRule.length;

// Check priority of font-family (empty string)
myBodyRule.getPropertyPriority('font-family');

// Check priority of margin in the "main" style rule (!important)
myMainRule.getPropertyPriority('margin');

CSSDOM的未来?

在我在本文中考虑过的所有内容之后,我不得不打破这样的消息,即有一天我们所知道的CSSOM可能已经过时了。

这是因为称为CSS Typed OM的东西,它是Houdini项目的一部分。虽然有些人已经注意到新的Typed OM与目前的CSSOM相比更加冗长,但Eric Bidelman在本文中概述的好处包括:

  • 更少的错误
  • 算术运算和单位转换
  • 更好的性能
  • 错误处理
  • CSS属性名称始终是字符串

有关这些功能的完整详细信息和语法的一瞥,请务必查看完整的文章。

在撰写本文时,CSS Typed OM仅在Chrome中受支持。

2018年学习计划(js原理篇)

发表于 2018-11-01 | 阅读次数

Javascript是单线程单一并发语言,这意味着它可以一次处理一个任务或一次处理一段代码。它有一个单独的调用堆栈,它与堆之类的其他部分一起构成了Javascript并发模型(在V8内部实现)。我们首先来看看这些术语:

demo

下面是我总结的一张图片:

demo

这里我展开讲,如果需要详细了解请参阅下面参考网址。

下面以一道面试题展开:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Foo() {
getName = function () { alert (1); };
return this;
}
Foo.getName = function () { alert (2);};
Foo.prototype.getName = function () { alert (3);};
var getName = function () { alert (4);};
function getName() { alert (5);}

//请写出以下输出结果:
getName();
Foo().getName();
getName();

执行上下文

代码开始,引擎创建全局执行上下文:

  1. 创建阶段

    • ThisBinding: 这边的this为window对象
    • 词法环境:
      • 环境记录:
        • Foo:
        • getName:
      • 外部环境引用:null
    • 变量环境:
      • 环境记录:
        • getName: undefined
      • 外部环境应用:

正是由于javascript引擎会在执行上下文之前的创建阶段(即变量提升),所以代码实际上是变成如下:

1
2
3
4
5
6
7
8
9
10
function Foo() {
getName = function () { alert (1); };
return this;
}
var getName;//只提升变量声明
function getName() { alert (5);}//提升函数声明,覆盖var的声明

Foo.getName = function () { alert (2);};
Foo.prototype.getName = function () { alert (3);};
getName = function () { alert (4);};//最终的赋值再次覆盖function getName声明

所以第一问的答案输出为4。

  1. 执行阶段

调用堆栈

假设当开始执行到Foo().getName()时,开始进入Foo函数,把Foo函数压入调用堆栈顶部。创建Foo局部执行上下文创建阶段:

  • ThisBinding: 由于这边没有对象调用,故为this为window对象
  • 词法环境:
    • 环境记录:
    • 外部环境引用:
  • 变量环境:
    • 环境记录:
      • getName: function(){ alert(4); }
    • 外部环境应用:

return this返回时,Foo函数弹出栈顶,Foo局部执行上下文销毁。

故执行时第二问答案输出1;第三问因为getName已被第二问中修改,故输出1。

事件循环

事件循环是指主线程重复从消息队列中取消息、执行的过程。如果遇到同步函数,直接压入调用栈,如果异步代码,则委托给事件队列,然后继续运行代码,当异步处理返回时压入事件队列,主线程在执行完当前循环中的所有代码后,就会到消息队列取出这条消息,并执行它:

demo

参考网址

  • https://tylermcginnis.com/javascript-visualizer/?code=function%20Foo%28%29%20%7B%0A%20%20%20%20getName%20%3D%20function%20%28%29%20%7B%20alert%20%281%29%3B%20%7D%3B%0A%20%20%20%20return%20this%3B%0A%7D%0AFoo.getName%20%3D%20function%20%28%29%20%7B%20alert%20%282%29%3B%7D%3B%0AFoo.prototype.getName%20%3D%20function%20%28%29%20%7B%20alert%20%283%29%3B%7D%3B%0Avar%20getName%20%3D%20function%20%28%29%20%7B%20alert%20%284%29%3B%7D%3B%0Afunction%20getName%28%29%20%7B%20alert%20%285%29%3B%7D%0A%0A%2F%2F%E8%AF%B7%E5%86%99%E5%87%BA%E4%BB%A5%E4%B8%8B%E8%BE%93%E5%87%BA%E7%BB%93%E6%9E%9C%EF%BC%9A%0AgetName%28%29%3B%0AFoo%28%29.getName%28%29%3B%0AgetName%28%29%3B
  • https://mp.weixin.qq.com/s/QljMmVPJ8k7eYB2ynV03LQ
  • https://medium.com/@gaurav.pandvia/understanding-javascript-function-executions-tasks-event-loop-call-stack-more-part-1-5683dea1f5ec
  • https://www.valentinog.com/blog/js-execution-context-call-stack/
  • https://blog.bitsrc.io/understanding-execution-context-and-execution-stack-in-javascript-1c9ea8642dd0

lerna在项目中的运用

发表于 2018-10-26 | 阅读次数

背景

公司自己编写的库越来越多,而且之间的关联性比较强,如果某个包升级了版本,其他包也得相对应升级,这样非常的繁琐。

什么是 monorepo 和 multirepo

在讲lerna之前,我们要先了解一下两个不同的代码管理组织方式 monorepo 和 multirepo:

  • monorepo:把所有的相关项目放在同一个仓库下,有点类似yarn workspaces

  • multirepo:每个子项目拥有自己的 repo

什么是lerna

lerna是一个管理多个javascript包的工具。它属于monorepo类型,当你的项目有相关联时最好使用monorepo方式进行管理。

以一个简单的示例进行说明

新建一个testLerna项目:

1
$ npx lerna init

初始化一个lerna命令,生成以下目录结构:

1
2
3
4
testLerna/
packages/
lerna.json
package.json

pcakges目录主要放置包,lerna.json为配置文件。

两种模式

  • Fixed/Locked 模式 (默认)

通过在lerna.json中指定version,当你运行lerna publish时,如果其中某一个包至上次发布以来有更新,则将会更新version。

这个模式是babel目前使用的模式,当你想把所有包关联在一起的时候可以考虑使用这种模式,需要注意的是,当其中某个包升级了大版本之后,其他的每个包都会自动升级大版本。

  • Independent mode
1
lerna init --independent

独立模式允许您更具体地更新每个包的版本,并对一组组件有意义。跟semantic-release等工具配合使用会更好。

已经有结合使用的示例

发布

假设以下项目:

1
2
3
4
packages
shareui
shareui-html
shareui-font

shareui依赖shareui-html,shareui-html依赖shareui-font,可以命令添加:

1
2
$ lerna add shareui-html --scope=shareui
$ lerna add shareui-font --scope=shareui-html

前提是要关联git,修改registry到公司私库

git提交到暂存区域,之后通过lerna publish发布:

1
$ lerna publish

以下情况需要考虑:

  • shareui-font升级了,相对应的shareui-html和shareui也要升级?

    运行lerna bootstrap升级对应依赖

参考网址

  • https://github.com/lerna/lerna
  • https://zhuanlan.zhihu.com/p/33858131
  • https://zhuanlan.zhihu.com/p/35237759

2018年学习计划(eslint篇)

发表于 2018-10-11 | 阅读次数

概述

ESLint 是在 ECMAScript/JavaScript 代码中识别和报告模式匹配的工具,它的目标是保证代码的一致性和避免错误。在许多方面,它和 JSLint、JSHint 相似,除了少数的例外:

  • ESLint 使用 Espree 解析 JavaScript。
  • ESLint 使用 AST 去分析代码中的模式
  • ESLint 是完全插件化的。每一个规则都是一个插件并且你可以在运行时添加更多的规则。

使用

1
2
3
4
5
6
7
8
// 安装
yarn add eslint --dev

// 生成配置文件,或者:npx eslint --init
./node_modules/.bin/eslint --init

// 运行
./node_modules/.bin/eslint yourfile.js

第二个值为规则的配置对象。

extends

extend 提供的是 eslint 现有规则的一系列预设。

它可以继承多个。如安装了 eslint-config-airbnb,就可以在 extends 这里引用 airbnb/base, 这样就相当于预设了 airbnb/base 的规则,常用的预设:”eslint:recommended” “airbnb/base” “stanard”(需先安装 eslint-config-standard)。当然,除了 eslint-config-xxx 提供了一系列预设,插件(eslint-plugin-xxx)也能提供预设用于继承,例如,当你安装了 eslint-plugin-react 时,就可以在 extends 这里指定 “plugin:react/recommended”,当然,也可以指定一个具体的 eslint 配置文件 path/to/file 继承。

1
2
3
4
5
6
{
extends: [
"airbnb/base",
"plugin:react/recommended"
]
}

假设以aribnb风格(一般以eslint-config-开头)为例:

1
yarn add eslint-config-aribnb --dev

然后在.eslintrc.js文件添加:

1
2
3
{
extends: ['aribnb']
}

aribnb的javascript风格:https://github.com/airbnb/javascript

plugins

这里指定插件,插件名一般为 eslint-plugin-xxx,这里可以缩写为 xxx,插件提供了除 eslint 规定之外额外的规则。

plugin 与 extend 的区别:extend 提供的是 eslint 现有规则的一系列预设而 plugin 则提供了除预设之外的自定义规则,当你在 eslint 的规则里找不到合适的的时候就可以借用插件来实现了

1
2
3
4
"plugins": [
// 这里安装了 eslint-plugin-import
"import"
]

plugins只提供rules不会设置eslint配置,所以最好在extends中使用会更好点

rules

在.eslintrc.js中会存在rules规则配置。第一个值是错误级别,可以使用以下值之一:

  • off或0:关闭规则
  • warn或者1:将规则视为一个警告(不会影响退出码)
  • error或者2:将规则视为一个错误 (退出码为1)

两种格式:”rule-name”: 0/1/2 “rule-name”: [0/1/2, configDetail],当然前面的0/1/2也可以使用英文表示。

parser

默认使用 eslint 自己的 Espree(可支持 ES5,ES6,ES7)来进行解析,但是在一些实验性语法等eslint本身不支持时会报错,所以一般我们会使用eslint-babel转换器:

1
yarn add babel-eslint --dev
1
2
3
{
parser: 'babel-eslint`
}

可以通过parserOptions来配置转换器,这里不展开说明,请查看相对应的转换器文档。

env

指定环境,每个环境都有自己预定义的全局变量,可以同时指定多个环境,不矛盾,主流的库或构建系统都能支持,列表见官方文档:http://eslint.cn/docs/user-guide/configuring#specifying-environments

  • browser - 浏览器环境中的全局变量。
  • node - Node.js 全局变量和 Node.js 作用域。
  • commonjs - CommonJS 全局变量和 CommonJS 作用域 (用于 Browserify/WebPack 打包的只在浏览器中运行的代码)。
  • shared-node-browser - Node.js 和 Browser 通用全局变量。
  • es6 - 启用除了 modules 以外的所有 ECMAScript 6 特性(该选项会自动设置 ecmaVersion 解析器选项为 6)。
  • worker - Web Workers 全局变量。
  • amd - 将 require() 和 define() 定义为像 amd 一样的全局变量。
  • mocha - 添加所有的 Mocha 测试全局变量。
  • jasmine - 添加所有的 Jasmine 版本 1.3 和 2.0 的测试全局变量。
  • jest - Jest 全局变量。
  • phantomjs - PhantomJS 全局变量。
  • protractor - Protractor 全局变量。
  • qunit - QUnit 全局变量。
  • jquery - jQuery 全局变量。
  • prototypejs - Prototype.js 全局变量。
  • shelljs - ShellJS 全局变量。
  • meteor - Meteor 全局变量。
  • mongo - MongoDB 全局变量。
  • applescript - AppleScript 全局变量。
  • nashorn - Java 8 Nashorn 全局变量。
  • serviceworker - Service Worker 全局变量。
  • atomtest - Atom 测试全局变量。
  • embertest - Ember 测试全局变量。
  • webextensions - WebExtensions 全局变量。
  • greasemonkey - GreaseMonkey 全局变量。

比如指定browser: true,则代码中的window、document等全局对象就不会报未定义。

global

指定环境为我们提供了其预置的全局变量,对于那些我们自定义的全局变量,可以在这里指定,设为 true 表示不应该被重写,设为 false 表示可以被重写。

1
2
3
4
5
6
{
"global": {
var1: true,
var2: false
}
}

settings

ESLint 支持在配置文件添加共享设置。你可以添加 settings 对象到配置文件,它将提供给每一个将被执行的规则。如果你想添加的自定义规则而且使它们可以访问到相同的信息,这将会很有用,并且很容易配置。

参考:https://github.com/benmosher/eslint-plugin-import#settings

overrides

有时,你可能需要更精细的配置,比如,如果同一个目录下的文件需要有不同的配置。因此,你可以在配置中使用 overrides 键,它只适用于匹配特定的 glob 模式的文件,使用你在命令行上传递的格式 (e.g., app/*/.test.js)。

在webstrom中使用

首先设置eslint修复的快捷键:

demo

我设置的是ctrl + E,本来想设置ctrl + S保存代码时自动修改,但是好像会冲突。

在webstrom中,需要开启eslint配置项:

demo

这里的Eslint Packge一定要选项目的中的eslint哦!

之后试一下ctrl + E看能不能自动修改代码,如果不行,请点击File -> Invalidate Caches / Restart重启webstrom即可。

禁用规则

可以在你的文件中使用以下格式的块注释来临时禁止规则出现警告:

1
2
3
4
5
/* eslint-disable */

alert('foo');

/* eslint-enable */

你也可以对指定的规则启用或禁用警告:

1
2
3
4
5
6
/* eslint-disable no-alert, no-console */

alert('foo');
console.log('bar');

/* eslint-enable no-alert, no-console */

如果在整个文件范围内禁止规则出现警告,将 /* eslint-disable */ 块注释放在文件顶部:

1
2
3
/* eslint-disable */

alert('foo');

你也可以对整个文件启用或禁用警告:

1
2
3
4
/* eslint-disable no-alert */

// Disables no-alert for the rest of the file
alert('foo');

可以在你的文件中使用以下格式的行注释或块注释在某一特定的行上禁用所有规则:

1
2
3
4
5
6
7
8
9
alert('foo'); // eslint-disable-line

alert('foo'); /* eslint-disable-line */

// eslint-disable-next-line
alert('foo');

/* eslint-disable-next-line */
alert('foo');

在某一特定的行上禁用指定的规则:

1
2
3
4
5
6
7
8
9
alert('foo'); // eslint-disable-line no-alert, quotes, semi

// eslint-disable-next-line no-alert
alert('foo');

alert('foo'); /* eslint-disable-line no-alert */

/* eslint-disable-next-line no-alert */
alert('foo');

你可以通过在项目根目录创建一个 .eslintignore 文件告诉 ESLint 去忽略特定的文件和目录。

命令行

1
eslint [options] [file|dir|glob]*

比如:eslint --ext .jsx,.js lib/将检测lib目录下以jsx或js结尾的文件。

eslint --ext .jsx,.js --fix lib/将检测并尽可能地修复lib目录下以jsx或js结尾的文件,剩下的未修复的问题才会输出。

配合Prettier使用

^_^配合prettier使你的代码更简洁!

eslint-plugin-prettier导出的recommanded配置打开了eslint-plugin-prettier和eslint-config-prettier,所以你只要把它加进去即可:

1
2
3
{
"extends": ["plugin:prettier/recommended"]
}

当然别忘了安装依赖:

1
yarn add --dev eslint-plugin-prettier eslint-config-prettier

还有就是要配置成和eslint相符合的配置:

1
2
3
4
5
6
{
"prettier/prettier": ['error', {
singleQuote: true, // 使用单引号
tabWidth: 4 //使用4个空格缩进
}]
}

参考网址:https://prettier.io/docs/en/eslint.html

总结

eslint对于规范项目代码有非常大的作用,以下是我自己项目中目前的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"extends": [
"airbnb-base",
"plugin:react/recommended",
"plugin:prettier/recommended"
],
"parser": "babel-eslint",
"rules": {
"prettier/prettier": ['error', {
singleQuote: true,
tabWidth: 4
}],
"class-methods-use-this": ['warn'],
"import/no-extraneous-dependencies": ["error", {"devDependencies": true}],
"indent": ['error', 4],
"eol-last": ['off'],
"import/no-named-as-default": ['off'],
"comma-dangle": ['off'],
"linebreak-style": ['off']
}
}

参考网址

  • https://gist.github.com/yangfch3/bfc268ccacda29bb8cb3ece610cfc5ee

  • https://medium.com/@sherryhsu/how-to-change-relative-paths-to-absolute-paths-for-imports-32ba6cce18a5

2018年学习计划(git篇)

发表于 2018-09-28 | 阅读次数

直接快照,而非比较差异

Git只关心文件数据的整体是否发生变化,而大多数其他系统则只关心文件内容的具体差异。Git更像是一个小型的文件系统,它保存每次更新时的文件快照。所以Git近乎所有的操作都可以在本地执行。

三种状态

  • 已提交(committed):该文件已经被安全地保存在本地数据库中了
  • 已修改(modified):修改了某个文件,但还没有提交保存
  • 已暂存(staged):把已修改的文件放在下次提交时要保存的清单中

分支

  • 创建分支

git checkout -b newBranch相当于:

1
2
git branch newBranch
git checkout newBranch
  • 删除分支
1
git branch -d newBranch
  • 基本合并
1
2
git checkout master
git merge newBranch
  • 冲突的合并

任何包含未解决冲突的文件都会以未合并(unmerged)状态列出。可以手动或通过图形工具来解决冲突:

1
git mergetool

冲突解决之后再add提交。

1
2
3
git branch // 查看所有分支
git branch -v // 查看所有分支最后一次commit的信息
git branch --merged/--no-merged // 查看已合并/未合并分支

分支式的工作流程

长期分支

master分支中保留完整稳定的代码,develop或next分支专门用于后续的开发,仅用于稳定性测试,当然并不是说一定要绝对稳定,不过一旦进入某种稳定的状态,便可以把它合并到master里。

特性分支

一个特性分支是指一个短期的,用来实现单一特性或与其相关工作的分支

远程分支

当我们从远程仓库克隆时,git会自动将你仓库命名为origin,并下载其中所有数据,建立一个指向它的master分支,在本地命名为origin/master,你无法在本地更改其数据。

接着,git建立一个本地属于你的master分支,始于origin上master分支相同的位置。只要你不和服务器通讯,你的origin/master分支不会移动。

可以通过git fetch origin命令来进行同步。

推送

如果你有个叫 serverfix 的分支需要和他人一起开发,可以运行 git push (远程仓库名) (分支名) :

1
git push origin serverfix

Git 自动把 serverfix 分支名扩展为 refs/heads/serverfix:refs/heads/serverfix ,意为“取出我的 serverfix 本地分支,推送它来更新远程仓库的 serverfix 分支”

也可以运行 git push origin serverfix:serferfix 来实现相同的效果,它的意思是“提取我的 serverfix 并更新到远程仓库的 serverfix”

值得注意的是,在 fetch 操作抓来新的远程分支之后,你仍然无法在本地编辑该远程仓库。换句话说,在本例中,你不会有一个新的 serverfix 分支,有的只是一个你无法移动的 origin/serverfix 指针。

如果要把该内容合并到当前分支,可以运行 git merge origin/serverfix 。如果想要一份自己的 serverfix 来开发,可以在远程分支的基础上分化出一个新的分支来:

1
git checkout -b serverfix origin/serverfix

跟踪分支

从远程分支检出的本地分支,称为跟踪分支(tracking branch)。跟踪分支是一种和远程分支有直接联系的本地分支。在跟踪分支里输入 git push ,Git 会自行推断应该向哪个服务器的哪个分支推送数据。反过来,在这些分支里运行 git pull 会获取所有远程索引,并把它们的数据都合并到本地分支中来。

在克隆仓库时,Git 通常会自动创建一个 master 分支来跟踪 origin/master 。这正是 git push 和 git pull 一开始就能正常工作的原因。当然,你可以随心所欲地设定为其它跟踪分支,比如 origin 上除了 master 之外的其它分支。刚才我们已经看到了这样的一个例子: git checkout -b [分支名] [远程名]/[分支名] 。如果你有 1.6.2 以上版本的 Git,还可以用 –track 选项简化:

1
2
3
$ git checkout --track origin/serverfix
Branch serverfix set up to track remote branch refs/remotes/origin/serverfix.
Switched to a new branch "serverfix"

要为本地分支设定不同于远程分支的名字,只需在前个版本的命令里换个名字:

1
2
3
$ git checkout -b sf origin/serverfix
Branch sf set up to track remote branch refs/remotes/origin/serverfix.
Switched to a new branch "sf"

删除远程分支

1
2
3
$ git push origin :serverfix
To git@github.com:schacon/simplegit.git
- [deleted] serverfix

衍合

把一个分支整合到另一个分支的办法有两种: merge(合并) 和 rebase(衍合)

衍合基础

rebase命令可以把在一个分支里提交的改变在另一个分支里重放一遍:

1
2
git checkout experiment
git rebase master

你可以经常使用衍合,确保在远程分支里的提交历史更清晰。比方说,某些项目自己不是维护者,但想帮点忙,就应该尽可能使用衍合:先在一个分支里进行开发,当准备向主项目提交补丁的时候,再把它衍合到origin/master 里面。这样,维护者就不需要做任何整合工作,只需根据你提供的仓库地址作一次快进,或者采纳你提交的补丁。

更多有趣的衍合

1
$ git rebase --onto master server client

检出 client 分支,找出 client 分支和 server 分支的共同祖先之后的变化,然后把它们在 master 上重演一遍

git rebase [主分支][特性分支]命令会先检出特性分支 server ,然后在主分支 master 上重演:

1
git rebase master server

衍合的风险

永远不要衍合那些已经推送到公共仓库的更新

如果把衍合当成一种在推送之前清理提交历史的手段,而且仅仅衍合那些永远不会公开的 commit,那就不会有任何问题。如果衍合那些已经公开的 commit,而与此同时其他人已经用这些 commit 进行了后续的开发工作,那你有得麻烦了。

koa2学习:路由

发表于 2018-09-05 | 阅读次数

参考网址:https://chenshenhai.github.io/koa2-note/

原生koa2路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
const Koa = require('koa');
const app = new Koa();
const fs = require('fs');

async function route(url) {
let view = '404.html';
switch (url) {
case '/':
view = 'index.html';
break;
case '/index':
view = 'index.html';
break;
case '/todo':
view = 'todo.html';
break;
case '/404':
view = '404.html';
break;
default:
break;
}

return await render(view);
}

async function render(page) {
return new Promise((resolve, reject) => {
fs.readFile(`./views/${page}`, 'utf8', (err, data) => {
if (err) {
reject(err);
}else{
resolve(data);
}
});
})
}

app.use(async (ctx) => {
const {url} = ctx.request;
ctx.body = await route(url);
});

app.listen(3000);
console.log('[demo] start-quick is starting at port 3000');

这里fs.readFile(..., 'utf8', ...)第二个参数是文件编码,如果不指定默认为raw buffer,刷新页面会变成下载。

koa-router中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const Router = require('koa-router');

// 主页路由
let home = new Router();

home.get('/', async(ctx) => {
ctx.body = `
<ul>
<li><a href="/page/helloworld">/page/helloworld</a></li>
<li><a href="/page/404">/page/404</a></li>
</ul>
`;
});

// 子页路由
let page = new Router();

page.get('/404', async(ctx) => {
ctx.body = '404 page';
}).get('/helloworld', async(ctx) => {
ctx.body = 'helloworld page';
});

// 主路由
let router = new Router();
router.use('/', home.routes(), home.allowedMethods());
router.use('/page', page.routes(), page.allowedMethods());

app.use(router.routes()).use(router.allowedMethods());

官方文档:https://github.com/alexmingoia/koa-router

获取请求参数

GET请求参数获取

在koa中,获取GET请求数据源头是koa中request对象中的query方法或querystring方法,query返回是格式化好的参数对象,querystring返回的是请求字符串,由于ctx对request的API有直接引用的方式,所以获取GET请求数据有两个途径。

POST请求参数获取

对于POST请求的处理,koa2没有封装获取参数的方法,需要通过解析上下文context中的原生node.js请求对象req,将POST表单数据解析成query string(例如:a=1&b=2&c=3),再将query string 解析成JSON格式(例如:{“a”:”1”, “b”:”2”, “c”:”3”})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
function parsePostData(ctx) {
return new Promise((resolve, reject) => {
try {
let postdata = '';
ctx.req.addListener('data', function (data) {
postdata += data;
});
ctx.req.addListener('end', function () {
let parseData = parseQueryStr(postdata);

resolve(parseData);
});
}catch(err){
reject(err);
}
});
}

// 将POST请求参数字符串解析成JSON
function parseQueryStr(queryStr) {
let queryData = {}
let queryStrList = queryStr.split('&')
for (let [index, queryStr] of queryStrList.entries()) {
let itemList = queryStr.split('=')
queryData[itemList[0]] = decodeURIComponent(itemList[1])
}
return queryData
}

app.use(async (ctx) => {
if (ctx.url === '/' && ctx.method === 'GET') {
ctx.body = `
<h1>koa2 request post demo</h1>
<form method="POST" action="/">
<p>userName</p>
<input name="userName" /><br/>
<p>nickName</p>
<input name="nickName" /><br/>
<p>email</p>
<input name="email" /><br/>
<button type="submit">submit</button>
</form>
`;
} else if (ctx.url === '/' && ctx.method === 'POST') {
ctx.body = await parsePostData(ctx);
} else {
// 其他请求显示404
ctx.body = '<h1>404!!! o(╯□╰)o</h1>'
}
});

koa-bodyparser中间件

1
2
3
4
5
6
7
8
9
10
11
var Koa = require('koa');
var bodyParser = require('koa-bodyparser');

var app = new Koa();
app.use(bodyParser());

app.use(async ctx => {
// the parsed body will store in ctx.request.body
// if nothing was parsed, body will be an empty object {}
ctx.body = ctx.request.body;
});

静态资源加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const serve = require('koa-static');
const Koa = require('koa');
const app = new Koa();

// $ GET /package.json
app.use(serve('.'));

// $ GET /hello.txt
app.use(serve('test/fixtures'));

// or use absolute paths
app.use(serve(__dirname + '/test/fixtures'));

app.listen(3000);

console.log('listening on port 3000');

session操作

koa2原生功能只提供了cookie的操作,但是没有提供session操作。session就只用自己实现或者通过第三方中间件实现。在koa2中实现session的方案有一下几种

  • 如果session数据量很小,可以直接存在内存中
  • 如果session数据量很大,则需要存储介质存放session数据

数据库存储方案:

  • 将session存放在MySQL数据库中

    需要用到中间件:

    • koa-session-minimal 适用于koa2 的session中间件,提供存储介质的读写接口 。
    • koa-mysql-session 为koa-session-minimal中间件提供MySQL数据库的session数据读写操作。
    • 将sessionId和对于的数据存到数据库
  • 将数据库的存储的sessionId存到页面的cookie中
  • 根据cookie的sessionId去获取对于的session信息

2018年学习计划(babel篇)

发表于 2018-09-04 | 阅读次数

本篇基于最新版的babel@7。详细请查看官网:https://babeljs.io/docs/en

使用指南

  1. 安装必须的包:
1
2
yarn add @babel/core @babel/cli @babel/preset-env --dev
yarn add @babel/polyfill
  1. 创建babel.config.js文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
const presets = [
["@babel/env", {
targets: {
edge: "17",
firefox: "60",
chrome: "67",
safari: "11.1"
},
useBuiltIns: "usage"
}]
];

module.exports = { presets };
  1. 运行编译命令:
1
./node_modules/.bin/babel src --out-dir lib

当然也可以使用npx babel命令

polyfill

@babel/polyfill最新的选项useBuiltIns包括三个选项:

  • false:不为每个文件自动添加polyfill文件或者转换`import ‘@babel/polyfill’;

  • entry:只会在入口引入@babel/polyfll将会自动转换成引用全部core-js模块;

1
2
3
4
5
6
7
8
// In
import "@babel/polyfill";

// Out
require("core-js/modules/es6.array.copy-within");
require("core-js/modules/es6.array.fill");
require("core-js/modules/es6.array.find");
...
  • usage:代码中不需要引入@babel/polyfll,它会自动引入需要的core-js模块:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// In
new Promise(function (resolve) {
setTimeout(function () {
resolve();
}, 2000);
});

// Out
require("core-js/modules/es6.promise");

new Promise(function (resolve) {
setTimeout(function () {
resolve();
}, 2000);
});

预设

一下是比较常用的官方预设:

  • @babel/preset-env

  • @babel/preset-flow

  • @babel/preset-react

  • @babel/preset-typescript

@babel/plugin-transform-runtime

  • 当我们使用gernerator/async时自动引用@babel/runtime/regenerator

  • 当需要使用垫片方法时,自动引用core-js中的相对应方法

  • 自动移除babel所需的一些帮助方法,使用@babel/runtime/helpers替代,以防多次引用

Babel 转译后的代码要实现源代码同样的功能需要借助一些帮助函数,例如,{ [name]: ‘JavaScript’ } 转译后的代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
'use strict';
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
var obj = _defineProperty({}, 'name', 'JavaScript');

类似上面的帮助函数 _defineProperty 可能会重复出现在一些模块里,导致编译后的代码体积变大。Babel 为了解决这个问题,提供了单独的包 babel-runtime 供编译模块复用工具函数。

启用插件 babel-plugin-transform-runtime 后,Babel 就会使用 babel-runtime 下的工具函数,转译代码如下:

1
2
3
4
5
6
'use strict';
// 之前的 _defineProperty 函数已经作为公共模块 `babel-runtime/helpers/defineProperty` 使用
var _defineProperty2 = require('babel-runtime/helpers/defineProperty');
var _defineProperty3 = _interopRequireDefault(_defineProperty2);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var obj = (0, _defineProperty3.default)({}, 'name', 'JavaScript');

除此之外,babel 还为源代码的非实例方法(Object.assign,实例方法是类似这样的 “foobar”.includes(“foo”))和 babel-runtime/helps 下的工具函数自动引用了 polyfill。这样可以避免污染全局命名空间,非常适合于 JavaScript 库和工具包的实现。例如 const obj = {}, Object.assign(obj, { age: 30 }); 转译后的代码如下所示:

1
2
3
4
5
6
7
8
9
'use strict';
// 使用了 core-js 提供的 assign
var _assign = require('babel-runtime/core-js/object/assign');
var _assign2 = _interopRequireDefault(_assign);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var obj = {};
(0, _assign2.default)(obj, {
age: 30
});

思考:babel-runtime 为什么适合 JavaScript 库和工具包的实现?

  • 避免 babel 编译的工具函数在每个模块里重复出现,减小库和工具包的体积;

  • 在没有使用 babel-runtime 之前,库和工具包一般不会直接引入 polyfill。否则像 Promise 这样的全局对象会污染全局命名空间,这就要求库的使用者自己提供 polyfill。这些 polyfill 一般在库和工具的使用说明中会提到,比如很多库都会有要求提供 es5 的 polyfill。在使用 @babel/runtime 后,库和工具只要在 package.json 中增加依赖 babel-runtime,交给 babel-runtime 去引入 polyfill 就行了;

总结:

  • 具体项目还是需要使用 babel-polyfill,只使用 babel-runtime 的话,实例方法不能正常工作(例如 “foobar”.includes(“foo”));

  • JavaScript 库和工具可以使用 babel-runtime,在实际项目中使用这些库和工具,需要该项目本身提供 polyfill;

参考网址

  • https://survivejs.com/webpack/optimizing/minifying/

  • http://2ality.com/2015/12/babel6-loose-mode.html

《svg精髓》学习笔记(路径)

发表于 2018-08-23 | 阅读次数

之前所有的基本形状都是元素的简写形式,path接受d属性(data)表示一系列路径。

moveto、lineto和closepath

如下:

1
<path d="M 10 10 L 100 10 L 100 100 L 10 100 Z"/>

表示,先移动到点(10, 10),然后lineto到点(100, 10),lineto到点(100, 100), lineto到点(10, 100),大写的Z表示自动关闭路径。

M和L大小写是有区别的,大写的M和L表示绝对坐标,小写的话表示相对于当前画笔的坐标。

路径的快捷方式

使用H和V可以简写水平和垂直方式的画线:

简写形式 等价的冗长形式 效果
H 20 L 20 current_y 绘制一条到绝对位置(20, current_y)的线
h 20 L 20 0 绘制一条到(current_x + 20, current_y)的线
V 20 L current_x 20 绘制一条到绝对位置(current_x, 20)的线
v 20 L 0 20 绘制一条到(current_x, current_y + 20)的线

路径快捷方式表示法

  1. 可以在L或l后面放多组坐标
1
<path d="M 10 10 L 30 50 60 80 90 20 Z"/>
  1. 所有不必要的空格都可以消除,比如字母后面的空格

椭圆弧

椭圆弧以字母A为命令,接受7个参数:

  • 点所在的椭圆的x半径和y半径

  • 椭圆的x轴旋转角度x-axis-rotation

  • large-arc-flag,如果需要圆弧角度小于180度,其为0;否则为1

  • sweep-flag,如果坐标需要以负角度绘制则为0,已正角度绘制则为1

  • 终点的x坐标和y坐标

1
<path d="M 100 100 A100,50 0 0,0 255 255" />

《svg精髓》学习笔记(常用形状绘制)

发表于 2018-08-16 | 阅读次数

基本形状

线段

1
<line x1="start-x" y1="start-y" x2="end-x" y2="end-y" />

笔画特性

笔画的尺寸、颜色和风格都会影响线段的表现,这些都可以通过style属性来指定。

stroke-width

画布中的坐标系统网格线是无线细的,网格线位于画笔的正中心:

1
<line x1="0" y1="0" x2="100" y2="100" style="stroke-width: 10; stroke: black" />

大部分svg阅读器都会默认开启反锯齿功能,可以通过指定CSS属性shape-rendering的值来控制反锯齿特性,取值crispEdges(在元素上,或者整个svg上)会关闭反锯齿特性。

笔画颜色

你可以通过以下几种方式指定画笔的颜色:

  • 基本颜色

  • 16进制

  • rgb/rgba

  • currentColor:当前元素应用的css属性的color值

1
<line x1="0" y1="0" x2="100" y2="100" style="stroke-width: 10; stroke: black" />

stroke-opacity

控制线条的透明度。

stroke-dasharray

如果你需要电线或者虚线,则需要使用这个属性,它的值由一系列数字构成,代表线的长度和空隙的长度,数字之间用逗号或空格隔开。数字的个数应为偶数。

1
2
// 9个像素的虚线,5个像素的间隙
stroke-dasharray: 9, 5

矩形

矩形只需要指定左上角坐标x和y,以及宽度width和高度height即可。如果不指定fill,则默认用黑色填充,需要指定stroke,不然它不会绘制:

1
<rect x="10" y="10" width="100" height="100" />

圆角矩形

圆角矩形可以通过指定x方向和y方向的圆角半径(rx和ry),rx的最大值是矩形宽度的一半,ry同理,如果只指定其中一个,则另外一个也相等。

圆和椭圆

圆使用circle元素,通过指定中心点(x,y)和半径(r),椭圆使用ellipse需要另外同时指定x方向的半径和y方向的半径(rx, ry):

多边形

元素可以用来画任意封闭图形,使用时只需为points属性指定一系列的x/y坐标点,并用逗号或空格隔开,指定坐标时不需要在最后指定返回起始坐标,因为图形总是封闭的,会自动回到起始点。

1
<polygon points="15, 10 55, 10 45, 20 5, 20" style="fill: red; stroke: black" />

填充边线交叉的多边形

可以使用fill-rule: [nonzero | evenodd]

参考网址:https://blog.csdn.net/wjnf012/article/details/72875739

折线

使用polyline元素,通过points属性指定点,但是和polygon不同的是,它不会自动封闭。

最好将polyline的fill设置为none,不然svg阅读器可能会自动填充黑色。

线帽和线连接

当使用line或polyline时,可以为stroke-linecap:butt、round、square指定。round和square在起始位置都超过了真实位置,默认值butt则精确和起止位置对齐。

你也可以通过stroke-linejoin:miter(尖的)、round(圆的)、bevel(平的)属性来指定线段在图形棱角处交叉时的效果。

《svg精髓》学习笔记(坐标系统)

发表于 2018-08-15 | 阅读次数

在网页中使用svg

  • 将svg作为图像:使用img元素包含svg文件

  • 将svg作为css背景:使用background-image

  • 将svg作为对象

  • 内联svg

坐标系统

视口

文档打算使用的画布区域被称为视口。我们可以使用svg元素上的width和height属性确定视口的大小。属性值如果没有单位默认是px。

默认用户坐标

原点(x=0, y=0)在阅读器的左上角

为视口指定用户坐标

没有单位的数值都被视为像素,但是有时候我们并不想这样。为了实现这一效果,我们可以在svg元素上设置viewBox属性,这个属性的值由4个数值组成,他们分别代表想要叠加在视口上的用户坐标系统的最小x坐标、最小y坐标、宽度、高度。

因此,要在4厘米*5厘米的图纸上设置一个每厘米16个单位的坐标系统,要使用这个开始标记:

1
<svg width="4cm" height="5cm" viewBox="0 0 64 80">

保留宽高比

如果viewBox和视口宽高比不一样,svg可以做三件事:

  • 按较小的尺寸等比例缩放

  • 按较大的尺寸等比例缩放并裁剪掉超出视口的部分

  • 拉伸或挤压绘图以恰好填充新的视口(不保留宽高比)

svg元素的preserveAspectRatio="align [meet | slice:裁剪]""属性允许我们指定被缩放的图像相对视口的对齐方式,以及它适配边缘还是要裁剪。

默认值:xMidYMid meet,它会缩小图像以适配可用的空间,并且使它水平和垂直居中。

<?xml>














Cat

如果preserveAspectRatio值为none,则不保留宽高比。

嵌套坐标系统

可以在文档中嵌套svg元素。默认新坐标是0,0,当然也可以通过x和y属性指定新原点。


参考网址:https://segmentfault.com/a/1190000007143300

12…6
liaoyf

liaoyf

加油!

55 日志
23 标签
GitHub
© 2018 liaoyf
由 Hexo 强力驱动
主题 - NexT.Pisces