vue学习笔记

ES6 中字符串的新方法

给字符串补足位数

padStart(2,’0’)

padEnd(,’’)

vue 指令

v-if

v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。

v-for

当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。这个类似 Vue 1.x 的 track-by="$index"

这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出

为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性:

1
2
3
<div v-for="item in items" v-bind:key="item.id">
<!-- 内容 -->
</div>

v-model

你可以用 v-model 指令在表单 <input><textarea><select> 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model 本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。

v-model 会忽略所有表单元素的 valuecheckedselected 特性的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。

v-model 在内部使用不同的属性为不同的输入元素并抛出不同的事件:

  • text 和 textarea 元素使用 value 属性和 input 事件;
  • checkbox 和 radio 使用 checked 属性和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件。

对于需要使用输入法 (如中文、日文、韩文等) 的语言,你会发现 v-model 不会在输入法组合文字过程中得到更新。如果你也想处理这个过程,请使用 input 事件。

VUE 自定义指令的方法

Vue 中所有的指令在调用时,都以 v-开头

全局定义

方法:

参数 1:指令名称,定义时指令名称不需要加 v-,调用时才加上

参数 2:是一个对象,对象身上有指令相关的函数,函数在特定阶段执行相关操作

钩子函数内的参数,(

第一个为 el,原生 dom 对象,

第二个为指令传入的参数如:v-focus(200) )

1
2
3
4
5
6
7
8
9
10
11
12
13
Vue.directive('focus', {
bind: function (el) {
//当指令绑定元素上,执行bind函数,只执行一次
// el.focus() 无效,因为此时dom还没有解析完成,绑定先行
},
inserted: function () {
//当元素插入DOM中时,执行该函数,只触发一次
el.focus() //插入dom时调用
},
updated: function () {
//当Vnode更新时,执行updated ,可能触发多次
},
})
私有定义

在实例中直接添加 directives 对象即可

1
2
3
4
5
6
7
8
9
10
11
12
directives: {

'fontweight': {
bind: function(el,binding){
el.style.fontWeight = bingding.value;
},
inserted: function(el,b){

}

}
}
简写方式

如果只需要对 bing 和 update 钩子,可以简写如下

1
2
3
4
5
directives: {//等同于同时写了bind和updated两个钩子

'fontsize': function(el,binding){
el.style.fontsize = parseInt(binding.value)+'px';
}

变异方法和非变异方法

变异方法 (mutation method),顾名思义,会改变被这些方法调用的原始数组。

Vue 包含一组观察数组的变异方法,所以它们也将会触发视图更新。这些方法如下:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

相比之下,也有非变异 (non-mutating method) 方法,例如:filter(), concat()slice() 。这些不会改变原始数组,但总是返回一个新数组。当使用非变异方法时,可以用新数组替换旧数组:

VUE 实例的生命周期

主要的生命周期函数分类:lifecycle
1.创建期间的生命周期函数:
  • beforeCreate:实例刚在内存中被创建出来,此时,还没有初始化好 data 和 methods 属性
  • created:实例已经在内存中创建 OK,此时 data 和 methods 已经创建 OK,此时还没有开始 编译模板
  • beforeMount:此时已经完成了模板的编译,但是还没有挂载到页面中
  • mounted:此时,已经将编译好的模板,挂载到了页面指定的容器中显示
2.运行期间的生命周期函数:
  • beforeUpdate:状态更新之前执行此函数, 此时 data 中的状态值是最新的,但是界面上显示的 数据还是旧的,因为此时还没有开始重新渲染 DOM 节点
  • updated:实例更新完毕之后调用此函数,此时 data 中的状态值 和 界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了!
3.销毁期间的生命周期函数:
  • beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
  • destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

VUE 组件

模块化和组件化的区别

模块化:从代码逻辑的角度进行划分;方便代码分层开发,保证每个功能模块的职能单一

组件化:从 UI 界面的角度进行划分;前端的组件化方便 UI 组件的重用

VUE 组件:为了拆分 Vue 实例的代码量,能够以不同的组件,来划分不同的功能模块

组件有的都是 vue 实例有的,除了仅有的例外是像 el这样根实例特有的选项。

6.Vue 组件化创建方式

每个组件必须只有一个根元素

1.全局组件

1.第一种

1
2
3
Vue.component('组件名称', {
template: '这里放标签',
})

第二种(字面量类型的模板组件)

1
2
3
4
5
Vue.component('组件名称',{

template: ‘#这里放id’

})

然后在实例控制区域之外定义 template 标签,直接在里面写结构

使用方法都是直接:<组件名称></组件名称>

注意:这里的组件名称如果使用驼峰命名法的话,那么在 html 结构中要使用-分隔开,而不能使用驼峰

2.私有组件

components 属性定义内部私有组件

1
2
3
4
components:{
'com1':{
template:''
}

7.组件中的 data 属性

组件里的 data 要定义为一个函数,且函数要返回一个对象

1
2
3
4
5
6
7
data:function(){
return { }
}
//可以简写为
data(){
return {}
},

使用方法和实例中的是一样的

问:为什么组件中的 data 必须是一个函数呢?

​ 为了不让复用的组件之间的数据相互影响,所以使用函数来创建,且返回一个内部对象,这样子每次创建一个复用的组件时,之间的数据是相互独立的。

8.组件切换(常用)

如登陆和注册窗口场景

方法 1:使用 v-if 属性和 v-else 属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<login v-if="flag"></login>
<register v-else="flag"></register>
<script>
Vue.component('login', {
template: '<h3>登录组件</h3>',
})

Vue.component('register', {
template: '<h3>注册组件</h3>',
})

// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: '#app',
data: {
flag: false,
},
methods: {},
})
</script>

方法 2:使用 component 标签的 is 属性

定义一个变量名 comName,用注册点击事件来更改展示组件的名称

1
2
3
<a href="" @click.prevent="comName='login'">登录</a>
<a href="" @click.prevent="comName='register'">注册</a>
<component *:is*="comName"></component>

9.组件传值

1.父组件向子组件传值(传递数据)

默认情况下子组件无法直接访问父组件中的 data 数据和 methods 方法

通过属性绑定的形式 v-bind 自定义属性可以传递给子属性

props 属性是唯一一个数组类型的,专门用来存储父组件传递来的数据,且该数据是只读的,无法重新赋值,

实际可修改,但是会报错

