修煉秘籍

引言

當(dāng)下鲜屏,正面臨著近幾年來的最嚴(yán)重的互聯(lián)網(wǎng)寒冬,聽得最多的一句話便是:相見于江湖~??国拇。縮減 HC惯殊、裁員不絕于耳酱吝,大家都是人心惶惶,年前如此土思,年后想必肯定又是一場更為慘烈的江湖廝殺务热。但博主始終相信,寒冬之中己儒,人才更是尤為珍貴崎岂。只要有過硬的操作和裝備,在逆風(fēng)局下闪湾,同樣也能來一波收割翻盤冲甘。

博主也是年前經(jīng)歷了一番廝殺,最終拿到多家大廠的 offer。在閉關(guān)修煉的過程中江醇,自己整理出了一套面試秘籍供自己反復(fù)研究濒憋,后來給了多位有需要的兄臺,均表示相當(dāng)靠譜陶夜,理應(yīng)在這寒冬之中回報(bào)于社會凛驮。于是決定花點(diǎn)精力整理成文,讓大家能比較系統(tǒng)的反復(fù)學(xué)習(xí)条辟,快速提升自己黔夭。

面試固然有技巧,但絕不是偽造與吹流弊羽嫡,通過一段短時(shí)間沉下心來閉關(guān)修煉本姥,出山收割,步入大廠厂僧,薪資翻番扣草,豈不爽哉???

修煉原則

想必大家很厭煩筆試和考察知識點(diǎn)颜屠。因?yàn)槠鋵?shí)在平時(shí)實(shí)戰(zhàn)中辰妙,講究的是開發(fā)效率,很少會去刻意記下一些細(xì)節(jié)和深挖知識點(diǎn)甫窟,腦海中都是一些分散的知識點(diǎn)密浑,無法系統(tǒng)性地關(guān)聯(lián)成網(wǎng),一直處于似曾相識的狀態(tài)粗井。不知道多少人和博主一樣尔破,至今每次寫阻止冒泡都需要谷歌一番如何拼寫。??浇衬。

以如此的狀態(tài)懒构,定然是無法在面試的戰(zhàn)場上縱橫的。其實(shí)面試就猶如考試耘擂,大家回想下高考之前所做的事胆剧,無非就是 理解系統(tǒng)性關(guān)聯(lián)記憶。本秘籍的知識點(diǎn)較多醉冤,花點(diǎn)時(shí)間一個(gè)個(gè)理解并記憶后秩霍,自然也就融會貫通,無所畏懼蚁阳。

由于本秘籍為了便于記憶铃绒,快速達(dá)到應(yīng)試狀態(tài),類似于復(fù)習(xí)知識大綱螺捐。知識點(diǎn)會盡量的精簡與提煉知識脈絡(luò)颠悬,并不去展開深入細(xì)節(jié)矮燎,面面俱到。有興趣或者有疑問的童鞋可以自行谷歌下對應(yīng)知識點(diǎn)的詳細(xì)內(nèi)容椿疗。??

CSS

1. 盒模型

頁面渲染時(shí)独撇,dom 元素所采用的 布局模型债热∽烫瘢可通過box-sizing進(jìn)行設(shè)置挑围。根據(jù)計(jì)算寬高的區(qū)域可分為:

  • content-box (W3C 標(biāo)準(zhǔn)盒模型)
  • border-box (IE 盒模型)
  • padding-box
  • margin-box (瀏覽器未實(shí)現(xiàn))

2. BFC

塊級格式化上下文,是一個(gè)獨(dú)立的渲染區(qū)域铝条,讓處于 BFC 內(nèi)部的元素與外部的元素相互隔離靖苇,使內(nèi)外元素的定位不會相互影響。

IE 下為 Layout班缰,可通過 zoom:1 觸發(fā)

  • 觸發(fā)條件:

    • 根元素
    • position: absolute/fixed
    • display: inline-block / table
    • float 元素
    • ovevflow !== visible
  • 規(guī)則:

    • 屬于同一個(gè) BFC 的兩個(gè)相鄰 Box 垂直排列
    • 屬于同一個(gè) BFC 的兩個(gè)相鄰 Box 的 margin 會發(fā)生重疊
    • BFC 中子元素的 margin box 的左邊贤壁, 與包含塊 (BFC) border box 的左邊相接觸 (子元素 absolute 除外)
    • BFC 的區(qū)域不會與 float 的元素區(qū)域重疊
    • 計(jì)算 BFC 的高度時(shí),浮動子元素也參與計(jì)算
    • 文字層不會被浮動層覆蓋埠忘,環(huán)繞于周圍
  • 應(yīng)用:

    • 阻止margin重疊
    • 可以包含浮動元素 —— 清除內(nèi)部浮動(清除浮動的原理是兩個(gè)div都位于同一個(gè) BFC 區(qū)域之中)
    • 自適應(yīng)兩欄布局
    • 可以阻止元素被浮動元素覆蓋

3.層疊上下文

元素提升為一個(gè)比較特殊的圖層脾拆,在三維空間中 (z 軸) 高出普通元素一等。

  • 觸發(fā)條件

    • 根層疊上下文(html)
    • position
    • css3 屬性
      • flex
      • transform
      • opacity
      • filter
      • will-change
      • -webkit-overflow-scrolling
  • 層疊等級:層疊上下文在 z 軸上的排序

    • 在同一層疊上下文中莹妒,層疊等級才有意義
    • z-index的優(yōu)先級最高
76ad2eda-85c8-f1b1-561f-f9976d62a07d.png
76ad2eda-85c8-f1b1-561f-f9976d62a07d.png

4. 居中布局

  • 水平居中

    • 行內(nèi)元素: text-align: center
    • 塊級元素: margin: 0 auto
    • absolute + transform
    • flex + justify-content: center
  • 垂直居中

    • line-height: height
    • absolute + transform
    • flex + align-items: center
    • table
  • 水平垂直居中

    • absolute + transform
    • flex + justify-content + align-items

5. 選擇器優(yōu)先級

  • !important > 行內(nèi)樣式 > #id > .class > tag > * > 繼承 > 默認(rèn)
  • 選擇器 從右往左 解析

6.去除浮動影響名船,防止父級高度塌陷

  • 通過增加尾元素清除浮動
    • :after / <br> : clear: both
  • 創(chuàng)建父級 BFC
  • 父級設(shè)置高度

7.link 與 @import 的區(qū)別

  • link功能較多,可以定義 RSS旨怠,定義 Rel 等作用渠驼,而@import只能用于加載 css
  • 當(dāng)解析到link時(shí),頁面會同步加載所引的 css鉴腻,而@import所引用的 css 會等到頁面加載完才被加載
  • @import需要 IE5 以上才能使用
  • link可以使用 js 動態(tài)引入迷扇,@import不行

8. CSS 預(yù)處理器(Sass/Less/Postcss)

CSS 預(yù)處理器的原理: 是將類 CSS 語言通過 Webpack 編譯 轉(zhuǎn)成瀏覽器可讀的真正 CSS。在這層編譯之上爽哎,便可以賦予 CSS 更多更強(qiáng)大的功能蜓席,常用功能:

  • 嵌套
  • 變量
  • 循環(huán)語句
  • 條件語句
  • 自動前綴
  • 單位轉(zhuǎn)換
  • mixin 復(fù)用

面試中一般不會重點(diǎn)考察該點(diǎn),一般介紹下自己在實(shí)戰(zhàn)項(xiàng)目中的經(jīng)驗(yàn)即可~

9.CSS 動畫

  • transition: 過渡動畫

    • transition-property: 屬性
    • transition-duration: 間隔
    • transition-timing-function: 曲線
    • transition-delay: 延遲
    • 常用鉤子: transitionend
  • animation / keyframes

    • animation-name: 動畫名稱课锌,對應(yīng)@keyframes
    • animation-duration: 間隔
    • animation-timing-function: 曲線
    • animation-delay: 延遲
    • animation-iteration-count: 次數(shù)
      • infinite: 循環(huán)動畫
    • animation-direction: 方向
      • alternate: 反向播放
    • animation-fill-mode: 靜止模式
      • forwards: 停止時(shí)瓮床,保留最后一幀
      • backwards: 停止時(shí),回到第一幀
      • both: 同時(shí)運(yùn)用 forwards / backwards
    • 常用鉤子: animationend
  • 動畫屬性: 盡量使用動畫屬性進(jìn)行動畫产镐,能擁有較好的性能表現(xiàn)

    • translate
    • scale
    • rotate
    • skew
    • opacity
    • color

經(jīng)驗(yàn)

通常,CSS 并不是重點(diǎn)的考察領(lǐng)域踢步,但這其實(shí)是由于現(xiàn)在國內(nèi)業(yè)界對 CSS 的專注不夠?qū)е碌难⒀牵嬲úW⒂?CSS 的團(tuán)隊(duì)和人才并不多。因此如果能在 CSS 領(lǐng)域有自己的見解和經(jīng)驗(yàn)获印,反而會為相當(dāng)?shù)募臃趾兔摲f而出述雾。

JavaScript

