JavaScript

1.數(shù)據(jù)類型

1.1概念篇

7種原始數(shù)據(jù)類型

- boolean
- string
- number
- undefined
- null
- Symbol
- bigInt

引用類型

- 對象Object
    - 普通對象-Object
    - 數(shù)組對象-Array
    - 正則對象-RegExp
    - 日期對象-Date
    - 數(shù)學(xué)函數(shù)-Math
    - 函數(shù)對象-Function
    - 基本包裝類型 Boolean String Number

null是對象嗎登淘?為什么?

  • 結(jié)論: null不是對象
  • 解釋: 雖然 typeof null 會輸出 object,但是這只是 JS 存在的一個悠久 Bug三妈。JS在運(yùn)行之前編譯成二進(jìn)制形式陪腌,在 JS 的最初版本中使用的是 32 位系統(tǒng)辱魁,為了性能考慮使用低位存儲變量的類型信息,000 開頭代表是對象然而 null 表示為全零诗鸭,所以將它錯誤的判斷為 object 染簇。

1 .toString()或者(1).toString為什么可以調(diào)用?

  • 數(shù)字后面的第一個點(diǎn)會被解釋為小數(shù)點(diǎn)强岸,而不是點(diǎn)調(diào)用锻弓。只不過不推薦這種使用方法,而且這樣做也沒什么意義
  • 基本包裝類型:為什么基本類型卻可以直接調(diào)用引用類型的方法呢蝌箍?其實(shí)是js引擎在解析上面的語句的時候青灼,會把這三種基本類型解析為包裝對象(就是下面的new String()),而包裝對象是引用類型可以調(diào)用Object.prototype上的方法妓盲。大概過程如下:

0.1+0.2為什么不等于0.3杂拨?

  • 在JS中數(shù)字采用的IEEE 754的雙精度標(biāo)準(zhǔn)進(jìn)行存儲,到這里我們都理解只要采取IEEE 754 FP的浮點(diǎn)數(shù)編碼的語言均會出現(xiàn)上述問題悯衬,只是它們的標(biāo)準(zhǔn)類庫已經(jīng)為我們提供了解決方案而已
  • 而對于像0.1這樣的數(shù)值用二進(jìn)制表示你就會發(fā)現(xiàn)無法整除弹沽,最后算下來會是 0.000110011….由于存儲空間有限(雙精度是64位存儲空間),最后根據(jù)IEEE 754的規(guī)則會舍棄后面的數(shù)值,所以我們最后就只能得到一個策橘,此時就已經(jīng)出現(xiàn)了精度的損失
  • 簡單理解 0.1和02不能被二進(jìn)制浮點(diǎn)數(shù)精確表示
  • 在0.1 + 0.2這個式子中炸渡,0.1和0.2都是近似表示的,在他們相加的時候丽已,兩個近似值進(jìn)行了計(jì)算偶摔,導(dǎo)致最后得到的值是0.30000000000000004,此時對于JS來說促脉,其不夠近似于0.3)辰斋,于是就出現(xiàn)了0.1 + 0.2 != 0.3 這個現(xiàn)象

既然十進(jìn)制0.1不能被二進(jìn)制浮點(diǎn)數(shù)精確存儲,那么為什么console.log(0.1)打印出來的確確實(shí)實(shí)是0.1這個精確的值瘸味?

  • 實(shí)際IEEE 754標(biāo)準(zhǔn)就是采用一套規(guī)則去近視于0宫仗。1 雖然無法精確存儲,但是可以用一個近視值去表示旁仿,比如:0.100000000000000002 ==
    0.100000000000000010 // true
  • 當(dāng)64bit的存儲空間無法存儲完整的無限循環(huán)小數(shù)藕夫,而IEEE 754 Floating-point采用round to nearest, tie to even的舍入模式,因此0.1實(shí)際存儲時的位模式是0-01111111011-1001100110011001100110011001100110011001100110011010枯冈;

解決浮點(diǎn)數(shù)運(yùn)算精度

  • 換算成整數(shù)進(jìn)行運(yùn)算毅贮,整數(shù)運(yùn)算就不存在精度缺失問題,??我們可以把需要計(jì)算的數(shù)字升級(乘以10的n次冪)成計(jì)算機(jī)能夠精確識別的整數(shù),等計(jì)算完成后再進(jìn)行降級(除以10的n次冪)尘奏,這是大部分編程語言處理精度問題常用的方法滩褥。例如:
  • 但是換算也是浮點(diǎn)數(shù)運(yùn)算的操作,同樣也會存在問題炫加,所以解決的方法就是采用字符串形式進(jìn)行換算 比如:3.14===> {times: 100, num: 314} ===>有點(diǎn)類似大數(shù)相加的原理
  • 利用第三方庫:Math.js瑰煎,decimal.js

解題思路:

  • 二進(jìn)制換算后(不會出現(xiàn)循環(huán)被截?cái)啵@是前提條件俗孝,這樣換算后值落在這個區(qū)間就保證了精度無誤酒甸,所以我們想辦法使運(yùn)算的數(shù)字落在這個區(qū)間 這個就是解搭問題的關(guān)鍵)由于僅位于Number.MIN_SAFE_INTEGER和Number.MAX_SAFE_INTEGER間的整數(shù)才能被精準(zhǔn)地表示,也就是只要保證運(yùn)算過程的操作數(shù)和結(jié)果均落在這個閥值內(nèi)赋铝,那么運(yùn)算結(jié)果就是精準(zhǔn)無誤的

  • 問題的關(guān)鍵落在如何將小數(shù)和極大數(shù)轉(zhuǎn)換或拆分為Number.MIN_SAFE_INTEGER至Number.MAX_SAFE_INTEGER閥值間的數(shù)了

  • 小數(shù)轉(zhuǎn)換為整數(shù)插勤,自然就是通過科學(xué)計(jì)數(shù)法表示,并通過右移小數(shù)點(diǎn)革骨,減小冪的方式處理农尖;(如0.000123 等價于 123 * 10-6)

  • Number.EPSILON實(shí)際上是 JavaScript 能夠表示的最小精度。誤差如果小于這個值苛蒲,就可以認(rèn)為已經(jīng)沒有意義了卤橄,即不存在誤差了。

可以這樣判斷:0.1+0.2-0.3<Number.EPSILON

1.2 檢測篇

typeof 是否能正確判斷類型臂外?

  • 對于原始類型來說窟扑,除了 null 都可以調(diào)用typeof顯示正確的類型喇颁。
  • 但對于引用數(shù)據(jù)類型,除了函數(shù)之外嚎货,都會顯示"object"橘霎。

instanceof能判斷基本數(shù)據(jù)類型

Object.is和===的區(qū)別?

  • Object在嚴(yán)格等于的基礎(chǔ)上修復(fù)了一些特殊情況下的失誤殖属,具體來說就是+0和-0(false)姐叁,NaN和NaN(true)。

Object.prototype.toString

1.3 轉(zhuǎn)換篇

JS中類型轉(zhuǎn)換有哪幾種洗显?

  • 轉(zhuǎn)換成數(shù)字
  • 轉(zhuǎn)換成布爾值
  • 轉(zhuǎn)換成字符串

== 和 ===有什么區(qū)別墨林?

  • ===叫做嚴(yán)格相等厅目,是指:左右兩邊不僅值要相等,類型也要相等,例如'1'===1的結(jié)果是false菩收,因?yàn)橐贿吺莝tring船惨,另一邊是number障贸。

  • ==不像===那樣嚴(yán)格胧辽,對于一般情況,只要值相等俄讹,就返回true哆致,但==還涉及一些類型轉(zhuǎn)換,它的轉(zhuǎn)換規(guī)則如下:(比較最終都是轉(zhuǎn)化為數(shù)字的)

      - 兩邊的類型是否相同患膛,相同的話就比較值的大小摊阀,例如1==2,返回false
      - 判斷的是否是null和undefined剩瓶,是的話就返回true
      - 判斷的類型是否是String和Number驹溃,是的話,把String類型轉(zhuǎn)換成Number延曙,再進(jìn)行比較
      - 判斷其中一方是否是Boolean,是的話就把Boolean轉(zhuǎn)換成Number亡哄,再進(jìn)行比較
      - 如果其中一方為Object枝缔,且另一方為String、Number或者Symbol蚊惯,會將Object轉(zhuǎn)換成字符串愿卸,再進(jìn)行比較
    

對象轉(zhuǎn)原始類型是根據(jù)什么流程運(yùn)行的

  • 如果存在Symbol.toPrimitive()方法,優(yōu)先調(diào)用再返回
  • 調(diào)用valueOf()截型,如果轉(zhuǎn)換為原始類型趴荸,則返回
  • 調(diào)用toString(),如果轉(zhuǎn)換為原始類型宦焦,則返回
  • 如果都沒有返回原始類型发钝,會報(bào)錯

如何讓if(a == 1 && a == 2)條件成立顿涣?

  • 其實(shí)就是上一個問題的應(yīng)用。
var a = {
  value: 0,
  valueOf: function() {
    this.value++;
    return this.value;
  }
};
console.log(a == 1 && a == 2);// true

2.拷貝

2.1 淺拷貝:shallowClone

  • 一個新的對象直接拷貝已存在的對象的對象屬性的引用酝豪,即淺拷貝涛碑。(對象的屬性)
  • Object.assign
  • ...展開運(yùn)算符
  • concat淺拷貝數(shù)組
  • slice淺拷貝

2.2 深拷貝:deepClone

  • 深拷貝會另外拷貝一份一個一模一樣的對象,從堆內(nèi)存中開辟一個新的區(qū)域存放新對象,新對象跟原對象不共享內(nèi)存,修改新對象不會改到原對象孵淘。

