DD每週前端七題詳解-第四期

語言: CN / TW / HK

DD每週前端七題詳解-第四期

系列介紹

你盼世界,我盼望你無bug。Hello 大家好!我是霖呆呆!

呆呆每週都會分享七道前端題給大家,系列名稱就是「DD每週七題」。

系列的形式主要是:3道JavaScript + 2道HTML + 2道CSS,幫助我們大家一起鞏固前端基礎。

所有題目也都會整合至 LinDaiDai/niubility-coding-jsissues中,歡迎大家提供更好的解題思路,謝謝大家😁。

一起來看看本週的七道題吧。

正題

一、following function return?

以下程式碼輸出什麼?

function getName () {
  return
  {
    name: 'LinDaiDai'
  }
}
console.log(getName())
複製程式碼

這道題其實涉及到了JavaScript中的一個名為ASI的機制,全名Automatic Semicolon Insertion,好吧,不要整的那麼高大上了,其實就是自動插入分號的機制。

按照ECMAScript標準,一些 特定語句(statement) 必須以分號結尾。分號代表這段語句的終止。但是有時候為了方便,這些分號是有可以省略的。這種情況下直譯器會自己判斷語句該在哪裡終止。這種行為被叫做“自動插入分號”,簡稱ASI (Automatic Semicolon Insertion) 。實際上分號並沒有真的**入,這只是個便於解釋的形象說法。

也就是說這道題在執行的時候,會在return關鍵字後面自動插入一個分號,所以這道題就相當於是這樣:

function getName () {
  return;
  {
    name: 'LinDaiDai'
  }
}
console.log(getName())
複製程式碼

因此最終的結果也就是undefined

github.com/LinDaiDai/n…

二、實現一個pipe函式

(題目來源:30-seconds-of-interviews)

如下所示,實現一個pipe函式:

const square = v => v * v
const double = v => v * 2
const addOne = v => v + 1
const res = pipe(square, double, addOne)
console.log(res(3)) // 19; addOne(double(square(3)))
複製程式碼

首先看到這道題,pipe是可以接收任意個數的函式,並且返回的是一個新的函式res

(1) pipe基本結構

那麼我們可以得出pipe的基本結構是這樣的:

const pipe = function (...fns) {
  return function (param) {}
}
複製程式碼

它本身是一個函式,然後我們可以利用...fns獲取到所有傳入的函式引數square、double這些。

之後它會返回一個函式,且這個函式中是可以接收引數param的。

(2) 返回的函式

接下來的邏輯主要就是在於返回的函式上了,在這個返回的函式中,我們需要對param進行層層處理。

OK👌,這很容易就讓人想到了...reduce...

我們可以對fns函式陣列使用reduce,之後reduce的初始值為傳入的引數param

讓我們一起來看看最終的程式碼:

const pipe = function (...fns) {
  return function (param) {
    return fns.reduce((pre, fn) => {
      return fn(pre)
    }, param)
  }
}
複製程式碼

最終返回的是經過fns陣列中所有函式處理過的值。

當然,我們也可以用簡潔點的寫法:

const pipe = (...fns) => param => fns.reduce((pre, fn) => fn(pre), param)
複製程式碼

這樣就得到了我們想要的pipe函數了:

const square = v => v * v
const double = v => v * 2
const addOne = v => v + 1
const pipe = (...fns) => param => fns.reduce((pre, fn) => fn(pre), param)
const res = pipe(square, double, addOne)
console.log(res(3)) // 19; addOne(double(square(3)))
複製程式碼

github.com/LinDaiDai/n…

三、Babel是如何編譯Class的?

(參考來源:相學長-你的Tree-Shaking並沒什麼卵用)

就拿下面的類來說:

class Person {
  constructor ({ name }) {
    this.name = name
    this.getSex = function () {
      return 'boy'
    }
  }
  getName () {
    return this.name
  }
  static getLook () {
    return 'sunshine'
  }
}
複製程式碼

如果你對Class或者裡面的static還不熟悉的話可得先看看呆呆的這篇文章了:《【何不三連】比繼承家業還要簡單的JS繼承題-封裝篇(牛刀小試)》

當我們在使用babel的這些plugin或者使用preset的時候,有一個配置屬性loose它預設是為false,在這樣的條件下:

Class編譯後:

  • 總體來說Class會被封裝成一個IIFE立即執行函式
  • 立即執行函式返回的是一個與類同名的建構函式
  • 例項屬性和方法定義在建構函式內(如namegetSex())
  • 類內部宣告的屬性方法(getName)和靜態屬性方法(getLook)是會被Object.defineProperty所處理,將其可列舉屬性設定為false