1
2
3
4
5
6
<com1 v-bindparentmsg="“msg”"></com1>
<!--子组件定义 -->
com1: { template: '
<h1>子组件</h1>
' , props:
['parentmsg'],//将父组件传递过来的属性在props数组中定义一下才能使用该数据 }
2.传递方法可以让子组件向父组件传值(使用 v-on)

实质:$emit()子组件向父组件传参

使用自定义事件的系统来解决这个问题。父级组件可以像处理 native DOM 事件一样通过 v-on 监听子组件实例的任意事件:

1
<blog-post ... v-on:enlarge-text="postFontSize += 0.1"></blog-post>

同时子组件可以通过调用内建的 $emit 方法 并传入事件名称来触发一个事件:

1
<button v-on:click="$emit('enlarge-text')">Enlarge text</button>

有了这个 v-on:enlarge-text="postFontSize += 0.1" 监听器,父级组件就会接收该事件并更新 postFontSize 的值。

<blog-post> 组件决定它的文本要放大多少。这时可以使用 $emit 的第二个参数来提供这个值:

1
//子组件 <button v-on:click="$emit('enlarge-text', 0.1)">Enlarge text</button>

然后当在父级组件监听这个事件的时候,我们可以通过 $event 访问到被抛出的这个值:

1
2
3
4
5
//父组件
<blog-post
...
v-on:enlarge-text="postFontSize += $event"
></blog-post>

如果这个事件处理函数是一个方法:

1
<blog-post ... v-on:enlarge-text="onEnlargeText"></blog-post>

那么这个值将会作为第一个参数传入这个方法:

1
2
3
4
5
methods: {
onEnlargeText: function (enlargeAmount) {
this.postFontSize += enlargeAmount
}
}

VUE 计算属性

计算属性与绑定方法的区别:计算属性具有缓存,只有相关依赖发生变化,才会重新进行求值。绑定方法则是在每次渲染都重新进行计算,当计算量大时,可以避免重新运算

计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。

tip:侦听属性:watch

1
2
3
4
5
6
7
watch: {
// 如果 `question` 发生改变,这个函数就会运行
question: function (newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
this.debouncedGetAnswer()
}
}

vue 的类切换

对象语法

我们可以传给 v-bind:class 一个对象,以动态地切换 class

绑定的数据对象不必内联定义在模板里:

1
2
<div v-bind:class="classObject"></div>
data: { classObject: { active: true, 'text-danger': false } }

数组语法

我们可以把一个数组传给 v-bind:class,以应用一个 class 列表:

1
2
<div v-bind:class="[activeClass, errorClass]"></div>
data: { activeClass: 'active', errorClass: 'text-danger' }

渲染为:

1
<div class="active text-danger"></div>

根据条件切换列表中的 class,可以用三元表达式

在数组语法中也可以使用对象语法:

1
<div v-bind:class="[{ active: isActive }, errorClass]"></div>

VUE 的$set 解决检测数组变动的问题

由于 JavaScript 的限制,Vue 不能检测以下变动的数组:

  1. 当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue
  2. 当你修改数组的长度时,例如:vm.items.length = newLength

举个例子:

1
2
3
4
5
6
7
var vm = new Vue({
data: {
items: ['a', 'b', 'c'],
},
})
vm.items[1] = 'x' // 不是响应性的
vm.items.length = 2 // 不是响应性的

为了解决第一类问题,以下两种方式都可以实现和 vm.items[indexOfItem] = newValue 相同的效果,同时也将触发状态更新:

1
2
3
4
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

你也可以使用 vm.$set 实例方法,该方法是全局方法 Vue.set 的一个别名:

1
vm.$set(vm.items, indexOfItem, newValue)

为了解决第二类问题,你可以使用 splice

1
vm.items.splice(newLength)

对象更改检测注意事项

还是由于 JavaScript 的限制,Vue 不能检测对象属性的添加或删除

1
2
3
4
5
6
7
8
9
var vm = new Vue({
data: {
a: 1
}
})
// `vm.a` 现在是响应式的

vm.b = 2
// `vm.b` 不是响应式的

对于已经创建的实例,Vue 不能动态添加根级别的响应式属性。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式属性。例如,对于:

1
2
3
4
5
6
7
var vm = new Vue({
data: {
userProfile: {
name: 'Anika'
}
}
})

你可以添加一个新的 age 属性到嵌套的 userProfile 对象:

1
Vue.set(vm.userProfile, 'age', 27)

你还可以使用 vm.$set 实例方法,它只是全局 Vue.set 的别名:

1
vm.$set(vm.userProfile, 'age', 27)

有时你可能需要为已有对象赋予多个新属性,比如使用 Object.assign()_.extend()。在这种情况下,你应该用两个对象的属性创建一个新的对象。所以,如果你想添加新的响应式属性,不要像这样:

1
2
3
4
Object.assign(vm.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})

你应该这样做:

1
2
3
4
vm.userProfile = Object.assign({}, vm.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})

显示数组过滤/排序 副本

我们想要显示一个数组的过滤或排序副本,而不实际改变或重置原始数据。在这种情况下,可以创建返回过滤或排序数组的计算属性。

在计算属性不适用的情况下 (例如,在嵌套 v-for 循环中) 你可以使用一个 method 方法:

1
2
3
4
5
6
7
8
9
10
11
12
<li v-for="n in even(numbers)">{{ n }}</li>

data: {
numbers: [ 1, 2, 3, 4, 5 ]
},
methods: {
even: function (numbers) {
return numbers.filter(function (number) {
return number % 2 === 0
})
}
}

VUE 事件

事件修饰符

在事件处理程序中调用 event.preventDefault()event.stopPropagation() 是非常常见的需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。

为了解决这个问题,Vue.js 为 v-on 提供了事件修饰符。之前提过,修饰符是由点开头的指令后缀来表示的。

  • .stop

  • .prevent

  • .capture

  • .self

  • .once 只触发一次

  • .passive Vue 还对应 addEventListener 中的 passive 选项提供了 .passive 修饰符。

    1
    2
    3
    4
    <!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
    <!-- 而不会等待 `onScroll` 完成 -->
    <!-- 这其中包含 `event.preventDefault()` 的情况 -->
    <div v-on:scroll.passive="onScroll">...</div>

    这个 .passive 修饰符尤其能够提升移动端的性能。

  • ```HTML

    ...
    ...
    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

    **TIP:**使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 `v-on:click.prevent.self` 会阻止**所有的点击**,而 `v-on:click.self.prevent` 只会阻止对元素自身的点击。

    #### VUE 的按键修饰符:监听固定按键

    **监听固定按键**

    - #### `.enter`

    - `.tab`

    - `.delete` (捕获“删除”和“退格”键)

    - `.esc`

    - `.space`

    - `.up`

    - `.down`

    - `.left`

    - `.right`

    也可使用键盘码

    **自定义全局按键修饰符的方法**

    Vue.config.keyCodes.f2 = 113;

    //@keyup.enter="add()"
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    # VUE 动画和过渡

    Vue 提供了 `transition` 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡

    - 条件渲染 (使用 `v-if`)
    - 条件展示 (使用 `v-show`)
    - 动态组件
    - 组件根节点

    这里是一个典型的例子:

    ```html
    <div id="demo">
    <button v-on:click="show = !show">Toggle</button>
    <transition name="fade">
    <p v-if="show">hello</p>
    </transition>
    </div>
1
2
3
4
5
6
new Vue({
el: '#demo',
data: {
show: true,
},
})
1
2
3
4
5
6
7
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}

VUE 混入

VUE 渲染函数

  1. render 方法的实质就是生成 template 模板;
  2. 通过调用一个方法来生成,而这个方法是通过 render 方法的参数传递给它的;
  3. 这个方法有三个参数,分别提供标签名,标签相关属性,标签内部的 html 内容
  4. 通过这三个参数,可以生成一个完整的木模板

虚拟 dom

虚拟 dom 不是真正意义上的 DOM,而是一个轻量级的 JavaScript 对象,在状态发生变化时,虚拟 dom 会进行 diff 运算,来更新只需要被替换的 DOM,而不是全部重绘

VNode 主要分为

TextVNode 文本节点

ELementVNode 普通元素节点

ComponentVNode 组件节点

EmptyVNode 没有内容的注释节点

CloneVNode 克隆节点,可以是以上任意类型的节点,唯一的区别在于 isCloned 属性为 true

Vue 通过建立一个虚拟 DOM 来追踪自己要如何改变真实 DOM。请仔细看这行代码:

1
return createElement('h1', this.blogTitle)

createElement 到底会返回什么呢?其实不是一个实际的 DOM 元素。它更准确的名字可能是 createNodeDescription,因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点,包括及其子节点的描述信息。我们把这样的节点描述为“虚拟节点 (virtual node)”,也常简写它为“VNode”。“虚拟 DOM”是我们对由 Vue 组件树建立起来的整个 VNode 树的称呼。

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
// @returns {VNode}
createElement(
// {String | Object | Function}
// 一个 HTML 标签名、组件选项对象,或者
// resolve 了上述任何一种的一个 async 函数。必填项。
'div',

// {Object}
// 一个与模板中属性对应的数据对象。可选。
{
// (详情见下一节)
},

// {String | Array}
// 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
// 也可以使用字符串来生成“文本虚拟节点”。可选。
[
'先写一些文字',
createElement('h1', '一则头条'),
createElement(MyComponent, {
props: {
someProp: 'foobar',
},
}),
]
)

渲染函数里的 v-model

jsx

虚拟 dom 详解(转)

一、真实 DOM 和其解析流程?

​ 浏览器渲染引擎工作流程都差不多,大致分为 5 步,创建 DOM 树——创建 StyleRules——创建 Render 树——布局 Layout——绘制 Painting

​ 第一步,用 HTML 分析器,分析 HTML 元素,构建一颗 DOM 树(标记化和树构建)。

​ 第二步,用 CSS 分析器,分析 CSS 文件和元素上的 inline 样式,生成页面的样式表。

​ 第三步,将 DOM 树和样式表,关联起来,构建一颗 Render 树(这一过程又称为 Attachment)。每个 DOM 节点都有attach 方法,接受样式信息,返回一个 render 对象(又名 renderer)。这些 render 对象最终会被构建成一颗 Render 树。

​ 第四步,有了 Render 树,浏览器开始布局,为每个 Render 树上的节点确定一个在显示屏上出现的精确坐标。