JSON.parse(JSON.stringify());

  • 無法解決循環(huán)引用的問題蒲障。舉個例子:
  • 無法拷貝一寫特殊的對象,諸如 RegExp, Date, Set, Map等
  • 無法拷貝函數(shù)(劃重點(diǎn))

動手實(shí)現(xiàn)一個深拷貝

  1. 普通類型

    • 直接返回就行
  2. 引用類型

    • 循環(huán)引用
  • 解決循環(huán)引用問題瘫证,我們可以額外開辟一個存儲空間揉阎,來存儲當(dāng)前對象和拷貝對象的對應(yīng)關(guān)系,當(dāng)需要拷貝當(dāng)前對象時背捌,先去存儲空間中找毙籽,有沒有拷貝過這個對象,如果有的話直接返回载萌,如果沒有的話繼續(xù)拷貝惧财,這樣就巧妙化解的循環(huán)引用的問題。

    • 可遍歷類型

        - Set
        - Map
        - Array
        - Object
      
    • 不可遍歷類型(考慮基本包裝類型(引用類型))

        - Boolean
        - Number
        - String
      
      • Date
        • Unix時間戳:value
          一個 Unix 時間戳(Unix Time Stamp)扭仁,它是一個整數(shù)值垮衷,表示自1970年1月1日00:00:00 UTC(the Unix epoch)以來的毫秒數(shù),忽略了閏秒乖坠。請注意大多數(shù) Unix 時間戳功能僅精確到最接近的秒搀突。
          - 重新生成一個Date實(shí)例,參數(shù)傳入一個Unix
    • Error

    • Symbol

      • Object(Symbol('foo'))
      • es6過后就不提倡用new 直接類似Symbol(xxx)這樣執(zhí)行就行
  • WeakMap熊泵、WeakSet仰迁、ArrayBuffer對象、TypedArray視圖和DataView視圖顽分、Float32Array徐许、Float64Array、Int8Array

  • Blob卒蘸、File雌隅、FileList、ImageData

拷貝函數(shù)

    - lodash對函數(shù)的處理 因?yàn)榭截惡瘮?shù)沒有啥意義
    - 函數(shù)(prototype來區(qū)分下箭頭函數(shù)和普通函數(shù)缸沃,箭頭函數(shù)是沒有prototype)

        - 箭頭函數(shù)

            - 我們可以直接使用eval和函數(shù)字符串來重新生成一個箭頭函數(shù)恰起,注意這種方法是不適用于普通函數(shù)的。

        - 非箭頭函數(shù)

            - 分別使用正則取出函數(shù)體和函數(shù)參數(shù)趾牧,然后使用new Function ([arg1[, arg2[, ...argN]],] functionBody)構(gòu)造函數(shù)重新構(gòu)造一個新的函數(shù)

賦值

  • 基本數(shù)據(jù)類型:賦值检盼,賦值之后兩個變量互不影響

  • 引用數(shù)據(jù)類型:賦,兩個變量具有相同的引用翘单,指向同一個對象吨枉,相互之間有影響

  • 為什么需要淺拷貝和深拷貝蹦渣?

    • 對引用類型進(jìn)行賦操作,兩個變量指向同一個對象东羹,改變變量 a 之后會影響變量 b剂桥,哪怕改變的只是對象 a 中的基本類型數(shù)據(jù)
    • 通常在開發(fā)中并不希望改變變量 a 之后會影響到變量 b,這時就需要用到淺拷貝和深拷貝属提。
    • 這就是為什么需要淺拷貝和深拷貝的緣由权逗,因?yàn)槲覀冊谫x值操作時候,操作引用類型的時候不想b影響a,所以需要淺拷貝或者深拷貝冤议,這也從中可以看出賦值與拷貝深拷貝的區(qū)別
    • 通常深淺拷貝是解決引用類型之間互相影響的斟薇,要明白這點(diǎn)
  • ??當(dāng)我們進(jìn)行賦值,考慮到引用類型賦值完做修改會相互影響恕酸,就引出了對應(yīng)的深淺拷貝方案去解決

this指向

其實(shí)JS中的this是一個非常簡單的東西堪滨,只需要理解它的??執(zhí)行規(guī)則就行

顯示綁定

  • call
  • apply
  • bind

隱式綁定

  • 全局上下文

    • 全局上下文默認(rèn)this指向window, 嚴(yán)格模式下指向undefined。
  • 直接調(diào)用函數(shù)

    • this相當(dāng)于全局上下文的情況
  • 對象.方法的形式調(diào)用

    • 誰調(diào)用這個方法蕊温,它就指向誰
  • DOM事件綁定(特殊)

    • onclick和addEventerListener中 this 默認(rèn)指向綁定事件的元素袱箱。

IE比較奇異,使用attachEvent义矛,里面的this默認(rèn)指向window发笔。

  • new構(gòu)造函數(shù)綁定

    • 此時構(gòu)造函數(shù)中的this指向?qū)嵗龑ο?/li>
  • 箭頭函數(shù)

    • 箭頭函數(shù)沒有this, 因此也不能綁定。里面的this會指向當(dāng)前最近的非箭頭函數(shù)的this凉翻,找不到就是window(嚴(yán)格模式是undefined)

JS數(shù)組

函數(shù)的arguments為什么不是數(shù)組了讨?如何轉(zhuǎn)化成數(shù)組?

  • 常見的類數(shù)組

    • 用getElementsByTagName/ClassName()獲得的HTMLCollection
    • 用querySelector獲得的nodeList
  • 轉(zhuǎn)換成數(shù)組

    • Array.prototype.slice.call()
    • Array.from()
    • ES6展開運(yùn)算符
    • 利用concat+apply

forEach中return有效果嗎制轰?如何中斷forEach循環(huán)前计?

  • 在forEach中用return不會返回,函數(shù)會繼續(xù)執(zhí)行垃杖。

  • 中斷方法:

    • 使用try監(jiān)視代碼塊男杈,在需要中斷的地方拋出異常
    • 官方推薦方法(替換方法):用every和some替代forEach函數(shù)。every在碰到return false的時候调俘,中止循環(huán)势就。some在碰到return true的時候,中止循環(huán) (面試問到團(tuán)隊(duì)規(guī)范?)

JS判斷數(shù)組中是否包含某個值

  • array.indexOf
  • array.includes(searcElement[,fromIndex]) 推薦?
  • array.find(callback[,thisArg])
  • array.findeIndex(callback[,thisArg])

JS中flat---數(shù)組扁平化

  • 遞歸
  • reduce+遞歸
  • 原型鏈上的flat方法(數(shù)組實(shí)例上的方法) [1,2,3].flat(2)

JS數(shù)組的高階函數(shù)

  • 什么是高階函數(shù):一個函數(shù)就可以接收另一個函數(shù)作為參數(shù)或者返回值為一個函數(shù)脉漏,這種函數(shù)就稱之為高階函數(shù)。

  • 數(shù)組中的高階函數(shù)

    • map
    • reduce
    • filter
    • sort

JS如何實(shí)現(xiàn)繼承

什么是繼承

  • 繼承是使用已存在的類的定義作為基礎(chǔ)建立新類的技術(shù)袖牙,新類的定義可以增加新的數(shù)據(jù)或新的功能侧巨,也可以用父類的功能,但不能選擇性地繼承父類鞭达。通過使用繼承我們能夠非常方便地復(fù)用以前的代碼司忱,能夠大大的提高開發(fā)的效率

第一種: 借助call(構(gòu)造函數(shù)式繼承

第二種: 借助原型鏈(原型鏈繼承)

第三種:將前兩種組合(組合繼承)

第四種:原型式繼承(如果我要繼承的父類是一個普通對象而不是構(gòu)造函數(shù)(因?yàn)镴avaScript 語言中皇忿,生成實(shí)例對象的傳統(tǒng)方法是通過構(gòu)造函數(shù)),那么如何實(shí)現(xiàn))

  • Object.create方法

第五種:寄生繼承

  • 核心:在原型式繼承的基礎(chǔ)上坦仍,增強(qiáng)對象鳍烁,返回構(gòu)造函數(shù)(類似工廠函數(shù)進(jìn)行包裝)

第六種:寄生組合繼承

原型鏈

原型對象和構(gòu)造函數(shù)有何關(guān)系?

  • 在JavaScript中繁扎,每當(dāng)定義一個函數(shù)數(shù)據(jù)類型(普通函數(shù)幔荒、類)時候,都會天生自帶一個prototype屬性梳玫,這個屬性指向函數(shù)的原型對象爹梁。
  • 當(dāng)函數(shù)經(jīng)過new調(diào)用時,這個函數(shù)就成為了構(gòu)造函數(shù)提澎,返回一個全新的實(shí)例對象姚垃,這個實(shí)例對象有一個proto屬性,指向構(gòu)造函數(shù)的原型對象盼忌。

能不能描述一下原型鏈

  • 首先要明白實(shí)例的proto屬性與構(gòu)造函數(shù)的protype屬性都是指向原型對象积糯,原型對象的constructor屬性又是指向構(gòu)造函數(shù)
  • JavaScript對象通過proto 指向父類的原型對象,直到指向Object的原型對象為止谦纱,這樣就形成了一個原型指向的鏈條, 即原型鏈看成。
  • 對象的 hasOwnProperty() 來檢查對象自身中是否含有該屬性
  • 使用 in 檢查對象中是否含有某個屬性時,如果對象中沒有但是原型鏈中有服协,也會返回 true

DOM事件

