詳解一套面試題

span的display值,文本example的顏色

<div class="outside">
  <span id="passage" style="color:blue;" data-color="red">example</span>
</div>

<style>
  #passage { color: yellow;}
  .outside span{ color: green; display: block;}
  span { display: inline;}
  [data-color="red"] { color: red;}
</style>

其實瀏覽器中,這張圖的排列順序,就很好的表示出了這個demo中的優先級關係:
javascript

優先級關係:內聯樣式 > ID 選擇器 > 類選擇器 = 屬性選擇器 = 僞類選擇器 > 標籤選擇器 = 僞元素選擇器。 ⚠️ !important是個例外,優先級最高。

更詳細的CSS優先級請查看MDN-優先級是如何計算的?html

寫一個滿屏的品字

這就是考驗一個佈局的能力,沒什麼好說的,辦法不少。我用的flex打個樣。前端

<div class="main">
  <div class="top"><h1>class="top"</h1></div>
  <div class="bottom">
    <div class="left">
      <h1>class="left"</h1>
    </div>
    <div class="right">
      <h1>class="right"</h1>
    </div>
  </div>
</div>
<style>
.main{
    display: flex;
    flex-direction: column;
}
.top,.bottom{
    height: 300px;
}
.top{
    border: 1px solid red;
}
.bottom{
    display: flex;
    border: 1px solid green;
}
.left,.right{
    flex: 1;
    height: 100%;
}
.left{
    border-right: 1px solid blue;
}
</style>

以下代碼,寫出執行結果

var fun = function(arr) {
  for(var i = 0; i< arr.length;i++) {
    setTimeout(function() {
      console.log(i);
    },0)
  }
  console.log(arr[i])
}
fun([1,2,3,4])

直接寫答案就沒什麼意思了,借這個題先扯一下執行上下文做用域做用域鏈閉包java

執行上下文

如下demo、圖示、結論絕大部分來自 這個網站,推薦閱讀!在這裏引用是爲了讓你們更好的理解,我確實講不了這麼好!!!

一段JavaScript的代碼執行的時候,都會產生一個執行上下文(也就是執行環境)。多段代碼執行就會產生多個執行上下文。git

console.log(1);
// 這段代碼的執行上下文就是--全局環境
function test() {
  console.log('test');
}
test();
// test() 執行上下文就是test--函數環境

JavaScript中的運行環境大概包括三種狀況:github

  1. 全局環境:JavaScript代碼運行起來會首先進入該環境
  2. 函數環境:當函數被調用執行時,會進入當前函數中執行代碼
  3. eval(不建議使用,可忽略)

⚠️JavaScript引擎會以棧的形式來處理這些執行上下文,棧底永遠都是全局上下文,而棧頂就是當前正在執行的上下文面試

看下面這個demo,相信你們一看就懂了:算法

var color = 'blue';

function changeColor() {
    var anotherColor = 'red';

    function swapColors() {
        var tempColor = anotherColor;
        anotherColor = color;
        color = tempColor;
    }

    swapColors();
}

changeColor();

這裏面有全局上下文(Global Context)changeColor()上下文swapColors()上下文,它們進棧出棧以下圖:segmentfault

每個執行上下文都有本身的生命週期:
數組

對執行上下文總結一些結論:

  1. 單線程
  2. 同步執行,只有棧頂的上下文處於執行中,其餘上下文須要等待
  3. 全局上下文只有惟一的一個,它在瀏覽器關閉時出棧
  4. 函數的執行上下文的個數沒有限制
  5. 每次某個函數被調用,就會有個新的執行上下文爲其建立,即便是調用的自身函數,也是如此。

做用域、做用域鏈與閉包

做用域與執行上下文是徹底不一樣的兩個概念。

JavaScript代碼的整個執行過程,分爲兩個階段,代碼編譯階段與代碼執行階段。編譯階段由編譯器完成,將代碼翻譯成可執行代碼,這個階段做用域規則會肯定。執行階段由引擎完成,主要任務是執行可執行代碼,執行上下文在這個階段建立。

⚠️JavaScript中只有全局做用域與函數做用域(由於eval咱們平時開發中幾乎不會用到它,這裏不討論)。

做用域鏈,是由當前環境與上層環境的一系列變量對象組成,它保證了當前執行環境對符合訪問權限的變量和函數的有序訪問。

看一個demo:

var a = 20;

function test() {
    var b = a + 10;

    function innerTest() {
        var c = 10;
        return b + c;
    }

    return innerTest();
}

test();