​ 第五步,Render 树和节点显示坐标都有了,就调用每个节点paint 方法,把它们绘制出来。

DOM 树的构建是文档加载完成开始的?构建 DOM 数是一个渐进过程,为达到更好用户体验,渲染引擎会尽快将内容显示在屏幕上。它不必等到整个 HTML 文档解析完毕之后才开始构建 render 数和布局。

Render 树是 DOM 树和 CSSOM 树构建完毕才开始构建的吗?这三个过程在实际进行的时候又不是完全独立,而是会有交叉。会造成一边加载,一遍解析,一遍渲染的工作现象。

CSS 的解析是从右往左逆向解析的(从 DOM 树的下-上解析比上-下解析效率高),嵌套标签越多,解析越慢。

img

webkit 渲染引擎工作流程

二、JS 操作真实 DOM 的代价!

​ 用我们传统的开发模式,原生 JS 或 JQ 操作 DOM 时,浏览器会从构建 DOM 树开始从头到尾执行一遍流程。在一次操作中,我需要更新 10 个 DOM 节点,浏览器收到第一个 DOM 请求后并不知道还有 9 次更新操作,因此会马上执行流程,最终执行 10 次。例如,第一次计算完,紧接着下一个 DOM 更新请求,这个节点的坐标值就变了,前一次计算为无用功。计算 DOM 节点坐标值等都是白白浪费的性能。即使计算机硬件一直在迭代更新,操作 DOM 的代价仍旧是昂贵的,频繁操作还是会出现页面卡顿,影响用户体验。

三、为什么需要虚拟 DOM,它有什么好处?

​ Web 界面由 DOM 树(树的意思是数据结构)来构建,当其中一部分发生变化时,其实就是对应某个 DOM 节点发生了变化,

​ 虚拟 DOM 就是为了解决浏览器性能问题而被设计出来的。如前,若一次操作中有 10 次更新 DOM 的动作,虚拟 DOM 不会立即操作 DOM,而是将这 10 次更新的 diff 内容保存到本地一个 JS 对象中,最终将这个 JS 对象一次性 attch 到 DOM 树上,再进行后续操作,避免大量无谓的计算量。所以,用 JS 对象模拟 DOM 节点的好处是,页面的更新可以先全部反映在 JS 对象(虚拟 DOM)上,操作内存中的 JS 对象的速度显然要更快,等更新完成后,再将最终的 JS 对象映射成真实的 DOM,交由浏览器去绘制。

四、实现虚拟 DOM

​ 例如一个真实的 DOM 节点。

img

真实 DOM

​ 我们用 JS 来模拟 DOM 节点实现虚拟 DOM。

img

虚拟 DOM

​ 其中的 Element 方法具体怎么实现的呢?

img

Element 方法实现

​ 第一个参数是节点名(如 div),第二个参数是节点的属性(如 class),第三个参数是子节点(如 ul 的 li)。除了这三个参数会被保存在对象上外,还保存了key 和 count。其相当于形成了虚拟 DOM 树。

img

虚拟 DOM 树

​ 有了 JS 对象后,最终还需要将其映射成真实 DOM

img

虚拟 DOM 对象映射成真实 DOM

​ 我们已经完成了创建虚拟 DOM 并将其映射成真实 DOM,这样所有的更新都可以先反应到虚拟 DOM 上,如何反应?需要用到Diff 算法

​ 两棵树如果完全比较时间复杂度是 O(n^3),但参照《深入浅出 React 和 Redux》一书中的介绍,React 的 Diff 算法的时间复杂度是 O(n)。要实现这么低的时间复杂度,意味着只能平层的比较两棵树的节点,放弃了深度遍历。这样做,似乎牺牲掉了一定的精确性来换取速度,但考虑到现实中前端页面通常也不会跨层移动 DOM 元素,这样做是最优的。

深度优先遍历,记录差异

​ 。。。。

Diff 操作

​ 在实际代码中,会对新旧两棵树进行一个深度的遍历,每个节点都会有一个标记。每遍历到一个节点就把该节点和新的树进行对比,如果有差异就记录到一个对象中。

​ 下面我们创建一棵新树,用于和之前的树进行比较,来看看 Diff 算法是怎么操作的。

img

old Tree

img

new Tree

​ 平层 Diff,只有以下 4 种情况:

​ 1、节点类型变了,例如下图中的 P 变成了 H3。我们将这个过程称之为REPLACE。直接将旧节点卸载并装载新节点。旧节点包括下面的子节点都将被卸载,如果新节点和旧节点仅仅是类型不同,但下面的所有子节点都一样时,这样做效率不高。但为了避免 O(n^3)的时间复杂度,这样是值得的。这也提醒了开发者,应该避免无谓的节点类型的变化,例如运行时将 div 变成 p 没有意义。

​ 2、节点类型一样,仅仅属性或属性值变了。我们将这个过程称之为PROPS。此时不会触发节点卸载和装载,而是节点更新。

img

查找不同属性方法

​ 3、文本变了,文本对也是一个 Text Node,也比较简单,直接修改文字内容就行了,我们将这个过程称之为TEXT

​ 4、移动/增加/删除 子节点,我们将这个过程称之为REORDER。看一个例子,在 A、B、C、D、E 五个节点的 B 和 C 中的 BC 两个节点中间加入一个 F 节点。

img

例子

​ 我们简单粗暴的做法是遍历每一个新虚拟 DOM 的节点,与旧虚拟 DOM 对比相应节点对比,在旧 DOM 中是否存在,不同就卸载原来的按上新的。这样会对 F 后边每一个节点进行操作。卸载 C,装载 F,卸载 D,装载 C,卸载 E,装载 D,装载 E。效率太低。

img

粗暴做法

​ 如果我们在 JSX 里为数组或枚举型元素增加上 key 后,它能够根据 key,直接找到具体位置进行操作,效率比较高。常见的最小编辑距离问题,可以用 Levenshtein Distance 算法来实现,时间复杂度是 O(M*N),但通常我们只要一些简单的移动就能满足需要,降低精确性,将时间复杂度降低到 O(max(M,N))即可。

img

最终 Diff 出来的结果

映射成真实 DOM

​ 虚拟 DOM 有了,Diff 也有了,现在就可以将 Diff 应用到真实 DOM 上了。深度遍历 DOM 将 Diff 的内容更新进去。

img

根据 Diff 更新 DOM

img

根据 Diff 更新 DOM

我们会有两个虚拟 DOM(js 对象,new/old 进行比较 diff),用户交互我们操作数据变化 new 虚拟 DOM,old 虚拟 DOM 会映射成实际 DOM(js 对象生成的 DOM 文档)通过DOM fragment操作给浏览器渲染。当修改 new 虚拟 DOM,会把 newDOM 和 oldDOM 通过 diff 算法比较,得出 diff 结果数据表(用 4 种变换情况表示)。再把 diff 结果表通过DOM fragment更新到浏览器 DOM中。

虚拟 DOM 的存在的意义?vdom 的真正意义是为了实现跨平台,服务端渲染,以及提供一个性能还算不错 Dom 更新策略。vdom 让整个 mvvm 框架灵活了起来

Diff 算法只是为了虚拟 DOM 比较替换效率更高,通过 Diff 算法得到 diff 算法结果数据表(需要进行哪些操作记录表)。原本要操作的 DOM 在 vue 这边还是要操作的,只不过用到了 js 的DOM fragment来操作 dom(统一计算出所有变化后统一更新一次 DOM)进行浏览器 DOM 一次性更新。其实DOM fragment我们不用平时发开也能用,但是这样程序员写业务代码就用把 DOM 操作放到 fragment 里,这就是框架的价值,程序员才能专注于写业务代码

VUE 过滤器

Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持)。过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符号指示:

1
2
3
4
5
<!-- 在双花括号中 -->
{{ message | capitalize }}

<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>

webpack 学习

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
var path = require('path')
//导入插件
var ExtractTextPlugin = require('extract-text-webpack-plugin')

var config = {
// 入口
entry: {
main: './main.js',
},
// 出口
output: {
path: path.join(__dirname, './dist'), //输出的路径
publicPath: '/dist/', //资源文件引用的目录
filename: 'main.js', //输出文件的名称
},
//模块配置
module: {
rules: [
//指定loader,每一个lodaer必须包含test和use两个选项
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
use: 'css-loader',
fallback: 'style-loader',
}),
},
],
},
//插件配置
plugins: [
//重命名提取后的css文件
new ExtractTextPlugin('main.css'),
],
}