1. 原型 / 構(gòu)造函數(shù) / 實(shí)例

  • 原型(prototype): 一個(gè)簡單的對象,用于實(shí)現(xiàn)對象的 屬性繼承〔C希可以簡單的理解成對象的爹唆缴。在 Firefox 和 Chrome 中,每個(gè)JavaScript對象中都包含一個(gè)__proto__ (非標(biāo)準(zhǔn))的屬性指向它爹(該對象的原型)黍翎,可obj.__proto__進(jìn)行訪問面徽。

  • 構(gòu)造函數(shù): 可以通過new新建一個(gè)對象 的函數(shù)。

  • 實(shí)例: 通過構(gòu)造函數(shù)和new創(chuàng)建出來的對象匣掸,便是實(shí)例趟紊。 實(shí)例通過__proto__指向原型,通過constructor指向構(gòu)造函數(shù)碰酝。

說了一大堆霎匈,大家可能有點(diǎn)懵逼,這里來舉個(gè)栗子送爸,以Object為例铛嘱,我們常用的Object便是一個(gè)構(gòu)造函數(shù),因此我們可以通過它構(gòu)建實(shí)例袭厂。

// 實(shí)例
const instance = new Object();

則此時(shí)墨吓, 實(shí)例為instance, 構(gòu)造函數(shù)為Object,我們知道嵌器,構(gòu)造函數(shù)擁有一個(gè)prototype的屬性指向原型肛真,因此原型為:

// 原型
const prototype = Object.prototype;

這里我們可以來看出三者的關(guān)系:

實(shí)例.__proto__ === 原型

原型.constructor === 構(gòu)造函數(shù)

構(gòu)造函數(shù).prototype === 原型

// 這條線其實(shí)是是基于原型進(jìn)行獲取的,可以理解成一條基于原型的映射線
// 例如:
// const o = new Object()
// o.constructor === Object   --> true
// o.__proto__ = null;
// o.constructor === Object   --> false
實(shí)例.constructor === 構(gòu)造函數(shù)

此處感謝 caihaihong 童鞋的指出爽航。

放大來看蚓让,我畫了張圖供大家徹底理解:

1cbeb6a1-96e6-2c05-4a2d-71213890be36.png
1cbeb6a1-96e6-2c05-4a2d-71213890be36.png

2.原型鏈:

原型鏈?zhǔn)怯稍蛯ο蠼M成,每個(gè)對象都有 __proto__ 屬性讥珍,指向了創(chuàng)建該對象的構(gòu)造函數(shù)的原型历极,__proto__ 將對象連接起來組成了原型鏈。是一個(gè)用來實(shí)現(xiàn)繼承和共享屬性的有限的對象鏈衷佃。

  • 屬性查找機(jī)制: 當(dāng)查找對象的屬性時(shí)趟卸,如果實(shí)例對象自身不存在該屬性,則沿著原型鏈往上一級查找氏义,找到時(shí)則輸出锄列,不存在時(shí),則繼續(xù)沿著原型鏈往上一級查找惯悠,直至最頂級的原型對象Object.prototype邻邮,如還是沒找到,則輸出undefined克婶;

  • 屬性修改機(jī)制: 只會修改實(shí)例對象本身的屬性筒严,如果不存在丹泉,則進(jìn)行添加該屬性,如果需要修改原型的屬性時(shí)鸭蛙,則可以用: b.prototype.x = 2摹恨;但是這樣會造成所有繼承于該對象的實(shí)例的屬性發(fā)生改變。

3. 執(zhí)行上下文(EC)

執(zhí)行上下文可以簡單理解為一個(gè)對象:

  • 它包含三個(gè)部分:

    • 變量對象(VO)
    • 作用域鏈(詞法作用域)
    • this指向
  • 它的類型:

    • 全局執(zhí)行上下文
    • 函數(shù)執(zhí)行上下文
    • eval執(zhí)行上下文
  • 代碼執(zhí)行過程:

    • 創(chuàng)建 全局上下文 (global EC)
    • 全局執(zhí)行上下文 (caller) 逐行 自上而下 執(zhí)行娶视。遇到函數(shù)時(shí)晒哄,函數(shù)執(zhí)行上下文 (callee) 被push到執(zhí)行棧頂層
    • 函數(shù)執(zhí)行上下文被激活,成為 active EC, 開始執(zhí)行函數(shù)中的代碼歇万,caller 被掛起
    • 函數(shù)執(zhí)行完后揩晴,callee 被pop移除出執(zhí)行棧,控制權(quán)交還全局上下文 (caller)贪磺,繼續(xù)執(zhí)行

2.變量對象

變量對象硫兰,是執(zhí)行上下文中的一部分,可以抽象為一種 數(shù)據(jù)作用域寒锚,其實(shí)也可以理解為就是一個(gè)簡單的對象劫映,它存儲著該執(zhí)行上下文中的所有 變量和函數(shù)聲明(不包含函數(shù)表達(dá)式)

活動對象 (AO): 當(dāng)變量對象所處的上下文為 active EC 時(shí)刹前,稱為活動對象泳赋。

3. 作用域

執(zhí)行上下文中還包含作用域鏈。理解作用域之前喇喉,先介紹下作用域祖今。作用域其實(shí)可理解為該上下文中聲明的 變量和聲明的作用范圍〖鸺迹可分為 塊級作用域函數(shù)作用域

特性:

  • 聲明提前: 一個(gè)聲明在函數(shù)體內(nèi)都是可見的, 函數(shù)優(yōu)先于變量
  • 非匿名自執(zhí)行函數(shù)千诬,函數(shù)變量為 只讀 狀態(tài),無法修改
let foo = (function() {
  console.log(1);
})(
  (function foo() {
    foo = 10; // 由于foo在函數(shù)中只為可讀膏斤,因此賦值無效
    console.log(foo);
  })()
);

// 結(jié)果打有彀蟆:  ? foo() { foo = 10 ; console.log(foo) }

4.作用域鏈

我們知道,我們可以在執(zhí)行上下文中訪問到父級甚至全局的變量莫辨,這便是作用域鏈的功勞傲茄。作用域鏈可以理解為一組對象列表,包含 父級和自身的變量對象沮榜,因此我們便能通過作用域鏈訪問到父級里聲明的變量或者函數(shù)盘榨。

  • 由兩部分組成:
    • [[scope]]屬性: 指向父級變量對象和作用域鏈,也就是包含了父級的[[scope]]AO
    • AO: 自身活動對象

如此 [[scopr]]包含[[scope]]蟆融,便自上而下形成一條 鏈?zhǔn)阶饔糜?/strong>草巡。

5. 閉包

閉包屬于一種特殊的作用域,稱為 靜態(tài)作用域振愿。它的定義可以理解為: 父函數(shù)被銷毀 的情況下捷犹,返回出的子函數(shù)的[[scope]]中仍然保留著父級的單變量對象和作用域鏈,因此可以繼續(xù)訪問到父級的變量對象冕末,這樣的函數(shù)稱為閉包萍歉。

  • 閉包會產(chǎn)生一個(gè)很經(jīng)典的問題:

    • 多個(gè)子函數(shù)的[[scope]]都是同時(shí)指向父級,是完全共享的档桃。因此當(dāng)父級的變量對象被修改時(shí)枪孩,所有子函數(shù)都受到影響。
  • 解決:

    • 變量可以通過 函數(shù)參數(shù)的形式 傳入藻肄,避免使用默認(rèn)的[[scope]]向上查找
    • 使用setTimeout包裹蔑舞,通過第三個(gè)參數(shù)傳入
    • 使用 塊級作用域,讓變量成為自己上下文的屬性嘹屯,避免共享

6. script 引入方式:

  • html 靜態(tài)<script>引入
  • js 動態(tài)插入<script>
  • <script defer>: 異步加載攻询,元素解析完成后執(zhí)行
  • <script async>: 異步加載,但執(zhí)行時(shí)會阻塞元素渲染

7. 對象的拷貝

  • 淺拷貝: 以賦值的形式拷貝引用對象州弟,仍指向同一個(gè)地址钧栖,修改時(shí)原對象也會受到影響

    • Object.assign
    • 展開運(yùn)算符(...)
  • 深拷貝: 完全拷貝一個(gè)新對象,修改時(shí)原對象不再受到任何影響

    • JSON.parse(JSON.stringify(obj)): 性能最快
      • 具有循環(huán)引用的對象時(shí)婆翔,報(bào)錯(cuò)
      • 當(dāng)值為函數(shù)拯杠、undefined、或symbol時(shí)啃奴,無法拷貝
    • 遞歸進(jìn)行逐一賦值

8. new 運(yùn)算符的執(zhí)行過程

  • 新生成一個(gè)對象
  • 鏈接到原型: obj.__proto__ = Con.prototype
  • 綁定 this: apply
  • 返回新對象(如果構(gòu)造函數(shù)有自己 retrun 時(shí)潭陪,則返回該值)

9. instanceof 原理

能在實(shí)例的 原型對象鏈 中找到該構(gòu)造函數(shù)的prototype屬性所指向的 原型對象,就返回true最蕾。即:

