前端学习之路

前端资源

gulp 是一个 js 自动化工具。

github 上的一些资料

谷歌字体

Open Graph protocol

MDN 学习 Web 开发

掘金 是真的挺不错的一个社区。好多干货文章。

相关文章

一文完全吃透 JavaScript 继承(面试必备良药)

这篇文章详细讲解了原型相关的知识,包括原型链、ES5 ES6中的继承实现以及原理。帮助理解 JavaScript 面向对象方向的继承原理和构造器相关概念。

[译] 在 async/await 中更好的处理错误

这篇文章详细介绍了在 JavaScript 中的异步问题,介绍了回调地狱、Promise、async/await,以及他们的错误处理机制。

中高级前端面试题(万字长文)

面试题以及知识框架整理。

理解Javascript的正则表达式

正则表达式是在应用中相当重要的一部分,需要去理解记忆并多加练习。

轻松理解JS中的面向对象,顺便搞懂prototype和proto

理解 JavaScript 中的原型、原型链、构造器等相关概念。

2020年你不能不知道的webpack基本配置

了解 webpack 工具,官方文档 https://www.webpackjs.com/concepts/

前端下载文件的5种方法的对比

关于前端的一些典型下载文件实现案例。

2020年大厂面试指南 - Vue篇

看大厂面试题对于理解相关知识也是很有用的。重要的是不要死记硬背,因为很多答案并不是从原理讲起,或者讲了看不懂,需要结合官方文档和实际上手去理解。

非常感谢这位作者,关于 Vue 的实现原理可以参考上面的连载专栏。不需要去深入算法,但是需要去了解一些基本的原理。同时自己可以尝试做一个类似的例如双重绑定、响应式等等。

这个源码系列好多都没看懂, 只能看个大概。感觉文档越看越浮躁了。

作为计算机相关行业人员对于数据结构和算法是必须要掌握一些的。

剖析一些经典的CSS布局问题,为前端开发+面试保驾护航

跟着某些作者看可以找到一些没有推荐过的文章。有的还没看过,先留着。

前端面试常考的手写代码不是背出来的!

这篇文章是对于代码规范和一些功能函数的实例研究,很基础但也很有用。

深入理解JS的原型和原型链

深入理解原型和原型链之间的关系,然后引申出执行上下文和 this 的相关概念。

vue 248个知识点(面试题)为你保驾护航

了解 Vue 的相关知识点和原理。

🔥(已更新3.1w字)《大前端吊打面试官系列》 之 ES6 精华篇(2020年)

深入理解 ES6 。

在前端开发环境下,对于 Webpack、gulp 等工具的理解使用对于提升开发效率、专注前端页面是有好处的。

面试题:说说事件循环机制(满分答案来了)

关于事件循环机制。

前端工程师的自我修养-关于 Babel 那些事儿

Babel 是可以对代码进行向后兼容性编译的编译器,使得代码在不同环境下依然可以稳定运行。

你再不知道怎么解决跨域,我就真的生气了

关于跨域相关的知识点。跨域指的是由于浏览器出于安全考虑所设置的同源策略,如果协议、域名、端口有一项不同就会产生跨域问题,而跨域问题所带来的是安全问题,解决跨域不仅仅是让前端可以成功访问到跨域资源,更重要的是理解背后的安全隐患。

记好这 24 个 ES6 方法,用来解决实际开发的 JS 问题

ES6 相关的对于实际问题的解决办法。

深入vue响应式原理(包含vue3.0)

看这个是想了解关于 Vue 2.0 和 3.0 之间的变化。

2020 前端面试 | 第一波面试题总结

实际面试题。

🔥 动画:《大前端吊打面试官系列》 之原生 JavaScript 精华篇

关于 JavaScript 相关的基础知识理解。

前端也能学算法:由浅入深讲解贪心算法

关于贪心算法的讲解。

「 如何优雅的使用VUE? 」不可不知的VUE实战技巧

Vue 的实战技巧。

2020年了,再不会webpack敲得代码就不香了(近万字实战)

webpack 相关知识。

【建议星星】要就来45道Promise面试题一次爽到底(1.1w字用心整理)

关于 Promise 的各种面试题型。加深对于异步的理解和处理。

从手写Promise到async/await(接近6千字,建议看一下)

关于异步和生成器、构造器的原理讲解和一些组合方法。

【建议👍】再来40道this面试题酸爽继续(1.2w字用手整理)

关于 this 的相关题型。

vue 组件通信看这篇就够了(12种通信方式)

详细介绍了不同的父子组件间的通信方式。

学习 BFC (Block Formatting Context)

了解在 CSS 中的 BFC 的相关概念。

【第1250期】彻底理解浏览器的缓存机制

了解浏览器的缓存机制

【译】Async/await和Promise的不为人知的秘密

关于 Async 和 Promise 的性能上的区别。

learnVue

github 上的 Vue 学习文章

Vue3响应式系统源码解析-单测篇

关于 Vue3 的响应式实现。

【面试篇】寒冬求职季之你必须要懂的原生JS(上)

经典面试题和解析

【面试篇】寒冬求职季之你必须要懂的原生JS(中)

经典面试题和解析

【面试篇】寒冬求职之你必须要懂的Web安全

关于 Web 安全的相关面试题和解析

「进击的前端工程师」浏览器中JavaScript的事件循环

深入了解 JavaScript 引擎的事件机制,后面的例题也很值得思考。

知识框架

  • HTML
    • HTML 不同版本的差异
    • HTML5
  • CSS
    • CSS 不同版本的差异
    • CSS3
    • Sass
  • JavaScript 廖雪峰
  • Node.js
  • jQuery(老掉牙了)
  • Ajax(也老掉牙了,了解XMR就行)
  • Webpack
  • Gulp
  • Vue
  • React
  • Angular
  • ES5
  • ES6

HTML 整理

HTML 只是一个标记性的语言,本身没有什么学习难度,HTML5 是新一代的 HTML 标准,实现了一些新的元素和属性, 改进了本地存储并且添加了很多语义元素,使得代码或者页面更加语义化。语义化的好处在于对代码的理解更加简单,同时也为视力障碍人士改善了网页的可阅读性。

关于更多的细节参考 https://www.runoob.com/html/html-tutorial.html

HTML DOM

关于 DOM 的理解可以参考 https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-tree-construction

DOM 是文档对象模型的缩写,按照对对象的理解,DOM 就是浏览器生成的这个页面的对象,每一个 HTML 元素就是一个节点,通过 HTML 的嵌套关系生成子节点和父节点关系的树,元素的属性也是元素对应节点的子节点。

DOM 的树并不是普通的二叉树或者特殊的树

DOM 节点关系

关系其实还是挺复杂的。不过 DOM 提供了各种方法实现了对于节点或者元素的选择、修改。

获取目标元素节点是对于 JavaScript 脚本比较重要的一环,获取到节点后即可对节点所产生的事件进行脚本控制。

当然在 Vue 或者 React 等框架中有对应的方法可以更加方便的获取节点添加事件。

CSS DOM

css 也是有一个 DOM 树的通过将两棵树合并生成一个渲染树供浏览器对页面进行渲染。

CSS 整理

CSS 的作用是给 HTML 元素加上样式,同时又可以多个样式层叠,所以叫层叠样式表。

CSS 的内容也是比较复杂的。细节参考 https://www.runoob.com/css/css-tutorial.html

需要理解不同元素类型能做的事和不能做的事,例如行内元素的宽度和高度是不能设置的。根据内容自动。

不过对于网页 CSS 设计来说,例如 Bootstrap 或者 Element 等前端组件库已经设定好了很多实用的样式。可以在此基础上进行自定义。关于页面的布局也是有对应的解决方案,需要理解的还是元素的类型,

参考 https://www.runoob.com/cssref/pr-class-display.html

CSS 相关的教程好少啊,但是感觉各种属性对应的布局却很麻烦。

CSS 字体相关属性

w3school 页面

font-style 规定字体样式。参阅:font-style 中可能的值。
font-variant 规定字体异体。参阅:font-variant 中可能的值。
font-weight 规定字体粗细。参阅:font-weight 中可能的值。
font-size/line-height 规定字体尺寸和行高。参阅:font-sizeline-height 中可能的值。
font-family 规定字体系列。参阅:font-family 中可能的值。
caption 定义被标题控件(比如按钮、下拉列表等)使用的字体。
icon 定义被图标标记使用的字体。
menu 定义被下拉列表使用的字体。
message-box 定义被对话框使用的字体。
small-caption caption 字体的小型版本。
status-bar 定义被窗口状态栏使用的字体。

CSS 文本相关属性

属性 描述
color 设置文本颜色
direction 设置文本方向。
line-height 设置行高。
letter-spacing 设置字符间距。
text-align 对齐元素中的文本。
text-decoration 向文本添加修饰。
text-indent 缩进元素中文本的首行。
text-shadow 设置文本阴影。CSS2 包含该属性,但是 CSS2.1 没有保留该属性。
text-transform 控制元素中的字母。
unicode-bidi 设置文本方向。
white-space 设置元素中空白的处理方式。
word-spacing 设置字间距。

关于各个布局

https://zh.learnlayout.com/ 可以用来了解布局相关的知识。

布局其实就是把网页根据内容进行一个规划。

网页的内容永远都是不确定的,那么布局当然是要怎么好看怎么来。

了解布局首先需要了解 display 这个属性。这个属性决定了 HTML 元素在 CSS 层面上的性质。每个 tag 都有默认的 display 属性,比如常见的 h1 div p form 就是块级元素,而 a b i span select img input 都是行内元素。

通过不同的元素特性,页面元素也会产生不同的显示效果。比如在弹性盒子中有很多新的特性和 CSS 样式,可以更加方便快捷的设定垂直居中效果,而在块级元素中就很难做到。大致上来说网页的布局也就分为一列、二列、三列,通过样式的叠加形成不同的网页显示效果。至于说的什么圣飞布局、双飞翼布局感觉没啥意思,究其原理还是浮动、弹性盒子、网格、定位这类基础属性,只能说是别人设计出来的网页布局就是了。重要的还是内容的排列。通过 display 属性获得不同的文字、图片、内容块的排列效果。

关于盒模型

盒模型是用来计算元素大小的一个标准模型,完整适用于块元素,部分适用于行内元素。盒模型还有一个替代模型IE盒模型,在IE盒模型中,所有的宽度和高度都是可见的,内容的宽度包括了Padding的宽度。

Diagram of the box model

一个被定义成块级的(block)盒子会表现出以下行为:

  • 盒子会在内联的方向上扩展并占据父容器在该方向上的所有可用空间,在绝大数情况下意味着盒子会和父容器一样宽
  • 每个盒子都会换行
  • widthheight 属性可以发挥作用
  • 内边距(padding), 外边距(margin) 和 边框(border) 会将其他元素从当前盒子周围“推开”

如果一个盒子对外显示为 inline,那么他的行为如下:

  • 盒子不会产生换行。
  • widthheight 属性将不起作用。
  • 内边距、外边距以及边框会被应用但是不会把其他处于 inline 状态的盒子推开。

一个元素使用 display: inline-block,实现我们需要的块级的部分效果:

  • 设置widthheight 属性会生效。
  • padding, margin, 以及border 会推开其他元素。

但是,它不会跳转到新行,如果显式添加widthheight 属性,它只会变得比其内容更大。

https://developer.mozilla.org/zh-CN/docs/Learn/CSS/CSS_layout/Flexbox 详细介绍了 display:flexible 弹性盒子的 flex 模型

flex_terms.png

  • 主轴(main axis)是沿着 flex 元素放置的方向延伸的轴(比如页面上的横向的行、纵向的列)。该轴的开始和结束被称为 main startmain end
  • 交叉轴(cross axis)是垂直于 flex 元素放置方向的轴。该轴的开始和结束被称为 cross startcross end
  • 设置了 display: flex 的父元素(在本例中是 <section>)被称之为 flex 容器(flex container)。
  • 在 flex 容器中表现为柔性的盒子的元素被称之为 flex 项flex item)(本例中是 article 元素。

关于网页字体

JavaScript 整理

关于语法就和其他语言是一样的,变量、运算、数据类型、函数、对象、数组、循环、作用域等等。

不一样的是,由于 JavaScript 是运行在浏览器上的。所以很多功能代码都是已经实现了的。不需要像其他语言一样需要自己写类。由于本身是面向对象的,JavaScript 的所有变量都是一个对象。对于 JavaScript 的学习需要去了解背后的原理。

由于浏览器的版本大家可能都不一样,所以在使用 JavaScript 特性的时候需要考虑版本问题。了解 ES5 和 ES6 的区别。可以使用 https://caniuse.com/ 来了解特性的支持情况。

细节参考 https://www.w3school.com.cn/js/index.asp

关于原型链

JavaScript 是解释型脚本语言,同时也是面向对象的语言,JavaScript 的对象是动态的属性“包”,会有一个__proto__的私有属性,该属性会指向构造函数的原型对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 让我们从一个自身拥有属性a和b的函数里创建一个对象o:
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

来自 MDN https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain

所以当一个对象通过 new 关键字从其他的对象“实例化”,会将先生成一个空对象,通过 Object.setPrototypeOf()原生函数将实例对象添加到原型链上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function polyNew(source, ...arg) {
// 创建一个空的简单JavaScript对象(即{})
let newObj = {};
// 链接该对象(即设置该对象的构造函数)到另一个对象
Object.setPrototypeOf(newObj, source.prototype);
// 将步骤1新创建的对象作为this的上下文 ;
const resp = source.apply(newObj, arg);
// 判断该函数返回值是否是对象
if (Object.prototype.toString.call(resp) === "[object Object]") {
// 如果该函数没有返回对象,则返回this。
return resp
} else {
// 如果该函数返回对象,那用返回的这个对象作为返回值。
return newObj
}
}

作者:dellyoung
链接:https://juejin.im/post/5e54d9e86fb9a07c944c932a
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

上面这个地址的文章写的非常详细。可以深入理解关于原型 Null 、原型链、This 的相关原理。

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

实现起来也是比较简单的。顺藤摸瓜就行了。

this 是什么?

理解 this 就需要理解 JavaScript 的执行上下文和调用栈,可以看上面的文章进行详细的步进的了解。

了解了执行上下文后,就知道了执行上下文包括了变量环境、词法环境、outer、this。

而这个 this 就是指向当下执行环境的一个指针。在浏览器环境下指的是 window。

1
2
3
4
5
6
7
8
9
10
11
12
执行下面代码:

function foo(){
console.log(this) // window
}
foo()
// 可以看到输出了window,说明在默认情况下调用一个函数,其执行上下文中的 this 也是指向 window 对象的。
// 可以认为 JavaScript 引擎在执行foo()时,将其转化为了:
function foo(){
console.log(this) // window
}
window.foo.call(window)

关于这一段我理解的是由于 foo() 函数是一个函数而不是一个对象,this 指向这个函数没有什么意义,于是默认情况下会将它指向上一层的执行上下文对象window window 在浏览器中表示浏览器的窗口。

1
2
3
4
5
6
7
8
9
10
function foo(){
console.log(this) // window
}
foo()
//Window {parent: Window, opener: Window, top: Window, length: 0, frames: Window, …}
a = new foo()
//foo {}
// __proto__:
// constructor: ƒ foo()
// __proto__: Object

可见,当我们把它实例化的时候,会通过 new 中的构造器,将 foo()链接到 a 的原型上。这个时候的 this 指的就是 foo() 了,当然这个 foo() 并不是说 foo()函数,而是指将foo()实例化的 a 对象。如果再实例化一个 b ,两者并不会相等。

或许我可以理解为 this 指代的永远是一个对象,而非一个函数,如果在函数中输出一个 this 而这个函数并非是某个对象的方法,那么 this 会重定向到 window 对象

关于执行上下文还有配套的 callapplybind 三个方法。详情参考https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/apply#

主要用处就是设置 this ,在调用一个存在的函数时,你可以为其指定一个 this 对象。 this 指当前对象,也就是正在调用这个函数的对象。 使用 apply, 你可以只写一次这个方法然后在另一个对象中继承它,而不用在新对象中重复写该方法。

1
2
3
4
5
function foo(){
console.log(this) // window
}
foo()
复制代码

可以看到输出了window,说明在默认情况下调用一个函数,其执行上下文中的 this 也是指向 window 对象的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function f(fn, x) {
console.log('into')
if (x < 1) {
f(g, 1);
} else {
fn();
}
function g() {
console.log('x' + x);
}
}

function h() {
}

f(h, 0)
// into
// into
// x0

作者:小黎也
链接:https://juejin.im/post/5e523e726fb9a07c9a195a95
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

这个面试题的逻辑看上去很简单,但是却有效的解释了执行上下文是如何影响程序的,同时也引出了作用域链的一个概念,上下文上下文,则说明是有上文也是有下文的。比如全局执行上下文就肯定是其他执行上下文的上文,那么参数在不同的执行上下文之间调用,每个参数不同的作用域,就形成了作用域链,和原型链是异曲同工的。如果一个函数在执行中找不到某个参数,就会顺着作用域链往上找。

g 函数中的 x 变量是引用父级的,而 f 函数执行了两次,x 变量依次为 0 1,在 f(h,0) 这个函数执行的时候,这个函数的作用域中的 x=0,这个时候 g 函数中引用的 x 就是当前执行上下文中的 x=0 这个变量,但这个函数还没被执行,接着到了 f(g, 1) 执行,这一层执行上下文中的 x=1 ,但注意两次 f 执行的作用域不是同一个对象,是作用域链上两个独立的对象,最后到了 fn() ,这个 fn 是一个参数,也就是在 f(h,0) 执行的时候 g 函数,那么 g 函数在这里被执行,g 打印出来的 x 就是 0 。

如果最后一段理解起来困难的话可以这样想,最后执行 f(g,1)的时候,执行的 fn() 并不是当下的 g(),而是传入的 g(),而这个传入的 g() 是来自 f(h,0) 这个环境下的。所以 x=0.

或许可以对这个题进行一个改变,如果 g() 函数的定义在是 f() 函数开头会怎么样?结果还是一样的,因为不管 g() 函数的位置如何变,始终是运行的 f(h,0) 环境下的 g()。

关于变量提升

https://developer.mozilla.org/zh-CN/docs/Glossary/Hoisting

变量提升(Hoisting)被认为是, Javascript中执行上下文 (特别是创建和执行阶段)工作方式的一种认识。

JavaScript 仅提升声明,而不提升初始化

变量提升可能是个好东西,因为它能让你的程序跑起来。但也可能是个坏东西,因为会养成到处声明的坏习惯。

1
2
3
4
5
6
7
8
console.log(num); // Returns undefined 
var num;
num = 6;
// If you declare the variable after it is used, but initialize it beforehand, it will return the value:

num = 6;
console.log(num); // returns 6
var num;

正确的编写原则是先声明再初始化,最后才是运用变量。

在 ES6 中的 let 和const不存在变量提升

var: 解析器在对 js 解析时,会将脚本扫描一遍,将变量的声明提前到代码块的顶部,赋值还是在原先的位置,若在赋值前调用,就会出现暂时性死区,值为 undefined

let const:不存在在变量提升,且作用域是存在于块级作用域下,所以这两个的出现解决了变量提升的问题,同时引用块级作用域。 注:变量提升的原因是为了解决函数互相调用的问题。

关于 Promise 和 Async/Await

在 MDN 中关于 Promise 是这样介绍的 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises

Promise 是一个对象,它代表了一个异步操作的最终完成或者失败。本质上,Promise 是一个被某些函数传出的对象,我们附加回调函数(callback)使用它,而不是将回调函数传入那些函数内部。

由于 JavaScript 是单线程的,所以我们需要考虑到网络的影响,如果在一个图片还没加载好的时候就去使用这个资源,是会造成图片缺失的,所以提出了异步,所谓异步就是使有关联性的程序按照顺序运行,防止资源加载未完成等问题。

我对于 Promise 的理解是,首先它的提出是为了解决回调函数过于复杂和冗余的问题。

1
2
3
4
5
6
7
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('Got the final result: ' + finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);

回调函数本身是把一个函数作为另一个函数的参数,然后在函数中调用这个函数达到异步的效果。

1
2
3
4
5
6
7
8
9
10
doSomething().then(function(result) {
return doSomethingElse(result);
})
.then(function(newResult) {
return doThirdThing(newResult);
})
.then(function(finalResult) {
console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);

这个示例通过返回 Promise 对象,实现了 Promise 对象的 then 链式写法,then里的参数是可选的,catch(failureCallback)then(null, failureCallback) 的缩略形式。如下所示,我们也可以用箭头函数来表示:

1
2
3
4
5
6
7
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {
console.log(`Got the final result: ${finalResult}`);
})
.catch(failureCallback);

上面的示例来自 MDN 的 Promise 文档

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises

需要注意的是,这个示例并不能在 console 中跑起来,因为没有具体的定义 doSomething(),在实际使用中,这几个函数都是需要有返回值的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const doSomething = () => {
return new Promise((resolve,reject)=>{
console.log("doSomething")
resolve("doSomething end")
})
}
const doSomethingElse = (argu) => {
return new Promise((resolve,reject)=>{
console.log(argu)
resolve("doSomethingElse end")
})
}
const doThirdThing = (argu) => {
return new Promise((resolve,reject)=>{
console.log(argu)
resolve("doThirdThing end")
})
}
// doSomething()
// .then(result => doSomethingElse(result))
// .then(newResult => doThirdThing(newResult))
// .then(finalResult => {
// console.log(`Got the final result: ${finalResult}`);
// })
// .catch(()=>{console.log("fail")});

// doSomething
// doSomething end
// doSomethingElse end
// Got the final result: doThirdThing end
// Promise {<resolved>: undefined}

当一个函数返回 Promise 对象的时候,可以把它当做一个异步函数,后面的 .then 方法是 Promise 对象的方法之一,用来接收上一个 Promise 在 resolve 的时候的消息,作为自定义的变量如 result 参与 .then 方法中的程序。由于几个函数都是返回的 Promise 对象,形成了一条 Promise 链。这条链是按照顺序异步运行的。.catch 方法是用来获取在前面 Promise 链中的错误,进行合适的错误抛出和处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
new Promise((resolve, reject) => {
console.log('初始化');

resolve();
})
.then(() => {
throw new Error('有哪里不对了');

console.log('执行「这个」”');
})
.catch(() => {
console.log('执行「那个」');
})
.then(() => {
console.log('执行「这个」,无论前面发生了什么');
});
// 初始化
// 执行“那个”
// 执行“这个”,无论前面发生了什么

.catch 也是返回的 Promise 对象,可以继续连接 .then 方法,使得程序在接收到错误后可以继续正常运行。

1
2
3
4
5
6
7
8
9
10
async function foo() {
try {
let result = await doSomething();
let newResult = await doSomethingElse(result);
let finalResult = await doThirdThing(newResult);
console.log(`Got the final result: ${finalResult}`);
} catch(error) {
failureCallback(error);
}
}

使用 Async/Await 同样可以达成异步操作,并且代码看上去更加的简洁。Async/Await 其实是 Promise 的一个语法糖,其原理还是 Promise,

async/await的目的是简化使用多个 promise 时的同步行为,并对一组 Promises执行某些操作。正如Promises类似于结构化回调,async/await更像结合了generators和 promises。

关于 Async/Await 的错误处理机制可以参考 https://juejin.im/post/5e535624518825496e784ccb

继续深入一下 Promise

Promise.all(iterable)方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise 的结果。

1
2
3
4
5
6
7
8
9
10
11
12
> var p1 = Promise.resolve(3);
> var p2 = 1337;
> var p3 = new Promise((resolve, reject) => {
> setTimeout(() => {
> resolve("foo");
> }, 100);
> });
>
> Promise.all([p1, p2, p3]).then(values => {
> console.log(values); // [3, 1337, "foo"]
> });
>

Promise.allSettled()方法返回一个在所有给定的promise已被决议或被拒绝后决议的promise,并带有一个对象数组,每个对象表示对应的promise结果。

1
2
3
4
5
6
7
8
9
10
11
> const promise1 = Promise.resolve(3);
> const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
> const promises = [promise1, promise2];
>
> Promise.allSettled(promises).
> then((results) => results.forEach((result) => console.log(result.status)));
>
> // expected output:
> // "fulfilled"
> // "rejected"
>

Promise.any() 接收一个Promise可迭代对象,只要其中的一个 promise 完成,就返回那个已经有完成值的 promise 。如果可迭代对象中没有一个 promise 完成(即所有的 promises 都失败/拒绝),就返回一个拒绝的 promise,返回值还有待商榷:无非是拒绝原因数组或AggregateError类型的实例,它是 Error 的一个子类,用于把单一的错误集合在一起。本质上,这个方法和Promise.all()是相反的。

Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
> const promise1 = new Promise(function(resolve, reject) {
> setTimeout(resolve, 500, 'one');
> });
>
> const promise2 = new Promise(function(resolve, reject) {
> setTimeout(resolve, 100, 'two');
> });
>
> Promise.race([promise1, promise2]).then(function(value) {
> console.log(value);
> // Both resolve, but promise2 is faster
> });
> // expected output: "two"
>

理解 Async/Await 除了理解 Promise 之外,我们知道它是 Promise 的一个语法糖。结合了 Promise 和 constructor。

AsyncFunction 就是 Async/Await 的构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function resolveAfter2Seconds(x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x);
}, 2000);
});
}

var AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
var a = new AsyncFunction('a',
'b',
'return await resolveAfter2Seconds(a) + await resolveAfter2Seconds(b);');
a(10, 20).then(v => {
console.log(v); // 4 秒后打印 30
});

这个就是使用 AsyncFunction 实现的一个异步函数。不过看上去好像跟普通的 Promise 没什么不同,我记得看过一个实现 Async 构造器的文章,找不到了。回头再写这里吧。

关于包装对象

在 JavaScript 中变量一般分为基本数据类型和对象类型。基本数据类型包括 Number、String、Boolean,在我们定义一个数字或者字符串的时候,它的类型 typeof() 出来就是普通的数据类型,但是我们可以使用 .length .toString() 等对象的方法,原因是 JavaScript 在执行的时候会使用数据类型对象包装原始数据,使其成为一个对象然后使用相关方法和属性。但是在输出后就会将其销毁,并不会改变原生数据的类型。

关于 Null 和 Undefined

1
2
3
4
undefined == null
true
undefined === null
false

两个数据类型都是表示为空,但是具体的含义不太一样,Null 表示数据有值,值的内容为空,这个空并不是 0 或者一个空字符串,所以可以使用 Null 来初始化变量,Undefined 表示数据没有值,当变量仅被声明而没有被初始化的时候即是 Undefined。

经典一点的问题比如一个空数组,如何判定为空,由于 JavaScript 的对象类型,数组是对象的一种,他的原型是 Array 原型对象。

1
2
3
4
5
6
7
a=[]
typeof(a)
"object"
a===null
false
a==null
false

需要使用原型对象的 .length 属性来判断数组中是否有值。

1
2
3
4
5
6
7
8
a=[1,2,3]
(3) [1, 2, 3]
a.length
3
a.length = 4
4
a
(4) [1, 2, 3, empty]

关于 JavaScript 执行机制和事件循环机制

JavaScript 引擎是 JavaScript 的解释器,类似于 Python,他们都是解释性语言。JavaScript 在最初被开发出来时,是为了用于浏览器的,所以注定了 JavaScript 是一个单线程的语言。那么当 JavaScript 需要处理两个事件的时候,如果一个事件是一个需要等待的事件,那么他会挂起等待的任务,先运行后面的任务。

img

这张图说明了,在主线程运行的时候,会生成堆栈,形成一个任务队列,然后主线程会循环访问这个任务队列,运行队列中的事件。

在堆中保存的是对象的数据,在栈中保存的是基本数据类型和函数执行时的内存信息。

栈中的代码会调用各种外部API,它们在任务队列中加入各种事件(onClick,onLoad,onDone),只要栈中的代码执行完毕(js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空),主线程就回去读取任务队列,在按顺序执行这些事件对应的回调函数。

也就是说主线程从任务队列中读取事件,这个过程是循环不断的,所以这种运行机制又成为Event Loop(事件循环)。

作者:童欧巴
链接:https://juejin.im/post/5d2036106fb9a07eb15d76e9
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

通常来说,大部分的代码按照从上到下的顺序执行,并不会有什么问题,即是同步任务,但是当遇到例如加载图片这种操作的时候,可能在图片还没加载完成的情况下就使用了该图片,就会造成显示不全等问题,所以提出了异步 的思想。

同步任务就是在主线程上排队执行的任务,严格按照代码顺序执行(变量提升后),异步任务则不进入主线程,会在 event table 中注册函数,当达到执行条件后才会进入任务队列,然后在任务队列等待主线程执行。

宏任务和微任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
> setTimeout(function() {
> console.log('a')
> });
>
> new Promise(function(resolve) {
> console.log('b');
>
> for(var i =0; i <10000; i++) {
> i ==99 && resolve();
> }
> }).then(function() {
> console.log('c')
> });
>
> console.log('d');
>
> // b
> // d
> // c
> // a
> 复制代码
>

1.首先执行script下的宏任务,遇到setTimeout,将其放入宏任务的队列里。

2.遇到Promisenew Promise直接执行,打印b。

3.遇到then方法,是微任务,将其放到微任务的队列里。

4.遇到console.log('d'),直接打印。

5.本轮宏任务执行完毕,查看微任务,发现then方法里的函数,打印c。

6.本轮event loop全部完成。

7.下一轮循环,先执行宏任务,发现宏任务队列中有一个setTimeout,打印a。

作者:童欧巴
链接:https://juejin.im/post/5d2036106fb9a07eb15d76e9
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

img

通过宏任务和微任务的划分,可以更加清晰的分辨出程序的运行顺序,

上面的文章里还有一个思考题。推荐看一看。同时也看看 https://juejin.im/post/5e5c7f6c518825491b11ce93 文章的理解,这篇文章更加详细,但是不太好懂。

JavaScript ES6 更新了什么?

详细的教程或者文档可以参考 阮一峰的教程Babel 的文档

在前端方面有相当多的规范,比如我们所使用的 HTML、CSS、JavaScript 都是有规范的,其中 HTML、CSS 的标准是由 W3C 设定的,还有 XML、XHTML 也是 W3C 制定的标准。JavaScript 的标准则是由 ECMAScript 所制定的,目前最新的规范是 ECMA-262 Edition 6,也被称为 ECMAScript 2015。这是一个大版本的更新,对比第五代的 ECMAScript 更新了很多东西。可以帮助开发者更加方便快捷的开发网页。

Babel 是一个 JavaScript 编译器。