module.exports = config

入口

出口

加载器

vue-loader

vue-style-loader

babel-loader

插件

箭头函数体内的 this 对象就是定义时所在的对象,而不是使用时所在的对象

对象字面量可以缩写,当对象的 key 和 value 名称一致时,可以缩写成一个

SPA 单页面应用部署过程:

SPA 只有一个 html 文件,一般将该 html’挂在后端程序下,由后端路由渲染这个页面,将所有的静态资源(css,js,image,iconfont 等)单独部署到 CDN,当然也可以和后端程序部署在一起,实现前后端完全分离

VUE 插件

通过install注册插件,通过Vue.use()使用插件

vue 的核心插件 vue-router、vuex

vue-router 路由

什么是路由?通俗的讲就是网址,专业的讲:每次 GET 或 POST 等请求在服务端会有一个专门的正则配置列表,然后匹配到具体的一条路径后,分发到不同的 Controller,进行操作,最终将 html 或数据返回给前端,完成了一次 IO

前端路由

优点:页面持久性,如音乐网站切换音乐不会中断;前后端完全分离

缺点:需要加载 css 和 js

后端路由

优点:seo 优化好,不需要等待前端加载

缺点:单独由后端维护,前端要修改模板需安装整套后端服务,前后端混杂,不易于分离


2019.8.7sui

今天想好好的认真开始维护自己的博客

记于 2019.8.7 sui

昨天晚上是我第一次面腾讯,其实当时投腾讯的时候就想着罢了罢了,反正啥也不会投着试试呗的心态,没想过腾讯居然会给我打面试电话。

可能真的神仙太强了招不到把,虽然腾讯给我打了电话,但我深知我现在的这种知识储备是绝对进不了腾讯的,所以就直接同面试官聊了起来,但是这个面试官似乎好像要加班赶业务。而且我投的是前端岗,面我的大哥居然是客户端开发的,头疼。

然后呢,问问我操作系统,我说我没学过不会,问我网络吧,知识太繁杂,看过但是忘了,唯一一个问我前端的问题是,JavaScript 的语言运行机制,当时答的还不错,那个面试官甚至发出了夸赞的声音,蛮自豪的,感觉自己对 JavaScript 的理解还是有点东西的。

最后呢,我觉得反正肯定过不了,就将就的答呗。

通过昨天的电面,我今天深刻的思考了一下,我目前仍存在的缺陷

对计算机基础还是非常薄弱的,操作系统,编译原理 ,计网,算法与数据结构全都是我的弱项

对原生 JavaScript 的一些字符串,数组,的 API 还不是特别的熟悉,目前只做到知道什么时候该使用这个 API,实际使用还是需要搜索一下,看一眼文档才能进行操作

对 JS 的设计模式知识空白,今天看了掘金的文档才对其有一个认知,设计模式在平常开发中是帮助很大的,很多时候我们开发已经在不知不觉中使用了设计模式,比如一些组件开发,涉及到一些逻辑的时候,如果有设计模式思想的指导,那么这些逻辑将很容易就实现,这个同数据结构是一样的,目前我就是做到,知道有这个东西,知道这个玩意在开发中可以用来干嘛,但是实际使用起来还是离不开文档,这也应该和我学习前端的时间长度有关,我还是需要一些时间去进行沉淀一下自己所学的东西

对 JS 的对象编程还不是特别熟悉,之前也是看过很多弄懂原型链啊、继承啊之类的文章,当时看的时候也写了一些小心得,但是这段时间在公司实习,过于专注于业务和工作流程的学习,发现自己对这些知识又有些疏生了

今日随笔突然被突如其来的业务需求打断,思路全没了,就到这里吧

T.T


DOM操作

节点查找 API

1
2
3
4
5
6
7
8
9
10
11
12
13
document.getElementById :根据ID查找元素,大小写敏感,如果有多个结果,只返回第一个;

document.getElementsByClassName :根据类名查找元素,多个类名用空格分隔,返回一个 HTMLCollection 。注意兼容性为IE9+(含)。另外,不仅仅是document,其它元素也支持 getElementsByClassName 方法;

document.getElementsByTagName :根据标签查找元素, * 表示查询所有标签,返回一个 HTMLCollection 。

document.getElementsByName :根据元素的name属性查找,返回一个 NodeList 。

document.querySelector :返回单个Node,IE8+(含),如果匹配到多个结果,只返回第一个。

document.querySelectorAll :返回一个 NodeList ,IE8+(含)。

document.forms :获取当前页面所有form,返回一个 HTMLCollection ;

节点创建 API

createElement 创建元素:

1
2
3
4
5
var elem = document.createElement("div");
elem.id = 'haorooms';
elem.style = 'color: red';
elem.innerHTML = '我是新创建的haorooms测试节点';
document.body.appendChild(elem);

通过 createElement 创建的元素并不属于 document 对象,它只是创建出来,并未添加到 html 文档中,要调用 appendChild 或 insertBefore 等方法将其添加到 HTML 文档中。

createTextNode 创建文本节点:

1
2
var node = document.createTextNode("我是文本节点");
document.body.appendChild(node);

cloneNode 克隆一个节点:

node.cloneNode(true/false) ,它接收一个 bool 参数,用来表示是否复制子元素。

1
2
3
4
var from = document.getElementById("test");
var clone = from.cloneNode(true);
clone.id = "test2";
document.body.appendChild(clone);

克隆节点并不会克隆事件,除非事件是用

这种方式绑定的,用 addEventListener 和 node.onclick=xxx; 方式绑定的都不会复制。

createDocumentFragment

本方法用来创建一个 DocumentFragment ,也就是文档碎片,它表示一种轻量级的文档,主要是用来存储临时节点,大量操作 DOM 时用它可以大大提升性能。

节点修改 API

1、appendChild

语法:

1
parent.appendChild(child);

2、insertBefore

1
parentNode.insertBefore(newNode, refNode);

3、insertAdjacentHTML

1
2
//js谷歌浏览器,火狐浏览器,IE8+
el.insertAdjacentHTML('beforebegin', htmlString);

关于 insertAdjacentHTML,这个 API 比较好用,具体可以看:https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML

1
2
3
4
5
6
7
<!-- beforebegin -->
<p>
<!-- afterbegin -->
foo
<!-- beforeend -->
</p>
<!-- afterend -->

4、Element.insertAdjacentElement()

用法和上面类似,

1
targetElement.insertAdjacentElement(position, element);

5、removeChild

removeChild 用于删除指定的子节点并返回子节点,语法:

1
var deletedChild = parent.removeChild(node);

deletedChild 指向被删除节点的引用,它仍然存在于内存中,可以对其进行下一步操作。另外,如果被删除的节点不是其子节点,则将会报错。一般删除节点都是这么删的:

1
2
3
4
5
function removeNode(node)
{
if(!node) return;
if(node.parentNode) node.parentNode.removeChild(node);
}

6、replaceChild

replaceChild 用于将一个节点替换另一个节点,语法:

1
parent.replaceChild(newChild, oldChild);

节点关系 API

1、父关系 API

parentNode :每个节点都有一个 parentNode 属性,它表示元素的父节点。Element 的父节点可能是 Element,Document 或 DocumentFragment;

parentElement :返回元素的父元素节点,与 parentNode 的区别在于,其父节点必须是一个 Element 元素,如果不是,则返回 null;

2、子关系 API

children :返回一个实时的 HTMLCollection ,子节点都是 Element,IE9 以下浏览器不支持;

childNodes :返回一个实时的 NodeList ,表示元素的子节点列表,注意子节点可能包含文本节点、注释节点等;

firstChild :返回第一个子节点,不存在返回 null,与之相对应的还有一个 firstElementChild ;

lastChild :返回最后一个子节点,不存在返回 null,与之相对应的还有一个 lastElementChild ;

3、兄弟关系型 API

previousSibling :节点的前一个节点,如果不存在则返回 null。注意有可能拿到的节点是文本节点或注释节点,与预期的不符,要进行处理一下。

nextSibling :节点的后一个节点,如果不存在则返回 null。注意有可能拿到的节点是文本节点,与预期的不符,要进行处理一下。

previousElementSibling :返回前一个元素节点,前一个节点必须是 Element,注意 IE9 以下浏览器不支持。

nextElementSibling :返回后一个元素节点,后一个节点必须是 Element,注意 IE9 以下浏览器不支持。