// __proto__: 代表原型對象鏈
instance.[__proto__...] === instance.constructor.prototype

// return true

10. 代碼的復(fù)用

當(dāng)你發(fā)現(xiàn)任何代碼開始寫第二遍時(shí)依溯,就要開始考慮如何復(fù)用。一般有以下的方式:

  • 函數(shù)封裝
  • 繼承
  • 復(fù)制extend
  • 混入mixin
  • 借用apply/call

11. 繼承

在 JS 中揖膜,繼承通常指的便是 原型鏈繼承誓沸,也就是通過指定原型,并可以通過原型鏈繼承原型上的屬性或者方法壹粟。

  • 最優(yōu)化: 圣杯模式
var inherit = (function(c, p) {
  var F = function() {};
  return function(c, p) {
    F.prototype = p.prototype;
    c.prototype = new F();
    c.uber = p.prototype;
    c.prototype.constructor = c;
  };
})();
  • 使用 ES6 的語法糖 class / extends

12. 類型轉(zhuǎn)換

大家都知道 JS 中在使用運(yùn)算符號或者對比符時(shí)拜隧,會自帶隱式轉(zhuǎn)換,規(guī)則如下:

  • -趁仙、*洪添、/、% :一律轉(zhuǎn)換成數(shù)值后計(jì)算
  • +:
    • 數(shù)字 + 字符串 = 字符串雀费, 運(yùn)算順序是從左到右
    • 數(shù)字 + 對象干奢, 優(yōu)先調(diào)用對象的valueOf -> toString
    • 數(shù)字 + boolean/null -> 數(shù)字
    • 數(shù)字 + undefined -> NaN
  • [1].toString() === '1'
  • {}.toString() === '[object object]'
  • NaN !== NaN+undefined 為 NaN

13. 類型判斷

判斷 Target 的類型盏袄,單單用 typeof 并無法完全滿足忿峻,這其實(shí)并不是 bug薄啥,本質(zhì)原因是 JS 的萬物皆對象的理論。因此要真正完美判斷時(shí)逛尚,我們需要區(qū)分對待:

  • 基本類型(null): 使用 String(null)
  • 基本類型(string / number / boolean / undefined) + function: 直接使用 typeof即可
  • 其余引用類型(Array / Date / RegExp Error): 調(diào)用toString后根據(jù)[object XXX]進(jìn)行判斷

很穩(wěn)的判斷封裝:

let class2type = {};
"Array Date RegExp Object Error"
  .split(" ")
  .forEach(e => (class2type["[object " + e + "]"] = e.toLowerCase()));

function type(obj) {
  if (obj == null) return String(obj);
  return typeof obj === "object"
    ? class2type[Object.prototype.toString.call(obj)] || "object"
    : typeof obj;
}

14. 模塊化

模塊化開發(fā)在現(xiàn)代開發(fā)中已是必不可少的一部分垄惧,它大大提高了項(xiàng)目的可維護(hù)、可拓展和可協(xié)作性绰寞。通常到逊,我們 在瀏覽器中使用 ES6 的模塊化支持,在 Node 中使用 commonjs 的模塊化支持滤钱。

  • 分類:

    • es6: import / export
    • commonjs: require / module.exports / exports
    • amd: require / defined
  • requireimport的區(qū)別

    • require支持 動態(tài)導(dǎo)入觉壶,import不支持,正在提案 (babel 下可支持)
    • require同步 導(dǎo)入件缸,import屬于 異步 導(dǎo)入
    • require值拷貝铜靶,導(dǎo)出值變化不會影響導(dǎo)入值;import指向 內(nèi)存地址停团,導(dǎo)入值會隨導(dǎo)出值而變化

15. 防抖與節(jié)流

防抖與節(jié)流函數(shù)是一種最常用的 高頻觸發(fā)優(yōu)化方式旷坦,能對性能有較大的幫助。

  • 防抖 (debounce): 將多次高頻操作優(yōu)化為只在最后一次執(zhí)行佑稠,通常使用的場景是:用戶輸入秒梅,只需再輸入完成后做一次輸入校驗(yàn)即可。
function debounce(fn, wait, immediate) {
  let timer = null;

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

    if (immediate && !timer) {
      fn.apply(context, args);
    }

    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(context, args);
    }, wait);
  };
}
  • 節(jié)流(throttle): 每隔一段時(shí)間后執(zhí)行一次舌胶,也就是降低頻率捆蜀,將高頻操作優(yōu)化成低頻操作,通常使用場景: 滾動條事件 或者 resize 事件幔嫂,通常每隔 100~500 ms 執(zhí)行一次即可辆它。
function throttle(fn, wait, immediate) {
  let timer = null;
  let callNow = immediate;

  return function() {
    let context = this,
      args = arguments;

    if (callNow) {
      fn.apply(context, args);
      callNow = false;
    }

    if (!timer) {
      timer = setTimeout(() => {
        fn.apply(context, args);
        timer = null;
      }, wait);
    }
  };
}

16. 函數(shù)執(zhí)行改變 this

由于 JS 的設(shè)計(jì)原理: 在函數(shù)中,可以引用運(yùn)行環(huán)境中的變量履恩。因此就需要一個(gè)機(jī)制來讓我們可以在函數(shù)體內(nèi)部獲取當(dāng)前的運(yùn)行環(huán)境锰茉,這便是this

因此要明白 this 指向切心,其實(shí)就是要搞清楚 函數(shù)的運(yùn)行環(huán)境飒筑,說人話就是,誰調(diào)用了函數(shù)绽昏。例如:

  • obj.fn()协屡,便是 obj 調(diào)用了函數(shù),既函數(shù)中的 this === obj
  • fn()全谤,這里可以看成 window.fn()肤晓,因此 this === window

但這種機(jī)制并不完全能滿足我們的業(yè)務(wù)需求,因此提供了三種方式可以手動修改 this 的指向:

  • call: fn.call(target, 1, 2)
  • apply: fn.apply(target, [1, 2])
  • bind: fn.bind(target)(1,2)

17. ES6/ES7

由于 Babel 的強(qiáng)大和普及,現(xiàn)在 ES6/ES7 基本上已經(jīng)是現(xiàn)代化開發(fā)的必備了补憾。通過新的語法糖漫萄,能讓代碼整體更為簡潔和易讀。

  • 聲明

    • let / const: 塊級作用域盈匾、不存在變量提升卷胯、暫時(shí)性死區(qū)、不允許重復(fù)聲明
    • const: 聲明常量威酒,無法修改
  • 解構(gòu)賦值

  • class / extend: 類聲明與繼承

  • Set / Map: 新的數(shù)據(jù)結(jié)構(gòu)

  • 異步解決方案:

    • Promise的使用與實(shí)現(xiàn)

    • generator:

      • yield: 暫停代碼
      • next(): 繼續(xù)執(zhí)行代碼
      function* helloWorld() {
        yield "hello";
        yield "world";
        return "ending";
      }
      
      const generator = helloWorld();
      
      generator.next(); // { value: 'hello', done: false }
      
      generator.next(); // { value: 'world', done: false }
      
      generator.next(); // { value: 'ending', done: true }
      
      generator.next(); // { value: undefined, done: true }
      
  • await / async: 是generator的語法糖, babel 中是基于promise實(shí)現(xiàn)挺峡。

    async function getUserByAsync() {
      let user = await fetchUser();
      return user;
    }
    
    const user = await getUserByAsync();
    console.log(user);
    

18. AST

抽象語法樹 (Abstract Syntax Tree)葵孤,是將代碼逐字母解析成 樹狀對象 的形式。這是語言之間的轉(zhuǎn)換橱赠、代碼語法檢查尤仍,代碼風(fēng)格檢查,代碼格式化狭姨,代碼高亮宰啦,代碼錯(cuò)誤提示,代碼自動補(bǔ)全等等的基礎(chǔ)饼拍。例如:


function square(n){
return n \* n
}

通過解析轉(zhuǎn)化成的AST如下圖:

4c2a1231-bf3e-fa66-0129-113d6f906310.png
4c2a1231-bf3e-fa66-0129-113d6f906310.png

19. babel 編譯原理

  • babylon 將 ES6/ES7 代碼解析成 AST
  • babel-traverse 對 AST 進(jìn)行遍歷轉(zhuǎn)譯赡模,得到新的 AST
  • 新 AST 通過 babel-generator 轉(zhuǎn)換成 ES5

20. 函數(shù)柯里化

在一個(gè)函數(shù)中,首先填充幾個(gè)參數(shù)师抄,然后再返回一個(gè)新的函數(shù)的技術(shù)漓柑,稱為函數(shù)的柯里化。通尺端保可用于在不侵入函數(shù)的前提下辆布,為函數(shù) 預(yù)置通用參數(shù),供多次重復(fù)調(diào)用茶鉴。

const add = function add(x) {
  return function(y) {
    return x + y;
  };
};

const add1 = add(1);

add1(2) === 3;
add1(20) === 21;

