前端面試題(偏應用)整理

前情提要: 這篇是偏向應用類的題目,關於涉及到的知識點,配合個人上一篇JavaScript知識點整理實用更佳喔~javascript

HTML+CSS

佈局

假定高度已知,請寫出三欄佈局,其中左欄、右欄寬度各爲300px,中間自適應

  • 浮動
  • 絕對定位

中間元素 position:absolute;left:300px;right:300phtml

  • 彈性盒子flex

容器 display:flex; 中間元素 flex:1;java

  • 表格佈局table

容器 display:table; 中間元素 display: table-cell;node

  • 網格佈局grid

容器 display:grid;grid-template-rows:300px;// 行高 中間元素 grid-template-columns:300px auto 300px;android

五種方案的優缺點git

  • 浮動後脫離文檔流,需清除浮動,兼容性較好
  • 絕對定位會脫離文檔流且子元素也會脫離文檔流,可以使用性較差
  • flex佈局較完美
  • 表格佈局兼容性好,但使用較麻煩且一行中全部表格等高不夠靈活
  • 網格佈局新技術,有兼容性問題 高度未知後,這幾種方案還有哪些可以使用 flex佈局和表格佈局

水平垂直居中的幾種實現方式

  • flex,div內子元素居中
div{
    display: flex;
    justify-content: center; // 水平居中
    align-items: center; // 垂直居中
}
複製代碼
  • CSS3中的transform屬性
div{
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%,-50%);
}
複製代碼
  • 絕對定位和margin-left/margin-top(僅適用寬高固定)
div{
    position: absolute;
    top: 50%;
    left: 50%;
    margin-left: -0.5*寬度;
    margin-top: -0.5*高度;
}
複製代碼
  • 絕對定位和margin:auto(僅適用寬高固定)
div{
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
}
複製代碼

居中爲何要用transform(爲何不用margin-left/margin-top)

若是使用margin-left/margin-top須要固定元素寬/高,而transform不須要,100%就是元素自身的寬高。github

flex佈局

display: flex;
// 主軸方向
flex-direction: row|row-reverse|column|column-reverse;
// 換行:wrap 不換行:nowrap
flex-wrap:nowrap|wrap|wrap-reverse;
// 主軸對齊方式
justify-content:flex-start|flex-end|center|space-between|space-around;
// 副軸對齊方式
align-items:flex-start|flex-end|center|baseline|stretch;
// 多根軸線對齊方式
align-content:flex-start|flex-end|center|space-between|space-around|stretch;
複製代碼

網格佈局grid

// display: grid;
// grid-template-rows // 定義行軌道
// grid-template-columns // 定義列軌道
// grid-column-gap和grid-row-gap定義網格間隙
// 實現上下網格高度爲50px,100px,中間自適應
grid{
    display:grid;
    grid-template-columns:50px auto 100px;
}
//實現第一列,第二列,第三列寬度爲90px,50px,150px
grid{
    display:grid;
    grid-template-rows:90px 50px 150px;
}
// 實現項目1佔1/4,項目2佔1/4寬度,項目3佔1/2寬度
grid{
    display: grid;
    grid-template-rows: 1fr 1fr 2fr;
}
複製代碼

position

position有哪些值?有什麼做用

  • static:默認值,不脫離文檔流
  • relative:相對定位,相對自身原來的位置定位,不會脫離文檔流
  • absolute:絕對定位,會脫離文檔流,若是有設置爲relative的父容器則相對父容器定位,若是沒有就相對body定位
  • fixed:固定定位,相對瀏覽器瀏覽器定位,會脫離文檔流
  • sticky:粘性定位,基於用戶滾動的位置,正常的時候像relative,當頁面滾動超出目標區域時,就像fixed(IE15以前不支持)能夠用來作吸頂效果
  • inherit:繼承父級的position屬性
  • initial:設置該屬性爲默認值

盒模型

每一個HTML元素均可以叫盒模型,盒模型包括margin,border,padding,content。web

標準盒模型和IE盒模型的區別

  • 標準盒模型內容大小爲content的大小 box-sizing:content-box;
  • IE盒模型內容大小爲content+padding+border的大小box-sizing:border-box 獲取盒模型寬高
  • 獲取內聯元素的寬高,但樣式通常不之內聯方式寫,因此不適用大多數開發 dom.style.width/height
  • 只適用於在IE瀏覽器獲取元素寬高 dom.currentStyle.width/height
  • 獲取元素的寬高,這個方法兼容性較好,支持Firefox,chrome window.getComputedStyle(dom).width/height
  • 根據元素在視窗中的絕對位置來獲取寬高 dom.getBoundingClientRect().width/height
  • 最經常使用,兼容性最好 dom.offsetWidth/offsetHeight

行內元素和塊元素

BFC

BFC塊級格式化上下文,是一種邊距解決方案算法

實例題(根據盒模型解釋邊距重疊)

兩個box都設置了邊距,垂直方向上兩個box的邊距會重疊,以絕對值大的爲最終結果顯示chrome

BFC的原理

BFC元素不會與float的元素重疊,BFC上下的邊距不會重疊,BFC是一個獨立的容器和外面的容器互不干擾,計算BFC高度的時候浮動子元素也會參與運算

如何建立BFC

  • 根元素
  • float屬性不爲none
  • position:absolute/fixed;
  • display:inline-block,table-cell,table-caption,flex,inline-flex;
  • overflow不爲visiable,爲hidden/auto

BFC的使用場景

  • 自適應兩欄佈局
  • 清除內部浮動
  • 防止垂直margin重疊

animation

使用CSS寫一個幻燈片效果

考察點:animation

