【芝士整理】JS基礎圖譜

JS有哪些基本數據類型呢?

值類型:undefined, Number, Boolean, String,nullhtml

引用類型:Objecthtml5

值類型存放在棧中windows

引用類型將地址存放在棧中,將數據實體存放在堆中數組

null和undefined,not defined的區別?

not defined是未聲明,當使用未聲明變量時瀏覽器會拋出這個錯誤瀏覽器

undefined是已聲明未賦值,typeof undefined是undefined閉包

null相似於空對象,是一個已定義,定義爲空的值,typeof null 是 objectapp

如何判斷數據類型?

若是是值類型,直接用typeof判斷異步

若是是引用類型,使用instanceof判斷,instanceof基於原型鏈,通常用於判斷自定義對象函數

constructor是prototype上的一個屬性,他容易被重寫覆蓋,因此不可信賴this

Object.prototype.toString.call,調用Object原型上的toString方法能夠獲得當前調用者的具體類型

Object.prototype.toString.call().slice(8, -1); // Object|Array|Number|String|Boolean...

什麼是原型鏈?

每個函數上都有一個prototype屬性,稱爲原型對象

函數實例化產生對象

每個對象都有一個__proto__(隱匿原型)屬性,指向構造它的原型對象。

原型對象自己也是對象,也有一個隱匿原型,指向它的原型對象。

沿着隱匿原型鏈最終會指向Object.prototype,它的原型對象是null

這就構成一個原型鏈

PS. 將原子類型賦給 prototype 的操做將會被忽略

function Foo() {}
Foo.prototype = 1; // 無效

instanceof的原理

A instanceof B
A的原型鏈是否會到達B.prototype

繼承

經過原型鏈實現繼承,原型對象上能夠定義屬性和方法。

當要在一個對象上尋找某個屬性,先在對象自己找,沒有的話,再沿着原型鏈向上找原型對象裏有沒有,向上查找找到爲止,到達頂部仍未找到,返回undefined

PS.判斷對象上是否有某個屬性,而非其原型鏈上有,使用 hasOwnProperty 函數

執行上下文

在函數調用時或者是全局代碼開始運行時產生,處理的事情:變量聲明,函數聲明,函數聲明形式的定義賦值,定義this,在函數內還有定義arguments的操做

PS. arguments 變量不是一個數組(Array)。 儘管在語法上它有數組相關的屬性 length,但它不從 Array.prototype 繼承,實際上它是一個對象(Object)。

所以,沒法對 arguments 變量使用標準的數組方法,好比 push, pop 或者 slice。 雖然使用 for 循環遍歷也是能夠的,可是爲了更好的使用數組方法,最好把它轉化爲一個真正的數組。

Array.prototype.slice.call(arguments);

執行上下文棧

全局代碼開始執行時,產生一個全局的執行上下文,壓棧

代碼執行到函數A調用時,產生一個函數A的執行上下文,壓棧

函數A中調用函數B,產生一個函數B的執行上下文,壓棧

函數B,執行完畢,出棧銷燬執行上下文

函數A,執行完畢,出棧並銷燬執行上下文

關於this的指向

this存在於執行上下文中

  1. 做爲構造函數調用時,指向實例化的對象
  2. 做爲對象屬性調用時,指向對象
  3. call, apply調用時,指向參數指定上下文
  4. 全局和普通函數都指向windows
  5. ES6中,箭頭函數自己沒有this,致使如下三種現象:根據外層(函數或者全局)做用域來決定this,箭頭函數不能做爲構造函數使用,不能使用call, apply手動修改this

PS. 一些誤解

// 1. 嚴格按照規範
Foo.method = function() {
    // 在這,this是Foo的實例化對象
    function test() {
        // this 將會被設置爲全局對象
    }
    test();
}

// 2. 函數別名
var test = someObject.methodTest;
test(); // this設置爲全局對象
PS. apply和call的用法

function.apply(null, arguments);

function.call(null, arg1, arg2);

做用域

ES5中只有函數做用域的概念,做用域是一個虛擬概念,沒有具體的數據類型或者結構。

一個函數的做用域在函數定義時肯定,建立函數的做用域成爲該函數的上級做用域

在函數中尋找變量,先找到函數做用域對應的執行上下文,在執行上下文中找變量。

沒有找到的話,看上級函數做用域,向上查找到,找到爲止。

若是找不到,則會拋出 ReferenceError異常。

PS. 好比,當訪問函數內的 foo 變量時,JavaScript 會按照下面順序查找:

  1. 當前做用域內是否有 var foo 的定義。
  2. 函數形式參數是否有使用 foo 名稱的。
  3. 函數自身是否叫作 foo
  4. 回溯到上一級做用域,而後從 #1 從新開始。

閉包

什麼是閉包?
一個函數中有依賴外部變量,函數在建立它的做用域以外被調用。
將會在執行上下文棧中保留上級做用域的執行上下文。
若在閉包使用完畢以後不手動解除引用,相關執行上下文將會一直保留於執行上下文棧中,佔據內存空間,若持續積累,容易形成內存泄漏。

常見應用,函數做爲返回值,函數做爲參數。

經典問題

for (var i = 0; i < 5; i++) {
    setTimeout(function() {  
      console.log(i);
      }, 1000);
} // 5,5,5,5,5

for (let i = 0; i < 5; i++) {
    setTimeout(function() {  
      console.log(i);
      }, 1000);
} // 0,1,2,3,4