Babel 作为 JavaScript 的编译器可以将 ES6 的代码转换成向后兼容的代码,防止出现浏览器兼容错误。

然后就是 JavaScript 的引擎,简单说就是浏览器的核心,目前使用较多的就是 Firefox 的 SpiderMonkeyChrome 的 V8 了,关于 JavaScript 引擎了解的不需要太多。还是关注 JavaScript 的语法标准和原理比较好。

let 命令

ES6 中新增了 letconst 两个命令,用来声明变量,取代 var,不同的是 letconst 都不会产生变量提升,也就是说他们所声明的变量只能在命令所在的代码块有效。

暂时性死区

1
2
3
4
5
6
var tmp = 123;

if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}

使用 var 声明的 tmp 变量可以看做是一个全局变量,但是由于在 if 循环语句内又声明了一个 tmp,即使 tmplet 声明之前赋值也是会直接报错的,如果在 let 声明之后赋值的话既是对 let 声明的 tmp 赋值。

ES6 明确规定,如果区块中存在letconst命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。

1
2
3
4
5
6
7
8
9
10
11
if (true) {
// TDZ开始
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError

let tmp; // TDZ结束
console.log(tmp); // undefined

tmp = 123;
console.log(tmp); // 123
}

由于暂时性死区的特性,在变量声明之前都是属于变量的死区,不管做什么操作都会报错。包括 typeof

let 也不支持重复声明,相比于 var 来说更加的严格。

新的作用域

详见 ES6-的块级作用域

作用域是用来确定变量的有效范围和调用顺序,在 ES5 中只有全局作用域和函数作用域,在 ES6 中通过 let 新增了块级作用域,尽可能的避免变量提升。

1
2
3
4
5
6
7
8
9
10
var tmp = new Date();

function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world';
}
}

f(); // undefined

在没有块级作用域的时候,上面代码的原意是,if代码块的外部使用外层的tmp变量,内部使用内层的tmp变量。但是,函数f执行后,输出结果为undefined,原因在于变量提升,导致内层的tmp变量覆盖了外层的tmp变量。

1
2
3
4
5
6
7
var s = 'hello';

for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}

console.log(i); // 5

还有就是在使用 for 循环的时候使用 var 声明的变量会泄露成为全局变量。

1
2
3
4
5
6
7
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}

在使用了 let 之后会产生块级作用域,使得变量仅在该区域内生效,

ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。

1
2
3
4
5
6
7
8
9
10
11
// 情况一
if (true) {
function f() {}
}

// 情况二
try {
function f() {}
} catch(e) {
// ...
}

上面两种函数声明,根据 ES5 的规定都是非法的。

但是,浏览器没有遵守这个规定,为了兼容以前的旧代码,还是支持在块级作用域之中声明函数,因此上面两种情况实际都能运行,不会报错。

ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。ES6 规定,块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。

函数表达式和函数声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 块级作用域内部的函数声明语句,建议不要使用
{
let a = 'secret';
function f() {
return a;
}
}

// 块级作用域内部,优先使用函数表达式
{
let a = 'secret';
let f = function () {
return a;
};
}

ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。

1
2
3
4
5
6
7
8
9
10
11
// 情况一
if (true) {
function f() {}
}

// 情况二
try {
function f() {}
} catch(e) {
// ...
}

但是由于浏览器为了兼容以前的代码,是会支持的,ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。ES6 规定,块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。

1
2
3
4
5
6
7
8
9
10
function f() { console.log('I am outside!'); }

(function () {
if (false) {
// 重复声明一次函数f
function f() { console.log('I am inside!'); }
}

f();
}());

在 ES5 中,这个函数声明会被提升到块级作用域头部,并不会受 if 影响,但是在 ES6 中,会因为浏览器的行为而报错。

  • 允许在块级作用域内声明函数。
  • 函数声明类似于var,即会提升到全局作用域或函数作用域的头部。
  • 同时,函数声明还会提升到所在的块级作用域的头部。
1
2
3
4
5
6
7
8
9
10
11
// 浏览器的 ES6 环境
function f() { console.log('I am outside!'); }
(function () {
var f = undefined;
if (false) {
function f() { console.log('I am inside!'); }
}

f();
}());
// Uncaught TypeError: f is not a function

所以在块级作用域内声明函数,最好是使用函数表达式而非函数声明。

const 命令

const 声明一个只读的常量。一旦声明,常量的值就不能改变。

1
2
3
4
5
const PI = 3.1415;
PI // 3.1415

PI = 3;
// TypeError: Assignment to constant variable.

const 命令需要在声明变量同时对变量进行初始化。只声明而不赋值的话也是会报错的。

constlet 一样,只会在声明的块级作用域内有效,存在暂时性死区,不会被变量提升。

const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

1
2
3
4
5
6
7
8
const foo = {};

// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123

// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only

如果需要一个彻底不能被修改的变量,不论是对象还是其他类型,可以使用 Object.freeze()

1
2
3
4
5
6
7
8
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, i) => {
if ( typeof obj[key] === 'object' ) {
constantize( obj[key] );
}
});
};

通过循环遍历对象的属性可以保证对象内的属性也得到冻结。

顶层对象和全局对象

顶层对象和全局对象 详情就不写了,主要是为了解决前面对于两个对象的区分不严格的问题,达到浏览器和 Node 环境中的统一,

变量解构赋值

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

以前,为变量赋值,只能直接指定值。

1
2
3
let a = 1;
let b = 2;
let c = 3;

ES6 允许写成下面这样。

1
let [a, b, c] = [1, 2, 3];

解构赋值的前提是他们的类型需要相同,所以在右边会使用 [] 来包裹值,如果等号左边比右边的值多的话,多的值会被赋值 undefined ,如果右边比左边多的话,多的值会被丢弃不用。

解构赋值允许指定默认值。

1
2
3
4
5
let [foo = true] = [];
foo // true

let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'

ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,只有当一个数组成员严格等于undefined,默认值才会生效。

然后就是对对象的解构赋值,也是同理的。只是写法上不太一样,需要指定目标对象属性值。

1
2
3
4
5
6
let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"

let { baz } = { foo: 'aaa', bar: 'bbb' };
baz // undefined

同样也是可以给对象指定默认值。

字符串相关

ES6 加强了对 Unicode 的支持,允许采用\uxxxx形式表示一个字符,其中xxxx表示字符的 Unicode 码点。

但是,这种表示法只限于码点在\u0000~\uFFFF之间的字符。超出这个范围的字符,必须用两个双字节的形式表示。

1
2
3
4
5
"\uD842\uDFB7"
// "𠮷"

"\u20BB7"
// " 7"

ES6 对这一点做出了改进,只要将码点放入大括号,就能正确解读该字符。

1
2
3
4
5
6
7
8
9
10
11
"\u{20BB7}"
// "𠮷"

"\u{41}\u{42}\u{43}"
// "ABC"

let hello = 123;
hell\u{6F} // 123

'\u{1F680}' === '\uD83D\uDE80'
// true

有了这种表示法之后,JavaScript 共有 6 种方法可以表示一个字符。

1
2
3
4
5
'\z' === 'z'  // true
'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true
'\u{7A}' === 'z' // true

ES6 为字符串添加了遍历器接口(详见《Iterator》一章),使得字符串可以被for...of循环遍历。

1
2
3
4
5
6
for (let codePoint of 'foo') {
console.log(codePoint)
}
// "f"
// "o"
// "o"

然后是关于模板字符串的改进,让字符串中的变量可以更加方便的输出。

1
2
3
4
5
6
7
8
9
10
11
$('#result').append(
'There are <b>' + basket.count + '</b> ' +
'items in your basket, ' +
'<em>' + basket.onSale +
'</em> are on sale!'
);
$('#result').append(`
There are <b>${basket.count}</b> items
in your basket, <em>${basket.onSale}</em>
are on sale!
`);

模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入 ${} 变量。

如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。

ES6 还为原生的 String 对象,提供了一个raw()方法。该方法返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,往往用于模板字符串的处理方法。

1
2
3
4
5
String.raw`Hi\n${2+3}!`
// 实际返回 "Hi\\n5!",显示的是转义后的结果 "Hi\n5!"

String.raw`Hi\u000A!`;
// 实际返回 "Hi\\u000A!",显示的是转义后的结果 "Hi\u000A!"

String.raw()本质上是一个正常的函数,只是专用于模板字符串的标签函数。如果写成正常函数的形式,它的第一个参数,应该是一个具有raw属性的对象,且raw属性的值应该是一个数组,对应模板字符串解析后的值。

1
2
3
// `foo${1 + 2}bar`
// 等同于
String.raw({ raw: ['foo', 'bar'] }, 1 + 2) // "foo3bar"

传统上,JavaScript 只有indexOf方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6 又提供了三种新方法。

  • includes():返回布尔值,表示是否找到了参数字符串。
  • startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
  • endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。

这三个方法都支持第二个参数,表示开始搜索的位置。

1
2
3
4
5
let s = 'Hello world!';

s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false

repeat方法返回一个新字符串,表示将原字符串重复n次。

1
2
3
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""

参数如果是小数,会被取整。

1
'na'.repeat(2.9) // "nana"

如果repeat的参数是负数或者Infinity,会报错。

1
2
3
4
'na'.repeat(Infinity)
// RangeError
'na'.repeat(-1)
// RangeError

但是,如果参数是 0 到-1 之间的小数,则等同于 0,这是因为会先进行取整运算。0 到-1 之间的小数,取整以后等于-0repeat视同为 0。

1
'na'.repeat(-0.9) // ""

参数NaN等同于 0。

1
'na'.repeat(NaN) // ""

如果repeat的参数是字符串,则会先转换成数字。

1
2
'na'.repeat('na') // ""
'na'.repeat('3') // "nanana"

正则表达式的改进

TypeScript 整理

关于浏览器需要知道些什么

经典例题之访问网站

当我在浏览器URL栏上输入了一串网址然后按下回车,之后会发生什么?

这是一个很简单的操作,几乎每个人都会,但是背后这项操作的原理是什么。

Node.js 整理

Vue 整理

Vue 是什么?

Vue 是一个开源的响应式前端框架,使用了 MVVM 框架,方便开发者关注数据图层。

MVVM 框架是什么?

Model-View-ViewModel 是 MVC 的一个改进版本。那么 MVC 又是什么?

Model-View-Controller,Model 是指数据模型,View 指视图,Controller 是控制器,这个框架使得视图和数据模型分开来,通过控制器来进行操作,在数据发生改变的时候通知控制器,然后控制器去更新视图。

https://www.runoob.com/design-pattern/mvc-pattern.html 菜鸟的这个示例实现了一个 MVC 架构的程序。

MVVM 就是将 MVC 中的 Controller 改进成 ViewModel,把 Model 和 View 关联起来的就是 ViewModel。ViewModel 负责把 Model 的数据同步到 View 显示出来,还负责把 View 的修改同步回 Model。

让MVVM框架去自动更新DOM的状态,从而把开发者从操作DOM的繁琐步骤中解脱出来!

https://www.liaoxuefeng.com/wiki/1022910821149312/1108898947791072

Vue 是如何实现响应式的?

Object.defineProperty()

一开始我觉得通过这个方法,就可以自定义 get() 和 set() 两个方法,这样当这个数据对象发生改变或者获取数据对象的时候,就可以先对数据对象进行处理,那么进行的是什么样的处理?

Virtual DOM

Vue源码系列一:Vue中的data是如何驱动视图渲染的? 中介绍了 Vue 的源码,分析了 Vue 的初始化顺序,主要就是为了生成虚拟 DOM 树。

浏览器不是已经有了 DOM 树了嘛?Vue 这个DOM 树有什么不同的嘛?

其实没什么不同,因为这个树就是为了给浏览器进行渲染的,在这个树的 官方文档 中,Vue 解释说使用这个虚拟 DOM 是

Vue 通过建立一个虚拟 DOM 来追踪自己要如何改变真实 DOM。

这就联系起来了。通过 Object.defineProperty() 方法可以劫持数据对象,通过虚拟 DOM 可以实现 MVVM 中的ViewModel 功能,更加详细的东西就是在 set() 和 get() 里面了。

Vue 实现了一个订阅-发布模式。在这个模式中,Vue 实现了依赖收集和派发更新。

VUE源码系列二:Vue响应式原理解析(附超详细源码注释和原理解析)

在上面的文章中,作者通过源码解析详细介绍了这个订阅-发布模式,可以将其分成三个模块,

  • Observer(劫持者)
    • 给对象的属性添加 getter 和 setter,用于依赖收集和派发更新
    • 被用在 defineReactive 中,使得对象的每个属性都添加上 getter 和 setter 成为响应式。
  • Dep(依赖收集)
    • 收集订阅者 subs : Array<Watcher>,用在 defineReactive 的重写 get 中为 dep.depend 方法,为数据提供收集功能。
    • 同时提供了发布更新的作用,在 defineReactive 的重写 set 中为 dep.notify() 方法。
  • Watcher(观察者)
    • 在 Dep 发布更新后使用 dep.notify() 方法,循环执行 Dep 中的 subs[i].update() 使得数据相关的订阅者更新视图。
    • watcher.update() 中,使用了 queuewatcher() 方法,通过一个队列检查是否含有目标观察者,使得视图不会重复刷新。在队列中异步执行 flushSchedulerQueue() 方法,在此方法中排序 queue 确保父子组件的顺序更新,然后执行 run() 函数,使用新旧 value 值执行 Watcher 的回调 cb.call()

响应系统源码版

clipboard.png

这两张图片可以用来参考,来自 从发布-订阅模式到Vue响应系统

总结一下

Vue 通过 Object.defineProperty() 实现对数据对象的劫持,通过 Observer 类定义 getter 和 setter,使得对象的所有属性都具备响应性,在 getter() 中通过依赖收集 Dep 的 dep.depends() 管理订阅者(当数据被 get 即视为被订阅),在 setter() 中通过 dep.notify() 函数向订阅了该数据对象的 Watcher 发送 update() ,Watcher 接收到更新信息后确定组件更新顺序然后映射到 Virtual DOM 中去,Virtual DOM 通过 patch 新旧节点,通过 diff 算法实现对新旧节点的对比,对同层的树节点进行比较而非对树进行逐层搜索遍历。然后生成真实 DOM。

这是 Vue2.x 的响应式原理,在 Vue3.0 中,官方重写了响应式的实现,改用 ProxyReflect 代替Object.defineProperty()。可以实现对更多数据的操作比如数组元素的劫持和更多的劫持方式。

Vue 的生命周期

Vue 实例生命周期

如果是 keep-alive 的话还有两个:activated 和 deactivated

keep-alive的生命周期

  1. activated: 页面第一次进入的时候,钩子触发的顺序是created->mounted->activated
  2. deactivated: 页面退出的时候会触发deactivated,当再次前进或者后退的时候只触发activated

那么 keep-alive 是什么?

keep-alive 是 Vue 提供的一个抽象组件,<keep-alive> 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和<transition> 相似, 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在组件的父组件链中。

这是 Vue 对象的生命周期,那么在 Vue 的父子组件中的生命周期是什么样的?

渲染过程:
父组件挂载完成一定是等子组件都挂载完成后,才算是父组件挂载完,所以父组件的mounted在子组件mouted之后
父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted

子组件更新过程:

  1. 影响到父组件: 父beforeUpdate -> 子beforeUpdate->子updated -> 父updted
  2. 不影响父组件: 子beforeUpdate -> 子updated

父组件更新过程:

  1. 影响到子组件: 父beforeUpdate -> 子beforeUpdate->子updated -> 父updted
  2. 不影响子组件: 父beforeUpdate -> 父updated

销毁过程:
父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed

所以不管是加载更新还是销毁都是父组件会等待子组件完后操作后才执行操作。

v-model 是什么?

v-model 是经常用来双向绑定数据的一个语法,实际是一个 v-bind 和 v-on 结合的一个语法糖,等同于

1
v-bind:value="msg" v-on:input="msg=$event.target.value"

v-bind 动态地绑定一个或多个特性,或一个组件 prop 到表达式。

v-on 绑定事件监听器。事件类型由参数指定。表达式可以是一个方法的名字或一个内联语句,如果没有修饰符也可以省略。

也就是说 v-model 通过绑定数据和绑定数据并监听数据的变化,形成双向绑定。

VUE源码系列七:v-model实现原理

这篇文章则是从源码角度去解释 v-model,并不是简单的转换为 v-bind 和 v-on。只是在最终结果上是一样的。

Vue 组件是什么?

简单说 Vue 组件是一个可以复用的 Vue 实例,通过将网页组件化,可以更加快捷的搭建更多的网页,所以 Vue 组件最主要的目的是复用,类似 Bootstrap 的组件库、Element-UI,他们其实都是组件库,通过自己设计的组件,形成组件库。

https://cn.vuejs.org/v2/guide/components.html 是 Vue 关于组件的文档,以及后续的深入组件内容。

1
Vue.component('my-component-name', { /* ... */ })

通常来说,我们可以使用这样的格式来注册一个组件,这种格式下注册的组件是可以全局使用的,但是很明显当你的页面比较多的时候,全局使用组件会造成一定程度的冗余,所以你可以通过变量赋值,然后通过 Vue 实例的 components 属性来添加当前实例的组件。

1
2
3
4
5
6
7
8
9
var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
new Vue({
el: '#app',
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})

在设计组件的时候,我们需要考虑这个组件需要什么,以及他最后会呈现什么样子。

1
2
3
4
5
Vue.component('blog-post', {
// 在 JavaScript 中是 camelCase 的
props: ['postTitle'],
template: '<h3>{{ postTitle }}</h3>'
})

通过 props 可以为组件添加属性,这个属性是可以由父组件传递给子组件使用的。可以理解为这是一种函数模式,通过传入参数,使得组件呈现不同的样子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})

除了 props 以外,我们还可以给组件添加自定义的事件属性,

注意这里在 template 中使用的是 v-bindv-on 而不是 v-model ,虽然他们所实现的目的是一样的,但是由于方便以及参数传输更加清楚,我们在使用这个组件的时候,通常使用 v-model

1
<base-checkbox v-model="lovingVue"></base-checkbox>

这样对应过来,lovingVue 属性绑定在了组件的 checked 属性上,并且在发生 change 事件时也会通过 v-on 方法返回到 lovingVue 属性上,实现子组件参数与父组件属性间的双重绑定。

更多的细节可以参考官方的详细文档,你就会发现组件其实和实例拥有几乎一模一样的属性和方法,包括computed watch 以及生命周期方法。

但是我们可以看到,在 template 的编写过程中,我们完全得不到语法提示,因为他是一个字符串属性。

所以更好的办法是我们把组件写成一个实例。通过局部引入来进行调用。也就是单文件组件。

在单文件组件中,组件编写更加的方便,同时数据上的传递思路也更加明确。

关于插槽

插槽是 Vue 组件中一个很重要的方法,简单来说,插槽的目的是提供另外一种参数传递的方式,

1
2
3
<navigation-link url="/profile">
Your Profile
</navigation-link>

当你使用了一个 navigation-link 的组件时,正常情况下,组件间的数据都会被忽略,因为对于组件来说,中间的数据没有任何意义,如果不能正确的传递参数,中间的数据很容易打乱组件的结构。

1
2
3
4
5
6
<a
v-bind:href="url"
class="nav-link"
>
<slot></slot>
</a>

但是当组件中设定了一个 slot 标签后,父组件在使用子组件时,标签内部的信息都会被转移到 slot 标签中,而不是被直接抛弃。所以最后会渲染成:

1
2
3
4
5
6
<a
v-bind:href="url"
class="nav-link"
>
Your Profile
</a>

插槽中不仅可以填写这种纯文本,使用 HTML 代码甚至是使用 --- title: 前端学习之路 date: 2020-01-17 17:38:36 tags: 学习前端 categories: 学习 description: 以后就开始学习前端了,等到年龄大了再转全栈什么的。感觉很多文章看了,但是还是不知道自己在学什么。看文章看文档看得我头都大。在想暂时先不看 Vue 相关了,再看看 JS 和 CSS,CSS实在是找不到什么合适的教程,但是 CSS 本身又是一个很复杂的东西,各个属性之间是相互关联的。 --- # 前端资源 [gulp](https://www.gulpjs.com.cn/) 是一个 js 自动化工具。 github 上的一些资料 - [Awsome-Front-End-learning-resource](https://github.com/helloqingfeng/Awsome-Front-End-learning-resource) [谷歌字体](https://www.font.im/) [Open Graph protocol](http://ogp.me/) [MDN 学习 Web 开发](https://developer.mozilla.org/zh-CN/docs/Learn) [掘金](https://juejin.im/) 是真的挺不错的一个社区。好多干货文章。 ## 相关文章 [^]: 收藏的文章都放这里好了。 [一文完全吃透 JavaScript 继承(面试必备良药)](https://juejin.im/post/5e5339b46fb9a07cb83e20d4) > 这篇文章详细讲解了原型相关的知识,包括原型链、ES5 ES6中的继承实现以及原理。帮助理解 JavaScript 面向对象方向的继承原理和构造器相关概念。 [[译] 在 async/await 中更好的处理错误](https://juejin.im/post/5e535624518825496e784ccb) > 这篇文章详细介绍了在 JavaScript 中的异步问题,介绍了回调地狱、Promise、async/await,以及他们的错误处理机制。 [中高级前端面试题(万字长文)](https://juejin.im/post/5e4c0b856fb9a07ccb7e8eca) > 面试题以及知识框架整理。 [理解Javascript的正则表达式](https://juejin.im/post/5e53a6d3f265da571671080f) > 正则表达式是在应用中相当重要的一部分,需要去理解记忆并多加练习。 [轻松理解JS中的面向对象,顺便搞懂prototype和__proto__](https://juejin.im/post/5e50e5b16fb9a07c9a1959af) > 理解 JavaScript 中的原型、原型链、构造器等相关概念。 [2020年你不能不知道的webpack基本配置](https://juejin.im/post/5e532b116fb9a07ce152c31a) > 了解 webpack 工具,官方文档 https://www.webpackjs.com/concepts/ [前端下载文件的5种方法的对比](https://juejin.im/post/5e50fa23518825494b3cccd7) > 关于前端的一些典型下载文件实现案例。 [2020年大厂面试指南 - Vue篇](https://juejin.im/post/5e4d24cce51d4526f76eb2ba) > 看大厂面试题对于理解相关知识也是很有用的。重要的是不要死记硬背,因为很多答案并不是从原理讲起,或者讲了看不懂,需要结合官方文档和实际上手去理解。 - [Vue源码系列一:Vue中的data是如何驱动视图渲染的?](https://juejin.im/post/5e06b4666fb9a0164f2956c0) - [VUE源码系列二:Vue响应式原理解析(附超详细源码注释和原理解析)](https://juejin.im/post/5e0dd467e51d45410f1232f5) - [VUE源码系列三:nextTick原理解析(附源码解读)](https://juejin.im/post/5e1ae62b51882526b831f1cb) - [VUE源码系列四:计算属性和监听属性,到底该用谁?](https://juejin.im/post/5e21619ce51d4552455a8896) - [VUE源码系列五:组件是怎样生成的?(附详细源码解析)](https://juejin.im/post/5e2804e1e51d453c9e155f08) - [VUE源码系列六:编译原理](https://juejin.im/post/5e2cfd695188252c5232ae3b) - [VUE源码系列七:v-model实现原理](https://juejin.im/post/5e3647816fb9a030133074b0) > 非常感谢这位作者,关于 Vue 的实现原理可以参考上面的连载专栏。不需要去深入算法,但是需要去了解一些基本的原理。同时自己可以尝试做一个类似的例如双重绑定、响应式等等。 > > 这个源码系列好多都没看懂, 只能看个大概。感觉文档越看越浮躁了。 - [(1.8w字)负重前行,前端工程师如何系统练习数据结构和算法?【上】](https://juejin.im/post/5e2f88156fb9a02fdd38a184) > 作为计算机相关行业人员对于数据结构和算法是必须要掌握一些的。 [剖析一些经典的CSS布局问题,为前端开发+面试保驾护航](https://juejin.im/post/5da282015188257d2a1c9e1d) > 跟着某些作者看可以找到一些没有推荐过的文章。有的还没看过,先留着。 [前端面试常考的手写代码不是背出来的!](https://juejin.im/post/5e57048b6fb9a07cc845a9ef) > 这篇文章是对于代码规范和一些功能函数的实例研究,很基础但也很有用。 [深入理解JS的原型和原型链](https://juejin.im/post/5e54d9e86fb9a07c944c932a) > 深入理解原型和原型链之间的关系,然后引申出执行上下文和 this 的相关概念。 [vue 248个知识点(面试题)为你保驾护航](https://juejin.im/post/5d153267e51d4510624f9809) > 了解 Vue 的相关知识点和原理。 [🔥(已更新3.1w字)《大前端吊打面试官系列》 之 ES6 精华篇(2020年)](https://juejin.im/post/5e4943d0f265da57537eaba9) > 深入理解 ES6 。 - [4W字长文带你深度解锁Webpack系列(上)](https://juejin.im/post/5e5c65fc6fb9a07cd00d8838) > 在前端开发环境下,对于 Webpack、gulp 等工具的理解使用对于提升开发效率、专注前端页面是有好处的。 [面试题:说说事件循环机制(满分答案来了)](https://juejin.im/post/5e5c7f6c518825491b11ce93) > 关于事件循环机制。 [前端工程师的自我修养-关于 Babel 那些事儿](https://juejin.im/post/5e5b488af265da574112089f) > Babel 是可以对代码进行向后兼容性编译的编译器,使得代码在不同环境下依然可以稳定运行。 [你再不知道怎么解决跨域,我就真的生气了](https://juejin.im/post/5e578a5f518825494707e4bb) > 关于跨域相关的知识点。跨域指的是由于浏览器出于安全考虑所设置的同源策略,如果协议、域名、端口有一项不同就会产生跨域问题,而跨域问题所带来的是安全问题,解决跨域不仅仅是让前端可以成功访问到跨域资源,更重要的是理解背后的安全隐患。 [记好这 24 个 ES6 方法,用来解决实际开发的 JS 问题](https://juejin.im/post/5e5ef2f9f265da57685dc9c1) > ES6 相关的对于实际问题的解决办法。 [深入vue响应式原理(包含vue3.0)](https://juejin.im/post/5e5b43b1f265da5716710e4c) > 看这个是想了解关于 Vue 2.0 和 3.0 之间的变化。 [2020 前端面试 | 第一波面试题总结](https://juejin.im/post/5e3d898cf265da5732551a56) > 实际面试题。 [🔥 动画:《大前端吊打面试官系列》 之原生 JavaScript 精华篇](https://juejin.im/post/5e34d19de51d4558864b1d1f) > 关于 JavaScript 相关的基础知识理解。 [前端也能学算法:由浅入深讲解贪心算法](https://juejin.im/post/5e575e02f265da573b0dad5f) > 关于贪心算法的讲解。 [「 如何优雅的使用VUE? 」不可不知的VUE实战技巧](https://juejin.im/post/5e475829f265da57444ab10f) > Vue 的实战技巧。 [2020年了,再不会webpack敲得代码就不香了(近万字实战)](https://juejin.im/post/5de87444518825124c50cd36) > webpack 相关知识。 [【建议星星】要就来45道Promise面试题一次爽到底(1.1w字用心整理)](https://juejin.im/post/5e58c618e51d4526ed66b5cf) > 关于 Promise 的各种面试题型。加深对于异步的理解和处理。 [从手写Promise到async/await(接近6千字,建议看一下)](https://juejin.im/post/5e5f52fce51d4526ea7efdec) > 关于异步和生成器、构造器的原理讲解和一些组合方法。 [【建议👍】再来40道this面试题酸爽继续(1.2w字用手整理)](https://juejin.im/post/5e6358256fb9a07cd80f2e70) > 关于 this 的相关题型。 [vue 组件通信看这篇就够了(12种通信方式)](https://mp.weixin.qq.com/s?__biz=Mzg2NTA4NTIwNA==&mid=2247484468&idx=1&sn=3c309945992fe4f0c6276d91d7cb67b8&chksm=ce5e364ff929bf59ae686e3fa2412632348d6e190054e0b83bed11b5fd851ebbe8d326b5caf0&token=1424393752&lang=zh_CN#rd) > 详细介绍了不同的父子组件间的通信方式。 [学习 BFC (Block Formatting Context)](https://juejin.im/post/59b73d5bf265da064618731d) > 了解在 CSS 中的 BFC 的相关概念。 [【第1250期】彻底理解浏览器的缓存机制](https://mp.weixin.qq.com/s/d2zeGhUptGUGJpB5xHQbOA) > 了解浏览器的缓存机制 [【译】Async/await和Promise的不为人知的秘密](https://juejin.im/post/5e57feede51d4526dd1ea7e9) > 关于 Async 和 Promise 的性能上的区别。 [learnVue](https://github.com/answershuto/learnVue) > github 上的 Vue 学习文章 [Vue3响应式系统源码解析-单测篇](https://juejin.im/post/5d9c9a135188252e097569bd) > 关于 Vue3 的响应式实现。 [【面试篇】寒冬求职季之你必须要懂的原生JS(上)](https://juejin.im/post/5cab0c45f265da2513734390) > 经典面试题和解析 [【面试篇】寒冬求职季之你必须要懂的原生JS(中)](https://juejin.im/post/5cbd1e33e51d45789161d053) > 经典面试题和解析 [【面试篇】寒冬求职之你必须要懂的Web安全](https://juejin.im/post/5cd6ad7a51882568d3670a8e) > 关于 Web 安全的相关面试题和解析 [「进击的前端工程师」浏览器中JavaScript的事件循环](https://juejin.im/post/5d2036106fb9a07eb15d76e9) > 深入了解 JavaScript 引擎的事件机制,后面的例题也很值得思考。 # 知识框架 - HTML - HTML 不同版本的差异 - HTML5 - CSS - CSS 不同版本的差异 - CSS3 - Sass - [JavaScript 廖雪峰](https://www.liaoxuefeng.com/wiki/1022910821149312) - [TypeScript](https://www.tslang.cn/index.html) - Node.js - jQuery(老掉牙了) - Ajax(也老掉牙了,了解XMR就行) - Webpack - Gulp - [Vue](https://cn.vuejs.org/) - [React](https://react.docschina.org/) - [Angular](https://angular.cn/) - ES5 - ES6 # HTML 整理 HTML 只是一个标记性的语言,本身没有什么学习难度,HTML5 是新一代的 HTML 标准,实现了一些新的元素和属性, 改进了本地存储并且添加了很多语义元素,使得代码或者页面更加语义化。语义化的好处在于对代码的理解更加简单,同时也为视力障碍人士改善了网页的可阅读性。 关于更多的细节参考 https://www.runoob.com/html/html-tutorial.html ## HTML DOM 关于 DOM 的理解可以参考 https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-tree-construction DOM 是文档对象模型的缩写,按照对对象的理解,DOM 就是浏览器生成的这个页面的对象,每一个 HTML 元素就是一个节点,通过 HTML 的嵌套关系生成子节点和父节点关系的树,元素的属性也是元素对应节点的子节点。 DOM 的树并不是普通的二叉树或者特殊的树 ![DOM 节点关系](https://www.w3school.com.cn/i/dom_navigate.gif) 关系其实还是挺复杂的。不过 DOM 提供了各种方法实现了对于节点或者元素的选择、修改。 获取目标元素节点是对于 JavaScript 脚本比较重要的一环,获取到节点后即可对节点所产生的事件进行脚本控制。 当然在 Vue 或者 React 等框架中有对应的方法可以更加方便的获取节点添加事件。 ### CSS DOM css 也是有一个 DOM 树的通过将两棵树合并生成一个渲染树供浏览器对页面进行渲染。 # CSS 整理 CSS 的作用是给 HTML 元素加上样式,同时又可以多个样式层叠,所以叫层叠样式表。 CSS 的内容也是比较复杂的。细节参考 https://www.runoob.com/css/css-tutorial.html 需要理解不同元素类型能做的事和不能做的事,例如行内元素的宽度和高度是不能设置的。根据内容自动。 不过对于网页 CSS 设计来说,例如 Bootstrap 或者 Element 等前端组件库已经设定好了很多实用的样式。可以在此基础上进行自定义。关于页面的布局也是有对应的解决方案,需要理解的还是元素的类型, 参考 https://www.runoob.com/cssref/pr-class-display.html CSS 相关的教程好少啊,但是感觉各种属性对应的布局却很麻烦。 ## CSS 字体相关属性 [w3school 页面](https://www.w3school.com.cn/cssref/pr_font_font.asp) | font-style | 规定字体样式。参阅:[font-style](https://www.w3school.com.cn/cssref/pr_font_font-style.asp) 中可能的值。 | | --------------------- | ------------------------------------------------------------ | | font-variant | 规定字体异体。参阅:[font-variant](https://www.w3school.com.cn/cssref/pr_font_font-variant.asp) 中可能的值。 | | font-weight | 规定字体粗细。参阅:[font-weight](https://www.w3school.com.cn/cssref/pr_font_weight.asp) 中可能的值。 | | font-size/line-height | 规定字体尺寸和行高。参阅:[font-size](https://www.w3school.com.cn/cssref/pr_font_font-size.asp) 和 [line-height](https://www.w3school.com.cn/cssref/pr_dim_line-height.asp) 中可能的值。 | | font-family | 规定字体系列。参阅:[font-family](https://www.w3school.com.cn/cssref/pr_font_font-family.asp) 中可能的值。 | | caption | 定义被标题控件(比如按钮、下拉列表等)使用的字体。 | | icon | 定义被图标标记使用的字体。 | | menu | 定义被下拉列表使用的字体。 | | message-box | 定义被对话框使用的字体。 | | small-caption | caption 字体的小型版本。 | | status-bar | 定义被窗口状态栏使用的字体。 | ## CSS 文本相关属性 | 属性 | 描述 | | :----------------------------------------------------------- | :---------------------------------------------------------- | | [color](https://www.w3school.com.cn/cssref/pr_text_color.asp) | 设置文本颜色 | | [direction](https://www.w3school.com.cn/cssref/pr_text_direction.asp) | 设置文本方向。 | | [line-height](https://www.w3school.com.cn/cssref/pr_dim_line-height.asp) | 设置行高。 | | [letter-spacing](https://www.w3school.com.cn/cssref/pr_text_letter-spacing.asp) | 设置字符间距。 | | [text-align](https://www.w3school.com.cn/cssref/pr_text_text-align.asp) | 对齐元素中的文本。 | | [text-decoration](https://www.w3school.com.cn/cssref/pr_text_text-decoration.asp) | 向文本添加修饰。 | | [text-indent](https://www.w3school.com.cn/cssref/pr_text_text-indent.asp) | 缩进元素中文本的首行。 | | text-shadow | 设置文本阴影。CSS2 包含该属性,但是 CSS2.1 没有保留该属性。 | | [text-transform](https://www.w3school.com.cn/cssref/pr_text_text-transform.asp) | 控制元素中的字母。 | | unicode-bidi | 设置文本方向。 | | [white-space](https://www.w3school.com.cn/cssref/pr_text_white-space.asp) | 设置元素中空白的处理方式。 | | [word-spacing](https://www.w3school.com.cn/cssref/pr_text_word-spacing.asp) | 设置字间距。 | ## 关于各个布局 https://zh.learnlayout.com/ 可以用来了解布局相关的知识。 布局其实就是把网页根据内容进行一个规划。 网页的内容永远都是不确定的,那么布局当然是要怎么好看怎么来。 了解布局首先需要了解 `display` 这个属性。这个属性决定了 HTML 元素在 CSS 层面上的性质。每个 tag 都有默认的 `display` 属性,比如常见的 `h1` `div` `p` `form ` 就是块级元素,而 `a` `b` `i` `span` `select` `img` `input` 都是行内元素。 通过不同的元素特性,页面元素也会产生不同的显示效果。比如在[弹性盒子](https://developer.mozilla.org/zh-CN/docs/Learn/CSS/CSS_layout/Flexbox)中有很多新的特性和 CSS 样式,可以更加方便快捷的设定垂直居中效果,而在块级元素中就很难做到。大致上来说网页的布局也就分为一列、二列、三列,通过样式的叠加形成不同的网页显示效果。至于说的什么圣飞布局、双飞翼布局感觉没啥意思,究其原理还是浮动、弹性盒子、网格、定位这类基础属性,只能说是别人设计出来的网页布局就是了。重要的还是内容的排列。通过 `display` 属性获得不同的文字、图片、内容块的排列效果。 ## 关于盒模型 盒模型是用来计算元素大小的一个标准模型,完整适用于块元素,部分适用于行内元素。盒模型还有一个替代模型IE盒模型,在IE盒模型中,所有的宽度和高度都是可见的,内容的宽度包括了Padding的宽度。 ![Diagram of the box model](https://media.prod.mdn.mozit.cloud/attachments/2019/03/19/16558/29c6fe062e42a327fb549a081bc56632/box-model.png) > 一个被定义成块级的(block)盒子会表现出以下行为: > > - 盒子会在内联的方向上扩展并占据父容器在该方向上的所有可用空间,在绝大数情况下意味着盒子会和父容器一样宽 > - 每个盒子都会换行 > - [`width`](https://developer.mozilla.org/zh-CN/docs/Web/CSS/width) 和 [`height`](https://developer.mozilla.org/zh-CN/docs/Web/CSS/height) 属性可以发挥作用 > - 内边距(padding), 外边距(margin) 和 边框(border) 会将其他元素从当前盒子周围“推开” > 如果一个盒子对外显示为 `inline`,那么他的行为如下: > > - 盒子不会产生换行。 > - [`width`](https://developer.mozilla.org/zh-CN/docs/Web/CSS/width) 和 [`height`](https://developer.mozilla.org/zh-CN/docs/Web/CSS/height) 属性将不起作用。 > - 内边距、外边距以及边框会被应用但是不会把其他处于 `inline` 状态的盒子推开。 > 一个元素使用 `display: inline-block`,实现我们需要的块级的部分效果: > > - 设置`width` 和`height` 属性会生效。 > - `padding`, `margin`, 以及`border` 会推开其他元素。 > > 但是,它不会跳转到新行,如果显式添加`width` 和`height` 属性,它只会变得比其内容更大。 https://developer.mozilla.org/zh-CN/docs/Learn/CSS/CSS_layout/Flexbox 详细介绍了 `display:flexible` 弹性盒子的 flex 模型 ![flex_terms.png](https://developer.mozilla.org/files/3739/flex_terms.png) > - **主轴(main axis)**是沿着 flex 元素放置的方向延伸的轴(比如页面上的横向的行、纵向的列)。该轴的开始和结束被称为 **main start** 和 **main end**。 > - **交叉轴(cross axis)**是垂直于 flex 元素放置方向的轴。该轴的开始和结束被称为 **cross start** 和 **cross end**。 > - 设置了 `display: flex` 的父元素(在本例中是 [`