21. 數(shù)組(array)

  • map: 遍歷數(shù)組锋玲,返回回調(diào)返回值組成的新數(shù)組

  • forEach: 無法break,可以用try/catchthrow new Error來停止

  • filter: 過濾

  • some: 有一項(xiàng)返回true涵叮,則整體為true

  • every: 有一項(xiàng)返回false惭蹂,則整體為false

  • join: 通過指定連接符生成字符串

  • push / pop: 末尾推入和彈出,改變原數(shù)組围肥, 返回推入/彈出項(xiàng)

  • unshift / shift: 頭部推入和彈出剿干,改變原數(shù)組,返回操作項(xiàng)

  • sort(fn) / reverse: 排序與反轉(zhuǎn)穆刻,改變原數(shù)組

  • concat: 連接數(shù)組置尔,不影響原數(shù)組, 淺拷貝

  • slice(start, end): 返回截?cái)嗪蟮男聰?shù)組氢伟,不改變原數(shù)組

  • splice(start, number, value...): 返回刪除元素組成的數(shù)組榜轿,value 為插入項(xiàng)幽歼,改變原數(shù)組

  • indexOf / lastIndexOf(value, fromIndex): 查找數(shù)組項(xiàng),返回對應(yīng)的下標(biāo)

  • reduce / reduceRight(fn(prev, cur)谬盐, defaultPrev): 兩兩執(zhí)行甸私,prev 為上次化簡函數(shù)的return值,cur 為當(dāng)前值(從第二項(xiàng)開始)

  • 數(shù)組亂序:

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
arr.sort(function() {
  return Math.random() - 0.5;
});
  • 數(shù)組拆解: flat: [1,[2,3]] --> [1, 2, 3]
Array.prototype.flat = function() {
  this.toString()
    .split(",")
    .map(item => +item);
};

22. 函數(shù)式編程(Shopee 面試題

const removeCharacter = str => str.replace(/[^\w\s]/g, " ");
const toUpper = str => str.toUpperCase();
const split = str => str.split(" ");
const filterEmpty = arr => arr.filter(str => !!str.trim().length);

const fn = compose(
  removeCharacter,
  toUpper,
  split,
  filterEmpty
);

fn("Hello, to8to World!"); // => ["HELLO","TO8TO","WORLD"]

// 請實(shí)現(xiàn)`compose`方法來達(dá)到效果

參考答案:

const compose = (...args) => {
  return str => args.reduce((prev, next) => next.call(null, prev), str);
};

瀏覽器

1. 跨標(biāo)簽頁通訊

不同標(biāo)簽頁間的通訊飞傀,本質(zhì)原理就是去運(yùn)用一些可以 共享的中間介質(zhì)皇型,因此比較常用的有以下方法:

  • 通過父頁面window.open()和子頁面postMessage

    • 異步下,通過 window.open('about: blank')tab.location.href = '*'
  • 設(shè)置同域下共享的localStorage與監(jiān)聽window.onstorage

    • 重復(fù)寫入相同的值無法觸發(fā)
    • 會受到瀏覽器隱身模式等的限制
  • 設(shè)置共享cookie與不斷輪詢臟檢查(setInterval)

  • 借助服務(wù)端或者中間層實(shí)現(xiàn)

2. 瀏覽器架構(gòu)

  • 用戶界面
  • 主進(jìn)程
  • 內(nèi)核
    • 渲染引擎
    • JS 引擎
      • 執(zhí)行棧
    • 事件觸發(fā)線程
      • 消息隊(duì)列
        • 微任務(wù)
        • 宏任務(wù)
    • 網(wǎng)絡(luò)異步線程
    • 定時(shí)器線程

3. 瀏覽器下事件循環(huán)(Event Loop)

事件循環(huán)是指: 執(zhí)行一個(gè)宏任務(wù)砸烦,然后執(zhí)行清空微任務(wù)列表弃鸦,循環(huán)再執(zhí)行宏任務(wù),再清微任務(wù)列表

  • 微任務(wù) microtask(jobs): promise / ajax / Object.observe(該方法已廢棄)
  • 宏任務(wù) macrotask(task): setTimout / script / IO / UI Rendering

面試題(Shopee 面試題)

題目:


實(shí)現(xiàn)一個(gè)LazyMan幢痘,可以按照以下方式調(diào)用:
LazyMan(“Hank”)輸出:
Hi! This is Hank!
?
LazyMan(“Hank”).sleep(10).eat(“dinner”)輸出
Hi! This is Hank!
//等待10秒..
Wake up after 10
Eat dinner~
?
LazyMan(“Hank”).eat(“dinner”).eat(“supper”)輸出
Hi This is Hank!
Eat dinner~
Eat supper~
?
LazyMan(“Hank”).sleepFirst(5).eat(“supper”)輸出
//等待5秒
Wake up after 5
Hi This is Hank!
Eat supper


function Lazyman ( name ) {
    return new _Lazyman ( name );
}

class _Lazyman{
    constructor ( name ) {
        this.tasks = [];//設(shè)置任務(wù)隊(duì)列
        let task = (name => () => {
            console.log ( `Hi! This is ${name} !` );
            this.next ();
        }) ( name );
        this.tasks.push ( task );
        //通過settimeout的方法唬格,將執(zhí)行函數(shù)放入下一個(gè)事件隊(duì)列中,從而達(dá)到先注冊事件颜说,后執(zhí)行的目的

        setTimeout ( () => {
            this.next ();
        }, 0 );

    }
    //尾調(diào)用函數(shù)购岗,一個(gè)任務(wù)執(zhí)行完然后再調(diào)用下一個(gè)任務(wù)
    next () {
        let task = this.tasks.shift ();
        task && task ();
    }

    eat ( food ) {
        let task = (food => () => {
            console.log ( `Eat ${food}` );
            this.next ();
        }) ( food );
        this.tasks.push ( task );
        return this;
    }

    sleep ( time ) {
        let task = (time => () => {
            setTimeout ( () => {
                console.log ( `Wake up after ${time} s!` );
                this.next ();
            }, time * 1000 )
        }) ( time );
        this.tasks.push ( task );
        return this;
    }

    sleepFirst ( time ) {
        let task = (time => () => {
            setTimeout ( () => {
                console.log ( `Wake up after ${time} s!` );
                this.next ();
            }, time * 1000 )
        }) ( time );
        this.tasks.unshift ( task );//sleepFirst函數(shù)需要最先執(zhí)行,所以我們需要在任務(wù)隊(duì)列前面放入门粪,然后再執(zhí)行后面的任務(wù)
        return this;
    }

}
}

4. 從輸入 url 到展示的過程

  • DNS 解析
  • TCP 三次握手
  • 發(fā)送請求喊积,分析 url,設(shè)置請求報(bào)文(頭玄妈,主體)
  • 服務(wù)器返回請求的文件 (html)
  • 瀏覽器渲染
    • HTML parser --> DOM Tree
      • 標(biāo)記化算法注服,進(jìn)行元素狀態(tài)的標(biāo)記
      • dom 樹構(gòu)建
    • CSS parser --> Style Tree
      • 解析 css 代碼,生成樣式樹
    • attachment --> Render Tree
      • 結(jié)合 dom 樹 與 style 樹措近,生成渲染樹
    • layout: 布局
    • GPU painting: 像素繪制頁面

5. 重繪與回流

當(dāng)元素的樣式發(fā)生變化時(shí)溶弟,瀏覽器需要觸發(fā)更新,重新繪制元素瞭郑。這個(gè)過程中辜御,有兩種類型的操作,即重繪與回流屈张。

  • 重繪(repaint): 當(dāng)元素樣式的改變不影響布局時(shí)擒权,瀏覽器將使用重繪對元素進(jìn)行更新,此時(shí)由于只需要 UI 層面的重新像素繪制阁谆,因此 損耗較少

  • 回流(reflow): 當(dāng)元素的尺寸碳抄、結(jié)構(gòu)或觸發(fā)某些屬性時(shí),瀏覽器會重新渲染頁面场绿,稱為回流剖效。此時(shí),瀏覽器需要重新經(jīng)過計(jì)算,計(jì)算后還需要重新頁面布局璧尸,因此是較重的操作咒林。會觸發(fā)回流的操作:

    • 頁面初次渲染
    • 瀏覽器窗口大小改變
    • 元素尺寸、位置爷光、內(nèi)容發(fā)生改變
    • 元素字體大小變化
    • 添加或者刪除可見的 dom 元素
    • 激活 CSS 偽類(例如::hover)
    • 查詢某些屬性或調(diào)用某些方法
      • clientWidth垫竞、clientHeight、clientTop蛀序、clientLeft
      • offsetWidth欢瞪、offsetHeight、offsetTop徐裸、offsetLeft
      • scrollWidth引有、scrollHeight、scrollTop倦逐、scrollLeft
      • getComputedStyle()
      • getBoundingClientRect()
      • scrollTo()

回流必定觸發(fā)重繪,重繪不一定觸發(fā)回流宫补。重繪的開銷較小檬姥,回流的代價(jià)較高。

