2022秋招前端面試題(九)(附答案)

發(fā)布訂閱模式(事件總線)

描述:實現(xiàn)一個發(fā)布訂閱模式,擁有 on, emit, once, off 方法

class EventEmitter {
    constructor() {
        // 包含所有監(jiān)聽器函數(shù)的容器對象
        // 內(nèi)部結(jié)構(gòu): {msg1: [listener1, listener2], msg2: [listener3]}
        this.cache = {};
    }
    // 實現(xiàn)訂閱
    on(name, callback) {
        if(this.cache[name]) {
            this.cache[name].push(callback);
        }
        else {
            this.cache[name] = [callback];
        }
    }
    // 刪除訂閱
    off(name, callback) {
        if(this.cache[name]) {
            this.cache[name] = this.cache[name].filter(item => item !== callback);
        }
        if(this.cache[name].length === 0) delete this.cache[name];
    }
    // 只執(zhí)行一次訂閱事件
    once(name, callback) {
        callback();
        this.off(name, callback);
    }
    // 觸發(fā)事件
    emit(name, ...data) {
        if(this.cache[name]) {
            // 創(chuàng)建副本,如果回調(diào)函數(shù)內(nèi)繼續(xù)注冊相同事件,會造成死循環(huán)
            let tasks = this.cache[name].slice();
            for(let fn of tasks) {
                fn(...data);
            }
        }
    }
}
復(fù)制代碼

原型修改、重寫

function Person(name) {
    this.name = name
}
// 修改原型
Person.prototype.getName = function() {}
var p = new Person('hello')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // true
// 重寫原型
Person.prototype = {
    getName: function() {}
}
var p = new Person('hello')
console.log(p.__proto__ === Person.prototype)        // true
console.log(p.__proto__ === p.constructor.prototype) // false
復(fù)制代碼

可以看到修改原型的時候p的構(gòu)造函數(shù)不是指向Person了,因為直接給Person的原型對象直接用對象賦值時,它的構(gòu)造函數(shù)指向的了根構(gòu)造函數(shù)Object,所以這時候p.constructor === Object ,而不是p.constructor === Person。要想成立,就要用constructor指回來:

Person.prototype = {
    getName: function() {}
}
var p = new Person('hello')
p.constructor = Person
console.log(p.__proto__ === Person.prototype)        // true
console.log(p.__proto__ === p.constructor.prototype) // true

復(fù)制代碼

為什么 0.1 + 0.2 != 0.3,請詳述理由

因為 JS 采用 IEEE 754 雙精度版本(64位),并且只要采用 IEEE 754 的語言都有該問題。

我們都知道計算機表示十進制是采用二進制表示的,所以 0.1 在二進制表示為

// (0011) 表示循環(huán)
0.1 = 2^-4 * 1.10011(0011)
復(fù)制代碼

那么如何得到這個二進制的呢,我們可以來演算下

小數(shù)算二進制和整數(shù)不同。乘法計算時,只計算小數(shù)位,整數(shù)位用作每一位的二進制,并且得到的第一位為最高位。所以我們得出 0.1 = 2^-4 * 1.10011(0011),那么 0.2 的演算也基本如上所示,只需要去掉第一步乘法,所以得出 0.2 = 2^-3 * 1.10011(0011)。

回來繼續(xù)說 IEEE 754 雙精度。六十四位中符號位占一位,整數(shù)位占十一位,其余五十二位都為小數(shù)位。因為 0.10.2 都是無限循環(huán)的二進制了,所以在小數(shù)位末尾處需要判斷是否進位(就和十進制的四舍五入一樣)。

所以 2^-4 * 1.10011...001 進位后就變成了 2^-4 * 1.10011(0011 * 12次)010 。那么把這兩個二進制加起來會得出 2^-2 * 1.0011(0011 * 11次)0100 , 這個值算成十進制就是 0.30000000000000004

下面說一下原生解決辦法,如下代碼所示

parseFloat((0.1 + 0.2).toFixed(10))
復(fù)制代碼

事件流

事件流是網(wǎng)頁元素接收事件的順序,"DOM2級事件"規(guī)定的事件流包括三個階段:事件捕獲階段、處于目標階段、事件冒泡階段。
首先發(fā)生的事件捕獲,為截獲事件提供機會。然后是實際的目標接受事件。最后一個階段是時間冒泡階段,可以在這個階段對事件做出響應(yīng)。
雖然捕獲階段在規(guī)范中規(guī)定不允許響應(yīng)事件,但是實際上還是會執(zhí)行,所以有兩次機會獲取到目標對象。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>事件冒泡</title>
</head>
<body>
    <div>
        <p id="parEle">我是父元素    <span id="sonEle">我是子元素</span></p>
    </div>
