438198602 / frontend Goto Github PK
View Code? Open in Web Editor NEW前端开发的一些随笔
Home Page: https://438198602.github.io/frontend/
License: MIT License
前端开发的一些随笔
Home Page: https://438198602.github.io/frontend/
License: MIT License
root = true
[*]
# 缩进样式 空格
indent_style = space
# 缩进宽度 4个空格
indent_size = 4
# 字符编码 utf-8
charset = utf-8
# 换行符使用 unix 的换行符 \n
end_of_line = lf
# 行尾允许空格
trim_trailing_whitespace = true
# 文件末尾加一个空行
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false # .md 文件不去掉每行末尾的空格
当图片加载失败或者没有图片的时候,使用伪元素来显示默认图片
img 标签必须有 alt 属性,否则 Firefox 浏览器不生效
img {
display: inline-block;
position: relative;
width: 540px;
height: 258px;
background: #ccc;
vertical-align: top;
}
img:after {
content: '';
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: url('https://www.baidu.com/img/bd_logo1.png') #ccc;
}
一、vue
https://www.jianshu.com/p/9db8eb16d76f
https://juejin.im/post/5b86f6cc5188256fd44c0ce9
vue的createElement 返回 VNode 对象。
// VNode
export default class VNode {
tag: string | void;
data: VNodeData | void;
children: ?Array<VNode>;
text: string | void;
elm: Node | void;
ns: string | void;
context: Component | void; // rendered in this component's scope
key: string | number | void;
componentOptions: VNodeComponentOptions | void;
componentInstance: Component | void; // component instance
parent: VNode | void; // component placeholder node
// strictly internal
raw: boolean; // contains raw HTML? (server only)
isStatic: boolean; // hoisted static node
isRootInsert: boolean; // necessary for enter transition check
isComment: boolean; // empty comment placeholder?
isCloned: boolean; // is a cloned node?
isOnce: boolean; // is a v-once node?
asyncFactory: Function | void; // async component factory function
asyncMeta: Object | void;
isAsyncPlaceholder: boolean;
ssrContext: Object | void;
fnContext: Component | void; // real context vm for functional nodes
fnOptions: ?ComponentOptions; // for SSR caching
fnScopeId: ?string; // functioanl scope id support
constructor (
tag?: string,
data?: VNodeData,
children?: ?Array<VNode>,
text?: string,
elm?: Node,
context?: Component,
componentOptions?: VNodeComponentOptions,
asyncFactory?: Function
) {
this.tag = tag // 当前节点标签名
this.data = data // 当前节点数据(VNodeData类型)
this.children = children // 当前节点子节点
this.text = text // 当前节点文本
this.elm = elm // 当前节点对应的真实DOM节点
this.ns = undefined // 当前节点命名空间
this.context = context // 当前节点上下文
this.fnContext = undefined // 函数化组件上下文
this.fnOptions = undefined // 函数化组件配置项
this.fnScopeId = undefined // 函数化组件ScopeId
this.key = data && data.key // 子节点key属性
this.componentOptions = componentOptions // 组件配置项
this.componentInstance = undefined // 组件实例
this.parent = undefined // 当前节点父节点
this.raw = false // 是否为原生HTML或只是普通文本
this.isStatic = false // 静态节点标志 keep-alive
this.isRootInsert = true // 是否作为根节点插入
this.isComment = false // 是否为注释节点
this.isCloned = false // 是否为克隆节点
this.isOnce = false // 是否为v-once节点
this.asyncFactory = asyncFactory // 异步工厂方法
this.asyncMeta = undefined // 异步Meta
this.isAsyncPlaceholder = false // 是否为异步占位
}
// 容器实例向后兼容的别名
get child (): Component | void {
return this.componentInstance
}
}
其实就是一个普通的 JavaScript Class 类,中间有各种数据用于描述虚拟 DOM。
二、react
class Element {
constructor(type, attr, children) {
this.type = type;
this.attr = attr;
this.children = children;
/*this.props={...attr,children:children}*/
}
render() {
// 这个方法将虚拟的DOM转成真实的DOM;
let ele = document.createElement(this.type);
// 2. 设置行间属性
for (let key in this.attr) {
let _key = key;
if (key === "className") {
_key = "class";
}
if (key === "htmlFor") {
_key = "for";
}
ele.setAttribute(_key, this.attr[key]);
}
// 3.children
this.children.forEach(item => {
// 如果数组中的成员是Element的实例,需要继续调用render方法;将虚拟的DOM转成真实的DOM;
// 循环子节点,都放入ele中;
let curEle =
item instanceof Element
? item.render()
: document.createTextNode(item);
ele.appendChild(curEle);
});
return ele; // 将创建的元素转成DOM返回;
}
}
let obj = {
createElement(type, attr, ...children) {
// type: 元素类型
// attr:行间属性
// children : 子节点 ... 把多余的参数放进一个数组中;
return new Element(type, attr, children);
}
};
let objDOM = {
render(element, container) {
// container : 容器,根元素;
// element: 虚拟的DOM对象;当render执行时,让这个虚拟的DOM转成真实的DOM;
container.appendChild(element.render());
}
};
let y = obj.createElement("div", { a: 1, className: 12 }, "**北京");
objDOM.render(y, document.getElementById("root"));
最终render结果
<div a="1" class="12">**北京</div>
apply和call两者在作用上是相同的,但两者在参数上有区别的。
第一个参数意义都一样。第二个参数:apply传入的是一个参数数组,也就是将多个参数组合成为一个数组传入,而call则作为call的参数传入(从第二个参数开始)。
例如 func.call(func1,var1,var2,var3) 对应的apply写法为:func.apply(func1,[var1,var2,var3]),同时使用apply的好处是可以直接将当前函数的arguments对象作为apply的第二个参数传入。
在开发商城项目的时候,会遇到这样的场景:用户创建了订单之后,前端开发需要一直轮询来知道用户是否支付。这时候大多数人的第一想法就是,哦,这个简单,setInterval就解决了。但 setInterval 真的完美吗。
我们来看一个例子:
let startTime = new Date().getTime(),
count = 0;
// 模拟耗时任务
setInterval(function() {
let i = 0;
while (i++ < 1000000000);
}, 0);
setInterval(function() {
count++;
console.log("间隔:", new Date().getTime() - (startTime + count * 1000), "毫秒");
}, 1000);
结果:
间隔: 852 毫秒
间隔: 1098 毫秒
间隔: 1950 毫秒
间隔: 2175 毫秒
间隔: 2980 毫秒
间隔: 3203 毫秒
间隔: 4035 毫秒
间隔: 4865 毫秒
间隔: 5102 毫秒
间隔: 6093 毫秒
间隔: 6976 毫秒
间隔: 7245 毫秒
间隔: 8050 毫秒
间隔: 8908 毫秒
间隔: 9131 毫秒
间隔: 10020 毫秒
间隔: 10882 毫秒
间隔: 11153 毫秒
...
可以看出,相差的时间是越来越大,越来越不准确,并且有时候两次执行间隔很短。
首先,我们要明白,定时器指定的时间间隔,表示的是何时将定时器的代码添加到消息队列,而不是何时执行代码。所以真正何时执行代码的时间是不能保证的,取决于何时被主线程的事件循环取到,并执行。
setInterval(function, N)
// 即:每隔 N 秒把 function 事件推到消息队列中
假设定时器里面的代码需要进行大量的计算(耗费时间较长),或者是 DOM 操作。这样一来,花的时间就比较长,有可能前一次代码还没有执行完,后一次代码就被添加到队列了。这就导致定时器变得不准确,甚至出现同一时间执行两次的情况。
注意:当使用 setInterval 时,仅当队列中没有该定时器代码实例时,才添加到任务队列中。
所以,使用 setInterval 时可能会导致:
1、某些间隔会被跳过;
2、可能多个定时器会连续执行;
可以这么理解:每个 setTimeout 产生的任务会直接 push 到任务队列中;而 setInterval 在每次把任务 push 到任务队列前,都要进行一下判断(看上次的任务是否仍在队列中,如果有则不添加,没有则添加)。
因而我们一般用 setTimeout 模拟 setInterval ,来规避掉上面的缺点。
在前一个定时器执行完前,不会向队列插入新的定时器(解决缺点一)
保证定时器间隔(解决缺点二)
上面的例子就可以改为:
let startTime = new Date().getTime(),
count = 0;
// 模拟耗时任务
setInterval(function() {
let i = 0;
while (i++ < 1000000000);
}, 0);
const repeat = (func, ms) => {
setTimeout(() => {
func();
repeat(func, ms);
}, ms);
};
repeat(() => {
count++;
console.log("间隔:", new Date().getTime() - (startTime + count * 1000), "毫秒");
}, 1000);
监听 touchmove 事件,阻止其默认操作 event.preventDefault()
vue
@touchmove.prevent
相信前端开发者们都听说过CSS属性书写顺序这种说法,那你知道CSS属性的最佳书写顺序?为什么要这样写吗?
定位属性:position、display、float、left、top、right、bottom、overflow、clear、z-index
自身属性:width、height、padding、border、margin、background
文字样式:font-family、font-size、font-style、font-weight、color
文本属性:text-align、vertical-align、text-wrap、text-transform、text-indent、text-decoration、letter-spacing、word-spacing、white-space、text-overflow
css3中新增属性:content、box-shadow、border-radius、transform
减少浏览器reflow(回流),从而提升浏览器渲染dom的性能。
详细点就是:
1、解析html构建dom树,解析css构建css树:将html解析成树形的数据结构,将css解析成树形的数据结构。
2、构建render树:DOM树和CSS树合并之后形成的render树。
3、布局render树:有了render树,浏览器已经知道网页中有哪些节点,各个节点的css定义以及它们的从属关系,从而计算出每个节点在屏幕中的位置。
4、绘制render树:按照计算出来的规则,通过显卡把内容画在屏幕上。
css样式解析到显示至浏览器屏幕上就发生在 2、3、4 步骤,可见浏览器并不是一获取到css样式就立马开始解析而是根据css样式的书写顺序将之按照dom树的结构分布render样式,完成第 2 步,然后开始遍历每个树结点的css样式进行解析,此时的css样式的遍历顺序完全是按照之前的书写顺序。
在解析过程中,一旦浏览器发现某个元素的定位变化影响布局,则需要倒回去重新渲染。正如按照这样的书写书序:
width: 100px;
height: 100px;
position: absolute;
当浏览器解析到 position 的时候突然发现该元素是绝对定位元素需要脱离文档流,而之前却是按照普通元素进行解析的,所以不得不重新渲染,解除该元素在文档中所占位置,然而由于该元素的占位发生变化,其他元素也可能会受到它回流的影响而重新排位。最终导致 3 步骤花费的时间太久而影响到 4 步骤的显示,影响了用户体验。
参考链接:https://hateonion.me/posts/aeb8/
// 对 JavaScript 文件使用 TypeScript 的 Type Checking(类型检查)
// 下面这行绝对不能删除
// @ts-check
// 对变量进行类型检查
/**
* @type {number}
*/
let x;
x = 0;
// 对函数进行类型检查
/**
* @param {string} foo
* @param {string} bar
* @returns {string}
*/
function test1(foo, bar) {
return `${foo} and ${bar}`;
}
类似 TypeScript 的写法
// @ts-check
/**
* @typedef Example1
* @prop {string} street 街道
* @prop {string} city 城市
* @prop {number} zip 邮政编码
*
* @typedef Example2
* @prop {string} name
* @prop {string} email
* @prop {Boolean} isShow
*/
/** @type {Example1} */
const data1 = {
street: '街道',
city: 100
};
/** @type {Example1 & Example2} */
const data2 = {
street: '街道',
city: 100,
name: '001',
isShow: 2
};
parseInt(string, radix)
参数 | 描述 |
---|---|
string | 必需。要被解析的字符串。 |
radix | 可选。表示要解析的数字的基数。该值介于 2 ~ 36 之间。 |
parseInt() 函数可解析一个字符串,并返回一个整数。
先来看几个例子:
console.log(parseInt(0.5)); // => 0
console.log(parseInt(0.05)); // => 0
console.log(parseInt(0.005)); // => 0
console.log(parseInt(0.0005)); // => 0
console.log(parseInt(0.00005)); // => 0
console.log(parseInt(0.000005)); // => 0
console.log(parseInt(0.0000005)); // => 5
最后一个为什么是 5 呢?
我们再看一看 parseInt 的第一个参数:如果它不是字符串,则将其转换为字符串,然后解析,并返回解析后的整数。
接下来,我们尝试将浮点数手动转换为字符串表示形式:
console.log(String(0.5)); // => '0.5'
console.log(String(0.05)); // => '0.05'
console.log(String(0.005)); // => '0.005'
console.log(String(0.0005)); // => '0.0005'
console.log(String(0.00005)); // => '0.00005'
console.log(String(0.000005)); // => '0.000005'
console.log(String(0.0000005)); // => '5e-7'
然后在使用 parseInt 解析,看下结果:
console.log(parseInt(0.0000005)); // => 5
console.log(parseInt(5e-7)); // => 5
console.log(parseInt('5e-7')); // => 5
是不是发现问题了。因为 parseInt 始终将其第一个参数转换为字符串,所以小于10的-6次方的浮点数将以指数表示。然后 parseInt 从 float 的指数表示法中提取整数。
所以,为了安全地提取浮点数的整数部分,建议使用 Math.floor() 函数。
parseInt 是将数字字符串解析为整数的函数。
使用 parseInt 提取浮点数的整数部分时必须小心。
小于10的-6次方 (例如0.0000005,也就是5*10-7) 的浮点数转换成字符串时被写成指数表示法 (例如 5e-7 是 0.0000005 的指数表示法)。这就是为什么在 parseInt 中使用这么小的浮点数会导致意想不到的结果:只有指数表记的重要部分(例如 5e-7 中的 5)会被解析。
0.1 + 0.2 = 0.30000000000000004
0.3 - 0.2 = 0.09999999999999998
0.1 * 3 = 0.30000000000000004
0.3 / 0.1 = 2.9999999999999996
让我们来看一下为什么 0.1 + 0.2 会等于 0.30000000000000004,而不是 0.3。
在 JavaScript 中所有数字包括整数和小数都只有一种类型 Number
。它的实现遵循 IEEE二进制浮点数算术标准(IEEE 754),使用64位固定长度来表示,也就是标准的 double 双精度浮点数。
首先,十进制的 0.1 和 0.2 会被转换成二进制的,但是由于浮点数用二进制表示时是无穷的:
0.1 -> 0.00011001100110011001100110011001100110011001100110011010
0.2 -> 0.0011001100110011001100110011001100110011001100110011010
IEEE 754 标准的 64 位双精度浮点数的小数部分最多支持53位二进制位,所以两者相加之后得到二进制为:
0.0100110011001100110011001100110011001100110011001100111
因浮点数小数位的限制而截断的二进制数字,再转换为十进制,就成了0.30000000000000004。所以在进行算术计算时会产生误差。
通常我们会使用 toFixed() 来解决,但其在常见浏览器上的表现不一致。
如:1.335.toFixed(2)
正常应该返回 1.34
浏览器 | IE7-11 | edge | Firefox | chrome |
---|---|---|---|---|
结果 | 1.34 | 1.33 | 1.33 | 1.33 |
测试结果只有 IE 符合预期。
针对 toFixed 的兼容性问题,我们可以把 toFixed 重写一下来解决。
我们通过判断最后一位是否大于等于5来决定需不需要进位,如果需要进位先把小数乘以倍数变为整数,加1之后,再除以倍数变为小数,这样就不用一位一位的进行判断。
// toFixed兼容方法
Number.prototype.toFixed = function(len) {
if (len > 20 || len < 0) {
throw new RangeError('toFixed() digits argument must be between 0 and 20');
}
// 对数字末尾加0
function padNum(num) {
num = num.toString();
var dotPos = num.indexOf('.');
if (dotPos === -1) {
// 整数的情况
num += '.';
for (var i = 0; i < len; i++) {
num += '0';
}
return num;
} else {
// 小数的情况
var need = len - (num.length - dotPos - 1);
for (var j = 0; j < need; j++) {
num += '0';
}
return num;
}
}
// .123 转为 0.123
var number = Number(this);
if (isNaN(number) || number >= Math.pow(10, 21)) {
return number.toString();
}
if (typeof (len) == 'undefined' || len == 0) {
return (Math.round(number)).toString();
}
var result = number.toString(),
numberArr = result.split('.');
if (numberArr.length < 2) {
// 整数的情况
return padNum(result);
}
var intNum = numberArr[0], // 整数部分
deciNum = numberArr[1], // 小数部分
lastNum = deciNum.substr(len, 1); // 最后一个数字
if (deciNum.length == len) {
// 需要截取的长度等于当前长度
return result;
}
if (deciNum.length < len) {
// 需要截取的长度大于当前长度 1.3.toFixed(2)
return padNum(result);
}
// 需要截取的长度小于当前长度,需要判断最后一位数字
result = intNum + '.' + deciNum.substr(0, len);
if (parseInt(lastNum, 10) >= 5) {
// 最后一位数字大于5,要进位
var times = Math.pow(10, len); // 需要放大的倍数
var changedInt = Number(result.replace('.', '')); // 截取后转为整数
changedInt++; // 整数进位
changedInt /= times; // 整数转为小数,注:有可能还是整数
result = padNum(changedInt + '');
}
return result;
}
一元运算符, 将操作数的值加一. 如果放在操作数前面 (++x), 则返回加一后的值; 如果放在操作数后面 (x++), 则返回操作数原值, 然后再将操作数加一.
var x = 3;
console.log(++x);
// 4
console.log(x);
// 4
var y = 3;
console.log(y++);
// 3
console.log(y);
// 4
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.