在上面的例子中,全局,函數test,函數innerTest的執行上下文前後建立。咱們設定他們的變量對象分別爲VO(global),VO(test), VO(innerTest)。而innerTest的做用域鏈,則同時包含了這三個變量對象,因此innerTest的執行上下文可以下表示。

innerTestEC = {
    VO: {...},  // 變量對象
    scopeChain: [VO(innerTest), VO(test), VO(global)], // 做用域鏈
}

至於這裏面的VO AO 有興趣的能夠去上面那個網站裏看看,這裏不提,我以爲不妨礙你們理解。

簡單說就是,在innerTest這個方法內,能拿到test()方法中的變量,也能拿到全局環境中的變量,這就造成了一個做用域鏈。

看到這裏相信你們都知道了,閉包不就是這個東東嘛。

它由兩部分組成。執行上下文(代號A),以及在該執行上下文中建立的函數(代號B)。
當B執行時,若是訪問了A中變量對象中的值,那麼閉包就會產生。

JavaScript擁有自動的垃圾回收機制,關於垃圾回收機制,有一個重要的行爲,那就是,當一個值,在內存中失去引用時,垃圾回收機制會根據特殊的算法找到它,並將其回收,釋放內存。

而咱們知道,函數的執行上下文,在執行完畢以後,生命週期結束,那麼該函數的執行上下文就會失去引用。其佔用的內存空間很快就會被垃圾回收器釋放。但是閉包的存在,會阻止這一過程。

setTimeout

繞了一圈回到這個題,這個題中的setTimeout又在什麼時候執行呢?

在這裏,將會介紹另一個特殊的隊列結構,頁面中全部由setTimeout定義的操做,都將放在同一個隊列中依次執行。

而這個隊列執行的時間,須要等待到函數調用棧清空以後纔開始執行。即全部可執行代碼執行完畢以後,纔會開始執行由setTimeout定義的操做。而這些操做進入隊列的順序,則由設定的延遲時間來決定。

  • 這個題中循環4次,每次往隊列里加入一個console.log(i),它們引用的都是同一個i在循環結束時,i已經變成4了。
  • console.log(arr[i])就是undefined
答案undefined , 4 ,4 ,4 ,4

以下代碼,寫出執行結果

function person(name) {
  if(name) {
    this.name = name;
  }
  console.log(this.name);
}
person.prototype.name = 'Tom';
var human = {
  person: person,
  name: 'Cat'
}
person();
person('Jack');
new person();
new person('Rose');
human.person();
person.call(window)
  • person(), 做爲函數直接調用,this指向windowthis.name = window.name=undefined
  • person('Jack') ,跟上面同樣,this指向windowthis.name = window.name=name='Jack'
  • new person() ,做爲構造函數調用,this指向新生成的對象,在自身沒有找到this.name就會沿着原型鏈查找,因此this.name = person.prototype.name=Tom
  • new person('Rose'),與上面相似,區別在於傳了name
  • human.person(),做爲對象方法調用,this指向human=>human.name = 'Cat'
  • person.call(window),用call方法將this指向window,⚠️這裏最容易錯❌,person('Jack') 已經將window.name='Jack'
答案: undefined、Jack、Tom、Rose、Cat、Jack

以下代碼,寫出執行結果

var a = window.a = 'finget.github.io'
function hello(){
  console.log(a);
  var a = 'hello';
  console.log(a);
  console.log(b);
  let b = 'finget';
}
hello();

這個題比較簡單,主要涉及的就是變量提高,和做用域鏈。坑點就是第一個console.log(a)究竟是打印undefined仍是finget.github.io

再看看做用域鏈那張圖:

hello()方法中定義了一個var a = 'hello',雖然在剛執行的時候,根據變量提高原則,a=undefined,可是它仍是頗有骨氣的,只要本身有毫不往上找。那若是換成let a = 'hello'呢?

來來來試一試:

var a = window.a = 'finget.github.io'
function hello(){
  console.log(a);
  let a = 'hello';
  console.log(a);
  console.log(b);
  let b = 'finget';
}
hello();

暫時性死區

只要塊級做用域內存在let命令,它所聲明的變量就「綁定」(binding)這個區域,再也不受外部的影響。

var tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}

上面代碼中,存在全局變量tmp,可是塊級做用域內let又聲明瞭一個局部變量tmp,致使後者綁定這個塊級做用域,因此在let聲明變量前,對tmp賦值會報錯。

上面的結果就很清楚了,直接報錯,後面的也不執行。

提取url的參數,以key-value形式返回

徹底考正則,本身惡補吧。沒辦法!

