css部分
盒子模型
margin+border+padding+content
-
Content边界/内边界: content边界环绕在由该元素的宽和高决定的一个矩形上,这个尺寸通常由该元素渲染后的内容决定,这四个content边界组成的矩形框就是该元素的content box.
-
Padding边界:padding边界环绕在该元素的padding区域的四周,顾名思义,填充背景色.如果padding的宽度为零,则padding边界与content边界重合.这四个padding边界组成的矩形就是该元素的padding box.
-
Border边界:border边界环绕在该元素的border区域的四周.如果border的宽度为零,则border边界与padding边界重合.这四个border边界组成的矩形就是该元素的border box.
-
Margin边界:margin边界环绕在该元素的margin区域的四周,如果margin的宽度为零,则margin边界与border边界重合.这四个margin边界组成的矩形就是该元素的margin box.
-
对于行内非替换元素(例如span),垂直方向上的margin不起作用
- 替换元素:input、textarea、img、video、select
- 非替换元素:div、span、h1~h6…
-
margin不适用于table类型的元素,如table-caption,table,inline-table,以及td,th,tr等.
-
border-style的可用值有:’none’(无样式),’hidden’(隐藏),’dotted’(点线),’dashed’(虚线 ),’solid’(实线),’double’(双线),’groove’(3D凹槽轮廓),’ridge’(3D吐槽轮廓),’inset’(3D凹边轮廓), ‘outset’(3D凸边轮廓)
-
行内非替换元素的padding会生效,但不会撑开父级元素(可以利用这个特性用来做一些类似时间轴一样的效果)
-
垂直方向的相邻的外边距会发生外边距的重叠现象
- 如果都是正边界,结果的边界宽度是相邻边界宽度中最大的值
- 如果出现负边界,则在最大的正边界中减去绝对值最大的负边界
- 如果没有正边界,则从零中减去绝对值最大的负边界
-
兄弟元素的相邻外边距会取最大值
-
父子元素的相邻外边距会传递给父元素
display
none | 此元素不会被显示。 |
---|---|
block | 此元素将显示为块级元素,此元素前后会带有换行符。 |
inline | 默认。此元素会被显示为内联元素,元素前后没有换行符。 |
inline-block | 行内块元素。(CSS2.1 新增的值) |
list-item | 此元素会作为列表显示。 |
run-in | 此元素会根据上下文作为块级元素或内联元素显示。 |
compact | CSS 中有值 compact,不过由于缺乏广泛支持,已经从 CSS2.1 中删除。 |
marker | CSS 中有值 marker,不过由于缺乏广泛支持,已经从 CSS2.1 中删除。 |
table | 此元素会作为块级表格来显示(类似 <table>),表格前后带有换行符。 |
inline-table | 此元素会作为内联表格来显示(类似 <table>),表格前后没有换行符。 |
table-row-group | 此元素会作为一个或多个行的分组来显示(类似 <tbody>)。 |
table-header-group | 此元素会作为一个或多个行的分组来显示(类似 <thead>)。 |
table-footer-group | 此元素会作为一个或多个行的分组来显示(类似 <tfoot>)。 |
table-row | 此元素会作为一个表格行显示(类似 <tr>)。 |
table-column-group | 此元素会作为一个或多个列的分组来显示(类似 |
table-column | 此元素会作为一个单元格列显示(类似 <col>) |
table-cell | 此元素会作为一个表格单元格显示(类似 <td> 和 <th>) |
table-caption | 此元素会作为一个表格标题显示(类似 <caption>) |
inherit | 规定应该从父元素继承 display 属性的值。 |
position
- absolute 相对于第一个不是static定位的父级的绝对定位
- relative 生成相对定位的元素,相对于其正常位置进行定位。
- fixed 相对与浏览器窗口的绝对定位
- static 默认值。没有定位,元素出现在正常的流中(忽略 top, bottom, left, right 或者 z-index 声明)。
- inherit 继承父级的定位方式
flex布局
设为 Flex 布局以后,子元素的float
、clear
和vertical-align
属性将失效。
flex-direction
属性决定主轴的方向(即项目的排列方向)。row
主轴为水平方向,起点在左端row-reverse
主轴为水平方向,起点在右端column
主轴为垂直方向,起点在上沿column-reverse
主轴为垂直方向,起点在下沿
flex-wrap
规定一条轴线上排不下时,项目如何换行(默认全都排列在同一行)nowrap
(默认)不换行wrap
正常换行,即第一行在上方wrap-reverse
反向换行,即第一行在下方
flex-flow
他是flex-direction
和flex-wrap
的缩写,默认为:row nowrap
justify-content
规定了项目在主轴方向上的对齐方式flex-start
左对齐flex-end
右对齐center
居中space-between
两端对齐,项目中间的间隔都相同space-around
每个项目两侧的间隔相同
align-items
属性 在交叉轴
也就是垂直方向上的对齐方式flex-start
顶端对齐flex-end
底端对齐center
垂直居中对齐stretch
(默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。baseline
: 项目的第一行文字的基线对齐
align-content
属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用flex-start
:与交叉轴的起点对齐。flex-end
:与交叉轴的终点对齐。center
:与交叉轴的中点对齐。space-between
:与交叉轴两端对齐,轴线之间的间隔平均分布。space-around
:每根轴线两侧的间隔都相等。所以,轴线之间的间隔比轴线与边框的间隔大一倍。stretch
(默认值):轴线占满整个交叉轴。
以上都是flex容器的属性,接下来再看看flex项目的一些属性
order
属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。flex-grow
属性定义项目的放大比例,默认为0
,即如果存在剩余空间,也不放大flex-shrink
属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小flex-basis
属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto
,即项目的本来大小flex
属性是flex-grow
,flex-shrink
和flex-basis
的简写,默认值为0 1 auto
。后两个属性可选align-self
属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items
属性。默认值为auto
,表示继承父元素的align-items
属性,如果没有父元素,则等同于stretch
flex
布局能够让我们相当自由的布局页面,那么,我们来看看使用flex
布局方式,还可以帮助我们做什么。
- 垂直居中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
* {
padding: 0;
margin: 0;
}
.box {
background: #e6e6e6;
width: 200px;
height: 200px;
display: flex;
align-items: center;
justify-content: center;
margin: 15px auto;
border-radius: 5px;
box-shadow: #000 0 0 5px, inset #ccc 0 0 5px;
}
.box article {
background: #FFFFFF;
padding: 15px;
border-radius: 5px;
line-height: 1.5;
color: #333;
}
</style>
</head>
<body>
<div class="box">
<article>
<p>这是一篇文章</p>
<p>这是一篇文章</p>
<p>这是一篇文章</p>
</article>
</div>
</body>
</html>
- 占满剩余屏幕
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
* {
padding: 0;
margin: 0;
}
.box2 {
display: flex;
flex-direction: column;
align-items: center;
height: 100vh;
width: 100%;
}
header,
footer {
width: 100%;
background: rgb(0, 133, 123);
color: #FFFFFF;
text-align: center;
height: 40px;
line-height: 40px;
}
main {
width: 100%;
/* 在头部和脚部固定,且父级节点高度已知的情况下,使用flex-grow,让内容区放大填充满剩余空间,等同于height: calc(100vh - 40px -40px) */
flex-grow: 1;
background: #CCCCCC;
text-align: center;
overflow: auto;
box-sizing: border-box;
}
main p {
padding: 15px 0;
background: #FFFFFF;
border-bottom: dashed 1px #e6e6e6;
animation: show 1s;
transform-origin: bottom center;
}
main p:nth-last-child(1) {
border: none;
}
main p:active {
background: #eeeeee;
}
@keyframes show {
from {
transform: scale(0);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}
</style>
</head>
<body>
<div class="box2">
<header>头部</header>
<main>
<p>内容区</p>
<p>内容区</p>
<p>内容区</p>
<p>内容区</p>
<p>内容区</p>
<p>内容区</p>
<p>内容区</p>
<p>内容区</p>
<p>内容区</p>
<p>内容区</p>
<p>内容区</p>
<p>内容区</p>
<p>内容区</p>
<p>内容区</p>
<p>内容区</p>
<p>内容区</p>
<p>内容区</p>
<p>内容区</p>
<p>内容区</p>
</main>
<footer>脚部</footer>
</div>
</body>
</html>x
javascript
使用ES5方式实现继承
function Super(name){
this.name = name;
this.sayName = function(){
console.log(this.name);
}
}
// 1. 构造函数继承
// 原理:直接调用构造函数,相当于将构造函数执行了一遍,将父级的属性复制过来一份
// 缺陷:相当于直接断开了原型链,没办法实现真正意义上的继承
function Child(name){
Super.apply(this,arguments);
this.name = name;
this.sayHello = function(){
console.log('你好,'+this.name);
}
}
// test case 1 start
var parent = new Super('kiner');
var child = new Child('kanger');
parent.sayName();
child.sayName();
child.sayHello();
// test case 1 end
function Super(name){
this.name = name;
this.sayName = function(){
console.log(this.name);
}
}
// 2. 原型链继承
// 原理:将子类的原型链指向父级的实例
// 缺陷:子类的构造函数和被修改了,并且由于子类的原型链直接引用父类的实例,因此如果一个实例引用的舒属性发生改变,也会导致其他继承与同一父类的其他子类也会跟着改变
function Child(name){
this.name = name;
this.sayHello = function(){
console.log('你好,'+this.name);
}
}
var parent = new Super('kiner');
Child.prototype = parent;
// test case 1 start
var child = new Child('kanger');
parent.sayName();
child.sayName();
child.sayHello();
// test case 1 end
function Super(name){
this.name = name;
this.sayName = function(){
console.log(this.name);
}
}
// 3. 组合继承
// 原理:结合前两种情况,修正原型链断裂和属性私有化以及构造函数被修改的问题
// 缺陷:
function Child(name){
Super.apply(this,arguments);
this.name = name;
this.sayHello = function(){
console.log('你好,'+this.name);
}
}
var parent = new Super('kiner');
Child.prototype = parent;
Child.prototype.constructor = Child;
// test case 1 start
var child = new Child('kanger');
parent.sayName();
child.sayName();
child.sayHello();
// test case 1 end
function Super(name){
this.name = name;
this.sayName = function(){
console.log(this.name);
}
}
// 4. 利用Object.create
// 原理:利用Object.create赋值一份原型链
// 缺陷:
function Child(name){
Super.apply(this,arguments);
this.name = name;
this.sayHello = function(){
console.log('你好,'+this.name);
}
}
Child.prototype = Object.create(Super.prototype);
Child.prototype.constructor = Child;
// 显式赋值了constructor之后,我们还需要修正一下constructor的可枚举属性,避免通过for...in循环时将constructor循环出来
Object.defineProperty(Child.prototype,"constructor",{
enumerable: false
})
// test case 1 start
var parent = new Super('kiner');
var child = new Child('kanger');
parent.sayName();
child.sayName();
child.sayHello();
// test case 1 end
function Super(name) {
this.name = name;
}
Super.prototype.sayName = function() {
console.log(this.name);
}
// 4. 组合寄生继承(推荐)
// 原理:利用上面总结的特性,实现一个相对完善的阶乘
// 缺陷:
function extend(Super, Child) {
function Fn() {}
Fn.prototype = Super.prototype;
var fn = new Fn();
fn.constructor = Child;
Child.prototype = fn;
Object.defineProperty(Child.prototype, "constructor", {
enumerable: false
});
}
function Child(name) {
this.name = name;
}
extend(Super, Child);
Child.prototype.sayHello = function() {
console.log('你好,' + this.name);
}
// test case 1 start
var parent = new Super('kiner');
var child = new Child('kanger');
parent.sayName();
child.sayName();
child.sayHello();
// test case 1 end
使用ES5方式实现深拷贝
var clone = function(obj){
var type = Object.prototype.toString.call(obj).slice(8,-1);
var res;
if(type === 'Array'){
res = [];
}else if(type === 'Object'){
res = {}
}
for(var item in obj){
if(typeof obj[item] === 'object'){
res[item] = clone(obj[item]);
}else{
res[item] = obj[item];
}
}
return res;
};
ES6新特性(ES2015)
-
类Class
class Demo{ constructor(){ } }
-
模块化 import export
// a.js export const filter = ()=>{}; export default {filter}; // b.js import util from './a.js'; import {filter} from './a.js';
-
箭头函数 ()=>{}
let show = ()=>{}; let show2 = name => console.log(name); let show3 = name => ({name});
-
函数参数默认值 function(arg=“name”){}
function showName(name=""){ }
-
模板字符串
let name = 'kiner'; let str = `这是一个模板字符串${name}`; console.log(str);// 这是一个模板字符串kiner
-
结构赋值
let userInfo = {name: 'kiner',age: 20, friends:[{name:'kanger'}]}; let {name,age,friends} = userInfo;// 结构对象 let [firstFriend] = friends;// 数组结构
-
延展操作符
let userInfo = {name: 'kiner',age: 20, friends:[{name:'kanger'}]}; let newUserInfo = {...userInfo}; let arr = [1,2,3]; let newArr = [...arr]; function fn(name,...rest){ }
-
对象属性简写
let name = "kiner"; let age = 18; let userInfo = { name, age, friends:[ {name: "kanger"} ] };
-
Promise
let p = new Promise((resolve, reject)=>{
resolve({data:{name: "kiner"}})
});
-
let 与 const
let userName = "kanger"; const user = {userName};
ES7新特性(ES2016)
- Array.prototype.includes()
let arr = [1,2,3,4,5];
console.log(arr.includes(3));// true
- 指数运算符 a
**
b
let res = 2 ** 10;// 1024
ES8新特性(ES2017)
async/await
和async/for await of
(async ()=>{
function request(){
return new Promise(resolve=>{
setTimeout(()=>{
resolve({
code: 0,
success: true,
data: {name: "kiner"}
});
},1000);
})
}
let res = await request();
console.log(res);// {code: 0, success: true, data: {name: "kienr"}}
})();
(async() => {
function request(params) {
return new Promise(resolve => {
setTimeout(() => {
resolve(`请求成功:${params.counter}`);
}, 1000);
})
}
const requests = [request({
counter: 0
}), request({
counter: 1
}), request({
counter: 2
}), request({
counter: 3
})];
for await (const item of requests) {
console.log(item);
// 请求成功: 0
// 请求成功: 1
// 请求成功: 2
// 请求成功: 3
}
})();
-
Object.values() 类似Object.keys(返回自身属性的键名)
class Parent { name = "kiner"; age = 20; } class Student extends Parent { classify = "student"; classNo = "0001"; } let p = new Parent(); let s = new Student(); console.log('Parent的属性', Object.keys(p)); console.log('Student的属性', Object.keys(s)); console.log('Parent的属性', Object.values(p)); console.log('Student的属性', Object.values(s));
-
Object.entries() 返回可枚举的键值对数组,结构如:[[key1,value1],[key2,value2]]
class Parent { name = "kiner"; age = 20; } class Student extends Parent { classify = "student"; classNo = "0001"; } let p = new Parent(); let s = new Student(); console.log(JSON.stringify(Object.entries(s), null, 4)); // [ // [ // "name", // "kiner" // ], // [ // "age", // 20 // ], // [ // "classify", // "student" // ], // [ // "classNo", // "0001" // ] // ]
-
String.prototype.padStart 和 String.prototype.padEnd
// 在字符串前面填充字符串,直至字符串长度等于目标长度为止 let str = 'kiner'; str = str.padStart(str.length+5,'*');// 在str前面填充5个* console.log(str);// 输出结果为:*****kiner str = str.padEnd(str.length+5,"#");// 在str后面填充5个# console.log(str);// 输出结果为:*****kiner#####
-
Object.getOwnProtertyDestriptors() 获取对象的属性描述符,如果没有任何属性则返回空对象
class Parent { name = "kiner"; age = 20; } class Student extends Parent { classify = "student"; classNo = "0001"; } let p = new Parent(); let s = new Student(); console.log(Object.getOwnPropertyDescriptors(s)); // { // "name": { // "value": "kiner", // "writable": true, // "enumerable": true, // "configurable": true // }, // "age": { // "value": 20, // "writable": true, // "enumerable": true, // "configurable": true // }, // "classify": { // "value": "student", // "writable": true, // "enumerable": true, // "configurable": true // }, // "classNo": { // "value": "0001", // "writable": true, // "enumerable": true, // "configurable": true // } // }
-
函数参数列表末尾允许出现逗号(在添加新的参数、属性、元素时是有用的,你可以直接新加一行而不必给上一行再补充一个逗号,这样使版本控制工具的修改记录也更加整洁)
function fn( arg1, arg2, ) { } fn( 'foo', 'bar', );
-
ShareArrayBuffer
SharedArrayBuffer
对象用来表示一个通用的,固定长度的原始二进制数据缓冲区,类似于 ArrayBuffer
对象,它们都可以用来在共享内存(shared memory)上创建视图。与 ArrayBuffer
不同的是,SharedArrayBuffer
不能被分离。
请注意,作为对Spectre的响应,所有主流浏览器均默认于2018年1月5日禁用SharedArrayBuffer
。 Chrome在启用了网站隔离功能的平台上的v67中重新启用了该功能,以防止出现Spectre风格的漏洞。
// create a SharedArrayBuffer with a size in bytes
const buffer = new SharedArrayBuffer(8);
console.log(buffer.byteLength);
// expected output: 8
- Atomics
Atomics
对象提供了一组静态方法用来对 SharedArrayBuffer
对象进行原子操作。
这些原子操作属于 Atomics
模块。与一般的全局对象不同,Atomics
不是构造函数,因此不能使用 new 操作符调用,也不能将其当作函数直接调用。Atomics
的所有属性和方法都是静态的(与 Math
对象一样)。
将指定位置上的数组元素与给定的值相加,并返回相加前该元素的值。
将指定位置上的数组元素与给定的值相与,并返回与操作前该元素的值。
如果数组中指定的元素与给定的值相等,则将其更新为新的值,并返回该元素原先的值。
将数组中指定的元素更新为给定的值,并返回该元素更新前的值。
返回数组中指定元素的值。
将指定位置上的数组元素与给定的值相或,并返回或操作前该元素的值。
将数组中指定的元素设置为给定的值,并返回该值。
将指定位置上的数组元素与给定的值相减,并返回相减前该元素的值。
将指定位置上的数组元素与给定的值相异或,并返回异或操作前该元素的值。
wait()
和 wake()
方法采用的是 Linux 上的 futexes 模型(fast user-space mutex,快速用户空间互斥量),可以让进程一直等待直到某个特定的条件为真,主要用于实现阻塞。
-
检测数组中某个指定位置上的值是否仍然是给定值,是则保持挂起直到被唤醒或超时。返回值为 “
ok
”、"not-equal
" 或 “time-out
”。调用时,如果当前线程不允许阻塞,则会抛出异常(大多数浏览器都不允许在主线程中调用wait()
)。 -
Atomics.wake()
唤醒等待队列中正在数组指定位置的元素上等待的线程。返回值为成功唤醒的线程数量。
-
可以用来检测当前系统是否支持硬件级的原子操作。对于指定大小的数组,如果当前系统支持硬件级的原子操作,则返回
true
;否则就意味着对于该数组,Atomics
对象中的各原子操作都只能用锁来实现。此函数面向的是技术专家。
ES9新特性(ES2018)
-
Promise.finally 无论是否成功,最终都会执行,可用于清理数据
let timer; let p = new Promise((resolve, reject) => { let num = Math.random(); timer = setInterval(() => { if (num < .5) { resolve("success"); } else { reject("fail"); } }, 1000); }); p.then(res => console.log(res)).catch(res => console.warn(res)).finally(() => { console.log('清理定时器'); clearInterval(timer) });
-
正则表达式的命名捕获分组
// 将 月-日-年 转换为 日-月-年 function toLocalDate(date){ return date.replace(/(?<month>\d{2})-(?<day>\d{2})-(?<year>\d{4})/, "$<day>-$<month>-$<year>") }
语法
命名捕获分组自身的语法是 (?…),比普通的分组多了一个 ? 字样,其中 name 的起法就和你平时起变量名一样即可(不过在这里关键字也可用)。
反向引用一个命名分组的语法是 \k,注意命名分组同样可以通过数字索引来反向引用,比如:
/(?<foo>a)\k<foo>\1/.test("aaa") // true
在 replace() 方法的替换字符串中反向引用是用 $:
"abc".replace(/(?<foo>a)/, "$<foo>-") // "a-bc",同样 $1 仍然可用
总结一下就是,和命名分组相关的有三种语法,分别是 ?、\k、$,相同点是都用尖括号包裹着分组名。
-
正则表达式正向断言(ES5)、反向断言(新增)、反向否定断言(新增)
// 正向断言 const reg1 = /\D(?=\d+)/; // 反向断言 const reg2 = /(?<=\D)\d+/; // 否定反向断言 const reg3 = /(?<!\D)\d+/; let money = "¥123.679"; let money2 = "123.679"; console.log(reg1.exec(money)); // ["¥", index: 0, input: "¥123.679", groups: undefined] console.log(reg2.exec(money)); // ["123", index: 1, input: "¥123.679", groups: undefined] console.log(reg3.exec(money)); // ["23", index: 2, input: "¥123.679", groups: undefined] console.log(reg3.exec(money2)); // ["123", index: 0, input: "123.679", groups: undefined]
-
正则表达式允许使用
.
匹配换行符console.log(/hello.word/.test('hello\nword')); // false console.log(/hello.word/s.test('hello\nword')); // true
ES10新特性(ES2019)
-
数组增加
flat
和flatMap
方法-
flat
将多为数组拍平为一维数组let arr = [1,[2,3],[4,[5,[6,7,8,9]]]]; console.log(arr.flat(2));// [1, 2, 3, 4, 5, Array(4)] // 使用Infinity,代表有多少层就拍平多少层 console.log(arr.flat(Infinity));// [1, 2, 3, 4, 5, 6, 7, 8, 9]
-
flatMap
根据映射函数将结果数组拍平,只会拍平一层// [Array(1), Array(1), Array(1), Array(1), Array(1)] var nums = [1,2,3,4,5];nums.map(num=>[num*2]); // [2, 4, 6, 8, 10] var nums = [1,2,3,4,5];nums.flatMap(num=>[num*2]); // [Array(1), Array(1), Array(1), Array(1), Array(1)] var nums = [1,2,3,4,5];nums.flatMap(num=>[[num*2]]);
-
-
String.prototype.trimStart
和String.prototype.trimEnd
let str = ' 你好,我叫 kiner ! '; // 将字符串前面的空白去除 console.log(`(${str.trimStart()})`) // 将字符串后面的空白去除 console.log(`(${str.trimEnd()})`);
-
Object.fromEntries
将Object.entries
产生的键值对数组反转描述成一个对象class Parent { name = "kiner"; age = 20; } class Student extends Parent { classify = "student"; classNo = "0001"; } let p = new Parent(); let s = new Student(); let entries = Object.entries(s); // [ // [ // "name", // "kiner" // ], // [ // "age", // 20 // ], // [ // "classify", // "student" // ], // [ // "classNo", // "0001" // ] // ] console.log(JSON.stringify(entries, null, 4)); // { // "name": "kiner", // "age": 20, // "classify": "student", // "classNo": "0001" // } console.log(JSON.stringify(Object.fromEntries(entries), null, 4))
-
Symbol.description
Symbol
对象可以通过一个可选的描述创建,可用于调试,但不能用于访问 symbol 本身。Symbol.prototype.description
属性可以用于读取该描述。与Symbol.prototype.toString()
不同的是它不会包含 “Symbol()
” 的字符串// 语法 // Symbol('myDescription').description; // Symbol.iterator.description; // Symbol.for('foo').description; console.log(Symbol('desc').description); // expected output: "desc" console.log(Symbol.iterator.description); // expected output: "Symbol.iterator" console.log(Symbol.for('foo').description); // expected output: "foo" console.log(Symbol('foo').description + 'bar'); // expected output: "foobar"
-
String.prototype.matchAll
matchAll()
方法返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。let regexp = /t(e)(st(\d?))/g; let str = 'test1test2'; let array = [...str.matchAll(regexp)]; console.log(array[0]); // expected output: Array ["test1", "e", "st1", "1"] console.log(array[1]); // expected output: Array ["test2", "e", "st2", "2"]
-
Function.prototype.toString
相比以前的toString,ES2019返回的是更加精确的一个字符串,包括空格和注释
function /* 这是一个注释 */ getName(arg1 /* 参数1 */ , arg2 /* 参数2 */ ) { console.log(arg1); } // 控制台输出 // function /* 这是一个注释 */ getName(arg1 /* 参数1 */ , arg2 /* 参数2 */ ) { // console.log(arg1); // } console.log(getName.toString());
-
try...catch
语句语句中,如果非必要,可以省略catch的异常参数e// 以前 try{ JSON.parse(jsonStr); }catch(e){} // ES2019 try{ JSON.parse(jsonStr); }catch{}
-
新增一个基础数据类型
BigInt
加上ES6增加的基础数据类型
Symbol
,目前js拥有以下7中基础数据类型:- String
- Number
- Boolean
- null
- undefined
- Symbol
- BigInt
事件循环
众所周知,js是单线程的,js执行代码是,都是在执行栈中执行,如果遇到一些比较耗时的操作,只有等带他执行完才能进行下一步的操作,为了解决这个问题,我们会把一些比较耗时的操作放到异步任务队列当中,当主线程所有代码都处理完了,执行栈会从任务队列中获取所有的微任务并执行,执行完后再取下一个宏任务执行,在执行过程中,又可能不断的产生新的宏任务和微任务加入到任务队列,而执行栈只要为空时便会根据上面的步骤从任务栈中拉取任务执行,循环往复,这就是我们常说的事件循环。
- 宏任务 Macro Task
- 整段的script代码
- setTimeout
- setInterval
- UI操作
- I/O操作
- setImmediate(浏览器支持不好)
- 微任务 Micro Task
- Promise.then
- Process.nextTick(仅node)
- MultationObserver
同步
- 同步编程方式即代码自上而下依次执行,如果代码执行需要花一定的时间,那么必须等其执行完了之后才能执行下面的代码
异步
- 回调函数方式实现异步编程(缺点:产生回调地狱)
- 事件监听
- 订阅发布(其实与事件监听是一回事)
- promise(解决了回调函数方式实现异步编程的回调地狱问题)
- generator 通过 * yield实现
- async/await 使用同步方式实现异步编程
浏览器
事件模型
-
捕获阶段
首先window会获捕获到事件,之后document、documentElement、body会捕获到,再之后就是在body中DOM元素一层一层的捕获到事件
-
目标阶段
抵达目标元素
-
冒泡阶段
会和捕获阶段相反的步骤将事件一步一步的冒泡到window
阻止事件冒泡的方法
- e.stopPropagation();
- e.cancelBubble = true;
阻止默认事件
- e.preventDefault()
选择器
-
标签选择器
span{ color: red; }
-
类选择器
.box{ color: red; }
-
id选择器
#box{ color: red; }
-
伪类选择器
body::after{ content:'' } body:active{ background: red; }
-
子代选择器
body div .box{ color: green; }
-
兄弟元素选择器
.box+span{ color: red; }
-
属性选择器
[type="text"]{ border: 1px soild red; }
前端路由
- hash路由
- 缺点:占用了hash,没办法使用原始的页面锚点功能
- 看起来没那么美观,对搜索引擎不太友好
- history路由
- back
- forward
- go
- pushState
- popState
- replaceState
前端框架及工程化
react
-
单向数据流
在react中的状态是单向数据绑定,而在vue中的特色是双向数据绑定。但是,无论是Vue还是React中,都是推荐使用单向数据绑定的方式,因为在构建一个复杂引用时单向数据流能够更加有条理的进行状态管理,Vue之所以呢引入双向绑定,是在实现UI组件时为了在一些需要实时反映用户输入情况的时候更加方便。但从Vuex和redux其实都是严格遵从单向数据流的原则的。
-
redux
redux是react的状态管理工具,通过redux,我们可以高效得管理应用状态,甚至做到时间旅行,回到某一个时刻的状态,Vue的Vuex便是借鉴redux并结合vue自身的一些特性而产生的仅使用于Vue的状态管理工具,与Vuex不同,redux是一个独立的状态管理系统,不仅仅可以使用于react,当然,如果要在react中使用redux,可使用react-redux。
Vue
-
双向数据绑定
vue中提供双向数据绑定的方式(v-model)允许用户可以更加方便的展示用户输入的内容,其实其原理也很简单,就是通过对input的value和oninput/onchange相结合,当用户输入内容改变时更新value所绑定的状态,由于状态的更新触发视图的重新渲染,从而达到双向数据绑定的目的(在新版的Vue中,双向绑定的功能不再只限于input、textarea、select这些表单组件使用,我们可以显示的指定要双向数据绑定的属性名和待触发的方法名【默认为input,我们可以显示指定如:change】,这样方便我们对一些需要双向数据绑定的非表单元素进行双向数据绑定,如控制modal弹框的显示与隐藏,如果使用双向数据绑定,我们只需要将是否显示的变量通过v-model进行双向绑定,在modal内部对控制弹框显示的属性设置触发事件名,当内部触发弹窗关闭时,调用改事件,便可以触发外层组件的状态改变。)
-
虚拟dom
虚拟dom简单的说其实就是由一组虚拟节点(vnode)组成的具有父子关系的树状结构的对象,而vnode则是一个用于描述dom节点的一个简单的描述对象而已,vnode中包含了我们需要创建dom节点时所需要的所有属性,再进行页面渲染,创建真实dom时,便是通过对虚拟dom中每一个需要更新的vnode提供的描述创建dom并与旧的dom进行补丁式更新操作。
webpack基础
http协议
浏览器从输入一个URL之后发生了什么
- 用户输入url
- 浏览器根据url的域名先从浏览器自身的缓存中查找,看是否存在该域名的缓存,若存在,则直接获取到域名对应的ip,否则从系统的缓存中查找(host),若系统中也无法查询到该域名缓存,再到连接的路由器上查询,如果路由器上依然不存在域名缓存,则请求DNS(域名解析服务器)获取目标服务器的ip
- 获取到ip后(端口号要么是默认端口,如:80、443,要么就是指定的自定义端口),通过传输层的tcp协议,通过三次握手(通过相互发送同步码和应答码的方式实现三次握手)建立TCP连接。
- 建立了TCP连接之后http将请求数据拆分成数据包并标上序号,将数据包传输到传输层,tcp协议在数据包中打上tcp的头部(如源端口、目标端口、同步码和应答码等),再将数据包传输到网络层经由ip协议打入ip协议头部(包含ip协议的版本(如ipv4、ipv6)、长度等信息等)后,再传递到数据链路层根据ip地址通过ARP(地址解析协议)反查出目标服务器网卡的物理地址MAC等信息,并将这些信息以以太网头部的形式打入到数据包中,这样,我们请求的数据包便已经准备好了,经过物理层的数据链路送达至目标网卡处,再经由数据链路层-网络层-传输层-应用层一层层的解析,每经过一层就将解析后的相应的头部去掉,最终到达应用层时,我们便已经得到我们需要的数据包了。
- 拿到数据包之后,经由应用服务解析获取到请求头部和请求体,没有缓存或者缓存已经失效的情况下按照要求进行查库、I/O、数据准备、服务端渲染(仅ssr情况需要),然后将目标资源返回给客户端。如果存在缓存,则根据不同的缓存策略,或询问源服务器缓存是否有效,或直接使用缓存返回等方案。
- 当前端接收到服务端返回的资源,如HTML字符串时。首先将html解析为Dom树
- 将css解析为样式树
- 将dom树与样式树合并为渲染树(dom树和渲染树的区别是渲染树中不包含不需要显示在页面上的元素,如display:none的元素)
- 浏览器根据渲染树渲染页面,同时异步请求如:图片、字体文件、音视频等媒体文件
- 解析js,生成js执行栈,在从上到下同步执行过程中,如发现一些异步操作,如setTimeout、事件监听、接口请求时,将这些任务加入到宏任务队列当中,如遇到如promise.then、MultationObserver时将任务加入到微任务队列,当js执行栈中的代码执行完毕,将会先将所有的微任务拿出来执行,执行完之后,再看看宏任务队列中是否存在宏任务,如果存在,拿出一个任务来执行,由于在执行宏任务和微任务时有有可能产生新的宏任务和微任务加入到任务队列中,因此这个过程其实是循环往复的,这就是我们常说的事件循环。
- js执行之后,其实我们一个具有交互的页面便已经呈现到用户面前的,下一步便是等待用户触发某些特定的操作处理各自的业务逻辑了。