2018春招前端面試: 闖關記(精排精校) | 掘金技術徵文

前言

年底研發組解散失業, 選擇回去學車了,也順利拿到了駕照,最近迴歸大深圳,開始踏上漫漫的找工做之路。javascript

拉勾上吊一百年不匹配, BOSS直聘日夜沒反應。css

題目範圍涵蓋我最近遇到的筆試題和麪談的(CSS/JS/HTTP/Node/Hybrid/Vue/NG/React)html

emm,這裏不列舉哪些公司了, 如果你完整的閱讀一遍,相信你有很多的收穫,謝謝閱讀前端

  • 問題截止日期(2018/3/23),我去面的創業,中大型皆有。
  • 期間死在各類一面/二面/三面/四面皆有之,也拿到部分和推掉部分offer,還有一些後續不清楚的

問題彙總,想到就寫

Q: CSS 有哪些樣式能夠給子元素繼承!

  • 可繼承的:font-size,font-weight,line-height,color,cursor
  • 不可繼承的通常是會改變盒子模型的:display,marginborderpaddingheight

更加全面的能夠到引擎找vue

Q: 行內元素有哪些?塊級元素有哪些? 空(void)元素有那些?

  • 行內: input,span,a,img以及display:inline的元素
  • 塊級: p,div,header,footer,aside,article,ul以及display:block這些
  • void: br,hr