綁定事件的?法

    1. HTML的內(nèi)聯(lián)屬性
    1. 元素的onXXX屬性添加事件
    1. addEventListener
    • 標(biāo)準(zhǔn)方法

      • el.addEventListener(eventNam
        e, handle, useCapture |
        options)

        • {String} eventName 事件名稱
        • {Function} handle 事件函數(shù)
        • {Boolean} useCapture 是否在事
          件捕獲階段觸發(fā)事件绍昂,true 代表
          捕獲階段觸發(fā),false 代表在冒
          泡階段觸發(fā)
        • {Object} options 選項(xiàng)對象
      • el.removeEventListener(eventN
        ame, handle)

    • IE?法

      • el.attachEvent(eventName,
        handle)
      • el.detachEvent(eventName,
        handle)
    • 對?:

      • 由于IE8不?持 事件捕獲 偿荷,所以
        通過 attachEvent/detachEvent
        綁定的時間也只能在 冒泡階段
        觸發(fā)
      • 通過 attachEvent/detachEvent
        綁定的事件函數(shù)會在全局作?域
        中運(yùn)?窘游,即: this === window
      • 通過 attachEvent/detachEvent
        綁定的事件函數(shù)以綁定時的先后
        順序 “倒序” 被執(zhí)?
      • attachEvent/detachEvent 的第
        ?個參數(shù)要在事件名稱前?加
        'on'
  • 評價

      1. 違反最佳實(shí)踐
      1. 由于只能賦值?個handler,
        因此會存在覆蓋的問題
      1. 調(diào)?addEventListener時跳纳,要
        注意銷毀組件時回收handler忍饰,
        removeEventListener。但是這
        樣的話寺庄,handler?必須??個變
        量保持引?

事件對象

  • 標(biāo)準(zhǔn)

    • 屬性

      • currentTarget:currentTarget
        的值始終等于 this艾蓝,即指向事件
        所綁定到的元素

      • target:真正觸發(fā)事件的元素

      • bubbles:表示事件是否冒泡

      • cancelable:是否可以取消默認(rèn)
        ?為

      • defaultPrevented:為真則被調(diào)
        ?了preventDefault()

      • detail:描述事件的細(xì)節(jié)

      • eventPhase:描述事件處理函數(shù)
        的階段

        • 1:捕獲
        • 2:處于?標(biāo)
        • 3:冒泡
      • trusted:為真則是瀏覽器原?事
        件,為假則是?動添加的事件

      • type:事件類型

    • 方法

      • event.preventDefault():阻?默
        認(rèn)事件

      • event.stopIPropagation():阻?
        冒泡 也可以阻止捕獲(根據(jù)dom事件流 捕獲階段被阻止了 處于目標(biāo)階段和事件冒泡也不會被觸發(fā)了)

      • stopImmediatePropagation 既能阻止事件向父元素冒泡斗塘,也能阻止元素同事件類型的其它監(jiān)聽器被觸發(fā)赢织。而 stopPropagation 只能實(shí)現(xiàn)前者的效果

        • react使用合成事件,如果出現(xiàn)點(diǎn)擊空白區(qū)域彈框消失馍盟,可以利用stopImmediatePropagation阻止事件的其它函數(shù)執(zhí)行 (只執(zhí)行我這個事件的回調(diào)函數(shù),其它不執(zhí)行)因?yàn)槲叶济芭莸絛ocument上了于置,阻止冒泡沒什么用了,另外一種解決方法關(guān)閉操作注冊到window上
        • e.nativeEvent.stopImmediatePropagation 這個解決問題的不是阻止冒泡 而是不允許其他的事件回調(diào)觸發(fā) 因?yàn)槲覀冞@時候事件已經(jīng)冒泡到document上了 為document再綁定了一個click事件 此時我們想觸發(fā)按鈕這個click事件(實(shí)際綁定到document上)觸發(fā)以后贞岭,不允許再觸發(fā)其他document上click的事件回調(diào)函數(shù)
  • IE

    • 屬性

      • srcElement:與target的值相同
      • returnValue:默認(rèn)為真八毯,若設(shè)置
        為false搓侄,可以阻?默認(rèn)事件
      • cancelBubble:默認(rèn)為假,設(shè)置
        為true可以阻?冒泡
    • 方法

      • el.onclick = function () {
        window.event
        }
      • el.attachEvent:回調(diào)中的event
        可以為傳?的event參數(shù)也可為
        window.event

DOM事件流(統(tǒng)一這兩種事件流)

  • 事件流:描述的是從頁面中接收事件的順序话速。但有意思的是

  • 執(zhí)行的三個階段

    • 事件捕獲

      • 當(dāng)事件發(fā)生時讶踪,首先發(fā)生的是事件捕獲,為父元素截獲事件提供了機(jī)會
    • 處于?標(biāo)

      • 事件到了具體元素時泊交,在具體元素上發(fā)生乳讥,并且被看成冒泡階段的一部分。
    • 事件冒泡

      • 冒泡階段發(fā)生活合,事件開始冒泡
  • 注意點(diǎn)

    • DOM事件流確實(shí)會按照這三個階段執(zhí)行雏婶,我們可以通過addEventListener注冊事件時候指定useCapture的值來規(guī)定事件在捕獲階段還是冒泡階段中執(zhí)行(如果該對象是目標(biāo)對象,則會在目標(biāo)階段執(zhí)行)
    • 你會注意到按照DOM事件流這種執(zhí)行順序白指,事件不會被觸發(fā)兩次吧留晚,造成重復(fù)觸發(fā),并不是的告嘲,我們可以有選擇是在冒泡階段觸發(fā)還是捕獲階段错维,默認(rèn)是冒泡階段
    • // 這段代碼表示該click事件會在事件捕獲階段執(zhí)行(??注意得判斷是不是目標(biāo)對象,如果是目標(biāo)對象就是表示它在處于目標(biāo)這個階段執(zhí)行)
      // 如何判斷是否是目標(biāo)對象:最具體的元素(文檔中嵌套層次最深的那個節(jié)點(diǎn))
      document.querySelector("#button").addEventListener(
      "click",
      function () {
      console.log("處于目標(biāo)button click");
      },
      true
      );

多種事件

  • UI 事件

    • load

      • window上觸發(fā):??完全加載
        完橄唬,包括所有圖像赋焕、js?件、css
        ?件仰楚、<object>內(nèi)嵌對象等等
      • window上觸發(fā):??完全加載
        完隆判,包括所有圖像、js?件僧界、css
        ?件侨嘀、<object>內(nèi)嵌對象等等
      • <script>/<link>:腳本或css加
        載成功后。注意:script標(biāo)簽只
        ?持內(nèi)聯(lián)屬性
    • resize

    • scroll

      • window上觸發(fā):滾動??時
      • 元素:可滾動元素滾動時
    • 焦點(diǎn):可以捕獲捂襟,但不會冒泡

      • focus
      • blur
  • ?標(biāo)與滾輪事件

    • click

    • dblclick

    • mousedown

    • mouseup

    • mouseenter/mouseleave 與
      mouseover/mouseout

      • 觸發(fā)時機(jī)

        • 不論?標(biāo)指針穿過被選元素或其
          ?元素咬腕,都會觸發(fā) mouseover
          事件。
        • 只有在?標(biāo)指針穿過被選元素
          時葬荷,才會觸發(fā) mouseenter 事件
      • 是否冒泡

        • mouseenter/leave不?持冒泡
        • mouseover/mouseout?持冒泡
    • 位置信息

      • 客戶區(qū)坐標(biāo):event.clientX/Y
      • ??坐標(biāo):event.pageX/Y
      • 屏幕坐標(biāo):event.screenX/Y
    • 修改鍵

      • event.shiftKey
      • event.ctrlKey
      • event.altKey
      • event.metaKey
  • 鍵盤事件

    • keydown
    • keypress
    • keyup
    • 注意:通過 event.keyCode 獲取
      鍵碼
  • ?本事件

    • ?本框插??字之前:textInput
  • HTML5事件

    • ?標(biāo)右鍵:contextmenu

    • ??卸載前:beforeunload

    • 形成完成DOM樹:
      DOMcontentLoaded

    • 頁面可見性

      • pageshow

      • pagehide

      • 注意

        • pageshow/pagehide 必須添加
          到 window對象上
        • pageshow可?來監(jiān)聽??前進(jìn)
          后退:??顯示時觸發(fā)涨共,load 事
          件只會在第?次加載??是觸
          發(fā),之后??會被 bfcache(往
          返緩存)管理宠漩,通過前進(jìn)后退按
          鈕來顯示??時举反,load 事件并不
          會觸發(fā),但是 pageshow 事件會
          觸發(fā)
    • 路由的哈希值變化:
      hashchange

DOM事件模型

  • DOM0級事件

    • on-event (HTML 屬性)
  • DOM1級事件

    • 沒有1級DOM扒吁。DOM級別1于1998年10月1日成為W3C推薦標(biāo)準(zhǔn)照筑。1級DOM標(biāo)準(zhǔn)中并沒有定義事件相關(guān)的內(nèi)容,所以沒有所謂的1級DOM事件模型
  • DOM2級事件

    • el.addEventListener(event-name, callback, useCapture)
    • 規(guī)定DOM事件流
  • DOM 3級事件

    • 在DOM 2級事件的基礎(chǔ)上添加了更多的事件類型。(同多種事件)

事件代理(事件委托)

  • 由于事件會在冒泡階段向上傳播到父節(jié)點(diǎn)凝危,因此可以把子節(jié)點(diǎn)的監(jiān)聽函數(shù)定義在父節(jié)點(diǎn)上,由父節(jié)點(diǎn)的監(jiān)聽函數(shù)統(tǒng)一處理多個子元素的事件晨逝。這種方法叫做事件的代理(delegation)蛾默。
  • 優(yōu)點(diǎn):減少內(nèi)存消耗,提高性能
  • 通過e.currentTarget拿到目標(biāo)對象

JavaScript三大家族

