daily-question-02(前端每日一題02)

在這裏記錄着每天自己遇到的一道印象深刻的前端問題,以及一道生活中隨處可見的小問題。

強迫自己形成積累的習慣,鞭撻自己不斷前行,共同學習。

Github 地址

2019/04/15 - 2019/04/21

1. 寫一個亂序函數 ?

遍歷數組元素,然後將當前元素與以後隨機位置的元素進行交換。

  function shuffle(a) {
    for (let i = a.length; i; i--) {
      let j = Math.floor(Math.random() * i);
      // es6語法
      [a[i - 1], a[j]] = [a[j], a[i - 1]];
    }
    return a;
  }

2. 什麼是惰性函數?

惰性函數就是返回一個重寫函數。For example:

  var foo = function() {
    var t = new Date();
    foo = function() {
      return t;
    };
    return foo();
  };

  foo();

3. 靜態作用域與動態作用域 ?

靜態作用域 —— 函數的作用域基於函數創建的位置。

動態作用域 —— 函數的作用域基於函數的使用位置。

var value = 1;

function foo() {
  console.log(value);
}

function bar() {
  var value = 2;
  foo();
}

bar(); // 輸出 1 。JavaScript 採用的是詞法作用域,也稱爲靜態作用域。相同的,動態作用域此代碼應該輸出 2

4. 手寫一個 function call()函數 ?

Function.prototype.call2 = function(context, ...args) {
  // 因爲傳進來的 context 有可能是 null
  context = context || window;
  // Function.prototype this 爲當前運行的函數
  // 讓 fn 的上下文爲 context
  context.fn = this;

  const result = context.fn(...args);

  delete context.fn;

  return result;
};

5. Vue 組件中的 name 屬性的作用 ?

組件在全局用 Vue.component() 註冊時,全局 ID 自動作爲組件的 name。

指定 name 選項的另一個好處是便於調試。有名字的組件有更友好的警告信息。另外,當在有 vue-devtools,未命名組件將顯示成 <AnonymousComponent>,這很沒有語義。通過提供 name 選項,可以獲得更有語義信息的組件樹。

6. Hash 路由和 History 路由的區別 ?

  1. hash 路由

hash 路由一個明顯的標誌是帶有#,我們主要是通過監聽 url 中的 hash 變化來進行路由跳轉。(window.addEventListener('hashchange', this.refresh, false);)

hash 的優勢就是兼容性更好,在老版 IE 中都有運行,問題在於 url 中一直存在#不夠美觀,而且 hash 路由更像是 Hack 而非標準,相信隨着發展更加標準化的 History API 會逐步蠶食掉 hash 路由的市場。

  1. history 路由

history 路由使用 History API 來實現,具體有:

window.history.back(); // 後退
window.history.forward(); // 前進
window.history.go(-3); // 後退三個頁面

history.pushState用於在瀏覽歷史中添加歷史記錄, history.replaceState方法的參數與pushState方法一模一樣,區別是它修改瀏覽歷史中當前紀錄,而非添加記錄,同樣不觸發跳轉。

7. Vue 的響應式原理中 Object.defineProperty 有什麼缺陷?爲什麼在 Vue3.0 採用了 Proxy,拋棄了 Object.defineProperty

  1. Object.defineProperty 無法監控到數組下標的變化,導致通過數組下標添加元素,不能實時響應;

  2. Object.defineProperty 只能劫持對象的屬性,從而需要對每個對象,每個屬性進行遍歷,如果,屬性值是對象,還需要深度遍歷。Proxy 可以劫持整個對象,並返回一個新的對象。

  3. Proxy 不僅可以代理對象,還可以代理數組。還可以代理動態增加的屬性。

2019/04/08 - 2019/04/14

4. 寫一個“終極類型”判斷函數?

function type(obj) {
  var toString = Object.prototype.toString;
  var toType = {};
  var typeArr = [
    "Undefined",
    "Null",
    "Boolean",
    "Number",
    "String",
    "Object",
    "Array",
    "Function",
    "Date",
    "RegExp",
    "Error",
    "Arguments"
  ];
  // 這裏利用了object 對象toString() 後 值爲 '[object Array]' 等情況進行判斷
  typeArr.map(function(item, index) {
    toType["[object " + item + "]"] = item.toLowerCase();
  });

  return typeof obj !== "object" ? typeof obj : toType[toString.call(obj)];
}

2. 寫一個函數,判斷各種類型的不同變量是否相等,即“終極等於”函數?

const equals = (a, b) => {
  if (a === b) return true;
  // 時間的判斷
  if (a instanceof Date && b instanceof Date)
    return a.getTime() === b.getTime();
  // 非 object 類型的判斷
  if (!a || !b || (typeof a !== "object" && typeof b !== "object"))
    return a === b;
  if (a.prototype !== b.prototype) return false;
  if (Array.isArray(a) && Array.isArray(b)) a.sort(), b.sort();

  let keys = Object.keys(a);
  if (keys.length !== Object.keys(b).length) return false;
  return keys.every(k => equals(a[k], b[k]));
};