`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/section))被称之为 **flex 容器(flex container)。** > - 在 flex 容器中表现为柔性的盒子的元素被称之为 **flex 项**(**flex item**)(本例中是 [`article`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/article) 元素。 ## 关于网页字体 # JavaScript 整理 关于语法就和其他语言是一样的,变量、运算、数据类型、函数、对象、数组、循环、作用域等等。 不一样的是,由于 JavaScript 是运行在浏览器上的。所以很多功能代码都是已经实现了的。不需要像其他语言一样需要自己写类。由于本身是面向对象的,JavaScript 的所有变量都是一个对象。对于 JavaScript 的学习需要去了解背后的原理。 由于浏览器的版本大家可能都不一样,所以在使用 JavaScript 特性的时候需要考虑版本问题。了解 ES5 和 ES6 的区别。可以使用 https://caniuse.com/ 来了解特性的支持情况。 细节参考 https://www.w3school.com.cn/js/index.asp ## 关于原型链 JavaScript 是解释型脚本语言,同时也是面向对象的语言,JavaScript 的对象是动态的属性“包”,会有一个`__proto__`的私有属性,该属性会指向构造函数的原型对象。 ```javascript // 让我们从一个自身拥有属性a和b的函数里创建一个对象o: 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 ``` 来自 MDN https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain 所以当一个对象通过 `new` 关键字从其他的对象“实例化”,会将先生成一个空对象,通过 [Object.setPrototypeOf()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf)原生函数将实例对象添加到原型链上。 ```javascript function polyNew(source, ...arg) { // 创建一个空的简单JavaScript对象(即{}) let newObj = {}; // 链接该对象(即设置该对象的构造函数)到另一个对象 Object.setPrototypeOf(newObj, source.prototype); // 将步骤1新创建的对象作为this的上下文 ; const resp = source.apply(newObj, arg); // 判断该函数返回值是否是对象 if (Object.prototype.toString.call(resp) === "[object Object]") { // 如果该函数没有返回对象,则返回this。 return resp } else { // 如果该函数返回对象,那用返回的这个对象作为返回值。 return newObj } } 作者:dellyoung 链接:https://juejin.im/post/5e54d9e86fb9a07c944c932a 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 ``` 上面这个地址的文章写的非常详细。可以深入理解关于原型 Null 、原型链、This 的相关原理。 **`instanceof`** **运算符**用于检测构造函数的 `prototype` 属性是否出现在某个实例对象的原型链上。 实现起来也是比较简单的。顺藤摸瓜就行了。 **this 是什么?** 理解 **this** 就需要理解 JavaScript 的执行上下文和调用栈,可以看上面的文章进行详细的步进的了解。 了解了执行上下文后,就知道了执行上下文包括了变量环境、词法环境、outer、this。 而这个 `this` 就是指向当下执行环境的一个指针。在浏览器环境下指的是 window。 ```javascript 执行下面代码: function foo(){ console.log(this) // window } foo() // 可以看到输出了window,说明在默认情况下调用一个函数,其执行上下文中的 this 也是指向 window 对象的。 // 可以认为 JavaScript 引擎在执行foo()时,将其转化为了: function foo(){ console.log(this) // window } window.foo.call(window) ``` 关于这一段我理解的是由于 `foo()` 函数是一个函数而不是一个对象,`this` 指向这个函数没有什么意义,于是默认情况下会将它指向上一层的执行上下文**对象**即 `window` window 在浏览器中表示浏览器的窗口。 ```javascript function foo(){ console.log(this) // window } foo() //Window {parent: Window, opener: Window, top: Window, length: 0, frames: Window, …} a = new foo() //foo {} // __proto__: // constructor: ƒ foo() // __proto__: Object ``` 可见,当我们把它实例化的时候,会通过 `new` 中的构造器,将 `foo()`链接到 `a` 的原型上。这个时候的 `this` 指的就是 `foo()` 了,当然这个 `foo()` 并不是说 `foo()`函数,而是指将`foo()`实例化的 `a` 对象。如果再实例化一个 `b ` ,两者并不会相等。 或许我可以理解为 **this 指代的永远是一个对象,而非一个函数,如果在函数中输出一个 this 而这个函数并非是某个对象的方法,那么 this 会重定向到 window 对象** 关于执行上下文还有配套的 `call`、`apply`、`bind` 三个方法。详情参考https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/apply# 主要用处就是设置 `this` ,在调用一个存在的函数时,你可以为其指定一个 `this` 对象。 `this` 指当前对象,也就是正在调用这个函数的对象。 使用 `apply`, 你可以只写一次这个方法然后在另一个对象中继承它,而不用在新对象中重复写该方法。 ```JavaScript function foo(){ console.log(this) // window } foo() 复制代码 ``` 可以看到输出了window,说明在**默认情况**下调用一个函数,其执行上下文中的 this 也是指向 window 对象的。 ```javascript function f(fn, x) { console.log('into') if (x < 1) { f(g, 1); } else { fn(); } function g() { console.log('x' + x); } } function h() { } f(h, 0) // into // into // x0 作者:小黎也 链接:https://juejin.im/post/5e523e726fb9a07c9a195a95 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 ``` 这个面试题的逻辑看上去很简单,但是却有效的解释了执行上下文是如何影响程序的,同时也引出了作用域链的一个概念,上下文上下文,则说明是有上文也是有下文的。比如全局执行上下文就肯定是其他执行上下文的上文,那么参数在不同的执行上下文之间调用,每个参数不同的作用域,就形成了作用域链,和原型链是异曲同工的。如果一个函数在执行中找不到某个参数,就会顺着作用域链往上找。 g 函数中的 x 变量是引用父级的,而 f 函数执行了两次,x 变量依次为 0 1,在 f(h,0) 这个函数执行的时候,这个函数的作用域中的 x=0,这个时候 g 函数中引用的 x 就是当前执行上下文中的 x=0 这个变量,但这个函数还没被执行,接着到了 f(g, 1) 执行,这一层执行上下文中的 x=1 ,但注意两次 f 执行的作用域不是同一个对象,是作用域链上两个独立的对象,最后到了 fn() ,这个 fn 是一个参数,也就是在 f(h,0) 执行的时候 g 函数,那么 g 函数在这里被执行,g 打印出来的 x 就是 0 。 如果最后一段理解起来困难的话可以这样想,最后执行 f(g,1)的时候,执行的 fn() 并不是当下的 g(),而是传入的 g(),而这个传入的 g() 是来自 f(h,0) 这个环境下的。所以 x=0. 或许可以对这个题进行一个改变,如果 g() 函数的定义在是 f() 函数开头会怎么样?结果还是一样的,因为不管 g() 函数的位置如何变,始终是运行的 f(h,0) 环境下的 g()。 ## 关于变量提升 https://developer.mozilla.org/zh-CN/docs/Glossary/Hoisting 变量提升(Hoisting)被认为是, Javascript中执行上下文 (特别是创建和执行阶段)工作方式的一种认识。 **JavaScript 仅提升声明,而不提升初始化**。 变量提升可能是个好东西,因为它能让你的程序跑起来。但也可能是个坏东西,因为会养成到处声明的坏习惯。 ```javascript console.log(num); // Returns undefined var num; num = 6; // If you declare the variable after it is used, but initialize it beforehand, it will return the value: num = 6; console.log(num); // returns 6 var num; ``` 正确的编写原则是先声明再初始化,最后才是运用变量。 在 ES6 中的 let 和const不存在变量提升 var: 解析器在对 js 解析时,会将脚本扫描一遍,将变量的声明提前到代码块的顶部,赋值还是在原先的位置,若在赋值前调用,就会出现暂时性死区,值为 `undefined` let const:不存在在变量提升,且作用域是存在于块级作用域下,所以这两个的出现解决了变量提升的问题,同时引用块级作用域。 注:变量提升的原因是为了解决函数互相调用的问题。 ## 关于 Promise 和 Async/Await 在 MDN 中关于 Promise 是这样介绍的 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises > [`Promise`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise) 是一个对象,它代表了一个异步操作的最终完成或者失败。本质上,Promise 是一个被某些函数传出的对象,我们附加回调函数(callback)使用它,而不是将回调函数传入那些函数内部。 由于 JavaScript 是单线程的,所以我们需要考虑到网络的影响,如果在一个图片还没加载好的时候就去使用这个资源,是会造成图片缺失的,所以提出了异步,所谓异步就是使有关联性的程序按照顺序运行,防止资源加载未完成等问题。 我对于 `Promise` 的理解是,首先它的提出是为了解决回调函数过于复杂和冗余的问题。 ```javascript doSomething(function(result) { doSomethingElse(result, function(newResult) { doThirdThing(newResult, function(finalResult) { console.log('Got the final result: ' + finalResult); }, failureCallback); }, failureCallback); }, failureCallback); ``` 回调函数本身是把一个函数作为另一个函数的参数,然后在函数中调用这个函数达到异步的效果。 ```javascript doSomething().then(function(result) { return doSomethingElse(result); }) .then(function(newResult) { return doThirdThing(newResult); }) .then(function(finalResult) { console.log('Got the final result: ' + finalResult); }) .catch(failureCallback); ``` 这个示例通过返回 Promise 对象,实现了 Promise 对象的 `then` 链式写法,then里的参数是可选的,`catch(failureCallback)` 是 `then(null, failureCallback)` 的缩略形式。如下所示,我们也可以用[箭头函数](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_functions)来表示: ```javascript doSomething() .then(result => doSomethingElse(result)) .then(newResult => doThirdThing(newResult)) .then(finalResult => { console.log(`Got the final result: ${finalResult}`); }) .catch(failureCallback); ``` 上面的示例来自 MDN 的 Promise 文档 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises 需要注意的是,这个示例并不能在 console 中跑起来,因为没有具体的定义 doSomething(),在实际使用中,这几个函数都是需要有返回值的 ```javascript const doSomething = () => { return new Promise((resolve,reject)=>{ console.log("doSomething") resolve("doSomething end") }) } const doSomethingElse = (argu) => { return new Promise((resolve,reject)=>{ console.log(argu) resolve("doSomethingElse end") }) } const doThirdThing = (argu) => { return new Promise((resolve,reject)=>{ console.log(argu) resolve("doThirdThing end") }) } // doSomething() // .then(result => doSomethingElse(result)) // .then(newResult => doThirdThing(newResult)) // .then(finalResult => { // console.log(`Got the final result: ${finalResult}`); // }) // .catch(()=>{console.log("fail")}); // doSomething // doSomething end // doSomethingElse end // Got the final result: doThirdThing end // Promise {: undefined} ``` 当一个函数返回 Promise 对象的时候,可以把它当做一个异步函数,后面的 `.then` 方法是 Promise 对象的方法之一,用来接收上一个 Promise 在 resolve 的时候的消息,作为自定义的变量如 result 参与 `.then` 方法中的程序。由于几个函数都是返回的 Promise 对象,形成了一条 Promise 链。这条链是按照顺序异步运行的。`.catch` 方法是用来获取在前面 Promise 链中的错误,进行合适的错误抛出和处理。 ```javascript new Promise((resolve, reject) => { console.log('初始化'); resolve(); }) .then(() => { throw new Error('有哪里不对了'); console.log('执行「这个」”'); }) .catch(() => { console.log('执行「那个」'); }) .then(() => { console.log('执行「这个」,无论前面发生了什么'); }); // 初始化 // 执行“那个” // 执行“这个”,无论前面发生了什么 ``` `.catch` 也是返回的 [Promise 对象](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch),可以继续连接 `.then` 方法,使得程序在接收到错误后可以继续正常运行。 ```javascript async function foo() { try { let result = await doSomething(); let newResult = await doSomethingElse(result); let finalResult = await doThirdThing(newResult); console.log(`Got the final result: ${finalResult}`); } catch(error) { failureCallback(error); } } ``` 使用 Async/Await 同样可以达成异步操作,并且代码看上去更加的简洁。Async/Await 其实是 Promise 的一个语法糖,其原理还是 Promise, > `async`/`await`的目的是简化使用多个 promise 时的同步行为,并对一组 `Promises`执行某些操作。正如`Promises`类似于结构化回调,`async`/`await`更像结合了generators和 promises。 关于 Async/Await 的错误处理机制可以参考 https://juejin.im/post/5e535624518825496e784ccb 继续深入一下 Promise - [`Promise.all()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) > **`Promise.all(iterable)`**方法返回一个 [`Promise`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise) 实例,此实例在 `iterable` 参数内所有的 `promise` 都“完成(resolved)”或参数中不包含 `promise` 时回调完成(resolve);如果参数中 `promise` 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 `promise` 的结果。 > > ```javascript > var p1 = Promise.resolve(3); > var p2 = 1337; > var p3 = new Promise((resolve, reject) => { > setTimeout(() => { > resolve("foo"); > }, 100); > }); > > Promise.all([p1, p2, p3]).then(values => { > console.log(values); // [3, 1337, "foo"] > }); > ``` - [`Promise.allSettled()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled) > 该**`Promise.allSettled()`**方法返回一个在所有给定的promise已被决议或被拒绝后决议的promise,并带有一个对象数组,每个对象表示对应的promise结果。 > > ```javascript > const promise1 = Promise.resolve(3); > const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo')); > const promises = [promise1, promise2]; > > Promise.allSettled(promises). > then((results) => results.forEach((result) => console.log(result.status))); > > // expected output: > // "fulfilled" > // "rejected" > ``` - [`Promise.any()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/any) > `Promise.any()` 接收一个[`Promise`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise)可迭代对象,只要其中的一个 `promise` 完成,就返回那个已经有完成值的 `promise` 。如果可迭代对象中没有一个 `promise` 完成(即所有的 `promises` 都失败/拒绝),就返回一个拒绝的 `promise`,返回值还有待商榷:无非是拒绝原因数组或`AggregateError`类型的实例,它是 [`Error`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Error) 的一个子类,用于把单一的错误集合在一起。本质上,这个方法和[`Promise.all()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/all)是相反的。 - [`Promise.prototype.catch()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch) - [`Promise.prototype.finally()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally) - [`Promise.prototype.then()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/then) - [`Promise.race()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/race) > **`Promise.race(iterable)`** 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。 > > ```javascript > const promise1 = new Promise(function(resolve, reject) { > setTimeout(resolve, 500, 'one'); > }); > > const promise2 = new Promise(function(resolve, reject) { > setTimeout(resolve, 100, 'two'); > }); > > Promise.race([promise1, promise2]).then(function(value) { > console.log(value); > // Both resolve, but promise2 is faster > }); > // expected output: "two" > ``` - [`Promise.reject()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/reject) - [`Promise.resolve()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve) Promise 对象的所有方法就是这些了。 理解 Async/Await 除了理解 Promise 之外,我们知道它是 Promise 的一个语法糖。结合了 Promise 和 constructor。 [AsyncFunction](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/AsyncFunction#AsyncFunction_原型对象) 就是 [`Async/Await`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/async_function) 的构造函数。 ```javascript function resolveAfter2Seconds(x) { return new Promise(resolve => { setTimeout(() => { resolve(x); }, 2000); }); } var AsyncFunction = Object.getPrototypeOf(async function(){}).constructor; var a = new AsyncFunction('a', 'b', 'return await resolveAfter2Seconds(a) + await resolveAfter2Seconds(b);'); a(10, 20).then(v => { console.log(v); // 4 秒后打印 30 }); ``` 这个就是使用 AsyncFunction 实现的一个异步函数。不过看上去好像跟普通的 Promise 没什么不同,我记得看过一个实现 Async 构造器的文章,~~找不到了。回头再写这里吧。~~ ## 关于包装对象 在 JavaScript 中变量一般分为基本数据类型和对象类型。基本数据类型包括 Number、String、Boolean,在我们定义一个数字或者字符串的时候,它的类型 `typeof()` 出来就是普通的数据类型,但是我们可以使用 `.length` `.toString()` 等对象的方法,原因是 JavaScript 在执行的时候会使用数据类型对象包装原始数据,使其成为一个对象然后使用相关方法和属性。但是在输出后就会将其销毁,并不会改变原生数据的类型。 ## 关于 Null 和 Undefined ```javascript undefined == null true undefined === null false ``` 两个数据类型都是表示为空,但是具体的含义不太一样,Null 表示数据有值,值的内容为空,这个空并不是 0 或者一个空字符串,所以可以使用 Null 来初始化变量,Undefined 表示数据没有值,当变量仅被声明而没有被初始化的时候即是 Undefined。 经典一点的问题比如一个空数组,如何判定为空,由于 JavaScript 的对象类型,数组是对象的一种,他的原型是 Array 原型对象。 ```javascript a=[] typeof(a) "object" a===null false a==null false ``` 需要使用原型对象的 `.length` 属性来判断数组中是否有值。 ```javascript a=[1,2,3] (3) [1, 2, 3] a.length 3 a.length = 4 4 a (4) [1, 2, 3, empty] ``` ## 关于 JavaScript 执行机制和事件循环机制 JavaScript 引擎是 JavaScript 的解释器,类似于 Python,他们都是解释性语言。JavaScript 在最初被开发出来时,是为了用于浏览器的,所以注定了 JavaScript 是一个单线程的语言。那么当 JavaScript 需要处理两个事件的时候,如果一个事件是一个需要等待的事件,那么他会挂起等待的任务,先运行后面的任务。 ![img](https://user-gold-cdn.xitu.io/2019/7/6/16bc675a7df7428e?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) 这张图说明了,在主线程运行的时候,会生成堆栈,形成一个任务队列,然后主线程会循环访问这个任务队列,运行队列中的事件。 在堆中保存的是对象的数据,在栈中保存的是基本数据类型和函数执行时的内存信息。 > 栈中的代码会调用各种外部API,它们在任务队列中加入各种事件(onClick,onLoad,onDone),只要栈中的代码执行完毕(js引擎存在`monitoring process`进程,会持续不断的检查主线程执行栈是否为空),主线程就回去读取任务队列,在按顺序执行这些事件对应的回调函数。 > > 也就是说主线程从任务队列中读取事件,这个过程是循环不断的,所以这种运行机制又成为`Event Loop`(事件循环)。 > > 作者:童欧巴 > 链接:https://juejin.im/post/5d2036106fb9a07eb15d76e9 > 来源:掘金 > 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 通常来说,大部分的代码按照从上到下的顺序执行,并不会有什么问题,即是同步任务,但是当遇到例如加载图片这种操作的时候,可能在图片还没加载完成的情况下就使用了该图片,就会造成显示不全等问题,所以提出了**异步** 的思想。 同步任务就是在主线程上排队执行的任务,严格按照代码顺序执行(变量提升后),异步任务则不进入主线程,会在 event table 中注册函数,当达到执行条件后才会进入任务队列,然后在任务队列等待主线程执行。 **宏任务和微任务** > ``` > setTimeout(function() { > console.log('a') > }); > > new Promise(function(resolve) { > console.log('b'); > > for(var i =0; i <10000; i++) {> i ==99 && resolve(); > } > }).then(function() { > console.log('c') > }); > > console.log('d'); > > // b > // d > // c > // a > 复制代码 > ``` > > 1.首先执行`script`下的宏任务,遇到`setTimeout`,将其放入宏任务的队列里。 > > 2.遇到`Promise`,`new Promise`直接执行,打印b。 > > 3.遇到`then`方法,是微任务,将其放到微任务的队列里。 > > 4.遇到`console.log('d')`,直接打印。 > > 5.本轮宏任务执行完毕,查看微任务,发现`then`方法里的函数,打印c。 > > 6.本轮`event loop`全部完成。 > > 7.下一轮循环,先执行宏任务,发现宏任务队列中有一个`setTimeout`,打印a。 > > 作者:童欧巴 > 链接:https://juejin.im/post/5d2036106fb9a07eb15d76e9 > 来源:掘金 > 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 ![img](https://user-gold-cdn.xitu.io/2019/7/6/16bc6bd331a2116a?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) 通过宏任务和微任务的划分,可以更加清晰的分辨出程序的运行顺序, 上面的文章里还有一个思考题。推荐看一看。同时也看看 https://juejin.im/post/5e5c7f6c518825491b11ce93 文章的理解,这篇文章更加详细,但是不太好懂。 ## JavaScript ES6 更新了什么? 详细的教程或者文档可以参考 [阮一峰的教程](https://es6.ruanyifeng.com/) 和 [Babel 的文档](https://www.babeljs.cn/docs/learn.html) 在前端方面有相当多的规范,比如我们所使用的 HTML、CSS、JavaScript 都是有规范的,其中 HTML、CSS 的标准是由 [W3C](https://www.w3.org/standards/) 设定的,还有 XML、XHTML 也是 W3C 制定的标准。JavaScript 的标准则是由 [ECMAScript](https://www.ecma-international.org/publications/standards/Standard.htm) 所制定的,目前最新的规范是 ECMA-262 Edition 6,也被称为 ECMAScript 2015。这是一个大版本的更新,对比第五代的 ECMAScript 更新了很多东西。可以帮助开发者更加方便快捷的开发网页。 > Babel 是一个 JavaScript 编译器。 Babel 作为 JavaScript 的编译器可以将 ES6 的代码转换成向后兼容的代码,防止出现浏览器兼容错误。 然后就是 JavaScript 的引擎,简单说就是浏览器的核心,目前使用较多的就是 [Firefox 的 SpiderMonkey](https://developer.mozilla.org/zh-CN/docs/Mozilla/Projects/SpiderMonkey) 和 [Chrome 的 V8](https://v8.dev/) 了,关于 JavaScript 引擎了解的不需要太多。还是关注 JavaScript 的语法标准和原理比较好。 ### let 命令 ES6 中新增了 `let` 和 `const` 两个命令,用来声明变量,取代 `var`,不同的是 `let` 和 `const` 都不会产生变量提升,也就是说他们所声明的变量只能在命令所在的代码块有效。 **暂时性死区** ```javascript var tmp = 123; if (true) { tmp = 'abc'; // ReferenceError let tmp; } ``` 使用 `var` 声明的 `tmp` 变量可以看做是一个全局变量,但是由于在 `if` 循环语句内又声明了一个 `tmp`,即使 `tmp` 在 `let` 声明之前赋值也是会直接报错的,如果在 `let` 声明之后赋值的话既是对 `let` 声明的 `tmp` 赋值。 > ES6 明确规定,如果区块中存在`let`和`const`命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。 ```javascript if (true) { // TDZ开始 tmp = 'abc'; // ReferenceError console.log(tmp); // ReferenceError let tmp; // TDZ结束 console.log(tmp); // undefined tmp = 123; console.log(tmp); // 123 } ``` 由于暂时性死区的特性,在变量声明之前都是属于变量的死区,不管做什么操作都会报错。包括 `typeof`。 `let` 也不支持重复声明,相比于 `var` 来说更加的严格。 #### 新的作用域 详见 [ES6-的块级作用域](https://es6.ruanyifeng.com/#docs/let#ES6-的块级作用域) 作用域是用来确定变量的有效范围和调用顺序,在 ES5 中只有全局作用域和函数作用域,在 ES6 中通过 `let` 新增了块级作用域,尽可能的避免变量提升。 ```javascript var tmp = new Date(); function f() { console.log(tmp); if (false) { var tmp = 'hello world'; } } f(); // undefined ``` 在没有块级作用域的时候,上面代码的原意是,`if`代码块的外部使用外层的`tmp`变量,内部使用内层的`tmp`变量。但是,函数`f`执行后,输出结果为`undefined`,原因在于变量提升,导致内层的`tmp`变量覆盖了外层的`tmp`变量。 ```javascript var s = 'hello'; for (var i = 0; i < s.length; i++) { console.log(s[i]); } console.log(i); // 5 ``` 还有就是在使用 `for` 循环的时候使用 `var` 声明的变量会泄露成为全局变量。 ```javascript function f1() { let n = 5; if (true) { let n = 10; } console.log(n); // 5 } ``` 在使用了 `let` 之后会产生块级作用域,使得变量仅在该区域内生效, ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。 ```javascript // 情况一 if (true) { function f() {} } // 情况二 try { function f() {} } catch(e) { // ... } ``` 上面两种函数声明,根据 ES5 的规定都是非法的。 但是,浏览器没有遵守这个规定,为了兼容以前的旧代码,还是支持在块级作用域之中声明函数,因此上面两种情况实际都能运行,不会报错。 ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。ES6 规定,块级作用域之中,函数声明语句的行为类似于`let`,在块级作用域之外不可引用。 #### 函数表达式和函数声明 ```JavaScript // 块级作用域内部的函数声明语句,建议不要使用 { let a = 'secret'; function f() { return a; } } // 块级作用域内部,优先使用函数表达式 { let a = 'secret'; let f = function () { return a; }; } ``` ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。 ```javascript // 情况一 if (true) { function f() {} } // 情况二 try { function f() {} } catch(e) { // ... } ``` 但是由于浏览器为了兼容以前的代码,是会支持的,ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。ES6 规定,块级作用域之中,函数声明语句的行为类似于`let`,在块级作用域之外不可引用。 ```javascript function f() { console.log('I am outside!'); } (function () { if (false) { // 重复声明一次函数f function f() { console.log('I am inside!'); } } f(); }()); ``` 在 ES5 中,这个函数声明会被提升到块级作用域头部,并不会受 `if` 影响,但是在 ES6 中,会因为浏览器的行为而报错。 - 允许在块级作用域内声明函数。 - 函数声明类似于`var`,即会提升到全局作用域或函数作用域的头部。 - 同时,函数声明还会提升到所在的块级作用域的头部。 ```javascript // 浏览器的 ES6 环境 function f() { console.log('I am outside!'); } (function () { var f = undefined; if (false) { function f() { console.log('I am inside!'); } } f(); }()); // Uncaught TypeError: f is not a function ``` 所以在块级作用域内声明函数,最好是使用函数表达式而非函数声明。 ### const 命令 `const` 声明一个只读的常量。一旦声明,常量的值就不能改变。 ```javascript const PI = 3.1415; PI // 3.1415 PI = 3; // TypeError: Assignment to constant variable. ``` `const` 命令需要在声明变量同时对变量进行初始化。只声明而不赋值的话也是会报错的。 `const` 和 `let` 一样,只会在声明的块级作用域内有效,存在暂时性死区,不会被变量提升。 > `const`实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,`const`只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。 ```javascript const foo = {}; // 为 foo 添加一个属性,可以成功 foo.prop = 123; foo.prop // 123 // 将 foo 指向另一个对象,就会报错 foo = {}; // TypeError: "foo" is read-only ``` 如果需要一个彻底不能被修改的变量,不论是对象还是其他类型,可以使用 [`Object.freeze()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) ```javascript var constantize = (obj) => { Object.freeze(obj); Object.keys(obj).forEach( (key, i) => { if ( typeof obj[key] === 'object' ) { constantize( obj[key] ); } }); }; ``` 通过循环遍历对象的属性可以保证对象内的属性也得到冻结。 #### 顶层对象和全局对象 [顶层对象和全局对象](https://es6.ruanyifeng.com/#docs/let#顶层对象的属性) 详情就不写了,主要是为了解决前面对于两个对象的区分不严格的问题,达到浏览器和 Node 环境中的统一, ### 变量解构赋值 > ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。 以前,为变量赋值,只能直接指定值。 ```javascript let a = 1; let b = 2; let c = 3; ``` ES6 允许写成下面这样。 ```javascript let [a, b, c] = [1, 2, 3]; ``` 解构赋值的前提是他们的类型需要相同,所以在右边会使用 `[]` 来包裹值,如果等号左边比右边的值多的话,多的值会被赋值 `undefined` ,如果右边比左边多的话,多的值会被丢弃不用。 解构赋值允许指定默认值。 ```javascript let [foo = true] = []; foo // true let [x, y = 'b'] = ['a']; // x='a', y='b' let [x, y = 'b'] = ['a', undefined]; // x='a', y='b' ``` > ES6 内部使用严格相等运算符(`===`),判断一个位置是否有值。所以,只有当一个数组成员严格等于`undefined`,默认值才会生效。 然后就是对对象的解构赋值,也是同理的。只是写法上不太一样,需要指定目标对象属性值。 ```javascript let { bar, foo } = { foo: 'aaa', bar: 'bbb' }; foo // "aaa" bar // "bbb" let { baz } = { foo: 'aaa', bar: 'bbb' }; baz // undefined ``` 同样也是可以给对象指定默认值。 ### 字符串相关 ES6 加强了对 Unicode 的支持,允许采用`\uxxxx`形式表示一个字符,其中`xxxx`表示字符的 Unicode 码点。 但是,这种表示法只限于码点在`\u0000`~`\uFFFF`之间的字符。超出这个范围的字符,必须用两个双字节的形式表示。 ```javascript "\uD842\uDFB7" // "𠮷" "\u20BB7" // " 7" ``` ES6 对这一点做出了改进,只要将码点放入大括号,就能正确解读该字符。 ```javascript "\u{20BB7}" // "𠮷" "\u{41}\u{42}\u{43}" // "ABC" let hello = 123; hell\u{6F} // 123 '\u{1F680}' === '\uD83D\uDE80' // true ``` 有了这种表示法之后,JavaScript 共有 6 种方法可以表示一个字符。 ```javascript '\z' === 'z' // true '\172' === 'z' // true '\x7A' === 'z' // true '\u007A' === 'z' // true '\u{7A}' === 'z' // true ``` ES6 为字符串添加了遍历器接口(详见《Iterator》一章),使得字符串可以被`for...of`循环遍历。 ```javascript for (let codePoint of 'foo') { console.log(codePoint) } // "f" // "o" // "o" ``` 然后是关于模板字符串的改进,让字符串中的变量可以更加方便的输出。 ```javascript $('#result').append( 'There are ' + basket.count + ' ' + 'items in your basket, ' + '' + basket.onSale + ' are on sale!' ); $('#result').append(` There are ${basket.count} items in your basket, ${basket.onSale} are on sale! `); ``` 模板字符串(template string)是增强版的字符串,用反引号(\`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入 `${}` 变量。 如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。 ES6 还为原生的 String 对象,提供了一个`raw()`方法。该方法返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,往往用于模板字符串的处理方法。 ```javascript String.raw`Hi\n${2+3}!` // 实际返回 "Hi\\n5!",显示的是转义后的结果 "Hi\n5!" String.raw`Hi\u000A!`; // 实际返回 "Hi\\u000A!",显示的是转义后的结果 "Hi\u000A!" ``` `String.raw()`本质上是一个正常的函数,只是专用于模板字符串的标签函数。如果写成正常函数的形式,它的第一个参数,应该是一个具有`raw`属性的对象,且`raw`属性的值应该是一个数组,对应模板字符串解析后的值。 ```javascript // `foo${1 + 2}bar` // 等同于 String.raw({ raw: ['foo', 'bar'] }, 1 + 2) // "foo3bar" ``` 传统上,JavaScript 只有`indexOf`方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6 又提供了三种新方法。 - **includes()**:返回布尔值,表示是否找到了参数字符串。 - **startsWith()**:返回布尔值,表示参数字符串是否在原字符串的头部。 - **endsWith()**:返回布尔值,表示参数字符串是否在原字符串的尾部。 这三个方法都支持第二个参数,表示开始搜索的位置。 ```javascript let s = 'Hello world!'; s.startsWith('world', 6) // true s.endsWith('Hello', 5) // true s.includes('Hello', 6) // false ``` `repeat`方法返回一个新字符串,表示将原字符串重复`n`次。 ```javascript 'x'.repeat(3) // "xxx" 'hello'.repeat(2) // "hellohello" 'na'.repeat(0) // "" ``` 参数如果是小数,会被取整。 ```javascript 'na'.repeat(2.9) // "nana" ``` 如果`repeat`的参数是负数或者`Infinity`,会报错。 ```javascript 'na'.repeat(Infinity) // RangeError 'na'.repeat(-1) // RangeError ``` 但是,如果参数是 0 到-1 之间的小数,则等同于 0,这是因为会先进行取整运算。0 到-1 之间的小数,取整以后等于`-0`,`repeat`视同为 0。 ```javascript 'na'.repeat(-0.9) // "" ``` 参数`NaN`等同于 0。 ```javascript 'na'.repeat(NaN) // "" ``` 如果`repeat`的参数是字符串,则会先转换成数字。 ```javascript 'na'.repeat('na') // "" 'na'.repeat('3') // "nanana" ``` ### 正则表达式的改进 ## TypeScript 整理 # 关于浏览器需要知道些什么 ## 经典例题之访问网站 当我在浏览器URL栏上输入了一串网址然后按下回车,之后会发生什么? 这是一个很简单的操作,几乎每个人都会,但是背后这项操作的原理是什么。 # Node.js 整理 # Vue 整理 ## Vue 是什么? Vue 是一个开源的响应式前端框架,使用了 MVVM 框架,方便开发者关注数据图层。 ### MVVM 框架是什么? Model-View-ViewModel 是 MVC 的一个改进版本。那么 MVC 又是什么? Model-View-Controller,Model 是指数据模型,View 指视图,Controller 是控制器,这个框架使得视图和数据模型分开来,通过控制器来进行操作,在数据发生改变的时候通知控制器,然后控制器去更新视图。 https://www.runoob.com/design-pattern/mvc-pattern.html 菜鸟的这个示例实现了一个 MVC 架构的程序。 MVVM 就是将 MVC 中的 Controller 改进成 ViewModel,把 Model 和 View 关联起来的就是 ViewModel。ViewModel 负责把 Model 的数据同步到 View 显示出来,还负责把 View 的修改同步回 Model。 > 让MVVM框架去自动更新DOM的状态,从而把开发者从操作DOM的繁琐步骤中解脱出来! > > https://www.liaoxuefeng.com/wiki/1022910821149312/1108898947791072 ## Vue 是如何实现响应式的? [`Object.defineProperty()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty) 一开始我觉得通过这个方法,就可以自定义 get() 和 set() 两个方法,这样当这个数据对象发生改变或者获取数据对象的时候,就可以先对数据对象进行处理,那么进行的是什么样的处理? Virtual DOM 在 [Vue源码系列一:Vue中的data是如何驱动视图渲染的?](https://juejin.im/post/5e06b4666fb9a0164f2956c0) 中介绍了 Vue 的源码,分析了 Vue 的初始化顺序,主要就是为了生成虚拟 DOM 树。 浏览器不是已经有了 DOM 树了嘛?Vue 这个DOM 树有什么不同的嘛? 其实没什么不同,因为这个树就是为了给浏览器进行渲染的,在这个树的 [官方文档](https://cn.vuejs.org/v2/guide/render-function.html#虚拟-DOM) 中,Vue 解释说使用这个虚拟 DOM 是 > Vue 通过建立一个**虚拟 DOM** 来追踪自己要如何改变真实 DOM。 这就联系起来了。通过 `Object.defineProperty()` 方法可以劫持数据对象,通过虚拟 DOM 可以实现 MVVM 中的ViewModel 功能,更加详细的东西就是在 set() 和 get() 里面了。 Vue 实现了一个订阅-发布模式。在这个模式中,Vue 实现了依赖收集和派发更新。 [VUE源码系列二:Vue响应式原理解析(附超详细源码注释和原理解析)](https://juejin.im/post/5e0dd467e51d45410f1232f5) 在上面的文章中,作者通过源码解析详细介绍了这个订阅-发布模式,可以将其分成三个模块, - Observer(劫持者) - 给对象的属性添加 getter 和 setter,用于依赖收集和派发更新 - 被用在 defineReactive 中,使得对象的每个属性都添加上 getter 和 setter 成为响应式。 - Dep(依赖收集) - 收集订阅者 `subs : Array`,用在 defineReactive 的重写 get 中为 `dep.depend ` 方法,为数据提供收集功能。 - 同时提供了发布更新的作用,在 defineReactive 的重写 set 中为 `dep.notify()` 方法。 - Watcher(观察者) - 在 Dep 发布更新后使用 `dep.notify()` 方法,循环执行 Dep 中的 `subs[i].update()` 使得数据相关的订阅者更新视图。 - 在 `watcher.update()` 中,使用了 `queuewatcher()` 方法,通过一个队列检查是否含有目标观察者,使得视图不会重复刷新。在队列中异步执行 `flushSchedulerQueue()` 方法,在此方法中排序 queue 确保父子组件的顺序更新,然后执行 `run()` 函数,使用新旧 value 值执行 Watcher 的回调 `cb.call()`。 ![响应系统源码版](https://image-static.segmentfault.com/162/469/1624694337-5a8fcee951762_articlex) ![clipboard.png](https://image-static.segmentfault.com/275/841/2758414791-5c99c0ef4e3c8_articlex) 这两张图片可以用来参考,来自 [从发布-订阅模式到Vue响应系统](https://segmentfault.com/a/1190000013338801) **总结一下** Vue 通过 `Object.defineProperty()` 实现对数据对象的劫持,通过 Observer 类定义 getter 和 setter,使得对象的所有属性都具备响应性,在 `getter()` 中通过依赖收集 Dep 的 `dep.depends()` 管理订阅者(当数据被 get 即视为被订阅),在 `setter()` 中通过 `dep.notify()` 函数向订阅了该数据对象的 Watcher 发送 `update()` ,Watcher 接收到更新信息后确定组件更新顺序然后映射到 Virtual DOM 中去,Virtual DOM 通过 `patch` 新旧节点,通过 `diff` 算法实现对新旧节点的对比,对同层的树节点进行比较而非对树进行逐层搜索遍历。然后生成真实 DOM。 这是 Vue2.x 的响应式原理,在 Vue3.0 中,官方重写了响应式的实现,改用 [`Proxy`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy) 和 [`Reflect`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect) 代替`Object.defineProperty()`。可以实现对更多数据的操作比如数组元素的劫持和更多的劫持方式。 ## Vue 的生命周期 ![Vue 实例生命周期](https://cn.vuejs.org/images/lifecycle.png) 如果是 keep-alive 的话还有两个:activated 和 deactivated keep-alive的生命周期 1. activated: 页面第一次进入的时候,钩子触发的顺序是created->mounted->activated 2. deactivated: 页面退出的时候会触发deactivated,当再次前进或者后退的时候只触发activated 那么 keep-alive 是什么? [`keep-alive`](https://cn.vuejs.org/v2/api/#keep-alive) 是 Vue 提供的一个抽象组件,`` 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和`` 相似, 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在组件的父组件链中。 这是 Vue 对象的生命周期,那么在 Vue 的父子组件中的生命周期是什么样的? **渲染过程:** 父组件挂载完成一定是等子组件都挂载完成后,才算是父组件挂载完,所以父组件的mounted在子组件mouted之后 父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted **子组件更新过程:** 1. 影响到父组件: 父beforeUpdate -> 子beforeUpdate->子updated -> 父updted 2. 不影响父组件: 子beforeUpdate -> 子updated **父组件更新过程:** 1. 影响到子组件: 父beforeUpdate -> 子beforeUpdate->子updated -> 父updted 2. 不影响子组件: 父beforeUpdate -> 父updated **销毁过程:** 父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed 所以不管是加载更新还是销毁都是父组件会等待子组件完后操作后才执行操作。 ## v-model 是什么? v-model 是经常用来双向绑定数据的一个语法,实际是一个 v-bind 和 v-on 结合的一个语法糖,等同于 ```javascript v-bind:value="msg" v-on:input="msg=$event.target.value" ``` [`v-bind`](https://cn.vuejs.org/v2/api/#v-bind) 动态地绑定一个或多个特性,或一个组件 prop 到表达式。 [`v-on`](https://cn.vuejs.org/v2/api/#v-on) 绑定事件监听器。事件类型由参数指定。表达式可以是一个方法的名字或一个内联语句,如果没有修饰符也可以省略。 也就是说 v-model 通过绑定数据和绑定数据并监听数据的变化,形成双向绑定。 [VUE源码系列七:v-model实现原理](https://juejin.im/post/5e3647816fb9a030133074b0) 这篇文章则是从源码角度去解释 v-model,并不是简单的转换为 v-bind 和 v-on。只是在最终结果上是一样的。 ## Vue 组件是什么? 简单说 Vue 组件是一个可以复用的 Vue 实例,通过将网页组件化,可以更加快捷的搭建更多的网页,所以 Vue 组件最主要的目的是复用,类似 Bootstrap 的组件库、Element-UI,他们其实都是组件库,通过自己设计的组件,形成组件库。 https://cn.vuejs.org/v2/guide/components.html 是 Vue 关于组件的文档,以及后续的深入组件内容。 ```JavaScript Vue.component('my-component-name', { /* ... */ }) ``` 通常来说,我们可以使用这样的格式来注册一个组件,这种格式下注册的组件是可以全局使用的,但是很明显当你的页面比较多的时候,全局使用组件会造成一定程度的冗余,所以你可以通过变量赋值,然后通过 Vue 实例的 `components` 属性来添加当前实例的组件。 ```javascript var ComponentA = { /* ... */ } var ComponentB = { /* ... */ } new Vue({ el: '#app', components: { 'component-a': ComponentA, 'component-b': ComponentB } }) ``` 在设计组件的时候,我们需要考虑这个组件需要什么,以及他最后会呈现什么样子。 ```javascript Vue.component('blog-post', { // 在 JavaScript 中是 camelCase 的 props: ['postTitle'], template: '