client家族(只讀)

  • Element.clientWidth:元素內(nèi)部的寬度(單位像素)捉貌,包含內(nèi)邊距(padding)支鸡,但不包括豎直滾動條、邊框(border)和外邊距(margin)
  • Element.clientHeight:元素內(nèi)部的高度(單位像素)趁窃,包含內(nèi)邊距(padding)牧挣,但不包括水平滾動條、邊框(border)和外邊距(margin)
  • MouseEvent.clientX:鼠標(biāo)距離可視區(qū)域左側(cè)距離
  • MouseEvent.clientY:鼠標(biāo)距離可視區(qū)域上側(cè)距離
  • Element.clientTop:表示一個元素頂部邊框的寬度
  • Element.clientLeft:表示一個元素的左邊框的寬度

Scroll家族

  • Element.scrollWidth(只讀):對內(nèi)容寬度的一種度量醒陆,包括由于overflow溢出而在屏幕上不可見的內(nèi)容瀑构,元素內(nèi)部的高度(單位像素),包含內(nèi)邊距(padding)刨摩,但不包括豎直滾動條寺晌、邊框(border)和外邊距(margin)
  • Element.scrollHeight(只讀):對內(nèi)容高度的一種度量,包括由于overflow溢出而在屏幕上不可見的內(nèi)容澡刹,元素內(nèi)部的高度(單位像素)呻征,包含內(nèi)邊距(padding),但不包括水平滾動條罢浇、邊框(border)和外邊距(margin)
  • Element.scrollTop(讀取或設(shè)置):一個元素的 scrollTop 值是這個元素的內(nèi)容頂部(卷起來的)到它的視口可見內(nèi)容(的頂部)的距離的度量陆赋。當(dāng)一個元素的內(nèi)容沒有產(chǎn)生垂直方向的滾動條,那么它的 scrollTop 值為0
  • Element.scrollLeft(讀取或設(shè)置):一個元素的 scrollLeft 值是這個元素的內(nèi)容左部(卷起來的)到它的視口可見內(nèi)容(的左部)的距離的度量嚷闭。當(dāng)一個元素的內(nèi)容沒有產(chǎn)生垂直方向的滾動條攒岛,那么它的 scrollLeft 值為0

offset家族(只讀)

  • Element.offsetWidth:通常,元素的offsetWidtht是一種元素CSS寬度度的衡量標(biāo)準(zhǔn)凌受,包含元素的邊框(border)阵子、水平線上的內(nèi)邊距(padding)、水平方向滾動條(scrollbar)(如果存在的話)胜蛉、以及CSS設(shè)置的寬度(width)的值挠进。
  • Element.offsetHeight:通常,元素的offsetHeight是一種元素CSS寬度的衡量標(biāo)準(zhǔn)誊册,包含元素的邊框(border)领突、水平線上的內(nèi)邊距(padding)、豎直方向滾動條(scrollbar)(如果存在的話)案怯、以及CSS設(shè)置的寬度(width)的值君旦。
  • Element.offsetLeft:返回當(dāng)前元素左上角相對于 Element.offsetParent 節(jié)點(diǎn)的左邊界偏移的像素值
  • Element.offsetTop:返回當(dāng)前元素相對于其 Element.offsetParent 元素的頂部內(nèi)邊距的距離。
  • Element.offsetParent:返回父系盒子中帶有定位的盒子節(jié)點(diǎn),1.返回該對象帶有定位的父級 2.如果當(dāng)前元素的父級元素沒有CSS定位金砍, offsetParent為body局蚀;如果當(dāng)前元素的父級元素中有CSS定位,offsetParent 取最近的那個有定位的父級元素恕稠。和盒子本身有無定位無關(guān)琅绅。
  • Element.offsetX:規(guī)定了事件對象與目標(biāo)節(jié)點(diǎn)的內(nèi)填充邊(padding edge)在 X 軸方向上的偏移量。(目標(biāo)節(jié)點(diǎn)坐上角為原點(diǎn))
  • Element.offsetY

拓展

  • Element.getBoundingClientRect(): 方法返回元素的大小及其相對于視口的位置鹅巍。以CSSS設(shè)置寬高作為衡量標(biāo)準(zhǔn)
  • MouseEvent.pageX: pageX 是一個由MouseEvent接口返回的相對于整個文檔的x(水平)坐標(biāo)以像素為單位的只讀屬性千扶。
    (pageY一樣)
  • MouseEvent.creenX 是只讀屬性,他提供了鼠標(biāo)相對于屏幕坐標(biāo)系的水平偏移量骆捧。

GC回收機(jī)制

原始數(shù)據(jù)類型是存儲在椗煨撸空間中的,引用類型的數(shù)據(jù)是存儲在堆空間中的敛苇,也就是去分析如何回收這兩種類型的內(nèi)存空間

調(diào)用棧中的數(shù)據(jù)是如何回收的

  • JS引擎中以棧的形式來處理執(zhí)行上下文妆绞,而原始數(shù)據(jù)類型就存儲在棧中,調(diào)用棧有一個記錄當(dāng)前執(zhí)行狀態(tài)的指針(稱為 ESP)接谨,指向當(dāng)前正在處理的執(zhí)行上下文
  • 當(dāng)函數(shù)執(zhí)行完后摆碉,對應(yīng)的執(zhí)行上下文就可以銷毀了,JavaScript 引擎會通過向下移動 ESP 來銷毀該函數(shù)保存在棧中的執(zhí)行上下文脓豪。
  • 如果存在內(nèi)部函數(shù)引用變量(基本類型或者引用類型的都行)巷帝,這時候是放入到閉包對象中的,閉包對象是儲存在堆內(nèi)存空間中的扫夜,這屬于堆內(nèi)存那塊的知識點(diǎn)了