let getSearch = function(url) { 
    let matched = /^(?:https?:\/\/[^?]*\?)(.*)/gi.exec(url) 
    return matched ? matched[1] : '' 
} 
// 遞歸函數,循環匹配search 
let searchFn = function (search, query) { 
    if (search) { 
        let matched = /(\w+)=(\w*)/g.exec(search) 
        if (matched) { 
            query[matched[1]] = decodeURIComponent(matched[2]) 
            searchFn(search.slice(matched.index + matched[0].length), query) 
        } 
    } 
} 
let parseUrl = function (url) { 
    let query = {} 
    searchFn(getSearch(url), query) 
    return query 
} 
let url = 'http://localhost:3009/h5/test?recordID=161851&order=2'
console.log(parseUrl(url)) // => { recordID: '161851', order: '2' }

判斷一個字符串中出現最多的字符,統計次數

function maxStr(str) {
    let map = {}
    for(let v of str) {
        map[v] = ~~map[v] + 1
    }
    // 這裏就相似這種結構 map={a:1,b:1}, ~~map[v]就相似parseInt(),若是某一個字符第一齣現就是0,=> 0+1,以此類推!
    
    // Object.values 能將一個對象的value返回成一個數組,再去最大值
    let max = Math.max(...Object.values(map))
    for (let key in map) {
        if (map[key] == max){
            return {[key]: max}
        }
    }
}
let str = 'aasdfasd,asdfjaslkdfjiqjwioaklsdf,asd,lqwejrio1ji3wioqjroiqqewslkasm'
console.log(maxStr(str))
按位非運算符「~」
先看看w3c的定義:

位運算 NOT 由否認號(~)表示,它是 ECMAScript 中爲數很少的與二進制算術有關的運算符之一。

位運算 NOT 是三步的處理過程:

把運算數轉換成 32 位數字

把二進制數轉換成它的二進制反碼(0->1, 1->0)

把二進制數轉換成浮點數

簡單的理解,對任一數值 x 進行按位非操做的結果爲 -(x + 1)

console.log('~null: ', ~null);       // => -1
console.log('~undefined: ', ~undefined);  // => -1
console.log('~0: ', ~0);          // => -1
console.log('~{}: ', ~{});         // => -1
console.log('~[]: ', ~[]);         // => -1
console.log('~(1/0): ', ~(1/0));      // => -1
console.log('~false: ', ~false);      // => -1
console.log('~true: ', ~true);       // => -2
console.log('~1.2543: ', ~1.2543);     // => -2
console.log('~4.9: ', ~4.9);       // => -5
console.log('~(-2.999): ', ~(-2.999));   // => 1

那麼, ~~x就爲 -(-(x+1) + 1) 至關因而 parseInt()

console.log('~~null: ', ~~null);       // => 0
console.log('~~undefined: ', ~~undefined);  // => 0
console.log('~~0: ', ~~0);          // => 0
console.log('~~{}: ', ~~{});         // => 0
console.log('~~[]: ', ~~[]);         // => 0
console.log('~~(1/0): ', ~~(1/0));      // => 0
console.log('~~false: ', ~~false);      // => 0
console.log('~~true: ', ~~true);       // => 1
console.log('~~1.2543: ', ~~1.2543);     // => 1
console.log('~~4.9: ', ~~4.9);       // => 4
console.log('~~(-2.999): ', ~~(-2.999));   // => -2

實現一個拷貝函數

JSON.parse()

const newObj = JSON.parse(JSON.stringify(oldObj));
️1.他沒法實現對函數 、RegExp等特殊對象的克隆
2.會拋棄對象的constructor,全部的構造函數會指向Object
3.對象有循環引用,會報錯

比較完善的深拷貝

我以爲面試手寫這個也太那啥了!
const isType = (obj, type) => {
  if (typeof obj !== 'object') return false;
  const typeString = Object.prototype.toString.call(obj);
  let flag;
  switch (type) {
    case 'Array':
      flag = typeString === '[object Array]';
      break;
    case 'Date':
      flag = typeString === '[object Date]';
      break;
    case 'RegExp':
      flag = typeString === '[object RegExp]';
      break;
    default:
      flag = false;
  }
  return flag;
};

const getRegExp = re => {
  var flags = '';
  if (re.global) flags += 'g';
  if (re.ignoreCase) flags += 'i';
  if (re.multiline) flags += 'm';
  return flags;
};