元素属性型 API

1、setAttribute 给元素设置属性:

1
element.setAttribute(name, value);

其中 name 是特性名,value 是特性值。如果元素不包含该特性,则会创建该特性并赋值。

2、getAttribute

getAttribute 返回指定的特性名相应的特性值,如果不存在,则返回 null:

1
var value = element.getAttribute("id");

3、hasAttribute

1
2
3
4
5
6
var result = element.hasAttribute(name);

var foo = document.getElementById("foo");
if (foo.hasAttribute("bar")) {
// do something
}

4、dataset

获取 html data-开头的属性,用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div id="user" data-id="1234567890" data-user="johndoe" data-date-of-birth>John Doe</div>

let el = document.querySelector('#user');

// el.id == 'user'
// el.dataset.id === '1234567890'
// el.dataset.user === 'johndoe'
// el.dataset.dateOfBirth === ''

el.dataset.dateOfBirth = '1960-10-03'; // set the DOB.

// 'someDataAttr' in el.dataset === false
el.dataset.someDataAttr = 'mydata';
// 'someDataAttr' in el.dataset === true

样式相关 API

1、直接修改元素的样式

1
2
3
elem.style.color = 'red';
elem.style.setProperty('font-size', '16px');
elem.style.removeProperty('color');

2、动态添加样式规则

1
2
3
var style = document.createElement('style');
style.innerHTML = 'body{color:red} #top:hover{background-color: red;color: white;}';
document.head.appendChild(style);

3、classList 获取样式 class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// div is an object reference to a <div> element with class="foo bar"
div.classList.remove("foo");
div.classList.add("anotherclass");

// if visible is set remove it, otherwise add it
div.classList.toggle("visible");

// add/remove visible, depending on test conditional, i less than 10
div.classList.toggle("visible", i < 10 );

alert(div.classList.contains("foo"));

// add or remove multiple classes
div.classList.add("foo", "bar", "baz");
div.classList.remove("foo", "bar", "baz");

// add or remove multiple classes using spread syntax
let cls = ["foo", "bar"];
div.classList.add(...cls);
div.classList.remove(...cls);

// replace class "foo" with class "bar"
div.classList.replace("foo", "bar");

4、window.getComputedStyle

通过 element.sytle.xxx 只能获取到内联样式,借助 window.getComputedStyle 可以获取应用到元素上的所有样式,IE8 或更低版本不支持此方法。

1
var style = window.getComputedStyle(element[, pseudoElt]);

获取相关高度 API

这里主要讲一下:

getBoundingClientRect

getBoundingClientRect 用来返回元素的大小以及相对于浏览器可视窗口的位置,用法如下:

1
var clientRect = element.getBoundingClientRect();

clientRect 是一个 DOMRect 对象,包含 width、height、left、top、right、bottom,它是相对于窗口顶部而不是文档顶部,滚动页面时它们的值是会发生变化的。


canvasAPI笔记

基本

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
window.onload = function () {
var dw = document.getElementById("abc").getContext("2d");;
//画直线
// dw.moveTo(0, 50);
// dw.lineTo(400, 50);
// dw.stroke();
// dw.beginPath();
// dw.lineWidth = 10;
// dw.strokeStyle = "#ccc";
// dw.moveTo(450, 50);
// dw.lineTo(100, 250);
// dw.stroke();

//画圆
//arc(x,y,r, startAngle ,endAngle ,counterclockwise)
// dw.arc(200,200,50,0,2*Math.PI,false);
// dw.stroke();
//画圆弧
// a点坐标取上次绘制终点
//arcTo(bx,by,cx,cy,圆弧半径)

//贝塞尔曲线
//起点坐标都取上次绘制最后的点,如需更换可以使用beginPath(),或moveTo()
// 二次方 quadraticCurveTo(控制点x,控制点y,终点x,终点y)
//三次方 bezierCurveTo(控制点1x,控制点1y,控制点2x,控制点2y,终点x,终点y)

// 绘制文本
// 直接往括号里加要绘制的文字
//相关属性,font textAlign textBaseline
//有填充fillText()
//无填充strokeText()

//线性渐变
// 创建线性的渐变对象createLinearGradient(x1,y1,x2,y2)
// addColorStop(stop,color) stop为0.0-1.0之间 color为结束位置显示的css颜色值
// var grd = dw.createLinearGradient(0,0,160,0);
// grd.addColorStop("0.1","red");
// grd.addColorStop("0.2","orange");
// grd.addColorStop("0.3","yellow");
// grd.addColorStop("0.4","green");
// grd.addColorStop("0.5","blue");
// grd.addColorStop("0.6","blue");
// grd.addColorStop("0.7","purple");
// dw.fillStyle = grd;
// dw.fillRect(0,0,150,150);

// 放射渐变
// var grd = dw.createRadialGradient(x1,y1,r1,x2,y2,r2结束圆半径)
// var grd = dw.createRadialGradient(100, 100, 5, 100, 100, 30);
// grd.addColorStop("0.1","red");
// grd.addColorStop("0.2","orange");
// dw.fillStyle = grd;
// dw.fillRect(0, 0, 200, 200);

//阴影
// dw.shadowColor = "black";
// dw.shadowBlur = 10;
// dw.shadowOffsetX = 0;
// dw.shadowOffsetY = 0;
// dw.fillStyle = "blue";
// dw.fillRect(20,20,100,100);


}

高级

放大

1
2
3
4
5
6
//原大小
dw.strokeRect(10, 10, 40, 20)
//放大,将坐标放大,如果使用负数则为翻转
dw.scale(2, 2)
//放大之后再画
dw.strokeRect(10, 10, 40, 20)

平移旋转

1
2
3
dw.translate(70,70);  //将绘制起点坐标平移到70,70

dw.rotate(30*Math.PI/180); //接收弧度单位

矩阵变换

1
dw.transform(m11, m12, m21, m22, dx, dy)

矩阵变化可以实现平移,旋转,缩放

合成

globalCompositeOperation

属性值

描述
source-over 默认。在目标图像上显示源图像。
source-atop 在目标图像顶部显示源图像。源图像位于目标图像之外的部分是不可见的。
source-in 在目标图像中显示源图像。只有目标图像内的源图像部分会显示,目标图像是透明的。
source-out 在目标图像之外显示源图像。只会显示目标图像之外源图像部分,目标图像是透明的。
destination-over 在源图像上方显示目标图像。
destination-atop 在源图像顶部显示目标图像。源图像之外的目标图像部分不会被显示。
destination-in 在源图像中显示目标图像。只有源图像内的目标图像部分会被显示,源图像是透明的。
destination-out 在源图像外显示目标图像。只有源图像外的目标图像部分会被显示,源图像是透明的。
lighter 显示源图像 + 目标图像。
copy 显示源图像。忽略目标图像。
xor 使用异或操作对源图像与目标图像进行组合

碰撞检测

实现一般通过封装好的轮子去实现即可

圆形

判断圆心距离是否小于两圆的半径和

矩形

其他应用

压缩和解压

图片处理


es6记忆手册

ES6 新特性一览

  • letconst
  • 暂时性死区
  • 解构赋值
  • 字符串的unicode表示
  • 模板字符串
  • 对象简写属性
  • 函数的默认参数
  • 函数的剩余参数(rest 参数)
  • spread 操作符
  • 箭头函数
  • 尾调用优化
  • 遍历器
  • for…of…
  • Symbol
  • Map 和 Set
  • class
  • Promise
  • Generator
  • async await
  • Proxy 和 Reflect
  • 二进制数组
  • 模块
  • Math、Number、String、Object、RegExp 扩展

let 和 const 的异同

相同点

  • letconst声明的变量,都是块级作用域,都只在其所在的代码块内有效
  • letconst声明的变量,都不存在变量提升
  • letconst声明的变量,都存在临时性死区
  • letconst都不允许在通过一个作用于内重复声明同一个变量
  • letconst都不允许在函数内对参数重新声明
  • letconst声明的变量,都不是顶层对象的属性

不同点

  • let声明的是变量,声明后,可以在任意时刻赋值,修改
  • const声明的是常量,声明后必须立刻赋值,且以后不允许修改

let 和 var 的异同

相同点

  • letvar都是声明一个变量
  • letvar声明的变量,都是可以在声明后,任意时刻赋值,修改