<style>
.ani{
    width:100px;
    heigth:100px;
    background-size:cover;
    background-position:center;
    animation: loop 20s infinite;
    -webkit-animation: loop 20s infinite;
}
@keyframes "loop"{
    0%{background-image: url('1.jpg')}
    25%{background-image: url('2.jpg')}
    50%{background-image: url('3.jpg')}
    75%{background-image: url('4.jpg')}
    100%{background-image: url('5.jpg')}
}
@-webkit-keyframes "loop"{
    0%{background-image: url('1.jpg')}
    25%{background-image: url('2.jpg')}
    50%{background-image: url('3.jpg')}
    75%{background-image: url('4.jpg')}
    100%{background-image: url('5.jpg')}
}
</style>
<div class="ani"></div>
複製代碼

僞類僞元素

寫一個表格,使表格奇數行爲紅色背景,偶數行爲黃色背景,鼠標移過去爲橘色背景

<style>
	#table tr:nth-child(odd){
		background: red;
	}
	#table tr:nth-child(even){
		background: yellow;
	}
	#table tr:hover{
		background: orange
	}
</style>
<table id="table">
	<tr><td>1</td></tr>
	<tr><td>2</td></tr>
	<tr><td>3</td></tr>
	<tr><td>4</td></tr>
	<tr><td>5</td></tr>
</table>
複製代碼

兼容性問題

移動端適配1px的問題

  • 僞類+transform實現
.scale-1px{
    position:relative;
    border:none;
}
// 下邊框
.scale-1px:after{
    content:'';
    position:absolute;
    left:0;
    bottom:0;
    background:#000;
    width:100%;
    height:1px;
    -webkit-tranform:scaleY(0.5);
    transform:scaleY(0.5);
    -webkit-transform-origin:0 0;
    transform-origin:0 0;
}
複製代碼

position:fixed;在android下無效怎麼處理?

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"/>

DOM

選擇器(改變頁面內容)

獲取全部的checkbox

<input type="checkbox" />
<script type="text/javascript">
var arr=[]''
var inputs = document.getElementsByName('input');
for(var i=0; i<inputs.length;i++){
    if(inputs[i].type="checkbox"){
        arr.push(inputs[i])
    }
}
</script>
複製代碼

找到html中id相同的元素

使用document.querySelectorAll('#id')

已知id的input輸入框,如何獲取輸入框輸入值?

var inp = document.getElementById('id');
console.log(inp.value)
複製代碼

設置已知id的div的html內容爲xxxx,字體顏色設爲黑色

var ele=document.getElementById('id');
ele.innerHTML="xxxx"
ele.style.color="#000"
複製代碼

遍歷DOM樹

<div class="root">
  <div class="container">
    <section class="sidebar">
      <ul class="menu"></ul>
    </section>
    <section class="main">
      <article id="post"></article>
      <p class="copyright"></p>
    </section>
  </div>
</div>
<script type="text/javascript">
var node = document.getElementsByClassName('root')[0];
var arr=[showNode(node)];
function mapNode(node){
  var nodeList=node.children;
  // console.log(node,nodeList)
  for(var i=0;i<nodeList.length;i++){
    var childNode=nodeList[i];
    console.log(childNode)
    arr.push(showNode(childNode))
    if(childNode.children.length>0){
      mapNode(childNode)
    }
  }
  return arr;
}
function showNode(node){
  if(node.className){
    return node.nodeName+" ."+node.className
  }else{
    return node.nodeName+" #"+node.id;
  }
}
</script>
複製代碼

事件

動態根據下拉列表的選項變化,更新圖片顯示

<select name="changeImg" id="select">
    <option value="1.jpg">1</option>
    <option value="2.jpg">2</option>
    <option value="3.jpg">3</option>
</select>
<script>
var select=document.getElementById('select')
var img=document.getElementById('img')
img.src=select.value;
select.onchange=function(){
    img.src=select.value;
}
</script>
複製代碼

給按鈕添加事件,確認用戶是否退出當前頁面,確認以後關閉窗口

考察點:window方法