{{ postTitle }}

' }) ``` 通过 `props` 可以为组件添加属性,这个属性是可以由父组件传递给子组件使用的。可以理解为这是一种函数模式,通过传入参数,使得组件呈现不同的样子。 ```javascript Vue.component('base-checkbox', { model: { prop: 'checked', event: 'change' }, props: { checked: Boolean }, template: ` ` }) ``` 除了 `props` 以外,我们还可以给组件添加自定义的事件属性, 注意这里在 `template` 中使用的是 `v-bind` 和 `v-on` 而不是 `v-model` ,虽然他们所实现的目的是一样的,但是由于方便以及参数传输更加清楚,我们在使用这个组件的时候,通常使用 `v-model` ```html ``` 这样对应过来,`lovingVue` 属性绑定在了组件的 `checked` 属性上,并且在发生 change 事件时也会通过 `v-on` 方法返回到 `lovingVue` 属性上,实现子组件参数与父组件属性间的双重绑定。 更多的细节可以参考官方的详细文档,你就会发现组件其实和实例拥有几乎一模一样的属性和方法,包括`computed` `watch` 以及生命周期方法。 但是我们可以看到,在 `template` 的编写过程中,我们完全得不到语法提示,因为他是一个字符串属性。 所以更好的办法是我们把组件写成一个实例。通过局部引入来进行调用。也就是单文件组件。 在单文件组件中,组件编写更加的方便,同时数据上的传递思路也更加明确。 ### 关于插槽 插槽是 Vue 组件中一个很重要的方法,简单来说,插槽的目的是提供另外一种参数传递的方式, ```html Your Profile ``` 当你使用了一个 `navigation-link` 的组件时,正常情况下,组件间的数据都会被忽略,因为对于组件来说,中间的数据没有任何意义,如果不能正确的传递参数,中间的数据很容易打乱组件的结构。 ```html ``` 但是当组件中设定了一个 `slot` 标签后,父组件在使用子组件时,标签内部的信息都会被转移到 `slot` 标签中,而不是被直接抛弃。所以最后会渲染成: ```html Your Profile ``` 插槽中不仅可以填写这种纯文本,使用 HTML 代码甚至是使用 `{{raw}}` 格式包裹起来的数据属性也是可以的。 但是在使用 `{{raw}}` 格式传递数据时我们需要思考一个问题,我们是在使用子组件的时候,在子组件标签中间使用的 `{{raw}}` ,那么里面的属性我们自然是填入父组件的数据属性。 ```html {{ user.name }} ``` 我们可以用作用域来解释,即子组件的作用域,仅仅是在子组件中,甚至说在父组件中这个子组件的标签属性,也是数据父组件的作用域范围内,所以我们可以通过标签属性来传递父组件的数据到子组件中。只有数据传递过去后,才能算是子组件的数据属性,属于子组件的作用域范围。 更多详细的关于插槽的信息参考文档 https://cn.vuejs.org/v2/guide/components-slots.html 需要了解的包括有插槽的内容、作用域、默认内容、具名插槽、插槽prop、动态插槽名等相关知识。 ### 关于动态组件 ```html ``` 在经典 Vue 示例中,我们经常会看到 `` 这个标签,这个标签常常使用在 Vue 项目的入口 `App.vue` 中,用来对视图进行切换,在实际项目中,当父组件动态调用子组件的时候,通常来说,在切换子组件后子组件会被销毁。但是当我们添加 `` 标签后,被切换隐藏掉的子组件并不会被销毁,这样可以在特定场景下, 提升网页反应速度。 在测试的时候发现在 `` 外也是可以使用 `` 来实现同样的效果。 ## [Vue Router](https://router.vuejs.org/zh/) ## [Vuex](https://vuex.vuejs.org/zh/) ## [Vue SSR](https://ssr.vuejs.org/zh/) # React 整理 # Webpack 整理 # Gulp 整理
格式包裹起来的数据属性也是可以的。

