一个合格的中级前端工程师需要掌握的技能笔记(上)

共 73708字,需浏览 148分钟

 ·

2021-06-03 07:45

Github来源:一个合格的中级前端工程师需要掌握的技能 | 求星星 ✨ | 给个❤️关注,❤️点赞,❤️鼓励一下作者

大家好,我是魔王哪吒,很高兴认识你~~

哪吒人生信条:如果你所学的东西 处于喜欢 才会有强大的动力支撑

每天学习编程,让你离梦想更新一步,感谢不负每一份热爱编程的程序员,不论知识点多么奇葩,和我一起,让那一颗四处流荡的心定下来,一直走下去,加油,2021加油!欢迎关注加我vx:xiaoda0423,欢迎点赞、收藏和评论

不要害怕做梦,但是呢,也不要光做梦,要做一个实干家,而不是空谈家,求真力行。

前言

如果这篇文章有帮助到你,给个❤️关注,❤️点赞,❤️鼓励一下作者,接收好挑战了吗?文章公众号首发,关注 程序员哆啦A梦 第一时间获取最新的文章

❤️笔芯❤️~

已阅:

  1. ES6 系列之 let 和 const
  2. ES6 系列之模板字符串
  3. ES6 系列之箭头函数
  4. ES6 系列之模拟实现 Symbol 类型
  5. ES6 系列之迭代器与 for of
  6. ES6 系列之模拟实现一个 Set 数据结构
  7. ES6 系列之 WeakMap
  8. ES6 系列之我们来聊聊 Promise
  9. ES6 完全使用手册
  10. ES6 系列之 defineProperty 与 proxy
  11. ES6 系列之模块加载方案
  12. ES6 系列之私有变量的实现
  13. 前端,校招,面淘宝,指南
  14. 前端,社招,面淘宝,指南
  15. 你累死累活做业务,绩效还不怎么样,我只能帮你到这了……
  16. 淘系前端校招负责人直播答疑文字实录
  17. 致2021届前端同学的一封信|砥砺前行,未来可期!
  18. 面试被问项目经验不用慌,按这个步骤回答绝对惊艳
  19. 项目不知道如何做性能优化?不妨试一下代码分割

HTML模块

HTML 标记包含一些特殊“元素”如 :

<head>,<title>,<body>,<header>,<footer>,<article>,<section>,<p>,<div>,<span>,<img>,<aside>,<audio>,<canvas>,<datalist>,<details>,<embed>,<nav>,<output>,<progress>,<video> 

article元素

表示文档、页面、应用或网站中的独立结构,其意在成为可独立分配的或可复用的结构,如在发布中,它可能是论坛帖子、杂志或新闻文章、博客、用户提交的评论、交互式组件,或者其他独立的内容项目。

section元素

表示一个包含在HTML文档中的独立部分,它没有更具体的语义元素来表示,一般来说会有包含一个标题。

aside元素

表示一个和其余页面内容几乎无关的部分,被认为是独立于该内容的一部分并且可以被单独的拆分出来而不会使整体受影响。

audio元素

用于在文档中嵌入音频内容。

<audio
    controls
    src="/zzz.mp3">
        dada
        <code>audio</code> .
</audio>

canvas元素

用来通过JavaScript(Canvas API 或 WebGL API)绘制图形及图形动画。

<canvas id="canvas" width="300" height="300"></canvas>

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.fillStyle = 'green';
ctx.fillRect(10, 10, 100, 100);

datalist元素

包含了一组<option>元素,这些元素表示其它表单控件可选值.

<input list="browsers" name="myBrowser" /></label>
<datalist id="browsers">
  <option value="Chrome">
  <option value="Firefox">
  <option value="Internet Explorer">
  <option value="Opera">
  <option value="Safari">
</datalist>

details元素

可创建一个挂件,仅在被切换成展开状态时,它才会显示内含的信息。<summary> 元素可为该部件提供概要或者标签。

<details>
  <summary>System Requirements</summary>
  <p>Requires a computer running an operating system. The computer
  must have some memory and ideally some kind of long-term storage.
  An input device as well as some form of output device is
  recommended.</p>
</details>
System Requirements

Requires a computer running an operating system. The computer  must have some memory and ideally some kind of long-term storage.  An input device as well as some form of output device is  recommended.

embed元素

外部内容嵌入元素

progress元素

用来显示一项任务的完成进度

<progress id="file" max="100" value="70"> 70% </progress>

output标签

表示计算或用户操作的结果。

nav元素

表示页面的一部分,其目的是在当前文档或其他文档中提供导航链接

表单小部件:

<form action="/my-handling-form-page" method="post">
  <div>
    <label for="name">Name:</label>
    <input type="text" id="name">
  </div>
  <div>
    <label for="mail">E-mail:</label>
    <input type="email" id="mail">
  </div>
  <div>
    <label for="msg">Message:</label>
    <textarea id="msg"></textarea>
  </div>
</form>

单行文本框

<input type="text" id="comment" name="comment" value="I'm a text field">

E-mail 地址框

<input type="email" id="email" name="email" multiple>

密码框

<input type="password" id="pwd" name="pwd">

搜索框

<input type="search" id="search" name="search">

电话号码栏:

<input type="tel" id="tel" name="tel">

URL 栏:

<input type="url" id="url" name="url">

多行文本框:

<textarea cols="30" rows="10"></textarea>

复选框:

<input type="checkbox" checked id="carrots" name="carrots" value="carrots">

单选按钮:

<input type="radio" checked id="soup" name="meal">

数字:

<input type="number" name="age" id="age" min="1" max="10" step="2">

滑块:

<input type="range" name="beans" id="beans" min="0" max="500" step="10">

日期时间选择器:

<input type="datetime-local" name="datetime" id="datetime">

<input type="month" name="month" id="month">

<input type="time" name="time" id="time">

<input type="week" name="week" id="week">

拾色器:

<input type="color" name="color" id="color">

文件选择器:

<input type="file" name="file" id="file" accept="image/*" multiple>

隐藏内容:

<input type="hidden" id="timestamp" name="timestamp" value="6354561">

发送表单数据

客户端/服务器体系结构

客户端(通常是web浏览器)向服务器发送请求(大多数情况下是Apache、Nginx、IIS、Tomcat等web服务器),使用HTTP 协议。

在客户端:定义如何发送数据:

action 属性-这个属性定义了发送数据要去的位置。

method属性-该属性定义了如何发送数据。

什么是表单数据校验?

访问任何一个带注册表单的网站,你都会发现,当你提交了没有输入符合预期格式的信息的表单时,注册页面都会给你一个反馈。

  • “该字段是必填的”(该字段不能留空)
  • 请输入你的电话号码
  • 请输入一个合法的邮箱地址

使用正则表达式校验

示例:

a — 匹配一个字符a(不能匹配 b,  aa等等.)
abc — 匹配 a, 其次 b, 最后  c.
a* — 匹配0个或者多个字符 a (+ 代表至少匹配一个或者多个).
[^a] — 匹配一个字符,但它不能是a.
a|b — 匹配一个字符 a 或者 b.
[abc] — 匹配一个字符,它可以是a,b或c.
[^abc] — 匹配一个字符,但它不可以是a,b或c.
[a-z] — 匹配字符范围 a-z且全部小写  (你可以使用 [A-Za-z] 涵盖大小写, 或 [A-Z] 来限制必须大写).
a.c — 匹配字符 a,中间匹配任意一个字符,最后匹配字符 c.
a{5} — 匹配字符 a五次.
a{5,7} — 匹配字符 a五到七次,不能多或者少.

AJAX 技术主要依靠 XMLHttpRequest (XHR) DOM 对象。它可以构造 HTTP 请求、发送它们,并获取请求结果。

表单数据(application/x-www-form-urlencoded)由 URL编码的键/值对列表组成。为了传输二进制数据,HTTP请求被重新整合成multipart/form-data形式。

构建 XMLHttpRequest:XMLHttpRequest 是进行 HTTP 请求的最安全和最可靠的方式。

示例:

function sendData(data) {
  var XHR = new XMLHttpRequest();
  var urlEncodedData = "";
  var urlEncodedDataPairs = [];
  var name;

  // 将数据对象转换为URL编码的键/值对数组。
  for(name in data) {
    urlEncodedDataPairs.push(encodeURIComponent(name) + '=' + encodeURIComponent(data[name]));
  }

  // 将配对合并为单个字符串,并将所有%编码的空格替换为
  // “+”字符;匹配浏览器表单提交的行为。
  urlEncodedData = urlEncodedDataPairs.join('&').replace(/%20/g, '+');

  // 定义成功数据提交时发生的情况
  XHR.addEventListener('load'function(event) {
    alert('耶! 已发送数据并加载响应。');
  });

  // 定义错误提示
  XHR.addEventListener('error'function(event) {
    alert('哎呀!出问题了。');
  });

  // 建立我们的请求
  XHR.open('POST''https://example.com/cors.php');

  // 为表单数据POST请求添加所需的HTTP头
  XHR.setRequestHeader('Content-Type''application/x-www-form-urlencoded');

  // 最后,发送我们的数据。
  XHR.send(urlEncodedData);
}