<button onclick="closeWin()">關閉</button>
<script>
function closeWin(){
    if(confirm('是否退出當前頁面?'){
        window.close()
    }
}
</script>
複製代碼

現有一個效果,有顯示用戶頭像,用戶暱稱,用戶其餘信息;當用戶鼠標移到頭像上,會彈出用戶的全部信息;如何實現此功能?

考察點:事件類型 鼠標的移入移出: mouseover和mouseout(會冒泡);mouseenter和mouseleave(不冒泡)

編寫通用的事件監聽函數

考察:對事件的瞭解

var EventUtil={
    addEvent:function(element,type,handler){
        if(element.addEventListener){
            element.addEventListener(type,handler)
        }else if(element.attachEvent){
            element.attachEvent('on'+type,handler)
        }else{
            element['on'+type]=handler
        }
    },
    removeEvent:function(element,type,handler){
        if(element.removeEventListener){
            element.removeEventListener(type,handler)
        }else if(element.detachEvent){
            element.detachEvent('on'+type,handler)
        }else{
            element['on'+type]=null
        }
    },
    getEvent:function(event){
        return event||window.event
    },
    getTarget:function(event){
        return event.target||event.srcElement
    },
    preventDefault:function(event){
        if(event.preventDefault){
            event.preventDefault()
        }else{
            event.returnValue=false
        }
    },
    stopPropagation:function(event){
        if(event.stopPropagation){
            event.stopPropagation()
        }else{
            event.cancelBubble=true
        }
    }
}
複製代碼

設計基於觀察者模式的事件綁定機制

觀察者模式簡單說就是當一個對象被修改時,會自動通知它的依賴對象,發佈-訂閱模式。

var Event={
    // 經過on接口監聽事件eventName
    // on至關於訂閱
    on: function(eventName, handler){
        if(!this.handler){
            this.handler={}
        }
        if(!this.handler[eventName]){
            this.handler[eventName]=[]
        }
        this.handler[eventName].push(handler)
    },
    // 觸發事件eventName
    // emit至關於發佈
    emit: function(eventName){
        // 若存在eventName,則調用全部事件處理函數
        if(this.handler[eventName]){
            for(var i=0;i<this.handler[eventName].length;i++){
                this.handler[eventName][i](arguments[1])
            }
        }
    }
}
Event.on('test', function(a){
    console.log(a)
})
Event.emit('test',78) //78
Event.on("test", function(a){
    console.log("個人數字是"+a)
});
Event.emit("test", 88);
// 88
//個人數字是88
複製代碼

存儲

請實現tip組件功能,描述:1.用戶第一次進來顯示,同一天訪問該頁面不顯示 2.用戶點擊我知道了而後訪問此頁面再也不顯示tip?

<div id="tip">提示</div>
<div id="know">我知道了</div>
<script type="text/javascript">
var tip=document.getElementById('tip');
var storage=window.localStorage;
// 存儲的日期
var date=storage.getItem('date');
// 用戶是否點擊過我知道了
var ifKnow=storage.getItem('know');
// 今天日期
var today=new Date();
today=today.getFullYear()+'-'+today.getMonth()+'-'+today.getDate();
// 點擊我知道了再也不顯示
var know=document.getElementById('know');
know.onclick=function(){
  storage.setItem('know', true)
  tip.style.display="none";
}
// 若是點擊過我知道了,則不顯示
if(ifKnow){
  tip.style.display="none";
}else{
  if(date!=null){
    if(date==today){
      // 今日已登陸過,不顯示
      tip.style.display="none"
    }else{
      // 今日還沒有登陸過
      storage.setItem('date',today)
    }
  }else{
    // 第一次訪問網站,尚未存儲日期
    storage.setItem('date', today)
  }
}
</script>
複製代碼

ECMAScript

數據類型和變量提高

看代碼輸出結果,解釋緣由

var a;
alert(typeof a);    
alert(b);          
b=10;
alert(typeof b);    
複製代碼

a使用了var聲明變量未賦值因此爲undefined,b因爲未聲明將報錯。 若b=10改成var b=0;則返回undefined由於var會提高聲明。

看代碼輸出結果,解釋緣由

var foo=1;
function test(){
    console.log(foo); 
    var foo=2;
    console.log(foo); 
}
test();
複製代碼

緣由:函數內部var聲明會提高到函數內部頂部,因此就至關於

var foo=1;
function test(){
    var foo;
    console.log(foo);
    foo=2;
    console.log(foo)
}
複製代碼

回答輸出結果

if(!("a" in window)){
    var a=1
}
console.log(a) 
複製代碼

由於var存在聲明提高,因此"a" in window爲true,就不執行if語句中的賦值了,因而爲undefined

回答輸出結果

var foo=1;
function bar(){
    if(!foo){
        var foo=10
    }
    console.log(foo)
}
bar(); // 10
複製代碼

var存在聲明提高,因此if(!foo)中的foo爲undefined,會被執行,結果爲10

回答輸出結果

var a=10,b=11,c=12;
function test(a){
    a=1;
    var b=2;
    c=3;
}
test(14);
console.log(a); // 10
console.log(b); // 11
console.log(c); // 3
複製代碼

我的認爲,test(a)傳入的參數a,會被解析成var a=14,因此a也是局部變量

回答輸出結果

function fn(){
    f=ff()
    return f;
    function ff(){
        return 'f' in window;
    }
    var f;
}
console.log(fn()); 
複製代碼

函數會被整個提高到最上方,因此ff()中會執行,但因爲函數做用域,window下沒有f屬性,返回false;

回答輸出結果

console.log(a); 
var a=13;
function fn(){
    console.log(a); 
    var a=1;
}
fn(); 
console.log(a); 
複製代碼

輸出undefined,undefined,13。緣由:js引擎會先把var聲明提到頂部,而後再把函數聲明整個函數提到var的前面,以上代碼等於

function fn(){
    var a;
    console.log(a);
    a=1;
}
var a;
console.log(a);
a=13;
fn();
console.log(a)
複製代碼

回答輸出結果

console.log(a); 
var a=13;
function fn(){
    console.log(a); 
    a=1;
}
fn()
console.log(a) 
複製代碼

輸出undefined,13,1。由於fn中的a=1沒有var,因此不存在變量提高,因此fn()中console.log(a)能夠訪問到外部的,等fn()以後,a=1沒有var,不是全局變量改變了原先a的值,因此最後一個輸出1

console.log(a); 
a=13;
function fn(){
    console.log(a);
    a=1;
}
fn()
console.log(a)
複製代碼

報錯,13,1

引用類型

回答代碼輸出結果,解釋緣由

var a=new Object();
a.value=1;
b=a;
b.value=2
console.log(a.value)
複製代碼

對象參數是按引用傳遞的

var a=[1];
var b=a;
b=[2];
console.log(a);
複製代碼

b=[2]指向了其餘的地址,不是原引用了

回答代碼輸出結果,解釋緣由

function changeObj(o){
    o.name="first";
    o = new Object();
    o.name="second";
}
var obj=new Object();
changeObj(obj);
console.log(obj.name);
複製代碼

o=new Object()切斷了原先的引用,此時obj.name還是指向原來的,新的就只是一個另外的對象

回答代碼輸出結果

var a=b=c=[1,2,3,4];
b=9;
a[1]=0;
console.log(b[0]) 
a=[1,2,3,4]
c=[1,2,3,4]
a[0]=b;
console.log(c);
console.log(a);
複製代碼

b=9時已經被切斷引用了,a,c也是被切斷了,a[0]=b=9,因此a=[9,2,3,4]

實現一個函數clone,能夠對JavaScript中的5種主要數據類型(Number,String,Object,Array,Boolean)進行值複製?

function clone(obj){
    var o;
    switch (typeof obj){
        case "undefined":
            break;
        case "string":
            o=obj+"";
            break;
        case "number":
            o=obj-0;
            break;
        case "boolean":
            o=obj;
            break;
        case "object":
            if(obj===null){
                o=null
            }else{
                if(Object.prototype.toString.call(obj)==="[object Array]"){
                    o=[];
                    for(var i=0;i<obj.length;i++){
                        o.push(clone(obj[i]))
                    }
                }else{
                    o={};
                    for(var k in obj){
                        o[k]=clone(obj[k])
                    }
                }
            }
            break;
        default:
            o=obj;
            break;
    }
    return o;
}
複製代碼

運算符

看代碼輸出結果,解釋緣由

function show(){
    var b=1;
    a=++b;
}
show();
alert(a);
複製代碼

a爲2,++b就是b=b+1而後返回b,b++就是先返回b,而後b=b+1

字符串

已知有字符串"get-element-by-id",將其轉換爲getElementById

var str='get-element-by-id';
function formatString(str){
  var arr=str.split('-');
  var newStr=arr[0]
  for(var i=1;i<arr.length;i++){
    var first=arr[i].slice(0,1).toUpperCase();
    var other=arr[i].slice(1)
    newStr=newStr+first+other
  }
  return newStr
}
formatString(str)
複製代碼

清除字符串先後的空格(兼容全部瀏覽器)

function myTrim(str){
  if(String.prototype.trim){
    return str.trim()
  }else{
    return str.replace(/^\s+/, "").replace(/\s+$/,"")
  }
}
console.log(myTrim(" hello "))
複製代碼

統計一個字符串中出現次數最多的字符和出現次數

function test(str){
  var obj={};
  // 先將出現過的字符和次數記錄下來
  for(var i=0; i<str.length;i++){
    var key = str[i]
    if(!obj[key]){
      obj[key]=1
    }else{
      obj[key]++;
    }
  }
  var max=0;
  var idx=0;
  // idx爲字符,max爲出現次數
  for(var i in obj){
    if(obj[i]>max){
      max=obj[i]
      idx=i;
    }
  }
  console.log(idx,max)
}
var str="abcdefgaddda";
test(str);
複製代碼

刪除與某個字符相鄰且相同的字符,好比fdaffdaaaklfjklja轉換成fdafdaklfjklja

function test(str){
  var newStr="";
  for(var i=0;i<str.length;i++){
    if(str.indexOf(str[i])!==i){
      newStr=newStr+""
    }else{
      newStr=newStr+str[i]
    }
  }
  return newStr;
}
複製代碼

求一個字符串的字節長度

假設中文字符佔兩個字節,英文字符佔一個字節

function getByteLen(str){
  var reg=/[\u4e00-\u9fa5]/;
  var len=0;
  for(var i=0;i<str.length;i++){
    if(reg.test(str.charAt(i))){
      len+=2
    }else{
      len++
    }
  }
  return len;
}
複製代碼

將url的各個參數提取出來,按key-value形式返回一個對象

var url='http://test.com?a=1&b=2&cd=344'
function formatUrl(url){
  var urlArr=url.split('?');
  url=urlArr[1];
  var arr=url.split('&')
  var obj={}
  for(var i=0;i<arr.length;i++){
    var res=arr[i].split('=');
    var key=res[0];
    var val=res[1];
    obj[key]=val;
  }
  return obj;
}
formatUrl(url)
複製代碼

正則

如何將浮點數左邊的數每三位添加一個逗號,如12000000.11轉換爲12,000,000.11?

function formatNum(num){
  var newStr="";
  var count=0; 
  var str=num.toString();
  for(var i=str.indexOf('.')-1;i>=0;i--){
    if(count%3==0 && count!=0){
      newStr=str.charAt(i)+','+newStr;
    }else{
      newStr=str.charAt(i)+newStr;
    }
    count++;
  }
  str=newStr+str.substr((str).indexOf('.'));
  return str;
}
formatNum(123456566.222)
複製代碼

匹配郵箱和手機號

function phone(str){
  var reg=/^1[3|4|5|7|8|9]\d{9}$/;
  if(reg.test(str)){
    console.log('yes')
  }else{
    console.log('no')
  }
}
function email(str){
  var reg=/^[0-9a-zA-Z_\.\-]+\@+[0-9a-zA-Z_\.\-]+\.(com|com.cn|edn|hk|cn|net)$/;
  if(reg.test(str)){
    console.log('yes')
  }else{
    console.log('no')
  }
}
複製代碼

去除字符串中HTML標籤

function replace(str){
  var newStr=str.replace(/<\/?\w+>/gi, "")
  console.log(newStr)
}
複製代碼

將<,>,&,"進行轉義

function test(str){
  return str.replace(/[<>"&]/g, function(match){ switch(match){ case "<": return '&lt;' case ">": return "&gt;" case "&": return "&amp;" case "\"":
        return "&quot";
    }
  })
}
複製代碼