但是在使用 --- title: 前端学习之路 date: 2020-01-17 17:38:36 tags: 学习前端 categories: 学习 description: 以后就开始学习前端了,等到年龄大了再转全栈什么的。感觉很多文章看了,但是还是不知道自己在学什么。看文章看文档看得我头都大。在想暂时先不看 Vue 相关了,再看看 JS 和 CSS,CSS实在是找不到什么合适的教程,但是 CSS 本身又是一个很复杂的东西,各个属性之间是相互关联的。 --- # 前端资源 [gulp](https://www.gulpjs.com.cn/) 是一个 js 自动化工具。 github 上的一些资料 - [Awsome-Front-End-learning-resource](https://github.com/helloqingfeng/Awsome-Front-End-learning-resource) [谷歌字体](https://www.font.im/) [Open Graph protocol](http://ogp.me/) [MDN 学习 Web 开发](https://developer.mozilla.org/zh-CN/docs/Learn) [掘金](https://juejin.im/) 是真的挺不错的一个社区。好多干货文章。 ## 相关文章 [^]: 收藏的文章都放这里好了。 [一文完全吃透 JavaScript 继承(面试必备良药)](https://juejin.im/post/5e5339b46fb9a07cb83e20d4) > 这篇文章详细讲解了原型相关的知识,包括原型链、ES5 ES6中的继承实现以及原理。帮助理解 JavaScript 面向对象方向的继承原理和构造器相关概念。 [[译] 在 async/await 中更好的处理错误](https://juejin.im/post/5e535624518825496e784ccb) > 这篇文章详细介绍了在 JavaScript 中的异步问题,介绍了回调地狱、Promise、async/await,以及他们的错误处理机制。 [中高级前端面试题(万字长文)](https://juejin.im/post/5e4c0b856fb9a07ccb7e8eca) > 面试题以及知识框架整理。 [理解Javascript的正则表达式](https://juejin.im/post/5e53a6d3f265da571671080f) > 正则表达式是在应用中相当重要的一部分,需要去理解记忆并多加练习。 [轻松理解JS中的面向对象,顺便搞懂prototype和__proto__](https://juejin.im/post/5e50e5b16fb9a07c9a1959af) > 理解 JavaScript 中的原型、原型链、构造器等相关概念。 [2020年你不能不知道的webpack基本配置](https://juejin.im/post/5e532b116fb9a07ce152c31a) > 了解 webpack 工具,官方文档 https://www.webpackjs.com/concepts/ [前端下载文件的5种方法的对比](https://juejin.im/post/5e50fa23518825494b3cccd7) > 关于前端的一些典型下载文件实现案例。 [2020年大厂面试指南 - Vue篇](https://juejin.im/post/5e4d24cce51d4526f76eb2ba) > 看大厂面试题对于理解相关知识也是很有用的。重要的是不要死记硬背,因为很多答案并不是从原理讲起,或者讲了看不懂,需要结合官方文档和实际上手去理解。 - [Vue源码系列一:Vue中的data是如何驱动视图渲染的?](https://juejin.im/post/5e06b4666fb9a0164f2956c0) - [VUE源码系列二:Vue响应式原理解析(附超详细源码注释和原理解析)](https://juejin.im/post/5e0dd467e51d45410f1232f5) - [VUE源码系列三:nextTick原理解析(附源码解读)](https://juejin.im/post/5e1ae62b51882526b831f1cb) - [VUE源码系列四:计算属性和监听属性,到底该用谁?](https://juejin.im/post/5e21619ce51d4552455a8896) - [VUE源码系列五:组件是怎样生成的?(附详细源码解析)](https://juejin.im/post/5e2804e1e51d453c9e155f08) - [VUE源码系列六:编译原理](https://juejin.im/post/5e2cfd695188252c5232ae3b) - [VUE源码系列七:v-model实现原理](https://juejin.im/post/5e3647816fb9a030133074b0) > 非常感谢这位作者,关于 Vue 的实现原理可以参考上面的连载专栏。不需要去深入算法,但是需要去了解一些基本的原理。同时自己可以尝试做一个类似的例如双重绑定、响应式等等。 > > 这个源码系列好多都没看懂, 只能看个大概。感觉文档越看越浮躁了。 - [(1.8w字)负重前行,前端工程师如何系统练习数据结构和算法?【上】](https://juejin.im/post/5e2f88156fb9a02fdd38a184) > 作为计算机相关行业人员对于数据结构和算法是必须要掌握一些的。 [剖析一些经典的CSS布局问题,为前端开发+面试保驾护航](https://juejin.im/post/5da282015188257d2a1c9e1d) > 跟着某些作者看可以找到一些没有推荐过的文章。有的还没看过,先留着。 [前端面试常考的手写代码不是背出来的!](https://juejin.im/post/5e57048b6fb9a07cc845a9ef) > 这篇文章是对于代码规范和一些功能函数的实例研究,很基础但也很有用。 [深入理解JS的原型和原型链](https://juejin.im/post/5e54d9e86fb9a07c944c932a) > 深入理解原型和原型链之间的关系,然后引申出执行上下文和 this 的相关概念。 [vue 248个知识点(面试题)为你保驾护航](https://juejin.im/post/5d153267e51d4510624f9809) > 了解 Vue 的相关知识点和原理。 [🔥(已更新3.1w字)《大前端吊打面试官系列》 之 ES6 精华篇(2020年)](https://juejin.im/post/5e4943d0f265da57537eaba9) > 深入理解 ES6 。 - [4W字长文带你深度解锁Webpack系列(上)](https://juejin.im/post/5e5c65fc6fb9a07cd00d8838) > 在前端开发环境下,对于 Webpack、gulp 等工具的理解使用对于提升开发效率、专注前端页面是有好处的。 [面试题:说说事件循环机制(满分答案来了)](https://juejin.im/post/5e5c7f6c518825491b11ce93) > 关于事件循环机制。 [前端工程师的自我修养-关于 Babel 那些事儿](https://juejin.im/post/5e5b488af265da574112089f) > Babel 是可以对代码进行向后兼容性编译的编译器,使得代码在不同环境下依然可以稳定运行。 [你再不知道怎么解决跨域,我就真的生气了](https://juejin.im/post/5e578a5f518825494707e4bb) > 关于跨域相关的知识点。跨域指的是由于浏览器出于安全考虑所设置的同源策略,如果协议、域名、端口有一项不同就会产生跨域问题,而跨域问题所带来的是安全问题,解决跨域不仅仅是让前端可以成功访问到跨域资源,更重要的是理解背后的安全隐患。 [记好这 24 个 ES6 方法,用来解决实际开发的 JS 问题](https://juejin.im/post/5e5ef2f9f265da57685dc9c1) > ES6 相关的对于实际问题的解决办法。 [深入vue响应式原理(包含vue3.0)](https://juejin.im/post/5e5b43b1f265da5716710e4c) > 看这个是想了解关于 Vue 2.0 和 3.0 之间的变化。 [2020 前端面试 | 第一波面试题总结](https://juejin.im/post/5e3d898cf265da5732551a56) > 实际面试题。 [🔥 动画:《大前端吊打面试官系列》 之原生 JavaScript 精华篇](https://juejin.im/post/5e34d19de51d4558864b1d1f) > 关于 JavaScript 相关的基础知识理解。 [前端也能学算法:由浅入深讲解贪心算法](https://juejin.im/post/5e575e02f265da573b0dad5f) > 关于贪心算法的讲解。 [「 如何优雅的使用VUE? 」不可不知的VUE实战技巧](https://juejin.im/post/5e475829f265da57444ab10f) > Vue 的实战技巧。 [2020年了,再不会webpack敲得代码就不香了(近万字实战)](https://juejin.im/post/5de87444518825124c50cd36) > webpack 相关知识。 [【建议星星】要就来45道Promise面试题一次爽到底(1.1w字用心整理)](https://juejin.im/post/5e58c618e51d4526ed66b5cf) > 关于 Promise 的各种面试题型。加深对于异步的理解和处理。 [从手写Promise到async/await(接近6千字,建议看一下)](https://juejin.im/post/5e5f52fce51d4526ea7efdec) > 关于异步和生成器、构造器的原理讲解和一些组合方法。 [【建议👍】再来40道this面试题酸爽继续(1.2w字用手整理)](https://juejin.im/post/5e6358256fb9a07cd80f2e70) > 关于 this 的相关题型。 [vue 组件通信看这篇就够了(12种通信方式)](https://mp.weixin.qq.com/s?__biz=Mzg2NTA4NTIwNA==&mid=2247484468&idx=1&sn=3c309945992fe4f0c6276d91d7cb67b8&chksm=ce5e364ff929bf59ae686e3fa2412632348d6e190054e0b83bed11b5fd851ebbe8d326b5caf0&token=1424393752&lang=zh_CN#rd) > 详细介绍了不同的父子组件间的通信方式。 [学习 BFC (Block Formatting Context)](https://juejin.im/post/59b73d5bf265da064618731d) > 了解在 CSS 中的 BFC 的相关概念。 [【第1250期】彻底理解浏览器的缓存机制](https://mp.weixin.qq.com/s/d2zeGhUptGUGJpB5xHQbOA) > 了解浏览器的缓存机制 [【译】Async/await和Promise的不为人知的秘密](https://juejin.im/post/5e57feede51d4526dd1ea7e9) > 关于 Async 和 Promise 的性能上的区别。 [learnVue](https://github.com/answershuto/learnVue) > github 上的 Vue 学习文章 [Vue3响应式系统源码解析-单测篇](https://juejin.im/post/5d9c9a135188252e097569bd) > 关于 Vue3 的响应式实现。 [【面试篇】寒冬求职季之你必须要懂的原生JS(上)](https://juejin.im/post/5cab0c45f265da2513734390) > 经典面试题和解析 [【面试篇】寒冬求职季之你必须要懂的原生JS(中)](https://juejin.im/post/5cbd1e33e51d45789161d053) > 经典面试题和解析 [【面试篇】寒冬求职之你必须要懂的Web安全](https://juejin.im/post/5cd6ad7a51882568d3670a8e) > 关于 Web 安全的相关面试题和解析 [「进击的前端工程师」浏览器中JavaScript的事件循环](https://juejin.im/post/5d2036106fb9a07eb15d76e9) > 深入了解 JavaScript 引擎的事件机制,后面的例题也很值得思考。 # 知识框架 - HTML - HTML 不同版本的差异 - HTML5 - CSS - CSS 不同版本的差异 - CSS3 - Sass - [JavaScript 廖雪峰](https://www.liaoxuefeng.com/wiki/1022910821149312) - [TypeScript](https://www.tslang.cn/index.html) - Node.js - jQuery(老掉牙了) - Ajax(也老掉牙了,了解XMR就行) - Webpack - Gulp - [Vue](https://cn.vuejs.org/) - [React](https://react.docschina.org/) - [Angular](https://angular.cn/) - ES5 - ES6 # HTML 整理 HTML 只是一个标记性的语言,本身没有什么学习难度,HTML5 是新一代的 HTML 标准,实现了一些新的元素和属性, 改进了本地存储并且添加了很多语义元素,使得代码或者页面更加语义化。语义化的好处在于对代码的理解更加简单,同时也为视力障碍人士改善了网页的可阅读性。 关于更多的细节参考 https://www.runoob.com/html/html-tutorial.html ## HTML DOM 关于 DOM 的理解可以参考 https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-tree-construction DOM 是文档对象模型的缩写,按照对对象的理解,DOM 就是浏览器生成的这个页面的对象,每一个 HTML 元素就是一个节点,通过 HTML 的嵌套关系生成子节点和父节点关系的树,元素的属性也是元素对应节点的子节点。 DOM 的树并不是普通的二叉树或者特殊的树 ![DOM 节点关系](https://www.w3school.com.cn/i/dom_navigate.gif) 关系其实还是挺复杂的。不过 DOM 提供了各种方法实现了对于节点或者元素的选择、修改。 获取目标元素节点是对于 JavaScript 脚本比较重要的一环,获取到节点后即可对节点所产生的事件进行脚本控制。 当然在 Vue 或者 React 等框架中有对应的方法可以更加方便的获取节点添加事件。 ### CSS DOM css 也是有一个 DOM 树的通过将两棵树合并生成一个渲染树供浏览器对页面进行渲染。 # CSS 整理 CSS 的作用是给 HTML 元素加上样式,同时又可以多个样式层叠,所以叫层叠样式表。 CSS 的内容也是比较复杂的。细节参考 https://www.runoob.com/css/css-tutorial.html 需要理解不同元素类型能做的事和不能做的事,例如行内元素的宽度和高度是不能设置的。根据内容自动。 不过对于网页 CSS 设计来说,例如 Bootstrap 或者 Element 等前端组件库已经设定好了很多实用的样式。可以在此基础上进行自定义。关于页面的布局也是有对应的解决方案,需要理解的还是元素的类型, 参考 https://www.runoob.com/cssref/pr-class-display.html CSS 相关的教程好少啊,但是感觉各种属性对应的布局却很麻烦。 ## CSS 字体相关属性 [w3school 页面](https://www.w3school.com.cn/cssref/pr_font_font.asp) | font-style | 规定字体样式。参阅:[font-style](https://www.w3school.com.cn/cssref/pr_font_font-style.asp) 中可能的值。 | | --------------------- | ------------------------------------------------------------ | | font-variant | 规定字体异体。参阅:[font-variant](https://www.w3school.com.cn/cssref/pr_font_font-variant.asp) 中可能的值。 | | font-weight | 规定字体粗细。参阅:[font-weight](https://www.w3school.com.cn/cssref/pr_font_weight.asp) 中可能的值。 | | font-size/line-height | 规定字体尺寸和行高。参阅:[font-size](https://www.w3school.com.cn/cssref/pr_font_font-size.asp) 和 [line-height](https://www.w3school.com.cn/cssref/pr_dim_line-height.asp) 中可能的值。 | | font-family | 规定字体系列。参阅:[font-family](https://www.w3school.com.cn/cssref/pr_font_font-family.asp) 中可能的值。 | | caption | 定义被标题控件(比如按钮、下拉列表等)使用的字体。 | | icon | 定义被图标标记使用的字体。 | | menu | 定义被下拉列表使用的字体。 | | message-box | 定义被对话框使用的字体。 | | small-caption | caption 字体的小型版本。 | | status-bar | 定义被窗口状态栏使用的字体。 | ## CSS 文本相关属性 | 属性 | 描述 | | :----------------------------------------------------------- | :---------------------------------------------------------- | | [color](https://www.w3school.com.cn/cssref/pr_text_color.asp) | 设置文本颜色 | | [direction](https://www.w3school.com.cn/cssref/pr_text_direction.asp) | 设置文本方向。 | | [line-height](https://www.w3school.com.cn/cssref/pr_dim_line-height.asp) | 设置行高。 | | [letter-spacing](https://www.w3school.com.cn/cssref/pr_text_letter-spacing.asp) | 设置字符间距。 | | [text-align](https://www.w3school.com.cn/cssref/pr_text_text-align.asp) | 对齐元素中的文本。 | | [text-decoration](https://www.w3school.com.cn/cssref/pr_text_text-decoration.asp) | 向文本添加修饰。 | | [text-indent](https://www.w3school.com.cn/cssref/pr_text_text-indent.asp) | 缩进元素中文本的首行。 | | text-shadow | 设置文本阴影。CSS2 包含该属性,但是 CSS2.1 没有保留该属性。 | | [text-transform](https://www.w3school.com.cn/cssref/pr_text_text-transform.asp) | 控制元素中的字母。 | | unicode-bidi | 设置文本方向。 | | [white-space](https://www.w3school.com.cn/cssref/pr_text_white-space.asp) | 设置元素中空白的处理方式。 | | [word-spacing](https://www.w3school.com.cn/cssref/pr_text_word-spacing.asp) | 设置字间距。 | ## 关于各个布局 https://zh.learnlayout.com/ 可以用来了解布局相关的知识。 布局其实就是把网页根据内容进行一个规划。 网页的内容永远都是不确定的,那么布局当然是要怎么好看怎么来。 了解布局首先需要了解 `display` 这个属性。这个属性决定了 HTML 元素在 CSS 层面上的性质。每个 tag 都有默认的 `display` 属性,比如常见的 `h1` `div` `p` `form ` 就是块级元素,而 `a` `b` `i` `span` `select` `img` `input` 都是行内元素。 通过不同的元素特性,页面元素也会产生不同的显示效果。比如在[弹性盒子](https://developer.mozilla.org/zh-CN/docs/Learn/CSS/CSS_layout/Flexbox)中有很多新的特性和 CSS 样式,可以更加方便快捷的设定垂直居中效果,而在块级元素中就很难做到。大致上来说网页的布局也就分为一列、二列、三列,通过样式的叠加形成不同的网页显示效果。至于说的什么圣飞布局、双飞翼布局感觉没啥意思,究其原理还是浮动、弹性盒子、网格、定位这类基础属性,只能说是别人设计出来的网页布局就是了。重要的还是内容的排列。通过 `display` 属性获得不同的文字、图片、内容块的排列效果。 ## 关于盒模型 盒模型是用来计算元素大小的一个标准模型,完整适用于块元素,部分适用于行内元素。盒模型还有一个替代模型IE盒模型,在IE盒模型中,所有的宽度和高度都是可见的,内容的宽度包括了Padding的宽度。 ![Diagram of the box model](https://media.prod.mdn.mozit.cloud/attachments/2019/03/19/16558/29c6fe062e42a327fb549a081bc56632/box-model.png) > 一个被定义成块级的(block)盒子会表现出以下行为: > > - 盒子会在内联的方向上扩展并占据父容器在该方向上的所有可用空间,在绝大数情况下意味着盒子会和父容器一样宽 > - 每个盒子都会换行 > - [`width`](https://developer.mozilla.org/zh-CN/docs/Web/CSS/width) 和 [`height`](https://developer.mozilla.org/zh-CN/docs/Web/CSS/height) 属性可以发挥作用 > - 内边距(padding), 外边距(margin) 和 边框(border) 会将其他元素从当前盒子周围“推开” > 如果一个盒子对外显示为 `inline`,那么他的行为如下: > > - 盒子不会产生换行。 > - [`width`](https://developer.mozilla.org/zh-CN/docs/Web/CSS/width) 和 [`height`](https://developer.mozilla.org/zh-CN/docs/Web/CSS/height) 属性将不起作用。 > - 内边距、外边距以及边框会被应用但是不会把其他处于 `inline` 状态的盒子推开。 > 一个元素使用 `display: inline-block`,实现我们需要的块级的部分效果: > > - 设置`width` 和`height` 属性会生效。 > - `padding`, `margin`, 以及`border` 会推开其他元素。 > > 但是,它不会跳转到新行,如果显式添加`width` 和`height` 属性,它只会变得比其内容更大。 https://developer.mozilla.org/zh-CN/docs/Learn/CSS/CSS_layout/Flexbox 详细介绍了 `display:flexible` 弹性盒子的 flex 模型 ![flex_terms.png](https://developer.mozilla.org/files/3739/flex_terms.png) > - **主轴(main axis)**是沿着 flex 元素放置的方向延伸的轴(比如页面上的横向的行、纵向的列)。该轴的开始和结束被称为 **main start** 和 **main end**。 > - **交叉轴(cross axis)**是垂直于 flex 元素放置方向的轴。该轴的开始和结束被称为 **cross start** 和 **cross end**。 > - 设置了 `display: flex` 的父元素(在本例中是 [`

