# 2019年08月刊
# 2019/08/26 - 2019/09/01 ⌚️
如何构建搜索二叉树 ?
点击
// 搜索二叉树是这么一种树,对于任何一个节点来说,它的左子树的值肯定小于它的值,它的右子树的值肯定大于它的值 function Node(val) { this.left = null; this.right = null; this.value = val; } function generateBST(root, array) { var length = array.length; for (var i = 1; i < length; i++) { insertNode(root, array[i]); } } function insertNode(node, value) { if (value < node.value) { if (node.left === null) { node.left = new Node(value); } else { node = node.left; insertNode(node, value); } } else { if (node.right === null) { node.right = new Node(value); } else { node = node.right; insertNode(node, value); } } } var array = [2, 3, 4, 12, 3, 54, 6, 7, 1]; var root = new Node(array[0]); generateBST(root, array); console.log(root);
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
39async await
在事件队列中是怎样的存在?点击
从字面意思上看 await 就是等待,await 等待的是一个表达式,这个表达式的返回值可以是一个 promise 对象也可以是其他值。
很多人以为 await 会一直等待之后的表达式执行完之后才会继续执行后面的代码,实际上 await 是一个让出线程的标志。await 后面的表达式会先执行一遍,将 await 后面的代码加入到 microtask 中,然后就会跳出整个 async 函数来执行后面的代码。
由于因为 async await 本身就是 promise+generator 的语法糖。所以 await 后面的代码是 microtask。所以
async function async1() { console.log('async1 start'); await async2(); console.log('async1 end'); } // 等价于 async function async1() { console.log('async1 start'); Promise.resolve(async2()).then(() => { console.log('async1 end'); }); }
1
2
3
4
5
6
7
8
9
10
11
12深度优先遍历和广度优先遍历?
点击
深度优先:找到一个节点后,把它的后辈都找出来,最常用递归法。
广度优先:找到一个节点后,把他同级的兄弟节点都找出来放在前边,把孩子放到后边,最常用 while
['1', '2', '3'].map(parseInt)
的输出结果是什么?点击
第一眼看到这个题目的时候,脑海跳出的答案是
[1, 2, 3]
,但是真正的答案是[1, NaN, NaN]
。首先让我们回顾一下,map 函数的第一个参数 callback:
var new_array = arr.map(function callback(currentValue[, index[, array]]) { // Return element for new_array }[, thisArg])
这个 callback 一共可以接收三个参数,其中第一个参数代表当前被处理的元素,而第二个参数代表该元素的索引。
而 parseInt 则是用来解析字符串的,使字符串成为指定基数的整数。
parseInt(string, radix)
接收两个参数,第一个表示被处理的值(字符串),第二个表示为解析时的基数。了解这两个函数后,我们可以模拟一下运行情况
parseInt('1', 0); //radix为0时,且string参数不以“0x”和“0”开头时,按照10为基数处理。这个时候返回1 parseInt('2', 1); //基数为1(1进制)表示的数中,最大值小于2,所以无法解析,返回NaN parseInt('3', 2); //基数为2(2进制)表示的数中,最大值小于3,所以无法解析,返回NaN
1
2
3
4
5map 函数返回的是一个数组,所以最后结果为
[1, NaN, NaN]
a.b.c.d
和a['b']['c']['d']
,哪个性能更高?点击
a.b.c.d 比 a['b']['c']['d'] 性能高点,后者还要考虑 [ ] 中是变量的情况,再者,从两种形式的结构来看,显然编译器解析前者要比后者容易些,自然也就快一点。
vue Diff 的比较逻辑
点击
Vue 只会对新旧节点中 父节点是相同节点 的 那一层子节点 进行比较
也可以说成是
只有两个新旧节点是相同节点的时候,才会去比较他们各自的子节点
最大的根节点一开始可以直接比较
这也叫做 同层级比较,并不需要递归,虽然好像降低了一些复用性,也是为了避免过度优化,是一种很高效的 Diff 算法
比较逻辑:
1、能不移动,尽量不移动
2、没得办法,只好移动
3、实在不行,新建或删除
组件的 data 为什么要写成函数形式?
点击
Vue 的组件都是可复用的,一个组件创建好后,可以在多个地方复用,而不管复用多少次,组件内的 data 都应该是相互隔离,互不影响的,所以组件每复用一次,data 就应该复用一次,每一处复用组件的 data 改变应该对其他复用组件的数据不影响。
为了实现这样的效果,data 就不能是单纯的对象,而是以一个函数返回值的形式,所以每个组件实例可以维护独立的数据拷贝,不会相互影响。
# 2019/08/19 - 2019/08/25 ⌚️
为什么 Vuex 的 mutation 中不能做异步操作?
点击
Vuex 中所有的状态更新的唯一途径都是 mutation,异步操作通过 Action 来提交 mutation 实现,这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
vue 子组件为什么不可以修改父组件传递的 Prop?
点击
Vue 提倡单向数据流,即父级 props 的更新会流向子组件,但是反过来则不行。这是为了防止意外的改变父组件状态,使得应用的数据流变得难以理解。如果破坏了单向数据流,当应用复杂时,debug 的成本会非常高。
package.json 中库版本号详解?
点击
当我们看 package.json 文件时,会看到以下几种版本号的表示方式,有的是一个^,有的是~,而有的就是单纯的数字
"babel-core": "7.0.0-bridge.0", "babel-eslint": "^10.0.1", "babel-jest": "~23.6.0",
1
2
3当我们使用 npm install —save 安装时,优先会使用插入符号(^)而不是波浪线(~)
版本号的含义 node 中的版本号都是由 3 个数字用(.)连接起来,三个数字的含义分别为 major, minor, patch
例如,10.0.1 对应 major, minor, patch 就是:10 是 marjor version, 0 是 minor version, 1 是 patch version
其中:
major version: 这个版本号变化了表示有了一个不可以和上个版本兼容的更新
minor version: 这个版本号变化了表示增加了新功能,并可以向后兼容
patch version: 这个版本号变化了表示修复了 bug, 并可以向后兼容
~ 和 ^ 的区别
波浪符号(~):会更新到当前 minor version(中间的数字)的最新版本,即 23.6.0 会更新到 23.6.x 的最新版本,如果出了一个 23.7.0,则不会自动升级
插入符号(^): 会更新到当前 major version(前面的数字)的最新版本,即 23.6.0 会更新到 23.x.x 的最新版本,但是不会更新到 24.x.x
最后如果前面不加符号,就安装特定版本的库,不会自动安装更高版本号的库
如何实现 js 的动态引入?
点击
首先定义一个函数,传入两个参数,url 和 callback. callback 用于 js 加载完成时调用。
因为可能出现重复加载,因此使用闭包来保存已经加载的 js 路径。代码如下:
function loadScript(url, callback) { // 通过闭包保存已经加载过的JS,防止重复加载 const urlList = []; return function() { if (urlList.indexOf(url) > -1) { return; } callback = callback || function() {}; const script = document.createElement('script'); script.type = 'text/javascript'; if (script.readyState) { //IE script.onreadystatechange = function() { if ( script.readyState === 'loaded' || script.readyState === 'complete' ) { script.onreadystatechange = null; callback(); } }; } else { //Others script.onload = function() { callback(); }; } script.src = url; document.body.appendChild(script); urlList.push(url); }; } // 使用方法 loadScript( 'https://cdn.bootcss.com/html2canvas/0.5.0-beta4/html2canvas.min.js' )();
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
37BFC 的约束规则?
点击
内部的 Box 会在垂直方向上一个接一个的放置
垂直方向上的距离由 margin 决定。(完整的说法是:属于同一个 BFC 的两个相邻 Box 的 margin 会发生重叠(塌陷),与方向无关。)
每个元素的左外边距与包含块的左边界相接触(从左向右),即使浮动元素也是如此。(这说明 BFC 中子元素不会超出他的包含块,而 position 为 absolute 的元素可以超出他的包含块边界)
BFC 的区域不会与 float 的元素区域重叠
计算 BFC 的高度时,浮动子元素也参与计算
BFC 就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面元素,反之亦然
如何反转二叉树??
点击
function Mirror(root) { if (root === null) { return; } let temp = root.left; root.left = root.right; root.right = temp; Mirror(root.left); Mirror(root.right); }
1
2
3
4
5
6
7
8
9
10
# 2019/08/12 - 2019/08/18 ⌚️
# 2019/08/05 - 2019/08/11 ⌚️
vm.$set()实现原理是什么?
点击
因为组件是可以复用的,JS 里对象是引用关系,如果组件 data 是一个对象,那么子组件中的 data 属性值会互相污染,产生副作用。
所以一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝。new Vue 的实例是不会被复用的,因此不存在以上问题。
export function set(target: Array<any> | Object, key: any, val: any): any { // target 为数组 if (Array.isArray(target) && isValidArrayIndex(key)) { // 修改数组的长度, 避免索引>数组长度导致splice()执行有误 target.length = Math.max(target.length, key); // 利用数组的splice变异方法触发响应式 target.splice(key, 1, val); return val; } // target为对象, key在target或者target.prototype上 且必须不能在 Object.prototype 上,直接赋值 if (key in target && !(key in Object.prototype)) { target[key] = val; return val; } // 以上都不成立, 即开始给target创建一个全新的属性 // 获取Observer实例 const ob = (target: any).__ob__; // target 本身就不是响应式数据, 直接赋值 if (!ob) { target[key] = val; return val; } // 进行响应式处理 defineReactive(ob.value, key, val); ob.dep.notify(); return val; }
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如果目标是数组,使用 vue 实现的变异方法 splice 实现响应式
如果目标是对象,判断属性存在,即为响应式,直接赋值
如果 target 本身就不是响应式,直接赋值
如果属性不是响应式,则调用 defineReactive 方法进行响应式处理
Vue 组件 data 为什么必须是函数 ?
点击
因为组件是可以复用的,JS 里对象是引用关系,如果组件 data 是一个对象,那么子组件中的 data 属性值会互相污染,产生副作用。
所以一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝。new Vue 的实例是不会被复用的,因此不存在以上问题。
vue 是如何对数组方法进行变异的 ?
点击
简单来说,Vue 通过原型拦截的方式重写了数组的 7 个方法,首先获取到这个数组的 ob,也就是它的 Observer 对象,如果有新的值,就调用 observeArray 对新的值进行监听,然后手动调用 notify,通知 render watcher,执行 update
Vue 中的 key 到底有什么用?
点击
key 是给每一个 vnode 的唯一 id,依靠 key,我们的 diff 操作可以更准确、更快速 (对于简单列表页渲染来说 diff 节点也更快,但会产生一些隐藏的副作用,比如可能不会产生过渡效果,或者在某些节点有绑定数据(表单)状态,会出现状态错位。)
diff 算法的过程中,先会进行新旧节点的首尾交叉对比,当无法匹配的时候会用新节点的 key 与旧节点进行比对,从而找到相应旧节点.
更准确 : 因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。所以会更加准确,如果不加 key,会导致之前节点的状态被保留下来,会产生一系列的 bug。
更快速 : key 的唯一性可以被 Map 数据结构充分利用,相比于遍历查找的时间复杂度 O(n),Map 的时间复杂度仅仅为 O(1),源码如下:
computed 和 watch 有什么区别及运用场景? (opens new window)
Computed本质是一个具备缓存的watcher,依赖的属性发生变化就会更新视图。 适用于计算比较消耗性能的计算场景。当表达式过于复杂时,在模板中放入过多逻辑会让模板难以维护,可以将复杂的逻辑放入计算属性中处理。 Watch没有缓存性,更多的是观察的作用,可以监听某些数据执行回调。当我们需要深度监听对象中的属性时,可以打开deep:true选项,这样便会对对象中的每一项进行监听。这样会带来性能问题,优化的话可以使用字符串形式监听,如果没有写到组件中,不要忘记使用unWatch手动注销哦。
1
2
3
4
# 2019/07/29 - 2019/08/04 ⌚️
JavaScript 严格模式下有哪些不同?
点击
- 不允许不使用 var 关键字去创建全局变量,抛出 ReferenceError
- 不允许对变量使用 delete 操作符,抛 ReferenceError
- 不可对对象的只读属性赋值,不可对对象的不可配置属性使用 delete 操作符,不可为不可拓展的对象添加属性,均抛 TypeError
- 对象属性名必须唯一
- 函数中不可有重名参数
- 在函数内部对修改参数不会反映到 arguments 中
- 淘汰 arguments.callee 和 arguments.caller
- 不可在 if 内部声明函数
- 抛弃 with 语句
说出下列代码的输出顺序?
点击
<div id="app"> <span id="name" ref="name">{{ name }}</span> <button @click="change">change name</button> <div id="content"></div> </div> <script> new Vue({ el: '#app', data() { return { name: 'SHERlocked93', }; }, methods: { change() { const $name = this.$refs.name; this.$nextTick(() => console.log('setter前:' + $name.innerHTML)); this.name = ' name改喽 '; console.log('同步方式:' + this.$refs.name.innerHTML); setTimeout(() => this.console('setTimeout方式:' + this.$refs.name.innerHTML) ); this.$nextTick(() => console.log('setter后:' + $name.innerHTML)); this.$nextTick().then(() => console.log('Promise方式:' + $name.innerHTML) ); }, }, }); </script>
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这里涉及的知识是 vue.$nextTick()原理,详情可查看《全面解析 Vue.nextTick 实现原理》 (opens new window)
同步方式:SHERlocked93 setter前:SHERlocked93 setter后:name改喽 Promise方式:name改喽 setTimeout方式:name改喽
1
2
3
4
5
实现一个 LazyMan,可以按照以下方式调用
LazyMan(“Hank”) 输出: Hi! This is Hank!
LazyMan(“Hank”).sleep(10).eat(“dinner”) 输出 Hi! This is Hank! // 等待 10 秒.. Wake up after 10 Eat dinner~
LazyMan(“Hank”).eat(“dinner”).eat(“supper”) 输出 Hi This is Hank! Eat dinner~ Eat supper~
点击
class LazyMan { constructor(name) { this.name = name; this.asyncFun = Promise.resolve(); console.log(`--------- 我就是 ${this.name}! ---------`); } sleep(delay) { this.asyncFun = this.asyncFun.then(() => { return new Promise(resolve => { setTimeout(() => { console.log(`--------- 我睡了 ${delay / 1000}s 然后 ----------`); resolve(); }, delay); }); }); return this; //提供 ”链式调用“ } eat(food) { this.asyncFun = this.asyncFun.then(() => { console.log(`--------- 吃 ${food}~ ---------`); return Promise.resolve(); }); return this; } } new LazyMan('小皮咖') .sleep(4000) .eat('豆浆') .eat('油条') .sleep(2000) .eat('炒年糕');
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
32vue.$set 的作用是?
向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新属性,因为 Vue 无法探测普通的新增属性
手写一个 sleep 函数?
点击
// 时间戳版本 function sleep(time) { let startTime = new Date().getTime(); while (new Date().getTime() - startTime < time) {} console.log('sleep ' + time + 'ms'); } // promise function sleep2(time) { return new Promise((resolve, reject) => { setTimeout(() => { console.log('sleep ' + time + 'ms'); resolve(); }, time); }); } // 测试 const haha = async () => { console.log(11); sleep(2000); await sleep2(3000); console.log(222); }; haha();
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在堆中怎么插入一个元素?
将元素置于堆的末尾,然后调整此堆