將字符串"<tr><td>{$id}</td><td>{$name}</td></tr>"中的{$id}替換成10,${name}替換成chen

function test(str){
  return str.replace(/{\$id}/g,'10').replace(/{\$name}/g, "chen")
}
複製代碼

匹配輸入的字符:第一個必須是字母或下劃線開頭,後面就是字母和數字或者下劃線構成,長度5-20

function testStr(str){
  var reg=/^[a-zA-Z_]+[0-9a-zA-Z_]{5,20}/;
  if(reg.test(str)){
    console.log('yes')
  }else{
    console.log('no')
  }
}
複製代碼

假設有一篇文章var content="....",涉及到一些敏感詞["死了","20","放屁"]等,如何在文章中發現這些敏感詞,並將背景置爲紅色或改變字體顏色標識出來。

<div id="content">
我真的要丟臉死了,我今天在課堂上放屁了並且很大聲,這是我18年來最丟臉的一次。
</div>
<script type="text/javascript">
function testStr(id){
  var node=document.getElementById(id);
  var content=node.innerHTML;
  var reg=/死了|18|放屁/g;
  var res=content.replace(reg, function(match){
    return "<span style='color:red'>"+match+"</span>"
  })
  node.innerHTML=res;
}
testStr('content')
</script>
複製代碼

Math對象

獲取一個長度一致的隨機的字符串

// 獲取隨機兩位整數
var random=Math.random();
random=Math.floor(random*100);

// 獲取隨機三位小數
var random=Math.random()+'00000';
random=random.slice(0,5);
複製代碼

如何實現數組的隨機排序?