`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/section))被称之为 **flex 容器(flex container)。** > - 在 flex 容器中表现为柔性的盒子的元素被称之为 **flex 项**(**flex item**)(本例中是 [`article`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/article) 元素。 ## 关于网页字体 # JavaScript 整理 关于语法就和其他语言是一样的,变量、运算、数据类型、函数、对象、数组、循环、作用域等等。 不一样的是,由于 JavaScript 是运行在浏览器上的。所以很多功能代码都是已经实现了的。不需要像其他语言一样需要自己写类。由于本身是面向对象的,JavaScript 的所有变量都是一个对象。对于 JavaScript 的学习需要去了解背后的原理。 由于浏览器的版本大家可能都不一样,所以在使用 JavaScript 特性的时候需要考虑版本问题。了解 ES5 和 ES6 的区别。可以使用 https://caniuse.com/ 来了解特性的支持情况。 细节参考 https://www.w3school.com.cn/js/index.asp ## 关于原型链 JavaScript 是解释型脚本语言,同时也是面向对象的语言,JavaScript 的对象是动态的属性“包”,会有一个`__proto__`的私有属性,该属性会指向构造函数的原型对象。 ```javascript // 让我们从一个自身拥有属性a和b的函数里创建一个对象o: 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 ``` 来自 MDN https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain 所以当一个对象通过 `new` 关键字从其他的对象“实例化”,会将先生成一个空对象,通过 [Object.setPrototypeOf()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf)原生函数将实例对象添加到原型链上。 ```javascript function polyNew(source, ...arg) { // 创建一个空的简单JavaScript对象(即{}) let newObj = {}; // 链接该对象(即设置该对象的构造函数)到另一个对象 Object.setPrototypeOf(newObj, source.prototype); // 将步骤1新创建的对象作为this的上下文 ; const resp = source.apply(newObj, arg); // 判断该函数返回值是否是对象 if (Object.prototype.toString.call(resp) === "[object Object]") { // 如果该函数没有返回对象,则返回this。 return resp } else { // 如果该函数返回对象,那用返回的这个对象作为返回值。 return newObj } } 作者:dellyoung 链接:https://juejin.im/post/5e54d9e86fb9a07c944c932a 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 ``` 上面这个地址的文章写的非常详细。可以深入理解关于原型 Null 、原型链、This 的相关原理。 **`instanceof`** **运算符**用于检测构造函数的 `prototype` 属性是否出现在某个实例对象的原型链上。 实现起来也是比较简单的。顺藤摸瓜就行了。 **this 是什么?** 理解 **this** 就需要理解 JavaScript 的执行上下文和调用栈,可以看上面的文章进行详细的步进的了解。 了解了执行上下文后,就知道了执行上下文包括了变量环境、词法环境、outer、this。 而这个 `this` 就是指向当下执行环境的一个指针。在浏览器环境下指的是 window。 ```javascript 执行下面代码: function foo(){ console.log(this) // window } foo() // 可以看到输出了window,说明在默认情况下调用一个函数,其执行上下文中的 this 也是指向 window 对象的。 // 可以认为 JavaScript 引擎在执行foo()时,将其转化为了: function foo(){ console.log(this) // window } window.foo.call(window) ``` 关于这一段我理解的是由于 `foo()` 函数是一个函数而不是一个对象,`this` 指向这个函数没有什么意义,于是默认情况下会将它指向上一层的执行上下文**对象**即 `window` window 在浏览器中表示浏览器的窗口。 ```javascript function foo(){ console.log(this) // window } foo() //Window {parent: Window, opener: Window, top: Window, length: 0, frames: Window, …} a = new foo() //foo {} // __proto__: // constructor: ƒ foo() // __proto__: Object ``` 可见,当我们把它实例化的时候,会通过 `new` 中的构造器,将 `foo()`链接到 `a` 的原型上。这个时候的 `this` 指的就是 `foo()` 了,当然这个 `foo()` 并不是说 `foo()`函数,而是指将`foo()`实例化的 `a` 对象。如果再实例化一个 `b ` ,两者并不会相等。 或许我可以理解为 **this 指代的永远是一个对象,而非一个函数,如果在函数中输出一个 this 而这个函数并非是某个对象的方法,那么 this 会重定向到 window 对象** 关于执行上下文还有配套的 `call`、`apply`、`bind` 三个方法。详情参考https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/apply# 主要用处就是设置 `this` ,在调用一个存在的函数时,你可以为其指定一个 `this` 对象。 `this` 指当前对象,也就是正在调用这个函数的对象。 使用 `apply`, 你可以只写一次这个方法然后在另一个对象中继承它,而不用在新对象中重复写该方法。 ```JavaScript function foo(){ console.log(this) // window } foo() 复制代码 ``` 可以看到输出了window,说明在**默认情况**下调用一个函数,其执行上下文中的 this 也是指向 window 对象的。 ```javascript function f(fn, x) { console.log('into') if (x < 1) { f(g, 1); } else { fn(); } function g() { console.log('x' + x); } } function h() { } f(h, 0) // into // into // x0 作者:小黎也 链接:https://juejin.im/post/5e523e726fb9a07c9a195a95 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 ``` 这个面试题的逻辑看上去很简单,但是却有效的解释了执行上下文是如何影响程序的,同时也引出了作用域链的一个概念,上下文上下文,则说明是有上文也是有下文的。比如全局执行上下文就肯定是其他执行上下文的上文,那么参数在不同的执行上下文之间调用,每个参数不同的作用域,就形成了作用域链,和原型链是异曲同工的。如果一个函数在执行中找不到某个参数,就会顺着作用域链往上找。 g 函数中的 x 变量是引用父级的,而 f 函数执行了两次,x 变量依次为 0 1,在 f(h,0) 这个函数执行的时候,这个函数的作用域中的 x=0,这个时候 g 函数中引用的 x 就是当前执行上下文中的 x=0 这个变量,但这个函数还没被执行,接着到了 f(g, 1) 执行,这一层执行上下文中的 x=1 ,但注意两次 f 执行的作用域不是同一个对象,是作用域链上两个独立的对象,最后到了 fn() ,这个 fn 是一个参数,也就是在 f(h,0) 执行的时候 g 函数,那么 g 函数在这里被执行,g 打印出来的 x 就是 0 。 如果最后一段理解起来困难的话可以这样想,最后执行 f(g,1)的时候,执行的 fn() 并不是当下的 g(),而是传入的 g(),而这个传入的 g() 是来自 f(h,0) 这个环境下的。所以 x=0. 或许可以对这个题进行一个改变,如果 g() 函数的定义在是 f() 函数开头会怎么样?结果还是一样的,因为不管 g() 函数的位置如何变,始终是运行的 f(h,0) 环境下的 g()。 ## 关于变量提升 https://developer.mozilla.org/zh-CN/docs/Glossary/Hoisting 变量提升(Hoisting)被认为是, Javascript中执行上下文 (特别是创建和执行阶段)工作方式的一种认识。 **JavaScript 仅提升声明,而不提升初始化**。 变量提升可能是个好东西,因为它能让你的程序跑起来。但也可能是个坏东西,因为会养成到处声明的坏习惯。 ```javascript console.log(num); // Returns undefined var num; num = 6; // If you declare the variable after it is used, but initialize it beforehand, it will return the value: num = 6; console.log(num); // returns 6 var num; ``` 正确的编写原则是先声明再初始化,最后才是运用变量。 在 ES6 中的 let 和const不存在变量提升 var: 解析器在对 js 解析时,会将脚本扫描一遍,将变量的声明提前到代码块的顶部,赋值还是在原先的位置,若在赋值前调用,就会出现暂时性死区,值为 `undefined` let const:不存在在变量提升,且作用域是存在于块级作用域下,所以这两个的出现解决了变量提升的问题,同时引用块级作用域。 注:变量提升的原因是为了解决函数互相调用的问题。 ## 关于 Promise 和 Async/Await 在 MDN 中关于 Promise 是这样介绍的 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises > [`Promise`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise) 是一个对象,它代表了一个异步操作的最终完成或者失败。本质上,Promise 是一个被某些函数传出的对象,我们附加回调函数(callback)使用它,而不是将回调函数传入那些函数内部。 由于 JavaScript 是单线程的,所以我们需要考虑到网络的影响,如果在一个图片还没加载好的时候就去使用这个资源,是会造成图片缺失的,所以提出了异步,所谓异步就是使有关联性的程序按照顺序运行,防止资源加载未完成等问题。 我对于 `Promise` 的理解是,首先它的提出是为了解决回调函数过于复杂和冗余的问题。 ```javascript doSomething(function(result) { doSomethingElse(result, function(newResult) { doThirdThing(newResult, function(finalResult) { console.log('Got the final result: ' + finalResult); }, failureCallback); }, failureCallback); }, failureCallback); ``` 回调函数本身是把一个函数作为另一个函数的参数,然后在函数中调用这个函数达到异步的效果。 ```javascript doSomething().then(function(result) { return doSomethingElse(result); }) .then(function(newResult) { return doThirdThing(newResult); }) .then(function(finalResult) { console.log('Got the final result: ' + finalResult); }) .catch(failureCallback); ``` 这个示例通过返回 Promise 对象,实现了 Promise 对象的 `then` 链式写法,then里的参数是可选的,`catch(failureCallback)` 是 `then(null, failureCallback)` 的缩略形式。如下所示,我们也可以用[箭头函数](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_functions)来表示: ```javascript doSomething() .then(result => doSomethingElse(result)) .then(newResult => doThirdThing(newResult)) .then(finalResult => { console.log(`Got the final result: ${finalResult}`); }) .catch(failureCallback); ``` 上面的示例来自 MDN 的 Promise 文档 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises 需要注意的是,这个示例并不能在 console 中跑起来,因为没有具体的定义 doSomething(),在实际使用中,这几个函数都是需要有返回值的 ```javascript const doSomething = () => { return new Promise((resolve,reject)=>{ console.log("doSomething") resolve("doSomething end") }) } const doSomethingElse = (argu) => { return new Promise((resolve,reject)=>{ console.log(argu) resolve("doSomethingElse end") }) } const doThirdThing = (argu) => { return new Promise((resolve,reject)=>{ console.log(argu) resolve("doThirdThing end") }) } // doSomething() // .then(result => doSomethingElse(result)) // .then(newResult => doThirdThing(newResult)) // .then(finalResult => { // console.log(`Got the final result: ${finalResult}`); // }) // .catch(()=>{console.log("fail")}); // doSomething // doSomething end // doSomethingElse end // Got the final result: doThirdThing end // Promise {: undefined} ``` 当一个函数返回 Promise 对象的时候,可以把它当做一个异步函数,后面的 `.then` 方法是 Promise 对象的方法之一,用来接收上一个 Promise 在 resolve 的时候的消息,作为自定义的变量如 result 参与 `.then` 方法中的程序。由于几个函数都是返回的 Promise 对象,形成了一条 Promise 链。这条链是按照顺序异步运行的。`.catch` 方法是用来获取在前面 Promise 链中的错误,进行合适的错误抛出和处理。 ```javascript new Promise((resolve, reject) => { console.log('初始化'); resolve(); }) .then(() => { throw new Error('有哪里不对了'); console.log('执行「这个」”'); }) .catch(() => { console.log('执行「那个」'); }) .then(() => { console.log('执行「这个」,无论前面发生了什么'); }); // 初始化 // 执行“那个” // 执行“这个”,无论前面发生了什么 ``` `.catch` 也是返回的 [Promise 对象](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch),可以继续连接 `.then` 方法,使得程序在接收到错误后可以继续正常运行。 ```javascript async function foo() { try { let result = await doSomething(); let newResult = await doSomethingElse(result); let finalResult = await doThirdThing(newResult); console.log(`Got the final result: ${finalResult}`); } catch(error) { failureCallback(error); } } ``` 使用 Async/Await 同样可以达成异步操作,并且代码看上去更加的简洁。Async/Await 其实是 Promise 的一个语法糖,其原理还是 Promise, > `async`/`await`的目的是简化使用多个 promise 时的同步行为,并对一组 `Promises`执行某些操作。正如`Promises`类似于结构化回调,`async`/`await`更像结合了generators和 promises。 关于 Async/Await 的错误处理机制可以参考 https://juejin.im/post/5e535624518825496e784ccb 继续深入一下 Promise - [`Promise.all()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) > **`Promise.all(iterable)`**方法返回一个 [`Promise`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise) 实例,此实例在 `iterable` 参数内所有的 `promise` 都“完成(resolved)”或参数中不包含 `promise` 时回调完成(resolve);如果参数中 `promise` 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 `promise` 的结果。 > > ```javascript > var p1 = Promise.resolve(3); > var p2 = 1337; > var p3 = new Promise((resolve, reject) => { > setTimeout(() => { > resolve("foo"); > }, 100); > }); > > Promise.all([p1, p2, p3]).then(values => { > console.log(values); // [3, 1337, "foo"] > }); > ``` - [`Promise.allSettled()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled) > 该**`Promise.allSettled()`**方法返回一个在所有给定的promise已被决议或被拒绝后决议的promise,并带有一个对象数组,每个对象表示对应的promise结果。 > > ```javascript > const promise1 = Promise.resolve(3); > const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo')); > const promises = [promise1, promise2]; > > Promise.allSettled(promises). > then((results) => results.forEach((result) => console.log(result.status))); > > // expected output: > // "fulfilled" > // "rejected" > ``` - [`Promise.any()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/any) > `Promise.any()` 接收一个[`Promise`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise)可迭代对象,只要其中的一个 `promise` 完成,就返回那个已经有完成值的 `promise` 。如果可迭代对象中没有一个 `promise` 完成(即所有的 `promises` 都失败/拒绝),就返回一个拒绝的 `promise`,返回值还有待商榷:无非是拒绝原因数组或`AggregateError`类型的实例,它是 [`Error`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Error) 的一个子类,用于把单一的错误集合在一起。本质上,这个方法和[`Promise.all()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/all)是相反的。 - [`Promise.prototype.catch()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch) - [`Promise.prototype.finally()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally) - [`Promise.prototype.then()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/then) - [`Promise.race()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/race) > **`Promise.race(iterable)`** 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。 > > ```javascript > const promise1 = new Promise(function(resolve, reject) { > setTimeout(resolve, 500, 'one'); > }); > > const promise2 = new Promise(function(resolve, reject) { > setTimeout(resolve, 100, 'two'); > }); > > Promise.race([promise1, promise2]).then(function(value) { > console.log(value); > // Both resolve, but promise2 is faster > }); > // expected output: "two" > ``` - [`Promise.reject()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/reject) - [`Promise.resolve()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve) Promise 对象的所有方法就是这些了。 理解 Async/Await 除了理解 Promise 之外,我们知道它是 Promise 的一个语法糖。结合了 Promise 和 constructor。 [AsyncFunction](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/AsyncFunction#AsyncFunction_原型对象) 就是 [`Async/Await`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/async_function) 的构造函数。 ```javascript function resolveAfter2Seconds(x) { return new Promise(resolve => { setTimeout(() => { resolve(x); }, 2000); }); } var AsyncFunction = Object.getPrototypeOf(async function(){}).constructor; var a = new AsyncFunction('a', 'b', 'return await resolveAfter2Seconds(a) + await resolveAfter2Seconds(b);'); a(10, 20).then(v => { console.log(v); // 4 秒后打印 30 }); ``` 这个就是使用 AsyncFunction 实现的一个异步函数。不过看上去好像跟普通的 Promise 没什么不同,我记得看过一个实现 Async 构造器的文章,~~找不到了。回头再写这里吧。~~ ## 关于包装对象 在 JavaScript 中变量一般分为基本数据类型和对象类型。基本数据类型包括 Number、String、Boolean,在我们定义一个数字或者字符串的时候,它的类型 `typeof()` 出来就是普通的数据类型,但是我们可以使用 `.length` `.toString()` 等对象的方法,原因是 JavaScript 在执行的时候会使用数据类型对象包装原始数据,使其成为一个对象然后使用相关方法和属性。但是在输出后就会将其销毁,并不会改变原生数据的类型。 ## 关于 Null 和 Undefined ```javascript undefined == null true undefined === null false ``` 两个数据类型都是表示为空,但是具体的含义不太一样,Null 表示数据有值,值的内容为空,这个空并不是 0 或者一个空字符串,所以可以使用 Null 来初始化变量,Undefined 表示数据没有值,当变量仅被声明而没有被初始化的时候即是 Undefined。 经典一点的问题比如一个空数组,如何判定为空,由于 JavaScript 的对象类型,数组是对象的一种,他的原型是 Array 原型对象。 ```javascript a=[] typeof(a) "object" a===null false a==null false ``` 需要使用原型对象的 `.length` 属性来判断数组中是否有值。 ```javascript a=[1,2,3] (3) [1, 2, 3] a.length 3 a.length = 4 4 a (4) [1, 2, 3, empty] ``` ## 关于 JavaScript 执行机制和事件循环机制 JavaScript 引擎是 JavaScript 的解释器,类似于 Python,他们都是解释性语言。JavaScript 在最初被开发出来时,是为了用于浏览器的,所以注定了 JavaScript 是一个单线程的语言。那么当 JavaScript 需要处理两个事件的时候,如果一个事件是一个需要等待的事件,那么他会挂起等待的任务,先运行后面的任务。 ![img](https://user-gold-cdn.xitu.io/2019/7/6/16bc675a7df7428e?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) 这张图说明了,在主线程运行的时候,会生成堆栈,形成一个任务队列,然后主线程会循环访问这个任务队列,运行队列中的事件。 在堆中保存的是对象的数据,在栈中保存的是基本数据类型和函数执行时的内存信息。 > 栈中的代码会调用各种外部API,它们在任务队列中加入各种事件(onClick,onLoad,onDone),只要栈中的代码执行完毕(js引擎存在`monitoring process`进程,会持续不断的检查主线程执行栈是否为空),主线程就回去读取任务队列,在按顺序执行这些事件对应的回调函数。 > > 也就是说主线程从任务队列中读取事件,这个过程是循环不断的,所以这种运行机制又成为`Event Loop`(事件循环)。 > > 作者:童欧巴 > 链接:https://juejin.im/post/5d2036106fb9a07eb15d76e9 > 来源:掘金 > 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 通常来说,大部分的代码按照从上到下的顺序执行,并不会有什么问题,即是同步任务,但是当遇到例如加载图片这种操作的时候,可能在图片还没加载完成的情况下就使用了该图片,就会造成显示不全等问题,所以提出了**异步** 的思想。 同步任务就是在主线程上排队执行的任务,严格按照代码顺序执行(变量提升后),异步任务则不进入主线程,会在 event table 中注册函数,当达到执行条件后才会进入任务队列,然后在任务队列等待主线程执行。 **宏任务和微任务** > ``` > setTimeout(function() { > console.log('a') > }); > > new Promise(function(resolve) { > console.log('b'); > > for(var i =0; i <10000; i++) {> i ==99 && resolve(); > } > }).then(function() { > console.log('c') > }); > > console.log('d'); > > // b > // d > // c > // a > 复制代码 > ``` > > 1.首先执行`script`下的宏任务,遇到`setTimeout`,将其放入宏任务的队列里。 > > 2.遇到`Promise`,`new Promise`直接执行,打印b。 > > 3.遇到`then`方法,是微任务,将其放到微任务的队列里。 > > 4.遇到`console.log('d')`,直接打印。 > > 5.本轮宏任务执行完毕,查看微任务,发现`then`方法里的函数,打印c。 > > 6.本轮`event loop`全部完成。 > > 7.下一轮循环,先执行宏任务,发现宏任务队列中有一个`setTimeout`,打印a。 > > 作者:童欧巴 > 链接:https://juejin.im/post/5d2036106fb9a07eb15d76e9 > 来源:掘金 > 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 ![img](https://user-gold-cdn.xitu.io/2019/7/6/16bc6bd331a2116a?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) 通过宏任务和微任务的划分,可以更加清晰的分辨出程序的运行顺序, 上面的文章里还有一个思考题。推荐看一看。同时也看看 https://juejin.im/post/5e5c7f6c518825491b11ce93 文章的理解,这篇文章更加详细,但是不太好懂。 ## JavaScript ES6 更新了什么? 详细的教程或者文档可以参考 [阮一峰的教程](https://es6.ruanyifeng.com/) 和 [Babel 的文档](https://www.babeljs.cn/docs/learn.html) 在前端方面有相当多的规范,比如我们所使用的 HTML、CSS、JavaScript 都是有规范的,其中 HTML、CSS 的标准是由 [W3C](https://www.w3.org/standards/) 设定的,还有 XML、XHTML 也是 W3C 制定的标准。JavaScript 的标准则是由 [ECMAScript](https://www.ecma-international.org/publications/standards/Standard.htm) 所制定的,目前最新的规范是 ECMA-262 Edition 6,也被称为 ECMAScript 2015。这是一个大版本的更新,对比第五代的 ECMAScript 更新了很多东西。可以帮助开发者更加方便快捷的开发网页。 > Babel 是一个 JavaScript 编译器。 Babel 作为 JavaScript 的编译器可以将 ES6 的代码转换成向后兼容的代码,防止出现浏览器兼容错误。 然后就是 JavaScript 的引擎,简单说就是浏览器的核心,目前使用较多的就是 [Firefox 的 SpiderMonkey](https://developer.mozilla.org/zh-CN/docs/Mozilla/Projects/SpiderMonkey) 和 [Chrome 的 V8](https://v8.dev/) 了,关于 JavaScript 引擎了解的不需要太多。还是关注 JavaScript 的语法标准和原理比较好。 ### let 命令 ES6 中新增了 `let` 和 `const` 两个命令,用来声明变量,取代 `var`,不同的是 `let` 和 `const` 都不会产生变量提升,也就是说他们所声明的变量只能在命令所在的代码块有效。 **暂时性死区** ```javascript var tmp = 123; if (true) { tmp = 'abc'; // ReferenceError let tmp; } ``` 使用 `var` 声明的 `tmp` 变量可以看做是一个全局变量,但是由于在 `if` 循环语句内又声明了一个 `tmp`,即使 `tmp` 在 `let` 声明之前赋值也是会直接报错的,如果在 `let` 声明之后赋值的话既是对 `let` 声明的 `tmp` 赋值。 > ES6 明确规定,如果区块中存在`let`和`const`命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。 ```javascript if (true) { // TDZ开始 tmp = 'abc'; // ReferenceError console.log(tmp); // ReferenceError let tmp; // TDZ结束 console.log(tmp); // undefined tmp = 123; console.log(tmp); // 123 } ``` 由于暂时性死区的特性,在变量声明之前都是属于变量的死区,不管做什么操作都会报错。包括 `typeof`。 `let` 也不支持重复声明,相比于 `var` 来说更加的严格。 #### 新的作用域 详见 [ES6-的块级作用域](https://es6.ruanyifeng.com/#docs/let#ES6-的块级作用域) 作用域是用来确定变量的有效范围和调用顺序,在 ES5 中只有全局作用域和函数作用域,在 ES6 中通过 `let` 新增了块级作用域,尽可能的避免变量提升。 ```javascript var tmp = new Date(); function f() { console.log(tmp); if (false) { var tmp = 'hello world'; } } f(); // undefined ``` 在没有块级作用域的时候,上面代码的原意是,`if`代码块的外部使用外层的`tmp`变量,内部使用内层的`tmp`变量。但是,函数`f`执行后,输出结果为`undefined`,原因在于变量提升,导致内层的`tmp`变量覆盖了外层的`tmp`变量。 ```javascript var s = 'hello'; for (var i = 0; i < s.length; i++) { console.log(s[i]); } console.log(i); // 5 ``` 还有就是在使用 `for` 循环的时候使用 `var` 声明的变量会泄露成为全局变量。 ```javascript function f1() { let n = 5; if (true) { let n = 10; } console.log(n); // 5 } ``` 在使用了 `let` 之后会产生块级作用域,使得变量仅在该区域内生效, ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。 ```javascript // 情况一 if (true) { function f() {} } // 情况二 try { function f() {} } catch(e) { // ... } ``` 上面两种函数声明,根据 ES5 的规定都是非法的。 但是,浏览器没有遵守这个规定,为了兼容以前的旧代码,还是支持在块级作用域之中声明函数,因此上面两种情况实际都能运行,不会报错。 ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。ES6 规定,块级作用域之中,函数声明语句的行为类似于`let`,在块级作用域之外不可引用。 #### 函数表达式和函数声明 ```JavaScript // 块级作用域内部的函数声明语句,建议不要使用 { let a = 'secret'; function f() { return a; } } // 块级作用域内部,优先使用函数表达式 { let a = 'secret'; let f = function () { return a; }; } ``` ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。 ```javascript // 情况一 if (true) { function f() {} } // 情况二 try { function f() {} } catch(e) { // ... } ``` 但是由于浏览器为了兼容以前的代码,是会支持的,ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。ES6 规定,块级作用域之中,函数声明语句的行为类似于`let`,在块级作用域之外不可引用。 ```javascript function f() { console.log('I am outside!'); } (function () { if (false) { // 重复声明一次函数f function f() { console.log('I am inside!'); } } f(); }()); ``` 在 ES5 中,这个函数声明会被提升到块级作用域头部,并不会受 `if` 影响,但是在 ES6 中,会因为浏览器的行为而报错。 - 允许在块级作用域内声明函数。 - 函数声明类似于`var`,即会提升到全局作用域或函数作用域的头部。 - 同时,函数声明还会提升到所在的块级作用域的头部。 ```javascript // 浏览器的 ES6 环境 function f() { console.log('I am outside!'); } (function () { var f = undefined; if (false) { function f() { console.log('I am inside!'); } } f(); }()); // Uncaught TypeError: f is not a function ``` 所以在块级作用域内声明函数,最好是使用函数表达式而非函数声明。 ### const 命令 `const` 声明一个只读的常量。一旦声明,常量的值就不能改变。 ```javascript const PI = 3.1415; PI // 3.1415 PI = 3; // TypeError: Assignment to constant variable. ``` `const` 命令需要在声明变量同时对变量进行初始化。只声明而不赋值的话也是会报错的。 `const` 和 `let` 一样,只会在声明的块级作用域内有效,存在暂时性死区,不会被变量提升。 > `const`实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,`const`只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。 ```javascript const foo = {}; // 为 foo 添加一个属性,可以成功 foo.prop = 123; foo.prop // 123 // 将 foo 指向另一个对象,就会报错 foo = {}; // TypeError: "foo" is read-only ``` 如果需要一个彻底不能被修改的变量,不论是对象还是其他类型,可以使用 [`Object.freeze()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) ```javascript var constantize = (obj) => { Object.freeze(obj); Object.keys(obj).forEach( (key, i) => { if ( typeof obj[key] === 'object' ) { constantize( obj[key] ); } }); }; ``` 通过循环遍历对象的属性可以保证对象内的属性也得到冻结。 #### 顶层对象和全局对象 [顶层对象和全局对象](https://es6.ruanyifeng.com/#docs/let#顶层对象的属性) 详情就不写了,主要是为了解决前面对于两个对象的区分不严格的问题,达到浏览器和 Node 环境中的统一, ### 变量解构赋值 > ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。 以前,为变量赋值,只能直接指定值。 ```javascript let a = 1; let b = 2; let c = 3; ``` ES6 允许写成下面这样。 ```javascript let [a, b, c] = [1, 2, 3]; ``` 解构赋值的前提是他们的类型需要相同,所以在右边会使用 `[]` 来包裹值,如果等号左边比右边的值多的话,多的值会被赋值 `undefined` ,如果右边比左边多的话,多的值会被丢弃不用。 解构赋值允许指定默认值。 ```javascript let [foo = true] = []; foo // true let [x, y = 'b'] = ['a']; // x='a', y='b' let [x, y = 'b'] = ['a', undefined]; // x='a', y='b' ``` > ES6 内部使用严格相等运算符(`===`),判断一个位置是否有值。所以,只有当一个数组成员严格等于`undefined`,默认值才会生效。 然后就是对对象的解构赋值,也是同理的。只是写法上不太一样,需要指定目标对象属性值。 ```javascript let { bar, foo } = { foo: 'aaa', bar: 'bbb' }; foo // "aaa" bar // "bbb" let { baz } = { foo: 'aaa', bar: 'bbb' }; baz // undefined ``` 同样也是可以给对象指定默认值。 ### 字符串相关 ES6 加强了对 Unicode 的支持,允许采用`\uxxxx`形式表示一个字符,其中`xxxx`表示字符的 Unicode 码点。 但是,这种表示法只限于码点在`\u0000`~`\uFFFF`之间的字符。超出这个范围的字符,必须用两个双字节的形式表示。 ```javascript "\uD842\uDFB7" // "𠮷" "\u20BB7" // " 7" ``` ES6 对这一点做出了改进,只要将码点放入大括号,就能正确解读该字符。 ```javascript "\u{20BB7}" // "𠮷" "\u{41}\u{42}\u{43}" // "ABC" let hello = 123; hell\u{6F} // 123 '\u{1F680}' === '\uD83D\uDE80' // true ``` 有了这种表示法之后,JavaScript 共有 6 种方法可以表示一个字符。 ```javascript '\z' === 'z' // true '\172' === 'z' // true '\x7A' === 'z' // true '\u007A' === 'z' // true '\u{7A}' === 'z' // true ``` ES6 为字符串添加了遍历器接口(详见《Iterator》一章),使得字符串可以被`for...of`循环遍历。 ```javascript for (let codePoint of 'foo') { console.log(codePoint) } // "f" // "o" // "o" ``` 然后是关于模板字符串的改进,让字符串中的变量可以更加方便的输出。 ```javascript $('#result').append( 'There are ' + basket.count + ' ' + 'items in your basket, ' + '' + basket.onSale + ' are on sale!' ); $('#result').append(` There are ${basket.count} items in your basket, ${basket.onSale} are on sale! `); ``` 模板字符串(template string)是增强版的字符串,用反引号(\`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入 `${}` 变量。 如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。 ES6 还为原生的 String 对象,提供了一个`raw()`方法。该方法返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,往往用于模板字符串的处理方法。 ```javascript String.raw`Hi\n${2+3}!` // 实际返回 "Hi\\n5!",显示的是转义后的结果 "Hi\n5!" String.raw`Hi\u000A!`; // 实际返回 "Hi\\u000A!",显示的是转义后的结果 "Hi\u000A!" ``` `String.raw()`本质上是一个正常的函数,只是专用于模板字符串的标签函数。如果写成正常函数的形式,它的第一个参数,应该是一个具有`raw`属性的对象,且`raw`属性的值应该是一个数组,对应模板字符串解析后的值。 ```javascript // `foo${1 + 2}bar` // 等同于 String.raw({ raw: ['foo', 'bar'] }, 1 + 2) // "foo3bar" ``` 传统上,JavaScript 只有`indexOf`方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6 又提供了三种新方法。 - **includes()**:返回布尔值,表示是否找到了参数字符串。 - **startsWith()**:返回布尔值,表示参数字符串是否在原字符串的头部。 - **endsWith()**:返回布尔值,表示参数字符串是否在原字符串的尾部。 这三个方法都支持第二个参数,表示开始搜索的位置。 ```javascript let s = 'Hello world!'; s.startsWith('world', 6) // true s.endsWith('Hello', 5) // true s.includes('Hello', 6) // false ``` `repeat`方法返回一个新字符串,表示将原字符串重复`n`次。 ```javascript 'x'.repeat(3) // "xxx" 'hello'.repeat(2) // "hellohello" 'na'.repeat(0) // "" ``` 参数如果是小数,会被取整。 ```javascript 'na'.repeat(2.9) // "nana" ``` 如果`repeat`的参数是负数或者`Infinity`,会报错。 ```javascript 'na'.repeat(Infinity) // RangeError 'na'.repeat(-1) // RangeError ``` 但是,如果参数是 0 到-1 之间的小数,则等同于 0,这是因为会先进行取整运算。0 到-1 之间的小数,取整以后等于`-0`,`repeat`视同为 0。 ```javascript 'na'.repeat(-0.9) // "" ``` 参数`NaN`等同于 0。 ```javascript 'na'.repeat(NaN) // "" ``` 如果`repeat`的参数是字符串,则会先转换成数字。 ```javascript 'na'.repeat('na') // "" 'na'.repeat('3') // "nanana" ``` ### 正则表达式的改进 ## TypeScript 整理 # 关于浏览器需要知道些什么 ## 经典例题之访问网站 当我在浏览器URL栏上输入了一串网址然后按下回车,之后会发生什么? 这是一个很简单的操作,几乎每个人都会,但是背后这项操作的原理是什么。 # Node.js 整理 # Vue 整理 ## Vue 是什么? Vue 是一个开源的响应式前端框架,使用了 MVVM 框架,方便开发者关注数据图层。 ### MVVM 框架是什么? Model-View-ViewModel 是 MVC 的一个改进版本。那么 MVC 又是什么? Model-View-Controller,Model 是指数据模型,View 指视图,Controller 是控制器,这个框架使得视图和数据模型分开来,通过控制器来进行操作,在数据发生改变的时候通知控制器,然后控制器去更新视图。 https://www.runoob.com/design-pattern/mvc-pattern.html 菜鸟的这个示例实现了一个 MVC 架构的程序。 MVVM 就是将 MVC 中的 Controller 改进成 ViewModel,把 Model 和 View 关联起来的就是 ViewModel。ViewModel 负责把 Model 的数据同步到 View 显示出来,还负责把 View 的修改同步回 Model。 > 让MVVM框架去自动更新DOM的状态,从而把开发者从操作DOM的繁琐步骤中解脱出来! > > https://www.liaoxuefeng.com/wiki/1022910821149312/1108898947791072 ## Vue 是如何实现响应式的? [`Object.defineProperty()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty) 一开始我觉得通过这个方法,就可以自定义 get() 和 set() 两个方法,这样当这个数据对象发生改变或者获取数据对象的时候,就可以先对数据对象进行处理,那么进行的是什么样的处理? Virtual DOM 在 [Vue源码系列一:Vue中的data是如何驱动视图渲染的?](https://juejin.im/post/5e06b4666fb9a0164f2956c0) 中介绍了 Vue 的源码,分析了 Vue 的初始化顺序,主要就是为了生成虚拟 DOM 树。 浏览器不是已经有了 DOM 树了嘛?Vue 这个DOM 树有什么不同的嘛? 其实没什么不同,因为这个树就是为了给浏览器进行渲染的,在这个树的 [官方文档](https://cn.vuejs.org/v2/guide/render-function.html#虚拟-DOM) 中,Vue 解释说使用这个虚拟 DOM 是 > Vue 通过建立一个**虚拟 DOM** 来追踪自己要如何改变真实 DOM。 这就联系起来了。通过 `Object.defineProperty()` 方法可以劫持数据对象,通过虚拟 DOM 可以实现 MVVM 中的ViewModel 功能,更加详细的东西就是在 set() 和 get() 里面了。 Vue 实现了一个订阅-发布模式。在这个模式中,Vue 实现了依赖收集和派发更新。 [VUE源码系列二:Vue响应式原理解析(附超详细源码注释和原理解析)](https://juejin.im/post/5e0dd467e51d45410f1232f5) 在上面的文章中,作者通过源码解析详细介绍了这个订阅-发布模式,可以将其分成三个模块, - Observer(劫持者) - 给对象的属性添加 getter 和 setter,用于依赖收集和派发更新 - 被用在 defineReactive 中,使得对象的每个属性都添加上 getter 和 setter 成为响应式。 - Dep(依赖收集) - 收集订阅者 `subs : Array`,用在 defineReactive 的重写 get 中为 `dep.depend ` 方法,为数据提供收集功能。 - 同时提供了发布更新的作用,在 defineReactive 的重写 set 中为 `dep.notify()` 方法。 - Watcher(观察者) - 在 Dep 发布更新后使用 `dep.notify()` 方法,循环执行 Dep 中的 `subs[i].update()` 使得数据相关的订阅者更新视图。 - 在 `watcher.update()` 中,使用了 `queuewatcher()` 方法,通过一个队列检查是否含有目标观察者,使得视图不会重复刷新。在队列中异步执行 `flushSchedulerQueue()` 方法,在此方法中排序 queue 确保父子组件的顺序更新,然后执行 `run()` 函数,使用新旧 value 值执行 Watcher 的回调 `cb.call()`。 ![响应系统源码版](https://image-static.segmentfault.com/162/469/1624694337-5a8fcee951762_articlex) ![clipboard.png](https://image-static.segmentfault.com/275/841/2758414791-5c99c0ef4e3c8_articlex) 这两张图片可以用来参考,来自 [从发布-订阅模式到Vue响应系统](https://segmentfault.com/a/1190000013338801) **总结一下** Vue 通过 `Object.defineProperty()` 实现对数据对象的劫持,通过 Observer 类定义 getter 和 setter,使得对象的所有属性都具备响应性,在 `getter()` 中通过依赖收集 Dep 的 `dep.depends()` 管理订阅者(当数据被 get 即视为被订阅),在 `setter()` 中通过 `dep.notify()` 函数向订阅了该数据对象的 Watcher 发送 `update()` ,Watcher 接收到更新信息后确定组件更新顺序然后映射到 Virtual DOM 中去,Virtual DOM 通过 `patch` 新旧节点,通过 `diff` 算法实现对新旧节点的对比,对同层的树节点进行比较而非对树进行逐层搜索遍历。然后生成真实 DOM。 这是 Vue2.x 的响应式原理,在 Vue3.0 中,官方重写了响应式的实现,改用 [`Proxy`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy) 和 [`Reflect`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect) 代替`Object.defineProperty()`。可以实现对更多数据的操作比如数组元素的劫持和更多的劫持方式。 ## Vue 的生命周期 ![Vue 实例生命周期](https://cn.vuejs.org/images/lifecycle.png) 如果是 keep-alive 的话还有两个:activated 和 deactivated keep-alive的生命周期 1. activated: 页面第一次进入的时候,钩子触发的顺序是created->mounted->activated 2. deactivated: 页面退出的时候会触发deactivated,当再次前进或者后退的时候只触发activated 那么 keep-alive 是什么? [`keep-alive`](https://cn.vuejs.org/v2/api/#keep-alive) 是 Vue 提供的一个抽象组件,`` 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和`` 相似, 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在组件的父组件链中。 这是 Vue 对象的生命周期,那么在 Vue 的父子组件中的生命周期是什么样的? **渲染过程:** 父组件挂载完成一定是等子组件都挂载完成后,才算是父组件挂载完,所以父组件的mounted在子组件mouted之后 父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted **子组件更新过程:** 1. 影响到父组件: 父beforeUpdate -> 子beforeUpdate->子updated -> 父updted 2. 不影响父组件: 子beforeUpdate -> 子updated **父组件更新过程:** 1. 影响到子组件: 父beforeUpdate -> 子beforeUpdate->子updated -> 父updted 2. 不影响子组件: 父beforeUpdate -> 父updated **销毁过程:** 父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed 所以不管是加载更新还是销毁都是父组件会等待子组件完后操作后才执行操作。 ## v-model 是什么? v-model 是经常用来双向绑定数据的一个语法,实际是一个 v-bind 和 v-on 结合的一个语法糖,等同于 ```javascript v-bind:value="msg" v-on:input="msg=$event.target.value" ``` [`v-bind`](https://cn.vuejs.org/v2/api/#v-bind) 动态地绑定一个或多个特性,或一个组件 prop 到表达式。 [`v-on`](https://cn.vuejs.org/v2/api/#v-on) 绑定事件监听器。事件类型由参数指定。表达式可以是一个方法的名字或一个内联语句,如果没有修饰符也可以省略。 也就是说 v-model 通过绑定数据和绑定数据并监听数据的变化,形成双向绑定。 [VUE源码系列七:v-model实现原理](https://juejin.im/post/5e3647816fb9a030133074b0) 这篇文章则是从源码角度去解释 v-model,并不是简单的转换为 v-bind 和 v-on。只是在最终结果上是一样的。 ## Vue 组件是什么? 简单说 Vue 组件是一个可以复用的 Vue 实例,通过将网页组件化,可以更加快捷的搭建更多的网页,所以 Vue 组件最主要的目的是复用,类似 Bootstrap 的组件库、Element-UI,他们其实都是组件库,通过自己设计的组件,形成组件库。 https://cn.vuejs.org/v2/guide/components.html 是 Vue 关于组件的文档,以及后续的深入组件内容。 ```JavaScript Vue.component('my-component-name', { /* ... */ }) ``` 通常来说,我们可以使用这样的格式来注册一个组件,这种格式下注册的组件是可以全局使用的,但是很明显当你的页面比较多的时候,全局使用组件会造成一定程度的冗余,所以你可以通过变量赋值,然后通过 Vue 实例的 `components` 属性来添加当前实例的组件。 ```javascript var ComponentA = { /* ... */ } var ComponentB = { /* ... */ } new Vue({ el: '#app', components: { 'component-a': ComponentA, 'component-b': ComponentB } }) ``` 在设计组件的时候,我们需要考虑这个组件需要什么,以及他最后会呈现什么样子。 ```javascript Vue.component('blog-post', { // 在 JavaScript 中是 camelCase 的 props: ['postTitle'], template: '

{{ postTitle }}