最佳實(shí)踐:

  • css

    • 避免使用table布局
    • 將動畫效果應(yīng)用到position屬性為absolutefixed的元素上
  • javascript

    • 避免頻繁操作樣式粉怕,可匯總后統(tǒng)一 一次修改
    • 盡量使用class進(jìn)行樣式修改
    • 減少dom的增刪次數(shù)健民,可使用 字符串 或者 documentFragment 一次性插入
    • 極限優(yōu)化時(shí),修改樣式可將其display: none后修改
    • 避免多次觸發(fā)上面提到的那些會觸發(fā)回流的方法贫贝,可以的話盡量用 變量存住

6. 存儲

我們經(jīng)常需要對業(yè)務(wù)中的一些數(shù)據(jù)進(jìn)行存儲秉犹,通常可以分為 短暫性存儲 和 持久性儲存稚晚。

  • 短暫性的時(shí)候崇堵,我們只需要將數(shù)據(jù)存在內(nèi)存中,只在運(yùn)行時(shí)可用

  • 持久性存儲客燕,可以分為 瀏覽器端 與 服務(wù)器端

    • 瀏覽器:
      • cookie: 通常用于存儲用戶身份鸳劳,登錄狀態(tài)等
        • http 中自動攜帶, 體積上限為 4K也搓, 可自行設(shè)置過期時(shí)間
      • localStorage / sessionStorage: 長久儲存/窗口關(guān)閉刪除赏廓, 體積限制為 4~5M
      • indexDB
    • 服務(wù)器:
      • 分布式緩存 redis
      • 數(shù)據(jù)庫

7. Web Worker

現(xiàn)代瀏覽器為JavaScript創(chuàng)造的 多線程環(huán)境“剩可以新建并將部分任務(wù)分配到worker線程并行運(yùn)行幔摸,兩個(gè)線程可 獨(dú)立運(yùn)行,互不干擾颤练,可通過自帶的 消息機(jī)制 相互通信既忆。

基本用法:

// 創(chuàng)建 worker
const worker = new Worker("work.js");

// 向主進(jìn)程推送消息
worker.postMessage("Hello World");

// 監(jiān)聽主進(jìn)程來的消息
worker.onmessage = function(event) {
  console.log("Received message " + event.data);
};

限制:

  • 同源限制
  • 無法使用 document / window / alert / confirm
  • 無法加載本地資源

8. V8 垃圾回收機(jī)制

垃圾回收: 將內(nèi)存中不再使用的數(shù)據(jù)進(jìn)行清理,釋放出內(nèi)存空間。V8 將內(nèi)存分成 新生代空間老生代空間尿贫。

  • 新生代空間: 用于存活較短的對象
    • 又分成兩個(gè)空間: from 空間 與 to 空間
    • Scavenge GC 算法: 當(dāng) from 空間被占滿時(shí)电媳,啟動 GC 算法
      • 存活的對象從 from space 轉(zhuǎn)移到 to space
      • 清空 from space
      • from space 與 to space 互換
      • 完成一次新生代 GC
  • 老生代空間: 用于存活時(shí)間較長的對象
    • 從 新生代空間 轉(zhuǎn)移到 老生代空間 的條件
      • 經(jīng)歷過一次以上 Scavenge GC 的對象
      • 當(dāng) to space 體積超過 25%
    • 標(biāo)記清除算法: 標(biāo)記存活的對象,未被標(biāo)記的則被釋放
      • 增量標(biāo)記: 小模塊標(biāo)記庆亡,在代碼執(zhí)行間隙執(zhí)匾乓,GC 會影響性能
      • 并發(fā)標(biāo)記(最新技術(shù)): 不阻塞 js 執(zhí)行
    • 壓縮算法: 將內(nèi)存中清除后導(dǎo)致的碎片化對象往內(nèi)存堆的一端移動,解決 內(nèi)存的碎片化

9. 內(nèi)存泄露

  • 意外的全局變量: 無法被回收
  • 定時(shí)器: 未被正確關(guān)閉又谋,導(dǎo)致所引用的外部變量無法被釋放
  • 事件監(jiān)聽: 沒有正確銷毀 (低版本瀏覽器可能出現(xiàn))
  • 閉包: 會導(dǎo)致父級中的變量無法被釋放
  • dom 引用: dom 元素被刪除時(shí)拼缝,內(nèi)存中的引用未被正確清空

可用 chrome 中的 timeline 進(jìn)行內(nèi)存標(biāo)記,可視化查看內(nèi)存的變化情況彰亥,找出異常點(diǎn)咧七。

服務(wù)端與網(wǎng)絡(luò)

1. http/https 協(xié)議

  • 1.0 協(xié)議缺陷:

    • 無法復(fù)用鏈接,完成即斷開任斋,重新慢啟動和 TCP 3 次握手
    • head of line blocking: 線頭阻塞继阻,導(dǎo)致請求之間互相影響
  • 1.1 改進(jìn):

    • 長連接(默認(rèn) keep-alive),復(fù)用
    • host 字段指定對應(yīng)的虛擬站點(diǎn)
    • 新增功能:
      • 斷點(diǎn)續(xù)傳
      • 身份認(rèn)證
      • 狀態(tài)管理
      • cache 緩存
        • Cache-Control
        • Expires
        • Last-Modified
        • Etag
  • 2.0:

    • 多路復(fù)用
    • 二進(jìn)制分幀層: 應(yīng)用層和傳輸層之間
    • 首部壓縮
    • 服務(wù)端推送
  • https: 較為安全的網(wǎng)絡(luò)傳輸協(xié)議

    • 證書(公鑰)
    • SSL 加密
    • 端口 443
  • TCP:

    • 三次握手
    • 四次揮手
    • 滑動窗口: 流量控制
    • 擁塞處理
      • 慢開始
      • 擁塞避免
      • 快速重傳
      • 快速恢復(fù)
  • 緩存策略: 可分為 強(qiáng)緩存協(xié)商緩存

    • Cache-Control/Expires: 瀏覽器判斷緩存是否過期废酷,未過期時(shí)瘟檩,直接使用強(qiáng)緩存,Cache-Control 的 max-age 優(yōu)先級高于 Expires

    • 當(dāng)緩存已經(jīng)過期時(shí)澈蟆,使用協(xié)商緩存

      • 唯一標(biāo)識方案: Etag(response 攜帶) & If-None-Match(request 攜帶墨辛,上一次返回的 Etag): 服務(wù)器判斷資源是否被修改,
      • 最后一次修改時(shí)間: Last-Modified(response) & If-Modified-Since (request趴俘,上一次返回的 Last-Modified)
        • 如果一致蚤吹,則直接返回 304 通知瀏覽器使用緩存
        • 如不一致俗壹,則服務(wù)端返回新的資源
    • Last-Modified 缺點(diǎn):

      • 周期性修改,但內(nèi)容未變時(shí),會導(dǎo)致緩存失效
      • 最小粒度只到 s矿酵, s 以內(nèi)的改動無法檢測到
    • Etag 的優(yōu)先級高于 Last-Modified

2. 常見狀態(tài)碼

  • 1xx: 接受琐驴,繼續(xù)處理
  • 200: 成功豺憔,并返回?cái)?shù)據(jù)
  • 201: 已創(chuàng)建
  • 202: 已接受
  • 203: 成為迅腔,但未授權(quán)
  • 204: 成功,無內(nèi)容
  • 205: 成功柜某,重置內(nèi)容
  • 206: 成功嗽元,部分內(nèi)容
  • 301: 永久移動,重定向
  • 302: 臨時(shí)移動喂击,可使用原有 URI
  • 304: 資源未修改剂癌,可使用緩存
  • 305: 需代理訪問
  • 400: 請求語法錯(cuò)誤
  • 401: 要求身份認(rèn)證
  • 403: 拒絕請求
  • 404: 資源不存在
  • 500: 服務(wù)器錯(cuò)誤

3. get / post

  • get: 緩存、請求長度受限翰绊、會被歷史保存記錄
    • 無副作用(不修改資源)佩谷,冪等(請求次數(shù)與資源無關(guān))的場景
  • post: 安全旁壮、大數(shù)據(jù)、更多編碼類型

兩者詳細(xì)對比如下圖:

3f34b6df-1962-9a4c-e089-5fb8a2e7b2c9.png
3f34b6df-1962-9a4c-e089-5fb8a2e7b2c9.png

4. Websocket

Websocket 是一個(gè) 持久化的協(xié)議谐檀, 基于 http 抡谐, 服務(wù)端可以 主動 push

  • 兼容:

    • FLASH Socket
    • 長輪詢: 定時(shí)發(fā)送 ajax
    • long poll: 發(fā)送 --> 有消息時(shí)再 response
  • new WebSocket(url)

  • ws.onerror = fn

  • ws.onclose = fn

  • ws.onopen = fn

  • ws.onmessage = fn

  • ws.send()

5. TCP 三次握手

建立連接前,客戶端和服務(wù)端需要通過握手來確認(rèn)對方:

  • 客戶端發(fā)送 syn(同步序列編號) 請求桐猬,進(jìn)入 syn_send 狀態(tài)麦撵,等待確認(rèn)
  • 服務(wù)端接收并確認(rèn) syn 包后發(fā)送 syn+ack 包,進(jìn)入 syn_recv 狀態(tài)
  • 客戶端接收 syn+ack 包后溃肪,發(fā)送 ack 包免胃,雙方進(jìn)入 established 狀態(tài)