不同点

  • let无变量提升,var有变量提升
  • let是块级作用域,var是函数级作用域
  • let不可在作用域内重复声明同一个变量,var可以在同一个作用域内声明同一个变量
  • let声明的变量不属于顶层对象的属性,var声明的变量属于顶层对象的属性
  • let存在临时性死区,var不存在临时性死区

其他

  1. 在同作用域内,不能同时使用letvar声明同名变量,不管谁先谁后都不行

const 的实质

const实际保证的,并不是变量的值不能改动,而是变量指向的那个内存地址所所保存的数据不能改变。

JavaScript 中的简单类型数据,比如string, number, boolean, null, undefined,值就保存在变量指向的那个内存地址,而复合类型的数据,变量指向的那个内存地址,保存的是指向实际数据的一个指针。

什么是临时性死区

ES6 新概念:临时性死区——TDZ——Temporal Dead Zone

由于代码(代码块,函数,模块……)中的变量还没有被初始化而不能使用的情况,具体表现为——报错:Uncaught ReferenceError: xxx is not definedlet,const,class都有临时性死区的表现。在 ES6 之前,如果在变量初始化之前使用变量,并不会报错,只是其值为undefined而已。

模板字符串

  1. 模板字符串简化了多行字符串的写法
  2. 模板字符串简化了在字符串中签入变量的写法
  3. 模板字符串中的变量如果没有声明的话,会报错
  4. 模板字符串默认会将字符串转义

ES6 中的函数

  1. 函数参数可以设置默认值
  2. 函数参数的默认值是惰性求值
  3. 函数参数设置默认值后 build 影响函数的length属性:function add(a, b, c=3){} add.length === 2;// true
  4. 在 ES6 中,如果函数参数使用了默认值、解构赋值、扩展运算符,就不能在函数内部显式指定为严格模式。函数指定默认值后,显式添加use strict,报错:Uncaught SyntaxError: Illegal 'use strict' directive in function with non-simple parameter list
  5. rest参数只能是尾参数
  6. rest参数不计入函数的length属性
  7. rest参数是一个真正的数组,arguments是类数组
  8. 在 ES6 中,name属性会返回实际的函数名

箭头函数

  1. 函数体内的this就是定义时的对象,而不是使用时的对象
  2. 箭头函数不可以当做构造函数
  3. 箭头函数内部不存在arguments对象,使用...rest参数代替
  4. 箭头函数不可以做Generator函数
  5. 箭头函数无法使用apply,bind,call改变this指向

解构赋值

可解构赋值的:

  • 数组
  • Set
  • 字符串
  • 对象
  • 函数参数
  1. 数组解构赋值:
1
2
3
// a === 12
// b === 33
const [a, b] = [12, 33];
  1. 数组解构默认值:
1
2
3
// f === 120
// h === 56
const [f, h = 100] = [120, 56];
  1. 数组解构默认值:
1
2
3
// f === 120
// h === 100
const [f, h = 100] = [120, undefined];
  1. 对象解构赋值:
1
2
3
// a === 'Pelli'
// b === 89
const {a, b} = {a: 'Pelli', b: 89};
  1. 对象解构赋值:
1
2
3
4
5
6
7
8
9
10
11
12
// a === 'pelli
// b === 18
// c === 'worker'
const {
myname: a,
age: b,
job: c
} = {
myname: 'pelli',
age: 18,
job: 'worker'
};
  1. 对象解构默认值:
1
2
3
// p === 'ppp'
// q === 'hello world'
const {p, q = 'hello world'} = {p: 'ppp', q: 'qqqq'};
  1. 解构赋值的默认值需 undefined 触发,
    • 对于数组来说,对应位置没有元素
    • 对于对象来说,没有同名属性
    • 或者将同名属性或元素显式赋值为 undefined
  2. 字符串解构赋值:
1
2
3
4
// a === 'h'
// b === 'e'
// c === 'l'
const [a, b, c] = 'hello world';
  1. 函数参数的解构赋值
1
2
3
4
5
6
7
8
9
const args = function(){return arguments;}
const ags = args(3, 4, 5, 6, 12, 2, 3);
// a === 3
// b === 4
// c === 5
// d === 6
// e === 12
// f = 2
const [a, b, c, d, e, f] = ags;
  1. 解构赋值时,如果等号右边是数值和布尔值,则会先转为对象

解构赋值圆括号

最佳实践:任何时候都不要在解构赋值中放置圆括号

以下情况不能使用

  1. 变量声明语句
  2. 函数参数
  3. 赋值语句模式

只有一种情况可以使用圆括号

  1. 赋值语句的非模式部分

解构赋值使用场景

  1. 变量交换
  2. 从函数返回多个值
  3. 定义函数参数
  4. 提取 json 数据
  5. 定义函数参数默认值
  6. 遍历 Map 结构
  7. 输入模块指定方法

Symbol

  1. Symbol不是构造函数,定义一个Symbol,前不用加new,正确使用方式为: const s = Symbol()
  2. Symbol值作为对象属性名时,只能使用[]方式访问,不能通过点运算符访问属性
  3. Symbol是独一无二的值

Set 和 Map

  1. Set 类似于数组,但是其中的值都是唯一的,不可重复
  2. Set 构造函数的参数必须是可遍历的:arguments,string,array,set,map
  3. Set 构造函数的参数只有第一个有效(只需要一个参数)
  4. Set 的add方法返回的是Set对象,可以链式调用
  5. Map 的set方法,返回的是Map对象,可以链式调用
  6. Set 内部只能存在一个NaN
  7. Object.is(0, -0); // false,但是,Set 内部0-0相等,只能存在一个.
  8. Set 内部:两个空对象,空数组,空函数总是不相等
  9. Map 数据结构是键值对的集合
  10. Map 数据结构的键不仅仅可以是字符串,任何数据类型都可以作为键

for…of…循环

  • arguments
  • 数组
  • 字符串
  • Set
  • Map

Promise 的特点

  1. Promise 对象的状态不受外界影响
  2. Promise 对象的状态一旦确定就不会再改变
  3. Promise 对象的状态变化只可能是两个结果中的一个:接受或拒绝

Promise 的缺点

  1. Promise 对象一旦创建,就无法中途取消
  2. 如果不设置回调函数,Promise 对象内部抛出的错误不会反映到外部
  3. 当 Promise 对象处于pending时,无法得知目前的进展在哪一个阶段,换一种说法是无法确定事件的完成度
  4. 代码冗余,相对于回调函数来说,语义弱化。不管什么操作都是一堆 then

Promise 对象和其他代码的执行顺序

  1. 立即执行的 Promise 比外层其他代码后执行
  2. 立即执行的 Promise 比setTimeout 0先执行

Promise本身并不是异步的,Promise中的“异步”特性是由resolve或者reject的执行时机带来的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 下面几行代码输出顺序为:3,2,1
setTimeout(() => {
console.log('1');
}, 0);

var p = new Promise((resolve, reject) => {
resolve('2')
});

p.then((val) => {
console.log(val, 'promise');
})

console.log('3');

另外的几行代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 下面几行代码输出顺序为:3,1,2
setTimeout(() => {
console.log('1');
}, 0);

var p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('2')
}, 0);
});

p.then((val) => {
console.log(val, 'promise');
})

console.log('3');

Promise 对象的写法最佳实践

参考来源:http://es6.ruanyifeng.com/#docs/promise

理由是下面这种写法可以捕获前面 then 方法执行中的错误(也就是说,catch 不仅可以捕获到 promise 被拒绝的状态,也可以捕获前面 then 方法中的错误),也更接近同步的写法(try/catch)。因此,建议总是使用 catch 方法,而不使用 then 方法的第二个参数。

p.then(function(val){}).catch(function(msg){})的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var p = new Promise(function(resolve, reject){
setTimeout(function(){
var status = Math.random() * 10;

if(status > 5){
resolve(status);
}else{
reject('失败');
}
}, 10000);
});

p.then(function(value){
console.log(value);
}).catch(function(msg){
console.log(msg);
});

不推荐的写法如下:

then方法传递两个参数,第二参数是当被拒绝的时候执行的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var p = new Promise(function(resolve, reject){
setTimeout(function(){
var status = Math.random() * 10;

if(status > 5){
resolve(status);
}else{
reject('失败');
}
}, 10000);
});

p.then(function(value){
console.log(value);
}, function(msg){
// 这里只有在promise被拒绝的时候才会执行
// 如果then方法报错了,这里无法获知
console.log(msg);
});