// 方法1
function randomArr(arr){
  for(var i=0;i<arr.length;i++){
    var random=Math.floor(Math.random()*arr.length);
    var val=arr[random];
    arr[random]=arr[i];
    arr[i]=val;
  }
  return arr;
}
// 方法2
arr.sort(function(){
    return Math.random-0.5;
})
複製代碼

實現隨機10-100之間的10個數字,存入一個數組並排序

function randomArr(){
  var arr=[]
  for(var i=0;i<5;i++){
    var random=Math.floor(Math.random()*(100-10)+10);
    arr.push(random)
  }
  return arr.sort();
}
複製代碼

數組

實現對數組的倒序

// 方法1
arr.reverse()
// 方法2
function test(arr){
    var newArr=[];
    for(var i=0;i<arr.length;i++){
        newArr.unshift(arr[i])
    }
}
複製代碼

實現數組的降序排列

// 方法1
function test(arr){
    arr.sort(function(x,y){
        return y-x;
    })
}
// 方法2
var arr=[1,2,34,40,56]
function test(arr){
  for(var i=0;i<arr.length-1;i++){
    for(var j=0;j<arr.length-1-i;j++){
      if(arr[j]<arr[j+1]){
        var val=arr[j]
        arr[j]=arr[j+1];
        arr[j+1]=val
      }
    }
  }
  return arr;
}
複製代碼

數組去重

// 方法1 利用indexOf
function test(arr){
  var newArr=[];
  for(var i=0;i<arr.length;i++){
    if(arr.indexOf(arr[i])===i){
      newArr.push(arr[i])
    }
  }
  console.log(newArr)
}
// 方法2 利用indexOf
function test1(arr){
  var newArr=[];
  for(var i=0;i<arr.length;i++){
    if(newArr.indexOf(arr[i])===-1){
      newArr.push(arr[i])
    }
  }
  console.log(newArr)
}
// 方法3 利用splice和indexOf
function test(arr){
  var newArr=[];
  for(var i=0;i<arr.length;i++){
    if(arr.indexOf(arr[i])!==i){
      arr.splice(i,1);
      i--;
    }
  }
  console.log(arr)
}
// 方法4 利用ES6的set
function test1(arr){
  return Array.from(new Set(arr))
}
複製代碼

找到數組arr中重複出現過的元素(若給出多種方式,請分別給出它們的複雜度)

// 方法1
var arr=[5,1,18,4,1,3,4,3];
function test(arr){
  var newArr=[];
  for(var i=0;i<arr.length;i++){
    for(var j=i+1;j<arr.length;j++){
      if(arr[i]===arr[j] && newArr.indexOf(arr[i])==-1){
        newArr.push(arr[i])
      }
    }
  }
  console.log(newArr)
}
複製代碼

不用循環,建立一個長度爲100的數組,而且每一個元素的值等於它的下標

// 方法1
[...new Array(100).keys()]

// 方法2
Array.from(new Array(100),(item,idx)=>idx)
複製代碼

實現一個函數,函數參數是一個數組,返回一個新數組,新數組的每一項是原數組的值的兩倍,如[2,4,6]返回[4,8,12]

function test(arr){
  return arr.map(function(item,index,array){
    return item*2;
  })
}
function test1(arr){
  var newArr=[];
  arr.forEach(function(item,index,array){
    newArr.push(item*2)
  })
  return newArr;
}
function test2(arr){
  for(var i=0;i<arr.length;i++){
    arr[i]=arr[i]*2;
  }
  return arr;
}
複製代碼

閱讀代碼,分析結果

var arr=new Array(1,3,5);
arr[4] = 'z';
arr2=arr.reverse();
arr3=arr.concat(arr2);
console.log(arr3)
複製代碼

["z", undefined, 5, 3, 1, "z", undefined, 5, 3, 1] 由於reverse()會改變原數組

寫一個能遍歷對象和數組的通用forEach函數

function forEach(obj,fn){
    // 判斷是否爲數組
    if(obj instanceof Array){
        obj.forEach(function(item,index){
            fn(index,item)  
        })
    }else{
        for(var key in obj){
            fn(key, obj[key])
        }
    }
}
複製代碼

判斷一個給定10位字符串是否爲合法的日期,例如:'2016-10-31'是一個合法日期則返回true,'2016-10-31'不是一個合法日期,則返回false

function test(str){
  var reg=str.match(/^(\d{1,4})(-|\/)(\d{1,2})\2(\d{1,2})$/);
  if(!reg){
    console.log('日期格式錯誤');
    return;
  }
  var d=str.split('-');
  var year=parseInt(d[0]);
  var month=parseInt(d[1]);
  var date=parseInt(d[2]);
  if(year<=0){
    console.log('日期錯誤')
    return;
  }
  if(month>12||month<=0){
    console.log('日期錯誤')
    return;
  }
  if((year%4===0&&year%100!==0)||year%400===0||year%500==0){
    // 爲閏年
    if(month==2&&(date>29||date<=0)){
      console.log('日期錯誤')
      return;
    }
  }else{
    if(month==2&&(date>28||date<=0)){
      console.log('日期錯誤')
      return;
    }
  }
  if(month==4||month==6||month==9||month==11){
    if(date>30||date<=0){
      console.log('日期錯誤')
      return;
    }
  }else{
    if(date>31||date<=0){
      console.log('日期錯誤')
      return;
    }
  }
  return str;
}
複製代碼

Function

回答輸出結果

// 第二步執行
var test=(function(a){
    // a=1,可是b尚未定義由於函數做用域
    this.a=a;
    return function(b){
        // 第三步執行,接收test(4)的參數,因此b=4,this.=1
        return this.a+b;
    }
}
// 第一步執行
(function(a,b){
    // a=1,b=2
    return a;
}(1,2))
)
console.log(test(4))
複製代碼

返回5

回答代碼輸出結果