3. mouseover 和 mouseenter 的區別 ?

mouseover:當鼠標移入元素或其子元素都會觸發事件,所以有一個重複觸發,冒泡的過程。對應的移除事件是 mouseout

mouseenter:當鼠標移除元素本身(不包含元素的子元素)會觸發事件,也就是不會冒泡,對應的移除事件是 mouseleave

4. 一句話形容閉包?

閉包就是能夠讀取其他函數內部變量的函數,或者子函數在外調用,子函數所在的父函數的作用域不會被釋放。

一個閉包小栗子:

  function f1(){

    n = 999;
  
    function f2(){
  
      console.log(n);
  
    }
  
    return f2;
  
  }
  
  var result = f1();  //返回的是f2函數
  
  result();  //999,讀取內部變量

5. js 的 new 操作符做了哪些事情 ?

new 操作符新建了一個空對象,這個對象原型指向構造函數的 prototype,執行構造函數後返回這個對象。

6. 實現一個深拷貝 ?

//所謂深度克隆,就是當對象的某個屬性值爲object或array的時候,要獲得一份copy,而不是直接拿到引用值

function deepClone1(origin, target) {
  //origin是被克隆對象,target是我們獲得copy
  var target = target || {}; //定義target
  for (var key in origin) {
    //遍歷原對象
    if (origin.hasOwnProperty(key)) {
      if (Array.isArray(origin[key])) {
        //如果是數組
        target[key] = [];
        deepClone1(origin[key], target[key]); //遞歸
      } else if (typeof origin[key] === "object" && origin[key] !== null) {
        target[key] = {};
        deepClone1(origin[key], target[key]); //遞歸
      } else {
        target[key] = origin[key];
      }
    }
  }
  return target;
}
  // 第二個function
function deepClone2(data) {
  if (!data || !(data instanceof Object) || typeof data === "function") {
    return data;
  }
  var constructor = data.constructor;
  var result = new constructor();
  for (var key in data) {
    if (data.hasOwnProperty(key)) {
      result[key] = deepClone2(data[key]);
    }
  }
  return result;
}
// 第三個fuction
function deepClone3(origin, target) {
  var target = target || {},
    toStr = Object.prototype.toString;
  for (var prop in origin) {
    if (origin.hasOwnProperty(prop)) {
      //不能把原型鏈上的一起拷貝了
      //判斷是元素類型還是引用類型
      if (typeof origin[prop] == "object" && typeof origin[prop] !== "null") {
        target[prop] = toStr.call(prop) == "[object Array]" ? [] : {};
        arguments.callee(origin[prop], target[prop]); //遞歸調用
      } else {
        target[prop] = origin[prop]; //原始類型直接複製
      }
    }
  }

  return target;
}
// 第四個function
function deepClone4(obj) {
  //判斷是否是簡單數據類型,
  if (typeof obj == "object") {
    //複雜數據類型
    var result = obj.constructor == Array ? [] : {};
    for (let i in obj) {
      result[i] =
        typeof obj[i] == "object" && obj[i] !== null
          ? deepClone4(obj[i])
          : obj[i];
    }
  } else {
    //簡單數據類型 直接 == 賦值
    var result = obj;
  }
  return result;
}

推薦使用 deepClone2()

7. 函數的防抖與節流 ?

防抖

所謂防抖,就是指觸發事件後在 n 秒內函數只能執行一次,如果在 n 秒內又觸發了事件,則會重新計算函數執行時間。(防誤觸)

// 延緩執行
function debounce(func, wait) {
  var timeout;

  return function() {
    var context = this;
    var args = arguments;
    console.log(args);
    console.log(func);
    if (timeout) clearTimeout(timeout);

    timeout = setTimeout(function() {
      func.apply(context, args);
    }, wait);
  };
}
// 立即執行
function debounce(func, wait) {
  var timeout;

  return function() {
    var context = this;
    var args = arguments;

    if (timeout) clearTimeout(timeout);

    var callNow = !timeout;
    timeout = setTimeout(function() {
      timeout = null;
    }, wait);

    if (callNow) func.apply(context, args);
  };
}

節流

所謂節流,就是指連續觸發事件但是在 n 秒中只執行一次函數。(限制流量)

// 時間戳
function throttle(func, wait) {
  var previous = 0;

  return function() {
    var now = Date.now();
    var context = this;
    var args = arguments;
    if (now - previous > wait) {
      func.apply(context, args);
      previous = now;
    }
  };
}
// 定時器
function throttle(func, wait) {
  var timeout;

  return function() {
    var context = this;
    var args = arguments;
    if (!timeout) {
      timeout = setTimeout(function() {
        timeout = null;
        func.apply(context, args);
      }, wait);
    }
  };
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章