堆中的數(shù)據(jù)是如何回收的

  • 垃圾回收的策略:代際假說和分代收集

    • 概念:不過在正式介紹 V8 是如何實(shí)現(xiàn)回收之前楞泼,你需要先學(xué)習(xí)下代際假說(The Generational Hypothesis)的內(nèi)容,這是垃圾回收領(lǐng)域中一個重要的術(shù)語笤闯,后續(xù)垃圾回收的策略都是建立在該假說的基礎(chǔ)之上的堕阔,所以很是重要。

    • 代際假說有以下兩個特點(diǎn):

      • 第一個是大部分對象在內(nèi)存中存在的時間很短颗味,簡單來說超陆,就是很多對象一經(jīng)分配內(nèi)存,很快就變得不可訪問浦马;
      • 第二個是不死的對象时呀,會活得更久。
    • 在 V8 中會把堆分為新生代和老生代兩個區(qū)域晶默,新生代中存放的是生存時間短的對象谨娜,老生代中存放的生存時間久的對象

    • 新生代

      • 新生代中存放的是生存時間短的對象(新生區(qū)通常只支持 1~8M 的容量)
      • 副垃圾回收器,主要負(fù)責(zé)新生代的垃圾回收
    • 老生代

      • 老生代中存放的生存時間久的對象
      • 主垃圾回收器磺陡,主要負(fù)責(zé)老生代的垃圾回收
  • 垃圾回收器的工作流程

    • 現(xiàn)在你知道了 V8 把堆分成兩個區(qū)域——新生代和老生代趴梢,并分別使用兩個不同的垃圾回收器漠畜。其實(shí)不論什么類型的垃圾回收器,它們都有一套共同的執(zhí)行流程坞靶。
    • 第一步是標(biāo)記空間中活動對象和非活動對象憔狞。所謂活動對象就是還在使用的對象,非活動對象就是可以進(jìn)行垃圾回收的對象滩愁。
    • 第二步是回收非活動對象所占據(jù)的內(nèi)存躯喇。其實(shí)就是在所有的標(biāo)記完成之后,統(tǒng)一清理內(nèi)存中所有被標(biāo)記為可回收的對象硝枉。
    • 第三步是做內(nèi)存整理。一般來說倦微,頻繁回收對象后妻味,內(nèi)存中就會存在大量不連續(xù)空間,我們把這些不連續(xù)的內(nèi)存空間稱為內(nèi)存碎片欣福。當(dāng)內(nèi)存中出現(xiàn)了大量的內(nèi)存碎片之后责球,如果需要分配較大連續(xù)內(nèi)存的時候,就有可能出現(xiàn)內(nèi)存不足的情況拓劝。所以最后一步需要整理這些內(nèi)存碎片雏逾,但這步其實(shí)是可選的,因?yàn)橛械睦厥掌鞑粫a(chǎn)生內(nèi)存碎片郑临,比如接下來我們要介紹的副垃圾回收器栖博。
  • 副垃圾回收器

    • 副垃圾回收器主要負(fù)責(zé)新生區(qū)的垃圾回收。而通常情況下厢洞,大多數(shù)小的對象都會被分配到新生區(qū)仇让,所以說這個區(qū)域雖然不大,但是垃圾回收還是比較頻繁的躺翻。
    • 新生代中用 Scavenge 算法來處理丧叽。所謂 Scavenge 算法,是把新生代空間對半劃分為兩個區(qū)域公你,一半是對象區(qū)域踊淳,一半是空閑區(qū)域
    • 在垃圾回收過程中,首先要對對象區(qū)域中的垃圾做標(biāo)記陕靠;標(biāo)記完成之后迂尝,就進(jìn)入垃圾清理階段,副垃圾回收器會把這些存活的對象復(fù)制到空閑區(qū)域中懦傍,同時它還會把這些對象有序地排列起來雹舀,所以這個復(fù)制過程,也就相當(dāng)于完成了內(nèi)存整理操作,復(fù)制后空閑區(qū)域就沒有內(nèi)存碎片了特咆。
    • 完成復(fù)制后,對象區(qū)域與空閑區(qū)域進(jìn)行角色翻轉(zhuǎn)怠肋,也就是原來的對象區(qū)域變成空閑區(qū)域签财,原來的空閑區(qū)域變成了對象區(qū)域串慰。這樣就完成了垃圾對象的回收操作,同時這種角色翻轉(zhuǎn)的操作還能讓新生代中的這兩塊區(qū)域無限重復(fù)使用下去唱蒸。
    • 由于新生代中采用的 Scavenge 算法邦鲫,所以每次執(zhí)行清理操作時,都需要將存活的對象從對象區(qū)域復(fù)制到空閑區(qū)域神汹。但復(fù)制操作需要時間成本庆捺,如果新生區(qū)空間設(shè)置得太大了,那么每次清理的時間就會過久屁魏,所以為了執(zhí)行效率滔以,一般新生區(qū)的空間會被設(shè)置得比較小。
  • 對象晉升策略

    • 也正是因?yàn)樾律鷧^(qū)的空間不大氓拼,所以很容易被存活的對象裝滿整個區(qū)域你画。為了解決這個問題,JavaScript 引擎采用了對象晉升策略桃漾,也就是經(jīng)過兩次垃圾回收依然還存活的對象坏匪,會被移動到老生區(qū)中。
  • 主垃圾回收器

    • 垃圾回收器是采用標(biāo)記 - 清除(Mark-Sweep)的算法進(jìn)行垃圾回收的

    • 首先是標(biāo)記過程階段撬统。標(biāo)記階段就是從一組根元素開始适滓,遞歸遍歷這組根元素,在這個遍歷過程中宪摧,能到達(dá)的元素稱為活動對象粒竖,沒有到達(dá)的元素就可以判斷為垃圾數(shù)據(jù)。然后就是擦除這些垃圾數(shù)據(jù)

    • 標(biāo)記 - 整理(Mark-Compact)

      • 上面的標(biāo)記過程和清除過程就是標(biāo)記 - 清除算法几于,不過對一塊內(nèi)存多次執(zhí)行標(biāo)記 - 清除算法后蕊苗,會產(chǎn)生大量不連續(xù)的內(nèi)存碎片。而碎片過多會導(dǎo)致大對象無法分配到足夠的連續(xù)內(nèi)存沿彭,于是又產(chǎn)生了另外一種算法——標(biāo)記 - 整理(Mark-Compact)朽砰,這個標(biāo)記過程仍然與標(biāo)記 - 清除算法里的是一樣的,但后續(xù)步驟不是直接對可回收對象進(jìn)行清理喉刘,而是讓所有存活的對象都向一端移動瞧柔,然后直接清理掉端邊界以外的內(nèi)
    • 增量標(biāo)記算法

      • 在 V8 新生代的垃圾回收中,因其空間較小睦裳,且存活對象較少造锅,所以全停頓的影響不大,但老生代就不一樣了廉邑。如果在執(zhí)行垃圾回收的過程中哥蔚,占用主線程時間過久倒谷,就像上面圖片展示的那樣,花費(fèi)了 200 毫秒糙箍,在這 200 毫秒內(nèi)渤愁,主線程是不能做其他事情的。比如頁面正在執(zhí)行一個 JavaScript 動畫深夯,因?yàn)槔厥掌髟诠ぷ鞫陡瘢蜁?dǎo)致這個動畫在這 200 毫秒內(nèi)無法執(zhí)行的,這將會造成頁面的卡頓現(xiàn)象咕晋。
      • 為了降低老生代的垃圾回收而造成的卡頓雹拄,V8 將標(biāo)記過程分為一個個的子標(biāo)記過程,同時讓垃圾回收標(biāo)記和 JavaScript 應(yīng)用邏輯交替進(jìn)行掌呜,直到標(biāo)記階段完成办桨,我們把這個算法稱為增量標(biāo)記(Incremental Marking)算法
      • 使用增量標(biāo)記算法,可以把一個完整的垃圾回收任務(wù)拆分為很多小的任務(wù)站辉,這些小的任務(wù)執(zhí)行時間比較短,可以穿插在其他的 JavaScript 任務(wù)中間執(zhí)行损姜,這樣當(dāng)執(zhí)行上述動畫效果時饰剥,就不會讓用戶因?yàn)槔厥杖蝿?wù)而感受到頁面的卡頓了。(??將大的任務(wù)拆分成小 減少卡頓)

??注意什么時候進(jìn)行標(biāo)記 什么時候進(jìn)行清除

  • 當(dāng)執(zhí)行上下文創(chuàng)建時摧阅,變量進(jìn)入該環(huán)境汰蓉,我們就可以對該變量對應(yīng)的內(nèi)存進(jìn)行標(biāo)記。如果執(zhí)行上下文執(zhí)行完畢棒卷,這個時候顾孽,就可以將所有進(jìn)入該環(huán)境的變量標(biāo)記為可清除狀態(tài)。我們通俗的說法就是比规,當(dāng)一份內(nèi)存失去了引用若厚,那么它就會被垃圾回收工具回收。
  • 不過還有兩個需要注意的地方蜒什。

一個是全局上下文测秸。在程序結(jié)束之前,全局上下文始終存在灾常。通常來說霎冯,JS程序運(yùn)行期間,全局上下文不會有執(zhí)行結(jié)束的時間節(jié)點(diǎn)钞瀑。因此定義在全局上下文的狀態(tài)永遠(yuǎn)都不會被標(biāo)記沈撞。除非我們手動將變量設(shè)置為null,它對應(yīng)的內(nèi)存都不會被回收

  • JS引擎執(zhí)行代碼是邊解釋邊執(zhí)行雕什,對于未執(zhí)行的函數(shù)代碼段 都還沒到到編譯階段呢缠俺,也不會分配變量內(nèi)存給他們了显晶,執(zhí)行到這個階段才會的

閉包

什么是閉包

  • 在 JavaScript 中,根據(jù)詞法作用域的規(guī)則晋修,內(nèi)部函數(shù)總是可以訪問其外部函數(shù)中聲明的變量吧碾,當(dāng)通過調(diào)用一個外部函數(shù)返回一個內(nèi)部函數(shù)后,即使該外部函數(shù)已經(jīng)執(zhí)行結(jié)束了墓卦,但是內(nèi)部函數(shù)引用外部函數(shù)的變量依然保存在內(nèi)存中倦春,我們就把這些變量的集合稱為閉包。比如外部函數(shù)是 foo落剪,那么這些變量的集合就稱為 foo 函數(shù)的閉包

從內(nèi)存模型的角度來分析閉包

  • 代碼例子:
    function foo() {
    var myName = "極客時間"
    let test1 = 1
    const test2 = 2
    var innerBar = {
    setName:function(newName){
    myName = newName
    },
    getName:function(){
    console.log(test1)
    return myName
    }
    }
    return innerBar
    }
    var bar = foo()
    bar.setName("極客邦")
    bar.getName()
    console.log(bar.getName())
  • 當(dāng) JavaScript 引擎執(zhí)行到 foo 函數(shù)時睁本,首先會編譯,并創(chuàng)建一個空執(zhí)行上下文
  • 在編譯過程中忠怖,遇到內(nèi)部函數(shù) setName呢堰,JavaScript 引擎還要對內(nèi)部函數(shù)做一次快速的詞法掃描,發(fā)現(xiàn)該內(nèi)部函數(shù)引用了 foo 函數(shù)中的 myName 變量凡泣,由于是內(nèi)部函數(shù)引用了外部函數(shù)的變量枉疼,所以 JavaScript 引擎判斷這是一個閉包,于是在堆空間創(chuàng)建換一個“closure(foo)”的對象(這是一個內(nèi)部對象鞋拟,JavaScript 是無法訪問的)骂维,用來保存 myName 變量。
  • 接著繼續(xù)掃描到 getName 方法時贺纲,發(fā)現(xiàn)該函數(shù)內(nèi)部還引用變量 test1航闺,于是 JavaScript 引擎又將 test1 添加到“closure(foo)”對象中。這時候堆中的“closure(foo)”對象中就包含了 myName 和 test1 兩個變量了猴誊。
  • 由于 test2 并沒有被內(nèi)部函數(shù)引用潦刃,所以 test2 依然保存在調(diào)用棧中。
  • ??閉包對象的創(chuàng)建在編譯的時候創(chuàng)建懈叹,在編譯過程中乖杠,遇到內(nèi)部函數(shù) setName,JavaScript 引擎還要對內(nèi)部函數(shù)做一次快速的詞法掃描项阴,所以這也解釋了閉包對象的名稱是外部函數(shù)滑黔,這是對的 (外面的文章寫內(nèi)部函數(shù)是閉包,這是錯誤的?)
  • ??總的來說环揽,產(chǎn)生閉包的核心有兩步:第一步是需要預(yù)掃描內(nèi)部函數(shù)略荡;第二步是把內(nèi)部函數(shù)引用的外部變量保存到堆中,在編譯階段就會產(chǎn)生閉包對象(前提是函數(shù)跟函數(shù)之前)歉胶,如果閉包對象存在引用汛兜,就不會被銷毀,這也是要注意到內(nèi)存泄漏的地方(??總注意形成閉包的前提是 外部函數(shù)和內(nèi)部函數(shù))

閉包對象是編譯階段就產(chǎn)生的通今,如果存在引用則不會被GC回收機(jī)制回收

閉包的應(yīng)用

  • 模仿塊級作用域
  • 私有變量

執(zhí)行上下文

概念:JavaScript代碼在執(zhí)行時粥谬,會進(jìn)入一個執(zhí)行上下文中肛根。執(zhí)行上下文可以理解為當(dāng)前代碼的運(yùn)行環(huán)境

