你是否是在前端羣見過不少這種前景:這個怎麼作?怎麼拿到這些數據?怎麼更新整個列表?javascript
回答:循環啊!遍歷啊!用一個數組保存,遍歷!jQuery!vue!css
而後有一些稍微高級的:我想快一點的解決方法。我想用性能好一點的方法。html
回答:遞歸啊!開個新的數組保存中間變量,再遍歷!document.querySelectorAll獲取所有,緩存一下長度、全部的元素,遍歷!快排,小的放左邊大的放右邊,遞歸!前端
而後當你發現水羣是解決不了的問題的時候,你已經邁出了進階的一步了。在羣裏問,若是不是一堆熟人,都是陌生人的話,太簡單人家嫌無腦,太難人家嫌麻煩或者壓根不會,中等的稍微查一下資料也能作出來。因此說在一個全是陌生人的羣,問不如本身動手,問了人家也多是答非所問、要不就是暴力循環解決或者拿一些其餘名詞裝逼(好比一些沒什麼知名度的js庫,查了一下原來是某個小功能的)。vue
某路人:循環,給每個li綁一個事件。大概是這樣子: html:java
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
</ul>
複製代碼
js:node
var li = document.querySelectorAll('li')
for(var i = 0;i<li.length;i++){//大約有一半的人連個length都不緩存的
li[i].onclick = function (i) {//而後順便扯一波閉包,扯一下經典面試題,再補一句let能秒殺
return function () {
console.log(i)
}
}(i)
}
複製代碼
問題少年:那我動態添加li呢?jquery
某路人:同樣啊,你加多少個,我就循環遍歷多少個es6
問題少年:假如我有一個按鈕,按了增長一個li,也要實現這個效果,怎麼辦面試
某路人:哈?同樣啊,就是在新增的時候再for循環從新綁事件
問題少年:...
場景還原:
html:
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
</ul>
<button onclick="addli()">add</button>
複製代碼
js:
//原來的基礎上再多一個函數,而後熱心地寫下友好的註釋
function addli(){
var newli = document.createElement('li')
newli.innerHTML = document.querySelectorAll('li').length;//先獲取長度,把序號寫進去
document.querySelectorAll('ul')[0].appendChild(newli)//加入
var li = document.querySelectorAll('li')
for(var i = 0;i<li.length;i++){//再綁一次事件
li[i].onclick = function (i) {
return function () {
console.log(i)
}
}(i)
}
}
複製代碼
問題少年看見功能是實現了,可是他總感受那麼多document是有問題,感受瀏覽器很不舒服,並且也聽聞過「操做dom是很大開銷的」。因而他翻了一下資料,瞭解了一下面向對象、事件代理,通宵達旦寫出本身的代碼。完過後已是三更半夜,打開聊天記錄,嘴角上揚地看了一下某路人的解答。問題少年看着本身親自寫的代碼,甚是欣慰,人生邁出一小步:
html:
<input type="number" id="input">
<button onclick="addli()">add</button>
複製代碼
js:
class Ul {
constructor () {
this.bindEvent = this.bindEvent()//緩存合適的dom n事件
this.ul = document.createElement('ul')
this.bindEvent(this.ul,'click',(e)=>{
console.log(e.target.innerHTML)
})
this.init = this.init()//單例模式,只能添加一個ul
this.init(this.ul)
this.counts = 0//li個數
}
bindEvent () {
if (window.addEventListener) {
return function (ele, event, handle, isBunble) {
isBunble = isBunble || false
ele.addEventListener(event, handle, isBunble)
}
} else if (window.attachEvent) {
return function (ele, event, handle) {
ele.attachEvent('on' + event, handle)
}
} else {
return function (ele, event, handle) {
ele['on' + event] = handle
}
}
}
appendLi (nodeCounts) {
this.counts += nodeCounts
var temp = ''
while (nodeCounts) {
temp += `<li>${this.counts - nodeCounts +1}</li>`
nodeCounts --
}
this.ul.innerHTML += temp
}
init () {//單例模式
var isappend = false
return function (node) {
if(!isappend){
document.body.appendChild(node)
isappend = true
}
}
}
}
var ul = new Ul()
function addli(){
if(!input.value){
ul.appendLi(1)
}else{
ul.appendLi(+input.value)
input.value = ''
}
}
複製代碼
也就是,幾個div,點哪一個哪一個亮,點另外一個,正在亮着的div立刻暗,新點擊的那個亮起來。
某路人:每個div有兩個類,click類表示被點。每次點一個div,循環遍歷所有div重置狀態爲test類,而後把被點的那個變成click。因而乎,很快寫出了一段代碼
html:
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
<div class="test"></div>
<div class="test"></div>
<div class="test"></div>
複製代碼
css:
.test{
width: 100px;
height: 100px;
background-color: #0f0;
margin: 10px
}
.click{
background-color: #f00
}
複製代碼
js:
var divs = $('.test')
divs.click(function(){
divs.each(function(){
this.className = 'test'
})
this.className = 'click'
})
複製代碼
問題少年反問:我不知道什麼是jQuery,可是我以爲應該這樣子寫
var divs = document.querySelectorAll('.test')
window.onclick = (function () {
var pre
return function (e) {
if(pre !== undefined){
divs[pre].className = 'test'
}
pre = Array.prototype.indexOf.call(divs,e.target)
e.target.className = 'click'
}
})()
複製代碼
某路人:我不知道你綁個事件搞個IIFE幹啥,並且我建議你去學一下jQuery
問題少年(心想):明明console測試的運行時間,他的平均在0.26ms左右,我這個方法就是0.12ms左右,爲何必定要用jQuery?無論了,滾回去繼續學習。
問題少年:rt,求一個快一點的方法
路人甲:
Array(4000).fill(1001).map((v,i)=>v+i).filter(n=>(n+"").split("").reduce(((s,x)=>+x+s),0)==5)
複製代碼
吃瓜羣衆:哇,大神,方法太簡單了,通俗易懂
路人乙:
let temp = []
for(let i = 1000;i<=5000;i++){
temp.push(i.toString().split('').map(x=>+x).reduce((a,b)=>a+b,0) === 5&&i)
}
console.log(temp.filter(x=>x!==false))
複製代碼
吃瓜羣衆:何時才能寫出像大神那樣優美的代碼
路人:其實也不算什麼,徹底沒有考慮算法複雜度,也沒有作優化
道高一尺魔高一丈,接着又來了一種通用的方法:
var f = (s, l) => --l ? Array(s+1).fill(0).map((_,i)=> f(s-i,l).map(v =>i+v)).reduce((x,s)=>[...s,...x],[]):[[s]];
f(5, 4).filter(s=>s[0]>0)
複製代碼
估計問題少年也沒有徹底知足,雖然答案是簡短的es6和api的靈活運用,可是方法仍是有點簡單無腦,作了多餘的循環。
稍微優化一下的版本,循環次數只是縮減到600屢次:(拋開ES6裝逼的光環)
function f (_from, _to, _n) {
var arr = []
var _to = _to + ''
var len = _to.length
!function q (n, str) {
if( str.length === len) {//保證4位數
if(str.split('').reduce((s,x)=>+x+s,0) == _n && str[0] != '0'){//每一位加起來等於5
arr.push(+str)
}
return
}
for(var i = 0;i <= _n - n;i++){//分別是0、一、二、三、四、5開頭的
q(i,i + '' + str)//一位一位地遞歸
}
}(~~_from/1000,'')
return arr
}
f (1000, 5000,5)
複製代碼
可讀性顯然差了不少,可是運行的時間就差太多了:
某路人不加思考秒回:var data2 = data
另一個路人稍加思考:不是吧,你先要深拷貝一下
問題少年:那怎麼深拷貝呢
某路人:JSON.stringify再JSON.parse
問題少年:謝啦,真好用
稍微瞭解的人都知道,一個stringify並不能解決全部的深拷問題。次日,問題少年又回來了:我用了轉義來拷貝,老闆說要弄死我,如今項目裏面全部的面向對象都炸了
某路人:臥槽,我就不信還有什麼是stringify和parse解決不了的
另外一個路人:原型鏈斷了、undefined變成空值、環引用出錯,用了面向對象的全都泡湯了
問題少年特別無助,也沒有人出來幫忙,也許上天有一個稍微好一點點的參考答案,等着他發掘:
function copy (arr) {
var temp
if (typeof arr === 'number'||typeof arr === 'boolean'||typeof arr === 'string') {
return arr
}
if(Object.prototype.toString.call(arr) === "[object Array]" ){
temp = []
for(x in arr){
temp[x] = copy(arr[x])
}
return temp
}else if(Object.prototype.toString.call(arr) === "[object RegExp]"){
temp = arr.valueOf()
var str = (temp.global ? 'g' : '') +(temp.ignoreCase ? 'i': '')+(temp.multiline ? 'm' : '')
return new RegExp(arr.valueOf().source,str)
}else if(Object.prototype.toString.call(arr) === "[object Function]"){
var str = arr.toString();
/^function\s*\w*\s*\(.*\)\s*\{(.*)/m.test(str);
var str1 = RegExp.$1.slice(0,-1);
return new Function(str1)//函數有換行就出事,求更好的解決方法
}else if(Object.prototype.toString.call(arr) === "[object Date]"){
return new Date(arr.valueOf());
}else if(Object.prototype.toString.call(arr) === "[object Object]"){
try{
temp = JSON.parse(JSON.stringify(arr))
}catch(e){//環引用解決:取出環引用部分stringify再放回去
var temp1 = {},circle,result,reset = false,hash
function traverse (obj) {
for(x in obj){
if(!reset&&obj.hasOwnProperty(x)){
if(!temp1[x]){
temp1[x] = obj[x]
}else if(typeof obj[x] === 'object'&&typeof temp1[x] === 'object'){
try{
JSON.stringify(obj[x])
}catch(e){
circle = obj[x]
hash = new Date().getTime()
obj[x] = hash
break
}finally{
return traverse(obj[x])
}
}
if(typeof obj[x] === 'object'){
return traverse(obj[x])
}
}else if(reset){
if(obj[x] === hash){
obj[x] = circle
return
}
if(typeof obj[x] === 'object'){
return traverse(obj[x])
}
}
}
}
traverse(arr)
result = JSON.parse(JSON.stringify(arr))
reset = true
traverse(result)
traverse(arr)
temp = result
}finally{//考慮到原型鏈和Object.create(null)
if(arr.__proto__.constructor && arr.__proto__.constructor !== Object){
temp.__proto__.constructor = arr.__proto__.constructor
}
if(!arr.__proto__.constructor){
temp.__proto__.constructor = null
}
return temp
}
}
if(!arr){
return arr
}
}
複製代碼
給出的圖片大概是這樣子,選取某個li就把他的價格算入sum裏面:
相信50%的人都會這樣子,某路人:vue啊,v-for顯示,computed
甚至有的人2分鐘把代碼擼出來了:
html:
<script src="https://cdn.bootcss.com/vue/2.5.13/vue.min.js"></script>
<div id='app'>
<ul>
<li v-for="x in list">
{{x.price}}
<input type="checkbox" v-model="x.selected">
</li>
</ul>
<button @click="add">add</button>
{{sum}}
</div>
複製代碼
js:
new Vue({
el:'#app',
data(){
return {
list:[{price:1,selected:false},{price:2,selected:false},{price:3,selected:false}]
}
},
methods:{
add(){
this.list.push({price:1,selected:false})
}
},
computed:{
sum(){
return this.list.reduce((s,x)=>x.selected?+x.price+s:s,0)
}
})
複製代碼
問題少年:咱們項目不用vue
頓時炸開了:臥槽,竟然原生。不用vue你怎麼寫?要不你把這個模塊改vue的……
問題少年,咱們項目這個模塊就用面向對象,工具庫偶爾用用lodash
又炸開了:lodash?什麼鬼?表示看不懂。本菜雞繼續潛水
那咱們繼續,而後問題少年就問了一下vue的問題,接着就潛了
因而能夠猜到問題少年想寫的代碼:
html:
<ul id="app"></ul>
複製代碼
js:
app.addEventListener('change',(function(){
var lastpick = 0
return function (e) {//相信不少人是每次change後,用循環一個個加起來的
lastpick += e.target.checked?+e.target.value:-e.target.value
res.innerHTML = lastpick
}
})())
var list = [{price:1,selected:false},{price:2,selected:false},{price:3,selected:false}]
var append = ''
for(var i = 0;i<list.length;i++){
append += '<li><input type="checkbox" value='+list[i].price+'>'+list[i].price+'</li>'
}
app.innerHTML = append
複製代碼
問題少年:是我的中心來的,數據很少,並且用戶通常都會一頁頁去瀏覽所有數據的,由於這些消費數據必須所有看一遍才能瞭解到狀況
路人甲:拿到所有數據後,根據每頁數據和第幾頁for循環取出元素並插入相應的html,每次切換也遍歷一次
路人乙:臥槽,這後端吃屎的吧,竟然不分頁
路人丙:固然是按需加載啊,你不該該一下加載徹底部
路人丁:用jQuery分頁插件啊
因而勤勞熱心愛解答問題的羣友立刻寫出了答案:
html:
每頁數據<select id="app">
<option>2</option>
<option>3</option>
<option>4</option>
</select>
<div>
<button onclick="pre()">上一頁</button>
<button onclick="next()">下一頁</button>
<p>當前是<span id="current"></span>頁</p>
<table>
<tbody id="content">
</tbody>
</table>
</div>
複製代碼
js:
var data = [1,2,3,4,5,6,7,8,9,10,11,12,13]//後端拿到的數據
var currentIndex = 1
var per = 2
app.onchange = function (e) {
per = e.target.value
update(1)//更改每頁數據時候返回第一頁
}
function pre() {
currentIndex = currentIndex > 1?currentIndex-1:currentIndex
update(currentIndex,per)
}
function next(){
currentIndex = currentIndex < Math.floor(data.length/per)+1?currentIndex+1:currentIndex
update(currentIndex,per)
}
window.onload = function () {
update()
}
function update(currentPage, perPage){
var currentPage = currentPage ||currentIndex
var perPage = perPage || per
var ctx = ''
for(var i = perPage*(currentPage-1);i<perPage*currentPage;i++){
if(data[i]!==undefined) ctx += '<tr><td>'+data[i]+'</td></tr>'
}
content.innerHTML = ctx
current.innerHTML = currentPage
}
複製代碼
固然,後端不幫忙分頁的狀況下,一般是數據量是不大的,上面的作法也是沒什麼問題。可是,在這個場景下,用戶須要瀏覽器全部的數據,因此不存在按需分頁。咱們不該該在用戶須要的時候才循環的,這樣子對於用戶,時間複雜度是n。若是是先分頁再直接拿出來,複雜度就是1。這個場景下,他是所有都瀏覽的,因此咱們先分頁再取數據。 改進的js:
var data = [1,2,3,4,5,6,7,8,9,10,11,12,13]
var currentIndex = 1
var per = 2
var list = []
app.onchange = function (e) {
per = +e.target.value
getdatalist(per)
update(1)
}
function pre() {
console.time('pre')
currentIndex = currentIndex>1?currentIndex-1:currentIndex
update(currentIndex, per)
console.timeEnd('pre')
}
function next(){
console.time('next')
currentIndex = currentIndex < Math.floor(data.length/per)+1?currentIndex+1:currentIndex
update(currentIndex, per)
console.timeEnd('next')
}
window.onload = function () {
getdatalist(2)
update()
}
function getdatalist (per) {
var per = per || 2
list = []
var pages = ~~(data.length/per)+1
for(var i = 0;i<pages;i++){
var temp = ''
data.slice(i*per,i*per+per).forEach(function(item){
temp += '<tr><td>'+item+'</td></tr>'
})
list.push(temp)
}
}
function update(currentPage, perPage){
currentIndex = currentPage || 1
var perPage = perPage || per
content.innerHTML = list[currentIndex-1]
current.innerHTML = currentIndex
}
複製代碼
時間測試:
用戶切換頁面的時候時間消耗
按需循環:
提早分頁:
隨着分頁愈來愈多,提早分頁在切換的時間上的優點愈來愈大。固然,正常的狀況下用戶通常都不會把所有數據都瀏覽完的,因此通常也是用按需分頁更好。
問題少年:隨機數字分佈得比較均勻(可是亂序),好比三、二、一、四、五、7,而不是五、一、六、七、8
路人甲:一個個循環,再判斷
let arr =[12,40,60,80,62,13,58,87]
let obj = {}
arr.map(item=>{
if(!obj[item]){
let val = 100-item
obj[item] = 1
if(arr.includes(val)){
obj[val] = 1
console.log(item,val)
}
}
})
複製代碼
因而,立刻有人喊666了。但是,這ES6用得有點浪費了。問題少年貌似不知足,說:咱們的數據量可能有一點大。
因而,有一個雙指針的版本:
function f(arr,res){
var l = arr.length
var p = ~~(l/2)
var i = p - 1
var j = p
var ishasresult = false
arr = arr.sort((a,b)=>a-b)
while(i >= 0 && j < l){
if(arr[i] + arr[j] > res){
i --
}else if(arr[i] + arr[j] < res){
j ++
}else{
ishasresult = true
break
}
}
return ishasresult
}
複製代碼
假設排序是快排,總的時間複雜度就是nlogn+logn
其實,用ES6的話,這樣子更快:
var myset = new Set(arr);
arr.find(function(value, index, arr) {
return myset.has(100 - value);
})
複製代碼
時間測試:
無腦ES6循環:2.419189453125ms 2.35595703125ms 1.330078125ms
雙指針: 0.0400390625ms 0.0283203125ms 0.0390625ms
簡化ES6:0.1240234375ms 0.10986328125ms 0.092041015625ms
路人甲:unshift,畢竟它是專門在頭部添加的,concat是鏈接數組的,算法確定比unshift複雜,es6的…算是淘汰了concat了吧
測試結果:
很明顯的,unshift和...是一個個遍歷,concat就是直接連起來複雜度是1。可是仍是須要看實際場景的,沒有絕對的淘汰、取代的這種說法。
是否是道出了一些熟悉的經歷?循環啊、遍歷,再xxx、用vue啊、用jQuery的xx、用xx插件。對於問問題的人,要先表示清楚需求,儘可能講詳細一點,而不是隨便截個圖就問,能百度能靠文檔的,也沒有必要問。若是是有意義的問題,那麼你們就得好好思考,瞭解人家的應用場景,而不是無腦循環,也不是直接拋一個xx插件、xx.js給人家,由於人家也懂的,只是想要一個更好的答案或者不是一個無腦的答案。固然,純小白的話就算了。
若是是大牛,也許朋友圈子裏面沒有這種事情,也沒有進這種羣。我也是學了半年的菜鳥,不少應用場景的經驗不足。可是有的人,工做了幾年還寫出一些無腦的代碼,代碼中又暴露了各類細節沒怎麼處理。不是說我以爲他們不如我,我只是感受,這可能就是有一些人工做10年1年經驗的緣由。
另外,面試的時候,是否是常常被問閉包拿來幹什麼的,上面例子有幾個經典的閉包應用,因此面試的時候不要只是說出閉包是什麼、閉包會內存泄漏,也要知道,閉包用於幹什麼,無非就是緩存、柯里化、單例模式、模塊化,並且能保護內部變量。那麼,也問一下本身,究竟有沒有用過閉包來幹一些有意義的事情,有沒有說過 ‘平白無故搞個IIFE有什麼用’ 這種話?你的ES6僅僅是否是拿來簡寫或者追求視覺上的代碼簡短來裝逼的呢,有沒有把proxy玩得飛起,解構賦值用得多嗎,有沒有知道不能捕獲異步的try能夠靠async+await捕捉異步,set用過最多的是否是去重,經常使用的除了let、const、promise、async-await、...、set、反引號+字符串模板以外是否是沒有其餘的了?
轉載時請註明出處,來自lhyt的掘金