Q: CSS3實現一個扇形

  • 思路跟畫實體三角形一個道理,只不過多了一個圓角屬性
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>扇形</title>
  <style> .sector { width: 0; height: 0; border-width: 50px; border-style: solid; border-color: #f00 transparent transparent; border-radius: 50px; } </style>
</head>
<body>
  <div class="sector"></div>
</body>

</html>

複製代碼

Q: box-sizing經常使用的屬性有哪些? 分別有啥做用?

box-sizing有兩個值:content-box(W3C標準盒模型),border-box(怪異模型),html5

這個css 主要是改變盒子模型大小的計算形式java

可能有人會問padding-box,這個以前只有 Firefox 標準實現了,目前50+的版本已經廢除;node

用一個栗子來距離,一個div的寬高分別100px,border5px,padding5pxwebpack

<style> .test { box-sizing: content-box; border: 5px solid #f00; padding:5px; width: 100px; height: 100px; } </style>
  <div class="test"></div>
<!-- content-box的計算公式會把寬高的定義指向 content,border和 padding 另外計算, 也就是說 content + padding + border = 120px(盒子實際大小) 而border-box的計算公式是總的大小涵蓋這三者, content 會縮小,來讓給另外二者 content(80px) + padding(5*2px) + border(5*2px) = 100px -->

複製代碼

Q: 清除浮動的方式有哪些?比較好的是哪種?

經常使用的通常爲三種.clearfix, clear:both,overflow:hidden;nginx

比較好是 .clearfix,僞元素萬金油版本,後二者有侷限性..等會再扯

.clearfix:after {
      visibility: hidden;
      display: block;
      font-size: 0;
      content: " ";
      clear: both;
      height: 0;
    }


<!--
爲毛沒有 zoom ,_height 這些,IE6,7這類須要 csshack 再也不咱們考慮以內了
.clearfix 還有另一種寫法,
-->

.clearfix:before, .clearfix:after {
	content:"";
	display:table;
}
.clearfix:after{
	clear:both;
	overflow:hidden;
}
.clearfix{
    zoom:1;
}

<!--display:table 是爲了不外邊距margin重疊致使的margin塌陷,
內部元素默認會成爲 table-cell 單元格的形式
-->

複製代碼

clear:both:如果用在同一個容器內相鄰元素上,那是賊好的,有時候在容器外就有些問題了, 好比相鄰容器的包裹層元素塌陷

overflow:hidden:這種如果用在同個容器內,能夠造成 BFC避免浮動形成的元素塌陷

Q: CSS 中transitionanimate有何區別? animate 如何停留在最後一幀!

這種問題見仁見智,個人回答大致是這樣的..待我捋捋.

transition通常用來作過渡的, 沒時間軸的概念, 經過事件觸發(一次),沒中間狀態(只有開始和結束)

animate則是作動效,有時間軸的概念(幀可控),能夠重複觸發和有中間狀態;

過渡的開銷比動效小,前者通常用於交互居多,後者用於活動頁居多;

至於如何讓animate停留在最後一幀也好辦,就它自身參數的一個值就能夠了

animation-fill-mode: forwards;
<!--backwards則停留在首幀,both是輪流-->
複製代碼

讓咱們來舉個栗子,.本身新建一個 html 跑一下,.

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Box-sizing</title>
  <style> .test { box-sizing: border-box; border: 5px solid #f00; padding: 5px; width: 100px; height: 100px; position:absolute; /* 簡寫的姿式排序 @keyframes name : 動畫名 duration 持續時間 timing-function 動畫頻率 delay 延遲多久開始 iteration-count 循環次數 direction 動畫方式,往返仍是正向 fill-mode 通常用來處理停留在某一幀 play-state running 開始,paused 暫停 ,. 更多的參數去查文檔吧..我就不一一列舉了 */ animation: moveChangeColor ease-in 2.5s 1 forwards running; } @keyframes moveChangeColor { from { top:0%; left:5%; background-color:#f00 } to{ top:0%; left:50%; background-color:#ced; } } </style>
</head>

<body>
  <div class="test"></div>
</body>

</html>


複製代碼

Q: 塊級元素水平垂直居中的方法

咱們要考慮兩種狀況,定寬高和不定寬高的;

方案 N 多種,我記得我很早寫過這類的筆記

傳送門:網頁元素居中攻略記

Q: 說說樣式權重的優先級;

!important > 行內樣式 > id > class > tag

樣式權重能夠疊加, 好比 id>class

Q: 對HTML語義化的理解

簡言之:就是不濫用標籤(好比 DIV)/隨意嵌套(好比 span>div) ,

類的命名要合理, 利於瀏覽器解析乃至引擎收錄,也利於團隊協做和維護

Q: JS有幾種數據類型,其中基本數據類型有哪些!

七種數據類型

  • Boolean
  • Null
  • Undefined
  • Number
  • String
  • Symbol (ECMAScript 6 新定義)
  • Object

(ES6以前)其中5種爲基本類型:string,number,boolean,null,undefined,

ES6出來的Symbol也是原始數據類型 ,表示獨一無二的值

Object 爲引用類型(範圍挺大),也包括數組、函數,

Q: nullundefined的差別

大致說一下,想要知其因此然請引擎搜索

相同點:

  • if判斷語句中,值都默認爲 false
  • 大致上二者都是表明,具體看差別

差別:

  • null轉爲數字類型值爲0,而undefined轉爲數字類型爲 NaN(Not a Number)
  • undefined是表明調用一個值而該值卻沒有賦值,這時候默認則爲undefined
  • null是一個很特殊的對象,最爲常見的一個用法就是做爲參數傳入(說明該參數不是對象)
  • 設置爲null的變量或者對象會被內存收集器回收

Q: JS 的DOM 操做(Node節點獲取及增刪查改);

  • 獲取(太多了,有document.getElementById/ClassName/Name/TagName 等,或者 querySelector)
// example

// get Node
var element = document.querySelector('#test');

// 追加
element.appendChild(Node);

// 刪除
element.removeChild(Node);

// 查找
element.nextSibling // 獲取元素以後的兄弟節點 , 會拿到註釋文本,空白符這些
element.nextElementSibling  // 等同, 獲取標籤(不會拿到註釋文本這些)

element.previousSibling // 和上面同理,往前找兄弟節點
element.previousElementSibling

// 改動,好比 屬性這些
element.setAttribute(name, value); // 增長屬性
element.removeAttribute(attrName); //刪除屬性

// 來一個簡易的練習題,隨便一個網頁追加插入一塊DOM(非覆蓋:不能 innerHTML);
/* <div id="test"> <span>Hello, World</span> </div> */

// 以上面的例子爲例
var test = document.createElement('div');  // 建立一個塊級元素
test.setAttribute("id","test"); // 設置其id 屬性
var span = document.createElement('span'); // 建立一個 span
span.innerText = "Hello,world"; // 插入 span 的文本內容
test.appendChild(span); // 組合節點

element.appendChild(test); //追加到某個節點區域


複製代碼

Q:Javascript中,有一個函數,執行時對象查找時,永遠不會去查找原型,這個函數是?

hasOwnProperty,這個更多的是用來區分自身屬性和原型鏈上的屬性。

Q: 給一個 DOM添加捕獲和冒泡的兩種寫法的事件點擊,誰先執行?

分狀況分析:

  • 有拿到節點的,優先捕獲,沒有才往上冒泡尋找
  • 如果經過node.addEventListener('event',callback,bubble or capture); 誰先調用誰先執行

stackoverflow 有相關的探討:

Q: 談談你對ajax 的理解,以及用原生 JS 實現有哪些要點須要注意;

ajax全稱是異步 javascript 和 XML,用來和服務端進行數據交互的,讓無刷新替換頁面數據成了可能;

至於有哪些要要點,來一個簡短的ajax請求

var xhr = new XMLHttpRequest(); // 聲明一個請求對象


xhr.onreadystatechange = function(){
    if(xhr.readyState === 4){  // readyState 4 表明已向服務器發送請求
        if(xhr.status === OK){ // // status 200 表明服務器返回成功
            console.log(xhr.responseText); // 這是返回的文本
        } else{
            console.log("Error: "+ xhr.status); // 鏈接失敗的時候拋出錯誤
        }
    }
}

xhr.open('GET', 'xxxx');

// 如何設置請求頭? xhr.setRequestHeader(header, value);
xhr.setRequestHeader('Content-Type', 'application/json');

xhr.send(null); // get方法 send null(亦或者不傳,則直接是傳遞 header) ,post 的 send 則是傳遞值



複製代碼

更爲詳細的能夠閱讀此處;

Q: JS 實現一個閉包函數,每次調用都自增1;

這裏主要考察了閉包,函數表達式以及 IIFE(當即執行表達式)

var add = (function() {
  // 聲明一變量,因爲下面 return因此變量只會聲明一次
  var count = 0;
  return function() {
    return console.log(count++);
  };
})();

add(); // 0
add(); // 1
add(); // 2

複製代碼

Q: ['1','2','3'].map(parseInt) 輸出什麼,爲何?

['1','2','3'].map(parseInt); // [1,NaN,NaN]

// 刨析

// map有三個參數:數組元素,元素索引,原數組自己
// parseInt有兩個參數,元素自己以及進制
// 理清了這兩個就好辦了,
// ['1','2','3'].map(parseInt); 等於以下
['1','2','3'].map(function(item,index,array){
    return parseInt(item,index); // 是否是一目瞭然
});

// parseInt("1",0); => 1
// parseInt("2",1); => NaN
// parseInt("3",2); => NaN


複製代碼

Q:如何實現瀏覽器內多個標籤頁之間的通訊?

WebSocket、localstorge、cookies均可以。

要考慮瀏覽器無痕模式的話用WebSocket會更好,否則功能基本失效或者報錯。

Q:webSocket如何兼容低瀏覽器?

最多見的就是輪詢XHR

Q: 什麼是window對象? 什麼是document對象?

window對象是指瀏覽器打開的窗口。

document對象是HTML 文檔對象的一個只讀引用,window對象的一個屬性。

Q: 對數組 ['2018-03-05', '2013-06-12','2019-03-12','2018-03-05','2014-02-22'] 去重且排序

我這裏用的是結合 ES6的,代碼量很短

//很好理解, Set 具備值惟一性(但不是全部值,等會我拋出個人另一篇文章)
// 結合,解構,能夠把可迭代(好比 arguments/nodelist 等)的轉爲數組
// sort 裏面傳入 兩個值比較,返回-1和1是由於1表明這個數大排後(相對),-1表明小(相對),0爲相等

let arr = [,new Set(['2018-03-05', '2013-06-12','2019-03-12','2018-03-05','2014-02-22'])].sort(function(a,b){
  return a<b ? -1:1; // 這裏返回的是升序的,降序改下返回值就行了.因此是相對
})

// ["2013-06-12", "2014-02-22", "2018-03-05", "2019-03-12"]

複製代碼

對於數組去重的,有興趣的能夠看下我這篇水文:

Q: 對數組[1,2,3,4,5,'6',7,'8','a','b','z']進行亂序

// 咱們依舊能夠用上面的 sort 的原理實現亂序

let tempArr = [1,2,3,4,5,'6',7,'8','a','b','z'].sort(function(){
  return Math.random() > 0.5 ? -1 : 1;
})

// 由於裏面有隨機數,因此答案沒有標準答案,我這邊跑了一次是輸出這個
//["6", "z", 3, "b", 5, 2, 7, "8", "a", 1, 4]


複製代碼

上面和這道題逗涉及到數組順序的問題,想了解下爲何 a-b,a>b這類能夠更改排序

能夠看看知乎對於這塊的探討: 傳送門:javascript排序return a-b?

Q: 求[1, 10, 11, -1,'-5',12, 13, 14, 15, 2, 3, 4, 7, 8, 9]內最大值與最小值之差

// 來一個很粗糙的版本,只當傳入是數組且能夠隱性轉爲數字的
function MaxMinPlus(arr) {
  // 返回最大值與最小值之差
  return Array.isArray(arr) ? Math.max.apply(Math, arr) - Math.min.apply(Math, arr) : console.log('傳入的不是數組亦或者未能解決的錯誤')
}

// 結果是 20

// 如果要完善的話,要考慮傳入的是非數組,
//傳入字符串的時候要判斷,而後切割爲數組..
// 都要考慮進去代碼量不短

複製代碼

Q: 請給Array實現一個方法,去重後返回重複的字符(新數組)

var testArr = [1,6,8,3,7,9,2,7,2,4,4,3,3,1,5,3];

  Array.prototype.extraChar = function(){
      var cacheExtraChar = []; // 緩存重複出現的字符
      var that = this; // 緩存 this;
      this.map(function(item,index){
          // 怎麼理解這段代碼呢?
          // 就是向前日後查找一遍和從後往前查找一遍,不等就是沒有重複
          // 爲何還要判斷一遍緩存,是過濾緩存數組內屢次寫入
          (that.indexOf(item) !== that.lastIndexOf(item)) && cacheExtraChar.indexOf(item) === -1 ? cacheExtraChar.push(item) : -1;
      });
      return cacheExtraChar;
  }


testArr.extraChar(); // [1, 3, 7, 2, 4]

// 如果還須要排序就再排序下

[1,6,8,3,7,9,2,7,2,4,4,3,3,1,5,3]
.extraChar()
.sort(function(a,b){return a-b}) // [1, 2, 3, 4, 7]

複製代碼

Q: 一個數組中 par中存放了多我的員的信息,每一個人員的信息由 nameage 構成({name:'張三',age:15}).請用 JS 實現年齡從小到大的排序;

var par = [{age:5,name:'張三'},{age:3,name:'李四'},{age:15,name:'王五'},{age:1,name:'隨便'}]

var parSort = par.sort(function(a,b){
    return a.age - b.age;
})
複製代碼

Q: 判斷一個迴文字符串和同字母異序字符串

迴文字符串

就是正序倒序都是同樣的;

同字母異序字符串

字符串都同樣,可是位置可能不必定同樣,好比abcefddceabf=>return true

後者的思路就是用排序把異序扭正

普通版

// 迴文判斷 , 好比用 abcba
var isPalindromes = function(params){
  params = params.toString().toLowerCase()
  return params === params.split('').reverse().join('');
}

// 同字母異序斷定,好比`abcefd`和`dceabf`
var isAnagram = function(str1, str2)  {
  str1 = str1.toString().toLowerCase();
  str2 = str2.toString().toLowerCase();
  return str1.split('').sort().join('') === str2.split('').sort().join('')
}



複製代碼

進階版:多一些特殊字符

如果咱們要去除全部非字母數字的字符,則須要用到正則

// 進階版: isPalindromes('abc_ &b #@a')

var isPalindromes = function(params){
  // 傳入參數先轉爲字符串且所有轉爲小寫,最後去除多餘字符比較
  params = params.toString().toLowerCase().replace(/[\W_\s]/g,'');
  console.log(params)
  return params === params.split('').reverse().join('');
}


// 進階版同字母異序: isAnagram('ab *&cef#d','!d@ce^abf')
var isAnagram = function(str1, str2) {
  str1 = str1.toString().toLowerCase().replace(/[\W_\s]/g,'');
  str2 = str2.toString().toLowerCase().replace(/[\W_\s]/g,'');
  return str1.split('').sort().join('') === str2.split('').sort().join('')
}



複製代碼

Q: JS 實現String.trim()方法;

// 原生是有 trim()方法的.咱們要模擬一個;

String.prototype.emuTrim = function(){
    // 這條正則很好理解,就是把頭部尾部多餘的空格字符去除
    return this.replace(/(^\s*)|(\s*$)/g,'');
}


' fsaf fsdaf f safl lllll '.emuTrim();  //"fsaf fsdaf f safl lllll"

複製代碼

Q: JS 實現函數運行一秒後打印輸出0-9;給定以下代碼

for(var i=0;i<10;i++){
  // TODO
}

複製代碼
  • 解法
// 這道題涉及到做用域
for(var i=0;i<10;i++){
  setTimeout((function(i){
   return function(){
       console.log(i);
   }
  })(i),1000);
}

複製代碼

如果用到 ES6,那簡直不能再簡便了

for(let i=0;i<10;i++){
  setTimeout(function(){
       console.log(i);
  },1000);
}

複製代碼

Q: 實現對一個數組或者對象的淺拷貝和"深度"拷貝

淺拷貝就是把屬於源對象的值都複製一遍到新的對象,不會開闢二者獨立的內存區域;

深度拷貝則是完徹底全兩個獨立的內存區域,互不干擾

  • 淺拷貝
// 這個 ES5的

function shallowClone(sourceObj) {
  // 先判斷傳入的是否爲對象類型
  if (!sourceObj || typeof sourceObj !== 'object') {
    console.log('您傳入的不是對象!!')
  }
  // 判斷傳入的 Obj是類型,而後給予對應的賦值
  var targetObj = sourceObj.constructor === Array ? [] : {};

  // 遍歷全部 key
  for (var keys in sourceObj) {
    // 判斷全部屬於自身原型鏈上的 key,而非繼承(上游 )那些
    if (sourceObj.hasOwnProperty(keys)) {
      // 一一複製過來
      targetObj[keys] = sourceObj[keys];
    }
  }
  return targetObj;
}

 // ES6 能夠用 Object.assign(targeObj, source1,source2,source3) 來實現對象淺拷貝


複製代碼
  • 深度拷貝
// 就是把須要賦值的類型轉爲基本類型(字符串這些)而非引用類型來實現
// JOSN對象中的stringify能夠把一個js對象序列化爲一個JSON字符串,parse能夠把JSON字符串反序列化爲一個js對象

var deepClone = function(sourceObj) {
  if (!sourceObj || typeof sourceObj !== 'object') {
    console.log('您傳入的不是對象!!');
    return;
  }
  // 轉->解析->返回一步到位
  return window.JSON
    ? JSON.parse(JSON.stringify(sourceObj))
    : console.log('您的瀏覽器不支持 JSON API');
};



複製代碼

Q: this對象的理解

簡言之:誰調用指向誰,運行時的上下文肯定,而非定義的時候就肯定;

強行綁定 this的話,能夠用 call,apply,bind,箭頭函數來來改變this的指向

這類的文章太多,自行搜索吧。

Q: 看到你說到 bind,能用 JS簡單的模擬個麼?

Function.prototype.emulateBind =  function (context) {
    var self = this;
    return function () {
        return self.apply(context);
    }

}

複製代碼

這個實現很粗糙,更爲詳細全面,考慮周全的(好比參數的處理什麼的),自行谷歌.

Q:JS 的做用域是什麼?有什麼特別之處麼?

做用域就是有它自身的上下文區域(好比函數內),內部會有變量聲明提高,函數聲明提高這些;

函數聲明提高優於變量聲明提高..

做用域有全局做用域和塊級做用域(局部,好比用 let 或者單純花括號的);

做用域會影響this的指向

坐等補充,我回答的時候,面試大佬只是 嗯..恩,恩,也不知道具體如何

Q: 怎麼解決跨域問題,有哪些方法,

我通常用這三種,cors,nginx反向代理,jsonp

  • jsonp : 單純的 get 一些數據,侷限性很大,就是利用script標籤的src屬性來實現跨域。
  • nginx 反向代理: 主要就是用了nginx.conf內的proxy_pass http://xxx.xxx.xxx,會把全部請求代理到那個域名,有利也有弊吧..
  • cors的話,可控性較強,須要先後端都設置,兼容性 IE10+ ,好比
    • Access-Control-Allow-Origin: foo.example // 子域乃至整個域名或全部域名是否容許訪問
    • Access-Control-Allow-Methods: POST, GET, OPTIONS // 容許那些行爲方法
    • Access-Control-Allow-Headers: X-PINGOTHER, Content-Type // 容許的頭部字段
    • Access-Control-Max-Age: 86400 // 有效期

Q: 對於想攜帶一些鑑權信息跨域如何走起?好比cookie!

須要配置下 header Access-Control-Allow-Credentials:true ,具體用法看下面的nginxdemo

固然cros的配置不只僅這些,還有其餘一些,具體引擎吧,.

如果咱們要用 nginx或者 express 配置cors應該怎麼搞起? 來個簡易版本的

  • nginx
location / {
   # 檢查域名後綴
    add_header Access-Control-Allow-Origin xx.xx.com;
    add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
    add_header Access-Control-Allow-Credentials true;
    add_header Access-Control-Allow-Headers DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type;
    add_header Access-Control-Max-Age 86400;
}

複製代碼
  • express, 固然這貨也有一些別人封裝好的 cors中間件,操做性更強,
let express = require('express');
let app = express();

//設置全部請求的頭部
app.all('*', (req, res, next) =>  {
    res.header("Access-Control-Allow-Origin", "xx.xx.com");
    res.header("Access-Control-Allow-Headers", "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type");
    res.header("Access-Control-Allow-Credentials","true")
    res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
    next();
});

複製代碼

有些還會跟你死磕,除了這些還有其餘姿式麼,我說了一個HTML5的postMessage,

由於真心沒用過,只是之前查閱的時候瞭解了下,只能大致點下

這貨用於iframe 傳遞消息居多, 大致有這麼兩步步

  • window打開一個實例,傳遞一個消息到一個x域名
  • x 域名下監聽message事件,獲取傳遞的消息

這貨的兼容性沒那麼好,並且沒考慮周全下容易遭受 CSRF 攻擊

Q: 對於XSSCSRF 如何防範

這裏就不說概念性的東西了

  • XSS的防範

    • 我能想到的就是轉義<>這些形成代碼直接運行的的標籤..輪詢或者正則替換
      • 而面試官說這種的效率最低下,我回來仔細找了找相關資料好像沒有更優方案,有的留言,
    • 如果有用到 cookie,設置爲http-only,避免客戶端的篡改
  • CSRF的防範通常這幾種

    • 驗證碼,用戶體驗雖然很差,,可是不少場合下能夠防範大多數攻擊
    • 驗證 HTTP Referer 字段,判斷請求來源
    • token加密解密,這種是目前很經常使用的手段了,

任何防範都有代價的,好比驗證碼形成的體驗很差,token濫用形成的性能問題,輪詢替換形成的響應時間等

Q: 描述下cookie,sessionStorage,localStorage的差別..

  • cookie : 大小4KB 左右,跟隨請求(請求頭),會佔用帶寬資源,可是如果用來判斷用戶是否在線這些挺方便
  • sessionStoragelocalStorage大同小異,大小看瀏覽器支持,通常爲5MB,數據只保留在本地,不參與服務端交互.
    • sessionStorage的生存週期只限於會話中,關閉了儲存的數據就沒了.
    • localStorage則保留在本地,沒有人爲清除會一直保留

Q: javascript的原型鏈你怎麼理解?

原型鏈算是 JS 內一種獨有的機制,

全部對象都有一個內置[[proto]]指向建立它的原型對象(prototype)

原型鏈的基本用來實現繼承用的

Q: javascript裏面的繼承怎麼實現,如何避免原型鏈上面的對象共享

我在寫的時候,用了兩種,一個是 ES5和 ES6的方案

  • ES5:寄生組合式繼承:經過借用構造函數來繼承屬性和原型鏈來實現子繼承父。
function ParentClass(name) {
      this.name = name;
    }
    ParentClass.prototype.sayHello = function () {
      console.log("I'm parent!" + this.name);
    }
    function SubClass(name, age) {
      //如果要多個參數能夠用apply 結合 ,解構
      ParentClass.call(this, name);
      this.age = age;
    }
    SubClass.prototype = Object.create(ParentClass.prototype);
    SubClass.prototype.constructor = SubClass;
    SubClass.prototype.sayChildHello = function (name) {
      console.log("I'm child " + this.name)
    }

    let testA = new SubClass('CRPER')

    // Object.create()的polyfill
    /* function pureObject(o){ //定義了一個臨時構造函數 function F() {} //將這個臨時構造函數的原型指向了傳入進來的對象。 F.prototype = obj; //返回這個構造函數的一個實例。該實例擁有obj的全部屬性和方法。 //由於該實例的原型是obj對象。 return new F(); } */

複製代碼
  • ES6: 其實就是ES5的語法糖,不過可讀性很強..
class ParentClass {
      constructor(name) {
        this.name = name;
      }
      sayHello() {
        console.log("I'm parent!" + this.name);
      }
    }

    class SubClass extends ParentClass {
      constructor(name) {
        super(name);
      }
      sayChildHello() {
        console.log("I'm child " + this.name)
      }
      // 從新聲明父類同名方法會覆寫,ES5的話就是直接操做本身的原型鏈上
      sayHello(){
        console.log("override parent method !,I'm sayHello Method")
      }
    }

    let testA = new SubClass('CRPER')

複製代碼

Q: ES6+你熟悉麼,用過哪些特性?

  • 箭頭函數
  • 類及引入導出和繼承( class/import/export/extends)
  • 字符串模板
  • Promise
  • let,const
  • async/await
  • 默認參數/參數或變量解構裝飾器
  • Array.inclueds/String.padStart|String.padEnd/Object.assign

Q: let 和 const 有啥差別?

  • let 會產生塊級做用域,不會形成變量提高,沒法從新聲明(但能夠從新賦值);
  • const
    • 是常量,如果基本數據類型,具備不變性(沒法從新賦值改動)
    • 引用值能夠調整內部值(可能設計的時候沒有考慮周全!

Q: asyncawait的用途?

  • promise 的異步變成同步運行成了可能,await 能夠等到 promise 執行完畢

Q: 箭頭函數的this指向誰?

確定不少小夥伴會說指向局部方法內!!答案是錯誤的,

箭頭函數所改變的並不是把 this 局部化,而是徹底不把 this 綁定到裏面去;

就是 this 是取自外部的上下級做用域(可是又不是常規 function的語法糖)..

由於箭頭函數裏並不支持 var self = this 或者 .bind(this) 這樣的寫法。

Q: 問的時候你用過靜態方法,靜態屬性,私有變量麼?

靜態方法是ES6以後纔有這麼個玩意,有這麼些特色

  • 方法不能給 this引用,能夠給類直接引用
  • 靜態不能夠給實例調用,好比 let a = new ParentClass => a.sayHello() 會拋出異常
  • 父類靜態方法,子類非static方法無法覆蓋父類
  • 靜態方法能夠給子類繼承
  • 靜態屬性能夠繼承也能夠被修改

看下面的代碼..

class ParentClass {
      constructor(name) {
        this.name = name;
      }
      static sayHello() {
        console.log("I'm parent!" + this.name);
      }

      static testFunc(){
        console.log('emm,Parent test static Func')
      }
    }

    class SubClass extends ParentClass {
      constructor(name) {
        super(name);
      }
      sayChildHello() {
        console.log("I'm child " + this.name)
      }
      static sayHello() {
        console.log("override parent method !,I'm sayHello Method")
      }

      static testFunc2() {
        console.log(super.testFunc() + 'fsdafasdf');
      }
    }
    ParentClass.sayHello(); // success print

    let a = new ParentClass('test');
    a.sayHello() // throw error

    SubClass.sayHello(); // 同名 static 能夠繼承且覆蓋

    SubClass.testFunc2(); // 能夠繼承

    let testA = new SubClass('CRPER');

複製代碼

私有變量能夠用WeakMap模擬,也能用語義化的下劃線,亦或者symbol,

因此回來只是找了下相關的資料,發現有一個比較好的模擬方案,就是WeakMap;

WeakMap能夠避免內存泄露,當沒有被值引用的時候會自動給內存寄存器回收了.

const _ = new WeakMap(); // 實例化,value 必須爲對象,有 delete,get,has,set四個方法,看名字都知道了

class TestWeakMap {
  constructor(id, barcode) {
    _.set(this, { id,barcode });
  }
  testFunc() {
    let { id,barcode } = _.get(this); // 獲取對應的值
    return { id,barcode };
  }
}

複製代碼

固然你也能夠用Symbol來實現一個私有變量,這也是一個好法子

Q: 談談你對 Promise 的理解? 和 ajax 有關係麼?

Promiseajax沒有半毛錢直接關係.promise只是爲了解決"回調地獄"而誕生的;

平時結合 ajax是爲了更好的梳理和控制流程,這裏咱們簡單梳理下..

Promise有三種狀態,Pending/resolve()/reject();

一些須要注意的小點,以下

  • Pending 轉爲另外兩種之一的狀態時候,狀態不可在改變..
  • Promisethen爲異步.而(new Promise())構造函數內爲同步
  • Promisecatch不能捕獲任意狀況的錯誤(好比 then 裏面的setTimout內手動拋出一個Error)
  • Promisethen返回Promise.reject()會中斷鏈式調用
  • Promiseresolve如果傳入值而非函數,會發生值穿透的現象
  • Promisecatch仍是then,return的都是一個新的 Promise(在 Promise 沒有被中斷的狀況下)

Promise 還有一些自帶的方法,好比race,all,前者有任一一個解析完畢就返回,後者全部解析完畢返回,

實現一個延時的 promise 函數, 能夠用asyncawait

const delay = (time)=> new Promise((resolve,reject)=>{
  setTimeout(resolve,time)
})


// test

let testRun = async function(){
   console.log(1);
   await delay(2000);
   console.log('我兩秒後才觸發',3)
}

// 1 => Promise = > 3



複製代碼

如下這段代碼的運行結果是什麼?

var test = new Promise((resolve,reject)=>{
   resolve();
});

test
  .then(data => {
    // promise start
    console.log('promise first then : ', data);
    return Promise.resolve(1); // p1
  })
  .then(data => {
    // promise p1
    console.log('get parent(p1) resolve data : ', data);
    return Promise.reject(new Error('哎呀,中斷了,你能奈我何!')); // p2

  })
  .then(data => {
    // promise p2
    console.log('result of p2: ', data);
    return Promise.resolve(3); // p3
  })
  .catch(err => {
    console.log('err: ', err);
    return false;
  });

// promise first then : undefined
// get parent(p1) resolve data : 1
// err: Error: 哎呀,中斷了,你能奈我何!

// 這裏在 then 返回 Promise.reject()的時候已經中斷了鏈式調用.直接給 catch捕獲到



複製代碼

別急,假如你無論有沒有捕獲到錯誤,最後再執行一個回調函數如何實現?

這裏說的就是相似try..catch..finally,給Promise實現一個 finally;

// finally比較好加,按照如今社區的討論,finally的特色以下:
// url : https://www.v2ex.com/t/205715
//1. 不接收任何參數,原來的value或者Error在finally裏是收不到的
//2. 處理後不影響原Promise的狀態,該reject仍是reject,該resolve仍是resolve
//3. 不影響Promise向後傳遞的傳,resolve狀態仍是傳遞原來的value,reject狀態仍是傳遞原來的Error

Promise.prototype.finally = function (callback) {
  let P = this.constructor; // 這裏拿到的是 Promise 的構造函數

  //無論前面的 Promise 是fulfilled仍是rejected,都會執行回調函數callback。
  return this.then(
    value => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};

// 用法很簡單,就是能夠傳入一個回調函數..
// https://developers.google.com/web/updates/2017/10/promise-finally
// 這個 url 中說了 node 及 chrome 的哪些版本已經實現了 finally 及用法
// ES 2018已經把 finally 追加到 promise 的原型鏈中..


複製代碼

Q: 談談你對 TCP 的理解;

Q: TCP 是在哪一個OSI 的哪一個層!通信過程是全雙工仍是半雙工(單工)?

A: 傳輸層,全雙工

Q: TCP的通信的過程是怎麼樣的!

A: 整個過程是三次握手,四次揮手..

Q: 你說的沒錯,說說整個過程如何?

A: 舉個栗子,我把 TCP 比作兩我的用對講機溝通(大白話)..三次握手就是.A1(吼叫方,客戶端)想要呼叫 A2(控制室的某某,服務端)..

A1對着對講機說"over over ,聽到請回答"(第一次,請求應答) ,

A2收到迴應"收到收到,你說"(第二次,確認應答)

A1開始巴拉巴拉個不停而 A2沒拒絕(第三次,通信創建)

而四次揮手則是二者確認互相傾述完畢的過程..

A1說:"控制室,報告完畢了"(第一次揮手)

A2說:"知道了,那麼你廢話說完就好好聽我指揮,.巴拉巴拉.."(第二次揮手)

A1此時等待控制室說完畢,而控制室等迴應(第三次揮手)

等到 A1回饋控制室確認都知道完畢了..(第四次揮手),

以上都是瞎掰,可能有些地方描述不當,笑笑就行了

TCP沒有百分百創建成功的,會形成連接失敗的狀況有不少..

好比長時間沒應答(A1吼了半天沒有反應或者 A2應答了而 A1再也不鳥它)..亦或者丟包(對講機也沒了);

TCP 協議相關的文章網上不少,如果要更加全面的瞭解該協議請自行引擎..

我建議閱讀<<TCP-IP詳解卷1~卷3>>,這個是網絡聖經,很厚,我只看了一丟丟..

Q: TCP 你瞭解了,那麼 OSI 七層協議和五層網絡架構應該知道吧?

對於這類的問題我也只能大致點了下,畢竟不是專攻網絡這塊的,

OSI 七層涵蓋:物理層,數據鏈路層,網絡層,傳輸層,會話層,表示層,應用層;

五層模型就是"會話,表示,應用層"同爲一層;

Q: DNS 的大致的執行流程瞭解麼,屬於哪一個層級?工做在哪一個層級?

DNS 屬於應用層協議, 至於TCP/UDP哪一層上面跑,看狀況 , 大致的執行流程是這樣的;

DNS 默認端口是53,走 UDP

  1. 優先讀取瀏覽器緩存
  2. 其次系統的緩存
  3. 都沒有的狀況下,找本地hosts文件(好比你寫了映射關係優先尋找)
  4. 再沒有的狀況找最近的域名解析服務器
  5. 再沒有則擴大訪問,最終找到根服務器,仍是沒有就失敗了..

DNS 的解析的幾個記錄類型須要瞭解:

  • A: 域名直接到 IP
  • CNAME: 能夠多個域名映射到一個主機,相似在 Github Page就用 CNAME 指向
  • MX: 郵件交換記錄,用的很少,通常搭建郵件服務器纔會用到
  • NS: 解析服務記錄,能夠設置權重,指定誰解析
  • TTL: 就是生存時間(也叫緩存時間),通常的域名解析商都有默認值,也能夠人爲設置
  • TXT: 通常指某個主機名或域名的說明

回來我找下相關的資料,有興趣的能夠深刻了解下,傳送門以下:

Q: HTTP 和 HTTPS 有何差別? 據說過 SPDY 麼?

我只是粗淺的回答了下,

HTTP相對於 HTTPS來講,速度較快且開銷較小(沒有 SSL/TSL) 對接,默認是80端口;

HTTP容易遭受域名劫持,而HTTPS相對來講就較爲安全(加密),默認端口爲443。

HTTP是明文跑在 TCP 上.而HTTPS跑在SSL/TLS應用層之下,TCP上的

Q: 那麼 HTTPS中的TLS/SSL是如何保護數據的,

通常有兩種形式,非對稱加密,生成公鑰和私鑰,私鑰丟服務器,公鑰每次請求去比對驗證;

更嚴謹的採用 CA(Certificate Authority),給密鑰簽名,.

Q: 你說到對稱加密和非對稱加密,能說說整個流程如何運轉的麼(HTTPS)

  • 對稱加密:
    • 雙方都有一樣的密鑰,每次通信都要生成一個惟一密鑰,速度很快
    • 安全性較低且密鑰增加的數量極快
  • 非對稱加密(通常用 RSA)
    • 安全性很高,對資源消耗很大(CPU),目前主流的加密算法(基本用於交換密鑰或簽名,而非全部通信內容)
  • CA(數字簽名):
    • 這個是爲了防止中間人給偷換了形成數據被竊取而誕生的
    • 用一些權威機構頒佈的算法來簽名,權威機構作中間人,通信過程都會跟機構覈對一遍

懂得真心很少,回來找了下相關資料,有興趣能夠點擊看看;

Q: SPDY 據說過麼.什麼來的?

谷歌推行一種協議(HTTP 之下SSL之上[TCP]),能夠算是HTTP2的前身,有這麼些優勢

  • 壓縮數據(HEADER)
  • 多路複用
  • 優先級(能夠給請求設置優先級)

而這些優勢基本 HTTP2也繼承下來了..

Q: 你對 HTTP 的狀態嗎瞭解多少,

這裏列舉一丟丟常見的..

  • 1XX: 通常用來判斷協議更換或者確認服務端收到請求這些
    • 100: 服務端收到部分請求,如果沒有拒絕的狀況下能夠繼續傳遞後續內容
    • 101: 客戶端請求變換協議,服務端收到確認
  • 2xx: 請求成功,是否建立連接,請求是否接受,是否有內容這些
    • 200: (成功)服務器已成功處理了請求。
    • 201: (已建立)請求成功而且服務器建立了新的資源。
    • 202: (已接受)服務器已接受請求,但還沒有處理。
    • 204: (無內容)服務器成功處理了請求,但沒有返回任何內容。
  • 3XX: 通常用來判斷重定向和緩存
    • 301: 全部請求已經轉移到新的 url(永久重定向),會被緩存
    • 302: 臨時重定向,不會被緩存
    • 304: 本地資源暫未改動,優先使用本地的(根據If-Modified-Since or If-Match去比對服務器的資源,緩存)
  • 4XX: 通常用來確認受權信息,請求是否出錯,頁面是否丟失
    • 400: 請求出錯
    • 401: 未受權,不能讀取某些資源
    • 403: 阻止訪問,通常也是權限問題
    • 404: 頁面丟失,資源沒找到
    • 408: 請求超時
    • 415: 媒介類型不被支持,服務器不會接受請求。
  • 5XX: 基本都是服務端的錯誤
    • 500: 服務端錯誤
    • 502: 網關錯誤
    • 504: 網關超時

Q: HTTP的請求報文是怎麼樣的,能大致的說下麼?

HTTP 的請求報文 = 請求行 + 請求頭 + 請求體;

  • 請求行: 這個好理解就是訪問的方法+ 協議+ 訪問的 URL 構成
  • 請求頭: 這個也好理解,好比 accept,content-type,user-agent這類值鍵對,服務端能夠直接讀取的
  • 請求體: 好比 POST 提交的一個表單,咱們編碼後放在上面須要傳遞的

想深刻了解的具體引擎搜索

Q: 請求報文知道,那你說說cookie是如何跟隨請求的?

Cookie 就是保存在 HTTP 協議的請求或者應答頭部(Cookie 是由服務端生成),這樣一路漂泊,

Q: Cookie 隔離是什麼,如何作;

cookie 隔離就是下降 header 的數據包含,以達到加快訪問速度的目的

方案: 靜態資源丟 CDN或者非主域來加載

Q: 瀏覽器緩存和服務端的緩存控制你瞭解多少,說說看?

  • Last-Modified:
    • 第一次請求資源從服務器拉取的會自動帶上該屬性
    • 第二次請求會跟服務端比對If-Modified-Since的時間,沒變更則使用本地的(狀態304)
    • 結合Expires(過時時間:緩存的載止時間),跟隨請求一塊兒發出..資源沒過時拿本地,不然從新請求
  • Cache-controlHTTP1.1的東西,判斷資源過時結合max-age來替代Expires[http 1.0]
  • Etag:
    • 第一次請求url 時候會給服務器上標記(一串字符串)
    • 第二次請求時候會比對服務端的If-None-Match,沒有改動依舊拿緩存(304)

Q: 幾個短而讓我印象深入的題

if(!("a" in window)){
    var a = 10;
}
console.log(a); // undefined

// !("a" i n window) , 返回 true
/* var a; if(!("a" in window)){ a = 10; } */

// 變種題
(function(){
 var  x = c =  b = {a:1}
})()

console.log(x.a); // error , x is not defined
console.log(c,b) // {a: 1} {a: 1}


複製代碼
var count = 0;

console.log(typeof count === "number"); // true , 這個不用解釋了

console.log(!!typeof count === "number"); // false

// 這裏涉及到就是優先級和布爾值的問題
// typeof count 就是字符串"number"
// !!是轉爲布爾值(三目運算符的變種),非空字符串布爾值爲 true
// 最後才=== 比較 , true === "number" , return false

複製代碼
(function(){
  var a = b = 3;
})()

console.log(typeof a === "undefined"); // false
console.log(typeof b === "undefined"); // false

// 這裏涉及的就是當即執行和閉包的問題,還有變量提高,運算符執行方向(=號自左向右)
// 那個函數能夠拆成這樣

(function() var a; /* 局部變量,外部無法訪問*/ b = 3; /* 全局變量,so . window.b === 3 , 外部能夠訪問到*/ a = b; })() // 如果改爲這樣,這道題應該是對的 console.log(typeof b === "number" && b ===3 ); // true 複製代碼
function foo(something){
  this.a = something;
}

var obj1 = {
  foo:foo
};

var obj2 = {};

obj1.foo(2)

console.log(obj1.a) // 2 ,此時的 this 上下文還在 obj1內,如果 obj1.foo 先保存當作引用再執行傳參,則上下文爲 window

obj1.foo.call(obj2,3); // 用 call 強行改變上下文爲 obj2內
console.log(obj2.a); // 3

var  bar = new obj1.foo(4); // 這裏產生了一個實例
console.log(obj1.a); // 2
console.log(bar.a); // 4; new的綁定比隱式和顯式綁定優先級更高


複製代碼
function fn(){
 alert(a);
 var a = 200;
 alert(a);
}

fn(); // undefined / 200 ; 涉及變量提高
alert(a); // undefined
var a;
alert(a); // undefined

var a = 300;
alert(a); // 300

複製代碼
var obj1= {
  name:'obj1',
  fn:function(){
    console.log(this.name);
  }
};

var obj2 = {name:'obj2'};
var obj3 = {name:'obj3'};

// 這道題主要涉及的是 this 指向的問題..
obj1.fn(); // obj1

var newFn = obj1.fn;
newFn(); // undefined, this 指向 window

newFn.call(obj2);// obj2, this 指向 obj2

obj3.fn = newFn;
/* ƒ (){ console.log(this.name); } */

obj3.fn(); // 這裏指向的是 obj3 .因此輸出 obj3




複製代碼
// 這道題來做爲筆試題很繞,由於要回答的答案不少(腦海構思)..反正我是遇到了..
// 這道題主要考覈的是對原型鏈繼承這塊的理解
function Parent(){
  this.a = 1;
  this.b = [1,2,this.a];
  this.c = {demo:5};
  this.show = function(){
   console.log(this.a + '' + this.c.demo + ':' + this.b)
  }
}

function Child(){
  this.a  = 2;
  this.change = function(){
    this.b.push(this.a);
    this.a = this.b.length;
    this.c.demo = this.a++;
  }

}

Child.prototype = new Parent();
var parent = new Parent();
var child1 = new Child();
var child2 = new Child();

child1.a = 11;
child2.a = 12;

// 這前面幾個還算簡單,繼續看下去
parent.show(); // 15:1,2,1

// 由於 Child 自身沒有 show 的方法,因此往原型鏈的上游找;
// 找到父類的,this 由於沒更改,因此輸出結果以下
child1.show(); // 115:1,2,1
child2.show(); // 125:1,2,1

child1.change();  // 改變一些數據,沒有輸出
child2.change();  // +1

parent.show(); // 15:1,2,1

child1.show(); // 55:1,2,1,11,12
child2.show(); // 65:1,2,1,11,12

複製代碼
// 這道題也很繞,函數遞歸調用的


function test(a,b){
  console.log(b);
  return {
    test:function(c){
       return test(c,a);
    }
};

// 這道題的理解,拆成這樣就好理解了
/*function test(a,b){ console.log("a:"+a,"b:"+b); return { test:function(c){ console.log("a:"+a,"b:"+b,"c"+c); return test(c,a); } } }*/



var a = test(100); // undefined, 這個是不言而喻的;
a.test(200); // 100;
a.test(300); // 100;

var b =  test(101).test(201).test(301); // undefined/101/201


var c =  test(102).test(202); // undefined / 102

c.test(302); // 202


複製代碼

Q:有字符串 var test='abc345efgabcab'; 請根據提示實現對應要求

  • 去掉字符串中的 a,b,c 字符 ,造成結果'345efg';
test.replace(/[abc]/g,''); // "345efg"

複製代碼
  • 將字符串的數字用括號括起來, 造成結果: abc[3][4][5]efg,.'
test.replace(/\d/g,'[$&]');  // "abc[3][4][5]efgabcab"

// 如果有分組則按照$1, $2, $3的形式進行引用,而 $& 則表示的是整個正則表達式匹配的內容。

複製代碼
  • 將字符串中的每一個數字的值分別乘以2,輸出:'abc6810,.'
var temp = test.split('').map(function(item){
  return /^\d$/.test(item) ? item * 2 : item;
}).join('');

// "abc6810efgabcab"



複製代碼

Q: 使用很多於三種方式替換文本"dream"改爲"package",提供字符串"I have a dream";

  • 正則替換
// 這是最簡單的代碼量了..
var str = "I have a dream";
str.replace(/dream/g,"package");

// 不用正則也能夠直接字符串替換
str.replace("dream","package")

複製代碼
  • 數組遍歷更改
// 很直白的大腦回路
var str = "I have a dream";

str.split(" ").map(function(item){
 return  item === "dream" ? item = "package":item;
}).join(" ");

複製代碼
  • 數組查詢切割法
var str = "I have a dream";

var tempArr = str.split(" "); // ["I", "have", "a", "dream"]
var removeIndex = tempArr.indexOf('dream'); // 3

tempArr.splice(removeIndex,1,"package");

var transStr = tempArr.join(" "); // "I have a package";

複製代碼

這類東東弄成數組仍是挺好弄的

這個是留言區小夥伴提供的方法..大同小異,以下;

// 源代碼
// 字符串也有數組的 slice 以及 concat 的方法..思路和數組差很少
var str = 'I haved a dream';
str.indexOf('dream') !== -1 ? str.slice(0,str.indexOf('dream')).concat('package'):str;



複製代碼

Q: 還有一道題目是涉及事件循環,執行優先權的..

就是 macrotaskmicrotask 相關的, 具體記不起來了,那時候給了答案雖然對了。

要說出因此然,給秀了一臉,回來找了下相關的資料;

Q: 你對基礎算法這塊掌握的如何,.

來,這紙給你,寫個快排試試,

// 快排的大致思路是這樣的,
// 找個中位值,從原數組切割出來,
// 剩下的做爲兩個數組,每次都去比較;
// 直到遞歸的結果出來, 平均複雜度O(nlog n)

function quickSort(arr) {
  //若是數組長度<=1,則直接返回
  if (arr.length <= 1) {
    return arr;
  }
  // 中間位(基準)取長度的一半向下取整
  var pivotIndex = Math.floor(arr.length / 2);
  //把中間位從原數組切割出來, splice 會改變原數組!!!!
  var pivot = arr.splice(pivotIndex, 1)[0];
  //定義兩個空數組來存放比對後的值
  var left = [];
  var right = [];

  //比基準小的放在left,比基準大的放在right
  for (var i = 0 , j = arr.length; i < j; i++) {
    if (arr[i] <= pivot) {
      left.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }
  //遞歸下去 arr = [ left , pivot , right]
  // 怎麼個遞歸法,就是比對後的數組仍是會重複以前的取基準再切開比較..直到最後沒有能夠切了
  return quickSort(left).concat([pivot], quickSort(right));
}

複製代碼

Q: 寫一個二分法查找

// 二分法跟快排的思路差很少,對半比較
// 這個只用於排序好數組內的查詢,高低位都知道的狀況下
function binSearch(target, arr, start, end) {
  var start = start || 0; // 容許從什麼位置開始,下標
  var end = end || arr.length - 1; // 什麼位置結束,下標
  start >= end ? -1 : ''; // 沒有找到,直接返回-1
  var mid = Math.floor((start + end) / 2); // 中位下標
  if (target == arr[mid]) {
    return mid; // 找到直接返回下標
  } else if (target > arr[mid]) {
    //目標值如果大於中位值,則下標往前走一位
    return binSearch(target, arr, start, mid - 1);
  } else {
    //如果目標值小於中位值,則下標日後退一位
    return binSearch(target, arr, mid + 1, end);
  }
}

// binSearch(5,[1,2,3,4,5,6,7,8]) => 4

// 無序的數組則須要先排序好數組,不然會堆棧溢出(死循環)

複製代碼

這類的文章不少,有興趣的能夠閱讀下面的一些文章

傳送門:

Q: 設計模式你瞭解多少?

Q: 思惟拓展題: 你有兩個玻璃球,有個100米的高樓,求玻璃球在哪一個樓層扔下會碎(用的次數最少);

問題的要點: 玻璃球碎(有限個數) ,肯定樓層數 , 最少次數 => 就是求最優的公式

在這道題上給秀的一臉,個人第一次的思路

先折半,就變成[1-50][51-100], 那就是 1+50 = 51次 ,

面試大佬說,你用了快排的思路就確定不是最優的..

憋了許久,想到開平方 \sqrt[2]{100} , 這樣的話,最多隻要20次

而後又說給我三個球,在1000米的高樓,判斷多少次,可是根據我上面的話,

開立方, \sqrt[3]{1000} , 那最多不超過30次;

至於第一次丟球的位置如何肯定, 就是開平以後的值做爲一個區間.

若 N 個球和 M 米的大廈,第一次丟球的高度區間就是這個了\frac{m}{\sqrt[n]{m}}

面試大佬說這個還能夠,那就暫且告一段落

,回來用萬能的搜索引擎找了下..最優方案+最少次數須要考慮的東西不少,沒那麼簡單

傳送門: 知乎有人討論了這個問題;

可是高數還老師了..這種帖子看的一臉懵逼,.抽空再好好研究下

Q: 你對優化這塊瞭解多少?

大致常見的手段瞭解.

客戶端着手

  • 壓縮代碼(JS/CSS),壓縮圖片
  • 合併一些小圖片(css sprite)
  • 如果打包的代碼儘量切割成多個 chunk,減小單一 chunk過大
  • 靜態文件採用 cdn 引入
  • HTTP的緩存頭使用的合理
  • 減少第三方庫的依賴
  • 對於代碼應該考慮性能來編寫,好比使用requestAnimationFrame繪製動畫,儘量減小頁面重繪(DOM 改變)
  • 漸進升級,引入preload這些預加載資源
  • 看狀況用service worker來緩存資源(好比移動端打算搞 PWA)

服務端着手

  • 帶寬,域名解析, 多域名解析等
  • 頁面作服務端渲染,減少對瀏覽器的依賴(不用客戶端解析)
  • 漸進升級,好比引入 HTTP2(多路複用,頭部壓縮這些能夠明顯加快加載速度)

固然,這是這些都是很片面的點到,實際工做中去開展要複雜的多;

好比咱們要多個維度去考慮的話,要去優化 DOM 的繪製時間,資源的加載時間,域名解析這些;

要全面的優化一個項目是一個大工程,

Q: MySQL有哪些索引類型? 索引的數據結構儲存方式? MySQL和 MongoDB的差別

MySQL索引類型:

  • 普通索引: 就普通的類型
  • 惟一索引: 表明索引的值惟一不重複(容許有空值),相對於上面多了個UNIQUE
  • 主鍵索引:(建立表的跟隨建立,惟一索引,不容許有空值)
  • 組合索引(就是將多個字段都創建到一個索引)

索引有利有弊,用的好加快查詢速度,濫用索引會形成大量磁盤空間佔用,維護性也會增多; 索引不會包含null的列;

索引的數據結構儲存方式,我只簡單瞭解過B-Tree

至於MySQL 和 MongoDB的差別;

前者是關係型數據庫, 後者非關係型數據庫(數據是以文檔的方式儲存,值爲 key-value);

MySQL應用層面很廣,有事務系統這些,鏈表查詢這些都很方便.常常做爲不少系統的主力數據庫

MongoDB做爲NoSQL,雖然有些層面不如 MySQL,可是應用層面也挺廣, 好比結合前端作一些用戶的概要信息的維護,一些緩存信息的維護.

em,.後端瞭解很少,也能點到即止,.大學的時候學過一些..都差很少還給老師,.

Q: JS時間分段

給定一個時間段和步長,枚舉該時間段內步長的劃分

例如:時間段3:00-5:00,步長爲20分鐘

那麼返回的數組爲

['3:00-3:20', '3:20-3:40',.]

這類問題,通常都要先梳理好思路再來寫;

  • 給定字符串時間段,切割,轉換爲分鐘
  • 跨日及跨時問題
// 這個東東個人小夥伴也寫出來了.個人是在它的解答方式上加以註釋和對參數的判斷作了考慮
// 他的解法方案在他的 github 上 https://github.com/lyh2668/blog/issues/1 , by lyh2668
// 方便一些小夥伴的理解,如下代碼包含ES6的姿式(參數默認值,剪頭函數)

let inputDateRange = (date, step = 30, separator = '-') => {
  let startTime, endTime; // 開始時間和結束時間

  if (Object.prototype.toString.call(date) === '[object String]') {
    date = date.trim(); // 去除兩邊的空格
    var tempDate = '';
    if (separator) {
      tempDate = date.split(separator);
    } else {
      if (date.indexOf('-') !== -1) {
        tempDate = date.split('-');
      } else if (date.indexOf('~')) {
        tempDate = date.split('~');
      } else {
        console.log('您傳入的也許不是一個時間段!!!');
      }
    }
    startTime = time2min(tempDate[0]); // 傳入的開始時間
    endTime = time2min(tempDate[1]); //傳入的結束時間
  } else if (Object.prototype.toString.call(date) === '[object Array]') {
    if (date.length === 2) {
      startTime = time2min(date[0]); // 傳入的開始時間
      endTime = time2min(date[1]); //傳入的結束時間
    }
  } else {
    console.log('您傳入的也許不是一個時間段!!!');
  }

  // 傳入的 step 是否爲數字,不然截圖數字部分轉化
  // 爲何和 NaN 比較(自身不等性),如果傳入的連正則都無法識別,那隻能給默認值了
  Object.prototype.toString.call(step) === '[object Number]'
    ? (step = parseInt(step, 10))
    : parseInt(step.replace(/[W\s\b]/g, ''), 10) === NaN
      ? (step = parseInt(step.replace(/[W\s\b]/g, ''), 10))
      : (step = 30);

  // 如果開始時間大於結束時間則結束時間日後追加一天
  startTime > endTime ? (endTime += 24 * 60) : '';

  let transformDate = []; // 儲存轉換後的數組,時間分段

  // 開始遍歷判斷,用 while
  while (startTime < endTime) {
    // 若是開始時間+步長大於結束時間,則這個分段結束,不然結束時間是步長遞增
    let right = startTime + step > endTime ? endTime : startTime + step;
    transformDate.push(`${min2time(startTime)}-${min2time(right)}`);
    startTime += step; // 步長遞增
  }
  return transformDate;
};

// 時間轉化爲分鐘
let time2min = time => {
  // 獲取切割的
  time.indexOf(':') ? (time = time.trim().split(':')) : '';
  return time[0] * 60 + parseInt(time[1]); // 返回轉化的分鐘
};

// 分鐘轉會字符串時間
let min2time = minutes => {
  let hour = parseInt(minutes / 60); // 返回多少小時
  let minute = minutes - hour * 60; // 扣除小時後剩餘的分鐘數

  hour >= 24 ? (hour = hour - 24) : ''; // 如果大於等於24小時須要扣除一天獲得所剩下的小時
  minute < 10 ? (minute = '0' + minute) : ''; // 小於10的都要補零
  hour < 10 ? (hour = '0' + hour) : ''; // 小於10的都要補零
  return `${hour}:${minute}`;
};


// test ,支持字符串傳入時間段
inputDateRange('3:00-5:00','20d'); // ["03:00-03:20", "03:20-03:40", "03:40-04:00", "04:00-04:20", "04:20-04:40", "04:40-05:00"]

// 亦或者數組傳入
inputDateRange(['3:00','5:00'],'45df.3d'); // ["03:00-03:45", "03:45-04:30", "04:30-05:00"]

// step 支持數字亦或者帶特殊字符的數字
inputDateRange(['6:00','8:00'],'55df.3d'); // ["06:00-06:55", "06:55-07:50", "07:50-08:00"]

inputDateRange('3:00-5:00',60); // ["03:00-04:00", "04:00-05:00"]


複製代碼

Q: Vue-Router的兩種模式主要依賴什麼實現的

  • hash主要依賴location.hash來改動 URL,達到不刷新跳轉的效果.每次 hash 改變都會觸發hashchange事件(來響應路由的變化,好比頁面的更換)
  • history主要利用了 HTML5historyAPI 來實現,用pushStatereplaceState來操做瀏覽歷史記錄棧

Q: MVVM 和 MVC 的差別? 據說過 MVP?

這類的文章好多,三個開發模式的誕生都有先後,不是同時出現的.

傳送門:

Q: 求100~999的全部"水仙花"數, 就是三位數中各數字的立方和等於自身,好比153=1^3+5^3+3^3

  • 常規遍歷法
function threeWaterFlower(rangeStart, rangeEnd) {
  var temp = [];
  rangeStart = rangeStart || 100;
  rangeEnd = rangeEnd || 999;
  for (var i = rangeStart; i <= rangeEnd; i++) {
    var t = i.toString().split('');
    Math.pow(t[0], 3) + Math.pow(t[1], 3) + Math.pow(t[2], 3) == i
      ? temp.push(i)
      : '';
  }
  return temp;
}


threeWaterFlower(100,999); // [153, 370, 371, 407]

threeWaterFlower(); // [153, 370, 371, 407]


複製代碼
  • 拓展寫法,ES6版+不定花數,不折騰不舒服版本
let manyWaterFlower = (rangeStart = 100, rangeEnd = 999, flower = 3) => {
  let temp = [];
  for (let i = rangeStart; i <= rangeEnd; i++) {
    let t = i
    .toString()
    .split('')
    .map(item => Math.pow(item, flower))
    .reduce((cur,next)=> parseInt(cur)+parseInt(next));
    let transformT = parseInt(t, 10);
    transformT == i ? temp.push(i) : '';
  }
  return temp;
}

manyWaterFlower(); // [153, 370, 371, 407]

manyWaterFlower(100,10000,4); // [1634, 8208, 9474]

manyWaterFlower(100,10000,5); // [4150, 4151]


複製代碼

這種是窮舉遍歷,如果要快一點呢(考慮的周全一點呢),以及傳參範圍的矯正

相信小夥伴都看得懂,我已經儘可能註釋了..

let manyWaterFlower = (flower = 3,rangeStart, rangeEnd ) => {
  let temp = [];// 緩存全部找到的花值

  // 這一段就是填充開始循環的範圍,處理完畢後轉爲數字,推薦的開始值
  let flowerRecommandStart = Number(
    ''.padStart(flower, '0').replace(/^(\d{1})/g, '1')
  );
  let flowerRecommandEnd = Number(''.padStart(flower, '9'));

  // 判斷是否傳入開始值
  if (rangeStart) {
    rangeStart > flowerRecommandStart
      ? (rangeStart = flowerRecommandStart)
      : rangeStart;
  } else {
    rangeStart = flowerRecommandStart;
  }

  // 判斷是否有傳入結束值
  if (rangeEnd) {
    rangeEnd > flowerRecommandEnd ? (rangeEnd = flowerRecommandEnd) : rangeEnd;
  } else {
    rangeEnd = flowerRecommandEnd;
  }

  // 如果初始值大於結束值
  if (rangeStart > rangeEnd) {
    rangeEnd = flowerRecommandEnd;
  }

  for (let i = rangeStart; i <= rangeEnd; i++) {
    let t = i
      .toString()
      .split('')
      .map(item => Math.pow(item, flower))
      .reduce((cur, next) => parseInt(cur) + parseInt(next));
    let transformT = parseInt(t, 10);
    transformT == i ? temp.push(i) : '';
  }

  return temp;
};

console.time('manyWaterFlower');
manyWaterFlower(4)
console.timeEnd('manyWaterFlower');
// VM34013:4 manyWaterFlower: 8.112060546875ms ,這個是跑出來的時間

用上個例子的代碼,從1009999的,咱們跑一下看看
console.time('manyWaterFlower');
manyWaterFlower(100,9999,4)
console.timeEnd('manyWaterFlower');
// VM3135:4 manyWaterFlower: 10.51904296875ms


// 個人 MBP 跑10花直接卡死,跑7花有點久,
console.time('7 flower')
manyWaterFlower(7);
console.timeEnd('7 flower')
// 7 flower: 6489.608154296875ms

// 8 花 CPU 的風扇狂叫,.
console.time('8 flower')
manyWaterFlower(8);
console.timeEnd('8 flower')
// VM644:3 8 flower: 68010.26489257812ms

// 對了咱們尚未考慮數值溢出的問題..由於正整數在 JS 的範圍是有限的.
// 有興趣的小夥伴能夠自行完善

複製代碼

Q: 請使用遞歸算法在 TODO 註釋後實現經過節點 key 數組尋找 json 對象中的對應值

好比console.log(findNode(['a1', 'b2'], data)) === data.a1.b2

// 請使用遞歸算法在 TODO 註釋後實現經過節點 key 數組尋找 json 對象中的對應值

var data = {
  a1: {
    b1: 1,
    b2: 2,
    b3: {
      b4: 5
    }
  },
  a2: {
    b1: 3,
    b2: 4
  }
};

function findNode(inPath, inData) {
  // TODO

  // 判斷傳入的是不是一個數組
  if (Array.isArray(inPath)) {
    // 當長度爲1的時候尋找該 key 是否有值,有則返回,無則返回-1
    if (inPath.length === 1) {
      return inData[inPath[0]] ? inData[inPath[0]]: -1;
    }else{
     return findNode(inPath.slice(1), inData[inPath[0]]);
    }
  } else{
    console.log('您傳入的不是一個數組')
  }
}

console.log(findNode(['a1', 'b2'], data)); // 2
console.log(findNode(['a1', 'b3','b4'], data)); // 5


複製代碼
  • 來個拓展版?支持字符串或數組傳入;findNode('a1.b2',data)?
var data = {
  a1: {
    b1: 1,
    b2: 2,
    b3: {
      b4: 5
    }
  },
  a2: {
    b1: 3,
    b2: 4
  }
};

// 判斷格式
function isType(params) {
  let type = Object.prototype.toString.call(params);
  if (type === '[object String]') {
    params = params.split('.');
    return params;
  }
  if (type === '[object Array]') {
    return params;
  }
}

function findNode(inPath, inData) {
  inPath = isType(inPath);
  // 判斷傳入的是不是一個數組
  if (Array.isArray(inPath)) {
    // 當長度爲1的時候尋找該 key 是否有值,有則返回,無則返回-1
    if (inPath.length === 1) {
      return inData[inPath[0]] ? inData[inPath[0]]: -1;
    }else{
     return findNode(inPath.slice(1), inData[inPath[0]]);
    }
  } else {
    console.log('您傳入的不是一個數組');
  }
}

console.log(findNode(['a1', 'b2'], data)); // 2

console.log(findNode('a1.b3.b4', data)); // 5



複製代碼

Q: webpack 是什麼?webpack 常見的優化手段有哪些;

webpack 是一個資源處理工具,它的出現節省了咱們的人力和時間; 能夠對資源打包,解析,區分開發模式等等,

常見的優化手段:

  • 分離第三方庫(依賴),好比引入dll
  • 引入多進程編譯,好比happypack
  • 提取公共的依賴模塊,好比commonChunkPlugin
  • 資源混淆和壓縮:好比UglifyJS
  • 分離樣式這些,減少bundle chunk的大小,好比ExtractTextPlugin
  • GZIP 壓縮,在打包的時候對資源對齊壓縮,只要部署的服務器能解析便可..減小請求的大小
  • 還有按需加載這些,通常主流的框架都有對應的模塊懶加載方式.
  • 至於tree shaking目前webpack3/4已經默認集成

Q: 從你輸入一個 URL 到頁面渲染的大致過程,

大致過程是這樣的,想了解很細緻的能夠自行引擎;

  1. IP->DNS(瀏覽器=>系統緩存=>DNS 服務器)->域名解析完成(這一步不用太多解析吧)
  2. TCP 協議走完->HTTP(S) 協議->緩存->(分析請求頭)-> 回饋報文
  3. 請求文檔下來->DOM->CSSDOM->靜態資源下載->render(繪製文檔)->js 解析
  4. 用戶看到頁面

Q: Vue 的組件的通信手段有哪些..

  • 父-> 子: props
  • 子-> 父: on+emit
  • 父<>子: on.sync(語法糖)來的
  • 兄弟 : event bus | vuex

Q: Vuex你怎麼理解?

vuex是一個狀態管理容器(你也能夠理解爲全局變量),數據的流向是是單向數據流,

且數據並不具備持久化的特性(默認狀況下刷新就重置全部狀態);

裏面的一些數據乃至方法,能夠大體理解爲 vue 的一些特性,好比

Vuex Vue
state data
getter computed
mutation/actions methods

至於單向數據流(全局單例模式)怎麼理解

state只能給mutation(同步操做) 改動, action只能反饋給mutation,能夠進行異步操做(好比和後端交互拉取數據), state能觸發 render,action能用dispatch分發..如圖

結語

還有一些題目記不起來了,就沒轍了,還有一些題目是看你我的發揮的,無法寫,好比

  • Q: 讓你來爲公司的一個項目作技術選型,你會怎麼作,爲何?
  • Q: React,Angular,Vue的比較?
  • Q: 說說你對 VNode的理解,diff的過程;
  • Q: Vue的雙向綁定如何實現,用了什麼模式(訂閱模式),大致如何實現的。
  • Q: cmd/amd/commonjs的差別
  • Q: 小程序以及React Native的差別..等等

面試的過程當中磕磕碰碰才能發現自身的不少不足和須要去努力的方向.

有不對之處請留言,會及時跟進修正,謝謝各位大佬

掘金技術徵文活動連接: juejin.im/post/5aaf2a…

相關文章
相關標籤/搜索