執(zhí)行上下文的三種類型

  • 全局環(huán)境:代碼運(yùn)行起來后會首先進(jìn)入全局環(huán)境
  • 函數(shù)環(huán)境:當(dāng)函數(shù)被調(diào)用執(zhí)行時,會進(jìn)入當(dāng)前函數(shù)中的執(zhí)行代碼
  • eval環(huán)境:不建議使用漏策,不做介紹

執(zhí)行上下文的生命周期

  • 編譯階段(創(chuàng)建階段)

    • 經(jīng)過JS引擎編譯后派哲,會生成兩部分內(nèi)容:執(zhí)行上下文(Execution context)和可執(zhí)行代碼
    • 在這個階段,執(zhí)行上下文會分別創(chuàng)建變量對象掺喻、確定作用域鏈芭届,以及this指向,明白這個階段也就會明白變量提升的現(xiàn)象,(也就是在編譯階段我們就確定了作用域鏈和this指向等)
  • 執(zhí)行階段

    • 創(chuàng)建階段之后感耙,就會開始執(zhí)行代碼褂乍,這個時候會完成變量賦值,函數(shù)引用即硼,以及執(zhí)行其它可執(zhí)行代碼逃片,如圖所示

變量對象

  • 在JavaScript代碼中聲明的所有變量都保存在變量對象中,除此之外只酥,變量對象中還可能包含以下內(nèi)容

  • 函數(shù)的所有參數(shù)(Firefox中為參數(shù)對象arguments)

  • 當(dāng)前上下文中的所有函數(shù)聲明(通過function聲明的函數(shù))

  • 當(dāng)前上下文的所有變量聲明(通過var聲明的變量)

  • 變量對象創(chuàng)建過程

    • 在Chrome瀏覽器中褥实,變量對象會首先獲得函數(shù)的參數(shù)變量及其值;在Firefox瀏覽器中裂允,是直接將參數(shù)對象arguments保存在變量對象中
    • 依次獲取當(dāng)前上下文中所有的函數(shù)聲明性锭,也就是使用function關(guān)鍵字聲明的函數(shù)。在變量中會以函數(shù)名建立一個屬性叫胖,屬性值為指向該函數(shù)所在的內(nèi)存地址引用。如果函數(shù)名的屬性已經(jīng)存在她奥,那么該屬性的值會被新的引用覆蓋
    • 依次獲取當(dāng)前上下文的變量聲明瓮增,也就是以var關(guān)鍵字聲明的變量。每找到一個變量聲明哩俭,就在變量對象中就以變量名建立一個屬性绷跑,屬性值為undefined,如果該變量名的屬性已經(jīng)存在凡资,為了防止同名的函數(shù)被修改為undefined,則會直接跳過砸捏,原屬性不會被修改,也就是如果變量與函數(shù)同名,則在這個階段隙赁,以函數(shù)值為準(zhǔn)

作用域和作用域鏈

  • 作用域

    • 作用域規(guī)定了如何查找變量垦藏,也就是確定當(dāng)前執(zhí)行代碼對變量的訪問權(quán)限。

    • 詞法作用域

      • 詞法作用域就是指作用域是由代碼中函數(shù)聲明的位置來決定的伞访,所以詞法作用域是靜態(tài)的作用域掂骏,通過它就能夠預(yù)測代碼在執(zhí)行過程中如何查找標(biāo)識符。(簡單理解書寫??代碼的時候厚掷,詞法作用域是代碼階段就決定好的弟灼,和函數(shù)是怎么調(diào)用的沒有關(guān)系)

        • 全局作用域

          • 最外層函數(shù)和在最外層函數(shù)外面定義的變量
          • 沒有通過關(guān)鍵字"var"聲明的變量(包括嵌套的函數(shù)內(nèi))
          • 瀏覽器中级解,window對象的屬性
        • 函數(shù)作用域

        • 塊級作用域

  • 動態(tài)作用域

  • 作用域鏈

    • 作用域鏈,是由當(dāng)前環(huán)境與上層環(huán)境的一系列變量對象組成田绑,它保證了當(dāng)前執(zhí)行環(huán)境對符合訪問權(quán)限的變量和函數(shù)的有序訪問勤哗。
    • 編譯階段就創(chuàng)造了作用域鏈,內(nèi)部函數(shù)copy拷貝外部函數(shù)的作用域掩驱,在函數(shù)內(nèi)部有[[scope]]屬性就是表示作用域鏈
    • ??理解作用域鏈?zhǔn)抢斫忾]包的基礎(chǔ)

V8

高級語言

  • 編譯型語言在程序執(zhí)行之前芒划,需要經(jīng)過編譯器的編譯過程,并且編譯之后會直接保留機(jī)器能讀懂的二進(jìn)制文件昙篙,這樣每次運(yùn)行程序時腊状,都可以直接運(yùn)行該二進(jìn)制文件,而不需要再次重新編譯了苔可。比如 C/C++缴挖、GO 等都是編譯型語言。

    • 在編譯型語言的編譯過程中焚辅,編譯器首先會依次對源代碼進(jìn)行詞法分析映屋、語法分析,生成抽象語法樹(AST)同蜻,然后是優(yōu)化代碼棚点,最后再生成處理器能夠理解的機(jī)器碼。如果編譯成功湾蔓,將會生成一個可執(zhí)行的文件瘫析。但如果編譯過程發(fā)生了語法或者其他的錯誤,那么編譯器就會拋出異常默责,最后的二進(jìn)制文件也不會生成成功
  • 而由解釋型語言編寫的程序贬循,在每次運(yùn)行時都需要通過解釋器對程序進(jìn)行動態(tài)解釋和執(zhí)行。比如 Python桃序、JavaScript 等都屬于解釋型語言杖虾。

    • 在解釋型語言的解釋過程中,同樣解釋器也會對源代碼進(jìn)行詞法分析媒熊、語法分析奇适,并生成抽象語法樹(AST),不過它會再基于抽象語法樹生成字節(jié)碼芦鳍,最后再根據(jù)字節(jié)碼來執(zhí)行程序嚷往、輸出結(jié)果。

V8 是如何執(zhí)行一段 JavaScript 代碼的

  • 生成抽象語法樹(AST)和執(zhí)行上下文

    • 將源代碼轉(zhuǎn)換為抽象語法樹柠衅,并生成執(zhí)行上下文间影,主要是代碼在執(zhí)行過程中的環(huán)境信息

    • 高級語言是開發(fā)者可以理解的語言,但是讓編譯器或者解釋器來理解就非常困難了。對于編譯器或者解釋器來說魂贬,它們可以理解的就是 AST 了巩割。所以無論你使用的是解釋型語言還是編譯型語言,在編譯過程中付燥,它們都會生成一個 AST(解釋器和編譯器先把源代碼編譯成AST)

    • 如何生成AST

      • 第一階段是分詞(tokenize)宣谈,又稱為詞法分析,其作用是將一行行的源碼拆解成一個個 token键科。所謂 token闻丑,指的是語法上不可能再分的、最小的單個字符或字符串勋颖。你可以參考下圖來更好地理解什么 token
      • 第二階段是解析(parse)嗦嗡,又稱為語法分析,其作用是將上一步生成的 token 數(shù)據(jù)饭玲,根據(jù)語法規(guī)則轉(zhuǎn)為 AST侥祭。如果源碼符合語法規(guī)則,這一步就會順利完成茄厘。但如果源碼存在語法錯誤矮冬,這一步就會終止,并拋出一個“語法錯誤”次哈。(??有了 AST 后胎署,那接下來 V8 就會生成該段代碼的執(zhí)行上下文)
  • 生成字節(jié)碼

    • 有了 AST 和執(zhí)行上下文后,那接下來的第二步窑滞,解釋器 Ignition 就登場了(解釋器也負(fù)責(zé)了解釋源代碼到AST任務(wù))琼牧,它會根據(jù) AST 生成字節(jié)碼,并解釋執(zhí)行字節(jié)碼哀卫。
    • 字節(jié)碼就是介于 AST 和機(jī)器碼之間的一種代碼障陶。但是與特定類型的機(jī)器碼無關(guān),字節(jié)碼需要通過解釋器將其轉(zhuǎn)換為機(jī)器碼后才能執(zhí)行聊训。
  • 執(zhí)行代碼

    • 生成字節(jié)碼之后,接下來就要進(jìn)入執(zhí)行階段了恢氯。
    • 通常带斑,如果有一段第一次執(zhí)行的字節(jié)碼,解釋器 Ignition 會逐條解釋執(zhí)行勋拟。到了這里勋磕,相信你已經(jīng)發(fā)現(xiàn)了,解釋器 Ignition 除了負(fù)責(zé)生成字節(jié)碼之外敢靡,它還有另外一個作用挂滓,就是解釋執(zhí)行字節(jié)碼。在 Ignition 執(zhí)行字節(jié)碼的過程中啸胧,如果發(fā)現(xiàn)有熱點(diǎn)代碼(HotSpot)赶站,比如一段代碼被重復(fù)執(zhí)行多次幔虏,這種就稱為熱點(diǎn)代碼,那么后臺的編譯器 TurboFan 就會把該段熱點(diǎn)的字節(jié)碼編譯為高效的機(jī)器碼贝椿,然后當(dāng)再次執(zhí)行這段被優(yōu)化的代碼時想括,只需要執(zhí)行編譯后的機(jī)器碼就可以了,這樣就大大提升了代碼的執(zhí)行效率烙博。

內(nèi)存機(jī)制

三種類型內(nèi)存空間

  • 代碼空間

  • 椛冢空間

    • 原始類型的數(shù)據(jù)值都是直接保存在“棧”中的
  • 堆空間

    • 引用類型的值是存放在“堆”中的

事件循環(huán)