</body>
</html>
<script type="text/javascript">
var sonEle = document.getElementById('sonEle');
var parEle = document.getElementById('parEle');parEle.addEventListener('click', function () {    alert('父級 冒泡');}, false);parEle.addEventListener('click', function () {    alert('父級 捕獲');}, true);sonEle.addEventListener('click', function () {    alert('子級冒泡');}, false);sonEle.addEventListener('click', function () {    alert('子級捕獲');}, true);

</script>
復(fù)制代碼

當容器元素及嵌套元素,即在捕獲階段又在冒泡階段調(diào)用事件處理程序時:事件按DOM事件流的順序執(zhí)行事件處理程序:

  • 父級捕獲
  • 子級捕獲
  • 子級冒泡
  • 父級冒泡

且當事件處于目標階段時,事件調(diào)用順序決定于綁定事件的書寫順序,按上面的例子為,先調(diào)用冒泡階段的事件處理程序,再調(diào)用捕獲階段的事件處理程序。依次alert出“子集冒泡”,“子集捕獲”。

事件是如何實現(xiàn)的?

基于發(fā)布訂閱模式,就是在瀏覽器加載的時候會讀取事件相關(guān)的代碼,但是只有實際等到具體的事件觸發(fā)的時候才會執(zhí)行。

比如點擊按鈕,這是個事件(Event),而負責(zé)處理事件的代碼段通常被稱為事件處理程序(Event Handler),也就是「啟動對話框的顯示」這個動作。

在 Web 端,我們常見的就是 DOM 事件:

  • DOM0 級事件,直接在 html 元素上綁定 on-event,比如 onclick,取消的話,dom.onclick = null,同一個事件只能有一個處理程序,后面的會覆蓋前面的。
  • DOM2 級事件,通過 addEventListener 注冊事件,通過 removeEventListener 來刪除事件,一個事件可以有多個事件處理程序,按順序執(zhí)行,捕獲事件和冒泡事件
  • DOM3級事件,增加了事件類型,比如 UI 事件,焦點事件,鼠標事件

JS 隱式轉(zhuǎn)換,顯示轉(zhuǎn)換

一般非基礎(chǔ)類型進行轉(zhuǎn)換時會先調(diào)用 valueOf,如果 valueOf 無法返回基本類型值,就會調(diào)用 toString

字符串和數(shù)字

  • "+" 操作符,如果有一個為字符串,那么都轉(zhuǎn)化到字符串然后執(zhí)行字符串拼接
  • "-" 操作符,轉(zhuǎn)換為數(shù)字,相減 (-a, a * 1 a/1) 都能進行隱式強制類型轉(zhuǎn)換
[] + {} 和 {} + []
復(fù)制代碼

布爾值到數(shù)字

  • 1 + true = 2
  • 1 + false = 1

轉(zhuǎn)換為布爾值

  • for 中第二個
  • while
  • if
  • 三元表達式
  • || (邏輯或) && (邏輯與)左邊的操作數(shù)

符號

  • 不能被轉(zhuǎn)換為數(shù)字
  • 能被轉(zhuǎn)換為布爾值(都是 true)
  • 可以被轉(zhuǎn)換成字符串 "Symbol(cool)"

寬松相等和嚴格相等

寬松相等允許進行強制類型轉(zhuǎn)換,而嚴格相等不允許

字符串與數(shù)字

轉(zhuǎn)換為數(shù)字然后比較

其他類型與布爾類型

  • 先把布爾類型轉(zhuǎn)換為數(shù)字,然后繼續(xù)進行比較

對象與非對象

  • 執(zhí)行對象的 ToPrimitive(對象)然后繼續(xù)進行比較

假值列表

  • undefined
  • null
  • false
  • +0, -0, NaN
  • ""

IE 兼容

  • attchEvent('on' + type, handler)
  • detachEvent('on' + type, handler)

代碼輸出結(jié)果

function SuperType(){
    this.property = true;
}

SuperType.prototype.getSuperValue = function(){
    return this.property;
};

function SubType(){
    this.subproperty = false;
}

SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
    return this.subproperty;
};

var instance = new SubType();
console.log(instance.getSuperValue());
復(fù)制代碼

輸出結(jié)果:true

實際上,這段代碼就是在實現(xiàn)原型鏈繼承,SubType繼承了SuperType,本質(zhì)是重寫了SubType的原型對象,代之以一個新類型的實例。SubType的原型被重寫了,所以instance.constructor指向的是SuperType。具體如下:

基于 Localstorage 設(shè)計一個 1M 的緩存系統(tǒng),需要實現(xiàn)緩存淘汰機制

設(shè)計思路如下:

  • 存儲的每個對象需要添加兩個屬性:分別是過期時間和存儲時間。
  • 利用一個屬性保存系統(tǒng)中目前所占空間大小,每次存儲都增加該屬性。當該屬性值大于 1M 時,需要按照時間排序系統(tǒng)中的數(shù)據(jù),刪除一定量的數(shù)據(jù)保證能夠存儲下目前需要存儲的數(shù)據(jù)。
  • 每次取數(shù)據(jù)時,需要判斷該緩存數(shù)據(jù)是否過期,如果過期就刪除。

以下是代碼實現(xiàn),實現(xiàn)了思路,但是可能會存在 Bug,但是這種設(shè)計題一般是給出設(shè)計思路和部分代碼,不會需要寫出一個無問題的代碼

class Store {
  constructor() {
    let store = localStorage.getItem('cache')
    if (!store) {
      store = {
        maxSize: 1024 * 1024,
        size: 0
      }
      this.store = store
    } else {
      this.store = JSON.parse(store)
    }
  }
  set(key, value, expire) {
    this.store[key] = {
      date: Date.now(),
      expire,
      value
    }
    let size = this.sizeOf(JSON.stringify(this.store[key]))
    if (this.store.maxSize < size + this.store.size) {
      console.log('超了-----------');
      var keys = Object.keys(this.store);
      // 時間排序
      keys = keys.sort((a, b) => {
        let item1 = this.store[a], item2 = this.store[b];
        return item2.date - item1.date;
      });
      while (size + this.store.size > this.store.maxSize) {
        let index = keys[keys.length - 1]
        this.store.size -= this.sizeOf(JSON.stringify(this.store[index]))
        delete this.store[index]
      }
    }
    this.store.size += size

    localStorage.setItem('cache', JSON.stringify(this.store))
  }
  get(key) {
    let d = this.store[key]
    if (!d) {
      console.log('找不到該屬性');
      return
    }
    if (d.expire > Date.now) {
      console.log('過期刪除');
      delete this.store[key]
      localStorage.setItem('cache', JSON.stringify(this.store))
    } else {
      return d.value
    }
  }
  sizeOf(str, charset) {
    var total = 0,
      charCode,
      i,
      len;
    charset = charset ? charset.toLowerCase() : '';
    if (charset === 'utf-16' || charset === 'utf16') {
      for (i = 0, len = str.length; i < len; i++) {
        charCode = str.charCodeAt(i);
        if (charCode <= 0xffff) {
          total += 2;
        } else {
          total += 4;
        }
      }
    } else {
      for (i = 0, len = str.length; i < len; i++) {
        charCode = str.charCodeAt(i);
        if (charCode <= 0x007f) {
          total += 1;
        } else if (charCode <= 0x07ff) {
          total += 2;
        } else if (charCode <= 0xffff) {
          total += 3;
        } else {
          total += 4;
        }
      }
    }
    return total;
  }
}
復(fù)制代碼

10 個 Ajax 同時發(fā)起請求,全部返回展示結(jié)果,并且至多允許三次失敗,說出設(shè)計思路

這個問題相信很多人會第一時間想到 Promise.all ,但是這個函數(shù)有一個局限在于如果失敗一次就返回了,直接這樣實現(xiàn)會有點問題,需要變通下。以下是兩種實現(xiàn)思路

// 以下是不完整代碼,著重于思路 非 Promise 寫法
let successCount = 0
let errorCount = 0
let datas = []
ajax(url, (res) => {
     if (success) {
         success++
         if (success + errorCount === 10) {
             console.log(datas)
         } else {
             datas.push(res.data)
         }
     } else {
         errorCount++
         if (errorCount > 3) {
            // 失敗次數(shù)大于3次就應(yīng)該報錯了
             throw Error('失敗三次')
         }
     }
})
// Promise 寫法
let errorCount = 0
let p = new Promise((resolve, reject) => {
    if (success) {
         resolve(res.data)
     } else {
         errorCount++
         if (errorCount > 3) {
            // 失敗次數(shù)大于3次就應(yīng)該報錯了
            reject(error)
         } else {
             resolve(error)
         }
     }
})
Promise.all([p]).then(v => {
  console.log(v);
});
復(fù)制代碼