(下面的程式碼看著好像很長,其實劃分一下並沒有什麼東西的)

編譯後的程式碼:

"use strict";

function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}

function _defineProperties(target, props) {
  for (var i = 0; i < props.length; i++) {
    var descriptor = props[i];
    descriptor.enumerable = descriptor.enumerable || false;
    descriptor.configurable = true;
    if ("value" in descriptor) descriptor.writable = true;
    Object.defineProperty(target, descriptor.key, descriptor);
  }
}

function _createClass(Constructor, protoProps, staticProps) {
  if (protoProps) _defineProperties(Constructor.prototype, protoProps);
  if (staticProps) _defineProperties(Constructor, staticProps);
  return Constructor;
}

var Person = /*#__PURE__*/ (function () {
  function Person(_ref) {
    var name = _ref.name;

    _classCallCheck(this, Person);

    this.name = name;

    this.getSex = function () {
      return "boy";
    };
  }

  _createClass(
    Person,
    [
      {
        key: "getName",
        value: function getName() {
          return this.name;
        },
      },
    ],
    [
      {
        key: "getLook",
        value: function getLook() {
          return "sunshine";
        },
      },
    ]
  );

  return Person;
})();
複製程式碼

為什麼Babel對於類的處理會使用Object.defineProperty這種形式呢?它和直接使用原型鏈有什麼不同嗎?

  • 通過原型鏈宣告的屬性和方法是可列舉的,也就是可以被for...of...搜尋到
  • 而類內部宣告的方法是不可列舉的

所以,babel為了符合ES6真正的語義,編譯類時採取了Object.defineProperty來定義原型方法。

但是可以通過設定babelloose模式(寬鬆模式)為true,它會不嚴格遵循ES6的語義,而採取更符合我們平常編寫程式碼時的習慣去編譯程式碼,在.babelrc中可以如下設定:

{
  "presets": [["env", { "loose": true }]]
}
複製程式碼

比如上述的Person類的屬性方法將會編譯成直接在原型鏈上宣告方法:

"use strict";

var Person = /*#__PURE__*/function () {
  function Person(_ref) {
    var name = _ref.name;
    this.name = name;

    this.getSex = function () {
      return 'boy';
    };
  }

  var _proto = Person.prototype;

  _proto.getName = function getName() {
    return this.name;
  };

  Person.getLook = function getLook() {
    return 'sunshine';
  };

  return Person;
}();
複製程式碼

總結

  • 當使用Babel編譯時預設的loosefalse,即非寬鬆模式

  • 無論哪種模式,轉換後的定義在類內部的屬性方法是被定義在建構函式的原型物件上的;靜態屬性被定義到建構函式上

  • 只不過非寬鬆模式時,這些屬性方法會被_createClass函式處理,函式內通過Object.defineProperty()設定屬性的可列舉值enumerablefalse

  • 由於在_createClass函式內使用了Object,所以非寬鬆模式下是會產生副作用的,而寬鬆模式下不會。

  • webpack中的UglifyJS依舊還是會將寬鬆模式認為是有副作用的,而rollup程式流程分析的功能,可以更好的判斷程式碼是否真正產生副作用,所以它會認為寬鬆模式沒有副作用。

    (副作用大致理解為:一個函式會、或者可能會對函式外部變數產生影響的行為。)

github.com/LinDaiDai/n…

四、JS三種載入方式的區別

(答案參考來源:前端效能優化-頁面載入渲染優化)

正常模式

這種情況下 JS 會阻塞瀏覽器,瀏覽器必須等待 index.js 載入和執行完畢才能去做其它事情。

<script src="index.js"></script>
複製程式碼

async(非同步) 模式

async 模式下,JS 不會阻塞瀏覽器做任何其它的事情。它的載入是非同步的,當它載入結束,JS 指令碼會立即執行。

<script async src="index.js"></script>
複製程式碼

defer(延緩) 模式

defer 模式下,JS 的載入是非同步的,執行是被推遲的。等整個文件解析完成、DOMContentLoaded 事件即將被觸發時,被標記了 defer 的 JS 檔案才會開始依次執行。

<script defer src="index.js"></script>
複製程式碼

從應用的角度來說,一般當我們的指令碼與 DOM 元素和其它指令碼之間的依賴關係不強時,我們會選用 async;當指令碼依賴於 DOM 元素和其它指令碼的執行結果時,我們會選用 defer。

github.com/LinDaiDai/n…

五、如何讓<p>測試 空格</p>這兩個詞之間的空格變大?