function foo(){
    foo.a=function(){
        console.log(1);
    }
    this.a=function(){
        console.log(2)
    }
    a=function(){
        console.log(3)
    }
    var a=function(){
        console.log(4)
    }
}
foo.prototype.a=function(){console.log(5)}
foo.a=function(){console.log(6)}
// 此時foo還沒真正執行,因此只有上面這個屬性a,因此返回6
foo.a();  // 執行了foo()
var obj=new foo();
// new方法調用以後this就是指向obj的,因此obj.a就至關於this.a
obj.a()
// 上面執行了foo()以後,foo.a改變了,因此輸出1
foo.a()
複製代碼

6,2,1

輸出結果

var a=5
function test(){
    a=0;
    console.log(a)
    console.log(this.a);
    var a;
    console.log(a);
}
test();
new test();
複製代碼

第一個test()是直接調用的因此this指向window,第二個new test()中this會被綁定一個空對象上,因此this.a爲undefined

輸出結果:

var myObj={
    foo: 'bar',
    func: function(){
        var self=this;
        console.log(this.foo)
        console.log(self.foo)
        (function(){
            console.log(this.foo)
            console.log(self.foo)
        }())
    }
}
myObj.func()
複製代碼

bar,bar,undefined,bar

myObj.func()因此this綁定在myObj上,而後在func()裏的函數this就會指向func,因此返回undefined。

關於this綁定不太清楚的,能夠看木易楊說的JavaScript深刻之史上最全--5種this綁定全面解析

定義一個log方法,讓它代理console.log的方法

var log=console.log.bind(console)
複製代碼

arguments

寫一個函數能夠計算sum(5,0,-5)輸出0;sum(1,2,3,4)輸出10;

能夠用...或arguments獲取參數

function sum(...arr){
    var sum=0;
    arr.forEach(function(item){
        sum=sum+item;
    })
    return sum;
}
複製代碼

只列舉一種,方法有不少。

閉包

回答代碼輸出結果,解釋緣由

var foo="hello"
(function(){
    var bar="world"
    console.log(foo+bar)
})()
console.log(foo+bar)
複製代碼

"helloworld", 報錯,緣由:函數做用域

輸出結果

var z=10;
function foo(){
    console.log(z);
}
(function(funArg){
    var z=20;
    funArg()
})(foo);
複製代碼

函數自動執行,函數參數是值傳遞,funArg引用外部foo函數,而foo函數做用域沒有z變量,因此找到全局變量z,輸出結果10

輸出結果:

(function(){
    var a=b=3
})();
console.log("a defined?"+(typeof a !== 'undefined'))
console.log("b defined?"+(typeof b !== 'undefined'))
複製代碼

var a=b=3能夠解析出var a=b,b=3;因此b是全局變量,因此typeof b爲number,typeof對於不存在的變量a返回undefined

輸出結果

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);
var b=fun(0).fun(1).fun(2).fun(3);
var c=fun(0).fun(1); c.fun(2); c.fun(3);
複製代碼

這裏是一個閉包的概念,保存變量的值 第一行

var a=fun(0);  // 這裏o是undefined,n=0,會保存在a中
a.fun(1);   // m=1,n=0 => n=1,o=0
a.fun(2);   // m=2,n=0 => n=2,o=0
a.fun(3);   // m=3,n=0 => n=3,o=0
複製代碼

從第二行開始,var b=fun(0).fun(1).fun(2).fun(3),能夠轉化爲

var b=fun(0);       // 在這裏面o是undefined,n=0會保存在b中
var b1=b.fun(1);    // o=undefined,m=1,n=0 => n=1,o=0保存到b1中
var b2=b1.fun(2);   // o=0,m=2,n=1 => n=2,o=1 保存到b2中
var b3=b2.fun(3);   // o=1,m=3,n=2 => n=3,o=2
複製代碼

第三行,轉化爲

var c=fun(0);       // 在這裏面o是undefined,n=0會保存在c中
var c1=c.fun(1);    // o=undefined,m=1,n=0 => n=1,o=0保存到c1中
c1.fun(2);          // o=0,m=2,n=1 => n=2,o=1
c1.fun(3);          // o=0,m=3,n=1 => n=3,o=1
複製代碼

回答輸出結果,若是不正確如何修改

for(var i=0;i<5;i++){
    setTimeout(function(){
        console.log(i)
    },100*i)
}
複製代碼

會輸出5個5

// 方法1
for(let i=0;i<5;i++){
    setTimeout(function(){
        console.log(i)
    },100*i)
}
// 方法2
for(let i=0;i<5;i++){
    (function(i){
        setTimeout(function(){
            console.log(i)
        },100*i)
    })(i)
}
複製代碼

寫一段代碼實現tab的切換

考察點:閉包

<style>
.hidden{
    display: none;
}
</style>
<ul id="tabs">
    <li>1</li>
    <li>2</li>
    <li>3</li>
</ul>
<div id="content">
    <div>第一</div>
    <div>第二</div>
    <div>第三</div>
</div>
<script>
var tabs=document.getElementById('tabs');
var tabList=tabs.children;
var content=document.getElementById('content');
var conList=content.children;
// 方式1
for(var i=0;i<tabList.length;i++){
    tabList[i].index=i;
    tabList[i].onclick=function(){
        for(var j=0;j<conList.length;j++){
            conList[j].style.display="none"
        }
        conList[this.index].style.display="block"
    }
}
// 方式2 用閉包
for(var i=0;i<tabList.length;i++){
    tabList[i].onclick=function(i){
        return function(){
            for(var j=0;j<conList.length;j++){
                conList[j].style.display="none"
            }
            conList[i].style.display="block"
        }
    }(i)
}
</script>
複製代碼

建立10個a標籤點擊時彈出對應的序號

// 1
for(let i=0;i<10;i++){
    var a=document.createElement('a');
    a.innerHTML=i+"<br>";
    a.onclick=function(){
        console.log(i)
    }
    document.body.appendChild(a)
}
// 2
for(var i=0;i<10;i++){
    (function(i){
        var a=document.createElement('a');
        a.innerHTML=i+"<br>";
        a.onclick=function(){
            console.log(i)
        }
        document.body.appendChild(a)
    })(i)
}
複製代碼