6. TCP 四次揮手

  • 客戶端 -- FIN --> 服務(wù)端, FIN—WAIT
  • 服務(wù)端 -- ACK --> 客戶端惫撰, CLOSE-WAIT
  • 服務(wù)端 -- ACK,FIN --> 客戶端羔沙, LAST-ACK
  • 客戶端 -- ACK --> 服務(wù)端,CLOSED

7. Node 的 Event Loop: 6 個(gè)階段

  • timer 階段: 執(zhí)行到期的setTimeout / setInterval隊(duì)列回調(diào)
  • I/O 階段: 執(zhí)行上輪循環(huán)殘流的callback
  • idle, prepare
  • poll: 等待回調(diào)
      1. 執(zhí)行回調(diào)
      1. 執(zhí)行定時(shí)器
      • 如有到期的setTimeout / setInterval厨钻, 則返回 timer 階段
      • 如有setImmediate扼雏,則前往 check 階段
  • check
    • 執(zhí)行setImmediate
  • close callbacks

8. HTTP2,詳細(xì)參考

  • 二進(jìn)制分幀
  • 頭部壓縮
  • 服務(wù)端推送
  • 多路復(fù)用
  • 優(yōu)化手段

跨域

  • JSONP: 利用<script>標(biāo)簽不受跨域限制的特點(diǎn)夯膀,缺點(diǎn)是只能支持 get 請求
function jsonp(url, jsonpCallback, success) {
  const script = document.createElement("script");
  script.src = url;
  script.async = true;
  script.type = "text/javascript";
  window[jsonpCallback] = function(data) {
    success && success(data);
  };
  document.body.appendChild(script);
}
  • 設(shè)置 CORS: Access-Control-Allow-Origin:*
  • postMessage

安全

  • XSS 攻擊: 注入惡意代碼
    • cookie 設(shè)置 httpOnly
    • 轉(zhuǎn)義頁面上的輸入內(nèi)容和輸出內(nèi)容
  • CSRF(騰訊 3.5诗充,詳細(xì)參考): 跨站請求偽造,防護(hù):
    • get 不修改數(shù)據(jù)
    • 不被第三方網(wǎng)站訪問到用戶的 cookie
    • 設(shè)置白名單棍郎,不被第三方網(wǎng)站請求
    • 請求校驗(yàn)

框架:Vue

1. nextTick

在下次dom更新循環(huán)結(jié)束之后執(zhí)行延遲回調(diào),可用于獲取更新后的dom狀態(tài)

  • 新版本中默認(rèn)是mincrotasks, v-on中會使用macrotasks

  • macrotasks任務(wù)的實(shí)現(xiàn):

    • setImmediate / MessageChannel / setTimeout

2. 生命周期

  • _init_

    • initLifecycle/Event银室,往vm上掛載各種屬性
    • callHook: beforeCreated: 實(shí)例剛創(chuàng)建
    • initInjection/initState: 初始化注入和 data 響應(yīng)性
    • created: 創(chuàng)建完成涂佃,屬性已經(jīng)綁定, 但還未生成真實(shí)dom
    • 進(jìn)行元素的掛載: $el / vm.$mount()
    • 是否有template: 解析成render function
      • *.vue文件: vue-loader會將<template>編譯成render function
    • beforeMount: 模板編譯/掛載之前
    • 執(zhí)行render function蜈敢,生成真實(shí)的dom辜荠,并替換到dom tree
    • mounted: 組件已掛載
  • update:

    • 執(zhí)行diff算法,比對改變是否需要觸發(fā) UI 更新
    • flushScheduleQueue
      • watcher.before: 觸發(fā)beforeUpdate鉤子 - watcher.run(): 執(zhí)行watcher中的 notify抓狭,通知所有依賴項(xiàng)更新 UI
    • 觸發(fā)updated鉤子: 組件已更新
  • actived / deactivated(keep-alive): 不銷毀伯病,緩存,組件激活與失活

  • destroy:

    • beforeDestroy: 銷毀開始
    • 銷毀自身且遞歸銷毀子組件以及事件監(jiān)聽
      • remove(): 刪除節(jié)點(diǎn)
      • watcher.teardown(): 清空依賴
      • vm.$off(): 解綁監(jiān)聽
    • destroyed: 完成后觸發(fā)鉤子

上面是vue的聲明周期的簡單梳理否过,接下來我們直接以代碼的形式來完成vue的初始化


new Vue({})

// 初始化 Vue 實(shí)例
function \_init() {
// 掛載屬性
initLifeCycle(vm)
// 初始化事件系統(tǒng)午笛,鉤子函數(shù)等
initEvent(vm)
// 編譯 slot、vnode
initRender(vm)
// 觸發(fā)鉤子
callHook(vm, 'beforeCreate')
// 添加 inject 功能
initInjection(vm)
// 完成數(shù)據(jù)響應(yīng)性 props/data/watch/computed/methods
initState(vm)
// 添加 provide 功能
initProvide(vm)
// 觸發(fā)鉤子
callHook(vm, 'created')

     // 掛載節(jié)點(diǎn)
    if (vm.$options.el) {
        vm.$mount(vm.$options.el)
    }

}

// 掛載節(jié)點(diǎn)實(shí)現(xiàn)
function mountComponent(vm) {
// 獲取 render function
if (!this.options.render) {
// template to render
// Vue.compile = compileToFunctions
let { render } = compileToFunctions()
this.options.render = render
}
// 觸發(fā)鉤子
callHook('beforeMounte')
// 初始化觀察者
// render 渲染 vdom苗桂,
vdom = vm.render()
// update: 根據(jù) diff 出的 patchs 掛載成真實(shí)的 dom
vm.\_update(vdom)
// 觸發(fā)鉤子
callHook(vm, 'mounted')
}

// 更新節(jié)點(diǎn)實(shí)現(xiàn)
funtion queueWatcher(watcher) {
nextTick(flushScheduleQueue)
}

// 清空隊(duì)列
function flushScheduleQueue() {
    // 遍歷隊(duì)列中所有修改
for(){
        // beforeUpdate
        watcher.before()

        // 依賴局部更新節(jié)點(diǎn)
        watcher.update()
        callHook('updated')
    }

}

// 銷毀實(shí)例實(shí)現(xiàn)
Vue.prototype.$destory = function() {
     // 觸發(fā)鉤子
    callHook(vm, 'beforeDestory')
    // 自身及子節(jié)點(diǎn)
    remove()
    // 刪除依賴
    watcher.teardown()
    // 刪除監(jiān)聽
    vm.$off()
// 觸發(fā)鉤子
callHook(vm, 'destoryed')
}

3. 數(shù)據(jù)響應(yīng)(數(shù)據(jù)劫持)

看完生命周期后药磺,里面的watcher等內(nèi)容其實(shí)是數(shù)據(jù)響應(yīng)中的一部分。數(shù)據(jù)響應(yīng)的實(shí)現(xiàn)由兩部分構(gòu)成: 觀察者( watcher )依賴收集器( Dep )煤伟,其核心是 defineProperty這個(gè)方法癌佩,它可以 重寫屬性的 get 與 set 方法木缝,從而完成監(jiān)聽數(shù)據(jù)的改變。

  • Observe (觀察者)觀察 props 與 state
    • 遍歷 props 與 state围辙,對每個(gè)屬性創(chuàng)建獨(dú)立的監(jiān)聽器( watcher )
  • 使用 defineProperty 重寫每個(gè)屬性的 get/set(defineReactive
    • get: 收集依賴
      • Dep.depend()
        • watcher.addDep()
    • set: 派發(fā)更新
      • Dep.notify()
      • watcher.update()
      • queenWatcher()
      • nextTick
      • flushScheduleQueue
      • watcher.run()
      • updateComponent()

大家可以先看下面的數(shù)據(jù)相應(yīng)的代碼實(shí)現(xiàn)后我碟,理解后就比較容易看懂上面的簡單脈絡(luò)了。

let data = { a: 1 };
// 數(shù)據(jù)響應(yīng)性
observe(data);

// 初始化觀察者
new Watcher(data, "name", updateComponent);
data.a = 2;

// 簡單表示用于數(shù)據(jù)更新后的操作
function updateComponent() {
  vm._update(); // patchs
}

// 監(jiān)視對象
function observe(obj) {
  // 遍歷對象姚建,使用 get/set 重新定義對象的每個(gè)屬性值
  Object.keys(obj).map(key => {
    defineReactive(obj, key, obj[key]);
  });
}

function defineReactive(obj, k, v) {
  // 遞歸子屬性
  if (type(v) == "object") observe(v);

  // 新建依賴收集器
  let dep = new Dep();
  // 定義get/set
  Object.defineProperty(obj, k, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      // 當(dāng)有獲取該屬性時(shí)矫俺,證明依賴于該對象,因此被添加進(jìn)收集器中
      if (Dep.target) {
        dep.addSub(Dep.target);
      }
      return v;
    },
    // 重新設(shè)置值時(shí)桥胞,觸發(fā)收集器的通知機(jī)制
    set: function reactiveSetter(nV) {
      v = nV;
      dep.nofify();
    }
  });
}