const clone = parent => {
  // 維護兩個儲存循環引用的數組
  const parents = [];
  const children = [];

  const _clone = parent => {
    if (parent === null) return null;
    if (typeof parent !== 'object') return parent;

    let child, proto;

    if (isType(parent, 'Array')) {
      // 對數組作特殊處理
      child = [];
    } else if (isType(parent, 'RegExp')) {
      // 對正則對象作特殊處理
      child = new RegExp(parent.source, getRegExp(parent));
      if (parent.lastIndex) child.lastIndex = parent.lastIndex;
    } else if (isType(parent, 'Date')) {
      // 對Date對象作特殊處理
      child = new Date(parent.getTime());
    } else {
      // 處理對象原型
      proto = Object.getPrototypeOf(parent);
      // 利用Object.create切斷原型鏈
      child = Object.create(proto);
    }

    // 處理循環引用
    const index = parents.indexOf(parent);

    if (index != -1) {
      // 若是父數組存在本對象,說明以前已經被引用過,直接返回此對象
      return children[index];
    }
    parents.push(parent);
    children.push(child);

    for (let i in parent) {
      // 遞歸
      child[i] = _clone(parent[i]);
    }

    return child;
  };
  return _clone(parent);
};

查找素數

試除法

這種方式很傳統理解上也簡單,給定一個範圍,那麼就逐個循環去試除小於它數。

如今咱們假設 N 等於 120

let N = 120;
let primes = [];
// 用於存素數結果集

loop:for(let x=2;x<=N;x++){
   for(let k=2;k<x;k++){
     if(x%k==0) continue loop;
     //一旦有被小於它的數整除,則退出試下一個數
   }
   //能走到這一步的就是素數了
   primes.push(x);
}

console.log(primes.join(','))

篩法

先把全部2的倍數去掉,而後剩下的那些數裏面,最小的是3,3就是素數,而後把3的倍數都去掉,剩下的數裏面,最小的是5,因此5也是素數…(能夠看出已跳過4的試除,越多到後面跳過的數越多)

上述過程依次進行,但不像試除法逐個進行,就能夠把某個範圍內的非素數全都除去,剩下的就是素數了。這種方式的好處在於運算不重複,高效。

有一張很形象的動畫,能直觀地體現出篩法的工做過程。 (非素數就像被篩子篩掉同樣)

let N = 120;
let primes = [];
// 用於存素數結果集
let nums = [];
// 待篩選的數據集
for(let x=2;x<=N;x++){
  //hooyes提示:此處初始化的時候,也可直接篩掉2的倍數數據減半。
  //if(x%2!==0)
   nums.push(x);
}
// 遞歸函數
function PrimeFn(data){

      let p = data.shift();
      // 數組最前端的一個數即素數,拿出來存起,並做爲下次篩除的分母。
      primes.push(p);
      let t = [];
      for(let v of data){
         v%p!==0 ? t.push(v) : ""
         // 能被 p 整除的都篩除掉,不能整除的放到臨時數組t存起來。
      }
      // t 是下次待篩數組,元素個數會愈來愈少,若還有就進行一次遞歸。
      t.length>0 ? PrimeFn(t) : ""

}
PrimeFn(nums);
console.log(primes.join(','));

/*
  獲得小於N的素數集合
  2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113
*/
原文地址( https://hooyes.net/p/javascri...[https://hooyes.net/p/javascript-prime-number]

處理金額

/**
 * 金額三位一劃分
 * @param  {[string/number]} money [金額]
 * @param  {[string/number]} round [小數位]
 * @param  {[any]}           flag  [是否四捨五入]
 * @return {[type]}       [description]
 */
function formatMoney(money,round,flag) {
    money = Number(money);
    round = Number(round);
    let formatReg = /(\d)(?=(\d{3})+\.)/g;
    let sliceReg = new RegExp (`([0-9]+\.[0-9]{${round}})[0-9]*`);
    if(!isNaN(money)&&Object.prototype.toString.call(money).slice(8,-1) === 'Number') {
        if (!isNaN(round)&&flag) {
            return String(money.toFixed(round)).replace(formatReg,'$1,')
        } else if(!isNaN(round)){
            return String(money).replace(sliceReg,'$1').replace(formatReg,'$1,')
        } else if(round === 'undefined'){
            return String(money).replace(formatReg,'$1,')
        } else {
            throw new Error('round is not Number!')
        }
    } else {
        throw new Error('money is not Number!')
    }
}

let res = formatMoney('1987562.12812',3,true)
console.log(res)

最後

建立了一個前端學習交流羣,感興趣的朋友,一塊兒來嗨呀!

相關文章
相關標籤/搜索