說一下原型鏈和原型鏈的繼承吧

  • 所有普通的 [[Prototype]] 鏈最終都會指向內(nèi)置的 Object.prototype,其包含了 JavaScript 中許多通用的功能
  • 為什么能創(chuàng)建 “類”,借助一種特殊的屬性:所有的函數(shù)默認都會擁有一個名為 prototype 的共有且不可枚舉的屬性,它會指向另外一個對象,這個對象通常被稱為函數(shù)的原型
function Person(name) {
  this.name = name;
}

Person.prototype.constructor = Person
復(fù)制代碼
  • 在發(fā)生 new 構(gòu)造函數(shù)調(diào)用時,會將創(chuàng)建的新對象的 [[Prototype]] 鏈接到 Person.prototype 指向的對象,這個機制就被稱為原型鏈繼承

  • 方法定義在原型上,屬性定義在構(gòu)造函數(shù)上

  • 首先要說一下 JS 原型和實例的關(guān)系:每個構(gòu)造函數(shù) (constructor)都有一個原型對象(prototype),這個原型對象包含一個指向此構(gòu)造函數(shù)的指針屬性,通過 new 進行構(gòu)造函數(shù)調(diào)用生成的實例,此實例包含一個指向原型對象的指針,也就是通過 [[Prototype]] 鏈接到了這個原型對象

  • 然后說一下 JS 中屬性的查找:當我們試圖引用實例對象的某個屬性時,是按照這樣的方式去查找的,首先查找實例對象上是否有這個屬性,如果沒有找到,就去構(gòu)造這個實例對象的構(gòu)造函數(shù)的 prototype 所指向的對象上去查找,如果還找不到,就從這個 prototype 對象所指向的構(gòu)造函數(shù)的 prototype 原型對象上去查找

  • 什么是原型鏈:這樣逐級查找形似一個鏈條,且通過 [[Prototype]] 屬性鏈接,所以被稱為原型鏈

  • 什么是原型鏈繼承,類比類的繼承:當有兩個構(gòu)造函數(shù) A 和 B,將一個構(gòu)造函數(shù) A 的原型對象的,通過其 [[Prototype]] 屬性鏈接到另外一個 B 構(gòu)造函數(shù)的原型對象時,這個過程被稱之為原型繼承。

** 標準答案更正確的解釋**

什么是原型鏈?

當對象查找一個屬性的時候,如果沒有在自身找到,那么就會查找自身的原型,如果原型還沒有找到,那么會繼續(xù)查找原型的原型,直到找到 Object.prototype 的原型時,此時原型為 null,查找停止。
這種通過 通過原型鏈接的逐級向上的查找鏈被稱為原型鏈

什么是原型繼承?

一個對象可以使用另外一個對象的屬性或者方法,就稱之為繼承。具體是通過將這個對象的原型設(shè)置為另外一個對象,這樣根據(jù)原型鏈的規(guī)則,如果查找一個對象屬性且在自身不存在時,就會查找另外一個對象,相當于一個對象可以使用另外一個對象的屬性和方法了。

如果new一個箭頭函數(shù)的會怎么樣

箭頭函數(shù)是ES6中的提出來的,它沒有prototype,也沒有自己的this指向,更不可以使用arguments參數(shù),所以不能New一個箭頭函數(shù)。

new操作符的實現(xiàn)步驟如下:

  1. 創(chuàng)建一個對象
  2. 將構(gòu)造函數(shù)的作用域賦給新對象(也就是將對象的proto屬性指向構(gòu)造函數(shù)的prototype屬性)
  3. 指向構(gòu)造函數(shù)中的代碼,構(gòu)造函數(shù)中的this指向該對象(也就是為這個對象添加屬性和方法)
  4. 返回新的對象

所以,上面的第二、三步,箭頭函數(shù)都是沒有辦法執(zhí)行的。

實現(xiàn)數(shù)組原型方法

forEach

語法:arr.forEach(callback(currentValue [, index [, array]])[, thisArg])