// 依賴收集器
class Dep {
  constructor() {
    this.subs = [];
  }
  addSub(sub) {
    this.subs.push(sub);
  }
  notify() {
    this.subs.map(sub => {
      sub.update();
    });
  }
}

Dep.target = null;

// 觀察者
class Watcher {
  constructor(obj, key, cb) {
    Dep.target = this;
    this.cb = cb;
    this.obj = obj;
    this.key = key;
    this.value = obj[key];
    Dep.target = null;
  }
  addDep(Dep) {
    Dep.addSub(this);
  }
  update() {
    this.value = this.obj[this.key];
    this.cb(this.value);
  }
  before() {
    callHook("beforeUpdate");
  }
}

4. virtual dom 原理實(shí)現(xiàn)(騰訊 3.5 面試

  • 創(chuàng)建 dom 樹

  • 樹的diff恳守,同層對比,輸出patchs(listDiff/diffChildren/diffProps)

    • 沒有新的節(jié)點(diǎn)贩虾,返回
    • 新的節(jié)點(diǎn)tagNamekey不變催烘, 對比props,繼續(xù)遞歸遍歷子樹
      • 對比屬性(對比新舊屬性列表):
        • 舊屬性是否存在與新屬性列表中
        • 都存在的是否有變化
        • 是否出現(xiàn)舊列表中沒有的新屬性
    • tagNamekey值變化了缎罢,則直接替換成新節(jié)點(diǎn)
  • 渲染差異

    • 遍歷patchs伊群, 把需要更改的節(jié)點(diǎn)取出來
    • 局部更新dom
// diff 算法的實(shí)現(xiàn)
function diff(oldTree, newTree) {
  // 差異收集
  let pathchs = {};
  dfs(oldTree, newTree, 0, pathchs);
  return pathchs;
}

function dfs(oldNode, newNode, index, pathchs) {
  let curPathchs = [];
  if (newNode) {
    // 當(dāng)新舊節(jié)點(diǎn)的 tagName 和 key 值完全一致時(shí)
    if (oldNode.tagName === newNode.tagName && oldNode.key === newNode.key) {
      // 繼續(xù)比對屬性差異
      let props = diffProps(oldNode.props, newNode.props);
      curPathchs.push({ type: "changeProps", props });
      // 遞歸進(jìn)入下一層級的比較
      diffChildrens(oldNode.children, newNode.children, index, pathchs);
    } else {
      // 當(dāng) tagName 或者 key 修改了后,表示已經(jīng)是全新節(jié)點(diǎn)策精,無需再比
      curPathchs.push({ type: "replaceNode", node: newNode });
    }
  }

  // 構(gòu)建出整顆差異樹
  if (curPathchs.length) {
    if (pathchs[index]) {
      pathchs[index] = pathchs[index].concat(curPathchs);
    } else {
      pathchs[index] = curPathchs;
    }
  }
}

// 屬性對比實(shí)現(xiàn)
function diffProps(oldProps, newProps) {
  let propsPathchs = [];
  // 遍歷新舊屬性列表
  // 查找刪除項(xiàng)
  // 查找修改項(xiàng)
  // 查找新增項(xiàng)
  forin(olaProps, (k, v) => {
    if (!newProps.hasOwnProperty(k)) {
      propsPathchs.push({ type: "remove", prop: k });
    } else {
      if (v !== newProps[k]) {
        propsPathchs.push({ type: "change", prop: k, value: newProps[k] });
      }
    }
  });
  forin(newProps, (k, v) => {
    if (!oldProps.hasOwnProperty(k)) {
      propsPathchs.push({ type: "add", prop: k, value: v });
    }
  });
  return propsPathchs;
}

// 對比子級差異
function diffChildrens(oldChild, newChild, index, pathchs) {
  // 標(biāo)記子級的刪除/新增/移動
  let { change, list } = diffList(oldChild, newChild, index, pathchs);
  if (change.length) {
    if (pathchs[index]) {
      pathchs[index] = pathchs[index].concat(change);
    } else {
      pathchs[index] = change;
    }
  }

  // 根據(jù) key 獲取原本匹配的節(jié)點(diǎn)舰始,進(jìn)一步遞歸從頭開始對比
  oldChild.map((item, i) => {
    let keyIndex = list.indexOf(item.key);
    if (keyIndex) {
      let node = newChild[keyIndex];
      // 進(jìn)一步遞歸對比
      dfs(item, node, index, pathchs);
    }
  });
}

// 列表對比,主要也是根據(jù) key 值查找匹配項(xiàng)
// 對比出新舊列表的新增/刪除/移動
function diffList(oldList, newList, index, pathchs) {
  let change = [];
  let list = [];
  const newKeys = getKey(newList);
  oldList.map(v => {
    if (newKeys.indexOf(v.key) > -1) {
      list.push(v.key);
    } else {
      list.push(null);
    }
  });

  // 標(biāo)記刪除
  for (let i = list.length - 1; i >= 0; i--) {
    if (!list[i]) {
      list.splice(i, 1);
      change.push({ type: "remove", index: i });
    }
  }

  // 標(biāo)記新增和移動
  newList.map((item, i) => {
    const key = item.key;
    const index = list.indexOf(key);
    if (index === -1 || key == null) {
      // 新增
      change.push({ type: "add", node: item, index: i });
      list.splice(i, 0, key);
    } else {
      // 移動
      if (index !== i) {
        change.push({
          type: "move",
          form: index,
          to: i
        });
        move(list, index, i);
      }
    }
  });

  return { change, list };
}

5. Proxy 相比于 defineProperty 的優(yōu)勢

  • 數(shù)組變化也能監(jiān)聽到
  • 不需要深度遍歷監(jiān)聽
let data = { a: 1 };
let reactiveData = new Proxy(data, {
  get: function(target, name) {
    // ...
  }
  // ...
});

6. vue-router

  • mode
    • hash
    • history
  • 跳轉(zhuǎn)
    • this.$router.push()
    • <router-link to=""></router-link>
  • 占位
    • <router-view></router-view>

7. vuex

  • state: 狀態(tài)中心
  • mutations: 更改狀態(tài)
  • actions: 異步更改狀態(tài)
  • getters: 獲取狀態(tài)
  • modules: 將state分成多個(gè)modules咽袜,便于管理

算法

其實(shí)算法方面在前端的實(shí)際項(xiàng)目中涉及得并不多丸卷,但還是需要精通一些基礎(chǔ)性的算法,一些公司還是會有這方面的需求和考核询刹,建議大家還是需要稍微準(zhǔn)備下谜嫉,這屬于加分題。

1. 五大算法

  • 貪心算法: 局部最優(yōu)解法
  • 分治算法: 分成多個(gè)小模塊凹联,與原問題性質(zhì)相同
  • 動態(tài)規(guī)劃: 每個(gè)狀態(tài)都是過去歷史的一個(gè)總結(jié)
  • 回溯法: 發(fā)現(xiàn)原先選擇不優(yōu)時(shí)沐兰,退回重新選擇
  • 分支限界法

2. 基礎(chǔ)排序算法

  • 冒泡排序: 兩兩比較
function bubleSort(arr) {
  var len = arr.length;
  for (let outer = len; outer >= 2; outer--) {
    for (let inner = 0; inner <= outer - 1; inner++) {
      if (arr[inner] > arr[inner + 1]) {
        [arr[inner], arr[inner + 1]] = [arr[inner + 1], arr[inner]];
      }
    }
  }
  return arr;
}
  • 選擇排序: 遍歷自身以后的元素,最小的元素跟自己調(diào)換位置
function selectSort(arr) {
  var len = arr.length;
  for (let i = 0; i < len - 1; i++) {
    for (let j = i; j < len; j++) {
      if (arr[j] < arr[i]) {
        [arr[i], arr[j]] = [arr[j], arr[i]];
      }
    }
  }
  return arr;
}
  • 插入排序: 即將元素插入到已排序好的數(shù)組中
function insertSort(arr) {
  for (let i = 1; i < arr.length; i++) {
    //外循環(huán)從 1 開始蔽挠,默認(rèn) arr[0]是有序段
    for (let j = i; j > 0; j--) {
      //j = i,將 arr[j]依次插入有序段中
      if (arr[j] < arr[j - 1]) {
        [arr[j], arr[j - 1]] = [arr[j - 1], arr[j]];
      } else {
        break;
      }
    }
  }
  return arr;
}

3. 高級排序算法

  • 快速排序
    • 選擇基準(zhǔn)值(base)住闯,原數(shù)組長度減一(基準(zhǔn)值),使用 splice
    • 循環(huán)原數(shù)組澳淑,小的放左邊(left 數(shù)組)比原,大的放右邊(right 數(shù)組);
    • concat(left, base, right)
    • 遞歸繼續(xù)排序 left 與 right
function quickSort(arr) {
  if (arr.length <= 1) {
    return arr; //遞歸出口
  }
  var left = [],
    right = [],
    current = arr.splice(0, 1);
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] < current) {
      left.push(arr[i]); //放在左邊
    } else {
      right.push(arr[i]); //放在右邊
    }
  }
  return quickSort(left).concat(current, quickSort(right));
}
  • 希爾排序:不定步數(shù)的插入排序,插入排序

  • 口訣: 插冒歸基穩(wěn)定杠巡,快選堆希不穩(wěn)定