' }) ``` 通过 `props` 可以为组件添加属性,这个属性是可以由父组件传递给子组件使用的。可以理解为这是一种函数模式,通过传入参数,使得组件呈现不同的样子。 ```javascript Vue.component('base-checkbox', { model: { prop: 'checked', event: 'change' }, props: { checked: Boolean }, template: ` ` }) ``` 除了 `props` 以外,我们还可以给组件添加自定义的事件属性, 注意这里在 `template` 中使用的是 `v-bind` 和 `v-on` 而不是 `v-model` ,虽然他们所实现的目的是一样的,但是由于方便以及参数传输更加清楚,我们在使用这个组件的时候,通常使用 `v-model` ```html ``` 这样对应过来,`lovingVue` 属性绑定在了组件的 `checked` 属性上,并且在发生 change 事件时也会通过 `v-on` 方法返回到 `lovingVue` 属性上,实现子组件参数与父组件属性间的双重绑定。 更多的细节可以参考官方的详细文档,你就会发现组件其实和实例拥有几乎一模一样的属性和方法,包括`computed` `watch` 以及生命周期方法。 但是我们可以看到,在 `template` 的编写过程中,我们完全得不到语法提示,因为他是一个字符串属性。 所以更好的办法是我们把组件写成一个实例。通过局部引入来进行调用。也就是单文件组件。 在单文件组件中,组件编写更加的方便,同时数据上的传递思路也更加明确。 ### 关于插槽 插槽是 Vue 组件中一个很重要的方法,简单来说,插槽的目的是提供另外一种参数传递的方式, ```html Your Profile ``` 当你使用了一个 `navigation-link` 的组件时,正常情况下,组件间的数据都会被忽略,因为对于组件来说,中间的数据没有任何意义,如果不能正确的传递参数,中间的数据很容易打乱组件的结构。 ```html ``` 但是当组件中设定了一个 `slot` 标签后,父组件在使用子组件时,标签内部的信息都会被转移到 `slot` 标签中,而不是被直接抛弃。所以最后会渲染成: ```html Your Profile ``` 插槽中不仅可以填写这种纯文本,使用 HTML 代码甚至是使用 `{{raw}}` 格式包裹起来的数据属性也是可以的。 但是在使用 `{{raw}}` 格式传递数据时我们需要思考一个问题,我们是在使用子组件的时候,在子组件标签中间使用的 `{{raw}}` ,那么里面的属性我们自然是填入父组件的数据属性。 ```html {{ user.name }} ``` 我们可以用作用域来解释,即子组件的作用域,仅仅是在子组件中,甚至说在父组件中这个子组件的标签属性,也是数据父组件的作用域范围内,所以我们可以通过标签属性来传递父组件的数据到子组件中。只有数据传递过去后,才能算是子组件的数据属性,属于子组件的作用域范围。 更多详细的关于插槽的信息参考文档 https://cn.vuejs.org/v2/guide/components-slots.html 需要了解的包括有插槽的内容、作用域、默认内容、具名插槽、插槽prop、动态插槽名等相关知识。 ### 关于动态组件 ```html ``` 在经典 Vue 示例中,我们经常会看到 `` 这个标签,这个标签常常使用在 Vue 项目的入口 `App.vue` 中,用来对视图进行切换,在实际项目中,当父组件动态调用子组件的时候,通常来说,在切换子组件后子组件会被销毁。但是当我们添加 `` 标签后,被切换隐藏掉的子组件并不会被销毁,这样可以在特定场景下, 提升网页反应速度。 在测试的时候发现在 `` 外也是可以使用 `` 来实现同样的效果。 ## [Vue Router](https://router.vuejs.org/zh/) ## [Vuex](https://vuex.vuejs.org/zh/) ## [Vue SSR](https://ssr.vuejs.org/zh/) # React 整理 # Webpack 整理 # Gulp 整理
格式传递数据时我们需要思考一个问题,我们是在使用子组件的时候,在子组件标签中间使用的 --- title: 前端学习之路 date: 2020-01-17 17:38:36 tags: 学习前端 categories: 学习 description: 以后就开始学习前端了,等到年龄大了再转全栈什么的。感觉很多文章看了,但是还是不知道自己在学什么。看文章看文档看得我头都大。在想暂时先不看 Vue 相关了,再看看 JS 和 CSS,CSS实在是找不到什么合适的教程,但是 CSS 本身又是一个很复杂的东西,各个属性之间是相互关联的。 --- # 前端资源 [gulp](https://www.gulpjs.com.cn/) 是一个 js 自动化工具。 github 上的一些资料 - [Awsome-Front-End-learning-resource](https://github.com/helloqingfeng/Awsome-Front-End-learning-resource) [谷歌字体](https://www.font.im/) [Open Graph protocol](http://ogp.me/) [MDN 学习 Web 开发](https://developer.mozilla.org/zh-CN/docs/Learn) [掘金](https://juejin.im/) 是真的挺不错的一个社区。好多干货文章。 ## 相关文章 [^]: 收藏的文章都放这里好了。 [一文完全吃透 JavaScript 继承(面试必备良药)](https://juejin.im/post/5e5339b46fb9a07cb83e20d4) > 这篇文章详细讲解了原型相关的知识,包括原型链、ES5 ES6中的继承实现以及原理。帮助理解 JavaScript 面向对象方向的继承原理和构造器相关概念。 [[译] 在 async/await 中更好的处理错误](https://juejin.im/post/5e535624518825496e784ccb) > 这篇文章详细介绍了在 JavaScript 中的异步问题,介绍了回调地狱、Promise、async/await,以及他们的错误处理机制。 [中高级前端面试题(万字长文)](https://juejin.im/post/5e4c0b856fb9a07ccb7e8eca) > 面试题以及知识框架整理。 [理解Javascript的正则表达式](https://juejin.im/post/5e53a6d3f265da571671080f) > 正则表达式是在应用中相当重要的一部分,需要去理解记忆并多加练习。 [轻松理解JS中的面向对象,顺便搞懂prototype和__proto__](https://juejin.im/post/5e50e5b16fb9a07c9a1959af) > 理解 JavaScript 中的原型、原型链、构造器等相关概念。 [2020年你不能不知道的webpack基本配置](https://juejin.im/post/5e532b116fb9a07ce152c31a) > 了解 webpack 工具,官方文档 https://www.webpackjs.com/concepts/ [前端下载文件的5种方法的对比](https://juejin.im/post/5e50fa23518825494b3cccd7) > 关于前端的一些典型下载文件实现案例。 [2020年大厂面试指南 - Vue篇](https://juejin.im/post/5e4d24cce51d4526f76eb2ba) > 看大厂面试题对于理解相关知识也是很有用的。重要的是不要死记硬背,因为很多答案并不是从原理讲起,或者讲了看不懂,需要结合官方文档和实际上手去理解。 - [Vue源码系列一:Vue中的data是如何驱动视图渲染的?](https://juejin.im/post/5e06b4666fb9a0164f2956c0) - [VUE源码系列二:Vue响应式原理解析(附超详细源码注释和原理解析)](https://juejin.im/post/5e0dd467e51d45410f1232f5) - [VUE源码系列三:nextTick原理解析(附源码解读)](https://juejin.im/post/5e1ae62b51882526b831f1cb) - [VUE源码系列四:计算属性和监听属性,到底该用谁?](https://juejin.im/post/5e21619ce51d4552455a8896) - [VUE源码系列五:组件是怎样生成的?(附详细源码解析)](https://juejin.im/post/5e2804e1e51d453c9e155f08) - [VUE源码系列六:编译原理](https://juejin.im/post/5e2cfd695188252c5232ae3b) - [VUE源码系列七:v-model实现原理](https://juejin.im/post/5e3647816fb9a030133074b0) > 非常感谢这位作者,关于 Vue 的实现原理可以参考上面的连载专栏。不需要去深入算法,但是需要去了解一些基本的原理。同时自己可以尝试做一个类似的例如双重绑定、响应式等等。 > > 这个源码系列好多都没看懂, 只能看个大概。感觉文档越看越浮躁了。 - [(1.8w字)负重前行,前端工程师如何系统练习数据结构和算法?【上】](https://juejin.im/post/5e2f88156fb9a02fdd38a184) > 作为计算机相关行业人员对于数据结构和算法是必须要掌握一些的。 [剖析一些经典的CSS布局问题,为前端开发+面试保驾护航](https://juejin.im/post/5da282015188257d2a1c9e1d) > 跟着某些作者看可以找到一些没有推荐过的文章。有的还没看过,先留着。 [前端面试常考的手写代码不是背出来的!](https://juejin.im/post/5e57048b6fb9a07cc845a9ef) > 这篇文章是对于代码规范和一些功能函数的实例研究,很基础但也很有用。 [深入理解JS的原型和原型链](https://juejin.im/post/5e54d9e86fb9a07c944c932a) > 深入理解原型和原型链之间的关系,然后引申出执行上下文和 this 的相关概念。 [vue 248个知识点(面试题)为你保驾护航](https://juejin.im/post/5d153267e51d4510624f9809) > 了解 Vue 的相关知识点和原理。 [🔥(已更新3.1w字)《大前端吊打面试官系列》 之 ES6 精华篇(2020年)](https://juejin.im/post/5e4943d0f265da57537eaba9) > 深入理解 ES6 。 - [4W字长文带你深度解锁Webpack系列(上)](https://juejin.im/post/5e5c65fc6fb9a07cd00d8838) > 在前端开发环境下,对于 Webpack、gulp 等工具的理解使用对于提升开发效率、专注前端页面是有好处的。 [面试题:说说事件循环机制(满分答案来了)](https://juejin.im/post/5e5c7f6c518825491b11ce93) > 关于事件循环机制。 [前端工程师的自我修养-关于 Babel 那些事儿](https://juejin.im/post/5e5b488af265da574112089f) > Babel 是可以对代码进行向后兼容性编译的编译器,使得代码在不同环境下依然可以稳定运行。 [你再不知道怎么解决跨域,我就真的生气了](https://juejin.im/post/5e578a5f518825494707e4bb) > 关于跨域相关的知识点。跨域指的是由于浏览器出于安全考虑所设置的同源策略,如果协议、域名、端口有一项不同就会产生跨域问题,而跨域问题所带来的是安全问题,解决跨域不仅仅是让前端可以成功访问到跨域资源,更重要的是理解背后的安全隐患。 [记好这 24 个 ES6 方法,用来解决实际开发的 JS 问题](https://juejin.im/post/5e5ef2f9f265da57685dc9c1) > ES6 相关的对于实际问题的解决办法。 [深入vue响应式原理(包含vue3.0)](https://juejin.im/post/5e5b43b1f265da5716710e4c) > 看这个是想了解关于 Vue 2.0 和 3.0 之间的变化。 [2020 前端面试 | 第一波面试题总结](https://juejin.im/post/5e3d898cf265da5732551a56) > 实际面试题。 [🔥 动画:《大前端吊打面试官系列》 之原生 JavaScript 精华篇](https://juejin.im/post/5e34d19de51d4558864b1d1f) > 关于 JavaScript 相关的基础知识理解。 [前端也能学算法:由浅入深讲解贪心算法](https://juejin.im/post/5e575e02f265da573b0dad5f) > 关于贪心算法的讲解。 [「 如何优雅的使用VUE? 」不可不知的VUE实战技巧](https://juejin.im/post/5e475829f265da57444ab10f) > Vue 的实战技巧。 [2020年了,再不会webpack敲得代码就不香了(近万字实战)](https://juejin.im/post/5de87444518825124c50cd36) > webpack 相关知识。 [【建议星星】要就来45道Promise面试题一次爽到底(1.1w字用心整理)](https://juejin.im/post/5e58c618e51d4526ed66b5cf) > 关于 Promise 的各种面试题型。加深对于异步的理解和处理。 [从手写Promise到async/await(接近6千字,建议看一下)](https://juejin.im/post/5e5f52fce51d4526ea7efdec) > 关于异步和生成器、构造器的原理讲解和一些组合方法。 [【建议👍】再来40道this面试题酸爽继续(1.2w字用手整理)](https://juejin.im/post/5e6358256fb9a07cd80f2e70) > 关于 this 的相关题型。 [vue 组件通信看这篇就够了(12种通信方式)](https://mp.weixin.qq.com/s?__biz=Mzg2NTA4NTIwNA==&mid=2247484468&idx=1&sn=3c309945992fe4f0c6276d91d7cb67b8&chksm=ce5e364ff929bf59ae686e3fa2412632348d6e190054e0b83bed11b5fd851ebbe8d326b5caf0&token=1424393752&lang=zh_CN#rd) > 详细介绍了不同的父子组件间的通信方式。 [学习 BFC (Block Formatting Context)](https://juejin.im/post/59b73d5bf265da064618731d) > 了解在 CSS 中的 BFC 的相关概念。 [【第1250期】彻底理解浏览器的缓存机制](https://mp.weixin.qq.com/s/d2zeGhUptGUGJpB5xHQbOA) > 了解浏览器的缓存机制 [【译】Async/await和Promise的不为人知的秘密](https://juejin.im/post/5e57feede51d4526dd1ea7e9) > 关于 Async 和 Promise 的性能上的区别。 [learnVue](https://github.com/answershuto/learnVue) > github 上的 Vue 学习文章 [Vue3响应式系统源码解析-单测篇](https://juejin.im/post/5d9c9a135188252e097569bd) > 关于 Vue3 的响应式实现。 [【面试篇】寒冬求职季之你必须要懂的原生JS(上)](https://juejin.im/post/5cab0c45f265da2513734390) > 经典面试题和解析 [【面试篇】寒冬求职季之你必须要懂的原生JS(中)](https://juejin.im/post/5cbd1e33e51d45789161d053) > 经典面试题和解析 [【面试篇】寒冬求职之你必须要懂的Web安全](https://juejin.im/post/5cd6ad7a51882568d3670a8e) > 关于 Web 安全的相关面试题和解析 [「进击的前端工程师」浏览器中JavaScript的事件循环](https://juejin.im/post/5d2036106fb9a07eb15d76e9) > 深入了解 JavaScript 引擎的事件机制,后面的例题也很值得思考。 # 知识框架 - HTML - HTML 不同版本的差异 - HTML5 - CSS - CSS 不同版本的差异 - CSS3 - Sass - [JavaScript 廖雪峰](https://www.liaoxuefeng.com/wiki/1022910821149312) - [TypeScript](https://www.tslang.cn/index.html) - Node.js - jQuery(老掉牙了) - Ajax(也老掉牙了,了解XMR就行) - Webpack - Gulp - [Vue](https://cn.vuejs.org/) - [React](https://react.docschina.org/) - [Angular](https://angular.cn/) - ES5 - ES6 # HTML 整理 HTML 只是一个标记性的语言,本身没有什么学习难度,HTML5 是新一代的 HTML 标准,实现了一些新的元素和属性, 改进了本地存储并且添加了很多语义元素,使得代码或者页面更加语义化。语义化的好处在于对代码的理解更加简单,同时也为视力障碍人士改善了网页的可阅读性。 关于更多的细节参考 https://www.runoob.com/html/html-tutorial.html ## HTML DOM 关于 DOM 的理解可以参考 https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-tree-construction DOM 是文档对象模型的缩写,按照对对象的理解,DOM 就是浏览器生成的这个页面的对象,每一个 HTML 元素就是一个节点,通过 HTML 的嵌套关系生成子节点和父节点关系的树,元素的属性也是元素对应节点的子节点。 DOM 的树并不是普通的二叉树或者特殊的树 ![DOM 节点关系](https://www.w3school.com.cn/i/dom_navigate.gif) 关系其实还是挺复杂的。不过 DOM 提供了各种方法实现了对于节点或者元素的选择、修改。 获取目标元素节点是对于 JavaScript 脚本比较重要的一环,获取到节点后即可对节点所产生的事件进行脚本控制。 当然在 Vue 或者 React 等框架中有对应的方法可以更加方便的获取节点添加事件。 ### CSS DOM css 也是有一个 DOM 树的通过将两棵树合并生成一个渲染树供浏览器对页面进行渲染。 # CSS 整理 CSS 的作用是给 HTML 元素加上样式,同时又可以多个样式层叠,所以叫层叠样式表。 CSS 的内容也是比较复杂的。细节参考 https://www.runoob.com/css/css-tutorial.html 需要理解不同元素类型能做的事和不能做的事,例如行内元素的宽度和高度是不能设置的。根据内容自动。 不过对于网页 CSS 设计来说,例如 Bootstrap 或者 Element 等前端组件库已经设定好了很多实用的样式。可以在此基础上进行自定义。关于页面的布局也是有对应的解决方案,需要理解的还是元素的类型, 参考 https://www.runoob.com/cssref/pr-class-display.html CSS 相关的教程好少啊,但是感觉各种属性对应的布局却很麻烦。 ## CSS 字体相关属性 [w3school 页面](https://www.w3school.com.cn/cssref/pr_font_font.asp) | font-style | 规定字体样式。参阅:[font-style](https://www.w3school.com.cn/cssref/pr_font_font-style.asp) 中可能的值。 | | --------------------- | ------------------------------------------------------------ | | font-variant | 规定字体异体。参阅:[font-variant](https://www.w3school.com.cn/cssref/pr_font_font-variant.asp) 中可能的值。 | | font-weight | 规定字体粗细。参阅:[font-weight](https://www.w3school.com.cn/cssref/pr_font_weight.asp) 中可能的值。 | | font-size/line-height | 规定字体尺寸和行高。参阅:[font-size](https://www.w3school.com.cn/cssref/pr_font_font-size.asp) 和 [line-height](https://www.w3school.com.cn/cssref/pr_dim_line-height.asp) 中可能的值。 | | font-family | 规定字体系列。参阅:[font-family](https://www.w3school.com.cn/cssref/pr_font_font-family.asp) 中可能的值。 | | caption | 定义被标题控件(比如按钮、下拉列表等)使用的字体。 | | icon | 定义被图标标记使用的字体。 | | menu | 定义被下拉列表使用的字体。 | | message-box | 定义被对话框使用的字体。 | | small-caption | caption 字体的小型版本。 | | status-bar | 定义被窗口状态栏使用的字体。 | ## CSS 文本相关属性 | 属性 | 描述 | | :----------------------------------------------------------- | :---------------------------------------------------------- | | [color](https://www.w3school.com.cn/cssref/pr_text_color.asp) | 设置文本颜色 | | [direction](https://www.w3school.com.cn/cssref/pr_text_direction.asp) | 设置文本方向。 | | [line-height](https://www.w3school.com.cn/cssref/pr_dim_line-height.asp) | 设置行高。 | | [letter-spacing](https://www.w3school.com.cn/cssref/pr_text_letter-spacing.asp) | 设置字符间距。 | | [text-align](https://www.w3school.com.cn/cssref/pr_text_text-align.asp) | 对齐元素中的文本。 | | [text-decoration](https://www.w3school.com.cn/cssref/pr_text_text-decoration.asp) | 向文本添加修饰。 | | [text-indent](https://www.w3school.com.cn/cssref/pr_text_text-indent.asp) | 缩进元素中文本的首行。 | | text-shadow | 设置文本阴影。CSS2 包含该属性,但是 CSS2.1 没有保留该属性。 | | [text-transform](https://www.w3school.com.cn/cssref/pr_text_text-transform.asp) | 控制元素中的字母。 | | unicode-bidi | 设置文本方向。 | | [white-space](https://www.w3school.com.cn/cssref/pr_text_white-space.asp) | 设置元素中空白的处理方式。 | | [word-spacing](https://www.w3school.com.cn/cssref/pr_text_word-spacing.asp) | 设置字间距。 | ## 关于各个布局 https://zh.learnlayout.com/ 可以用来了解布局相关的知识。 布局其实就是把网页根据内容进行一个规划。 网页的内容永远都是不确定的,那么布局当然是要怎么好看怎么来。 了解布局首先需要了解 `display` 这个属性。这个属性决定了 HTML 元素在 CSS 层面上的性质。每个 tag 都有默认的 `display` 属性,比如常见的 `h1` `div` `p` `form ` 就是块级元素,而 `a` `b` `i` `span` `select` `img` `input` 都是行内元素。 通过不同的元素特性,页面元素也会产生不同的显示效果。比如在[弹性盒子](https://developer.mozilla.org/zh-CN/docs/Learn/CSS/CSS_layout/Flexbox)中有很多新的特性和 CSS 样式,可以更加方便快捷的设定垂直居中效果,而在块级元素中就很难做到。大致上来说网页的布局也就分为一列、二列、三列,通过样式的叠加形成不同的网页显示效果。至于说的什么圣飞布局、双飞翼布局感觉没啥意思,究其原理还是浮动、弹性盒子、网格、定位这类基础属性,只能说是别人设计出来的网页布局就是了。重要的还是内容的排列。通过 `display` 属性获得不同的文字、图片、内容块的排列效果。 ## 关于盒模型 盒模型是用来计算元素大小的一个标准模型,完整适用于块元素,部分适用于行内元素。盒模型还有一个替代模型IE盒模型,在IE盒模型中,所有的宽度和高度都是可见的,内容的宽度包括了Padding的宽度。 ![Diagram of the box model](https://media.prod.mdn.mozit.cloud/attachments/2019/03/19/16558/29c6fe062e42a327fb549a081bc56632/box-model.png) > 一个被定义成块级的(block)盒子会表现出以下行为: > > - 盒子会在内联的方向上扩展并占据父容器在该方向上的所有可用空间,在绝大数情况下意味着盒子会和父容器一样宽 > - 每个盒子都会换行 > - [`width`](https://developer.mozilla.org/zh-CN/docs/Web/CSS/width) 和 [`height`](https://developer.mozilla.org/zh-CN/docs/Web/CSS/height) 属性可以发挥作用 > - 内边距(padding), 外边距(margin) 和 边框(border) 会将其他元素从当前盒子周围“推开” > 如果一个盒子对外显示为 `inline`,那么他的行为如下: > > - 盒子不会产生换行。 > - [`width`](https://developer.mozilla.org/zh-CN/docs/Web/CSS/width) 和 [`height`](https://developer.mozilla.org/zh-CN/docs/Web/CSS/height) 属性将不起作用。 > - 内边距、外边距以及边框会被应用但是不会把其他处于 `inline` 状态的盒子推开。 > 一个元素使用 `display: inline-block`,实现我们需要的块级的部分效果: > > - 设置`width` 和`height` 属性会生效。 > - `padding`, `margin`, 以及`border` 会推开其他元素。 > > 但是,它不会跳转到新行,如果显式添加`width` 和`height` 属性,它只会变得比其内容更大。 https://developer.mozilla.org/zh-CN/docs/Learn/CSS/CSS_layout/Flexbox 详细介绍了 `display:flexible` 弹性盒子的 flex 模型 ![flex_terms.png](https://developer.mozilla.org/files/3739/flex_terms.png) > - **主轴(main axis)**是沿着 flex 元素放置的方向延伸的轴(比如页面上的横向的行、纵向的列)。该轴的开始和结束被称为 **main start** 和 **main end**。 > - **交叉轴(cross axis)**是垂直于 flex 元素放置方向的轴。该轴的开始和结束被称为 **cross start** 和 **cross end**。 > - 设置了 `display: flex` 的父元素(在本例中是 [`
`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/section))被称之为 **flex 容器(flex container)。** > - 在 flex 容器中表现为柔性的盒子的元素被称之为 **flex 项**(**flex item**)(本例中是 [`article`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/article) 元素。 ## 关于网页字体 # JavaScript 整理 关于语法就和其他语言是一样的,变量、运算、数据类型、函数、对象、数组、循环、作用域等等。 不一样的是,由于 JavaScript 是运行在浏览器上的。所以很多功能代码都是已经实现了的。不需要像其他语言一样需要自己写类。由于本身是面向对象的,JavaScript 的所有变量都是一个对象。对于 JavaScript 的学习需要去了解背后的原理。 由于浏览器的版本大家可能都不一样,所以在使用 JavaScript 特性的时候需要考虑版本问题。了解 ES5 和 ES6 的区别。可以使用 https://caniuse.com/ 来了解特性的支持情况。 细节参考 https://www.w3school.com.cn/js/index.asp ## 关于原型链 JavaScript 是解释型脚本语言,同时也是面向对象的语言,JavaScript 的对象是动态的属性“包”,会有一个`__proto__`的私有属性,该属性会指向构造函数的原型对象。 ```javascript // 让我们从一个自身拥有属性a和b的函数里创建一个对象o: 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 ``` 来自 MDN https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain 所以当一个对象通过 `new` 关键字从其他的对象“实例化”,会将先生成一个空对象,通过 [Object.setPrototypeOf()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf)原生函数将实例对象添加到原型链上。 ```javascript function polyNew(source, ...arg) { // 创建一个空的简单JavaScript对象(即{}) let newObj = {}; // 链接该对象(即设置该对象的构造函数)到另一个对象 Object.setPrototypeOf(newObj, source.prototype); // 将步骤1新创建的对象作为this的上下文 ; const resp = source.apply(newObj, arg); // 判断该函数返回值是否是对象 if (Object.prototype.toString.call(resp) === "[object Object]") { // 如果该函数没有返回对象,则返回this。 return resp } else { // 如果该函数返回对象,那用返回的这个对象作为返回值。 return newObj } } 作者:dellyoung 链接:https://juejin.im/post/5e54d9e86fb9a07c944c932a 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 ``` 上面这个地址的文章写的非常详细。可以深入理解关于原型 Null 、原型链、This 的相关原理。 **`instanceof`** **运算符**用于检测构造函数的 `prototype` 属性是否出现在某个实例对象的原型链上。 实现起来也是比较简单的。顺藤摸瓜就行了。 **this 是什么?** 理解 **this** 就需要理解 JavaScript 的执行上下文和调用栈,可以看上面的文章进行详细的步进的了解。 了解了执行上下文后,就知道了执行上下文包括了变量环境、词法环境、outer、this。 而这个 `this` 就是指向当下执行环境的一个指针。在浏览器环境下指的是 window。 ```javascript 执行下面代码: function foo(){ console.log(this) // window } foo() // 可以看到输出了window,说明在默认情况下调用一个函数,其执行上下文中的 this 也是指向 window 对象的。 // 可以认为 JavaScript 引擎在执行foo()时,将其转化为了: function foo(){ console.log(this) // window } window.foo.call(window) ``` 关于这一段我理解的是由于 `foo()` 函数是一个函数而不是一个对象,`this` 指向这个函数没有什么意义,于是默认情况下会将它指向上一层的执行上下文**对象**即 `window` window 在浏览器中表示浏览器的窗口。 ```javascript function foo(){ console.log(this) // window } foo() //Window {parent: Window, opener: Window, top: Window, length: 0, frames: Window, …} a = new foo() //foo {} // __proto__: // constructor: ƒ foo() // __proto__: Object ``` 可见,当我们把它实例化的时候,会通过 `new` 中的构造器,将 `foo()`链接到 `a` 的原型上。这个时候的 `this` 指的就是 `foo()` 了,当然这个 `foo()` 并不是说 `foo()`函数,而是指将`foo()`实例化的 `a` 对象。如果再实例化一个 `b ` ,两者并不会相等。 或许我可以理解为 **this 指代的永远是一个对象,而非一个函数,如果在函数中输出一个 this 而这个函数并非是某个对象的方法,那么 this 会重定向到 window 对象** 关于执行上下文还有配套的 `call`、`apply`、`bind` 三个方法。详情参考https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/apply# 主要用处就是设置 `this` ,在调用一个存在的函数时,你可以为其指定一个 `this` 对象。 `this` 指当前对象,也就是正在调用这个函数的对象。 使用 `apply`, 你可以只写一次这个方法然后在另一个对象中继承它,而不用在新对象中重复写该方法。 ```JavaScript function foo(){ console.log(this) // window } foo() 复制代码 ``` 可以看到输出了window,说明在**默认情况**下调用一个函数,其执行上下文中的 this 也是指向 window 对象的。 ```javascript function f(fn, x) { console.log('into') if (x < 1) { f(g, 1); } else { fn(); } function g() { console.log('x' + x); } } function h() { } f(h, 0) // into // into // x0 作者:小黎也 链接:https://juejin.im/post/5e523e726fb9a07c9a195a95 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 ``` 这个面试题的逻辑看上去很简单,但是却有效的解释了执行上下文是如何影响程序的,同时也引出了作用域链的一个概念,上下文上下文,则说明是有上文也是有下文的。比如全局执行上下文就肯定是其他执行上下文的上文,那么参数在不同的执行上下文之间调用,每个参数不同的作用域,就形成了作用域链,和原型链是异曲同工的。如果一个函数在执行中找不到某个参数,就会顺着作用域链往上找。 g 函数中的 x 变量是引用父级的,而 f 函数执行了两次,x 变量依次为 0 1,在 f(h,0) 这个函数执行的时候,这个函数的作用域中的 x=0,这个时候 g 函数中引用的 x 就是当前执行上下文中的 x=0 这个变量,但这个函数还没被执行,接着到了 f(g, 1) 执行,这一层执行上下文中的 x=1 ,但注意两次 f 执行的作用域不是同一个对象,是作用域链上两个独立的对象,最后到了 fn() ,这个 fn 是一个参数,也就是在 f(h,0) 执行的时候 g 函数,那么 g 函数在这里被执行,g 打印出来的 x 就是 0 。 如果最后一段理解起来困难的话可以这样想,最后执行 f(g,1)的时候,执行的 fn() 并不是当下的 g(),而是传入的 g(),而这个传入的 g() 是来自 f(h,0) 这个环境下的。所以 x=0. 或许可以对这个题进行一个改变,如果 g() 函数的定义在是 f() 函数开头会怎么样?结果还是一样的,因为不管 g() 函数的位置如何变,始终是运行的 f(h,0) 环境下的 g()。 ## 关于变量提升 https://developer.mozilla.org/zh-CN/docs/Glossary/Hoisting 变量提升(Hoisting)被认为是, Javascript中执行上下文 (特别是创建和执行阶段)工作方式的一种认识。 **JavaScript 仅提升声明,而不提升初始化**。 变量提升可能是个好东西,因为它能让你的程序跑起来。但也可能是个坏东西,因为会养成到处声明的坏习惯。 ```javascript console.log(num); // Returns undefined var num; num = 6; // If you declare the variable after it is used, but initialize it beforehand, it will return the value: num = 6; console.log(num); // returns 6 var num; ``` 正确的编写原则是先声明再初始化,最后才是运用变量。 在 ES6 中的 let 和const不存在变量提升 var: 解析器在对 js 解析时,会将脚本扫描一遍,将变量的声明提前到代码块的顶部,赋值还是在原先的位置,若在赋值前调用,就会出现暂时性死区,值为 `undefined` let const:不存在在变量提升,且作用域是存在于块级作用域下,所以这两个的出现解决了变量提升的问题,同时引用块级作用域。 注:变量提升的原因是为了解决函数互相调用的问题。 ## 关于 Promise 和 Async/Await 在 MDN 中关于 Promise 是这样介绍的 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises > [`Promise`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise) 是一个对象,它代表了一个异步操作的最终完成或者失败。本质上,Promise 是一个被某些函数传出的对象,我们附加回调函数(callback)使用它,而不是将回调函数传入那些函数内部。 由于 JavaScript 是单线程的,所以我们需要考虑到网络的影响,如果在一个图片还没加载好的时候就去使用这个资源,是会造成图片缺失的,所以提出了异步,所谓异步就是使有关联性的程序按照顺序运行,防止资源加载未完成等问题。 我对于 `Promise` 的理解是,首先它的提出是为了解决回调函数过于复杂和冗余的问题。 ```javascript doSomething(function(result) { doSomethingElse(result, function(newResult) { doThirdThing(newResult, function(finalResult) { console.log('Got the final result: ' + finalResult); }, failureCallback); }, failureCallback); }, failureCallback); ``` 回调函数本身是把一个函数作为另一个函数的参数,然后在函数中调用这个函数达到异步的效果。 ```javascript doSomething().then(function(result) { return doSomethingElse(result); }) .then(function(newResult) { return doThirdThing(newResult); }) .then(function(finalResult) { console.log('Got the final result: ' + finalResult); }) .catch(failureCallback); ``` 这个示例通过返回 Promise 对象,实现了 Promise 对象的 `then` 链式写法,then里的参数是可选的,`catch(failureCallback)` 是 `then(null, failureCallback)` 的缩略形式。如下所示,我们也可以用[箭头函数](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_functions)来表示: ```javascript doSomething() .then(result => doSomethingElse(result)) .then(newResult => doThirdThing(newResult)) .then(finalResult => { console.log(`Got the final result: ${finalResult}`); }) .catch(failureCallback); ``` 上面的示例来自 MDN 的 Promise 文档 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises 需要注意的是,这个示例并不能在 console 中跑起来,因为没有具体的定义 doSomething(),在实际使用中,这几个函数都是需要有返回值的 ```javascript const doSomething = () => { return new Promise((resolve,reject)=>{ console.log("doSomething") resolve("doSomething end") }) } const doSomethingElse = (argu) => { return new Promise((resolve,reject)=>{ console.log(argu) resolve("doSomethingElse end") }) } const doThirdThing = (argu) => { return new Promise((resolve,reject)=>{ console.log(argu) resolve("doThirdThing end") }) } // doSomething() // .then(result => doSomethingElse(result)) // .then(newResult => doThirdThing(newResult)) // .then(finalResult => { // console.log(`Got the final result: ${finalResult}`); // }) // .catch(()=>{console.log("fail")}); // doSomething // doSomething end // doSomethingElse end // Got the final result: doThirdThing end // Promise {: undefined} ``` 当一个函数返回 Promise 对象的时候,可以把它当做一个异步函数,后面的 `.then` 方法是 Promise 对象的方法之一,用来接收上一个 Promise 在 resolve 的时候的消息,作为自定义的变量如 result 参与 `.then` 方法中的程序。由于几个函数都是返回的 Promise 对象,形成了一条 Promise 链。这条链是按照顺序异步运行的。`.catch` 方法是用来获取在前面 Promise 链中的错误,进行合适的错误抛出和处理。 ```javascript new Promise((resolve, reject) => { console.log('初始化'); resolve(); }) .then(() => { throw new Error('有哪里不对了'); console.log('执行「这个」”'); }) .catch(() => { console.log('执行「那个」'); }) .then(() => { console.log('执行「这个」,无论前面发生了什么'); }); // 初始化 // 执行“那个” // 执行“这个”,无论前面发生了什么 ``` `.catch` 也是返回的 [Promise 对象](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch),可以继续连接 `.then` 方法,使得程序在接收到错误后可以继续正常运行。 ```javascript async function foo() { try { let result = await doSomething(); let newResult = await doSomethingElse(result); let finalResult = await doThirdThing(newResult); console.log(`Got the final result: ${finalResult}`); } catch(error) { failureCallback(error); } } ``` 使用 Async/Await 同样可以达成异步操作,并且代码看上去更加的简洁。Async/Await 其实是 Promise 的一个语法糖,其原理还是 Promise, > `async`/`await`的目的是简化使用多个 promise 时的同步行为,并对一组 `Promises`执行某些操作。正如`Promises`类似于结构化回调,`async`/`await`更像结合了generators和 promises。 关于 Async/Await 的错误处理机制可以参考 https://juejin.im/post/5e535624518825496e784ccb 继续深入一下 Promise - [`Promise.all()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) > **`Promise.all(iterable)`**方法返回一个 [`Promise`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise) 实例,此实例在 `iterable` 参数内所有的 `promise` 都“完成(resolved)”或参数中不包含 `promise` 时回调完成(resolve);如果参数中 `promise` 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 `promise` 的结果。 > > ```javascript > var p1 = Promise.resolve(3); > var p2 = 1337; > var p3 = new Promise((resolve, reject) => { > setTimeout(() => { > resolve("foo"); > }, 100); > }); > > Promise.all([p1, p2, p3]).then(values => { > console.log(values); // [3, 1337, "foo"] > }); > ``` - [`Promise.allSettled()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled) > 该**`Promise.allSettled()`**方法返回一个在所有给定的promise已被决议或被拒绝后决议的promise,并带有一个对象数组,每个对象表示对应的promise结果。 > > ```javascript > const promise1 = Promise.resolve(3); > const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo')); > const promises = [promise1, promise2]; > > Promise.allSettled(promises). > then((results) => results.forEach((result) => console.log(result.status))); > > // expected output: > // "fulfilled" > // "rejected" > ``` - [`Promise.any()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/any) > `Promise.any()` 接收一个[`Promise`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise)可迭代对象,只要其中的一个 `promise` 完成,就返回那个已经有完成值的 `promise` 。如果可迭代对象中没有一个 `promise` 完成(即所有的 `promises` 都失败/拒绝),就返回一个拒绝的 `promise`,返回值还有待商榷:无非是拒绝原因数组或`AggregateError`类型的实例,它是 [`Error`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Error) 的一个子类,用于把单一的错误集合在一起。本质上,这个方法和[`Promise.all()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/all)是相反的。 - [`Promise.prototype.catch()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch) - [`Promise.prototype.finally()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally) - [`Promise.prototype.then()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/then) - [`Promise.race()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/race) > **`Promise.race(iterable)`** 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。 > > ```javascript > const promise1 = new Promise(function(resolve, reject) { > setTimeout(resolve, 500, 'one'); > }); > > const promise2 = new Promise(function(resolve, reject) { > setTimeout(resolve, 100, 'two'); > }); > > Promise.race([promise1, promise2]).then(function(value) { > console.log(value); > // Both resolve, but promise2 is faster > }); > // expected output: "two" > ``` - [`Promise.reject()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/reject) - [`Promise.resolve()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve) Promise 对象的所有方法就是这些了。 理解 Async/Await 除了理解 Promise 之外,我们知道它是 Promise 的一个语法糖。结合了 Promise 和 constructor。 [AsyncFunction](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/AsyncFunction#AsyncFunction_原型对象) 就是 [`Async/Await`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/async_function) 的构造函数。 ```javascript function resolveAfter2Seconds(x) { return new Promise(resolve => { setTimeout(() => { resolve(x); }, 2000); }); } var AsyncFunction = Object.getPrototypeOf(async function(){}).constructor; var a = new AsyncFunction('a', 'b', 'return await resolveAfter2Seconds(a) + await resolveAfter2Seconds(b);'); a(10, 20).then(v => { console.log(v); // 4 秒后打印 30 }); ``` 这个就是使用 AsyncFunction 实现的一个异步函数。不过看上去好像跟普通的 Promise 没什么不同,我记得看过一个实现 Async 构造器的文章,~~找不到了。回头再写这里吧。~~ ## 关于包装对象 在 JavaScript 中变量一般分为基本数据类型和对象类型。基本数据类型包括 Number、String、Boolean,在我们定义一个数字或者字符串的时候,它的类型 `typeof()` 出来就是普通的数据类型,但是我们可以使用 `.length` `.toString()` 等对象的方法,原因是 JavaScript 在执行的时候会使用数据类型对象包装原始数据,使其成为一个对象然后使用相关方法和属性。但是在输出后就会将其销毁,并不会改变原生数据的类型。 ## 关于 Null 和 Undefined ```javascript undefined == null true undefined === null false ``` 两个数据类型都是表示为空,但是具体的含义不太一样,Null 表示数据有值,值的内容为空,这个空并不是 0 或者一个空字符串,所以可以使用 Null 来初始化变量,Undefined 表示数据没有值,当变量仅被声明而没有被初始化的时候即是 Undefined。 经典一点的问题比如一个空数组,如何判定为空,由于 JavaScript 的对象类型,数组是对象的一种,他的原型是 Array 原型对象。 ```javascript a=[] typeof(a) "object" a===null false a==null false ``` 需要使用原型对象的 `.length` 属性来判断数组中是否有值。 ```javascript a=[1,2,3] (3) [1, 2, 3] a.length 3 a.length = 4 4 a (4) [1, 2, 3, empty] ``` ## 关于 JavaScript 执行机制和事件循环机制 JavaScript 引擎是 JavaScript 的解释器,类似于 Python,他们都是解释性语言。JavaScript 在最初被开发出来时,是为了用于浏览器的,所以注定了 JavaScript 是一个单线程的语言。那么当 JavaScript 需要处理两个事件的时候,如果一个事件是一个需要等待的事件,那么他会挂起等待的任务,先运行后面的任务。 ![img](https://user-gold-cdn.xitu.io/2019/7/6/16bc675a7df7428e?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) 这张图说明了,在主线程运行的时候,会生成堆栈,形成一个任务队列,然后主线程会循环访问这个任务队列,运行队列中的事件。 在堆中保存的是对象的数据,在栈中保存的是基本数据类型和函数执行时的内存信息。 > 栈中的代码会调用各种外部API,它们在任务队列中加入各种事件(onClick,onLoad,onDone),只要栈中的代码执行完毕(js引擎存在`monitoring process`进程,会持续不断的检查主线程执行栈是否为空),主线程就回去读取任务队列,在按顺序执行这些事件对应的回调函数。 > > 也就是说主线程从任务队列中读取事件,这个过程是循环不断的,所以这种运行机制又成为`Event Loop`(事件循环)。 > > 作者:童欧巴 > 链接:https://juejin.im/post/5d2036106fb9a07eb15d76e9 > 来源:掘金 > 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 通常来说,大部分的代码按照从上到下的顺序执行,并不会有什么问题,即是同步任务,但是当遇到例如加载图片这种操作的时候,可能在图片还没加载完成的情况下就使用了该图片,就会造成显示不全等问题,所以提出了**异步** 的思想。 同步任务就是在主线程上排队执行的任务,严格按照代码顺序执行(变量提升后),异步任务则不进入主线程,会在 event table 中注册函数,当达到执行条件后才会进入任务队列,然后在任务队列等待主线程执行。 **宏任务和微任务** > ``` > setTimeout(function() { > console.log('a') > }); > > new Promise(function(resolve) { > console.log('b'); > > for(var i =0; i <10000; i++) {> i ==99 && resolve(); > } > }).then(function() { > console.log('c') > }); > > console.log('d'); > > // b > // d > // c > // a > 复制代码 > ``` > > 1.首先执行`script`下的宏任务,遇到`setTimeout`,将其放入宏任务的队列里。 > > 2.遇到`Promise`,`new Promise`直接执行,打印b。 > > 3.遇到`then`方法,是微任务,将其放到微任务的队列里。 > > 4.遇到`console.log('d')`,直接打印。 > > 5.本轮宏任务执行完毕,查看微任务,发现`then`方法里的函数,打印c。 > > 6.本轮`event loop`全部完成。 > > 7.下一轮循环,先执行宏任务,发现宏任务队列中有一个`setTimeout`,打印a。 > > 作者:童欧巴 > 链接:https://juejin.im/post/5d2036106fb9a07eb15d76e9 > 来源:掘金 > 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 ![img](https://user-gold-cdn.xitu.io/2019/7/6/16bc6bd331a2116a?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) 通过宏任务和微任务的划分,可以更加清晰的分辨出程序的运行顺序, 上面的文章里还有一个思考题。推荐看一看。同时也看看 https://juejin.im/post/5e5c7f6c518825491b11ce93 文章的理解,这篇文章更加详细,但是不太好懂。 ## JavaScript ES6 更新了什么? 详细的教程或者文档可以参考 [阮一峰的教程](https://es6.ruanyifeng.com/) 和 [Babel 的文档](https://www.babeljs.cn/docs/learn.html) 在前端方面有相当多的规范,比如我们所使用的 HTML、CSS、JavaScript 都是有规范的,其中 HTML、CSS 的标准是由 [W3C](https://www.w3.org/standards/) 设定的,还有 XML、XHTML 也是 W3C 制定的标准。JavaScript 的标准则是由 [ECMAScript](https://www.ecma-international.org/publications/standards/Standard.htm) 所制定的,目前最新的规范是 ECMA-262 Edition 6,也被称为 ECMAScript 2015。这是一个大版本的更新,对比第五代的 ECMAScript 更新了很多东西。可以帮助开发者更加方便快捷的开发网页。 > Babel 是一个 JavaScript 编译器。 Babel 作为 JavaScript 的编译器可以将 ES6 的代码转换成向后兼容的代码,防止出现浏览器兼容错误。 然后就是 JavaScript 的引擎,简单说就是浏览器的核心,目前使用较多的就是 [Firefox 的 SpiderMonkey](https://developer.mozilla.org/zh-CN/docs/Mozilla/Projects/SpiderMonkey) 和 [Chrome 的 V8](https://v8.dev/) 了,关于 JavaScript 引擎了解的不需要太多。还是关注 JavaScript 的语法标准和原理比较好。 ### let 命令 ES6 中新增了 `let` 和 `const` 两个命令,用来声明变量,取代 `var`,不同的是 `let` 和 `const` 都不会产生变量提升,也就是说他们所声明的变量只能在命令所在的代码块有效。 **暂时性死区** ```javascript var tmp = 123; if (true) { tmp = 'abc'; // ReferenceError let tmp; } ``` 使用 `var` 声明的 `tmp` 变量可以看做是一个全局变量,但是由于在 `if` 循环语句内又声明了一个 `tmp`,即使 `tmp` 在 `let` 声明之前赋值也是会直接报错的,如果在 `let` 声明之后赋值的话既是对 `let` 声明的 `tmp` 赋值。 > ES6 明确规定,如果区块中存在`let`和`const`命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。 ```javascript if (true) { // TDZ开始 tmp = 'abc'; // ReferenceError console.log(tmp); // ReferenceError let tmp; // TDZ结束 console.log(tmp); // undefined tmp = 123; console.log(tmp); // 123 } ``` 由于暂时性死区的特性,在变量声明之前都是属于变量的死区,不管做什么操作都会报错。包括 `typeof`。 `let` 也不支持重复声明,相比于 `var` 来说更加的严格。 #### 新的作用域 详见 [ES6-的块级作用域](https://es6.ruanyifeng.com/#docs/let#ES6-的块级作用域) 作用域是用来确定变量的有效范围和调用顺序,在 ES5 中只有全局作用域和函数作用域,在 ES6 中通过 `let` 新增了块级作用域,尽可能的避免变量提升。 ```javascript var tmp = new Date(); function f() { console.log(tmp); if (false) { var tmp = 'hello world'; } } f(); // undefined ``` 在没有块级作用域的时候,上面代码的原意是,`if`代码块的外部使用外层的`tmp`变量,内部使用内层的`tmp`变量。但是,函数`f`执行后,输出结果为`undefined`,原因在于变量提升,导致内层的`tmp`变量覆盖了外层的`tmp`变量。 ```javascript var s = 'hello'; for (var i = 0; i < s.length; i++) { console.log(s[i]); } console.log(i); // 5 ``` 还有就是在使用 `for` 循环的时候使用 `var` 声明的变量会泄露成为全局变量。 ```javascript function f1() { let n = 5; if (true) { let n = 10; } console.log(n); // 5 } ``` 在使用了 `let` 之后会产生块级作用域,使得变量仅在该区域内生效, ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。 ```javascript // 情况一 if (true) { function f() {} } // 情况二 try { function f() {} } catch(e) { // ... } ``` 上面两种函数声明,根据 ES5 的规定都是非法的。 但是,浏览器没有遵守这个规定,为了兼容以前的旧代码,还是支持在块级作用域之中声明函数,因此上面两种情况实际都能运行,不会报错。 ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。ES6 规定,块级作用域之中,函数声明语句的行为类似于`let`,在块级作用域之外不可引用。 #### 函数表达式和函数声明 ```JavaScript // 块级作用域内部的函数声明语句,建议不要使用 { let a = 'secret'; function f() { return a; } } // 块级作用域内部,优先使用函数表达式 { let a = 'secret'; let f = function () { return a; }; } ``` ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。 ```javascript // 情况一 if (true) { function f() {} } // 情况二 try { function f() {} } catch(e) { // ... } ``` 但是由于浏览器为了兼容以前的代码,是会支持的,ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。ES6 规定,块级作用域之中,函数声明语句的行为类似于`let`,在块级作用域之外不可引用。 ```javascript function f() { console.log('I am outside!'); } (function () { if (false) { // 重复声明一次函数f function f() { console.log('I am inside!'); } } f(); }()); ``` 在 ES5 中,这个函数声明会被提升到块级作用域头部,并不会受 `if` 影响,但是在 ES6 中,会因为浏览器的行为而报错。 - 允许在块级作用域内声明函数。 - 函数声明类似于`var`,即会提升到全局作用域或函数作用域的头部。 - 同时,函数声明还会提升到所在的块级作用域的头部。 ```javascript // 浏览器的 ES6 环境 function f() { console.log('I am outside!'); } (function () { var f = undefined; if (false) { function f() { console.log('I am inside!'); } } f(); }()); // Uncaught TypeError: f is not a function ``` 所以在块级作用域内声明函数,最好是使用函数表达式而非函数声明。 ### const 命令 `const` 声明一个只读的常量。一旦声明,常量的值就不能改变。 ```javascript const PI = 3.1415; PI // 3.1415 PI = 3; // TypeError: Assignment to constant variable. ``` `const` 命令需要在声明变量同时对变量进行初始化。只声明而不赋值的话也是会报错的。 `const` 和 `let` 一样,只会在声明的块级作用域内有效,存在暂时性死区,不会被变量提升。 > `const`实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,`const`只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。 ```javascript const foo = {}; // 为 foo 添加一个属性,可以成功 foo.prop = 123; foo.prop // 123 // 将 foo 指向另一个对象,就会报错 foo = {}; // TypeError: "foo" is read-only ``` 如果需要一个彻底不能被修改的变量,不论是对象还是其他类型,可以使用 [`Object.freeze()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) ```javascript var constantize = (obj) => { Object.freeze(obj); Object.keys(obj).forEach( (key, i) => { if ( typeof obj[key] === 'object' ) { constantize( obj[key] ); } }); }; ``` 通过循环遍历对象的属性可以保证对象内的属性也得到冻结。 #### 顶层对象和全局对象 [顶层对象和全局对象](https://es6.ruanyifeng.com/#docs/let#顶层对象的属性) 详情就不写了,主要是为了解决前面对于两个对象的区分不严格的问题,达到浏览器和 Node 环境中的统一, ### 变量解构赋值 > ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。 以前,为变量赋值,只能直接指定值。 ```javascript let a = 1; let b = 2; let c = 3; ``` ES6 允许写成下面这样。 ```javascript let [a, b, c] = [1, 2, 3]; ``` 解构赋值的前提是他们的类型需要相同,所以在右边会使用 `[]` 来包裹值,如果等号左边比右边的值多的话,多的值会被赋值 `undefined` ,如果右边比左边多的话,多的值会被丢弃不用。 解构赋值允许指定默认值。 ```javascript let [foo = true] = []; foo // true let [x, y = 'b'] = ['a']; // x='a', y='b' let [x, y = 'b'] = ['a', undefined]; // x='a', y='b' ``` > ES6 内部使用严格相等运算符(`===`),判断一个位置是否有值。所以,只有当一个数组成员严格等于`undefined`,默认值才会生效。 然后就是对对象的解构赋值,也是同理的。只是写法上不太一样,需要指定目标对象属性值。 ```javascript let { bar, foo } = { foo: 'aaa', bar: 'bbb' }; foo // "aaa" bar // "bbb" let { baz } = { foo: 'aaa', bar: 'bbb' }; baz // undefined ``` 同样也是可以给对象指定默认值。 ### 字符串相关 ES6 加强了对 Unicode 的支持,允许采用`\uxxxx`形式表示一个字符,其中`xxxx`表示字符的 Unicode 码点。 但是,这种表示法只限于码点在`\u0000`~`\uFFFF`之间的字符。超出这个范围的字符,必须用两个双字节的形式表示。 ```javascript "\uD842\uDFB7" // "𠮷" "\u20BB7" // " 7" ``` ES6 对这一点做出了改进,只要将码点放入大括号,就能正确解读该字符。 ```javascript "\u{20BB7}" // "𠮷" "\u{41}\u{42}\u{43}" // "ABC" let hello = 123; hell\u{6F} // 123 '\u{1F680}' === '\uD83D\uDE80' // true ``` 有了这种表示法之后,JavaScript 共有 6 种方法可以表示一个字符。 ```javascript '\z' === 'z' // true '\172' === 'z' // true '\x7A' === 'z' // true '\u007A' === 'z' // true '\u{7A}' === 'z' // true ``` ES6 为字符串添加了遍历器接口(详见《Iterator》一章),使得字符串可以被`for...of`循环遍历。 ```javascript for (let codePoint of 'foo') { console.log(codePoint) } // "f" // "o" // "o" ``` 然后是关于模板字符串的改进,让字符串中的变量可以更加方便的输出。 ```javascript $('#result').append( 'There are ' + basket.count + ' ' + 'items in your basket, ' + '' + basket.onSale + ' are on sale!' ); $('#result').append(` There are ${basket.count} items in your basket, ${basket.onSale} are on sale! `); ``` 模板字符串(template string)是增强版的字符串,用反引号(\`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入 `${}` 变量。 如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。 ES6 还为原生的 String 对象,提供了一个`raw()`方法。该方法返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,往往用于模板字符串的处理方法。 ```javascript String.raw`Hi\n${2+3}!` // 实际返回 "Hi\\n5!",显示的是转义后的结果 "Hi\n5!" String.raw`Hi\u000A!`; // 实际返回 "Hi\\u000A!",显示的是转义后的结果 "Hi\u000A!" ``` `String.raw()`本质上是一个正常的函数,只是专用于模板字符串的标签函数。如果写成正常函数的形式,它的第一个参数,应该是一个具有`raw`属性的对象,且`raw`属性的值应该是一个数组,对应模板字符串解析后的值。 ```javascript // `foo${1 + 2}bar` // 等同于 String.raw({ raw: ['foo', 'bar'] }, 1 + 2) // "foo3bar" ``` 传统上,JavaScript 只有`indexOf`方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6 又提供了三种新方法。 - **includes()**:返回布尔值,表示是否找到了参数字符串。 - **startsWith()**:返回布尔值,表示参数字符串是否在原字符串的头部。 - **endsWith()**:返回布尔值,表示参数字符串是否在原字符串的尾部。 这三个方法都支持第二个参数,表示开始搜索的位置。 ```javascript let s = 'Hello world!'; s.startsWith('world', 6) // true s.endsWith('Hello', 5) // true s.includes('Hello', 6) // false ``` `repeat`方法返回一个新字符串,表示将原字符串重复`n`次。 ```javascript 'x'.repeat(3) // "xxx" 'hello'.repeat(2) // "hellohello" 'na'.repeat(0) // "" ``` 参数如果是小数,会被取整。 ```javascript 'na'.repeat(2.9) // "nana" ``` 如果`repeat`的参数是负数或者`Infinity`,会报错。 ```javascript 'na'.repeat(Infinity) // RangeError 'na'.repeat(-1) // RangeError ``` 但是,如果参数是 0 到-1 之间的小数,则等同于 0,这是因为会先进行取整运算。0 到-1 之间的小数,取整以后等于`-0`,`repeat`视同为 0。 ```javascript 'na'.repeat(-0.9) // "" ``` 参数`NaN`等同于 0。 ```javascript 'na'.repeat(NaN) // "" ``` 如果`repeat`的参数是字符串,则会先转换成数字。 ```javascript 'na'.repeat('na') // "" 'na'.repeat('3') // "nanana" ``` ### 正则表达式的改进 ## TypeScript 整理 # 关于浏览器需要知道些什么 ## 经典例题之访问网站 当我在浏览器URL栏上输入了一串网址然后按下回车,之后会发生什么? 这是一个很简单的操作,几乎每个人都会,但是背后这项操作的原理是什么。 # Node.js 整理 # Vue 整理 ## Vue 是什么? Vue 是一个开源的响应式前端框架,使用了 MVVM 框架,方便开发者关注数据图层。 ### MVVM 框架是什么? Model-View-ViewModel 是 MVC 的一个改进版本。那么 MVC 又是什么? Model-View-Controller,Model 是指数据模型,View 指视图,Controller 是控制器,这个框架使得视图和数据模型分开来,通过控制器来进行操作,在数据发生改变的时候通知控制器,然后控制器去更新视图。 https://www.runoob.com/design-pattern/mvc-pattern.html 菜鸟的这个示例实现了一个 MVC 架构的程序。 MVVM 就是将 MVC 中的 Controller 改进成 ViewModel,把 Model 和 View 关联起来的就是 ViewModel。ViewModel 负责把 Model 的数据同步到 View 显示出来,还负责把 View 的修改同步回 Model。 > 让MVVM框架去自动更新DOM的状态,从而把开发者从操作DOM的繁琐步骤中解脱出来! > > https://www.liaoxuefeng.com/wiki/1022910821149312/1108898947791072 ## Vue 是如何实现响应式的? [`Object.defineProperty()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty) 一开始我觉得通过这个方法,就可以自定义 get() 和 set() 两个方法,这样当这个数据对象发生改变或者获取数据对象的时候,就可以先对数据对象进行处理,那么进行的是什么样的处理? Virtual DOM 在 [Vue源码系列一:Vue中的data是如何驱动视图渲染的?](https://juejin.im/post/5e06b4666fb9a0164f2956c0) 中介绍了 Vue 的源码,分析了 Vue 的初始化顺序,主要就是为了生成虚拟 DOM 树。 浏览器不是已经有了 DOM 树了嘛?Vue 这个DOM 树有什么不同的嘛? 其实没什么不同,因为这个树就是为了给浏览器进行渲染的,在这个树的 [官方文档](https://cn.vuejs.org/v2/guide/render-function.html#虚拟-DOM) 中,Vue 解释说使用这个虚拟 DOM 是 > Vue 通过建立一个**虚拟 DOM** 来追踪自己要如何改变真实 DOM。 这就联系起来了。通过 `Object.defineProperty()` 方法可以劫持数据对象,通过虚拟 DOM 可以实现 MVVM 中的ViewModel 功能,更加详细的东西就是在 set() 和 get() 里面了。 Vue 实现了一个订阅-发布模式。在这个模式中,Vue 实现了依赖收集和派发更新。 [VUE源码系列二:Vue响应式原理解析(附超详细源码注释和原理解析)](https://juejin.im/post/5e0dd467e51d45410f1232f5) 在上面的文章中,作者通过源码解析详细介绍了这个订阅-发布模式,可以将其分成三个模块, - Observer(劫持者) - 给对象的属性添加 getter 和 setter,用于依赖收集和派发更新 - 被用在 defineReactive 中,使得对象的每个属性都添加上 getter 和 setter 成为响应式。 - Dep(依赖收集) - 收集订阅者 `subs : Array`,用在 defineReactive 的重写 get 中为 `dep.depend ` 方法,为数据提供收集功能。 - 同时提供了发布更新的作用,在 defineReactive 的重写 set 中为 `dep.notify()` 方法。 - Watcher(观察者) - 在 Dep 发布更新后使用 `dep.notify()` 方法,循环执行 Dep 中的 `subs[i].update()` 使得数据相关的订阅者更新视图。 - 在 `watcher.update()` 中,使用了 `queuewatcher()` 方法,通过一个队列检查是否含有目标观察者,使得视图不会重复刷新。在队列中异步执行 `flushSchedulerQueue()` 方法,在此方法中排序 queue 确保父子组件的顺序更新,然后执行 `run()` 函数,使用新旧 value 值执行 Watcher 的回调 `cb.call()`。 ![响应系统源码版](https://image-static.segmentfault.com/162/469/1624694337-5a8fcee951762_articlex) ![clipboard.png](https://image-static.segmentfault.com/275/841/2758414791-5c99c0ef4e3c8_articlex) 这两张图片可以用来参考,来自 [从发布-订阅模式到Vue响应系统](https://segmentfault.com/a/1190000013338801) **总结一下** Vue 通过 `Object.defineProperty()` 实现对数据对象的劫持,通过 Observer 类定义 getter 和 setter,使得对象的所有属性都具备响应性,在 `getter()` 中通过依赖收集 Dep 的 `dep.depends()` 管理订阅者(当数据被 get 即视为被订阅),在 `setter()` 中通过 `dep.notify()` 函数向订阅了该数据对象的 Watcher 发送 `update()` ,Watcher 接收到更新信息后确定组件更新顺序然后映射到 Virtual DOM 中去,Virtual DOM 通过 `patch` 新旧节点,通过 `diff` 算法实现对新旧节点的对比,对同层的树节点进行比较而非对树进行逐层搜索遍历。然后生成真实 DOM。 这是 Vue2.x 的响应式原理,在 Vue3.0 中,官方重写了响应式的实现,改用 [`Proxy`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy) 和 [`Reflect`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect) 代替`Object.defineProperty()`。可以实现对更多数据的操作比如数组元素的劫持和更多的劫持方式。 ## Vue 的生命周期 ![Vue 实例生命周期](https://cn.vuejs.org/images/lifecycle.png) 如果是 keep-alive 的话还有两个:activated 和 deactivated keep-alive的生命周期 1. activated: 页面第一次进入的时候,钩子触发的顺序是created->mounted->activated 2. deactivated: 页面退出的时候会触发deactivated,当再次前进或者后退的时候只触发activated 那么 keep-alive 是什么? [`keep-alive`](https://cn.vuejs.org/v2/api/#keep-alive) 是 Vue 提供的一个抽象组件,`` 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和`` 相似, 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在组件的父组件链中。 这是 Vue 对象的生命周期,那么在 Vue 的父子组件中的生命周期是什么样的? **渲染过程:** 父组件挂载完成一定是等子组件都挂载完成后,才算是父组件挂载完,所以父组件的mounted在子组件mouted之后 父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted **子组件更新过程:** 1. 影响到父组件: 父beforeUpdate -> 子beforeUpdate->子updated -> 父updted 2. 不影响父组件: 子beforeUpdate -> 子updated **父组件更新过程:** 1. 影响到子组件: 父beforeUpdate -> 子beforeUpdate->子updated -> 父updted 2. 不影响子组件: 父beforeUpdate -> 父updated **销毁过程:** 父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed 所以不管是加载更新还是销毁都是父组件会等待子组件完后操作后才执行操作。 ## v-model 是什么? v-model 是经常用来双向绑定数据的一个语法,实际是一个 v-bind 和 v-on 结合的一个语法糖,等同于 ```javascript v-bind:value="msg" v-on:input="msg=$event.target.value" ``` [`v-bind`](https://cn.vuejs.org/v2/api/#v-bind) 动态地绑定一个或多个特性,或一个组件 prop 到表达式。 [`v-on`](https://cn.vuejs.org/v2/api/#v-on) 绑定事件监听器。事件类型由参数指定。表达式可以是一个方法的名字或一个内联语句,如果没有修饰符也可以省略。 也就是说 v-model 通过绑定数据和绑定数据并监听数据的变化,形成双向绑定。 [VUE源码系列七:v-model实现原理](https://juejin.im/post/5e3647816fb9a030133074b0) 这篇文章则是从源码角度去解释 v-model,并不是简单的转换为 v-bind 和 v-on。只是在最终结果上是一样的。 ## Vue 组件是什么? 简单说 Vue 组件是一个可以复用的 Vue 实例,通过将网页组件化,可以更加快捷的搭建更多的网页,所以 Vue 组件最主要的目的是复用,类似 Bootstrap 的组件库、Element-UI,他们其实都是组件库,通过自己设计的组件,形成组件库。 https://cn.vuejs.org/v2/guide/components.html 是 Vue 关于组件的文档,以及后续的深入组件内容。 ```JavaScript Vue.component('my-component-name', { /* ... */ }) ``` 通常来说,我们可以使用这样的格式来注册一个组件,这种格式下注册的组件是可以全局使用的,但是很明显当你的页面比较多的时候,全局使用组件会造成一定程度的冗余,所以你可以通过变量赋值,然后通过 Vue 实例的 `components` 属性来添加当前实例的组件。 ```javascript var ComponentA = { /* ... */ } var ComponentB = { /* ... */ } new Vue({ el: '#app', components: { 'component-a': ComponentA, 'component-b': ComponentB } }) ``` 在设计组件的时候,我们需要考虑这个组件需要什么,以及他最后会呈现什么样子。 ```javascript Vue.component('blog-post', { // 在 JavaScript 中是 camelCase 的 props: ['postTitle'], template: '