利用FormData对象来处理表单数据请求。

示例:

function sendData(data) {
  var XHR = new XMLHttpRequest();
  var FD  = new FormData();

  // 把我们的数据添加到这个FormData对象中
  for(name in data) {
    FD.append(name, data[name]);
  }

  // 定义数据成功发送并返回后执行的操作
  XHR.addEventListener('load'function(event) {
    alert('Yeah! 已发送数据并加载响应。');
  });

  // 定义发生错误时执行的操作
  XHR.addEventListener('error'function(event) {
    alert('Oops! 出错了。');
  });

  // 设置请求地址和方法
  XHR.open('POST''https://example.com/cors.php');

  // 发送这个formData对象,HTTP请求头会自动设置
  XHR.send(FD);
}

使用绑定到表单元素上的 FormData

<form id="myForm">
  <label for="myName">告诉我你的名字:</label>
  <input id="myName" name="name" value="John">
  <input type="submit" value="提交">
</form>

示例:

window.addEventListener("load"function () {
  function sendData() {
    var XHR = new XMLHttpRequest();

    // 我们把这个 FormData 和表单元素绑定在一起。
    var FD  = new FormData(form);

    // 我们定义了数据成功发送时会发生的事。
    XHR.addEventListener("load"function(event) {
      alert(event.target.responseText);
    });

    // 我们定义了失败的情形下会发生的事
    XHR.addEventListener("error"function(event) {
      alert('哎呀!出了一些问题。');
    });

    // 我们设置了我们的请求
    XHR.open("POST""https://example.com/cors.php");

    // 发送的数据是由用户在表单中提供的
    XHR.send(FD);
  }

  // 我们需要获取表单元素
  var form = document.getElementById("myForm");

  // ...然后接管表单的提交事件
  form.addEventListener("submit"function (event) {
    event.preventDefault();

    sendData();
  });
});

CORS 处理跨域图片

通过搭配 crossorigin 属性和适当的 CORS 标头,在 <img> 元素中定义的图片可以从外部来源加载并在 <canvas> 元素中使用,就像是从本地源加载一样。

启用了 CORS 的图片

尽管不通过 CORS 就可以在 <canvas> 中使用其他来源的图片,但是这会污染画布,并且不再认为是安全的画布,这将可能在 <canvas> 检索数据过程中引发异常。

在"被污染"的画布中调用以下方法:

  • <canvas> 的上下文上调用getImageData()

    • <canvas> 上调用 toBlob()
  • <canvas> 上调用  toDataURL()

CORS 设置属性

一些提供了对 CORS 的支持的 HTML 元素,比如 <img><video> ,具有 crossorigin 元素属性/attributecrossOrigin 对象属性/property),该属性能使你配置其跨域获取资源的请求。

CanvasRenderingContext2D.getImageData()

CanvasRenderingContext2D.getImageData() 返回一个ImageData对象,用来描述canvas区域隐含的像素数据,这个区域通过矩形表示,起始点为(sx, sy)、宽为sw、高为sh

语法

ImageData ctx.getImageData(sx, sy, sw, sh);

sx
将要被提取的图像数据矩形区域的左上角 x 坐标。

sy
将要被提取的图像数据矩形区域的左上角 y 坐标。

sw
将要被提取的图像数据矩形区域的宽度。

sh
将要被提取的图像数据矩形区域的高度。

返回值
一个ImageData 对象,包含canvas给定的矩形图像数据。

使用 getImageData 方法:

<canvas id="canvas"></canvas>

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.rect(10, 10, 100, 100);
ctx.fill();

console.log(ctx.getImageData(50, 50, 100, 100));
// ImageData { width: 100, height: 100, data: Uint8ClampedArray[40000] }

返回值
一个ImageData 对象,包含canvas给定的矩形图像数据。

HTMLCanvasElement.toBlob()

HTMLCanvasElement.toBlob() 方法创造Blob对象,用以展示canvas上的图片;这个图片文件可以被缓存或保存到本地,由用户代理端自行决定。如不特别指明,图片的类型默认为 image/png,分辨率为96dpi

语法

canvas.toBlob(callback, type, encoderOptions);

callback
回调函数,可获得一个单独的Blob对象参数。

type 可选
DOMString类型,指定图片格式,默认格式为image/png。

encoderOptions 可选
Number类型,值在0与1之间,当请求图片格式为image/jpeg或者image/webp时用来指定图片展示质量。如果这个参数的值不在指定类型与范围之内,则使用默认值,其余参数将被忽略。

返回值
无。

HTMLCanvasElement.toDataURL()

HTMLCanvasElement.toDataURL() 方法返回一个包含图片展示的 data URI 。可以使用 type 参数其类型,默认为 PNG 格式。图片的分辨率为96dpi

  • 如果画布的高度或宽度是0,那么会返回字符串“data:,”。
  • 如果传入的类型非“image/png”,但是返回的值以“data:image/png”开头,那么该传入的类型是不支持的。
  • Chrome支持“image/webp”类型。

语法

canvas.toDataURL(type, encoderOptions);

type 可选
图片格式,默认为 image/png

encoderOptions 可选
在指定图片格式为 image/jpeg 或 image/webp的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。其他参数会被忽略。

返回值
包含 data URI 的DOMString。

示例:

<canvas id="canvas" width="5" height="5"></canvas>

var canvas = document.getElementById("canvas");
var dataURL = canvas.toDataURL();
console.log(dataURL);
// "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNby
// blAAAADElEQVQImWNgoBMAAABpAAFEI8ARAAAAAElFTkSuQmCC"

设置图片的质量:

var fullQuality = canvas.toDataURL("image/jpeg", 1.0);
// data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ...9oADAMBAAIRAxEAPwD/AD/6AP/Z"
var mediumQuality = canvas.toDataURL("
image/jpeg", 0.5);
var lowQuality = canvas.toDataURL("
image/jpeg", 0.1);

允许浏览器在下载图像数据时允许跨域访问请求

用户点击 "Download" 按钮时开始下载:

function startDownload() {
  let imageURL = "https://xxxx";

  downloadedImg = new Image;
  downloadedImg.crossOrigin = "Anonymous";
  downloadedImg.addEventListener("load", imageReceived, false);
  downloadedImg.src = imageURL;
}

canvastoDataURL() 方法用于将图像转换为 data:// URL 形式的 PNG 格式图片

通过rel="preload"进行内容预加载

<link> 标签最常见的应用情形就是被用来加载CSS文件,进而装饰你的页面:

<link rel="stylesheet" href="styles/main.css">

<link rel="preload" href="style.css" as="style">
<link rel="preload" href="main.js" as="script">

哪些类型的内容可以被预加载?

audio: 音频文件。
document: 一个将要被嵌入到<frame>或<iframe>内部的HTML文档。
embed: 一个将要被嵌入到<embed>元素内部的资源。
fetch: 那些将要通过fetch和XHR请求来获取的资源,比如一个ArrayBuffer或JSON文件。
font: 字体文件。
image: 图片文件。
object: 一个将会被嵌入到<embed>元素内的文件。
script: JavaScript文件。
style: 样式表。
track: WebVTT文件。
worker: 一个JavaScript的web worker或shared worker。
video: 视频文件。

如何制作快速加载的HTML页面

  1. 减小页面的大小。
  2. 最小化文件数量:减少一个页面引用的文件数量可以降低在下载一个页面的过程中需要的HTTP请求数量,从而减少这些请求的收发时间。
  3. 使用 CDN。
  4. 减少域名查找:每个独立的域名都会消耗DNS查找的时间,页面加载时间会随着独立域名数量、CSS链接数量、JavaScript还有图片资源的数量增加而增加。
  5. 缓存重用的内容:确保任何内容可以被缓存,并且拥有一个合理的有效期。
  6. 高效地排列页面组件。
  7. 减少内联脚本的数量。

网页中添加矢量图形

image.png

示例:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Vector versus raster</title>
  </head>
  <body>
    <h1>Vector versus raster</h1>

    <img src="star.png" alt="A raster star">
    <img src="star.svg" alt="A vector star">
  </body>
</html>

创建一个圆和一个矩形:

image.png
<svg version="1.1"
     baseProfile="full"
     width="300" height="200"
     xmlns="http://www.w3.org/2000/svg">
  <rect width="100%" height="100%" fill="black" />
  <circle cx="150" cy="100" r="90" fill="blue" />
</svg>

示例:

<img
    src="equilateral.svg"
    alt="triangle with all three sides equal"
    height="87px"
    width="100px" />
<svg width="300" height="200">
    <rect width="100%" height="100%" fill="green" />
</svg>

使用SVG:

image.png
<svg width="100%" height="100%">
<rect width="100%" height="100%" fill="red" />
<circle cx="100%" cy="100%" r="150" fill="blue" stroke="black" />
<polygon points="120,0 240,225 0,225" fill="green"/>
<text x="50" y="100" font-family="Verdana" font-size="55"
      fill="white" stroke="black" stroke-width="2">
        Hello!
</text>
</svg>

CSS模块

外边距重叠

块的上外边距(margin-top)和下外边距(margin-bottom)有时合并(折叠)为单个边距,其大小为单个边距的最大值(或如果它们相等,则仅为其中一个),这种行为称为边距折叠。

布局和包含块

image.png

层叠上下文

满足以下任意一个条件的元素形成:

  • 文档根元素(<html>);
  • position 值为 absolute(绝对定位)或  relative(相对定位)且 z-index 值不为 auto 的元素;
  • position 值为 fixed(固定定位)或 sticky(粘滞定位)的元素(沾滞定位适配所有移动设备上的浏览器,但老的桌面浏览器不支持);
  • flex (flexbox) 容器的子元素,且 z-index 值不为 auto;
  • grid (grid) 容器的子元素,且 z-index 值不为 auto;
  • opacity 属性值小于 1 的元素

块格式化上下文

块格式化上下文(Block Formatting Context,BFC) 是Web页面的可视CSS渲染的一部分,是块盒子的布局过程发生的区域,也是浮动元素与其他元素交互的区域。

会创建块格式化上下文:

  • 根元素(<html>
  • 浮动元素(元素的 float 不是 none
  • 绝对定位元素(元素的 position 为 absolute 或 fixed
  • 行内块元素(元素的 display 为 inline-block
  • 表格单元格(元素的 display 为 table-cell,HTML表格单元格默认为该值)
  • 表格标题(元素的 display 为 table-caption,HTML表格标题默认为该值)
  • contain 值为 layout、content 或 paint 的元素
  • 弹性元素(display 为 flex 或 inline-flex 元素的直接子元素)
  • 网格元素(display 为 grid 或 inline-grid 元素的直接子元素)

使用overflow: auto

创建一个会包含这个浮动的 BFC,通常的做法是设置父元素 overflow: auto 或者设置其他的非默认的 overflow: visible 的值。

设置 overflow: auto 创建一个新的BFC来包含这个浮动。我们的 <div> 元素现在变成布局中的迷你布局。任何子元素都会被包含进去。

image.png
image.png

CSS 弹性盒子布局

此属性是以下CSS属性的简写:

  • flex-grow
  • flex-shrink
  • flex-basis

flex-shrink 属性指定了 flex 元素的收缩规则。flex 元素仅在默认宽度之和大于容器的时候才会发生收缩,其收缩的大小是依据 flex-shrink 的值。

flex-shrink: 2;
flex-shrink: 0.6;

/* Global values */
flex-shrink: inherit;
flex-shrink: initial;
flex-shrink: unset

flex-grow设置了一个flex项主尺寸的flex增长系数。它指定了flex容器中剩余空间的多少应该分配给项目(flex增长系数)。

主尺寸是项的宽度或高度,这取决于flex-direction值。

flex-basis 指定了 flex 元素在主轴方向上的初始大小。如果不使用  box-sizing 改变盒模型的话,那么这个属性就决定了 flex 元素的内容盒(content-box)的尺寸。

flex-wrap 指定 flex 元素单行显示还是多行显示 。如果允许换行,这个属性允许你控制行的堆叠方向。

order 属性规定了弹性容器中的可伸缩项目在布局时的顺序。元素按照 order 属性的值的增序进行布局。拥有相同 order 属性值的元素按照它们在源代码中出现的顺序进行布局。

flex-flow 属性是 flex-directionflex-wrap 的简写。

flex-direction: row
flex-wrap: nowrap

对齐属性

align-content 属性设置了浏览器如何沿着弹性盒子布局的纵轴和网格布局的主轴在内容项之间和周围分配空间。

/* 基本位置对齐 */
/*align-content不采用左右值 */
align-content: center;     /* 将项目放置在中点 */
align-content: start;      /* 最先放置项目 */
align-content: end;        /* 最后放置项目 */
align-content: flex-start; /* 从起始点开始放置flex元素 */
align-content: flex-end;   /* 从终止点开始放置flex元素 */

/* 默认对齐 */
align-content: normal;

/*基线对齐*/
align-content: baseline;
align-content: first baseline;
align-content: last baseline;

/* 分布式对齐 */
align-content: space-between; /* 均匀分布项目
                                 第一项与起始点齐平,
                                 最后一项与终止点齐平 */
align-content: space-around;  /* 均匀分布项目
                                 项目在两端有一半大小的空间*/
align-content: space-evenly;  /* 均匀分布项目
                                 项目周围有相等的空间 */
align-content: stretch;       /* 均匀分布项目
                                 拉伸‘自动’-大小的项目以充满容器 */

/* 溢出对齐 */
align-content: safe center;
align-content: unsafe center;

/* 全局属性 */
align-content: inherit; /* 继承 */
align-content: initial;  /* 初始值 */
align-content: unset; /* 未设置 */
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png

align-items

示例:

align-items: stretch;

align-items: center;

align-items: start;

align-items: end;

align-self: stretch;效果如下:

image.png

align-self: center;效果如下:

image.png

align-self: start;效果如下:

image.png

align-self: end;效果如下:

image.png

justify-content

justify-content: center;     /* 居中排列 */
justify-content: start;      /* Pack items from the start */
justify-content: end;        /* Pack items from the end */
justify-content: flex-start; /* 从行首起始位置开始排列 */
justify-content: flex-end;   /* 从行尾位置开始排列 */
justify-content: left;       /* Pack items from the left */
justify-content: right;      /* Pack items from the right */

justify-content: space-between;  /* 均匀排列每个元素
                                   首个元素放置于起点,末尾元素放置于终点 */
justify-content: space-around;  /* 均匀排列每个元素
                                   每个元素周围分配相同的空间 */
justify-content: space-evenly;  /* 均匀排列每个元素
                                   每个元素之间的间隔相等 */
justify-content: stretch;       /* 均匀排列每个元素
                                   'auto'-sized 的元素会被拉伸以适应容器的大小 */

place-content 属性是align-contentjustify-content的简写

animation

  • animation-name
  • animation-duration
  • animation-timing-function
  • animation-delay
  • animation-iteration-count
  • animation-direction
  • animation-fill-mode
  • animation-play-state
animation: 3s ease-in 1s infinite reverse both running slidein;

animation: 3s linear 1s infinite running slidein;

animation: 3s linear 1s infinite alternate slidein;

animation: .5s linear 1s infinite alternate slidein;

@keyframes slidein {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}
  • animation-name: none
  • animation-duration: 0s
  • animation-timing-function: ease
  • animation-delay: 0s
  • animation-iteration-count: 1
  • animation-direction: normal
  • animation-fill-mode: none
  • animation-play-state: running

长度单位

css中有两种长度:相对长度和绝对长度。绝对长度不依赖于其他任何量度。绝对量度无论其所应用的环境如何都保持其长度。

相对长度依赖于使用的环境,如计算机显示器的屏幕分辨率或字体的大小。

绝对长度根据实际单位定义,如英寸,厘米和点等。

长度单位分相对长度单位和绝对长度单位:相对/绝对

  • 相对长度单位。相对长度单位分:相对字体长度单位和相对视区长度单位
  • 相对字体长度单位,如em和ex,还有css3的rem和ch
  • 相对视区长度单位,如vh, vw, vmin, vmax
  • 绝对长度单位,常见有px,pt,cm,mm,pc

css中的单位:

时间单位s,ms,角度单位deg,rad,长度单位px,em

%不是长度单位

选择器:1,类选择器,2,ID选择器,3,属性选择器,4,伪类选择器,5,伪元素选择器。

:first-child
:last-child
::first-line
::first-letter
::before
::after

关系选择器:1,后代选择器,2,相邻后代选择器,3,兄弟选择器,4,相邻兄弟选择器。

规则:@

@规则指的是以@字符开始的一些规则,如@media, @font-face, @page, @support

块级元素和display为block的元素不是一个概念。

so,清除浮动:✍

.clear:after {
 content: '',
 display: table; // 可以是block, list-item
 clear: both;
}

不使用list-item是:

  1. 第一它字符比较多
  2. 会出现不需要的项目符号
  3. IE浏览器不支持伪元素的display指为list-item,主要是兼容性不好。对于IE浏览器,普通元素设置display:list-item有效的,但:before/:after伪元素就无效了。

块级盒子就负责结构,内联盒子就负责内容。

a标签元素默认display是inline,设置display:block为块状化,后面width: 100%就没有问题。

box-sizing

box-sizing盒尺寸,width作用的细节,box-sizing属性的作用是改变width的作用细节。内在盒子的4个盒子:

  1. content box
  2. padding box
  3. border box
  4. margin box

默认情况下,width是作用在content box上的,box-sizing的作用就是可以把width作用的盒子变成其他几个。

理论上写法:✍

.box1 { box-sizing: content-box; }
.box2 { box-sizing: padding-box; }
.box3 { box-sizing: border-box; }
.box4 { box-sizing: margin-box; }

不过只能写以下写法:✍

.box1 { box-sizing: content-box; } // 默认值
.box2 { box-sizing: padding-box; } // 曾经支持
.box3 { box-sizing: border-box; } // 支持
.box4 { box-sizing: margin-box; } // 从未支持

建议不要全局重置的做法。这种容易产生没必要的消耗,这种做法并不能解决所有问题。

*通配符尽量不使用,因为它会选择所有标签元素,对于普通内联元素,box-sizing无论是什么值,对其渲染表现都没有影响。同样有些元素,其默认的box-sizing就是border-box,所以也没有必要的消耗。

Internet工作原理

在浏览器中输入Internet地址会发生什么情况:

  1. 在浏览器中输入www地址
  2. 浏览器与该地址的HTTP服务器进行交互
  3. HTTP服务器接收浏览器的请求
  4. HTTP服务器查找web文档
  5. HTTP服务器发送Web文档
  6. 浏览器接收该文档
  7. 浏览器处理源代码
  8. 浏览器显示网页

服务器端计算器包含了用来处理所有web页面请求的http服务器软件。在浏览器中输入internet地址时,浏览器发出一个请求,经过很长的计算机网络传播,直到找到远程计算机的地址。在请求到达HTTP服务器后,HTTP服务器分析该请求,在服务器的硬盘上搜索请求的页面,并对该请求做出响应,返回所需的web页面。

响应经过另一条计算机链进行传播,一直到达您的计算机。然后浏览器打开该响应并读取HTTP服务器送回内容。如果服务器发送的是浏览器能够分析的HTML文档或其他文档,则浏览器会读取该文档的源代码并处理成可显示的web页面。

  • 样式表是由规则组成的
  • 规则由选择器和声明组成。
  • 声明由属性和值组成。
  • 值可以是关键字,长度,颜色,字符串,整型,实型,或者是URL。
  • em量度最好用于屏幕布局。
  • URI用于在CSS中包含样式表和背景图像。
  • 可以使用style属性直接在HTML元素中内联地包含样式。

选择器

  • 类和ID选择器
  • 通用选择器
  • 后代选择器
  • 直接子选择器
  • 相邻选择器
  • 属性选择器
  • 伪元素
  • 伪类

基于属性值的选择

属性值选择器基于属性的存在性和值应用样式声明。

input[type="text"]{
 background: blue;
 color: white;
 border: 3px solid royalblue;
}
<input type="text" name="first_name" value="name" size="25"/>

属性子字符串选择器

出现在另一个字符串中的字符串被称为字符串。

a[href^="ftp://"] {
 background: blue;
 color: white;
 border: 3px solid royalblue;
}
<a href="ftp://ftp.example.com"/>dadaqianduan.cn</a>

伪类:伪类是用来表示动态事件,状态改变或者是在文档中以其他方法不能轻易实现的情况,可能是用户的鼠标悬停或单击某元素。伪类对目标元素出现某种特殊的状态应用样式。

动态伪类

  • :link,表示未访问的超链接
  • :visited,表示已访问的超链接
  • :hover,表示鼠标指针当前停留在该元素上
  • :active,表示用户正在单击该元素
a:link {
 color: red;
}
a:visited {
 color: yellow;
}

:first-child结构化伪类只用于当一个元素是另一个元素的第一个子元素时。

文本属性

  • letter-spacing属性以及使用它增加和减少单词字母间隔的方法
  • word-spacing属性以及使用它增加和减少句子中单词间隔的方法
  • text-indent属性以及使用它控制段落中文本缩进的方法
  • text-align属性以及使用它对齐文档中的文本的方法
  • text-decoration属性以及使用它对文本加下划线,上划线和删除线的方法
  • text-transform属性以及使用它控制文本的大小写和进行字母大小写之间转换的方法
  • white-space属性以及使用它控制文本流及格式的方法。
  1. letter-spacing属性,用来控制字母间隔的属性。
letter-spacing: normal;
  1. word-spacing属性,用来孔子单词之间的间隔
word-spacing: normal;
  1. text-indent属性缩进段落文本
  2. text-align属性对齐文本
  3. text-decoration属性,用来对文本加下划线,上划线,删除线。
  4. text-transform属性,用来控制文本的大小写
#capitalize {
 text-transform: capitalize;
}
#uppercase {
 text-transform: uppercase;
}
#lowercase {
 text-transform: lowercase;
}
  1. white-space属性,允许控制web文档源代码的文本格式化

字体属性

  1. font-family属性指定字体。
  2. font-style属性用来在一种特定字体提供的不同样式之间切换。
  3. font-weight样式表属性提供了指定字体粗细的功能。
  4. font-size属性用来控制字体的大小

溢出的内容

css的overflow属性用来处理易受尺寸限制影响的内容,这些内容很可能溢出尺寸限制的边界。overflow属性最常见的两个作用是当内容超过可用空间时隐藏内容,或者是通过滚动条使多余的内容可以被访问。

overflow: visible | hidden | scroll | auto

三角等图形绘制

image.png
div {
 width: 0;
 border: 10px solid;
 border-color: #f30 transparent transparent;
}
image.png
<style type="text/css">
        .div5 {
                width: 0;
                border: 10px solid;
                border-color: #f30 transparent transparent;
        }
        .div1 {
                width: 10px;
                height: 10px;
                border: 10px solid;
                border-color: #f30 transparent transparent;
        }
        .div2 {
                width: 10px;
                height: 10px;
                border: 10px solid;
                border-color: #f30 #00f #396 #0f0;
        }
        .div3 {
                width: 0;
                border-width: 10px 20px;
                border-style: solid;
                border-color: #f30 transparent transparent;
        }
        .div4 {
                width: 0;
                border-width: 10px 20px;
                border-style: solid;
                border-color: #f30 #f30 transparent transparent;
        }
</style>

css中举足轻重的角色

  • line-height行高的定义就是两基线的间距
  • vertical-align的默认值就是基线

JavaScript模块

事件示例:

const btn = document.querySelector('button');

btn.onclick = function() {
  const rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
  document.body.style.backgroundColor = rndCol;
}

Array

创建数组

var fruits = ['Apple''Banana'];

console.log(fruits.length);
// 2

通过索引访问数组元素

var first = fruits[0];
// Apple

var last = fruits[fruits.length - 1];
// Banana

遍历数组

fruits.forEach(function (item, index, array) {
    console.log(item, index);
});
// Apple 0
// Banana 1

添加元素到数组的末尾

var newLength = fruits.push('Orange');
// newLength:3; fruits: ["Apple""Banana""Orange"]

删除数组末尾的元素

var last = fruits.pop(); // remove Orange (from the end)
// last: "Orange"; fruits: ["Apple""Banana"];

删除数组最前面(头部)的元素

var first = fruits.shift(); // remove Apple from the front
// first: "Apple"; fruits: ["Banana"];

添加元素到数组的头部

var newLength = fruits.unshift('Strawberry') // add to the front
// ["Strawberry""Banana"];

找出某个元素在数组中的索引

fruits.push('Mango');
// ["Strawberry""Banana""Mango"]

var pos = fruits.indexOf('Banana');
// 1

复制一个数组

var shallowCopy = fruits.slice(); // this is how to make a copy
// ["Strawberry""Mango"]

方法

Array.prototype.pop()删除数组的最后一个元素,并返回这个元素。

Array.prototype.push()在数组的末尾增加一个或多个元素,并返回数组的新长度。

Array.prototype.reverse()颠倒数组中元素的排列顺序,即原先的第一个变为最后一个,原先的最后一个变为第一个。

Array.prototype.shift()删除数组的第一个元素,并返回这个元素。

Array.prototype.sort()对数组元素进行排序,并返回当前数组。

Array.prototype.splice()在任意的位置给数组添加或删除任意个元素。

Array.prototype.unshift()在数组的开头增加一个或多个元素,并返回数组的新长度。

Array.prototype.concat()返回一个由当前数组和其它若干个数组或者若干个非数组值组合而成的新数组。

Array.prototype.join()连接所有数组元素组成一个字符串。

Array.prototype.slice()抽取当前数组中的一段元素组合成一个新数组。

Array.prototype.toString()返回一个由所有数组元素组合而成的字符串。遮蔽了原型链上的 Object.prototype.toString() 方法。

Array.prototype.indexOf()返回数组中第一个与指定值相等的元素的索引,如果找不到这样的元素,则返回 -1

Array.prototype.lastIndexOf()返回数组中最后一个(从右边数第一个)与指定值相等的元素的索引,如果找不到这样的元素,则返回 -1。

Array.prototype.forEach()为数组中的每个元素执行一次回调函数。

Array.prototype.every()如果数组中的每个元素都满足测试函数,则返回 true,否则返回 false。

Array.prototype.some()如果数组中至少有一个元素满足测试函数,则返回 true,否则返回 false。

Array.prototype.filter()将所有在过滤函数中返回 true 的数组元素放进一个新数组中并返回。

Array.prototype.map()返回一个由回调函数的返回值组成的新数组。

Array.prototype.reduce()reduce() 方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。

const array1 = [1, 2, 3, 4];
const reducer = (accumulator, currentValue) => accumulator + currentValue;

// 1 + 2 + 3 + 4
console.log(array1.reduce(reducer));
// expected output: 10

// 5 + 1 + 2 + 3 + 4
console.log(array1.reduce(reducer, 5));
// expected output: 15

Array.prototype.reduceRight()reduceRight() 方法接受一个函数作为累加器(accumulator)和数组的每个值(从右到左)将其减少为单个值。

const array1 = [[0, 1], [2, 3], [4, 5]].reduceRight(
  (accumulator, currentValue) => accumulator.concat(currentValue)
);

console.log(array1);
// expected output: Array [4, 5, 2, 3, 0, 1]

Boolean

Boolean对象是一个布尔值的对象包装器。

0,-0,null,false,NaN,undefined,或空字符串(""),该对象具有的初始值false

其值不是undefined或null的任何对象在传递给条件语句时都将计算为true。

var x = new Boolean(false);
if (x) {
  // 这里的代码会被执行
}

var x = false;
if (x) {
  // 这里的代码不会执行
}

var x = Boolean(expression);     // 推荐
var x = !!(expression);          // 推荐
var x = new Boolean(expression); // 不太好

Boolean.prototype.toString()根据对象的值返回字符串"true"或"false"。重写Object.prototype.toString()方法。

Boolean.prototype.valueOf()返回Boolean对象的原始值。重写Object.prototype.valueOf()方法。

Date创建一个 JavaScript Date 实例,该实例呈现时间中的某个时刻。

image.png
new Date();
new Date(value);
new Date(dateString);
new Date(year, monthIndex [, day [, hours [, minutes [, seconds [, milliseconds]]]]]);

创建一个新Date对象的唯一方法是通过new 操作符,例如:let now = new Date();若将它作为常规函数调用(即不加 new 操作符),将返回一个字符串,而非 Date 对象。

Date()构造函数有四种基本形式

year
表示年份的整数值。

monthIndex
表示月份的整数值,从 0(1月)到 11(12月)。

date
表示一个月中的第几天的整数值,从1开始。默认值为1。

hours
表示一天中的小时数的整数值 (24小时制)。默认值为0(午夜)。

minutes
表示一个完整时间(如 01:10:00)中的分钟部分的整数值。默认值为0。

seconds
表示一个完整时间(如 01:10:00)中的秒部分的整数值。默认值为0。

milliseconds 
表示一个完整时间的毫秒部分的整数值。默认值为0。

实例方法

Date.prototype.getDate()根据本地时间返回指定日期对象的月份中的第几天(1-31)。

Date.prototype.getDay()根据本地时间返回指定日期对象的星期中的第几天(0-6)。

Date.prototype.getFullYear()根据本地时间返回指定日期对象的年份(四位数年份时返回四位数字)。

Date.prototype.getHours()根据本地时间返回指定日期对象的小时(0-23)。

Date.prototype.getMilliseconds()根据本地时间返回指定日期对象的毫秒(0-999)。

Date.prototype.getMinutes()根据本地时间返回指定日期对象的分钟(0-59)。

Date.prototype.getMonth()根据本地时间返回指定日期对象的月份(0-11)。

Date.prototype.getSeconds()根据本地时间返回指定日期对象的秒数(0-59)。

Date.prototype.getTime()返回从1970-1-1 00:00:00 UTC(协调世界时)到该日期经过的毫秒数,对于1970-1-1 00:00:00 UTC之前的时间返回负值。

Function

Function.prototype.apply()

Function.prototype.bind()

Function.prototype.call()

Function.prototype.toString()

JSON

JSON.parse()

JSON.stringify()

Math方法

Math.abs(x)
返回一个数的绝对值。
Math.acos(x)
返回一个数的反余弦值。
Math.acosh(x)
返回一个数的反双曲余弦值。
Math.asin(x)
返回一个数的反正弦值。
Math.asinh(x)
返回一个数的反双曲正弦值。
Math.atan(x)
返回一个数的反正切值。
Math.atanh(x)
返回一个数的反双曲正切值。
Math.atan2(y, x)
返回 y/x 的反正切值。
Math.cbrt(x)
返回一个数的立方根。
Math.ceil(x)
返回大于一个数的最小整数,即一个数向上取整后的值。
Math.clz32(x)
返回一个 32 位整数的前导零的数量。
Math.cos(x)
返回一个数的余弦值。
Math.cosh(x)
返回一个数的双曲余弦值。
Math.exp(x)
返回欧拉常数的参数次方,Ex,其中 x 为参数,E 是欧拉常数(2.718...,自然对数的底数)。
Math.expm1(x)
返回 exp(x) - 1 的值。
Math.floor(x)
返回小于一个数的最大整数,即一个数向下取整后的值。
Math.fround(x)
返回最接近一个数的单精度浮点型表示。
Math.hypot([x[, y[, …]]])
返回其所有参数平方和的平方根。
Math.imul(x, y)
返回 32 位整数乘法的结果。
Math.log(x)
返回一个数的自然对数(㏒e,即 ㏑)。
Math.log1p(x)
返回一个数加 1 的和的自然对数(㏒e,即 ㏑)。
Math.log10(x)
返回一个数以 10 为底数的对数。
Math.log2(x)
返回一个数以 2 为底数的对数。
Math.max([x[, y[, …]]])
返回零到多个数值中最大值。
Math.min([x[, y[, …]]])
返回零到多个数值中最小值。
Math.pow(x, y)
返回一个数的 y 次幂。
Math.random()
返回一个 0 到 1 之间的伪随机数。
Math.round(x)
返回四舍五入后的整数。
Math.sign(x)
返回一个数的符号,得知一个数是正数、负数还是 0。
Math.sin(x)
返回一个数的正弦值。
Math.sinh(x)
返回一个数的双曲正弦值。
Math.sqrt(x)
返回一个数的平方根。
Math.tan(x)
返回一个数的正切值。
Math.tanh(x)
返回一个数的双曲正切值。
Math.toSource()
返回字符串 "Math"
Math.trunc(x)
返回一个数的整数部分,直接去除其小数点及之后的部分。

Number方法

Number.isNaN()
确定传递的值是否是 NaN。
Number.isFinite()
确定传递的值类型及本身是否是有限数。
Number.isInteger()
确定传递的值类型是“number”,且是整数。

Number.parseFloat()
和全局对象 parseFloat() 一样。
Number.parseInt()
和全局对象 parseInt() 一样。

Object

Object 构造函数创建一个对象包装器。

Object.assign()
通过复制一个或多个对象来创建一个新的对象。

Object.create()
使用指定的原型对象和属性创建一个新对象。

Object.defineProperty()
给对象添加一个属性并指定该属性的配置。

Object.defineProperties()
给对象添加多个属性并分别指定它们的配置。

Object.entries()
返回给定对象自身可枚举属性的 [key, value] 数组。

Object.freeze()
冻结对象:其他代码不能删除或更改任何属性。

Object.getOwnPropertyDescriptor()
返回对象指定的属性配置。

Object.getOwnPropertyNames()
返回一个数组,它包含了指定对象所有的可枚举或不可枚举的属性名。

Object.getOwnPropertySymbols()
返回一个数组,它包含了指定对象自身所有的符号属性。

Object.getPrototypeOf()
返回指定对象的原型对象。

Object.is()
比较两个值是否相同。

Object.isExtensible()
判断对象是否可扩展。

Object.isFrozen()
判断对象是否已经冻结。

Object.isSealed()
判断对象是否已经密封。

Object.setPrototypeOf()
设置对象的原型(即内部 [[Prototype]] 属性)。

WeakMap

WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。

WeakSet

WeakSet 对象允许你将弱保持对象存储在一个集合中。

语法

new WeakSet([iterable]);

instanceof

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}
const auto = new Car('2323''2324', 1234);

console.log(auto instanceof Car);
// expected output: true

console.log(auto instanceof Object);
// expected output: true

typeof

typeof 操作符返回一个字符串,表示未经计算的操作数的类型。

console.log(typeof 42);
// expected output: "number"

console.log(typeof 'blubber');
// expected output: "string"

console.log(typeof true);
// expected output: "boolean"

console.log(typeof undeclaredVariable);
// expected output: "undefined"

new

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}

const car1 = new Car('sdfs''sdfsd sdf', 2234);

console.log(car1.make);
// expected output: "Eagle"

Promises/A+规范

  • fulfill,解决,指一个promise成功时进行一些列操作,如状态的改变,回调的执行。虽然规范中用fulfill来表示解决,但在后面的promise实现多以resolve来指代。
  • reject,拒绝,指一个promise失败时进行的一些列操作
  • eventual value,指的是promise被解决时传递给解决回调的指,由于promise有一次性的特征,因此当这个值被传递时,标志着promise等待态的结束,称为终值,有时称为值。
  • reason,拒绝原因,指在promise被拒绝时传递给拒绝回调的值。

示例:

var fs = require('fs')
function writeFileAsync(fpath, data, cb) {
 fs.writeFile(fpath, data, function(err) {
  cb(err);
 });
}

var fs = require('fs')
var Promise = require('bluebird');

function writeFileAsync(fpath, data) {
 return new Promise(function(resolve, reject) {
  fs.writeFile(fpath, data, function(err){
   if(err) reject(err)
   else resolve()
  })
 })
}
// 回调嵌套
request(url, funcion(err, res, body) {
 if(err) handleError(err)
 
 fs.writeFile('1.txt', body, function(err) {
  if(err) handleError(err)
  request(url2, function(err, res, body) {
   if(err) handleError(err)
  })
 })
})

// Promise写法
request(url)
 .then(function(result) {
  return wirteFileAsync('1.txt', result)
 })
 .then(function(result) {
  return request(url2)
 })
 .catch(function(e){
  handleError(e)
 })

交通灯问题

function red() {
 console.log('red');
}
function green() {
 console.log('green');
}
function yellow() {
 console.log('yellow');
}
var tic = function(timer, cb) {
 return new Promise(function(resolve, reject) {
  setTimeout(function(){
   cb();
   resolve();
  },timer);
 };
};
var d = new Promise(function(resolve, reject){resolve();});
var step = function(def) {
 def.then(function() {
  return tic(3000, red);
 }).then(function() {
  return tic(2000, green);
 }).then(function() {
  return tic(1000, yellow);
 });
}

var d = new Promise(function(resolve, reject) {resolve();});
var step = function(def){
 while(true) {
  def.then(function() {
   return tic(3000, red);
  }).then(function() {
   return tic(2000, green);
  }).then(function() {
   return tic(1000, yellow);
  });
 }
}

var d = new Promise(function(resolve, reject) { resolve();});
var step = function(def) {
 def.then(function() {
  return tic(3000, red);
 }).then(function() {
  return tic(2000, green);
 }).then(function() {
  step(def);
 });
}

优化:

var tic = function(timmer, str){
 return new Promise(function(resolve, reject) {
  setTimeout(function() {
   console.log(str);
   resolve(1);
  }, timmer);
 });
};


function *gen(){
 yield tic(3000, 'red');
 yield tic(1000, 'green');
 yield tic(2000, 'yellow');
}

var iterator = gen();
var step = function(gen, iterator){
 var s = iterator.next();
 if (s.done) {
  step(gen, gen());
 } else {
  s.value.then(function() {
   step(gen, iterator);
  });
 }
}

step(gen, iterator);
var promise = new Promise(function(resolve) {
 resolve(42);
});
promise.then(function(value) {
 console.log(value);
}).catch(function(error) {
 console.log(error);
});

使用了回调函数的异步处理

getAsync("file.txt"function(error, result) {
 if(error){ // 取得失败时的处理
  throw error;
 }
 // 取得成功时的处理
});

使用Promise进行异步处理的一个例子

var promise = getAsyncPromise('fileA.txt');
promise.then(function(result) {
 // 获取文件内容成功时的处理
}).catch(function(error) {
 // 获取文件内容失败时的处理
});

Constructor

Promise类似于 XMLHttpRequest,从构造函数 Promise 来创建一个新建新promise对象作为接口。

要想创建一个promise对象、可以使用new来调用Promise的构造器来进行实例化。

var promise = new Promise(function(resolve, reject) {
    // 异步处理
    // 处理结束后、调用resolve 或 reject
});
promise.then(onFulfilled, onRejected)

resolve(成功)时onFulfilled 会被调用

reject(失败)时onRejected 会被调用

Promise的状态

用new Promise 实例化的promise对象有以下三个状态。

"has-resolution" - Fulfilledresolve(成功)时。此时会调用 onFulfilled

"has-rejection" - Rejectedreject(失败)时。此时会调用 onRejected

"unresolved" - Pending既不是resolve也不是reject的状态。也就是promise对象刚被创建后的初始化状态等

promise对象的状态,从Pending转换为FulfilledRejected之后, 这个promise对象的状态就不会再发生任何变化。

也就是说,PromiseEvent等不同,在.then 后执行的函数可以肯定地说只会被调用一次。

创建XHR的promise对象

function getURL(URL) {
 return new Promise(function (resolve, reject) {
  var req = new XMLHttpRequest();
  req.open('GET', URL, true);
  req.onload = function() {
   if(req.status === 200) {
    resolve(req.responseText);
   }else{
    reject(new Error(req.statusText));
   }
  };
  req.onerror = function() {
   reject(new Error(req.statusText));
  };
  req.send();
 });
}
// 运行示例xxx
var URL = "http://x'x'x.org/get";
getURL(URL).then(function onFulfilled(value){
    console.log(value);
}).catch(function onRejected(error){
    console.error(error);
});
Promise.resolve(42).then(function(value){
    console.log(value);
});
var promise = new Promise(function (resolve){
    console.log("inner promise"); // 1
    resolve(42);
});
promise.then(function(value){
    console.log(value); // 3
});
console.log("outer promise"); // 2

nner promise // 1
outer promise // 2
42            // 3

示例:

function taskA() {
    console.log("Task A");
}
function taskB() {
    console.log("Task B");
}
function onRejected(error) {
    console.log("Catch Error: A or B", error);
}
function finalTask() {
    console.log("Final Task");
}

var promise = Promise.resolve();
promise
    .then(taskA)
    .then(taskB)
    .catch(onRejected)
    .then(finalTask);
    
Task A
Task B
Final Task
image.png

AMD 与 CMD 的区别

  • CMD 推崇依赖就近,AMD 推崇依赖前置。
  • AMD 是提前执行,CMD 是延迟执行

AMD 和 CMD 都是用于浏览器端的模块规范,而在服务器端比如 node,采用的则是 CommonJS 规范。

CommonJS 规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。

AMD规范则是非同步加载模块,允许指定回调函数。

  1. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  2. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。

如何查看代码利用率

  • 打开 Chrome Dev Tool;
  • 按下 Cmd + Shift + P or Ctrl + Shift + P ;
  • 输入 Coverage,并选择第一个出现的选项;

使用 Array.includes 来处理多个条件

示例:

function test(fruit) {
  if (fruit == 'apple' || fruit == 'strawberry') {
    console.log('red');
  }
}

优化:

function test(fruit) {
  // 条件提取到数组中
  const redFruits = ['apple''strawberry''cherry''cranberries'];
 
  if (redFruits.includes(fruit)) {
    console.log('red');
  }
}

减少嵌套,提前使用 return 语句

function test(fruit, quantity) {
  const redFruits = ['apple''strawberry''cherry''cranberries'];
 
  // 条件 1:fruit 必须有值
  if (fruit) {
    // 条件 2:必须为红色
    if (redFruits.includes(fruit)) {
      console.log('red');
 
      // 条件 3:数量必须大于 10
      if (quantity > 10) {
        console.log('big quantity');
      }
    }
  } else {
    throw new Error('No fruit!');
  }
}
 
// 测试结果
test(null); // 抛出错误:No fruits
test('apple'); // 打印:red
test('apple', 20); // 打印:red,big quantity

优化:

/* 在发现无效条件时提前 return */
 
function test(fruit, quantity) {
  const redFruits = ['apple''strawberry''cherry''cranberries'];
 
  // 条件 1:提前抛出错误
  if (!fruit) throw new Error('No fruit!');
 
  // 条件2:必须为红色
  if (redFruits.includes(fruit)) {
    console.log('red');
 
    // 条件 3:数量必须大于 10
    if (quantity > 10) {
      console.log('big quantity');
    }
  }
}

优化:

/* 在发现无效条件时提前 return */
 
function test(fruit, quantity) {
  const redFruits = ['apple''strawberry''cherry''cranberries'];
 
  if (!fruit) throw new Error('No fruit!'); // 条件 1:提前抛出错误
  if (!redFruits.includes(fruit)) return;  // 条件 2:当 fruit 不是红色的时候,提前 return
 
  console.log('red');
 
  // 条件 3:必须是大量存在
  if (quantity > 10) {
    console.log('big quantity');
  }
}

使用函数的默认参数 和 解构

使用 JavaScript 时总是需要检查 null / undefined 值并分配默认值:

function test(fruit, quantity) {
  if (!fruit) return;
  const q = quantity || 1; // 如果没有提供 quantity 参数,则默认为 1
 
  console.log(`We have ${q} ${fruit}!`);
}
 
// 测试结果
test('banana'); // We have 1 banana!
test('apple', 2); // We have 2 apple!

优化:

function test(fruit, quantity = 1) { // i如果没有提供 quantity 参数,则默认为 1
  if (!fruit) return;
  console.log(`We have ${quantity} ${fruit}!`);
}
 
// 测试结果
test('banana'); // We have 1 banana!
test('apple', 2); // We have 2 apple!
function test(fruit) { 
  // 如果有值,则打印 fruit.name
  if (fruit && fruit.name)  {
    console.log (fruit.name);
  } else {
    console.log('unknown');
  }
}
 
//测试结果
test(undefined); // unknown
test({ }); // unknown
test({ name: 'apple', color: 'red' }); // apple

优化:

// 解构 —— 只获得 name 属性
// 参数默认分配空对象 {}
function test({name} = {}) {
  console.log (name || 'unknown');
}
 
//测试结果
test(undefined); // unknown
test({ }); // unknown
test({ name: 'apple', color: 'red' }); // apple

优化:

// 引入 lodash 库,我们将获得 _.get()
function test(fruit) {
  console.log(_.get(fruit, 'name''unknown'); // 获取 name 属性,如果没有分配,则设为默认值 unknown
}
 
//测试结果
test(undefined); // unknown
test({ }); // unknown
test({ name: 'apple', color: 'red' }); // apple

选择 Map / Object 字面量,而不是Switch语句

示例:

function test(color) {
  // 使用 switch case 语句,根据颜色找出对应的水果
  switch (color) {
    case 'red':
      return ['apple''strawberry'];
    case 'yellow':
      return ['banana''pineapple'];
    case 'purple':
      return ['grape''plum'];
    default:
      return [];
  }
}
 
//测试结果
test(null); // []
test('yellow'); // ['banana''pineapple']

优化:

// 使用对象字面量,根据颜色找出对应的水果
  const fruitColor = {
    red: ['apple''strawberry'],
    yellow: ['banana''pineapple'],
    purple: ['grape''plum']
  };
 
function test(color) {
  return fruitColor[color] || [];
}
// 使用 Map ,根据颜色找出对应的水果
  const fruitColor = new Map()
    .set('red', ['apple''strawberry'])
    .set('yellow', ['banana''pineapple'])
    .set('purple', ['grape''plum']);
 
function test(color) {
  return fruitColor.get(color) || [];
}
 const fruits = [
    { name: 'apple', color: 'red' }, 
    { name: 'strawberry', color: 'red' }, 
    { name: 'banana', color: 'yellow' }, 
    { name: 'pineapple', color: 'yellow' }, 
    { name: 'grape', color: 'purple' }, 
    { name: 'plum', color: 'purple' }
];
 
function test(color) {
  // 使用 Array filter  ,根据颜色找出对应的水果
 
  return fruits.filter(f => f.color == color);
}

使用 Array.everyArray.some 来处理全部/部分满足条件

const fruits = [
    { name: 'apple', color: 'red' },
    { name: 'banana', color: 'yellow' },
    { name: 'grape', color: 'purple' }
  ];
 
function test() {
  let isAllRed = true;
 
  // 条件:所有的水果都必须是红色
  for (let f of fruits) {
    if (!isAllRed) break;
    isAllRed = (f.color == 'red');
  }
 
  console.log(isAllRed); // false
}

优化:

const fruits = [
    { name: 'apple', color: 'red' },
    { name: 'banana', color: 'yellow' },
    { name: 'grape', color: 'purple' }
  ];
 
function test() {
  // 条件:简短方式,所有的水果都必须是红色
  const isAllRed = fruits.every(f => f.color == 'red');
 
  console.log(isAllRed); // false
}

使用 Array.some

const fruits = [
    { name: 'apple', color: 'red' },
    { name: 'banana', color: 'yellow' },
    { name: 'grape', color: 'purple' }
];
 
function test() {
  // 条件:是否存在红色的水果
  const isAnyRed = fruits.some(f => f.color == 'red');
 
  console.log(isAnyRed); // true
}

Spread operator(展开操作符)

示例:

const favoriteFood = ['Pizza''Fries''Swedish-meatballs'];
 
console.log(...favoriteFood);
//Pizza Fries Swedish-meatballs

for…of 迭代器

const toolBox = ['Hammer''Screwdriver''Ruler']
for(const item of toolBox) {
  console.log(item)
}
 
// Hammer
// Screwdriver
// Ruler

Includes() 方法

const garge = ['BMW''AUDI''VOLVO'];
const findCar = garge.includes('BMW');
console.log(findCar);
 
// true

清空或截断数组

const arr = [11, 22, 33, 44, 55, 66];
// truncanting
arr.length = 3;
console.log(arr); //=> [11, 22, 33]
// clearing
arr.length = 0;
console.log(arr); //=> []
console.log(arr[2]); //=> undefined

使用对象解构(destructuring)模拟命名参数

示例:

doSomething({ foo: 'Hello', bar: 'Hey!', baz: 42 });
function doSomething(config) {
  const foo = config.foo !== undefined ? config.foo : 'Hi';
  const bar = config.bar !== undefined ? config.bar : 'Yo!';
  const baz = config.baz !== undefined ? config.baz : 13;
  // ...
}

优化:

function doSomething({ foo = 'Hi', bar = 'Yo!', baz = 13 } = {}) {
  // ...
}

使用 async/awaitawait多个async函数

await Promise.all([anAsyncCall(), thisIsAlsoAsync(), oneMore()])

创建纯(pure)对象

示例:

const pureObject = Object.create(null);
console.log(pureObject); //=> {}
console.log(pureObject.constructor); //=> undefined
console.log(pureObject.toString); //=> undefined
console.log(pureObject.hasOwnProperty); //=> undefined

平铺多维数组

示例:

// 仅仅适用于二维数组
const arr = [11, [22, 33], [44, 55], 66];
const flatArr = [].concat(...arr); //=> [11, 22, 33, 44, 55, 66]

优化:

unction flattenArray(arr) {
  const flattened = [].concat(...arr);
  return flattened.some(item => Array.isArray(item)) ? 
    flattenArray(flattened) : flattened;
}
 
const arr = [11, [22, 33], [44, [55, 66, [77, [88]], 99]]];
const flatArr = flattenArray(arr); 
//=> [11, 22, 33, 44, 55, 66, 77, 88, 99]

Array.prototype

Array.prototype  属性表示 Array 构造函数的原型,并允许您向所有Array对象添加新的属性和方法。

示例:

/*
如果JavaScript本身不提供 first() 方法,
添加一个返回数组的第一个元素的新方法。
*/

if(!Array.prototype.first) {
    Array.prototype.first = function() {
        console.log(`如果JavaScript本身不提供 first() 方法,
添加一个返回数组的第一个元素的新方法。`);
        return this[0];
    }
}
Array.isArray(Array.prototype);
// true

方法-会改变自身的方法

Array.prototype.pop()
删除数组的最后一个元素,并返回这个元素。
Array.prototype.push()
在数组的末尾增加一个或多个元素,并返回数组的新长度。
Array.prototype.reverse()
颠倒数组中元素的排列顺序,即原先的第一个变为最后一个,原先的最后一个变为第一个。
Array.prototype.shift()
删除数组的第一个元素,并返回这个元素。
Array.prototype.sort()
对数组元素进行排序,并返回当前数组。
Array.prototype.splice()
在任意的位置给数组添加或删除任意个元素。
Array.prototype.unshift()
在数组的开头增加一个或多个元素,并返回数组的新长度。

不会改变自身的方法

Array.prototype.concat()
返回一个由当前数组和其它若干个数组或者若干个非数组值组合而成的新数组。
Array.prototype.join()
连接所有数组元素组成一个字符串。
Array.prototype.slice()
抽取当前数组中的一段元素组合成一个新数组。
Array.prototype.toString()
返回一个由所有数组元素组合而成的字符串。遮蔽了原型链上的 Object.prototype.toString() 方法。
Array.prototype.indexOf()
返回数组中第一个与指定值相等的元素的索引,如果找不到这样的元素,则返回 -1。
Array.prototype.lastIndexOf()
返回数组中最后一个(从右边数第一个)与指定值相等的元素的索引,如果找不到这样的元素,则返回 -1。

遍历方法

Array.prototype.forEach()
为数组中的每个元素执行一次回调函数。

Array.from()

Array.from() 方法从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。

console.log(Array.from('foo'));
// expected output: Array ["f""o""o"]

console.log(Array.from([1, 2, 3], x => x + x));
// expected output: Array [2, 4, 6]

语法

Array.from(arrayLike[, mapFn[, thisArg]])

返回值
一个新的数组实例。

从 String 生成数组

Array.from('foo');
// [ "f""o""o" ]

从 Set 生成数组

const set = new Set(['foo''bar''baz''foo']);
Array.from(set);
// [ "foo""bar""baz" ]

从类数组对象(arguments)生成数组

function f() {
  return Array.from(arguments);
}

f(1, 2, 3);

// [ 1, 2, 3 ]

数组去重合并

function combine(){
    let arr = [].concat.apply([], arguments);  //没有去重复的新数组
    return Array.from(new Set(arr));
}

var m = [1, 2, 2], n = [2,3,3];
console.log(combine(m,n));                     // [1, 2, 3]

Array.isArray()

Array.isArray() 用于确定传递的值是否是一个 Array。

示例:

Array.isArray([1, 2, 3]);
// true
Array.isArray({foo: 123});
// false
Array.isArray("foobar");
// false
Array.isArray(undefined);
// false

instanceof 和 isArray

当检测Array实例时, Array.isArray 优于 instanceof,因为Array.isArray能检测iframes.

var iframe = document.createElement('iframe');
document.body.appendChild(iframe);
xArray = window.frames[window.frames.length-1].Array;
var arr = new xArray(1,2,3); // [1,2,3]

// Correctly checking for Array
Array.isArray(arr);  // true
// Considered harmful, because doesn't work though iframes
arr instanceof Array; // false

Polyfill

示例:

if (!Array.isArray) {
  Array.isArray = function(arg) {
    return Object.prototype.toString.call(arg) === '[object Array]';
  };
}

Array.of()

Array.of() 方法创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型。

Array.of() 和 Array 构造函数之间的区别在于处理整数参数:Array.of(7) 创建一个具有单个元素 7 的数组,而 Array(7) 创建一个长度为7的空数组(注意:这是指一个有7个空位(empty)的数组,而不是由7个undefined组成的数组)。

Array.of(7);       // [7]
Array.of(1, 2, 3); // [1, 2, 3]

Array(7);          // [ , , , , , , ]
Array(1, 2, 3);    // [1, 2, 3]

语法

Array.of(element0[, element1[, ...[, elementN]]])

示例

Array.of(1);         // [1]
Array.of(1, 2, 3);   // [1, 2, 3]
Array.of(undefined); // [undefined]

兼容旧环境

if (!Array.of) {
  Array.of = function() {
    return Array.prototype.slice.call(arguments);
  };
}

get Array[@@species]

Array[@@species] 访问器属性返回 Array 的构造函数。

语法

Array[Symbol.species]

返回值

Array 的构造函数。

描述

species 访问器属性返回 Array 对象的默认构造函数。子类的构造函数可能会覆盖并改变构造函数的赋值。

示例

species 属性返回默认构造函数, 它用于 Array 对象的构造函数 Array:

Array[Symbol.species]; // function Array()

闭包

闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。

词法作用域

示例:

function init() {
    var name = "Mozilla"; // name 是一个被 init 创建的局部变量
    function displayName() { // displayName() 是内部函数,一个闭包
        alert(name); // 使用了父函数中声明的变量
    }
    displayName();
}
init();

词法(lexical)一词指的是,词法作用域根据源代码中声明变量的位置来确定该变量在何处可用。嵌套函数可访问声明于它们外部作用域的变量。

闭包

示例:

function makeFunc() {
    var name = "Mozilla";
    function displayName() {
        alert(name);
    }
    return displayName;
}

var myFunc = makeFunc();
myFunc();

闭包是由函数以及声明该函数的词法环境组合而成的。该环境包含了这个闭包创建时作用域内的任何局部变量。

示例:

// add5 和 add10 都是闭包。它们共享相同的函数定义,但是保存了不同的词法环境。
function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12

示例:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
  this.getName = function() {
    return this.name;
  };

  this.getMessage = function() {
    return this.message;
  };
}

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype = {
  getName: function() {
    return this.name;
  },
  getMessage: function() {
    return this.message;
  }
};


function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype.getName = function() {
  return this.name;
};
MyObject.prototype.getMessage = function() {
  return this.message;
};

继承与原型链

当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象( object )都有一个私有属性(称之为 __proto__ )指向它的构造函数的原型对象(prototype )。该原型对象也有一个自己的原型对象( __proto__ ) ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。

几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。

JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。

遵循ECMAScript标准,someObject.[[Prototype]] 符号是用于指向 someObject 的原型。从 ECMAScript 6 开始,[[Prototype]] 可以通过 Object.getPrototypeOf()Object.setPrototypeOf() 访问器来访问。这个等同于 JavaScript 的非标准但许多浏览器实现的属性 __proto__

示例:

// 让我们从一个函数里创建一个对象o,它自身拥有属性a和b的:
let f = function () {
   this.a = 1;
   this.b = 2;
}
/* 这么写也一样
function f() {
  this.a = 1;
  this.b = 2;
}
*/
let o = new f(); // {a: 1, b: 2}

// 在f函数的原型上定义属性
f.prototype.b = 3;
f.prototype.c = 4;

// 不要在 f 函数的原型上直接定义 f.prototype = {b:3,c:4};这样会直接打破原型链
// o.[[Prototype]] 有属性 b 和 c
//  (其实就是 o.__proto__ 或者 o.constructor.prototype)
// o.[[Prototype]].[[Prototype]] 是 Object.prototype.
// 最后o.[[Prototype]].[[Prototype]].[[Prototype]]是null
// 这就是原型链的末尾,即 null,
// 根据定义,null 就是没有 [[Prototype]]。

// 综上,整个原型链如下:

// {a:1, b:2} ---> {b:3, c:4} ---> Object.prototype---> null

console.log(o.a); // 1
// a是o的自身属性吗?是的,该属性的值为 1

console.log(o.b); // 2
// b是o的自身属性吗?是的,该属性的值为 2
// 原型上也有一个'b'属性,但是它不会被访问到。
// 这种情况被称为"属性遮蔽 (property shadowing)"

console.log(o.c); // 4
// c是o的自身属性吗?不是,那看看它的原型上有没有
// c是o.[[Prototype]]的属性吗?是的,该属性的值为 4

console.log(o.d); // undefined
// d 是 o 的自身属性吗?不是,那看看它的原型上有没有
// d 是 o.[[Prototype]] 的属性吗?不是,那看看它的原型上有没有
// o.[[Prototype]].[[Prototype]] 为 null,停止搜索
// 找不到 d 属性,返回 undefined

继承方法

var o = {
  a: 2,
  m: function(){
    return this.a + 1;
  }
};

console.log(o.m()); // 3
// 当调用 o.m 时,'this' 指向了 o.

var p = Object.create(o);
// p是一个继承自 o 的对象

p.a = 4; // 创建 p 的自身属性 'a'
console.log(p.m()); // 5
// 调用 p.m 时,'this' 指向了 p
// 又因为 p 继承了 o 的 m 函数
// 所以,此时的 'this.a' 即 p.a,就是 p 的自身属性 'a' 

在 JavaScript 中使用原型

function doSomething(){}
console.log( doSomething.prototype );
// 和声明函数的方式无关,
// JavaScript 中的函数永远有一个默认原型属性。
var doSomething = function(){};
console.log( doSomething.prototype );

控制台:

{
    constructor: ƒ doSomething(),
    __proto__: {
        constructor: ƒ Object(),
        hasOwnProperty: ƒ hasOwnProperty(),
        isPrototypeOf: ƒ isPrototypeOf(),
        propertyIsEnumerable: ƒ propertyIsEnumerable(),
        toLocaleString: ƒ toLocaleString(),
        toString: ƒ toString(),
        valueOf: ƒ valueOf()
    }
}

给doSomething函数的原型对象添加新属性,如下:

function doSomething(){}
doSomething.prototype.foo = "bar";
console.log( doSomething.prototype );

{
    foo: "bar",
    constructor: ƒ doSomething(),
    __proto__: {
        constructor: ƒ Object(),
        hasOwnProperty: ƒ hasOwnProperty(),
        isPrototypeOf: ƒ isPrototypeOf(),
        propertyIsEnumerable: ƒ propertyIsEnumerable(),
        toLocaleString: ƒ toLocaleString(),
        toString: ƒ toString(),
        valueOf: ƒ valueOf()
    }
}

使用语法结构创建的对象

var o = {a: 1};

// o 这个对象继承了 Object.prototype 上面的所有属性
// o 自身没有名为 hasOwnProperty 的属性
// hasOwnProperty 是 Object.prototype 的属性
// 因此 o 继承了 Object.prototype 的 hasOwnProperty
// Object.prototype 的原型为 null
// 原型链如下:
// o ---> Object.prototype ---> null

var a = ["yo""whadup""?"];

// 数组都继承于 Array.prototype
// (Array.prototype 中包含 indexOf, forEach 等方法)
// 原型链如下:
// a ---> Array.prototype ---> Object.prototype ---> null

function f(){
  return 2;
}

// 函数都继承于 Function.prototype
// (Function.prototype 中包含 call, bind等方法)
// 原型链如下:
// f ---> Function.prototype ---> Object.prototype ---> null

使用构造器创建的对象

function Graph() {
  this.vertices = [];
  this.edges = [];
}

Graph.prototype = {
  addVertex: function(v){
    this.vertices.push(v);
  }
};

var g = new Graph();
// g 是生成的对象,他的自身属性有 'vertices' 和 'edges'
// 在 g 被实例化时,g.[[Prototype]] 指向了 Graph.prototype。

使用 Object.create 创建的对象

可以调用这个方法来创建一个新对象。新对象的原型就是调用 create 方法时传入的第一个参数:

var a = {a: 1};
// a ---> Object.prototype ---> null

var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (继承而来)

var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null

var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined, 因为d没有继承Object.prototype

使用 class 关键字创建的对象

关键字包括 class, constructor,static,extends 和 super

示例:

"use strict";

class Polygon {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

class Square extends Polygon {
  constructor(sideLength) {
    super(sideLength, sideLength);
  }
  get area() {
    return this.height * this.width;
  }
  set sideLength(newLength) {
    this.height = newLength;
    this.width = newLength;
  }
}

var square = new Square(2);

❤️关注+点赞+收藏+评论+转发❤️,原创不易,鼓励笔者创作更好的文章

点赞、收藏和评论

我是Jeskson(达达前端),感谢各位人才的:点赞、收藏和评论,我们下期见!(如本文内容有地方讲解有误,欢迎指出☞谢谢,一起学习了)

我们下期见!

github收录,欢迎Star:https://github.com/webVueBlog/WebFamily

浏览 23
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报