02ed96c9-28a6-d177-240e-eebb59ef1ca0.png
02ed96c9-28a6-d177-240e-eebb59ef1ca0.png

穩(wěn)定性: 同大小情況下是否可能會被交換位置, 虛擬 dom 的 diff春寿,不穩(wěn)定性會導(dǎo)致重新渲染;

4. 遞歸運(yùn)用(斐波那契數(shù)列): 爬樓梯問題

初始在第一級忽孽,到第一級有 1 種方法(s(1) = 1)绑改,到第二級也只有一種方法(s(2) = 1)谢床, 第三級(s(3) = s(1) + s(2))

function cStairs(n) {
  if (n === 1 || n === 2) {
    return 1;
  } else {
    return cStairs(n - 1) + cStairs(n - 2);
  }
}

5. 數(shù)據(jù)樹

  • 二叉樹: 最多只有兩個(gè)子節(jié)點(diǎn)
    • 完全二叉樹
    • 滿二叉樹
      • 深度為 h, 有 n 個(gè)節(jié)點(diǎn),且滿足 n = 2^h - 1
  • 二叉查找樹: 是一種特殊的二叉樹厘线,能有效地提高查找效率
    • 小值在左识腿,大值在右
    • 節(jié)點(diǎn) n 的所有左子樹值小于 n,所有右子樹值大于 n
7e94450b-9402-c9fa-d304-f8adff15976d.png
7e94450b-9402-c9fa-d304-f8adff15976d.png
  • 遍歷節(jié)點(diǎn)
    • 前序遍歷
        1. 根節(jié)點(diǎn)
        1. 訪問左子節(jié)點(diǎn)造壮,回到 1
        1. 訪問右子節(jié)點(diǎn)渡讼,回到 1
    • 中序遍歷
        1. 先訪問到最左的子節(jié)點(diǎn)
        1. 訪問該節(jié)點(diǎn)的父節(jié)點(diǎn)
        1. 訪問該父節(jié)點(diǎn)的右子節(jié)點(diǎn), 回到 1
    • 后序遍歷
        1. 先訪問到最左的子節(jié)點(diǎn)
        1. 訪問相鄰的右節(jié)點(diǎn)
        1. 訪問父節(jié)點(diǎn)耳璧, 回到 1
  • 插入與刪除節(jié)點(diǎn)

6. 天平找次品

有 n 個(gè)硬幣成箫,其中 1 個(gè)為假幣,假幣重量較輕旨枯,你有一把天平蹬昌,請問,至少需要稱多少次能保證一定找到假幣?

  • 三等分算法:
      1. 將硬幣分成 3 組攀隔,隨便取其中兩組天平稱量
      • 平衡皂贩,假幣在未上稱的一組,取其回到 1 繼續(xù)循環(huán)
      • 不平衡昆汹,假幣在天平上較輕的一組明刷, 取其回到 1 繼續(xù)循環(huán)

7. 大整數(shù)相加(騰訊 3.5 面試題

function sum(a, b) {
  var res = "",
    temp = 0;
  const aArr = `${a}`.split("");
  const bArr = `$`.split("");
  const langArr = aArr.length > bArr.length ? aArr : bArr;
  const shortArr = aArr.length > bArr.length ? bArr : aArr;
  while (shortArr.length || temp) {
    temp += ~~aArr.pop() + ~~bArr.pop();
    res = (temp % 10) + res;
    temp = temp > 9;
  }
  res = langArr.join("") + res;
  return res.replace(/^0+/, "");
}

// 面試官說還有更優(yōu)的算法满粗,我想了下應(yīng)該可以對數(shù)字分段補(bǔ)0相加的方式辈末。懶得寫了

前端性能優(yōu)化(重要重要重要,騰訊就問這個(gè)S辰浴挤聘!

參考文章:
https://juejin.im/post/59672fbff265da6c3f70cd53

1. 瀏覽器渲染頁面

  • CSS 為什么要放到<head>里面
  • js 放到</body>前面
  • js 的異步加載(async、defer)等優(yōu)化

2. 減少 HTTP 請求

  • CSS/JS 合并打包
  • 小圖標(biāo)等用 iconfont 代替
  • 使用 base64 格式的圖片
  • 使用 HTTP2劫扒,減少同域 TCP 連接次數(shù)

3. 減少靜態(tài)資源體積

  • 壓縮靜態(tài)資源
  • 編寫高效率的 CSS檬洞,減少嵌套
  • 服務(wù)端開啟gzip壓縮

4. 使用緩存

詳細(xì)參考:緩存策略

5. 內(nèi)存溢出

  • Performance 面板使用
  • Memory 面板使用
  • 最好舉個(gè)項(xiàng)目中的例子說明

前端性能監(jiān)控(重要重要重要狸膏,騰訊就問這個(gè)9导ⅰ!

參考文章:
https://juejin.im/post/5b7a50c0e51d4538af60d995

結(jié)語

由于精力時(shí)間及篇幅有限湾戳,這篇就先寫到這贤旷。大家慢慢來不急。砾脑。??幼驶。下篇打算準(zhǔn)備以下內(nèi)容,我也得補(bǔ)補(bǔ)課先:

  • Webpack 相關(guān)
    • 原理
    • Loader
    • Plugin
  • 項(xiàng)目性能優(yōu)化
    • 首屏渲染優(yōu)化
    • 用戶體驗(yàn)優(yōu)化
    • webpack 性能優(yōu)化
  • Hybrid 與 Webview
    • webview 加載過程
    • bridge 原理
    • hybrid app 經(jīng)驗(yàn)
  • 框架: React

在面試中韧衣,很多領(lǐng)域并沒有真正的答案盅藻,能回答到什么樣的深度购桑,還是得靠自己真正的去使用和研究。知識面的廣度與深度應(yīng)該并行氏淑,盡量的拓張自己的領(lǐng)域勃蜘,至少都有些基礎(chǔ)性的了解,在被問到的時(shí)候可以同面試官嘮嗑兩句假残,然后在自己喜歡的領(lǐng)域缭贡,又有著足夠深入的研究,讓面試官覺得你是這方面的專家辉懒。

知識大綱還在不斷的完善和修正阳惹,由于也是精力時(shí)間有限,我會慢慢補(bǔ)充后面列出來的部分眶俩。當(dāng)然莹汤,我也是在整理中不斷的學(xué)習(xí),也希望大家能一起參與進(jìn)來仿便,要補(bǔ)充或修正的地方麻煩趕緊提出体啰。另外,剛新建了個(gè)公眾號嗽仪,想作為大家交流和分享的地方荒勇,有興趣想法的童鞋聯(lián)系我哈~~??

作者:郭東東
鏈接:https://juejin.im/post/5c64d15d6fb9a049d37f9c20
來源:掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)闻坚,非商業(yè)轉(zhuǎn)載請注明出處沽翔。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市窿凤,隨后出現(xiàn)的幾起案子仅偎,更是在濱河造成了極大的恐慌,老刑警劉巖雳殊,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件橘沥,死亡現(xiàn)場離奇詭異,居然都是意外死亡夯秃,警方通過查閱死者的電腦和手機(jī)座咆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來仓洼,“玉大人介陶,你說我怎么就攤上這事∩ǎ” “怎么了哺呜?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長箕戳。 經(jīng)常有香客問我某残,道長国撵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任玻墅,我火速辦了婚禮卸留,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘椭豫。我一直安慰自己耻瑟,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布赏酥。 她就那樣靜靜地躺著喳整,像睡著了一般。 火紅的嫁衣襯著肌膚如雪裸扶。 梳的紋絲不亂的頭發(fā)上框都,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機(jī)與錄音呵晨,去河邊找鬼魏保。 笑死,一個(gè)胖子當(dāng)著我的面吹牛摸屠,可吹牛的內(nèi)容都是我干的谓罗。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼季二,長吁一口氣:“原來是場噩夢啊……” “哼檩咱!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起胯舷,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤刻蚯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后桑嘶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體炊汹,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年逃顶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了讨便。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡口蝠,死狀恐怖器钟,靈堂內(nèi)的尸體忽然破棺而出津坑,到底是詐尸還是另有隱情妙蔗,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布疆瑰,位于F島的核電站眉反,受9級特大地震影響昙啄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜寸五,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一梳凛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧梳杏,春花似錦韧拒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至劲适,卻和暖如春楷掉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背霞势。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工烹植, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人愕贡。 一個(gè)月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓草雕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親固以。 傳聞我的和親對象是個(gè)殘疾皇子促绵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容