Promise 的其他一些常识

  • Promise不能直接做函数调用,即不能:Promise()

class 的一些常识

  • class可以看做是语法糖,只是让对象原型的写法更加清晰,更像面向对象的编程语言
  • 类的数据类型是函数,类本身指向构造函数
  • 类不能做函数直接调用,必须和new一起使用
  • 类没有变量提升
  • 类的所有方法都定义在prototype上面
  • 类内部定义的所有属性都是不可枚举的
  • 类的length属性值是constructor的参数个数(不包括有默认值的参数)

ES6 中的NaN

  1. Number扩展,Number.isNaN()
  2. ES6中,window的方法isNaN()是为了保证向下兼容性,在ES6中,建议使用Nunber.isNaN()
  3. Object.is()中,NaNNaN是相等的,Object.is(NaN, NaN) === true
  4. Set中,只允许存在一个NaN

ES6 中的模块

  • 模块相关名词:CommonJSAMDCMDUMD,ES6 Module
  • ES6 之前的模块规范:CommonJSAMD,CommonJS用于服务器,AMD用于浏览器
  • ES6 的模块设计思想是尽量静态化,使得在编译时就能确定模块的依赖关系以及输入输出的变量
  • CommonJSAMD模块,都只能在运行时确定这些东西,比如,CommonJS模块就是对象,输入时必须查找对象属性
  • ES6 模块是编译时加载,使得静态分析成为可能
  • ES6 模块自动采用严格模式,不管有没有显式声明"use strict;"
  • 在 ES6 模块中,顶层的this指向undefined
  • 如果 A 模块引入的模块 B 是基本类型,A 对 B 重新赋值的话会报错,如果 B 是引用类型,A 对其属性重新赋值, B 的属性值会改变,如果别的模块(比如 C 模块)也引用了 B 模块,则 A 对 B 的属性值修改也会体现在 C 模块中
  • 最佳实践:不要对引入的模块进行修改
  • 模块中的import有提升效果,会提升到整个模块之前。提升效果的实质是:import命令是编译阶段执行的,编译阶段总是在代码实际运行之前
  • 由于import是静态执行的,不能使用表达式和变量,因为表达式和变量只有在运行时才能获取到结果
  • 如果多次执行同一条import语句,则只会真正执行一次,不会执行多次
  • ES6 模块中的几个关键语句:import 'react';,import {lodash} from 'lodash';, , import * from 'react', export var a = 12;, const a = 55; export default a;
  • 正是因为export default实际上是输出一个名为default的变量,所以不能在export default后面加变量声明语句
  • ES6 模块无法动态加载、按条件加载、运行时加载。因为import语句是在编译时执行的,是静态的

CommonJS 和 ES6 模块的不同

  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
  • Node 环境中,ES6 模块之中,顶层的this指向undefinedCommonJS 模块的顶层this指向当前模块,这是两者的一个重大差异。
  • 在 Node 环境中,以下几个变量在模块中是不存在的:arguments,require,module,exports,__filename,__dirname

ES6 模块的运行机制

ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令 import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的 import 有点像 Unix 系统的“符号连接”,原始值变了,import 加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。


let -const -function提升问题

let 的[创建]过程被提升了,但是[初始化]没有提升

var 的[创建] 和[初始化]都被提升了

function 的 [创建] [初始化] [赋值] 都被提升了


前端中堆和栈的概念

!!!内容整理自各大博客+理解!!!

内存中堆和栈概念

  • 栈:先进后出;由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

  • 堆:队列优先,先进先出;动态分配的空间 一般由程序员分配释放, 若程序员不释放,程序结束时可能由 OS 回收,分配方式类似于链表。

堆与栈区别

堆与栈实际上是操作系统对进程占用的内存空间的两种管理方式,主要有如下几种区别:
(1)管理方式不同。栈由操作系统自动分配释放,无需我们手动控制;堆的申请和释放工作由程序员控制,容易产生内存泄漏

(2)空间大小不同。每个进程拥有的栈的大小要远远小于堆的大小。理论上,程序员可申请的堆大小为虚拟内存的大小,进程栈的大小 64bits 的 Windows 默认 1MB,64bits 的 Linux 默认 10MB;

(3)生长方向不同。堆的生长方向向上,内存地址由低到高;栈的生长方向向下,内存地址由高到低。

(4)分配方式不同。堆都是动态分配的,没有静态分配的堆。栈有 2 种分配方式:静态分配和动态分配。静态分配是由操作系统完成的,比如局部变量的分配。动态分配由 alloca 函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由操作系统进行释放,无需我们手工实现。

(5)分配效率不同。栈由操作系统自动分配,会在硬件层级对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是由 C/C++提供的库函数或运算符来完成申请与管理,实现机制较为复杂,频繁的内存申请容易产生内存碎片。显然,堆的效率比栈要低得多。

(6)存放内容不同。栈存放的内容,函数返回地址、相关参数、局部变量和寄存器内容等。当主函数调用另外一个函数的时候,要对当前函数执行断点进行保存,需要使用栈来实现,首先入栈的是主函数下一条语句的地址,即扩展指针寄存器的内容(EIP),然后是当前栈帧的底部地址,即扩展基址指针寄存器内容(EBP),再然后是被调函数的实参等,一般情况下是按照从右向左的顺序入栈,之后是被调函数的局部变量,注意静态变量是存放在数据段或者 BSS 段,是不入栈的。出栈的顺序正好相反,最终栈顶指向主函数下一条语句的地址,主程序又从该地址开始执行。堆,一般情况堆顶使用一个字节的空间来存放堆的大小,而堆中具体存放内容是由程序员来填充的。

从以上可以看到,堆和栈相比,由于大量 malloc()/free()或 new/delete 的使用,容易造成大量的内存碎片,并且可能引发用户态和核心态的切换,效率较低。栈相比于堆,在程序中应用较为广泛,最常见的是函数的调用过程由栈来实现,函数返回地址、EBP、实参和局部变量都采用栈的方式存放。虽然栈有众多的好处,但是由于和堆相比不是那么灵活,有时候分配大量的内存空间,主要还是用堆。

无论是堆还是栈,在内存使用时都要防止非法越界,越界导致的非法内存访问可能会摧毁程序的堆、栈数据,轻则导致程序运行处于不确定状态,获取不到预期结果,重则导致程序异常崩溃,这些都是我们编程时与内存打交道时应该注意的问题。

数据结构中的堆和栈

这里不做讨论,其他资料已经写的很明白了

与前端相关的内容

JavaScript 的数据类型有两大类

基本类型

包含 null、undefined、Number、String、Boolean,ES6还多了一种 Symbol

​ 基本数据类型可以直接访问,他们是按照值进行分配的,存放在栈(stack)内存中的简单数据段,数据大小确定,内存空间大小可以分配。

引用型

即 Object ,是存放在堆(heap)内存中的对象,变量实际保存的是一个指针,这个指针指向另一个位置。

概念闲扯

讲到了堆栈内存那么自然就会牵扯到了闭包、浅拷贝、深拷贝然后就会扯到 call、bind、apply

先来扯一下浅拷贝和深拷贝

首先!!!!记得只有引用类型才有浅拷贝深拷贝这么 一说,基本数据类型雨女无瓜

最常用的深拷贝:序列法和反序列法

1
2
3
4
// 序列化反序列化法
function deepClone(obj) {
return JSON.parse(JSON.stringify(obj))
}

在我的面试笔记里,还有更详细的方法,转载自一个博客大佬,真可谓把深拷贝浅拷贝完全剖析

以下为该大佬地址

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

再来扯一下闭包

闭包我个人的理解就是他是一个函数,可以访问别人作用域内变量的函数

闭包需要理解以下案例

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
var name = 'The Window'
var object = {
name: 'My Object',
getNameFunc: function () {
return function () {
return this.name
}
},
}
alert(object.getNameFunc()()) //The Window

var name = 'The Window'
var object = {
name: 'My Object',
getNameFunc: function () {
var that = this
return function () {
return that.name
}
},
}
alert(object.getNameFunc()()) //My Object

function fun(n, o) {
console.log(o)
return {
fun: function (m) {
return fun(m, n)
},
}
}
var a = fun(0)
a.fun(1)
a.fun(2)
a.fun(3) //undefined,?,?,?
var b = fun(0).fun(1).fun(2).fun(3) //undefined,?,?,?
var c = fun(0).fun(1)
c.fun(2)
c.fun(3) //undefined,?,?,?