起因:在 JS 中渣窜,大部分的任務(wù)都是在主線程上執(zhí)行铺根,常見的任務(wù)有渲染事件,用戶交互事件乔宿,js腳本執(zhí)行位迂,網(wǎng)絡(luò)請求、文件讀寫完成事件等等予颤,為了讓這些事件有條不紊地進(jìn)行囤官,JS引擎需要對之執(zhí)行的順序做一定的安排,V8 其實(shí)采用的是一種隊(duì)列的方式來存儲這些任務(wù)蛤虐, 即先進(jìn)來的先執(zhí)行党饮。

注意??事件循環(huán)不單單是為了解決JS是單線程(渲染主線程)解決異步的原因,而是更大更全的去理解他是瀏覽器渲染主線程的調(diào)度系統(tǒng)驳庭,通過這個調(diào)度系統(tǒng)去有條不紊的安排任務(wù)執(zhí)行(JavaScript沒有自己循環(huán)系統(tǒng)刑顺,它依賴的就是瀏覽器的循環(huán)系統(tǒng),也就是渲染進(jìn)程提供的循環(huán)系統(tǒng)K浅!)

宏任務(wù)

  • 渲染事件(如解析 DOM蹲堂、計(jì)算布局、繪制)
  • 用戶交互事件(如鼠標(biāo)點(diǎn)擊贝淤、滾動頁面柒竞、放大縮小等)
  • JavaScript 腳本執(zhí)行事件;
  • 網(wǎng)絡(luò)請求完成播聪、文件讀寫完成事件朽基。

微任務(wù)

  • 起因

    • 宏任務(wù)的時間粒度比較大,執(zhí)行的時間間隔是不能精確控制的离陶,對一些高實(shí)時性的需求就不太符合了稼虎,比如后面要介紹的監(jiān)聽 DOM 變化的需求

      • 如何處理高優(yōu)先級的任務(wù)。
    • 監(jiān)聽 DOM 變化技術(shù)方案的演化史

      • 從輪詢到 Mutation Event 再到最新使用的 MutationObserver招刨。MutationObserver 方案的核心就是采用了微任務(wù)機(jī)制霎俩,有效地權(quán)衡了實(shí)時性和執(zhí)行效率的問題
  • 我們知道當(dāng) JavaScript 執(zhí)行一段腳本的時候,V8 會為其創(chuàng)建一個全局執(zhí)行上下文,在創(chuàng)建全局執(zhí)行上下文的同時打却,V8 引擎也會在內(nèi)部創(chuàng)建一個微任務(wù)隊(duì)列杉适。顧名思義,這個微任務(wù)隊(duì)列就是用來存放微任務(wù)的学密,因?yàn)樵诋?dāng)前宏任務(wù)執(zhí)行的過程中淘衙,有時候會產(chǎn)生多個微任務(wù),這時候就需要使用這個微任務(wù)隊(duì)列來保存這些微任務(wù)了腻暮。不過這個微任務(wù)隊(duì)列是給 V8 引擎內(nèi)部使用的彤守,所以你是無法通過 JavaScript 直接訪問的

  • ??也就是說每個宏任務(wù)都關(guān)聯(lián)了一個微任務(wù)隊(duì)列

  • 微任務(wù)的工作流程

    • 微任務(wù)和宏任務(wù)是綁定的,每個宏任務(wù)在執(zhí)行時哭靖,會創(chuàng)建自己的微任務(wù)隊(duì)列
    • 微任務(wù)的執(zhí)行時長會影響到當(dāng)前宏任務(wù)的時長具垫。比如一個宏任務(wù)在執(zhí)行過程中,產(chǎn)生了 100 個微任務(wù)试幽,執(zhí)行每個微任務(wù)的時間是 10 毫秒筝蚕,那么執(zhí)行這 100 個微任務(wù)的時間就是 1000 毫秒,也可以說這 100 個微任務(wù)讓宏任務(wù)的執(zhí)行時間延長了 1000 毫秒铺坞。所以你在寫代碼的時候一定要注意控制微任務(wù)的執(zhí)行時長起宽。
    • 在一個宏任務(wù)中,分別創(chuàng)建一個用于回調(diào)的宏任務(wù)和微任務(wù)济榨,無論什么情況下坯沪,微任務(wù)都早于宏任務(wù)執(zhí)行。
  • MutationObserver

  • Promise.then(或.reject) 以及以 Promise 為基礎(chǔ)開發(fā)的其他技術(shù)(比如fetch API)

消息隊(duì)列

  • 基于不同的場景來動態(tài)調(diào)整消息隊(duì)列的優(yōu)先級

    • 可以創(chuàng)建輸入事件的消息隊(duì)列擒滑,用來存放輸入事件腐晾。
    • 可以創(chuàng)建合成任務(wù)的消息隊(duì)列,用來存放合成事件丐一。
    • 可以創(chuàng)建默認(rèn)消息隊(duì)列藻糖,用來保存如資源加載的事件和定時器回調(diào)等事件。
    • 還可以創(chuàng)建一個空閑消息隊(duì)列库车,用來存放 V8 的垃圾自動垃圾回收這一類實(shí)時性不高的事件巨柒。

事件循環(huán)的流程

  • 宏任務(wù)==>清空所有的微任務(wù)===>UI渲染

rAF

  • window.requestAnimationFrame() 告訴瀏覽器——你希望執(zhí)行一個動畫,并且要求瀏覽器在下次重繪之前調(diào)用指定的回調(diào)函數(shù)更新動畫柠衍。該方法需要傳入一個回調(diào)函數(shù)作為參數(shù)洋满,該回調(diào)函數(shù)會在瀏覽器下一次重繪之前執(zhí)行

  • VSync

    • 當(dāng)顯示器將一幀畫面繪制完成后,并在準(zhǔn)備讀取下一幀之前拧略,顯示器會發(fā)出一個垂直同步信號(vertical synchronization)給 GPU,簡稱 VSync
  • 為什么需要用rAF代替setTimeout

    • 我們知道 CSS 動畫是由渲染進(jìn)程自動處理的瘪弓,所以渲染進(jìn)程會讓 CSS 渲染每幀動畫的過程與 VSync 的時鐘保持一致, 這樣就能保證 CSS 動畫的高效率執(zhí)行垫蛆。
    • 用戶體驗(yàn)動畫流暢的幀率大概是60FPS,使用setTimout很難精確控制,可所以使用rAF交由系統(tǒng)控制袱饭,保持跟顯示器的幀率大概一致
    • 但是 JavaScript 是由用戶控制的川无,如果采用 setTimeout 來觸發(fā)動畫每幀的繪制,那么其繪制時機(jī)是很難和 VSync 時鐘保持一致的虑乖,所以 JavaScript 中又引入了 window.requestAnimationFrame懦趋,用來和 VSync 的時鐘周期同步
    • VSync 和系統(tǒng)的時鐘不同步就會造成掉幀、卡頓疹味、不連貫等問題
  • requestAnimationFrame 在 EventLoop 中是一個什么位置仅叫?

    • rAF會在UI渲染之前

問題

  • UI渲染也會產(chǎn)生宏任務(wù),那么按照實(shí)際循環(huán)流程糙捺,是會無限遞歸的那種

    • 消息隊(duì)列也是分為優(yōu)先級的(雖然微任務(wù)是高優(yōu)先級任務(wù) 但是依賴于宏任務(wù) 比如交互操作 點(diǎn)擊事件產(chǎn)生的回調(diào)是個宏任務(wù) )也就是這個每次執(zhí)行一次宏任務(wù)诫咱,觸發(fā)UI渲染 這個宏任務(wù)應(yīng)該屬于交互消息隊(duì)列的類型的, 應(yīng)該是根據(jù)消息隊(duì)列類別來判斷的 (本身屬于合成消息隊(duì)列就不會再觸發(fā)UI渲染了)
  • 觸發(fā)一次宏任務(wù)就一定會執(zhí)行UI渲染嗎

    • 進(jìn)入更新渲染階段洪灯,判斷是否需要渲染坎缭,這里有一個 rendering opportunity 的概念,也就是說不一定每一輪 event loop 都會對應(yīng)一次瀏覽 器渲染签钩,要根據(jù)屏幕刷新率掏呼、頁面性能、頁面是否在后臺運(yùn)行來共同決定铅檩,通常來說這個渲染間隔是固定的憎夷。(所以多個 task 很可能在一次渲染之間執(zhí)行)
  • 靜止不動的頁面需要每隔16ms觸發(fā)一次UI渲染嗎

    • 我覺得完全沒必要,因?yàn)闆]啥意義柠并,都靜止不動了岭接,根據(jù)瀏覽器FPS觀察幾乎為0,所以說瀏覽器不一定非跟顯示器保持百分百的幀率一致
  • 16ms

    • 渲染幀是指瀏覽器一次完整繪制過程臼予,幀之間的時間間隔是 DOM 視圖更新的最小間隔鸣戴。 由于主流的屏幕刷新率都在 60Hz,那么渲染一幀的時間就必須控制在 16ms 才能保證不掉幀粘拾。 也就是說每一次渲染都要在 16ms 內(nèi)頁面才夠流暢不會有卡頓感

    • 為什么需要這個判斷窄锅,為了動畫順暢性,所以不存在時間基點(diǎn)判定缰雇,你交互開始時候就算入偷,保持一幀16ms左右就是流暢届惋,也就是滿足這個幀率間隔就不會卡頓 (我這個交互或者動畫是幀率60左右就是流暢 靜止的頁面都不需要流暢這個概念 幀率為0就行 所以不要有絕對的那種時間線)

      • scroll
      • resize
    • 這段時間內(nèi)瀏覽器需要完成如下事情

      • 腳本執(zhí)行(JavaScript):腳本造成了需要重繪的改動痕届,比如增刪 DOM惨恭、請求動畫等
      • 樣式計(jì)算(CSS Object Model):級聯(lián)地生成每個節(jié)點(diǎn)的生效樣式杆故。
      • 布局(Layout):計(jì)算布局歌粥,執(zhí)行渲染算法
      • 重繪(Paint):各層分別進(jìn)行繪制(比如 3D 動畫)
      • 合成(Composite):合成各層的渲染結(jié)果