for (var i = 0; i < 5; i++) {
    (function (i) {
        setTimeout(function() {  
          console.log(i);
          }, 1000);
    })(i)
} // 0, 1, 2, 3, 4

for(var i = 0; i < 5; i++) {
    setTimeout((function(e) {
        return function() {
            console.log(e);
        }
    })(i), 1000)
} // 0, 1, 2, 3, 4

JS中實現繼承的方法

  1. 基礎繼承

    var Bar = function () {};
    Bar.prototype = {
        greet: function (name) {
            console.log(name);
        }
    }
    var Foo = function () {}
    Foo.prototype.__proto__ = Bar.prototype;

    等價於

    var Bar = function () {};
    var Foo = function () {}
    Foo.prototype = new Bar();

    原理:實現原型鏈

    缺點:屬性不獨立

  2. 組合繼承

    var Bar = function (name) {
        this.name = name;
    }
    Bar.prototype = {
        greet: function () {
            console.log(this.name);
        }
    }
    var Foo = function (name) {
        Bar.apply(this, arguments);
    }
    Foo.prototype = new Bar();

    原理:把this屬性賦值在子類的做用域執行一次,方法經過原型鏈繼承
    缺點:this屬性賦值進行了兩次

  3. 寄生組合式繼承

    var Bar = function (name) {
        this.name = name;
    }
    Bar.prototype = {
        greet: function () {
            console.log(this.name);
        }
    }
    var Foo = function (name) {
        Bar.apply(this, arguments);
    }
    Foo.prototype = Object.create(Bar.prototype);
    Foo.prototype.constructor = Foo;

    原理: 把this屬性賦值在子類的做用域執行一次,手動鏈接原型對象的拷貝
    優勢:解決組合繼承的缺點

  4. extends方法

    class Point {
        constructor(x, y) {
            this.x = x;
            this.y = y;
        }
        
        toString () {
            return this.x + ' ' + this.y;
        }
    }
    
    class ColorPoint extends Point {
      constructor(x, y, color) {
        super(x, y); // 調用父類的constructor(x, y)
        this.color = color;
      }
    
      toString() {
        return this.color + ' ' + super.toString(); // 調用父類的toString()
      }
    }

    子類本身的this對象,必須先經過父類的構造函數完成塑造,獲得與父類一樣的實例屬性和方法,而後再對其進行加工,加上子類本身的實例屬性和方法。若是不調用super方法,子類就得不到this對象。

PS. new 運算符作了什麼

// 1. 首先建立一個空對象
var o = new Object();
// 2. 將空對象的原型賦值爲構造器函數的原型
o.__proto__ = A.prototype;
// 3. 更改構造器函數內部this,將其指向新建立的空對象
A.call(o);

類型轉換

強制轉換

  1. 轉爲字符串::.toString(), String()
  2. 轉爲數值:Number針對全部類型,parseInt和parseFloat針對字符串

    字符串轉換爲數字的經常使用方法:

    +'010' === 10
    Number('010') === 10
    parseInt('010', 10) === 10  // 用來轉換爲整數
    
    +'010.2' === 10.2
    Number('010.2') === 10.2
    parseInt('010.2', 10) === 10
    parseFloat('10.1.2') === 10.1 // 字符轉換爲浮點數
    
    Number(undefined) // NaN
    Number嚴格轉換,只要有一個字符沒法轉爲數值輸出NaN
    parseInt原理爲從左往右讀字符串,讀到非數值字符爲止
    parseFloat原理爲從左往右讀字符串,讀到第二個小數點或者非數值非小數點字符爲止
  3. 轉爲布爾值:Boolean(),""、null、undefined、+0、-0 和 NaN 轉爲布爾型是 false,其餘的都是 true

自動轉換

  1. 轉爲字符串:包含字符串的+法運算,'5' + 1 === '51'
  2. 轉爲數值:不包含字符串的算術運算
  3. 轉爲布爾值:條件語句判斷,非運算

事件輪詢機制

主線程運行時產生堆和執行棧

主線程以外,還存在一個"任務隊列"。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。

一旦"執行棧"中的全部同步任務執行完畢,系統就會讀取"任務隊列",看看裏面有哪些事件。對應的異步任務,結束等待狀態,進入執行棧,開始執行

任務隊列

任務隊列分爲宏任務隊列和微任務隊列

  • 宏任務包括:script(全局任務), setTimeout, setInterval, setImmediate, I/O, UI rendering。
  • 微任務包括: new Promise().then(回調), process.nextTick, Object.observe(已廢棄), MutationObserver(html5新特性)

執行同步任務 -> 處理微任務隊列 -> 處理宏任務隊列裏隊首任務 -> 處理微任務隊列

Promise.resolve().then(()=>{
  console.log('Promise1')  
  setTimeout(()=>{
    console.log('setTimeout1')
  },0)
})
setTimeout(()=>{
  console.log('setTimeout2')
  Promise.resolve().then(()=>{
    console.log('Promise2')    
  })
  Promise.resolve().then(()=>{
  console.log('Promise3')    
  })
},0)
setTimeout(()=>{
  console.log('setTimeout4')
  Promise.resolve().then(()=>{
    console.log('Promise4')    
  })
},0)

Output: Promise1 setTimout2 Promise2 Promise3 setTimeout4 Promise4 setTimeout1

setTimeout和setInterval的區別

setTimeout:產生一個宏任務,在指定時間以後加入任務隊列。

setInterval:循環產生宏任務,但存在問題,若任務執行時間長於指定時間間隔,會產生堆疊執行效果。

相關文章
相關標籤/搜索