(題目來源:https://github.com/haizlin/fe-interview/issues/2440)

這道題的意思是說,原本有一段HTML程式碼如下:

<p>測試 空格</p>
複製程式碼

"測試""空格"兩個詞之間有一個空格,然後如何將這個空格變大。

這邊有這麼兩種方法:

  • 通過給p標籤設定word-spacing,將這個屬性設定成自己想要的值。
  • 將這個空格用一個span標籤包裹起來,然後設定span標籤的letter-spacing或者word-spacing

我分別用letter-spacingword-spacing來處理了pspan標籤:

<style>
  .p-letter-spacing {
    letter-spacing: 10px;
  }
  .p-word-spacing {
    word-spacing: 10px;
  }
  .span-letter-spacing {
    letter-spacing: 10px;
  }
  .span-word-spacing {
    word-spacing: 10px;
  }
</style>
<body>
  <p>測試 空格</p>
  <p class="p-letter-spacing">測試 空格</p>
  <p class="p-word-spacing">測試 空格</p>
  <p>測試<span class="span-letter-spacing"> </span>空格</p>
  <p>測試<span class="span-word-spacing"> </span>空格</p>
</body>
複製程式碼

讓我們一起來看看效果:

大家可以看到效果,我用letter-spacingword-spacing處理p標籤,是會呈現不同的效果的,letter-spacing把中文之間的間隙也放大了,而word-spacing則不放大中文之間的間隙。

span標籤中只有一個空格,所以letter-spacingword-spacing效果一樣。

因此我們可以得出letter-spacingword-spacing的結論:

  • letter-spacingword-spacing這兩個屬性都用來新增他們對應的元素中的空白。
  • letter-spacing新增字母之間的空白,而word-spacing新增每個單詞之間的空白。
  • word-spacing對中文無效。

github.com/LinDaiDai/n…

六、如何解決inline-block空白問題?

原本的程式碼為:

<style>
.sub {
  background: hotpink;
  display: inline-block;
}
</style>
<body>
  <div class="super">
    <div class="sub">
      孩子
    </div>
    <div class="sub">
      孩子
    </div>
    <div class="sub">
      孩子
    </div>
  </div>
</body>
複製程式碼

效果為:

可以看到每個孩子之間都會有一個空白。inline-block元素間有空格或是換行,因此產生了間隙。

解決辦法:

  • (1) 刪除html中的空白:不要讓元素之間換行:

    <div class="super">
      <div class="sub">
        孩子
      </div><div class="sub">
        孩子
      </div><div class="sub">
        孩子
      </div>
    </div>
    複製程式碼
  • (2) 設定負的邊距:你可以用負邊距來補齊空白。但你需要調整font-size,因為空白的寬度與這個屬性有關係。例如下面這個例子:

    .sub {
      background: hotpink;
      display: inline-block;
      font-size:16px;
      margin-left: -0.4em;
    }
    複製程式碼
  • (3) 給父級設定font-size: 0:不管空白多大,由於空白跟font-size的關係,設定這個屬性即可把空白的寬度設定為0。但是如果你的子級有字的話,也得單獨給子級設定字型大小。

  • (4) 註釋

    <div class="super">
      <div class="sub">
        孩子
      </div><!--
      --><div class="sub sub2">
        孩子
      </div><!--
      --><div class="sub">
        孩子
      </div>
    </div>
    複製程式碼

github.com/LinDaiDai/n…

七、脫離文件流是不是指該元素從DOM樹中脫離?

並不會,DOM樹是HTML頁面的層級結構,指的是元素與元素之間的關係,例如包裹我的是我的父級,與我並列的是我的兄弟級,類似這樣的關係稱之為層級結構。

而文件流則類似於排隊,我本應該在隊伍中的,然而我脫離了隊伍,但是我與我的父親,兄弟,兒子的關係還在。

github.com/LinDaiDai/n…

參考文章

知識無價,支援原創。

參考文章:

後語

你盼世界,我盼望你無bug。這篇文章就介紹到這裡。

您每週也許會花48小時的時間在工作💻上,會花49小時的時間在睡覺😴上,也許還可以再花20分鐘的時間在呆呆的7道題上,日積月累,我相信我們都能見證彼此的成長😊。

什麼?你問我為什麼系列的名字叫DD?因為呆呆呀,哈哈😄。

喜歡霖呆呆的小夥還希望可以關注霖呆呆的公眾號 LinDaiDai 或者掃一掃下面的二維碼👇👇👇。

我會不定時的更新一些前端方面的知識內容以及自己的原創文章🎉

你的鼓勵就是我持續創作的主要動力 😊。

本文使用 mdnice 排版