異步

輸出結果:

(function(){
    console.log(1);
    setTimeout(function(){
        console.log(2)
    },1000)
    setTimeout(function(){
        console.log(3)
    },0);
    console.log(4)
})()
複製代碼

1,4,3,2 緣由:會先將同步代碼執行完畢,再執行異步代碼

看代碼輸出結果,解釋緣由

var a=6
setTimeout(function(){
    console.log(a);
    a=666
}, 1000)
a=66;
複製代碼

66 緣由:會先執行同步代碼,再執行異步代碼

EventLoop

看代碼輸出結果,解釋緣由

//請寫出輸出內容
async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
	console.log('async2');
}

console.log('script start');

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

async1();

new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});
console.log('script end')
複製代碼
  • JS分爲同步任務和異步任務,同步任務在主線程上,主線程外有一個任務隊列,一旦同步任務執行完畢就會讀取任務隊列將可運行的異步任務添加到執行棧中,開始執行。
  • EventLoop中能夠有一個或多個任務隊列,任務隊列是一系列有序任務task的集合,每一個任務都有一個任務源。不一樣任務源會被分配到不一樣task隊列中,任務源分爲微任務microtask和宏任務macrotask。
  • 宏任務包含script(總體代碼),setTimeout,setInterval,I/O,UI交互時間,postMessage,MessageChannel,setImmediate
  • 微任務包含promise,MutationObserver,process.nextTick
  • EventLoop運行機制 1.執行一個宏任務 2.執行過程若是遇到微任務就添加到微任務的任務隊列中 3.宏任務執行完畢後,當即執行當前微任務隊列中的全部微任務(依次執行) 4.當前宏任務執行完畢,開始檢查渲染,而後GUI線程接管渲染 5.渲染完畢後,JS線程繼續接管,開始下一個宏任務
  • 關於Promise 寫在Promise中的代碼是被當作同步任務當即執行,而then和catch是異步的
  • 關於await await是一個讓出線程的標誌,await後面的代碼會先執行一遍,將await後面的代碼加入microtask,而後就會跳出整個async函數來執行後面的代碼
async function a1(){
    console.log('a1 start')
    await a2() // 執行,執行完畢纔會回來執行a1 end
    cosnole.log('a1 end') // 會被註冊爲microtask
}
複製代碼

實現一個簡易版Promise

const PENDING='pending'
const RESOLVED='resolved'
const REJECTED='rejected'
function myPromise(fn){
    const that=this;
    that.state=PENDING; // Promise當前狀態
    that.value=null;    // Promise的值
    that.resolvedCallbacks=[]
    that.rejectedCallbacks=[]
    function resolve(value){
        if(value instancof myPromise){
            return value.then(resolve, reject)
        }
        setTimeout(() => {
            if(that.state===PENDING){
                that.state===RESOLVED;
                that.value===value
                // 依次執行成功以後的函數棧
                that.resolvedCallbacks.map(cb => cb(that.value))
            }
        },0)
    }
    function reject(error){
        setTimeout(() => {
            if(that.state===PENDING){
                that.state===REJECTED
                that.value=error;
                // 依次執行失敗以後的函數棧
                that.rejectedCallbacks.map(cb => cb(that.error))
            }
        },0)
    }
    try{
        fn(resolve, reject)
    }catch(e){
        reject(e)
    }
}
myPromise.prototype.then = function(onFulfilled, onjected){
    const that=this;
    onFulfilled=typeof onFulfilled === 'function' ? onFulfilled: v => v;
    onRejected=typeof onRejected === 'function' ? onRejected : r => {throw r}
    if(that.state === PENDING){
        that.resolvedCallbacks.push(onFulfilled)
        that.rejectedCallback.push(onRejected)
    }
    if(that.state === RESOLVED){
        onFulfilled(that.value)
    }
    if(that.state === REJECTED){
        onRejected(that.value)
    }
}
new myPromise((resolve,reject) => {
    setTimeout(() => {
        resolve(1)
    }, 0)
}).then(value => {
    console.log(value)
})
複製代碼

關於優化

關於圖片優化

  • 對於修飾圖片能夠用CSS替代
  • 對於移動端沒必要加載原圖,可使用CDN加載,計算出適配屏幕的寬度,而後請求相應裁剪好的圖片
  • 小圖使用base64格式
  • 將多個圖標文件整合到一張圖片中(雪碧圖)
  • 選擇正確的圖片格式,小圖使用png,圖標可使用svg替代,照片使用jpeg

怎麼控制一次加載一張圖片,加載完後再加載下一張?

監控圖片是否加載完成,加載完再加載下一張

<img src="">
<img src="">
<img src="">
<img src="">
var imgList=document.getElementsByTagName('img');
var urlList=[
  'http://pica.nipic.com/2007-07-15/200771515512480_2.jpg',
  'http://www.zyzw.com/sjms/sjmst/sjmsdg021.jpg',
  'http://pic11.nipic.com/20100803/4038389_093502059852_2.jpg',
  'https://img03.sogoucdn.com/app/a/100520093/ac75323d6b6de243-6a8470ba18ff4002-5f37053dc99bdc42f6f306e09de5e133.jpg'
]
// 加載圖片
function loadImg(i){
  var obj=new Image();
  obj.src=urlList[i];
  obj.onload=function(){
    imgList[i].src=urlList[i];
    i=i+1;
    if(i<imgList.length){
      loadImg(i)
    }
  }
}
loadImg(0);
複製代碼

圖片懶加載和預加載

  • 預加載:提早加載圖片,當用戶須要查看時可直接從本地緩存中渲染
  • 懶加載:主要是爲了減小請求數或延遲請求數,不在可視區域的圖片先不加載。

節流與防抖

  • 防抖:防止手抖,短期內連續點擊只會執行一次