參數(shù):

callback:為數(shù)組中每個元素執(zhí)行的函數(shù),該函數(shù)接受1-3個參數(shù)currentValue: 數(shù)組中正在處理的當前元素index(可選): 數(shù)組中正在處理的當前元素的索引array(可選): forEach() 方法正在操作的數(shù)組 thisArg(可選): 當執(zhí)行回調(diào)函數(shù) callback 時,用作 this 的值。

返回值:undefined

Array.prototype.forEach1 = function(callback, thisArg) {
    if(this == null) {
        throw new TypeError('this is null or not defined');
    }
    if(typeof callback !== "function") {
        throw new TypeError(callback + 'is not a function');
    }
    // 創(chuàng)建一個新的 Object 對象。該對象將會包裹(wrapper)傳入的參數(shù) this(當前數(shù)組)。
    const O = Object(this);
    // O.length >>> 0 無符號右移 0 位
    // 意義:為了保證轉(zhuǎn)換后的值為正整數(shù)。
    // 其實底層做了 2 層轉(zhuǎn)換,第一是非 number 轉(zhuǎn)成 number 類型,第二是將 number 轉(zhuǎn)成 Uint32 類型
    const len = O.length >>> 0;
    let k = 0;
    while(k < len) {
        if(k in O) {
            callback.call(thisArg, O[k], k, O);
        }
        k++;
    }
}
復(fù)制代碼

map

語法: arr.map(callback(currentValue [, index [, array]])[, thisArg])

參數(shù):與 forEach() 方法一樣

返回值:一個由原數(shù)組每個元素執(zhí)行回調(diào)函數(shù)的結(jié)果組成的新數(shù)組。

Array.prototype.map1 = function(callback, thisArg) {
    if(this == null) {
        throw new TypeError('this is null or not defined');
    }
    if(typeof callback !== "function") {
        throw new TypeError(callback + 'is not a function');
    }
    const O = Object(this); 
    const len = O.length >>> 0;
    let newArr = [];  // 返回的新數(shù)組
    let k = 0;
    while(k < len) {
        if(k in O) {
            newArr[k] = callback.call(thisArg, O[k], k, O);
        }
        k++;
    }
    return newArr;
}
復(fù)制代碼

filter

語法:arr.filter(callback(element [, index [, array]])[, thisArg])

參數(shù):

callback: 用來測試數(shù)組的每個元素的函數(shù)。返回 true 表示該元素通過測試,保留該元素,false 則不保留。它接受以下三個參數(shù):element、index、array,參數(shù)的意義與 forEach 一樣。

thisArg(可選): 執(zhí)行 callback 時,用于 this 的值。

返回值:一個新的、由通過測試的元素組成的數(shù)組,如果沒有任何數(shù)組元素通過測試,則返回空數(shù)組。

Array.prototype.filter1 = function(callback, thisArg) {
    if(this == null) {
        throw new TypeError('this is null or not defined');
    }
    if(typeof callback !== "function") {
        throw new TypeError(callback + 'is not a function');
    }
    const O = Object(this); 
    const len = O.length >>> 0;
    let newArr = [];  // 返回的新數(shù)組
    let k = 0;
    while(k < len) {
        if(k in O) {
            if(callback.call(thisArg, O[k], k, O)) {
                newArr.push(O[k]);
            }
        }
        k++;
    }
    return newArr;
}
復(fù)制代碼

some

語法:arr.some(callback(element [, index [, array]])[, thisArg])

參數(shù):

callback: 用來測試數(shù)組的每個元素的函數(shù)。接受以下三個參數(shù):element、index、array,參數(shù)的意義與 forEach 一樣。

thisArg(可選): 執(zhí)行 callback 時,用于 this 的值。
返回值:數(shù)組中有至少一個元素通過回調(diào)函數(shù)的測試就會返回 true;所有元素都沒有通過回調(diào)函數(shù)的測試返回值才會為 false

Array.prototype.some1 = function(callback, thisArg) {
    if(this == null) {
        throw new TypeError('this is null or not defined');
    }
    if(typeof callback !== "function") {
        throw new TypeError(callback + 'is not a function');
    }
    const O = Object(this); 
    const len = O.length >>> 0;
    let k = 0;
    while(k < len) {
        if(k in O) {
            if(callback.call(thisArg, O[k], k, O)) {
                return true
            }
        }
        k++;
    }
    return false;
}
復(fù)制代碼

