hileix / blogs Goto Github PK
View Code? Open in Web Editor NEW个人博客
个人博客
因为 DOM 1 级中,没有定义事件相关的内容,所以不存在 DOM 1 级事件处理
var div = document.getElementById('div');
div.onclick = function() {
console.log('div clicked');
}
var div = document.getElementById('#div');
div.addEventListener('click', handleDivClick, false);
function handleDivClick() {
console.log('div clicked');
}
通过 Element.addEventListener(type, callback, useCapture) 来监听一个元素的事件,通过改变第三个参数,即可改变事件流:
当 useCapture 为 true 时,表示事件会在捕获阶段执行事件的回调函数:window -> document -> html -> body -> div
否则事件会在冒泡阶段执行事件的回调函数:div -> body -> html -> document -> window
事件对象是事件回调函数的 event 对象
event 对象有一些比较重要的方法:
支持
事件委托(focus、blur 等不支持事件冒泡的事件)适合
使用事件委托(mousemove、mouseout 需要大量的计算,不适合使用)最近在研究 css3 的动画属性 transition 和 animation,发现自己之前对这两个 css3 的动画属性并没有太多深入的理解。本篇将介绍 css3 的 transition 以及自己的一些理解。
首先,transition 意为 “过渡,转场”,即从一种状态变为另外一种状态的中间过程。css3 的 transition 属性,就是用来配置这个中间状态的。
css3 中的 transition 属性是 css3 过渡的四个属性的简写形式,其四个 css3 的属性分别为:
例子:
<div>transition</div>
div {
background: #fff;
color: #000;
transition: all 1s linear 0.5s;
}
div:hover {
background: #000;
color: #fff;
}
效果如下:
https://codepen.io/reai3041/pen/Vdoqvm?editors=1100
在代码中,可以看到,我们需要预先
使用 transition 属性,配置
选择器样式的变化过程
。这个过程包含了四个属性
:需要有过渡效果的属性
、过渡时间
、过渡速度变化函数
以及延迟多久开始过渡
。
然后,配置完 transition 属性之后,在用户产生某个行为时
(如鼠标 hover 到该元素上时),重新设置需要有过渡效果的属性的新值
。
在上面的例子中:
没 hover 时的状态(开始状态)
为:背景颜色为白色
,字体颜色为黑色
。被 hover 时的状态(结束状态)
为:背景颜色为黑色
,字体颜色为白色
。瞬间
从没 hover 时的状态
变为 被 hover 时的状态
,没有过渡的效果。上面的例子中,使用了 transition 属性:
all
,即 div 的所有可以变化的样式变化时都有过渡效果1s
linear(线性)
,即变化速度为匀速0.5s
,即 hover 0.5s 之后,才开始过渡注意
只有当过渡状态过程中,transition 属性存在(transition 被选择器应用时)时,才会有过渡的效果,否则没有过渡效果。
什么意思呢?
我们可以看到上面的例子中:
我们改一下代码(将 transition 属性应用在 div 被 hover 时的选择器上)
div {
background: #fff;
color: #000;
}
div:hover {
background: #000;
color: #fff;
transition: all 1s linear 0.5s;
}
效果如下:
https://codepen.io/reai3041/pen/zagybK
此时可以看到:
移出时,div 上没有了 transition 属性,此时便没有了过渡效果
,而是瞬间效果
。
1. 其实 transition 很简单,就四个属性:
2. 注意只有当选择器在过渡的过程中,被应用了 transition 属性,才会有过渡效果,否则是没有过渡效果
的,只有瞬间效果
下一篇:css3 动画(二)贝塞尔曲线以及利用 transition 和 贝塞尔曲线函数写出一个加入购物车的动态效果(平抛运动效果)
最近在研究 react 源码(v16.8)的过程中,发现 react 使用了 requestAnimationFrame
和 requestIdleCallback
这两个 api。同时,这两个 api 与 浏览器的 fps
存在着关系。
首先介绍一下一些名词的含义
fps 全称为 Frames Per Second
,即 每一秒的帧数
。我们在显示器上看到的各种各样的动画效果,都是由一帧一帧组成的。可以将 fps 理解为动画播放的速度。fps 越大,动画越流畅。
一般浏览器的 fps 为 60。当然,如果你的显示器的刷新率能够达到 144 hz 的话,浏览器的 fps 可以达到 144 fps
那什么是刷新率呢?屏幕的刷新率
是指 屏幕每秒能够显示图像的次数
,单位为 hz(赫兹)。
fps 的值受限于屏幕的刷新率,即 fps 的值小于等于屏幕刷新率的值
。
总结:浏览器的 fps 指浏览器每一秒的帧数。fps 越大,每秒的画面就越多,浏览器的显示就越流畅。浏览器的 fps 一般等于屏幕的刷新率,不会超过屏幕的刷新率。
先看一下 MDN 上对 requestAnimationFrame()
的定义:
window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行
当你准备更新动画时你应该调用此方法。这将使浏览器在下一次重绘之前调用你传入给该方法的动画函数(即你的回调函数)。
回调函数执行次数通常是每秒60次,但在大多数遵循W3C建议的浏览器中,回调函数执行次数通常与浏览器屏幕刷新次数相匹配
var start = null;
var element = document.getElementById('SomeElementYouWantToAnimate');
element.style.position = 'absolute';
function step(timestamp) {
if (!start) start = timestamp;
var progress = timestamp - start;
element.style.left = Math.min(progress / 10, 200) + 'px';
if (progress < 2000) {
window.requestAnimationFrame(step);
}
}
window.requestAnimationFrame(step);
element.style.left
的值会每 16.7ms(1000 / 60)变化一次,一秒变化 60 次,这样就形成了动画rAf
就表示 requestAnimationFrame()。在 Update Rendering
(更新渲染)前执行requestAnimationFrame() 总结:
重绘前
去执行传入的回调后台标签页
或者隐藏的 <iframe> 里
时,requestAnimationFrame() 会被暂停调用以提升性能和电池寿命MDN 上对 requestIdleCallback()
的定义:
window.requestIdleCallback()方法将在浏览器的空闲时段内调用的函数排队。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应
浏览器的空闲时间段
呢?来看一下刚刚这张图:在一个最大 fps 为 60 的浏览器中,每一帧的时间长度我们可以得出:1000 / 60 = 16.7ms。当浏览器在 Update Rendering
这个阶段以及之前的阶段所用的时间小于 16.7 ms 时,就会有 idle time(空闲时间)
。这个 idle time
就是 浏览器的空闲时间段
再看下图。当浏览器动画达到最流畅的效果时(也就是 60 fps),浏览器给每一帧所分配的时间为 16.7ms。60 帧中,就有 60 个 idle time
。这可不是一笔小的时间长度(这段时间不用白不用~)。
所以,在 react 中就会利用这段时间,将对于用户来说,不那么重要的任务放在 idle time
中执行。而对于用户体验来说比较重要的任务(如:动画、click 事件任务等)放在 requestAnimationFrame()
回调中执行。
空闲时间
,requestIdleCallback()
的回调就会一直得不到调用。我们可以通过设置第二个的 timeout 属性
,让 requestIdleCallback()
在 timeout
时间之后,必定执行。上一篇 css3 动画(一) transition 中,介绍了 transition 的用法。其中 transition 包含四个可设置的属性:
其中,1、2 以及 4 都挺好理解的,但是 3
是个什么东西?其实 3
是 css3 中的 timing-function,其中 3
有两种类型的值:
本篇就总结一下 cubic-bezier(x1, y1, x2, y2):立方贝塞尔曲线
贝塞尔曲线(Bezier curve)是计算机图形学中重要的参数曲线,它通过一个方程来描述一条曲线。根据方程的最高阶数,可以分为线性贝塞尔曲线、二次贝塞尔曲线、三次贝塞尔曲线以及更高次的贝塞尔曲线。
贝塞尔曲线扫盲
在 css3
中使用的 cubic-bezier() 函数,是一个 三次贝塞尔曲线函数
。
三次贝塞尔曲线中四个点,在 cubic-bezier() 中:
第一个点 p0(0, 0)
和最后一个点 p3(1, 1)
是固定坐标的p1(x1, y1)
和 p2(x2, y2)
是传入 cubic-bezier() 函数中的参数的。其中 x∈[0, 1]
,y 可以不在 [0, 1] 区间内,但最大值最好不要大于 1.5
,最小值不要小于 -0.5
0 和 1
分别表示 0% 和 100%
那我们怎么获取我们想要的贝塞尔曲线呢?进这个 网站
在上面那个 网站 中,我们可以通过拖拽 p1 和 p2 点,来改变两点的坐标,从而产生一条曲线。
那么这条曲线代表什么含义呢?
时间是匀速增加的
,进度增加的快慢是受斜率(速度)影响的。所以这是一条表示进度变化快慢的速度曲线
这个 进度
在 css 中,实际指的就是样式变化前后的值
。如:
width
从 100px 变为 200px,纵坐标的起点就为 100px
,终点为 200px
opacity
从 0 变为 1,纵坐标的起点就为 0
,终点为 1
最终效果如下:
https://codepen.io/reai3041/pen/RBbwzo
我们知道,平抛运动可以分解为两个方向的运动:
这样,我们就可以分解为水平和垂直方向上的 过渡效果
:
<div class="ball"></div>
.ball {
width: 10px;
height: 10px;
border: 1px solid #000;
border-radius: 50%;
position: absolute;
left: 80px;
top: 30px;
}
/* 终点 */
.ball.end {
left: 180px;
top: 230px;
transition: left 0.2s linear, top 0.2s cubic-bezier(0.48, 0, 0.94, 0.28);
}
通过改变 left 和 top 值:
其中,cubic-bezier() 函数的参数,可以在 网站 里自定义点的位置,然后得到自己想要的速度变化曲线。
在平抛运动垂直方向的速度曲线大概是这样子的:
这样,我们就知道了 cubic-bezier() 函数的参数(图片中的这条曲线,其实就可以看做是实际的平抛的曲线)
代码及效果:
https://codepen.io/reai3041/pen/RBbwzo
css3 中的贝塞尔曲线其实很简单:一条以 时间为横坐标
,以 进度为纵坐标
的 和速度相关
的曲线,它表示了 过渡/动画
中间状态的 变化快慢
。
{
"name": "foo",
"main:: "lib/index.js"
}
// 此时导入的 foo package 就是导入 node_modules/foo/lib/index.js 文件
const foo = require('foo');
resolve.mainFields
来指定 package.json 中的哪个 field 表示 package 的入口其也有默认值,默认值与 target 配置有关:
1. 如果 target 为 webworker, web 或 未指定,则:
module.exports = {
//...
resolve: {
mainFields: ['browser', 'module', 'main']
}
};
2. target 为其他值(如:node)
module.exports = {
//...
resolve: {
mainFields: ['module', 'main']
}
};
优先级为在前面的优先级高
z-index 属性用来控制元素在 z 轴上的顺序
z-index 仅适用于定位元素。即 postition
值为 relative
, absolute
和 fixed
属性
堆叠顺序
堆叠上下文
z
轴上的值。值越大表示元素越靠近屏幕,反之元素越远离屏幕z-index
值越大,越靠近屏幕。除了 z-index
控制着元素的 堆叠顺序
,还有其他因素控制着元素的 堆叠顺序
,如下:堆叠上下文
是一个在该元素内的堆叠顺序不会影响到其他堆叠上下文堆叠顺序的一个 环境
React Native 可以开发 ios 和 android 的 app,在开发过程中,势必会遇上屏幕适配(ios 好几种尺寸的屏幕以及 android 各种尺寸的屏幕)的问题,下面介绍一种几行代码搞定 RN 适配的方法!
虽然单位不同,但是元素所占屏幕宽度的比例是相同的
利用元素所占屏幕比例不变的特性,来将 px 转为 dp(这样实现屏幕适配的话,在不同尺寸的屏幕下,元素会等比放大或缩小)
公式如下:
设计稿元素宽度(px) / 设计稿总宽度(px) = 元素的宽度(dp) / 屏幕的总宽度(dp)
我们要求的就是 元素的宽度(dp)
可以得出:
元素的宽度(dp) = 设计稿元素宽度(px)* 屏幕的总宽度(dp) / 设计稿总宽度(px)
// util.js
import { Dimensions } from 'react-native';
// 设备宽度,单位 dp
const deviceWidthDp = Dimensions.get('window').width;
// 设计稿宽度(这里为640px),单位 px
const uiWidthPx = 640;
// px 转 dp(设计稿中的 px 转 rn 中的 dp)
export const pTd = uiElePx => {
return (uiElePx * deviceWidthDp) / uiWidthPx;
};
每次给元素设置尺寸样式时,使用 pTd() 函数即可(传入设计稿中元素的实际 px)。
上一篇 css3 动画(三)animation 简介 中只是简单的介绍了一下 animation 的子属性,并没有真正的使用。在本篇中,通过阅读 animate.css这个 css 的动画库,来加深对
css3 的 animation 属性的理解。
animate.css 是一个具有非常多的动画效果的 css 动画库。动画效果演示
<h1 class="animated flash">Example</h1>
通过查看 演示,可以看到该动画库的动画类型分为 14 类:
在 animate.css 的源码目录中,也根据其分类分为了 14 个文件夹:
首先看 _base.css 中的基础类:
.animated {
animation-duration: 1s;
animation-fill-mode: both;
}
.animated.infinite {
animation-iteration-count: infinite;
}
.animated.delay-1s {
animation-delay: 1s;
}
.animated.delay-2s {
animation-delay: 2s;
}
.animated.delay-3s {
animation-delay: 3s;
}
.animated.delay-4s {
animation-delay: 4s;
}
.animated.delay-5s {
animation-delay: 5s;
}
可以看到:
.animate 基础类设置了动画的两个子属性:animation-duration 和 animation-fill-mode。其值分别为 1s 和 both。animation-fill-mode 详解
.animate.infinite 基础类设置了动画的播放次数为无限次
.animated.delay-ns 基础类设置了动画的延时
然后,我们来看一个动画例子的源码:flash.css
@keyframes flash {
from,
50%,
to {
opacity: 1;
}
25%,
75% {
opacity: 0;
}
}
.flash {
animation-name: flash;
}
在 flash.css 中,首先定义了名为 flash 的关键帧的序列:
@keyframes flash {
from,
50%,
to {
opacity: 1;
}
25%,
75% {
opacity: 0;
}
}
在 0%、50%、100% 关键帧中,其样式 opacity 为 0
在 25%、75% 关键帧中,其样式 opacity 为 1
然后,下面有 .flash 类,使用了 flash 作为 animation-name 属性的值,flash 即为上面定义关键帧的名称
所以,通过添加 flash 类,就可以使元素具有 “闪烁” 的动画效果!
通过上面实例的一个 flash 动画源码的阅读,加深了对 css3 animation 属性的使用。
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function(nums, target) {
const ret = [];
for (let i = 0; i < nums.length - 1; i++) {
let flag = false;
for (let j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] === target) {
ret.push(i);
ret.push(j);
flag = true;
break;
}
}
if (flag) {
break;
}
}
return ret;
};
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function(nums, target) {
let ret = [];
const numsObj = {};
for (let i = 0; i < nums.length; i++) {
numsObj[nums[i]] = i;
}
for (let i = 0; i < nums.length; i++) {
const value = target - nums[i];
const index = numsObj[value];
if (typeof index === 'number' && i !== index) {
ret = [i, index];
break;
}
}
return ret;
};
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function(nums, target) {
let ret = [];
const numsMap = new Map();
for (let i = 0; i < nums.length; i++) {
numsMap.set(nums[i], i);
}
for (let i = 0; i < nums.length; i++) {
const value = target - nums[i];
const index = numsMap.get(value);
if (typeof index === 'number' && i !== index) {
ret = [i, index];
break;
}
}
return ret;
};
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function(nums, target) {
let ret = [];
const numsObj = {};
for (let i = 0; i < nums.length; i++) {
const value = target - nums[i];
const index = numsObj[value];
if (typeof index === 'number' && i !== index) {
ret = [i, index];
break;
}
numsObj[nums[i]] = i;
}
return ret;
};
如何参加一个 github 开源项目来贡献自己的代码呢?下面来介绍一下 github flow 工作流程。
在开源项目首页中,可以在右上角找个 fork 按钮。点击 fork 按钮,将该开源项目 fork 到自己的项目列表中。
将 fork 完之后的代码 clone 到本地。
有时候,我们需要将开源项目原仓库的代码同步到 fork 的仓库,该怎么做?
git remote add upstream https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY.git
git remote -v
查看远程仓库地址和上游仓库地址git fetch upstream master
git merge upstream/master
git checkout master
切换到主分支git push origin master
说到 event loop,不得不提的是: JavaScript 是单线程的。即一次只能做一件事情。Event Loop 就是让 JavaScript 在运行时具有并发的能力。
// global sync task
function add(a, b) {
return a + b;
}
// add sync task
const sum = add();
// bar async task
setTimeout(function bar() {
console.log('bar called!');
}, 1000);
// fetchUsers async task
fetch('https://www.example.com/api/v1/users').then(function fetchUsers(res) {
console.log({ res });
});
global task
和 foo task
,包含的异步任务有 bar task
要想理解 Event Loop,就得先了解 JavaScript 中的 执行栈
(JavaScript execution context stack)
JavaScript 是单线程的,只有一个主线程。在这个主线程中,有一个栈。
在每个函数 运行时
,会生成一个 执行上下文(execution context)
,这个执行上下文包含了当前函数运行时的参数,局部变量等信息。
开始执行函数
时,执行上下文会被 推到执行栈中
;函数执行完毕后
,这个 执行上下文又会从栈中弹出
。
例子
function foo() {
console.log('foo');
}
function bar() {
console.log('bar');
foo();
}
bar();
入执行栈:
整个脚本开始运行,global 执行上下文
被推入 执行栈
中
bar()
函数开始运行,bar() 函数执行上下文
被推入 执行栈
中
foo()
函数开始运行,foo() 函数执行上下文
被推入 执行栈
中
出执行栈:
foo()
函数执行完毕,弹出
bar()
函数执行完毕,弹出
总结:执行栈
相当于一条流水线,所有的 同步任务
运行时
,都会被放到这条流水线上去一个一个 按照顺序(函数调用顺序)
执行。
既然 同步任务运行时
是被放到 执行栈
中,那 异步任务运行时以及定义时
是被放到哪里呢?
在异步任务中,又分为两类任务:
要回答上面这个问题,首先了解一下 macrotask
和 microtask
。
以下派发的任务就是 macrotask
每一个 Event Loop 都有一个 macrotask 队列
:
setTimeout(function foo() {
console.log('foo');
}, 1000);
setTimeout(function bar() {
console.log('foo');
}, 2000);
foo()
macrotask 在 1000ms 后被推入 macrotask 队列
中bar()
macrotask 在 2000ms 后被推入 macrotask 队列
中以下派发的任务时 microtask:
每一个 Event Loop 都有一个 microtask 队列
:
// 调用 Promise 构造函数时,传入的回调是会立即调用的,所以 foo 不算 microtask
// new Promise(function foo(resolve) {
// console.log('foo');
// resolve();
// });
// 调用 Promise 构造函数时,传入的回调是会立即调用的,所以 bar 不算 microtask
// new Promise(function bar(resolve) {
// console.log('bar');
// resolve();
// });
Promise.resolve()
.then(function foo() {
console.log('promise1');
})
.then(function bar() {
console.log('promise2');
});
foo()
microtask 被推入 microtask 队列
中bar()
microtask 被推入 microtask 队列
中所以,现在来看看上面的问题:
异步任务
运行时
以及 定义时
是被放到哪里呢?异步任务
属于 macrotask
,则在 定义时
被放到 macrotask 队列
中;如果 异步任务
属于 microtask
,则在 定义时
被放到 microtask 队列
中上面只回答了 异步任务
定义时
被放置的位置。接下来通过 Event Loop 来回答 异步任务
在 运行时
会在哪里。
上面讲了:
代码层面
,存在 定义状态
和 执行状态
。在 执行状态
时被推入 执行栈
中执行代码层面
,只存在 定义状态
。异步任务在 定义时
,根据异步任务的类型,被推入不同的队列中
运行时
被推入到 执行栈
中执行但上面还留下了一个问题:异步任务
在 运行时
会在哪里?
这个时候,就需要知道 Event Loop 了。
Event Loop 翻译成中文为 事件循环
。循环
二字,就说明了 Event Loop 表示是一个在不断 循环
的过程。
这个 循环
包含了以下过程:
执行栈
中的所有任务是否全部执行完毕了。若未执行完,则继续执行;否则进入下一步microtask 队列
是否存在 microtask
。若存在 microtask
,则将 microtask 队列
中的所有 microtask
都推入 执行栈
中执行(否则进入下一步)。执行完毕后,进入下一步macrotask 队列
中是否存在 macrotask
。若存在 macrotask
,则将 macrotask 队列
中的 第一个
出队,推入到 执行栈
中执行(否则进入第 1 步,一个 Event Loop 完成)。执行完毕后,进入第 1 步(一个 Event Loop 完成)所以,现在可以回答上面的问题了:异步任务
在 运行时
会经过 Event Loop,且被推入到 执行栈
中。
Event Loop(事件循环)就是一个对 异步任务
进行 协调的过程
上周,同事抱怨说 react 怎么不能像 angular 那样,使用命令行工具来生成一个组件。对呀,平时工作时,想要创建一个 react 的组件,都是直接 copy 一个组件,然后做一些修改。为什么不能将这个过程交给程序去做呢?当天晚上,我就仿照 angular-cli 的 api,写了一个生成 react 组件的命令行工具 rcli。在这里记录一下实现的过程。
rcli new PROJECT-NAME
命令,创建一个 react 项目,其中生成项目的脚手架当然 create-react-app 啦rcli g component MyComponent
命令,创建一个 MyComponent
组件,这个组件是一个文件夹,在文件夹中包含 index.js
、MyComponent.js
、MyComponent.css
三个文件后来发现 rcli g component MyComponent
命令在 � 平时开发过程中是不够用的,因为这个命令只是创建了一个类组件,且继承自 React.Component
。
在平时开发过程中,我们会用到这三类组件:
React.Component
的类组件React.PureComponent
的类组件注:将来可以使用 Hooks 来代替之前的类组件
于是就有了 0.2.0 版本的 rcli
Usage: rcli [command] [options]
Commands:
new <appName>
g <componentName>
`new` command options:
-n, --use-npm Whether to use npm to download dependencies
`g` command options:
-c, --component <componentName> The name of the component
--no-folder Whether the component have not it's own folder
-p, --pure-component Whether the component is a extend from PureComponent
-s, --stateless Whether the component is a stateless component
create-react-app
来创建一个应用rcli new PROJECT-NAME
cd PROJECT-NAME
yarn start
或者你可以使用 npm
安装依赖
rcli new PROJECT-NAME --use-npm
cd PROJECT-NAME
npm start
rcli g -c MyNewComponent -p
rcli g -c MyNewComponent
等于:
rcli g -c ./MyNewComponent
rcli g -c MyNewComponent -s
# �默认生成的组件都会都�包含在文件夹中的,�若�不想生成的组件被文件夹包含,则加上 --no-folder ��选项
rcli g -c MyNewComponent --no-folder
hileix-rcli
的项目npm init -y
初始化一个 npm package 的基本信息(即生成 package.json 文件)index.js
文件,用来写用户输入命令后的主要逻辑代码package.json
文件,添加 bin
字段:{
"name": "hileix-rcli",
"version": "0.2.0",
"description": "",
"main": "index.js",
"bin": {
"rcli": "./index.js"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/hileix/rcli.git"
},
"keywords": [],
"author": "hileix <[email protected]> (https://github.com/hileix)",
"license": "MIT",
"bugs": {
"url": "https://github.com/hileix/rcli/issues"
},
"homepage": "https://github.com/hileix/rcli#readme",
"dependencies": {
"chalk": "^2.4.1",
"commander": "^2.19.0",
"cross-spawn": "^6.0.5",
"fs-extra": "^7.0.1"
}
}
npm link
命令,创建软链接指向到本项目的 index.js
文件。这样,就能再开发的时候,直接使用 rcli
命令直接进行测试 ~rcli
会依赖一些包:rcli new PROJECT-NAME
#!/usr/bin/env node
'use strict';
const program = require('commander');
const log = console.log;
// new command
program
// 定义 new 命令,且后面跟一个必选的 projectName 参数
.command('new <projectName>')
// 对 new 命令的描述
.description('use create-react-app create a app')
// 定义使用 new 命令之后可以使用的选项 -n(使用 npm 来安装依赖)
// 在使用 create-react-app 中,我们可以可以添加 --use-npm 选项,来使用 npm 安装依赖(默认使用 yarn 安装依赖)
// 所以,我将这个选项添加到了 rcli 中
.option('-n, --use-npm', 'Whether to use npm to download dependencies')
// 定义执行 new 命令后调用的回调函数
// 第一个参数便是在定义 new 命令时的必选参数 projectName
// cmd 中包含了命令中选项的值,当我们在 new 命令中使用了 --use-npm 选项时,cmd 中的 useNpm 属性就会为 true,否则为 undefined
.action(function(projectName, cmd) {
const isUseNpm = cmd.useNpm ? true : false;
// 创建 react app
createReactApp(projectName, isUseNpm);
});
program.parse(process.argv);
/**
* 使用 create-react-app 创建项目
* @param {string} projectName 项目名称
* @param {boolean} isUseNpm 是否使用 npm 安装依赖
*/
function createReactApp(projectName, isUseNpm) {
let args = ['create-react-app', projectName];
if (isUseNpm) {
args.push('--use-npm');
}
// 创建子进程,执行 npx create-react-app PROJECT-NAME [--use-npm] 命令
spawn.sync('npx', args, { stdio: 'inherit' });
}
上面的代码边实现了 rcli new PROJECT-NAME
的功能:
#!/usr/bin/env node
表示使用 node 执行本脚本rcli g [options]
#!/usr/bin/env node
'use strict';
const program = require('commander');
const spawn = require('cross-spawn');
const chalk = require('chalk');
const path = require('path');
const fs = require('fs-extra');
const log = console.log;
program
// 定义 g 命令
.command('g')
// 命令 g 的描述
.description('Generate a component')
// 定义 -c 选项,接受一个必选参数 componentName:组件名称
.option('-c, --component-name <componentName>', 'The name of the component')
// 定义 --no-folder 选项:表示当使用该选项时,组件不会被文件夹包裹
.option('--no-folder', 'Whether the component have not it is own folder')
// 定义 -p 选项:表示当使用该选项时,组件为继承自 React.PureComponent 的类组件
.option(
'-p, --pure-component',
'Whether the component is a extend from PureComponent'
)
// 定义 -s 选项:表示当使用该选项时,组件为无状态的函数组件
.option(
'-s, --stateless',
'Whether the component is a extend from PureComponent'
)
// 定义执行 g 命令后调用的回调函数
.action(function(cmd) {
// 当 -c 选项没有传参数进来时,报错、退出
if (!cmd.componentName) {
log(chalk.red('error: missing required argument `componentName`'));
process.exit(1);
}
// 创建组件
createComponent(
cmd.componentName,
cmd.folder,
cmd.stateless,
cmd.pureComponent
);
});
program.parse(process.argv);
/**
* 创建组件
* @param {string} componentName 组件名称
* @param {boolean} hasFolder 是否含有文件夹
* @param {boolean} isStateless 是否是无状态组件
* @param {boolean} isPureComponent 是否是纯组件
*/
function createComponent(
componentName,
hasFolder,
isStateless = false,
isPureComponent = false
) {
let dirPath = path.join(process.cwd());
// 组件在文件夹中
if (hasFolder) {
dirPath = path.join(dirPath, componentName);
const result = fs.ensureDirSync(dirPath);
// 目录已存在
if (!result) {
log(chalk.red(`${dirPath} already exists`));
process.exit(1);
}
const indexJs = getIndexJs(componentName);
const css = '';
fs.writeFileSync(path.join(dirPath, `index.js`), indexJs);
fs.writeFileSync(path.join(dirPath, `${componentName}.css`), css);
}
let component;
// 无状态组件
if (isStateless) {
component = getStatelessComponent(componentName, hasFolder);
} else {
// 有状态组件
component = getClassComponent(
componentName,
isPureComponent ? 'React.PureComponent' : 'React.Component',
hasFolder
);
}
fs.writeFileSync(path.join(dirPath, `${componentName}.js`), component);
log(
chalk.green(`The ${componentName} component was successfully generated!`)
);
process.exit(1);
}
/**
* 获取类组件字符串
* @param {string} componentName 组件名称
* @param {string} extendFrom 继承自:'React.Component' | 'React.PureComponent'
* @param {boolean} hasFolder 组件是否在文件夹中(在文件夹中的话,就会自动加载 css 文件)
*/
function getClassComponent(componentName, extendFrom, hasFolder) {
return '省略...';
}
/**
* 获取无状态组件字符串
* @param {string} componentName 组件名称
* @param {boolean} hasFolder 组件是否在文件夹中(在文件夹中的话,就会自动加载 css 文件)
*/
function getStatelessComponent(componentName, hasFolder) {
return '省略...';
}
/**
* 获取 index.js 文件内容
* @param {string} componentName 组件名称
*/
function getIndexJs(componentName) {
return `import ${componentName} from './${componentName}';
export default ${componentName};
`;
}
rcli g [options]
命令的功能git 作为一个版本控制工具,是我们程序员平时工作中不可缺少的一部分。但有一个问题,我们开发完一个小功能或修改了一个 bug,都需要 add 然后 commit 一下,每次都要敲这么多的字符。作为经常使用 git 的我们来说,这是不能忍受的!
这个时候,可以使用 git alias!
根据 git 官方文档说明,我们可以通过以下命令定义 git alias:
git config --global alias.a add
git config --global alias.c commit
git config --global alias.o checkout
上面那种用命令定义 alias 的方式,需要敲这么多前置的命令,太麻烦了。这个时候,我们可以通过 git 的配置文件来配置 alias
~/
目录下找到 .gitconfig
文件.gitconfig
文件末尾添加:[alias]
a = add
c = commit
o = checkout
# ...
这样,我们就可以直接使用 git a
、git c
、git o
来代替 git add
、git commit
、git o
啦!
之前的都是我们自己配置的一些 git alias,当然有别人给我们配好了的:git alias。里面包含了非常非常非常多的 git alias,具体的 alias 所对应的真正的 git 命令,可以查看该项目的 gitalias.txt
文件。
# 如:
# gitalias.txt 文件中一个单词的 alias
a = add
b = branch
c = commit
d = diff
f = fetch
g = grep
l = log
m = merge
o = checkout
p = pull
r = remote
s = status
w = whatchanged
gitalias.txt
文件下载下来.gitconfig
文件里面加入:[include]
path = gitalias.txt
gitalias.txt
中的所有 alias,都已被引入,就可以直接使用了!git
命令用 g
命令替代~/.bash_profile
文件alias g=git
source ~/.bash_profile
命令这样,git
也可以使用 g
命令来替代了!
根据 git alias more ideas 介绍,我们可以使用其他工具来使用 git alias,如:
完~
在工作了两年之后,就一直想着跳槽,去一家大公司。在 5 月 25 号(2019 年)时,我向老板提了离职,并且此时我开始在网上投简历,开始面试。
我的打算是,一开始先去面面看,积累积累面试的经验,等做好了充分的准备之后,再投大公司的简历。
这家公司是一家不太大的公司。
首先是做了两道面试题,在半个小时之内做出来。我做出了一道。然后是面试官和 hr 对我进行面试,问了一些基础的问题:
第二家公司是 “晓信”,是一家挺大的公司。
在面试时,因为没有准备好,回答的挺差的。未通过面试。
无
这次,我有了充分的准备。主要是介绍了自己毕业自哪里;所学专业是什么;自己所掌握的技术有哪些;在上家公司主要是做什么工作的;自己对自己的评价以及自己的优势;
通过了面试。
无
通过了面试。
// 父类型
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
};
// 子类型
function SubType() {
this.subproperty = false;
}
// 子类型继承 SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function() {
return this.subproperty;
};
var instance = new SubType();
console.log(instance.getSuperValue()); // true
原型链实现继承的关键代码:SubType.prototype = new SuperType;
// 父类型
function SuperType() {
this.colors = ['red', 'blue', 'green'];
}
// 子类型
function SubType() {}
// 通过原型链实现继承
SubType.prototype = new SuperType(); // { colors: ['red', 'blue', 'green'] }
// 实例 1
var instance1 = new SubType();
// 操作实例 1 的 colors 属性
instance1.colors.push('black');
console.log(instance1.colors); // ['red', 'blue', 'green', 'black']
// 实例 2
var instance2 = new SubType();
console.log(instance2.colors); // ['red', 'blue', 'green', 'black']
// 父类型
function SuperType(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
// 子类型
function SubType() {
// 执行父类型构造函数,且修改其运行时的 this 值
// this 值表示子类型的实例对象
// 所以当 new SubType 时,此时子类型的实例对象就拥有了 colors 属性
SuperType.call(this, 'Bob');
}
var instance1 = new SubType();
instance1.colors.push('black');
console.log(instance1.colors); // ['red', 'blue', 'green', 'black']
var instance2 = new SubType();
console.log(instance2.colors); // ['red', 'blue', 'green']
借用构造函数实现继承关键代码:SuperType.call(this)
原型链实现继承
中的子类型中的引用类型的实例属性会被共享以及不能给父类型构造函数传参的问题组合继承就是将 原型链实现继承
和 借用构造函数实现继承
结合起来
// 父类型
function SuperType(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
SuperType.prototype.sayName = function() {
console.log(this.name);
};
// 子类型
function SubType(name, age) {
// 继承属性
SuperType.call(this, name);
}
// 继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为了 js 中最常用的继承模式
借用构造函数
继承属性原型链
继承方法function object(o) {
// 定义 F 构造函数
function F() {}
// 将需要继承的对象赋值给 F 的原型
F.prototype = o;
// 返回实例对象,通过该实例对象可以访问到 F 的原型对象,所以可以访问到 o 对象的属性
return new F();
}
// 或者通过 Object.create() 方法实现
function createAnother(original) {
// 使用原型式继承的 object 方法(如上)
var clone = object(original);
// 添加方法,对 clone 对象增强
clone.sayHi = function() {
console.log('hi);
}
return clone;
}
function inheritPrototype(subType, superType) {
// 复制一份父类型的原型对象
var prototype = Object(superType.prototype);
// 然后赋值给子类型的原型
// 这样子类型的实例就可以通过原型访问到父类型原型上的方法和属性了
prototype.constructor = subType;
subType.prototype = prototype;
}
// 父类型
function SuperType(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
SuperType.prototype.sayName = function() {
console.log(this.name);
};
// 子类型
function SubType(name, age) {
// 继承属性
SuperType.call(this, name);
this.age = age;
}
// 继承方法
inheritPrototype(SubType, SuperType);
寄生组合式继承结合了寄生和组合的优点,是引用类型最理想的继承范式
上一篇中,总结了一下 transition 以及 cubic-bezier()。本篇中,将介绍 css3 动画中的更为灵活的动画属性:animation。
css3 中的 animation 属性是一系列配置的简写形式,其子属性有:
animation-name 表示的是关键帧的名称,那么如何定义关键帧呢?使用 @keyframes。
@Keyframes 是定义 css3 animation 动画的关键所在。通过定义每一帧的样式状态,比 transition 能更好地控制中间过程。假如说 transition 只能定义 “两帧” 的状态,则 animation 可以定义 “n 帧(n >= 2)” 的状态。
“@Keyframes + 名称 { // 关键帧样式... }”
@keyframes move {
from {
width: 100px;
}
to {
width: 200px;
}
}
/* 或 */
@keyframes move {
0% {
width: 100px;
}
100% {
width: 200px;
}
}
其实 animation 也并不复杂,其有 8 个子属性。
下一篇:css3 动画(四)animation.css 源码分析(通过阅读 animation.css 动画库的源码,来提高对 css3 中 animation 属性的认识)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.