{{ postTitle }}

' }) ``` 通过 `props` 可以为组件添加属性,这个属性是可以由父组件传递给子组件使用的。可以理解为这是一种函数模式,通过传入参数,使得组件呈现不同的样子。 ```javascript Vue.component('base-checkbox', { model: { prop: 'checked', event: 'change' }, props: { checked: Boolean }, template: ` ` }) ``` 除了 `props` 以外,我们还可以给组件添加自定义的事件属性, 注意这里在 `template` 中使用的是 `v-bind` 和 `v-on` 而不是 `v-model` ,虽然他们所实现的目的是一样的,但是由于方便以及参数传输更加清楚,我们在使用这个组件的时候,通常使用 `v-model` ```html ``` 这样对应过来,`lovingVue` 属性绑定在了组件的 `checked` 属性上,并且在发生 change 事件时也会通过 `v-on` 方法返回到 `lovingVue` 属性上,实现子组件参数与父组件属性间的双重绑定。 更多的细节可以参考官方的详细文档,你就会发现组件其实和实例拥有几乎一模一样的属性和方法,包括`computed` `watch` 以及生命周期方法。 但是我们可以看到,在 `template` 的编写过程中,我们完全得不到语法提示,因为他是一个字符串属性。 所以更好的办法是我们把组件写成一个实例。通过局部引入来进行调用。也就是单文件组件。 在单文件组件中,组件编写更加的方便,同时数据上的传递思路也更加明确。 ### 关于插槽 插槽是 Vue 组件中一个很重要的方法,简单来说,插槽的目的是提供另外一种参数传递的方式, ```html Your Profile ``` 当你使用了一个 `navigation-link` 的组件时,正常情况下,组件间的数据都会被忽略,因为对于组件来说,中间的数据没有任何意义,如果不能正确的传递参数,中间的数据很容易打乱组件的结构。 ```html ``` 但是当组件中设定了一个 `slot` 标签后,父组件在使用子组件时,标签内部的信息都会被转移到 `slot` 标签中,而不是被直接抛弃。所以最后会渲染成: ```html Your Profile ``` 插槽中不仅可以填写这种纯文本,使用 HTML 代码甚至是使用 `{{raw}}` 格式包裹起来的数据属性也是可以的。 但是在使用 `{{raw}}` 格式传递数据时我们需要思考一个问题,我们是在使用子组件的时候,在子组件标签中间使用的 `{{raw}}` ,那么里面的属性我们自然是填入父组件的数据属性。 ```html {{ user.name }} ``` 我们可以用作用域来解释,即子组件的作用域,仅仅是在子组件中,甚至说在父组件中这个子组件的标签属性,也是数据父组件的作用域范围内,所以我们可以通过标签属性来传递父组件的数据到子组件中。只有数据传递过去后,才能算是子组件的数据属性,属于子组件的作用域范围。 更多详细的关于插槽的信息参考文档 https://cn.vuejs.org/v2/guide/components-slots.html 需要了解的包括有插槽的内容、作用域、默认内容、具名插槽、插槽prop、动态插槽名等相关知识。 ### 关于动态组件 ```html ``` 在经典 Vue 示例中,我们经常会看到 `` 这个标签,这个标签常常使用在 Vue 项目的入口 `App.vue` 中,用来对视图进行切换,在实际项目中,当父组件动态调用子组件的时候,通常来说,在切换子组件后子组件会被销毁。但是当我们添加 `` 标签后,被切换隐藏掉的子组件并不会被销毁,这样可以在特定场景下, 提升网页反应速度。 在测试的时候发现在 `` 外也是可以使用 `` 来实现同样的效果。 ## [Vue Router](https://router.vuejs.org/zh/) ## [Vuex](https://vuex.vuejs.org/zh/) ## [Vue SSR](https://ssr.vuejs.org/zh/) # React 整理 # Webpack 整理 # Gulp 整理
,那么里面的属性我们自然是填入父组件的数据属性。

1
2
3
<navigation-link url="/profile">
{{ user.name }}
</navigation-link>

我们可以用作用域来解释,即子组件的作用域,仅仅是在子组件中,甚至说在父组件中这个子组件的标签属性,也是数据父组件的作用域范围内,所以我们可以通过标签属性来传递父组件的数据到子组件中。只有数据传递过去后,才能算是子组件的数据属性,属于子组件的作用域范围。

更多详细的关于插槽的信息参考文档 https://cn.vuejs.org/v2/guide/components-slots.html

需要了解的包括有插槽的内容、作用域、默认内容、具名插槽、插槽prop、动态插槽名等相关知识。

关于动态组件

1
2
3
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>

在经典 Vue 示例中,我们经常会看到 <router-view> 这个标签,这个标签常常使用在 Vue 项目的入口 App.vue 中,用来对视图进行切换,在实际项目中,当父组件动态调用子组件的时候,通常来说,在切换子组件后子组件会被销毁。但是当我们添加 <keep-alive> 标签后,被切换隐藏掉的子组件并不会被销毁,这样可以在特定场景下, 提升网页反应速度。

在测试的时候发现在 <router-view> 外也是可以使用 <keep-alive> 来实现同样的效果。

Vue Router

Vuex

Vue SSR

React 整理

Webpack 整理

Gulp 整理