reduce

語法:arr.reduce(callback(preVal, curVal[, curIndex [, array]])[, initialValue])

參數(shù):

callback: 一個 “reducer” 函數(shù),包含四個參數(shù):

preVal:上一次調(diào)用 callback 時的返回值。在第一次調(diào)用時,若指定了初始值 initialValue,其值則為 initialValue,否則為數(shù)組索引為 0 的元素 array[0]

curVal:數(shù)組中正在處理的元素。在第一次調(diào)用時,若指定了初始值 initialValue,其值則為數(shù)組索引為 0 的元素 array[0],否則為 array[1]。

curIndex(可選):數(shù)組中正在處理的元素的索引。若指定了初始值 initialValue,則起始索引號為 0,否則從索引 1 起始。

array(可選):用于遍歷的數(shù)組。
initialValue(可選): 作為第一次調(diào)用 callback 函數(shù)時參數(shù) preVal 的值。若指定了初始值 initialValue,則 curVal 則將使用數(shù)組第一個元素;否則 preVal 將使用數(shù)組第一個元素,而 curVal 將使用數(shù)組第二個元素。
返回值:使用 “reducer” 回調(diào)函數(shù)遍歷整個數(shù)組后的結(jié)果。

Array.prototype.reduce1 = function(callback, initialValue) {
    if(this == null) {
        throw new TypeError('this is null or not defined');
    }
    if(typeof callback !== "function") {
        throw new TypeError(callback + 'is not a function');
    }
    const O = Object(this);
    const len = O.length >>> 0;
    let k = 0;
    let accumulator = initialValue;
    // 如果第二個參數(shù)為undefined的情況下,則數(shù)組的第一個有效值(非empty)作為累加器的初始值
    if(accumulator === undefined) {
        while(k < len && !(k in O)) {
            k++;
        }
        // 如果超出數(shù)組界限還沒有找到累加器的初始值,則TypeError
        if(k >= len) {
            throw new TypeError('Reduce of empty array with no initial value');
        }
        accumulator = O[k++];
    }
    while(k < len) {
        if(k in O) {
            accumulator = callback(accumulator, O[k], k, O);
        }
        k++;
    }
    return accumulator;
}
復(fù)制代碼

類數(shù)組轉(zhuǎn)化為數(shù)組的方法

題目描述:類數(shù)組擁有 length 屬性 可以使用下標來訪問元素 但是不能使用數(shù)組的方法 如何把類數(shù)組轉(zhuǎn)化為數(shù)組?

實現(xiàn)代碼如下:

const arrayLike=document.querySelectorAll('div')

// 1.擴展運算符
[...arrayLike]
// 2.Array.from
Array.from(arrayLike)
// 3.Array.prototype.slice
Array.prototype.slice.call(arrayLike)
// 4.Array.apply
Array.apply(null, arrayLike)
// 5.Array.prototype.concat
Array.prototype.concat.apply([], arrayLike)

復(fù)制代碼

new 操作符

題目描述:手寫 new 操作符實現(xiàn)

實現(xiàn)代碼如下:

function myNew(fn, ...args) {
  let obj = Object.create(fn.prototype);
  let res = fn.call(obj, ...args);
  if (res && (typeof res === "object" || typeof res === "function")) {
    return res;
  }
  return obj;
}
用法如下:
// // function Person(name, age) {
// //   this.name = name;
// //   this.age = age;
// // }
// // Person.prototype.say = function() {
// //   console.log(this.age);
// // };
// // let p1 = myNew(Person, "lihua", 18);
// // console.log(p1.name);
// // console.log(p1);
// // p1.say();
復(fù)制代碼

寫代碼:實現(xiàn)函數(shù)能夠深度克隆基本類型

淺克?。?/p>

function shallowClone(obj) {
  let cloneObj = {};

  for (let i in obj) {
    cloneObj[i] = obj[i];
  }

  return cloneObj;
}
復(fù)制代碼

深克?。?/p>

  • 考慮基礎(chǔ)類型
  • 引用類型
    • RegExp、Date、函數(shù) 不是 JSON 安全的
    • 會丟失 constructor,所有的構(gòu)造函數(shù)都指向 Object
    • 破解循環(huán)引用
function deepCopy(obj) {
  if (typeof obj === 'object') {
    var result = obj.constructor === Array ? [] : {};

    for (var i in obj) {
      result[i] = typeof obj[i] === 'object' ? deepCopy(obj[i]) : obj[i];
    }
  } else {
    var result = obj;
  }

  return result;
}
復(fù)制代碼

