1、雨水算法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| function rain(arr){ let l_m = [arr[0]]; let r_m = [arr[arr.length-1]]; let res = 0; for(let i = 1;i < arr.length-1;i++){ if(l_m[l_m.length-1] < arr[i]){ l_m.push(arr[i]) }else{ l_m.push(l_m[l_m.length-1]) } if(r_m[0] < arr[arr.length-i-1]){ r_m.unshift(arr[arr.length-i-1]) }else{ r_m.unshift(r_m[0]); } } for(let i = 1;i < arr.length-1;i++){ let now = Math.min(l_m[i],r_m[i])-arr[i] res += now>0?now:0; } return res; } console.log(rain([0,1,0,2,1,0,1,3,2,1,2,1]));
|
2、Vue.nextTick()的原理和用途
Vue实现响应式并不是数据发生变化之后DOM立刻变化,而是按一定的策略进行DOM的更新。
在Vue的文档中,说明Vue是异步执行的DOM更新的。
具体来说,异步执行的运行机制如下。
- 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)
- 主线程之外,还存在一个”任务队列”(task queue)。只要异步任务有了运行结果,就在“任务队列”之中放置一个事件。
- 一旦“执行栈”中所有同步任务执行完毕,系统就会读取“任务队列”,看看里面有哪些事件。哪些对应的异步任务于是结束等待状态,进入执行栈,开始执行。
- 主线程不断重复上面的第三步。
简单来说,Vue在修改数据后,视图不会立刻更新,而是等同一事件循环
中的所有数据变化完成之后,再统一进行视图更新。
1 2 3 4 5 6 7 8 9
| vm.message = 'changed'
console.log(vm.$el.textContent)
Vue.nextTick(function(){ console.log(vm.$el.textContent) })
|
事件循环:
第一个tick,就是本次循环:首先会修改数据,这是同步任务。同一事件循环的所有的同步任务都在主线程上执行,形成一个执行栈,此时未涉及DOM。Vue开启一个异步队列,并缓冲在此事件循环中发生的所有数据改变。如果同一个watcher被多次触发,只会被推入到队列中一次。
第二个tick,就是下次更新循环:同步任务执行完毕,开始执行异步watcher队列的任务,更新DOM。Vue在内部尝试对异步队列使用原生的Promise.then和MessageChannel方法,如果执行环境不支持,会采用setTimeout(fn,0)代替。
第三个tick:也就是下次DOM更新循环结束之后,此时通过Vue.nextTick获取到改变后的DOM。通过setTImeout(fn,0)也可以同样获取到。
注意
:在created和mounted阶段,如果需要操作渲染后的视图,也要使用nextTick方法。
mounted 不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用 vm.$nextTick 替换掉 mounted
3、会引起回流和重绘的情况,并且如何优化
大多数浏览器都会通过队列化修改并批量执行来优化重排过程。
浏览器会把所有会引起回流、重绘的操作都放入一个队列,等过一定的时间或者操作达到了一定的阈值,浏览器就会flush队列,进行一个批处理。这样让多次的回流和重绘变成一次回流重绘。
但是,当获取布局信息操作的时候,会强制浏览器提前flush队列,触发回流重绘返回信息,这种情况下,浏览器的优化就不起作用了。比如以下属性或方法:
- offsetTop、offsetLeft、offsetWidth、offsetHeight
- scrollTop、scrollLeft、scrollWidth、scrollHeight
- clientTop、clientLeft、clientWidth、clientHeight
- getComputedStyle()
- getBoundingClientRect
那么,除了浏览器的优化,我们还有什么方法可以来减少回流和重绘达到性能优化?
通过合并多次DOM样式的修改,来减少回流和重绘的发生次数。
利用className修改元素的class
const el = document.getElementById(‘test’);
el.className += ’ active’;
利用cssText合并样式修改操作
const el = document.getElementById(‘test’);
el.style.cssText += ‘border-left: 1px; border-right: 2px; padding: 5px;’;
4、如何实现一秒输出一个数字里的元素
1 2 3 4 5 6 7 8 9 10 11 12
| function print(arr) { for(let i = 0;i < arr.length;i++){ (function (i) { setTimeout(()=>console.log(arr[i]),2000*i) })(i)} }
let arr = [1,2,3,4] print(arr);
|
5、手写一个双向绑定
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
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <input type="text" id ='inpName'> 姓名: <span id = 'spanName'><span> <script> let obj = { name: '' } obj = new Proxy(obj,{ get(target,prop){ return target[prop]; }, set(target,prop,value){ target[prop] = value; observer(); } })
function observer(){ spanName.innerHTML = obj.name; inpName.value = obj.name; }
inpName.oninput = function (){ obj.name = this.value; }
setTimeout(()=>{ obj.name = "数据影响视图" },2000)
</script> </body> </html>
|
6、Scheduler
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
| class Scheduler { constructor() { this.limitCount = 2 this.locks = [] this.values = [] } async push(value) { if (this.values.length >= this.limitCount) { await new Promise(resolve => this.locks.push(resolve)) } this.values.push(value) } get() { (this.locks.shift() || (() => {}))() return this.values.shift() } async add(promiseCreactor) { await this.push(1) await promiseCreactor() this.get() } } const timeout = (time) => new Promise(resolve => { setTimeout(resolve, time) }) const scheduler = new Scheduler(); const addTask = (time, order) => { scheduler.add(() => timeout(time)) .then(() => console.log(time, 'time, order', order)) } addTask(500, '1'); addTask(800, '2'); addTask(1000, '3'); addTask(1200, '4');
|
7、封装ajax请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const request = function (method, url, isajax) { return new Promise((resolve, reject) => { let req = new XMLHttpRequest(); req.open(method, url, isajax); req.onload = function () { if (req.readState === 4 && req.status === 200) { resolve(req.response) } else { reject(req.statusText); } req.onerror = function () { reject(Error("网络异常")) } } req.send(); }) }
|
8、__proto__
和prototype的区别
__proto__
就是对象的[[prototype]]属性,ES5中是用Object.getPrototypeOf函数获得一个对象的[[prototype]]。ES6中,使用Object.setPrototypeOf可以直接修改一个对象的[[prototype]]
1 2
| Array.prototype.__proto__ = Object.__proto__ Object.protptype.__proto__ = null
|
9、判断对象是否存在循环引用
首先,循环引用对象本来没有什么问题,序列化的时候才会发生问题,比如调用JSON.stringify()
对该类对象进行序列化,就会报错: Converting circular structure to JSON.
,而序列化需求很常见,比如发起一个ajax请求提交一个对象就需要对对象进行序列化。
针对上面这样的问题,可以通过JSON
扩展包的var c = JSON.decycle(a)
和var a = JSON.retrocycle(c)
来处理。这里就不做过多的解释了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function cycle(obj, parent){ var parentArr = parent || [obj]; for(var i in obj){ if(typeof obj[i] === "object"){ parentArr.forEach(element => { if(element === obj[i]){ obj[i] = "[cycle]"; } }); cycle(obj[i], [...parentArr,obj[i]]) } } return obj; }
var a = { b:null, c:null } a.b = a; a.c = a.c console.log(cycle(a))
|
10、实现一个任务队列
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
| class Queue{ constructor(){ this.taskList = []; } task(time,func){ this.taskList.push(function(){ return new Promise((resolve)=>{ setTimeout(()=>{ func.call(this) resolve(); },time) }) }) return this; } async start(){ while(this.taskList.length){ await this.taskList.shift()(); } } } new Queue() .task(1000, ()=>{console.log(1)}) .task(2000, ()=>{console.log(2)}) .task(1000, ()=>{console.log(3)}) .start()
|
11、如果给vue对象新增一个属性,如果也实现响应式
根据官方文档定义:如果在实例创建之后添加新的属性到实例上,它不会触发视图更新。
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data
选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty
)把这些属性全部转为 getter/setter
。
1 2 3 4 5
| Vue.set(vm.obj, 'e', 0)
this.obj= Object.assign({}, this.obj, { a: 1, e: 2 })
|
12、vue自定义指令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| Vue.directive('focus', { inserted: function (el) { el.focus() } })
new Vue({ el: '#app', directives: { focus: { inserted: function (el) { el.focus() } } } })
|
钩子函数
一个指令定义对象可以提供如下几个钩子函数 (均为可选):
inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
unbind:只调用一次,指令与元素解绑时调用。
指令钩子函数会被传入以下参数:
el:指令所绑定的元素,可以用来直接操作 DOM 。
binding:一个对象,包含以下属性:
name:指令名,不包括 v- 前缀。
value:指令的绑定值,例如:v-my-directive=“1 + 1” 中,绑定值为 2。
oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
expression:字符串形式的指令表达式。例如 v-my-directive=“1 + 1” 中,表达式为 “1 + 1”。
arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 “foo”。
modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
vnode: Vue编译生成的虚拟节点。移步 VNode API 来了解更多详情。
oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。