Yuqi Qin's Blog


  • Home

  • Categories

  • Archives

  • About

  • Tags

es6面试题

Posted on 2020-09-04

https://juejin.im/post/6844904152850497544

1、es5和es6的区别,说一下你所知道的es6

ECMAScript5,即ES5,是ECMAScript的第五次修订,于2009年完成标准化ECMAScript6,即ES6,是ECMAScript的第六次修订,于2015年完成,也称ES2015ES6是继ES5之后的一次改进,相对于ES5更加简洁,提高了开发效率ES6新增的一些特性:
1)let声明变量和const声明常量,两个都有块级作用域ES5中是没有块级作用域的,并且var有变量提升,在let中,使用的变量一定要进行声明
2)箭头函数ES6中的函数定义不再使用关键字function(),而是利用了()=>来进行定义
3)模板字符串模板字符串是增强版的字符串,用反引号(`)标识,可以当作普通字符串使用,也可以用来定义多行字符串
4)解构赋值ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值
5)for of循环for…of循环可以遍历数组、Set和Map结构、某些类似数组的对象、对象,以及字符串
6)import、export导入导出ES6标准中,Js原生支持模块(module)。将JS代码分割成不同功能的小块进行模块化,将不同功能的代码分别写在不同文件中,各模块只需导出公共接口部分,然后通过模块的导入的方式可以在其他地方使用
7)set数据结构Set数据结构,类似数组。所有的数据都是唯一的,没有重复的值。它本身是一个构造函数
8)… 展开运算符可以将数组或对象里面的值展开;还可以将多个值收集为一个变量
9)修饰器 @decorator是一个函数,用来修改类甚至于是方法的行为。修饰器本质就是编译时执行的函数
10)class 类的继承ES6中不再像ES5一样使用原型链实现继承,而是引入Class这个概念11)async、await使用 async/await, 搭配promise,可以通过编写形似同步的代码来处理异步流程, 提高代码的简洁性和可读性async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成
12)promisePromise是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理、强大
13)SymbolSymbol是一种基本类型。Symbol 通过调用symbol函数产生,它接收一个可选的名字参数,该函数返回的symbol是唯一的
14)Proxy代理使用代理(Proxy)监听对象的操作,然后可以做一些相应事情

2、var、let、const之间的区别

var声明变量可以重复声明,而let不可以重复声明
var是不受限于块级的,而let是受限于块级
var会与window相映射(会挂一个属性),而let不与window相映射
var可以在声明的上面访问变量,而let有暂存死区,在声明的上面访问变量会报错
const声明之后必须赋值,否则会报错
const定义不可变的量,改变了就会报错
const和let一样不会与window相映射、支持块级作用域、在声明的上面访问变量会报错

3、使用箭头函数应注意什么?

(1)用了箭头函数,this就不是指向window,而是父级(指向是可变的)
(2)不能够使用arguments对象
(3)不能用作构造函数,这就是说不能够使用new命令,否则会抛出一个错误
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数

4、ES6的模板字符串有哪些新特性?并实现一个类模板字符串的功能

基本的字符串格式化。
将表达式嵌入字符串中进行拼接。
用${}来界定在ES5时我们通过反斜杠()来做多行字符串或者字符串一行行拼接。
ES6反引号(``)就能解决类模板字符串的功能

1
2
3
4
5
6
7
let name = 'web';
let age = 10;
let str = '你好,${name} 已经 ${age}岁了'
str = str.replace(/\$\{([^}]*)\}/g,function(){
return eval(arguments[1]);
})
console.log(str);//你好,web 已经 10岁了

5、介绍下 Set、Map的区别?

应用场景
Set用于数据重组,Map用于数据储存Set: 
(1)成员不能重复
(2)只有键值没有键名,类似数组
(3)可以遍历,方法有add, delete,has
Map:
(1)本质上是健值对的集合,类似集合
(2)可以遍历,可以跟各种数据格式转换

6、ECMAScript 6 怎么写 class ,为何会出现 class?

ES6的class可以看作是一个语法糖,它的绝大部分功能ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法

1
2
3
4
5
6
7
8
9
10
//定义类
class Point {
constructor(x,y) {
//构造方法
this.x = x; //this关键字代表实例对象
this.y = y;
} toString() {
return '(' + this.x + ',' + this.y + ')';
}
}

7、Promise构造函数是同步执行还是异步执行,那么 then 方法呢?

promise构造函数是同步执行的,then方法是异步执行的

8、setTimeout、Promise、Async/Await 的区别

事件循环中分为宏任务队列和微任务队列
其中setTimeout的回调函数放到宏任务队列里,等到执行栈清空以后执行promise.then里的回调函数会放到相应宏任务的微任务队列里,等宏任务里面的同步代码执行完再执行async函数表示函数里面可能会有异步方法,await后面跟一个表达式
async方法执行时,遇到await会立即执行表达式,然后把表达式后面的代码放到微任务队列里,让出执行栈让同步代码先执行

Untitled

Posted on 2020-08-24
浮动实现三栏布局

面试题

Posted on 2020-08-24

https://juejin.im/post/6844904146168987661#heading-65

三栏布局

题目:假设高度已知,请写出三栏布局,其中左栏、右栏宽度各为 300px,中间自适应。
解答:可以有很多种布局方式,这里列出五种:float布局,absolute布局,flex布局,table布局,grid布局,代码如下:

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>三栏布局</title>
<link rel="stylesheet" href="">
<style type="text/css" media="screen">
html *{
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<section class="layout float">
<style type="text/css" media="screen">
.layout.float .wrapper>div{
min-height: 100px;
}
.layout.float .left{
float: left;
width: 300px;
background: red;
}
.layout.float .center{
background: yellow;
}
.layout.float .right{
float: right;
width: 300px;
background: blue;
}

</style>
<article class="wrapper">
<div class="left"></div>
<div class="right"></div>
<div class="center">
<h1>float布局</h1>
1.我是float布局的中间部分
2.我是float布局的中间部分
</div>
</article>
</section>


<section class="layout absolute">
<style type="text/css" media="screen">
.layout.absolute .wrapper{
width: 100%;
margin-top: 20px;
}
.layout.absolute .wrapper>div{
min-height: 100px;
}
.layout.absolute .left{
position: absolute;
left: 0;
width: 300px;
background: red;
}
.layout.absolute .center{
position: absolute;
left: 300px;
right: 300px;
background: yellow;
}
.layout.absolute .right{
position: absolute;
right: 0;
width: 300px;
background: blue;
}
</style>
<article class="wrapper">
<div class="left"></div>
<div class="center">
<h1>absolute布局</h1>
1.我是absolute布局的中间部分
2.我是absolute布局的中间部分
</div>
<div class="right"></div>
</article>
</section>


<section class="layout flex">
<style type="text/css" media="screen">
.layout.flex .wrapper{
width: 100%;
min-height: 100px;
display: flex;
margin-top: 140px;
}
.layout.flex .left{
width: 300px;
background: red;
}
.layout.flex .center{
flex: 1;
background: yellow;
}
.layout.flex .right{
width: 300px;
background: blue;
}
</style>
<article class="wrapper">
<div class="left"></div>
<div class="center">
<h1>flex布局</h1>
1.我是flex布局的中间部分
2.我是flex布局的中间部分
</div>
<div class="right"></div>
</article>
</section>


<section class="layout table">
<style type="text/css" media="screen">
.layout.table .wrapper{
display: table;
width: 100%;
min-height: 100px;
margin-top: 20px;
}
.layout.table .left{
display: table-cell;
width: 300px;
background: red;
}
.layout.table .center{
display: table-cell;
background: yellow;
}
.layout.table .right{
display: table-cell;
width: 300px;
background: blue;
}

</style>
<article class="wrapper">
<div class="left"></div>
<div class="center">
<h1>table布局</h1>
1.我是table布局的中间部分
2.我是table布局的中间部分
</div>
<div class="right"></div>
</article>
</section>


<section class="layout grid">
<style type="text/css" media="screen">
.layout.grid .wrapper{
display: grid;
grid-template-columns: 300px auto 300px;
grid-template-rows: 100px;
width: 100%;
margin-top: 20px;
}
.layout.grid .left{
background: red;
}
.layout.grid .center{
background: yellow;
}
.layout.grid .right{
background: blue;
}

</style>
<article class="wrapper">
<div class="left"></div>
<div class="center">
<h1>grid布局</h1>
1.我是grid布局的中间部分
2.我是grid布局的中间部分
</div>
<div class="right"></div>
</article>
</section>
</body>
</html>

每种布局的优缺点

  1. float 布局
    优点: 比较简单,兼容性也比较好。只要清除浮动做的好,是没有什么问题的缺点:浮动元素是脱离文档流,要做清除浮动,这个处理不好的话,会带来很多问题,比如高度塌陷等。
  2. 绝对布局
    优点:很快捷,设置很方便,而且也不容易出问题缺点:绝对定位是脱离文档流的,意味着下面的所有子元素也会脱离文档流,这就导致了这种方法的有效性和可使用性是比较差的。
  3. flex 布局
    优点:简单快捷缺点:不支持 IE8 及以下
  4. table布局
    优点:实现简单,代码少缺点:当其中一个单元格高度超出的时候,两侧的单元格也是会跟着一起变高的,而有时候这种效果不是我们想要的。
  5. grid布局
    跟 flex 相似。

水平垂直居中

  1. absolute + 负margin
    这种方式比较好理解,兼容性也很好,缺点是需要知道子元素的宽高
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div class="out">
<div class="inner">12345</div>
</div>

<style type="text/css">
.out{
position: relative;
width: 300px;
height: 300px;
background: red;
}

.inner{
position: absolute;
width: 100px;
height: 100px;
background: yellow;
left: 50%;
top: 50%;
margin-left: -50px;
margin-top: -50px;
}
</style>
  1. absolute + auto margin
    这种方法兼容性也很好,缺点是需要知道子元素的宽高
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<style type="text/css">
.out{
position: relative;
width: 300px;
height: 300px;
background: red;
}

.inner{
position: absolute;
width: 100px;
height: 100px;
background: yellow;
left: 0;
top: 0;
right: 0;
bottom: 0;
margin: auto;
}
</style>
  1. absolute + calc
    这种方法的兼容性依赖于 calc,且也需要知道宽高
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<style type="text/css">
.out{
position: relative;
width: 300px;
height: 300px;
background: red;
}

.inner{
position: absolute;
width: 100px;
height: 100px;
background: yellow;
left: calc(50% - 50px);
top: calc(50% - 50px);
}
</style>
  1. absolute + transform
    兼容性依赖 translate,不需要知道子元素宽高
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<style type="text/css">
.out{
position: relative;
width: 300px;
height: 300px;
background: red;
}

.inner{
position: absolute;
background: yellow;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
</style>
  1. table
    css新增的table属性,可以让我们把普通元素,变为table元素的显示效果,通过这个特性也可以实现水平垂直居中。
    这种方法兼容性也不错。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <style type="text/css">
    .out{
    display: table-cell;
    width: 300px;
    height: 300px;
    text-align: center;
    vertical-align: middle;
    background: red;
    }

    .inner{
    display: inline-block;
    background: yellow;
    width: 100px;
    height: 100px;
    }
    </style>
  2. flex
    flex 实现起来比较简单,三行代码即可搞定。可通过父元素指定子元素的对齐方式,也可通过 子元素自己指定自己的对齐方式来实现。第二种方式见 grid 布局。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <style type="text/css">
    .out{
    display: flex;
    justify-content: center;
    align-items: center;
    width: 300px;
    height: 300px;
    background: red;
    }

    .inner{
    background: yellow;
    width: 100px;
    height: 100px;
    }
    </style>
  3. grid

    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
    //方法一:父元素指定子元素的对齐方式
    <style type="text/css">
    .out{
    display: grid;
    align-content: center;
    justify-content: center;
    width: 300px;
    height: 300px;
    background: red;
    }

    .inner{
    background: yellow;
    width: 100px;
    height: 100px;
    }
    </style>

    //方法二:子元素自己指定自己的对齐方式
    <style type="text/css">
    .out{
    display: grid;
    width: 300px;
    height: 300px;
    background: red;
    }

    .inner{
    background: yellow;
    width: 100px;
    height: 100px;
    align-self: center;
    justify-self: center;
    }
    </style>

页面布局小结:

语义化掌握到位
页面布局理解深刻
CSS基础知识扎实
思维灵活且积极上进
代码书写规范

CSS盒模型

CSS盒模型是前端的基石,这个问题由浅入深,由易到难,可以依次问出下面几个问题

  • 基本概念:标准模型 + IE模型
  • 标准模型 和 IE模型的区别
  • CSS如何设置这两种模型
  • JS如何设置和获取盒模型对应的宽和高
  • 实例题(根据盒模型解释边距重叠)
  • BFC(边距重叠解决方案)

1. 基本概念

所有HTML元素可以看作盒子,在CSS中,”box model”这一术语是用来设计和布局时使用。CSS盒模型本质上是一个盒子,封装周围的HTML元素,它包括:边距,边框,填充,和实际内容。盒模型允许我们在其它元素和周围元素边框之间的空间放置元素。下面的图片说明了盒子模型(Box Model):
margin->border->padding->content

2. 标准模型与IE模型的区别

标准模型与 IE 模型的区别在于宽高的计算方式不同。

标准模型计算元素的宽高只算 content 的宽高,
IE模型是 content + padding + border 的总尺寸。

假如 content 宽高是 100100px,padding 为 10px,border为 10px,margin为10px,那么在标准模型下,这个元素的宽为 100px,高为 100px。
IE模型下,宽为 100px + 210px(左右padding) + 210px(左右border) = 140px;高为 100px + 210px(上下padding) + 2*10px(上下border) = 140px;

3. 如何设置这两种模型

1
2
3
4
//设置标准模型
box-sizing: content-box;
//设置IE模型
box-sizing: border-box;

box-sizing 的默认值是 content-box,即默认标准模型。

4. JS如何设置盒模型的宽和高

1
2
3
4
5
6
7
8
9
10
11
//只能获取内联样式设置的宽高
dom.style.width/height

//获取渲染后即时运行的宽高,值是准确的。但只支持 IE
dom.currentStyle.width/height

//获取渲染后即时运行的宽高,值是准确的。兼容性更好
window.getComputedStyle(dom).width/height;

//获取渲染后即时运行的宽高,值是准确的。兼容性也很好,一般用来获取元素的绝对位置,getBoundingClientRect()会得到4个值:left, top, width, height
dom.getBoundingClientRect().width/height;

5. BFC

Block Formatting Context(块级格式化上下文)。
在解释什么是BFC之前,我们需要先知道Box、Formatting Context的概念。

Box:css布局的基本单位

Box 是 CSS 布局的对象和基本单位, 直观点来说,就是一个页面是由很多个 Box 组成的。元素的类型和 display 属性,决定了这个 Box 的类型。 不同类型的 Box, 会参与不同的 Formatting Context(一个决定如何渲染文档的容器),因此Box内的元素会以不同的方式渲染。让我们看看有哪些盒子:

  • block-level box: display 属性为 block, list-item, table 的元素,会生成 block-level box。并且参与 block fomatting context;
  • inline-level box: display 属性为 inline, inline-block, inline-table 的元素,会生成 inline-level box。并且参与 inline formatting context;
  • run-in box: css3 中才有, 这儿先不讲了。
Formatting Context

Formatting context 是 W3C CSS2.1 规范中的一个概念。它是页面中的一块渲染区域,并且有一套渲染规则,它决定了其子元素将如何定位,以及和其他元素的关系和相互作用。最常见的 Formatting context 有 Block fomatting context (简称BFC)和 Inline formatting context (简称IFC)。

BFC的布局规则
  • 内部的Box会在垂直方向,一个接一个地放置。
  • Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠。
  • 每个盒子(块盒与行盒)的margin box的左边,与包含块border box的左边相接触(对于从左往*右的格式化,否则相反)。即使存在浮动也是如此。
  • BFC的区域不会与float box重叠。
  • BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。
  • 计算BFC的高度时,浮动元素也参与计算。
如何创建BFC
  1. float的值不是none。
  2. position的值不是static或者relative。
  3. overflow的值不是visible。
  4. display的值是inline-block、table-cell、flex、table-caption或者inline-flex。
BFC的作用

利用BFC避免margin重叠
应两栏布局自适
清除浮动

DOM事件

1. 事件级别

三个事件级别,注意没有 DOM1,因为 DOM1 标准制定的时候没有涉及 DOM事件。DOM3 比 DOM2 只是增加了一些事件类型。

DOM0:element.onclick = function(){}
DOM2:element.addEventListener(‘click’, function(){}, false)
DOM3:element.addEventListener(‘keyup’, function(){}, false)

2. 事件模型和事件流

DOM事件模型包括捕获和冒泡。事件流即用户与界面交互的过程中,事件的流向过程。

3. DOM事件捕获的具体流程

捕获的流程为:window -> document -> html -> body -> … -> 目标元素。
冒泡的流程为:目标元素 -> … -> body -> html -> document -> window。

4. Event 对象常见应用

event. preventDefault()

取消事件的默认动作。

event.stopPropagation()

阻止事件冒泡。

event.stopImmediatePropagation()

阻止剩下的事件处理程序被执行。如果一个元素上绑定了三个事件,在其中一个事件上调用了这个方法,那其他 的两个事件将不会被执行。

HTTP协议

1. http协议的主要特点

简单快速、灵活、无连接、无状态HTTP三点注意事项:

  • HTTP是无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
  • HTTP是媒体独立的:这意味着,只要客户端和服务器知道如何处理的数据内容,任何类型的数据都可以通过HTTP发送。客户端以及服务器指定使用适合的MIME-type内容类型。
  • HTTP是无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

2. 请求报文

一个HTTP请求报文由请求行(request line)、请求头(header)、空行和请求数据4个部分组成,下图给出了请求报文的一般格式。

  • 请求行:

包括请求方法字段、URL字段和HTTP协议版本,如:GET /index.html HTTP/1.1。

  • 请求头

请求头部由关键字/值对组成,每行一对,关键字和值用英文冒号“:”分隔。请求头部通知服务器有关于客户端请求的信息,典型的请求头有:

  • User-Agent:产生请求的浏览器类型。
  • Accept:客户端可识别的内容类型列表。
  • Host:请求的主机名,允许多个域名同处一个IP地址,即虚拟主机。
  • Content-Type:请求体的MIME类型 (用于POST和PUT请求中)。如:Content-Type: application/x-www-form-urlencoded
  • 空行

最后一个请求头之后是一个空行,发送回车符和换行符,通知服务器以下不再有请求头。

  • 请求数据

请求数据不在GET方法中使用,而是在POST方法中使用。POST方法适用于需要客户填写表单的场合。与请求数据相关的最常使用的请求头是Content-Type和Content-Length。

3. 响应报文

包括:状态行、响应头、空行、响应正文。

4. HTTP 状态码

HTTP状态码的英文为HTTP Status Code。状态代码由三位数字组成,第一个数字定义了响应的类别,且有五种可能取值。

1xx:指示信息–表示请求已接收,继续处理。
2xx:成功–表示请求已被成功接收、理解、接受。
3xx:重定向–要完成请求必须进行更进一步的操作。
4xx:客户端错误–请求有语法错误或请求无法实现。
5xx:服务器端错误–服务器未能实现合法的请求。

常见状态代码、状态描述的说明如下。

200 OK:客户端请求成功。
400 Bad Request:客户端请求有语法错误,不能被服务器所理解。
401 Unauthorized:请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用。
403 Forbidden:服务器收到请求,但是拒绝提供服务。
404 Not Found:请求资源不存在,举个例子:输入了错误的URL。
500 Internal Server Error:服务器发生不可预期的错误。
503 Server Unavailable:服务器当前不能处理客户端的请求,一段时间后可能恢复正常,举个例子:HTTP/1.1 200 OK(CRLF)。

5. 浏览器输入 url 之后发生了什么

https://www.cnblogs.com/yuanzhiguo/p/8119470.html

原型链

1 创建对象的几种方法

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>原型链</title>
<link rel="stylesheet" href="">
</head>
<body>
<script type="text/javascript" charset="utf-8">
//创建对象的几种方式
//1.字面量
var obj1 = {name: 'solo obj1'};
//2.new Object
var obj2 = new Object({name: 'solo obj2'})
//3.构造函数创建
var M = function(name){
this.name = name;
}
var obj3 = new M('solo obj3');
//4.Object.create
var p = {name: 'p'};
var obj4 = Object.create(p);

</script>
</body>
</html>

2 原型、构造函数、实例、原型链

构造函数.prototype.constructor === 构造函数

M.prototype.constructor === M 的结果为 true

构造函数.prototype === 实例对象.proto

M.prototype === obj3._ proto_ 的结果为 true

3 instanceof 的原理

instanceof 用于判断一个引用类型是否属于某构造函数;还可以在继承关系中用来判断一个实例是否属于它的父类型。
instanceof 的原理是判断实例对象的 __proto__ 是否与构造函数的 prototype 指向同一个引用。

1
2
3
4
5
6
7
8
9
10
11
12
//M-构造函数,obj-实例对象
obj3.__proto__ === M.prototype
true
M.prototype.__proto__ === Object.prototype
true


obj3.__proto__.constructor === M
true

obj3.__proto__.constructor === Object
false

4 instanceof 和 typeof 的区别

typeof 对于基本数据类型(null, undefined, string, number, boolean, symbol),除了 null 都会返回正确的类型。null 会返回 object。
typeof 对于对象类型,除了函数会返回 function,其他的都返回 object。如果我们想获得一个变量的正确类型,可以通过 Object.prototype.toString.call(xx)。这样我们就可以获得类似 [object Type] 的字符串。
判断是否等于 undefined 的方法:

1
2
3
4
5
6
7
8
9
10
let a
// 我们也可以这样判断 undefined
a === undefined
// 但是 undefined 不是保留字,能够在低版本浏览器被赋值
let undefined = 1
// 这样判断就会出错
// 所以可以用下面的方式来判断,并且代码量更少
// 因为 void 后面随便跟上一个组成表达式
// 返回就是 undefined
a === void 0

5 new运算符

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>模拟new的过程</title>
<link rel="stylesheet" href="">
</head>
<body>
<script type="text/javascript" charset="utf-8" async defer>
function Person(name, age){
this.name = name;
this.age = age;
}

//手动实现new
function _new(){
//1.拿到传入的参数中的第一个参数,即构造函数名Func
var Func = [].shift.call(arguments)
//2.创建一个空对象obj,并让其继承Func.prototype
var obj = Object.create(Func.prototype)
//第二步也可以这样写
// var obj = {}
// obj.__proto__ = Func.prototype
//3.执行构造函数,并将this指向创建的空对象obj
var ret = Func.apply(obj, arguments)
//4.如果构造函数返回的值是对象则返回,不是对象则返回创建的对象obj
return typeof ret === 'object' ? ret : obj
}

var p1 = _new(Person, 'bob', 19)
console.log(p1)
</script>
</body>
</html>

如何理解 [].shift.call(arguments)
因为 shift 内部实现是使用的this代表调用对象。那么当[].shift.call() 传入 arguments对象的时候,通过 call函数改变原来 shift 方法的 this 指向, 使其指向 arguments,并对 arguments 进行复制操作,而后返回一个新数组。至此便是完成了 arguments 类数组转为数组的目的!其实这可以理解为,让类数组调用数组的方法!

面向对象

1 类的声明和实例化

声明类有两种方法:

1
2
3
4
5
6
7
8
9
function Animal(name){
this.name = name;
}

class Animal2 {
constructor(name){
this.name = name;
}
}

类的实例化只有一种方式

1
2
var a1 = new Animal('shape');
var a2 = new Animal2('cat');

2 继承

继承有多种实现方式:

  1. 借助构造函数实现继承
    Parent1 是父类,Child1 是子类。通过 Parent1.call(this, name) 改变了 this 指向,使子类继承了父类的属性,即 Child1 也有了 name 属性。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /*
    构造函数实现继承
    */
    function Parent1(name){
    this.name = name;
    }

    function Child1(name, age){
    Parent1.call(this, name)
    this.age = age
    }

    var c1 = new Child1('bobo', 19)
    console.log(c1)

但是,这种方式不能继承父类原型链上的属性,只能继承在父类显式声明的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Parent1(name){
this.name = name;
}

Parent1.prototype.say = function(){
console.log('say hello')
}

function Child1(name, age){
Parent1.call(this, name)
this.age = age
}

var p1 = new Parent1('hehe')
var c1 = new Child1('bobo', 19)
console.log(p1)
console.log(c1)
  1. 借助原型链实现继承
    实现原理是将 Child.prototype 赋值为一个新的 Parent 对象,即 Child2.prototype = new Parent2(‘bob’)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function Parent2(name){
    this.name = name;
    this.arr = [1,2,3]
    }

    function Child2(age){
    this.age = age
    }

    //重点在这句
    Child2.prototype = new Parent2('bob')

    c2 = new Child2(20)
    c3 = new Child2(22)
    console.log('c2', c2)
    console.log('c3', c3)

这种方法也有缺点,看 arr 属性是一个数组,如果创建两个实例对象 c2、c3,因为这两个实例对象的 arr 指向同一个引用,所以改变其中一个的值,另一个也会跟着改变。我们来看下面的实验:c2.arr 和 c3.arr 值都是 [1,2,3],此时通过 c2.arr.push(4) 给 c2.arr 添加一个元素,c2.arr 变成了 [1,2,3,4],这没有问题。但再看 c3.arr ,也是 [1,2,3,4],这就有问题了,不是我们期望的。

  1. 组合方式实现继承
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //组合方式优化2
    function Parent5(name){
    this.name = name
    }
    function Child5(name, age){
    Parent5.call(this, name)
    this.age = age
    }
    Child5.prototype = Object.create(Parent5.prototype)
    Child5.prototype.constructor = Child5

    var c8 = new Child5()
    console.log(c8 instanceof Child5, c8 instanceof Parent5)
    console.log(c8.constructor)

通信类

1 同源策略及限制

同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。
协议、域名、端口只要有一个不一样,就是不同的源。

不同源的限制:

  • Cookie、LocalStorage、IndexDB 无法获取
  • DOM 无法获取
  • Ajax 请求不能发送(Ajax 只限于同源使用,不能跨域使用)

2 前后端如何通信

Ajax
WebSocket
CORS

3 跨域通信的几种方式

JSONP(利用 script 标签的异步加载实现的)
Hash(window.location.hash + iframe)
postMessage (H5中新增的)
WebSocket
CORS
https://segmentfault.com/a/1190000011145364

正向代理代理客户端,反向代理代理服务器。

安全类

  1. CSRF
    CSRF(Cross-site request forgery)跨站请求伪造。

攻击原理

用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;
在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;
用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;
网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;
浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。

防御措施

Token验证
Referer 验证(简单易行,但 referer 可能被改变)
隐藏令牌(跟 Token验证差不多,把令牌存到 header 中)
2. XSS
XSS(cross-site scripting)跨域脚本攻击

攻击原理
往 Web 页面里插入恶意Script代码

防御措施
HTML:对以下这些字符进行转义:

1
2
3
4
5
6
&:&amp;
<:&alt;
>:&gt;
':&#x27;
":&quot;
/:&#x2F;

Javascript:把所有非字母、数字的字符都转义成小于256的ASCII字符;
URL:使用Javascript的encodeURIComponent()方法对用户的输入进行编码,该方法会编码如下字符:, / ? : @ & = + $ #

渲染机制

1 DOCTYPE 及其作用

DTD (Document type definition,文档类型定义) 是一系列的语法规则,用来定义 XML 或 HTML 的文件类型。浏览器会使用它来判断文档类型,决定使用何种协议来解析,以及切换浏览器模式。
DOCTYPE 是用来声明文档类型和 DTD 规范的,一个主要的用途便是文件的合法性验证。如果文件代码不合法,那么浏览器解析时便会出一些差错。
注意:<!DOCTYPE> 声明不区分大小写。

2 浏览器渲染过程

这个问题照着这张图讲清楚就好。浏览器拿到 HTML 和 CSS 之后,通过 HTML Parser 把 HTML 解析成 DOM Tree , 通过 CSS Parser 把 CSS 解析成 Style Rules 即 CSS 规则,然后 DOM Tree 和 CSS规则 结合起来形成 Render Tree 。然后进行布局 Layout 和绘制 Painting,最终 Display 显示在页面上。

3 重排 Reflow

定义

DOM 结构中各个元素都有自己的盒子(模型),这些都需要浏览器根据各种样式来计算并根据计算结果将元素放到它该出现的位置,这个过程称为 reflow。

触发 Reflow

什么情况会触发 Reflow 呢?(记住两三个就可以)

  • 增加、删除、修改 DOM 节点时,会导致 Reflow 或 Repaint
  • 移动 DOM 位置,或搞个动画时
  • 修改 CSS 样式时
  • Resize 窗口(移动端没这个问题)或滚动的时候
  • 修改网页默认字体时

    4 重绘 Repaint

    定义
    当各种盒子的位置、大小以及其他属性,例如颜色、字体大小等都确定之后,浏览器把这些元素按照各自的特性绘制了一遍,于是页面内容出现了,这个过程称之为 repaint。
    触发 Repaint

DOM 改动
CSS 改动

如何最小程度的 Repaint

比如要添加多个 DOM 节点,一次性添加,而不要一个个添加。

5 dispaly:none 和 visibility:hidden

dispaly:none 设置该属性后,该元素下的元素都会隐藏,占据的空间消失。visibility:hidden 设置该元素后,元素虽然不可见了,但是依然占据空间的位置。
display:none和visibility:hidden的区别?
1.visibility具有继承性,其子元素也会继承此属性,若设置visibility:visible,则子元素会显示2.visibility不会影响计数器的计算,虽然隐藏掉了,但是计数器依然继续运行着。3.在css3的transition中支持visibility属性,但是不支持display,因为transition可以延迟执行,因此配合visibility使用纯css实现hover延时显示效果可以提高用户体验4. display:none会引起回流(重排)和重绘 visibility:hidden会引起重绘

JS运行机制

1 为什么 JavaScript 是单线程

JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊。JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

2 任务队列

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入”任务队列”(task queue)的任务,只有”任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。具体来说,异步执行的运行机制如下:(同步执行也是如此,因为它可以被视为没有异步任务的异步执行。)

1
2
3
4
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。

只要主线程空了,就会去读取”任务队列”,这就是JavaScript的运行机制。这个过程会不断重复。

3 宏任务 & 微任务

这里需要注意的是new Promise是会进入到主线程中立刻执行,而promise.then则属于微任务

宏任务(macro-task):整体代码script、setTimeOut、setInterval
微任务(mincro-task):promise.then、promise.nextTick(node)

4 EventLoop 事件循环

宏任务一般是:包括整体代码script,setTimeout,setInterval、I/O、UI render。
微任务主要是:Promise、Object.observe、MutationObserver。

主线程从”任务队列”中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。
整体的script(作为第一个宏任务)开始执行的时候,会把所有代码分为两部分:“同步任务”、“异步任务”;
同步任务会直接进入主线程依次执行;
异步任务会再分为宏任务和微任务;
宏任务进入到Event Table中,并在里面注册回调函数,每当指定的事件完成时,Event Table会将这个函数移到Event Queue中;
微任务也会进入到另一个Event Table中,并在里面注册回调函数,每当指定的事件完成时,Event Table会将这个函数移到Event Queue中;
当主线程内的任务执行完毕,主线程为空时,会检查微任务的Event Queue,如果有任务,就全部执行,如果没有就执行下一个宏任务;
上述过程会不断重复,这就是Event Loop事件循环;

首先执行script下的宏任务->微任务队列->宏任务队列

一旦遇到await 就立刻让出线程,阻塞后面的代码

等候之后,对于await来说分两种情况

  • 不是promise 对象
  • 是promise对象
    如果不是promise,await会阻塞后面的代码,先执行async外面的同步代码,同步代码执行完毕后,在回到async内部,把promise的东西,作为await表达式的结果

如果它等到的是一个 promise 对象,await 也会暂停async后面的代码,先执行async外面的同步代码,等着 Promise 对象 fulfilled,然后把 resolve 的参数作为 await 表达式的运算结果。

如果一个 Promise 被传递给一个 await 操作符,await 将等待 Promise 正常处理完成并返回其处理结果。

5 定时器

除了放置异步任务的事件,”任务队列”还可以放置定时事件,即指定某些代码在多少时间之后执行。这叫做”定时器”(timer)功能,也就是定时执行的代码。定时器功能主要由setTimeout()和setInterval()这两个函数来完成,它们的内部运行机制完全一样,区别在于前者指定的代码是一次性执行,后者则为反复执行。以下主要讨论setTimeout()。setTimeout()接受两个参数,第一个是回调函数,第二个是推迟执行的毫秒数。

总之,setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,也就是说,尽可能早得执行。它在”任务队列”的尾部添加一个事件,因此要等到同步任务和”任务队列”现有的事件都处理完,才会得到执行。HTML5标准规定了setTimeout()的第二个参数的最小值(最短间隔),不得低于4毫秒,如果低于这个值,就会自动增加。在此之前,老版本的浏览器都将最短间隔设为10毫秒。另外,对于那些DOM的变动(尤其是涉及页面重新渲染的部分),通常不会立即执行,而是每16毫秒执行一次。这时使用requestAnimationFrame()的效果要好于setTimeout()。需要注意的是,setTimeout()只是将事件插入了”任务队列”,必须等到当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是当前代码耗时很长,有可能要等很久,所以并没有办法保证,回调函数一定会在setTimeout()指定的时间执行。

6 哪些是异步任务

setTimeout 和 setInterval
DOM 事件
Promise
网络请求
I/O

7 常考题

1) 第1题
1
2
3
4
5
console.log(1);
setTimeout(function(){
console.log(2)
}, 0);
console.log(3)

答案:1,3,2解析: console.log() 是同步任务, setTimeout 是异步任务。异步任务会等同步任务执行完再执行。虽然 setTimeout 设置的延迟是 0,但浏览器规定延迟最小为 4ms,所以 console.log(2) 在 4ms 后被放入任务队列。当同步任务执行完,即打印完 1,3 之后,主线程再从任务队列中取任务,打印 2。

2) 第2题
1
2
3
console.log('A')
while(true){}
console.log('B')

答案:A
解析:代码从上往下执行,先打印 A,然后 while 循环,因为条件一直是 true,所以会进入死循环。while 不执行完就不会执行到第三行

1
2
3
4
5
console.log('A');
setTimeout(function(){
console.log('B')
}, 0);
while(1){}

同样只会输出 A。因为异步任务需要等同步任务执行完之后才执行,while 进入了死循环,所以不会打印 B。

3) 第3题
1
2
3
4
5
for(var i=0; i<4; i++){
setTimeout(function(){
console.log(i)
}, 0)
}

结果:4个4。解析:这题主要考察异步任务放入任务队列的时机。当执行到 setTimeout 即定时器时,并不会马上把这个异步任务放入任务队列,而是等时间到了之后才放入。然后等执行栈中的同步任务执行完毕后,再从任务队列中依次取出任务执行。for 循环是同步任务,会先执行完循环,此时 i 的值是 4。4ms后 console.log(i) 被依次放入任务队列,此时如果执行栈中没有同步任务了,就从任务队列中依次取出任务,所以打印出 4 个 4。
那么如何才能按照期望打印出 0, 1,2,3 呢?有三个方法:

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
//方法1:把 var 换成 let
for(let i=0; i<4; i++){
setTimeout(function(){
console.log(i)
}, 0)
}

//方法2:使用立即执行函数
for(let i=0; i<4; i++){
(function(i){
setTimeout(function(){
console.log(i)
}, 0)
})(i)
}

//方法3:加闭包
for(let i=0; i<4; i++){
var a = function(){
var j = i;
setTimeout(function(){
console.log(j)
}, 0)
}
a();
}
4) 第4题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
setTimeout(function(){
console.log(1)
});
new Promise(function(resolve){
console.log(2);
for(var i = 0; i < 10000; i++){
i == 9999 && resolve();
}
}).then(function(){
console.log(3)
});
console.log(4);
执行结果:
// 2, 4, 3, 1

分析:1.setTimeout是异步,且是宏函数,放到宏函数队列中;2.new Promise是同步任务,直接执行,打印2,并执行for循环;3.promise.then是微任务,放到微任务队列中;4.console.log(4)同步任务,直接执行,打印4;5.此时主线程任务执行完毕,检查微任务队列中,有promise.then,执行微任务,打印3;6.微任务执行完毕,第一次循环结束;从宏任务队列中取出第一个宏任务到主线程执行,打印1;7.结果:2,4,3,1

5) 第5题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
console.log(1);

setTimeout(function() {
console.log(2);
}, 0);

Promise.resolve().then(function() {
console.log(3);
}).then(function() {
console.log('4.我是新增的微任务');
});

console.log(5);

执行结果:
// 1,5,3,4.我是新增的微任务,2

分析:1.console.log(1)是同步任务,直接执行,打印1;2.setTimeout是异步,且是宏函数,放到宏函数队列中;3.Promise.resolve().then是微任务,放到微任务队列中;4.console.log(5)是同步任务,直接执行,打印5;5.此时主线程任务执行完毕,检查微任务队列中,有Promise.resolve().then,执行微任务,打印3;6.此时发现第二个.then任务,属于微任务,添加到微任务队列,并执行,打印4.我是新增的微任务;7.这里强调一下,微任务执行过程中,发现新的微任务,会把这个新的微任务添加到队列中,微任务队列依次执行完毕后,才会执行下一个循环;8.微任务执行完毕,第一次循环结束;取出宏任务队列中的第一个宏任务setTimeout到主线程执行,打印2;9.结果:1,5,3,4.我是新增的微任务,2

6) 第6题
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
function add(x, y) {
console.log(1)
setTimeout(function() { // timer1
console.log(2)
}, 1000)
}
add();

setTimeout(function() { // timer2
console.log(3)
})

new Promise(function(resolve) {
console.log(4)
setTimeout(function() { // timer3
console.log(5)
}, 100)
for(var i = 0; i < 100; i++) {
i == 99 && resolve()
}
}).then(function() {
setTimeout(function() { // timer4
console.log(6)
}, 0)
console.log(7)
})

console.log(8)

执行结果
//1,4,8,7,3,6,5,2

分析:1.add()是同步任务,直接执行,打印1;2.add()里面的setTimeout是异步任务且宏函数,记做timer1放到宏函数队列;3.add()下面的setTimeout是异步任务且宏函数,记做timer2放到宏函数队列;4.new Promise是同步任务,直接执行,打印4;5.Promise里面的setTimeout是异步任务且宏函数,记做timer3放到宏函数队列;6.Promise里面的for循环,同步任务,执行代码;7.Promise.then是微任务,放到微任务队列;8.console.log(8)是同步任务,直接执行,打印8;9.此时主线程任务执行完毕,检查微任务队列中,有Promise.then,执行微任务,发现有setTimeout是异步任务且宏函数,记做timer4放到宏函数队列;10.微任务队列中的console.log(7)是同步任务,直接执行,打印7;11.微任务执行完毕,第一次循环结束;12.检查宏任务Event Table,里面有timer1、timer2、timer3、timer4,四个定时器宏任务,按照定时器延迟时间得到可以执行的顺序,即Event Queue:timer2、timer4、timer3、timer1,取出排在第一个的timer2;13.取出timer2执行,console.log(3)同步任务,直接执行,打印3;14.没有微任务,第二次Event Loop结束;15.取出timer4执行,console.log(6)同步任务,直接执行,打印6;16.没有微任务,第三次Event Loop结束;17.取出timer3执行,console.log(5)同步任务,直接执行,打印5;18.没有微任务,第四次Event Loop结束;19.取出timer1执行,console.log(2)同步任务,直接执行,打印2;20.没有微任务,也没有宏任务,第五次Event Loop结束;21.结果:1,4,8,7,3,6,5,2

7) 第7题
1
2
3
4
5
6
7
8
9
10
11
12
setTimeout(function() { // timer1
console.log(1);
setTimeout(function() { // timer3
console.log(2);
})
}, 0);
setTimeout(function() { // timer2
console.log(3);
}, 0);

执行结果
//1,3,2

分析:1.第一个setTimeout是异步任务且宏函数,记做timer1放到宏函数队列;2.第三个setTimeout是异步任务且宏函数,记做timer2放到宏函数队列;3.没有微任务,第一次Event Loop结束;4.取出timer1,console.log(1)同步任务,直接执行,打印1;5.timer1里面的setTimeout是异步任务且宏函数,记做timer3放到宏函数队列;6.没有微任务,第二次Event Loop结束;7.取出timer2,console.log(3)同步任务,直接执行,打印3;8.没有微任务,第三次Event Loop结束;9.取出timer3,console.log(2)同步任务,直接执行,打印2;10.没有微任务,也没有宏任务,第四次Event Loop结束;11.结果:1,3,2

页面性能

提升页面性能的方法有哪些?

  1. 资源压缩合并,减少 HTTP 请求
  2. 非核心代码异步加载(异步加载的方式,异步加载的区别)
  3. 利用浏览器缓存(缓存的分类,缓存原理)
  4. 使用 CDN
  5. 预解析 DNS
    1
    2
    3
    4
    //强制打开 <a> 标签的 dns 解析
    <meta http-equiv="x-dns-prefetch-controller" content="on">
    //DNS预解析
    <link rel="dns-prefetch" href="//host_name_to_prefetch.com">

1 异步加载

异步加载的方式
  1. 动态脚本加载
  2. defer
  3. async
    异步加载的区别
    defer 是在 HTML 解析完之后才会执行,如果是多个,按照加载的顺序依次执行。defer脚本会在DOMContentLoaded和load事件之前执行。
    async 是在脚本加载完之后立即执行,如果是多个,执行顺序和加载顺序无关。async会在load事件之前执行,但并不能确保与DOMContentLoaded的执行先后顺序。

    2 浏览器缓存

    https://www.jianshu.com/p/256d0873c398
    缓存策略的分类:
  • 强缓存

  • 协商缓存
    缓存策略都是通过设置 HTTP Header 来实现的。
    浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识。
    浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中。

    1 强缓存

    强缓存:不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的Network选项中可以看到该请求返回200的状态码,并且Size显示from disk cache或from memory cache。强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control。

    1. Expires

    缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。也就是说,Expires=max-age + 请求时间,需要和Last-modified结合使用。Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。Expires 是 HTTP/1 的产物,受限于本地时间,如果修改了本地时间,可能会造成缓存失效。Expires: Wed, 22 Oct 2018 08:41:00 GMT表示资源会在 Wed, 22 Oct 2018 08:41:00 GMT 后过期,需要再次请求。

    2. Cache-Control

    在HTTP/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存。比如当Cache-Control:max-age=300时,则代表在这个请求正确返回时间(浏览器也会记录下来)的5分钟内再次加载资源,就会命中强缓存。Cache-Control 可以在请求头或者响应头中设置,并且可以组合使用多种指令:

    3. Expires和Cache-Control两者对比

    其实这两者差别不大,区别就在于 Expires 是http1.0的产物,Cache-Control是http1.1的产物,两者同时存在的话,Cache-Control优先级高于Expires;在某些不支持HTTP1.1的环境下,Expires就会发挥用处。所以Expires其实是过时的产物,现阶段它的存在只是一种兼容性的写法。强缓存判断是否缓存的依据来自于是否超出某个时间或者某个时间段,而不关心服务器端文件是否已经更新,这可能会导致加载文件不是服务器端最新的内容,那我们如何获知服务器端内容是否已经发生了更新呢?此时我们需要用到协商缓存策略。

    2 协商缓存

    协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:

  • 协商缓存生效,返回304和Not Modified

  • 协商缓存失效,返回200和请求结果

协商缓存可以通过设置两种 HTTP Header 实现:Last-Modified 和 ETag 。

1. Last-Modified 和 If-Modified-Since

浏览器在第一次访问资源时,服务器返回资源的同时,在response header中添加 Last-Modified 的header,值是这个资源在服务器上的最后修改时间,浏览器接收后缓存文件和 header;
Last-Modified: Fri, 22 Jul 2016 01:47:00 GMT
浏览器下一次请求这个资源,浏览器检测到有 Last-Modified这个header,于是添加If-Modified-Since这个header,值就是Last-Modified中的值;服务器再次收到这个资源请求,会根据 If-Modified-Since 中的值与服务器中这个资源的最后修改时间对比,如果没有变化,返回304和空的响应体,直接从缓存读取,如果If-Modified-Since的时间小于服务器中这个资源的最后修改时间,说明文件有更新,于是返回新的资源文件和200。但是 Last-Modified 存在一些弊端:

  • 如果本地打开缓存文件,即使没有对文件进行修改,但还是会造成 Last-Modified 被修改,服务端不能命中缓存导致发送相同的资源
  • 因为 Last-Modified 只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源

既然根据文件修改时间来决定是否缓存尚有不足,能否可以直接根据文件内容是否修改来决定缓存策略?所以在 HTTP / 1.1 出现了 ETag 和If-None-Match

2. ETag 和 If-None-Match

Etag 是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,Etag就会重新生成。浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的Etag值放到request header里的If-None-Match里,服务器只需要比较客户端传来的If-None-Match跟自己服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。如果服务器发现ETag匹配不上,那么直接以常规GET 200回包形式将新的资源(当然也包括了新的ETag)发给客户端;如果ETag是一致的,则直接返回304知会客户端直接使用本地缓存即可。

3. 两者之间对比:

首先在精确度上,Etag要优于Last-Modified。

Last-Modified的时间单位是秒,如果某个文件在1秒内改变了多次,那么他们的Last-Modified其实并没有体现出来修改,但是Etag每次都会改变确保了精度;如果是负载均衡的服务器,各个服务器生成的Last-Modified也有可能不一致。

第二在性能上,Etag要逊于Last-Modified,毕竟Last-Modified只需要记录时间,而Etag需要服务器通过算法来计算出一个hash值。
第三在优先级上,服务器校验优先考虑Etag

3 缓存机制

强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,返回200,重新返回资源和缓存标识,再存入浏览器缓存中;生效则返回304,继续使用缓存。

4 强缓存与协商缓存的区别

强缓存与协商缓存的区别可以用下表来表示:
缓存类型 获取资源形式 状态码 发送请求到服务器
强缓存 从缓存取 200(from cache) 否,直接从缓存取
协商缓存 从缓存取 304(Not Modified) 是,通过服务器来告知缓存是否可用

用户行为对缓存的影响
用户操作 Expires/Cache-Control Last-Modied/Etag
地址栏回车 有效 有效
页面链接跳转 有效 有效
新开窗口 有效 有效
前进回退 有效 有效
F5刷新 无效 有效
Ctrl+F5强制刷新 无效 无效

git常用指令

Posted on 2020-06-15

创建新的Git仓库

$ git init

拷贝一个Git仓库到本地

git clone [url]

添加到缓存

git add

$ git add README hello.php

1
2
3
4
$ git add .
$ git status -s
A README
A hello.php

查看在你上次提交之后是否有修改

1
2
3
4
5
6
7
8
9
10
$ git status
On branch master

Initial commit

Changes to be committed:
(use "git rm --cached <file>..." to unstage)

new file: README
new file: hello.php

将缓存区内容添加到仓库中

git commit

1
2
$ git config --global user.name 'runoob'
$ git config --global user.email test@runoob.com
1
2
3
4
5
6
7
8
9
$ git add hello.php
$ git status -s
A README
A hello.php
$ git commit -m '第一次版本提交'
[master (root-commit) d32cf1f] 第一次版本提交
2 files changed, 4 insertions(+)
create mode 100644 README
create mode 100644 hello.php

git commit -a 可以跳过git add

推送到远程服务器

git push

取消已缓存的内容

git reset HEAD

创建分支命令

git branch (branchname)

切换分支命令

git checkout (branchname)

合并分支命令

git merge

删除分支命令

git branch -d (branchname)

REACT面试题总结

Posted on 2020-03-30

react生命周期有哪些

组件载入阶段:

componentWillMount:组件即将被装载、渲染到页面上,只调用1次
componentDidMount:组件真正在被装载之后,这里可以拿到真实DOM执行操作,只调用1次

运行中状态:

componentWillReceiveProps(nextProps):组件将要接收到新属性的时候调用,在这时setState不会触发额外的render,因为此时已经有一次来自父组件引发的render了。

shouldComponentUpdate:组件接受到新属性或者新状态的时候(返回 false,接收数据后不更新,阻止 render ,后面的函数不会继续执行)

这个方法用来判断是否需要调用 render 方法重绘 dom。
因为 dom 的描绘非常消耗性能,如果我们能在这个方法中能够写出更优化的 dom diff 算法,可以极大的提高性能。

componentWillUpdate:组件即将更新不能修改属性和状态
componentDidUpdate:组件已经更新

销毁阶段:

componentWillUnmount:组件即将销毁,这时候可以销毁绑定的事件监听或者定时器什么的。

有些好像把render也算进生命周期了:
render:组件在这里生成虚拟的 DOM 节点

https://www.jianshu.com/p/b331d0e4b398

为什么不能用数组下标来作为react组件中的key?

react 使用diff算法,使用key来做同级比对。如果使用数组下标作为key,有以下情况:
在数组头部或中部插入或删除元素: 所有key对应的节点的值发生更改,进行重新渲染。造成性能损耗
而如果使用数组中唯一值来作为key:不管是在何处插入或删除节点,其他key对应的节点的值未发生更改,只需插入或删除操作的数组节点。

react的diff算法是怎么完成的

  1. 把树形结构按照层级分解,只比较同级元素。
  2. 通过给列表结构的每个单元添加的唯一 key值进行区分同层次的子节点的比较。
  3. React 只会匹配相同 class 的 component(这里面的 class 指的是组件的名字)
  4. 合并操作,调用 component 的 setState 方法的时候, React 将其标记为 dirty.
    到每一个事件循环结束, React 检查所有标记 dirty 的 component 重新绘制。
  5. 选择性渲染。开发人员可以重写 shouldComponentUpdate 提高 diff 的性能。

https://www.jianshu.com/p/3ba0822018cf

react虚拟DOM实现原理,以及为什么虚拟 dom 会提高性能

  1. 用 js对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中。
  2. 当状态变更的时候,重新构造一棵新的对象树。然后对比新旧虚拟DOM树,记录两棵树差异。
  3. 把 2 所记录的差异应用到步骤 1 所构建的真正的 DOM 树上,视图就更新了。

原因:虚拟 dom 相当于在 js 和真实 dom 中间加了一个缓存,利用 dom diff 算法减少了对真实DOM的操作次数,从而提高性能。

react怎么从虚拟dom中拿出真实dom

Refs 是 React 提供给我们的安全访问 DOM 元素或者某个组件实例的句柄。
我们可以为元素添加 ref 属性然后在回调函数中接受该元素在 DOM 树中的句柄,该值会作为回调函数的第一个参数返回。或者ref可以传字符串。

例如:<input ref=((input)=>{return this.name=input}) />, this.name.value取值
或者 <input ref="name" />,this.refs.name取值

React中的props和state的用法

  • state 是一种数据结构,用于组件挂载时所需数据的默认值。state 可能会随着时间的推移而发生突变,但多数时候是作为用户事件行为的结果。
  • props则是组件的配置。props 由父组件传递给子组件,并且就子组件而言,props 是不可变的(immutable)。

react组件之间如何通信

父子:父传子:props; 子传父:子调用父组件中的函数并传参;
兄弟:利用redux实现。

react的setState的原理及用法

原理:

当调用setState时,它并不会立即改变,而是会把要修改的状态放入一个任务队列,等到事件循环结束时,再合并指更新。
因此,setState有 异步,合并更新更新两个特性。

Batch Update:https://zhuanlan.zhihu.com/p/28532725

使用:

1.最常见的用法就是传入一个对象。

1
2
3
this.setState({
isLoading:false
})

2.还可以接收一个函数

1
2
3
4
this.setState((prevState,props)=>{
// 要做的事件
return {isLoading:false};
})

3.因为setState是异步的,所以它还可以接收第二个参数,一个回调函数

1
2
3
this.setState({count:2},()=>{
isLoading:this.state.count===2 ? true : false
})

setState为什么是异步的

https://segmentfault.com/a/1190000013040438

  1. 保证内部的一致性
    因为props是要等到父组件渲染过后才能拿到,也就是不能同步更新,state出于统一性设成异步更新。
  2. 性能优化
    举例说你正在一个聊天窗口输入,如果来了一条新消息又要render,那就会阻塞你的当前操作,导致延迟什么的。
  3. 支持state在幕后渲染
    异步可以使state在幕后更新,而不影响你当前旧的页面的交互,提升用户体验。
    详情可以点击上面的参考链接,写的很详细的。

另外:setstate在原生事件,setTimeout,setInterval,promise等异步操作中,state会同步更新。

https://segmentfault.com/a/1190000014131698

react的优势以及特点

优势:

  1. 实现对虚拟DOM的操作,使得它速度快,提高了Web性能。
  2. 组件化,模块化。react里每一个模块都是一个组件,组件化开发,可维护性高。
  3. 单向数据流,比较有序,有便于管理,它随着React视图库的开发而被Facebook概念化。
  4. 跨浏览器兼容:虚拟DOM帮助我们解决了跨浏览器问题,它为我们提供了标准化的API,甚至在IE8中都是没问题的。

不足:

  1. react中只是MVC模式的View部分,要依赖引入很多其他模块开发。、
  2. 当父组件进行重新渲染操作时,即使子组件的props或state没有做出任何改变,也会同样进行重新渲染。

特点: 

  1. 声明式设计:React采用声明范式,可以轻松描述应用。
  2. 高效:React通过对DOM的模拟,最大限度地减少与DOM的交互。
  3. 灵活:React可以与已知的库或框架很好地配合。

React如何性能优化

  1. 充分利用shouldComponentUpdate函数,不过这需要你的组件尽量最小化,如果当前组件数据过于复杂,其实是很难优化的。

  2. 给你的DOM遍历上加上唯一的key,注意尽量不要用index,因为如果你新DOM中删了某一个节点,它会重新排列index,
    那跟原来同层级一比就都会完全不一样,而重新渲染了,所以最好使用id值什么的作key值。

  3. 能用const声明的就用const。

  4. DOM里少用箭头函数,当然其实要传参时也还是得用。再者,函数bind尽量写在constructor,避免每次render重新bind。

  5. 减少对真实DOM的操作。

  6. 如果是用webpack搭建环境的话,当一个包过大加载过慢时,可分打成多个包来优化。

react与vue的对比

相同点:

  1. 都用虚拟DOM实现快速渲染
  2. 我觉得父子,兄弟通信这些都挺像的,也都有自己的状态管理器:react=>redux, vue=>vuex
  3. 都是轻量级框架
  4. 现在vue也在渐渐吸收react中的一些语法,比如JSX语法,类式声明写法等

不同点:

  1. React属于单向数据流——MVC模式,vue则属于双向——MVVM模式。
  2. react兼容性比vue好,vue不兼容IE8.
  3. react采用JSX语法,vue采用的则是html模板语法。
  4. vue的css可以有组件的私有作用域,react则没有。
  5. react比vue好的另一点是,它是团队维护,而vue属于个人,一般来说,大型项目更倾向于react,小型则用vue,当然这也不是绝对。

Redux的实现流程

用户页面行为触发一个Action,然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action。Reducer 会返回新的 State 。每当state更新之后,view会根据state触发重新渲染。

react-redux的实现原理

Redux作为一个通用模块,主要还是用来处理应用中state的变更,通过react-redux做连接,可以在React+Redux的项目中将两者结合的更好。
react-redux是一个轻量级的封装库,它主要通过两个核心方法实现:

Provider:从最外部封装了整个应用,并向connect模块传递store。
Connect:
1、包装原组件,将state和action通过props的方式传入到原组件内部。
2、监听store tree变化,使其包装的原组件可以响应state变化

前端面试题总结

Posted on 2019-10-15

HTTP 304状态码

客户端在请求一个文件的时候,发现自己缓存的文件有 Last Modified ,那么在请求中会包含 If Modified Since ,这个时间就是缓存文件的 Last Modified 。因此,如果请求中包含 If Modified Since,就说明已经有缓存在客户端。服务端只要判断这个时间和当前请求的文件的修改时间就可以确定是返回 304 还是 200 。
对于静态文件,例如:CSS、图片,服务器会自动完成 Last Modified 和 If Modified Since 的比较,完成缓存或者更新。但是对于动态页面,就是动态产生的页面,往往没有包含 Last Modified 信息,这样浏览器、网关等都不会做缓存,也就是在每次请求的时候都完成一个 200 的请求。
因此,对于动态页面做缓存加速,首先要在 Response 的 HTTP Header 中增加 Last Modified 定义,其次根据 Request 中的 If Modified Since 和被请求内容的更新时间来返回 200 或者 304 。虽然在返回 304 的时候已经做了一次数据库查询,但是可以避免接下来更多的数据库查询,并且没有返回页面内容而只是一个 HTTP Header,从而大大的降低带宽的消耗,对于用户的感觉也是提高。当这些缓存有效的时候,通过 Fiddler 或HttpWatch 查看一个请求会得到这样的结果

缓存的分类

在Web应用领域,Web缓存大致可以分为以下几种类型:

  1. 数据库数据缓存
    Web应用,特别是社交网络服务类型的应用,往往关系比较复杂,数据库表繁多,如果频繁进行数据库查询,很容易导致数据库不堪重荷。为了提供查询的性能,会将查询后的数据放到内存中进行缓存,下次查询时,直接从内存缓存直接返回,提供响应效率。比如常用的缓存方案有memcached,redis等。

  2. 服务器端缓存

    • 代理服务器缓存
      代理服务器是浏览器和源服务器之间的中间服务器,浏览器先向这个中间服务器发起Web请求,经过处理后(比如权限验证,缓存匹配等),再将请求转发到源服务器。代理服务器缓存的运作原理跟浏览器的运作原理差不多,只是规模更大。可以把它理解为一个共享缓存,不只为一个用户服务,一般为大量用户提供服务,因此在减少相应时间和带宽使用方面很有效,同一个副本会被重用多次。常见代理服务器缓存解决方案有Squid,Nginx,Apache等。

    • CDN缓存
      CDN(Content delivery networks)缓存,也叫网关缓存、反向代理缓存。CDN缓存一般是由网站管理员自己部署,为了让他们的网站更容易扩展并获得更好的性能。浏览器先向CDN网关发起Web请求,网关服务器后面对应着一台或多台负载均衡源服务器,会根据它们的负载请求,动态将请求转发到合适的源服务器上。虽然这种架构负载均衡源服务器之间的缓存没法共享,但却拥有更好的处扩展性。从浏览器角度来看,整个CDN就是一个源服务器,浏览器和服务器之间的缓存机制,在这种架构下同样适用。

  3. 浏览器端缓存
    浏览器缓存根据一套与服务器约定的规则进行工作,在同一个会话过程中会检查一次并确定缓存的副本足够新。如果你浏览过程中,比如前进或后退,访问到同一个图片,这些图片可以从浏览器缓存中调出而即时显现。

  4. Web应用层缓存
    应用层缓存指的是从代码层面上,通过代码逻辑和缓存策略,实现对数据,页面,图片等资源的缓存,可以根据实际情况选择将数据存在文件系统或者内存中,减少数据库查询或者读写瓶颈,提高响应效率。

    前端性能优化

    减少http请求数量:就是资源的合并
    减少http请求大小:就是资源的压缩

    HTTPS比HTTP慢的原因

    HTTPs链接比不加密的HTTP链接慢很多。

HTTPs链接和HTTP链接都建立在TCP协议之上。HTTP链接比较单纯,使用三个握手数据包建立连接之后,就可以发送内容数据了。

客户端首先发送SYN数据包,然后服务器发送SYN+ACK数据包,最后客户端发送ACK数据包,接下来就可以发送内容了。这三个数据包的发送过程,叫做TCP握手。

再来看HTTPs链接,它也采用TCP协议发送数据,所以它也需要上面的这三步握手过程。而且,在这三步结束以后,它还有一个SSL握手。

HTTP耗时 = TCP握手

HTTPs耗时 = TCP握手 + SSL握手
所以,HTTPs肯定比HTTP耗时,这就叫SSL延迟。

命令行工具curl有一个w参数,可以用来测量TCP握手和SSL握手的具体耗时,以访问支付宝为例。

1
2
$ curl -w "TCP handshake: %{time_connect}, SSL handshake: %{time_appconnect}\n" -so 
/dev/null https://www.alipay.comTCP handshake: 0.022, SSL handshake: 0.064

上面命令中的w参数表示指定输出格式,timeconnect变量表示TCP握手的耗时,timeappconnect变量表示SSL握手的耗时,s参数和o参数用来关闭标准输出。

从运行结果可以看到,SSL握手的耗时(64毫秒)大概是TCP握手(22毫秒)的三倍。

也就是说,在建立连接的阶段,HTTPs链接比HTTP链接要长3倍的时间,具体数字取决于CPU的快慢。

SSL是安全套接层,是Netscape所研发,用以保障在Internet上数据传输的安全,利用数据加密(Encryption)技术,可确保数据在网络上之传输过程中不会被截取及窃听的一种应用软件

所以,如果是对安全性要求不高的场合,为了提高网页性能,建议不要采用保密强度很高的数字证书。一般场合下,1024位的证书已经足够了,2048位和4096位的证书将进一步延长SSL握手的耗时。

前端主流MVVM框架双向绑定原理简述:

AngularJs(脏检测机制):

脏检查机制,主要是依据 $watch 对象来监测数据是否更新。
https://github.com/xufei/blog/issues/10

Vue.js(前端数据对象劫持):

Vue.js是通过数据劫持结合发布者-订阅者模式的方式来实现的。

React(手动触发绑定):

React本身并没有提到双向绑定的概念,但是可以利用setState api对states数据进行更新,从而实现数据层于视图层的同步更新(结构更新,重新“渲染”子树(虚拟DOM),找出最小改动步骤,打包DOM操作,对真实DOM树进行修改)。

网页从输入网址到渲染完成经历了哪些过程?

大致可以分为如下7步:

  • 输入网址
  • 发送到DNS服务器,并获取域名对应的web服务器对应的ip地址
  • 与web服务器建立TCP连接
  • 浏览器向web服务器发送http请求
  • web服务器响应请求,并返回指定url的数据(或错误信息,或重定向的新的url地址)
  • 浏览器下载web服务器返回的数据及解析html源文件
  • 生成DOM树,解析css和js,渲染页面,直至显示完成

jQuery获取的dom对象和原生的dom对象有何区别?

js原生获取的dom是一个对象,jQuery对象就是一个数组对象,其实就是选择出来的元素的数组集合,所以说他们两者是不同的对象类型不等价。

原生DOM对象转jQuery对象:

1
2
var box = document.getElementById('box');
var $box = $(box);

jQuery对象转原生DOM对象:

1
2
var $box = $('#box');
var box = $box[0];

对于MVVM的理解

MVVM 是 Model-View-ViewModel 的缩写。
Model代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑。
View 代表UI 组件,它负责将数据模型转化成UI 展现出来。
ViewModel 监听模型数据的改变和控制视图行为、处理用户交互,简单理解就是一个同步View 和 Model的对象,连接Model和View。
在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。
ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。

axios是什么?怎么使用?

axios是基于Promise的,用于浏览器和nodeJS的http客户端,主要作用就是向后台发送请求。其存在许多优点:

  • 支持Promise
  • 支持并发请求
  • 提供拦截器
  • 浏览器支持防止csrf(跨站请求伪造)

axios、fetch、ajax(jquery)的区别

axios和fetch是基于Promise的,ajax是基于callback的形式。fetch脱离了xhr,是新的语法,默认是不传cookie的,监听不到请求进度。

VUE前端面试题总结

Posted on 2019-10-06

简述Vue的响应式原理(Vue的双向数据绑定原理)

当一个Vue实例创建时,vue会遍历data选项的属性,用 Object.defineProperty 将它们转为 getter/setter并且在内部追踪相关依赖,在属性被访问和修改时通知变化。
每个组件实例都有相应的 watcher 程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。

vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
具体实现步骤,感兴趣的可以看看:

  • 当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 都加上 setter和getter 这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化
  • compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
  • Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是:1、在自身实例化时往属性订阅器(dep)里面添加自己;2、自身必须有一个update()方法;3、待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。
  • MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。

https://www.jianshu.com/p/c4dfc5806178

Vue-给对象新增属性

官方定义:

Vue 不允许在已经创建的实例上动态添加新的根级响应式属性 (root-level reactive property)。然而它可以使用 Vue.set(object, key, value) 方法将响应属性添加到嵌套的对象上:

Vue.set(vm.obj, ‘e’, 0)
您还可以使用 vm.$set 实例方法,这也是全局 Vue.set 方法的别名:

this.$set(this.obj,’e’,02)

有时你想向已有对象上添加一些属性,例如使用 Object.assign() 或 _.extend() 方法来添加属性。但是,添加到对象上的新属性不会触发更新。在这种情况下可以创建一个新的对象,让它包含原对象的属性和新的属性:

// 代替 Object.assign(this.obj, { a: 1, e: 2 })
this.obj= Object.assign({}, this.obj, { a: 1, e: 2 })

v-model语法糖

1
2
3
<input v-model="something">
等于
<input v-bind:value="something" v-on:input="something = $event.target.value">

v-model=”something”则表示将value值绑定在something上,当值发生改变时触发绑定的oninput事件。oninput事件绑定的函数是将触发oninput事件的目标(该input)的value值赋值给something这个变量。

v-model主要用于数据的双向绑定,其内部主要完成了两个操作: 通过v-bind绑定value属性值和监听input事件,并更新数据

v-if和v-show指令

v-if 是条件渲染指令,控制的是组件是否创建(渲染),值为true则渲染该组件,值为false则不渲染该组件,对应Html元素则不会存在于浏览器的html文档中,即打开浏览器调试工具找不到该组件对应的渲染结果。
v-show控制的是组件是否可见,并且是通过css样式的display属性来控制组件的显示与隐藏,所以其值无论是true还是false,对应Html元素都会存在于浏览器的html文档中,即打开浏览器调试工具都能够找到该组件对应的渲染结果,只不过值为false的时候,会在该组件上添加style=”display: none;”;
需要注意的是,在vue组件开发的时候,即在.vue中使用v-show的时候,当给<style>标签添加上scoped,会导致其中的css优先级变高,此时如果添加了v-show的元素上同时使用了display样式,那么将会导致v-show为false的时候添加的dispaly:none失效。

v-for 与 v-if 的优先级

v-for比v-if优先级更高,所以不建议v-for和v-if一起使用,如果v-for和v-if同时使用,那么数据发生变化时,v-for首先会进行遍历,然后通过v-if进行判断,这样v-for和v-if都会同时执行一遍,对性能和展现不友好。
所以vue建议用计算属性进行替代,返回过滤后的列表再进行遍历。

v-for循环中key作用

key的作用主要就是为了性能优化,key让组件具有了唯一性,能让diff算法更快的找到需要更新的组件dom,在绑定key的时候,最好是一个唯一的值,如 item.id 而不能是简单的index,如果不使用唯一key,那么在有状态组件中会出现渲染错误。因为它默认用就地复用策略,如果数据项的顺序被改变,那么vue将不是移动DOM元素来匹配数据项的改变,而是简单复用此处每个元素,不会重新排列元素的位置。如果是使用 key,它会基于key重新排列元素顺序,并且会移除 key 不存在的元素。
简单说就是,不使用key就会原地复用,使用key就会对元素位置进行重新排列,能够关联数据状态。

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
<template>
<div class="home">
<input type="text" v-model="name">
<button @click="add">添加</button>
<ul>
<li v-for="(item, index) in list" :key="item.id"><!--注意,这里不能使用:key="index"-->
<input type="checkbox">{{item.name}}
</li>
</ul>
</div>
</template>
<script>
export default {
data: () => {
return {
list: [
{ id: 0, name: 'A' },
{ id: 1, name: 'B' },
{ id: 2, name: 'C' }
]
}
},
methods: {
add() {
this.list.unshift({ id:3, name:'D'}) // 将D添加到A的前面
}
},
}
</script>

如果v-for上不加key,那么当勾选A前面的复选框后,再点击添加按钮,D会添加到A的前面,由于原地复用原则,不会进行位置的移动,所以第一个位置的复选框是勾选状态会被继承到D上,即D会变成勾选状态而A将失去勾选状态,这个显然与原来状态不符;如果v-for上加上:key="item.id",那么D添加到A前面之后,A、B、C都会向后移动,然后再将D插入到A的前面,所以插入D后,A仍然保持勾选状态。

vue路由传参数的三种方式

① query: 直接输入url地址,url地址上带上查询参数,如: localhost:8080/home?foo=1 或者 通过路由对象 $router 调用push()方法进行传参this.$router.push({path:"/home",query:{"foo":"1"}});然后通过this.$route.query.foo进行获取传递过来的参数

② params: 通过路由对象$router调用push()方法进行传参this.$router.push({name:"home", params:{foo: 1}});然后通过this.$route.params.foo获取传递过来的参数

③ 动态路由传参: 路由path位置为/home/:foo,然后输入url,如: localhost:8080/home/1 然后通过this.$route.params.foo获取传递过来的参数

v-on 常用修饰符

.stop 该修饰符将阻止事件向上冒泡。同理于调用 event.stopPropagation() 方法,即如果当前元素添加了.stop修饰符,那么当点击该元素时候,click事件不会冒泡到其父元素上,即父元素不会触发click事件。
.prevent 该修饰符会阻止当前事件的默认行为。同理于调用 event.preventDefault() 方法,即如果<a href=”http://www.baidu.com" @click.stop=”show”>连接,点击后默认会跳转到百度,但是添加上.stop修饰符之后,就不会跳转到百度了,而是执行show()方法了。
.self 该指令只有当事件是从事件绑定的元素本身触发时才触发回调,即冒泡事件到达该元素上时,并不会触发事件,但是其不影响事件继续向上冒泡,其父元素仍然会触发冒泡事件
.native 就是给自定义组件的根元素添加一个原生事件,所以其通常用在自定义组件上,如果给普通的HTML元素添加.native修饰符,那么该HTML元素将无法监听到该事件了。
.capture 就是让事件监听变成捕获,默认为冒泡,通常用于修饰父元素,如果给父元素添加@click.capture修饰符,那么当点击子元素的时候,父元素的click事件将先触发,然后才是子元素的click事件。
.once 该修饰符表示绑定的事件只会被触发一次。

vue中的 ref

ref 被用来给元素或子组件注册引用信息,引用信息将会注册在父组件的 $refs 对象上,即类似于给组件或者DOM元素上添加一个标识id,然后通过这个标识id拿到对应的DOM元素或组件实例,如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例。

$route和$router的区别

$route是”路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。
$router是”路由实例”对象包括了路由的跳转方法,钩子函数等。

Vue定义组件

全局定义:调用Vue的component()方法创建,Vue.component(组件名, {template: 模板字符串})
​局部定义:在创建Vue实例时传递的options对象中的components对象中进行定义,components:{组件名: {template: 模板字符串}}
单文件组件:在.vue文件中定义,包含template,script,style三部分。

Vue-cli的src文件夹中有哪些文件

assets文件夹是放静态资源;
components是放组件;
router是定义路由相关的配置;
view视图;
app.vue是一个应用主组件;
main.js是入口文件

vue等单页面应用及其优缺点

优点:Vue 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件,核心是一个响应的数据绑定系统。MVVM、数据驱动、组件化、轻量、简洁、高效、快速、模块友好。
缺点:不支持低版本的浏览器,最低只支持到IE9;不利于SEO的优化(Search Engine Optimization,搜索引擎优化)(如果要支持SEO,建议通过服务端来进行渲染组件);第一次加载首页耗时相对长一些;不可以使用浏览器的导航按钮需要自行实现前进、后退。

计算属性(computed)、方法(methods)和侦听属性(watch)的区别与使用场景

  • methods VS computed

    我们可以将同一函数定义为一个 method 而不是一个计算属性。对于最终的结果,两种方式确实是相同的。
    然而,不同的是计算属性是基于它们的依赖进行缓存的。计算属性只有在它的相关依赖发生改变时才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。
    相比而言,只要发生重新渲染,method 调用总会执行该函数。总之,重新计算开销很大的话请选计算属性,不希望有缓存的请选methods。
  • watch VS computed

    当你在模板内使用了复杂逻辑的表达式时,你应当使用计算属性。
    侦听属性是一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。
    当你有一些数据需要随着其它数据变动而变动时,或者当需要在数据变化时执行异步或开销较大的操作时,你可以使用 watch。

vuex是什么?哪种功能场景使用它

vuex是一个专门为vue构建的状态机管理机制,它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化,主要解决组件间数据共享的问题,其实就是采用类似全局对象的形式来管理所有组件的公共数据,其强调的是集中式管理,主要是为了便于维护、组件解耦,适合大型项目,有多个视图组件共同依赖一个状态的情况下使用,比如商城系统、外卖系统。
vuex的核心: state、getters、mutations、actions、modules。

视图通过点击事件,触发mutations中方法,可以更改state中的数据,一旦state数据发生更改,getters把数据反映到视图。

那么actions,可以理解处理异步,而单纯多加的一层。

既然提到了mutions actions这时候 就不得不提commit,dispatch这两个有什么作用呢?

在vue例子中,通过click事件,触发methods中的方法。当存在异步时,而在vuex中需要dispatch来触发actions中的方法,actions中的commit可以触发mutations中的方法。同步,则直接在组件中commit触发vuex中mutations中的方法。

vue当中常用的指令及其用法

v-if: 这是一个条件渲染指令,代表存在和销毁,用于控制组件的创建与否;
v-bind: 这是一个绑定指令,用于绑定属性,可简写为冒号;
v-on: 这是一个监听指令,用于监听事件,可简写为@;
v-for: 这是一个循环指令,用于遍历数组;

路由懒加载

所谓路由懒加载,即在项目打包的时候,项目中通常会有多个路由,如果将所有路由对应的组件都打包到一个文件中,那么最终打包的文件就会变得非常大,会影响页面的加载性能,如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才异步加载出对应组件,这样就会变得更加高效。
所以其原理就是利用了webpack的代码分割(按需加载)机制和vue的异步组件功能,代码被分割后就会变成一个单独的文件,所以路由被访问的时候需要向服务器发起请求加载该组件,这是一个异步的过程,所以需要使用到vue的异步组件机制。

异步组件,就是在注册组件的时候,传入一个函数,然后这个函数返回一个Promise对象,resolve的值为这个组件对象,如:

1
2
3
4
5
6
7
8
9
10
11
12
export default new Router({
routes: [
{
path: '/about',
name: 'about',
component: () => { // 注册为一个异步组件
const About = require("./views/About.vue");
return Promise.resolve(About);
}
}
]
});

或者在注册异步组件的时候传入resolve和reject,如:

1
2
3
4
5
6
7
8
9
10
11
12
export default new Router({
routes: [
{
path: '/about',
name: 'about',
component: (resolve, reject) => { // 注册为一个异步组件
const About = require("./views/About.vue");
resolve(About);
}
}
]
});

webpack提供的import()函数会返回一个Promise对象,并且会对引入的组件进行代码分割,所以可以通过import()同时实现代码分割和组件异步加载,即路由懒加载,如:

1
2
3
4
5
6
7
8
9
10
export default new Router({
routes: [
{
path: '/about',
name: 'about',
component: () => import('./views/About.vue')
// 等价于注册异步组件并返回一个Promise对象,分割代码的同时进行异步加载
}
]
});

插槽

插槽其实就是组件中提供的占位符,所以插槽占位符在组件中使用,插槽有三种: 匿名插槽、具名插槽、作用域插槽。

  • 匿名插槽:即没有名字的插槽,即<slot></slot>,使用组件的时候会将组件中的innerHTML插入到<slot></slot>位置上,相当于动态向组件内部传递数据。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // About.vue组件
    <template>
    <div class="hello">
    <slot></slot>
    </div>
    </template>

    // 使用About组件
    <About>
    <h1>hello world</h1><!--该内容会插入到上面<slot></slot>位置上,即替换掉<slot></slot>-->
    </About>
  • 具名插槽: 即有名字的插槽,需要在<slot>标签上添加一个name属性,指定<slot>的名称,即<slot name="header"></slot>,同时使用组件的时候需要给其中的innerHTML添加slot属性,属性值为<slot>的name属性值,如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // About.vue组件
    <template>
    <div class="hello">
    <slot name="header"></slot> <!--定义slot的名称为header-->
    </div>
    </template>

    // 使用About组件
    <About>
    <h1 slot="header">header</h1> <!--该内容会被插入到名称为header的slot上-->
    </About>
  • 作用域插槽: 作用域插槽可以理解为组件向外输出数据,我们可以在组件的<slot>标签上添加上一些属性,然后其中的属性值可以传到组件外使用,会将slot标签上的所以属性合并到一个对象对外输出,组件外通过slot-scope指定一个变量名来接收这个对象,如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // About.vue组件
    <template>
    <div class="hello">
    <h1 slot="footer" slot-scope="innerData">{{innerData.msg}} {{innerData.foo}}</h1><!--指定innerData变量接收组件slot标签中属性集对象-->
    </div>
    </template>

    // 使用About组件
    <About>
    <slot name="footer" msg="haha" foo="foo"></slot><!--会将其中的属性合并成一个对象对外输出-->
    </About>

请详细说下你对vue生命周期的理解

vue生命周期总共分为8个阶段:创建前/后,载入前/后,更新前/后,销毁前/后。

  • beforeCreate (创建前)vue实例的挂载元素$el和数据对象data都是undefined, 还未初始化
  • created (创建后) 完成了 data数据初始化, el还未初始化
  • beforeMount (载入前) vue实例的$el和data都初始化了, 相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。注意此时还没有挂载html到页面上。
  • mounted (载入后) 在el 被新创建的 vm.$el替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html页面中。此过程中进行ajax交互
  • beforeUpdate (更新前) 在数据更新之前调用,发生在虚拟DOM重新渲染和打补丁之前。可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。
  • updated (更新后) 在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
  • beforeDestroy (销毁前) 在实例销毁之前调用。实例仍然完全可用。
  • destroyed (销毁后) 在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。

+路由生命周期
https://segmentfault.com/a/1190000013956945#item-1-4

Vue组件复用时,vue-router如何响应路由参数的变化

当使用路由参数时,例如从 /user/lucy 导航到 /user/lily,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用,复用组件时,想对路由参数的变化作出响应的话,有两种方式:

  • 监听$route对象数据变化

    1
    2
    3
    4
    5
    6
    7
    8
    export default {
    watch: {
    '$route': (to, from) =>{
    console.log("route change");// 对路由变化作出响应...

    }
    }
    }
  • 通过beforeRouteUpdate路由钩子

    1
    2
    3
    4
    5
    6
    export default {
    beforeRouteUpdate(to, from ,next) {
    console.log("beforeRouteUpdate hook run.");
    next();
    }
    }

Vue 组件中 data 为什么必须是函数

因为组件是可以多次复用的,也就是说会有多个组件实例同时存在,同时由于对象是引用数据类型,如果所有组件实例都共用一个data数据对象,那么一个组件对data数据对象进行修改,那么其他组件实例也会受到影响,所以需要使用函数返回data对象的独立拷贝,使得每个组件实例都会拥有自己的data数据对象,相互之间独立,不会互相受影响,便于组件维护。

当data定义为对象后,这就表示所有的组件实例共用了一份data数据,因此,无论在哪个组件实例中修改了data,都会影响到所有的组件实例。组件中的data写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份data,就会造成一个变了全都会变的结果。

vue-loader

vue-loader就是.vue组件的加载器,可以将.vue组件转换为javascript模块,及动态渲染一些数据,同时vue-loader还对.vue组件中的三个标签都进行了相应的优化。<template>标签中可以使用src属性引入一个组件,引入的组件可以直接使用当前组件中的数据,<script>标签中可以直接使用ES6语法,<style>标签可以默认使用sass并且支持scoped作用域选择。

vue-loader会解析文件,提取出每个语言块,如果有必要会通过其他loader处理,最后将他们组装成一个commonjs模块;module.exports出一个vue.js组件对象;

  1. < temlate>语言块
    (1)默认语言:html
    (2)每个.vue文件最多包含一个< template>块
    (3)内容将被提取为字符串,将编译用作VUE组件的template选项;
  2. < script>
    (1)默认语言:JS(在监测到babel-loader或者buble-loader配置时,自动支持ES2015)
    (2)每个.vue文件最多包含一个< script>块
    (3)该脚本在类CommonJS环境中执行(就像通过webpack打包的正常JS模块)。所以你可以require()其他依赖。在ES2015支持下,也可以使用import跟export语法
    (4)脚本必须导出Vue.js组件对象,也可以导出由VUE.extend()创建的扩展对象;但是普通对象是更好的选择;
  3. < style>
    (1)默认语言:css
    (2)一个.vue文件可以包含多个< style>标签
    (3)这个标签可以有 scoped 或者 module属性来帮助你讲样式封装到当前组件;具有不同封装模式的多个< style>标签可以在同一个组件中混合使用
    (4)默认情况下,可以使用style-loader提取内容,并且通过< style>标签动态假如文档的< head>中,也可以配置webpack将所有的styles提取到单个CSS文件中;
  4. 自定义块
    可以在.vue文件中添加额外的自定义块来实现项目的特殊需求;例如< docs>块;vue-loader将会使用标签名来查找对应的webpack loaders来应用到对应的模块上;webpack需要在vue-loader的选项loaders中指定;

vue-loader支持使用非默认语言,比如CSS预处理器,预编译的HTML模板语言,通过设置语言块的lang属性

Vue中keep-alive的作用以及用法

<keep-alive>是vue中一个内置的组件,主要用于缓存组件,其会在组件created的时候,将需要缓存的组件放到缓存中,然后再render的时候再根据name进行取出。<keep-alive>主要配合路由进行使用,在配置路由的时候添加上meta元数据对象,里面添加上keepAlive属性,表示是否缓存该组件,然后将<router-view>放到<keep-alive>中,router-view通过v-if指令,从路由配置上的meta对象中取出keepAlive的值进行判断是否需要缓存

组件缓存后就不会执行组件的beforeCreate、created和beforeMount、mounted钩子了,所以其提供了actived和deactived钩子,actived钩子主要用于承担原来created钩子中获取数据的任务。

  • mounted钩子 在主页挂载时执行一次,如果没有缓存的话,再次回到主页时,mounted还会执行,从而导致ajax反复获取数据。

  • activated钩子 则不受缓存的影响,每次重新回到主页都会执行。

Vue.nextTick

在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

nextTick的由来:
  由于VUE的数据驱动视图更新,是异步的,即修改数据的当下,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。
nextTick的触发时机:
  在同一事件循环中的数据变化后,DOM完成更新,立即执行nextTick(callback)内的回调。
应用场景:
  需要在视图更新之后,基于新的视图进行操作。
以上出现了事件循环的概念,其涉及到JS的运行机制,包括主线程的执行栈、异步队列、异步API、事件循环的协作,此处不展开之后再总结。大致理解:主线程完成同步环境执行,查询任务队列,提取队首的任务,放入主线程中执行;执行完毕,再重复该操作,该过程称为事件循环。而主线程的每次读取任务队列操作,是一个事件循环的开始。异步callback不可能处在同一事件循环中。

https://www.cnblogs.com/hity-tt/p/6729118.html

Vue 父子组件通信方式

  1. 通过prop向子组件传递数据,子组件向父组件传递数值$emit
  2. Vuex

Vue 路由的实现 hash模式 和 history模式

hash模式:在浏览器中符号“#”,#以及#后面的字符称之为hash,用window.location.hash读取;
特点:hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全无用,hash不会重加载页面。
hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如http://www.xxx.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。

history模式:history采用HTML5的新特性;且提供了两个新方法:pushState(),replaceState()可以对浏览器历史记录栈进行修改,以及popState事件的监听到状态变更。
history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如http://www.xxx.com/items/id。后端如果缺少对 /items/id 的路由处理,将返回 404 错误。Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。

https://www.jianshu.com/p/bfffb4b8c9fa

Object.defineProperty()

Posted on 2019-10-06

两种定义形式的区别

1.直接定义

1
2
3
let obj = {}
obj.name = 'jack'
//obj['name'] = 'jack'

等同于

1
2
3
4
5
6
7
let obj = {}
Object.defineProperty(obj,'name',{
value:'jack',
writable:true,
configurable:true,
enumberable:true
})

2.Object.defineProperty()定义

1
2
3
Object.defineProperty(obj,'name',{
value:'jack'
})

等同于

1
2
3
4
5
6
7
let obj = {}
Object.defineProperty(obj,'name',{
value:'jack',
writable:false,
configurable:false,
enumberable:false
})

Object.defineProperty()语法说明

Object.defineProperty(obj, prop, desc)

1
2
3
obj : 需要定义属性的当前对象
prop : 当前需要定义的属性名
desc : 属性描述符
描述符 configurable enumerable value writable get set
数据描述符 Yes Yes Yes Yes No No
存取描述符 Yes Yes No No Yes Yes

描述符参数实例说明

get & set

1
2
3
4
get
一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。当访问该属性时,该方法会被执行,方法执行时没有参数传入,但是会传入this对象(由于继承关系,这里的this并不一定是定义该属性的对象)。 默认为 undefined。
set
一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。 默认为 undefined。
1
2
3
4
5
6
7
8
9
10
let Person = {}
let temp = null
Object.defineProperty(Person, 'name', {
get: function () {
return temp
},
set: function (val) {
temp = val
}
})

configurable

1
2
3
4
5
当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。

这个属性起到两个作用:
(1)目标属性是否可以使用delete删除
(2)目标属性是否可以再次设置特性

enumerable

1
当且仅当该属性的enumerable为true时,该属性才能够出现在对象的枚举属性中。默认为 false。

value

1
该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined。

writable

1
当且仅当该属性的writable为true时,value才能被赋值运算符改变。默认为 false。

React搭建工程脚手架

Posted on 2019-10-06

基础安装

  • 安装node
  • 安装git
  • VSCode

create-react-app基础脚手架

安装create-react-app

1
npm install -g create-react-app

安装完成后

1
2
3
create-react-app react-plan 
cd react-test/
npm start

结束后,在浏览器访问localhost:3000,可以看到默认应用

###

npm install 时--save-dev和--save的区别

Posted on 2019-10-06

在控制台敲指令时,经常碰到npm install –save-dev和–save

我们在使用 npm install 安装模块的模块的时候 ,一般会使用下面这几种命令形式:

1
2
3
4
5
6
7
npm install moduleName # 安装模块到项目目录下

npm install -g moduleName # -g 的意思是将模块安装到全局,具体安装到磁盘哪个位置,要看 npm config prefix 的位置。

npm install -save moduleName # -save 的意思是将模块安装到项目目录下,并在package文件的dependencies节点写入依赖。

npm install -save-dev moduleName # -save-dev 的意思是将模块安装到项目目录下,并在package文件的devDependencies节点写入依赖。

npm install moduleName 命令

  1. 安装模块到项目node_modules目录下。
  2. 不会将模块依赖写入devDependencies或dependencies 节点。
  3. 运行 npm install 初始化项目时不会下载模块。

npm install -g moduleName 命令

  1. 安装模块到全局,不会在项目node_modules目录中保存模块包。
  2. 不会将模块依赖写入devDependencies或dependencies 节点。
  3. 运行 npm install 初始化项目时不会下载模块。

npm install -save moduleName 命令

  1. 安装模块到项目node_modules目录下。
  2. 会将模块依赖写入dependencies 节点。
  3. 运行 npm install 初始化项目时,会将模块下载到项目目录下。
  4. 运行npm install –production或者注明NODE_ENV变量值为production时,会自动下载模块到node_modules目录中。

npm install -save-dev moduleName 命令

  1. 安装模块到项目node_modules目录下。
  2. 会将模块依赖写入devDependencies 节点。
  3. 运行 npm install 初始化项目时,会将模块下载到项目目录下。
  4. 运行npm install –production或者注明NODE_ENV变量值为production时,不会自动下载模块到node_modules目录中。

总结

dependencies是运行时依赖,devDependencies是开发时的依赖。

  • devDependencies 下列出的模块,我们在发布后用不到它,而只是在我们开发才用到它。
  • dependencies 下的模块,则是我们发布后还需要依赖的模块,譬如像react或者Angular框架类似的,我们在开发完后后肯定还要依赖它们,否则就运行不了。
12
Yuqi Qin

Yuqi Qin

11 posts
GitHub E-Mail Instagram 微博
© 2020 Yuqi Qin
Powered by Hexo
|
Theme — NexT.Pisces v5.1.4