ES6中模板語法與字符串處理

ES6 提出了“模板語法”的概念。在 ES6 以前,拼接字符串是很麻煩的事情:

var name = 'css'   
var career = 'coder' 
var hobby = ['coding', 'writing']
var finalString = 'my name is ' + name + ', I work as a ' + career + ', I love ' + hobby[0] + ' and ' + hobby[1]
復(fù)制代碼

僅僅幾個變量,寫了這么多加號,還要時刻小心里面的空格和標點符號有沒有跟錯地方。但是有了模板字符串,拼接難度直線下降:

var name = 'css'   
var career = 'coder' 
var hobby = ['coding', 'writing']
var finalString = `my name is ${name}, I work as a ${career} I love ${hobby[0]} and ${hobby[1]}`
復(fù)制代碼

字符串不僅更容易拼了,也更易讀了,代碼整體的質(zhì)量都變高了。這就是模板字符串的第一個優(yōu)勢——允許用${}的方式嵌入變量。但這還不是問題的關(guān)鍵,模板字符串的關(guān)鍵優(yōu)勢有兩個:

  • 在模板字符串中,空格、縮進、換行都會被保留
  • 模板字符串完全支持“運算”式的表達式,可以在${}里完成一些計算

基于第一點,可以在模板字符串里無障礙地直接寫 html 代碼:

let list = `    <ul>        <li>列表項1</li>        <li>列表項2</li>    </ul>`;
console.log(message); // 正確輸出,不存在報錯
復(fù)制代碼

基于第二點,可以把一些簡單的計算和調(diào)用丟進 ${} 來做:

function add(a, b) {
  const finalString = `${a} + $ = ${a+b}`
  console.log(finalString)
}
add(1, 2) // 輸出 '1 + 2 = 3'
復(fù)制代碼

除了模板語法外, ES6中還新增了一系列的字符串方法用于提升開發(fā)效率:

(1)存在性判定:在過去,當判斷一個字符/字符串是否在某字符串中時,只能用 indexOf > -1 來做?,F(xiàn)在 ES6 提供了三個方法:includes、startsWith、endsWith,它們都會返回一個布爾值來告訴你是否存在。

  • includes:判斷字符串與子串的包含關(guān)系:
const son = 'haha' 
const father = 'xixi haha hehe'
father.includes(son) // true
復(fù)制代碼
  • startsWith:判斷字符串是否以某個/某串字符開頭:
const father = 'xixi haha hehe'
father.startsWith('haha') // false
father.startsWith('xixi') // true
復(fù)制代碼
  • endsWith:判斷字符串是否以某個/某串字符結(jié)尾:
const father = 'xixi haha hehe'
  father.endsWith('hehe') // true
復(fù)制代碼

(2)自動重復(fù):可以使用 repeat 方法來使同一個字符串輸出多次(被連續(xù)復(fù)制多次):

const sourceCode = 'repeat for 3 times;'
const repeated = sourceCode.repeat(3) 
console.log(repeated) // repeat for 3 times;repeat for 3 times;repeat for 3 times;
復(fù)制代碼

iframe 有那些優(yōu)點和缺點?

iframe 元素會創(chuàng)建包含另外一個文檔的內(nèi)聯(lián)框架(即行內(nèi)框架)。

優(yōu)點:

  • 用來加載速度較慢的內(nèi)容(如廣告)
  • 可以使腳本可以并行下載
  • 可以實現(xiàn)跨子域通信

缺點:

  • iframe 會阻塞主頁面的 onload 事件
  • 無法被一些搜索引擎索識別
  • 會產(chǎn)生很多頁面,不容易管理

const對象的屬性可以修改嗎

const保證的并不是變量的值不能改動,而是變量指向的那個內(nèi)存地址不能改動。對于基本類型的數(shù)據(jù)(數(shù)值、字符串、布爾值),其值就保存在變量指向的那個內(nèi)存地址,因此等同于常量。

但對于引用類型的數(shù)據(jù)(主要是對象和數(shù)組)來說,變量指向數(shù)據(jù)的內(nèi)存地址,保存的只是一個指針,const只能保證這個指針是固定不變的,至于它指向的數(shù)據(jù)結(jié)構(gòu)是不是可變的,就完全不能控制了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容