call bind apply 引用大佬文章,每次忘记都看一遍!舒服

JS 基础-面试官想知道你有多理解 call,apply,bind?[不看后悔系列]

https://juejin.im/post/5d469e0851882544b85c32ef


性能优化笔记

优化页面加载时间

html 标签顺序

应该把所有能移动的 script 标签移到 html前,这样整个页面都会在各项脚本加载和解析之前进行渲染,从而提升了页面的可感知响应能力

JavaScript 代码 gizp 编码传输

在服务器设置 gzip 编码

缩编混淆和编译

JSMin 缩编

UglifyJS 混淆

Google closure compiler 代码编译

避免全局变量的使用

异步加载 JavaScript 代码

JS 延时加载

减少 dom 操作

实现对页面元素的最小化访问

以变量保存对 dom 元素的引用以便后续使用

通过对单独父元素的引用来访问其子 dom 元素

对新建元素实施 dom 修改后才将其添加至当前实时页面

尽量利用已有元素

复制已经存在的元素以提高性能

尽量使用 css 而非 JavaScript 来操控页面样式

因为修改元素的 style 属性会引发一次重排

建议隐藏元素,再修改元素的 style 属性,减少重排

提升 dom 事件性能

使用事件委托

使用框架化处理频密发出的事件

将计算密集型的代码移至单独的函数中,此函数按较长时间的计时器或时间间隔来执行代码,所使用的是保存在变量中的数据而不是直接取自于事件处理函数

提升函数性能

使用记忆功能保存先前函数的返回结果

使用正则表达式实现更快速的字符串操作

提高数组使用性能

1
2
var myArray = [] ;  //快
var myArray = new Array(); //慢

循环,for 是最快的

避免在循环中创建函数

转移密集型任务到 web worker


懒加载

懒加载

第一步:加载 loading 图片

第二步:判断哪些图片要加载

第三步:隐形加载图片

第四部:图片替换,节流消抖

function lazyload(){

​ var h = window.innerHeight;

​ var s = document.documentElement.sc

}


计网专业回答版输入url到页面显示发生了哪些事情

经典面试题:从输入URL到页面显示发生了哪些事情

以前一直都记不住,这次自己理解了一下

用自己的话总结了一次,不对的地方希望大佬给我指出来

1.主机通过DHCP协议获取客户端的IP地址、子网掩码和DNS服务器的IP地址

2.然后开始向服务器发生请求,发生请求需要生产一个TCP套接字,生成这个套接字是需要网站域名对应的IP地址,要获取IP地址需要解析域名,这时候就用到了DNS解析协议

3.要使用DNS解析协议,需要通过网关路由器转发DNS解析请求,这时候通过ARP协议将DHCP获取到的网关路由器IP解析成网关路由器的MAC地址,通过网关路由器发生DNS查询请求,DNS服务器响应后,通过UDP协议传输 [ 其中间的路程:路由器反向转发->网关路由器->以太网交换机->主机(客户端) ]

4.这时候取到了HTTP服务器的IP地址,能够成功生成TCP套接字,具备了与服务器通信的容器。这时候就开始TCP/IP三次握手建立连接,建立成功后,就通过TCP套接字进行数据读取传输,传输结束后进行四次挥手,关闭连接,浏览器渲染—PS:这里又可以引出DOM渲染,JS和CSS阻塞之类的知识点

以下为参考资料:附原文地址

[https://github.com/CyC2018/CS-Notes/blob/master/notes/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%20-%20%E5%BA%94%E7%94%A8%E5%B1%82.md#%E5%8A%A8%E6%80%81%E4%B8%BB%E6%9C%BA%E9%85%8D%E7%BD%AE%E5%8D%8F%E8%AE%AE](https://github.com/CyC2018/CS-Notes/blob/master/notes/计算机网络 - 应用层.md#动态主机配置协议)

1. DHCP 配置主机信息

  • 假设主机最开始没有 IP 地址以及其它信息,那么就需要先使用 DHCP 来获取。
  • 主机生成一个 DHCP 请求报文,并将这个报文放入具有目的端口 67 和源端口 68 的 UDP 报文段中。
  • 该报文段则被放入在一个具有广播 IP 目的地址(255.255.255.255) 和源 IP 地址(0.0.0.0)的 IP 数据报中。
  • 该数据报则被放置在 MAC 帧中,该帧具有目的地址 FF:FF:FF:FF:FF:FF,将广播到与交换机连接的所有设备。
  • 连接在交换机的 DHCP 服务器收到广播帧之后,不断地向上分解得到 IP 数据报、UDP 报文段、DHCP 请求报文,之后生成 DHCP ACK 报文,该报文包含以下信息:IP 地址、DNS 服务器的 IP 地址、默认网关路由器的 IP 地址和子网掩码。该报文被放入 UDP 报文段中,UDP 报文段有被放入 IP 数据报中,最后放入 MAC 帧中。
  • 该帧的目的地址是请求主机的 MAC 地址,因为交换机具有自学习能力,之前主机发送了广播帧之后就记录了 MAC 地址到其转发接口的交换表项,因此现在交换机就可以直接知道应该向哪个接口发送该帧。
  • 主机收到该帧后,不断分解得到 DHCP 报文。之后就配置它的 IP 地址、子网掩码和 DNS 服务器的 IP 地址,并在其 IP 转发表中安装默认网关。

2. ARP 解析 MAC 地址

  • 主机通过浏览器生成一个 TCP 套接字,套接字向 HTTP 服务器发送 HTTP 请求。为了生成该套接字,主机需要知道网站的域名对应的 IP 地址。
  • 主机生成一个 DNS 查询报文,该报文具有 53 号端口,因为 DNS 服务器的端口号是 53。
  • 该 DNS 查询报文被放入目的地址为 DNS 服务器 IP 地址的 IP 数据报中。
  • 该 IP 数据报被放入一个以太网帧中,该帧将发送到网关路由器。
  • DHCP 过程只知道网关路由器的 IP 地址,为了获取网关路由器的 MAC 地址,需要使用 ARP 协议。
  • 主机生成一个包含目的地址为网关路由器 IP 地址的 ARP 查询报文,将该 ARP 查询报文放入一个具有广播目的地址(FF:FF:FF:FF:FF:FF)的以太网帧中,并向交换机发送该以太网帧,交换机将该帧转发给所有的连接设备,包括网关路由器。
  • 网关路由器接收到该帧后,不断向上分解得到 ARP 报文,发现其中的 IP 地址与其接口的 IP 地址匹配,因此就发送一个 ARP 回答报文,包含了它的 MAC 地址,发回给主机。

3. DNS 解析域名

  • 知道了网关路由器的 MAC 地址之后,就可以继续 DNS 的解析过程了。
  • 网关路由器接收到包含 DNS 查询报文的以太网帧后,抽取出 IP 数据报,并根据转发表决定该 IP 数据报应该转发的路由器。
  • 因为路由器具有内部网关协议(RIP、OSPF)和外部网关协议(BGP)这两种路由选择协议,因此路由表中已经配置了网关路由器到达 DNS 服务器的路由表项。
  • 到达 DNS 服务器之后,DNS 服务器抽取出 DNS 查询报文,并在 DNS 数据库中查找待解析的域名。
  • 找到 DNS 记录之后,发送 DNS 回答报文,将该回答报文放入 UDP 报文段中,然后放入 IP 数据报中,通过路由器反向转发回网关路由器,并经过以太网交换机到达主机。

4. HTTP 请求页面

  • 有了 HTTP 服务器的 IP 地址之后,主机就能够生成 TCP 套接字,该套接字将用于向 Web 服务器发送 HTTP GET 报文。
  • 在生成 TCP 套接字之前,必须先与 HTTP 服务器进行三次握手来建立连接。生成一个具有目的端口 80 的 TCP SYN 报文段,并向 HTTP 服务器发送该报文段。
  • HTTP 服务器收到该报文段之后,生成 TCP SYN ACK 报文段,发回给主机。
  • 连接建立之后,浏览器生成 HTTP GET 报文,并交付给 HTTP 服务器。
  • HTTP 服务器从 TCP 套接字读取 HTTP GET 报文,生成一个 HTTP 响应报文,将 Web 页面内容放入报文主体中,发回给主机。
  • 浏览器收到 HTTP 响应报文后,抽取出 Web 页面内容,之后进行渲染,显示 Web 页面。