函數(shù)式編程

高階函數(shù)

  • 高階函數(shù)(higher-order function)指操作函數(shù)的函數(shù)诈豌,一般地婆翔,有以下兩種情況

  • 函數(shù)可以作為參數(shù)被傳遞

  • 函數(shù)可以作為返回值輸出

  • 作用

    • 增強(qiáng)函數(shù)的功能姥宝,Redux中間件就是高階函數(shù)的產(chǎn)物

純函數(shù)

  • 定義:純函數(shù)是這樣一種函數(shù),即相同的輸入其骄,永遠(yuǎn)會得到相同的輸出亏镰,而且沒有任何可觀察的副作用。
  • 場景:比如 slice 和 splice拯爽,這兩個函數(shù)的作用并無二致——但是注意索抓,它們各自的方式卻大不同,但不管怎么說作用還是一樣的毯炮。我們說 slice 符合純函數(shù)的定義是因?yàn)閷ο嗤妮斎胨WC能返回相同的輸出逼肯。而 splice 卻會嚼爛調(diào)用它的那個數(shù)組,然后再吐出來否副;這就會產(chǎn)生可觀察到的副作用汉矿,即這個數(shù)組永久地改變了。

函數(shù)柯里化

  • 概念:在數(shù)學(xué)和計(jì)算機(jī)科學(xué)中备禀,柯里化是一種將使用多個參數(shù)的一個函數(shù)轉(zhuǎn)換成一系列使用一個參數(shù)的函數(shù)的技術(shù)洲拇。

  • 作用

    • 參數(shù)復(fù)用

    • 提前返回

      • 比如判斷一次類型以后下次直接使用該類型對應(yīng)的特性就行
    • 延遲計(jì)算/運(yùn)行

  • /** 利用遞歸加函數(shù)的length熟悉實(shí)現(xiàn)柯里化 */
    const curry = fn =>
    judge = (...args) =>
    args.length === fn.length
    ? fn(...args)
    : (arg) => judge(...args, arg)

偏函數(shù)

  • 概念:在計(jì)算機(jī)科學(xué)中,局部應(yīng)用是指固定一個函數(shù)的一些參數(shù)曲尸,然后產(chǎn)生另一個更小元的函數(shù)赋续。

  • 柯里化與局部應(yīng)用

    • 柯里化是將一個多參數(shù)函數(shù)轉(zhuǎn)換成多個單參數(shù)函數(shù),也就是將一個 n 元函數(shù)轉(zhuǎn)換成 n 個一元函數(shù)另患。
    • 局部應(yīng)用則是固定一個函數(shù)的一個或者多個參數(shù)纽乱,也就是將一個 n 元函數(shù)轉(zhuǎn)換成一個 n - x 元函數(shù)。
  • 實(shí)現(xiàn)

    • 使用bind:add.bind(null, 1),然而使用 bind 我們還是改變了 this 指向昆箕,我們要寫一個不改變 this 指向的方法鸦列。
    • function partial(fn) {
      var args = [].slice.call(arguments, 1);
      return function() {
      var newArgs = args.concat([].slice.call(arguments));
      return fn.apply(this, newArgs);
      };
      };

惰性函數(shù)

  • 概念:惰性載入表示函數(shù)執(zhí)行的分支只會在函數(shù)第一次調(diào)用的時候執(zhí)行,在第一次調(diào)用過程中鹏倘,該函數(shù)會被覆蓋為另一個按照合適方式執(zhí)行的函數(shù)薯嗤,這樣任何對原函數(shù)的調(diào)用就不用再經(jīng)過執(zhí)行的分支了。

  • const foo = function() {
    var t = new Date();
    foo = function() {
    return t;
    };
    return foo();
    }; // 重寫覆蓋foo函數(shù)

  • 應(yīng)用

    • DOM 事件添加中纤泵,為了兼容現(xiàn)代瀏覽器和 IE 瀏覽器骆姐,我們需要對瀏覽器環(huán)境進(jìn)行一次判斷

函數(shù)組合

  • 概念:函數(shù)組合就是組合兩到多個函數(shù)來生成一個新函數(shù)的過程。 將函數(shù)組合在一起捏题,就像將一連串管道扣合在一起玻褪,讓數(shù)據(jù)流過一樣。 簡而言之公荧,函數(shù) f 和 g 的組合可以被定義為 f(g(x)) 带射,從內(nèi)到外(從右到左)求值

  • function compose(...funcs) {
    if (funcs.length === 0) {
    return arg => arg
    }

    if (funcs.length === 1) {
    return funcs[0]
    }

    return funcs.reduce((a, b) => (...args) => a(b(...args)))
    }

  • f1(f2(f3(..args))) 從右到左 compose(f1,f2,f3)(...args) 函數(shù)f3執(zhí)行過后把值return給f2

  • reduce方法

    • callback:執(zhí)行數(shù)組中每個值 (如果沒有提供 initialValue則第一個值除外)的函數(shù),包含四個參數(shù):

      • accumulator:累計(jì)器累計(jì)回調(diào)的返回值; 它是上一次調(diào)用回調(diào)時返回的累積值循狰,或initialValue(見于下方)
      • currentValue:數(shù)組中正在處理的元素窟社。
      • index 可選
        數(shù)組中正在處理的當(dāng)前元素的索引捻浦。 如果提供了initialValue,則起始索引號為0桥爽,否則從索引1起始。
      • array:調(diào)用reduce()的數(shù)組
      • initialValue:作為第一次調(diào)用 callback函數(shù)時的第一個參數(shù)的值昧识。 如果沒有提供初始值钠四,則將使用數(shù)組中的第一個元素。 在沒有初始值的空數(shù)組上調(diào)用 reduce 將報(bào)錯跪楞。沒有提供initialValue的話累計(jì)計(jì)算就從數(shù)組下標(biāo)1開始

函數(shù)記憶

  • 函數(shù)記憶是指將上次的計(jì)算結(jié)果緩存起來缀去,當(dāng)下次調(diào)用時,如果遇到相同的參數(shù)甸祭,就直接返回緩存中的數(shù)據(jù)缕碎。(簡單點(diǎn)講就是緩存函數(shù))
  • let memoize = function (func, content) {
    let cache = Object.create(null)
    content = content || this
    return (...key) => {
    if (!cache[key]) {
    cache[key] = func.apply(content, key)
    }
    return cache[key]
    }
    }

XMind - Trial Version

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市池户,隨后出現(xiàn)的幾起案子咏雌,更是在濱河造成了極大的恐慌,老刑警劉巖校焦,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赊抖,死亡現(xiàn)場離奇詭異,居然都是意外死亡寨典,警方通過查閱死者的電腦和手機(jī)氛雪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來耸成,“玉大人报亩,你說我怎么就攤上這事【猓” “怎么了弦追?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長毙沾。 經(jīng)常有香客問我骗卜,道長,這世上最難降的妖魔是什么左胞? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任寇仓,我火速辦了婚禮,結(jié)果婚禮上烤宙,老公的妹妹穿的比我還像新娘遍烦。我一直安慰自己,他們只是感情好躺枕,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布服猪。 她就那樣靜靜地躺著供填,像睡著了一般。 火紅的嫁衣襯著肌膚如雪罢猪。 梳的紋絲不亂的頭發(fā)上近她,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天,我揣著相機(jī)與錄音膳帕,去河邊找鬼粘捎。 笑死,一個胖子當(dāng)著我的面吹牛危彩,可吹牛的內(nèi)容都是我干的攒磨。 我是一名探鬼主播,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼汤徽,長吁一口氣:“原來是場噩夢啊……” “哼娩缰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起谒府,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤拼坎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后完疫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體演痒,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年趋惨,在試婚紗的時候發(fā)現(xiàn)自己被綠了鸟顺。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡器虾,死狀恐怖讯嫂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情兆沙,我是刑警寧澤欧芽,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站葛圃,受9級特大地震影響千扔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜库正,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一曲楚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧褥符,春花似錦龙誊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鹤树。三九已至,卻和暖如春逊朽,著一層夾襖步出監(jiān)牢的瞬間罕伯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工叽讳, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留捣炬,地道東北人。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓绽榛,卻偏偏與公主長得像,于是被迫代替她去往敵國和親婿屹。 傳聞我的和親對象是個殘疾皇子灭美,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評論 2 351

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

  • JavaScript的組成 JavaScript 由以下三部分組成:ECMAScript(核心):JavaScri...
    紋小艾閱讀 3,174評論 0 3
  • js基礎(chǔ) 1.javaScript的數(shù)據(jù)類型有什么 基本數(shù)據(jù)類型:Undefined、Null昂利、Boolean届腐、N...
    說書人_子將閱讀 515評論 0 0
  • 原型鏈的理解 看一個實(shí)例 原型和原型鏈?zhǔn)紫纫缼讉€概念:在js里,繼承機(jī)制是原型繼承蜂奸。繼承的起點(diǎn)是 對象的原型(...
    __bomb__閱讀 136評論 0 0
  • 目錄 1犁苏、談?wù)勀銓jax的理解?(概念扩所、特點(diǎn)围详、作用) 2、說說你對延遲對象deferred的理解? 3祖屏、什么是跨...
    w_zhuan閱讀 987評論 1 28
  • 黑色的海島上懸著一輪又大又圓的明月袁勺,毫不嫌棄地把溫柔的月色照在這寸草不生的小島上雹食。一個少年白衣白發(fā),悠閑自如地倚坐...
    小水Vivian閱讀 3,102評論 1 5