// func是用戶傳入須要防抖的函數
// wait是等待時間
const debounce = (func, wait=50) => {
    // 緩存一個定時器id
    let timer=0
    // 返回的參數就是每次用戶實際調用的防抖函數
    return function(...args){
        // 清空上一次的定時器
        if(timer) clearTimeout(timer)
        timer=setTimeout(() => {
            func.apply(this,args)
        }, wait)
    }
}
function sayHi(){
    console.log('防抖成功')
}
var inp=document.getElementById('inp');
inp.addEventListener('input', debounce(sayHi, 500));
複製代碼
  • 節流:高頻事件觸發,只會在n秒內執行一次,稀釋函數的執行頻率(防抖是將屢次執行變爲最後一次執行,節流是將屢次執行變成每隔一段時間執行)
const throttle(fn, wait=500) => {
    let canRun=true;
    return function(...args){
        if(!canRun) return;
        canRun=false;
        setTimeout(()=>{
            fn.apply(this, args)
            canRun=true
        }, wait)
    }
}
function resizeWindow(e){
    console.log(e.target.innerWidth, e.target.innerHeight)
}
window.addEventListener('resize', throttle(resizeWindow))
複製代碼

算法

已知以下數組:

var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10]; 編寫一個程序將數組扁平化去併除其中重複部分數據,最終獲得一個升序且不重複的數組

var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];
// 方法1
// 扁平化數組
function flat(arr){
  var newArr=[];
  arr.forEach(function(item){
    if(Array.isArray(item)){
      newArr=newArr.concat(flat(item))
    }else{
      newArr.push(item)
    }
  })
  return newArr;
}
// 去重和排序
function  formatArr(arr) {
  var newArr=flat(arr);
  newArr.sort(function(a,b){
    return a-b;
  });
  for(var i=0;i<newArr.length;i++){
    if(newArr.indexOf(newArr[i])!==i){
      newArr.splice(i,1)
    }
  }
  return newArr;
}
formatArr(arr);

// 方法2
function flat(arr){
    arr=arr.toString().split(',') // 扁平化數組
    arr=arr.sort(function(a,b){
        return a-b;
    })
    arr=arr.map(Number) // 逐個將每一個元素轉爲數字類型
    arr=Array.from(new Set(arr)) // 數組去重
    return arr;
}
複製代碼

幾種排序算法的實現

冒泡排序

相鄰的兩個數進行比較,若是前者大於後者就交換位置,這樣一來,第一輪就能夠選出一個最大的數放在最後面;那麼通過n-1輪,就會完成全部數的排序。

function bubbleSort(arr){
    for(var i=0;i<arr.length-1;i++){
        for(var j=0;j<arr.length-1-i;j++){
            if(arr[j]>arr[j+1]){
                var temp=arr[j];
                arr[j]=arr[j+1];
                arr[j+1]=temp;
            }
        }
    }
    return arr;
}
複製代碼

選擇排序

  • 在未排序的數組中找到最小元素,存放到數組起始位置
  • 從剩餘未排序元素中繼續尋找最小元素,放到已排序序列的末尾
  • 重複第二步,直到全部元素排序完畢 好處:不佔用額外的內存空間,壞處:時間複雜度爲O(n^2),因此數據規模越小越好
function selectSort(arr){
  for(var i=0; i<arr.length-1;i++){
    var minIdx=i;
    // 找到未排序序列中的最小值
    for(var j=i+1;j<arr.length;j++){
      if(arr[j]<arr[minIdx]){
        minIdx=j;
      }
    }
    // 將最小值放到已排序序列的後面
    var temp=arr[i];
    arr[i]=arr[minIdx];
    arr[minIdx]=temp;
  }
  return arr;
}
複製代碼

插入排序

  • 插入排序跟整理撲克牌是同樣的,即每次拿到一個數,按大小順序將其插入到有序的數組中。
  • 首先初始化第一個元素爲有序數列,取出一個無序數列中的元素按大小順序插入有序數組中。
function insertSort(arr){
  for(var i=1;i<arr.length;i++){
    // 將要插入的數
    let temp=arr[i];
    // 有序數列
    for(var j=i-1;j>=0;j--){
      // 要插入的數與有序數列一一比較
      if(temp<arr[j]){
        arr[i]=arr[j]
        arr[j]=temp;
      }
    }
  }
  return arr;
}
複製代碼

希爾排序

  • 希爾排序也叫遞減增量排序算法,是插入排序的升級版。
  • 先將無序數組分割成若干子序列,子序列是相隔特定增量的子序列,對每一個子序列進行插入排序
  • 而後再選擇一個更小的增量,再將以前排序後的數組按這個增量分割成多個子序列
  • 不斷選擇更小的增量,直到增量爲1時,再對序列進行一次插入排序,使序列最終成爲有序序列
function shellSort(arr){
  var gap=Math.floor(arr.length/2);
  while(gap>0){
    for(var i=gap; i<arr.length;i++){
      var temp=arr[i];
      for(var j=i; j-gap>=0 && arr[j-gap]>temp; j=j-gap){
        arr[j]=arr[j-gap];
      }
      arr[j]=temp;
    }
    gap=Math.floor(gap/2);
  }
  return arr;
}
複製代碼

歸併排序

先遞歸分解數列再合併數列,將一個數組拆分紅A,B兩個小組,一直拆到每一個小組只有一個元素爲止。 看小冊

快速排序

  • 先取出一個數做爲基準
  • 而後把大於這個數的放到它右邊,小於等於這個數的放到左邊
  • 再對左右區間重複第二步,直到各區間只有一個數
function quickSort(arr){
    if(arr.length<=1){
        return arr;
    }
    // 基準位置(理論上可任意選取)
    var idx=arr.length-1;
    // 基準值
    var num=arr[idx];
    var left=[];
    var right=[];
    for(var i=0;i<arr.length-1;i++){
        if(arr[i]<=num){
            left.push(arr[i])
        }else{
            right.push(arr[i])
        }
    }
    // 會先將左邊的排序好再開始對右邊進行排序
    return quickSort(left).concat([num],quickSort(right));
}
複製代碼
相關文章
相關標籤/搜索